docs(tasks): accept BUG-558~560 (b938c76a); exhaustion-exit fix brief (BUG-565~567)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
Jesse_Chen
2026-09-06 15:16:28 +00:00
parent b938c76ac4
commit 211f9bb2ac
3 changed files with 122 additions and 1 deletions
+2 -1
View File
@@ -61,7 +61,8 @@
| `TASK-consultation-context-and-cache-20260906.md` | `PROGRESS-consultation-context-and-cache-20260906.md` | 普通对话历史只取每条前 4,000 字(报告结论被砍、无标记)、历史窗口不看模型 `context_window`、无溢出识别;改为检查点式会话摘要 + append-only 尾巴 + 按模型预算 + 一次降级重试;Anthropic 历史断点;共享方法段进系统块;后台用量页显示缓存命中率 | 已验收 | `bf8ad0d1`BUG-555/556);`test:db` 环境缺口;部署前先 Migrate Staging Database |
| `TASK-session-title-guard-fix-20260906.md` | `PROGRESS-session-title-guard-fix-20260906.md` | BUG-553 修复单:`consult/route.ts` 标题守卫比较的是 RPC 前快照,`append_consultation_question` 已改写标题,守卫恒 0 行,模型标题只靠客户端 PATCH 落库 | 待验收 | `codex/session-title-guard-fix-20260906`BUG-557 |
| `TASK-rectification-collect-vs-offer-consistency-20260905.md` | `PROGRESS-rectification-collect-vs-offer-consistency-20260905.md` | 带年份采集没问完就出采用卡 + 报告,同一轮又被搬家采集题把卡挤掉:决策层判 `adopt_representative` 而计划层仍有 dated 采集(BUG-546 只修了一半);改为剩余采集未完保持 `collect_evidence`,出牌轮才出卡写报告 | 已验收通过 `ca4e2408`2026-09-05staging 部署仍停在 `afd14948``deploy-staging``c295b853` 起连续失败,先解决 `bab07187` 的待迁移) | `codex/rectification-collect-vs-offer-consistency-20260905`BUG-550 |
| `TASK-rectification-convergence-exit-20260906.md` | `PROGRESS-rectification-convergence-exit-20260906.md` | 采集问完落到「也可以再说一件事」无出口(非收敛出牌/非终止修复兜底 `other`)、同年月多领域拆题与同域同年重复问、`relative_support` 正比例归一把引擎证据压平成 7–9 使 lead 8 不可达;改为穷尽即交付 + 口述态停止按钮、同年月合并为「发生了什么」四选卡 + 同域同年去重 + 性格题须锚点、prior 按 (分−窗口最低分) 归一并过 20 例公开 holdout 校准门 | 验收 | `codex/rectification-convergence-exit-20260906`BUG-558560;B2 未做、C1 未启用,见进度) |
| `TASK-rectification-convergence-exit-20260906.md` | `PROGRESS-rectification-convergence-exit-20260906.md` | 采集问完落到「也可以再说一件事」无出口(非收敛出牌/非终止修复兜底 `other`)、同年月多领域拆题与同域同年重复问、`relative_support` 正比例归一把引擎证据压平成 7–9 使 lead 8 不可达;改为穷尽即交付 + 口述态停止按钮、同年月合并为「发生了什么」四选卡 + 同域同年去重 + 性格题须锚点、prior 按 (分−窗口最低分) 归一并过 20 例公开 holdout 校准门 | 验收(部分完成:B2 合并卡未做、C 校准未过门未启用;1 P1 + 2 P2 见修复单) | `150d7ef1` / `3a9ae736` / `62afa521`BUG-558560,已合入 staging|
| `TASK-rectification-convergence-exit-fix-20260906.md` | `PROGRESS-rectification-convergence-exit-fix-20260906.md` | BUG-558 修复单:门槛翻译句每回合写 2~3 条相同助手消息(`persistExhaustionCollect` 自己写 turn + 非终止修复再写一次)、穷尽分支排在问题持久化前吞掉可渲染区分卡/holdout 题、范围小字变按钮但文案仍是状态句;并把校准结论(引擎原始分分钟级区分力≈随机)写进 BUG-560 与 BLOCKED | 待领取 | `codex/rectification-convergence-exit-fix-20260906`BUG-565567 |
| `TASK-api-not-configured-mislabel-20260904.md` | `PROGRESS-api-not-configured-mislabel-20260904.md` | 16 处路由把数据库瞬断(部署切换窗口)兜底翻译成 503「服务尚未配置」;改为仅配置错误用该文案,其余 `service_unavailable`,收敛为共享 helper | 已验收 | `5483649b`(BUG-542);2 条子进程测试留 CI Node 22 复核 |
| `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505509 |
@@ -210,3 +210,32 @@ out = score_candidates(req)
print([(c["time"], c["relative_support"]) for c in out["candidate_decisions"]])
print([(p["semantic_key"], p["information_gain"]) for p in out["decision_receipt"]["discriminating_event_probes"]])
```
## 验收(Claude2026-09-06,基线 `origin/staging` @ `b938c76a`
实现 `150d7ef1`A/ `3a9ae736`B/ `62afa521`(C+D)已由执行方直接合入 staging。独立复跑:
| 门 | 结果 |
| --- | --- |
| `tsc --noEmit` | 0 错 |
| `npm run lint` | 0 error / 82 warning(新增测试文件带 1 条既有类型的 warning) |
| 非 DB `tests/rectification-*.test.ts` + `agent-voice-copy-contract` | 905 pass / 0 fail |
| `scripts/rectification_prior_calibration.py` | 与进度记录同数:holdout proportional 17/20 · 中位宽 21offset 12/20 · 10softmax 0.25/0.5/1/2 = 10/11/12/16 · 6/9/14.5/21;无方案过门 |
| Python 定向(event_probes / engine_convergence / relative_support / v5_services / diagnostics_clusters / active_rectification_api | 1 失败:`test_long_real_conversation_reaches_vedastro_after_local_range_is_narrow`,在基线 `e2f4b55c` 上同样失败(已复跑),非本轮引入 |
逐条结论:
| 项 | 结论 |
| --- | --- |
| A1 日志 | 通过。字段齐全,无 summary / evidence`probe_key` 含探针年份(大运边界年,非出生资料),可接受 |
| A2 穷尽即交付 | **未通过(P1**:门槛翻译句路径每个回合写 2 条相同助手消息(scratch 计数 2;evidence 动作回合为 3;点选路径一条独立 + 一条拼在「已记录你的选择」后)。见修复单 BUG-565 |
| A2 决策层 | 通过。`keep_collecting` 仍采集;`probe_pool_exhausted` 停止原因进 `finish` |
| A3 口述停止 + `ask_about_result` | 通过(服务端 STOP 在采集焦点上不抛,`answerClass=null` 分支放行)。P2:范围小字改成按钮但文案仍是状态句,与新按钮重复,建议还原为状态句 |
| A4 形状回归 | 通过,三例齐 |
| B1 同域同年去重 | 通过,两侧生效,`asked_probe_keys` 不进指纹 |
| B2 合并卡 | 未做,按让步顺序允许,状态「部分完成」正确 |
| B3 varga 锚点 | 通过 |
| C1/C2 | 通过(默认未启用;校准数字复现一致)。**校准门本身有缺陷**:旧方案"覆盖 17/20"是因为它根本不收窄(中位宽 21 = 整个 21 分钟窗口),拿它当基线任何收窄方案都过不了。真正的发现是:把先验拉尖后,真实分钟落在领先集合里的只有 12/20(宽 10),接近随机(10/21≈48%)。引擎原始分在分钟级的区分力近乎没有,这是产品级结论,见修复单 §2 |
| C3 | 通过(未 bump 版本,旧 Case 不重算,清单已按此写) |
| D | 通过;BUG-560 标 blocked 正确 |
| 新问题 | P2`persistNextInterviewIfIdle` 新增的「无剩余采集且不可采用 → 穷尽」分支排在问题持久化之前,引擎采用门关闭但仍有可渲染区分卡 / holdout 题时会把题吞掉 |
@@ -0,0 +1,91 @@
# TASK · 生时校正穷尽出口修复单:门槛句重复写两遍、穷尽分支吞掉剩余问题、范围小字伪装成按钮(2026-09-06)
- 基线:`origin/staging` @ `b938c76a`(含 BUG-558560 实现 `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` 现在渲染成 `<button>`,点击即停止访谈,文案仍是「目前范围 …–…,还在收窄」。用户看不出它可点,点了却是"先这样"。同一屏下方已经有明确的「先这样,先看当前范围」按钮,这个入口多余且歧义。产品偏好:多余入口宁可删除。
## 2. 校准结论(不是本单任务,但要写进记录)
父任务书 C2 的门"覆盖 ≥ 旧方案 − 1 且中位宽度更窄"设计有缺陷:旧方案 proportional 在 20 例公开 holdout 上中位宽度 21 分钟 = 整个候选窗口(半径 10),它"覆盖 17/20"只是因为不收窄。把它当基线,任何真正收窄的方案都过不了。真正的发现是:先验一旦按引擎原始分拉尖(offset),真实分钟落在领先集合里的只有 12/20(中位宽 10 分钟),与随机取 10 分钟(≈48%)相当。**引擎原始分在分钟级几乎没有区分力**,这是产品级事实:现有八法计分不足以在 20 分钟窗口内可靠收窄;"收敛"只能来自用户答题和更强的引擎证据,不能靠换尺度。本单只要求把这段结论写进 BUG-560(状态保持 blocked,补"根因升级"一段)与 `BLOCKED.md`,不做算法改动。
## 3. 决策记录
1. 门槛翻译句是**回合正文**,不是独立消息。写入方只能有一个:谁负责写本回合的助手消息,谁把门槛句并入正文;`persistExhaustionCollect` 本身不得写 turn。
2. `inspectNonTerminalTurnExit` 的"满足"要认得穷尽态:`!remainingCollect && blockingMethodsCovered && !canAdopt`(同 `persistNextInterviewIfIdle` 的判定,抽成一个纯函数共用),满足即视为已交付,不再修复。
3. 穷尽分支只在 `decision.nextAction ∈ {offer_provisional_range, complete_with_range, ask_fact_collection}` 且无剩余采集时进入;`ask_candidate_discriminator` / `ask_holdout_validation` 照常持久化问题。
4. 范围小字还原为纯状态句(`<p role="status">`),删掉 `onStop` 入口;口述态停止按钮保留。
5. 不改 Skill、不改引擎、不改任何门槛数值。
## 4. 硬红线
- BUG-558 三个形状用例原样通过(改断言写三栏);BUG-503/520/544/546/547/549/550 用例不动。
- 不得重新引入 `USER_COLLECT_QUESTION.other` 兜底。
- tsc 0 错、lint 0 error、测试总数 ≥ 905(非 DB 校正套件)。
## 5. 任务分解
### 5.1 单一写入方(BUG-565
- `persistExhaustionCollect` 门槛分支:删除内部 `persistV9DeterministicTurn`,只返回 `{ hostNarration, terminalNote: true, persisted: false }`
- `finalizeSuccessfulTurnExit``persistNextInterviewIfIdle` 返回 `terminalNote` 时,由它写**一次** deterministic turn`requestId``${askedTurnId}:gate` 之类可幂等的键),并跳过 `ensureNonTerminalTurnExit``agent-run.ts` 对 opening/evidence 的那次调用返回 `terminalNote` 时同样只写一次,finalize 不再写。
- `route.ts` 无焦点分支:只保留 route 自己的那次写入。
- 点选路径:`persistApplied` 拼进正文即可,不再有独立消息。
- 验收:在 `rectification-exhaustion-exit-20260906.test.ts` 加三例——`finalizeSuccessfulTurnExit({action:"message"})``{action:"evidence"}` 经 agent-run 路径、`applyRectificationChoice` 最后一张卡——各自 `append_agentic_rectification_turn` 计数为 **1**,正文含门槛句一次。
### 5.2 穷尽态视为已交付
-`isExhaustedGateState(decision, remainingCollect, methods)` 纯函数;`inspectNonTerminalTurnExit.satisfied``persistNextInterviewIfIdle` 共用。
- 验收:A4 用例 2 之后再调 `ensureNonTerminalTurnExit``append` 计数不增加。
### 5.3 穷尽分支不吞题
- 分支加 `decision.nextAction` 白名单(§3.3)。
- 验收:新用例——同 A4 用例 2 形状但 `discriminating_event_probes` 留一条可渲染探针、receipt `acceptance_allowed=false` → 持久化的是区分卡焦点,不是门槛句;holdout 同理一例。
### 5.4 范围小字还原 + 记录
- `RectificationReadonlyRange` 回到 `<p>`;删 `onStop` prop`frontend/DESIGN.md` 对应行改回;源扫描断言(`rectification-exhaustion-exit` "collect spoken stop button and range line share CHOICE_STOP_LABEL")改为只要求口述态按钮,写三栏。
- `docs/BUG_HISTORY.md`BUG-565(重复消息)、BUG-566(吞题)、BUG-567(伪按钮);BUG-560 追加 §2 的校准结论。
- `BLOCKED.md``test_long_real_conversation_reaches_vedastro_after_local_range_is_narrow` 在基线失败一事(复现命令、失败断言、首次失败 SHA 若能二分)。
- `CHANGELOG.md``docs/tasks/PROGRESS-rectification-convergence-exit-fix-20260906.md``docs/testing/rectification-convergence-exit-20260906.md` 第 1 条补"门槛句只出现一次"。
## 6. 让步顺序
5.1 + 5.2 不可拆;5.3 必做;5.4 的 UI 还原可后置到同分支第二次提交,记录不可省。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-convergence-exit-fix-20260906 .worktrees/rectification-convergence-exit-fix-20260906 origin/staging
cd .worktrees/rectification-convergence-exit-fix-20260906
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
cd frontend && ls tests/rectification-*.test.ts tests/agent-voice-copy-contract.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"
```
收尾同一命令 fail=0、总数 ≥ 905;`tsc --noEmit``npm run lint`