# TASK · 生时校正穷尽出口修复单:门槛句重复写两遍、穷尽分支吞掉剩余问题、范围小字伪装成按钮(2026-09-06) - 基线:`origin/staging` @ `b938c76a`(含 BUG-558~560 实现 `150d7ef1` / `3a9ae736` / `62afa521`) - 分支:`codex/rectification-convergence-exit-fix-20260906` - 执行方:coding agent;验收:Claude - 涉及文件:`frontend/src/lib/rectification-agentic/v9/answer-choice.ts`、`frontend/src/app/api/rectification/agent/route.ts`、`frontend/src/components/rectification-agentic-chat.tsx`、`frontend/tests/rectification-exhaustion-exit-20260906.test.ts` - BUG 编号起点:**BUG-565**(开工时 `grep -o "^## BUG-5[0-9][0-9]" docs/BUG_HISTORY.md | tail -1` 复核) - 父任务书:`TASK-rectification-convergence-exit-20260906.md`(验收记录在其末尾) ## 0. 验收时的门(供对照) `b938c76a` 上:tsc 0 错;lint 0 error;非 DB 校正套件 905/0;校准脚本数字与进度记录一致。Python 定向套件结论见父任务书验收段(`test_active_rectification_api.py::test_long_real_conversation_reaches_vedastro_after_local_range_is_narrow` 执行方报告在基线上也失败,本单不负责,但 §5.4 要求把它记进 `BLOCKED.md`)。 ## 1. 事故实证 ### 1.1 P1 · 门槛翻译句每回合写两条相同助手消息(BUG-565) 在 A4 用例 2 的形状(方法覆盖完成、无剩余采集、引擎 `acceptance_allowed=false`)上,对 `finalizeSuccessfulTurnExit({ action: "message" })` 计数 `append_agentic_rectification_turn`:**2 次**,两条 `p_assistant_message` 完全相同(范围句 + 门槛句)。原因链: 1. `persistExhaustionCollect` 的门槛分支自己调用 `persistV9DeterministicTurn`(`requestId: randomUUID()`,不幂等)写第 1 条。 2. `finalizeSuccessfulTurnExit` 先 `persistNextInterviewIfIdle`(触发上面的写入,返回值被丢弃),再 `ensureNonTerminalTurnExit` → `inspectNonTerminalTurnExit.satisfied` 仍只认「有焦点 / 已采用 / 用户已停 / 可采用」,门槛句不算载体 → 判不满足 → 再次 `persistExhaustionCollect` → 写第 2 条。 3. `agent-run.ts` 对 `opening` / `evidence` 动作在 finalize 之前还会先调一次 `persistNextInterviewIfIdle` → 这两种动作是 **3 条**。 4. 点选路径:`persistApplied` → `persistNextInterviewAfterChoice` → 门槛分支写 1 条独立消息;`persistApplied` 又把同一句拼在「已记录你的选择」后面作为点选回合的正文写第 2 条。 5. `route.ts` 无焦点分支:`persistNextInterviewIfIdle`(写 1 条)之后又 `persistV9DeterministicTurn` 同一段文字(第 2 条)。 用户看到的就是同一段"范围已经收到…还差带月份的经历"连出两三遍。 ### 1.2 P2 · 穷尽分支排在问题持久化之前,吞掉仍可问的题 `persistNextInterviewIfIdle` 新增的分支 `!remainingCollect && !acceptedTime && !decision.canAdopt → persistExhaustionCollect` 排在 `persistNextInterviewAfterChoice` 之前,不看 `decision.nextAction`。当引擎采用门关闭(例如 `low_date_quality`)但决策层仍给出 `ask_candidate_discriminator`(有可渲染区分卡)或 `ask_holdout_validation`(holdout 题)时,这些题被直接换成门槛句。答题仍能收窄范围,而门槛句只在用户补一件事之后才有意义,两者不冲突,不该二选一。 ### 1.3 P2 · 范围小字变成按钮但文案还是状态句 `RectificationReadonlyRange` 现在渲染成 `