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
103 lines
11 KiB
Markdown
103 lines
11 KiB
Markdown
# 进度 · 自建探针只活在内存里(修复单,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 决策记录)。
|
||
BUG:915~917(开工时 `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-915~917 新增;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/D(09-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;候选集变化后仍 stale(BUG-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 仍建立在错分答案上,不要采用(与上一单结论相同)。
|