Merge current main into versioned dependency workspace

Why: Include the latest Dev task-card closure and governance fix while preserving the versioned dependency migration.
Evidence: SELF merge conflict inspection, clean staged diff, bash syntax check, and prior isolated E2E replay; full final-head verification pending.
Impact: Keep existing Backend and Frontend source directories outside the Git index and retain their on-disk Git data. Preserve password setup, audit grants, scoped browser coverage, and owned process cleanup.
Refs: #537, #538, mq-dev-20261014-537-01
This commit is contained in:
2026-10-14 05:11:13 +00:00
8 changed files with 1089 additions and 21 deletions
+42 -17
View File
@@ -1,10 +1,9 @@
#!/usr/bin/env bash
# e2e.sh —— U4 运行验收一条龙(真实浏览器:Playwright × RDM 审计工作区)。
#
# 动线(与 frontend/e2e/rdm-audit.spec.ts 的环境契约一一对应):
# 1. 构建:RDM 二进制(RDM 仓 origin/main 临时 worktree,pins 物化)
# + 基座 CLI(本 worktree modules/backend,建号用)
# + frontend dist(本 worktree modules/frontend)
# 动线(与 Frontend e2e/rdm-audit.playwright.ts 的环境契约一一对应):
# 1. 构建:版本化候选从固定 Dev 组合取得 RDM、同源基座 CLI 与 Frontend dist;
# 历史候选继续按原 gitlink 和 pins 路径物化。
# 2. 种子:RDM -bootstrap-admin(域管理员)→ 基座 user create(登录账号)
# → serve(auth on)→ API rdm.project.create×2 + rdm.ingest(主项目)
# 3. 验收:vite preview(dist+/api 代理)→ npx playwright test
@@ -17,6 +16,7 @@
# RDM_REPO RDM 仓路径(默认 <本仓上级>/Agent-System-RDM)
# E2E_API_PORT/E2E_UI_PORT 服务端口(默认 18791/4173)
# E2E_OWNER/E2E_PASSWORD 登录账号(默认 e2e-auditor/E2eAudit#2026)
# E2E_PASSWORD2 首次登录后的设密结果(默认 E2eAudit#2026b)
# E2E_PROJECT_MAIN/E2E_PROJECT_ISOLATED 项目名(默认 审计演示项目/隔离检查项目)
# E2E_OUT 工件目录(默认 .tmp/e2e/<ts>;.gitignore 已覆盖 .tmp)
# 依赖:go、node/npm、curl;首次运行会 npx playwright install chromium。
@@ -39,6 +39,7 @@ API_PORT=${E2E_API_PORT:-18791}
UI_PORT=${E2E_UI_PORT:-4173}
OWNER=${E2E_OWNER:-e2e-auditor}
PASSWORD=${E2E_PASSWORD:-E2eAudit#2026}
PASSWORD2=${E2E_PASSWORD2:-E2eAudit#2026b}
PROJ_MAIN=${E2E_PROJECT_MAIN:-审计演示项目}
PROJ_ISO=${E2E_PROJECT_ISOLATED:-隔离检查项目}
API_BASE="http://127.0.0.1:$API_PORT"
@@ -53,7 +54,8 @@ die() { printf '\033[31m[e2e][FAIL]\033[0m %s\n' "$*" >&2; exit 1; }
command -v go >/dev/null || die "go not found"
command -v node >/dev/null || die "node not found"
command -v curl >/dev/null || die "curl not found"
git -C "$RDM_REPO" rev-parse --git-dir >/dev/null 2>&1 || die "RDM repo not found at $RDM_REPO (set RDM_REPO)"
[ "$(git -C "$RDM_REPO" rev-parse --is-inside-work-tree 2>/dev/null)" = true ] \
|| die "RDM repo not found at $RDM_REPO (set RDM_REPO)"
if [ "$BUILD_ONLY" != 1 ]; then
for p in "$API_PORT" "$UI_PORT"; do
@@ -66,10 +68,11 @@ fi
API_PID=""
UI_PID=""
cleanup() {
if [ -n "${API_PID:-}" ]; then kill "$API_PID" 2>/dev/null || true; fi
if [ -n "${UI_PID:-}" ]; then
if [ -n "${API_PID:-}" ]; then kill "$API_PID" 2>/dev/null || true; wait "$API_PID" 2>/dev/null || true; fi
# Preview is a single owned Vite process. Check its command before signaling
# so a recycled PID cannot stop a process from another worktree.
if [ -n "${UI_PID:-}" ] && ps -p "$UI_PID" -o args= 2>/dev/null | rg -Fq -- "vite preview --outDir $FRONTEND_DIST --port $UI_PORT"; then
kill "$UI_PID" 2>/dev/null || true
pkill -f "vite preview --port $UI_PORT" 2>/dev/null || true
fi
}
trap cleanup EXIT
@@ -179,9 +182,23 @@ stop_serve() {
[ -n "${API_PID:-}" ] && { kill "$API_PID" 2>/dev/null || true; wait "$API_PID" 2>/dev/null || true; API_PID=""; }
}
login() {
curl -sf -c "$JAR" -X POST "$API_BASE/api/auth/login" \
local i
for i in $(seq 1 20); do
if curl -sf -c "$JAR" -X POST "$API_BASE/api/auth/login" \
-H 'Content-Type: application/json' \
-d "{\"owner\":\"$OWNER\",\"password\":\"$PASSWORD\"}" >/dev/null; then
return 0
fi
sleep 0.25
done
die "login failed"
}
finish_password_gate() {
curl -sf -b "$JAR" -c "$JAR" -X POST "$API_BASE/api/auth/password" \
-H 'Content-Type: application/json' \
-d "{\"owner\":\"$OWNER\",\"password\":\"$PASSWORD\"}" >/dev/null || die "login failed"
-d "{\"next\":\"$PASSWORD2\"}" >/dev/null || die "password setup failed"
PASSWORD=$PASSWORD2
login
}
cmd() { # cmd <id> <params-json> → payload(失败即死,种子步骤不容忍部分成功)
@@ -196,11 +213,17 @@ cmd() { # cmd <id> <params-json> → payload(失败即死,种子步骤不容
start_serve
JAR="$OUT/cookies.txt"
login
finish_password_gate
log "seed: create projects"
PID_MAIN=$(cmd rdm.project.create "{\"name\":\"$PROJ_MAIN\"}" | python3 -c 'import json,sys; print(json.load(sys.stdin)["projectId"])')
PID_ISO=$(cmd rdm.project.create "{\"name\":\"$PROJ_ISO\"}" | python3 -c 'import json,sys; print(json.load(sys.stdin)["projectId"])')
log "seed: projects main=$PID_MAIN iso=$PID_ISO"
log "seed: grant explicit audit.read for the fixture owner"
cmd rdm.perm.cap '{"action":"audit.read","allow":true}' >/dev/null
cmd rdm.perm.set "{\"projectId\":\"$PID_MAIN\",\"member\":\"$OWNER\",\"action\":\"audit.read\",\"allow\":true}" >/dev/null
cmd rdm.perm.set "{\"projectId\":\"$PID_ISO\",\"member\":\"$OWNER\",\"action\":\"audit.read\",\"allow\":true}" >/dev/null
log "seed: import requirement ledger into $PID_MAIN"
cmd rdm.reqs.import "{\"projectId\":\"$PID_MAIN\",\"items\":$(
python3 - <<'PY'
@@ -253,11 +276,12 @@ if [ "$SEED_ONLY" = 1 ]; then
fi
log "start vite preview at http://127.0.0.1:$UI_PORT"
(cd "$FRONTEND_DIR" && \
PLAYWRIGHT_API_TARGET="$API_BASE" npx vite preview --outDir "$FRONTEND_DIST" --port "$UI_PORT" --strictPort \
>"$OUT/preview.log" 2>&1 &
echo $! > "$OUT/preview.pid")
UI_PID=$(cat "$OUT/preview.pid")
(
cd "$FRONTEND_DIR"
export PLAYWRIGHT_API_TARGET="$API_BASE"
exec ./node_modules/.bin/vite preview --outDir "$FRONTEND_DIST" --port "$UI_PORT" --strictPort
) >"$OUT/preview.log" 2>&1 &
UI_PID=$!
for i in $(seq 1 40); do
curl -sf -o /dev/null "http://127.0.0.1:$UI_PORT/" && break
sleep 0.25
@@ -269,10 +293,11 @@ log "playwright (install chromium if missing)"
log "run specs"
cd "$FRONTEND_DIR"
export PLAYWRIGHT_BASE_URL="http://127.0.0.1:$UI_PORT" E2E_OWNER="$OWNER" E2E_PASSWORD="$PASSWORD" \
export PLAYWRIGHT_BASE_URL="http://127.0.0.1:$UI_PORT" E2E_OWNER="$OWNER" \
E2E_PASSWORD="${E2E_PASSWORD:-E2eAudit#2026}" E2E_PASSWORD2="$PASSWORD2" \
E2E_PROJECT_MAIN="$PROJ_MAIN" E2E_PROJECT_ISOLATED="$PROJ_ISO"
set +e
npx playwright test --reporter=list >"$OUT/playwright.log" 2>&1
npx playwright test e2e/rdm-audit.playwright.ts --reporter=list >"$OUT/playwright.log" 2>&1
PW_EXIT=$?
set -e
cat "$OUT/playwright.log"
+4 -2
View File
@@ -64,7 +64,9 @@ cmd_precheck() {
pb=$(card_of_branch "$pc"); [ -n "$pb" ] || continue
pst=$(card_status "$pc"); case "$pst" in 已完成*) continue ;; esac
card_pending "${pc##*/}" && continue
if git log --merges --format='%s' "origin/${TARGET_BRANCH}" 2>/dev/null | grep -qF " from $pb into ${TARGET_BRANCH}"; then
# Consume the complete producer output: grep -q exits on the first match,
# making git log die with SIGPIPE (141 under pipefail) on long histories.
if git log --merges --format='%s' "origin/${TARGET_BRANCH}" 2>/dev/null | grep -F " from $pb into ${TARGET_BRANCH}" >/dev/null; then
open_cards="${open_cards} ${pc##*/}"
fi
done
@@ -392,7 +394,7 @@ card_of_branch() { sed -n 's/^- 工作区:.*分支:\([A-Za-z0-9._/-]\+\).*/\
card_status() { sed -n 's/^.*;状态:\([^;]*\).*/\1/p' "$1" | head -1; }
card_merge() { # $1=branch → "sha|subject"(该分支合入 origin/<target> 的合并提交)
git log --merges --format='%H|%s' "origin/${TARGET_BRANCH}" 2>/dev/null \
| grep -F " from $1 into ${TARGET_BRANCH}" | head -1
| grep -F " from $1 into ${TARGET_BRANCH}" | sed -n '1p'
}
card_pending() { # $1=卡片文件名——闭合是否已在某 origin/feature/card-close-* 分支待合入
local cb