Files
Jyotisha/docs/tasks/TASK-rectification-adopt-narration-fix2-20260904.md
T

102 lines
6.6 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.
# 修复单 2 · 采用旁白超时测试挂死事件循环,拖垮同文件后三条用例(2026-09-04)
基线:`origin/staging` `13dded9f``TASK-rectification-adopt-narration-fix-20260904.md` 的实现)。本单只改 `adopt-narration-agent.ts` 的超时实现与对应进度记录,不动决策逻辑、不动提示词。
## 0. 验收结论(对照修复单 1)
| 项 | 结论 | 证据 |
| --- | --- | --- |
| 5.1 早退分支用本轮决策;三条调用方传 `decision`facts / 模板用合成 receipt | 通过 | `persistNextInterviewAfterChoice` 函数体无 `decideFromDossier(``dossierWithCurrentInference`;点选用例改为点选前 dossierrevision 5、23/16/7),断言模型 1 次、facts 05:00 / 21/18/9 / `ready_to_adopt`;非法输出时模板范围来自本轮决策 |
| 5.2 提示词「不要出现」;诊断枚举 `adopt_narration=`8s 超时 | 通过(实现)/ **未通过(测试,见 §1** | 提示词与校验器同口径;`deliverAdoptNarration` 返回 `adopt_narration``console.info` 不含模型原文;四种结果各有断言 |
| 5.3 `stopFactsFromDropped` 婚恋标签 | 推迟(允许) | 进度记录已写明 |
| BUG-521 / BUG-522 | 通过 | 无案例 ID、无用户资料;复发链接 BUG-440 |
| CHANGELOG、`docs/testing` 清单 | 通过 | 点选入口列为优先手测项 |
| tsc / lint | 通过 | `tsc` 0 错;lint 0 error 74 warning(既有) |
| 定向套件 | **未通过** | `rectification-* / agentic-rectification-* / birth-time-*`871 条,pass 858fail 0**cancelled 4**,进程退出码 1 |
## 1. 事故实证(P1 · 超时测试挂死)
`adopt-narration-agent.ts`
```ts
function composedAbortSignal(signal, timeoutMs) {
const timeout = AbortSignal.timeout(timeoutMs);
return signal ? AbortSignal.any([signal, timeout]) : timeout;
}
```
Node 的 `AbortSignal.timeout()` 内部计时器是 **unref** 的(所有版本,含 CI 用的 Node 22):它不会让事件循环保持活跃。测试「adopt narration times out to the template without throwing」把 `generateText` 挂成永不 resolve 的 Promise,此时进程里唯一待办就是这个 unref 计时器 → 事件循环直接排空,测试运行器判 `cancelledByParent: Promise resolution is still pending but the event loop has already resolved`。同一文件后面三条用例(`applyCollectFocusDenial …``distinguish declined …``family collect declined vs extra distinguish declined …`——后两条正是 BUG-520 的回归锁)也被一并取消。
本地复现(Node 20.19`npx tsx --test tests/rectification-adopt-narration-20260904.test.ts`):
```
ok 1 … ok 6
not ok 7 - adopt narration times out to the template without throwing (cancelledByParent)
not ok 8 / 9 / 10 (cancelledByParent)
# tests 10 # pass 6 # fail 0 # cancelled 4
exit=1
```
独立验证(去掉所有业务代码):
```js
const s = AbortSignal.timeout(30);
await Promise.race([new Promise(() => {}), new Promise((_, rej) => s.addEventListener("abort", () => rej(s.reason)))]);
// 进程直接退出,退出码 13,abort 从未触发
```
后果:`backend-quality-gate``npm test --prefix frontend` 会以退出码 1 失败,`13dded9f` 不会部署(本单落笔时 staging `/api/health` 仍是 `0aaa0d70`)。
进度记录写的「adopt-narration 10/10、871 pass / 0 fail」与实测不符:Node 摘要行 `# fail 0` 后面还有 `# cancelled 4`,且退出码为 1。
生产侧:`next start` 有监听 socket 撑住事件循环,超时在真实请求里会生效,本单**不是**线上功能故障;但计时器不随模型返回而清理,每次调用都会留一个 8 秒的悬挂计时器。
## 2. 根因
超时用了 `AbortSignal.timeout()`,没意识到它的计时器是 unref 的;测试又依赖它在空事件循环里触发。验收只看了 `# fail`,没看 `# cancelled` 与退出码。
## 3. 决策记录
原任务书与修复单 1 的决策不变。本单不新增产品决策。
## 4. 硬红线
修复单 1 §4 全部沿用。追加:
1. 超时必须用 **ref** 的计时器(`setTimeout` + `AbortController`),并在模型返回、校验完成或外部 signal 触发后 `clearTimeout`;不得靠 `AbortSignal.timeout()`
2. 不得为了让测试过而删掉超时用例或把它改成 `todo` / `skip`
3. 进度记录必须贴 Node 摘要的完整六行(tests / pass / fail / cancelled / skipped / todo)与进程退出码。
## 5. 任务分解
### 5.1 P1 · 超时改为可清理的 ref 计时器(BUG-523
1. `composedAbortSignal` 改为返回 `{ signal, dispose }`:内部 `new AbortController()``setTimeout(() => controller.abort(new DOMException("adopt narration timed out", "TimeoutError")), timeoutMs)`(不 `unref`),若有外部 `signal` 则监听其 `abort` 转发到 controller`dispose()` 清计时器并移除监听。
2. `deliverAdoptNarration``Promise.race``try / finally` 调用 `dispose()`;超时仍归 `template:model_error`(枚举不变)。
3. `whenAborted` 的监听在 `dispose` 时一并移除,避免每次调用留下悬挂监听器。
验收:
- `npx tsx --test tests/rectification-adopt-narration-20260904.test.ts``# tests 10 # pass 10 # cancelled 0`,退出码 0。
- 定向套件 `rectification-* / agentic-rectification-* / birth-time-*``# fail 0 # cancelled 0`,退出码 0。
- `npm test`(全量):与无 Docker 基线一致,即除 `docker ENOENT` 的 25 条外无其它失败,`# cancelled 0`
- 新增一条用例:fake `generateText` 立即返回合法文案时,`deliverAdoptNarration` resolve 后不再有活跃计时器(可用 `setTimeout` 计数或断言 `dispose` 被调用),防止每次调用漏一个 8 秒计时器。
- `docs/BUG_HISTORY.md` 新增 BUG-523,关联 BUG-522;防复发写明「超时不用 `AbortSignal.timeout`」与「验收看 cancelled 与退出码」。
- `PROGRESS-rectification-adopt-narration-fix-20260904.md` 补一节更正:原「10/10、871 pass / 0 fail」实测为 pass 858 / cancelled 4 / exit 1。
## 6. 让步顺序
只有 5.1 一条,必做;没有它 `13dded9f` 进不了 staging。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-adopt-narration-fix2-20260904 .worktrees/rectification-adopt-narration-fix2-20260904 origin/staging
cd .worktrees/rectification-adopt-narration-fix2-20260904/frontend
npx tsx --test tests/rectification-adopt-narration-20260904.test.ts; echo "exit=$?" # 开工前应复现 cancelled 4 / exit 1
```
## 8. BUG 编号起点
截至本单:BUG-522。本单从 **BUG-523** 起;开工时再核对。