docs(tasks): brief for duplicate delivery turn and refused adoption (BUG-1241~1243)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-10-06 11:52:09 +08:00
co-authored by Claude Opus 5.5
parent e3f3a1fcde
commit 65b61ed523
2 changed files with 111 additions and 0 deletions
+1
View File
@@ -423,3 +423,4 @@
| `TASK-gate-log-volume-20261004.md` | `PROGRESS-gate-log-volume-20261004.md` | 门禁 validate 单步日志 4.4 万行 / 2 MB 网页打不开(run 3170):快速门只打摘要(失败给末 200 行 + 日志文件)、前端测试门禁上 dot + 失败汇总(本机仍 TAP)、拆分 validate 为 7 个 step(产品授权改 workflow,只限拆分与重定向);检查一项不少 | **已实现待验收**(BUG-1230;未推送、未部署;Gitea 各 step 页面是否打得开留待推送后由产品确认) | 分支 `codex/gate-log-volume-20261004` |
| `TASK-consult-latency-quickwins-20261005.md` | `PROGRESS-consult-latency-quickwins-20261005.md` | 普通对话耗时两项快修:咨询链校正闸不再同步白等 VedAstro 官方快照子进程(每域约 4 s,复用顶层缓存 + 负缓存,不推翻 BUG-301);补分段计时(第 0/1 步耗时、推理 token、分类耗时进日志与 usage)。推理强度/精简说明待模型 key 另单(BUG-1231、BUG-1232) | 已验收(Claude 10-05 Linux:Python 62 / 前端 24 失败与基线逐名相同,Static、gzip 0%;装 SDK 实测每领域 4.47→1.66 s,剩余 1.33 s 是保留的顶层前台等待;/admin/usage 真机欠) | 分支 `codex/consult-latency-quickwins-20261005` |
| `TASK-rectification-nadi-seconds-research-20261005.md` | `PROGRESS-rectification-nadi-seconds-research-20261005.md` | **「纳迪秒级校准」可证伪检验(离线)**:竞品宣传「问前事到天 → 秒级」。本仓主链只用三层小运,Sookshma / Prana 与 D150 从未进评价集;v5 真值 52/77 是整 5 分钟(秒级无真值可对)。N0 五层小运 + D150 底座(前三层与主链对账 0 差)、N1 拟合率 vs 安慰剂日期(核心)、N2 留一件预测、N3 六题后区间内再细分能否提头名、N4 岁差 / 坐标 / 时间扰动的噪声地板、N5 D150 结构层(原文比对 blocked)、N6 结论 + 对外口径草稿。规则先登记再跑;不改生产代码;不得重调 BUG-1091 已关的权重 | 已验收(Claude 10-05:N1/N2/N3/N5 不过门、N4 触发文案禁令;Linux 复跑内容 0 差,结果改 LF;补 Lahiri/KP/True Chitra 对照,月亮差 0.83′ 第 5 层仍换 94%)→ 不立实现单 | 分支 `codex/rectification-nadi-seconds-research-20261005`(BUG-1240 `closed_by_design`) |
| `TASK-rectification-delivery-dup-adopt-20261006.md` | `PROGRESS-rectification-delivery-dup-adopt-20261006.md` | **校正交付一次两段回答 + 点「改用…盘解读」被拒 `adoption_not_allowed`**(10-06 真机):收尾补位按文字子串防重,遇采用旁白 Agent 改写版失效(BUG-1153 复发);accept/GET 调 `decideFromDossier` 不带出生日期,未成年探针复活致判定与出卡时相反(BUG-598/680 同族);卡片按钮不看公开 can_adopt、拒绝码直出英文。不放宽采用门,判定入参统一 + 结构防重 + 按钮判据 + 中文提示(BUG-1241~1243) | 待领取 | — |
@@ -0,0 +1,110 @@
# TASK · 校正交付「一次两段回答」与「点采用被拒 adoption_not_allowed」(2026-10-06)
- 基线:`origin/staging` @ `e3f3a1fc`
- 执行分支:`codex/rectification-delivery-dup-adopt-20261006`,worktree `.worktrees/rectification-delivery-dup-adopt-20261006`
- BUG 编号起点:**BUG-1241**(本单用 1241~1243)。开工时核对 `docs/BUG_HISTORY.md` 最大号,并扫全部 `docs/tasks/TASK-*.md` 正文里的预留号;撞号顺延,在 PROGRESS 写明。
- 模式:任务书模式;执行方实现,Claude 事后验收。
## 1. 事故实证(产品 10-06 11:45 staging 真机,iPhone)
盘型口径(segment-v1)校正,窗口 04:45–05:15(31 分钟),答完 4 道选择题后:
1. **一次出了两条助手消息**,内容是同一件事:
- 第 1 条:「已记录,范围没变。因为再问下去也分不开 04:53 和 05:00,所以这一轮到这里停住。…采用后没有还能核对的前事,之后新建对话即按此时间排盘,对不上可改选。我按你说的经历认真分析过了,下面是这次的结果。」(采用旁白 Agent 改写版 + `adoptCue`)
- 第 2 条:「D1 上升:巨蟹(确定);D9 上升:天蝎(分不开),备选:射手、天秤;D10 上升:狮子(分不开),备选:巨蟹、处女。分钟范围 04:45–05:15。用了 4 道选择题。这个窗口按现在的方法缩不下去。」(`deliveryTurnNarration` 确定性原文,逐字一致)
2. 盘型卡显示「采用后按 1997-08-08 05:03 排盘」和按钮 **「改用 D1 + D9 + D10 盘解读」**;点击后卡下红框显示英文 **`adoption_not_allowed`**。
## 2. 根因
### 2.1 两段回答(BUG-1241,BUG-1153 复发)
- 答题回复走 `persistNextInterviewAfterChoice`(`answer-choice.ts`),交付轮用 `input.narrateAdopt(facts, fallback)` 把确定性交付话交给采用旁白 Agent 改写,第 1 条就是改写后的文本。
- 之后某条收尾路径又调 `persistNextInterviewIfIdle`,但**没有传 `narrateAdopt`**,于是拿到确定性 `fallback`(`adoptHostNarration` 在盘型口径下只返回 `deliveryAdoptNarration`,正是第 2 条),再交给 `persistExhaustionGateTurn` 落库。可能的调用方有三处,执行方须用回放确认是哪一处:`turn-exit.ts` 的 `finalizeSuccessfulTurnExit`、`agent-run-finish.ts`、`answer-choice.ts` 的出口修复(日志 `missing_question_and_adopt_carrier`)。
- `persistExhaustionGateTurn` 的防重(BUG-1153)是 `narrationAlreadyInLastAssistant`:**按去空白后的子串比对**上一条助手消息。确定性原文被模型改写后不再是子串,防重失效,第 2 条落库。
- 为什么 BUG-1153 的测试没拦住:当时上一条是确定性原文;后来交付轮接入了采用旁白 Agent 改写,测试没有覆盖「上一条是改写版」。
### 2.2 点采用被拒(BUG-1242,BUG-598 / BUG-680 同族)
- 出卡时:答题路径 `decideFromDossier(dossier, { birthDate, snapshotCurrent })` 带了出生日期。`inspectDiscriminatorProbes` / `discriminatorProbeIfFollowupCanAsk` / `nakshatraProbeIfFollowupCanAsk` 用 `probeBelowAdultFloor`(`adult-floor.ts`)筛掉未成年年份的探针,探针池为空后命中 `tied_first` 交付,公开 `can_adopt=true`,出了卡。
- 点采用时:`candidates/accept/route.ts` 调 `decideFromDossier(dossier, { currentEvidenceFingerprint })`,**没有 `birthDate`**。`probeBelowAdultFloor` 在没有出生日期时一律返回 false,未成年探针重新算成「还能问的题」,于是 `tied_first` 分支的 `!probe` 不成立,判成不可采用,返回 409 `adoption_not_allowed`。
- GET 也没有 `birthDate`:`case-dossier-response.ts` 的 `decideFromDossier`,以及 `interview-state.ts` 里三处。推测用户刷新后卡片会消失或回到采集状态,也就是 BUG-680「POST 交付、GET 回采集」的症状。
- BUG-598 只把出生日期接到了答题与 inspect 回退两条路径,GET 和 accept 两条漏了。BUG-680 防复发条款要求的「POST/GET 对拍测试」没有覆盖 `birthDate` 这个入参。
- **嫌疑须先证实(T0)**:`snapshotCurrent` 也只在答题路径传入,同样会让两边结论不同。执行方须用回放确认真正起作用的是哪一个入参(或两个都起作用),再动手修。
### 2.3 卡片按钮不看公开 can_adopt,拒绝码直出英文(BUG-1243)
- `rectification-segment-delivery.tsx` 只要 `summary.adoption_minute` 和 `representative_candidate_id` 都在,就渲染采用按钮,不看公开 `can_adopt` / `session_outcome`。旧分钟卡 `rectification-range-delivery.tsx` 的采用按钮同样不看。所以出现了「卡上能点、服务器必拒」。
- accept 路由 409 返回 `{ error: "adoption_not_allowed" }`,没有中文。`rectification-chat-accept-run.ts` 把 `payload.error` 原样抛出,显示出来就是英文代号。
## 3. 决策记录
产品 10-06 同意按以下方案出任务书。本单**没有**推翻既有产品决策。
1. **不放宽采用门**。修法是让 GET 和 accept 与答题路径用同一组判定入参(以出生日期成年下限为准,它是 BUG-399 / 417 / 598 确立的产品规则),不得删掉 `can_adopt` 校验、不得让 accept 跳过 `decideFromDossier`。
2. **防重改为结构判断,不再比文字**:本轮答题回复已经承载交付(交付轮已落库,或 `markDeliveryTurn` / `alreadyDelivered` 已记),收尾补位就不再写。可以保留文本比对作第二道保险,但不得只靠它。不得删掉 BUG-1149 让收尾轮次能落库的修复(没有回复承载时,收尾句仍须落库)。
3. **按钮只在公开 `can_adopt=true` 时出现**,判据与 accept 路由一致:`ADOPT_OUTCOMES`,盘型卡与旧分钟卡都改。`DELIVERY_OUTCOMES` 下不能采用时照常出卡(BUG-681),但不出采用按钮。
4. 拒绝提示用中文,口径对照 `frontend/docs/VOICE.md`,建议为「这次的结果还不能采用,已刷新到最新状态」。被拒后客户端重拉一次快照,让卡片和题回到服务端真实状态,不留一个点不动的按钮。
## 4. 硬红线
1. 不改引擎、不改 `decision_policy`、不改 `ADOPT_OUTCOMES` / `DELIVERY_OUTCOMES` 集合成员、不改数据库结构与迁移。
2. 不得让公开 `can_adopt` 在 `collect_evidence` / `discriminate_candidates` / `validate_holdout` 放行(BUG-497 防复发)。
3. `scripts/jyotish_api_server.py`、`frontend/src/app/page.tsx` 不得增长。
4. 改任何既有断言须写「原值 / 新值 / 原因」三栏;测试总数不低于基线。
5. 不得改采用旁白 Agent 的提示词来「对齐」确定性原文(那是绕开问题,不是修)。
6. 隐私:回放 fixture 用虚构或公开名人资料,不得写入产品本次会话的出生资料。
## 5. 任务分解
### T0 复现(先做,结论写进 PROGRESS 再动 T1~T3)
- 用本机 PG17 真库 harness(参照 `frontend/tests/research/rectification-segment-persisted-replay.ts`;无 Docker 的做法见 BLOCKED / 既有 PROGRESS),构造一个盘型口径案例。条件:出生年份使至少一道剩余区分探针落在成年下限以下;窗口约 ±15;最后两名后验并列(`tied_first`)。
- 依次走「答完最后一题 → 列出所有助手消息 → 调 accept → GET 案例」,记录:助手消息条数与文本来源、答题时 / accept 时 / GET 时的 `session_outcome` 与 `can_adopt`、哪条路径写了第 2 条消息。
- 分别只补 `birthDate`、只补 `snapshotCurrent`,看哪一个让三处结论一致。
- **验收**:PROGRESS 有修前复现记录:两条消息,accept 409 `adoption_not_allowed`,GET 的 outcome 与答题时不同(若不同)。
### T1 判定入参统一(BUG-1242)
- 抽一个共享的「取案例判定入参」函数,从出生快照读 `birthDate`。accept 路由、`case-dossier-response.ts`、`interview-state.ts` 的三处、`agent-run-attempt.ts` 等所有 `decideFromDossier` 调用方都用它,不再各自拼参。T0 证实 `snapshotCurrent` 也起作用的话,一并处理。
- 读出生快照失败时:accept 返回 503(暂时无法采用),不得退化成无 `birthDate` 判定后给出相反结论。
- **验收**:新增对拍测试:同一份 dossier 的答题路径、GET、accept 三处的 `session_outcome` / `can_adopt` 逐字段相等,用表驱动覆盖「有 / 无未成年探针」×「并列 / 不并列」;T0 场景修后 accept 成功、GET 仍是交付态。再加一条源码合同测试:新增的 `decideFromDossier` 调用必须经过共享入参函数,防止漏传。
### T2 收尾补位不重复(BUG-1241)
- `persistExhaustionGateTurn` 写入前先看结构状态:最近一条助手消息已经是本结果的交付轮(交付标记 / `result_id` / 请求号可证),就跳过。改写版与原文都要能识别。确认 T0 找到的那条调用方是否该传 `narrateAdopt`:要么所有落库路径共用同一段文本,要么根本不该再写。二者选一,在 PROGRESS 说明。
- **验收**:T0 场景修后助手消息恰好 1 条(实时、GET 快照、刷新三路径一致)。保留 BUG-1149 / 1153 既有测试且通过。新增「上一条是 Agent 改写版」用例。没有回复承载时,收尾句仍落库 1 条。
### T3 卡片与提示(BUG-1243)
- 盘型卡、旧分钟卡的采用按钮加公开 `can_adopt` 判据,`RectificationCandidateResult` 上已有的字段够用就不新增字段。不能采用时卡片照出,但不出按钮,也不出「采用后按…排盘」这一行。
- accept 路由 `adoption_not_allowed` 返回中文 `error` + `code: "adoption_not_allowed"`。客户端遇到 409 时显示中文、重拉快照。
- 同一提交更新 `frontend/DESIGN.md` 校正状态表(不能采用时卡片的样子)。
- **验收**:组件测试覆盖 outcome × can_adopt 表(按钮出现当且仅当 `ADOPT_OUTCOMES` 且 can_adopt);DOM 不出现 `adoption_not_allowed` 字样。
### T4 记录
- `docs/BUG_HISTORY.md` 新增 BUG-1241(复发自 BUG-1153)、BUG-1242(复发自 BUG-598,关联 BUG-680)、BUG-1243(关联 BUG-681 / 497);写明旧测试为何没拦住。
- `CHANGELOG.md` 一条;`docs/testing/rectification-delivery-dup-adopt-20261006.md` 真机清单(走到并列交付 → 只有一条交付消息 → 点采用成功 → 刷新卡片仍在且显示已采用)。
- `docs/tasks/README.md` 状态板改行。
## 6. 验收口径
- `tsc --noEmit` 0;`npm run lint` 0 error;`npm test` 失败清单与基线逐名一致,总数不降;`next build` 后 `/` Static,首屏 gzip ±2%。
- 本机 PG17 真库走 T0 场景修后全通(假数据库测试不算,见 BUG-1149/1150 教训)。
- 推 staging 前按镜像 COPY 清单构建一次(ERR-113);推前等上一轮部署结束。
- 部署后 `/api/health` 的 `deployment.gitCommit` = 本单 staging SHA。
## 7. 让步顺序
时间不够时按此顺序保:T0 → T1(按钮能点就必须能采用,这是 P0)→ T3 中文提示与按钮判据 → T2 去重 → T4。T2 若来不及,须在 PROGRESS 写 blocked,不得只靠调文本比对糊过去。
## 8. 开工前置命令
```bash
git -C /workspace/Jyotisha status -sb | head -1
git -C /workspace/Jyotisha fetch origin --prune
git -C /workspace/Jyotisha worktree add -b codex/rectification-delivery-dup-adopt-20261006 \
.worktrees/rectification-delivery-dup-adopt-20261006 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1
grep -rho "BUG-12[4-9][0-9]" docs/tasks | sort -u
```