From fec316b3e3e37950ecb80e09d68938d9d931f945 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Tue, 8 Sep 2026 02:09:30 +0000 Subject: [PATCH] docs(tasks): probe replay loss after new evidence fix brief (BUG-587/588) Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P --- docs/tasks/README.md | 1 + ...ectification-probe-replay-loss-20260908.md | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 docs/tasks/TASK-rectification-probe-replay-loss-20260908.md diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 489bbd48..ef82b764 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -74,6 +74,7 @@ | `TASK-rectification-range-delivery-fix-20260907.md` | `PROGRESS-rectification-range-delivery-fix-20260907.md` | 区间卡修复单:`candidateResult` 判空提到最前(tsc 阻断);预测窗口接到采用后首轮或删句;「再补一件经历」隐藏卡后给采集提示;进度记录须贴 tsc 原文 | 待验收 | `codex/rectification-range-delivery-fix-20260907`(BUG-583) | | `TASK-rectification-duplicate-narration-20260907.md` | `PROGRESS-rectification-duplicate-narration-20260907.md` | 每轮采集旁白说两遍 + 题干在正文与问题块各一次:`step-answer` 的 set-focus 例外(BUG-533)放行了 set-focus 前后两个 step 的正文并拼接;`detachCollectSpokenAssistantText` 只剥逐字后缀,模型写进正文的问句没有代码守卫(BUG-488 防线只在提示词) | 待验收 | `codex/rectification-duplicate-narration-20260907`(BUG-584~585) | | `TASK-rectification-other-collect-fallback-20260908.md` | `PROGRESS-rectification-other-collect-fallback-20260908.md` | 七领域问完落到「也可以再说一件」、职业题从未出现、不自动出卡:`USER_COLLECT_QUESTION.other` 仍有三条活路(职业焦点 `target_domain` 压成 other 后被 active-focus 承接 followup 重建;set-focus 两次无效兜底查表;无领域采集归 other),`collect:other:*` 焦点挂着让 `persistNextInterviewIfIdle` 的出卡路径永远不跑 | 待验收(2.2 触发链仍 investigating) | `codex/rectification-other-collect-fallback-20260908`(BUG-586) | +| `TASK-rectification-probe-replay-loss-20260908.md` | `PROGRESS-rectification-probe-replay-loss-20260908.md` | **P0** 收敛不了的直接原因:答完 5 道题范围收到 8 分钟,之后每补一件经历触发的重算换了候选集,`buildInferenceState` 找不到已答题定义(引擎与对照包按 asked keys 排除、状态 probes 不携带、分盘 key 内嵌分钟列表)→ `rounds:[]`、posterior=prior,交付回到 29 分钟;证据轮旁白不报范围变化;交付卡闪现待证实 | 待执行 | `codex/rectification-probe-replay-loss-20260908`(BUG-587~588) | | `TASK-api-not-configured-mislabel-20260904.md` | `PROGRESS-api-not-configured-mislabel-20260904.md` | 16 处路由把数据库瞬断(部署切换窗口)兜底翻译成 503「服务尚未配置」;改为仅配置错误用该文案,其余 `service_unavailable`,收敛为共享 helper | 已验收 | `5483649b`(BUG-542);2 条子进程测试留 CI Node 22 复核 | | `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505~509) | diff --git a/docs/tasks/TASK-rectification-probe-replay-loss-20260908.md b/docs/tasks/TASK-rectification-probe-replay-loss-20260908.md new file mode 100644 index 00000000..69c7cded --- /dev/null +++ b/docs/tasks/TASK-rectification-probe-replay-loss-20260908.md @@ -0,0 +1,85 @@ +# 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` L54–80)、`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 staging,Case 导出;只写结构) + +| 时点 | 事实 | +| --- | --- | +| 事业事件写入后 | 引擎候选集 9 分钟(含 04:57、05:13),连问 5 张卡(D9 风格、D10 风格、事业 2023 前后、事业 2024 前后、迁居 2015 前后),旁白逐轮报"范围从 05:00–05:13 收到 … 04:50–04: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:47–05:15`(29 分钟) | +| 交付卡 | "这次给出的范围 04:47–05:15,排盘用 05:00"。用户:"这里给的时间范围有点长了吧" | +| 期间旁白 | 三轮证据旁白只说"记下了",从未说范围已从 8 分钟回到 29 分钟 | + +## 2. 根因 + +### 2.1 BUG-587(P0):候选集一变,旧答案就没有可重放的题目定义 + +`core/build-state.ts::buildInferenceState`: + +1. L54–55 `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-588(P1):证据轮不报范围变化 + +点选题轮次由服务端写"范围从 X 收到 Y"(`answer-choice`),证据轮的旁白是模型写的,服务端不补范围句。范围从 8 分钟回到 29 分钟这件事,用户在三轮里没有任何机会知道。 + +### 2.3 P3(未证实):交付/选择卡闪现 + +用户报告:最后一题的选择卡出现前一瞬,"时间选择的卡片"弹出又消失。`showSelectionCards`(`rectification-agentic-chat.tsx` L1401–1408)没有 `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..`(按各分钟所属星座分组的稳定表示,例如 `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:50–04:57 变为 04:47–05: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(决策 1–3) +- 验收(`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 一轮证据 turn,compare 前范围 04:50–04:57、后 04:47–05: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 无重叠。