merge: feature/docrev-ui-rerun-2026-09-22 (freeform bigint + UI e2e)

This commit is contained in:
WeiCHEN
2026-09-23 00:47:23 +08:00
8 changed files with 283 additions and 26 deletions
@@ -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: "c9a9b901722085fde5179f77f78d20beb576ccb9"
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 | `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/) |
| 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-<slug>.md` / `.doc/remediation/2026-09-13-document-revision-<slug>.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。**
@@ -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)
@@ -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;
+19
View File
@@ -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{
+1 -1
View File
@@ -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"
}
+4 -4
View File
@@ -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'
+4 -2
View File
@@ -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)
+106 -6
View File
@@ -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();
@@ -454,21 +464,100 @@ 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 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('必需文档已批准,可推进')).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<void> {
if (!docId) fail('openWorkflowDocById requires docId');
await selectDeliverableCategory(page, requiredCategoryForDocOpen({ kind: 'exact-id', docId }));
@@ -1267,6 +1356,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 +1374,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.