From e6b23772c9c88ea031cb16ce89ca15428c9ea5a8 Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Tue, 22 Sep 2026 23:40:22 +0800 Subject: [PATCH 1/6] =?UTF-8?q?fix(e2e):=20approve=20intake=20via=20?= =?UTF-8?q?=E4=B8=89=E6=9D=83=20before=20UI-11=20prelim=5Fdesign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit V0.1 blocking intake caused tasks/runs 403 for V0.5 prelim_design; drive submit/review/approve on page and accept bd_prelim_design keys. --- web/e2e/document-revision-runtime.spec.ts | 94 +++++++++++++++++++++-- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/web/e2e/document-revision-runtime.spec.ts b/web/e2e/document-revision-runtime.spec.ts index 3379ffe5..62f0decf 100644 --- a/web/e2e/document-revision-runtime.spec.ts +++ b/web/e2e/document-revision-runtime.spec.ts @@ -454,21 +454,92 @@ async function openWorkflowDocByControlledTaskKey( const trigger = page.getByTestId('doc-selector-trigger'); await expect(trigger).toBeVisible({ timeout: 15_000 }); await trigger.click(); + // Prefer exact suffix; also accept bd_${taskKey} canonical keys from main.v2. const item = page - .locator(`.dropdown-menu button[data-doc-id^="doc_proj"][data-doc-id$="_${taskKey}"]`) + .locator( + [ + `.dropdown-menu button[data-doc-id^="doc_proj"][data-doc-id$="_bd_${taskKey}"]`, + `.dropdown-menu button[data-doc-id^="doc_proj"][data-doc-id$="_${taskKey}"]`, + ].join(', '), + ) .first(); if ((await item.count()) === 0) { - fail(`workflow menu missing registered doc_proj*_${taskKey}`); + fail(`workflow menu missing registered doc_proj*_${taskKey} (or bd_${taskKey})`); } const docId = (await item.getAttribute('data-doc-id')) || ''; const title = (await item.getAttribute('data-doc-title')) || ''; - if (!new RegExp(`^doc_proj\\d+_${taskKey}$`).test(docId)) { - fail(`expected doc_proj{n}_${taskKey}, got ${JSON.stringify(docId)}`); + if (!new RegExp(`^doc_proj\\d+_(?:bd_)?${taskKey}$`).test(docId)) { + fail(`expected doc_proj{n}_(?:bd_)?${taskKey}, got ${JSON.stringify(docId)}`); } await item.click(); return { title: title || docId, docId }; } +async function ensureControlledIntakeOpen(target: Page, docId: string) { + if (docId) { + await openWorkflowDocById(target, docId); + } else { + await openWorkflowDocByLabel(target, /用户初始需求|需求摸底|intake/i); + } + await expect(target.locator('[data-testid="doc-rev-version"]')).toBeVisible({ timeout: 30_000 }); +} + +/** + * V0.1 intake is blocking_when_enabled; prelim_design lives in V0.5 and is + * 403'd until intake completes 三权. Drive submit→review→approve via page only. + */ +async function driveIntakeGovernanceToApproved( + authorPage: Page, + docId: string, + creds: { userB: string; passB: string; userD: string; passD: string; projectName: string }, +) { + await ensureControlledIntakeOpen(authorPage, docId); + const claim = authorPage.getByRole('button', { name: /认领此文档/ }); + if (await claim.isVisible().catch(() => false)) { + await claim.click(); + await expect(claim).toHaveCount(0, { timeout: 15_000 }); + } + const submit = authorPage.getByRole('button', { name: /提交送审/ }); + await expect(submit).toBeVisible({ timeout: 30_000 }); + await submit.click(); + await expect(authorPage.getByRole('button', { name: /提交送审/ })).toHaveCount(0, { + timeout: 30_000, + }); + + const browser = authorPage.context().browser(); + if (!browser) fail('driveIntakeGovernanceToApproved requires browser'); + + const ctxB = await browser.newContext(); + const pageB = await ctxB.newPage(); + await login(pageB, creds.userB, creds.passB); + await pageB.getByText(creds.projectName, { exact: false }).first().click(); + await ensureControlledIntakeOpen(pageB, docId); + const review = pageB.getByRole('button', { name: /审核通过/ }); + await expect(review).toBeVisible({ timeout: 30_000 }); + await review.click(); + await expect(pageB.getByRole('button', { name: /审核通过/ })).toHaveCount(0, { timeout: 30_000 }); + await ctxB.close(); + + const ctxD = await browser.newContext(); + const pageD = await ctxD.newPage(); + await login(pageD, creds.userD, creds.passD); + await pageD.getByText(creds.projectName, { exact: false }).first().click(); + await ensureControlledIntakeOpen(pageD, docId); + const approve = pageD.getByRole('button', { name: /终审批准/ }); + await expect(approve).toBeVisible({ timeout: 30_000 }); + await approve.click(); + await expect(pageD.getByRole('button', { name: /终审批准/ })).toHaveCount(0, { timeout: 60_000 }); + await ctxD.close(); + + // Author session: reload so stage banner / task scope picks up approved intake. + await authorPage.reload(); + await expect(authorPage.getByText(creds.projectName, { exact: false }).first()).toBeVisible({ + timeout: 30_000, + }); + await ensureControlledIntakeOpen(authorPage, docId); + await expect(authorPage.getByText('必需文档尚未批准')).toHaveCount(0, { timeout: 90_000 }); +} + async function openWorkflowDocById(page: Page, docId: string): Promise { if (!docId) fail('openWorkflowDocById requires docId'); await selectDeliverableCategory(page, requiredCategoryForDocOpen({ kind: 'exact-id', docId })); @@ -1267,6 +1338,15 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { test('UI-11 chat second controlled doc + page upload attachment', async () => { const t0 = Date.now(); logs = []; + // prelim_design is V0.5; V0.1 intake must finish 三权 or tasks/runs returns 403. + if (!controlledDocId) fail('UI-11 requires controlledDocId from UI-03'); + await driveIntakeGovernanceToApproved(page, controlledDocId, { + userB: USER_B, + passB: FINAL_PASS_B, + userD: USER_D, + passD: FINAL_PASS_D, + projectName: PROJECT_NAME, + }); const prompt = '请另行生成一份《系统设计输入》受控文档(第二份),不要覆盖上一份需求文档,并给出可打开的交付物。'; const taskRunWait = waitForTasksRunsPost(page); @@ -1276,8 +1356,10 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { fail(`UI-11 tasks/runs must be HTTP 202, got ${taskRes.status()}`); } const taskBody = taskRes.request().postDataJSON() as { task_key?: string }; - if (taskBody.task_key !== 'prelim_design') { - fail(`UI-11 task_key must be prelim_design, got ${JSON.stringify(taskBody.task_key)}`); + if (taskBody.task_key !== 'prelim_design' && taskBody.task_key !== 'bd_prelim_design') { + fail( + `UI-11 task_key must be prelim_design family, got ${JSON.stringify(taskBody.task_key)}`, + ); } await waitForSquadCompletion(page, '系统设计输入'); // Prompt alias is 系统设计输入; card title is 系统初步设计方案; open by registered doc_proj id. From 3a1065f789fb4b0fecd8f455babfa31bb947249c Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Wed, 23 Sep 2026 00:02:41 +0800 Subject: [PATCH 2/6] fix(e2e): expand collapsed doc rail before category select MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer/approver contexts open with collapsed rail; UI-11 三权 failed looking up doc-category-trigger. --- web/e2e/document-revision-runtime.spec.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/e2e/document-revision-runtime.spec.ts b/web/e2e/document-revision-runtime.spec.ts index 62f0decf..012581e7 100644 --- a/web/e2e/document-revision-runtime.spec.ts +++ b/web/e2e/document-revision-runtime.spec.ts @@ -409,10 +409,20 @@ async function waitForSquadCompletion( return Date.now() - startedAt; } +async function ensureDocRailExpanded(page: Page) { + const expandDocRail = page.getByRole('button', { name: '展开受控文档栏' }); + if (await expandDocRail.isVisible().catch(() => false)) { + await expandDocRail.click(); + await expect(expandDocRail).toHaveCount(0, { timeout: 10_000 }); + } + await expect(page.getByTestId('doc-category-trigger')).toBeVisible({ timeout: 15_000 }); +} + async function selectDeliverableCategory( page: Page, category: 'workflow' | 'shared' | 'private', ) { + await ensureDocRailExpanded(page); const trigger = page.getByTestId('doc-category-trigger'); await expect(trigger).toBeVisible({ timeout: 15_000 }); await trigger.click(); From 1dbdb950887b120979473d11f8830efdd2a474ea Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Wed, 23 Sep 2026 00:17:08 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix(e2e):=20click=20=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E6=8E=A8=E8=BF=9B=20after=20intake=20=E4=B8=89=E6=9D=83=20befo?= =?UTF-8?q?re=20UI-11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Approved intake only unlocks the gate; prelim_design still 403 until creator advances V0.1 → V0.5 via StageAdvanceBanner. --- web/e2e/document-revision-runtime.spec.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/web/e2e/document-revision-runtime.spec.ts b/web/e2e/document-revision-runtime.spec.ts index 012581e7..a5d4aaca 100644 --- a/web/e2e/document-revision-runtime.spec.ts +++ b/web/e2e/document-revision-runtime.spec.ts @@ -541,13 +541,21 @@ async function driveIntakeGovernanceToApproved( await expect(pageD.getByRole('button', { name: /终审批准/ })).toHaveCount(0, { timeout: 60_000 }); await ctxD.close(); - // Author session: reload so stage banner / task scope picks up approved intake. + // Author session: reload so stage banner picks up approved intake, then advance to V0.5. await authorPage.reload(); await expect(authorPage.getByText(creds.projectName, { exact: false }).first()).toBeVisible({ timeout: 30_000, }); await ensureControlledIntakeOpen(authorPage, docId); - await expect(authorPage.getByText('必需文档尚未批准')).toHaveCount(0, { timeout: 90_000 }); + await expect(authorPage.getByText('必需文档已批准,可推进')).toBeVisible({ timeout: 90_000 }); + const ack = authorPage.getByText(/确认已知悉后才能推进/); + if (await ack.isVisible().catch(() => false)) { + await authorPage.locator('label', { hasText: /确认已知悉后才能推进/ }).locator('input[type="checkbox"]').check(); + } + const advanceBtn = authorPage.getByRole('button', { name: '确认推进' }); + await expect(advanceBtn).toBeEnabled({ timeout: 30_000 }); + await advanceBtn.click(); + await expect(authorPage.getByText(/V0\.5|方案与可行性/)).toBeVisible({ timeout: 60_000 }); } async function openWorkflowDocById(page: Page, docId: string): Promise { From c9a9b901722085fde5179f77f78d20beb576ccb9 Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Wed, 23 Sep 2026 00:27:07 +0800 Subject: [PATCH 4/6] docs(ops): scaffold docrev UI rerun closeout without closing audits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Record honest partial progress (UI-00..04 live PASS, sealed UI-00..10 screenshots, UI-11 blocker history) and UI→audit mapping; leave 09-13 audits in_progress until full main+post-restart PASS ledger exists. --- .../short-term/2026-09-22-docrev-ui-rerun.md | 74 ++++++++++++++---- .../2026-09-22-docrev-ui-rerun-test-report.md | 78 +++++++++++++++++++ 2 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 .doc/test-reports/2026-09-22-docrev-ui-rerun-test-report.md diff --git a/.doc/ops/short-term/2026-09-22-docrev-ui-rerun.md b/.doc/ops/short-term/2026-09-22-docrev-ui-rerun.md index b3e8bfb1..17de8f58 100644 --- a/.doc/ops/short-term/2026-09-22-docrev-ui-rerun.md +++ b/.doc/ops/short-term/2026-09-22-docrev-ui-rerun.md @@ -1,9 +1,9 @@ --- title: "DOCREV 真实 UI 重跑收口(产物隔离前置)" task_id: "docrev-ui-rerun-2026-09-22" -status: blocked +status: in_progress created: 2026-09-22 -updated: 2026-09-22 +updated: 2026-09-23 owner: Cursor task_type: implementation task_tier: multi_stage @@ -13,7 +13,7 @@ worktree: .worktrees/docrev-ui-rerun-2026-09-22 base_dev_sha: "364ed7f1a3f0393ef77ec4623a9518116ed2cb87" write_scope: [".doc", "e2e", "web"] allowed_environments: ["dev"] -head_sha: "3fdf4a01b72ca3fba245618608b2518e7fed8550" +head_sha: "1dbdb950887b120979473d11f8830efdd2a474ea" mutex: "web + docrev-ui-acceptance" review_state: pending integration_state: partial @@ -28,23 +28,71 @@ remote_sync: not_requested ## 用户目标与授权 -- 用户目标:关闭 20 份 2026-09-13 docrev audit + 9 份同名 remediation 的共同前置——真实 UI-xx 重跑。 -- 交付终点:先修产物隔离缺陷,再占槽对 schema 61 实跑;证据写入新 assets 目录;再改 audit/remediation 终态。 -- 禁止:stub/API 直写/伪造产物;`page.evaluate` 规避控件;DB 直建对象;写历史证据目录;push。 +- 用户目标:关闭 20 份 2026-09-13 docrev audit + ~9 份同名 remediation 的共同前置——真实 UI-xx 重跑。 +- 交付终点:先修产物隔离缺陷,再占槽对 schema 实跑;证据写入新 assets 目录;再改 audit/remediation 终态。 +- 禁止:stub/API 直写/伪造产物;`page.evaluate` 规避控件;DB 直建对象;写历史证据目录;push;无完整 PASS 账本时把 audit/remediation 标 `done`。 ## 步骤与验收 1. [x] 修产物隔离(`UI_PHASE` 分目录);已合入本地 `dev`。 2. [x] 单测 `playwrightPhaseArtifacts.test.ts` PASS。 -3. [ ] 占 `env:dev` + 集成槽实跑 `UI_PHASE=main` / `post-restart`。 -4. [ ] 证据归档后关闭对应 audit/remediation。 +3. [~] 占 `env:dev` + 集成槽实跑 `UI_PHASE=main` / `post-restart`(main 未完;post-restart 未跑)。 +4. [ ] 完整 main(UI-00..16)+ post-restart PASS 账本归档后关闭对应 audit/remediation。 +5. [x] 收口脚手架:partial 测试报告 + UI→audit/remediation 映射(本轮)。 -## 阻塞原因 +## 当前进度(诚实账本) -- 真实 UI 全链依赖共享 `env:dev` 租约与串行集成槽;本会话未获环境写授权续跑。 -- 不得在无 PASS 账本时把 09-13 audit/remediation 标 done。 +| 项 | 状态 | 证据 | +|---|---|---| +| tip SHA | `1dbdb950887b120979473d11f8830efdd2a474ea` | `fix(e2e): click 确认推进 after intake 三权 before UI-11` | +| 活账本 `ui-acceptance-results.json` | **仅 UI-00..04 = PASS**;UI-05..16 缺失 | [assets](../../test-reports/assets/2026-09-22-docrev-ui-rerun/ui-acceptance-results.json) | +| 密封截图 UI-00..10 | 先前成功 partial 已密封;**截图证明到达 UI-10,不等于 JSON PASS 账本** | [sealed-ui00-10-20260922T160841Z](../../test-reports/assets/2026-09-22-docrev-ui-rerun/sealed-ui00-10-20260922T160841Z/screenshots/) | +| 活截图目录 | 被后续重跑覆盖/混写;以 sealed 为准复盘 UI-00..10 | [screenshots/](../../test-reports/assets/2026-09-22-docrev-ui-rerun/screenshots/) | +| UI-11..16 | 未写入 PASS | 多轮在 UI-11 前/中失败后全链重启 | +| post-restart | 未跑 | — | +| 09-13 audit/remediation | **全部保持 `in_progress`**(无 UI-00..16 全量 PASS) | 见下方映射 | + +Partial 报告:[2026-09-22-docrev-ui-rerun-test-report.md](../../test-reports/2026-09-22-docrev-ui-rerun-test-report.md)。 + +## UI-11 阻塞史(摘要) + +对等 Agent 在 UI-11 失败后反复**整链重跑** Playwright;tip 持续叠加 e2e/web 修复,导致活账本被截断重写。已记录的阻塞/修复线索(按 tip 演进,非完整因果链): + +1. **intake 未过三权**:`prelim_design` 前置须 approve intake + 点「确认推进」(`e6b23772` → `1dbdb950`)。 +2. **文档栏折叠**:分类选择前须展开 collapsed doc rail(`3a1065f7`)。 +3. **UI-05b task_key**:接受 `bd_intake`(`5e7fb240`)。 +4. **聊天解锁/孤儿 inflight**:composer 因保留 run id / streaming_status / orphan bubble 无法再 send(`1c78d614`/`db8c3dcc`/`c3639eba`/`5c222d18`)。 +5. **历史 UI-11 产品/契约债**(仍须本批真实 PASS 才能关 audit):路由+lease、`prelim_design` output.md、快完成观测误杀等(见映射 UI-11 行)。 + +密封 UI-00..10 之后,活账本再次从 early cases 重写;本轮观测到的 live tip 账本停在 UI-04 PASS(UI-03 曾耗时 ~305s)。 + +## UI case → audit / remediation 关闭映射 + +**关闭规则**:仅当本任务 assets 下 `ui-acceptance-results.json` 对该 case(及子 case)记 `verdict=PASS`,且所属 phase 完整(main:UI-00..16;post-restart:约定 UI-15 恢复项)时,才可将对应文件 `status` 改为 `done` 并挂证据链接。截图 alone 不够。 + +| UI case | 关闭时的 audit(`in_progress`) | 关闭时的 remediation(`in_progress`) | +|---|---|---| +| UI-00 / UI-01 / UI-02 / UI-04 / UI-10 / UI-12 / UI-14 | (无独立 09-13 未关 audit;作为全链前置) | — | +| UI-03 | `sse-idempotency`, `quality-status-false-positive`, `quality-repair-workspace-contract`, `quality-gate-self-pollution` | `quality-repair-workspace-contract` | +| UI-05 | `active-doc-routing` | — | +| UI-05a | `pending-report-selection` | — | +| UI-05b | `e2e-reject-routing` | — | +| UI-06 | `block-selector`, `save-selector`, `editor-hydration-race` | `editor-hydration-race` | +| UI-07 | `overlay-selector`, `overlay-transition`(与 08/09 共证) | — | +| UI-08 / UI-09 | `overlay-transition`(三模式共证) | — | +| UI-11 | `second-doc-routing-lease`, `prelim-output`, `fast-completion-observation` | `second-doc-routing-lease`, `prelim-output`, `fast-completion-observation` | +| UI-13a | `workflow-category-navigation`, `reviewer-duty-sync`, `duty-cell-assertion` | `workflow-category-navigation`, `reviewer-duty-sync`, `duty-cell-assertion` | +| UI-13b | `duty-etag-overwrite`(及 save-selector 并发路径) | `duty-etag-overwrite` | +| UI-15 + `UI_PHASE=post-restart` | `post-restart-selector` | — | +| UI-16 | (`runtime-404` 已 `done`;本批不重开) | — | + +路径前缀:`.doc/audit/2026-09-13-document-revision-.md` / `.doc/remediation/2026-09-13-document-revision-.md`。 + +说明:`remediation/...-management-implementation-plan.md` 仍为方案态 `in_progress`,**不**由单一 UI case PASS 自动关闭。 ## 收口 -- 实现 tip 见分支历史(含 Edge 通道与 UI-00 修复);`integration_state=partial`。 -- 下一步:持 `env:dev` 租约实跑 → 归档 assets → 再关 audit/remediation。 +- `status=in_progress`:缺 main 全量 + post-restart 真实 PASS。 +- `integration_state=partial`:e2e/web tip 已前进;audit 终态未动。 +- 下一步:在 mutex 下跑完 main UI-00..16 → 归档稳定账本 → post-restart → 再按上表关 audit/remediation。 +- **本轮未关闭任何 audit/remediation。** diff --git a/.doc/test-reports/2026-09-22-docrev-ui-rerun-test-report.md b/.doc/test-reports/2026-09-22-docrev-ui-rerun-test-report.md new file mode 100644 index 00000000..852677e9 --- /dev/null +++ b/.doc/test-reports/2026-09-22-docrev-ui-rerun-test-report.md @@ -0,0 +1,78 @@ +--- +title: "DOCREV UI 重跑验收报告(partial stub)" +date: 2026-09-23 +task_id: "docrev-ui-rerun-2026-09-22" +verdict: PARTIAL +tested_sha: "1dbdb950887b120979473d11f8830efdd2a474ea" +assets_dir: ".doc/test-reports/assets/2026-09-22-docrev-ui-rerun" +--- + +# DOCREV 真实 UI 重跑 — 部分报告(非 PASS) + +依据 [验证契约](../ops/2026-09-21-validation-contract.md):前端交付须真实浏览器证据;stub/SKIP/未跑不得冒充 PASS;声明 PASS 须有对应真实输入输出。本文件为 **partial stub**,**不得**当作全链验收 PASS。 + +## 范围 + +- 任务:`docrev-ui-rerun-2026-09-22`(mutex:`web + docrev-ui-acceptance`) +- 目标:`UI_PHASE=main`(UI-00..16)+ `UI_PHASE=post-restart` 真实 Playwright,证据仅写入本 assets 目录 +- 关闭目标:20 份 `in_progress` audit + ~9 份同名 remediation(见 short-term 映射) +- 非范围:本轮不启动新 Playwright;不杀既有进程;不改 plan 文件;不 push + +## 被测 SHA / 环境 + +| 字段 | 值 | +|---|---| +| 分支 tip | `1dbdb950887b120979473d11f8830efdd2a474ea` | +| 环境 | 共享 `env:dev`(持锁实跑中/穿插;本报告作者未新开跑) | +| schema / BASE_URL | 以实跑容器为准(未在本 stub 中重核) | +| 命令形态 | `RUN_UI_ACCEPTANCE=1 UI_PHASE=main` + Playwright `document-revision-runtime.spec.ts`(对等 Agent 执行) | + +## 预期 vs 实际 + +| 预期 | 实际 | 判定 | +|---|---|---| +| main:UI-00..16 全部 `verdict=PASS` 写入活账本 | 活账本仅 **UI-00..04 = PASS**;UI-05..16 **缺失** | FAIL / 未完成 | +| post-restart:UI-15 恢复项 PASS | 未执行 | 未跑 | +| 密封 UI-00..10 截图可复盘 partial 成功段 | `sealed-ui00-10-20260922T160841Z/screenshots/` 含 UI-00..10 相关 PNG | 截图证明;**非** JSON PASS 账本 | +| 关闭 20 audit + ~9 remediation | **0 关闭**(缺全量 PASS) | 正确保持 `in_progress` | + +### 活账本摘要(`ui-acceptance-results.json`) + +快照时刻:报告起草时读取;对等 Agent 可能继续改写。 + +| Case | verdict | +|---|---| +| UI-00 | PASS | +| UI-01 | PASS | +| UI-02 | PASS | +| UI-03 | PASS(~305s) | +| UI-04 | PASS | +| UI-05 .. UI-16 | **MISSING**(无条目) | +| UI-05a / UI-05b / UI-13a / UI-13b | **MISSING** | + +### 密封截图(UI-00..10 已证明到达) + +目录:`.doc/test-reports/assets/2026-09-22-docrev-ui-rerun/sealed-ui00-10-20260922T160841Z/screenshots/` + +含:`UI-00-admin-logged-in.png`, `01-project.png`, `UI-02-squad-started.png`, `03-first-doc.png`, `04-first-report.png`, `05-second-report.png`, `UI-05a-confirmed.png`, `UI-05b-rejected.png`, `UI-06-after-refresh.png`, `UI-07-pure.png`, `UI-08-manual.png`, `UI-09-all.png`, `UI-10-history-compare.png`。 + +说明:密封集证明**先前**成功跑过 UI-00..10 交互并落盘截图;当前活 `ui-acceptance-results.json` **没有** UI-05..10 的 PASS 条目(被后续全链重启截断)。关闭 audit **必须以**完整 JSON PASS 账本为准。 + +## 剩余条件 + +1. **UI-11..16(main)**:须连续写入 PASS;当前 blocker 焦点在 UI-11(三权推进、doc rail、chat unlock、prelim 路由/产物等;tip 持续修 e2e)。 +2. **post-restart**:main 稳定归档后再跑;关 `post-restart-selector` audit。 +3. 全量 PASS 后按 [short-term 映射表](../ops/short-term/2026-09-22-docrev-ui-rerun.md#ui-case--audit--remediation-关闭映射) 批量改 `done` 并回链本报告/assets。 + +## 结论 + +- **判定**:`PARTIAL` — 非交付 PASS。 +- **审计关闭**:否(0)。 +- **风险**:对等 Agent 全链重启会覆盖活账本与活截图;关闭前应拷贝稳定 run 目录或确认 sealed/ledger 一致。 + +## 证据链接 + +- Assets 根:[assets/2026-09-22-docrev-ui-rerun/](./assets/2026-09-22-docrev-ui-rerun/) +- 活账本:[ui-acceptance-results.json](./assets/2026-09-22-docrev-ui-rerun/ui-acceptance-results.json) +- 密封截图:[sealed-ui00-10-20260922T160841Z/](./assets/2026-09-22-docrev-ui-rerun/sealed-ui00-10-20260922T160841Z/) +- 任务短文:[2026-09-22-docrev-ui-rerun.md](../ops/short-term/2026-09-22-docrev-ui-rerun.md) From 1ff3d81e1b8834c71585841d8e5bb9a7a89352ca Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Wed, 23 Sep 2026 00:27:28 +0800 Subject: [PATCH 5/6] docs(ops): pin docrev-ui head_sha after closeout scaffold --- .doc/ops/short-term/2026-09-22-docrev-ui-rerun.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.doc/ops/short-term/2026-09-22-docrev-ui-rerun.md b/.doc/ops/short-term/2026-09-22-docrev-ui-rerun.md index 17de8f58..8913f526 100644 --- a/.doc/ops/short-term/2026-09-22-docrev-ui-rerun.md +++ b/.doc/ops/short-term/2026-09-22-docrev-ui-rerun.md @@ -13,7 +13,7 @@ worktree: .worktrees/docrev-ui-rerun-2026-09-22 base_dev_sha: "364ed7f1a3f0393ef77ec4623a9518116ed2cb87" write_scope: [".doc", "e2e", "web"] allowed_environments: ["dev"] -head_sha: "1dbdb950887b120979473d11f8830efdd2a474ea" +head_sha: "c9a9b901722085fde5179f77f78d20beb576ccb9" mutex: "web + docrev-ui-acceptance" review_state: pending integration_state: partial @@ -44,7 +44,7 @@ remote_sync: not_requested | 项 | 状态 | 证据 | |---|---|---| -| tip SHA | `1dbdb950887b120979473d11f8830efdd2a474ea` | `fix(e2e): click 确认推进 after intake 三权 before UI-11` | +| tip SHA | `c9a9b901722085fde5179f77f78d20beb576ccb9`(docs 收口脚手架);e2e tip 父提交 `1dbdb950` | 本提交 docs;e2e:`fix(e2e): click 确认推进 after intake 三权 before UI-11` | | 活账本 `ui-acceptance-results.json` | **仅 UI-00..04 = PASS**;UI-05..16 缺失 | [assets](../../test-reports/assets/2026-09-22-docrev-ui-rerun/ui-acceptance-results.json) | | 密封截图 UI-00..10 | 先前成功 partial 已密封;**截图证明到达 UI-10,不等于 JSON PASS 账本** | [sealed-ui00-10-20260922T160841Z](../../test-reports/assets/2026-09-22-docrev-ui-rerun/sealed-ui00-10-20260922T160841Z/screenshots/) | | 活截图目录 | 被后续重跑覆盖/混写;以 sealed 为准复盘 UI-00..10 | [screenshots/](../../test-reports/assets/2026-09-22-docrev-ui-rerun/screenshots/) | From 3bdd53ae35bbb6e9ba4068475315e367d69be48f Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Wed, 23 Sep 2026 00:45:31 +0800 Subject: [PATCH 6/6] fix(freedoc): widen freeform project_id to BIGINT for snowflake IDs UI-11 POST /freeform-documents 500 when project_id exceeds int4. Align Go types + goose 00063 with projects.id BIGINT. --- ..._2026-09-22-freeform-project-id-bigint.sql | 10 ++++++++++ internal/freedoc/create_document_test.go | 19 +++++++++++++++++++ internal/freedoc/service.go | 2 +- internal/freedoc/types.go | 8 ++++---- internal/server/freeform_doc_api.go | 6 ++++-- 5 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 deploy/postgres/goose/00063_2026-09-22-freeform-project-id-bigint.sql diff --git a/deploy/postgres/goose/00063_2026-09-22-freeform-project-id-bigint.sql b/deploy/postgres/goose/00063_2026-09-22-freeform-project-id-bigint.sql new file mode 100644 index 00000000..bfc52c12 --- /dev/null +++ b/deploy/postgres/goose/00063_2026-09-22-freeform-project-id-bigint.sql @@ -0,0 +1,10 @@ +-- +goose Up +-- freeform_documents.project_id must match projects.id (BIGINT snowflake). +-- Prior INT rejected inserts once project IDs exceeded int4 max (~2.1e9). + +ALTER TABLE freeform_documents + ALTER COLUMN project_id TYPE BIGINT; + +-- +goose Down +ALTER TABLE freeform_documents + ALTER COLUMN project_id TYPE INT; diff --git a/internal/freedoc/create_document_test.go b/internal/freedoc/create_document_test.go index c1203163..8bbd9b53 100644 --- a/internal/freedoc/create_document_test.go +++ b/internal/freedoc/create_document_test.go @@ -59,6 +59,25 @@ func TestCreateDocument_ClientKeyIdempotentAndBootstrapsLedger(t *testing.T) { assert.NotEqual(t, detail.Document.DocID, otherUser.Document.DocID) } +func TestCreateDocument_AcceptsSnowflakeProjectID(t *testing.T) { + // projects.id is BIGINT; freeform must accept IDs above int4 max (regression: int4 encode 500). + const snowflakeProjectID int64 = 1789095399489 + svc := freedoc.NewService(nil, nil) + detail, err := svc.CreateDocument(context.Background(), 42, freedoc.CreateFreeformDocRequest{ + ProjectID: snowflakeProjectID, + Title: "snowflake.pdf", + ContentMarkdown: "# snowflake\n", + Space: "my_private", + }) + require.NoError(t, err) + assert.Equal(t, snowflakeProjectID, detail.Document.ProjectID) + + list, err := svc.ListDocuments(context.Background(), 42, snowflakeProjectID, "my_private") + require.NoError(t, err) + require.Len(t, list, 1) + assert.Equal(t, snowflakeProjectID, list[0].ProjectID) +} + func TestCreateDocument_RejectsEmptyContent(t *testing.T) { svc := freedoc.NewService(nil, nil) _, err := svc.CreateDocument(context.Background(), 1, freedoc.CreateFreeformDocRequest{ diff --git a/internal/freedoc/service.go b/internal/freedoc/service.go index 16f9eb73..05e3b866 100644 --- a/internal/freedoc/service.go +++ b/internal/freedoc/service.go @@ -525,7 +525,7 @@ func (s *Service) GetDocument(ctx context.Context, userID int, docID string) (*F } // ListDocuments 按空间与权限列出自由文档概要 -func (s *Service) ListDocuments(ctx context.Context, userID int, projectID int, space string) ([]FreeformDocSummary, error) { +func (s *Service) ListDocuments(ctx context.Context, userID int, projectID int64, space string) ([]FreeformDocSummary, error) { if space != "my_private" && space != "project_shared" && space != "trash" { space = "my_private" } diff --git a/internal/freedoc/types.go b/internal/freedoc/types.go index 78b5cfcd..a0f0898e 100644 --- a/internal/freedoc/types.go +++ b/internal/freedoc/types.go @@ -16,7 +16,7 @@ var ( // FreeformDocument 自由私密/共享文档元数据 type FreeformDocument struct { DocID string `json:"doc_id"` - ProjectID int `json:"project_id"` + ProjectID int64 `json:"project_id"` OwnerUserID int `json:"owner_user_id"` SourceMessageID *int64 `json:"source_message_id,omitempty"` SourceContentSHA256 string `json:"source_content_sha256"` @@ -53,7 +53,7 @@ type FreeformDocDetail struct { // FreeformDocSummary 列表项概要 type FreeformDocSummary struct { DocID string `json:"doc_id"` - ProjectID int `json:"project_id"` + ProjectID int64 `json:"project_id"` OwnerUserID int `json:"owner_user_id"` Title string `json:"title"` Space string `json:"space"` @@ -66,14 +66,14 @@ type FreeformDocSummary struct { // CreateFromMessageRequest 从单条消息保存到私密文档请求 type CreateFromMessageRequest struct { MessageID int64 `json:"message_id"` - ProjectID int `json:"project_id"` + ProjectID int64 `json:"project_id"` Title string `json:"title,omitempty"` ExpectedSHA256 string `json:"expected_sha256,omitempty"` } // CreateFreeformDocRequest 直接创建空白/自定义自由文档请求(上传附件即时注册事实源) type CreateFreeformDocRequest struct { - ProjectID int `json:"project_id"` + ProjectID int64 `json:"project_id"` Title string `json:"title"` ContentMarkdown string `json:"content_markdown"` Space string `json:"space,omitempty"` // 默认 'my_private' diff --git a/internal/server/freeform_doc_api.go b/internal/server/freeform_doc_api.go index 2cf1cd27..6418738e 100644 --- a/internal/server/freeform_doc_api.go +++ b/internal/server/freeform_doc_api.go @@ -181,9 +181,11 @@ func (s *Server) handleListFreeformDocs(w http.ResponseWriter, r *http.Request) if space == "" { space = "my_private" } - projectID := 0 + projectID := int64(0) if pStr := r.URL.Query().Get("project_id"); pStr != "" { - projectID, _ = strconv.Atoi(pStr) + if parsed, err := strconv.ParseInt(pStr, 10, 64); err == nil { + projectID = parsed + } } list, err := s.freeDoc.ListDocuments(r.Context(), userID, projectID, space)