docs(tasks): P0 stale-compare fix brief (BUG-575~578)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
Jesse_Chen
2026-09-07 06:14:21 +00:00
parent ed0cd087d7
commit 4716fe46b3
2 changed files with 93 additions and 0 deletions
+1
View File
@@ -68,6 +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-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 |
@@ -0,0 +1,92 @@
# TASK · 生时校正修复单:答完性格卡后每次候选比较都静默失败,快照过期把对话拖进死胡同(2026-09-07)
- 基线:`origin/staging` @ `7cf7705a`(代码头 `8e31680b`,staging 已部署该版本,事故就是这版跑出来的)
- 分支:`codex/rectification-stale-compare-fix-20260907`
- 执行方:coding agent;验收:Claude
- 涉及文件:`frontend/src/lib/rectification-agentic/v9/inference-adapter.ts``engine-client.ts``frontend/src/mastra/rectification-v9-tools.ts``frontend/src/lib/rectification-agentic/core/rectification-decision.ts``v9/answer-choice.ts``v9/method-followup.ts``holdoutFollowupFor`)、`v9/agent-run.ts``scripts/rectification/contracts.py``frontend/src/lib/rectification-agentic/user-copy.ts`
- BUG 编号起点:**BUG-575**BUG-574 已被报告列表修复占用;开工时 `grep -o "^## BUG-5[0-9][0-9]" docs/BUG_HISTORY.md | tail -1` 复核)
- 优先级:**P0**,先于 `docs/testing/rectification-scenarios-20260907.md` 的走查;不修这条,走查全部作废
## 1. 事故实证(staging2026-09-07,产品负责人实测;Case JSON 已由产品负责人提供,本单只写结构,不写年份与经历)
对话走向:开场 → 三条口述采集(学业、感情、工作,共 5 件带年月事件)→ 引擎比较成功(21.6 s)→ 两张性格卡(D9 相处方式、D10 做事风格)→ 三张年月存在卡 → 家人拒答 → 财务采集(1 件)→ 搬家采集(1 件)→ 健康拒答 → 职业口述 → 助手说"也再说一件你能记得大致时间的事吧" → 用户点「先这样,先看当前范围」→ 助手写了一段"再问下去也分不开 X 和 Y…下面是这次的结果" → **没有候选卡**,界面只有"目前范围 X–Y,还在收窄"和"没有拿到下一个问题"。
Case JSON 里的关键事实:
| # | 事实 | 位置 |
| --- | --- | --- |
| 1 | `latest_result.createdAt` 停在第 3 条口述之后;财务与搬家两轮的 `rectification-compare-candidates` 都是 `status: failed, detail: {error: "tool_failed"}`,耗时 28 ms / 37 ms(不是引擎超时,是请求被拒) | `turns[].receipt.tool_activities` |
| 2 | 财务轮之后 `askedProbeKeys` 里带上了两张性格卡的 `candidate_split_hash`,形如 `04:45-05:15:<9 个分钟>:varga.d9.<分组>`**长 128 字符**nakshatra 边界探针的 hash 126 字符 | `inference_state.answered_probes[].candidate_split_hash` |
| 3 | 引擎 `normalize_rectification_request``asked_probe_keys` 每条限 **120 字符**,超限直接 `ValueError``BadRequest` → 工具抛错 | `scripts/rectification/contracts.py` L339 |
| 4 | TS 把 `probe_id``semantic_key``candidate_split_hash` 三种键都塞进 `asked_probe_keys` | `inference-adapter.ts::askedProbeKeysFromReceipt` L5355BUG-559 引入) |
| 5 | 于是:**任何 varga_style / nakshatra 卡答过之后,后续每一次比较都失败**;账本已有 7 件事,`latest_result` 只算了 5 件,`evidenceLedgerFingerprint` 与当前账本不等 → `snapshotCurrent=false` | `decision-from-dossier.ts::scoreableSnapshotCurrentFromDossier` |
| 6 | `decideRectification``snapshotCurrent === false` 分支排在 `userStopped` 之前:用户点了停止、`case.status=paused`,决策仍是 `collect``type: ask_fact_collection``session_outcome: collect_evidence``can_adopt: false` → 卡片条件 `ADOPT_OUTCOMES` 不满足 → 无卡;`current_question: null` → 客户端显示"没有拿到下一个问题" | `rectification-decision.ts` L285290`rectification-surface-state.ts` L31 |
| 7 | 工具失败被 Agent 吞掉:助手正文仍写"这条记下了…对缩小候选范围很有帮助",用户不知道比较根本没跑 | `rectification-v9-tools.ts` L2073 只记 receipt 后 `throw`agent-run 继续作答 |
| 8 | "也再说一件你能记得大致时间的事吧"来自 `oos_blind:holdout` 焦点(`kind: reverse_verify`):`holdoutFollowupFor` 从过期 `oos_blind_prompts` 里取了"财务这条线还没用过"(账本其实已有财务事件),模型把它改写成泛泛的"再说一件事" | `method-followup.ts` L16841694`next_user_action.user_meaning` |
| 9 | 采集轮正文重复:第 1、2 轮助手文字各出现两段近似复述;工作采集题在正文出现一次、问题块又出现一次 | `turns[2].text``turns[4].text` |
## 2. 根因
1. **BUG-575P0**`asked_probe_keys``candidate_split_hash` 也传给引擎,varga/nakshatra 的 hash 超过引擎 120 字符上限,比较请求被 400 拒绝。BUG-559 只在 `test_rectification_event_probes.py` 用短键测过,没有用真实 split hash 测过整条 compare。
2. **BUG-576P1**:比较失败没有任何用户可见后果:receipt 只有 `tool_failed`,没有错误码;Agent 继续写"记下了、很有帮助";下一轮也不重试。
3. **BUG-577P1**`decideRectification` 让"快照过期"压过"用户已停止",停止后交付不出来;`persistNextInterviewIfIdle` 的停止路径也不先重算。
4. **BUG-578P1**`holdoutFollowupFor` 只排除拒答领域,不排除账本里已有事件的领域,而且用的是可能过期的 `oos_blind_prompts`;焦点是 `reverse_verify`,题干交给模型自由改写,成了"再说一件事"。
5. **P2**:采集轮正文双写;题干进正文。
## 3. 决策记录
1. `asked_probe_keys` 只传 `semantic_key`(去重、≤120),不传 `probe_id``candidate_split_hash`;引擎侧上限提到 200 并对超长键**跳过而不是拒绝**(记 `dropped_asked_probe_keys` 计数进 receipt)。TS 侧再加一层保险:超过 200 的键在 `engineRequestBody` 里过滤并 `console.warn`
2. 比较失败必须可见:receipt `detail``safe_error_code` 与引擎返回的错误文本前 120 字;agent-run 在工具失败后给正文追加服务端固定句"候选比较这次没跑成,下一句话时会自动再试";下一轮 `persistNextInterviewIfIdle``snapshotCurrent=false` 且账本有可评分事件时先重算一次(复用 `rescoreMinuteAfterWindowChange`),再决策。
3. 用户停止优先于快照过期:`decideRectification``userStopped && ranked.length>0` 提到 `snapshotCurrent` 判断之前;停止路径(route 的 `stop_and_review``applyRectificationChoice` STOP)在决策前先尝试重算,重算失败则按上一次有效结果交付,旁白加一句"这是按上一次成功比较给出的范围"。
4. `holdoutFollowupFor`:候选领域 = `oos_blind_prompts`**既未拒答、账本里也没有该领域带年月事件** 的领域;没有则 holdout 记为 `unavailable`,直接进交付;题干用服务端固定句(`USER_COLLECT_QUESTION[domain]` 同源,不给模型改写),焦点 kind 改为 `collect_spoken` 而不是 `reverse_verify`
5. 正文双写:`composeCollectSpokenAssistantText` 与模型正文若都包含采集题,只保留问题块;模型正文里与 `spokenPrompt` 相同或高度相似(前 12 字相同)的句子删除;两段复述的根因要查(`phases``intent.classified` 出现两次),进度记录写明。
## 4. 硬红线
- 不动采用门、确认门、`MIN_SEPARATION_LEAD``_relative_support`
- `minute_step=1` 路径字节不变(指纹用例)。
- BUG-559 的去重用例照常通过(去重靠 `semantic_key`,不靠 hash)。
- 测试总数 ≥ 1899tsc 0 错;lint 0 error。
- 任务书 / 进度 / Bug 历史不得写入产品负责人 Case JSON 里的年份、经历、Case ID。
## 5. 任务分解
### 5.1 BUG-575
- `askedDiscriminatorKeys` 拆成两份:`askedSemanticKeys`(给引擎)与现有全量(给 TS 去重);`engineRequestBody` 只用前者并过滤 >200`contracts.py` 上限 200、超长跳过、计数进 `decision_receipt.dropped_asked_probe_keys`
- 验收:`tests/test_rectification_input_contract.py`——128 字符键不再报错、被跳过并计数;TS `rectification-v9-engine-contract.test.ts`——用真实 varga split hash(≥128 字符)构造 receipt`engineRequestBody.asked_probe_keys` 全部 ≤120 且不含 `:varga.`**端到端**`rectification-probe-year-dedupe` 加一例——答过 D9 卡后 compare 请求体合法。
### 5.2 BUG-576
- receipt failed detail 加 `safe_error_code` + `engine_message`(截 120 字,不含用户资料);agent-run 工具失败 → 正文追加固定句;`persistNextInterviewIfIdle``inspectNonTerminalTurnExit``snapshotCurrent=false` 时先重算一次(有节流:同一证据指纹只重试一次,记 `rescore_attempted_fingerprint`)。
- 验收:`rectification-exhaustion-exit` 或新文件——比较抛错的回合正文含固定句;下一回合看到过期快照会调用一次 `runV9CandidateScore`,成功后决策不再是 `collect`
### 5.3 BUG-577
- `decideRectification` 顺序调整(决策 3);stop 路径先重算。
- 验收:`rectification-decision-authority.test.ts` 三栏——`snapshotCurrent=false && userStopped && ranked>0``completeWithRange(user_stopped)``session_outcome ∈ ADOPT_OUTCOMES``rectification-window-widen` 或 answer-choice 测试——STOP 在过期快照上先重算再交付。
### 5.4 BUG-578
- `holdoutFollowupFor` 过滤已有事件领域;无可问领域 → `holdoutValidation="unavailable"`;焦点改 `collect_spoken` + 服务端题干。
- 验收:`rectification-choice-card``collect-direction` 测试——账本已有财务事件且家人/健康拒答 → 不出 holdout 题,决策直接交付;有可问领域时题干等于 `USER_COLLECT_QUESTION[domain]`,不是"再说一件事"。
### 5.5 P2 正文双写
- 定位 `intent.classified` 双触发与正文拼接;去重规则见决策 5。
- 验收:`rectification-spoken-collect.test.ts`——采集轮最终正文不含与 `spokenPrompt` 重复的句子;同一段复述不出现两次。
### 5.6 记录
- `docs/BUG_HISTORY.md` BUG-575578575 标"复发自 BUG-559");`CHANGELOG.md``PROGRESS-rectification-stale-compare-fix-20260907.md``docs/testing/rectification-scenarios-20260907.md` 加第 0 条"答完性格卡后再说一件事,顶部范围必须变化或旁白说明比较结果"。
## 6. 让步顺序
5.1 单独就能解封线上,**当天必须合入并部署**;5.2 + 5.3 其次;5.4 必做;5.5 可后置到同分支第二次提交;5.6 不可省。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-stale-compare-fix-20260907 .worktrees/rectification-stale-compare-fix-20260907 origin/staging
cd .worktrees/rectification-stale-compare-fix-20260907
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
ln -s /workspace/Jyotisha/.venv .venv
.venv/bin/python -c "print(len('04:45-05:15:04:47,04:51,04:53,04:59,05:00,05:06,05:08,05:13,05:15:varga.d9.04:47|04:51/05:00|05:06|04:59|04:53/05:08|05:13|05:15'))" # 128
.venv/bin/python -m pytest tests/test_rectification_input_contract.py tests/test_rectification_v5_services.py -q
cd frontend && ls tests/rectification-*.test.ts tests/consultation-*.test.ts tests/agent-voice-copy-contract.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"
```