BUG-915:BUG-912 首版只在盖戳那一刻把自建探针注入内存 state,答题 / GET /
idle 三处读持久化 receipt 都找不到它 → 点选与打字回答报 stale_probe、GET 无卡、
刚落库的焦点每次 idle persist 被 superseded。新增
withOwnedDistinguishProbes(state, receipt) 按 window_scan.transitions + state
候选确定性重建(照 withNakshatraBoundaryProbe 的既有模式),接入盖戳、点选答题、
打字答题、GET 投影、idle 过期五处,并加源码契约测试钉住。重建探针的 source 为
owned_varga_style,像 nakshatra_boundary 一样从 contrast packet 与 event 探针池
排除,避免答题写回 state 后它们变成可问的题。
产品决策(任务书 §1d,选项 b):分盘风格题不再作为 distinguish_candidates 计分题。
分层判别题只在引擎给出该领域带年份事件探针时以「某年前后有没有…」出题并按该探针
计分;没有事件探针时不出题,计划直接走下一条线。删除 attachVargaDistinguishIdentity
与 withFollowupOwnedProbe,保留 buildVargaDistinguishFields / conflictProbeFromFollowup
供重建与将来选项 (a)。
BUG-917:最新助手消息带未答点选而 GET 无卡时,缺口态改走 unavailable 修复出口,
不再与「再说一件带年月的事」同屏;superseded 且未作答的焦点不再渲染成灰色选项。
BUG-916:年月阶段 host 前置挪到会话校验之后,phase 复用 answer.host_fallback。
测试改走生产路径(inferenceForPersistedAnswer / choiceCardFromCaseDossier /
persistedDistinguishFocusStale),删掉手工把自建探针塞进 state 的 fixture。
tsc 0 错;lint 0 error / 116 warning;npm test 3421 条 / 31 红,失败清单与基线
c32f81e7 逐条一致;next build --webpack 后 / 仍 ○ Static,产物 JS gzip
1,496,339 → 1,496,408(+0.005%);pytest rectification 定向 63 绿,快速门 Python
段 798 绿。Skill 未 bump。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
88 lines
11 KiB
Markdown
88 lines
11 KiB
Markdown
# 验收修复单 · D9 自建探针只活在内存里:答题报 stale_probe、GET 无卡、idle 立刻 superseded(2026-09-16)
|
||
|
||
## 0. 基线与验收对象
|
||
|
||
- 验收对象:`227a7571` + `8d7dfbf0`(任务书 `TASK-rectification-dead-d9-choice-20260916.md`,已合入并部署 staging)。
|
||
- 代码基线:`8d7dfbf0`。分支:`codex/rectification-dead-d9-choice-fix-20260916`。
|
||
- BUG 段:**BUG-915 起**(基线最大 BUG-914)。
|
||
- 门禁数字(本机 Linux,`node_modules` 软链,`next build --webpack`):`tsc` 0 错;lint 0 error / 116 warning;`npm test` 3414 条、31 红,失败清单与基线 `05cf20fb`(3405 条、31 红)**逐条相同**;Python 定向 `refinement_packet` / `event_probes` / `engine_memoization` 63 绿。门禁本身是绿的,**红在生产路径没被测试覆盖**。
|
||
|
||
## 1. 验收结论
|
||
|
||
| 项 | 结论 | 证据 |
|
||
| --- | --- | --- |
|
||
| T1a 不再借最高增益探针 | 通过 | `inference-adapter.ts:553` `const next = matched`;测试断言 `probe_id` 不再是 D24 |
|
||
| T1b 风格题自建探针 | **未通过(P0)** | 探针只在 `expectedAnswerSchemaFor` 盖戳那一刻由 `withFollowupOwnedProbe` 注入内存 state(`server-focus.ts:136`),**没有进 `decision_receipt.inference_state`**。生产的答题路径 `answer-choice.ts:591` 用 `withNakshatraBoundaryProbe(previousInferenceFromReceipt(receipt))` 读持久化 state,`matchProbeForChoice` 找不到 `probe:d9_relationship:relationship_style` → `stale_probe` → `:852` 抛 `agentic_rectification_stale_probe`。我用任务书自己的 fixture 按生产路径回放(state 不含自建探针):`applied=false reason=stale_probe`。**点选或打字回答新的 D9 题都会报错,比修前更糟**(修前至少错分入账,修后答不了) |
|
||
| T1c 从落库副本投影 | **未通过** | `persistedDistinguishCard`(`method-followup.ts:3319-3350`)先要 `liveInferenceDistinguishProbe(input.inferenceState)` 在持久化 state 里找到该探针,找不到即 null;同一回放里 GET `choice_card` 为 **null**。任务书第二条测试是手工把自建探针塞进 `buildInferenceState({probes:[D24, 自建]})` 再断言有卡——fixture 不是生产形状(AGENTS §7.4 精神) |
|
||
| T1c 过期 → superseded | **未通过(反向误伤)** | `persistNextInterviewIfIdle`(`answer-choice.ts:1607-1643`):`liveDistinguishProbe(持久化 state, schema)` 对自建探针恒为 null → `staleDistinguish=true` → 刚落库的 D9 焦点在**每次 idle persist 时被 superseded**(route.ts 四处 + agent-run 一处都会调) |
|
||
| T2 BUG-913 closed_by_design | 通过 | `yearlessPersonalityCanAsk({datedCount:5})===false`;结论与 BUG-689 出口一致 |
|
||
| T3 BUG-914 一列一句 | 通过 | Python 断言 `traits` 长度 1、两 option 不同句;前端断言两极不同时进 `shared_traits`;63 绿 |
|
||
| T4 记录 | 通过(需随本单改状态) | BUG-912 现状写 `resolved` 不成立,改 `investigating` 直到本单过门 |
|
||
|
||
**总结论:未通过。** 根因是一个:自建探针没有像月宿边界探针那样在**每个读 state 的地方**重建。
|
||
|
||
## 1b. 2026-09-17 07:58 真机复现(产品负责人手机截图)
|
||
|
||
- 定向健康题答「2024 年 8 月」入账后,正文出「亲密关系里,你更接近哪一种相处方式?」A/B/C/D。**四个选项灰显(`opacity .48`,`fieldset:disabled`)、点不了**——消息内的卡是 `embeddedCard` 且 `disabled={!liveQuestion}`(`rectification-message-entry.tsx:169-180`),GET 没有 `choice_card`(§1 T1c)就是死卡。这正是 §1 预测的现象,不是新缺陷。
|
||
- 同屏矛盾:只读范围行写「再说一件带年月的事就能继续」,输入框占位也是「再说一件带年月的事」。原因:`currentQuestion` 为 null(idle 已把焦点 superseded,§1 T1c 反向误伤)→ `interviewCollectWaiting` 为真 → `collect_waiting`;而最后一条消息上还挂着未答的死卡。`rectificationQuestionGapState` 只看 `currentQuestion`,看不到消息级的 `unansweredDeadChoice`。
|
||
- **补进 T1 验收(BUG-917)**:最后一条助手消息带未答的点选题而 GET 无卡时,缺口态不得是 `collect_waiting`;要么是修好后的活卡,要么走 `unavailable` 修复出口(「没有拿到下一个问题」+ 接着问),占位与范围行同步。把 `unansweredDeadChoice`(或等价的「最新消息未答点选」)作为 `rectificationQuestionGapState` 的输入,加断言。
|
||
|
||
## 1c. 产品决策待确认:D9/D10「你更像哪种」题的计分口径
|
||
|
||
- 实现把 D9/D10 判别题做成 `choice_kind=varga_style`:选项是「尽量避免冲突…」「投入很深…」这类分盘上升类型描述,答案按 `expected_outcomes` 全额更新后验、淘汰候选。
|
||
- 与既有口径冲突:`SKILL.md:84`「不要问两套盘哪个更像」;产品 2026-09-09 拍板「性格题降级为平局裁决(±1、不淘汰、报告参考)」;hint 原文是「按探针年份问感情这条线的**前事是否发生**」。
|
||
- **本单默认按既有口径执行(b)**:分盘风格题不得作为 `distinguish_candidates` 全额计分。D9/D10 判别题只在引擎给出该领域带年份事件探针(`discriminating_event_probes` / `prospective_probes`)时以「某年前后有没有…」出题并计分;没有事件探针时不出风格题,直接走下一条线(引导窗口 / 跳过线重问 / 未覆盖领域 / 交付)。风格描述只留在既有的平局参考题(±1)与交付卡列。产品若改口为 (a)「允许风格题全额计分」,在本单决策记录追加一行即可,执行方不得自行选边。
|
||
- 这条不改变 T1 的重建要求:事件探针来自引擎 state,本就在持久化里;自建探针的重建逻辑仍要做,供 (a) 与将来使用,但默认路径不再产生风格计分题。
|
||
|
||
## 1d. 决策记录(2026-09-17,产品负责人)
|
||
|
||
- **§1c 采用 (b)**:分盘风格题不得作为 `distinguish_candidates` 全额计分题。D9/D10 等分层判别题只在引擎给出该领域带年份事件探针时才出题(题面是「某年前后有没有…」,按该事件探针计分);没有事件探针时该题不出,计划直接走下一条线(引导窗口 / 跳过线重问 / 未覆盖领域 / 交付)。风格描述只留在既有的 ±1 平局参考题与交付卡列。
|
||
- T1 的重建要求照做:`withOwnedDistinguishProbes` 在每个读 state 的地方按 receipt 确定性重建,保证 (b) 决策之前已落库的风格焦点可答、有卡、不被 idle 误 supersede。
|
||
- 执行方据此删除了「给风格题自建探针再出题」的生产入口(`attachVargaDistinguishIdentity` / `withFollowupOwnedProbe`),保留 `buildVargaDistinguishFields` + `conflictProbeFromFollowup` 供重建与将来 (a) 使用。
|
||
|
||
## 2. 修法(照 `withNakshatraBoundaryProbe` 的既有模式)
|
||
|
||
月宿边界探针也不在引擎 state 里,它的做法是三处都从 receipt 重建:`server-focus.ts:124`(盖戳)、`answer-choice.ts:591`(答题)、`inference-adapter.ts:235`(`previousInferenceFromReceipt` 之后)。自建分盘探针照抄:
|
||
|
||
### T1 `withOwnedDistinguishProbes(state, receipt)`(BUG-915)
|
||
|
||
- 新函数从 `decision_receipt` 的 `window_scan.transitions` + `candidates` **确定性**重建所有 `varga_observation` / `precision_stage` 风格题的自建探针(对每个 `candidates_differ` 的层调 `buildVargaDistinguishFields`),追加进 `state.probes`(同 id 不重复)。不依赖当前 followup,避免「哪个 followup 在问」的时序问题。
|
||
- 接入点(缺一不可,写成源码契约断言):`server-focus.ts` 盖戳;`answer-choice.ts:591` 答题;`inference-adapter.ts` `previousInferenceFromReceipt` 出口(或紧随其后的同一包装);`interview-state.ts` `choiceCardFromCaseDossier` 传给投影的 `inferenceState`;`persistNextInterviewIfIdle` 的 `inferenceForStale`;`method-followup.ts` 计划层承接焦点用的 `liveDistinguishProbe`。
|
||
- 重算后 `answered_probes` 里的自建条目要能被引擎回写的 state 保留:确认 `autoRescoreAfterEvidenceChange` / compare 回写 `inference_state` 时 `answered_probes` 不按 `probes` 白名单过滤;若过滤,自建条目要在重建时一并保留。
|
||
- 验收(**必须走生产路径**):
|
||
- 回放:持久化 state 只含 D24 → 盖戳得到自建 `probe_id` → 用 `answer-choice.ts` 同一段代码(导出一个薄函数或直接调用 `answerRectificationChoice` 的纯部分)答 C → `applied=true`、`answered_probes` 末位 `semantic_key=d9_relationship:relationship_style`、后验只按 D9 分组变化。
|
||
- GET:同一持久化 state 下 `choiceCardFromCaseDossier` 返回卡且 `question_id` 与 `current_question` 一致。
|
||
- idle:同一状态下 `persistNextInterviewIfIdle` **不** supersede 该焦点;改账本使 `candidate_split_hash` 变化后才 supersede 并落下一问。
|
||
- 删掉手工塞探针的 fixture,改用上面的生产形状。
|
||
|
||
### T2 顺带(BUG-916,P2/P3)
|
||
|
||
- `agent-run.ts:341-360` 年月阶段 host 前置(BUG-910)排在 `dossier.case.sessionId !== sessionId` 校验之前,跨会话消息也会被写成确定性轮;把前置挪到该校验之后(计费之前不变)。
|
||
- `phases: ["answer.host_year_entry"]` 不在 `PUBLIC_RECTIFICATION_PHASES`;要么登记,要么复用 `answer.host_fallback`。tsc 不报是因为类型是 string。
|
||
|
||
### T3 记录
|
||
|
||
- BUG-912 改 `investigating` → 本单过门后 `resolved`,并写「首版只在盖戳时注入内存 state,答题/GET/idle 三处读持久化 state 找不到」。BUG-915/916 新增。BUG-375 复发段补一句。CHANGELOG、PROGRESS、真机清单更新。
|
||
|
||
## 3. 让步顺序
|
||
|
||
1. 若 `previousInferenceFromReceipt` 出口统一注入会让既有 `rectification-inference-machine` 断言变红(state.probes 数量),允许只在六个接入点各自包装,但要有源码契约测试钉住六处。
|
||
2. T2 的 phase 登记可让步为复用 `answer.host_fallback`。
|
||
3. T1 验收三条与 T3 不可让步。
|
||
|
||
## 4. 开工前置命令
|
||
|
||
```bash
|
||
git fetch origin --prune
|
||
git worktree add -b codex/rectification-dead-d9-choice-fix-20260916 .worktrees/rectification-dead-d9-choice-fix-20260916 origin/staging
|
||
cd .worktrees/rectification-dead-d9-choice-fix-20260916/frontend
|
||
./node_modules/.bin/tsx --test tests/rectification-dead-d9-choice-20260916.test.ts tests/rectification-inference-machine*.test.ts 2>&1 | tail -5
|
||
grep -o "BUG-[0-9]\+" ../docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1
|
||
```
|
||
|
||
## 5. 给产品负责人
|
||
|
||
- **staging 当前状态**:D9/D10 风格题不再错分,但**答不了**(点选会报错)。在修复单过门前,遇到风格题请先跳过(D)或补一件带年月的事。
|
||
- 事发 Case 的 04:48–04:59 仍不可采用(建立在错分上)。
|
||
- 09-17 手机上那张灰卡就是本单 P0;1c 的计分口径请产品确认或默认 (b)。
|