Skip to content

kernel authorization request: emergency-truncate latch can store a clip under the BLOCKED sentinel, corrupting every BLOCKED-resolving message (#5218) #5255

Open

Description

@ranxianglei

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

按 Kernel Boundary 第 1 条与授权卡点政策(2026-10-26),本修复触碰 kernel/**,实现前需 owner 批准。批准后立即开 PR(PR 号回填本帖),带 kernel-change: 声明 + 回归测试 + kernel 版本 bump。

查重: 无(open/closed 均无同机制条目;在途的 5 个 kernel authorization request #5180/#5208/#5098/#4886/#4877 机制各不相同)。

一句话(白话)

长会话触发 EMERGENCY truncate 时,裁剪出来的文本片段会存进一个「以消息编号为键」的缓存表;但受保护消息的编号是共享哨兵 "BLOCKED"——于是这段无关文本从此被贴到每一个解析为 BLOCKED 的消息上。tool-call 的 text 就是 function.arguments,所以无辜工具调用的参数整体变成非 JSON,上游每轮 400,会话永久砖化、重启无效、无自愈路径(#5218,已线上复现于 v0.1.209/dsh/OpenAI wire)。

证据(master == v0.1.209:git diff --stat v0.1.209..HEAD -- kernel/src/compress.ts kernel/src/truncate-tools.ts kernel/src/sync.ts 为空)

拟提交 diff(已在树上验证后还原工作区,approve 后原样进 PR)

--- a/kernel/src/compress.ts
+++ b/kernel/src/compress.ts
@@ -1349,11 +1349,15 @@ const emergencyTruncateNode: PipelineNode = {
-    const latchLive = latch.filter((e) => liveRefs.has(e.ref));
+    const latchLive = latch.filter(
+      (e) => e.ref !== BLOCKED_REF && liveRefs.has(e.ref),
+    );
     if (latch.length > 0) {
-      const byRef = new Map(latch.map((e) => [e.ref, e.text]));
+      const byRef = new Map(
+        latch.filter((e) => e.ref !== BLOCKED_REF).map((e) => [e.ref, e.text]),
+      );
@@ -1435,7 +1439,7 @@ const emergencyTruncateNode: PipelineNode = {
-      if (ref !== undefined && clippedMsg?.text !== undefined) {
+      if (ref !== undefined && ref !== BLOCKED_REF && clippedMsg?.text !== undefined) {

三处守卫分工:①持久化侧——把已落盘的毒条目在下一轮持久化时清掉;②回放侧——存量中毒会话升级后第一轮即不再被砸,无需手改 store;③存储侧——堵住生产者。另:kernel/package.json 版本 0.0.169 → 0.0.170(CI 强制);新增回归测试 kernel/tests/issue5218-blocked-latch-poison.test.ts(负向:不存 BLOCKED 键 / 种子毒状态不砸 tool-call JSON 且被清除;正向对照:数字 ref clip 仍逐字节回放)。

验证记录:补丁在树上实测——复现脚本三阶段全部转干净;模拟存量中毒 store 的 heal 检查通过(arguments 完整 JSON、毒条目被清除、合法数字条目保留);latch/truncate/BLOCKED 相关定向测试 44/44 绿(issue3939-latch ×4、issue3945-id-collision、terminal-escape、truncate-keep-filter、truncate-surrogates、issue3953-straddle);tests/verif-subset-guard 绿(compress 属 verif Tier A 守护文件,补丁语法在子语言内)。

为什么宿主侧(src/)修不了

生产者在内核存储路径。宿主侧只能在 hydration/出站前滤掉哨兵键条目——那是堵不是疏(mask):内核不变量「latch 条目以消息身份为键」依旧失守,acp-kernel 作为独立 npm 包的其他消费方继续暴露,且未来任何新的哨兵键状态都会再踩一遍。

行为变更披露(Deliverables 第 4 条)

旧:解析为 BLOCKED 的消息也会被 clip 锁存/回放/持久化(哨兵被当身份用)。新:哨兵永不做 latch 键。副作用(已知且有界):超大受保护消息失去稳定回放——用量在 95% 水位上下穿越的那一轮,该消息可能出现一次 clip↔全文翻转(#3691 振荡类,仅缓存成本,仅限此类消息)。其余行为零变化。

选项与代价

请拍板 A/B(/C)。

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions