docs(tasks): superseded collect focus dead-end brief (BUG-644/645)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
8f9eb58738
commit
82eb3b79f2
@@ -98,6 +98,7 @@
|
||||
| `TASK-rectification-unwritten-evidence-claim-20260910.md` | `PROGRESS-rectification-unwritten-evidence-claim-20260910.md` | 证据轮模型只说『记下了』却没调 batch、没设下一问,财务一件静默丢失、流程停在原题且照常扣点(BUG-635);校正流不识别 Mastra schema 拒绝信封,会把被拒的 batch 报成 completed(BUG-636) | 待验收 | `codex/rectification-unwritten-evidence-claim-20260910` |
|
||||
| `TASK-rectification-latent-audit-20260910.md` | `PROGRESS-rectification-latent-audit-20260910.md` | 顺带审计:账本派生同年键误杀 `known_event_quality`(BUG-637,BUG-389 复发);双轨一致性按 31 分钟逐分判冲突并降置信度、与报告两套口径(BUG-638);不可分宽度按簇代表分钟少算(BUG-639);引擎与推断层两套代表分钟、回执宫位表不是卡片那一分钟(BUG-640);另 8 条观察项 | 待验收 | `codex/rectification-unwritten-evidence-claim-20260910` |
|
||||
| `TASK-rectification-collect-semantics-20260910.md` | `PROGRESS-rectification-collect-semantics-20260910.md` | 产品决策三改:Skill 去掉「财务/健康只有主动说才问」并清空 Python `VOLUNTEER_ONLY`,升 10.0.22(BUG-641);带年份线索的采集题优先、题干带线索不矛盾,推翻 BUG-539 的整句去前缀(BUG-642);删掉「没有/记不清」原字匹配,分类器分 no/unsure 两类(BUG-643) | 待执行 | `codex/rectification-collect-semantics-20260910` |
|
||||
| `TASK-rectification-superseded-focus-duplicate-20260910.md` | `PROGRESS-rectification-superseded-focus-duplicate-20260910.md` | P0:一轮两次 batch 让感情采集题被顶替成 superseded 行;题号唯一约束含已结束行、汇总只聚合 declined/skipped,计划层看不见→撞号 `duplicate_focus` 被当「已问过」→出口误走交付旁白无卡(BUG-644);交付口吻与 `can_adopt=false` 矛盾(BUG-645) | 待执行 | `codex/rectification-superseded-focus-duplicate-20260910` |
|
||||
|
||||
### 聊天主链路与首页
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# TASK · 被顶替的采集题号永久占位:最后一条线答「没有」后,感情采集题落库撞号被当成「已问过」,出口误走交付旁白却无卡(2026-09-10)
|
||||
|
||||
- 基线:`origin/staging` @ `301827ad`(代码同 `d96b24c2`,staging 已部署)
|
||||
- 分支:`codex/rectification-superseded-focus-duplicate-20260910`,基于 `origin/staging`
|
||||
- 串行:改 `server-focus.ts`、`answer-choice.ts`(`persistExhaustionCollect`)。与 `TASK-rectification-collect-semantics-20260910.md`(BUG-641~643,改 `route.ts` / `method-followup.ts` / 分类器)有 `answer-choice.ts` 交集(那份改 `applyCollectFocusDenial` 的入口状态,本单改它下游的 `persistExhaustionCollect`),**本单先做**,那份在本单分支之上叠加。与 BUG-635/636、BUG-637~640 两单无交集。
|
||||
- 执行方:coding agent;验收:Claude
|
||||
- BUG 编号起点:**BUG-644**(641~643 已由采集语义单占用;开工时核对 `docs/BUG_HISTORY.md` 最大号)
|
||||
- 优先级:BUG-644 **P0**(真实用户第二次当天断流;凡是一轮里 batch 被调两次的 Case 都会在采集收尾时复现);BUG-645 **P1**(出口旁白说「这次给出的范围」却没有卡、`can_adopt=false`,文案与能力矛盾)
|
||||
- 不改 Skill 10.0.21、不改引擎、不加迁移(TS 侧即可闭合;若执行方认为必须改 RPC,先写 `BLOCKED.md` 说明并停,等产品拍板——动表要 Docker 跑 `npm run test:db`)。
|
||||
|
||||
## 0. 给产品的即时绕行
|
||||
|
||||
这个 Case 现在没有活动焦点。两条路都能走:直接打一句带年月的感情经历(例如「某年某月开始交往」),batch 会照常写入并重算,不需要那道采集题;或者按「先这样」,`selection_allowed=true`,会出区间卡。
|
||||
|
||||
## 1. 事故实证(staging 2026-09-10 20:23–20:29 +0800,Skill 10.0.21;只写结构)
|
||||
|
||||
顺序:开场 → 学业两件(同一轮)→ 家人题「没有」→ 财务一件 → 搬家一件 → 六道带年月点选题(事业 / 感情 / 迁居 / 学业发挥质量 / 事业 / 感情)→ 健康采集题「没有」→ 助手说 **「这次给出的范围 04:48–05:07。对照了 4 件经历,事件吻合率 100%。这只是代表性候选…」** → 下面是「没有拿到下一个问题。」,顶部「目前范围 04:48–05:07,还在核对」。
|
||||
|
||||
| 项 | 值 |
|
||||
| --- | --- |
|
||||
| 最后一轮 | 走确定性路径(原字匹配「没有」):`phases=[]`、`tool_activities=[]`;正文是交付三句(`deliveryTurnNarration` 形状) |
|
||||
| `current_question` | `null`;`question_source="focus"`;`choice_card=null`;该轮 assistant 行 `question=null` |
|
||||
| `interview` | `type=ask_fact_collection`、`session_outcome=collect_evidence`、`can_adopt=false`、`can_offer_range=false`、`selection_allowed=true` |
|
||||
| `next_user_action` | `id=ask_method_followup`,`user_meaning`=「可以先说一段记得大概时间的感情变化…本题绑定 D9」——**读取接口的计划说下一题是感情采集题** |
|
||||
| 学业那一轮 | 两件证据 `createdAt` 相差 11 秒(12:23:36 / 12:23:47);回执 batch 只有一条(12:23:47 起);客户端显示「已完成 5 步」而回执只有 3 条活动;`rectification-set-focus` **failed `focus_idempotency_conflict`**;该轮最终问题是家人采集题 |
|
||||
| 家人 / 财务 / 搬家 / 健康采集题 | 全部问过;**感情采集题从头到尾没出现过**,但感情是 `DATED_COLLECT_ORDER` 最后一项,计划层一直想问 |
|
||||
|
||||
## 2. 根因
|
||||
|
||||
### 2.1 BUG-644(P0):被顶替(superseded)的焦点题号永久占位,计划层看不见它,落库时被当成「已问过」
|
||||
|
||||
逐环都已在代码里核实:
|
||||
|
||||
1. **两次 batch → 两次计划落库。** 学业那一轮模型把两件事分两次调 `record-evidence-batch`(证据 `createdAt` 差 11 秒;客户端 5 步 vs 回执 3 条)。每次 batch 都跑 `autoRescoreAfterEvidenceChange` → `persistPlanFocus`(`rectification-v9-tools.ts` L911–947)。第一次只有 1 件事,方法覆盖链第一站是感情 → 落下 `collect:relationship:collect_method_evidence`;第二次有 2 件事,`nextDatedCollectFollowup` 按 `DATED_COLLECT_ORDER` 先问家人 → 落下 `collect:family:…`。
|
||||
2. **顶替不是删除。** `set_v10_conversation_focus` RPC(`20260814020000_rectification_v10_runtime.sql` L536–543)把所有 `active` 焦点改成 `superseded` 再插新行。感情采集题于是成了一条**从未展示给用户**的 `superseded` 行。模型随后再调 `set-focus`(想写感情或家人),撞号 → 回执里那条 `focus_idempotency_conflict`。
|
||||
3. **题号唯一约束包含已结束行。** 同 RPC L523–535:同 `question_id` 已存在且不是「完全相同的 active」→ 一律 `focus_idempotency_conflict`;`unique (case_id, question_id)`(L142)不分状态。
|
||||
4. **计划层看不见 superseded。** 汇总 `declined_skipped_topics` 只聚合 `status in ('declined','skipped')`(同文件 L290–294)。于是 `domainCollectFocusAsked(askedRows, "relationship")`(`method-followup.ts` L536)为 false → `collectRetry=false`(L1932–1935)→ `persistCollectFocus`(`server-focus.ts` L359)在撞号时**不走 `:next` 重试**,直接返回 `duplicate_focus`。
|
||||
5. **`duplicate_focus` 被当成「这题已经问过并关掉了」。** `persistExhaustionCollect`(`answer-choice.ts`)注释原文:「duplicate_focus: this ask is already closed; fall through to adopt/gate.」于是跳过口语采集,进入 `ceiling.acceptanceAllowed && trainingGate.open && decision.canAdopt` 分支,用 `adoptHostNarration` 写出交付三句并 `terminalNote=true`。
|
||||
6. **读取接口不知道这些。** GET 的 `decideFromDossier` 仍说下一题是感情采集,`next_user_action=ask_method_followup`;但数据库永远拒绝这个题号,于是每次走到这里都同样断流。客户端 `current_question=null` → `questionGap` → 「没有拿到下一个问题」。
|
||||
|
||||
这也是 BUG-462(「区间出口采集撞上开场题号」)和 BUG-626(「撞同 id 焦点 duplicate_focus 静默」)同一片土壤的第三次露头:题号唯一约束与「顶替不删行」叠加,任何被顶替过的采集题都再也问不出来。
|
||||
|
||||
### 2.2 BUG-645(P1):出口旁白与能力矛盾——说了「这次给出的范围」却 `can_adopt=false`、没有卡
|
||||
|
||||
`persistExhaustionCollect` 的 adopt 分支只看内部 `decision.canAdopt`;公开 `can_adopt`(`publicCanAdopt`,`core/rectification-decision.ts` L125 注释:「Public can_adopt is true only in these outcomes. Internal decision.canAdopt may still be true during collect」)在 `session_outcome=collect_evidence` 时为 false,客户端据此不出卡。用户读到一句交付口吻的话,却没有任何可点的东西,也没有下一问。
|
||||
|
||||
## 3. 决策记录
|
||||
|
||||
1. **被顶替且从未答过的采集题必须能再问。** 不改 RPC 的前提下,在 TS 侧闭合:`persistCollectFocus` 撞号时**总是**尝试后缀题号(`:next`、`:next2`、`:next3`,上限 3 次),不再以 `collect_retry` 作为「允不允许换号」的开关;`collect_retry` 只保留「换用重问文案」的语义,且只在该领域确实 `declined / skipped / resolved` 过才为 true。
|
||||
2. **`duplicate_focus` 不再等于「已问过」。** `persistExhaustionCollect` 收到 `duplicate_focus` 时与其它落库失败同样处理:返回口语采集题(或中间态范围句),不得落到 adopt 分支;并把 `duplicate_focus` 连同题号写进 `rectification_exhaustion_collect` 日志。
|
||||
3. **交付口吻只能配交付能力。** 任何路径要输出 `deliveryTurnNarration` / `adoptHostNarration` 之前,必须 `publicCanAdopt(decision) === true` 或 `selection_allowed` 且明确走 `offer_provisional_range`;否则改用 `nonConvergingRangeNarration(variant="intermediate")` + 当前采集题。做成一个守卫函数,三处调用(`persistNextInterviewAfterChoice` 的 skip 分支、`persistExhaustionCollect` 的 adopt 分支、路由的 `isNonConvergingRangeOffer` 分支)共用。
|
||||
4. **同一轮多次 batch 不再各落一次计划焦点。** `persistPlanFocus` 在同一 `turnId` 内若已经为本轮创建过一个 `collect_method_evidence` 焦点,第二次只做 `resolve(status="superseded")`+ 新建时带 `askedTurnId`——这一条只是减少 superseded 行,**不是**本单的闭合点;若实现复杂度高可延后,写进度记录。
|
||||
5. 不改 Skill、不改引擎、不加迁移;不给 `declined_skipped_topics` 加 superseded(那会让「没问过」被当成「拒答」,是 BUG-520 的反面)。
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
- 不得把 superseded 当 declined(BUG-520:点选题答「没有」不是拒答;这里同理,没问过更不是)。
|
||||
- 不得在 `can_adopt=false` 时输出任何含「这次给出的范围」「代表性候选」的交付句(静态词表合同里加断言)。
|
||||
- 题号后缀只用于 `collect_method_evidence`;点选探针题号不得加后缀(它们带 `candidate_split_hash`,撞号即真重复,BUG-559)。
|
||||
- 既有测试总数不降;改断言写「原值 / 新值 / 原因」。
|
||||
- Bug 历史与测试不得出现真实用户的年月事件与 Case id。
|
||||
|
||||
## 5. 任务分解
|
||||
|
||||
### T1 · `persistCollectFocus` 撞号必换号(BUG-644)
|
||||
|
||||
- `server-focus.ts`:撞号 → 依次尝试 `${questionId}:next`、`:next2`、`:next3`;全部撞号才返回 `duplicate_focus`。`collect_retry` 不再参与换号判定。
|
||||
- 验收(`rectification-spoken-collect.test.ts` / 新 `rectification-superseded-focus.test.ts`,用假 RPC):基础题号撞号、`:next` 成功 → `status=created`、`questionId` 带后缀、`prompt` 为原文案(非重问文案);`collect_retry=true` 时文案换重问模板;四个号全撞 → `duplicate_focus`。
|
||||
|
||||
### T2 · `duplicate_focus` 不落 adopt(BUG-644)
|
||||
|
||||
- `answer-choice.ts` `persistExhaustionCollect`:删除 `duplicate_focus` 的特判,与其它失败同路返回 `spoken ?? range`;日志加 `persist_status` 与 `question_id`。
|
||||
- 验收:构造「计划层 next_followup=感情采集、落库返回 duplicate_focus、acceptance_allowed=true、training gate open、内部 canAdopt=true」→ 返回 `hostNarration` 为采集口语、`terminalNote` 不为 true、无交付句。
|
||||
|
||||
### T3 · 交付口吻守卫(BUG-645)
|
||||
|
||||
- 新增 `deliveryNarrationAllowed(decision, nextAction)`,三处调用;不允许时用中间态范围句 + 采集题。
|
||||
- 验收:`can_adopt=false` + `session_outcome=collect_evidence` 的决策,三处都不出交付句;`agent-voice-copy-contract` 加「`can_adopt=false` 时不得出现『这次给出的范围』」的源码/词表断言;`can_adopt=true` 路径回归不变(`rectification-delivery-report-facts.test.ts`)。
|
||||
|
||||
### T4 · 同轮多次 batch 的计划焦点(决策 4,可延后)
|
||||
|
||||
- `rectification-v9-tools.ts` `persistPlanFocus`:同一 `turnId` 第二次落采集焦点时,先 `resolve` 本轮已创建的那条再新建,避免堆 superseded 行。
|
||||
- 验收:假两次 batch(各 1 件)→ 最终只有一条 active 采集焦点,且上一条不以 `superseded` 留下;点选探针焦点不受影响。
|
||||
|
||||
### T5 · 记录
|
||||
|
||||
- `docs/BUG_HISTORY.md` BUG-644、BUG-645(关联 BUG-462、BUG-520、BUG-525、BUG-559、BUG-626、BUG-627、BUG-633)。
|
||||
- `CHANGELOG.md`:「采集题被系统顶替后仍会再问;没有可采用结果时助手不再用交付口吻收尾」。
|
||||
- `docs/tasks/PROGRESS-rectification-superseded-focus-duplicate-20260910.md`。
|
||||
- `docs/testing/`:真人清单——新 Case 一轮里分两句说两件学业经历(诱发两次 batch),走完所有采集题后回「没有」,期望出现感情采集题而不是「没有拿到下一个问题」。
|
||||
|
||||
## 6. 让步顺序
|
||||
|
||||
T4 可延后(写进度记录);T1 + T2 是最低交付;T3 与 T2 同批。
|
||||
|
||||
## 7. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-superseded-focus-duplicate-20260910 .worktrees/rectification-superseded-focus-duplicate-20260910 origin/staging
|
||||
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | tail -1
|
||||
cd frontend && npm test -- tests/rectification-spoken-collect.test.ts tests/rectification-exhaustion-exit-20260906.test.ts tests/rectification-delivery-report-facts.test.ts tests/agent-voice-copy-contract.test.ts
|
||||
```
|
||||
|
||||
## 8. 验收口径
|
||||
|
||||
- `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build` 后 `/` 仍 Static,首屏 gzip ±2%。
|
||||
- 推 staging 后 `/api/health` 的 `deployment.gitCommit` 等于最新含代码改动的提交。
|
||||
- 真人清单见 T5;做不了的写成环境缺口。
|
||||
Reference in New Issue
Block a user