# PROGRESS · 证据轮只说「记下了」却没写入(2026-09-10) 工作树:`.worktrees/rectification-unwritten-evidence-claim-20260910` 分支:`codex/rectification-unwritten-evidence-claim-20260910` 任务书:`docs/tasks/TASK-rectification-unwritten-evidence-claim-20260910.md` 基线:任务书写 `origin/staging` @ `d96b24c2`;开工接到 `84572c07`;合入前再接到 `origin/staging` @ `301827ad`(collect-semantics 任务书 + BUG-635 决策 4 去掉年份正则)。 ## 开工回执 - 编号 **BUG-635**(P1)/ **BUG-636**(P2)。`docs/BUG_HISTORY.md` 当时最大已发布号是 634。 - 顺序:分类器 `expectedWrite` → 无写入的「记下了」收回并重试一次 → 第二次主持人正文且不结算 → 客户端只认最后一条 settled 助手消息 → schema 拒绝信封失败映射 → 记录。 - 未实现 BUG-641~643。同一工作树交付 BUG-637~640,见 `PROGRESS-rectification-latent-audit-20260910.md`。 ## 决策 3 选 **turn `status=completed` + `answer_origin=host_fallback` + 不结算计费**。 实现:`completeAttempt(false)` → `settleBilling === false` → `billing.release()`,不写 `billing.settled`。客户端仍走完成轮刷新;未答焦点靠 T4 的主持人问题行。 ## 决策 4(`301827ad` 修订) `expectedWrite: "evidence" | "none" | "unknown"`。分类器 null/出错重试一次,仍失败则 `"unknown"`,守卫 fail-open。**没有年份正则 / 关键词兜底。** 无焦点的 `message` 路径也跑同一分类器。正文里的「记下了」仍触发守卫(决策 1)。 ## 已完成 - **T1**:`expectedWrite` 声明在 `immediateResponse` IIFE **之外**(`POST` 作用域),否则 `runV9AgentTurn({ expectedWrite })` 编译失败(TS18004)。collect / choice / 无焦点 message 都走 `classifyTurnIntentWithRetry`。`provide_new_evidence` 或 `answer_current_focus` + `has_new_dated_event` → `"evidence"`。`opening` / `read_only` 不触发。 - **T2**:`streamAttempt` 在 `empty_stream` 之后、`loadV9CaseDossier` 之前:`needsWrite && !publicWriteToolCompleted && !hostFallbackUsed` → 第一次 `evidence_not_written` + `retractSpoken()`(不进 `RETRYABLE_ERROR_CODES`);第二次主持人正文 `RECTIFICATION_USER_COPY.evidenceNotRecorded`。`needsWrite` = `expectedWrite === "evidence"` 或正文含「记下了」。batch 已完成且无正文仍走 BUG-633 `applyHostFallback`。 - **T3**:`buildAgentMessages` 在 `attempt > 1` 时用 `retryConstraintForAttempt(lastAttemptError)`。 - **T4**:`liveQuestionOnMessages` 只认 `latestSettledAssistant`。未答焦点落到既有 `persisted_question` 主持人问题行。`frontend/DESIGN.md` 表已补完成轮未答焦点。 - **T5**:schema 拒绝信封 `{ error: true, validationErrors: object }` → `tool.activity failed code=tool_call_rejected`;phase 映射与 batch recap 返回 null;公开事件不含 `validationErrors`。 - **T6**:BUG-635/636、CHANGELOG、走查第 15 条、任务表待验收、本文件。Skill 版本不变(`10.0.21`)。 ## 验收命令(实测) 工作树 `codex/rectification-unwritten-evidence-claim-20260910`,相对 `origin/staging` @ `301827ad`。 | 命令 | 结果 | | --- | --- | | `frontend` `tsx --test` 任务书相关文件(unwritten / host-fallback / spoken-collect / turn-intent / agentic-entry / agent-voice / v9-stream) | **141/141 pass** | | `frontend` `./node_modules/.bin/tsc --noEmit` | **0 error** | | `frontend` `npm run lint` | **0 error** / 111 warning(既有债) | | `git diff --check` | 干净 | | `next build` | 未跑;记环境缺口 | ### 本单测试名 - `collect intent maps provide_new_evidence and dated current-focus to expectedWrite` - `runner does not use a year-utterance fallback` - `retry bootstrap uses the unwritten-evidence constraint only for that error` - `route passes classifier expectedWrite into the agent runner` - `classifyTurnIntentWithRetry fails open as unknown after two misses` - `unwritten 记下了 retries once then host-falls-back without billing` - `read-case then batch then set-focus then 记下了 stays on the baseline path` - `empty body after a completed batch still uses BUG-633 host fallback` - `read_only action with a dated utterance does not trigger the write guard` - `schema rejection envelopes fail the tool and never count as a batch recap` - `rejected batch tool-result is failed in the runner terminal status` - `chat live question only reads the last settled assistant message` - host-fallback:`schema rejection envelopes are not treated as batch recaps or write completion` - host-fallback:`year fallback is gone; only explicit evidence expectedWrite triggers the guard` ### 既有断言改动 - `rectification-v9-stream.test.ts` empty_stream 二次成功正文:原值「记下了这件事。」/ 新值「先继续核对。」;原因:BUG-635 把无写入的「记下了」收口成 `evidence_not_written`,该用例只锁 empty_stream 重试。 - `rectification-v9-agent.test.ts` `evidence persist keeps the first sentence when the model writes three`:断言未改。原夹具 read-case 后直接写三句「记下了」、无 batch / 新夹具 batch + set-focus completed 后再写那三句 / 原因:Gitea `Independent Staging Quality Gate` run 2545 全文套件 3082/3083,该条被 635 收口成「这件我还没记上」;用例名是 persist,夹具必须真写入。 ## 环境缺口 - 无登录态:`docs/testing/rectification-scenarios-20260907.md` 第 15 节真人走查未做,不得写通过。 - 未跑 `next build`,未核 `/` 是否仍 `○ Static`、首屏 gzip ±2%。 - 推 staging 后才核 `/api/health` 的 `deployment.gitCommit`;未部署则记缺口。 ## 未做 - 未提升 `main`。Skill 未升版。 - 观察项与 BUG-641~643 未做。 ## 验收结论(Claude,2026-09-10,基线 `origin/staging` @ `8f9eb587`,staging 已部署同 SHA) | 项 | 结论 | | --- | --- | | T1 路由 `expectedWrite` → 运行器;无焦点 message 也跑分类器;分类器失败重试一次后 `unknown` fail-open;无年份正则 | 通过(`grep 'd{4}'` 四个文件为空) | | T2 收尾守卫:无写入的「记下了」/`expectedWrite=evidence` → 收回、`evidence_not_written` 重试一次;第二次主持人正文、`answer.host_fallback`、不结算(`billing.release`) | 通过(`rectification-unwritten-evidence.test.ts` 5 条运行器用例) | | T3 重试约束句只对 `evidence_not_written` | 通过 | | T4 客户端只认最后一条 settled 助手消息;`DESIGN.md` 已补 | 通过 | | T5 Mastra 拒绝信封 → `tool.activity failed code=tool_call_rejected`,batch recap / phase 不认信封 | 通过 | | T6 BUG-635/636、CHANGELOG、`docs/testing` 第 15 条 | 通过;Bug 历史无个人资料 | | `tsc --noEmit` | 0 错 | | `npm run lint` | 0 error / 110 warning(既有债) | | 前端全套 | 3083 条:3042 过、14 跳过、27 失败;18 条 `spawnSync docker ENOENT`,其余 9 条在基线 `d96b24c2` 同环境下逐条同样失败(`@/` 别名 eval 解析、部署/环境断言),非本轮引入 | | 静态测试数 | 3045 → 3066(+21) | | 既有断言改动 | 两处均写明原值 / 新值 / 原因,理由成立 | | 部署 | `/api/health` `deployment.gitCommit` = `8f9eb587` | **口径缺口(P2,已并入 `TASK-rectification-collect-semantics-20260910.md` T3)**:`expectedWriteFromCollectIntent` 只把 `provide_new_evidence` 与 `answer_current_focus + has_new_dated_event` 判为需要写入;按分类器提示词,用户**直接用一件带年月的事回答采集题**会被标成 `answer_current_focus + yes` 且 `has_new_dated_event=false`(该字段定义为「除了回答当前问题之外」),于是 `expectedWrite="none"`,守卫只剩「记下了」文字判定兜着。这是任务书决策 4 自身的漏写,不是执行方偏差。 环境缺口:未跑 `next build`(worktree 下 Turbopack 拒外部软链),`/` 是否仍 Static 与首屏 gzip 未核;无登录态,第 15 条真人走查未做。