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

9.2 KiB
Raw Blame History

TASK · 生时校正修复单:范围已收到 2 分钟、决策已是采用,却把答过的财务题再问一遍(2026-09-08)

  • 基线:origin/staging @ df182c16BUG-584589 已合入并部署;事故 Case 就是这版跑出来的,Skill 10.0.15
  • 分支:codex/rectification-covered-domain-recollect-20260908,基于 origin/staging
  • 执行方:coding agent;验收:Claude
  • 涉及文件:frontend/src/lib/rectification-agentic/v9/method-followup.tsL2224 起的 yearless→采集分支、holdoutFollowupFor)、v9/server-focus.tspersistCollectFocus:next 重试)、v9/answer-choice.tsshouldSkipFollowupPersist
  • BUG 编号起点:BUG-590docs/BUG_HISTORY.md 当前最大 BUG-589
  • 优先级:P1(收敛已经成功,出口被一道重复题堵死;用户原话"怎么又问相同的问题 而且时间已经收敛到 2 分钟的范围了")

1. 事故实证(2026-09-08 staging df182c16Case 导出;只写结构)

事实
推断状态 6 道题全部重放(rounds 6 条、carried: true、分盘 key 已是星座分区),credible_range = 04:5104:53stop_reason = probe_pool_exhausted
决策 type = ready_to_adoptsession_outcome = adopt_representativecan_adopt = truestep_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-590P1):职业覆盖后的"无年份分盘题→采集"分支不看领域是否已覆盖

method-followup.ts L2224!next && dashaCovered && coverageComplete && occupationCovered 时取 yearlessDiscriminators[0](本例是 D11 财富分盘的对照题,D24 学业题因账本已提到该分盘被降权、D12 家人题被拒答),把它的领域直接变成一道 dated collectintent = collect_method_evidencesource = method_coverage、无 choice_frame)。分支只检查 !declined.has(domain),不检查该领域是否已有带年月事件、是否已问过采集题。财务已经答过,于是再问一遍。

这个分支的本意是"分盘对照题没有年份可锚,先向该领域要一件带年月的事";领域已经有带年月事件时,再要一件也锚不上(引擎把它标成 yearless_ungrounded_contrast 不是因为缺事件),应当跳过该领域、继续找下一个,没有就交付。

链条第二环:answer-choice.ts::shouldSkipFollowupPersistcanAdopt = true 的决策,只要 isRemainingEvidenceCollect(followup)(领域在 DATED_COLLECT_ORDER 里)就不跳过 → 采用被这道题挡住。第三环:工具层 set-focus 用同一 next_followup 落库,questionId 与已 resolved 的财务焦点相同 → focus_idempotency_conflict;闲置路径再来一次。

2.2 BUG-591P2):采集焦点撞 id 时无条件加 :next 再插一次

server-focus.ts::persistCollectFocus L359insertFocus(questionId)focus_idempotency_conflict 就改用 ${questionId}:next 再插。这是 75fc456dBUG-462 附带)为"穷尽采集撞开场题号"加的;BUG-586 删掉 other 穷尽采集后,它只剩副作用——同一领域的采集题只要被计划层重复派出,就一定能以 :next 落地。撞 id 本身就是"这题问过了"的信号,非 collect_retry 的采集题应当在这里止步。

2.3 P2holdout 的 occupied 不认 health / health_pressure 同义

holdoutFollowupFor 用账本领域算 occupied,账本里健康事件的 domain = "health",而 oos_blind_promptshealth_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. 采用优先。 shouldSkipFollowupPersistcanAdopt && nextAction === "adopt_representative" && stopReason === "probe_pool_exhausted" 时,isRemainingEvidenceCollect 只有在该领域没有确认事件且未拒答时才算"剩余采集";否则跳过持久化、走交付(terminalNote)。
  3. 撞 id 不再 :next persistCollectFocusfocus_idempotency_conflictfollowup.collect_retry === true 才用 :next,否则返回 duplicate_focus、不插入。persistFocusAfterChoiceduplicate_focusskipped 处理,继续走交付分支。
  4. holdout occupied 用同义表healthhealth_pressure 归并(复用 hasConfirmedHealth 的口径)。
  5. 不动采用门、确认门、MIN_SEPARATION_LEAD_relative_supportminute_step=1SCORE_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 进 persistNextInterviewIfIdledecision adopt_representativeprobe_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_pressureholdoutFollowupFor 不返回健康题。

4.4 记录

  • docs/BUG_HISTORY.md BUG-590(关联 BUG-586:同一"职业覆盖后多问一题"的现象,不同分支;关联 BUG-587:收敛成功后才暴露)、BUG-591(关联 BUG-462 / 75fc456d);P2 记进 BUG-590 的防复发;CHANGELOG.mdPROGRESS-rectification-covered-domain-recollect-20260908.mddocs/testing/rectification-scenarios-20260907.md 加"七领域 + 职业问完后,下一步必须是区间交付卡,不得再出任何已答领域的采集题"。

5. 让步顺序

4.1 (a)(c) 当天必做并部署;(b)(d) 必做;4.2、4.3 同一提交;4.4 不可省。

6. 开工前置命令

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)"

验收(Claude2026-09-08origin/staging @ 7ca6ade2staging 已部署同 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_focuscollect_retry:next
4.3 P2 通过。holdout occupied 归并 health/health_pressure
4.4 记录 通过

结论:通过。真实环境"职业答完直接出卡"待下次实测;本轮转录尚未走到该步,先暴露了 BUG-592(同年点选题问两遍),另立修复单。