Files
Jyotisha/docs/tasks/PROGRESS-rectification-dead-d9-choice-fix-20260916.md
T
Jesse_ChenandClaude Fable 5.1 5113d457b7 fix(rectification): 自建分盘探针按 receipt 重建;风格题不再计分;死卡不配采集占位
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
2026-09-17 00:48:50 +00:00

103 lines
11 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.
# 进度 · 自建探针只活在内存里(修复单,2026-09-17)
基线:`origin/staging` @ `c32f81e7`。分支:`codex/rectification-dead-d9-choice-fix-20260916`
任务书:`docs/tasks/TASK-rectification-dead-d9-choice-fix-20260916.md`(§1d 已补 2026-09-17 决策记录)。
BUG915917(开工时 `docs/BUG_HISTORY.md` 最大号 914)。Skill **未 bump**
## 0. 结论
| 任务 | 结果 |
| --- | --- |
| T1 `withOwnedDistinguishProbes` 五处重建(BUG-915) | 完成,三条生产路径验收断言全绿 |
| §1c 计分口径 | 按 **(b)** 落地:风格题不再是计分判别题 |
| BUG-917 死卡与 `collect_waiting` 不得同屏 | 完成(缺口态改 `unavailable`,死卡不再画灰选项) |
| T2 BUG-916 host 前置挪到会话校验之后 + phase 登记 | 完成(phase 让步为复用 `answer.host_fallback` |
| T3 记录 | BUG-912 改 `resolved` 并补首版缺陷;BUG-915917 新增;BUG-375 复发段补一行;CHANGELOG / 真机清单 / 本进度 |
## 1. 做了什么
### T1 · 自建探针按 receipt 确定性重建(BUG-915
- `frontend/src/lib/rectification-agentic/v9/varga-distinguish-probe.ts`
-`withOwnedDistinguishProbes(state, receipt)`:按 `OWNED_DISTINGUISH_QUESTIONS`(七条分层题,`<method_id>:<ask_theme>`)× `receipt.window_scan.transitions`(缺失时退到 `state.transitions`)× `state.candidates` 重建所有自建探针,同 id / 同 semantic_key 不重复,无变化时原样返回同一对象。不依赖「当前哪个 followup 在问」。
- 重建探针的 `source` 是专用的 `owned_varga_style``decision-from-dossier.ts``contrastPacketFromLatestResult` / `rectificationFollowupCatalog` / `contrastPacketFromState` 三处像排除 `nakshatra_boundary` 一样把它排除:答题会把重建后的 state 写回 receipt,若不排除,这些探针会在下一轮变成 contrast / event 候选,等于绕开 (b)。
- 删掉 `withFollowupOwnedProbe`(只在盖戳那一刻往内存 state 注入,是本单 P0 的根因)与 `attachVargaDistinguishIdentity`(风格计分题的生产入口,按 (b) 不再需要)。保留 `buildVargaDistinguishFields` + `conflictProbeFromFollowup` 供重建与将来 (a) 使用。
- 接入点(五处,全部写成源码契约断言):
1. `server-focus.ts` `expectedAnswerSchemaFor` 盖戳;
2. `answer-choice.ts` `applyRectificationChoice` 点选答题;
3. `interview-state.ts` `choiceCardFromCaseDossier` 的 GET 投影(`persistedDistinguishCard` 用的就是这个 state);
4. `answer-choice.ts` `persistNextInterviewIfIdle``inferenceForStale`
5. `src/mastra/rectification-v9-tools.ts` `rectification-resolve-focus`(打字回答同一焦点的路径)。
- 2 与 5 共用新导出的 `inferenceForPersistedAnswer(receipt)`,测试直接调它,保证断言走的是生产代码而不是测试自己拼的 state。
- idle 的过期判定抽成导出的 `persistedDistinguishFocusStale(inference, focus)`(逻辑与原来逐字一致,只是可被测),BUG-559 的 split-hash 判定原样保留。
**为什么没有在 `previousInferenceFromReceipt` 出口统一注入**(任务书 §2 首选、§3 让步 1 允许退成逐点包装):那个出口的返回值同时喂给 `contrastPacketFromLatestResult``rectificationFollowupCatalog``eventProbes``decision-from-dossier.ts:226/384`)。自建风格探针一旦进那两条投影,就会作为 `yearless` / `contrast` 候选重新变成可问的题,等于绕开本单的 (b) 决策,并且会改动与本单无关的决策面。因此按让步 1 逐点包装,并用源码契约测试钉住五处。
### §1c 计分口径按 (b) 落地
- `method-followup.ts` `makeFollowup``varga_observation` / `precision_stage` 的分层判别题(`d9_relationship` / `d10_career` / `d4_home` / `d5_education` / `relatives` / `d2_finance` / `d30_health`,且自己没有 `semantic_key`
- 若引擎在该领域还有**带年份**事件探针(`year > 0``choice_kind``varga_style` / `event_quality`、未在 `askedProbeKeys` / `answeredProbes` 里),就绑定它:题面是「某年前后有没有…」,`semantic_key` / `expected_outcomes` / `probe_year` 全部取该探针,按该探针计分;探针没有 `semantic_key`(盖不上戳)时不绑定身份,维持基线行为(落不了库走 BUG-674 耗尽分支,不会错分)。
- 没有可用事件探针时**不出题**:不挂 `choice_frame`,由既有 `takeRenderableDistinguish``frameless_distinguish` 后计划继续往下走(引导窗口 / 跳过线重问 / 未覆盖领域 / 交付)。
- `lagna_frame``dasha_events``domain` 为 null)不在这条规则里,行为与基线一致。
- 实测对照(同一输入,同机):基线 `next_followup` = `d9_relationship` / `precision_stage` / `choice_kind=varga_style` / 题面「亲密关系里,你更接近哪一种相处方式?」;本单同输入 `next_followup = null` + `dropped_probes` 两条 `frameless_distinguish:d9_relationship`,即 09-17 真机那道题不会再出现。
### BUG-917 · 死卡不得与 `collect_waiting` 同屏
- `rectification-surface-state.ts``RectificationQuestionGapInput` 新增 `unansweredDeadChoice`,在 `delivered` 之后、`collect_waiting` 之前返回 `unavailable`(「没有拿到下一个问题」+「接着问」),占位与范围行随之同步。
- `rectification-agentic-chat.tsx`:按最新一条 settled 助手消息计算 `unansweredDeadChoiceOnMessages`kind 为 `choice` / `reverse_verify`、无 `answer_option`、status 为 `active``superseded`、且没有活卡)并传进缺口态。
- `rectification-message-entry.tsx``unansweredDeadChoice` 补上「status `superseded` 且没有作答」——这类焦点没有答案可展示,原来会画成 `disabled` 的灰色 A/B/C/D09-17 手机截图那张)。
### T2 · BUG-916
- `agent-run.ts`:年月阶段 host 前置(`shouldHostReaskYearEntry`)挪到 `dossier.case.sessionId !== sessionId` 校验之后,跨会话消息不再被写成确定性轮;计费位置不变。
- `phases` 从未登记的 `answer.host_year_entry` 改成既有 `answer.host_fallback`(任务书 §3 让步 2),公开快照的 `answer_origin: "host_fallback"` 语义也对得上。
## 2. 门禁数字
| 项 | 基线 `c32f81e7` | 本单 |
| --- | --- | --- |
| `./node_modules/.bin/tsc --noEmit` | 0 错 | **0 错** |
| `npm run lint` | 0 error / 116 warning | **0 error / 116 warning** |
| `npm test` | tests 3414 · pass 3368 · fail 31 · skipped 15 | tests **3421** · pass 3375 · fail **31** · skipped 15 |
| `npm test` 失败清单 | 31 条无 Docker 既有缺口 | **`diff` 逐条一致**(见下) |
| `next build --webpack` `/` 渲染标记 | `○ Static` | **`○ Static`** |
| 产物 JS gzip 合计(`.next/static/**/*.js` | 1,496,339 字节 | **1,496,408 字节(+0.005%** |
| `pytest tests/test_rectification_refinement_packet.py tests/test_rectification_event_probes.py tests/test_rectification_engine_memoization.py` | 63 绿 | **63 绿** |
| `run_quality_gate.py --profile quick` | 卡在末尾 `npm test`31 条无 Docker 失败) | 同:pytest 段 **798 passed / 1 skipped**,末尾 `npm test` 步骤 exit 1(同样的 31 条) |
gzip 口径:本仓没有首屏 gzip 测量脚本,Next 16 的路由表也不再输出 Size 列;取 `.next/static` 下全部 js chunk 的 gzip 字节合计,基线与本单同机同命令(`next build --webpack`)各测一次。
失败清单比对:`diff` 基线与本单的 `not ok` 名单为空输出。31 条全部是无 Docker 的既有环境缺口(数据库 / 迁移 / 部署 / 缓存 appendix / ephemeris 等),与 `BLOCKED.md` 一致。
测试总数 3414 → 3421:本单测试文件由 4 条改写为 11 条(见下)。没有弱化任何既有断言。
### 改过的既有断言
| 文件 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| `frontend/tests/rectification-dead-d9-choice-20260916.test.ts` | `GET projects the persisted distinguish copy when its probe is still live`:手工把自建探针塞进 `buildInferenceState({probes:[D24, 自建]})` 再断言有卡 | 删除该 fixture;改成 `choiceCardFromCaseDossier``{inference_state: 只含 D24, window_scan}` 的落库形状 | 任务书 §1:手塞探针的 state 不是生产形状,正是它让首版「测试全绿、生产报错」 |
| 同上 | `year-stage D9 followup does not borrow the D24 contrast probe`:答题用 `withFollowupOwnedProbe(inference, followup)` | 拆成两条:盖戳断言 + 用生产的 `inferenceForPersistedAnswer(receipt)` 答题,并先断言未重建时确实 `stale_probe` | `withFollowupOwnedProbe` 已删除;答题必须走生产读 state 的那一段 |
其余既有断言一字未改。
### 本单测试(`frontend/tests/rectification-dead-d9-choice-20260916.test.ts`11 条全绿)
1. 盖戳按 receipt 重建自建探针、不借 D24(并断言落库 state 里确实没有该探针)。
2. 生产答题路径:同一 schema 在未重建的 state 上 `stale_probe`,在 `inferenceForPersistedAnswer``applied``answered_probes` 末位 `semantic_key=d9_relationship:relationship_style`,不含 D24。
3. `choiceCardFromCaseDossier` 对该落库焦点返回卡,`question_id` 与焦点一致、四个选项。
4. `persistedDistinguishFocusStale`:未重建判 stale(这就是误伤),重建后不 stale;候选集变化后仍 staleBUG-559 保留)。
5. 五处接入点的源码契约。
6. (b) 决策:无带年份事件探针时不出风格题(题面不含「更接近哪一种」,`dropped_probes``frameless_distinguish:d9_relationship`)。
7. (b) 决策的另一半:引擎给出 2020 年感情事件探针时该线照问,`semantic_key` 为该探针、`choice_kind=existence`、题面含年份。
7b. 重建出来的探针不得变成可问的题:`rectificationFollowupCatalog``eventProbes``contrastPacket.probes` 里都不含该 key。
8. BUG-917`collect_waiting` + 死卡 → `unavailable`chat 与 message-entry 的源码契约。
9. BUG-913 `closed_by_design` 复核(原样保留)。
10. BUG-914 两极不得同时进 `shared_traits`(原样保留)。
## 3. 环境缺口
- 无 Docker:31 条失败与基线逐条一致;`run_quality_gate.py --profile quick` 因此仍停在末尾 `npm test`
- 无登录态 / 无 Chrome / 无模型凭据:浏览器级验收写成 `docs/testing/rectification-dead-d9-choice-fix-20260916.md`,未声称通过。
- staging 部署核对(`/api/health``deployment.gitCommit`)不在本单能力范围:只推分支,未推 `staging`
- 事发 Case 的 04:48–04:59 仍建立在错分答案上,不要采用(与上一单结论相同)。