fix(rectification): stop a stale candidate-set prefix from hiding the choice card
Independent Staging Quality Gate / validate (push) Successful in 11m31s
Independent Staging Quality Gate / publish (push) Successful in 14m41s

Scoring scoped the contrast packet to the previous round's candidate set while
minting probes for the new one, so every later read re-prefixed the stored hash
and the persisted focus schema could never match. Mint one prefix per set and
compare splits by probe identity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVapmh2oGNyr6ECHKjPJY8
This commit is contained in:
Jesse_Chen
2026-08-29 08:51:19 +00:00
parent 0044d4f358
commit 3cecb25f42
6 changed files with 276 additions and 8 deletions
+16
View File
@@ -6686,3 +6686,19 @@
- 复发自:无
- 修复版本:待发布
## BUG-437 | 区分卡因过期候选集前缀被二次加前缀,界面无卡且 Agent 只承接不提问
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:`probeFromEngine``candidate_split_hash` 归一化、`projectRectificationChoiceCard` 身份校验、`scoreAndPersistCurrentEvidence` 组包的 `candidateSetVersion`、GET `choice_card`
- 用户现象:`current_question` 是合法选择题(`kind=choice``focus_id``probe_id` 齐全、题干已生成),`choice_card` 却是 null。Agent 按提示第 4 条只承接不复述,正文只剩「请你凭印象选一个」,界面没有卡片,用户无法继续。`dropped_probes` 为空,看不出被抑制的原因。
- 触发条件:某一轮打分改变了候选分钟列表(例如 05:05 换成 05:00),此前铸出的 varga 对比探针仍带旧候选集前缀,随后任意一次读路径重建对比包。
- 根因:`scoreAndPersistCurrentEvidence` 组对比包时 `candidateSetVersion` 取上一轮 `inference_state.candidate_set_id``candidateTimes` 取本轮 `score.candidates`,于是新写入的探针 hash 带着旧集合前缀,而同一份 state 的 `candidate_set_id` 按新候选生成。之后 `probeFromEngine``hash.includes(candidateSetVersion)` 判断是否已有前缀,旧前缀不含新集合 id,于是再加一层前缀得到双前缀 hash。落 focus 时 `stampChoiceSchemaWithProbe` 写的是 state 里探针的单前缀原值,`projectRectificationChoiceCard``candidate_split_hash` 全等校验因此永远不成立,直接 return null,且不记入任何丢弃账本。
- 修复:新增 `splitHashForCandidateSet`hash 为 `<候选集 id>:<语义键>` 形态时按当前候选集重铸,已带当前前缀时保持不变,不透明引擎 hash 只加一次前缀,杜绝前缀叠加。新增 `isSameCandidateSplit`,两侧同为 `<任意候选集 id>:<同一语义键>` 时视为同一 split(语义键本身已编码候选分组),`projectRectificationChoiceCard` 改用它做身份校验,语义键不同或不透明 hash 不同仍然 fail closed。组包 `candidateSetVersion` 改为按本轮 `candidateRange``score.candidates` 计算的 `candidateSetId`,与写入 state 的候选集同源。Skill 版本保持 `10.0.13`,未放宽 confirmation gate。
- 验证:`frontend/tests/rectification-choice-card.test.ts` 锁定「探针 hash 带旧候选集前缀时 GET 仍返回卡片」(去掉修复即失败)与「持久化 split 属于另一探针时仍隐藏」;`rectification-candidate-contrast-packet.test.ts` 锁定前缀重铸幂等、不透明 hash 只加一次、split 身份忽略前缀但不忽略探针、组包按本轮候选集作用域。`npx tsc --noEmit``npx tsx --test tests/rectification-*.test.ts tests/birth-time-rectification-contract.test.ts tests/agentic-rectification-*.test.ts` 658 项 0 失败;全量 `npx tsx --test tests/*.test.ts` 失败集合与改动前一致(23 项数据库/部署环境类)。
- 防复发:探针 `candidate_split_hash` 只能有一层候选集前缀,任何读路径不得对已带前缀的 hash 再加前缀。打分写入的对比包必须与本轮候选集同源,不得沿用上一轮 `candidate_set_id`。卡片身份校验不得因候选集前缀差异静默吞掉合法卡片。
- 相关记录:BUG-410、BUG-411、BUG-432
- 复发自:无
- 修复版本:待发布