Files
Jyotisha/docs/tasks/PROGRESS-rectification-delivery-dup-adopt-20261006.md
T
Jesse_ChenandClaude Opus 5.5 e62d0a3257
Independent Staging Quality Gate / validate (push) Successful in 14m30s
Independent Staging Quality Gate / publish (push) Successful in 3m52s
fix(rectification): match the delivery inside the acknowledged reply (BUG-1241)
The answered choice persists the acknowledgement, a blank line, and the
rewritten delivery. The exit fill-in required equality and wrote the
deterministic delivery again. Check containment instead, and pin it on the
real answer path. The suspected widen staleness regression did not hold;
keep a guard test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-10-06 16:14:24 +08:00

13 KiB
Raw Blame History

PROGRESS · 校正交付一次两段回答与点采用被拒(2026-10-06)

  • 任务书:docs/tasks/TASK-rectification-delivery-dup-adopt-20261006.md(staging 65b61ed5,只改文档,未部署)
  • 执行分支:codex/rectification-delivery-dup-adopt-20261006
  • 基线:任务书写的产品基线是 e3f3a1fc;实现从已含任务书的 65b61ed5 开始
  • BUG:1241、1242、1243。开工时 docs/BUG_HISTORY.md 最大号是 BUG-1240,没有撞号
  • 修复版本:待发布。本轮不推送,不声称已部署

T0 修前复现(先于 T1–T3)

虚构出生日期 1988-03-12。开口窗口 03:00–07:00,可信范围 04:45–05:15,并列分钟 04:53 与 05:00。1998 年搬家探针(10 岁,低于搬家下限 18 岁)。四条带日期的证据。候选集合 04:45-05:15:04:53,05:00。探针的支持/冲突写的是分钟,不是候选编号。

下面两轮诊断不算复现:第一轮内存夹具把支持写成了候选编号,开口窗口又等于可信范围,采用被「范围没变窄」挡住,三处都不可采用。第一轮 PG 回执的展示许可与选择许可对不上,能力上限关掉,旁白没有改写,两条消息是同一段确定性原文。成功的那一轮才是下面的记录。

内存矩阵

当前证据指纹 f0cbba1a89013d33aaa89a7ee39fc81caf3528e5a9e9407a3b41eddfea2f2c06。

入参 session_outcome can_adopt 探针
答题:出生日期 + snapshotCurrent adopt_representative true 无(低于成年下限)
采用:只有指纹 discriminate_candidates false relocation.1998.dasha_activation
只补出生日期 adopt_representative true 无
只补 snapshotCurrent discriminate_candidates false 探针还在
两个都补 adopt_representative true 无

指纹已经对上时,只补出生日期就和答题一致;只补 snapshotCurrent 不行。没有未成年探针时,缺出生日期也不改变结论。

过期指纹(64 个 b)上,答题路径若强行 snapshotCurrent: true,结论也会变。生产里的重算只在重新读出的卷宗已经对上、或重算之后再从这份卷宗算出来时才标当前,不会对着过期指纹硬写成 true。所以共享入参必须带校验过的出生日期,snapshotCurrent 由卷宗自己算,答题路径不再私自传 true。

PG17

真 PostgreSQL 17。种的是答完之后的并列卷宗,没有跑星历引擎。算法 rectification-v5-matrix-scoring-8,策略 fictional-decision-policy-v1,回执的展示/选择/确认三项与列一致且上限打开。Windows 上 db/migrations 的四个符号链接被检出成普通文件,和 supabase/migrations 重名,两目录迁移会停。这一轮把 supabase 全部加上 db 里不重名的本地身份文件放进一个临时目录,设 MIGRATIONS_DIRECTORY。这不是「两目录迁移在 Windows 上通过」。

同一份已对上指纹的卷宗:

路径 session_outcome can_adopt
采用那种只传指纹 discriminate_candidates false
答题:出生日期 + snapshotCurrent adopt_representative true
只补出生日期 adopt_representative true

采用路由在 can_adopt !== true 时返回 409 adoption_not_allowed。GET 用的也是只传指纹,和答题不一致。

助手消息修前正好 2 条:

  1. 采用旁白改写「这一轮已经把范围收窄,可以按代表时间看盘。」写入方是答题后的 persistV9DeterministicTurn。
  2. 确定性兜底,写入方是 finalizeSuccessfulTurnExit(日志 rectification_delivery_turn,trigger 为该函数)。正文含「分钟范围 04:45–05:15」和「现在还剩 04:45–05:15 里 2 个候选」。改写句不是这段的子串,子串防重没挡住。

