Files
Jyotisha/docs/tasks/TASK-rectification-probe-replay-loss-20260908.md
T

10 KiB
Raw Blame History

TASK · 生时校正修复单:新证据重算后已答题全部失效,范围从 8 分钟弹回 29 分钟且旁白不说(2026-09-08

  • 基线:origin/staging @ 6c7a8b02(代码头 a00b069dstaging 已部署 a00b069d;事故 Case 就是这版跑出来的,Skill 10.0.15
  • 分支:codex/rectification-probe-replay-loss-20260908,基于 origin/staging
  • 执行方:coding agent;验收:Claude
  • 涉及文件:frontend/src/lib/rectification-agentic/core/build-state.tsbuildInferenceState L5480)、v9/inference-adapter.tsbuildCaseInferenceState)、v9/score-persist.tsv9/agent-run.tsv9/answer-choice.ts(证据轮范围变化旁白)、frontend/src/components/rectification-agentic-chat.tsxshowSelectionCards
  • BUG 编号起点:BUG-587586 已由 other-collect 修复单占用)
  • 优先级:P0(这是"收敛不了"的直接原因:用户答了 5 道题把范围收到 8 分钟,之后每补一件经历都被静默作废,交付时又回到 29 分钟)

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

时点 事实
事业事件写入后 引擎候选集 9 分钟(含 04:57、05:13),连问 5 张卡(D9 风格、D10 风格、事业 2023 前后、事业 2024 前后、迁居 2015 前后),旁白逐轮报"范围从 05:0005:13 收到 … 04:5004:57"8 分钟)
之后 家人拒答 → 财务事件 → 迁居事件 → 健康拒答 → 职业 → 学业事件,每件带年月的经历都触发 batch 内 compare 重算
交付时 inference_state answered_probes 5 条仍在;rounds: []9 个候选 posterior_score === prior_scorecandidate_set_id 已变(04:57→04:59、05:13→05:14);credible_range = 04:4705:1529 分钟)
交付卡 "这次给出的范围 04:4705:15,排盘用 05:00"。用户:"这里给的时间范围有点长了吧"
期间旁白 三轮证据旁白只说"记下了",从未说范围已从 8 分钟回到 29 分钟

2. 根因

2.1 BUG-587(P0):候选集一变,旧答案就没有可重放的题目定义

core/build-state.ts::buildInferenceState

  1. L5455 sameSet = previous.candidate_set_id === setId; previous = sameSet ? input.previous : null —— 只要引擎给出的 9 个分钟有任何一个不同(新证据改变分数排序就会),rounds 从零开始。这本身可接受,因为下面的循环会用答案账本重建。
  2. 重建循环 L72–79 要先找到题目定义:input.probes(本轮引擎 discriminating_event_probes + 对照包 conflictProbesFromContrast)→ input.previous.probes。但两处都故意排除已答题score-persist.tsaskedSemanticKeysForEngine 传给引擎让它不再生成(BUG-559 去重),buildCandidateContrastPacket({askedKeys}) 也过滤掉已答的分盘对照题;而新状态的 probes 就是这两份的并集,所以第一次重算后,已答题的定义不再进入状态;第二次重算时 input.previous.probes 里也没有了 → if (!probe) continue → 5 条答案全部变成账本里的死记录,posterior = prior
  3. 分盘风格题的 semantic_key 还把分钟列表编进了 keyvarga.d9.04:50|04:47/05:00|…),候选集一变 key 就对不上,连按 key 找都不可能。

题目本身的语义是按分钟定义的(某分钟的 D9 上升是什么、某分钟的大运边界在哪一年),换了证据也不变;applyProbeOutcome 已经是按候选 id(分钟字符串)查 supports / conflicts 成员,只要定义还在,7 个未变的分钟就能正确重放,新出现的分钟落为中性。丢的只是定义,不是可重放性。

2.2 BUG-588(P1):证据轮不报范围变化

点选题轮次由服务端写"范围从 X 收到 Y"answer-choice),证据轮的旁白是模型写的,服务端不补范围句。范围从 8 分钟回到 29 分钟这件事,用户在三轮里没有任何机会知道。

2.3 P3(未证实):交付/选择卡闪现

用户报告:最后一题的选择卡出现前一瞬,"时间选择的卡片"弹出又消失。showSelectionCardsrectification-agentic-chat.tsx L14011408)没有 busy 守卫;loadCaseSnapshot 内先 setCandidateResultawait 之后才 setMessages(mergeTurnQuestions),两次渲染之间 interviewQuestionBlocksAdoptOffer 看不到新问题。但按导出的最终状态 can_adopt=falsesession_outcome=discriminate_candidatescanShowRectificationSelectionCards 应为假,闪现的来源没有证实。执行方按 4.3 加守卫即可,不得声称已复现。

