Skip to content

refactor(sdk): break ACP route table cycle - #10040

Merged
DragonnZhang merged 6 commits into
QwenLM:mainfrom
DragonnZhang:dragon/sdk-acp-route-cycle
Aug 29, 2026
Merged

refactor(sdk): break ACP route table cycle#10040
DragonnZhang merged 6 commits into
QwenLM:mainfrom
DragonnZhang:dragon/sdk-acp-route-cycle

Conversation

@DragonnZhang

Copy link
Copy Markdown
Collaborator

What this PR does

This PR removes the ACP route table's reverse runtime dependency on the transport utility layer. Route parameter extraction now keeps its small plain-record predicate locally, while both HTTP and WebSocket transports continue to share the same route table and matcher.

Why it's needed

The route table imported the transport utility layer only for a generic record guard, while the transport utility layer imported the route table to implement matching. That formed a runtime cycle between policy data and its consumer. Keeping the four-condition predicate at the extraction boundary restores the intended one-way dependency, avoids a new abstraction for a single-use helper, and preserves route behavior.

Reviewer Test Plan

How to verify

  1. Match representative session, workspace, file, and authentication routes with object and non-object request bodies. Confirm the same JSON-RPC methods and parameters are produced.
  2. Exercise the HTTP and WebSocket ACP transports. Confirm both still resolve requests through the shared matcher and route table.
  3. Inspect the runtime import direction. Confirm transport utilities load the route table, while the route table no longer loads transport utilities.

Evidence (Before & After)

N/A — this is a non-UI import-ownership refactor. The baseline has runtime edges in both directions; after this change only the transport-utility-to-route-table runtime edge remains.

Tested on

OS Status
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

Environment (optional)

Verified on macOS 27.0 with Node.js v26.7.0. Focused route-table, transport-utility, HTTP transport, and WebSocket transport suites passed 190 tests. The SDK lint, typecheck, and build passed. An independent clean npm ci completed the repository build and bundle, and the full workspace plus integration typecheck passed.

Risk & Scope

  • Main risk or tradeoff: A local guard could drift from the transport utility's exported guard; this change copies the predicate exactly, and the complete route and transport suites pass.
  • Not validated / out of scope: Local Windows and Linux execution; route mappings, transport behavior, and public exports are intentionally unchanged.
  • Breaking changes / migration notes: None.

Linked Issues

N/A

中文说明

本 PR 做了什么

本 PR 移除了 ACP 路由表对传输工具层的反向运行时依赖。路由参数提取现在在本地保留一个很小的普通对象判断,而 HTTP 与 WebSocket 传输仍继续共享同一套路由表和匹配器。

为什么需要它

路由表仅为了一个通用对象守卫而导入传输工具层,同时传输工具层又为实现匹配而导入路由表,因此策略数据与其消费者之间形成了运行时循环。在参数提取边界保留这个四条件判断,可以恢复预期的单向依赖,避免为单次使用的辅助逻辑新增抽象,并保持路由行为不变。

Reviewer 测试计划

如何验证

  1. 使用对象和非对象请求体匹配有代表性的 session、workspace、file 和 authentication 路由。确认生成的 JSON-RPC 方法和参数保持一致。
  2. 覆盖 HTTP 与 WebSocket ACP 传输。确认两者仍通过共享匹配器和路由表解析请求。
  3. 检查运行时导入方向。确认传输工具会加载路由表,而路由表不再加载传输工具。

证据(修改前与修改后)

N/A——这是非 UI 的导入所有权重构。修改前两个模块之间存在双向运行时依赖;修改后仅保留传输工具指向路由表的运行时依赖。

测试平台

OS 状态
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

环境(可选)

在 macOS 27.0 和 Node.js v26.7.0 下完成验证。路由表、传输工具、HTTP 传输和 WebSocket 传输聚焦测试共 190 项通过;SDK lint、typecheck 和 build 通过;独立的干净 npm ci 完成了整仓 build 与 bundle,完整 workspace 和 integration typecheck 也通过。

风险与范围

  • 主要风险或取舍:本地守卫可能与传输工具导出的守卫发生语义漂移;本次修改逐字复用了相同判断,并且完整路由与传输测试全部通过。
  • 未验证 / 范围外:Windows 和 Linux 本地执行;路由映射、传输行为和公共导出有意保持不变。
  • Breaking change / 迁移说明:无。

关联 Issue

N/A

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR! Second re-run on @wenshao's @qwen-code /triage (triggered right after his approval) — head unchanged since the last pass, so this re-verifies c155dc94 end to end.

Template looks good ✓

Problem: verified, not theoretical — acpRouteTable.ts imports isRecord from acpTransportUtils.ts (used at runtime in parameter extraction), while acpTransportUtils.ts imports ROUTE_TABLE back. That is a genuine two-edge runtime cycle between policy data and its consumer. Since the last pass @wenshao additionally proved it at runtime (Node ESM loader hooks + Tarjan over the instantiated module graph: 1 strongly connected component on base, 0 with this PR).

Direction: aligned — this is the same cycle-cleanup series the project is actively landing: #9959 (config client cycle) merged 2026-08-25, #10039 (auth flow client cycle) merged shortly after. No CHANGELOG reference for internal SDK import hygiene, but the area is clearly in scope.

