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
@@ -0,0 +1,51 @@
# PROGRESS · 生时校正顺带审计:同年去重、双轨按簇、簇宽、代表分钟(2026-09-10)
工作树:`.worktrees/rectification-unwritten-evidence-claim-20260910`
分支:`codex/rectification-unwritten-evidence-claim-20260910`
任务书:`docs/tasks/TASK-rectification-latent-audit-20260910.md`
基线:任务书写 `origin/staging` @ `a15fc3ef`;本工作树同时交付 BUG-635/636。BUG 编号从 **637** 起,与任务书一致。
## 开工回执
- 编号 **BUG-637**P2/ **BUG-638**P2/ **BUG-639**P3/ **BUG-640**P3)。
- 观察项 §5 未立单。
- 与 BUG-635/636 不改同一代码文件;文档与本单一起验收。
## 决策(BUG-638
Python 按 **argmax 映射到所在簇** 比较,不改成代表分钟列表。两轨峰值同簇即 `agree`;没有簇时仍按分钟比较。确认门不因本次 `agree` 从关到开。
## 已完成
- **BUG-637**:账本派生的精确 `domain.year` 键只拦存在性探针。`known_event_quality` / `event_quality``askedKeysForSameYearDedup` 去掉这些键后再做同年去重;已答质量键仍丢。`remainingConflictProbes` 本来就跳过质量题,未改。不改 `SCORE_DELTA`、四选项、确认门。
- **BUG-638**`dasha_agreement(..., clusters=)` 与 TS `dashaAgreementAmongActive(..., clusters)` 按簇比较。`decision_policy``candidate_decisions` 传入 `build_refinement_packet`。引擎原值仍是 `dasha_agreement_pre_inference`。BUG-593 记录已补正。
- **BUG-639**`indistinguishable_width_minutes` / `indistinguishableWidthMinutes``max(cluster_end)-min(cluster_start)+1`;缺字段回退到 `time``tied_minute_count` 不动。
- **BUG-640**:推断代表分钟 ≠ 引擎代表分钟时,公开 `house_table` / `natal_recast` 跟推断分钟;引擎原值改名 `*_pre_inference`。Agent 可见投影剥掉这三个字段。不改引擎。
- 记录:BUG-389/593/637640、CHANGELOG、本文件。Skill 版本不变(`10.0.21`)。
## 验收命令(实测)
工作树 `codex/rectification-unwritten-evidence-claim-20260910`
| 命令 | 结果 |
| --- | --- |
| `.venv` `pytest` `tests/test_rectification_refinement_packet.py` `tests/test_rectification_confirmation_and.py` | **23/23 pass** |
| `frontend` `tsx --test` 探针去重 / 收敛 / 确认门 / 交付报告 / ingest-p0 | **38/38 pass** |
| `frontend` `./node_modules/.bin/tsc --noEmit` | **0 error** |
| `frontend` `npm run lint` | **0 error** / 111 warning(既有债) |
| `git diff --check` | 干净 |
### 既有断言改动
- `tests/test_rectification_refinement_packet.py` `test_decision_receipt_downgrades_confidence_on_dasha_conflict`:原值 05:13 vs 05:14、两分钟同 d1;新值 05:13 vs 05:20、d1=1 vs d1=2。原因:BUG-638 同簇改为 `agree`,相邻 1 分钟会被并成一簇,必须改成跨簇才能继续锁 conflict。
## 环境缺口
- 无登录态:未做真人走查。
- 未跑 `next build`,未核 `/` 是否仍 `○ Static`、首屏 gzip ±2%。
- 未跑 `run_quality_gate.py --profile quick`
## 未做
- 观察项 §5 未立产品单。
- 未提升 `main`。Skill 未升版。
@@ -0,0 +1,75 @@
# PROGRESS · 证据轮只说「记下了」却没写入(2026-09-10)
工作树:`.worktrees/rectification-unwritten-evidence-claim-20260910`
分支:`codex/rectification-unwritten-evidence-claim-20260910`
任务书:`docs/tasks/TASK-rectification-unwritten-evidence-claim-20260910.md`
基线:任务书写 `origin/staging` @ `d96b24c2`;开工接到 `84572c07`;合入前再接到 `origin/staging` @ `301827ad`collect-semantics 任务书 + BUG-635 决策 4 去掉年份正则)。
## 开工回执
- 编号 **BUG-635**P1/ **BUG-636**P2)。`docs/BUG_HISTORY.md` 当时最大已发布号是 634。
- 顺序:分类器 `expectedWrite` → 无写入的「记下了」收回并重试一次 → 第二次主持人正文且不结算 → 客户端只认最后一条 settled 助手消息 → schema 拒绝信封失败映射 → 记录。
- 未实现 BUG-641643。同一工作树交付 BUG-637640,见 `PROGRESS-rectification-latent-audit-20260910.md`
## 决策 3
**turn `status=completed` + `answer_origin=host_fallback` + 不结算计费**
实现:`completeAttempt(false)``settleBilling === false``billing.release()`,不写 `billing.settled`。客户端仍走完成轮刷新;未答焦点靠 T4 的主持人问题行。
## 决策 4`301827ad` 修订)
`expectedWrite: "evidence" | "none" | "unknown"`。分类器 null/出错重试一次,仍失败则 `"unknown"`,守卫 fail-open。**没有年份正则 / 关键词兜底。** 无焦点的 `message` 路径也跑同一分类器。正文里的「记下了」仍触发守卫(决策 1)。
## 已完成
- **T1**`expectedWrite` 声明在 `immediateResponse` IIFE **之外**`POST` 作用域),否则 `runV9AgentTurn({ expectedWrite })` 编译失败(TS18004)。collect / choice / 无焦点 message 都走 `classifyTurnIntentWithRetry``provide_new_evidence``answer_current_focus` + `has_new_dated_event``"evidence"``opening` / `read_only` 不触发。
- **T2**`streamAttempt``empty_stream` 之后、`loadV9CaseDossier` 之前:`needsWrite && !publicWriteToolCompleted && !hostFallbackUsed` → 第一次 `evidence_not_written` + `retractSpoken()`(不进 `RETRYABLE_ERROR_CODES`);第二次主持人正文 `RECTIFICATION_USER_COPY.evidenceNotRecorded``needsWrite` = `expectedWrite === "evidence"` 或正文含「记下了」。batch 已完成且无正文仍走 BUG-633 `applyHostFallback`
- **T3**`buildAgentMessages``attempt > 1` 时用 `retryConstraintForAttempt(lastAttemptError)`
- **T4**`liveQuestionOnMessages` 只认 `latestSettledAssistant`。未答焦点落到既有 `persisted_question` 主持人问题行。`frontend/DESIGN.md` 表已补完成轮未答焦点。
- **T5**schema 拒绝信封 `{ error: true, validationErrors: object }``tool.activity failed code=tool_call_rejected`phase 映射与 batch recap 返回 null;公开事件不含 `validationErrors`
- **T6**BUG-635/636、CHANGELOG、走查第 15 条、任务表待验收、本文件。Skill 版本不变(`10.0.21`)。
## 验收命令(实测)
工作树 `codex/rectification-unwritten-evidence-claim-20260910`,相对 `origin/staging` @ `301827ad`
| 命令 | 结果 |
| --- | --- |
| `frontend` `tsx --test` 任务书相关文件(unwritten / host-fallback / spoken-collect / turn-intent / agentic-entry / agent-voice / v9-stream | **141/141 pass** |
| `frontend` `./node_modules/.bin/tsc --noEmit` | **0 error** |
| `frontend` `npm run lint` | **0 error** / 111 warning(既有债) |
| `git diff --check` | 干净 |
| `next build` | 未跑;记环境缺口 |
### 本单测试名
- `collect intent maps provide_new_evidence and dated current-focus to expectedWrite`
- `runner does not use a year-utterance fallback`
- `retry bootstrap uses the unwritten-evidence constraint only for that error`
- `route passes classifier expectedWrite into the agent runner`
- `classifyTurnIntentWithRetry fails open as unknown after two misses`
- `unwritten 记下了 retries once then host-falls-back without billing`
- `read-case then batch then set-focus then 记下了 stays on the baseline path`
- `empty body after a completed batch still uses BUG-633 host fallback`
- `read_only action with a dated utterance does not trigger the write guard`
- `schema rejection envelopes fail the tool and never count as a batch recap`
- `rejected batch tool-result is failed in the runner terminal status`
- `chat live question only reads the last settled assistant message`
- host-fallback`schema rejection envelopes are not treated as batch recaps or write completion`
- host-fallback`year fallback is gone; only explicit evidence expectedWrite triggers the guard`
### 既有断言改动
- `rectification-v9-stream.test.ts` empty_stream 二次成功正文:原值「记下了这件事。」/ 新值「先继续核对。」;原因:BUG-635 把无写入的「记下了」收口成 `evidence_not_written`,该用例只锁 empty_stream 重试。
## 环境缺口
- 无登录态:`docs/testing/rectification-scenarios-20260907.md` 第 15 节真人走查未做,不得写通过。
- 未跑 `next build`,未核 `/` 是否仍 `○ Static`、首屏 gzip ±2%。
- 推 staging 后才核 `/api/health``deployment.gitCommit`;未部署则记缺口。
## 未做
- 未提升 `main`。Skill 未升版。
- 观察项与 BUG-641643 未做。
+2 -2
View File
@@ -95,8 +95,8 @@
| `TASK-rectification-yearless-probe-downgrade-20260909.md` | `PROGRESS-rectification-yearless-probe-downgrade-20260909.md` | 产品拍板:无年月性格题(D9/D10 风格、月宿边界)降级为平局裁决——带年月题问完且候选仍分不开才问,分值减半(±1)、不计淘汰,报告标「参考」;三列卡性格描述不动;新增离线命中率测量脚本 + 导出 SQL(有出生证用户,聚合);Skill 10.0.21 | 已验收通过 | `fef54b6e`BUG-629Skill 10.0.21 |
| `TASK-rectification-followups-20260909.md` | `PROGRESS-rectification-followups-20260909.md` | 验收补漏:申报时段拦截只看钟点样式,带钟点的经历(『20:00 左右分手』『3 点到 5 点被车撞』)会被吞(BUG-631);by_time 只算引擎前 9 个候选,一小时窗 17 个候选时卡片列写『还没对照』(BUG-632) | 待验收 | `codex/rectification-followups-20260909`BUG-631632 |
| `TASK-rectification-evidence-turn-empty-answer-20260910.md` | `PROGRESS-rectification-evidence-turn-empty-answer-20260910.md` | 证据轮模型无正文被判整轮失败:证据、评分、下一问都已落库却只剩『没有拿到下一个问题』(BUG-633);答题旁白只说『范围没变』、时间线写死『还在收窄』(BUG-634) | 待验收 |
| `TASK-rectification-unwritten-evidence-claim-20260910.md` | `PROGRESS-rectification-unwritten-evidence-claim-20260910.md` | 证据轮模型只说『记下了』却没调 batch、没设下一问,财务一件静默丢失、流程停在原题且照常扣点(BUG-635);校正流不识别 Mastra schema 拒绝信封,会把被拒的 batch 报成 completedBUG-636 | 待执行 | `codex/rectification-unwritten-evidence-claim-20260910` |
| `TASK-rectification-latent-audit-20260910.md` | `PROGRESS-rectification-latent-audit-20260910.md` | 顺带审计:账本派生同年键误杀 `known_event_quality`BUG-637BUG-389 复发);双轨一致性按 31 分钟逐分判冲突并降置信度、与报告两套口径(BUG-638);不可分宽度按簇代表分钟少算(BUG-639);引擎与推断层两套代表分钟、回执宫位表不是卡片那一分钟(BUG-640);另 8 条观察项 | 待执行 | `codex/rectification-latent-audit-20260910` |
| `TASK-rectification-unwritten-evidence-claim-20260910.md` | `PROGRESS-rectification-unwritten-evidence-claim-20260910.md` | 证据轮模型只说『记下了』却没调 batch、没设下一问,财务一件静默丢失、流程停在原题且照常扣点(BUG-635);校正流不识别 Mastra schema 拒绝信封,会把被拒的 batch 报成 completedBUG-636 | 待验收 | `codex/rectification-unwritten-evidence-claim-20260910` |
| `TASK-rectification-latent-audit-20260910.md` | `PROGRESS-rectification-latent-audit-20260910.md` | 顺带审计:账本派生同年键误杀 `known_event_quality`BUG-637BUG-389 复发);双轨一致性按 31 分钟逐分判冲突并降置信度、与报告两套口径(BUG-638);不可分宽度按簇代表分钟少算(BUG-639);引擎与推断层两套代表分钟、回执宫位表不是卡片那一分钟(BUG-640);另 8 条观察项 | 待验收 | `codex/rectification-unwritten-evidence-claim-20260910` |
| `TASK-rectification-collect-semantics-20260910.md` | `PROGRESS-rectification-collect-semantics-20260910.md` | 产品决策三改:Skill 去掉「财务/健康只有主动说才问」并清空 Python `VOLUNTEER_ONLY`,升 10.0.22(BUG-641);带年份线索的采集题优先、题干带线索不矛盾,推翻 BUG-539 的整句去前缀(BUG-642);删掉「没有/记不清」原字匹配,分类器分 no/unsure 两类(BUG-643 | 待执行 | `codex/rectification-collect-semantics-20260910` |
### 聊天主链路与首页
@@ -71,8 +71,8 @@ BUG-278 已实测:`createTool` 的 `inputSchema` 校验失败时**不抛**
### T1 · 路由把「本轮应有写入」传给运行器(BUG-635)
- `route.ts` collect 焦点分支:分类结果落到 Agent 的两种情形计算 `expectedWrite`其他路径传 `null`,由运行器按年份正则兜底
- `V9AgentRunOptions` 新增 `expectedWrite?: "evidence" | null`
- `route.ts` collect 焦点分支:分类结果落到 Agent 的两种情形计算 `expectedWrite`非 collect 的 `message` 路径也对原话跑同一分类器。分类失败两次则 `"unknown"`,守卫 fail-open,不用年份正则。
- `V9AgentRunOptions` 新增 `expectedWrite?: "evidence" | "none" | "unknown"`
- 验收:单测覆盖 `provide_new_evidence``"evidence"``answer_current_focus` + `has_new_dated_event``"evidence"``answer_current_focus` + `no` 不进 Agent(既有);分类器两次都抛错 → `"unknown"` 且守卫不触发、诊断里有记录;源码合同断言 `agent-run.ts` / `route.ts` 不含年份正则。
### T2 · 收尾守卫:无写入的「记下了」不得交付,可重试一次(BUG-635)