3. 决策记录

  1. 已答题的定义随状态一起活下去。 buildCaseInferenceStateprobes = [...本轮 probes, ...previous.probes 中 id 或 semantic_key 出现在 previous.answered_probes 里且本轮没有同 key 的]。这些"携带题"只用于重放,不参与 nextProbe 选题(加 carried: true 或按 answered 过滤,selectHighestGainProbe 已排除已答)。
  2. 重放按分钟。 applyProbeOutcome 不改;新候选集里不在旧 supports/conflicts 的分钟落为 neutral,不做邻近插值(保守、确定)。roundssameSet=false 时按答案账本顺序重建(现有循环即可),kind 照旧。
  3. 分盘风格题的 semantic_key 去掉分钟列表:改为 varga.<layer>.<sign-partition>(按各分钟所属星座分组的稳定表示,例如 varga.d9.libra|scorpio|sagittarius),分钟→星座的映射来自 window_scan.transitionscandidate_split_hash 仍按当前候选集算,只用于"同一题是否已在当前集问过"。已答账本里的旧 key 通过决策 1 的携带题仍能命中(携带题保留旧 key)。
  4. 证据轮补一句范围句(服务端写、模型不写)。 agent-run 在 evidence 轮结算前,比较本轮 compare 前后的 credible_range:变了就把 RECTIFICATION_USER_COPY.rangeChangedAfterEvidence(from, to)(对照 VOICE.md,形如"范围从 04:5004:57 变为 04:4705:15。")接到 answerText 末尾、题干之前;没变不写。与 BUG-585 的剪问句在同一位置串行(先剪、再接)。
  5. 卡片守卫showSelectionCards 追加 && !busy && caseSnapshotLoadedloadCaseSnapshotsetCandidateResult 与随后的问题合并放进同一次 startTransition/同步块。
  6. 不动采用门、确认门、MIN_SEPARATION_LEAD_relative_supportminute_step=1 指纹、四选项合同;SCORE_DELTA 不变;Skill 不 bump。

4. 任务分解

4.1 BUG-587(决策 13

  • 验收(rectification-inference-*.test.ts / rectification-stale-compare-fix-20260907.test.ts 扩):
    • (a) 状态 S1:9 分钟候选、5 条已答(2 分盘风格 + 3 大运边界),范围 8 分钟;新证据 → 候选集换掉其中 2 个分钟,且本轮 probes 不含这 5 题 → 新状态 rounds.length === 57 个未变分钟的 posterior_score 与 S1 相同增减,2 个新分钟 delta 为 0,credible_range 宽度 ≤ S1 宽度 + 新分钟数。
    • (b) 连续两次重算(模拟财务 → 迁居)后,答案仍全部重放(rounds 5),不退化为 posterior === prior
    • (c) 分盘风格题的 semantic_key 不含 \d\d:\d\d;同一分区不同候选集下 key 相同;已答旧 key 的账本能命中携带题。
    • (d) nextProbe 不会再把携带题当新题问出(selectHighestGainProbe 排除)。
    • (e) 引擎侧 asked_probe_keys 行为不变(tests/test_rectification_* 绿)。

4.2 BUG-588(决策 4

  • 验收:mock 一轮证据 turncompare 前范围 04:5004:57、后 04:4705:15 → 落库 assistant_message 末尾含范围句;范围不变 → 不含;agent-voice-copy-contract 收录新句。

4.3 P3(决策 5

  • 验收:rectification-agentic-entry 源码锁 showSelectionCards!busy;无需复现闪现。

4.4 记录

  • docs/BUG_HISTORY.mdBUG-587(关联 BUG-559 —— 其"去重"把已答题从生成侧删掉,副作用是重放丢定义;关联 BUG-581 的 keepAnswers 路径:那次修的是停止路径,这次是证据路径同一根因的另一半)、BUG-588;P3 写成 investigatingCHANGELOG.mdPROGRESS-rectification-probe-replay-loss-20260908.mddocs/testing/rectification-scenarios-20260907.md 加一条"答完点选题再补经历,范围不得变宽超过新增分钟数;证据轮旁白必须报范围变化"。

5. 让步顺序

4.1 (a)(b) 当天必做并部署;(c) 可后置为 P2(携带题已能按旧 key 命中);4.2 必做;4.3 顺手;4.4 不可省。

6. 开工前置命令

git fetch origin --prune
git worktree add -b codex/rectification-probe-replay-loss-20260908 .worktrees/rectification-probe-replay-loss-20260908 origin/staging
cd .worktrees/rectification-probe-replay-loss-20260908
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
ln -s /workspace/Jyotisha/.venv .venv
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)"
cd .. && .venv/bin/python -m pytest tests/test_rectification_v5_services.py -q

与 BUG-584/585step-answer.ts / agent-run.ts 剪问句)在 agent-run.ts 有交叠:范围句接在剪问句之后,先合 584/585 再做本单,或本单 rebase 到其上。与 BUG-586 无重叠。

验收(Claude2026-09-08origin/staging @ df182c16staging 已部署同 SHA

结果
tsc 0 错
lint 0 error / 102 warning
前端 rectification + consultation + voice + skill-registry(非 DB 1253 / 0
Python v5_services + growth contract 全绿(执行方记的 15s 壁钟用例本机未复现超时)
Docker DB 套件、浏览器走查 环境缺口,同执行方记录
结论
4.1 BUG-587 通过。真实 Case 导出:6 道题 carried: true 全部重放(rounds 6 条),分盘 key 已是星座分区,credible_range 04:5104:53(此前同类流程为 29 分钟)
4.2 BUG-588 代码路径通过(compare 轮先剪问句再接范围句);真实转录里没有范围变化的轮次可核,留待场景清单
4.3 P3 !busy && caseSnapshotLoaded 守卫已加;闪现未复现,BUG-589 investigating
4.4 记录 通过

结论:通过。收敛成功后出口被 BUG-590 挡住,另立修复单。