收尾里的引擎刷新失败得很快(本机没有引擎),失败之后仍然写下第 2 条。选择题 JSON 没有带 x-rectification-turn-id,所以收尾看不到已经问过的轮次,补位照写。

T2 做法:不把采用旁白函数传进收尾,也不改旁白提示词。答题回复已经记下交付原文;收尾补位在「上一条助手原文去掉空白后等于这份已记原文」时不再写。子串比对留下。没有任何回复承载时,补位仍写 1 条。

实现

共享入参在 decision-inputs.ts。出生日期读不出来就抛 birth_snapshot_unavailable,不退回缺日期的判定。GET 和接续仍回到原来的中文 503。accept 在这条失败上返回 503「暂时无法采用」。快照是否当前仍由卷宗比较指纹,答题路径不再单独传 true。

收尾补位先看子串,再看已记下的交付句是否就是上一条助手原文,然后才写。先记下的那句在 60 秒内不被后一句盖掉。选择题 JSON 在有轮次编号时带 x-rectification-turn-id。真库验收没有靠这个头:收尾函数自己比对上一条。

盘型卡和分钟卡的按钮都要「结果属于采用集合,且 can_adopt 为真」。frontend/DESIGN.md 校正状态表的候选行补了「不能采用时卡片仍在、没有按钮」。

测试缝:interview-state 和 projectTurnDecision 在调用方没传出生日期时仍只传指纹,避免改掉几十条既有单测的结论。生产的 GET、accept、答题都传校验后的日期。dossierResponse 缺日期会直接抛错。

既有断言改动:

文件 原值 新值 原因
rectification-v9-contracts.test.ts step_state: stepStateFromCaseDossier(dossier) step_state: stepStateFromCaseDossier(dossier, decisionInputs.birthDate) GET 投影与答题共用出生日期(BUG-1242)

rectification-readopt-card-20261002.test.ts 的两处盘型卡渲染补了 canAdopt: true,断言原文没改。若干 dossierResponse 测试补了虚构日期 1900-06-15,好让成年下限不滤掉夹具里的现代探针。新夹具用 1988-03-12,没有复用事故卡片上的日期。

修后真库

仍是种好的答完并列卷宗,没有跑星历。同一份已对上指纹的卷宗:

路径 session_outcome can_adopt
旧的只传指纹 discriminate_candidates false
共享出生日期 adopt_representative true
GET adopt_representative true

然后走答题后的改写落库,再调收尾且不带已问轮次编号。助手交付消息恰好 1 条,就是那句改写。日志里收尾补位被调用了,没有写出第 2 条。引擎刷新仍然很快失败(本机没有引擎),失败后也没有补写。

本机测试

tsc --noEmit:0 错。npm run lint:0 error,126 条既有 warning,没有改它们。

新文件 rectification-delivery-dup-adopt-20261006.test.ts 16 项通过。相关既有文件 122 项通过,含 BUG-1153 原测试。# cancelled 都是 0。

全量 npm test(node scripts/run-tests.mjs,退出码 1):

tests pass fail cancelled
基线 65b61ed5 4848 4704 144 0
本分支 4864 4719 145 0

失败名比基线多 1 条、少 0 条。多出来的是「发出后 300 毫秒内出现第一行阶段句」(rectification-latency-20260926.test.tsx)。这次全量跑了 414 秒,这条墙钟断言没过。同一条单独再跑通过。它不读出生快照,也不写交付轮。基线全量里没有这条名字。不记成新 bug。

其余 144 条名字与基线相同。测试总数多 16,就是新文件的 16 条,都通过。

假账本原来不返回出生快照。决策入口改成缺快照就拒绝之后,第一次全量多了 60 个失败名。给没桩过的计算 RPC 补了虚构日期 1900-06-15(成年下限不滤掉夹具里的现代探针),采用路由的工具桩补了 loadV9CaseCompute,范围句那条路由测试补了同一份快照。这三处的断言原文没改,除了 persistApplied 的源码顺序:原值查 loadV9CaseCompute,新值查 loadCaseDecisionInputs,原因是出生日期改由共享入口读取。三栏写在对应测试里。

构建

next build --webpack。Turbopack 会拒绝工作树里指到主检出的 node_modules 联接,所以用 webpack。基线 65b61ed5 编译 2.9 分钟、构建自带的 TypeScript 100 秒,都通过,收集页面数据时停在 /api/rectification/agent。本分支编译 2.2 分钟、TypeScript 83 秒,都通过,收集页面数据时停在 /api/birth-time-guide。两边都是 Windows 不允许为 Skill 运行别名建符号链接(EPERM)。先碰到哪条路由取决于收集页面的工人,不是这次改动。路由表没有打出来,这台机器没能确认 / 仍是 Static,也没有首屏 gzip 数字。没有为了构建改符号链接,也没有在工作树里重装依赖。首页 page.tsx 没改;校正卡片组件有改,gzip 要等能建符号链接的环境再量。

