Skip to content

[PR] test: reproduce legacy folded tool coverage loss after upgrade - #5836

Draft
mariopasquali wants to merge 1 commit into
ranxianglei:masterfrom
mariopasquali:test/legacy-tool-fold-coverage
Draft

mariopasquali wants to merge 1 commit into
ranxianglei:masterfrom
mariopasquali:test/legacy-tool-fold-coverage

Conversation

@mariopasquali

Copy link
Copy Markdown
Contributor

Relates to #5824.

An unchanged Anthropic replay can emit an already-folded tool call/result after upgrading legacy IDs when their old anchors are unavailable. This draft adds a small synthetic reproducer, with no runtime or kernel changes.

The fixture contains 13 invented messages decoded with the official 0.1.200 kernel. Three controls pass: legacy IDs retain their saved coverage; current decoding preserves the same payloads apart from IDs; and retaining the old anchors lets current IDs reconcile correctly. Without those anchors, the actual prune view contains both folded tool messages again (2 !== 0). Persisted lastMessages is empty, and the replay clears the auxiliary-request guard.

This exercises unmatched coverage. The related work in #4141 handles explained churn after full reanchoring.

Validation: clean install/build and typecheck; focused test has three passes and one deliberately failing regression assertion on 0.1.212 and current master. This is a draft test proposal for discussing the supported compatibility behavior. It does not prescribe a fix, reproduce the whole large-session outage, or measure SQLite performance. All fixture content is synthetic.

@ranxianglei ranxianglei changed the title test: reproduce legacy folded tool coverage loss after upgrade [PR] test: reproduce legacy folded tool coverage loss after upgrade Nov 6, 2026
@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

查重: 不重复。 Closest neighbors checked: #4131/#4141 (merged — fixed the explained-churn destruction path: the #1001 rewrite detector pruning the ref map during full reanchoring), #1921 (original ladder), #3752 (cap-eviction lite anchors), #2903 (legacy-anchor drift evidence), #4484 (block-merge coverage). None reproduces this case: coverage loss with zero stored identity evidence. This PR is the only reproducer of that residual. Related family cited, not duplicated.

标签: bug · scope/kernel · p2 · needs-decision(折叠判定面的升级残差缺陷;src 侧压缩决策面;可用但有明确代价,有绕行)。


🔶 需要决策

