From e42a62dfaca51ca2c405a1462c787fc54e4f0cf2 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Mon, 14 Sep 2026 08:33:17 +0000 Subject: [PATCH] docs(tasks): regression fix brief for premature tied-first delivery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit d5a8db0a 把 tied_first 早退放到 coverageBlocks 之前且没有 probe 守卫,新案子 答两题、区间仍是整个开局窗口就判 adopt_representative/can_adopt:true/代表 分钟(BUG-683)。配套测试传 discriminatorProbe: null,与实现语义不一致,所以 没拦住。另立 BUG-684(investigating):交付态下仍有画不出的死卡,delivered 终态因要求无题而不触发,界面仍印「没有拿到下一个问题」。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu --- docs/tasks/README.md | 2 + ...d-first-premature-delivery-fix-20260914.md | 130 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 docs/tasks/TASK-rectification-tied-first-premature-delivery-fix-20260914.md diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 51a9a147..9055f0ad 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -184,6 +184,8 @@ | `TASK-rectification-targeted-card-dead-20260913.md` | `PROGRESS-rectification-targeted-card-dead-20260913.md` | **P0**:定向补事卡在快照投影里拿不到 `choice_card`(承接焦点分支不重建 `choice_frame`),卡片看得见点不动、流程停在采集等待态;模型还会把定向题改写成口述题(BUG-669~671)。先于 tie-break 修复单执行 | 待验收 | `codex/rectification-targeted-card-dead-20260913` | +| `TASK-rectification-tied-first-premature-delivery-fix-20260914.md` | `PROGRESS-rectification-tied-first-fix-20260914.md` | **P0 回归修复单**:BUG-680 的 `tied_first` 早退被实现到 `coverageBlocks` 之前且无 `probe` 守卫 → 新案子答两题、区间还是整个开局窗口就宣布 `can_adopt`/代表分钟(BUG-683);交付态下仍出现死卡致 `delivered` 终态不触发、印「没有拿到下一个问题」(BUG-684 investigating) | 待执行 | `codex/rectification-tied-first-fix-20260914` | + | `TASK-rectification-delivery-vs-collect-split-20260914.md` | `PROGRESS-rectification-delivery-vs-collect-20260914.md` | **P0**:并列到顶(tied_first)时 POST 播了交付结论、GET 又算回 collect_evidence——刷新尝试落库失败仍回报「已尝试」+ `stillNeedNarrowing` 压过 exhausted(BUG-680);交付话术闸门与交付卡闸门不一致,`completed_with_range`/`provisional_range` 能说不能画(BUG-681);缺口状态机没有终态,兜底印「没有拿到下一个问题」(BUG-682) | 待验收 | `codex/rectification-delivery-vs-collect-20260914` | | `TASK-rectification-spoken-orphan-and-engine-representative-20260914.md` | `PROGRESS-rectification-spoken-orphan-20260914.md` | **P0**:口述题(定向补事年份追问)没有 `askedTurnId` 时仍是无头像裸行——BUG-675 的挂回规则只覆盖选择题(BUG-678);同屏两个范围口径(旁白用活跃候选首尾、时间轴用 credible_range);引擎 `representative_time` 把已淘汰分钟的宫位表带进模型上下文(BUG-676 收敛为 resolved) | 待验收 | `codex/rectification-spoken-orphan-20260914` | diff --git a/docs/tasks/TASK-rectification-tied-first-premature-delivery-fix-20260914.md b/docs/tasks/TASK-rectification-tied-first-premature-delivery-fix-20260914.md new file mode 100644 index 00000000..0426d097 --- /dev/null +++ b/docs/tasks/TASK-rectification-tied-first-premature-delivery-fix-20260914.md @@ -0,0 +1,130 @@ +# 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 快照(已脱敏): + +```jsonc +"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` 里加的早退: + +```ts +// 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); +} +``` + +问题有两层: + +1. **位置比任务书写的高一层。** `TASK-…-delivery-vs-collect-split-20260914.md` 的 D2 原文是「`decideRectification` 的 **`!separation.sufficient` 分支里**,把 `tied_first` 的判断提到 `stillNeedNarrowing` 之前」。该分支的第一行是 `if (probe) return discriminateOrExhaust(...)`,所以按任务书实现时"还有带年月探针可问"会先接管。实现把它提到了 `coverageBlocks` 之前、整个 `!separation.sufficient` 分支之外,于是**连"还有探针"和"方法覆盖未达标"都绕过了**。 +2. **缺 `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. 硬红线 + +1. `tsc --noEmit` 0 错;`npm run lint` 0 error;`npm test` 失败清单与基线逐条一致;`next build` 通过且 `/` 仍 `○ Static`;首屏 gzip ±2%。 +2. 新增断言必须是行为断言。 +3. 不得回退 BUG-680/681/682 的三项修复(刷新落库失败不算已尝试、交付话术与交付卡同真同假、`delivered` 终态)——本单只收紧 `tied_first` 的触发条件。 +4. 不得为了让并列局面"能收尾"而把 `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-683 `resolved`(**必须写明"复发自 BUG-680 的修复",并说明实现位置比任务书更宽、测试用 `discriminatorProbe: null` 而实现无此守卫,所以没被拦住**);BUG-684 `investigating`,列出缺的四项证据。 +- `CHANGELOG.md` 一行;`PROGRESS-rectification-tied-first-fix-20260914.md`;`docs/testing/` 清单加一条:新建校正答两题后,不得出现「可采用 / 代表分钟」,范围应仍在收窄中。 + +## 6. 让步顺序 + +1. 任务 1 必做且优先——线上每个新案子答两题就被判"可采用",是当前最严重的问题。 +2. 任务 2 必做(诚实边界)。 +3. 任务 3 可只做兜底与日志。 + +## 7. 开工前置命令 + +```bash +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)。