本轮没有打开浏览器,也没有在手机上点采用。真机步骤在 docs/testing/rectification-delivery-dup-adopt-20261006.md。修复版本待发布,没有推送,没有部署。

验收修复单(Claude 直接执行,2026-10-06)

修复单:docs/tasks/TASK-rectification-delivery-dup-adopt-fix-20261006.md。在本分支 b977f482 之上追加。Linux,Node 22.14。

F1 防重改为包含判断(BUG-1241 重开后修复)

  • 事实:答题回复真实落库是「答题回执 + 空行 + 改写句」。persistApplied 在交付轮 skippedNextInterview=true 时拼 ${input.narration}\n\n${hostNarration}。首版 deliveryCarrierMatchesLastAssistant 要求全等,事故原样仍补第二条。
  • 修:delivery-turn-guard.ts 改为 text.includes(needle)(去空白,不足 8 字不匹配)。
  • 新测试(均在 b977f482 上红、修后绿):
    • rectification-delivery-dup-adopt-20261006.test.ts:「已记录,范围没变。」+ 改写 → 0 条;「已记录,范围没变;…领先…落后。」+ 改写 → 0 条;只有回执 → 仍 1 条。
    • rectification-answer-choice.test.ts「a rewritten delivery reply is not followed by a second exit fill-in turn」:走 applyRectificationChoice 真实答题路径并注入改写旁白,取 append_agentic_rectification_turn 实际写入的文本(实测为「已记录,范围收到 …」+ 空行 + 改写),再调 persistExhaustionGateTurn:b977f482 上写入 1 条,修后 0 条。
  • 真库:本轮没有做 PG17 重放(修复单要求)。替代证据是上面这条:落库文本取自生产拼接代码,而不是手写夹具,首版恰恰是手写夹具漏掉了回执前缀。改动只是内存比较,不涉及 SQL。PG17 重放记为欠项。

F2 撤回(修复单 §2.2 判断有误,BUG-1246 不成立,不入 Bug 历史)

修复单怀疑 persistApplied 删掉 snapshotCurrent: input.snapshotCurrent 后,放宽窗口失败时会按旧窗口出卡。复核结果:

  • 放宽窗口(mutateCaseForWidenWindow)与选时段(mutateCaseForBlockChoice)进 persistApplied 时 decisionState: null。decideAfterInferenceChange 在 state 为空时 candidateScores: [],一律 collect_evidence,snapshotCurrent 不参与。
  • 唯一带 state 的调用(stop,snapshotCurrent: rescored.snapshotCurrent)里,这个值本来就是 scoreableSnapshotCurrentFromDossier 对同一卷宗算出来的。stored 与 current 只差证据指纹,revision 两边抵消,与函数内自算结果相同。
  • 结论:删那一行不改变行为。保留一条守护测试「answering widen A with a failed rescore never delivers or adopts on the old window's candidates」(对照组同一份存量快照按当前读是可采用的并列交付;放宽后 can_adopt=false、不出 ready_to_adopt / complete_with_range),修前修后都绿。BUG-1246 号码不使用。

本机测试(对照基线 86cafe68,同机同命令)

tests pass fail cancelled
基线 86cafe68 4936 4871 24 0
b977f482 4952 4886 25(多 1 条首页计时 flaky,单跑 17/17) 0
本轮 4957 4892 24 0

失败名与基线逐名相同(diff 为空)。新增 21 条,没有删除。tsc --noEmit 0;npm run lint 0 error,126 warning(与基线相同)。 next build:本轮只改了服务端的 delivery-turn-guard.ts 和测试,没有重建。b977f482 的构建 Claude 已验:/ 是 ○ Static,首屏 gzip 636,730 对 636,565(+0.03%)。

既有断言:未改。

变基到 staging da226db1 后复验

staging 在这期间进了普通对话的代码(da226db1),本分支已变基到它之上。CHANGELOG、BUG_HISTORY、README 的冲突都是两边各追加,按「BUG 号升序」「两条 CHANGELOG 都留」解决。复验以 da226db1 为基线,同机同命令:

tests fail cancelled
基线 da226db1 4941 24 0
本分支(变基后) 4962 24 0

失败名逐名相同,测试名无消失,新增 21 条。tsc 0;lint 0 error。next build:/ 是 ○ Static;首屏 gzip 636,129 对 635,958(+171 B,+0.03%)。tests/test_repo_privacy_markers.py 通过。