Files
Jyotisha/docs/tasks/TASK-rectification-probe-pool-exhausted-exit-20260911.md
T

74 lines
11 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-11)
- 基线:`origin/staging` @ `1fa994ea`(已部署 stagingSkill 10.0.23;含 BUG-646650
- 分支:`codex/rectification-probe-pool-exhausted-exit-20260911`,基于 `origin/staging`
- 执行方:coding agent;验收:Claude
- BUG 编号起点:**BUG-651**650 已被 1fa994ea 占用;开工时 `grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1` 核对)
- 不改引擎计分、不改确认门、不动 `SCORE_DELTA`;Skill 只在改用户可见口径时升 10.0.24(本单预计不用升)
## 1. 事故实证(2026-09-11 staging 真机,代码 1fa994ea
流程(脱敏):开场 → 两件学业(2016-09 / 2020-06)→ 邀请「还有吗」→ 事业两件(2020-04 入职实习、2020-10 离职)→ 训练门开(3 件训练 + 1 件 holdout,2 类)→ 连续六道带年月选择题:事业 2023-05 C、感情 2023-05 C、迁居 2015-05 C、学业 2016 质量 B、事业 2024-04 A、感情 2024-04 C → 助手只回「已记录,范围没变;04:54–04:59 领先,05:0605:07 落后。」→ 时间轴写「目前范围 04:48–05:07,选择题已问完,再补带年月的经历才会变」→ 界面「没有拿到下一个问题。」。没有结果卡、没有采用入口、没有缺口句。
回执关键字段(第六题答前快照,`case_revision=6`):
| 字段 | 值 | 含义 |
| --- | --- | --- |
| `gates.event_quality / domain_diversity` | 3/3 通过、career+education 通过 | 训练门开 |
| `decisionReceipt.acceptance_allowed / selection_allowed / propose_allowed` | 全 true | 引擎已允许交付区间 |
| `inference_state.rounds` | 5 轮 informative,无 low_information | 平台期计数为 0,收敛门未开(04:53=14 vs 05:06=13margin 2.8% |
| `inference_state.probes` 剩余 | `career.2023.dasha_activation`(与已答 2023-05 同域同年,BUG-559 去重)、5 道无年月存在题(`yearless_ungrounded_contrast` 已 drop)、D9/D10 两道 `varga_style` 性格题、月宿边界题(IG 0.01) | 第六题答完后**带年月的区分题池已空** |
| `interview.session_outcome`(答后) | `discriminate_candidates`(时间轴据此写「选择题已问完」) | 决策层仍判「继续出区分题」 |
| `current_question / choice_card`(答后) | null | 没有落下任何载体 |
## 2. 根因(按 1fa994ea 源码定位)
1. **决策层把无年月性格题当成「下一道区分题」**`rectification-decision.ts` `decideRectification``!separation.sufficient` 分支里先看 `probe``decision-from-dossier.ts` `discriminatorProbeIfFollowupCanAsk``buildMethodFollowupPlan(sessionOutcome="discriminate_candidates")``next_followup``method-followup.ts` 区分分支在带年月探针为空时走 `firstRenderableYearlessFollowup()`(BUG-629 允许:带年月题空且 ≥2 候选未分开),把 D9 性格题包成 `distinguish_candidates + choice_frame` 返回。于是决策 = `discriminate``nextAction=ask_candidate_discriminator``sessionOutcome=discriminate_candidates`),而不是重设计单 S3 的「探针池空 → 交付」。
2. **持久化层没有把这张卡落下来,也没有任何兜底**`answer-choice.ts` `persistNextInterviewIfIdle`(约 L1362)显式豁免:`!followup && nextAction !== "ask_candidate_discriminator" && nextAction !== "ask_holdout_validation"` 才走 `persistExhaustionCollect`;否则进 `persistNextInterviewAfterChoice`L774 起)。后者对 `distinguish_candidates` 的 followup 调 `persistFocusAfterChoice → persistServerOwnedFocus``server-focus.ts` L470 起),返回 `skipped / zero_information_gain / probe_already_answered / invalid_choice_schema / duplicate_focus` 任一时,`openQuestionFromPersistedFocus` 为 null,函数最终 `return { hostNarration: spokenFollowupForUser(followup) ?? hostNarrationFallback, choiceReady: false }`——没有焦点、没有卡;`spokenFollowupForUser` 对区分题返回空串时 `??` 不兜底,正文也为空(本案正文确实只有「已记录…」一句)。
3. **选择题路径与 GET 路径一致地「有决策、无载体」**GET 用 `decideFromDossier` 重算同样得到 `discriminate_candidates``current_question` 因无活动焦点为 null,客户端 `rectificationQuestionGapState` 走到 `unavailable` → 「没有拿到下一个问题」。BUG-627 的 `ensureNonTerminalTurnExit` 只在点「接着问」时触发,不在答题事务里。
4. 具体是哪一个 persist 状态让性格卡没落下来,本单开工时用 §5 T0 的回放测试打印 `persist_status` 定死,不得猜。候选:`expectedAnswerSchemaFor``state && scoring !== false && !schemaProbeId(stamped)` 时返回 null`candidate_split_hash` 形如 `04:45-05:15:04:46,…:varga.d9.…`,需与 `inference_state.probes[].candidate_split_hash` 逐字相等才能盖 `probe_id`);`shouldSkipDiscriminatorFollowup``already_open`/`duplicate_focus` 撞旧焦点。
5. 时间轴文案 `rectification-surface-state.ts` L391「选择题已问完,再补带年月的经历才会变」在没有结果卡时等于告诉用户「做不了」,违反 2026-09-10 口径 3。
## 3. 决策记录(产品负责人 2026-09-11
1. **带年月区分题池空 = 进 S3 交付**(重设计单 §2 S3 的「探针池空」以带年月题为准)。训练门开、引擎 `acceptance_allowed` 为真、带年月探针无可问时,决策必须是 `offer_provisional_range` / `adopt_representative`(按 `capability.canAdopt`),先出区间三列卡 + 一句定性 + 一句「再补什么能收窄」。
2. **无年月性格题(D9 / D10 `varga_style`、月宿边界)不得挡在结果前面**。它们仍按 BUG-629 只作 ±1 参考:交付卡之后可作为可选追问(卡下一行「还可以再答两道参考题微调排序」,点了才问),不再作为 `ask_candidate_discriminator` 的唯一探针。BUG-629 防复发「不得把 varga_style / nakshatra_boundary 加回带年月区分池」不变,本单是把它们从「必答」降为「可选」。
3. **决策有动作就必须有载体**:任何 `nextAction` 在持久化后既无可渲染焦点又无正文时,同一事务内必须回落到 `persistExhaustionCollect`(门关 → 精确缺口句;门开 → 交付卡)。撤回 `persistNextInterviewIfIdle``ask_candidate_discriminator / ask_holdout_validation` 的豁免;`persistNextInterviewAfterChoice` 末尾不得返回空正文。
4. 时间轴文案:有结果卡时写「选择题已问完,下面是当前范围」;只有训练门关时才写「再补一件带年月的经历」。禁词沿用重设计单 §2 末。
## 4. 硬红线
- 不改 `MIN_ACCEPTANCE_EVENTS / MIN_ACCEPTANCE_DOMAINS``SCORE_DELTA`、收敛门常量;不开 `confirmation_allowed`
- 不得把性格题重新提权或让它参与淘汰(BUG-629)。
- 不得靠客户端「接着问」按钮补救;答题事务本身必须留下载体。
- 既有测试总数不降;改断言逐条写原值 / 新值 / 原因。
- 记录与测试只用脱敏的领域 + 年月。
## 5. 任务分解
- **T0 回放定位**(不编号):用 §1 序列写 `frontend/tests/rectification-probe-pool-exhausted-20260911.test.ts`(可复用 `rectification-replay-20260911.test.ts` 的装置),在第六题答后断言并打印:`decision.nextAction``plan.next_followup.semantic_key / choice_kind``persistServerOwnedFocus` 返回的 `status`。把结果写进进度记录第一段。
- **T1 决策层:带年月池空即交付**(BUG-651):`decideRectification``!separation.sufficient` 分支里,`probe` 只认带年月探针(`probe_year > 0``source=event_probe` 且有 period);`discriminatorProbeIfFollowupCanAsk` / `decideAfterInferenceChange` 不再把 `varga_style` / `nakshatra_boundary``discriminatorProbe` 传入。池空时按现有顺序落到 `completeWithRange(..., "offer")``finish("adopt_representative", stopReason="probe_pool_exhausted")`。验收:§1 序列第六题答后 `next_user_action.id ∈ {offer_provisional_range, adopt_representative}``can_offer_range=true`,快照带 `range_delivery` 三列,正文含一句「再补什么」且无禁词。
- **T2 性格题改可选追问**(BUG-651):交付卡渲染后,若 `yearlessPersonalityCanAsk` 为真,卡下给一个「再答两道参考题」入口;点击后才 `set-focus` 性格卡,答完按 BUG-629 ±1 重排并刷新卡,不改 `can_adopt`。验收:不点入口时不出性格卡;点了出 D9 卡,答后 `rounds.kind=tie_break`
- **T3 载体不变量**BUG-652):`persistNextInterviewIfIdle` 删除对 `ask_candidate_discriminator / ask_holdout_validation` 的豁免;`persistNextInterviewAfterChoice``open` 不可渲染且 `spokenFollowupForUser` 为空时调 `persistExhaustionCollect``persistServerOwnedFocus``created/already_open` 的状态一律 `console.warn` 一条 `rectification_discriminator_persist_skipped {case_id, status, semantic_key}`。验收:合同测试构造 `persistServerOwnedFocus` 返回 `skipped` 的区分 followup,断言答题事务结束后 host 正文非空且(门开时)`can_offer_range=true`
- **T4 时间轴文案**BUG-652):`rectificationReadonlyRangeCopy` 按决策分支写:有卡「选择题已问完,下面是当前范围」;门关「再说一件带年月的事就能继续」;其余「还在核对」。`agent-voice-copy-contract` 加禁词「才会变」。
- **T5 记录**BUG-651/652 入 `docs/BUG_HISTORY.md`651 关联 BUG-629、BUG-442「训练门已开且无可渲染区分卡时必须给用户出路」、BUG-627);`CHANGELOG.md``docs/tasks/PROGRESS-rectification-probe-pool-exhausted-exit-20260911.md``docs/testing/rectification-scenarios-20260907.md` 加「六道选择题答完必须出卡」一条。
## 6. 让步顺序
T2 的入口可先只做「卡后不再问性格题」(即性格题完全不问),入口留下一单;T0 + T1 + T3 不可省。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-probe-pool-exhausted-exit-20260911 .worktrees/rectification-probe-pool-exhausted-exit-20260911 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 650
cd frontend && npm test -- tests/rectification-replay-20260911.test.ts tests/rectification-yearless-probe-downgrade-20260909.test.ts tests/rectification-exhaustion-exit-20260906.test.ts tests/rectification-decide-next-action.test.ts tests/rectification-server-focus.test.ts
```
## 8. 验收口径
- 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build``/` 仍 Static、首屏 gzip ±2%。
- Python 不动则只跑 `run_quality_gate.py --profile quick`
- 部署后真机:按 §1 序列走一遍,第六题答完必须看到三列区间卡与「再补什么」句,不出现「没有拿到下一个问题」。