fix(rectification): keep compare requests valid after style cards (BUG-577–580)

Engine asked_probe_keys no longer include varga split hashes that 400 the scorer, failed compares become visible and retry, user stop can still deliver a range on a stale snapshot, and holdout no longer reasks domains already in the ledger.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-07 15:46:33 +08:00
parent 3f4d38d485
commit 4e0db55f03
34 changed files with 1079 additions and 156 deletions
+65
View File
@@ -8934,4 +8934,69 @@
- 复发自:无
- 修复版本:`e87c58d6e48850749ea9e0a92e532133db03e292`
## BUG-577 | 性格卡答过之后,后续每次候选比较都被 400 拒绝
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:`asked_probe_keys``normalize_rectification_request``rectification-compare-candidates``inference-adapter.ts`
- 用户现象:性格对照卡或月宿边界卡答完后,再说一件事时候选比较立刻失败(约 30ms,不是引擎超时)。顶部范围不再随新经历变化。
- 触发条件:答过带 `candidate_split_hash` 的性格/边界卡后,再走候选比较。
- 根因:BUG-559 把 `probe_id``semantic_key``candidate_split_hash` 都塞进引擎 `asked_probe_keys`。性格卡 hash 约 128 字符,引擎原上限 120,整单请求 400。去重仍应靠 `semantic_key`,不该把 hash 传给引擎。
- 修复:引擎请求只传 `semantic_key` 与账本年份键;TS 再丢掉 `>200` 或含 `:varga.` 的键。引擎上限改为 200,超长键跳过并在 receipt 计数 `dropped_asked_probe_keys`,不再 400。128 字符键在新上限下会保留(决策是提上限,不是跳过 128)。
- 验证:`tests/test_rectification_input_contract.py``frontend/tests/rectification-v9-engine-contract.test.ts``frontend/tests/rectification-probe-year-dedupe-20260906.test.ts`
- 防复发:比较请求体不得含 `:varga.` hash;引擎契约须覆盖真实长度的 split hash,不能只用短键。
- 相关记录:BUG-559、BUG-578、BUG-579、BUG-580
- 复发自:BUG-559
- 修复版本:待合入 SHA
## BUG-578 | 候选比较失败被吞掉,过期快照也不自动重算
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:`rectification-compare-candidates` receipt、`agent-run.ts``persistNextInterviewIfIdle`
- 用户现象:比较已经失败,助手仍写「这条记下了、很有帮助」。用户看不到失败,下一轮也不重试。
- 触发条件:比较工具抛错(含引擎 400)后 Agent 继续作答。
- 根因:失败 receipt 只有笼统 `tool_failed`;Agent 不追加可见说明;闲置下一问不因快照过期重算。
- 修复:失败 detail 带 `safe_error_code` 与引擎信息前 120 字;正文追加「候选比较这次没跑成,下一句话时会自动再试。」;闲置持久化若快照过期且账本有可评分事件,同一证据指纹只重算一次。
- 验证:`frontend/tests/rectification-stale-compare-fix-20260907.test.ts`
- 防复发:比较失败必须有用户可见固定句;过期快照的闲置路径必须先重算。
- 相关记录:BUG-577
- 复发自:无
- 修复版本:待合入 SHA
## BUG-579 | 用户停止被过期快照压过,点「先这样」后没有候选卡
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:`decideRectification``applyRectificationChoice` STOP、`stop_and_review`
- 用户现象:点「先这样,先看当前范围」后出现「再问下去也分不开」一类旁白,但没有候选卡,界面写「没有拿到下一个问题」。
- 触发条件:账本已比最新比较结果多出可评分事件(快照过期)时停止。
- 根因:`snapshotCurrent === false` 排在 `userStopped` 之前,停止后仍判采集;停止路径也不先重算。
- 修复:有排序候选时,用户停止优先于快照过期,进入 `complete_with_range``session_outcome` 属于可出采用卡的集合。停止路径先重算;重算失败则按上一次成功比较交付,旁白加「这是按上一次成功比较给出的范围。」
- 验证:`frontend/tests/rectification-decision-authority.test.ts``frontend/tests/rectification-stale-compare-fix-20260907.test.ts``frontend/tests/rectification-answer-choice.test.ts`
- 防复发:`snapshotCurrent=false && userStopped && ranked>0` 必须交付,不得回到采集。
- 相关记录:BUG-577、BUG-578
- 复发自:无
- 修复版本:待合入 SHA
## BUG-580 | Holdout 题用过期盘外领域,被改写成「再说一件事」
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:`holdoutFollowupFor`、采集题干
- 用户现象:账本里已经有该领域带年月的事,助手仍问「也再说一件你能记得大致时间的事」。
- 触发条件:过期 `oos_blind_prompts` 里的领域其实已在账本;焦点按盘外核对给模型改写。
- 根因:holdout 只排除拒答领域,不排除账本已有带年月事件的领域;题干不是服务端固定采集句。
- 修复:候选领域须既未拒答、账本也没有该领域带年月事件。没有可问领域则不再出 holdout 题。题干用 `USER_COLLECT_QUESTION[domain]`,焦点为口述采集,不给选择题框。决策层不因此改成 `unavailable`,以免走成无卡的 offer。
- 验证:`frontend/tests/rectification-collect-direction-20260904.test.ts`
- 防复发:账本已有财务等带年月事件时不得再出该领域 holdout;可问时题干必须等于对应采集固定句。
- 相关记录:BUG-577
- 复发自:无
- 修复版本:待合入 SHA
@@ -0,0 +1,39 @@
# PROGRESS · 生时校正过期比较修复(2026-09-07)
分支:`codex/rectification-stale-compare-fix-20260907`
任务书:`docs/tasks/TASK-rectification-stale-compare-fix-20260907.md`
编号:任务书写 BUG-575~578;开工时 `docs/BUG_HISTORY.md` 已占用 575(悬停/步骤条)、576(长报告附录)。本单落地为 **BUG-577580**
## 做了什么
- **BUG-577**:引擎比较只传 `semantic_key` + 账本年份键;丢掉 `candidate_split_hash` / `:varga.` / 超 200 字符键。Python 上限 200,超长跳过并计数 `dropped_asked_probe_keys`。128 字符键在新上限下保留(任务书 5.1 写「跳过 128」与决策 1「上限 200」冲突,按决策 1)。
- **BUG-578**:比较失败 receipt 带 `safe_error_code` + `engine_message`(120 字);Agent 正文追加固定句;闲置/非终止退出在快照过期时先重算一次(同 case+指纹+turn 只一次)。
- **BUG-579**`userStopped && ranked>0` 排在 `snapshotCurrent === false` 之前。STOP / 无焦点 `stop_and_review` 先重算;重算失败旁白「这是按上一次成功比较给出的范围。」
- **BUG-580**holdout 排除账本已有带年月事件的领域;无可问则不再出 holdout 题。题干 = `USER_COLLECT_QUESTION[domain]`,口述采集,无选择题框。不把该状态改写成决策层 `unavailable`(那条路径会变成 `offer_provisional_range` 而不是交付)。
- **P2**`composeCollectSpokenAssistantText` 去掉与题干前 12 字相同的复述句。`intent.classified` 仍可能因 `case.loaded` 两次出现;本单未拆 Agent 生命周期,记在偏离。
未改:采用门、确认门、`MIN_SEPARATION_LEAD``_relative_support``minute_step=1` 指纹身份。
## 三栏(被触碰断言)
| 用例 | 原值 | 新值 | 理由 |
| --- | --- | --- | --- |
| `four scoreable events skip declined OOS domain and ask education holdout` | `intent=out_of_sample_check` / `source=oos_blind` / `domain=education` | holdout 为 null;下一问不是已在账本的 education/finance | BUG-580 账本已有学业/财务不得再 holdout |
| `stop_and_review does not write an inference transition` | 旁白匹配「已记录你的选择」 | `session_outcome ∈ ADOPT_OUTCOMES``can_adopt=true`;仍不写 inference | BUG-579 停止必须能出卡 |
| Python 128 字符键 | 任务书 5.1:跳过并计数 | 保留,无 `dropped_asked_probe_keys` | 决策 1 把上限提到 200 |
## 测试
- Python `tests/test_rectification_input_contract.py` + `tests/test_rectification_v5_services.py`38 passed
- 任务书指定 TS 套件(`rectification-*.test.ts` + `consultation-*.test.ts` + `agent-voice-copy-contract.test.ts`,排除 database):1196 passed / 0 failed
- 全量 frontend `tests/*.test.ts`(排除 database):2814 passed / 0 failed(≥1899
- `frontend` `tsc --noEmit`0
- 指定 Python 契约:128 字符键保留、不 400
## 偏离
1. Bug 编号 577580,不是任务书的 575578。
2. 128 字符键保留,不跳过。
3. P2 只做了题干近重复删除;`intent.classified` 双触发未在本提交拆掉。
4. 预检:本机按 ERR-078 用项目 `.venv`;不把 `.workbuddy` 当主仓。
5. 无可问 holdout 领域时不把决策层改成 `unavailable`,只在提问层返回 null,避免误入 `offer_provisional_range`
+1 -1
View File
@@ -68,7 +68,7 @@
| `TASK-rectification-unknown-time-20260906.md` | `PROGRESS-rectification-unknown-time-20260906.md` | 完全不知道出生时间的两段式路线:`stage=block_scan` 以 10 分钟步长扫 24 小时只做事件计分、出五时段四选卡(不写账本不采用),选定后进现有分钟流程;引擎加 `minute_step`;开场读 `birth_time_clue`;删 intake 劝退文案 | 已验收(带修复单:时段支持度按段长偏置,下午段先天 25%,P1) | `814c924e`;修复单 `TASK-rectification-capability-fix-20260907.md`BUG-570 |
| `TASK-rectification-capability-fix-20260907.md` | `PROGRESS-rectification-capability-fix-20260907.md` | 能力补齐修复单:答后旁白把 `range_start/range_end`(搜索窗口)当范围比较,每题都说「范围没变」;`block_scan` 五段支持度按段内原始分求和,长时段先天占优(24/24/36/30/30 个候选);`TRACK_LABEL` 音译与产品 Vimshottari/Narayana 口径不一;BLK-001 写到 `docs/BLOCKED.md` 应回根目录 | 已验收通过(P2block_scan 15 s 壁钟断言在门禁里可能间歇红) | `517df002`BUG-569570);staging 未部署,需先 Migrate Staging Database |
| `TASK-rectification-declared-uncertainty-20260907.md` | `PROGRESS-rectification-declared-uncertainty-20260907.md` | 出生时间「有多确定」只在 intake 问一次:三档(医院记录 / 家人大概 ±15·30·60·120 / 时段或未知),校正窗口读档案(现在有钟点一律 ±15,声明值被忽略,BUG-571);吻合率 <60% 且代表分钟贴窗口边缘时出服务端一键放宽卡,放宽后重算并写回档案(BUG-572);窗口 >120 分钟先切三子段迭代到 ≤120 再进分钟(BUG-573);日级事件问一次可靠度;四个脚本化手测场景进 docs/testing | 已验收通过(2 P3 建议:可靠度正则去掉「记得」、档案写回改走 account-profile-patch | `8e31680b`BUG-571573);迁移 `20260907020000` 待应用,部署前先 Migrate Staging Database |
| `TASK-rectification-stale-compare-fix-20260907.md` | `PROGRESS-rectification-stale-compare-fix-20260907.md` | **P0**BUG-559 把 128 字符的 `candidate_split_hash` 塞进 `asked_probe_keys`,引擎限 120 直接 400,答完任何性格/边界卡后每次候选比较静默失败receipt 只有 tool_failed),快照永远过期;`decideRectification` 让快照过期压过用户停止,点「先这样」后无卡、「没有拿到下一个问题」holdout 用过期 oos 领域被模型改写成「再说一件事」;采集轮正文双写 | 待领取(当天须合入部署) | `codex/rectification-stale-compare-fix-20260907`BUG-575578 |
| `TASK-rectification-stale-compare-fix-20260907.md` | `PROGRESS-rectification-stale-compare-fix-20260907.md` | **P0**BUG-559 把过长 `candidate_split_hash` 塞进引擎比较键,答完性格/边界卡后比较静默失败;快照过期压过用户停止;holdout 用过期领域被改写成「再说一件事」;采集轮正文双写 | 待验收(当天须合入部署) | `codex/rectification-stale-compare-fix-20260907`BUG-577~580;任务书仍写 575578,开工时 575/576 已被占用 |
| `TASK-api-not-configured-mislabel-20260904.md` | `PROGRESS-api-not-configured-mislabel-20260904.md` | 16 处路由把数据库瞬断(部署切换窗口)兜底翻译成 503「服务尚未配置」;改为仅配置错误用该文案,其余 `service_unavailable`,收敛为共享 helper | 已验收 | `5483649b`(BUG-542);2 条子进程测试留 CI Node 22 复核 |
| `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505509 |
@@ -2,7 +2,19 @@
虚构走查。不要填真实姓名、出生资料或真实经历。对应 `TASK-rectification-declared-uncertainty-20260907.md` 决策 9。
测之前先看 `rectification-declared-uncertainty-20260907.md` 第 0 条。
测之前先看本文件第 0 条。
## 0. 性格卡之后再说一件事
资料:家人记得大概时间,钟点任意,范围「差不多准」。地点任意公开城市。
开场后依次说三件不同领域、带年月的虚构经历,等到出现候选比较和性格对照卡。答完一张性格卡后,再说一件带年月的事。
期望:
- 顶部范围必须变化,或旁白说明这次比较没跑成、下一句会再试
- 不得出现:比较失败后仍只写「记下了、很有帮助」,顶部范围完全不动
- 点「先这样,先看当前范围」后必须出现候选卡或当前范围,不得只剩「没有拿到下一个问题」
## 1. 范围:家人说两点到四点