fix(rectification): stop unwritten-evidence claims and same-cluster dasha false conflicts (BUG-635–640)
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled

Host only says 记下了 after a real write; Mastra schema rejections fail closed. Ledger year keys no longer drop quality probes, dual-dasha agreement is per cluster, width uses cluster span, and public house tables follow the inference minute.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-10 17:38:37 +08:00
co-authored by Cursor
parent 301827ad59
commit a998b6ec53
38 changed files with 1437 additions and 124 deletions
+106 -9
View File
@@ -5679,16 +5679,16 @@
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:`method_followup_plan``persistServerOwnedFocus``known_event_quality`
- 最近更新:2026-09-10
- 影响面:`method_followup_plan``persistServerOwnedFocus``known_event_quality``askedKeysForSameYearDedup`
- 用户现象:记下带年份的学业经历后,口语会问那次考试有没有发挥失常,界面却没有 A/B/C/D 点选卡,只能打字。dasha 冲突反推卡仍按采用门等待,不在此列。
- 触发条件:账本已有该年学业等可评分事件;decision receipt 含 `known_event_quality`;采用门 3 件/2 领域未齐,下一方法层仍是感情收集。
- 根因:`remainingReverseVerifyProbes` 跳过 `known_event_quality`。采用前只有 dasha 冲突探针能变成 `event_probe` 点选卡,且还要等 3 件/2 领域。发挥质量探针留在 receipt 里,模型用自然语言问,服务器不盖 `choice_frame`。即便盖了,`information_gain` 为 0 也会被 `shouldSkipDiscriminatorFollowup` 丢掉。
- 修复:已记下对应年份后,`known_event_quality` 在方法轮换之前出 `event_quality` 点选卡,不要求采用门。摘要已编码发挥质量则不再出卡。零信息增益不再挡住发挥质量卡。dasha 冲突探针仍等 3 件/2 领域。不改 Skill `10.0.11`
- 验证:`frontend/tests/rectification-eight-method.test.ts``frontend/tests/rectification-server-focus.test.ts``tests/test_rectification_event_probes.py`
- 防复发:已记下年份的发挥质量探针必须挂 `choice_frame` 并持久化。不得把 dasha 冲突探针的采用门门槛套到发挥质量卡上。不得把 `confirmation_allowed` 改成 true。
- 相关记录:BUG-348、BUG-384、BUG-386、BUG-388
- 复发自:BUG-384(口语问发挥质量,点选卡却被冲突探针占住;采用门修好后变成完全没有卡)
- 修复:已记下对应年份后,`known_event_quality` 在方法轮换之前出 `event_quality` 点选卡,不要求采用门。摘要已编码发挥质量则不再出卡。零信息增益不再挡住发挥质量卡。dasha 冲突探针仍等 3 件/2 领域。不改 Skill `10.0.11`2026-09-10:账本派生的 `domain.year` 键不得再把发挥质量题判成 `same_year_asked`(见 BUG-637)。
- 验证:`frontend/tests/rectification-eight-method.test.ts``frontend/tests/rectification-server-focus.test.ts``tests/test_rectification_event_probes.py`2026-09-10 补 `rectification-probe-year-dedupe-20260906.test.ts``rectification-engine-convergence.test.ts`
- 防复发:已记下年份的发挥质量探针必须挂 `choice_frame` 并持久化。不得把 dasha 冲突探针的采用门门槛套到发挥质量卡上。不得把 `confirmation_allowed` 改成 true。账本 `domain.year` 键只拦存在性探针。
- 相关记录:BUG-348、BUG-384、BUG-386、BUG-388、BUG-592、BUG-637
- 复发自:BUG-384(口语问发挥质量,点选卡却被冲突探针占住;采用门修好后变成完全没有卡)2026-09-10 再次复发自 BUG-592(见 BUG-637
- 修复版本:待发布
## BUG-390 | 事业经历未过采用门就出高考发挥点选卡
@@ -9194,15 +9194,16 @@
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 最近更新:2026-09-10
- 影响面:`latestResultToolProjection``buildSkillVerificationPacket``jyotish-birth-time-rectification@10.0.16`
- 用户现象:区间卡已是约 3 分钟可信窗,交付轮正文却写宽度约半小时、双轨偏向已淘汰分钟、某候选 D10 升成下一座。
- 触发条件:推断层已把候选收到短区间并出交付卡;引擎 `tied_minute_count` / `dasha_agreement` 仍按全部候选计算;`window_scan` 换升在代表分钟之后。
- 根因:结果投影把 `candidates` 换成推断层,但报告仍喂引擎 `indistinguishable_width_minutes``dasha_agreement`。分盘上升没有按候选分钟给出星座,模型按换升时刻自己算。
- 修复:有推断层时报告宽度用 `credible_range` 含两端分钟数;双轨在 active 候选内重算,引擎原值只留 `dasha_agreement_pre_inference` 供审计。顶层宽度改名 `engine_indistinguishable_width_minutes`。报告给出 `sign_by_candidate`,与分歧面板共用换升函数。确认门不改。Skill 10.0.16 要求只抄该表。
补正(2026-09-10):代码里**已有** `dasha_agreement_pre_inference`。BUG-638 修的是引擎原值仍按网格分钟判冲突、与报告口径不一致,不是这个字段缺失。
- 验证:`frontend/tests/rectification-delivery-report-facts.test.ts`:推断窗 04:5104:53、引擎宽 29、双轨 top 为已淘汰分钟时,报告 `width_minutes === 3` 且正文不含 29 / 已淘汰分钟;D10 05:00 换升时 `sign_by_candidate["04:53"].d10 === "巨蟹座"``agent-voice-copy-contract` / `skill-registry` 锁 10.0.16 新句。
- 防复发:交付轮宽度、双轨、分盘星座必须来自推断层报告字段,不得再把引擎原跨度或换升时刻交给模型自算。
- 相关记录:BUG-545、BUG-568、BUG-290
- 相关记录:BUG-545、BUG-568、BUG-290、BUG-638
- 复发自:BUG-290(宽度字段进投影后未随推断层更新);BUG-568(区间读盘,交付正文仍用开工跨度)
- 修复版本:`06e44104`
@@ -9859,3 +9860,99 @@
- 相关记录:BUG-593、BUG-606、BUG-629
- 复发自:无
- 修复版本:待发布
## BUG-635 | 证据轮只说「记下了」却没写入,财务经历静默丢失、流程停在原题
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:`runV9AgentTurn` `expectedWrite``streamAttempt` 收尾、`RECTIFICATION_USER_COPY.evidenceNotRecorded``liveQuestionOnMessages`
- 用户现象:财务采集题下回一句带年月的欠债/收入变化后,助手只回「记下了:…。」下面没有下一问也没有卡;时间线仍是原范围;证据数不变;该轮照常扣点。
- 触发条件:collect 焦点下用户提供带年月经历,模型只调 `rectification-read-case` 后按格式写「记下了」,不调 `rectification-record-evidence-batch` / `rectification-set-focus`
- 根因:路由分类结果没传给运行器,收尾只要正文非空就 `completeAttempt`,不看有没有公开写工具。客户端把旧消息上的同 `focus_id` 当成问题仍在显示,缺口不补主持人问题行。
- 修复:路由把 `expectedWrite` 设为 `"evidence" | "none" | "unknown"` 传进运行器;`provide_new_evidence``answer_current_focus` + `has_new_dated_event``"evidence"`。分类器 null/出错重试一次,仍失败则 `"unknown"` 且守卫 fail-open,**不用年份正则或关键词兜底**。无焦点的 `message` 路径也跑同一分类器。无写入的「记下了」第一次 `evidence_not_written` 可重试(零写工具,不进 `RETRYABLE_ERROR_CODES`);第二次主持人正文「这件我还没记上。请再说一次大概年月和发生的事。」,`answer.host_fallback`,不结算计费。`liveQuestionOnMessages` 只认最后一条 settled 助手消息。
- 验证:`frontend/tests/rectification-unwritten-evidence.test.ts`,以及 host-fallback / spoken-collect / agentic-entry / turn-intent-classifier / agent-voice-copy-contract。
- 防复发:有公开写工具 completed 不得重试(BUG-186);兜底正文不得由宿主复述用户原话冒充「记下了」;旧消息同 `focus_id` 不得挡住主持人问题行。
- 相关记录:BUG-186、BUG-278、BUG-359、BUG-449、BUG-633
- 复发自:BUG-633(只补了 batch 已完成且无正文,没补正文声称记下了但 batch 没跑)
- 修复版本:待发布
## BUG-636 | 校正流把 Mastra inputSchema 拒绝信封报成 tool completed
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:`mapStreamChunkToActivity` / `mapStreamChunkToPhase` / `batchResultFromToolChunk`
- 用户现象:本事故未触发。若模型给 batch 传了不在 `EVIDENCE_KINDS` 的 kind,回执会显示 `evidence.proposed` completed,写工具守卫会被假 completed 绕过。
- 触发条件:`createTool``inputSchema` 校验失败时 resolve `{ error: true, message, validationErrors }`,流层当普通 `tool-result`
- 根因:咨询流 BUG-278 已按信封改发 `tool.failed`;校正流 `tool-result` 没有同样判定。
- 修复:信封结构 `error === true``validationErrors` 为对象时发 `tool.activity failed code=tool_call_rejected`,不发 completed phase`batchResultFromToolChunk``composeHostFallbackNarration` 对信封返回 null。公开回执不含 `validationErrors`
- 验证:`frontend/tests/rectification-unwritten-evidence.test.ts``rectification-host-fallback.test.ts`
- 防复发:不得把 Mastra 拒绝信封当 batch 返回值;不得把 `validationErrors` 写进公开事件。
- 相关记录:BUG-278、BUG-635
- 复发自:BUG-278(咨询流已修,校正流未跟)
- 修复版本:待发布
## BUG-637 | 账本同年键把已记学业的发挥质量题一并丢掉
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:`askedKeysForSameYearDedup``inspectDiscriminatorProbes``renderableEventProbe``remainingReverseVerifyProbes`
- 用户现象:账本已有某年学业经历,`dropped_probes``education.YYYY.known_event_quality` 被标 `same_year_asked`,界面再也问不到「那次发挥怎么样」。
- 触发条件:账本派生 `domain.year` 键进入 `askedDiscriminatorKeys`;发挥质量语义键必然同域同年。
- 根因:BUG-592 的同年硬排除对账本键和已答键一视同仁。发挥质量题存在的前提就是账本已有那年事件,于是被误杀。BUG-389 的测试没覆盖「账本已有同年事件」。
- 修复:账本派生的精确 `domain.year` 键只拦存在性探针。`known_event_quality` / `event_quality` 仍受回执已答键约束,同一道不问两遍。不改 `SCORE_DELTA`、四选项、确认门。
- 验证:`frontend/tests/rectification-probe-year-dedupe-20260906.test.ts``rectification-engine-convergence.test.ts`
- 防复发:账本 `domain.year` 不得单独让发挥质量题变成 `same_year_asked`。已答过该质量键后仍须丢掉。
- 相关记录:BUG-389、BUG-592
- 复发自:BUG-389(被 BUG-592 覆盖)
- 修复版本:待发布
## BUG-638 | 双轨一致性按网格分钟判冲突,同簇两个峰值被降置信度
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:`dasha_agreement``dashaAgreementAmongActive``decision_policy`
- 用户现象:主限和分盘大运峰值落在同一段候选里,回执仍写冲突并降低把握;报告另算一套说只作观察。
- 触发条件:两轨 argmax 分钟不同,但同属一个 `cluster_startcluster_end`
- 根因:Python 对整窗网格分钟逐分取峰值,分钟不等即 `conflict`。决策把该冲突写进 reasons 并降置信度。TS 报告只在推断层 active 候选上重算。
- 修复:按簇比较:两轨 argmax 映射到所在簇,同簇即 `agree`。没有簇时仍按分钟比较。推断层 active 簇一致也视为 agree,即使某一轨峰值不是代表分钟。引擎原值保留为 `dasha_agreement_pre_inference`。确认门不因本次 `agree` 从关到开。
- 验证:`tests/test_rectification_refinement_packet.py``frontend/tests/rectification-delivery-report-facts.test.ts`
- 防复发:同簇不同分钟不得再写 `vimshottari_narayana_conflict`。跨簇仍须 `conflict`。不得把 `confirmation_allowed` 因本次 agree 打开。
- 相关记录:BUG-593
- 复发自:无
- 修复版本:待发布
## BUG-639 | 不可分宽度按簇代表分钟少算两端延伸
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:`indistinguishable_width_minutes``indistinguishableWidthMinutes`、确认门 `adjacent_passed`
- 用户现象:报告宽度跟区间两端对得上,引擎「不可分宽度」却按各簇代表分钟算,少算两端延伸;极端时可把 ≤5 分钟的确认门误开。
- 触发条件:各簇 `cluster_start` / `cluster_end` 超出代表分钟。
- 根因:宽度公式用代表分钟的 `max-min+1`,不是簇覆盖。
- 修复:改为 `max(cluster_end)-min(cluster_start)+1`;缺簇字段时回退到 `time``tied_minute_count` 不动。
- 验证:`tests/test_rectification_confirmation_and.py``frontend/tests/rectification-delivery-report-facts.test.ts``rectification-confirmation-gate.test.ts`
- 防复发:两簇 04:5004:55 与 04:5605:02 必须得到 13,不得算成 7。确认门不得因代表分钟看起来相邻而打开。
- 相关记录:BUG-593
- 复发自:无
- 修复版本:待发布
## BUG-640 | 宫位表和本命重算跟的不是卡片上的代表分钟
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:`latestResultToolProjection``publicChartForMinute``natalRecastMeaning`
- 用户现象:卡片代表分钟是推断层的一分钟,回执宫位表和「已按该分钟重算」写的是引擎另一分钟;两者若跨分盘换升,正文会写错盘。
- 触发条件:推断代表分钟 ≠ 引擎代表分钟,且 `house_tables_by_time` 两分钟都有表。
- 根因:投影已能按推断分钟取表,但引擎原表没有改名留下;Agent 看见的事实仍可能混用。
- 修复:有推断层且两分钟不同时,公开 `house_table` / `natal_recast` 跟推断代表分钟;引擎原值改名 `representative_time_pre_inference` / `house_table_pre_inference` / `natal_recast_pre_inference`。Agent 可见投影剥掉这三个字段。不改引擎。
- 验证:`frontend/tests/rectification-delivery-report-facts.test.ts`
- 防复发:公开 `house_table.time` 必须等于投影 `representative_time`。Agent 可见 JSON 不得含 `*_pre_inference`
- 相关记录:BUG-593
- 复发自:无
- 修复版本:待发布