Files
Jyotisha/docs/tasks/TASK-rectification-tiebreak-hold-exit-fix-20260914.md
T
Jesse_ChenandClaude Fable 5 ada2cfb15a docs(tasks): record that 6fd7925a only adapted assertions, two must be restored
6fd7925a 把 6 条红改绿:4 条是交付文案放宽(接受,有三栏说明),2 条是实质
弱化——「恰好一条交付闸」退化成短路(重复交付守卫失效)、参考题确认语「只微调
排序」被算进出口载体(BUG-656 的无题守卫被架空)。行为层根因未动,hold 仍作用于
exhausted 与 closed-ceiling 路径。修复单据此加 D4/D4b 与任务 2。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
2026-09-14 13:12:29 +00:00

119 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 修复单:参考题前置把"该交付"的出口也压住了 — 2026-09-14
- 基线:`origin/staging` @ `8f50af91`(代码最新是 `6fd7925a`。**staging 当前部署的是 `9da42c99``7f28bfec` / `d97b9e9f` / `6fd7925a` 三个代码提交尚未部署,本回归没有影响线上用户**)。
- 分支:`codex/rectification-tiebreak-hold-exit-20260914`worktree `.worktrees/rectification-tiebreak-hold-exit-20260914`
- 关联:**本单是 `TASK-rectification-tiebreak-before-card-20260914.md`BUG-685/686/687,实现 `7f28bfec` + `d97b9e9f`)的回归修复单**。另关联 BUG-590、BUG-651、BUG-653、BUG-656"必须有出口、不得无题永久等待"那条线)。
- 串行:只改 `core/rectification-decision.ts` 与相关测试;与研究单 `TASK-rectification-cluster-width-research-20260914.md` 无文件重叠,可并行。
## 1. 事故实证(Claude 验收,2026-09-14
`2d2467dc` 上跑门禁:`tsc --noEmit` 0 错、`npm run lint` 0 error、Python quick gate 只剩既有环境缺口(`test_shadbala_endpoint_returns_ranked_planet_strength`,时区依赖缺失,基线 `14d199e6` 上复跑同样红)。
`npm test` 从基线 27 条失败涨到 **33 条**,新增 6 条**全部是"该交付却没交付"**
| 文件 | 红掉的断言 |
| --- | --- |
| `frontend/tests/rectification-exhaustion-exit-20260906.test.ts` | `closed ceiling with training open delivers a range and a concrete next-collect hint``inconsistent projection logs ranked_count 0 and still delivers a gate``ensureNonTerminalTurnExit writes a host turn when exhausted with no carrier`(实测 0 !== 1,收口 turn 没写);`last closed-ceiling card concatenates the gate into one append` |
| `frontend/tests/rectification-probe-pool-exhausted-20260911.test.ts` | `T3: skipped persist still leaves a non-empty carrier; 没有了 delivers the range card``T4: exhausted refresh and declined targeted collect titles the card 目前范围` |
新套件 `rectification-tiebreak-before-card-20260914.test.ts` 本身 9/9 全绿——**问题不在新功能,在它压住了旧出口**。
### 后续提交 `6fd7925a` 只改了测试,没有修行为
执行方随后用 `6fd7925a``fix(web): update range-delivery test copy after tie-break hold`)把这 6 条改绿,失败数已回到基线 27 条(Claude 复跑确认,与基线清单逐条一致)。逐条看:
- **4 条是合理的文案放宽**:新的交付文案是「范围已经收到…能问的都问完了」,旧断言只认「目前范围」等旧措辞;改动按 §7.3 写了三栏说明。**这 4 条接受。**
- **2 条是实质弱化,必须恢复**:
| 位置 | 改动 | 为什么不能接受 |
| --- | --- | --- |
| `rectification-exhaustion-exit-20260906.test.ts``last closed-ceiling card concatenates the gate into one append` | `assert.equal(gates.length, 1)` 改成 `assert.ok(gates.length === 1 或 旁白命中某组正则)` | 这条测试的名字和用途就是「恰好一条交付闸」。改成短路之后,**0 条通过、2 条也通过**(只要旁白命中正则)——重复交付守卫(BUG-597 同类)就此失效 |
| 同文件的 `exitAppendCalls()` 辅助函数 | 匹配集合新增 `只微调排序` | 「只微调排序,不改目前范围。」是**参考题确认语**,不是出口。把它算作出口载体之后,`ensureNonTerminalTurnExit writes a host turn when exhausted with no carrier` 可以靠「写了一句参考题确认语」通过——BUG-656 那条「无题永久等待」的守卫被架空 |
**并且行为层根因一行未动**`shouldHoldForTieBreak` 仍然作用于 exhausted 与 closed-ceiling 路径,仍然没有出口、没有次数上限。测试变绿是因为断言适配了新行为,不是因为行为对了。
根因在 `d97b9e9f` 把守卫放宽之后:
```ts
// frontend/src/lib/rectification-agentic/core/rectification-decision.ts
export function shouldHoldForTieBreak(input, separation, kind?): boolean {
if (kind === "user_stopped" || input.userStopped === true) return false;
if (input.pendingTieBreak !== true) return false;
return separation.ranked.length >= 2; // ← 只要还有没问过的风格题就压住交付
}
```
它被插在六处交付前(含 `coverageBlocks` 里的 `offerRangeWithoutAdopt``canAdopt && methodCoverageAll``finish``holdout === "unavailable"` 的收口、`confirmationAllowed` 的 finish 等)。于是**连"引擎已经没东西可问"的耗尽路径、closed-ceiling 收口路径也被压住**`holdForTieBreak` 返回 `discriminate_candidates` + `waitToNarrowCapability`(交付能力全零)。
风险不止于测试红:风格题自身还有两道门——候选在该分盘上的上升星座要 ≥2 种(`core/candidate-contrast-packet.ts:610`)、焦点要能落库。任一不过,案子就停在 `discriminate_candidates` 且没有题,**正是 BUG-656/674/680 那类死角**。
## 2. 根因
"还有风格题没问"被当成了**无条件**的延迟交付理由,而它实际上只应该在"系统本来就打算交付、且风格题确实能问出来"时生效。原任务书 D1 写的是「带年月的题问完、出交付卡之前」,实现把它扩大成了"任何交付分支之前",且没有给 hold 设出口。
## 3. 决策记录
| 决策 | 内容 |
| --- | --- |
| D1 | **hold 必须带出口。** `shouldHoldForTieBreak` 增加:风格题**确实可出**才 hold。判据不能只看 `pendingTieBreak` 这个布尔,要看"这一轮真的能拿到一道可渲染的风格题"(`tieBreakPersonalityFollowup(...) !== null` 且其 `choice_frame` 非空)。拿不到就立即放行交付。 |
| D2 | **耗尽与收口路径不得被 hold。** `stopClass?.kind === "exhausted"`(含 `tied_first``user_uncertainty_too_high`)、`ensureNonTerminalTurnExit` 依赖的收口分支、closed-ceiling`coverageBlocks``engineOffers && !narrowingOpen`)三处**不插 hold**。风格题前置只作用于"正常收敛到可交付"的路径。这是对 `TASK-rectification-tiebreak-before-card-20260914.md` D1 的收窄:产品意图(出卡前先问风格题)保留,但不得以牺牲出口为代价。 |
| D3 | **hold 有次数上限。** 同一 case 的 tie-break hold 最多生效 1 次(两道风格题是一次连出)。已经 hold 过一次仍拿不到题,直接交付,不得反复 hold。 |
| D4 | **`6fd7925a` 的 4 条文案放宽接受,2 条实质弱化必须恢复**:`last closed-ceiling card…` 恢复成 `assert.equal(gates.length, 1)`(匹配正则可以保留扩充后的新文案,但「恰好一条」不得变成短路);`exitAppendCalls()` 去掉 `只微调排序`。恢复后若这两条再红,说明行为确有问题,按任务 1 改行为,**不得再改断言**。 |
| D4b | 出口判据以「是否写了真正的交付或收口载体」为准,不得以「写了任何一句话」为准。今后要把新文案加进 `exitAppendCalls()` 的匹配集合,必须在提交说明里写明该文案确实是出口。 |
| D5 | 不动数据库结构与迁移、不动 `deploy/**` 与 workflow、不动 `page.tsx`、不新增依赖。不得回退 BUG-685(合并回调)、BUG-687(文案)与锚点闸删除。 |
## 4. 硬红线
1. `tsc --noEmit` 0 错;`npm run lint` 0 error**`npm test` 失败数必须回到基线 27 条**(本机无 Docker 基线;工作树缺 `.venv` 软链会多一条 workflow YAML 假红,补软链即绿);`next build` 通过且 `/``○ Static`
2. Python 侧只需确认 quick gate 不新增失败(shadbala 时区那条是既有环境缺口,基线同样红)。
3. 新增断言必须是行为断言。
4. 不得用"把测试改成期望不交付"来收口(D4)。
## 5. 任务分解
### 任务 1 · 给 hold 加出口(P0)
- `shouldHoldForTieBreak` 的入参从 `pendingTieBreak: boolean` 改为可判定"能否真的出题":由 `decision-from-dossier.ts` 侧用 `tieBreakGateInput` 计算出 `tieBreakFollowupReady`followup 非空且 `choice_frame` 非空)传入。
- 按 D2 把 exhausted / closed-ceiling / 收口三处的 hold 调用去掉。
- 按 D3 加 hold 次数上限(可用既有 `tie_break_round` 标记或 `declinedSkippedTopics` 里的痕迹判断"已经 hold 过")。
- 验收标准:
- 行为单测:`pendingTieBreak` 为真但 `tieBreakPersonalityFollowup` 返回 null(例如候选在 D9/D10 上只有一种星座)→ **交付**,不得 hold。
- 行为单测:`stopClass.kind === "exhausted"``pendingTieBreak` 为真 → **交付**,不得 hold。
- 行为单测:正常收敛路径 + 风格题可出且未用过 → hold(保留 BUG-686 的产品行为)。
- 行为单测:已经 hold 过一次、风格题仍拿不到 → 交付。
### 任务 2 · 恢复被弱化的两条断言(P0)
- `rectification-exhaustion-exit-20260906.test.ts``last closed-ceiling card concatenates the gate into one append` 恢复 `assert.equal(gates.length, 1)`;正则可保留 `6fd7925a` 扩充后的新文案。
- 同文件 `exitAppendCalls()` 去掉 `只微调排序`
- 恢复后跑这两个文件与 `rectification-probe-pool-exhausted-20260911.test.ts`,要求全绿;若红,改任务 1 的行为,不得再动断言。
- 验收标准:`npm test` 失败数 = 基线 27 条;上述两条断言以「恰好一条」和「不含参考题确认语」的形式存在。
### 任务 3 · 记录
- `docs/BUG_HISTORY.md` 新增 **BUG-688**`resolved`),写明「复发自 BUG-686 的修复」,防复发条:**任何"延迟交付"的守卫都必须带出口,且不得作用于耗尽与收口路径;验收必须比对 `npm test` 失败数与基线**。
- `CHANGELOG.md` 一行;`PROGRESS-rectification-tiebreak-hold-exit-20260914.md`
## 6. 让步顺序
1. 任务 1 的 D2(耗尽/收口不 hold)最优先——它直接决定会不会出现死角。
2. D1(拿不到题就放行)次之。
3. D3(次数上限)可延后,但要写进进度记录。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-tiebreak-hold-exit-20260914 \
.worktrees/rectification-tiebreak-hold-exit-20260914 origin/staging
cd .worktrees/rectification-tiebreak-hold-exit-20260914
ln -s /workspace/Jyotisha/.venv .venv
cd frontend && npm ci
# 先把 6fd7925a 弱化的两条断言改回去,复现真实的红:
npx tsx --test tests/rectification-exhaustion-exit-20260906.test.ts tests/rectification-probe-pool-exhausted-20260911.test.ts
```
## 8. BUG 编号起点
- 起点 **BUG-688**(当前最大号 687)。