docs(tasks): exhausted-gate exit brief (BUG-646/647)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
Jesse_Chen
2026-09-10 14:57:16 +00:00
co-authored by Claude Fable 5.1
parent fff6da6875
commit 3cd7a95d58
2 changed files with 104 additions and 0 deletions
+1
View File
@@ -99,6 +99,7 @@
| `TASK-rectification-latent-audit-20260910.md` | `PROGRESS-rectification-latent-audit-20260910.md` | 顺带审计:账本派生同年键误杀 `known_event_quality`BUG-637BUG-389 复发);双轨一致性按 31 分钟逐分判冲突并降置信度、与报告两套口径(BUG-638);不可分宽度按簇代表分钟少算(BUG-639);引擎与推断层两套代表分钟、回执宫位表不是卡片那一分钟(BUG-640);另 8 条观察项 | 已验收通过(2026-09-10Claude;环境缺口:`next build` / 真人走查) | `codex/rectification-unwritten-evidence-claim-20260910` |
| `TASK-rectification-collect-semantics-20260910.md` | `PROGRESS-rectification-collect-semantics-20260910.md` | 产品决策三改:Skill 去掉「财务/健康只有主动说才问」并清空 Python `VOLUNTEER_ONLY`,升 10.0.22(BUG-641);带年份线索的采集题优先、题干带线索不矛盾,推翻 BUG-539 的整句去前缀(BUG-642);删掉「没有/记不清」原字匹配,分类器分 no/unsure 两类(BUG-643 | 待验收 | `52db714b`BUG-641643 |
| `TASK-rectification-superseded-focus-duplicate-20260910.md` | `PROGRESS-rectification-superseded-focus-duplicate-20260910.md` | P0:一轮两次 batch 让感情采集题被顶替成 superseded 行;题号唯一约束含已结束行、汇总只聚合 declined/skipped,计划层看不见→撞号 `duplicate_focus` 被当「已问过」→出口误走交付旁白无卡(BUG-644);交付口吻与 `can_adopt=false` 矛盾(BUG-645) | 待验收(环境缺口:`next build` / 真人走查) | `a3a51c32`BUG-644/645 |
| `TASK-rectification-exhausted-gate-exit-20260910.md` | `PROGRESS-rectification-exhausted-gate-exit-20260910.md` | 五领域全拒答 + 三件带月经历:门槛句后无载体、客户端误报「没有拿到下一个问题」并循环「接着问」(BUG-646);第三件被留作 holdout 导致永远没有选择题(BUG-647)。含产品建议:一次性开放追问 + 终态标记 + 穷尽后释放 holdout | 待执行(决策 3 建议执行前向产品确认) | `codex/rectification-exhausted-gate-exit-20260910` |
### 聊天主链路与首页
@@ -0,0 +1,103 @@
# TASK · 六条线全答「没有」、只有三件带年月经历时:门槛句之后没有任何载体,页面只剩「没有拿到下一个问题」;第三件被留作验证导致永远没有选择题(2026-09-10)
- 基线:`origin/staging` @ `fff6da68`(代码 `52db714b`,已部署;含 BUG-635645
- 分支:`codex/rectification-exhausted-gate-exit-20260910`,基于 `origin/staging`
- 串行:改 `answer-choice.ts``persistExhaustionCollect`)、`method-followup.ts``turn-exit.ts`、客户端缺口态。BUG-641~645 均已合入,无并行单。
- 执行方:coding agent;验收:Claude
- BUG 编号起点:**BUG-646**(当前最大 645
- 优先级:BUG-646 **P1**(真实用户第三次断流,同一天);BUG-647 **P2**(三件带年月经历仍没有选择题、范围不动,用户以为坏了)
- 不改 Skill 10.0.22、不改引擎计分、不加迁移。
## 0. 给产品的即时绕行
页面没坏。输入框还能用:直接再打一件带年月的经历(任何领域,哪怕只记得年份),流程会继续;或者按「先这样」按现有材料要区间。「没有拿到下一个问题」这行字在这个状态下是误报,见 §2。
## 1. 事故实证(staging 2026-09-10 晚,Skill 10.0.22;只写结构)
顺序:开场 → 学业两件(同一轮)→ 感情「没有」→ 事业「没有」→ 家人「没有」→ 财务「也没有」→ 迁居「没有」→ 健康采集题答一件带年月的外伤 → 职业「程序员」→ 助手:「记下了:你提到自己是程序员。目前还差 1 件带大概年月的经历,想到哪件就说哪件——比如…」→ 下面「没有拿到下一个问题」,顶部「目前范围 04:45–05:15,还在核对」。全程没有一道选择题,范围没动。
按代码复盘这个状态:
| 项 | 值 |
| --- | --- |
| 账本 | 学业 2 件(带月)、健康 1 件(带月)、职业备注 1 条(不计分) |
| 拒答 | 感情、事业、家人、财务、迁居五个带年份领域全部 `declined` |
| 训练门 | `MIN_ACCEPTANCE_EVENTS=3``MIN_ACCEPTANCE_DOMAINS=2`;带月事件 3 件 ≥ `MIN_EVENTS_TO_RESERVE_HOLDOUT=2``splitHoldoutEvents` 留出 1 件作 holdout → **训练 2 件**,门关;`collection_progress.missing=1` |
| 区分探针 | 训练门关 → `rankedCatalog` 为空 → 没有任何带年月点选题,范围不可能动 |
| 采集计划 | `nextDatedCollectFollowup` 全部被 declined 挡住;职业已覆盖;`exhaustionSpokenCollectFollowup` 返回 nullBUG-558 决策:不再问 `other` |
| 出口 | `persistExhaustionCollect``followup=null` → 训练门关 → 不 adopt → 写门槛翻译句(`acceptanceGateNarration` → 「还差 1 件带月份的经历,领域不限」,再经旁白 Agent 改写成用户看到的那段)→ **`terminalNote=true`,不落任何焦点**`rectification-exhaustion-exit-20260906.test.ts`「closed ceiling translates the gate and does not persist a focus」正是锁这个行为) |
| 客户端 | 快照 `current_question=null``question_source="focus"``rectificationQuestionGapState``preparing` → 重试用尽 → `unavailable` → 「没有拿到下一个问题」+「接着问」(BUG-627)。按「接着问」调 `repair-exit`,回来的还是同一句门槛,无限循环 |
## 2. 根因
### 2.1 BUG-646P1):「穷尽 + 门关」是合法终态,但既没有载体也没有出口,客户端还把它当故障
1. BUG-558 去掉了「也可以再说一件」的无限循环,改成「写门槛句、不落焦点、`terminalNote`」;BUG-627 让非终止闸门接受 terminalNote host turn。**两条都只保证服务端不抛错,没有给用户任何可点的东西。**
2. 客户端的缺口态(BUG-633)是为「服务端明明有下一问但没渲染出来」设计的;它分不清「没拿到」和「本来就没有」,于是把设计内的终态渲染成故障提示,按钮「接着问」又把用户送回同一句。
3. 「先这样」在口述采集下是否可见,取决于 BUG-558 放在输入框上方的停止控件;本例用户没看到可点的东西(截图只有门槛句与缺口提示)。
4. 门槛句「还差 1 件带月份的经历,领域不限」对用户不成立:他刚刚已经把六个领域都答了「没有」。句子没有告诉他「任何领域再补一件都行、哪怕只记得年份」,也没告诉他「说不出就按先这样」。
### 2.2 BUG-647P2):第三件带月经历被留作 holdout,用户给了三件仍看不到一道选择题
`splitHoldoutEvents` 在带月事件 ≥2 时保留 1 件作 holdout;训练门要 3 件。于是用户必须给到 **4 件**才有选择题,而所有文案(开场「想到几件说几件」、门槛「还差 1 件」)都让人以为 3 件就够。当五个领域都已拒答、再补一件的可能性很低时,把第三件继续留作验证等于放弃筛选。
## 3. 决策记录(产品负责人 2026-09-10 口头授权处理「走不下去」;以下为 Claude 的产品建议,执行前若产品另有口径以产品为准)
1. **终态必须有载体。** 穷尽 + 门关时,服务端落一个**一次性**的开放领域采集焦点 `collect:any:collect_method_evidence`,题干:「带年月的经历现在只有 N 件,还分不开候选。任何领域再补一件都行,哪怕只记得年份;实在没有,就按「先这样」按现有材料给区间。」它只问一次(同 Case 已问过则不再落),答「没有 / 记不清」→ 进入决策 3。这**不是**恢复 BUG-558 删掉的 `other` 循环:那时是每轮都回同一句且没有出口;这里是一次明确的开放追问 + 明确的出口句。
2. **客户端不得把设计内终态当故障。** 快照新增 `interview.stop_reason="collect_exhausted_gate_closed"`(或 `question_source="terminal"`);有此标记时缺口态不渲染「没有拿到下一个问题」与「接着问」,改渲染门槛句 + 「先这样」按钮 + 输入框占位「再补一件带年月的经历,任何领域都行」。
3. **开放追问也被拒后,释放 holdout 打开筛选。** 带月事件 ≥3、训练 <3 且全部采集领域已穷尽时,`splitHoldoutEvents` 不再留出(回执 `holdout.status="released_exhausted"`),训练门按实际事件数判定,允许出带年月的区分点选题;`overall_confidence` 上限 `low`,确认门、唯一分钟门**不动**(本来也关着)。若产品不接受释放 holdout,则改为:直接交付区间卡(`selection_allowed`)并写明「只有 N 件经历,没有做区分」,本单 T3 换成这一版;执行方开工前向产品确认一次,未确认按释放 holdout 做。
4. **门槛句改口径。** 「还差 N 件带月份的经历,领域不限」→ 「带月份的经历还差 N 件;已经答过的领域也可以再补别的年份的事,哪怕只记得年份。」当 holdout 占了一件时加一句「有一件留作最后核对,不参与筛选」。
5. 不改 `MIN_ACCEPTANCE_EVENTS/DOMAINS`、不改 `SCORE_DELTA`、不改 Skill。
## 4. 硬红线
- 开放追问焦点每个 Case 最多落一次;不得再造成每轮重复(BUG-558 防复发条款仍有效:`exhaustionSpokenCollectFollowup` 不返回 `other`,本单用新 domain `any` 与独立判定函数实现,不复用 `other`)。
- 释放 holdout 只在「采集穷尽 + 开放追问已拒」后发生,不得在正常流程里提前用掉 holdout;回执必须留下 `released_exhausted` 痕迹,报告的 Technique Audit 里 `Real Case Calibration / holdout` 行写 `released`
- 确认门与唯一分钟门不得因本单任何改动打开。
- 客户端不得新增 spinner;「没有拿到下一个问题」只在 `question_source` 表明确实缺失时出现。
- 既有测试总数不降;改动「closed ceiling translates the gate and does not persist a focus」等断言写原值 / 新值 / 原因。
## 5. 任务分解
### T1 · 开放追问焦点(BUG-646
- `method-followup.ts`:新增 `exhaustedOpenCollectFollowup(evidence, declinedTopics, closedCollectFocuses)`,条件:dated 领域全部 declined/covered、职业已覆盖、训练门关、且 `collect:any:*` 未问过 → 返回 `domain="any"``intent=collect_method_evidence`、题干见决策 1。`persistExhaustionCollect` 在门槛句之前先试它;`persistableFocusDomain("any")` 允许落库(映射到 DB 允许的 `other` 或新增枚举,执行方按 DB 约束选,写进进度记录)。
- 验收:本单事故形状(学业 2 + 健康 1 + 五领域拒答 + 职业)→ `next_followup.domain="any"`、焦点落库、`hostNarration` 为题干;同 Case 再次到达 → 不再落,走 T3。
### T2 · 客户端终态(BUG-646
- 快照 `interview.stop_reason` / `question_source="terminal"``rectificationQuestionGapState` 新增 `terminal_gate` 态;缺口组件按决策 2 渲染;`frontend/DESIGN.md` 更新缺口态表。
- 验收:`rectification-surface-state.test.ts`:terminal 标记下不得出现「没有拿到下一个问题」「接着问」;有「先这样」按钮与新占位。
### T3 · 释放 holdout 打开筛选(BUG-647
- `split-holdout.ts` 增加 `options.releaseHoldout``trainingScoreableGate` / `decideFromDossier` 在「开放追问已拒」时传入;回执 `holdout.status="released_exhausted"``overall_confidence` 上限 low。
- 验收:3 件带月(2 学业 + 1 健康)+ 开放追问 declined → 训练门开、`rankedCatalog` 非空、出带年月点选题;未拒开放追问前仍留 holdout;确认门断言不变;报告 audit 行写 `released`
### T4 · 门槛句(BUG-646
- `acceptanceGateNarration` 按决策 4 改;`agent-voice-copy-contract` 词表同步。
- 验收:missing=1 且 holdout 占一件 → 句子含「留作最后核对」;missing=1 无 holdout → 不含。
### T5 · 记录
- `docs/BUG_HISTORY.md` BUG-646、647(关联 BUG-558、627、633);BUG-558 补「本单加一次性开放追问,不是恢复循环」。
- `CHANGELOG.md``PROGRESS-rectification-exhausted-gate-exit-20260910.md``docs/testing/` 真人清单:六个领域全答「没有」+ 一件健康经历 → 期望看到开放追问;再答「没有」→ 期望出现带年月选择题或区间卡,页面不得出现「没有拿到下一个问题」。
## 6. 让步顺序
T3 可延后并改为「直接交付区间卡」的备选;T1 + T2 + T4 最低交付。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-exhausted-gate-exit-20260910 .worktrees/rectification-exhausted-gate-exit-20260910 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | tail -1
cd frontend && npm test -- tests/rectification-exhaustion-exit-20260906.test.ts tests/rectification-surface-state.test.ts tests/rectification-spoken-collect.test.ts
```
## 8. 验收口径
- `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build``/` 仍 Static,首屏 gzip ±2%。
- 推 staging 后 `/api/health``deployment.gitCommit` 等于最新含代码改动的提交;真人清单见 T5。