Repository navigation
[PR] fix(kernel): stub plain-text line-form compress calls in consumed-call cleanup (#3975) - #4032
ranxianglei wants to merge 14 commits into
Conversation
adc4c38 to
b0c09ae
Compare
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.8549-pr.4032.8817d90This exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr4032.tgz
npm install -g packageInstall instructions are refreshed on each push. |
b0c09ae to
cedd2a4
Compare
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复。 本 PR 是 #3975 的专属修复 PR(Closes #3975)。相邻条目核对:#3785(line-form 双重转义换行打捞,已合)改的是解析入口的容错,不是 consumed-call 清理;#2970/#2977(MCP compress 路由进清理层)、#3769/#3784(orphan 清扫门控)是本 PR 依赖的既有机制而非平行修复;#4311(摘要双份保留,已关)与本 PR 同症状家族但指向的是载体去重边界,PR 已按该边界刻意不扩大 carrier 索引。无并列的同机制在途 PR。 标签:pr + bug + scope/kernel + p2(缺陷类;根因与改动都在 kernel 的 consumed-call 清理面;能用但有冗余驻留、有绕行——p2;MCP shim 为多宿主共用面,不打具体客户端标签)。 开始独立审核(不照抄 #3975 线程结论):拉分支通读 diff、清点触碰路径的既有行为、本地定向跑新增测试与相关套件、核对 CI。另注意到 PR head 相对当前 master 显示 merge conflict(master 已推进约 3 天),审核中一并处理 rebase。结论随后按八问回复。 |
cedd2a4 to
40c9d06
Compare
(ai: vllm-qwen/qwen3.8-27b) PR #4032 独立复审结论(对最新 master 重读 diff 与行为,不复述 #3975 线程结论)。 已执行的动作
八问
已知残留(不阻塞,已在 #3975 记为 checklist 项):parser 仍接受的两种 legacy flat 形状( 判定:代码闭环、测试有牙(10+2 kernel / 2 host,含负向门)、冲突已解、本地定向证据齐。等 CI 全局门转绿后建议合并——kernel 变更属 human-only review 档,请人工合并:#4032 。CI 结果出来我再补一条确认。 |
40c9d06 to
c86ab0c
Compare
…anup (#3975) parseCallText only understood object-array content (and stringified JSON arrays), so a plain-text line-form content string — which the compress tool itself accepts via parseLineEntry — resolved to no quoted ranges. Such calls mis-resolved as orphans and rode the keep-last-two orphan window UNSTUBBED, retaining their full summary text forever alongside the kernel acp_summary. Reuse the tool's own acceptance ladder: export parseLineContent from parse-compress-input.ts (stripJsonWrapperResidue -> splitLineEntries -> parseLineEntry) so hide-consumed resolves ranges from exactly what the tool accepts instead of a second hand-rolled reader. Line-form calls now resolve active/inactive by range like object-array calls, get their long summaries stubbed to 200 chars with headers re-emitted verbatim, and free their orphan window slots for genuinely failed calls. Unparsable/garbage strings stay byte-identical (no refs claimed, no rewrite). kernel/package.json 0.0.147 -> 0.0.148 (CI kernel-guard).
Host-level regression for the issue's expectations 2-3: after hideConsumedCompressCalls + stripKernelSummaries, a plain-text mcp__bili__compress call ends stubbed while its acp_summary anchor stays byte-complete as the sole full carrier — guarding against future buildCarrierIndex over-widening that would strip the anchor off stubbed args. Parity pin: object-array end state unchanged.
c86ab0c to
38fd9ac
Compare
(ai: vllm-qwen/qwen3.8-27b) lock-sync 红已在分支侧自行修复,本轮无需再推任何提交。 阻塞快照指向的旧 head 根因(旧 head 为何红): 0.0.180→0.0.181 的版本 bump 是用一次把换行压成空格的编辑落进去的。 验证(本地用与 CI 完全相同的命令复跑,新 head
一处遗留小瑕疵(未动,仅标记): 结构化结论(八问)
收工判定:lock-sync 阻塞已解除(分支侧自修 + 本地与 CI 双重验证),本 CI 车道无需进一步动作;等待剩余 test 矩阵完成后由 reviewer/owner 推进合并。 |
ranxianglei
left a comment
There was a problem hiding this comment.
(ai: vllm-qwen/qwen3.8-27b)
独立审核结论(review-worker-r1,对 head 38fd9ac91 重读全量 diff——不复述前轮/issue 结论)
判定:代码与测试层面可合。因触及 kernel/ 且携带 kernel-change: 声明,按 Kernel Boundary 属 human-only 审核档,本审核不执行 approve——转 🔶 需要决策 流程请 owner 授权(见后续评论)。
已审既有行为清单(回归审查,逐项判读改动前代码而非只看 diff):
- 对象数组 / 字符串化数组路径:
parseCallText仅在内层 JSON 非数组或解析失败时才改道lineEntries;两既有形状解析、重写、字节稳定性不变,#230「字符串形状保留」约定有回归钉。→ 保持 ✓ - #3769 fold-gated sweep:fate/sweep 本体未动;line-form carrier 经
fate=active在 sweep 分支之前被豁免,真孤儿照旧在 foldMinRef 处清扫,quiet turn 不扫(测试双向钉住)。→ 保持 ✓ - acp_summary 锚点保留:
buildCarrierIndex刻意未扩名(stub ≠ 完整载体,删锚即丢全文),issue 期望 #3 守住。→ 有意不变更,PR 已披露 ✓ - 谓词面:
isMcpBiliCompress仅新增一个精确全名条目(带流量证据注释),unobserved 服务名负向钉住;invariant-judge.tslockstep twin 同步扩展;接线已核实到全部消费点(src/config.ts:1715→src/loop/core.ts:1469、src/server/relay.ts:2188、src/plugin-render.ts:148、src/reasoning-drop.ts:77)。→ 有意变更已披露 ✓ - 两种压缩模式:清理层作用于重发历史,MCP/bare-name 双车道各有测试钉。→ 均过 ✓
发现(三条均不阻塞):
quotesActiveBlock(kernel/src/hide-consumed.ts,约 L44)仍不解析 line-form——对纯文本字符串恒返回 false。唯一可达角落:bare-name 且 id 失配的孤儿调用引用活跃块范围时,fold 轮仍会被扫。该角落要求「块先于 compressCallId 记录时代且至今仍 active」的遗留状态;改前同样存在(改前更糟:所有 line-form 调用都是孤儿),非本 PR 引入的回归。可选收尾:让它复用parseLineContent,或注释钉死不可达性。- refs 规范化不对称(既有):object 形
validateEntry原样存 ref,line-form 经normalizeLineRef补零——跨形状引用同一范围时键可能不匹配而停留在孤儿态。只欠认领(安全方向)、需同会话混用形状+补位风格,非本 PR 引入。 kernel/package.json:3:版本行与 description 挤在同一行(与旧 head lock-sync 红同一手误,CI 车道 floor 13 已标记)。合法 JSON、不影响任何门禁;落在人工把关的kernel/内,本审核未代改——建议合并前拆回两行。
测试有效性(质量关): 12 kernel + 3 host + 谓词真值表正/负行,断言有牙(精确长度/逐字节/双方向门);fixpoint 稳定性测试直接钉「重写输出跨轮字节一致」(前缀缓存稳定的直接证据);end-state 测试跑完整 hideConsumedCompressCalls + stripKernelSummaries 管道并显式防住「扩大 carrier 索引 → 删锚丢全文」的错误修法。issue 期望 1–4 全覆盖。真测试,能兜底。
八问
- 是什么问题:compress 工具接受纯文本 line-form
content(首行m00100–m00120 主题),但 consumed-call 清理是第二个读取器、缺这一形状——此类调用被误判孤儿,全文摘要永驻并与 kernelacp_summary重复占上下文,fold 轮还被当失败残留扫掉(它正是该块的摘要 carrier);另有观测到的mcp__client__compress桥接方言不被任何清理谓词识别,整条车道全文永驻。 - 什么原因导致的:清理层没有复用工具自己的接受阶梯(stripJsonWrapperResidue→splitLineEntries→parseLineEntry),两个读取器漂移,line-form 形状缺失;方言缺失是精确名允许列表里没有该条目(设计如此——新方言须带流量证据入列)。
- 影响范围与严重程度:所有发 line-form compress 的会话(MCP 各车道 + bare-name 代理车道);#4252 之后 line-form 是 schema 唯一 advertised 形状,新流量主力命中。冗余上下文成本 p2 级:报告者实测 15 次调用 2 次未简化(~2.68K tokens args);OpenCode 车道字段报告每请求 ~25–30K tokens 重复。无数据丢失、可用、有绕行。
- 怎么修复的:kernel 导出
parseLineContent作为单一解码源(逐字走工具自身阶梯,消灭第二读取器);hide-consumed把「内层非数组/解析失败」的字符串内容路由给它;range key 用规范化 refs;rewrite/compact 按 line-form 重建(header 逐字、>200 字符摘要截 stub、字符串形状保留 #230 约定);垃圾/无 ref 字符串字节不变。方言走精确全名允许列表 + invariant-judge lockstep twin。 - 是否有回归问题:无。见上「已审既有行为清单」逐项结论;高危模式过完——无语义漂移、无 fail-fast 变吞错、无阈值/时序/输出格式变化(SUMMARY_STUB_CHARS=200 未动、sweep 门控本体未动)。发现 1/2 均为既有残留、安全方向、非本 PR 引入。
- 是否破坏缓存:否。重写输出是定点(第二遍处理字节一致有测试实证)、JSON.stringify 键序确定,前缀缓存稳定;不动去重键/增量游标/重试策略。
- 是否改了设计方向:否。方向 = 单一共享解码源归 kernel(#3780/#3236 同源纪律);carrier 去重边界刻意不扩(stub 不是完整载体)。
- 是否引入新配置:无(PR 已声明 Config surface: None)。
CI:head 38fd9ac91 镜像缓存 summary total=30 / failed=0 / pending=0(含 12 个 test 分片、static-gates、mysql-contract、gate;ci.yml 中 kernel 套件 job 与 kernel-guard/version-guard/lock-sync 均在该 run 内绿)。mergeable_state=blocked = 等审核(分支保护),与本派单一致。
收工判定:代码闭环、测试有牙、CI 全绿。待 owner 对 kernel 变更授权后由人工合并(Kernel Boundary human-only 档,agent 不合并不 approve):#4032 。发现 3 建议合并前顺手拆回两行。
|
🔶 需要决策 (ai: vllm-qwen/qwen3.8-27b) PR #4032 触及 受门禁 diff(kernel/)
审核状态:技术审核已完成(见上方 review 评论,含八问与三条不阻塞发现);CI 全绿(head 选项
|
(ai: vllm-qwen/qwen3.8-27b) 本条为 review-worker 自己刚发的 🔶 需要决策 评论经上游同步回显,非新用户输入,无新动作。当前状态不变:技术审核完成、CI 全绿、待 owner 在选项 A(批准人工合并)/ B(打回)中拍板。 |
38fd9ac to
72f7103
Compare
# Conflicts: # kernel/package-lock.json # kernel/package.json # package-lock.json
…-rebase onto master at 0.0.183)
(ai: vllm-qwen/qwen3.8-27b) 🔶 需要决策 —— 本 PR 剩余红 = master 级 born-red( 背景一句话:#5652 fastpath-v6 的 owner 裁定(v6 F1/F2,2026-11-04)被 #5826 review 的 parity 测试钉进 CI(36210fc + 33b2f82),但 #5652 实现随后被 #5854 revert(bebc239/66aadbd89f)——测试钉的是 v6 语义,代码退回 pre-v6「? travels」宽容语义,master 自 revert 起对该文件确定性 2 红(子测 3「one hole voids the WHOLE list」expected 选项(互斥):
推荐:B(最小解锁、零行为变更;重新落地被有意 revert 的实现应等 owner 明确指示,而不是由 CI 车道反推)。 本车道状态:合并冲突已解(master 并入,唯一文本冲突 = 三处版本行,按 Kernel Boundary max+1 取 0.0.184)、ci-fixes 经 ancestry 验证已完整在树内、本地 build/typecheck/定向套件全绿,新 head acd8853 已推;本轮无进一步可做的机械修复,停手等裁决(needs-decision 标签已在案)。 |
(ai: vllm-qwen/qwen3.8-27b) 本轮 CI 工程处置总结(旧 head e8fc736 → 新 head acd8853)
收工判定:本车道机械工作完结——冲突已解、可消化的继承红已随 master 消失、新 head acd8853 已推且除 master 级 born-red 外预期全绿(lock-sync/static-gates/mysql-contract 等门禁已绿);剩余 3 片红等待 owner 对 v6 裁定存废的 A/B 裁决(#4599 floor 1091 + 本帖 🔶 评论),裁决落地后本 PR 重跑即转绿,无需再动分支。 |
…er independently took 0.0.184 for the phantom-pressure guard, #5860)
(ai: vllm-qwen/qwen3.8-27b) 本轮 CI 工程处置总结(head acd8853 → bdf8c10)
收工判定:新 head bdf8c10 已推(master df0a402 + 本 PR 内容,kernel 0.0.185),预期全矩阵转绿——此前的确定性 2 红已随 master 修复消失,门禁类(lock-sync 等)已按 CI 同款命令本地钉住;等 CI 重跑结果,#4599 已留一行收账结论。 |
(ai: vllm-qwen/qwen3.8-27b) 本轮 CI 工程处置总结(自动修复车道 attempt 1/3,head 一句话结论:9 个红分片全部是继承性 master born-red,不是本 PR 引入;本 PR 自身代码与测试已验证全绿,本轮零推送(修 host 侧 nudge 逻辑不属于这个 kernel line-form PR 的范围,收敛走 ci-fixes→master)。已在 #4599 登记该继承家族。 证据链
八问
收工判定:本 CI 车道的机械工作到此为止——可修的在范围内项为零(红全是继承),已登记 #4599 并把方向判断交给哨兵/owner;等 master 侧 nudge born-red 收敛后本 PR 重跑即转绿,届时再补确认。kernel-guard 的人工授权卡点维持 floor 16 的 🔶 需要决策(A 批准人工合并 / B 打回)不变,不在本轮范围。 |
…ted-state shape #5860 added compress.requireUsageSampleForPressure; the resolved Config snapshot persisted in session.metadata["effectiveConfig"] (src/session.ts cloneSafe) grew by exactly that field, so the metadata section digest drifts deterministically on every lane (F1 of the #5860 born-red wave, signatures pinned in #4599). Regenerated all four snapshots at 5af5f9f with scripts/update-storage-render-goldens.ts; state/blockContents/stats sections unchanged, no behavior change - snapshot realignment only. Verified green under the CI preload env (BILI_MESSAGE_TIMESTAMMS=0 via tests/preload-test-env.mjs).
…ract kernel.json buildKernelGolden() serializes behavior.defaultConfig100k = defaultConfig(100_000); #5860's additive NudgeConfig field broke the canonicalized snapshot (F2 of the #5860 born-red wave). Regenerated with scripts/update-wire-contract-goldens.ts: diff is the single additive field requireUsageSampleForPressure: true in the nudge section - the ledger grows, nothing shrinks (Kernel Boundary rule 4).
(ai: vllm-qwen/qwen3.8-27b) 本轮 CI 工程处置(attempt 1/3;head 已从 bdf8c10 → 66caf50) 一句话结论:本次唤醒列的 6 片红 = 已在 #4599 归因钉死、并由 owner 立项待裁定的 #5860 phantom-pressure guard born-red 波次的 F3 行为面;机械半边(F1 storage-render 快照 + F2 wire-contract golden)哨兵已修好、并已由哨兵把 ci-fixes merge 进本分支(即当前 head 66caf50)。剩余 F3 属 owner 在 #5867 拍板的 A/B/C 设计决策,本轮零推送(无 in-scope 机械修复可做)。 live 复核(不凭记忆):镜像缓存 fetched_at 11:18Z——run 42366601829 @ 66caf50 仍在跑(pending 10 / in_progress 3),目前仅 为什么本轮不修、不推:
八问
收工判定:本 CI 车道无可做的机械工作——机械半边已被哨兵合入本分支、剩余 F3 属 owner 在 #5867 的待决设计项(拍板前保持红为预期),零推送、零改动。等 #5867 裁定落地进 ci-fixes→master 后本 PR 重跑即转绿,届时再补确认。kernel-guard 人工授权卡点维持 floor 16 的 🔶 需要决策(A 批准人工合并 / B 打回)不变,不在本轮范围。 |
[ework] auto-fix stalled: test (ubuntu-latest, 22, 1), test (ubuntu-latest, 24, 3), test (ubuntu-latest, 22, 3), test (ubuntu-latest, 24, 1), test (windows-latest, 22, 2), test (windows-latest, 22, 1) — stopping for human review |
…0.0.185, #5867 ruling) supersedes sentinel golden realigns
….0.185); clears kernel-guard version-discipline red The #5867 estimate-hold backstop landed on master at acp-kernel 0.0.185 (bcda253), colliding with this branch's 0.0.185. The #3975 line-form stub (hide-consumed.ts / parse-compress-input.ts) is new kernel/src not yet in master, so kernel-guard Rule 2 fired (kernel/src changed without a version-line delta). Bump to 0.0.186 = max+1 over master per Kernel Boundary rule 2 — same routine as the earlier 0.0.184/0.0.185 max+1 syncs on this branch. Version number + regenerated lockfiles only (CI-identical command, idempotent); no kernel-source or PR-body change. The owner's human-authorization of the #3975 kernel change (floor 16) is untouched and still required for merge.
(ai: vllm-qwen/qwen3.8-27b) 本轮 CI 工程处置(attempt 1/3;head d6949d1 → 74653f2) 一句话结论:唯一红 = kernel-guard,根因是版本对撞——master 经 #5867 estimate-hold backstop 已把 acp-kernel 升到 0.0.185,与本分支的 0.0.185 撞车,使本 PR 独有的 #3975 kernel/src line-form stub「改了源码却没有版本增量」触发 Rule 2。这是机械性 max+1 版本同步(与本线程 floor 25/28 同一套路),已修:bump 0.0.185→0.0.186 + 两份 lockfile 按 CI 同款命令重生成,新 head live 复核(不凭记忆):镜像缓存 fetched_at 15:25Z 显示 30 checks / 仅 kernel-guard failure(其余含 test 矩阵全绿——F3 已随 #5867 裁定落地转绿)。本地按
为什么这是机械修复而非越权代批:① 失败在 Rule 2(版本纪律),不在缺声明——正文声明一直都在。② 我只改版本号 + 生成的 lockfile,没碰一行 kernel 源码(floor 16 待 owner 拍板的是 #3975 那段源码,其字节未变)。③ max+1 是确定性规则、零设计依赖;本线程 floor 25(→0.0.184)、floor 28(→0.0.185)已就同一「master 前进撞版本」机制做过同样的 max+1 同步。④ 按 AGENTS.md「无决策依赖的部分先修」,不应把它串行阻塞在 owner 授权之后。 八问
收工判定:kernel-guard 机械红已清(head |
Closes #3975
kernel-change: the consumed-call cleanup (
kernel/src/hide-consumed.ts) cannot parse the plain-text line-formcontentthat the compress tool itself accepts (parseLineEntry); the host side cannot fix this without duplicating the line-form parser in a second location (format-drift risk — the #3780 precedent already locates the shared home for this decode in the kernel). Issue #3975 is the dedicated issue naming the kernel root cause.Rebased onto current master
c9ba48566(2026-11-04; incl. #3769, which replaced the old keep-last-two orphan rotation with the fold-gated sweep); tests adapted to the current semantics, end-state guarantees unchanged. Kernel version 0.0.180 → 0.0.181.What breaks today
parseCallTextunderstood object-array content and stringified JSON arrays only. A plain-text line-form content string (first linem00100–m00120 topic, remaining lines the summary) resolved to no quoted ranges, so such calls mis-resolved as orphans: on quiet turns they stayed visible unstubbed — retaining their full summary text forever alongside the kernelacp_summary— and on fold turns they were swept as failure residue although they ARE the folded block's summary carrier (#1567). Measured in the reporter's snapshot: 2 of 15mcp__bili__compresscalls unsimplified (~2.68K tokens args, ~2.54K redundant full-text overlap across 10 active blocks).The same mechanism also kept bare-name proxy-lane line-form calls at full length forever (id-matched fate always keeps; the rewrite returned null pre-fix).
Field validation (2026-11-05, v0.1.211 running / v0.1.212 code-checked): the reporter applied an equivalent parser patch to the shipped v0.1.212
dist/index.js— offline against the 8 compress calls of one real long-session request, rendered arguments dropped 48,425 → 5,569 chars with every range parsed and the output kept as plain text; live, the session's next request after restarting bili already carried stubbedtool_use.inputfor the past compress calls (~25–30K redundant CJK tokens per request removed).Fix
Reuse the tool's own acceptance ladder instead of a second reader:
parse-compress-input.ts: new exportedparseLineContent(content)—stripJsonWrapperResidue → splitLineEntries → parseLineEntry, i.e. exactly what the compress tool accepts whencontentis not a JSON array. Unparsable entries keepsource/headeronly and never claim a range.hide-consumed.ts: string content whose inner JSON value is not an array (or fails to parse) flows throughparseLineContent; quoted-range keys use the normalized refs (aligning with how line-form specs store block refs); rewrite/compact rebuild the line form with headers verbatim and long summaries stubbed to 200 chars, re-emitting the full args object with the string shape preserved (fix: persistent overlay homes for pi/omp/hermes launchers (session history loss) #230 convention). Garbage/refless strings stay byte-identical.Line-form calls now resolve active/inactive by range exactly like object-array calls and get stubbed identically; on fold turns they are fate=active and therefore exempt from the #3769 sweep, while true orphans are still swept at/after the fold start.
An exact-name allowlist entry for
mcp__client__compresswas considered after the first field report and DROPPED on correction: that name is minted by a downstream gateway that re-exposes client tools above bili and never reaches bili itself (which sees the bare proxy-injectedcompress); it has no traffic evidence, andclientis a generic MCP server name carrying the same misidentification risk for which suffix matching was rejected (#2578). The PR ships the parser fix only.Out of scope (owner-deferred from the 2026-10-23 review): the two legacy flat arg shapes (
{startId,endId,summary}top-level;{content:<prose>,startId,endId}salvage form) — data-safe (the anchor carries the full text), population shrinks since #4252 stopped advertising them; left for a separate decision.Behavior change (old → new)
acp_summaryretention for these blocksObject-array, stringified-array, and garbage paths are regression-pinned unchanged. One-time consequence of rolling out: sessions with historical line-form calls get one cache-prefix rewrite when their arguments are first stubbed; the stubs are deterministic and stable across turns thereafter.
Design note for future carrier-index extensions (field report, 2026-11-05)
A second field report (OpenCode 2 plugin lane, bare
compress) confirmed the line-form end state above and raised a forward constraint worth recording here: in the plugin lane, array-form folds ALREADY reach a stub-only wire steady state —hideConsumedCompressCallstruncates the re-sent call to a 200-char stub andstripKernelSummariesstrips theacp_summaryrender, because the client's own re-sent pair IS the carrier by contract (#1567); the full text stays recoverable from the client's stored history and via decompress. Line-form folds sit in the conservative counterpart:buildCarrierIndexcannot parse line-form args, so the render remains the standing full copy (unparseable ⇒ anchor kept). If anyone later teachesbuildCarrierIndex/reconcilePluginFoldCallIdsthe line form, line-form blocks move to the same stub-only steady state — acceptable only while at least one complete carrier survives (the client-side pair in plugin lanes, the decompress path); do NOT ship a variant where neither the stubbed call nor the render carries the full text and the lane does not retain it either.Config surface
None — no config/env/CLI changes.
Tests
kernel/tests/issue3975-hide-consumed-lineform.test.ts(new, 12): normalization (m100→m00100, b2/b5, topic), refless-garbage verbatim, MCP synthetic-id plain-text carrier stubbed as STRING, fold-sweep exemption for a live-range plain-text carrier while a true orphan is swept ([Bug] MCP failed compression record rotation prematurely rewrites the history prefix, triggering expensive cache recompu | [Bug] MCP 失败压缩记录轮换提前改写历史前缀,触发大额缓存重算 #3769/plugin mode: in-place acp_summary never stripped when the client's own compress pair rides inbound — double summary carrier on every post-fold turn #1567), partial-range invalidation (live kept+stubbed / dead dropped), all-dead hidden, quiet-turn failed-fold orphans visible but stubbed, bare-name id-matched lane, stringified-array shape+stub regression (fix: persistent overlay homes for pi/omp/hermes launchers (session history loss) #230), plus the two review pins (clamped-range compact fallback, stub fixpoint stability).tests/issue3975-mcp-lineform-endstate.test.ts(new, host level, 2): issue expectations 2–3 pinned end-to-end — afterhideConsumedCompressCalls+stripKernelSummaries, the plain-text call ends stubbed while itsacp_summary_b0anchor stays byte-complete; object-array parity pin.