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
11 KiB
进度 · 自建探针只活在内存里(修复单,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) 使用。
- 新
- 接入点(五处,全部写成源码契约断言):
server-focus.tsexpectedAnswerSchemaFor盖戳;answer-choice.tsapplyRectificationChoice点选答题;interview-state.tschoiceCardFromCaseDossier的 GET 投影(persistedDistinguishCard用的就是这个 state);answer-choice.tspersistNextInterviewIfIdle的inferenceForStale;src/mastra/rectification-v9-tools.tsrectification-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.tsmakeFollowup: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补上「statussuperseded且没有作答」——这类焦点没有答案可展示,原来会画成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 条全绿)
- 盖戳按 receipt 重建自建探针、不借 D24(并断言落库 state 里确实没有该探针)。
- 生产答题路径:同一 schema 在未重建的 state 上
stale_probe,在inferenceForPersistedAnswer上applied,answered_probes末位semantic_key=d9_relationship:relationship_style,不含 D24。 choiceCardFromCaseDossier对该落库焦点返回卡,question_id与焦点一致、四个选项。persistedDistinguishFocusStale:未重建判 stale(这就是误伤),重建后不 stale;候选集变化后仍 stale(BUG-559 保留)。- 五处接入点的源码契约。
- (b) 决策:无带年份事件探针时不出风格题(题面不含「更接近哪一种」,
dropped_probes有frameless_distinguish:d9_relationship)。 - (b) 决策的另一半:引擎给出 2020 年感情事件探针时该线照问,
semantic_key为该探针、choice_kind=existence、题面含年份。 7b. 重建出来的探针不得变成可问的题:rectificationFollowupCatalog的eventProbes与contrastPacket.probes里都不含该 key。 - BUG-917:
collect_waiting+ 死卡 →unavailable;chat 与 message-entry 的源码契约。 - BUG-913
closed_by_design复核(原样保留)。 - 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 仍建立在错分答案上,不要采用(与上一单结论相同)。