@@ -0,0 +1,241 @@
# 快慢通道 U3 质量修订 Implementation Plan r0.2
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. 本任务沿用已登记的 Dev worktree 和原生逐项实施方式;不再创建同名任务或重复启动 precheck。本文是阶段 5 的**待人工评审计划**,不是新增付费模型授权。
**Goal: ** 让同一任务卡的材料阶段成果忠于已提供原文、清楚标出待核对判断,并在受控预算内尽量形成真实最终答复。
**Architecture: ** Backend 用现有 `materialSourceRegistry` 把来源编号和实际提供的原文块一次呈现,不改变来源权限或 Core JSON。Backend 在检查点提交前挡住模型生成的肯定批准断言,材料子代理依据已返回 Usage 为最终答复留请求空间;UI 在既有卡片中区分材料记载与待核对判断。旧失败样本、执行偏差和现有三栏仍是验收基线。
**Tech Stack: ** Go、`agent-native/runtime` 子代理循环、SQLite/PostgreSQL、React/TypeScript、现有 `@ui` /Vitest/Playwright、Dev `make evidence` 与 Gitea CI。
**Spec: ** [已批准书面设计 r0.2 ](2026-10-13-fast-slow-u3-quality-revision.md )。原 [U2/U3 计划 r0.1 ](2026-10-13-fast-slow-u2-u3-plan.md ) 的 U2 契约、执行权及失败恢复不重做。书面设计获用户批准;本实施计划仍待阶段 5 评审。
## Global Constraints
- 保留现有三栏、同一任务卡及右栏「工作 / 文件」;不增加通道页、命令详情或新的确认能力。
- 不改变 Core `WorkCheckpointCandidate` JSON、Store 表、SSE 事件、owner/project 约束及来源 quote 必须为连续原文的校验。
- 不新增摘要/审核模型回合;旧样本及其原始 `exit=1` 、57,997 Usage、14,231-token 停止规则偏差不重写。
- 本轮只做零付费代码与浏览器验证。任何新真实模型样本须**另行明确批准** run 数、单 run/总 Usage 目标、在途越额风险和停止条件;未获批准为 `NOT_RUN` 。
- UI 颜色/圆角/字号只消费 `@ui/tokens.css` ,动效只用 `@ui/motion.css` ,折叠只用 `@ui` Collapsible;异步状态仍由现有 Frontend store 单一所有者按 seq 处理。
- 组件先各自提交;Dev gitlink 每次推进后跑 `make test && make build` 。正式验收、请求审核与合入前按 `policy/delivery.md` 和 AGENTS.md 执行 `make task-sync` ; SELF 原始输出经 `make evidence` 进入任务卡。新候选的 GATE 只能引用准确 HEAD。
## Review Focus
1. **重复文字/无 Origin/截断末块 ** :模型只得到注册表授权的 sourceId,输入文字仍与实际提供前缀一致;Task 1 的 `TestMaterialSourceInlineAnnotationsPreserveProvidedPrefix` 和 `TestMaterialSourceInlineAnnotationsNeverExposeHiddenBlocks` 覆盖。
2. **否定、疑问与肯定审批表达 ** :“审批尚未完成”“是否已批准”“防止把50万写成已批准”不能被误拒;“预算是已批准决定”必须整批拒绝;Task 2 的表驱动测试覆盖。
3. **默认摘要把 analysis 当事实 ** :既有材料记载可见,待核对判断带限定且总默认项数不超过三;Task 3 的 UI 组件测试覆盖。
4. **两个检查点后仍无限继续/只有一个检查点时余额不足 ** :优先真实最终答复或诚实部分结果,不多发 provider 请求;Task 4 的假 provider 用量序列覆盖。
5. **正常通知、来源导航与停止边界 ** :测试通知不代表生产通知质量;Task 5 的实际 HTTP/browser 用例及 Task 6 的独立真实样本出口分别覆盖,任何工具拒绝后不继续付费。
---
### Task 1:一次呈现原文和可引用来源(Backend)
**Files: ** Modify `modules/backend/internal/runmgr/material_sources.go` 、`material.go` 、`material_sources_test.go` 、`material_test.go` ;不改 `modules/core/task_snapshot.go` 。
**Interfaces: ** `materialSources(docs, perDoc)` 继续产出唯一授权注册表。给内部 `materialSource` 增加 `Start, End int` (相对于 `strings.TrimSpace(d.PlainText())` 的 byte offset),新增 `annotateProvidedMaterial(d core.ParsedDocument, provided string, sources materialSourceRegistry) string` 。`buildMaterialTask` 仅渲染各文档提供的前缀与紧邻的 `sourceId/blockIndex` ,删除末尾重复索引;`validate` 原语义不动。
- [ ] **Step 1: 在既有测试写 RED。 ** `material_sources_test.go` 用含重复段落、截断段落、缺 Origin 和表格的文档,断言只对注册表里的块插入 `sourceId=... blockIndex=...` ;移除这些标记后,正文与当前 `strings.TrimSpace(d.PlainText())` 的实际提供前缀逐字节相同。`material_test.go` 断言同一个短事实只出现一次、每个可引用标记紧邻其实际块、超长提示仍出现。
```go
func TestMaterialSourceInlineAnnotationsPreserveProvidedPrefix(t *testing.T) {
d := materialDocs()[0]
d.Meta.Origin = &core.DocumentOrigin{ArtifactID: "art1", ContentHash: strings.Repeat("a", 64), ParserRevision: "docx1"}
docs := []core.ParsedDocument{d}
provided := strings.TrimSpace(d.PlainText())
got := annotateProvidedMaterial(d, provided, materialSources(docs, 48000))
markers := regexp.MustCompile(` (?m)^sourceId=src_[a-f0-9]+ blockIndex=[0-9]+\n`)
if !markers.MatchString(got) { t.Fatal("provided block has no source marker") }
if markers.ReplaceAllString(got, "") != provided {
t.Fatal("source annotations changed the provided material bytes")
}
}
` ``
- [ ] **Step 2: 运行 RED。** ` cd modules/backend && go test ./internal/runmgr -run 'TestMaterialSourceInlineAnnotations|TestBuildMaterialTaskInlineBudget' -count=1`;期望新增的“一次呈现/标记权限”断言失败,而旧来源 CAS 测试仍可运行。
- [ ] **Step 3: 实现最小改动。** 在 ` materialSources` 记录已提供块的 byte 起止;按 ` Start` 排序,在原 ` provided` 字符串里只插入来源标记,保持所有非标记字节、顺序和截断位置不变;缺 Origin 不赋 ID。` buildMaterialTask` 只构造一次注册表,不再调用末尾的 ` prompt()`; ` materialRevision` 的 ` material-recipe.v7` 更新为 ` v8`,旧完成结果不被新输入布局复用。
` ``go
sources := materialSources(docs, perDoc)
// 原有每份文档的 cap/left 计算保持不变。
take := cap
if runes < take { take = runes }
provided := string([]rune(text)[:take])
b.WriteString(annotateProvidedMaterial(d, provided, sources))
` ``
- [ ] **Step 4: 验证 GREEN 与权限。** 重跑 Step 2,加 ` go test ./internal/runmgr -run 'TestMaterialSourceQuoteRequiresActualProvidedBlock|TestMaterialSourceDuplicateTextDoesNotAuthorizeUnprovidedBlock|TestMaterialInputRevisionIncludesProcessingVersion' -count=1`。用旧 A01 材料在离线诊断中记录新旧输入字符数、sourceId 集合与 hash;只声明实际测得的长度,不推断新模型 token 使用量。
- [ ] **Step 5: 自审并提交 Backend 独立提交。** 检查 ` git diff --check`、新增 ID 与注册表一一对应、没有未提供文本或权限放宽;按本仓 ` Why/Evidence/Impact/Refs` 提交。此项可被独立退回而不影响 Task 2 的审批防误判实现。
### Task 2:检查点审批断言守卫(Backend)
**Files:** Create ` modules/backend/internal/runmgr/checkpoint_claim_guard.go` 与 ` checkpoint_claim_guard_test.go`; modify ` task_checkpoint.go`、` task_checkpoint_test.go`、` internal/runtime/work_checkpoint.go` 的工具说明。生产拒绝只作用于系统材料整理的受控检查点,不修改一般对话正文。
**Interfaces:** ` validateMaterialApprovalClaims(candidate core.WorkCheckpointCandidate) error` 在 ` PublishWorkCheckpoint` 的 Core 基础校验后、任何持久写入前调用。检查 ` analysis/provisional` 的 ` Text/Impact/Assumptions` 及默认可见的 ` Focus/Remaining/NextStep`。` material_record` 继续由宿主重写成原文;` needs_user` 可询问审批状态。肯定审批表达至少覆盖“已批准/已获批/审批通过/批复同意/已通过审批”,否定与待核对表达单列测试;返回稳定的 ` checkpoint.claim: approval needs explicit source record` 错误,不把它包装成已通过校验。
- [ ] **Step 1: 写 RED。** 在 ` task_checkpoint_test.go` 复用 ` checkpointEnvironment` 的合法“上线六月”quote,却让 analysis 无依据声称“预算是已批准决定”,断言 ` PublishWorkCheckpoint` 返回 ` checkpoint.claim` 且卡 revision/发现数/事件数均不变。表驱动测试覆盖 ` Text/Impact/Assumptions/Focus/Remaining/NextStep`,并包含“审批尚未完成”“是否已批准”“防止把50万写成已批准”、确有批准原文的 ` material_record` 与 ` needs_user` 问句,断言不误拒。
` ``go
func TestTaskCheckpointRejectsUnsupportedApproval(t *testing.T) {
c := checkpointEnvironment(t)
cp := validCheckpoint(c, "unsupported-approval")
cp.Findings[0].Kind = "analysis"
cp.Findings[0].Text = "需求规定50万元,是已批准决定"
cp.Findings[0].Assumptions = "把需求条目作为项目基线"
if err := c.PublishWorkCheckpoint(cp); err == nil || !strings.Contains(err.Error(), "checkpoint.claim") {
t.Fatalf("unsupported approval was published: %v", err)
}
}
` ``
- [ ] **Step 2: 运行 RED。** ` cd modules/backend && go test ./internal/runmgr -run 'TestTaskCheckpointRejectsUnsupportedApproval|TestMaterialApprovalClaims' -count=1`;期望错误分析目前被接受,修复后转为拒绝。
- [ ] **Step 3: 实现有限守卫。** 新文件按中文句子边界检查明确的肯定审批短语,排除同句的未批准/疑问/防止误写语境;仅以此处理可确定的表达,不宣称通用语义正确。` PublishWorkCheckpoint` 先运行守卫,再走原有来源、owner、版本、CAS 与事务路径;工具说明要求批准事实只作为明确原文 ` material_record`,推断为待核对。
` ``go
if err := core.ValidateWorkCheckpoint(candidate); err != nil { return err }
if err := validateMaterialApprovalClaims(candidate); err != nil { return err }
// 原有来源、权限、版本及 CommitTaskSources 路径继续执行。
` ``
- [ ] **Step 4: 跑 GREEN 与邻接回归。** Step 2 加 ` go test ./internal/runmgr -run '^TestTaskCheckpoint' -count=1` 和 ` go test ./internal/runtime -run '^TestWorkCheckpoint' -count=1`。若短语误拒或遗漏已批准变体,补夹具后只调整这个守卫;不借更宽泛的模型分数降低原来源门禁。
- [ ] **Step 5: 自审并提交 Backend 独立提交。** 复核分析仍保留来源和 assumptions、材料记载的原文不被删除、被拒批次无半落盘;` git diff --check` 后按四段提交。
### Task 3:任务卡把原文与判断分层(UI)
**Files:** Modify ` modules/ui/src/ProgressCardBody.tsx`、` src/activitySummary.ts`、` test/progressive-cards.test.tsx`;仅在现有卡内分组,不修改 Frontend store 或新增 UI token。
**Interfaces:** ` TaskProgressBody` 保持同一个 ` ActivityCard.Task`。默认最多三条:优先展示至多两条 ` material_record`,其余位置可放带“待核对的判断”标识的 analysis/provisional; ` needs_user` 与更正继续即时可见。` activitySummary` 给模型判断加限定,不得把 analysis 的原始句子作为无保留的摘要事实;完整依据仍在现有 Collapsible。
- [ ] **Step 1: 写 RED。** 在 ` test/progressive-cards.test.tsx` 构造两条材料记载、一条 analysis “预算已批准”、一条 ` needs_user` 和第四条普通记录,断言默认三个以内且 analysis 所在区域有“待核对的判断”标签;` activitySummary` 的摘要不能出现无前缀的“预算已批准”;点击现有“查看依据/全部发现”仍能读原 quote 与历史更正。另用仅 analysis 的卡断言说明“已有待核对判断”,不冒称“已核对”。
` ``tsx
// 在 test/progressive-cards.test.tsx 增加 activitySummary 与 TaskFinding import。
const subject = card()
const source = { id: 's', artifactId: 'art', contentHash: 'h', parserRevision: 'txt', blockIndex: 0, label: '需求.txt · 段落1', quote: '预算50万尚未审批', availability: 'available' }
const finding = (id: string, kind: string, text: string): TaskFinding =>
({ id, kind, text, status: 'active', revision: 1, sources: [source] })
subject.task = { schemaVersion: 1, taskId: 't', attemptId: 'a', cardId: subject.id,
projectId: subject.projectId, conversationId: subject.conversationId!, ownerInstanceId: 'i',
revision: 3, sourceSeq: 3, streamScope: 'conversation:' + subject.conversationId,
title: subject.title, executionState: 'running', outcomeCompleteness: 'partial',
deliveryState: 'available', phase: { id: 'crosscheck', label: '核对材料' },
findings: [finding('r1', 'material_record', '材料记载1'), finding('r2', 'material_record', '材料记载2'),
finding('a', 'analysis', '预算已批准'), finding('q', 'needs_user', '请确认预算'),
finding('r3', 'material_record', '材料记载3')] }
render(<ActivityCardView card={subject} />)
expect(screen.getByText('待核对的判断')).toBeTruthy()
expect(screen.getByText('预算已批准').closest('.acard-findings')?.textContent).toContain('待核对')
expect(activitySummary(subject).findings).not.toContain('预算已批准')
` ``
- [ ] **Step 2: 运行 RED。** ` cd modules/ui && npm test -- test/progressive-cards.test.tsx`;预期当前同组“当前发现”会把 analysis 当普通项。
- [ ] **Step 3: 实现最小 UI。** 沿用 ` @ui ` Collapsible 和现有 ` acard-*` 类;按 kind 投影到材料记载/待核对判断/需要留意,默认总数上限三,剩余到“查看全部发现与更正记录”;更新摘要选择,不改变卡片状态或来源导航。若需 CSS,只消费现有 ` @ui/tokens .css`、` --fs-ui-*` 和 ` mk-*`。
` ``tsx
const records = ordinary.filter(f => f.kind === 'material_record')
const judgments = ordinary.filter(f => f.kind === 'analysis' || f.kind === 'provisional')
const visibleRecords = records.slice(0, 2)
const visibleJudgments = judgments.slice(0, 3 - visibleRecords.length)
// needs_user/更正仍走既有关键项路径;未展示项保留在原 Collapsible。
` ``
- [ ] **Step 4: 跑 GREEN 与设计守护。** ` cd modules/ui && npm test -- test/progressive-cards.test.tsx test/design-guard.test.ts test/contrast-guard.test.ts && npm run typecheck`;检查浅/深色和窄屏文本层级,没有新裸色、私有 keyframes 或 ` <details>`。
- [ ] **Step 5: 提交 UI 独立提交。** 自审普通用户能一眼区分原文与模型判断,` git diff --check` 后按四段提交;它独立于 Task 4 的预算实现。
### Task 4:材料子代理的最终答复预留(Backend)
**Files:** Create ` modules/backend/internal/runtime/material_final_reserve.go`、` material_final_reserve_test.go`; modify ` sub_loop.go`; replay the existing ` run_budget_stream_test.go` unchanged。一般子代理、前台、生产模型路由和 ` budgetedStream` 的已返回 Usage 守卫不扩大。
**Interfaces:** ` type materialBudgetState struct { Limit, Used, LastPrompt, OutputCap, SavedCheckpoints, Responses int; UsageKnown bool }` 与 ` materialFinalDecision(s materialBudgetState) (finalOnly, stop bool)` 只处理 ` agent.sub.material`。` Responses=0` 是尚未产生调用的初态;已有响应但 Usage 未知则 fail-closed。增长余量 ` margin=max(1024, LastPrompt/4)`;最终预留 ` reserve=LastPrompt+OutputCap+margin`,再做一次检查点的估算成本也取同一值。` Limit-Used <= LastPrompt+margin` 时停止;否则 ` SavedCheckpoints>=2` 或 ` Limit-Used <= 2*reserve` 时进入 final-only。首个响应前不套用该估算。这样保留一次最终请求的空间,且一次成果后发现下一次发布会挤占预留时优先收尾,明确这是部分结论而非第二项。final-only 不再向 provider 声明 ` publish_work_checkpoint`,并添加系统拥有的简短最终答复指令;若剩余额度不容纳预计最终 prompt,则不发新请求,保留已保存卡和真实失败状态。它不保证在途请求不越过总目标。
- [ ] **Step 1: 写 RED。** ` material_final_reserve_test.go` 用假 stream 顺序返回用量,断言“初始零响应允许首个请求”“两个合法检查点→下一次仅最终答复”“只有一个检查点且额度临界→真实部分最终答复”“额度不足→0 次额外 provider 请求”“已有响应而 Usage 缺失→停止”“非材料子代理保持原行为”。故意让假 provider 在 final-only 中仍请求隐藏的检查点,断言 ` runTool` 拒绝且没有持久发布。测试检查 provider 请求里的工具声明和系统提醒,而非只检查内部布尔值。
` ``go
func TestMaterialFinalReserveAfterTwoCheckpoints(t *testing.T) {
finalOnly, stop := materialFinalDecision(materialBudgetState{Limit: 30000, Used: 16000, LastPrompt: 5000, OutputCap: 4000, SavedCheckpoints: 2, Responses: 2, UsageKnown: true})
if !finalOnly || stop { t.Fatalf("want one final-only request: final=%v stop=%v", finalOnly, stop) }
finalOnly, stop = materialFinalDecision(materialBudgetState{Limit: 30000, Used: 27000, LastPrompt: 8000, OutputCap: 4000, SavedCheckpoints: 1, Responses: 2, UsageKnown: true})
if !stop { t.Fatalf("insufficient final prompt budget started another request: final=%v", finalOnly) }
}
` ``
- [ ] **Step 2: 运行 RED。** ` cd modules/backend && go test ./internal/runtime -run 'TestMaterialFinalReserve|TestRunBudgetStreamStopsReportedTokenExhaustion' -count=1`;期望当前两检查点之后仍继续暴露发布工具或临界余额耗尽。
- [ ] **Step 3: 实现调度。** ` subRun.runTool` 仅在检查点真实成功时增加 ` savedCheckpoints`,在 final-only 阶段拒绝模型凭旧记忆仍提交的检查点;` onEvent(message_end)` 用返回 Usage 更新 ` Used/LastPrompt/Responses/UsageKnown`。在 ` SubLLMRunner.Execute` 的 ` wrapped` 调用前计算决策;final-only 只改当前请求的工具声明和系统提示,不写回历史、不提高 ` MaxTokens` 或单次输出上限。预算不能证明可用时保留原失败/部分投影,不伪造正文。
` ``go
limit := *rc.Profile().Budget.MaxTokens // 仅在 MaxTokens 非 nil 且 >0 的材料分支调用
outputCap := outputTokenCap(rc.Profile().Budget, rc.Settings(), "models.chat.maxOutputTokens")
finalOnly, stop := materialFinalDecision(materialBudgetState{Limit: limit,
Used: st.usageTotal.TotalTokens, LastPrompt: st.usageTotal.LastPromptTokens,
OutputCap: outputCap, SavedCheckpoints: st.savedCheckpoints,
Responses: st.responses, UsageKnown: st.usageKnown})
if stop {
out := make(chan agentllm.Event, 1)
out <- agentllm.Event{Kind: agentllm.KindError, Err: fmt.Errorf("budget_exceeded: final-answer reserve unavailable")}
close(out)
return out
}
requestDecls := append([]agentllm.ToolDecl(nil), decls...)
requestMessages := append([]agentllm.Message(nil), messages...)
if finalOnly {
filtered := make([]agentllm.ToolDecl, 0, len(requestDecls))
for _, d := range requestDecls { if d.Name != workCheckpointTool { filtered = append(filtered, d) } }
requestDecls = filtered
requestMessages = append([]agentllm.Message{{Role: agentllm.RoleSystem, Content: "阶段成果已保存;现在用现有材料写出真实最终结论与未决项,不再发布检查点。"}}, requestMessages...)
}
in := stream(ctx, model, requestMessages, requestDecls, opts)
` ``
- [ ] **Step 4: 跑 GREEN 与现有子代理回归。** Step 2 加 ` go test ./internal/runtime -run 'TestWorkCheckpoint|TestSub|TestRunBudget' -count=1`。用 A01 离线 Usage 序列(6,778/ 10,134/ 11,343 prompt,整体 35,632)验证决策会在原子预算检查之前转入 final-only 或诚实停止;不能把这个离线判断当新版真实模型通过。
- [ ] **Step 5: 自审并提交 Backend 独立提交。** 检查无隐藏的附加模型回合、无流式并发数据竞争、无“两个检查点”计数伪造;按四段提交。
### Task 5:固定组件组合、零付费回归与独立 Review(Dev)
**Files:** Modify Dev ` modules/backend`、` modules/ui`、` modules/documents` gitlinks、` .task/feature-fast-slow-u2-u3-plan.md` and Documents 核验报告;Core/Frontend 不变。若测试证明公开契约或 Frontend store 必须改变,先返回方案评审再扩围。
- [ ] **Step 1: 确认每个组件工作树干净、提交 SHA 与 PR HEAD 一致。** Backend 完整 ` go test ./... -count=1`, UI ` npm test && npm run typecheck`, Frontend 受影响的现有 store/阅读测试重放;每项保留真实命令、SHA 和输出。
- [ ] **Step 2: 在已登记 Dev 任务 worktree 固定 gitlink 和任务卡。** 先提交组件,再推进 Dev 指针;不要把旧样本的 ` SELF` 或旧 HEAD CI 当新候选证据。
- [ ] **Step 3: 正式组合验收。** 在干净的 Dev HEAD 以 ` RECEIPT=/tmp/fast-slow-u2-u3-r02-sync-20261013.json CMD='make task-sync' make evidence` 同步最新主干,导入工具收据;随后以 ` RECEIPT=/tmp/fast-slow-u2-u3-r02-combination-20261013.json CMD='make test && make build' make evidence` 捕获完整 SELF 输出并导入任务卡。执行前检查同名收据不存在;若已占用则用新的任务专用时间后缀,不覆盖。对数据库、来源 CAS 和预算路径执行直接相关 SQLite/真实 PG 测试。
` ``bash
test ! -e /tmp/fast-slow-u2-u3-r02-sync-20261013.json
RECEIPT=/tmp/fast-slow-u2-u3-r02-sync-20261013.json CMD='make task-sync' make evidence
IMPORT_RECEIPT=/tmp/fast-slow-u2-u3-r02-sync-20261013.json make evidence
# 先提交任务卡中的同步收据,确保组合测试从干净 HEAD 开始。
test ! -e /tmp/fast-slow-u2-u3-r02-combination-20261013.json
RECEIPT=/tmp/fast-slow-u2-u3-r02-combination-20261013.json CMD='make test && make build' make evidence
IMPORT_RECEIPT=/tmp/fast-slow-u2-u3-r02-combination-20261013.json make evidence
` ``
- [ ] **Step 4: 实际浏览器验收。** 用任务自有、带 PID/二进制 SHA/数据目录 manifest 的 mechanism 实例运行 ` e2e/work-checkpoints.playwright.ts`;该 manifest 必须由本轮启动过程写入并在执行前验证,不复用旧文件。证明来源跳转、后台不抢阅读、刷新/旧版本、材料记载标签与浅色默认阅读;analysis 的限定标签由 Task 3 组件测试证明。不得使用旧端口或旧服务作为新版本证据。
` ``bash
mkdir -p /tmp/fast-slow-u2-u3-r02
test ! -e /tmp/fast-slow-u2-u3-r02/mechanism-data
test ! -e /tmp/fast-slow-u2-u3-r02/mechanism-manifest.json
test ! -e /tmp/fast-slow-u2-u3-r02/server-eval.test
cd modules/backend
go test -tags live -c ./internal/server -o /tmp/fast-slow-u2-u3-r02/server-eval.test
# 在保持打开的任务终端中执行前台服务,不用 nohup 后台启动。
backend_sha="$(git rev-parse HEAD)"
dev_sha="$(git -C ../.. rev-parse HEAD)"
WORK_CHECKPOINT_SERVER_MODE=mechanism WORK_CHECKPOINT_SERVER_DATA=/tmp/fast-slow-u2-u3-r02/mechanism-data WORK_CHECKPOINT_SERVER_MANIFEST=/tmp/fast-slow-u2-u3-r02/mechanism-manifest.json WORK_CHECKPOINT_BACKEND_SHA="$backend_sha" WORK_CHECKPOINT_DEV_SHA="$dev_sha" /tmp/fast-slow-u2-u3-r02/server-eval.test -test.run='^TestServeWorkCheckpointEval$' -test.v
` ``
在任务终端 B 从 Dev 根目录验证 manifest,启动预览。代理目标必须从本轮 manifest 读取,不能沿用旧端口;4179 若已占用则停止并选择另一个本任务专用空闲端口,不能接管既有预览:
` ``bash
cd modules/frontend
npm run build
python3 -c 'import hashlib,json,os; m=json.load(open("/tmp/fast-slow-u2-u3-r02/mechanism-manifest.json")); assert m["mode"]=="mechanism" and m["dataDir"]=="/tmp/fast-slow-u2-u3-r02/mechanism-data" and m["base"].startswith("http://127.0.0.1:") and os.path.realpath("/proc/%s/exe" % m["pid"])==m["binary"] and hashlib.sha256(open(m["binary"],"rb").read()).hexdigest()==m["binarySHA256"]'
api_target="$(python3 -c 'import json; print(json.load(open("/tmp/fast-slow-u2-u3-r02/mechanism-manifest.json"))["base"])')"
PLAYWRIGHT_API_TARGET="$api_target" PLAYWRIGHT_PREVIEW_PORT=4179 npm run preview -- --host 127.0.0.1 --strictPort
` ``
在同时保持打开的任务终端 C,从 Frontend 目录执行浏览器用例;结束后只停止这两个任务拥有的前台进程并保存截图/追踪:
` ``bash
cd modules/frontend
PLAYWRIGHT_BASE_URL=http://127.0.0.1:4179 WORK_CHECKPOINT_HTTP_MANIFEST=/tmp/fast-slow-u2-u3-r02/mechanism-manifest.json npm run e2e -- e2e/work-checkpoints.playwright.ts
` ``
- [ ] **Step 5: 独立代码审阅与草稿 PR。** 审阅准确的 Backend/UI/Docs/Dev SHA,重放来源映射、审批误判、预算、停止边界及浏览器最小集;修正发现后只重跑受影响的检查。推送草稿 PR 并核验当前 HEAD 的 Gitea CI 运行身份、SHA、每个必需 job/step;质量出口仍 ` NOT_RUN` 时不合入。
### Task 6:真实模型质量出口与交付决策(单独资源授权)
**Files:** Backend ` eval/live/work_checkpoints_test.go` 仅在固定候选所需的受控入口/读数缺口确有证据时修改;Dev 任务卡、Documents 核验报告写入结果。旧原始样本不覆盖、不补写。
- [ ] **Step 1: 先准备可审查资源提案。** 固定 Backend/Dev/Frontend/UI/Docs 精确 SHA、模型路由来源及受保护配置引用、A01 同一合成材料、Run/单 run/returned Usage 目标、输出上限、300 秒、缺 Usage、任何检查点拒绝、越额的停止规则与成本边界。明确在途调用可能越过目标;不给出虚假的账单硬上限。
- [ ] **Step 2: 等待用户另行明确批准该一次性付费范围。** 未获批准时只记录 ` NOT_RUN`,不以本计划或“继续”推定新样本授权;保持六个 PR 草稿。
- [ ] **Step 3: 只在批准范围内运行一次。** 启动新的隔离数据目录与一份独立结果文件,启动前验证 PID/二进制哈希、route 文件权限及零既有 run;运行后逐调用对账真实付费 run ID、Usage、检查点持久事件、来源字面与语义、用户可读窗口和最终正文。拒绝工具候选后全实例停止,不自动重试或补写。
- [ ] **Step 4: 独立审阅与门禁。** 若两个阶段、逐项语义、最终结果或停止规则任一 FAIL,就保留证据与草稿 PR,不合入或部署。若全部 PASS,依 ` policy/delivery.md` 再同步 base/head、复核独立 Review 与当前 HEAD CI,然后由智能体合入、核验实际结果并按任务卡规则安全收口;部署仍不在本计划授权内。