背景一句话:跨 v0.1.201 升级后工具消息 id 按设计重铸(#3642),当会话既无 foldAnchors 也无 foldPositions 时,reconcile 阶梯没有任何精确证据通道,已折叠的 tool 对每轮重新展开上送——本 PR 用合成 fixture 钉住了这个残差。

待决问题:「升级后的 envelope 缺失全部身份证据」是受支持状态(则阶梯需要兜底机制),还是迁移方违约(则契约写清、测试改特征化)?

选项 内容 代价
A(推荐) 本 PR 转为通过的特征化测试(T4 断言当前行为:展开 2 条、unmatched 2),PR 正文写明迁移契约:任何存储迁移必须携带 session.metadata(实测 foldPositions 单独存活即足以恢复,见下) 零运行时风险;受影响用户在重新压缩前持续丢覆盖
B 阶梯加固:把 #3301 的 stored-decomposition backfill(目前只补 p-less assistant core)推广为「covered id 缺证据时用统一存储行字节精确回填锚点」,claim 仍走既有精确通道(byTool/byTwin/byNorm,T3 已证明可行) 修掉「远古休眠会话跨 v0.1.201 升级」整类;但触碰身份核心(§7.4 人审区)、legacy-tier 无统一存储行的安装仍盲(部分修复);中量工作;若拍板,以追加 commit 形式落在本 PR 分支(一线程一 PR)
C 接受现状 + 锐化终态:covered 集全失配且零证据通道时,跳过 3 轮浸泡直接归档块,日志给出可操作指引(重压缩/检查迁移是否携带 metadata) 最小运行时改动;仍需人审(reconcile 核心)

推荐:先 A(低风险、钉住边界、解锁讨论),B 作为后续加固由 owner 定夺。不决定的默认行为:PR 保持 draft,T4 红断言不可合并,问题继续挂在 #5824 线程下。


Verification (done locally on PR head 95e4b4734, base = master c3dfbe6b2)

  • Clean install + build + typecheck clean; focused run: pass 3 / fail 1, failing assertion exactly as claimed (2 !== 0, "already-folded tool messages must stay folded on unchanged replay"). All author claims verified.
  • Diff is tests-only (fixture + test file, 249 insertions) — matches "no runtime or kernel changes".

Mechanism — what the repro actually exercises

  1. The id churn is real, intentional, and tool-scoped. kernel/src/wire/identity.ts (The intermediate message (core) is not protocol-agnostic: switching protocols/models causes identity churn; cross-protoc | 中间消息(core)没有做到协议无关:切协议/切模型制造身份 churn,补齐跨协议映射 #3642) derives ids from role|contentType||toolName|canonicalJSON(text) — the toolCallId slot pinned empty, args key-sorted. Decoding the fixture body with the in-repo kernel vs the pinned 0.1.200 kernel: only the two tool messages re-mint (h_ad375fbf578d70f4 → h_07dd7fbf960656e7, h_5acf06bd84245dbd → h_5ab11c45df00b8d9); all 11 text messages keep byte-stable ids. Consistent with [PR] fix: cross-version restart no longer destroys fold state on explained id churn (#4131) #4141's "re-mints non-text message ids by design … D1 accepted this one-time mass churn and promised the reconcile ladder would absorb it".
  2. The ladder absorbs only with stored evidence — and I probed the exact boundary:
    • surviving foldAnchors (your T3) → byTool/byTwin claim both cells, full recovery;
    • surviving foldPositions ALONE (new probe) → Pass 0 claims both (byPos: 2, unmatched 0, nothing unfolded) — the positional fingerprint excludes the volatile toolCallId by design (折叠身份改为「位置为主 + 自存规范副本」:彻底终结跨协议 fold 漂移补丁链 (#2454 根因范式) #2480), so byte-identical content at intact order/count recovers even with zero anchors;
    • neither (your T4 shape) → every exact channel starved → total unmatched. By design: "never fuzzy similarity" means content that merely looks identical cannot self-prove identity without a stored coordinate system.
  3. Reachability on supported paths. In-repo persistence carries session.metadata wholesale (envelope + unified index), and foldAnchors/foldPositions have been persisted since ~v0.1.190 (verified by tag). So the blind state arises from: (a) sessions whose last reconcile pass ran on a pre-anchor-persistence build, then upgraded across v0.1.201; (b) third-party/private storage migrations dropping free-form metadata — e.g. the Expected performance after JSON-to-SQLite migration #5824 SQLite candidate. Both feed the same mechanism family as [Bug][opencode lane] Cross-version restart replays the whole session: old v0.1.197 and new v0.1.202 write the same sessions dir, 212 folded refs re-anchor onto churned bytes and the ref map is pruned to 392 raw ids #4131, which is why this belongs in that discussion rather than a fresh issue.
  4. Steady state after the first loss. Once the order backbone rolls onto the new ids, the stale covered ids sit outside the churn region forever — permanent per-pass missing (drift warn each turn; below the 10-id error-escalation bar), and per the documented [dsh] 原生压缩拦截门的 SHAPE 信号(≤4 条消息)在 dsh-compaction-basic 0.2.0-rc.2 上恒假 → 压缩静默落地 → 1510 条 fold 锚点永久失配,连刷 166 条 no-anchor-match 且面板仍报 netSaved 355.9M #2193/[Bug][proxy] unframed host-side history deletion leaves zombie fold blocks rendering carriers forever — one carrier flaps in/out every turn (persistent prefix-cache churn) #2695 lifecycle a block at sustained majority loss is reaped after 3 consecutive passes: summary archived away, originals ride the wire verbatim from then on. So the end state is honest degradation, not corruption — but on large folds the per-turn bloat is exactly the cost class your Expected performance after JSON-to-SQLite migration #5824 numbers care about.

Review notes (small, for the follow-up commit whichever option wins)

  1. Precision: the operative precondition is "no foldAnchors and no aligned foldPositions", not just "anchors unavailable" — the probe above shows positions alone recover the replay. legacySession() omits both implicitly; the comment at tests/legacy-anthropic-identity-replay.test.ts:16-19 and the PR description should say so explicitly, since it changes what a migration must carry (positions suffice — cheaper than the full anchor map).
  2. T4 must resolve before merge: as a characterization assert (option A) or behind a fix (B/C). Red-draft is fine meanwhile.
  3. T2 is the load-bearing control (payload equality modulo ids across decoders) — it's what makes the whole repro trustworthy; keep it verbatim.

结构化八问

  1. 是什么问题:跨版本升级(v0.1.201,The intermediate message (core) is not protocol-agnostic: switching protocols/models causes identity churn; cross-protoc | 中间消息(core)没有做到协议无关:切协议/切模型制造身份 churn,补齐跨协议映射 #3642 身份重设计)后,若会话无 foldAnchors 且无 foldPositions,已折叠的 tool-call/result 对不再被识别为已折叠,每轮以展开态重新上送。
  2. 什么原因导致的:The intermediate message (core) is not protocol-agnostic: switching protocols/models causes identity churn; cross-protoc | 中间消息(core)没有做到协议无关:切协议/切模型制造身份 churn,补齐跨协议映射 #3642 有意变更工具消息 id 种子(去 toolCallId 槽位 + 参数 JSON 规范化)= 一次性 mass churn,承诺由 reconcile 阶梯吸收;阶梯吸收依赖存储证据(anchors→byTool/byTwin;positions→byPos,实测任一单独存活即够);两者皆失时所有精确通道饿死(设计上禁止模糊匹配)→ 全量 unmatched。
  3. 影响范围与严重程度:最后 reconcile 跑在 ~v0.1.190 之前且含覆盖工具消息的活跃折叠、再跨 v0.1.201 升级的会话;以及丢弃 free-form metadata 的第三方存储迁移(Expected performance after JSON-to-SQLite migration #5824 私选 SQLite 候选即此类)。症状:折叠 tool 对每轮展开重发(token 膨胀 + 该处缓存接缝破坏)+ 每轮 drift warn,3 轮多数失配后块被 reap、摘要永久失去。p2:可用、有绕行(新会话/重压缩)、无数据丢失;若 owner 判定受影响 envelope 面大,升 p1。
  4. 怎么修复的:待决策(见上方选项表);推荐 A 先行,B 作后续加固。
  5. 是否有回归问题:PR 本身仅测试文件,无行为变更、无回归;唯一风险是 T4 红断言在方向未定时阻塞合并。
  6. 是否破坏缓存:bug 本身在折叠处破坏前缀缓存接缝(展开 tool 重新注入出站);PR 不改变任何缓存策略。
  7. 是否改了设计方向:未改;提出一个设计问题——无证据 envelope 是受支持状态(契约层)还是阶梯需 stored-bytes 兜底(机制层)。
  8. 是否引入了新的配置:无。

判定:复现成立(本地实测 3 过 1 败、typecheck 干净),方向正确,与已合入的 #4131 互补而非重复;决策(A/B/C)落定前不宜合并。我的推荐:A 现在做,B 请 owner 定夺。

@ranxianglei

Copy link
Copy Markdown
Owner

该PR已按根因归类纳入 [dsh-lane] 根因汇总 #6294,由汇总单统一跟踪处理;本单不再单独跟进,故关闭。如认为归类有误或问题仍未解决,请直接回复,会重新打开。

@ranxianglei

Copy link
Copy Markdown
Owner

状态更正(自动化脚本误操作): 本单此前被「纳入根因聚合」流程误关闭, 现重新打开。机器人工作已停, 本单由根因聚合线程统一跟踪, 根因修复落地后请手动关闭或在本单回复「close」。
[automated: re-open after bot-stop-only policy]

@ranxianglei ranxianglei reopened this Nov 12, 2026
@ranxianglei

Copy link
Copy Markdown
Owner

重新打开:此前被归类聚合脚本误关。按 owner 口径,纳入根因聚合的 issue 只停机器人、不关闭,由根因聚合线程统一跟踪,根因修复落地后统一关闭。
(reopened: absorbed issues must not be closed, only bot work stopped — owner policy 2026-11-12)

@ranxianglei

Copy link
Copy Markdown
Owner

(更正)此前按"清理"口径误关闭;按 owner 口径纠正:被根因聚合纳入的 issue 只停机器人、不关闭。本 issue 重新打开,机器人工作已停。

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

[bot] CI 红项核查结论:这是草稿 PR 里按设计故意失败的回归断言,不是 CI 故障,无需修。

核查过程

  • 本镜像 [Bug][DSH] Codex auto/WebSocket bypasses native fetch interception while compaction is disabled #5838 = GitHub PR [PR] test: reproduce legacy folded tool coverage loss after upgrade #5836(mariopasquali 的草稿测试提案),只新增 2 个文件、不改任何运行时/kernel 代码:
    • tests/legacy-anthropic-identity-replay.test.ts(+74)
    • tests/fixtures/legacy-anthropic-0200.json(+175)
  • 3 个红 shard(ubuntu-24 shard1 / ubuntu-22 shard1 / windows-22 shard1)全部挂在同一个新测试文件上。该文件 4 个用例:
    1. legacy IDs still match persisted coverage without surviving anchors(tests/legacy-anthropic-identity-replay.test.ts:37)— 通过
    2. synthetic old and current decoders preserve the same message payloads(:46)— 通过
    3. surviving tool anchors recover the same current replay(:54)— 通过
    4. unchanged Anthropic replay keeps legacy folded tool coverage after upgrade(:66)— 故意失败:断言 prune(...).length === 0,实际 2(2 !== 0),即「升级后无 anchors/positions 时已折叠的 tool 对每轮重新展开」。
  • 与作者 OP 声明("three passes and one deliberately failing regression assertion")及 triage(floor 2)本地实测(pass 3 / fail 1)完全一致。CI 日志接口当前返回 404,故以测试文件源码 + 作者声明 + triage 实测三方交叉确认。

冲突:无文本冲突(mergeable: True),仅 base 落后于当前 master(c26c31a → 33104b1)。草稿阶段不强行 rebase——rebase 后 T4 仍会失败(除非 master 已修该 bug,无证据),只会重跑一遍同样的红 CI。

判定与处置

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants