Files
Jyotisha/docs/tasks/TASK-rectification-covered-domain-recollect-20260908.md
T

102 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 生时校正修复单:范围已收到 2 分钟、决策已是采用,却把答过的财务题再问一遍(2026-09-08)
- 基线:`origin/staging` @ `df182c16`(BUG-584~589 已合入并部署;事故 Case 就是这版跑出来的,Skill 10.0.15)
- 分支:`codex/rectification-covered-domain-recollect-20260908`,基于 `origin/staging`
- 执行方:coding agent;验收:Claude
- 涉及文件:`frontend/src/lib/rectification-agentic/v9/method-followup.ts`(L2224 起的 yearless→采集分支、`holdoutFollowupFor`)、`v9/server-focus.ts`(`persistCollectFocus` 的 `:next` 重试)、`v9/answer-choice.ts`(`shouldSkipFollowupPersist`)
- BUG 编号起点:**BUG-590**(`docs/BUG_HISTORY.md` 当前最大 BUG-589)
- 优先级:**P1**(收敛已经成功,出口被一道重复题堵死;用户原话"怎么又问相同的问题 而且时间已经收敛到 2 分钟的范围了")
## 1. 事故实证(2026-09-08 staging `df182c16`,Case 导出;只写结构)
| 项 | 事实 |
| --- | --- |
| 推断状态 | 6 道题全部重放(`rounds` 6 条、`carried: true`、分盘 key 已是星座分区),`credible_range = 04:51–04:53`,`stop_reason = probe_pool_exhausted` |
| 决策 | `type = ready_to_adopt`、`session_outcome = adopt_representative`、`can_adopt = true`、`step_state.stage = deliver` |
| 当前问题 | `collect:finance:collect_method_evidence:next`,题干 = `USER_COLLECT_QUESTION.finance` 逐字。财务领域账本里已有一条带年月的确认事件(`usage = holdout`),该领域焦点早已 `resolved` |
| 该轮 receipt | `rectification-set-focus` **failed** `focus_idempotency_conflict`;随后闲置路径把同题以 `:next` 后缀落库 |
| 界面 | 没有区间交付卡(采集焦点挂着即 `interviewQuestionBlocksAdoptOffer`),输入框上方是财务题 |
BUG-587 的重放已经把范围收到 2 分钟,出口本该是区间交付卡。
## 2. 根因
### 2.1 BUG-590(P1):职业覆盖后的"无年份分盘题→采集"分支不看领域是否已覆盖
`method-followup.ts` L2224:`!next && dashaCovered && coverageComplete && occupationCovered` 时取 `yearlessDiscriminators[0]`(本例是 D11 财富分盘的对照题,D24 学业题因账本已提到该分盘被降权、D12 家人题被拒答),把它的领域直接变成一道 **dated collect**(`intent = collect_method_evidence`、`source = method_coverage`、无 `choice_frame`)。分支只检查 `!declined.has(domain)`,不检查该领域是否已有带年月事件、是否已问过采集题。财务已经答过,于是再问一遍。
这个分支的本意是"分盘对照题没有年份可锚,先向该领域要一件带年月的事";领域已经有带年月事件时,再要一件也锚不上(引擎把它标成 `yearless_ungrounded_contrast` 不是因为缺事件),应当跳过该领域、继续找下一个,没有就交付。
链条第二环:`answer-choice.ts::shouldSkipFollowupPersist` 对 `canAdopt = true` 的决策,只要 `isRemainingEvidenceCollect(followup)`(领域在 `DATED_COLLECT_ORDER` 里)就不跳过 → 采用被这道题挡住。第三环:工具层 set-focus 用同一 `next_followup` 落库,`questionId` 与已 resolved 的财务焦点相同 → `focus_idempotency_conflict`;闲置路径再来一次。
### 2.2 BUG-591(P2):采集焦点撞 id 时无条件加 `:next` 再插一次
`server-focus.ts::persistCollectFocus` L359:`insertFocus(questionId)` 报 `focus_idempotency_conflict` 就改用 `${questionId}:next` 再插。这是 `75fc456d`(BUG-462 附带)为"穷尽采集撞开场题号"加的;BUG-586 删掉 other 穷尽采集后,它只剩副作用——同一领域的采集题只要被计划层重复派出,就一定能以 `:next` 落地。撞 id 本身就是"这题问过了"的信号,非 `collect_retry` 的采集题应当在这里止步。
### 2.3 P2:holdout 的 `occupied` 不认 `health` / `health_pressure` 同义
`holdoutFollowupFor` 用账本领域算 `occupied`,账本里健康事件的 `domain = "health"`,而 `oos_blind_prompts` 用 `health_pressure`;用户答了健康事件后,holdout 仍会把健康当"没用过的线"再问一遍。`datedCollectDomainBlocked` 已经用 `hasConfirmedHealth` 处理了这对同义词,`holdoutFollowupFor` 没有。本例没触发(财务题先挡住),但下一次会。
### 2.4 记录:转录里选择题题干看似出现两次
选择题的题干在问题块 `<p>` 与卡片 `<legend class="sr-only">` 各一份,后者只给读屏,复制文本时会一起带出。视觉上只有一份,不是缺陷,不立单。
## 3. 决策记录
1. **L2224 分支加覆盖守卫。** 遍历 `yearlessDiscriminators`(不只取 `[0]`),跳过:`declined` 领域、`hasConfirmedDomain`(含 `health`/`health_pressure` 同义)为真的领域、账本或已关闭焦点里已出现过 `collect:<domain>:*` 的领域。全部跳过则 `next` 保持空。
2. **采用优先。** `shouldSkipFollowupPersist`:`canAdopt && nextAction === "adopt_representative" && stopReason === "probe_pool_exhausted"` 时,`isRemainingEvidenceCollect` 只有在该领域**没有**确认事件且未拒答时才算"剩余采集";否则跳过持久化、走交付(`terminalNote`)。
3. **撞 id 不再 `:next`。** `persistCollectFocus` 遇 `focus_idempotency_conflict`:`followup.collect_retry === true` 才用 `:next`,否则返回 `duplicate_focus`、不插入。`persistFocusAfterChoice` 把 `duplicate_focus` 当 `skipped` 处理,继续走交付分支。
4. **holdout `occupied` 用同义表**:`health` ↔ `health_pressure` 归并(复用 `hasConfirmedHealth` 的口径)。
5. 不动采用门、确认门、`MIN_SEPARATION_LEAD`、`_relative_support`、`minute_step=1`、`SCORE_DELTA`、四选项合同;Skill 不 bump。
## 4. 任务分解
### 4.1 BUG-590(决策 1、2)
- 验收(形状用例,新建或扩 `rectification-occupation-coverage-exit.test.ts`):
- (a) 账本:学业 2 件、感情 2 件、财务 1 件(`usage = holdout`)、事业 2 件、迁居 1 件、健康 1 件、职业 1 件;家人拒答;6 道题已答;yearless 目录里 D11/D2(财务)、D24/D5(学业)、D4(迁居)、D7/D12(家人)都在 → `buildMethodFollowupPlan().next_followup === null`。
- (b) 同 (a) 但迁居账本为空、未拒答 → `next_followup.domain === "relocation"`(守卫只跳过已覆盖领域,不是删分支)。
- (c) 同 (a) 的 dossier 进 `persistNextInterviewIfIdle`(decision `adopt_representative`、`probe_pool_exhausted`)→ 返回 `terminalNote: true` 的采用旁白,0 次 `setV10ConversationFocus`。
- (d) agent-run 集成:该轮结束后 `current_question === null`、客户端 `canOfferCards` 为真。
### 4.2 BUG-591(决策 3)
- 验收(`rectification-server-focus.test.ts` 扩):非 retry 采集题撞 id → `status: "duplicate_focus"`,RPC 只调一次;`collect_retry: true` 撞 id → 仍以 `:next` 落地(原值 / 新值 / 原因三栏:原"任何撞 id 都 `:next`"→ 新"仅 retry"→ 决策 3)。
### 4.3 P2(决策 4)
- 验收:账本 `domain = "health"` 已确认、oos 提示 `health_pressure` → `holdoutFollowupFor` 不返回健康题。
### 4.4 记录
- `docs/BUG_HISTORY.md` BUG-590(关联 BUG-586:同一"职业覆盖后多问一题"的现象,不同分支;关联 BUG-587:收敛成功后才暴露)、BUG-591(关联 BUG-462 / `75fc456d`);P2 记进 BUG-590 的防复发;`CHANGELOG.md`;`PROGRESS-rectification-covered-domain-recollect-20260908.md`;`docs/testing/rectification-scenarios-20260907.md` 加"七领域 + 职业问完后,下一步必须是区间交付卡,不得再出任何已答领域的采集题"。
## 5. 让步顺序
4.1 (a)(c) 当天必做并部署;(b)(d) 必做;4.2、4.3 同一提交;4.4 不可省。
## 6. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-covered-domain-recollect-20260908 .worktrees/rectification-covered-domain-recollect-20260908 origin/staging
cd .worktrees/rectification-covered-domain-recollect-20260908
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
cd frontend && ./node_modules/.bin/tsc --noEmit; npm run lint 2>&1 | tail -1
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)"
```
## 验收(Claude,2026-09-08,`origin/staging` @ `7ca6ade2`,staging 已部署同 SHA)
| 门 | 结果 |
| --- | --- |
| tsc | 0 错 |
| lint | 0 error / 102 warning |
| 前端 rectification + consultation + voice + skill-registry(非 DB) | 1259 / 0 |
| Python v5_services + growth contract | 全绿 |
| 项 | 结论 |
| --- | --- |
| 4.1 BUG-590 | 通过。yearless→采集分支遍历并跳过已覆盖/拒答/已问领域;`shouldSkipFollowupPersist` 在题库耗尽且可采用时不再把已覆盖领域当剩余采集;偏离 1(同时认 `ready_to_adopt`)合理 |
| 4.2 BUG-591 | 通过。非 retry 撞 id 返回 `duplicate_focus`,`collect_retry` 仍 `:next` |
| 4.3 P2 | 通过。holdout `occupied` 归并 health/health_pressure |
| 4.4 记录 | 通过 |
结论:通过。真实环境"职业答完直接出卡"待下次实测;本轮转录尚未走到该步,先暴露了 BUG-592(同年点选题问两遍),另立修复单。