diff --git a/CHANGELOG.md b/CHANGELOG.md index 95dce639..e6c6f3b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # 印度占星 Skill 更新日志 +## 2026-10-01 — 采用后的核对题可以作答了;交付收尾那句话不再丢 + +- 修复:采用后的核对题点任何选项都提示「这次没提交上」。现在能正常作答(BUG-1150)。 +- 修复:采用后的单独消息、交付时补写的那句收尾话之前都没能存进数据库(编号格式不对,被拒后没有报错)。现在都能存下,刷新也在(BUG-1149)。 +- 修复前已经出现的核对题仍答不了,可点「这题跳过」或新开一次校正。Skill 版本不变;不改数据库结构。 + ## 2026-10-01 — 首页开场语换成知心姐姐口吻;今日星语说清为什么 - 开场语:深夜不再说「夜猫子」「还没睡呀」,改成「这会儿很安静」「夜里好,{名字}」「慢慢说,{名字}」;其余时段同样改成关心的口吻,例如「中午好,记得吃饭」「今天辛苦了」「周五了,松口气」。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 5972e843..866a7039 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -15394,7 +15394,7 @@ ## BUG-1146 | 采用后的核对题只有题干、没有选项按钮,刷新也不出;题挂在上一题的旧回复下 -- 状态:resolved(2026-10-01 Claude 直接执行;单元与客户端回归测试;部署与真机复核以部署后 health 与 `docs/testing/rectification-post-adopt-card-20261001.md` 为准) +- 状态:resolved(2026-10-01 Claude 直接执行;单元与客户端回归测试。**第一版 `282455cc` 真机仍挂错位置**:采用轮次的 requestId 不是 uuid,真库拒写、被吞(BUG-1149),补修后真库重放通过;真机复核以 `docs/testing/rectification-post-adopt-card-20261001.md` 为准) - 首次发现 / 最近更新:2026-10-01 / 2026-10-01 - 影响面:`v9/method-followup.ts`(已采用时承接活焦点的 keep 分支)、`candidates/accept/route.ts`、`v9/answer-choice.ts::writeAdoptFollowupTurn`(新)、`rectification-chat-accept-run.ts`。所有点「采用」后出现核对题的校正,盘型口径之前也会。 - 用户现象:真机点「采用」后出现「2016 年 9 月那次升学…」题干,下面没有四个按钮;刷新后仍然没有。题干挂在答上一题那条消息(「已记录,范围没变;…」)下面。 @@ -15436,3 +15436,34 @@ - 相关记录:BUG-1132(人话自检)。 - 复发自:无 - 修复版本:`codex/consult-term-glossary-20261001`。 + +## BUG-1149 | 采用轮次与交付收尾轮次的请求号不是 uuid,真库拒写且被吞 + +- 状态:resolved(2026-10-01 Claude 直接执行;本机真 PostgreSQL 17 重放:修前报 uuid 语法错误,修后采用轮次写入成功) +- 首次发现 / 最近更新:2026-10-01 / 2026-10-01 +- 影响面:`v9/answer-choice.ts::writeAdoptFollowupTurn`(BUG-1146 新增)、`exhaustionGateRequestId` → `persistExhaustionGateTurn`(`turn-exit.ts`、`agent-run-finish.ts`、`ensureNonTerminalTurnExit` 三处调用)。 +- 用户现象:BUG-1146 部署后真机点「采用」,核对题仍挂在上一题「已记录,范围收到…」下,没有单独的采用消息;交付收尾那句(无轮次时补写的旁白)在库里从来没有,刷新或重挂后就不见了。 +- 触发条件:每次采用;每次走交付收尾补写。 +- 根因:`append_agentic_rectification_turn` 现行(V10)重载的 `p_request_id` 是 uuid;`<请求号>:adopt` 与 `<轮次号或案例号>:gate` 都不是 uuid,PostgreSQL 报 `invalid input syntax for type uuid`。两处调用方都在 try/catch 里只打一行 warn,交付标记却已经打上,所以只读续跑也不写轮次。假数据库测试不校验类型(BUG-1146 的单元测试断言的正是错误的 `req-1:adopt`)。 +- 修复:`deterministicTurnRequestId(seed)` 用 sha256 派生 v5 形 uuid(同一种子同一 uuid,重试仍幂等);`adoptTurnRequestId`、`exhaustionGateRequestId` 改用它。 +- 验证:`frontend/tests/rectification-post-adopt-card-20261001.test.ts`(两种请求号符合 uuid 格式且同种子稳定;采用轮次断言改为 `adoptTurnRequestId("req-1")`——原值 `req-1:adopt` / 新值派生 uuid / 原因:原值在真库必然失败)。本机 PG17 + 真引擎重放虚构案例:修前 `invalid input syntax for type uuid: ":adopt"`,修后 `adopt_turn` 写入。 +- 防复发:写轮次的请求号一律 uuid;新增确定性轮次必须在真库(PG 替身)至少跑一次。 +- 相关记录:BUG-1146、BUG-596、BUG-1150。 +- 复发自:无(`:gate` 自 BUG-596 一侧引入后一直未在真库写成功过) +- 修复版本:`codex/rectification-post-adopt-verify-20261001`。 + +## BUG-1150 | 采用后核对题点任何选项都报「这次没提交上」 + +- 状态:resolved(2026-10-01 Claude 直接执行;本机真 PostgreSQL 17 重放:修前 `stale_probe`,修后作答成功并收尾) +- 首次发现 / 最近更新:2026-10-01 / 2026-10-01 +- 影响面:`v9/server-focus.ts::expectedAnswerSchemaFor`(reverse_verify / out_of_sample_check 的探针身份)。所有计分的采用后核对题。 +- 用户现象:采用后核对题四个按钮出现了,点任一个都弹「这次没提交上,请再点一次。」,再点也一样。 +- 触发条件:采用后核对题计分(`scoring` 为真),且推断态里该探针的 id 带拆分哈希。 +- 根因:核对题盖章时不看推断态(BUG-912 防串题),`probe_id` 写成 `probe:<语义键>`;推断态里同一探针的 id 是 `probe:<语义键>:<拆分哈希>`。作答时前端层按语义键找到探针、用带哈希的 id 写转移,数据库 `persist_agentic_rectification_inference_transition` 发现它与活焦点 schema 的 `probe_id` 不同,拒为 `agentic_rectification_stale_probe`,接口回非 200。 +- 修复:核对题盖章时,推断态里若有**同语义键、同拆分哈希**的那一道,就写它的 id;找不到时保持原样(仍不盖无关的高增益探针,BUG-912 不放宽)。 +- 验证:`frontend/tests/rectification-post-adopt-card-20261001.test.ts`「a scoring post-adopt check carries the state's probe id」(修前红:实际 `probe:career.2018.dasha_activation`)。本机 PG17 重放虚构案例:修前 `stale_probe`,修后 `applied: true`、收尾句「前事核对到这里…」。 +- 防复发:计分题的 schema `probe_id` 必须与推断态 id 同形;采用后流程的回归需在真库走到「作答」一步。 +- 已存在的卡:修复前已落库的核对题仍是旧 id,作答仍会被拒;可点「这题跳过」,或新开一次校正。 +- 相关记录:BUG-912、BUG-498、BUG-1146、BUG-1149。 +- 复发自:无 +- 修复版本:`codex/rectification-post-adopt-verify-20261001`。 diff --git a/docs/tasks/PROGRESS-rectification-post-adopt-card-20261001.md b/docs/tasks/PROGRESS-rectification-post-adopt-card-20261001.md index 5c7832a7..1cacdcd9 100644 --- a/docs/tasks/PROGRESS-rectification-post-adopt-card-20261001.md +++ b/docs/tasks/PROGRESS-rectification-post-adopt-card-20261001.md @@ -42,3 +42,16 @@ | `tests/test_repo_privacy_markers.py` | — | 通过 | Python 快速门:本单不改 `scripts/**`、`tests/**`,未跑。`test:db`:不改表与 SQL,所用 RPC 采用后已有调用路径,未跑;部署后真机清单覆盖。 + +## 第二轮(真机回报后,BUG-1149~1150) + +真机:采用后核对题仍挂在上一题下面;点任何选项都「这次没提交上」。本机真 PostgreSQL 17 + 真引擎重放虚构案例复现两处: + +| 步骤 | 修前 | 修后 | +| --- | --- | --- | +| 写采用轮次 | `invalid input syntax for type uuid: ":adopt"`(被吞) | 写入 | +| 作答核对题 | `agentic_rectification_stale_probe`(schema `probe:<键>` ≠ 推断态 `probe:<键>:<哈希>`) | `applied: true`,收尾句 | + +同根问题:交付收尾补写轮次的 `:gate` 请求号也不是 uuid,一并修。真机反映的「选中后旁白和卡片消失一会儿」本机无浏览器未复现;采用路径现在不再发只读续跑、收尾句能落库,待真机复测,复现则请录屏。 + +第二轮门禁(`52495df7`,基线 staging `fa6d026f`):tsc 0、lint 0 error、用例 4866(失败 24 条与 f1a169cb 基线逐条一致、cancelled 0;唯一「缺失」名为 c0f05912 改名)、`/` ○ Static、首屏 gzip JS 555,642、镜像 COPY 布局构建通过;真库重放 8 条中可采用的 1 条:采用轮次写入、作答成功。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 6f04ef0f..91101f12 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -265,7 +265,7 @@ | `TASK-rectification-chart-tier-caveats-20261001.md` | — | **报告与聊天标注判断不了的分盘**:采用后读 `active_birth_provenance`(segment-v1)→ 结果 `segment_summary` 档位,blocked/indistinct 的 D9/D10 在聊天 answer_policy 与一句说明、报告章节顶部提示 + 主题降级;旧资料逐字节不变;不改库、不改 consult/route.ts | 待领取 | BUG-1138~1139 | | `TASK-rectification-angle-timing-research-20261001.md` | `PROGRESS-rectification-angle-timing-research-20261001.md` | **角度类时间技法研究(离线)**:行运土木罗压本命上升 / 天顶度数、次限推运角、太阳弧角(1° ≈ 4 分钟出生时间),77 例 v5;乱序日期对照 ≥ 50 次必做、留一法、不得挤出真值;显著才叠加到六题后验比头段命中与区间宽度 | 已实现待验收(0/9 预登记检验显著,BUG-1141 closed_by_design;分支 codex/rectification-angle-timing-research-20261001,未推送) | BUG-1141 | | (直接执行,无任务书) | `PROGRESS-rectification-segment-order-20261001.md` | **健康区分题写入失败(BUG-1143)+ 按盘型挑题默认开启(BUG-1144)**:区分类焦点把 `health_pressure` 原样写入、违反表约束、写入被吞后提前交付(BUG-672 同族);修后 77 例真库回放平均问题数 ±10 2.8→4.6、±30 3.6→5.4,D10 头段 ±30 39→52;按段选题 ≤61 分钟默认开,±30 D9 46→51、D10 52→55,真值段 76/76 | 已验收(Claude 10-01 直接执行) | 分支 codex/rectification-segment-order-20261001 | -| (直接执行,无任务书) | `PROGRESS-rectification-post-adopt-card-20261001.md` | **采用后核对题无按钮(BUG-1146)+ 盘型口径回复念分钟排名(BUG-1147)**:keep 分支丢探针身份致 GET 空卡(BUG-498 复发);采用接口不写轮次、只读运行被 60 秒交付窗口挡掉,题挂到上一题回复下;改为采用接口写「已采用 HH:MM。再核对一件过去的事。」轮次并挂题。盘型口径下去掉「领先 / 落后」、交付旁白不再说「分不开」、边界句只说一次 | 已验收(Claude 10-01 直接执行:门禁对基线 f1a169cb 一致);真机清单 `docs/testing/rectification-post-adopt-card-20261001.md` | 分支 codex/rectification-post-adopt-verify-20261001 | +| (直接执行,无任务书) | `PROGRESS-rectification-post-adopt-card-20261001.md` | **采用后核对题无按钮(BUG-1146)+ 盘型口径回复念分钟排名(BUG-1147)**:keep 分支丢探针身份致 GET 空卡(BUG-498 复发);采用接口不写轮次、只读运行被 60 秒交付窗口挡掉,题挂到上一题回复下;改为采用接口写「已采用 HH:MM。再核对一件过去的事。」轮次并挂题。盘型口径下去掉「领先 / 落后」、交付旁白不再说「分不开」、边界句只说一次 | 已验收(Claude 10-01 直接执行);真机回报后补修 BUG-1149(请求号非 uuid 被真库拒写)、BUG-1150(核对题作答 stale_probe),本机真库重放通过;真机清单 `docs/testing/rectification-post-adopt-card-20261001.md` | 分支 codex/rectification-post-adopt-verify-20261001 | | `TASK-rectification-typed-event-scoring-research-20260929.md` | `PROGRESS-rectification-typed-event-research-20260929.md` | **打字经历按选择题规则计分(离线研究,不上线)**:计分通道不对称 + 已入账年份挡题;R0 学业质量题措辞 / 年精度显示成 1 月(冻结文件,需重新冻结) | 已验收关单(Claude 10-01:三类技法 9 项检验 0 显著,真值平均排位 0.49~0.59;Claude 补正对照——埋入信号排位 0.077、挪年 0.502——流程有效;BUG-1141 closed_by_design) | BUG-1088、1089 | | `TASK-report-reader-polish-20260929.md` | `PROGRESS-report-reader-polish-20260929.md` | **报告页打磨**:生成入口挪进页面主体(删标题栏按钮)、详情页到底部按钮(懒渲染一次到底)、导出按钮带文字、目录一级/二级分层、去掉「字段」与 RL/NL 缩写表头、状态列同义重复去重、报告表格淡底色、分块导出显示真实文件大小 | Claude 直接执行并自验(tsc 0、lint 0 error、全量 fail 与基线同 24 条、`/` Static、gzip +7 B、快速门 Python 1000 passed),已部署 staging `d7772011`,真机欠 | BUG-1092~1094 | | `TASK-report-english-edition-20260929.md` | `PROGRESS-report-english-edition-20260929.md` | **报告中英两版**:同一次引擎计算渲染 zh/en 两遍(不用模型翻译),瑜伽库 477 条补英文、模板与前端表头英文化;中文逐字节不变、英文零汉字、两版数字序列一致;阅读页 `?lang=en` 切换,导出当前语言 + 「问 AI 建议导出英文版」提示;旧报告不补英文 | Claude 直接执行(含两个 fork 子代理)并自验(tsc 0、lint 0 error、全量 fail 与基线同 24 条、`/` Static、gzip +0.06%、Python 定向全绿),已部署 staging `d7772011`,真机欠 | — | diff --git a/docs/testing/rectification-post-adopt-card-20261001.md b/docs/testing/rectification-post-adopt-card-20261001.md index ffd9c0af..8b32d4b9 100644 --- a/docs/testing/rectification-post-adopt-card-20261001.md +++ b/docs/testing/rectification-post-adopt-card-20261001.md @@ -1,6 +1,6 @@ # 真机清单:采用后的核对题(2026-10-01,BUG-1146~1147) -> staging 部署后,用自己的账号在手机(375px 宽)走一遍。用虚构经历即可。 +> staging 部署后,用自己的账号在手机(375px 宽)走一遍。用虚构经历即可。**请新开一次校正**——修复前已出现的核对题仍答不了(可点「这题跳过」)。 1. **走到交付**:新开一次生时校正,答到出现交付卡(每张盘的上升 + 档位)。 - 交付卡旁边的话里**没有**「这几个候选按现有信息分不开」; @@ -10,7 +10,8 @@ - 出现一条新的助手消息「已采用 HH:MM。再核对一件过去的事。」,不是挂在上一题「已记录…」下面; - 如果有核对题,题目下有**四个按钮**,可以点。 4. **刷新**:刷新页面,核对题还在、四个按钮还在、仍挂在那条采用消息下面。 -5. **答核对题**:点一个按钮,能正常进入下一题或收尾,不报错。 +5. **答核对题**:点一个按钮,不出现「这次没提交上」,能进入下一题或看到「前事核对到这里…」。 +5a. **选中时**:点「更像这个」后,上面的旁白和卡片不应消失;如果仍消失一下,请录屏发给 Claude。 6. **旧校正会话**:从会话列表打开一个以前的校正会话,能打开、不报错。 有任何一项不符,截图发给 Claude。 diff --git a/frontend/src/lib/rectification-agentic/v9/answer-choice.ts b/frontend/src/lib/rectification-agentic/v9/answer-choice.ts index d6d6efc9..6917c079 100644 --- a/frontend/src/lib/rectification-agentic/v9/answer-choice.ts +++ b/frontend/src/lib/rectification-agentic/v9/answer-choice.ts @@ -6,6 +6,7 @@ * returns a fallback narration in one receipt. */ +import { createHash } from "node:crypto"; import { posteriorMap, scoreDeltas } from "../core/decision-fingerprint"; import { productDeliveryForDomain } from "./product-delivery.ts"; import { nextProbe } from "../core/build-state"; @@ -188,7 +189,7 @@ function exhaustionCarrierNarration(range: string | null | undefined, gate: stri } export function exhaustionGateRequestId(askedTurnId: string | null | undefined, caseId: string): string { - return `${askedTurnId ?? caseId}:gate`; + return deterministicTurnRequestId(`gate:${askedTurnId ?? caseId}`); } export async function persistExhaustionGateTurn(input: { @@ -1621,6 +1622,21 @@ export async function persistCollectDenialTurn(input: { return { streamText, turnId: turn.turnId }; } +/** + * `append_agentic_rectification_turn` keys idempotency on a uuid `p_request_id`; a suffixed + * id (「:adopt」「:gate」) is rejected by PostgreSQL (BUG-1149). Same seed → same uuid, + * so a retried request still lands on the same turn. + */ +export function adoptTurnRequestId(requestId: string): string { + return deterministicTurnRequestId(`adopt:${requestId}`); +} + +export function deterministicTurnRequestId(seed: string): string { + const hex = createHash("sha256").update(`rectification-turn:${seed}`).digest("hex"); + const variant = ((parseInt(hex[16]!, 16) & 0x3) | 0x8).toString(16); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-${variant}${hex.slice(17, 20)}-${hex.slice(20, 32)}`; +} + /** * BUG-1146: after 采用 the first post-adopt check is persisted by persistNextInterviewIfIdle, but the * accept route wrote no turn. The read_only agent call that follows short-circuits as @@ -1645,7 +1661,7 @@ export async function writeAdoptFollowupTurn(input: { && (focus.intent === "reverse_verify" || focus.intent === "out_of_sample_check")); // The stem leaves the body once the card attaches; the lead is what the message says. const turn = await persistV9DeterministicTurn(input.accounting, input.userId, input.caseId, { - requestId: `${input.requestId}:adopt`, + requestId: adoptTurnRequestId(input.requestId), userMessage: null, assistantMessage: check ? `${postAdoptCheckLead(input.savedTime)}${narration}` : narration, }); diff --git a/frontend/src/lib/rectification-agentic/v9/server-focus.ts b/frontend/src/lib/rectification-agentic/v9/server-focus.ts index 1f5e80d5..4d16628a 100644 --- a/frontend/src/lib/rectification-agentic/v9/server-focus.ts +++ b/frontend/src/lib/rectification-agentic/v9/server-focus.ts @@ -149,6 +149,14 @@ export function expectedAnswerSchemaFor( if (typeof followup.probe_year === "number" && followup.probe_year > 0) { stamped.probe_year = followup.probe_year; } + // BUG-1150: a scoring post-adopt check names its own probe. When the state holds that probe + // (same semantic key and split), carry the state's id — split-hashed ids are + // 「probe::」 and the transition RPC rejects the bare 「probe:」 as stale. + if (verifyOnly && state && followup.semantic_key) { + const own = state.probes.find((probe) => probe.semantic_key === followup.semantic_key + && (!followup.candidate_split_hash || probe.candidate_split_hash === followup.candidate_split_hash)); + if (own) stamped.probe_id = own.id; + } if (verifyOnly || targetedCollect) return stamped; return state && stamped.scoring !== false && !schemaProbeId(stamped) ? null : stamped; } diff --git a/frontend/tests/rectification-post-adopt-card-20261001.test.ts b/frontend/tests/rectification-post-adopt-card-20261001.test.ts index 19779ba2..f2b73d41 100644 --- a/frontend/tests/rectification-post-adopt-card-20261001.test.ts +++ b/frontend/tests/rectification-post-adopt-card-20261001.test.ts @@ -2,8 +2,9 @@ import assert from "node:assert/strict"; import test from "node:test"; import { buildMethodFollowupPlan, projectRectificationChoiceCard } from "../src/lib/rectification-agentic/v9/method-followup.ts"; import type { DiscriminatingEventProbe } from "../src/lib/rectification-agentic/v9/refinement-packet.ts"; -import { persistServerOwnedFocus } from "../src/lib/rectification-agentic/v9/server-focus.ts"; -import { writeAdoptFollowupTurn } from "../src/lib/rectification-agentic/v9/answer-choice.ts"; +import { expectedAnswerSchemaFor, persistServerOwnedFocus } from "../src/lib/rectification-agentic/v9/server-focus.ts"; +import { INFERENCE_ALGORITHM_VERSION } from "../src/lib/rectification-agentic/core/types.ts"; +import { adoptTurnRequestId, exhaustionGateRequestId, inferenceForPersistedAnswer, writeAdoptFollowupTurn } from "../src/lib/rectification-agentic/v9/answer-choice.ts"; import { composeChoiceNarration } from "../src/lib/rectification-agentic/v9/choice-action.ts"; import { alreadyDelivered, resetDeliveryTurnGuardForTests } from "../src/lib/rectification-agentic/v9/delivery-turn-guard.ts"; import { deliveryTurnNarration, REPRESENTATIVE_MINUTE_DISCLAIMER } from "../src/lib/rectification-agentic/user-copy.ts"; @@ -104,7 +105,7 @@ test("采用 writes its own turn, hangs the post-adopt check on it and quiets th }); assert.deepEqual(out, { turnId: "44444444-4444-4444-8444-444444444444" }); const turn = accounting.calls.find((call) => call.fn === "append_agentic_rectification_turn"); - assert.equal(turn?.args.p_request_id, "req-1:adopt"); + assert.equal(turn?.args.p_request_id, adoptTurnRequestId("req-1")); assert.equal(turn?.args.p_user_message, null); assert.equal(turn?.args.p_assistant_message, "已采用 05:00。再核对一件过去的事。2018 年前后,有没有入职或职责明显加重?"); const link = accounting.calls.find((call) => call.fn === "set_agentic_rectification_conversation_focus"); @@ -184,3 +185,44 @@ test("client: no adopt turn (idempotent accept) keeps the read_only continuation assert.equal(recorder.sends.length, 1); assert.equal(recorder.sends[0]!.action, "read_only"); }); + +// BUG-1149: real PostgreSQL types p_request_id as uuid; 「:adopt」/「:gate」 raised 22P02, +// the accept route swallowed it, and the gate narration never persisted. +const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; +test("deterministic turn request ids are uuids and stable per seed", () => { + const adopt = adoptTurnRequestId("66666666-6666-4666-8666-666666666666"); + assert.match(adopt, UUID); + assert.equal(adopt, adoptTurnRequestId("66666666-6666-4666-8666-666666666666")); + assert.notEqual(adopt, adoptTurnRequestId("77777777-7777-4777-8777-777777777777")); + const gate = exhaustionGateRequestId("55555555-5555-4555-8555-555555555555", CASE); + assert.match(gate, UUID); + assert.equal(gate, exhaustionGateRequestId("55555555-5555-4555-8555-555555555555", CASE)); + assert.match(exhaustionGateRequestId(null, CASE), UUID); + assert.notEqual(adopt, adoptTurnRequestId("66666666-6666-4666-8666-666666666666:x")); +}); + +// BUG-1150: post-adopt checks were stamped 「probe:」 while the state holds the split-hashed +// 「probe::」; the transition RPC rejected every answer as stale_probe. +test("a scoring post-adopt check carries the state's probe id", () => { + const plan = buildMethodFollowupPlan({ + evidence: EVIDENCE as never, accepted: true, sessionOutcome: "adopt_representative", + selectionAllowed: true, proposeAllowed: true, eventProbes: [DATED_CHECK], + } as never); + const followup = plan.next_followup!; + assert.equal(followup.intent, "reverse_verify"); + const stateId = "probe:career.2018.dasha_activation:career.2018.dasha_activation"; + const inference = { + algorithm_version: INFERENCE_ALGORITHM_VERSION, candidate_set_id: "04:58-05:06:05:00,05:04", revision: 6, + phase: "discrimination", result_status: "discriminating", range_start: "04:58", range_end: "05:06", + candidates: ["05:00", "05:04"].map((time, index) => ({ id: time, time, cluster_range: ["04:58", "05:06"], prior_score: 10, + posterior_score: 10, probability: 0.5, status: "active", rank: index + 1, strong_conflict_count: 0 })), + events: [], answered_probes: [], rounds: [], entropy: 1, representative_time: "05:00", credible_range: ["04:58", "05:06"], + probes: [{ ...DATED_CHECK, id: stateId, question: "2018", information_gain: 0.5, year: 2018 }], + }; + const receipt = { inference_state: inference }; + assert.ok(inferenceForPersistedAnswer(receipt)?.probes.some((probe) => probe.id === stateId), "fixture state parses"); + const schema = expectedAnswerSchemaFor(followup.choice_frame!, followup.choice_frame!.question_id, receipt, followup); + assert.equal(schema?.probe_id, stateId); + const bare = expectedAnswerSchemaFor(followup.choice_frame!, followup.choice_frame!.question_id, null, followup); + assert.equal(bare?.probe_id, "probe:career.2018.dasha_activation", "no state: the named key, as before"); +});