d5a8db0a 把 tied_first 早退放到 coverageBlocks 之前且没有 probe 守卫,新案子
答两题、区间仍是整个开局窗口就判 adopt_representative/can_adopt:true/代表
分钟(BUG-683)。配套测试传 discriminatorProbe: null,与实现语义不一致,所以
没拦住。另立 BUG-684(investigating):交付态下仍有画不出的死卡,delivered
终态因要求无题而不触发,界面仍印「没有拿到下一个问题」。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
10 KiB
TASK · 修复单:并列早退把第二题就判成"可采用",交付态下又出现死卡 — 2026-09-14
- 基线:
origin/staging@d5a8db0a(staging 已部署该 SHA)。 - 分支:
codex/rectification-tied-first-fix-20260914,worktree.worktrees/rectification-tied-first-fix-20260914。 - 关联:本单是
TASK-rectification-delivery-vs-collect-split-20260914.md(BUG-680/681/682,实现d5a8db0a)的回归修复单。BUG-680 的 D2 被实现得比任务书宽,造成线上更严重的问题。另关联 BUG-674(不可盖章探针 → 死卡)、BUG-671。 - 串行:改
core/rectification-decision.ts与其测试;与其他在途分支无重叠。
1. 事故实证(产品负责人 staging 真机,2026-09-14,部署 SHA d5a8db0a)
新建校正,只回答了两轮采集(2016-09 上大学 / 2020-06 毕业;2020-04 实习 / 2020-10 离职),助手随即问第三题「2023 年 5 月前后,你有没有入职、换工作,或者当时手上的职责明显变重?」,界面同时弹出 「没有拿到下一个问题。」+「接着问」按钮。
同一会话 GET 快照(已脱敏):
"interview": {
"type": "complete_with_range", "session_outcome": "adopt_representative",
"can_offer_range": true, "can_adopt": true,
"selection_allowed": true, "propose_allowed": true,
"precision_stage": "ready_to_adopt", "stop_reason": "tied_first",
"representative_time": "05:06", "credible_range": ["04:45","05:15"],
"collection_progress": { "scoreable": 3, "minimum": 3, "missing": 0 }
}
候选(9 个,前三名并列):05:06 = 13、05:08 = 13、05:14 = 13、05:03 = 12、04:59 = 11、04:46 = 10、04:53 = 10、05:15 = 10、04:50 = 8。
credible_range 就是开局窗口 04:45–05:15(30 分钟,完全没有收窄),系统却宣布 can_adopt: true、代表分钟 05:06、precision_stage: ready_to_adopt。
P0-1 · 并列早退缺少"还有题可问"的守卫(BUG-683)
d5a8db0a 在 decideRectification 里加的早退:
// core/rectification-decision.ts,插在 `if (coverageBlocks) {` 之前
if (
stopClass?.kind === "exhausted"
&& stopClass.reason === "tied_first"
&& input.trainingGateOpen !== false
&& separation.ranked.length > 0
) {
return completeWithRange(separation, holdout, range, "exhausted", capability, stopClass.reason);
}
问题有两层:
- 位置比任务书写的高一层。
TASK-…-delivery-vs-collect-split-20260914.md的 D2 原文是「decideRectification的!separation.sufficient分支里,把tied_first的判断提到stillNeedNarrowing之前」。该分支的第一行是if (probe) return discriminateOrExhaust(...),所以按任务书实现时"还有带年月探针可问"会先接管。实现把它提到了coverageBlocks之前、整个!separation.sufficient分支之外,于是连"还有探针"和"方法覆盖未达标"都绕过了。 - 缺
probe与定向补事守卫。 校正刚开始时,9 个候选分数都很接近,前两名并列是常态(本例第 2 轮就三方并列 13 分),此时tiedForFirst为真 → 直接交付。结果是任何新案子答两题就被宣布"可采用代表分钟",而区间等于整个开局窗口。
配套测试 frontend/tests/rectification-delivery-vs-collect-20260914.test.ts:213 传的是 discriminatorProbe: null,也就是说测试锁的是"没有探针可问时才交付",实现却没有这个条件——测试与实现的语义不一致,所以回归没被拦住。
P0-2 · 交付态下仍出现死卡,delivered 终态因此不触发(BUG-684,investigating)
d5a8db0a 新增的 interviewDeliveredGap 要求 questionMissing === true。本例屏幕上有一道问题(「2023 年 5 月前后…」),说明 current_question 不为空 → delivered 不触发;而这道题又没有可点选项 → interviewChoiceCardUnavailable 判死卡 → questionLoadFailed → 落到 unavailable → 印「没有拿到下一个问题」+「接着问」。
即:决策层已经终局(complete_with_range),对话层还在问题态,而这道题是一张画不出来的死卡。 这与 BUG-674 同源(焦点写得进、卡画不出,或反之)。
证据缺口:需要同一会话 GET 的 current_question、choice_card、question_source,以及最后一条助手 turn 的 offer_result_id。缺这四项之前,本条按 investigating 立号,不得臆断是哪条写入路径。
2. 根因
- BUG-683:并列(
tied_first)被当成"无论如何都问不下去"的终局信号,但它在采集早期只是"分数还没拉开"。终局的真正条件是没有带年月探针可问、且定向补事也问完。 - BUG-684:待取证(见上)。
3. 决策记录
| 决策 | 内容 |
|---|---|
| D1 | tied_first 早退必须同时满足:没有可问的带年月探针(probe === null)、定向补事不再有题(targetedCollectExhausted !== false)、训练门开、ranked 非空。 refreshExhausted 不参与该判断(它的落库不可靠,BUG-680 已证)。这修正上一单 D2 的表述含糊:不是"提到 stillNeedNarrowing 之前就完事",而是"在已经没有别的问法时才算终局"。 |
| D2 | 区间没有收窄就不得宣布可采用。 当 credible_range 等于开局候选窗口(首尾与 case.candidateRange 相同)时,即便走到交付,sessionOutcome 也只能是 provisional_range / completed_with_range,can_adopt 必须为 false:可以给并列区间、可以让用户看候选,但不得给"推荐采用的代表分钟"。这是 AGENTS.md Part B B4 的诚实边界,不得因为想让流程收尾而放宽。 |
| D3 | 测试与实现语义必须一致:tied_first 的交付测试要成对——有探针时不交付、无探针且定向补事问完时交付。上一轮只写了后者。 |
| D4 | BUG-684 本轮只做取证:在死卡分支加一条结构化日志(event: "rectification_delivered_state_dead_choice",带 session_outcome、stop_reason、question_id、has_choice_card),并把 delivered 终态的判定从"必须无题"放宽为"无题或当前题是画不出来的死卡"——交付态下不得再出现「没有拿到下一个问题」。根因确认后另立修复单。 |
| D5 | 不动数据库结构与迁移、不动 deploy/** 与 workflow、不动 page.tsx、不新增依赖。不得放宽任何置信度或确认边界。 |
4. 硬红线
tsc --noEmit0 错;npm run lint0 error;npm test失败清单与基线逐条一致;next build通过且/仍○ Static;首屏 gzip ±2%。- 新增断言必须是行为断言。
- 不得回退 BUG-680/681/682 的三项修复(刷新落库失败不算已尝试、交付话术与交付卡同真同假、
delivered终态)——本单只收紧tied_first的触发条件。 - 不得为了让并列局面"能收尾"而把
can_adopt或confirmation_allowed放宽。
5. 任务分解
任务 1 · 收紧 tied_first 早退(BUG-683,P0)
core/rectification-decision.ts:早退条件加&& !probe && input.targetedCollectExhausted !== false;位置维持在coverageBlocks之前即可(有了!probe守卫就不会抢走判别题)。- 验收标准(成对):
- 行为单测:并列 +
discriminatorProbe非空 → 决策不是complete_with_range,仍走判别/采集,can_adopt === false。 - 行为单测:并列 +
discriminatorProbe: null+targetedCollectExhausted: true→complete_with_range、stop_reason = tied_first、can_offer_range = true(保留:213现有用例,改成targetedCollectExhausted: true,并在测试里写"原值 / 新值 / 原因"三栏说明)。 - 行为单测:并列 +
discriminatorProbe: null+targetedCollectExhausted: false→ 不交付,继续定向补事。 - 回归用例(真机形状):9 个候选、前三名并列 13 分、
datedEventCount: 3、探针非空、credible_range= 开局窗口 → 断言session_outcome !== "adopt_representative"。
- 行为单测:并列 +
任务 2 · 未收窄不得宣布可采用(BUG-683 的第二半,P0)
decideRectification/completeWithRange:当range与开局候选窗口相同(需把case.candidateRange传进决策输入,followupCaseArgs已有该字段,确认可直接取用)时,capability.canAdopt与selectionAllowed的"采用"语义降级为provisional_range。- 验收标准:行为单测——
credible_range = ["04:45","05:15"]且开局窗口相同 →session_outcome属于交付集合但can_adopt === false;收窄过(如 04:48–05:07)→ 维持现有行为。
任务 3 · 交付态死卡取证与兜底(BUG-684,P1,investigating)
interviewDeliveredGap:questionMissing放宽为questionMissing || deadChoice(入参增加deadChoice),调用点同步。- 死卡且处于交付态时打上述结构化日志。
- 验收标准:行为单测——交付 outcome + 死卡(
kind = choice、无choice_card)→ gap 返回delivered,不是unavailable;非交付态的死卡仍返回unavailable(不得误伤 BUG-671 的修复入口)。
任务 4 · 记录
docs/BUG_HISTORY.md:BUG-683resolved(必须写明"复发自 BUG-680 的修复",并说明实现位置比任务书更宽、测试用discriminatorProbe: null而实现无此守卫,所以没被拦住);BUG-684investigating,列出缺的四项证据。CHANGELOG.md一行;PROGRESS-rectification-tied-first-fix-20260914.md;docs/testing/清单加一条:新建校正答两题后,不得出现「可采用 / 代表分钟」,范围应仍在收窄中。
6. 让步顺序
- 任务 1 必做且优先——线上每个新案子答两题就被判"可采用",是当前最严重的问题。
- 任务 2 必做(诚实边界)。
- 任务 3 可只做兜底与日志。
7. 开工前置命令
git fetch origin --prune
git worktree add -b codex/rectification-tied-first-fix-20260914 \
.worktrees/rectification-tied-first-fix-20260914 origin/staging
cd .worktrees/rectification-tied-first-fix-20260914
ln -s /workspace/Jyotisha/.venv .venv
cd frontend && npm ci
开工前读 docs/BUG_HISTORY.md 的 BUG-680、681、682、674 四条,以及上一份任务书的决策记录。
8. BUG 编号起点
- 起点 BUG-683(当前最大号 682)。