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

105 lines
10 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 · 生时校正修复单:新证据重算后已答题全部失效,范围从 8 分钟弹回 29 分钟且旁白不说(2026-09-08
- 基线:`origin/staging` @ `6c7a8b02`(代码头 `a00b069d`staging 已部署 `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.ts``buildInferenceState` L5480)、`v9/inference-adapter.ts``buildCaseInferenceState`)、`v9/score-persist.ts``v9/agent-run.ts``v9/answer-choice.ts`(证据轮范围变化旁白)、`frontend/src/components/rectification-agentic-chat.tsx``showSelectionCards`
- BUG 编号起点:**BUG-587**586 已由 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_score``candidate_set_id` 已变(04:57→04:59、05:13→05:14);`credible_range = 04:4705:15`29 分钟) |
| 交付卡 | "这次给出的范围 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.ts``askedSemanticKeysForEngine` 传给引擎让它不再生成(BUG-559 去重),`buildCandidateContrastPacket({askedKeys})` 也过滤掉已答的分盘对照题;而新状态的 `probes` 就是这两份的并集,所以**第一次重算后,已答题的定义不再进入状态**;第二次重算时 `input.previous.probes` 里也没有了 → `if (!probe) continue` → 5 条答案全部变成账本里的死记录,`posterior = prior`
3. 分盘风格题的 `semantic_key` 还把分钟列表编进了 key`varga.d9.04:50|04:47/05:00|…`),候选集一变 key 就对不上,连按 key 找都不可能。
题目本身的语义是**按分钟**定义的(某分钟的 D9 上升是什么、某分钟的大运边界在哪一年),换了证据也不变;`applyProbeOutcome` 已经是按候选 id(分钟字符串)查 `supports / conflicts` 成员,只要定义还在,7 个未变的分钟就能正确重放,新出现的分钟落为中性。丢的只是定义,不是可重放性。
### 2.2 BUG-588P1):证据轮不报范围变化
点选题轮次由服务端写"范围从 X 收到 Y"`answer-choice`),证据轮的旁白是模型写的,服务端不补范围句。范围从 8 分钟回到 29 分钟这件事,用户在三轮里没有任何机会知道。
### 2.3 P3(未证实):交付/选择卡闪现
用户报告:最后一题的选择卡出现前一瞬,"时间选择的卡片"弹出又消失。`showSelectionCards``rectification-agentic-chat.tsx` L14011408)没有 `busy` 守卫;`loadCaseSnapshot` 内先 `setCandidateResult``await` 之后才 `setMessages(mergeTurnQuestions)`,两次渲染之间 `interviewQuestionBlocksAdoptOffer` 看不到新问题。但按导出的最终状态 `can_adopt=false``session_outcome=discriminate_candidates``canShowRectificationSelectionCards` 应为假,闪现的来源没有证实。执行方按 4.3 加守卫即可,不得声称已复现。
## 3. 决策记录
1. **已答题的定义随状态一起活下去。** `buildCaseInferenceState``probes = [...本轮 probes, ...previous.probes 中 id 或 semantic_key 出现在 previous.answered_probes 里且本轮没有同 key 的]`。这些"携带题"只用于重放,不参与 `nextProbe` 选题(加 `carried: true` 或按 answered 过滤,`selectHighestGainProbe` 已排除已答)。
2. **重放按分钟。** `applyProbeOutcome` 不改;新候选集里不在旧 `supports/conflicts` 的分钟落为 `neutral`,不做邻近插值(保守、确定)。`rounds``sameSet=false` 时按答案账本顺序重建(现有循环即可),`kind` 照旧。
3. **分盘风格题的 `semantic_key` 去掉分钟列表**:改为 `varga.<layer>.<sign-partition>`(按各分钟所属星座分组的稳定表示,例如 `varga.d9.libra|scorpio|sagittarius`),分钟→星座的映射来自 `window_scan.transitions``candidate_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 && caseSnapshotLoaded``loadCaseSnapshot``setCandidateResult` 与随后的问题合并放进同一次 `startTransition`/同步块。
6. 不动采用门、确认门、`MIN_SEPARATION_LEAD``_relative_support``minute_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 === 5`7 个未变分钟的 `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.md`BUG-587(关联 BUG-559 —— 其"去重"把已答题从生成侧删掉,副作用是重放丢定义;关联 BUG-581 的 `keepAnswers` 路径:那次修的是停止路径,这次是证据路径同一根因的另一半)、BUG-588;P3 写成 `investigating``CHANGELOG.md``PROGRESS-rectification-probe-replay-loss-20260908.md``docs/testing/rectification-scenarios-20260907.md` 加一条"答完点选题再补经历,范围不得变宽超过新增分钟数;证据轮旁白必须报范围变化"。
## 5. 让步顺序
4.1 (a)(b) 当天必做并部署;(c) 可后置为 P2(携带题已能按旧 key 命中);4.2 必做;4.3 顺手;4.4 不可省。
## 6. 开工前置命令
```bash
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/585`step-answer.ts` / `agent-run.ts` 剪问句)在 `agent-run.ts` 有交叠:范围句接在剪问句之后,**先合 584/585 再做本单**,或本单 rebase 到其上。与 BUG-586 无重叠。
## 验收(Claude2026-09-08`origin/staging` @ `df182c16`staging 已部署同 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 挡住,另立修复单。