fix(serve): Bound ACP HTTP pre-attach buffers by bytes - #9007
Conversation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 4 scenario(s). — Qwen Code · serve A/B |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Re-run on six new commits (review rounds R2–R7) — gate re-checked at Template ✓ (complete, unchanged). Problem: real, and now evidenced rather than inferred. The count-only 256-frame pre-attach cap with silent eviction is a tracked item of the daemon memory workstream (#8091), established by code inspection and the in-repo design doc. Since the last pass, a maintainer drove a real-daemon A/B on an earlier head of this PR and observed the failure mode directly: base silently dropped 44 of 300 JSON-RPC replies (callers left waiting forever) and retained +613 MB RSS after 128 × ~1 MiB replies, while this PR's head failed closed at the documented cap and settled back to baseline. "Does the problem exist" is no longer a question. Direction: aligned — one checklist line of #8091, design doc in-repo, continuation of already-merged increments (#8245, #8423, #8462, #8508). Size: 27 files, +3,071/−464. Breakdown: 1,660 production logic lines, 1,812 test, 64 docs/E2E-plan. It spans three packages ( Approach: unchanged and still right — serialize once, charge serialized UTF-8 bytes at admission, tiered stream/connection/process-global budgets, refusal retires the exact owner instead of evicting an older frame, ownership grants provisional until local delivery. The R2–R7 delta (+169/−40 production) is precisely the settlement machinery the review rounds shook out: generation-counted ownership identity, an Risk: no high-risk revert-correlated paths matched. The fail-closed settlement edges remain the review focus; every round's Critical so far targeted exactly those edges and was fixed in the following commit. Moving on to code review. 🔍 Maintainer note unchanged: per the core-module two-tier rule, a cross-package PR of this size needs a human sign-off before merge regardless of how clean the automated review comes back. 中文说明基于六个新 commit(R2–R7 审查轮次)的 re-run——已在 模板完整 ✓(未变化)。 问题:真实,且现在有了实证而非仅靠推断。只计帧数的 256 帧 pre-attach 上限加静默淘汰,是 daemon 内存工作流(#8091)的跟踪项,由代码现状与仓库内设计文档确立。上一轮之后,维护者在更早的 head 上做了真实 daemon A/B,直接观测到失败形态:base 静默丢弃 300 个 JSON-RPC 回复中的 44 个(调用方永久挂起),128 × ~1 MiB 回复后 RSS 滞留 +613 MB;本 PR head 在文档化上限处 fail closed 并回落到基线。"问题是否存在"已不再是疑问。 方向:对齐——#8091 清单一行,设计文档在仓库内,是已合并增量(#8245、#8423、#8462、#8508)的延续。 规模:27 个文件,+3,071/−464。构成:1,660 行生产逻辑、1,812 行测试、64 行文档/E2E 计划。跨三个包( 方案:未变且仍然正确——一次性序列化、按序列化后的 UTF-8 字节在准入时计费、stream/connection/进程全局三级预算、拒绝时精确关闭当事 owner 而非淘汰旧帧、ownership 在本地投递前保持 provisional。R2–R7 的增量(+169/−40 生产行)恰是审查轮次震出来的结算机制:带代数的 ownership 身份、面向 WebSocket/SSE 歧义投递的 风险:未命中 revert 相关高风险路径。fail-closed 的结算边界仍是审查焦点;迄今为止每一轮的 Critical 都正中这些边界,且都在下一个 commit 修复。 进入代码审查 🔍 给维护者的说明不变:按核心模块双层规则,这种跨包规模的 PR 合并前需要人工签核,与自动审查多干净无关。 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRe-run focus: the delta since the last reviewed commit (
No correctness blockers in the delta. The five deferred items (R6-2 unreachable live-send helper, R6-3/R6-4 guard and notification attribution, R7-1 astral fast path, R7-2 docs wording) are Suggestion-grade, recorded in-thread per the review-round cap — after seven rounds that is the documented behavior, and none of them is being silently dropped. The prior pass's note that the estimator over-bills array string elements by one byte (conservative direction) still stands. sequenceDiagram
participant P1 as Dispatcher
participant P2 as AcpConnection
participant P3 as PreAttachBudget
participant P4 as Stream writer
P1->>P2: reply with ownership receipt
P2->>P2: serialize once to UTF-8 Buffer
alt live stream and empty buffer
P2->>P4: sendSerialized, no lease needed
else must buffer
P2->>P3: admission - stream frames, connection frames and bytes, global lease
P3-->>P2: lease, or refuse and close exact owner with 1013
P2->>P2: buffer frame and charge owner
P4-->>P2: attach
P2->>P4: sendSerialized and markPendingDelivery
end
P4-->>P2: write callback settles delivered, unknown, or discarded
P2->>P3: release lease
P2->>P1: delivered commits ownership, unknown re-checks identity, discarded rolls back
Files changed (17 of 27 shown)
Testing evidence (CI run — no PR code executed locally)Unattended re-run; nothing was built or run here — the evidence is the PR's own CI on the reviewed commit, fetched via API. CI is complete and fully green on CI results for
One row per check name (latest run); skipped checks omitted. / 每个检查名一行(取最新一次运行),省略 skipped。 The behavioural claims now carry real weight, and the gaps are named rather than hidden. A maintainer drove a real-daemon, no-mock A/B on the predecessor head Not verified here: the delta's settlement fixes rest on ~466 new lines of fake-stream tests plus green CI — daemon-level behaviour on the current head has not been independently re-run by this review. That is exactly what the sandboxed verification triggered by this re-run settles: 中文说明代码审查:本次 re-run 聚焦上一轮已审 commit 之后的增量(
增量中未发现正确性阻塞项。五个延期项(R6-2 不可达的 live-send 辅助、R6-3/R6-4 guard 与 notification 归因、R7-1 astral 快路径、R7-2 文档措辞)均为 Suggestion 级,已按审查轮次上限规则记录在帖——七轮之后这是既定行为,且没有一项被静默丢弃。上一轮指出的估算器对数组字符串元素多计 1 字节(保守方向)依然存在。 测试证据(CI 运行,本地未执行 PR 代码): 行为声明现在有了实打实的支撑,缺口也被点名而非遮掩:维护者在先驱 head 本处未验证:增量的结算修复依托约 466 行新 fake-stream 测试加全绿 CI——当前 head 的 daemon 级行为本审查未独立复跑。这正是本次 re-run 触发的沙箱验证要收口的: — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 3/5 — clean review; the cap is policy, not doubt. A ~1,660-production-line change crossing three packages is core infrastructure under the two-tier rule, which keeps the bot's approval off and hands the merge decision to a maintainer. Stepping back over the whole arc: this PR went through seven review rounds and the pattern is exactly what you want to see — every round's Critical (broken SDK type assertions; the The evidence stack is now unusually complete for this repo: CI fully green on the reviewed head; a maintainer's real-daemon, no-mock A/B on the predecessor head that watched base silently drop 44 replies while this PR's head failed closed at the exact documented cap; ~466 lines of new tests aimed at the races rather than the happy path; and the sandboxed What keeps this from an approval is standing policy, not a finding:
Verdict: defer to maintainer — nothing blocks from this side. @wenshao, when the 中文说明置信度:3/5 —— 审查干净;封顶来自政策而非疑虑。约 1,660 行生产逻辑跨三个包,按双层规则属核心基础设施,机器人不投批准票,合并决定交由维护者。 退一步看整条弧线:本 PR 经历七轮审查,呈现的正是最理想的形态——每一轮的 Critical(损坏的 SDK 类型断言、 就本仓库而言,现在的证据链罕见地完整:所审 head 上 CI 全绿;维护者在先驱 head 上做了真实 daemon 无 mock A/B,亲见 base 静默丢弃 44 条回复而本 PR head 在文档上限处精确 fail closed;约 466 行新测试对准竞态而非顺路径;针对当前 head 的沙箱 挡住批准的只有既定政策,不是任何发现:
结论:转交维护者——本侧已无阻塞。@wenshao,待 — Qwen Code · qwen3.8-max Reviewed at |
yiliang114
left a comment
There was a problem hiding this comment.
Reviewed the full diff (26 files) with focus on the byte-bounding, overflow semantics, and lease/receipt lifecycle. Approving.
Byte-bound correctness
- Limits are enforced on real serialized sizes: frames are frozen via
JSON.stringify→BufferatprepareAndBuffertime, and budget checks run againstpayload.byteLength(plus a synchronous re-validation of stream/binding identity after serialization — no TOCTOU since it's all within one tick). - Three tiers all enforced: per-stream 256 frames, per-connection 1024 frames / 64 MB, daemon-global 4096 frames / 256 MB via shared
AcpPreAttachBudget(one instance shared across workspace mounts, covered by tests). Subtraction-form checks (bytes > maxBytes - usedBytes) avoid overflow. The oldpushCappedframe-count-only path is fully removed — no unbounded pre-attach growth path remains.
Overflow/discard semantics
- Overflow changed from silent drop-oldest to explicit retirement: session stream closed or connection retired with WS close 1013 "Resource limit", stderr log, and daemon-status counters (
preAttachGuardFailures, high-water marks, used frames/bytes). Observable, not silent. DeliveryReceipt+ ownership-commit-on-delivery is a real improvement:session/new|load|resume|forknow only commitclientId/ownership after the success reply is actually delivered (canCommitSessionOwnershipre-validates binding identity,closingSessions, destroyed state); undelivered replies roll the session back (detach / orphan removal) instead of leaking owned-but-unreachable sessions. Notification forms of these methods are now rejected, closing a session-leak path.
Transport interaction
- Both transports settle in-flight
sendSerializedpromises on close (activeSendClosers/activeWriteClosers), so leases backing pending deliveries are always released and accounting unwinds during teardown — covered by the "keeps a lease until an in-flight delivery settles after teardown" tests. - SSE
doWritenow waits for both the write callback and drain and settles false on close/finish/error; WSenqueueSendkeeps the serialized write chain and settles on socket close. - Bridge-side
estimatePreparedResponseBytesswitched to escape-awareestimateJsonStringBytes, fixing the previous undercount for escaped strings; property-tested againstJSON.stringifyfor all UTF-16 code unit classes.
Test coverage: byte-exact boundary ("accepts the exact byte limit and rejects the next byte"), cross-mount budget sharing, re-entry/serialization races, receipt-callback throws, teardown-vs-delivery races, notification rejection, and bootstrap status shape. Solid.
Nits (non-blocking)
- P2: an attached-but-stalled SSE client (open socket, never reads) can pin its flushed backlog's lease until the socket dies, since
doWritewaits on drain indefinitely; bounded by the budget but up to the global cap can be held, and later connections then fail pre-attach buffering. Bounded + observable is strictly better than the pre-PR unbounded state; a write-side stall timeout on live streams would be a good follow-up. - P3:
SseStream.close()ignores theTransportCloseReason(WS surfaces 1013); resource-limit closes on SSE are indistinguishable from a normal stream end client-side. - P3:
sendSerializedissues up to 4res.writecalls per SSE frame (id line /data:/ payload /\n\n); a single coalesced write would cut syscalls under throughput.
CI: Test (ubuntu-latest, Node 22.x) is red on DaemonSessionProvider.test.tsx ("keeps the current attachment live while a same-session load fails") — a fully mocked webui React timing test. Not related to this PR: this branch's two earlier runs today (e64e891, a76a5af) were fully green, the last commit only touches daemon-status/run-qwen-serve/SDK types, and today's main run is red on the same job. Flake, not gating.
|
@qwen-code /triage |
|
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: 558 passed · 0 failed · 558 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:558 通过 · 0 失败 · 558 总计 Verification reportPR #9007 Deep Verification —
|
| Cell | Scenario | HEAD (this PR) | BASE (50097c15) |
|---|---|---|---|
| C1 | 257 id-bearing frames, one session, no stream attached | #257 refused (failed); owning session closed; connection survives; guard logs ACP pre-attach frame limit |
all 257 accepted (void API); session stays open; buffer capped at 256 by silently evicting the oldest frame (buffer[0].id = 2, id 1 lost; id 257 admitted) |
| C2 | 257 id-less replies | #257 refused; session closed | buffer grows to 257 > "256 cap" (id-less frames exempt from eviction up to the 1024 hard cap — unbounded-growth path) |
| C3 | 3 × ~120 KiB frames, 300 KB per-connection byte limit (constructor seam) | frames 1–2 buffered; #3 refused; connection retired (registry.size 1→0); guard logs connection budget |
all 3 retained (~360 KB charged nowhere — no byte bound exists); connection alive |
| C4 | two connections, shared 6-frame global budget | A:3 + B:3 = 6 charged; B's 4th refused; B destroyed, bystander A intact with all 3 frames (no cross-connection eviction); guardFailures counted exactly once |
no global budget — both connections retain everything (B holds 4) |
| C5 | buffered pre-attach frames flushed on attach, real SSE wire | 3 frames reach the wire byte-equal, in order; all delivery promises resolve delivered |
same delivery parity (3/3 byte-equal) — normal path unchanged |
| C6 | 3 × 256 KiB buffered, attach with a stalled writer | charged at buffering (786,582 B); at the delivery seam pendingDeliveryFrames=3, budget usedFrames=3 still charged; after client close all counters return to 0/0 |
n/a (no lease accounting on base) |
| C7 | delivery-receipt lifecycle (commit/rollback signal) | teardown before attach → receipt discarded; while buffered → unsettled; wire delivery → delivered |
n/a (base sendConn has no receipt parameter — ownership was granted pre-delivery by construction) |
Head arm: 28/28; base arm: 12/12 (every base cell asserts the predicted base behaviour, including the silent-eviction oracle). C6's mid-flight snapshot is taken synchronously at the delivery seam (settlement needs ≥1 async hop); loopback kernel buffering otherwise drains a JS-level "stalled" client, so the seam snapshot is the deterministic observation of lease retention — the same seam a truly TCP-stalled writer holds open longer.
Secondary claim 1 (transactional ownership). Registry half proven in C7 (receipt discarded on teardown / delivered only on wire delivery). Dispatch half (ownershipReceipt wiring into session/new|load|resume|fork, notification rejection) exercised by the green transport suite (309 tests) and mutations M3/M3b below; a base-side dispatch A/B would require the full bridge and is listed under Not covered.
Secondary claim 2 (estimator). Wire oracle = Buffer.byteLength(JSON.stringify(·)) itself: exact equality on all 65,536 single UTF-16 code units, 2,000 seeded random strings, adversarial surrogate/escape combos; early-exit returns exactly min(actual, limit+1) across a full limit sweep; 2 MB mixed payload in 17 ms. Replaying the base formula (mechanical reproduction of the reverted hunk) against the same oracle shows systematic under-charging on every escape-requiring shape (700×U+0001: actual 4202, base charged 702 — under by 3500; object shape {content:…}: 4214 vs 714), while head is exact. Witness: 03-estimator-oracle.png.
Targeted gates (pristine tree)
| Suite | Result |
|---|---|
acp-bridge spawnChannel.test.ts + json-string-bytes.test.ts |
2 files, 46/46 |
cli pre-attach-budget + connection-registry + sse-stream + ws-stream + transport + daemon-status |
6 files, 443/443 (transport 309, connection-registry 50) |
sdk-typescript daemon-public-surface.test.ts |
1 file, 14/14 |
cli dispatch-error.test.ts + dispatch.sandbox.test.ts (extra: dispatch.ts is heavily changed) |
2 files, 7/7 |
Matches the PR's claimed local counts (46 / 442±1 / 14). All green on Linux, the OS the PR's test table marks "not tested".
Mutation matrix (vacuity)
Witness: 04-mutation-matrix-and-gates.png. Raw logs in logs/mut-*.log.
| Mutant | Change | Suite | Result | First failing assertion |
|---|---|---|---|---|
| M2 | spawnChannel.ts: revert estimator hunks to the exact base formula (Buffer.byteLength+2/+3) |
acp-bridge focused | KILLED — 1 red / 45 green | expected [Function] to throw an error (the new admission test; behavioural, not compile) |
| M1 | registry: disable per-connection + global refusal (if (false)) |
connection-registry + pre-attach-budget | KILLED — 6 red / 47 green | expected { …(2) } to be undefined (tryReserve must refuse) |
| M3 | dispatch: remove the canCommitSessionOwnership gate in delivered() |
transport.test.ts | SURVIVED — 0 red / 309 green | — |
| M3b | dispatch: disable rollback-on-discard (positive control) | transport.test.ts | KILLED — 7 red / 302 green | expected [] to include '550e8400-…' (rolled-back session id must vanish) |
M3b proves the suite can go red through the receipt mechanism in that exact file, so M3's survival is not harness failure. Classification of the M3 survivor: coverage gap, not dead code — the gate is live: it catches the close-race where a conn-stream write settles delivered while/after session/close runs for that id (closingSessions membership); without the gate ownSession() would re-add a session being torn down. No test drives that interleaving. The other half of the transaction (rollback-on-discard) is pinned by 7 tests. Note also the deliberate double per-stream frame check around serialization in prepareAndBuffer is re-entry defence (a toJSON re-entering the registry mid-JSON.stringify), pinned by the suite's serialization-reentry test — not dead duplication.
Findings
- Suggestion — ownership commit gate is unpinned (coverage gap, M3 survivor). The
delivered()path ofownershipReceiptis the only part of the new transaction no test asserts: removing thecanCommitSessionOwnershipcheck leaves all 309 transport tests green, while the symmetric rollback path is pinned by 7 tests. The guard is real (close-race on a live connection stream), so a regression here would be silent. A fixture that would pin it:session/newreply in flight on a live connection stream,session/closefor that id landing before the write settles, then assert the session is not re-owned. Not merge-blocking — the guard exists and is correct as written; this is completeness reporting.
Not covered
- Real-daemon E2E (committed plan steps 1–9: 128 × 1 MiB distinct-payload RSS, dynamic-workspace mounts competing on the real daemon,
GET /daemon/status?detail=fullon a liveqwen serve): not executed — the PR itself states this scenario was not run locally either. The A/B above proves the same mechanisms at the registry/stream level over real HTTP/SSE; this reproduces the handling (bounds, refusal attribution, lease accounting), not the end-to-end RSS trigger. - Per-commit attribution: the metadata lists 4 commits; the depth-2 merge-ref checkout makes only the aggregate reachable (
git rev-list HEAD^1..HEAD^2at a shallow boundary returns a grafted 1). Verified the aggregateHEAD^1..HEADdiff. - Dispatch-level A/B against base for ownership transactionality (would need the full bridge runtime); covered instead by C7 + gates + M3/M3b.
- Ordinary live SSE/WS new-frame queues and transient single-frame
JSON.stringifyamplification (PR-declared non-goals); Windows; repo-wide lint/typecheck (PR's own CI ran them; the base-controlnpm run buildand a cold HEAD-core typecheck ran clean here as part of A/B setup).
Methodology
Environment: node:22-bookworm CI container, Node v22.23.2, tree at refs/pull/9007/merge (depth 2), npm ci + npm run build pre-run at HEAD. A/B harness (ab-pre-attach.mjs) imports each arm's compiled packages/cli/dist/src/serve/acp-http/*.js — closure verified node-builtin-only via per-file import scan and node --check, so the control cannot silently load head code; base control built in git worktree tmp/base-tree at HEAD^1 with npm run build -w packages/cli (final build EXIT=0, 0 TS errors, after wiring the worktree to the root node_modules plus per-package node_modules directories, which the worktree lacks — earlier build attempts failing on 222 TS errors were this environmental gap plus a missing generated git-commit.ts, not a base-source problem; packages/core is untouched by the PR, confirmed by diff, and a cold tsc --noEmit of HEAD core exits 0). Estimator oracle (estimator-oracle.mjs) compares against JSON.stringify bytes directly and replays the base formula mechanically from the reverted hunk. Gates ran via npx vitest run <exact files> inside each package. Mutations applied in place with file backups, each restored and verified by empty git diff afterwards; raw per-cell logs in logs/. One pre-existing environmental artifact: the PR's own E2E-plan file was absent from the worktree (root-owned read-only .qwen/ directory prevented restore); it was read from git show HEAD: and does not affect build or tests.
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
Maintainer verification — real daemon, no mocksI built two full bundles from source ( The lever for ~1 MiB replies is config-only, so the payloads are genuine dispatcher output rather than injected frames: the fixture registers 245 models under This closes the evidence gap the PR itself names ("that real daemon RSS scenario was not executed in this local pass"). The byte bound, the per-owner refusal, the status surface, the shared global budget and the ownership transactionality all behave as described. Two things are worth a maintainer's attention before merge — they are noted in §6 and §7, and neither is a regression. 1. The pre-attach byte bound holds against a real daemonOne connection, session stream never attached, 128 × ~1 MiB replies.
The refusal lands exactly where the constant says it should: 65 frames × 1,025,198 B = 66,637,870 B fits, the 66th would cross 64 MiB, and that admission closes the connection. Reproduced identically across two independent runs. One honest caveat on the RSS column: at equal frame counts head's RSS is not lower than base's (transient 2. Silent eviction becomes fail-closed — this is the strongest argument for the breaking change300 × On base, all 300 POSTs are accepted with 3. Refusal closes only the admitting ownerThe daemon stayed healthy on both arms after the guard fired: a fresh 4. Status and SDK surfaceAll five limits are exposed with the documented values, the runtime counters track and release correctly, and per-mount attribution is populated. The same endpoint on base has none of these fields. 5. Ownership is transactional with local delivery
6. One global budget across mounts — verified, but not to exhaustionWith I did not drive this to the 4,096-frame / 256 MiB global ceiling — reaching it needs four-plus connections each parked just under their own 64 MiB cap, i.e. ~250 MiB of live buffers. The sharing is proven; the global refusal edge is still only covered by unit tests. 7. Scope note: an attached-but-unread WebSocket is still unboundedSame fixture, WS transport, client stops reading its socket after
These frames take the live fast path into 8. Independent check of the JSON byte estimatorI wrote my own differential oracle (not the PR's test) against the built package: every UTF-16 code unit standalone, every code unit in escaping-sensitive context, astral code points across all planes, 40,000 deterministic random strings, and 5,000 randomized early-exit limit cases. 9. Test suites reproduce on head
(Counts are higher than the PR body's 46/442 because I ran the whole files rather than a filtered subset.) VerdictEverything the PR claims about the pre-attach byte bound, per-owner refusal, delivery-tied ownership and the status surface reproduces on a real daemon, and the base-vs-head contrast is decisive — most of all the 44 silently dropped JSON-RPC replies on base. From a verification standpoint this is good to merge. §6 and §7 are follow-up scope for the tracking issue, not blockers: the global ceiling is proven-shared but not driven to exhaustion, and live WS/SSE queues remain unbounded by design after this change. Not covered: Windows and Linux, the WS 中文版本维护者本地验证 —— 真实 daemon,无 mock我从源码构建了两份完整 bundle( 产生 ~1 MiB 回复的杠杆完全来自配置,因此 payload 是真实的 dispatcher 输出而不是注入帧:fixture 在 这补上了 PR 自己声明的证据缺口("该真实 daemon RSS 场景本次本地未执行")。字节边界、按 owner 精确拒绝、status surface、跨 mount 共享全局预算,以及 ownership 事务性,行为都与描述一致。合并前有两点值得维护者注意,见 §6 和 §7,两者都不是回归。 1. 真实 daemon 上字节边界成立单连接,session stream 始终不 attach,128 × ~1 MiB 回复。
拒绝点与常量完全吻合:65 × 1,025,198 = 66,637,870 B 可容纳,第 66 帧会越过 64 MiB,于是这次 admission 关闭连接。两次独立运行结果一致。 RSS 一栏有一个如实的说明:在相同帧数下 head 的 RSS 并不低于 base( 2. 静默淘汰变为 fail closed —— 这是支持该 breaking change 最有力的证据在未 attach stream 的连接上发 300 次 base:300 次 POST 全部 3. 拒绝只关闭发起 admission 的 owner两条腿在 guard 触发后 daemon 都保持健康:新的 4. status 与 SDK surface五个 limits 都以文档值暴露,runtime 计数正确累计与释放,per-mount 归因有值。base 上同一端点完全没有这些字段。 5. ownership 与本地投递构成事务
6. 跨 mount 共享同一预算 —— 已验证,但未跑到耗尽使用 我没有把它推到 4,096 帧 / 256 MiB 的全局上限:要触达需要四个以上连接各自停在自己 64 MiB 上限之下,即约 250 MiB 的活缓冲。共享已被证明,全局拒绝的边界目前仍只有单测覆盖。 7. 范围说明:已 attach 但不读取的 WebSocket 仍然无界同一 fixture,WS transport,客户端在
这些帧走 live 快路径进入 8. JSON 字节估算器的独立校验我针对已构建的包写了自己的差分 oracle(不是 PR 自带的测试):每个 UTF-16 code unit 单独、每个 code unit 处于转义敏感上下文、覆盖全部平面的 astral code point、40,000 个确定性随机字符串,以及 5,000 个随机化的提前退出 limit 用例。 9. head 上测试套件复现
(数量高于 PR 正文的 46/442,因为我跑的是整文件而不是过滤后的子集。) 结论PR 关于 pre-attach 字节边界、按 owner 拒绝、ownership 绑定投递以及 status surface 的全部声明,都在真实 daemon 上复现,base 与 head 的对比结论明确 —— 尤其是 base 上 44 条被静默丢弃的 JSON-RPC 回复。从验证角度可以合并。§6 与 §7 属于 tracking issue 的后续范围,不是阻塞项:全局上限已证明共享但未跑到耗尽,live WS/SSE 队列在本次改动后按设计仍然无界。 未覆盖:Windows 与 Linux、WS |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): PR #9007 bounds the daemon's ACP HTTP pre-attach bufferin...: none — all checks above completed within the tool budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks I started were completed within budget.; chunk 3: I could not execute the test file (the review worktree has no node_modules). All assertions were manually traced against the implementation instead.; chunk 3: could not execute connection-registry.test.ts to confirm it passes (no node_modules in the review worktree); all assertions were verified by manual trace ag…; You are review agent reverse-audit — Reverse audit agen...: none — all checks I started were completed within budget., and 9 more.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
中文说明
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):PR #9007 bounds the daemon's ACP HTTP pre-attach bufferin...:none — all checks above completed within the tool budget.;You are review agent reverse-audit — Reverse audit agen...:none — all checks I started were completed within budget.;chunk 3:I could not execute the test file (the review worktree has no node_modules). All assertions were manually traced against the implementation instead.;chunk 3:could not execute connection-registry.test.ts to confirm it passes (no node_modules in the review worktree); all assertions were verified by manual trace ag…;You are review agent reverse-audit — Reverse audit agen...:none — all checks I started were completed within budget.,另有 9 条。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
— qwen3.8-max via Qwen Code /review (v0.21.10)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Addressed the latest review round in
Verification: explicit TypeScript compilation of the test file (0 diagnostics), negative mutation checks for all four type-pin groups, SDK public-surface tests 14/14, SDK typecheck, root ESLint, Prettier, and |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...: none — all planned checks completed within budget.; PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...: none (~17 of ~53 tool calls used; all planned checks completed).; PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...: none — all checks I started were completed within budget.; PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...: none — all planned checks (baseline frame-write comparison, replay exclusivity, fake-vs-real attached shapes, receipt result mapping, onFatalConnection wiri…; PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...: none — all planned checks completed within budget., and 11 more.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
中文说明
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...:none — all planned checks completed within budget.;PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...:none (~17 of ~53 tool calls used; all planned checks completed).;PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...:none — all checks I started were completed within budget.;PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...:none — all planned checks (baseline frame-write comparison, replay exclusivity, fake-vs-real attached shapes, receipt result mapping, onFatalConnection wiri…;PR #9007 bounds daemon ACP HTTP pre-attach buffering by f...:none — all planned checks completed within budget.,另有 11 条。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
— qwen3.8-max via Qwen Code /review (v0.21.10)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Review round R4 summary (
Verification: focused ACP HTTP tests (382/382 before the added registry assertion, then 75/75 stream/registry), |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
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 explored to full depth (tool budget reached): "PR #9007 bounds the daemon's ACP HTTP pre-attach buffers…": none — all checks above completed within budget.; "PR #9007 bounds the daemon's ACP HTTP pre-attach buffers…": none — all checks above were completed within budget..
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"PR #9007 bounds the daemon's ACP HTTP pre-attach buffers…":none — all checks above completed within budget.;"PR #9007 bounds the daemon's ACP HTTP pre-attach buffers…":none — all checks above were completed within budget.。
— qwen3.8-max via Qwen Code /review (v0.21.11)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Addressed review round 5 on
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — stopped at the 5-round cap without two consecutive dry rounds; round 5's re-reported Critical candidate was adjudicated a duplicate of a previously rejected finding, and its recurring Suggestion is a previously-discussed deferred thread.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": none — all checks I started were completed within budget.; "PR #9007 bounds the daemon ACP HTTP pre-attach buffers by…": none — all checks I started completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks I started were completed within budget.; "PR #9007 bounds the daemon ACP HTTP pre-attach buffers by…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget., and 15 more.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:reverse audit — stopped at the 5-round cap without two consecutive dry rounds; round 5's re-reported Critical candidate was adjudicated a duplicate of a previously rejected finding, and its recurring Suggestion is a previously-discussed deferred thread。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…":none — all checks I started were completed within budget.;"PR #9007 bounds the daemon ACP HTTP pre-attach buffers by…":none — all checks I started completed within budget.;"You are review agent reverse-audit — Reverse audit agent…":none — all checks I started were completed within budget.;"PR #9007 bounds the daemon ACP HTTP pre-attach buffers by…":none — all checks above completed within budget.;"You are review agent reverse-audit — Reverse audit agent…":none — all checks above completed within budget.,另有 15 条。
— qwen3.8-max via Qwen Code /review (v0.21.11)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Review round 6 follow-up
Commit: Verification: focused ACP transport/registry tests 78/78, |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
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 — did not converge within the reverse-audit round cap of 5.
Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": verifying whether the new fire-and-forget rollback ( ownership.discarded() in the catch /destroyed paths) releasing requestedSessionIdAdmission in finally …; "You are review agent reverse-audit — Reverse audit agent…": none — all planned checks completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget., and 13 more.
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:reverse audit — did not converge within the reverse-audit round cap of 5。
未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…":verifying whether the new fire-and-forget rollback ( ownership.discarded() in the catch /destroyed paths) releasing requestedSessionIdAdmission in finally …;"You are review agent reverse-audit — Reverse audit agent…":none — all planned checks completed within budget.;"You are review agent reverse-audit — Reverse audit agent…":none — all checks above completed within budget.;"You are review agent reverse-audit — Reverse audit agent…":none — all checks above completed within budget.;"You are review agent reverse-audit — Reverse audit agent…":none — all checks above completed within budget.,另有 13 条。
— qwen3.8-max via Qwen Code /review (v0.21.11)
|
Review round 7 follow-up
No branch changes were made. All 20 code checks are passing. |
|
@qwen-code /triage |
|
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: 648 passed · 0 failed · 648 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:648 通过 · 0 失败 · 648 总计 Verification reportPR #9007 Deep Verification (follow-up round) —
|
| # | Finding (previous round, head aea72fd3) |
Severity | Status at 23f1af33 |
|---|---|---|---|
| 1 | Ownership commit gate (canCommitSessionOwnership consulted in ownershipReceipt.delivered()) unpinned by any test — M3 survivor (309/309 green with the gate removed) |
Suggestion (coverage gap) | stands — re-measured: with the gate removed, transport 311/311 and the dispatch trio 55/55 stay green. The delta's two new transport tests pin rollback/serialization paths, not the commit gate. Positive control M3b now kills 9 (was 7) — the rollback half got more pinned while the commit half remains unpinned. The predicate itself is unit-pinned at registry level (rejects an ownership commit while session/close is in flight), so the gap is exactly "nobody drives the dispatch-level close-race through delivered()". |
Re-measured carry-forward measurements (all re-run at the new head, none diffed from the old report): A/B cells C1–C7 (new base tip), estimator oracle (65,536 code units + sweeps), mutation matrix M1–M3b, and all focused gates. The input closure changed (6 new commits), so no measurement was carried by shortcut.
Central claim + A/B
Central claim: daemon-owned ACP HTTP pre-attach buffering is bounded by frame count and serialized bytes at per-stream / per-connection / process-global scope, and refusal fails closed on the admitting owner instead of silently evicting an older frame.
The A/B drives each arm's compiled dist/ through real loopback HTTP (express + real SseStream + real HTTP client). Import closure of the acp-http dist is node-builtins-only (re-scanned per file at the new head), so no workspace symlink can contaminate an arm; the base control's packages/cli/node_modules symlink resolves into the main tree, which is a clean control because the PR leaves package.json/package-lock.json/packages/core untouched (verified by diff). Witness: 01-ab-head-arm-bounds-fail-closed.png, 02-ab-base-arm-silent-eviction.png.
| Cell | Scenario | HEAD (23f1af33) |
BASE (299d29c4) |
|---|---|---|---|
| C1 | 257 id-bearing frames, one session, no stream attached | #257 refused (failed); owning session closed; connection survives; guard logs ACP pre-attach frame limit |
all 257 accepted (void API); buffer capped at 256 by silently evicting the oldest (buffer[0].id 1→2) |
| C2 | 257 id-less replies | #257 refused; session closed | buffer grows to 257 > 256 (id-less exempt up to the 1024 hard cap) |
| C3 | 3 × ~120 KiB frames vs 300 KB per-connection cap (constructor seam) | frames 1–2 buffered, bytes charged exactly (245,794); #3 refused; connection retired; guard logs connection budget |
all 3 retained; no byte bound; connection alive |
| C4 | two connections, shared 6-frame global budget | A:3+B:3 charged; B's 4th refused; B destroyed; bystander A intact with all 3; guardFailures counted exactly once |
no global budget — B retains all 4 |
| C5 | buffered frames flushed on attach, real SSE wire | 3 frames byte-equal, in order; promises resolve delivered |
same delivery parity (void API) |
| C6 | 3 × 256 KiB buffered, attach with non-reading client | charged 786,480 B at buffering; seam snapshot pendingDeliveryFrames=3, budget usedFrames=3; after client close all counters 0 |
no lease accounting (fields absent) |
| C7 | delivery-receipt lifecycle | unsettled while buffered; teardown → discarded; wire delivery → delivered |
receipt never invoked (ownership granted pre-delivery by construction) |
Head arm 29/29; base arm 19/19 (every base assertion encodes the predicted broken behaviour).
Delta probes (the six commits since the last round)
Ambiguous WebSocket deliveries (6f3b0770) — mock-free, real ws server/client over loopback TCP, compression off, paused receiver so the write genuinely stays in flight (ws-ambiguity.mjs; witness 03-ws-ambiguous-delivery-preserved.png):
| Probe | HEAD | BASE |
|---|---|---|
| W1 healthy send to a reading client | delivered, frame received byte-equal |
n/a |
| W2 accepted-but-unconfirmed send when the stream closes mid-flight | outcome_unknown (not delivered/failed/closed) |
no sendSerialized at all — ambiguity inexpressible (void send) |
| W3 buffered frame + receipt, WS stream closed mid-delivery | receipt settles exactly outcomeUnknown, never discarded → ownership not rolled back for a delivery that may have happened |
n/a |
Dispatch-level semantics read from code: ownershipReceipt.outcomeUnknown commits only while !conn.destroyed && canCommitSessionOwnership(...), else detaches-without-deleting — never a destructive rollback. Vacuity: M5 (flatten outcome_unknown→delivered) kills exactly the 2 committed ambiguity tests (2 red / 18 green).
Failed-delivery reporting (16cde030) — base replyConn was conn.sendConn(success(id, result)), fire-and-forget: a client whose response could not be delivered waited forever with no signal. Head tracks the outcome and, on 'failed' with the connection alive, sends error(id, INTERNAL_ERROR, 'Response delivery failed') on the same connection. The information is observable to the JSON-RPC client (and guardFailures/preAttach counters surface to GET /daemon/status for guard refusals). Pinned end-to-end by the committed over-the-wire tests (session/load returns an internal error when its %s response cannot be serialized, live+buffered: -32603 frame arrives, connection survives, provisional ownership rolled back, bridge client detached). Vacuity: M4 (delete the retry) kills exactly those 2 tests (2 red / 309 green).
Secondary claims
- Estimator (wire oracle =
Buffer.byteLength(JSON.stringify(·))itself): exact equality on all 65,536 single UTF-16 code units, 2,000 seeded random strings, 31 adversarial surrogate/escape combos; early-exit returns exactlymin(actual, limit+1)across a 48-point limit sweep; 2 MB mixed payload in 35 ms. Base formula replay under-charges 4/6 escape-requiring shapes (700×U+0001: actual 4202 vs base 702) and matched where no escaping was needed (CJK) — the regression is exactly bounded. Witness:04-estimator-oracle-exact.png. - Transactional ownership: rollback half pinned (M3b kills 9, +2 vs last round); commit half = Finding 1.
Mutation matrix (re-run live at the new head)
Witness: 05-mutation-matrix-at-new-head.png. Raw logs logs/cap-*.log, logs/mut-*.log.
| Mutant | Change | Suite | Result | First failing assertion |
|---|---|---|---|---|
| M1 | registry: disable per-connection + global refusal | connection-registry + pre-attach-budget | KILLED — 5 red / 56 green | bounds retained serialized payload bytes before stream attachment (+4 budget tests) |
| M2 | spawnChannel: revert estimator to base formula | acp-bridge focused | KILLED — 1 red / 46 green | charges JSON string escaping before admitting prepared responses (expected [Function] to throw an error) |
| M3 | dispatch: remove canCommitSessionOwnership gate in delivered() |
transport + dispatch trio | SURVIVED — 0 red / 311+55 green | — |
| M3b | dispatch: disable rollback-on-discard (positive control) | transport | KILLED — 9 red / 302 green | rolls back an undelivered fresh session when the connection closes (+8) |
| M4 | dispatch: delete failed-delivery error-frame retry | transport | KILLED — 2 red / 309 green | the two session/load … cannot be serialized tests |
| M5 | ws-stream: flatten outcome_unknown → delivered |
ws-stream | KILLED — 2 red / 18 green | close() marks an active accepted send outcome unknown (+1) |
M3b/M4/M5 prove the harness can make every relevant suite red through the receipt/ambiguity mechanisms, so M3's survival is a coverage statement, not harness failure. All mutants reverted after their runs; git diff -- packages/ clean afterwards.
Findings
- Suggestion (carried forward, stands) — ownership commit gate unpinned. Reproduce: apply
mut-apply.py m3(removes thecanCommitSessionOwnershipconsultation fromownershipReceipt.delivered()), thencd packages/cli && npx vitest run src/serve/acp-http/transport.test.ts src/serve/acp-http/dispatch-error.test.ts src/serve/acp-http/dispatch.sandbox.test.ts src/serve/acp-http/workspace-qualified-acp.test.ts→ 366/366 green;mut-apply.py m3bon the same transport file → 9 red. Removing the gate leaves all 311 transport + 55 dispatch tests green, while the symmetric rollback path is pinned by 9 tests. The gate is live, not dead:closingSessions.add()runs in thesession/closehandler andcloseSessionStreambumps the generation, so a reply whose write settles after a close/destroy race is exactly the interleaving the gate decides — and the registry-level predicate test passes unchanged under M3 because M3 removes the caller, not the predicate. A fixture that would pin it:session/newreply in flight on a live connection stream,session/closefor that id landing before the write settles, then assert the session is not re-owned. Not merge-blocking — the guard exists and is correct as written; completeness reporting.
Not covered
- Real-daemon E2E (committed plan: 128 × 1 MiB distinct-payload RSS, dynamic-workspace mounts, live
GET /daemon/status?detail=full): not executed — the PR states it was not run locally either. The plan file is again absent from this worktree (root-owned.qwen/prevents restore; read viagit show HEAD:— environmental, same as last round). The A/B above proves the same mechanisms at registry/stream level over real HTTP/SSE/WS; that is the handling, not the end-to-end RSS trigger. - Per-commit attribution for the six delta commits: the depth-2 merge-ref checkout makes only the aggregate reachable; the delta was scoped by commit messages plus code inspection (base-side diffs of
ws-stream.ts,sse-stream.ts,dispatch.ts), and each named fix was driven through a harness. - Dispatch-level A/B against base for ownership transactionality (needs the full bridge runtime); covered by C7 + gates + M3/M3b/M4.
- Ordinary live SSE/WS new-frame queues and transient single-frame
JSON.stringifyamplification (PR-declared non-goals); Windows; repo-wide lint/typecheck (PR's own CI ran them; the base-controlnpm run buildand HEAD builds ran clean here as part of A/B setup).
Methodology
Environment: node:22-bookworm CI container, Node v22.23.2, tree at refs/pull/9007/merge (depth 2), npm ci + npm run build pre-run at HEAD. A/B harness (ab-pre-attach.mjs) imports each arm's compiled packages/cli/dist/src/serve/acp-http/*.js (closure node-builtin-only, re-verified); base control built in a scratch worktree at HEAD^1 (npm run build -w packages/acp-bridge -w packages/cli after wiring root + per-package node_modules symlinks and regenerating the gitignored git-commit.ts; BUILD_EXIT=0) and removed after capture; base arm's internal-dependency realpath asserted. WS probe (ws-ambiguity.mjs) uses the real ws package with compression disabled and a paused receiver to keep writes genuinely in flight. Estimator oracle (estimator-oracle.mjs) compares against JSON.stringify bytes directly and replays the base formula mechanically. Gates ran via npx vitest run <exact files> per package. Mutations applied by mut-apply.py with backups in mut-backup/, each restored and verified by empty git diff; the full matrix was re-executed under scripts/verify-capture.mjs (mutation-runner.sh). Raw per-cell logs in logs/.
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
|
Sandbox verification follow-up: 648/648 scripted assertions passed, with no blocking finding. The surviving item is a Suggestion to add dispatch-level mutation coverage for the ownership commit gate; the production guard and registry-level predicate coverage are present. Per the repository rule after seven review rounds, this test-only enhancement is deferred to follow-up work, with no branch change. |
wenshao
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — did not converge within the 5-round cap; round 5 still produced a new static Critical candidate, and static candidates without an executable witness were withheld for maintainer review.
Not reviewed: build-and-test — the full packages/cli test command timed out after 241 seconds; focused changed-area suites passed.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:reverse audit — did not converge within the 5-round cap; round 5 still produced a new static Critical candidate, and static candidates without an executable witness were withheld for maintainer review。
未审查:build-and-test — the full packages/cli test command timed out after 241 seconds; focused changed-area suites passed。
— qwen3.8-max via Qwen Code /review (v0.21.11)
wenshao
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
— qwen via Qwen Code /review (v0.21.11)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Review handling summary
Validation: 411 focused ACP HTTP transport/registry/SSE/WS tests passed, including the 257-reply overflow regression; repository build, typecheck, lint, and diff check passed. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
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 explored to full depth (tool budget reached): "agent reverse-audit (round 4)": none — the full scope was read and no check was left unfinished.; "agent reverse-audit (round 3)": none material — I did not run the test suite or a build (review-only scope; the PR's own commands claim typecheck/lint pass), and I did not execute the E2E plan….
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 4)":none — the full scope was read and no check was left unfinished.;"agent reverse-audit (round 3)":none material — I did not run the test suite or a build (review-only scope; the PR's own commands claim typecheck/lint pass), and I did not execute the E2E plan…。
— qwen3.8-max via Qwen Code /review (v0.21.11)
wenshao
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — only chunks 9 and 11 were independently re-audited; the full 15-chunk convergence pair did not run.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — the full CLI and web-shell test commands timed out under the review budget, and the webui test command did not run; targeted ACP HTTP registry, SSE, transport, bridge, SDK, and CLI typecheck checks passed.
[Critical] Existing Critical R8-1 still stands: ownershipReceipt.commit() delays markInitialReplayPending(sessionId) until local reply delivery, so an already-owned session can attach while the load reply is buffered, miss the promised initial history replay, and cause the replay flag to leak into the next attach. This is already reported by PR comment 3785702108 and is not duplicated inline.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:reverse audit — only chunks 9 and 11 were independently re-audited; the full 15-chunk convergence pair did not run。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — the full CLI and web-shell test commands timed out under the review budget, and the webui test command did not run; targeted ACP HTTP registry, SSE, transport, bridge, SDK, and CLI typecheck checks passed。
[Critical] Existing Critical R8-1 still stands: ownershipReceipt.commit() delays markInitialReplayPending(sessionId) until local reply delivery, so an already-owned session can attach while the load reply is buffered, miss the promised initial history replay, and cause the replay flag to leak into the next attach. This is already reported by PR comment 3785702108 and is not duplicated inline.
— gpt-5.6-sol via Qwen Code /review (v0.21.11)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Handled the latest Critical review finding in 40aaa2f. The fix restores response-production replay arming for already-owned session/load operations without weakening provisional ownership for fresh loads. Replay arming is guarded by the captured session ownership identity, so a stale load cannot contaminate a replacement session generation. Added real HTTP/SSE coverage for first-attach snapshot replay before connection-reply delivery, no late re-arm on the second attach, and close/re-own generation replacement. Verification: ACP HTTP transport 314/314; full repository build, typecheck, lint, and git diff --check all passed. 最新 Critical 反馈已在 40aaa2f 中处理。 修复为已拥有会话的 session/load 恢复了响应生成阶段的 replay 置位,同时保留 fresh load 仅在 provisional ownership 成功提交后置位的语义。置位操作受捕获的会话 ownership identity 保护,因此旧 load 不会污染同 ID 的新 generation。新增真实 HTTP/SSE 回归,覆盖 connection reply 投递前首次 attach 的快照回放、晚到 reply 不会让第二次 attach 重复回放,以及 close/re-own generation 替换。 验证:ACP HTTP transport 314/314;全仓 build、typecheck、lint 和 git diff --check 均通过。 |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Handled the newly surfaced Critical connection-response handoff finding in 6578807. Connection-scoped sends now retain an attempt-owned serialized frame until delivery is definitive. Frames reported closed before a complete response reached the wire move, in original order, into the bounded pre-attach buffer and are delivered by the replacement stream with the original receipt. Ambiguous complete deliveries remain terminal and are not retransmitted. Verification: connection registry + ACP HTTP transport 377/377; real SSE write-chain probe confirmed original response delivery exactly once, outcome_unknown no-retry, terminal cleanup, and zero residual budget; full repository build, typecheck, lint, and git diff --check passed. 新出现的 connection response handoff Critical 反馈已在 6578807 中处理。 connection-scoped send 现在会保留 attempt-owned 的序列化帧,直到投递结果明确。若 stream 在完整响应到达 wire 前返回 closed,帧会保持原顺序、原 receipt,并转入受限的 pre-attach buffer,由 replacement stream 投递。完整响应可能已送达的 outcome_unknown 仍为终态,不会重传。 验证:connection registry + ACP HTTP transport 377/377;真实 SSE write-chain 探针确认原响应仅投递一次、outcome_unknown 不重试、terminal cleanup 正确且预算完全归零;全仓 build、typecheck、lint 和 git diff --check 均通过。 |














What this PR does
This PR makes daemon-owned ACP HTTP pre-attach buffering bounded by both frame count and serialized payload bytes. It serializes buffered replies once into UTF-8 buffers, applies per-stream, per-connection, and process-global limits shared by primary and dynamic workspace mounts, retains budget ownership through stalled SSE or WebSocket delivery, and closes the exact session or logical connection on refusal instead of silently evicting an older frame.
It also makes session ownership grants transactional with local transport delivery.
session/new,session/load,session/resume, andsession/forkremain provisional until their JSON-RPC reply is locally delivered; teardown, overflow, serialization failure, or delivery failure rolls back fresh sessions, persisted forks, and new client attachments without revoking ownership that already existed. Notification forms of these ownership-granting methods no longer mutate state.The daemon status and TypeScript SDK surfaces now expose the effective pre-attach limits, current and high-water frame/byte usage, pending delivery ownership, guard failures, per-mount attribution, and per-connection owned frames/bytes. The accompanying capacity and resumable-stream documentation records the compatibility changes and the remaining non-goals.
The first commit also corrects prepared-response estimation for JSON string escaping, UTF-8, and surrogate handling so admission matches the bytes that
JSON.stringifyactually emits.Why it's needed
The existing 256-frame pre-attach cap controlled only item count and could silently drop an older frame. A small number of very large replies, multiple connections across dynamic workspaces, or an attached but stalled writer could still retain large buffers without a daemon-global byte boundary. Silent eviction was especially unsafe for id-less JSON-RPC replies because the client could wait forever for a response that had been discarded.
Session creation and attach operations were also recorded as owned before their reply was delivered. If a connection closed or overflowed before the client learned the session ID, the daemon could leave an unreachable fresh session, persistent fork, or bridge client attachment behind. Tying ownership to the local delivery result closes that lifecycle gap.
Reviewer Test Plan
How to verify
Run the focused ACP bridge tests and confirm the JSON string estimator matches
JSON.stringifyfor every UTF-16 code unit, mixed escaping, surrogate pairs, and deterministic random strings.Run the focused CLI transport tests and confirm the N+1 frame/byte admission closes only the correct owner, primary and dynamic workspace registries compete for one global budget, stalled SSE/WS delivery retains leases until callback/close, serialization re-entry cannot revive stale stream identities, replay-deferred replies preserve order, and every provisional ownership receipt is either committed after delivery or rolled back on teardown/failure.
Run the daemon status and SDK surface tests and confirm the new limits and runtime counters are stable when ACP HTTP is disabled and populated when enabled.
The committed E2E plan describes the real 128 × 1 MiB distinct-payload reproduction, cross-workspace global-budget competition, stalled writer inspection, and workspace disposal baseline check. That real daemon RSS scenario was not executed in this local pass.
Validation completed locally: ACP bridge focused tests 46/46, CLI focused tests 442/442, SDK public-surface tests 14/14,
npm run build,npm run typecheck,npm run lint, andgit diff --check.Evidence (Before & After)
N/A — this is daemon transport, lifecycle, status, SDK type, documentation, and test coverage with no TUI change.
Tested on
Environment (optional)
macOS, Node.js v24.12.0, repository npm workspaces and package-local Vitest configurations. The repository requires Node.js >=22; no Docker or sandbox runtime was used for the focused tests.
Risk & Scope
JSON.stringifyamplification, remote exactly-once receipts, and the real 128 × 1 MiB daemon RSS E2E remain outside this PR or were not run locally.Linked Issues
Tracking: #8091
中文说明
本 PR 做了什么
本 PR 为 daemon 自有的 ACP HTTP pre-attach 缓冲增加帧数和序列化 payload 字节数双重边界。缓冲回复只序列化一次并保存为 UTF-8 Buffer,同时应用单 stream、单 connection 和 primary/动态 workspace mount 共享的进程级全局限制;即使 SSE 或 WebSocket writer 已 attach 但阻塞,预算所有权也会保留到本地投递完成或明确失败。资源拒绝时不再静默淘汰旧帧,而是关闭精确 session 或逻辑 connection。
本 PR 还把 session ownership grant 与本地 transport delivery 做成事务闭环。
session/new、session/load、session/resume和session/fork在 JSON-RPC 回复本地投递完成前保持 provisional;teardown、overflow、序列化失败或投递失败会回滚 fresh session、持久化 fork 和新增 client attach,同时不会撤销原本已经存在的 ownership。这些会授予 ownership 的方法以 notification 形式调用时不再执行 mutation。daemon status 和 TypeScript SDK 现在会暴露有效的 pre-attach limits、当前和高水位帧数/字节数、pending delivery ownership、guard failure、每个 mount 的归因,以及每个 connection 自有的帧数/字节数。配套的 capacity 与 resumable-stream 文档记录了兼容性变化和剩余非目标。
第一笔 commit 还修复了 prepared-response 对 JSON 字符串转义、UTF-8 和 surrogate 的计费,使 admission 与
JSON.stringify实际输出字节一致。为什么需要
现有 256 帧 pre-attach 限制只控制条目数,并且可能静默丢弃旧帧。少量超大回复、多个动态 workspace connection,或已 attach 但阻塞的 writer,仍可能在没有 daemon 全局字节边界的情况下保留大量 buffer。对于无 id 的 JSON-RPC 回复,静默淘汰尤其危险,因为客户端可能永远等待一个已经被丢弃的响应。
session create/attach 操作之前也会在回复投递前就记录为 owned。如果 connection 在客户端得知 session ID 之前关闭或 overflow,daemon 可能遗留客户端无法访问的 fresh session、持久化 fork 或 bridge client attachment。把 ownership 与本地 delivery result 绑定可以闭合这一生命周期缺口。
Reviewer 测试计划
如何验证
运行聚焦 ACP bridge 测试,确认 JSON 字符串 estimator 对全部 UTF-16 code unit、混合转义、surrogate pair 和确定性随机字符串都与
JSON.stringify一致。运行聚焦 CLI transport 测试,确认第 N+1 个 frame/byte admission 只关闭正确 owner,primary 与动态 workspace registry 竞争同一全局预算,阻塞的 SSE/WS delivery 在 callback/close 前持续持有 lease,序列化重入不能复活 stale stream identity,replay deferred reply 保持顺序,并且每个 provisional ownership receipt 都只会在 delivery 后提交或在 teardown/failure 时回滚。
运行 daemon status 与 SDK surface 测试,确认 ACP HTTP 关闭时新 limits/runtime 字段保持稳定,开启时正确填充。
已提交的 E2E 计划描述了真实 128 × 1 MiB distinct payload 复现、跨 workspace 全局预算竞争、阻塞 writer 观察和 workspace dispose 后回到基线的验证流程。本次本地验证未实际执行真实 daemon RSS 场景。
本地已完成验证:ACP bridge 聚焦测试 46/46、CLI 聚焦测试 442/442、SDK public-surface 测试 14/14、
npm run build、npm run typecheck、npm run lint和git diff --check。证据(Before & After)
N/A —— 本 PR 修改 daemon transport、生命周期、status、SDK 类型、文档和测试,不涉及 TUI 变化。
测试平台
环境(可选)
macOS,Node.js v24.12.0,仓库 npm workspaces 和包级 Vitest 配置。仓库要求 Node.js >=22;聚焦测试未使用 Docker 或 sandbox runtime。
风险与范围
JSON.stringify瞬时放大、远端 exactly-once receipt 不在本 PR 范围内;真实 128 × 1 MiB daemon RSS E2E 本地未执行。关联 Issue
Tracking: #8091