Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
74 lines
11 KiB
Markdown
74 lines
11 KiB
Markdown
# TASK · 带年月选择题问完后必须交付结果,不得停在「没有拿到下一个问题」(2026-09-11)
|
||
|
||
- 基线:`origin/staging` @ `1fa994ea`(已部署 staging;Skill 10.0.23;含 BUG-646~650)
|
||
- 分支:`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:06–05: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=13,margin 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 序列走一遍,第六题答完必须看到三列区间卡与「再补什么」句,不出现「没有拿到下一个问题」。
|