Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
7.1 KiB
7.1 KiB
TASK · 健康题答「记不清」后职业题一答完就断:holdout 把跳过的健康线换个名字再问,撞焦点 id 静默失败;出口闸门把"穷尽"当成已交付(2026-09-09)
- 基线:
origin/staging@6008c07c(BUG-623~625 已合入) - 分支:
codex/rectification-skipped-health-deadend-20260909,基于origin/staging - 执行方:coding agent;验收:Claude
- 涉及文件:
frontend/src/lib/rectification-agentic/v9/method-followup.ts(holdoutFollowupForL1676–1697、declinedDomainsL509)、v9/answer-choice.ts(persistNextInterviewAfterChoice的duplicate_focus分支、inspectNonTerminalTurnExitL1739–1753、ensureNonTerminalTurnExit)、v9/agent-run.ts(idle 异常吞掉后的处理 L555–575)、frontend/src/components/rectification-agentic-chat.tsx("没有拿到下一个问题 / 重新加载")、frontend/src/app/api/rectification/cases/[caseId]/...(重载改为修复) - BUG 编号起点:BUG-626(
docs/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-626(P1):跳过的健康线在 holdout 里没被识别,再问一次时撞上刚跳过的焦点 id
- 「记不清」把健康采集焦点标
skipped,declined_skipped_topics里这条的target_domain是health(DB 约束只有health,没有health_pressure;persistableFocusDomain把health_pressure映成health)。 - 职业答完后计划层走
pendingHoldout = holdoutFollowupFor(input, declined):declined由declinedDomains(topics)得到{family, health};holdout 候选来自oos_blind_prompts,其domain是health_pressure;!declined.has("health_pressure")为真 → 把刚跳过的健康线当"没用过的线"再问一遍。BUG-590 只给occupied做了 health / health_pressure 归并,没给declined做。 - 这道 holdout 采集题的
questionId是collect:health_pressure:collect_method_evidence,与刚被跳过的焦点同 id →focus_idempotency_conflict→ BUG-591 后非 retry 不再:next,返回duplicate_focus。 persistNextInterviewAfterChoice对 collect 类 followup:状态不是created / already_open就转persistExhaustionCollect;那里exhaustionSpokenCollectFollowup为空(健康按declinedHealth已封)、canAdopt为假(决策仍是 holdout / 区分)→ 只剩门槛句路径。截图里连门槛句都没有,说明这条链上某处返回了空旁白或抛了异常被agent-runL565 的catch吞掉(日志有persist interview before collect attach failed)。执行方开工先从 staging 日志确认是哪一种,两种都要修。
2.2 BUG-627(P1):出口闸门把"穷尽"当成"已交付"
inspectNonTerminalTurnExit 的 satisfied 包含 exhausted(!remainingCollect && blockingMethodsCovered && !canAdopt && !accepted)。穷尽只是一个状态,不是用户能看到的出口;本例正是穷尽为真、却没有任何问题 / 卡 / 句子落库,闸门照样放行,ensureNonTerminalTurnExit 不修复。BUG-558 的防复发写的是"穷尽必须有 rectification_exhaustion_collect 日志且写出 terminalNote",这个闸门把它绕过去了。
2.3 P2:客户端"重新加载"只重取快照
questionGap === "unavailable" 的按钮只 refetchQuestion();服务端没有新东西,点多少次都一样。
3. 决策记录
- holdout 的
declined与occupied一样做 health / health_pressure 归并(declinedHealth口径),跳过的领域本会话不再作 holdout 问。 duplicate_focus不得静默。persistNextInterviewAfterChoice遇duplicate_focus时记rectification_focus_duplicate日志,并必定走persistExhaustionCollect;persistExhaustionCollect在三条分支(继续采集 / 采用 / 门槛句)之外不得返回空旁白——门槛句为空时用RECTIFICATION_USER_COPY.noCandidatesGate或范围句兜底,terminalNote: true。- 出口闸门只认可见载体。
satisfied= 有活动问题 / 已采用 / 已确认 / 本轮已落库带terminalNote的 host turn /publicCanAdopt;exhausted从satisfied里删掉。穷尽且无载体 →ensureNonTerminalTurnExit调persistExhaustionCollect并把结果写成 host turn(现有persistExhaustionGateTurn)。 - idle 异常不再吞。
agent-runL565 的catch记日志后,仍调用ensureNonTerminalTurnExit(闸门已在路由里,但要保证异常路径也到得了它)。 - "重新加载"改为"修复"。 客户端按钮改调
POST /api/rectification/cases/[caseId]/repair-exit(新路由,只做ensureNonTerminalTurnExit+ 返回快照),文案"接着问";连续两次仍无载体才显示"暂时接不上,请新建一次校正"。不新增迁移。 - 不动采用门、确认门、各道门槛、四选项合同;Skill 不 bump。
4. 任务分解
- 4.1 BUG-626:决策 1、2。用例——账本七领域 + 健康
skipped(topictarget_domain=health)+ oos 含health_pressure→holdoutFollowupFor为 null;同形状进persistNextInterviewIfIdle→ 返回非空hostNarration且(canAdopt时)terminalNote;mockpersistCollectFocus返回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.mdBUG-626(复发关联 BUG-558 / BUG-590 决策 4 / BUG-591 / BUG-605)、BUG-627(关联 BUG-558 防复发条款);CHANGELOG.md;PROGRESS-…;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)"