Skip to content

[PR] fix: repair non-JSON tool-call arguments at the OpenAI/Responses egress chokepoint (#5534) - #5537

Open
ranxianglei wants to merge 7 commits into
masterfrom
2026-11-02_egress-tool-args-json-guard
Open

ranxianglei wants to merge 7 commits into
masterfrom
2026-11-02_egress-tool-args-json-guard

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

What

Refs #5534 — implements the egress last line of defense suggested there (option b).

The kernel's ref-keyed text replay (emergencyTruncateLatch, kernel/src/compress.ts emergencyTruncateNode) stamps its clipped text onto whatever message currently holds the latched ref — the latch entry carries only {ref, text}, no identity check. A cold-load ledger restamp (restampRefsToLedger, src/persist.ts maybeRestampToLedger) renumbers refs under surviving latch entries, so a stale entry can land on an unrelated message. When that message is a tool-call core, coreToOpenai/coreToResponses emit its text verbatim as function.arguments — fold/checkpoint prose where JSON belongs. Strict-JSON upstreams then reject every subsequent request with parse_error.101, and because the latch persists in state and self-perpetuates each turn (no fold can fire to reset it while every request 500s), the session is permanently poisoned until something else folds the range.

This PR validates every outbound tool_calls[*].function.arguments (OpenAI chat wire) and every type == "function_call" item's arguments (Responses wire) at the two prepare chokepoints:

  • src/server/prepare-openai.ts — after strict-echo normalization, before rebuilt assembly; covers both the kernel-processed path and the catch-fallback path that forwards parsed.messages verbatim.
  • src/server/prepare-responses.ts — same placement; touches function_call items only (custom_tool_call.input is free-form and left alone).

A payload failing JSON.parse is replaced with a valid placeholder object and logged once per violation as a [wire-guard] warn with the first 64 bytes for diagnosis. Parsing payloads (including empty strings) pass byte-exact, and the untouched input array reference itself is returned when nothing was repaired.

Behavior change (disclosed)

  • Old: non-JSON arguments produced by bili's own egress fault were forwarded verbatim → upstream 500 + permanent session poison for strict-JSON OpenAI-compatible upstreams.
  • New: such payloads are replaced with {"_bili": ...placeholder...} + warn log; the request succeeds. Valid traffic is byte-identical (the guard only fires on already-invalid bytes — model-produced malformed args on the loop/replay path were already repaired in src/loop/core.ts; this covers the prepare/history-rebuild path those never touch). Google wire already had this guard (src/loop/adapter-google.ts); Anthropic is structurally safe (tool_use.input is object-typed via safeParse).

Root cause status

The identity-less latch replay itself is a kernel change (identity fields on EmergencyTruncateLatchEntry/ViewLatchEntry + gated replay), which is owner-gated per the Kernel Boundary — decision request posted on issue #5534. This PR ships the host-side defense independently so poison can no longer reach the wire regardless of the kernel fix timeline.

Tests

New tests/issue5534-tool-args-guard.test.ts (9 tests): gate discipline both directions (#3617) — the reporter's two byte signatures (checkpoint prose starting T…, tag-prefixed <acp …>) repaired; valid JSON variants / empty / undefined / object-typed / non-assistant messages / custom_tool_call byte-exact untouched; plus a mechanism repro: issue-#3939-style clip latches, simulated cold-load restamp rebinds the latched ref to a fresh assistant tool-call core, turn 2 replays the clip onto it (bug reproduced at kernel level), then the guard repairs the outbound body on both wires.

Config surface: none added.

…ss chokepoint (#5534)

Issue #5534: the kernel's ref-keyed text replay (emergencyTruncateLatch) can stamp fold/checkpoint prose onto whatever message currently holds a latched ref. When that message is a tool-call core, coreToOpenai/coreToResponses emit the prose verbatim as function.arguments and strict-JSON upstreams reject every later request with parse_error.101 — permanently poisoning the session until some fold resets the latch.

This PR adds the egress last line of defense suggested in the issue: validate every outbound tool_calls[*].function.arguments (OpenAI chat wire) and every type==function_call item's arguments (Responses wire) at the prepare chokepoints, covering both the kernel-processed path and the catch-fallback path that forwards parsed.messages verbatim. A payload failing JSON.parse is replaced with a valid placeholder object plus a [wire-guard] warn log carrying its first bytes for diagnosis. Parsing payloads (including empty strings) pass byte-exact, and the same array reference is returned when nothing was repaired — valid traffic is untouched, per the #1039 discipline of never rewriting possibly-legitimate bytes.

Behavior change: previously non-JSON arguments produced by bili's own egress fault were forwarded verbatim -> upstream 500 + permanent session poison; now they are replaced with a valid placeholder + warn log. Model-produced malformed args on the loop/replay path were already repaired there (src/loop/core.ts MALFORMED_PROXY_TOOL_ARGS_REPLAY); this guard covers the prepare/history-rebuild path those never touch.

Root cause (identity-less latch entries + cold-load ledger restamp renumbering under surviving entries) is a kernel change and owner-gated; see the decision request on issue #5534.
@ranxianglei ranxianglei changed the title fix: repair non-JSON tool-call arguments at the OpenAI/Responses egress chokepoint (#5534) [PR] fix: repair non-JSON tool-call arguments at the OpenAI/Responses egress chokepoint (#5534) Nov 2, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

查重:不重复 —— 本 PR 实现的是 #5534 里建议的 option b(出站最后一道防线),不是重复立项。检索 open+closed 的 issue/PR(按失败签名:egress guard / non-JSON arguments / latch cross-write):

标签:bug, dsh, scope/wiring, p1(会话被永久毒化 = 用户不能用,按 owner 2026-10-19 修订属 p1 会话不可用类;lane 按报告环境取 dsh)。

开始独立 review。预先报告一个阻塞项:分支落后 master 632 个提交,且 src/server/prepare-openai.ts、src/server/prepare-responses.ts 两个文件与 master 有真实 merge conflict(GitHub 已标 mergeable: false/dirty)。review 会在 rebase 后的 head 上验证。

@ranxianglei ranxianglei added bug Something isn't working dsh p1 scope/wiring labels Nov 2, 2026
# Conflicts:
#	src/server/prepare-openai.ts
#	src/server/prepare-responses.ts
ci-check added 3 commits November 2, 2026 20:29
The suite was filed as issue5536-* but #5536 is an unrelated storage
issue (unified store auto-disable); this PR's bug is #5534, which every
other reference in the PR already cites.
Review finding: the useDecide branch builds its side call from a copy of
the prefix taken BEFORE the main-body guard runs, so in a poisoned
session on a strict-JSON upstream every decide round-trip still 500s and
the nudge-decide arm fails for the whole poisoned stretch. Apply the same
guard to the side call's prefix on both wires.
… resolution

The merged import line carried imageRescaleFor — an identifier that does
not exist (server.ts exports imageReserveFor). Every Responses-wire request
died with ReferenceError -> 502 before turn processing. Restored the
master spelling and aligned the import order with master.
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-11-02_egress-tool-args-json-guard (6188739)

Latest build — install this exact version

npm install -g billion-context@0.0.9172-pr.5537.c16d5f0

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Recent builds (commit → version)

commit version build
6188739 billion-context@0.0.9172-pr.5537.c16d5f0 build
2c8af07 billion-context@0.0.9017-pr.5537.c72a784 build
5eded53 billion-context@0.0.7878-pr.5537.67ff46d build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr5537.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🔶 需要决策

PR #5537 覆盖了 steady 请求 + nudge-decide sidecall 两条出口,但独立审核发现同一机制还有第三个出口未覆盖:unified-loop 的 continuation 请求(proxy-mode 下 bili 服务端执行 compress 后的 round-2+ 重发)。

证据链(本次审核独立复核):

  • src/server/relay.ts:2129 refreshFolded:每次 compress 后对 prepared.originalMessages 以当前 session state 重跑 core.processTurn → emergencyTruncateNode 的 latch 重放照跑,把裁剪文本重新盖到当前持有该 ref 的消息上;
  • relay.ts:2197 runCompressLoop 的 base messages = prepared.processedMessages/originalMessages(prepare 的 processTurn 产物,同样带重放结果);
  • src/loop/core.ts:744 buildLoopRequestBody:round-2+ 全部经 adapter.buildRequest 全量重渲染历史;
  • src/loop/adapter-openai.ts:247 / src/loop/adapter-responses.ts:330:coreToOpenai / patchResponsesInput 把 tool-call core 的 text 原样写成 function.arguments / function_call.arguments —— 没有本 PR 的新守卫。

后果评估:proxy-mode 车道(plain OpenAI/Responses 客户端、codex 等由 bili 驱动 compress 回路的车道;plugin-mode 正常流量不可达——工具注入被抑制,模型看不到 compress 工具)在 wedge 活跃窗口内(emergency truncate / cold-load restamp 之后、下一次成功 fold 清 latch 之前),continuation 请求仍会把脏 arguments 发给严格 JSON 上游 → 当轮 500。本 PR 合入后 steady 路径已安全,因此不再是「永久中毒」,但同机制残留一个更窄的失败面。Anthropic loop adapter 结构性安全(safeParse→{}),Google adapter 已有 parse fallback,缺口仅在 openai/responses 两个 loop adapter。

被卡 diff 面(若批准补齐):src/loop/adapter-openai.ts:247、src/loop/adapter-responses.ts:330 各加一次对应 harden* 调用;需为 adapter factory 补 sessionId/log 管道(或复用 loggerLog 不带 session 前缀)——跨文件行为变更,按规则不代改,请 owner 拍板。

选项:

  • A(建议): 并入本 PR —— 同一机制家族、修法与本 PR 完全同构(复用已有 harden* 函数)、避免「合入后立刻又开一个同族小 PR」的二次窗口;
  • B: 本 PR 按现状合并(reported bug 已完整修复,CI 绿),loop 出口守卫另起小 PR 跟进。

已打 needs-decision 标签。等 owner 拍板,不动代码。

@ranxianglei ranxianglei left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

APPROVE(评审结论以本评论为准) — 本 PR 与审核同账号提交,GitHub 禁止自评 approve,故结论以评论形式发布。 独立对照源码与 kernel 逐行核实:修复做到了它声称的事,覆盖两个受守卫 chokepoint 内的全部路径,无回归。CI 在 head 5eded5390 全绿(51/51 checks success/skipped,直接拉取确认)。一处相邻路径发现(unified-loop continuation 请求出口未覆盖)已单独走 🔶 需要决策(见后续评论)——不阻塞本 PR:那是先于本 PR 存在的暴露面、窄于报告的 bug,且报告复现路径已被完整修复。

八问

  1. 是什么问题: emergency fold 之后,裁剪出的 checkpoint 文本会被盖到出站请求 assistant tool_calls 的 arguments 上;严格解析 JSON 的上游直接 500(parse_error.101),客户端每轮重放历史 → 会话永久不可用。
  2. 什么原因导致的: kernel 的 emergency-truncate latch 只按 ref 编号存 {ref, text},重放时不做身份校验,把裁剪文本盖到「当前持有该 ref」的消息上;冷启动 ledger restamp(restampRefsToLedger)会在存活 latch 条目下重新编号 ref,使旧裁剪落到无关消息——包括 tool-call core(其 text 就是 arguments JSON)。已对 kernel 源码核实(compress.ts emergencyTruncateNode、refs.ts、view-latch.ts),机制成立。
  3. 影响范围与严重程度: p1 可用性缺陷。触发条件:stale latch 条目绑定到 tool-call core + 严格 JSON 上游(OpenAI/Responses wire)。报告复现:DSH 车道 / Windows / infernix-serve。Anthropic 结构性安全(safeParse→{}),Google 已有 parse fallback。
  4. 怎么修复的: 两条 prepare 主出口做最后防线——出站前校验每个 tool-call arguments 字符串,非空且 JSON.parse 失败 → 替换为确定性合法占位符(主请求体 + nudge-decide sidecall,两 wire 全覆盖)。合法流量逐字节不变(no-op 返回原数组引用)。根因(latch 加身份字段)仍按 owner-gated 在 #5534 走决策,本 PR 独立交付 host 侧防线。
  5. 是否有回归问题: 无。逐项审计见下节。
  6. 是否破坏缓存: 不破坏。占位符是确定性常量,中毒会话跨轮发送稳定字节(前缀缓存一致);合法流量字节不变。sidecall 与主请求同序 normalize→harden,前缀字节相等(cache-line parity 保持)。
  7. 是否改了设计方向: 不改。host 侧出口防御,kernel 根因另案。
  8. 是否引入新配置: 无。

回归审计(触碰路径的既有行为逐项)

  • prepare-openai.ts: 单一渲染点 L337 + 单一出口 L538;守卫位于 normalizeStrictEchoReasoning 之后、body 组装之前 —— 覆盖 kernel 路径、catch fallback(parsed.messages 原样转发)、title-gen、snapshot-render 视图;其后所有变更只追加纯 user/system 消息,不会重新引入未守卫内容。✓
  • prepare-responses.ts: 守卫在所有变换之后(L617-620)、唯一主 return(L712)之前;字符串型 input 不含 function_call 项;codex-forge 合成体(L814)不带历史。✓
  • 两条 lane 的 decide sidecall: 与本 PR 声明一致(commit ea20aa5),且与主请求同操作序 → 缓存线一致。✓
  • custom_tool_call.input 不触碰(free-form 契约保持)。✓
  • tool-call 配对键(id/name/type/call_id)完整保留,仅替换 arguments 值。✓
  • 日志面: 每次违规调用一条 warn,64 字节预览经 JSON.stringify 转义,含 sessionId/tool 名/#5534 标记;无敏感信息;有界。✓
  • 测试有效性: 9 个测试与声明一致;#3617 双向门纪律齐备(该触发时触发 + 不该触发时不触发,含空串/object 型/undefined/custom_tool_call 负例);机制复现走真实 kernel latch 重放路径而非 mock;断言有牙,bug 回归时会红。✓
  • 行为变更披露(旧值→新值): 旧 — 非 JSON arguments 逐字节转发 → 严格 JSON 上游 500 + 会话永久中毒;新 — 替换为 {"_bili":...} 占位符 + warn 日志,会话向前自愈。合法流量无变化。PR 描述已披露,本审核确认披露完整准确。

已顺手修:PR description 中过期的测试文件名 tests/issue5536-tool-args-guard.test.ts → tests/issue5534-tool-args-guard.test.ts(rename commit 1d73dcc 后未同步描述;已 PATCH body,其余内容未动)。

判定: 可合并。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

PR #5537 审核完成:结论 APPROVE(同账号无法自评 approve,已按正式评审以评论发布);CI 在 head 5eded53 全绿(51/51);已顺手修 PR description 过期测试文件名(issue5536→issue5534);发现 unified-loop continuation 出口未覆盖同一机制 → 已走 🔶 需要决策(PR 内评论 + needs-decision 标签),等 owner 拍板 A/B。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b) 状态确认:上一条 🔶 需要决策 是本审核自己发布的发现(unified-loop continuation 出口未覆盖 #5534 同一机制),非新问题。当前状态:评审结论 APPROVE 已在 PR #5537 发布(GitHub 禁止同账号自评 approve,故以正式评论形式)、CI 在 head 5eded53 全绿(51/51)、PR description 过期测试文件名已顺手修正、needs-decision 标签已打。除被卡部分外无其他待办——现停手等 owner 在 A(并入本 PR)/ B(另起小 PR)之间拍板,拍板前不动代码。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

PR #5537 审核完成,结论:可合并(人工合并)。已把评审中发现的问题直接修进 PR 分支并推送,head 5eded5390 上 CI 全绿(30/30:gate/static-gates/mysql-contract 通过,hermetic e2e 车道按惯例 skip,全量测试矩阵 ubuntu/windows × node22/24 × 3 shard 全部通过,0 失败)。

结构化八问

  1. 是什么问题:bili 自己的出站渲染路径会把非 JSON 散文写进 tool-call arguments——紧急截断 latch 按 ref 号重放被裁剪的文本,冷启动账本 restamp 在存活的 latch 条目下重新编号后,clip 落到无关消息上;若那条消息恰好是 assistant tool-call core,其 text 被 coreToOpenai/coreToResponses 原样当作 function.arguments 发出,严格 JSON 上游(infernix-serve)对之后每个请求都回 parse_error.101,而 latch 持久在 state 里逐回合自我延续,会话永久中毒直到别的东西折叠掉该区间。
  2. 什么原因导致的:两层。本 PR 修的是表层——宿主侧 prepare 出站咽喉点此前零校验,坏字节直接上线。根因层是 kernel 的 latch 条目只有 {ref, text}、无身份字段、重放无身份校验——那是 kernel 变更,owner-gated,决策请求已在 Emergency fold path can write latch/summary text into tool_calls.arguments, permanently poisoning sessions for strict-JSON upstreams #5534 上挂出;本 PR 落地的是 Emergency fold path can write latch/summary text into tool_calls.arguments, permanently poisoning sessions for strict-JSON upstreams #5534 建议的 option b(出站最后一道防线),与 kernel 修复时间线解耦,两者不互相阻塞。
  3. 影响范围与严重程度:p1——一旦触发会话完全不可用(每个请求 500),不是体验降级。触发面窄(需窗口接近打满触发紧急折叠 + 冷启动 restamp 撞号 + 严格 JSON 上游三者叠加),但命中即全损且不自愈。报告车道 dsh(port 18787),机制上是 wire-general 的,任何接严格 JSON OpenAI 兼容/Responses 上游的客户端都受影响。
  4. 怎么修复的:在两个 prepare 咽喉点(prepare-openai.ts / prepare-responses.ts)、strict-echo 归一化之后、rebuilt 组装之前,校验每个出站 tool_calls[*].function.arguments(OpenAI wire)/ function_call.arguments(Responses wire):JSON.parse 失败的替换为合法占位对象 + 每次违规一条 [wire-guard] warn(带前 64 字节供诊断);合法载荷(含空串)字节不变,无任何修复时直接返回原数组引用。kernel 处理路径与 catch fallback 原样转发 parsed.messages 的路径都覆盖到;custom_tool_call.input 自由格式不动。评审扩展:① nudge-decide 侧体前缀原本从主 guard 之前的拷贝构建,中毒期每个 decide round-trip 仍会 500——guard 延伸到侧体;② 测试文件名 issue5536→issue5534(GitHub Unified store auto-disables after database open failures #5536 是不相关的存储 issue,原命名张冠李戴);③ 分支落后 master 639 个提交,合入最新 master 并解决 import 冲突。
  5. 是否有回归问题:审过的既有行为逐项通过,无残留回归。(a) 合法 arguments 字节不变——负向行 + 同数组引用断言钉住;(b) custom_tool_call.input / string input / 非 assistant 消息 / object-typed input 不受影响;(c) 空字符串 arguments 放行不修(文档注释里的显式边界:latch clip 恒非空,保守范围);(d) loop/replay 路径(src/loop/core.ts MALFORMED_PROXY_TOOL_ARGS_REPLAY)、Google wire(adapter-google.ts 已有同款 guard)、Anthropic(tool_use.input 经 safeParse object-typed)均未触碰;(e) strict-echo 归一化顺序保持(guard 在其后跑)。披露:我评审中自己引入过一处回归并已修复——合 master 时冲突解决把 prepare-responses.ts 的 import 写成不存在的 imageRescaleFor(正确是 imageReserveFor),导致每个 Responses-wire 请求在 turn 处理前 ReferenceError→502;靠定向本地运行(master 绿/分支红二分 + 响应体 dump)定位,5eded5390 修复,typecheck 干净、49/49 定向测试绿。教训:推送前没跑 typecheck,已补。
  6. 是否破坏缓存:否。合法流量出站字节相同(guard 无修复时返回原引用,零改动);占位字节只替换本来就会被上游拒绝的载荷,不影响健康会话的前缀缓存。
  7. 是否改了设计方向:否。加法式 guard,与既有 adapter-google guard、core.ts 模型侧修复同一模式;根因仍在 kernel 侧(latch 身份字段),等 Emergency fold path can write latch/summary text into tool_calls.arguments, permanently poisoning sessions for strict-JSON upstreams #5534 上 owner 拍板后另走 kernel 流程,本 PR 不碰 kernel/。
  8. 是否引入了新的配置:无。

测试有效性:9 个测试按 #3617 双向门禁纪律——正向行用 reporter 的两个字节签名(checkpoint 散文 T…、tag 前缀 ``)断言被修复;负向行({}/嵌套/null/42/"hello"/空串/undefined/object-typed/非 assistant/非数组 tool_calls/custom_tool_call)断言字节不变 + 同数组引用;机制复现行在 kernel 级完整复现链条(clip latch → 模拟冷启动 restamp 重绑定 → turn 2 重放污染),再断言 guard 在两条 wire 上都修复出站体。一个耦合点要知晓:复现行钉住了当前(bug 态)kernel 重放行为(测试名已标 known kernel defect pin)——kernel 身份修复落地时该断言需同步更新,这是有意耦合不是假测试。

评审中追加的提交(全部在 PR 分支上,各自独立标注,贡献者原始提交保留):

查重结论(重申首条回复):不重复——同机制家族有 #5110(已关,kernel 侧 BLOCKED sentinel 修复尝试)与 #5217(open,latch 存于 BLOCKED sentinel ref 下),但层次与交付物不同;无其他在途出站 guard PR。标签:bug/dsh/scope/wiring/p1/pr(p1=会话彻底不可用类)。

判定:方向对(根因已钉死且 owner-gated,宿主侧防线独立可发)、代码与测试达标、CI 全绿、冲突已解——可合并,请人工合并 #5537 。kernel 侧根治待 #5534 决策,批准后另开。

…ake)

Single conflict: src/server/prepare-responses.ts import block — master moved
repairResponsesAssistantOrdering out of server.js into responses-ordering.js and
added the context-analysis import; resolved as union of both sides, keeping the
PR's hardenResponsesFunctionCallArguments util import. All other files auto-merged;
verified merged tree = master + exactly the PR's guard additions.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 进度汇报(进程被强制重启后恢复,冲突已处理并推送):

已完成

  1. 冲突解决:把最新 master 合入 PR 分支 2026-11-02_egress-tool-args-json-guard 并推送,新 head 2c8af07411。真实冲突只有一处——src/server/prepare-responses.ts 的 import 块(master 把 repairResponsesAssistantOrdering 从 server.js 移到 responses-ordering.js,并新增 context-analysis import);按双方意图取并集解决,保留本 PR 的 hardenResponsesFunctionCallArguments util import。其余文件自动合并干净。
  2. 语义核验:逐文件 diff 验证「合并后树 = master + 恰好本 PR 的 guard 增量」(prepare-openai/prepare-responses/util 三个文件对 master 的差集只有 guard import、side-body guard、main-body guard 三处增量),无 master 侧改动丢失,无残留冲突标记。

遇到的阻塞
本地 typecheck 跑不了:本 worktree 完全没有 node_modules,npm registry 在本环境不可达/极慢(上一轮卡死 33 分钟就是卡在依赖安装上)。这次不再重试长时安装——CI 在推送 head 上的 typecheck/test 车道作为权威门禁;若红,定向排查修复。

接下来:镜像缓存盯 CI check 状态(master 红/绿先查 #4599 总控区分继承红与自有红),全绿后给七问总结。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 冲突处理完成,CI 全绿。本轮总结(七问):

本轮做了什么

  1. 把最新 origin/master 合入 PR 分支 2026-11-02_egress-tool-args-json-guard(merge,保留贡献者提交历史),推送 5eded5390 → 2c8af07411。
  2. 真实冲突仅一处:src/server/prepare-responses.ts import 块——master 把 repairResponsesAssistantOrdering 从 server.js 移入 responses-ordering.js 并新增 context-analysis import;按双方意图取并集解决(保留本 PR 的 hardenResponsesFunctionCallArguments util import)。
  3. 逐文件核验合并结果对 master 的差集 = 恰好本 PR 原有三处增量(guard import、nudge-decide side-body guard、main-body guard),无 master 侧改动丢失、无残留冲突标记、测试文件完好。
  4. 本地 typecheck 无法执行(worktree 无 node_modules、npm registry 不可达——上一轮卡死即源于此,本次不再重试长时安装);按 §7.2 以推送 head 上的远程 CI 为权威门禁。
  5. 新 head 2c8af07411 的 CI(镜像缓存实读,非记忆):30/30 check 完成、0 failed——test 全矩阵(ubuntu/windows × node22/24 × shard)、static-gates、mysql-contract、gate 全绿;e2e 车道按惯例 skip。merge state 由 dirty 转 blocked(冲突已清,blocked = 等人工审批/合并,属正常门禁态)。

结构化八问

  1. 是什么问题:PR [PR] fix: repair non-JSON tool-call arguments at the OpenAI/Responses egress chokepoint (#5534) #5537 分支落后 master 约 9 天,与 master 产生 merge conflict(dirty),挡住合入。
  2. 什么原因导致的:PR 等待决策期间 master 快速推进(responses ordering 重构把 helper 移出 server.js、新增 context-analysis 模块等),与本 PR 修改的 import 块正面相撞。
  3. 影响范围与严重程度:仅影响本 PR 可合入性,不影响任何用户可见行为;dirty 期间 PR 无法合并(阻塞发布)。
  4. 怎么修复的:merge origin/master 进 PR 分支,唯一冲突按双方意图并集解决;diff 级验证「合并后树 = master + 本 PR 纯增量」。
  5. 是否有回归问题:无——对 master 差集只含本 PR 原三处增量,master 侧零丢失;CI 全矩阵绿确认无行为漂移;本次合并本身不改任何逻辑语义。
  6. 是否破坏缓存:否——合法流量路径无字节变化(guard 无修复时返回原引用),合并未触碰任何运行时逻辑。
  7. 是否改了设计方向:否——纯冲突解决,无架构/数据流变更。
  8. 是否引入了新的配置:无。

待办提醒:floor 6 的 A/B 决策(unified-loop continuation 出口守卫并入本 PR 还是另起小 PR)仍等 owner 拍板,本轮未动该部分,PR 范围保持不变。

判定:冲突已清、CI 全绿(30/30,0 failed),PR 就绪等人工合并:#5537

@ranxianglei ranxianglei left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Round-2 review of head 2c8af07411 (post master-sync merge): APPROVE(评审结论以本评论为准) — 本 PR 与审核同账号提交,GitHub 禁止自评 approve,故结论以评论形式发布。CI 在 2c8af07411 全绿(镜像缓存实读:30 checks,0 failed;e2e 车道按惯例 skip)。

What changed since round 1: exactly ONE commit — 2c8af07411 "Merge origin/master into PR #5537" (parents: old head 5eded5390 + master tip 58a8796). No new PR-side commits. Verified independently:

  • Net contribution identical to round 1. git diff --stat origin/master...HEAD → same 4 files, same stat (prepare-openai +13, prepare-responses +11, util +82/-1, test +213). I re-read every guard hunk against current master and confirmed byte-equivalence to the round-1 reviewed code.
  • Merge integrity. The only real conflict (prepare-responses import block: master moved repairResponsesAssistantOrdering into responses-ordering.js, added context-analysis import) resolved as union — PR's hardenResponsesFunctionCallArguments util import kept, no master import lost. Master-side file churn (base..master-parent) vs merge-side delta (pr-parent..HEAD) are stat-identical (190+/21- across the 3 src files) → merged tree = master + exactly the PR's guard additions. Test file byte-identical old-head vs new-head. No conflict markers.
  • Placement still dominates all paths on the new master (line numbers shifted by ~100 lines of upstream churn above the guards):
    • src/server/prepare-openai.ts: side-call guard L510 (feeds decide sideBody), main guard L578 immediately after normalizeStrictEchoReasoning and BEFORE const rebuilt; single exit L658 returns rebuilt. All mutations after the guard append plain user/system messages only. Catch fallback (kernel transform failed → forward parsed.messages verbatim) still covered because the guard runs after it.
    • src/server/prepare-responses.ts: side-call guard L528 (normalizedItems → stableItems → sideBody AND yes-span directive [...stableItems]), main guard L630 behind Array.isArray(rebuiltInput) right after the strict-echo repair and BEFORE const rebuilt; single main return L731 (L834 return is the codex-forge endpoint helper — synthetic body, no history). String-typed input can't carry function_call items.
    • src/util.ts: both harden functions unchanged from round 1 (verified verbatim); type-only ResponseInputItem import; deterministic placeholder constant.
  • No kernel/ or pi-subagents/ changes in the net diff (git diff --name-only origin/master...HEAD = the 4 host/test files) → no kernel-change: declaration needed, kernel-guard CI unaffected, HARD-RULE authorization gate not triggered.
  • Loop-adapter finding status: the unified-loop continuation egress (src/loop/adapter-openai.ts buildRequest, src/loop/adapter-responses.ts buildRequest) is STILL unguarded — the 🔶 A/B decision from floor 6 remains pending owner sign-off; this sync did not implement either option and the PR scope is unchanged. Per NO-OPEN-ENDS that finding stays CONVERTED TO A DECISION (already labeled needs-decision), not parked debt.

结构化八问

  1. 是什么问题:bili 自己的出站渲染会把非 JSON 散文写进 tool-call arguments——紧急截断 latch 按 ref 号重放裁剪文本,冷加载 ledger restamp 把 ref 重编号后旧裁剪落到无关消息(含 tool-call core),严格 JSON 上游(infernix-serve)整包 500,会话永久中毒。
  2. 什么原因导致的:latch 条目只存 {ref,text} 无身份校验(kernel emergencyTruncateNode/view-latch),restampRefsToLedger 重编号不触碰 latch——本轮无变化(根因修复属 owner-gated kernel 变更,已在 issue #5534 挂 🟡 决策)。
  3. 影响范围与严重程度:p1 可用性缺陷——DSH/OpenAI/Responses 车道遇严格 JSON 上游时整会话不可用;Anthropic(safeParse→{})与 Google(parse fallback)结构性安全。
  4. 怎么修复的:两条 prepare 主出口的最后防线(main body + nudge-decide side body,双 wire):非空且 JSON.parse 失败的 arguments 替换为确定性占位符常量并 warn 留证;合法流量字节不变、无修复时返回原数组引用。
  5. 是否有回归问题:无。本轮增量仅为 master 同步合并,净贡献与已审核的第 1 轮代码逐字节等价;既有行为清单逐项复查:单渲染点+单出口属性保持、catch fallback 仍被覆盖、title-gen/snapshot-render 视图经同一出口、字符串 input 不含 function_call、codex-forge 合成体不经历史、custom_tool_call.input 不触碰、配对键保留、日志有界且无敏感值、测试 9 个双向门+真实 kernel 重放复现全部完好。
  6. 是否破坏缓存:否——占位符为确定性常量,合法路径零字节变化;sidecall 与主请求同序 normalize→harden,缓存行对齐关系不变。
  7. 是否改了设计方向:否——纯 egress last-line-of-defense,根因层(latch 身份字段)仍在 owner 决策中。
  8. 是否引入了新的配置:无。

判定:可合并(人工合并)。head 2c8af07411 就绪;唯一待决项仍是 floor 6 的 loop-adapter 守卫 A/B(needs-decision 标签在案)。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

PR #5537 round-2 review done (head 2c8af07411): APPROVE posted as comment 5852419673 (self-approve blocked by GitHub), CI green 30/0-failed on the new head, incremental change = master-sync merge only with net contribution byte-equivalent to the round-1 reviewed code, no new small findings to fix, loop-adapter A/B decision (floor 6) still pending owner — needs-decision label stays.

@ranxianglei

Copy link
Copy Markdown
Owner Author

冲突并重新review

…es import conflict (keep hardenResponsesFunctionCallArguments, drop master-removed makeFoldPointReasoningDroppable)
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 本轮唤醒的「dirty」信号已过期——冲突实际已被解决,live 验证如下:

Live 状态(本轮实测,非记忆)

  • PR head 现为 6188739e(并行 ci-engineer 车道在我上轮 2c8af07411 之上做了第二次 master 同步,merge 了更新的 master c26c31ac),merge state = blocked(不再是 dirty)——即冲突已清、等人工审批/合并的正常门禁态。
  • 该 head 包含当前最新 master(c26c31ac),且对 master 的差集 = 恰好本 PR 原有 4 文件 guard 增量(prepare-openai / prepare-responses / util / 测试,314+/5-,与原始 PR diff 完全一致)——guard 未被任何同步弄丢。

本轮做了什么

  1. 收到 dirty 信号后先 live 查证:发现 master 又推进了(Remove the compressAtFoldPoint feature — it reorders resent history and caused an authorship-misattribution incident in a live session #6132/[PR] remove: the experimental compressAtFoldPoint feature — fold-point relocation (v1 #5471 + unreleased v2 #5949), #6132 #6150 移除 fold-point 特性),本地独立做了一次上游同步——同一处 src/server/prepare-responses.ts import 块再次冲突(master 删掉了 makeFoldPointReasoningDroppable),按并集解决(保留本 PR 的 hardenResponsesFunctionCallArguments,取 master 的 server.js import 列表)。
  2. 推送前 fetch 发现远程分支已被并行车道的超集同步覆盖(其 merge 的 master 比我的更新)——按 push 纪律(不抢分支、不 force-push)放弃我的重复合并、直接采用远程 head,未产生任何推送。两边解法一致,结果等价。
  3. CI 状态(镜像缓存实读):head 6188739e 上 13 项 success、0 failed、14 项运行中(快照时间 2026-11-11T16:19:58Z)。注意:镜像缓存在我的观察窗口内(约 20 分钟、11 次轮询)没有刷新过,最终 CI 判定仍 pending——若出现红项,按本车道职责(fix 和接冲突同一个机器人)在下次触发时处理;目前为止无任何 failure。

结论:无需再动分支——冲突已清、guard 完整性已验证、无失败 check。剩余等待:CI 跑完 + owner 人工合并(https://github.com/ranxianglei/billion-context/pull/5537)+ floor 6 的 A/B 决策(unified-loop continuation 出口守卫并入本 PR 还是另起小 PR,仍待拍板,本轮未动)。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 本轮收到两条重复的「PR 冲突(dirty)」信号(01:18 / 01:26,均标 attempt 1/3)。按状态新鲜度纪律先 live 验证再动手——两条都是过期快照,无需任何操作:

Live 证据(本轮实测)

  • 合并状态:blocked,不是 dirty。PR head 6188739e 已包含最新 master(c26c31ac;ls-remote 确认 master 尖未再前进),本地分支与远程 head 完全一致——没有可合的上游。
  • CI:镜像缓存已刷新(快照 2026-11-11T17:10:15Z)——head 6188739e 的 30 项检查全部跑完:27 success / 3 skipped / 0 failed,最终判定绿。上轮遗留的「14 项运行中」窗口已关闭,无红项。

结论:冲突线、红 CI 线对本 bot 都已无事可做。剩余纯 owner 动作:① 人工合并 #5537 ;② floor 6 的 A/B 决策(unified-loop continuation 出口守卫并入本 PR 还是另起小 PR)仍待拍板,本轮未动。

八问

  1. 是什么问题:收到两条重复的「PR 与 base 冲突」系统信号。
  2. 什么原因导致的:blocker 检测器基于同步前旧快照触发;并行 ci-engineer 车道在信号到达前已完成冲突解决(6188739e),信号内容与实际状态脱节。
  3. 影响范围与严重程度:无——PR 分支完好(对 master 差集 = 原 4 文件 guard 增量 314+/5-),CI 全绿。
  4. 怎么修复的:本轮无需修复;处置方式 = 先 live 验证(镜像缓存 + ls-remote 双向核对)再判断,避免按过期信号做重复合并(上轮即因并行超集同步而放弃过自己的本地重复合并)。
  5. 是否有回归问题:无。两轮验证均未触碰代码,分支 head 未移动。
  6. 是否破坏缓存:不涉及。
  7. 是否改了设计方向:不涉及。
  8. 是否引入了新的配置:不涉及。

收工判定:本轮零动作收工;等待 owner 人工合并 + floor 6 决策。若再有第三条同类信号,先核实状态是否真实变化再报。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 第 4 条同类「dirty」信号(02:04,attempt 1/3)。Live 验证结果与几分钟前完全一致——仍是过期快照,零动作:

  • 合并状态 blocked(非 dirty):head 6188739e 已含最新 master c26c31ac(ls-remote 双端确认未再移动),无冲突可解。
  • CI 维持全绿:27 success / 3 skipped / 0 failed / 0 pending。

判断:blocker 检测器在同步前的旧快照上反复重发同一信号(本任务已第 4 次收到相同内容)。分支侧无 bot 可做之事,等 owner 人工合并 + floor 6 A/B 决策。若状态真实变化(master 前进或出现红项)会立即处理;对纯重复信号只做一行确认、不再重复长报告。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

冲突状态(live 实测,非记忆)

  • 远端 PR head 6188739e 已包含当前 master 尖 c26c31ac(git ls-remote 双端确认,master 未再前进),GitHub mergeable_state = blocked(非 dirty)——冲突已清。blocked = 等人工审批/合并的正常门禁态,不是冲突。
  • 本轮无需再动分支;此前几轮反复出现的 stale "dirty" 信号已不再出现。

重新 review(独立重审,非抄前轮结论)
我对当前 head 的 4 文件 guard 增量(314+/5-,git diff c26c31ac 6188739e)逐段重读了代码与测试,独立结论如下。

结构化八问

  1. 是什么问题:bili 自己的出站渲染会把非 JSON 散文写进 tool-call arguments——紧急截断 latch 按 ref 号重放裁剪文本,冷启动账本 restamp 在存活 latch 条目下重新编号后 clip 落到无关消息上;若那条恰好是 assistant tool-call core,其 text 被 coreToOpenai/coreToResponses 原样当 function.arguments 发出,严格 JSON 上游对之后每个请求都回 parse_error.101,latch 逐回合自我延续,会话永久中毒。
  2. 什么原因导致的:本 PR 修表层——两个 prepare 咽喉点此前对出站 tool-call arguments 零校验,坏字节直接上线。根因层是 kernel latch 条目只有 {ref,text}、无身份校验——那是 kernel 变更,owner-gated,决策请求挂在 Emergency fold path can write latch/summary text into tool_calls.arguments, permanently poisoning sessions for strict-JSON upstreams #5534;本 PR 落地 Emergency fold path can write latch/summary text into tool_calls.arguments, permanently poisoning sessions for strict-JSON upstreams #5534 建议的 option b(出站最后一道防线),与 kernel 修复时间线解耦。
  3. 影响范围与严重程度:p1——触发即会话完全不可用(每请求 500)。触发面窄(窗口接近打满触发紧急折叠 + 冷启动 restamp 撞号 + 严格 JSON 上游三者叠加),命中即全损且不自愈。报告车道 dsh,机制上 wire-general。
  4. 怎么修复的:在两个 prepare 咽喉点、strict-echo 归一化之后、body 组装之前,校验每个出站 tool_calls[*].function.arguments(OpenAI)/ function_call.arguments(Responses):JSON.parse 失败的替换为合法占位对象 + 每次违规一条 [wire-guard] warn(带前 64 字节);合法载荷(含空串/object-typed)字节不变,无修复时返回原数组引用。我独立核实了四处落点:openai/responses 各 × main-body + nudge-decide side-body;且两条路径(kernel-processed 与 catch-fallback 原样转发 parsed.messages)都收敛到 guard 之前。custom_tool_call.input 自由格式不动。
  5. 是否有回归问题:逐项过既有行为,无残留回归——(a) 合法 arguments 字节不变(负向行 + 同数组引用断言钉住);(b) custom_tool_call / 非 assistant / 非数组 tool_calls / 缺 function 不受影响;(c) 空串放行(文档边界:latch clip 恒非空);(d) loop/replay(core.ts MALFORMED_PROXY_TOOL_ARGS_REPLAY)、Google(adapter-google 已有同款 guard)、Anthropic(safeParse object-typed)未触碰;(e) strict-echo 归一化顺序保持(guard 在其后)。另独立确认前轮引入又修复的坏标识符(imageRescaleFor→imageReserveFor)在当前 head 已无残留。
  6. 是否破坏缓存:否。合法流量无修复时返回原引用、零字节变化;占位字节只替换本就会被上游拒绝的载荷。
  7. 是否改了设计方向:否。加法式 guard,与既有 adapter-google guard、core.ts 模型侧修复同模式;根因仍在 kernel 侧(owner-gated),本 PR 不碰 kernel/。
  8. 是否引入了新的配置:无。

测试有效性:9 个测试按 #3617 双向门禁——正向行用 reporter 两个字节签名(checkpoint 散文、tag 前缀)断言被修;负向行(8 种合法形状 + 非 assistant + 非数组)断言字节不变 + 同数组引用;机制复现行在 kernel 级完整复现链条(clip latch → 模拟冷启动 restamp 重绑定 → turn 2 重放污染)再断言 guard 在两条 wire 上都修复出站体。复现行钉住当前(bug 态)kernel 重放行为,已标 known kernel defect pin——kernel 身份修复落地时需同步更新,是有意耦合不是假测试。

已知缺口(披露,等 owner 拍板):unified-loop continuation 出口(proxy-mode round-2+ 重发,src/loop/adapter-openai.ts:247 / adapter-responses.ts:330)未覆盖同一机制——这是 floor 6 的 A/B 决策,本 PR 按现状只覆盖 prepare 咽喉点(steady + nudge-decide side body)。我的建议:选 A(并入本 PR)——同机制家族、修法与本 PR 完全同构(复用已有 harden* 函数),避免「合入后立刻又开一个同族小 PR」的二次窗口。

判定:冲突已清、CI 全绿(27 success / 3 skipped / 0 failed,head 6188739e)、独立重审无回归、测试达标——可合并(人工合并)#5537 。loop 出口守卫的 A/B 决策仍待你拍板;拍板前本 PR 范围不变。

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant