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

10 KiB
Raw Blame History

TASK · 修复单:参考题前置把"该交付"的出口也压住了 — 2026-09-14

  • 基线:origin/staging @ 8f50af91(代码最新是 6fd7925astaging 当前部署的是 9da42c997f28bfec / d97b9e9f / 6fd7925a 三个代码提交尚未部署,本回归没有影响线上用户)。
  • 分支:codex/rectification-tiebreak-hold-exit-20260914worktree .worktrees/rectification-tiebreak-hold-exit-20260914
  • 关联:本单是 TASK-rectification-tiebreak-before-card-20260914.mdBUG-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 hintinconsistent projection logs ranked_count 0 and still delivers a gateensureNonTerminalTurnExit 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 cardT4: exhausted refresh and declined targeted collect titles the card 目前范围

新套件 rectification-tiebreak-before-card-20260914.test.ts 本身 9/9 全绿——问题不在新功能,在它压住了旧出口

后续提交 6fd7925a 只改了测试,没有修行为

执行方随后用 6fd7925afix(web): update range-delivery test copy after tie-break hold)把这 6 条改绿,失败数已回到基线 27 条(Claude 复跑确认,与基线清单逐条一致)。逐条看:

  • 4 条是合理的文案放宽:新的交付文案是「范围已经收到…能问的都问完了」,旧断言只认「目前范围」等旧措辞;改动按 §7.3 写了三栏说明。这 4 条接受。
  • 2 条是实质弱化,必须恢复
位置 改动 为什么不能接受
rectification-exhaustion-exit-20260906.test.tslast 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 把守卫放宽之后:

// 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 里的 offerRangeWithoutAdoptcanAdopt && methodCoverageAllfinishholdout === "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_firstuser_uncertainty_too_high)、ensureNonTerminalTurnExit 依赖的收口分支、closed-ceilingcoverageBlocksengineOffers && !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 errornpm 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 计算出 tieBreakFollowupReadyfollowup 非空且 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.tslast 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-688resolved),写明「复发自 BUG-686 的修复」,防复发条:任何"延迟交付"的守卫都必须带出口,且不得作用于耗尽与收口路径;验收必须比对 npm test 失败数与基线
  • CHANGELOG.md 一行;PROGRESS-rectification-tiebreak-hold-exit-20260914.md

6. 让步顺序

  1. 任务 1 的 D2(耗尽/收口不 hold)最优先——它直接决定会不会出现死角。
  2. D1(拿不到题就放行)次之。
  3. D3(次数上限)可延后,但要写进进度记录。

7. 开工前置命令

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)。