Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
9.3 KiB
9.3 KiB
TASK · 带年月题问完不等于收敛:交付前先按剩余候选再出题、再定向补事(2026-09-11)
- 基线:
origin/staging@9941c34d(已部署;代码f870d3d7,含 BUG-651/652) - 分支:
codex/rectification-narrow-before-deliver-20260911,基于origin/staging - 执行方:coding agent;验收:Claude
- BUG 编号起点:BUG-653(开工时
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1核对) - 不改确认门、不改
SCORE_DELTA、不改MIN_ACCEPTANCE_*;Skill 若改用户可见流程升 10.0.24
1. 事故实证(2026-09-11 staging 真机,代码 f870d3d7)
同一序列(两件学业 + 实习入职/离职 + 六道带年月选择题 C/C/C/B/A/C)跑完后,系统按 BUG-651 直接出了三列区间卡:范围 04:48–05:07(20 分钟,5 个簇),头名 04:53 只有 29%,第二名 05:06 22%,第三名 04:50 18%。产品负责人原话:「没到出卡的时候就开始出卡了」「范围没缩短多少,这咋能算结束」。
回执事实:
| 字段 | 值 | 含义 |
|---|---|---|
latest_result.createdAt |
08:10:54(记完实习那一轮) | 之后六道题都是 TS 侧推理,引擎没有再跑 |
inference_state.probes |
7 道带年月(事业 3、感情 2、迁居 1、学业质量 1)+ 7 道无年月 + 月宿 | 引擎一次性生成,MAX_PROBES=8、每域 ≤3、每域每年 1 道 |
| 家人 / 财务 / 健康三条线 | 0 道带年月题;oos_blind_prompts 列出感情、家人、财务「校时还没用过」 |
("family","existence") 先验 yes=0.85 > DOMINANT_ANSWER_PRIOR=0.8,家人存在题全部被 dominant_answer_prior 丢弃;财务无有效换运边界 |
| 剩余 5 簇的结构差异 | 04:52 D9 换升;05:00 D10 / D4 / D12 / D24 / D2 同时换升;05:06 D24;05:07 D11 | 能切开 04:48–04:59 与 05:00–05:07 的是事业 / 迁居 / 家人 / 学业 / 财务 这些线上的带年月事件,不是性格题 |
| 收敛门 | lead = 16 vs 15,top share 29% | 远未收敛;rounds 六轮全 informative,无平台期 |
2. 根因
- 探针池是静态的:
event_probes.py的区分探针只在引擎跑(账本变化 →rescoreStaleMinuteSnapshotIfNeeded→rescoreMinuteAfterWindowChange)时按初始 9 簇生成一次;选择题答完后 TS 侧只更新候选分数,不会拿剩余候选去引擎再生成一批。六道题一问完池就空,BUG-651 于是交付。 - 生成上限与先验把可用题掐掉了:
MAX_PROBES=8、MAX_PROBES_PER_DOMAIN=3、_best_probe_per_year每域每年只留一道;家人存在题被 0.85 的先验整体丢弃(没有任何家人题曾出现过)。 - 交付前没有定向补事:重设计单 S1 的锚定追问在训练门开后就停了;S2 池空直接 S3。可是回执里
oos_blind_prompts已经写明感情 / 家人 / 财务三条线没用过,而这三条线正是 05:00 换升边界所需要的。 - 卡片文案把「当前范围」写成「结束」:「这次给出的范围 04:48–05:07 · 对照了 4 件经历」+ 三个「更像这个」按钮,没有一句「还能再收窄:…」。BUG-651 单 T5 的「再补什么」句在真机卡片上没有出现(待 T0 核实是没落还是被裁)。
3. 决策记录(产品负责人 2026-09-11)
- 带年月题池空 ≠ 结束。池空后先做两步再交付:(a) 拿剩余活跃候选去引擎刷新一批带年月探针(保持候选集与已答题,
keepAnswers);(b) 刷新后仍无题,则按剩余候选的换升层做定向补事(只问能切开剩余候选的那几条线,带具体例子,不带推算年份)。用户给了新事 → 重算 → 回到 S2;用户说「没有了 / 就这些」→ S3 交付。 - 交付条件改为:收敛门开,或(刷新后带年月池空 且 定向补事被拒/池空),或用户主动停。BUG-651 的「池空即交付」收窄为「刷新与定向补事都用尽后交付」。「永远给结果」不变:任何时候用户说停都出卡。
- 家人存在题的先验丢弃只对无年月 / 宽窗题生效:带具体年月的家人换运边界题(
dasha_boundary,月级)不再因 0.85 先验被整体丢弃;先验只用于排序。 - 卡片文案:标题「目前范围 04:48–05:07(对照了 4 件经历)」;卡下必有一句「还能再收窄:如果记得 …(来自定向补事池的首条)」;「更像这个」按钮保留。禁用「这次给出」「结束」「最终」。
- 不承诺分钟级:4 件月精度经历本身分辨力有限,本单目标是把「引擎已生成的 8 道题问完」变成「剩余候选能问的都问完、能补的都补完」,不是保证 5 分钟。
4. 硬红线
- 探针刷新不得改变
candidate_set_id/ 已答题;重算必须走现有rescoreMinuteAfterWindowChange(keepAnswers: true)的回放路径(BUG-587 / BUG-594 防复发)。 - 刷新时
asked_probe_keys必须带上全部已答键,同域同年去重(BUG-559)照旧。 - 定向补事题干不得带出生年推算年份(重设计单 §4);例子只从「能切开剩余候选的层」对应的领域取。
- 性格题仍按 BUG-651 决策 2:只作卡后可选,不进本单的刷新池。
- 不得静默返回空载体(BUG-652 不变量)。
- 既有测试总数不降;改断言写原值 / 新值 / 原因。
5. 任务分解
- T0 核实:回放 §1 序列,打印交付时
range_delivery与 host 正文,确认 BUG-651 T5 的「再补什么」句是否落下;未落则并入 T4 修。 - T1 探针刷新(BUG-653):新增
refreshDiscriminatorProbes(caseId):带年月池空且未收敛时,用inference_state.candidates[status=active]作candidate_times、全部answered_probes作asked_probe_keys调引擎的探针生成(build_discriminating_event_probes走_remaining_contexts分支),把新探针并入inference_state.probes(不动 candidates / rounds / answered)。触发点:persistNextInterviewIfIdle与persistNextInterviewAfterChoice在discriminatorProbe=null时先刷新一次再决策;每个candidate_set_id最多刷新 2 次(写进inference_state.refresh_count)。验收:§1 序列第六题答完后先出现新的带年月题(预期落在家人 / 财务 / 迁居 2015–2026 的换运边界),rounds继续为 informative;刷新不改candidate_set_id。 - T2 引擎侧放开(BUG-653):
event_probes.py(a) 剩余候选 ≤5 时MAX_PROBES_PER_DOMAIN提到 4、MAX_PROBES提到 12(只在candidate_times给定的刷新调用生效,首轮不变);(b)_dominant_existence_prior对source=dasha_boundary且带月份的家人题不丢弃,只降序。验收:tests/test_candidate_discriminator_contract.py新增剩余 5 簇夹具,家人月级边界题出现在 public 里。 - T3 定向补事(BUG-654):刷新后仍无带年月题 →
collection-question-pool.ts新增targetedCollectPool(remainingLayers, evidence, declined):按LAYER_DOMAIN把剩余候选的换升层映射到领域,排除已拒答 / 已覆盖,生成一条带 ≥2 个具体例子的口述题(例如「还有两条线能把 04:50 和 05:06 分开:家里哪年添丁或长辈住院、哪年收入明显变过。记得哪件说哪件,年月大概就行。」),焦点collect:targeted:<domain>;答「没有了」→ 移除并进 S3。验收:§1 序列在刷新池空后先出这条题;答一件家人事 → 引擎重算、回到 S2;答「没有了」→ 出卡。 - T4 交付条件与卡片文案(BUG-654):
decideRectification的!separation.sufficient && !probe分支在refreshExhausted && targetedCollectExhausted之前不得走completeWithRange / finish;卡片标题改「目前范围」,卡下「还能再收窄」句取targetedCollectPool首条;agent-voice-copy-contract加禁词「这次给出」「最终」。 - T5 记录:BUG-653/654 入历史(关联 BUG-651、BUG-629、BUG-559、BUG-587);CHANGELOG;PROGRESS;
docs/testing/rectification-scenarios-20260907.md加「六道题问完必须先刷新再补事再出卡」。
6. 让步顺序
T2(a) 的上限放宽可先只做刷新(T1)看真机效果;T2(b)、T3、T4 不可省。
7. 开工前置命令
git fetch origin --prune
git worktree add -b codex/rectification-narrow-before-deliver-20260911 .worktrees/rectification-narrow-before-deliver-20260911 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 652
cd frontend && npm test -- tests/rectification-probe-pool-exhausted-20260911.test.ts tests/rectification-replay-20260911.test.ts tests/rectification-probe-replay-loss-20260908.test.ts tests/rectification-collection-question-pool.test.ts
cd .. && .venv/bin/python -m pytest tests/test_candidate_discriminator_contract.py -q
8. 验收口径
- 前端
tsc --noEmit0 错、npm run lint0 error、相关套件 fail=0、测试总数 ≥ 基线;next build后/仍 Static、首屏 gzip ±2%。 - Python:
tests/test_candidate_discriminator_contract.py、tests/test_event_probes*.py;run_quality_gate.py --profile quick。 - 部署后真机:按 §1 序列走,第六题答完不得直接出卡;先出新的带年月题或定向补事题;说「没有了」后出卡,卡片标题为「目前范围」且带「还能再收窄」句。