docs(tasks): adopt narration accepted except click entry; fix brief

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
This commit is contained in:
Jesse_Chen
2026-09-04 06:33:35 +00:00
parent 0aaa0d702e
commit e18bd25b19
2 changed files with 111 additions and 1 deletions
+2 -1
View File
@@ -44,7 +44,8 @@
| `TASK-rectification-adopt-flow-20260902.md` | — | 采用流程整治 | 已验收 | `35e5781e`BUG-497500 |
| `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` | `PROGRESS-rectification-adopt-narration-20260904.md` | 探针池耗尽时采用卡旁白改 Agent 生成 + 已丢弃探针绕过 BUG-472 早退 + 区分题答否关线 | 验收 | 同步后基线 `cfa82499`BUG-519 / BUG-520 |
| `TASK-rectification-adopt-narration-20260904.md` | `PROGRESS-rectification-adopt-narration-20260904.md` | 探针池耗尽时采用卡旁白改 Agent 生成 + 已丢弃探针绕过 BUG-472 早退 + 区分题答否关线 | 验收(4.1.4 点选入口未通过,见修复单) | `0aaa0d70`BUG-519/520 |
| `TASK-rectification-adopt-narration-fix-20260904.md` | — | 采用旁白 Agent 在点选入口从不运行(早退分支重算过期决策)+ 校验器/可观测/超时 | 待执行 | 基线 `0aaa0d70`BUG-521 起 |
| `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505509 |
### 聊天主链路与首页
@@ -0,0 +1,109 @@
# 修复单 · 采用旁白 Agent 在点选入口从不运行(2026-09-04)
基线:`origin/staging` `0aaa0d70``TASK-rectification-adopt-narration-20260904.md` 的实现)。本单只改 `answer-choice.ts` 与新测试,不动 Agent 提示词以外的决策逻辑。
## 0. 验收结论(对照原任务书)
| 项 | 结论 | 证据 |
| --- | --- | --- |
| 4.1.1 nakshatra 计划/决策同口径,无 frame 的 distinguish 不进 `deferred_followup` | 通过 | `rectificationFollowupCatalog``inspectDiscriminatorProbes`;计划层兜底丢进 `dropped_probes(not_renderable)` |
| 4.1.2 `isRemainingDiscriminatorFollowup` / `interviewToPersist` 忽略无 frame distinguish | 通过 | 代码 + 14 探针 fixture |
| 4.1.3 `persistableFocusDomain` 非白名单返回 nullappearance 零 RPChorary 断言三栏说明 | 通过 | `rectification-server-focus``rectification-collect-stall` |
| 4.1.4 采用旁白 Agent + 模板兜底,三条入口都接 | **未通过(P1** | 见 §1;点选入口 Agent 从不运行,且模板改用了过期决策 |
| 4.1.5 采用轮 `current_question` 为 null、零次 set_focus | 通过 | 新测试 `assertNoFocusWrite` |
| 4.2 区分题答否不关整条领域 | 通过 | `declinedDomains` 按 intent`dossierWithClosedFocus` 带 intentBUG-520 |
| `rectification-collect-stall` live fixture 补 nakshatra + window_scan 后原断言仍过 | 通过 | 本地重跑 |
| tsc / lint / 定向套件 | 通过 | `tsc` 0 错;lint 0 error 74 warning(既有);`rectification-* / agentic-rectification-* / birth-time-*` 869 条 fail=0 |
| 全量 `tests/*.test.ts` | 环境缺口 | 2638 条,fail 25 全部为 `docker ENOENT` 的 DB / 部署 / 备份套件,与无 Docker 基线一致;无一条校正相关 |
| BUG-519 / BUG-520、CHANGELOG、docs/testing 清单 | 通过 | 无案例 ID、无用户资料 |
## 1. 事故实证(P1 · 点选入口 Agent 从不运行)
`answer-choice.ts` `persistNextInterviewAfterChoice` 的采用早退分支:
```ts
const decision = decideFromDossier(input.dossier, { birthDate });
const facts = adoptDeliveryFacts(decision, input.dossier);
const fallback = adoptHostNarration({ dossier: input.dossier, decision, ... });
```
点选路径(`applyRectificationChoice``persistApplied`)传进来的 `input.dossier` 是**点选前**加载的 dossier,其 `latestResult.decisionReceipt.inference_state` 还是上一轮;本轮新状态在 `input.decisionState`,本轮决策在 `input.nextAction``persistApplied` 手里还有完整的 `nextDecision`)。函数开头已经把两者合成了 `latest`,但早退分支没有用它,而是从旧 dossier 重新 `decideFromDossier`
- 旧 dossier 的决策是「还有一道题要问」(`theme_refine` / `ask_candidate_discriminator``probe` 非空),`precisionStage !== "ready_to_adopt"``shouldWriteAdoptNarration` 为 false → **`narrateAdopt` 直接返回 fallback,模型一次都没调**。
- fallback 模板的 `credibleRange / representativeTime / stopReason` 也来自这份旧决策,而不是本轮的 `nextAction`。改造前模板用的是 `nextAction.credible_range`,这是回归。
本地复现(用 14 探针 fixture,把 dossier 里的 receipt 回退成「最后一道题未答、分数 23/16/7」,`decisionState` 用答完后的状态,`nextAction` 用答完后的决策):
```
pre-click decision: theme_refine, probe=true
agent called: false
narration: <模板>
fresh nextAction: offer_provisional_range [05:00, 05:06] 05:00
```
新测试「three adopt entry points call the model once」没拦住,是因为它给点选入口传的 `dossier` 已经是答完后的状态(receipt 与 `decisionState` 相同),与生产的点选路径不一致。
用户可感知:**点最后一道区分题**(采用卡最常见的出现方式)时,旁白仍是模板句;只有采集题答「没有」和空闲兜底两条入口才会走 Agent。
## 2. 根因
早退分支把「决策」当成可以从 dossier 随手再算一遍的东西,违反 BUG-440 防复发条款「同一次调用里出题层与决策层必须用同一个 sessionOutcome」——这里是决策层自己算了两遍、用了两份不同输入。
## 3. 决策记录
原任务书决策不变。本单不新增产品决策。
## 4. 硬红线
原任务书 §3 全部沿用。追加:
1. `persistNextInterviewAfterChoice` 内不得再调用 `decideFromDossier`;决策由调用方传入,或用 `decideAfterInferenceChange({ dossier, state: input.decisionState })``persistApplied` 保持同一入口。
2. 「三条入口」测试的点选用例必须用**点选前** dossier + 答完后 `decisionState` + 答完后 `nextAction` 的形状;不得再用已答完的 dossier 冒充。
## 5. 任务分解
### 5.1 P1 · 早退分支用本轮决策(BUG-521)
1. `persistNextInterviewAfterChoice` 新增可选入参 `decision?: RectificationDecision`;三条调用方都传:`persistApplied``nextDecision``persistNextInterviewIfIdle` 传已算好的 `decision``applyCollectFocusDenial` 传它用来生成 `nextAction` 的那份决策。未传时用 `decideAfterInferenceChange({ dossier: input.dossier, state: input.decisionState, userStopped: false, birthDate })`,不得用 `decideFromDossier(input.dossier)`
2. `adoptDeliveryFacts``adoptHostNarration` 的 dossier 参数改传合成后的 `{ ...input.dossier, latestResult: latest }`(函数开头已有的 `latest`),保证活跃候选与相对支持度是本轮的。
3. `persistApplied``adoptionFacts = adoptDeliveryFacts(nextDecision, input.dossier)` 同样改用合成 receipt。
验收:
- `rectification-adopt-narration-20260904.test.ts`「three adopt entry points」点选用例改成 §1 的形状:`dossier` receipt 为「最后一道题未答、分数 23/16/7、revision 5」,`decisionState = fourteenProbeState()``nextAction = publicNextAction(decideFromDossier(caseDossier()))`。断言:模型被调用 1 次;传给模型的 `facts.representative_minute === "05:00"``facts.active_candidates` 的分数为 21/18/9(不是 23/16/7)、`facts.precision_stage === "ready_to_adopt"`
- 同一形状下让 fake model 返回非法文本,断言 fallback 模板里的范围与代表分钟来自 `nextAction`05:0005:06 / 05:00),并含「分不开 05:00 和 05:06」。
- 加一条源码级断言:`persistNextInterviewAfterChoice` 函数体内不出现 `decideFromDossier(`
- `docs/BUG_HISTORY.md` 新增 BUG-521,复发自 BUG-440(决策层重复计算)。
### 5.2 P2 · 校验器把「不是确认」也打回(BUG-522,可与 5.1 同批)
`validateAdoptNarration``/确认|精确/` 是全文匹配;提示词只说「不得承诺确认或精确」,模型很自然会写「这不是确认的分钟」——会被整段打回、静默退到模板,Agent 路径在真实环境的命中率会被这条拉低而且没人知道。
1. 提示词改成「不要出现『确认』『精确』这两个词」(与校验器同口径)。
2. 增加一条可观测:Agent 路径的结果写进本轮 run diagnostic / 服务端日志一个枚举字段 `adopt_narration: "agent" | "template:<validate reason>" | "template:model_error" | "template:not_ready"`,不写模型原文。没有这条,部署后没人能证明 Agent 到底有没有写。
3. `agent.generate` 加超时:`AbortSignal.any([request.signal, AbortSignal.timeout(8000)])`,超时走模板。分类器没有超时是既有缺口,不在本单修,写进 `BLOCKED.md` 或进度记录。
验收:fake model 返回「这不是确认的分钟,先用 05:00。」→ 仍打回(校验器不变),但提示词测试断言含「不要出现」;诊断字段在三种结果下各有一条测试;超时用 fake `generateText` 挂起 + 短 timeout 断言走模板且不抛。
### 5.3 P3 · `stopFactsFromDropped` 的「婚恋题只覆盖窗口两端」标签
`insufficient_candidates || not_renderable` 一律标成婚恋题,而 `not_renderable` 现在也用于 nakshatra。改成按 `semantic_key` 前缀分桶(`relationship.` → 婚恋,其它 → 「有几道题渲染不出来」),或去掉领域词。可推迟。
## 6. 让步顺序
1. 5.1 必做,否则 4.1.4 在最常见入口上等于没做。
2. 5.2 的可观测字段与超时优先于提示词改动。
3. 5.3 可推迟到下一轮。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-adopt-narration-fix-20260904 .worktrees/rectification-adopt-narration-fix-20260904 origin/staging
cd .worktrees/rectification-adopt-narration-fix-20260904/frontend
./node_modules/.bin/tsc --noEmit
npx tsx --test tests/rectification-adopt-narration-20260904.test.ts tests/rectification-answer-choice.test.ts tests/rectification-collect-stall.test.ts
```
## 8. BUG 编号起点
截至本单:BUG-520。本单从 **BUG-521** 起;开工时再核对。