Files
Jyotisha/docs/tasks/TASK-rectification-skipped-health-deadend-20260909.md
T

7.1 KiB
Raw Blame History

TASK · 健康题答「记不清」后职业题一答完就断:holdout 把跳过的健康线换个名字再问,撞焦点 id 静默失败;出口闸门把"穷尽"当成已交付(2026-09-09

  • 基线:origin/staging @ 6008c07cBUG-623625 已合入)
  • 分支:codex/rectification-skipped-health-deadend-20260909,基于 origin/staging
  • 执行方:coding agent;验收:Claude
  • 涉及文件:frontend/src/lib/rectification-agentic/v9/method-followup.tsholdoutFollowupFor L16761697、declinedDomains L509)、v9/answer-choice.tspersistNextInterviewAfterChoiceduplicate_focus 分支、inspectNonTerminalTurnExit L17391753、ensureNonTerminalTurnExit)、v9/agent-run.ts(idle 异常吞掉后的处理 L555–575)、frontend/src/components/rectification-agentic-chat.tsx("没有拿到下一个问题 / 重新加载")、frontend/src/app/api/rectification/cases/[caseId]/...(重载改为修复)
  • BUG 编号起点:BUG-626docs/BUG_HISTORY.md 当前最大 BUG-625
  • 优先级:P1(真实用户,流程在最后一步断掉,只剩"没有拿到下一个问题"和一个无效的重新加载)

1. 事故实证(2026-09-09 真实用户截图 + 转录;只写结构)

顺序:学业 → 感情 → D9 风格 → 2018.04 事业 → 2024.08 迁居 → 家人「没有」→ 事业事件 → D10 风格 → 财务事件 → 2024.08 财务 → 2025.09 财务 → 健康题点「记不清」→ 职业题答完("已完成 2 步",正文一句复述)→ "没有拿到下一个问题。[重新加载]",没有卡、没有旁白、没有题。时间轴 14:33–14:57,三个实心点。

2. 根因

2.1 BUG-626P1):跳过的健康线在 holdout 里没被识别,再问一次时撞上刚跳过的焦点 id

  1. 「记不清」把健康采集焦点标 skippeddeclined_skipped_topics 里这条的 target_domainhealthDB 约束只有 health,没有 health_pressurepersistableFocusDomainhealth_pressure 映成 health)。
  2. 职业答完后计划层走 pendingHoldout = holdoutFollowupFor(input, declined)declineddeclinedDomains(topics) 得到 {family, health}holdout 候选来自 oos_blind_prompts,其 domainhealth_pressure!declined.has("health_pressure") 为真 → 把刚跳过的健康线当"没用过的线"再问一遍。BUG-590 只给 occupied 做了 health / health_pressure 归并,没给 declined 做。
  3. 这道 holdout 采集题的 questionIdcollect:health_pressure:collect_method_evidence,与刚被跳过的焦点同 id → focus_idempotency_conflict → BUG-591 后非 retry 不再 :next,返回 duplicate_focus
  4. persistNextInterviewAfterChoice 对 collect 类 followup:状态不是 created / already_open 就转 persistExhaustionCollect;那里 exhaustionSpokenCollectFollowup 为空(健康按 declinedHealth 已封)、canAdopt 为假(决策仍是 holdout / 区分)→ 只剩门槛句路径。截图里连门槛句都没有,说明这条链上某处返回了空旁白或抛了异常被 agent-run L565 的 catch 吞掉(日志有 persist interview before collect attach failed)。执行方开工先从 staging 日志确认是哪一种,两种都要修。

2.2 BUG-627P1):出口闸门把"穷尽"当成"已交付"

inspectNonTerminalTurnExitsatisfied 包含 exhausted!remainingCollect && blockingMethodsCovered && !canAdopt && !accepted)。穷尽只是一个状态,不是用户能看到的出口;本例正是穷尽为真、却没有任何问题 / 卡 / 句子落库,闸门照样放行,ensureNonTerminalTurnExit 不修复。BUG-558 的防复发写的是"穷尽必须有 rectification_exhaustion_collect 日志且写出 terminalNote",这个闸门把它绕过去了。

2.3 P2:客户端"重新加载"只重取快照

questionGap === "unavailable" 的按钮只 refetchQuestion();服务端没有新东西,点多少次都一样。

3. 决策记录

  1. holdout 的 declinedoccupied 一样做 health / health_pressure 归并declinedHealth 口径),跳过的领域本会话不再作 holdout 问。
  2. duplicate_focus 不得静默。 persistNextInterviewAfterChoiceduplicate_focus 时记 rectification_focus_duplicate 日志,并必定persistExhaustionCollectpersistExhaustionCollect 在三条分支(继续采集 / 采用 / 门槛句)之外不得返回空旁白——门槛句为空时用 RECTIFICATION_USER_COPY.noCandidatesGate 或范围句兜底,terminalNote: true
  3. 出口闸门只认可见载体。 satisfied = 有活动问题 / 已采用 / 已确认 / 本轮已落库带 terminalNote 的 host turn / publicCanAdoptexhaustedsatisfied 里删掉。穷尽且无载体 → ensureNonTerminalTurnExitpersistExhaustionCollect 并把结果写成 host turn(现有 persistExhaustionGateTurn)。
  4. idle 异常不再吞。 agent-run L565 的 catch 记日志后,仍调用 ensureNonTerminalTurnExit(闸门已在路由里,但要保证异常路径也到得了它)。
  5. "重新加载"改为"修复"。 客户端按钮改调 POST /api/rectification/cases/[caseId]/repair-exit(新路由,只做 ensureNonTerminalTurnExit + 返回快照),文案"接着问";连续两次仍无载体才显示"暂时接不上,请新建一次校正"。不新增迁移。
  6. 不动采用门、确认门、各道门槛、四选项合同;Skill 不 bump。

4. 任务分解

  • 4.1 BUG-626:决策 1、2。用例——账本七领域 + 健康 skippedtopic target_domain=health+ oos 含 health_pressureholdoutFollowupFor 为 null;同形状进 persistNextInterviewIfIdle → 返回非空 hostNarration 且(canAdopt 时)terminalNotemock persistCollectFocus 返回 duplicate_focus → 仍有非空旁白。
  • 4.2 BUG-627:决策 3、4。用例——穷尽为真、无活动焦点、无 host turn → ensureNonTerminalTurnExit 写出 host turn;有 terminalNote turn 时不重复写。
  • 4.3 决策 5:路由 + 客户端;rectification-agentic-entry 合同:unavailable 态按钮文案与请求路径。
  • 4.4 记录:docs/BUG_HISTORY.md BUG-626(复发关联 BUG-558 / BUG-590 决策 4 / BUG-591 / BUG-605)、BUG-627(关联 BUG-558 防复发条款);CHANGELOG.mdPROGRESS-…docs/testing/rectification-scenarios-20260907.md 加"健康题点『记不清』后职业答完必须出卡或门槛句,不得出现『没有拿到下一个问题』"。

5. 让步顺序

4.1 决策 1 一行改动先部署;4.1 决策 2、4.2 必做;4.3 可后置为 P2;4.4 不可省。

6. 开工前置命令

git fetch origin --prune
git worktree add -b codex/rectification-skipped-health-deadend-20260909 .worktrees/rectification-skipped-health-deadend-20260909 origin/staging
cd .worktrees/rectification-skipped-health-deadend-20260909
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
cd frontend && ./node_modules/.bin/tsc --noEmit; npm run lint 2>&1 | tail -1
ls tests/rectification-*.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"