diff --git a/docs/tasks/README.md b/docs/tasks/README.md index adad0a88..b8ee1ef3 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -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-05;staging 部署仍停在 `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-558~560;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-558~560,已合入 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-565~567) | | `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-505~509) | diff --git a/docs/tasks/TASK-rectification-convergence-exit-20260906.md b/docs/tasks/TASK-rectification-convergence-exit-20260906.md index ea95409d..4b479c8b 100644 --- a/docs/tasks/TASK-rectification-convergence-exit-20260906.md +++ b/docs/tasks/TASK-rectification-convergence-exit-20260906.md @@ -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"]]) ``` + +## 验收(Claude,2026-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 · 中位宽 21;offset 12/20 · 10;softmax 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 题时会把题吞掉 | diff --git a/docs/tasks/TASK-rectification-convergence-exit-fix-20260906.md b/docs/tasks/TASK-rectification-convergence-exit-fix-20260906.md new file mode 100644 index 00000000..5b89e325 --- /dev/null +++ b/docs/tasks/TASK-rectification-convergence-exit-fix-20260906.md @@ -0,0 +1,91 @@ +# 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` 现在渲染成 `