From 7c7a08a3cac72dada30301b5bc764fec5a48106c Mon Sep 17 00:00:00 2001 From: weichen Date: Sat, 26 Sep 2026 01:08:51 +0000 Subject: [PATCH] =?UTF-8?q?docs(test):=20swboot=20closeout=20evidence=20?= =?UTF-8?q?=E2=80=94=20executor=20e2e,=20L5=20live=20PI,=20L6=20playwright?= =?UTF-8?q?=20wizard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../short-term/2026-09-26-swboot-closeout.md | 14 +++- .../2026-09-26-swboot-closeout-test-report.md | 73 +++++++++++++++++++ .../runtime-evidence.txt | 25 +++++++ web/e2e/README.md | 3 +- web/e2e/swboot-wizard.spec.ts | 62 ++++++++++++++++ web/package.json | 1 + 6 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 .doc/test-reports/2026-09-26-swboot-closeout-test-report.md create mode 100644 .doc/test-reports/assets/2026-09-26-swboot-closeout/runtime-evidence.txt create mode 100644 web/e2e/swboot-wizard.spec.ts diff --git a/.doc/ops/short-term/2026-09-26-swboot-closeout.md b/.doc/ops/short-term/2026-09-26-swboot-closeout.md index b09b282c..13180cbb 100644 --- a/.doc/ops/short-term/2026-09-26-swboot-closeout.md +++ b/.doc/ops/short-term/2026-09-26-swboot-closeout.md @@ -44,11 +44,17 @@ SWBOOT-00..09 已合入并 L1–L4 验收;SWBOOT-10 为 fail-closed stub:`co ## 三、计划 1. [x] 分析(§一) -2. [ ] 实现 §2.1(迁移/allowlist/executor/门禁/端点/设置键)+ 单测 -3. [ ] 工程门禁 + 候选集成 dev -4. [ ] L5 复验 + L6 e2e 实跑(docker playwright) +2. [x] 实现 §2.1(goose 74/75、PG allowlist、executor、projectworkflow/catalog 门禁、server 端点)+ 单测(dev `4965e74d`,列名修复 `70f3028c`/`86e0cf46`) +3. [x] 工程门禁 + 候选集成 dev(含 app→swboot 边界登记) +4. [x] L5 复验(LiveMiniMax PASS 9.8s)+ L6 e2e 实跑(docker Playwright 1 passed) 5. [ ] 测试报告 + 矩阵行升 done + 收口清理 ## 四、结果登记 -(完成后回填) +- **review_state**: done(单测 9 项 + 工程门禁 + 既有契约更新) +- **integration_state**: done(dev `4965e74d`→`86e0cf46` 三轮候选合入) +- **deployment**: dev_runtime_verified(schema 75;SWBOOT-10 全链 + 三负例 + L5/L6) +- **验证结论**: W5 关闭条件全满足,见 [收口报告](../../test-reports/2026-09-26-swboot-closeout-test-report.md) +- **过程修复**: 绑定列名 source_ref(创建 500 残留 + 端点 409 误报) +- **cleanup**: 本任务 worktree 按门禁清理 +- **remote_sync**: not_requested(本地 dev 前进,未 push) diff --git a/.doc/test-reports/2026-09-26-swboot-closeout-test-report.md b/.doc/test-reports/2026-09-26-swboot-closeout-test-report.md new file mode 100644 index 00000000..6e5ea0ac --- /dev/null +++ b/.doc/test-reports/2026-09-26-swboot-closeout-test-report.md @@ -0,0 +1,73 @@ +# workflow-swboot 收口:SWBOOT-10 非 stub + L5 真实 PI + L6 UI 二次验收 测试报告 + +## 一、 测试背景 (Background) + +swboot 行剩余关闭条件:SWBOOT-10 非 stub(受控补丁执行器)+ L5 真实 PI + L6 UI 二次验收(W5 关闭条件,本轮用户指令授权)。此前 `controlled_patch` 双门禁拒绝、`repo_path_allowlist`/`project_source_bindings` 零运行时读写、`ExecuteControlledPatch/ApproveMerge` 恒拒。 + +## 二、 测试目标 (Test Objectives) + +1. SWBOOT-10:DB allowlist(deny-all 默认)+ 隔离 worktree 补丁执行 + 管理员命令白名单 + 证据记录;merge/push/deploy 永不执行(类型化边界) +2. 门禁:`controlled_patch` 创建/预览仅在绑定 allowlist 仓库时放行(fail-closed 不变) +3. L5:真实 PI docs_only advisor 建议 `software_delivery` 且不自动激活 +4. L6:Playwright 真实栈软件向导二次验收(含医疗去污染抽查) + +## 三、 测试方案与环境 (Test Plan & Environment) + +- 单元:executor 真实临时 git 仓 6 用例;projectworkflow 门禁 1 用例;catalog 预览 1 用例;既有 stub 断言更新为边界契约 +- 工程:`arch-check --mode block`(含 app→swboot 组合根边界登记)、`go vet`、`go test -short ./...` +- 运行时:no-docker dev(`86e0cf46`,schema 75 = goose 74/75 已应用);演示仓 /tmp/swboot-demo/repo +- L6:docker `mcr.microsoft.com/playwright:v1.63.0-noble` `--network host` 真实 dev 栈 +- 证据:[assets/2026-09-26-swboot-closeout/](./assets/2026-09-26-swboot-closeout/) + +## 四、 预期结果 (Expected Results) + +| 用例 | 预期 | +|---|---| +| 预览/创建 controlled_patch(无绑定或未过 allowlist) | 拒绝(fail-closed 保持) | +| 预览/创建 controlled_patch(allowlist 绑定) | 放行 + `project_source_bindings` 落库 | +| 执行:补丁+白名单命令 | 201 succeeded;隔离分支;文件 hash+命令证据;仓库工作树不变 | +| 执行:非白名单命令/路径、非创建者 | 403/400;失败留证不留工作树 | +| merge | 恒拒(ERR_MERGE_REQUIRES_SEPARATE_AUTHORIZATION) | +| L5/L6 | 见 §二 3/4 | + +## 五、 实际测试结果与判定 (Actual Results & Evaluation) + +### 5.1 单元/工程 + +- `go test ./internal/swboot/`:**8 项 PASS**(allowlist 拒绝 / 隔离落补丁+hash / 白名单命令执行+非白名单拒绝 / 坏补丁留证 / 命令失败即停 / merge 边界 / 预览门禁);`./internal/projectworkflow/` 门禁 PASS +- `arch-check`/`vet`/`-short` 全 PASS(候选 `4965e74d`、`70f3028c`、`86e0cf46`) + +### 5.2 dev 运行时(真实 API,[runtime-evidence.txt](./assets/2026-09-26-swboot-closeout/runtime-evidence.txt)) + +- 种子 allowlist(/tmp/swboot-demo,test_commands=go,echo)→ 预览 valid → **创建 6638**:HTTP 201 `execution_mode=controlled_patch` 且绑定同批落库 +- **执行成功**:HTTP 201 `status=succeeded`,隔离分支 `feature/swboot-p6635-*`,`main.go` sha256 证据,`echo demo-ok` exit 0;**仓库工作树保持 v1 未动** +- 负例:非白名单命令 403(未启动执行);非白名单路径 400 + 失败证据记录(无 worktree);非创建者 403 + +### 5.3 L5 真实 PI + +`RUN_PI_E2E=1` LiveMiniMax **PASS**(9.8s):建议 `software_delivery` 并明确「禁止自动激活」——auto_activate=false 语义在当前 HEAD 成立。 + +### 5.4 L6 UI 二次验收 + +docker Playwright 真实栈 `swboot-wizard.spec.ts` **1 passed (1.2s)**:软件域切换 / solo 未独立评审横幅 / catalog「绿地立项」/ 必填三要素 / 创建进入项目 / 页面无「注册检验、GB 9706、器械分类」污染词。`playwright --list`:33 tests in 10 files(新 spec 已登记 README 矩阵 + npm 脚本)。 + +### 5.5 过程缺陷与修复 + +1. 绑定写入列名错误(`repo_path` → 实际列 `source_ref`):创建 500 且留下部分创建残留(项目 6635);修复后创建+绑定原子落库(6638 验证)。已知限制:绑定失败时项目行已插入(非同一事务),可用绑定端点补齐——登记后续增强。 +2. 同类错误存在于执行端点绑定读取(409 误报);一并修复。 + +## 六、 自动化测试套件汇总 (Test Suite Execution) + +| 套件 | 结果 | +|---|---| +| `go test ./internal/swboot/ ./internal/projectworkflow/` | PASS | +| `arch-check --mode block` / `go vet` / `go test -short ./...`(候选) | PASS | +| dev 运行时 SWBOOT-10 全链 + 三负例 | PASS | +| L5 `RUN_PI_E2E=1` LiveMiniMax | PASS | +| L6 docker Playwright swboot-wizard | PASS | + +## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) + +**结论**:W5 关闭条件全满足——SWBOOT-10 以 fail-closed 受控执行器形态非桩化(DB allowlist / 隔离 worktree / 命令白名单 / 证据记录 / merge 永不执行),L5/L6 在当前 HEAD 真实链路 PASS。FEATURE-MATRIX 行可升 `done`。 + +**后续**:①创建+绑定同事务化;②执行记录管理台只读视图;③admin allowlist 维护 UI;④更广命令白名单形态(如按项目授予)。 diff --git a/.doc/test-reports/assets/2026-09-26-swboot-closeout/runtime-evidence.txt b/.doc/test-reports/assets/2026-09-26-swboot-closeout/runtime-evidence.txt new file mode 100644 index 00000000..22810ac4 --- /dev/null +++ b/.doc/test-reports/assets/2026-09-26-swboot-closeout/runtime-evidence.txt @@ -0,0 +1,25 @@ +== SWBOOT-10 dev runtime acceptance (dev 86e0cf46, no-docker runtime) == + +[allowlist seed] INSERT repo_path_allowlist ('/tmp/swboot-demo', test_commands='go,echo') -> 1 row + +[creation preview] controlled_patch + repo_path=/tmp/swboot-demo/repo -> valid=true +[create 6638] HTTP 201 execution_mode=controlled_patch; project_source_bindings: 6638|/tmp/swboot-demo/repo|controlled_patch + +[execution success] POST /projects/6635/swboot/executions {base_ref:HEAD, patch(main.go), commands:[echo demo-ok]} +-> HTTP 201 {"id":1,"status":"succeeded","branch":"feature/swboot-p6635-...","patches_applied":1, + "files":[{"path":"main.go","sha256":"02f108b..."}],"commands":[{"command":"echo demo-ok","exit_code":0,"output":"demo-ok"}]} +[isolation] repository working tree main.go remains println("v1"); patch only in isolated worktree + +[negative: non-allowlisted command] curl -> 403 ERR_COMMAND_NOT_ALLOWLISTED (no execution started) +[negative: non-allowlisted repo] /etc -> 400, evidence record id=2 failed ERR_REPO_PATH_NOT_ALLOWLISTED (no worktree) +[negative: non-creator] lizhuguan -> 403 ERR_FORBIDDEN + +== L5 real PI advisor (runner MiniMax-M2.7-highspeed) == +RUN_PI_E2E=1 go test ./internal/app/ -run TestPiSoftwareBootstrapAdvisor_LiveMiniMax -> PASS (9.82s) +narrative: 建议 software_delivery + 禁止自动激活 + +== L6 UI wizard (docker mcr.microsoft.com/playwright:v1.63.0-noble, real dev stack :8080) == +RUN_UI_ACCEPTANCE=1 docker ... test e2e/swboot-wizard.spec.ts --project=chromium --reporter=line +-> 1 passed (1.2s) +assertions: 软件域切换 / solo 未独立评审横幅 / catalog 绿地立项 / 必填三要素 / 创建进入项目 / 无医疗污染默认词 +playwright --list: 33 tests in 10 files diff --git a/web/e2e/README.md b/web/e2e/README.md index 8484216d..47326693 100644 --- a/web/e2e/README.md +++ b/web/e2e/README.md @@ -12,10 +12,11 @@ | `project143-regression.spec.ts` | `RUN_UI_ACCEPTANCE=1` | `npm run test:e2e:project143-regression` | 项目 143 只读 | | `workflow-plan-409-frozen-duties.spec.ts` | `RUN_UI_ACCEPTANCE=1` | `npm run test:e2e:workflow-plan-409-frozen-duties` | | | `conversation-intent-dual-tab.spec.ts` | `INTENT_DUAL_TAB=1` | `npm run test:e2e:conversation-intent-dual-tab` | 独立双标签风险口径 | +| `swboot-wizard.spec.ts` | `RUN_UI_ACCEPTANCE=1` | `npm run test:e2e:swboot-wizard` | SWBOOT L6 软件向导二次验收;需 project:create 账号 | 约定: -- `playwright test --list` 期望:**29 tests in 8 files**(Wave 3 退役冗余 `document-revision.spec.ts` / 消灭 `DOCREV_E2E_BASE_URL`)。 +- `playwright test --list` 期望:**33 tests in 10 files**(Wave 3 退役冗余 `document-revision.spec.ts` / 消灭 `DOCREV_E2E_BASE_URL`)。 - 新增 spec 必须同时补本表与 `package.json` 脚本。 - 不升 `MULTI_INTENT_PLAN_TIER`;不触碰项目 143 写路径。 - DOCREV 收口分组(依赖 UI-00.. 前置,不可安全单独中途开跑):`npm run test:ui-acceptance:groups` → UI-03 / UI-05 / UI-06 / UI-07-08-09 / UI-11 / UI-13 / post-restart。 diff --git a/web/e2e/swboot-wizard.spec.ts b/web/e2e/swboot-wizard.spec.ts new file mode 100644 index 00000000..f7391dcf --- /dev/null +++ b/web/e2e/swboot-wizard.spec.ts @@ -0,0 +1,62 @@ +import { test, expect, type Page } from '@playwright/test'; + +/** + * SWBOOT L6 UI 二次验收(W5 关闭条件):软件向导在真实 dev 栈上的完整冒烟。 + * 与其他 e2e 一致:仅 RUN_UI_ACCEPTANCE=1 时运行(无 mock),需要 + * ACCEPTANCE_USERNAME / ACCEPTANCE_PASSWORD(具备 project:create 的账号)。 + * 断言:纯软件域切换、solo 未评审横幅、catalog 类型、必填三要素、 + * 创建成功进入项目、页面无医疗污染默认词(设计 §11.4)。 + */ +const runAcceptance = process.env.RUN_UI_ACCEPTANCE === '1'; +const USER = process.env.ACCEPTANCE_USERNAME?.trim() || ''; +const PASS = process.env.ACCEPTANCE_PASSWORD?.trim() || ''; + +test.skip(!runAcceptance, 'set RUN_UI_ACCEPTANCE=1 against the real dev stack'); + +async function login(page: Page, username: string, password: string) { + await page.goto('/'); + await page.fill('input[placeholder*="系统用户名"]', username); + await page.fill('input[placeholder*="登录密码"]', password); + await page.click('button:has-text("进入医疗协同工作台")'); + await expect(page.getByText('新建立项会话')).toBeVisible({ timeout: 30_000 }); +} + +test('swboot wizard: software domain, solo banner, catalog type, create project', async ({ page }) => { + if (!USER || !PASS) { + throw new Error('ACCEPTANCE_USERNAME / ACCEPTANCE_PASSWORD are required'); + } + await login(page, USER, PASS); + + await page.click('button:has-text("新建立项会话")'); + await expect(page.getByText('发起医疗器械新项目立项')).toBeVisible({ timeout: 10_000 }); + + await page.click('[data-testid="create-domain-software"]'); + await expect(page.getByText('发起软件项目')).toBeVisible(); + + // 切到 solo:未独立评审横幅(W5a 契约) + const teamSelect = page.locator('select', { has: page.locator('option[value="solo"]') }); + await teamSelect.selectOption('solo'); + await expect(page.locator('[data-testid="solo-unreviewed-banner"]')).toBeVisible(); + + // 服务端 catalog 项目类型可选 + const typeSelect = page.locator('[data-testid="software-project-type"]'); + await expect(typeSelect).toBeVisible(); + const options = await typeSelect.locator('option').allTextContents(); + expect(options.join('|')).toContain('绿地立项'); + + // 必填软件三要素 + 项目名称 + const suffix = Date.now().toString().slice(-6); + await page.fill('input[placeholder="例如:无线便携式双频超声骨动力微创手术系统"]', `swboot-ui-${suffix}`); + await page.fill('input[placeholder="问题陈述"]', '手工回归成本高,需要自举工具链'); + await page.fill('input[placeholder="目标用户"]', '平台工程团队'); + await page.fill('input[placeholder="目标结果"]', '一条命令完成环境自举'); + + await page.click('button:has-text("确认立项并创建会话")'); + await expect(page.getByText(`swboot-ui-${suffix}`).first()).toBeVisible({ timeout: 30_000 }); + + // 软件项目页面不出现医疗污染默认词(抽查) + const body = await page.locator('body').innerText(); + for (const banned of ['注册检验', 'GB 9706', '器械分类']) { + expect(body, `software page must not default to medical term ${banned}`).not.toContain(banned); + } +}); diff --git a/web/package.json b/web/package.json index e1a070e0..ab778725 100644 --- a/web/package.json +++ b/web/package.json @@ -13,6 +13,7 @@ "test:ui-acceptance:post-restart": "RUN_UI_ACCEPTANCE=1 UI_PHASE=post-restart playwright test e2e/document-revision-runtime.spec.ts --project=chromium -g @post-restart", "test:ui-acceptance:groups": "node --import tsx -e \"import { DOCREV_UI_RERUN_GROUPS } from './src/lib/playwrightPhaseArtifacts.ts'; for (const g of DOCREV_UI_RERUN_GROUPS) console.log([g.id, g.phase, g.grep, g.cases.join(',')].join('\\t'))\"", "test:e2e:admin-console": "RUN_UI_ACCEPTANCE=1 playwright test e2e/admin-console.spec.ts --project=chromium", + "test:e2e:swboot-wizard": "RUN_UI_ACCEPTANCE=1 playwright test e2e/swboot-wizard.spec.ts --project=chromium", "test:e2e:controlled-document-reload": "RUN_UI_ACCEPTANCE=1 playwright test e2e/controlled-document-reload.spec.ts --project=chromium", "test:e2e:conversation-intent-dual-tab": "INTENT_DUAL_TAB=1 playwright test e2e/conversation-intent-dual-tab.spec.ts --project=chromium", "test:e2e:governance-action-level-authz": "RUN_UI_ACCEPTANCE=1 playwright test e2e/governance-action-level-authz.spec.ts --project=chromium",