Size: packages/sdk-typescript/src/daemon/** is not a Stage 0 core path; 1 file, 13 production lines, no test/generated lines — below every threshold regardless.

Approach: minimal and right — copy the three-line guard locally instead of inventing a new leaf module for a single-use helper, matching the precedent #10039 set in DaemonHttpError.ts. Every line in the diff serves the stated goal; no drive-by edits.

Risk: no elevated risk signals (no Stage 1e high-risk path match).

Moving on to code review. 🔍

中文说明

感谢贡献!应 @wenshao@qwen-code /triage 第二次重审(在其批准后触发)——head 与上次审查相同,本次对 c155dc94 做端到端复核。

模板完整 ✓

问题:已核实,非理论性问题——acpRouteTable.tsacpTransportUtils.ts 导入 isRecord(参数提取时运行时使用),而 acpTransportUtils.ts 又反向导入 ROUTE_TABLE。这是策略数据与其消费者之间真实存在的双向运行时循环。上次审查后 @wenshao 还在运行时层面证明了这一点(Node ESM loader hooks + 对实例化模块图跑 Tarjan:base 上 1 个强连通分量,本 PR 上为 0)。

方向:对齐——这与项目正在合并的同一循环清理系列一致:#9959(config client 循环)已于 2026-08-25 合并,#10039(auth flow client 循环)随后合并。SDK 内部导入治理无需 CHANGELOG 引用,该领域显然在范围内。

规模:packages/sdk-typescript/src/daemon/** 不属于 Stage 0 核心路径;1 个文件、13 行生产代码,无测试/生成代码行——远低于任何阈值。

方案:最小且正确——本地复制三行守卫,而不是为单次使用的辅助新建叶子模块,与 #10039DaemonHttpError.ts 中确立的先例一致。diff 中每一行都服务于既定目标,无顺手改动。

风险:无升级风险信号(未命中 Stage 1e 高风险路径)。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at c155dc94e5b6f18f052d7134a32987214b714726 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Code review

Re-verified at the unchanged head c155dc94. My independent proposal is unchanged: the sensible ways to break this cycle are (a) copy the guard locally into the route table, (b) extract a new leaf module both sides import, (c) move matching into the route table — and (a) remains the right pick: (b) adds a module for a single-use three-line helper (the abstraction AGENTS.md argues against), (c) is large churn. The codebase already chose (a) for the same situation in DaemonHttpError.ts.

Findings: no blockers. Re-verified against the worktree this pass:

  • Predicate equivalence: the local isRecord is byte-for-byte identical to the export in acpTransportUtils.ts — same value is Record<string, unknown> signature, same three conjuncts — so all four call sites in the route table (bodyRecord feeding most routes, session/new's body guard and _meta handling, artifacts/remove) behave exactly as before.
  • Cycle actually broken: after the change acpRouteTable.ts has zero imports; the only remaining runtime edge is acpTransportUtils.tsacpRouteTable.ts (consumer → policy data), the intended one-way direction.
  • No public surface change: ROUTE_TABLE, RouteEntry, RouteMapping are unchanged. The exported isRecord stays in acpTransportUtils.ts (its only importer was this route table; it keeps using the guard internally).
  • The why-comment (docs commit 76ef4dc2): states the invariant this PR exists to protect ("do not consolidate without breaking the cycle some other way first") — exactly the hidden-constraint comment AGENTS.md asks for.
  • Minor, not blocking: the PR body says "four-condition predicate"; the guard has three conditions.

New since the last pass — @wenshao's deep A/B verification (his report above, reproduced here as context, not as CI evidence): runtime cycle proof as described in Stage 1, a 43,641-case differential between base and PR route extractors with zero mismatches, four injected drift mutants all caught, and 554 real wire round trips per arm with byte-identical dumps. Two of his non-blocking notes are worth the author's attention:

  • Drift protection: measured that the focused suites kill only 1 of the 4 realistic predicate drifts — today the real guard against consolidation is the comment, not the tests.
  • A six-line pinning test (in his report, validated locally: passes on this PR, fails on all four mutants) would close that gap — e.g. asserting session/new's extractor returns {} for non-record bodies (arrays, null, primitives, functions, array-with-properties). Worth adding, though the maintainer has signalled it is not a merge condition.
  • Separately (not for this PR): the repo has no import/no-cycle gate, which is why the comment has to carry the invariant.

Test evidence

CI has fully landed on the reviewed head — everything completed is green, nothing pending, no failures (both pull_request workflow runs, Qwen Code CI and Security Checks, completed successfully). The skips in the table are repo policy, not a gap: the macOS/Windows unit jobs and CLI integration tests only run on merge_group/schedule/workflow_dispatch events, never on PR events. Fork PRs run the full CI profile, so the ubuntu Test job is the complete gate — lint, static analysis, build, and the workspace unit suite, which includes the four focused suites under packages/sdk-typescript/test/unit/ covering the route table, transport utils, and both transports. The PR body's "190 tests passed locally" remains the author's claim; the CI unit suite is the evidence.

Check Conclusion
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Secret scan (TruffleHog) ✅ success
Dependency CVE audit ✅ success
precheck-pr / precheck ✅ success
Classify PR ✅ success
Test (macos-latest, Node 22.x) ⏭️ skipped (merge_group/schedule/dispatch only)
Test (windows-latest, Node 22.x) ⏭️ skipped (merge_group/schedule/dispatch only)
Integration Tests (CLI, No Sandbox) ⏭️ skipped (merge_group only)

Real-scenario testing: N/A — unattended run, and this is a pure import-direction refactor with no user-visible surface. The behavioural claim ("route behavior unchanged") is a structural one, already settled by CI plus the maintainer's A/B verification above.

中文说明

代码审查

在未变化的 head c155dc94 上复核。我的独立方案不变:打破该循环的合理方式有 (a) 在路由表本地复制守卫、(b) 抽出双方都导入的新叶子模块、(c) 把匹配逻辑移入路由表——(a) 仍是正确选择:(b) 为单次使用的三行辅助新增模块(AGENTS.md 反对此类抽象),(c) 改动面过大。代码库已在 DaemonHttpError.ts 对同样情形选择了 (a)。

结论:无阻塞问题。本次已对照 worktree 重新核实:

  • 判断等价性:本地 isRecordacpTransportUtils.ts 的导出版本逐字一致——签名相同、三个条件相同——因此路由表中全部四个调用点(供大多数路由使用的 bodyRecordsession/new 的 body 守卫与 _meta 处理、artifacts/remove)行为完全不变。
  • 循环确实被打破:改动后 acpRouteTable.ts 零导入;仅存的运行时依赖边为 acpTransportUtils.tsacpRouteTable.ts(消费者 → 策略数据),即预期的单向依赖。
  • 公共面未变ROUTE_TABLERouteEntryRouteMapping 不变。acpTransportUtils.ts 继续导出 isRecord(其唯一导入方就是本路由表;该模块内部仍在使用此守卫)。
  • 说明注释(docs 提交 76ef4dc2):写明了本 PR 要保护的不变量("未以其他方式打破循环前不要合并此守卫")——正是 AGENTS.md 要求的隐藏约束类注释。
  • 小问题,不阻塞:PR 正文称"四条件判断",实际为三个条件。

上次审查后的新情况——@wenshao 的深度 A/B 验证(见其上方报告,此处仅作背景转述,不作为 CI 证据):如上所述的运行时循环证明、base 与 PR 路由提取器之间 43,641 例差分对比零不一致、注入的四个漂移变体全部被捕获、每个 arm 554 次真实 wire 往返且转储逐字节一致。其中两条非阻塞意见值得作者关注:

  • 漂移防护:实测聚焦套件只能杀死四个现实谓词漂移中的一个——当前防止守卫被合并的真正护栏是注释,而不是测试。
  • 他报告中给出的六行钉桩测试(已本地验证:本 PR 通过、四个变体全部失败)可以补上这个缺口——例如断言 session/new 的提取器对非对象请求体(数组、null、原始值、函数、带属性的数组)返回 {}。值得补上,但维护者已表明这不是合入条件。
  • 另(不属于本 PR):仓库没有 import/no-cycle 门禁,因此该不变量只能由注释承载。

测试证据

CI 已在被审查的 head 上全部落定——已完成检查全绿,无进行中项、无失败(两个 pull_request 工作流 Qwen Code CISecurity Checks 均已成功完成)。表格中的 skipped 项是仓库策略而非缺口:macOS/Windows 单测与 CLI 集成测试仅在 merge_group/schedule/workflow_dispatch 事件运行,从不在 PR 事件运行。fork PR 执行完整 CI profile,因此 ubuntu Test 任务即完整门禁——lint、静态分析、构建与全 workspace 单测套件,其中包含覆盖路由表、传输工具与两种传输的四个聚焦套件。PR 正文中"本地 190 项测试通过"仍是作者的陈述;CI 单测套件才是证据。

真实场景测试:N/A——无人值守运行,且本改动为纯导入方向重构,无用户可见面。"路由行为不变"这一声明属结构性声明,已由 CI 与上述维护者 A/B 验证共同钉住。

Qwen Code · qwen3.8-max

Reviewed at c155dc94e5b6f18f052d7134a32987214b714726 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review, fully green CI on the reviewed head, and a maintainer approval already standing on this exact commit; capped at 3 by the fork-refactor guardrail, which keeps the bot's own approval off the table. The cap is pure policy, not doubt.

What changed since the last pass is not the code — it's the evidence around it. @wenshao ran a full A/B verification at this head (runtime cycle proof, 43k-case extractor differential, byte-identical wire dumps, four drift mutants caught), concluded merge-ready, and approved. That is exactly the human sign-off the fork-refactor guardrail exists to require; the review itself found nothing to add — the approach matches my independent proposal and the DaemonHttpError.ts precedent, the predicate is byte-identical, the diff carries nothing drive-by, and the why-comment pins the one real risk of this pattern.

The two remaining notes from his report (the focused suites only catch 1 of 4 predicate drifts; a six-line pinning test would close that) are worth the author picking up, but the maintainer has already signalled they are not merge conditions.

So the only thing left is procedural: this is a fork-authored refactor, which the bot never auto-approves regardless of how clean the review is. My approval is withheld by policy; main requires two approvals, @wenshao's stands on this commit, and one more human approval is what remains.

中文说明

置信度:3/5 —— 审查干净、被审查 head 上 CI 全绿,且维护者的批准已落在同一 commit 上;因 fork 重构护栏封顶为 3 分,机器人自身不予批准。封顶纯属策略,并非存在疑虑。

上次审查以来变化的不是代码,而是围绕它的证据。@wenshao 在该 head 上完成了完整 A/B 验证(运行时循环证明、4.3 万例提取器差分、逐字节一致的 wire 转储、四个漂移变体全部捕获),结论为可合入,并已批准。这正是 fork 重构护栏所要求的人工签字;审查本身没有新发现——方案与我的独立方案及 DaemonHttpError.ts 先例一致,谓词逐字一致,diff 无顺手改动,说明注释钉住了该模式唯一真实的风险。

他报告中剩下的两条意见(聚焦套件只能捕获四个谓词漂移中的一个;六行钉桩测试即可补上)值得作者跟进,但维护者已表明这些不是合入条件。

因此唯一剩余的是流程问题:这是 fork 提交的重构,无论审查多干净,机器人从不自动批准。按策略我不予批准;main 需要两个批准,@wenshao 的批准已在该 commit 上,还差一个人工批准。

Qwen Code · qwen3.8-max

Reviewed at c155dc94e5b6f18f052d7134a32987214b714726 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

⏸️ Deferring to @wenshao — fork refactor PRs are never auto-approved by the bot, so my approval stays withheld by policy even though this re-run found nothing new to flag. Your approval already stands on c155dc94 (after your A/B verification report), the review is clean, and CI is fully green on the reviewed head — the skips are by design, those jobs never run on PR events. main requires two approvals, so what remains is one more human approval. Your two non-blocking notes (drift-pinning test, three-condition wording) are relayed to the author in the Stage 2 comment.

中文说明

⏸️ 转交 @wenshao —— fork 提交的重构 PR 从不被机器人自动批准,因此即使本次复审没有新问题,按策略我仍不予批准。您的批准已在 c155dc94 上(附您的 A/B 验证报告),审查干净,CI 已在被审查的 head 上全绿——skipped 项属预期设计,这些任务从不在 PR 事件运行。main 需要两个批准,目前还差一个人工批准。您的两条非阻塞意见(漂移钉桩测试、"三条件"表述)已在 Stage 2 评论中转达给作者。

Qwen Code · qwen3.8-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: reverse audit — stopped before round 1 by the review time budget.

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:反向审计——评审时间预算不足,未能开始第 1 轮。

— qwen3.8-max via Qwen Code /review (v0.22.0)

Comment thread packages/sdk-typescript/src/daemon/acpRouteTable.ts
acpTransportUtils imports ROUTE_TABLE from this module; re-importing
its byte-identical isRecord would silently restore the runtime cycle
this change breaks. Mirrors the house-convention comment in
DaemonHttpError.ts so a dedupe refactor doesn't undo the fix.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not reviewed: reverse audit — stopped before round 1 by the review time budget.

Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:

  • packages/sdk-typescript/src/daemon/acpRouteTable.ts:24 — [probe] edge-body branches of the local isRecord copy are not pinned by tests (mutation survivors)
中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:反向审计——评审时间预算不足,未能开始第 1 轮。

收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。

— qwen3.8-max via Qwen Code /review (v0.22.0)

@DragonnZhang

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

@github-actions

Copy link
Copy Markdown
Contributor

Qwen Code review request accepted. Review is queued in workflow run.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max via Qwen Code /review (v0.22.2)

@DragonnZhang
DragonnZhang enabled auto-merge August 29, 2026 03:43
@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 23 passed · 0 failed · 23 total

Flakiness gate: not applicable — no runnable changed test files (0 out-of-scope file(s) noted in the log)

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:23 通过 · 0 失败 · 23 总计

抖动门:不适用 — no runnable changed test files (0 out-of-scope file(s) noted in the log)

Verification report

PR #10040 — refactor(sdk): break ACP route table cycle

Verdict: merge-ready — 23/23 scripted assertions passed, 0 unexpected failures. Verified head c155dc94e5b6f18f052d7134a32987214b714726 (merge-ref base a268b7d175). All four evidence captures in evidence/, raw logs in logs/, rerunnable harnesses in harness/.

中文摘要
  • 结论merge-ready。23/23 脚本化断言通过,0 个意外失败。
  • A/B 结论:中心主张(运行时循环被切断)由 import-graph 探针证明:head 下 acpRouteTable.js 单独加载,不再拉入 acpTransportUtils.js;base 对照组显示该边确实存在(探针灵敏度得到验证);反向边 acpTransportUtils → acpRouteTable 两侧均保留。行为等价 A/B:28 条路由 × 11 种 body(对象/数组/字符串/数字/布尔/null/undefined)= 308 个单元,head 与 base 逐单元一致(0 差异),两种模块入口顺序均无关。
  • Findings:无阻塞项。1 条建议级完整性记录:isRecord 的 Array 分支未被现有测试钉住(删除 !Array.isArray(value) 后原 190 个测试仍全绿)——此为 PR 之前就存在的覆盖缺口,PR 逐字复制谓词未引入新风险,作者已在风险栏披露漂移取舍;提供了会转红的 canary fixture。
  • 未覆盖范围:浅克隆下逐 commit 归因(仅聚合 diff 可验证);真实 loopback HTTP/WS daemon E2E(由聚焦传输单测 + 直接 matcher 驱动覆盖);esbuild 打包产物内的循环不可观测(探针针对逐模块 tsc 产物);base 臂的 M2 突变运行(以 fixture 普查 + 谓词逐字节相同论证)。

Central claim + A/B

Central claim: the runtime cycle acpRouteTable → acpTransportUtils → acpRouteTable is removed by keeping a local isRecord in the route table, leaving only the intended acpTransportUtils → acpRouteTable edge, with route behavior unchanged.

Both arms are the two modules compiled per-module with the package's own tsconfig.json (noEmitOnError: true, both exit 0) into emit/head and emit/base (base = scratch worktree at HEAD^1). The edge oracle is a Node module-load hook recording every file:// module each entry pulls in; the behavior oracle drives matchRoute + extractParams over a 308-cell corpus (28 representative session/workspace/file/auth routes × 11 body shapes incl. object, array, string, number, boolean, null, undefined) and diffs serialized {method, params} per cell.

A/B #1 — runtime import edges (01-import-graph-ab-edge-matrix.png, logs/01-import-graph-ab.txt): 8/8.

cell (arm / entry) loaded modules assertion
head / acpRouteTable.js acpRouteTable.js only edge removed ✔
head / acpTransportUtils.js both reverse edge intact ✔
base / acpRouteTable.js both positive control: probe sees an edge when one exists ✔
base / acpTransportUtils.js both

The base control is what makes the head "absence" meaningful: the same probe that reports one module on head reports two on base. Both base entry orders also load cleanly, confirming the PR's own statement that the cycle compiled and tested green (behaviorally inert, structurally wrong).

A/B #2 — behavior parity (02-behavior-parity-308-cells.png, logs/02-behavior-ab.txt): 5/5.

comparison differing cells
head vs base, same entry order 0/308
head utils-first vs table-first 0/308
base utils-first vs table-first 0/308

297/308 cells matched a route (11 are the intended NO_MATCH negative); both arms agree on every cell, including the POST /session _meta/sessionId rewrite, DELETE …/artifacts/:id clientId conditional, and the query-coercion routes (?maxBytes=, ?detail=, ?size= empty-value-absent semantics).

Predicate identity: the local guard is byte-identical to the exported one (typeof value === 'object' && value !== null && !Array.isArray(value)), verified by diff of the extracted definitions; static grep confirms head's acpRouteTable.ts has zero import statements while base's line 15 imports isRecord from acpTransportUtils.js.

Reviewer Test Plan walkthrough: step 1 verified by A/B #2 (object and non-object bodies across session/workspace/file/auth routes, identical JSON-RPC methods+params); step 2 verified by the focused transport suites (below) plus the matcher harness both transports share; step 3 verified by A/B #1 (transport utils loads the route table; route table loads nothing).

Mutation matrix (drift detection on the PR's declared tradeoff)

Run in a scratch worktree via harness/mutation-matrix.sh (03-mutation-matrix-drift-detection.png, logs/03-mutation-matrix.txt): 4/4 expectations met.

row mutation to local isRecord focused suites (4 files) result
M0 none (control) 191/191 green control holds
M1 return true 3 failed | 188 passed killed — both failures in the mutated file: POST /session with non-record body returns empty params and DELETE /session/:id/artifacts/:artifactId …
M2 drop !Array.isArray(value) 1 failed | 190 passed survivor — only the canary array-body fixture fails; the original 190 stay green
M3 restore 191/191 green recovery control holds

M1 is the positive control proving the command collects tests that exercise the mutated file. M2's survival is a pre-existing coverage gap, not one the PR introduced: the base arm ships the identical predicate against the identical 190-test suite, and a fixture census shows no test on either arm feeds an array body to an extractor. Classification per method: coverage gap (behavior correct, nothing asserts the Array axis), reported as completeness, not a merge condition. The canary fixture (POST /session/:id/prompt with body [1,2], expecting {sessionId:'s1'}) goes red under M2 and green on restored head — it is the test the suite is missing if a maintainer wants the axis pinned.

Findings

S1 (Suggestion, completeness, non-blocking) — The isRecord Array clause is unpinned by the focused suites on both arms (M2 survivor above). Pre-existing; the PR copies the predicate verbatim and discloses the drift tradeoff in its Risk section, so it inherits rather than creates the gap. If the author wants to close it, the one-case fixture from the matrix section is the pin. Not a merge condition.

No blocking findings. The PR description's claims were all confirmed as stated (byte-identical predicate, single remaining edge, 190 focused tests, unchanged behavior); no correction needed.

Not covered

  • Per-commit attribution: the checkout is depth 2 (merge commit, base tip, PR head only); the snapshot lists 6 commits. The aggregate HEAD^1..HEAD diff is what was verified.
  • Live loopback HTTP/WS daemon E2E: transport behavior is covered by the focused unit suites (which exercise matchRoute through the transports) and the direct matcher harness; I did not boot a real daemon.
  • esbuild bundle: dist/daemon/index.js inlines both modules, so the cycle is unobservable there by construction; the probe targets the per-module compiled graph, which is what source-level consumers (vitest, tsx, tsc project builds) see.
  • M2 mutation on the base arm: argued from the fixture census (identical suite, no array-body case) plus byte-identical predicate rather than a separate run.
  • Repo-wide suites / integration lanes: left to CI; only the affected workspace's focused suites and typecheck were run here.

Methodology

Environment: node:22-bookworm CI container, refs/pull/10040/merge at depth 2; npm ci + npm run build pre-ran at head. A/B arms: per-module tsc emit of acpRouteTable.ts + acpTransportUtils.ts using the package's own tsconfig (head from the main tree, base from a scratch git worktree at HEAD^1, removed afterwards); no workspace-internal imports exist in these modules, so root node_modules reuse is a clean control (realpath-irrelevant by construction — verified by grep that neither module imports any @qwen-code/* package). The import-graph probe registers a Node ESM load hook over a MessageChannel and records every file:// module each entry loads in a fresh process (4 cells). The behavior harness imports matchRoute from the compiled output and drives 308 corpus cells per arm × entry order, comparing sorted-key JSON per cell. Mutations ran only in a third scratch worktree. Raw per-cell logs in logs/, captures in evidence/, all harnesses rerunnable from harness/.

Flakiness gate log


verdict: n/a
summary: no runnable changed test files (0 out-of-scope file(s) noted in the log)

Evidence images

01-import-graph-ab-edge-matrix

02-behavior-parity-308-cells

03-mutation-matrix-drift-detection

04-focused-gate-190-on-head

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot's only review on c155dc94e5b6f18f052d7134a32987214b714726 is a COMMENTED one, which carries no vote — so it has no verdict of its own on this commit, and main needs two approving reviews: an approval left by another account is a separate vote and does not count as the bot's own. Two different things look like this, and the stage-3 comment above says which: the triage skill deferring on purpose at 3/5 — a fork refactor hitting the approval guardrail, or a core change escalated for maintainer awareness, both normal outcomes — or an earlier approval that a push dismissed, leaving only the comment behind, which needs a fresh review.

⚠️ 机器人在 c155dc94e5b6f18f052d7134a32987214b714726 上唯一的评审是 COMMENTED不带票 —— 因此它在该 commit 上没有自己的裁决,而 main 需要两个批准(其他账号的批准是另一张票)。有两种情况长这样,上方的 stage-3 评论会说明是哪一种:triage skill 在 3/5 时有意 defer(fork refactor 命中审批护栏,或核心改动被升级交由维护者把关,两者都是正常结果);或者更早的批准被一次推送作废、只剩下这条评论,此时需要重新评审。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Local verification report

I rebuilt this change locally and put it through a real A/B verification environment rather than re-reading the diff. Verdict: merge-ready. All three claims in the Reviewer Test Plan hold, behaviour is bit-for-bit identical, and every gate is green on both arms. Four non-blocking notes at the end.

Setup. Two worktrees at the PR head (c155dc9), differing by exactly the PR diff: the base arm is the same tree with acpRouteTable.ts restored from the merge-base b85d7c8. Verified with diff -rq that no other file differs. Linux, Node v22.22.2. Every number below was produced on both arms.


1 · The cycle is real, and it is really gone

Rather than grepping imports, I registered Node's own ESM resolve/load hooks and recorded the graph as the runtime instantiates it, then ran Tarjan over it.

runtime module graph, both arms

  • base: acpRouteTable → acpTransportUtils true, reverse true, 1 cyclic component.
  • PR: forward edge false, reverse edge true, 0 cyclic components — the one-way dependency the PR describes, confirmed at runtime.
  • Whole-package scan of all 68 compiled modules: 2 cycles → 1. The PR removes exactly the cycle it claims and nothing else.

2 · What the removal actually buys

The comment in the diff is right that the base cycle is inert today — I confirmed it (CONTROL row: both arms load cleanly from every entry point). So I measured what it buys by applying two ordinary, behaviour-preserving future edits to a copy of each arm's compiled output and loading each module in a fresh process.

TDZ fragility experiment

  • Edit A (precompute a module-level Set from ROUTE_TABLE) throws ReferenceError: Cannot access 'ROUTE_TABLE' before initialization on base — but only when acpRouteTable is the entry module. That is the nasty shape of an ESM cycle: it is import-order dependent, so it can pass every test and still break under a different bundler or entry point.
  • Edit B (converting the exported function isRecord to a const arrow — a routine lint/style change) breaks base on 2 of 3 entry points, including the real AcpHttpTransport entry.
  • On the PR arm both edits are safe, and Edit B is impossible by construction.

This is the concrete justification for the change, and it is stronger than "tidier imports".

3 · Behaviour is bit-for-bit unchanged — and the oracle can tell

I loaded both arms' real compiled ROUTE_TABLE and matchRoute into one process and fed them an identical hostile corpus, comparing with a serializer that survives NaN/undefined/bigint/symbol/Proxy/null-prototype.

differential + negative control

43,641 cases, 0 mismatches (41,832 extractParams × every route × 36 body shapes × 7 query shapes × 3 segment shapes; 225 matchRoute; 1,584 composed). Route shape — httpMethod, pattern, JSON-RPC method, notification flag — identical for all 82 routes.

To prove that is not green-on-green, I injected the exact drift the PR names as its main risk, as four mutants of the copied predicate. All four are caught by the differential and by the wire E2E below.

4 · Real transports on a real wire, and every gate

A real node:http + SSE ACP server and a real ws WebSocket server, driven by the arm's actual AcpHttpTransport / AcpWsTransport classes, with one concrete URL per route derived from that route's own regex.

wire E2E and gate sweep

554 real round trips per arm (277 requests × 2 transports), 550 JSON-RPC frames on the wire, 80 distinct methods, notifications correctly not awaited. The two arms' full wire dumps are byte-identical (md5 0b1f0eb0…).

Gates, both arms: focused suites 190/190 (matches your number exactly), full SDK suite 1667/1667, eslint --max-warnings 0 exit 0, all three tsc projects exit 0, npm run build exit 0. Public .d.ts surface byte-identical across all four entry points.


Non-blocking notes

N1 — the risk mitigation in the description doesn't hold. The PR names predicate drift as its main risk and offers "the complete route and transport suites pass" as the mitigation. Measured: the repo's own 190-test focused suite kills only 1 of the 4 realistic drifts (see the last column of image 3). !Array.isArray-dropped, null-guard-dropped, and functions-as-records all survive 190/190 green. So the real guard today is the code comment, not the tests. I'm not asking you to change the code — just to not rely on the suites for this.

N2 — a six-line test would close it. Validated locally: this passes on the PR as submitted and fails on all four mutants.

it('session/new ignores every non-record body (pins the local isRecord)', () => {
  const route = ROUTE_TABLE.find((r) => r.mapping.method === 'session/new')!;
  for (const body of [[], [1], null, undefined, 'str', 42, () => {}, Object.assign(['x'], { a: 1 })]) {
    expect(route.mapping.extractParams([], body, 'POST')).toEqual({});
  }
});

N3 — nothing stops the cycle coming back. There is no import/no-cycle rule or equivalent CI gate anywhere in the repo, which is exactly why the diff has to carry a "do not consolidate" comment. eslint-plugin-import is already in node_modules, so a deterministic guard is cheap — though it would also flag the pre-existing DaemonAuthFlow ⇄ DaemonClient cycle, so it's a separate PR, not this one.

N4 — small factual nits. The description says "four-condition predicate"; it's three (typeof === 'object', !== null, !Array.isArray). And dist/daemon/transports.js grows +68 B from the duplicated predicate — worth a line in the description since that bundle has a byte budget, though at 40,097 / 49,152 B there's 9,055 B of headroom. dist/daemon/index.js and dist/index.mjs are byte-identical.

中文版本

本地验证报告

我在本地重建了这个改动,用真实的 A/B 验证环境跑了一遍,而不是只重读 diff。结论:可以合入。 Reviewer 测试计划里的三条声明全部成立,行为逐字节一致,两个 arm 上所有门禁全绿。文末有四条非阻塞意见。

环境。 两个 worktree 都在 PR head(c155dc9),二者差异恰好是本 PR 的 diff:base arm 是同一棵树、把 acpRouteTable.ts 还原到 merge-base b85d7c8 的版本。用 diff -rq 确认没有第二个文件不同。Linux,Node v22.22.2。下面每个数字都在两个 arm 上各跑了一遍。


1 · 环真的存在,也真的消失了

我没有用 grep 看 import,而是注册了 Node 自己的 ESM resolve/load hook,记录运行时实际实例化出来的模块图,再跑 Tarjan。

  • base:acpRouteTable → acpTransportUtils true,反向 true,1 个环。
  • PR:正向边 false,反向边 true0 个环——PR 描述的单向依赖,在运行时层面得到确认。
  • 对全部 68 个编译产物模块做整包扫描:2 个环 → 1 个。PR 精确地只移除了它声称的那个环。

2 · 移除这个环到底换来了什么

diff 里的注释说得对:这个环目前是无害的,我也确认了(CONTROL 行:两个 arm 从任意入口都能正常加载)。所以我改为测量它换来了什么——对每个 arm 编译产物的副本施加两处普通的、不改变行为的未来修改,然后在全新进程里加载每个模块。

  • 改动 A(在模块顶层用 ROUTE_TABLE 预计算一个 Set)在 base 上抛 ReferenceError: Cannot access 'ROUTE_TABLE' before initialization——但只在 acpRouteTable 作为入口模块时。这正是 ESM 环最难缠的形态:它依赖 import 顺序,所以能通过全部测试,却在换一个打包器或入口时挂掉。
  • 改动 B(把导出的 function isRecord 改成 const 箭头函数——一个很常规的 lint/风格改动)在 base 上 3 个入口挂 2 个,其中包括真实的 AcpHttpTransport 入口。
  • 在 PR arm 上两处改动都安全,而且改动 B 在结构上就不可能发生。

这是这次改动的具体正当性,比"import 更整洁"有力得多。

3 · 行为逐字节不变——而且这个 oracle 分辨得出来

我把两个 arm 各自真实编译出来的 ROUTE_TABLEmatchRoute 加载进同一个进程,喂同一份恶意语料,用一个能承受 NaN/undefined/bigint/symbol/Proxy/null 原型的序列化器做比对。

43,641 个用例,0 处不一致(41,832 个 extractParams=每条路由 × 36 种 body 形态 × 7 种 query 形态 × 3 种 segment 形态;225 个 matchRoute;1,584 个组合用例)。全部 82 条路由的形状——httpMethod、pattern、JSON-RPC method、notification 标志——完全一致。

为了证明这不是"绿对绿",我把 PR 自己点名的主要风险(守卫漂移)注入成四个变异体。四个全部被差分和下面的线级 E2E 抓到。

4 · 真实传输、真实网络,以及全部门禁

一个真的 node:http + SSE ACP 服务器和一个真的 ws WebSocket 服务器,由该 arm 真实的 AcpHttpTransport / AcpWsTransport 类驱动,每条路由的 URL 都由这条路由自己的正则反推出来。

每个 arm 554 次真实往返(277 个请求 × 2 种传输),线上 550 个 JSON-RPC 帧,80 个不同的方法,notification 正确地没有被等待。两个 arm 的完整线上 dump 逐字节相同md5 0b1f0eb0…)。

两个 arm 的门禁:聚焦测试 190/190(与你给的数字完全吻合),SDK 全量测试 1667/1667,eslint --max-warnings 0 exit 0,三个 tsc 工程全部 exit 0,npm run build exit 0。四个入口的公开 .d.ts 表面逐字节相同。


非阻塞意见

N1 — 描述里的风险缓解措施其实不成立。 PR 把守卫漂移列为主要风险,缓解措施写的是"完整的路由与传输测试全部通过"。实测:仓库自己的 190 项聚焦测试只能杀掉 4 个中的 1 个现实漂移(见第 3 张图最后一列)。去掉 !Array.isArray、去掉 null 判断、把函数当作 record,这三种都能在 190/190 全绿下存活。所以今天真正的守卫是那段注释,不是测试。我不要求改代码,只是希望不要把这件事托付给测试套件。

N2 — 六行测试就能补上。 本地已验证:它在当前 PR 上通过,在四个变异体上全部失败。

it('session/new ignores every non-record body (pins the local isRecord)', () => {
  const route = ROUTE_TABLE.find((r) => r.mapping.method === 'session/new')!;
  for (const body of [[], [1], null, undefined, 'str', 42, () => {}, Object.assign(['x'], { a: 1 })]) {
    expect(route.mapping.extractParams([], body, 'POST')).toEqual({});
  }
});

N3 — 没有任何机制阻止这个环回来。 整个仓库没有 import/no-cycle 规则或等价的 CI 门禁,这正是 diff 里不得不写一段"不要合并"注释的原因。eslint-plugin-import 已经在 node_modules 里,所以加一道确定性门禁成本很低——不过它同时会报出既有的 DaemonAuthFlow ⇄ DaemonClient 环,所以那是另一个 PR,不是这个。

N4 — 两处小事实。 描述写的是"四条件判断",实际是三条(typeof === 'object'!== null!Array.isArray)。另外 dist/daemon/transports.js 因为重复的判断增长了 +68 B——这个 bundle 有字节预算,值得在描述里写一句,虽然 40,097 / 49,152 B 还有 9,055 B 余量。dist/daemon/index.jsdist/index.mjs 逐字节不变。


🤖 Generated with Claude Code — Claude Opus 5 (1M context)

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 47 passed · 0 failed · 47 total

Flakiness gate: not applicable — no runnable changed test files (0 out-of-scope file(s) noted in the log)

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:47 通过 · 0 失败 · 47 总计

抖动门:不适用 — no runnable changed test files (0 out-of-scope file(s) noted in the log)

Verification report

<!-- qwen-triage:verify -->
<!-- qwen-triage:verify-substantive -->

Sandboxed verification (follow-up round): ✅ passed — merge-ready (agent verdict) — verified head c155dc94e5b6f18f052d7134a32987214b714726 (merge-ref base a268b7d1754bf7856fa74ce464a7dce5a59dff41). 47/47 scripted assertions passed, 0 unexpected failures.

中文 — 判定:✅ 通过 · 可合入(agent 判定,跟进轮)
  • 结论merge-ready。47/47 脚本化断言通过,0 个意外失败。本轮为跟进轮:上一轮验证的 head(c155dc94e5)与 merge-ref base(a268b7d175)与本轮逐字节相同,即整个输入闭包(源码、测试、lockfile、配置)未变;按规则仍全部重新执行测量,未沿用旧数字。
  • A/B 结论:中心主张(运行时循环被切断)由 import-graph 探针重新证明:head 下 acpRouteTable.js 单独加载;base 对照组仍显示该边(探针灵敏度验证);反向边两侧保留。行为等价 A/B 扩展至 96 场景 × 11 种 body = 1056 单元/臂,head 与 base 逐单元 0 差异,两种模块加载顺序均无关。
  • Findings:无新发现。上轮 S1(isRecord 的 Array 分支未被钉住)状态为 stands:本轮在 head 与 base 两臂实际运行 M2 突变,均为「仅 canary 转红、原 190 全绿」——以执行(而非普查论证)证明该缺口为 PR 之前就存在;非阻塞。
  • 未覆盖范围:浅克隆逐 commit 归因;真实 loopback HTTP/WS daemon E2E;esbuild 打包产物内的循环(构造上不可观测);整仓测试与集成 lane(交由 CI)。
Verification report

PR #10040 (follow-up round) — refactor(sdk): break ACP route table cycle

Verdict: merge-ready — 47/47 scripted assertions passed, 0 unexpected failures. Verified head c155dc94e5b6f18f052d7134a32987214b714726; merge-ref base a268b7d1754bf7856fa74ce464a7dce5a59dff41. All evidence captures in evidence/, raw logs in logs/, rerunnable harnesses in harness/.

中文摘要
  • 判定merge-ready,47/47 脚本化断言通过。
  • 输入闭包比较:本轮 HEAD^2c155dc94e5…)与 HEAD^1a268b7d175…)与上一轮报告中引用的 verified head / merge-ref base 完全相同,git diff HEAD^1..HEAD 仍为单文件 11+/2−;lockfile、tsconfig、vitest 配置、四个聚焦测试文件均在该 diff 之外且两提交间未变。旧报告的所有测量仍全部重新执行。
  • A/B:import-graph 4/4 单元(head 单边消失、base 正对照可见);行为等价 1056 单元/臂 × 4 运行 0 差异;NO_MATCH 普查一致(44/运行)。
  • 突变矩阵:head 14/14、base 7/7 行断言。M1(return true)在突变文件自身套件内被杀(正对照同文件);M2(删 Array 分支)head 与 base 均仅 canary 转红 → 覆盖缺口为预存,S1 stands。
  • 门禁:聚焦 4 套件 190/190(逐套件计数 104/3/34/49 与上轮一致);sdk typecheck 0;typecheck 活性以植入类型错误证明(tsc exit 2 并点名该文件)。
  • Findings:无新发现;S1 stands(非阻塞)。

Previous-finding status (follow-up round)

# finding (round 1) severity status at new head notes
S1 isRecord Array clause unpinned by the focused suites (M2 survivor) Suggestion (completeness) stands Re-measured by execution on both arms this round: head M2 = 1 failed | 190 passed with only the canary red, and — new this round — base M2 (mutating the exported predicate in acpTransportUtils.ts) = identical 1 failed | 190 passed. The gap is pre-existing, confirmed by run rather than by the fixture census the first round used. The canary fixture (this round: harness/canary.test.ts, copied into scratch worktrees as zz-canary-array-body.test.ts) is still the pin if a maintainer wants it. Non-blocking; I agree with the round-1 classification.

The new head is byte-identical to the old one (HEAD^2 = c155dc94e5… in both rounds; base a268b7d175… in both rounds), so "stands" here is a re-measurement at the same commits, not a diff of the old report: every harness was rebuilt and re-executed this round (fresh tsc emits, fresh vitest runs, fresh probe processes).

Central claim + A/B

Central claim: the runtime cycle acpRouteTable → acpTransportUtils → acpRouteTable is removed by keeping a local isRecord in the route table, leaving only the intended acpTransportUtils → acpRouteTable edge, with route behavior unchanged.

Arms: per-module tsc emit of the two modules using the package's own tsconfig.json (noEmitOnError: true, both exit 0) into emit/head and emit/base (base = scratch worktree at HEAD^1, removed afterwards). Neither module imports any @qwen-code/* package (grep-verified), so root node_modules reuse is a clean control by construction.

A/B #1 — runtime import edges (01-import-graph-ab-edge-matrix.png, logs/01-import-graph-ab.txt): 4/4.

cell (arm / entry) loaded modules assertion
head / acpRouteTable.js acpRouteTable.js only edge removed ✔
head / acpTransportUtils.js both reverse edge intact ✔
base / acpRouteTable.js both positive control: probe sees the edge when it exists ✔
base / acpTransportUtils.js both

Static corroboration (logs/00-static-checks.txt): head's acpRouteTable.ts has zero import statements; base's imports isRecord from ./acpTransportUtils.js; the local predicate body is byte-identical to the exported one; and the house already keeps a third local copy in AcpEventDenormalizer.ts (precedent for the pattern).

A/B #2 — behavior parity (02-behavior-parity-1056-cells.png, logs/02-behavior-ab.txt): 9/9 assertions over 1,056 cells per run (96 scenarios — session, workspace, file, auth routes plus 4 NO_MATCH negatives — × 11 body shapes: full record, empty object, two array shapes, string, number, boolean, null, undefined, clientId-only record, non-record _meta).

comparison differing cells
head vs base, same entry order 0/1056
head table-first vs utils-first 0/1056
base table-first vs utils-first 0/1056
NO_MATCH census 44 on all four runs

Spot oracles asserted on head: POST /session _meta/sessionId rewrite with sessionScope stripped; non-record _meta starts fresh; DELETE …/artifacts/:id forwards clientId only when a string; ?maxBytes= empty-value-absent vs coerced number; array body yields {sessionId} only.

Reviewer Test Plan walkthrough: step 1 = A/B #2 (object and non-object bodies across session/workspace/file/auth routes, identical JSON-RPC methods + params); step 2 = the focused transport suites (49 + 34 tests green, 04-focused-gate-head-190.png) plus the shared-matcher harness both transports import from acpTransportUtils; step 3 = A/B #1 (utils loads the table; the table loads nothing).

Mutation matrix (drift detection on the PR's declared tradeoff)

harness/matrix.mjs in scratch worktrees; canary fixture = POST /session/s1/prompt with body [1,2] expecting {sessionId:'s1'}. 03-mutation-matrix-both-arms.png, logs/03-mutation-matrix-{head,base}.txt: head 14/14, base 7/7 row assertions.

row mutation arm result
M0 none (control + canary) head 191/191 green
M1 return true head killed: 3 failed | 188 passed; failures in the mutated file's own suite (acpRouteTable.test.ts) + canary — positive control in the same file as the mutant
M2 drop !Array.isArray(value) head survivor: 1 failed | 190 passed, canary-only red
M3 restore head 191/191 green
M0 none base 191/191 green
M2 drop !Array.isArray(value) (exported predicate) base same survivor: 1 failed | 190 passed, canary-only red

The base-arm M2 is new this round and upgrades the round-1 census argument to a run: the shipped 190-test suite pins the Array axis on neither arm. Classification unchanged: coverage gap (behavior correct, nothing asserts it), pre-existing, completeness reporting — not a merge condition.

Findings

No new findings. No corrections needed — the PR description's claims (byte-identical predicate, single remaining edge, 190 focused tests, unchanged behavior) all re-confirmed as stated.

S1 (carried from round 1, Suggestion, non-blocking) — the isRecord Array clause remains unpinned by the shipped suites on both arms (M2 rows above). The PR copies the predicate verbatim and discloses the drift tradeoff in its Risk section, so it inherits rather than creates the gap. The canary fixture in harness/canary.test.ts is the one-case pin.

Informational, not a finding: the predicate now exists in three places (utils export, route-table local, AcpEventDenormalizer local). The third is pre-existing house style; the PR's comment is the guard against a dedupe refactor restoring the cycle.

Not covered

  • Per-commit attribution: checkout is depth 2; git rev-list HEAD^1..HEAD^2 returns 1 commit locally while the snapshot lists 6 — shallow-boundary artifact. Aggregate HEAD^1..HEAD diff is what was verified.
  • Live loopback HTTP/WS daemon E2E: transport behavior covered by the focused unit suites (which drive matchRoute through both transports) and the direct matcher harness; no real daemon booted.
  • esbuild bundle: dist bundles inline both modules, so the cycle is unobservable there by construction; the probe targets the per-module compiled graph that source-level consumers (vitest, tsx, tsc project builds) see.
  • Repo-wide suites / integration lanes: left to CI; only the affected workspace's focused suites and typecheck ran here.
  • Snapshot baseRefOid drift: the metadata snapshot's baseRefOid (b85d7c81…) is newer than the merge-ref base (a268b7d175…); per the merge-ref contract HEAD^1 is authoritative and is what both rounds used.

Methodology

Environment: node:22-bookworm CI container, refs/pull/10040/merge at depth 2; npm ci + npm run build pre-ran at head. A/B arms: per-module tsc emit of acpRouteTable.ts + acpTransportUtils.ts with the package's own tsconfig (head from the main tree, base from a scratch worktree at HEAD^1); the import-graph probe registers a Node ESM resolve hook recording every file:// module each entry pulls in a fresh process (4 cells). The parity harness imports the compiled matchRoute and drives 1,056 corpus cells per arm × two module-load orders, comparing sorted-key JSON per cell. Mutations and the typecheck liveness plant ran only in scratch worktrees, restored via git checkout and removed afterwards (worktree list shows only the main tree; main tree git status clean). Raw per-run logs in logs/, captures in evidence/, all harnesses rerunnable from harness/.

Flakiness gate log

<pre><code>
verdict: n/a
summary: no runnable changed test files (0 out-of-scope file(s) noted in the log)
</code></pre>

Evidence images

01-import-graph-ab-edge-matrix

02-behavior-parity-1056-cells

03-mutation-matrix-both-arms

04-focused-gate-head-190

Qwen Code · sandboxed verification (follow-up round)

Flakiness gate log


verdict: n/a
summary: no runnable changed test files (0 out-of-scope file(s) noted in the log)

Evidence images

01-import-graph-ab-edge-matrix

02-behavior-parity-1056-cells

03-mutation-matrix-both-arms

04-focused-gate-head-190

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@DragonnZhang
DragonnZhang added this pull request to the merge queue Aug 29, 2026
Merged via the queue into QwenLM:main with commit 866b7fe Aug 29, 2026
75 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.23.0.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants