docs(tasks): rectification adopt narration brief
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
| `TASK-rectification-adopt-flow-20260902.md` | — | 采用流程整治 | 已验收 | `35e5781e`(BUG-497~500) |
|
||||
| `TASK-rectification-adopt-flow-fix-20260903.md` | — | 采用流程修复单(含删「用这个时间看盘」) | 已验收 | `e8c98c37`(BUG-501/502) |
|
||||
| `TASK-rectification-uncertainty-stop-20260903.md` | — | 不确定度停止规则加样本下限 | 已验收 | `0c0df426`(BUG-503) |
|
||||
| `TASK-rectification-adopt-narration-20260904.md` | — | 探针池耗尽时采用卡旁白「继续往下收」+ 已丢弃探针绕过 BUG-472 早退 + 区分题答否关线 | 待执行 | 基线 `4d0a67a2`,BUG-516 起 |
|
||||
| `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505~509) |
|
||||
|
||||
### 聊天主链路与首页
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# 任务书 · 采集题答「没有」后旁白说「继续往下收」却直接出采用卡(2026-09-04)
|
||||
|
||||
基线:`origin/staging` `4d0a67a2`。本单只改 TypeScript 决策/落焦点层与测试,不改 Python 引擎、不改 Skill 包、不动确认门。与 `TASK-rectification-uncertainty-stop-20260903.md`(BUG-503,已合入)无文件级冲突。
|
||||
|
||||
## 0. 真实环境复现(用户 09-04 staging 反馈,事实)
|
||||
|
||||
用户答完 5 道区分题(D9 风格、D10 风格、两道事业年份题、一道搬家年份题)后,系统出了一道口述采集题:
|
||||
|
||||
> 2021 年前后,家里如果有结婚、添丁或住院这类事,记得大概哪年就行。
|
||||
|
||||
用户回「没有」。系统回一句:
|
||||
|
||||
> 我按现有材料继续往下收。
|
||||
|
||||
然后**同一轮**直接出现「当前可能的出生时间」采用卡:05:00(相对支持度 21)/ 05:06(18)/ 04:53(9),composer 没有任何问题。GET 返回 `interview.type = offer_provisional_range`、`session_outcome = adopt_representative`、`precision_stage = ready_to_adopt`、`stop_reason = null`、`current_question = null`、`question_source = "focus"`。
|
||||
|
||||
用户原话:"这个问题我回答的没有 结果就直接弹出卡片了 但是不应该继续往下提问或者走流程吗"、"弹出的时候也不明不白的"、"在这个阶段不应该继续引导收敛吗"。
|
||||
|
||||
## 1. 事实核对(用本案 decision receipt 在本地重放,不是猜)
|
||||
|
||||
用本案 `latest_result.decisionReceipt` 原样喂给 `decideFromDossier` → `publicNextAction` → `persistNextInterviewAfterChoice`(fake accounting),结论与线上一致:
|
||||
|
||||
1. **决策层是对的。** `inference_state` 里 14 条探针,已答 5 条;剩余 9 条对活跃集合 {05:00, 05:06, 04:53} 的 `expected_outcomes` 全部把 05:00 与 05:06 放在同一侧(或根本不含活跃候选):
|
||||
- 两道 `relationship.2024.known_event_quality`:`candidate_ids` 只覆盖窗口两端(04:45–04:51、05:08–05:15),不含任何活跃候选。
|
||||
- `career.2023.dasha_activation`:yes/no 都是三个活跃候选同进同退 → `no_split_among_active`。
|
||||
- `varga.d24 / d12 / d7 / d4 / d5` 五条无年份对比:至多把 04:53 单独分出来,05:00 与 05:06 永远同组;且按 BUG-444 规则作 `yearless_ungrounded_contrast` 丢弃。
|
||||
- `nakshatra-boundary`:yes 同时支持三个活跃候选 → 无区分力。
|
||||
于是 `decideRectification` 走 `!separation.sufficient → 无 probe → holdout 因 canAdopt 已真而跳过(BUG-463 决策)→ 无 dated collect → completeWithRange("offer")`,得到 `offer_provisional_range / adopt_representative / ready_to_adopt`。**引擎手里已经没有任何一道能把 05:00 和 05:06 分开的题**;receipt `reasons` 里也写着 `adjacent_minutes_indistinguishable`。这一步不是 bug,是设计:先给范围与代表分钟,采用后再用保留的 holdout 事件和 OOS 三条线(family / finance / health_pressure)核对。
|
||||
|
||||
2. **旁白与落焦点层错了,而且错在 BUG-472 修过的那条路上。** 链路如下(符号定位,行号会漂):
|
||||
- `v9/method-followup.ts` `buildMethodFollowupPlan` 的 nakshatra 分支(`if (!next && input.holdoutValidation !== "not_started" && !datedMethodCollectOpen(methods) && input.nakshatraProbe)`)只看「有没有 nakshatra 探针」,**不看决策层是否已把它丢弃**。`rectificationFollowupCatalog` 把 `inference.probes` 里的 nakshatra 探针原样传进来,于是计划产出一个 `intent=distinguish_candidates / source=nakshatra_boundary / domain="appearance" / choice_frame=null` 的 followup;因 `sessionOutcome=adopt_representative` 被放进 `deferred_followup`。
|
||||
- `v9/answer-choice.ts` `interviewToPersist` 取 `next_followup ?? deferred_followup`,`isRemainingDiscriminatorFollowup` 见 `source=nakshatra_boundary` 返回 true → `shouldSkipFollowupPersist` 返回 **false** → **BUG-472 加的「已可采用就不再落下一问、改出 adoptHostNarration」早退被绕过**。
|
||||
- 接着 `persistFocusAfterChoice` → `server-focus.ts` `persistServerOwnedFocus`:无 `choice_frame` 的 distinguish → `persistSpokenChoiceFallback` → `persistCollectFocus`,要写一个 `collect:appearance:collect_method_evidence` 焦点,题干是开场通用题「从你最容易想起来的一件事开始就好……」。
|
||||
- `persistableFocusDomain("appearance")` 落到兜底 `return domain`,而 `agentic_rectification_conversation_focus.target_domain` 的 check 只允许 `education/career/relationship/relocation/finance/health/family/other` → 写库失败(非 idempotency 冲突)→ `status: "skipped"` → 重试一次仍 skipped → `open` 为 null → followup 既非 collect 也无 frame → 落到最后一行 `spokenFollowupForUser(followup) ?? RECTIFICATION_USER_COPY.hostNarrationFallback` = **「我按现有材料继续往下收。」**
|
||||
- 本地重放(fake accounting 不做 check 约束)得到的是更糟的形状:真的落下了 `collect:appearance` 焦点并把**开场题**再问一遍,同时采用卡照出。两种结果都是错的;线上只是碰巧被 DB 约束挡成了空旁白。
|
||||
|
||||
3. **`rectification-collect-stall.test.ts` 的「live five-evidence case opens adopt」为什么没拦住**:该 fixture 的 `inference_state.probes` 里没有 nakshatra 探针、receipt 里没有 `window_scan`,所以计划 `deferred_followup=null`,早退成立,断言 `/可以从下面选一个先用着/` 通过。真实 receipt 两者都有。
|
||||
|
||||
4. **潜在的第二个问题(本案未改变结局,列为 P2)**:`choice-action.ts` `focusStatusForAnswer` 把区分题选 C「明确没有发生」记为 `declined`;`v_declined_skipped` 投影不带 question_id;`method-followup.ts` `declinedDomains` 把任何 declined 焦点的 `target_domain` 当成「用户拒答该领域」。本案用户对事业 2023 题、搬家 2015 题都选了 C(这两个答案**都参与了计分**,把 05:00 从 23 压到 21、04:53 从 7 抬到 9),却顺带把 career、relocation 两条线整体标成 declined:后续 `remainingConflictProbes`、D10/D4 分盘观察分支、`d10_refine / d4_refine` 精度阶段分支都会因 `declined.has(domain)` 跳过。用本案 receipt 重放,declined 只留 family 与留 {career, relocation, family} 决策相同,所以本案没有被它改写;但换一个还有事业年份题可问的案子,一次「没发生」就会把整条线关掉。
|
||||
|
||||
## 2. 决策记录(产品授权)
|
||||
|
||||
1. **采用门语义不变。** 候选分不开、探针池空时先出范围 + 代表分钟 + 采用卡,采用后再核对(BUG-463 / BUG-472 / BUG-501 的既定设计)。本单**不**把 holdout / OOS 提前到采用前——引擎剩余探针与保留事件都不能把相邻 6 分钟分开,提前问只是表演。若产品负责人希望改成「采用前先问保留事件」,另开任务书并在此处覆盖本条。
|
||||
2. 用户看到的应当是 **BUG-472 设计的交付旁白**(`deliveryAdoptNarration`:「已经从最初的 … 收到 05:00–05:06 … 代表分钟是 05:00 … 可以从下面选一个先用着。」),并且在同一句里说清**为什么停**:剩下的问题分不开 05:00 和 05:06。
|
||||
3. 「没有」= 对这一道题的否定,不等于「这条线以后都别问」。仅当焦点本身是**采集题**(`collect_method_evidence`)时,否定才算拒答该领域;对计分区分题的否定只计分、不关线。这条覆盖 BUG-440 里「答 C declined 计分」的表述中「declined 即覆盖」的那一半:D12 同领域卡「没有」仍计分,但 career/relocation 等**有年份**的区分题答「没有」不再写成领域拒答。
|
||||
|
||||
## 3. 硬红线
|
||||
|
||||
1. 不改 `decideRectification` 分支顺序、`publicCanAdopt`、`sessionOutcomeAllowsAdopt`、确认门、`MIN_SEPARATION_LEAD`、`minUncertaintyAnswers`、`maxPlateauRounds`。
|
||||
2. 不改 Python 引擎、不 bump Skill 版本(保持 10.0.14)、不改探针身份 / hash / `dropped_probes` reason 集合。
|
||||
3. 不得为了让 `collect:appearance` 能落库而放宽 DB check;`appearance / marks / horary / nakshatra` 这类非证据领域不得成为口述采集焦点。
|
||||
4. 不得再新增一处「先落焦点、失败了再兜底旁白」的路径;早退判定只在 `shouldSkipFollowupPersist` 一处。
|
||||
5. 测试总数不得低于 `origin/staging` `4d0a67a2` 的实测;改既有断言写「原值 / 新值 / 原因」。
|
||||
|
||||
## 4. 任务分解
|
||||
|
||||
### 4.1 P0 · 已丢弃的区分探针不得绕过采用早退(BUG-516)
|
||||
|
||||
1. `buildMethodFollowupPlan` 的 nakshatra 分支必须与决策层同一口径:只有 `nakshatraProbeIfFollowupCanAsk` 会选中的探针才产出 followup。实现方式二选一,实现者定并写进 BUG 条目:(a) `rectificationFollowupCatalog` 传入的 `nakshatraProbe` 先过 `inspectDiscriminatorProbes`(同 `askedKeys / topCandidateTimes`),不可渲染即传 null;(b) 分支内 `makeFollowup(..., true, true)` 产出无 `choice_frame` 时直接丢弃并进 `dropped_probes`。**两种都要保证** `plan.deferred_followup` 不再含无 `choice_frame` 的 `distinguish_candidates`。
|
||||
2. `answer-choice.ts` `isRemainingDiscriminatorFollowup`:无 `choice_frame` 的 distinguish followup 不算「剩余区分题」(它本来就渲染不出来)。`interviewToPersist` 对 `deferred_followup` 只在其可渲染时才取。
|
||||
3. `server-focus.ts` `persistableFocusDomain`:非白名单领域返回 `null`(不得返回 `appearance / horary / nakshatra`);`persistSpokenChoiceFallback` 只允许 `collectQuestionDomain` 后仍在白名单内的领域,否则返回 `status: "skipped"` 且**不**触发 `persistFocusAfterChoice` 的二次重试。既有断言 `persistableFocusDomain("horary") === "horary"` 改为 `null`:原值 `"horary"` / 新值 `null` / 原因:DB check 不含 horary,原断言锁的是一条必然写库失败的路径。
|
||||
4. 交付旁白带停止原因:`adoptHostNarration` / `deliveryAdoptNarration` 在 `stopReason` 为空但 `nextAction.type === "offer_provisional_range"` 且决策层 `droppedProbes` 非空、`probe` 为 null 时,在进度句之前加一句(放 `user-copy.ts`,进 `listUserVisibleCopy`):`剩下的问题已经分不开 05:00 和 05:06,先给范围。`(分钟由 `credible_range` 填入;两端相同则只说代表分钟)。`decisionReceipt` 已有 `dropped_probes`,不需要新字段;若需要在 `publicNextAction` 增加只读 `exhausted_probe_pool: boolean`,允许。
|
||||
5. 采用卡出现的那一轮,`current_question` 必须为 null 且不得有任何 `set_agentic_rectification_conversation_focus` 调用(BUG-472 断言原样搬到新 fixture)。
|
||||
|
||||
验收:
|
||||
- 新测试 `frontend/tests/rectification-adopt-narration-20260904.test.ts`:fixture 复刻本案形状——五件已确认证据(education×1 作 holdout、relationship×2、career×2)、`inference_state` 含 14 条探针(其中 nakshatra 一条、五条 yearless varga、一条 dasha_activation、两条 candidate_ids 不含活跃候选的 known_event_quality)、5 条 answered、活跃 {05:00, 05:06, 04:53} 分数 21/18/9、receipt 含 `window_scan.d*_candidates_differ=true`、`declined_skipped_topics=[family]`。断言:`decideFromDossier` 为 `offer_provisional_range / adopt_representative / canAdopt=true / probe=null`;`plan.next_followup` 与 `plan.deferred_followup` 均为 null 或均带 `choice_frame`;`persistNextInterviewAfterChoice` 返回 `persisted=false`、`choiceReady=false`、`hostNarration` 匹配 `/分不开 05:00 和 05:06/` 且 `/可以从下面选一个先用着/`、不匹配 `/继续往下收/`;`accounting.calls` 无 `set_agentic_rectification_conversation_focus`。
|
||||
- 同一 fixture 走 `applyCollectFocusDenial`(family 采集焦点 + 否定)得到同样旁白。
|
||||
- `rectification-server-focus`:`persistableFocusDomain("appearance") === null`、`("horary") === null`、`("nakshatra") === null`;对 `domain="appearance"` 的 distinguish followup(无 frame)调用 `persistServerOwnedFocus` 返回 `skipped` 且零次 RPC。
|
||||
- `rectification-collect-stall` 的「live five-evidence case」fixture 补上 nakshatra 探针与 `window_scan`,原断言不变仍需通过(这是回归锁)。
|
||||
- `docs/BUG_HISTORY.md` 新增 BUG-516:复发自 BUG-472(同一早退被 nakshatra deferred followup 绕过);相关 BUG-440、BUG-444、BUG-463。
|
||||
|
||||
### 4.2 P2 · 区分题答「明确没有发生」不再把整条领域标成拒答(BUG-517)
|
||||
|
||||
1. `focusStatusForAnswer` 保持返回 `declined`(DB 状态语义与 `uncertaintyHighFromAnswers` 的 `classified_from="declined"` 计数都依赖它,不动)。
|
||||
2. 改消费端:`method-followup.ts` `declinedDomains` 只把 **`intent === "collect_method_evidence"`** 的 declined/skipped 焦点算成领域拒答;`intent === "distinguish_candidates"` 的 declined 只是那一题的答案。`v_declined_skipped` 投影已带 `intent` 字段,不需要动迁移。
|
||||
3. `dossierWithClosedFocus`(`answer-choice.ts`)追加的行必须带 `intent`,否则消费端无法区分。
|
||||
4. `domainCollectFocusAsked` 语义不变。
|
||||
|
||||
验收:
|
||||
- `rectification-collect-stall`「denying the dated family collect declines relatives」原断言不变(采集题否定仍算拒答)。
|
||||
- 新增:同一 dossier,把 `declined_skipped_topics` 换成 `[{ target_domain: "career", intent: "distinguish_candidates", status: "declined" }]`,`buildMethodFollowupPlan` 的 `methods` 中 `d10_career` 不得因此变成 `covered`,且 career 域的剩余有年份探针仍可被 `remainingConflictProbes` 选中。
|
||||
- 用本案 receipt 形状重放,declined 为 `[family(collect)]` 与 `[family(collect), career(distinguish), relocation(distinguish)]` 两种输入,决策与旁白相同(本案不受影响,作为不变量锁住)。
|
||||
- `docs/BUG_HISTORY.md` 新增 BUG-517,关联 BUG-440(说明 BUG-440 的「答 C declined 计分」为什么当时对 D12 同领域卡成立,而对有年份区分题不成立)。
|
||||
|
||||
## 5. 让步顺序
|
||||
|
||||
1. P0 全做。若 4.1.4 的停止原因句与 BUG-503 已加的 `stopReasonPrefix` 排版冲突,先保留 BUG-503 的原因句,新句放其后,不得删任何一句。
|
||||
2. P2 若时间不够可拆成 `-fix` 单独做,但 BUG-517 条目必须以 `investigating` 状态先写进 `docs/BUG_HISTORY.md`。
|
||||
3. 任何一步需要放宽采用门 / 确认门才能通过测试,立即停手写 `BLOCKED.md`。
|
||||
|
||||
## 6. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-adopt-narration-20260904 .worktrees/rectification-adopt-narration-20260904 origin/staging
|
||||
cd .worktrees/rectification-adopt-narration-20260904/frontend
|
||||
./node_modules/.bin/tsc --noEmit
|
||||
npx tsx --test tests/rectification-collect-stall.test.ts tests/rectification-server-focus.test.ts tests/rectification-provisional-adopt.test.ts tests/rectification-adopt-flow-fix-20260903.test.ts
|
||||
npx tsx --test tests/*.test.ts 2>&1 | tail -12 # 记下开工时总数与失败清单
|
||||
```
|
||||
|
||||
验收命令(交付前):`tsc --noEmit` 0 错;`npm run lint` 0 error;`tests/rectification-*.test.ts tests/agentic-rectification-*.test.ts tests/birth-time-rectification-contract.test.ts` fail=0;全量 `tests/*.test.ts` 失败清单与开工基线逐条一致;`next build` 后 `/` 仍 Static。
|
||||
|
||||
## 7. BUG 编号起点
|
||||
|
||||
开工时核对 `docs/BUG_HISTORY.md` 最大号;截至本单撰写为 **BUG-515**,本单从 **BUG-516** 起。
|
||||
|
||||
## 8. 真实环境复核清单(实现者做不了的,交给产品负责人)
|
||||
|
||||
见 `docs/testing/`(实现者补一条 `rectification-adopt-narration-20260904.md`):在 staging 新开一个校正会话,走到探针池耗尽;预期采用卡出现的那一轮旁白说出「分不开 A 和 B,先给范围……可以从下面选一个先用着」,composer 旁有「先这样」而没有再冒出开场题;采用后进入核对题(BUG-501/502 流程)。
|
||||
Reference in New Issue
Block a user