Skip to content

test(managed-agent): stub the acquire provisioner overload the Broker calls - #14498

Merged
wenshao merged 1 commit into
mainfrom
test/workspace-csi-it-provisioner-overload
Oct 17, 2026
Merged

wenshao merged 1 commit into
mainfrom
test/workspace-csi-it-provisioner-overload

Conversation

@wenshao

@wenshao wenshao commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Updates the MariaDB integration test's mocked provisioner so it answers the overload the Runtime Broker now calls when a Session acquires a Tool Runtime.

Why it's needed

The test failed on the Runtime Broker and Managed Agent MariaDB lane: tool-sessions:acquire answered 400 runtime_broker_invalid_request. The cause was not the request the test sends — it was a stale mock.

The Broker resolves placement through the three-argument form of the provisioner's request factory so a bound Session's Harness id can travel on its own. The test's mock only stubbed the two-argument overload, and Mockito never runs interface default methods, so the three-argument call returned null. The Broker handed that null to the binding repository, which rejected it with "request is required"; that IllegalArgumentException surfaced as the generic 400 at the acquire route.

The production default still delegates the three-argument form to the two-argument one, so every real provisioner keeps its previous behaviour — only the mock stopped matching. Stubbing the overload the Broker calls restores the test's intent: the acceptance chain receives the request the test constructed.

Reviewer Test Plan

How to verify

Start a disposable MariaDB 10.11 (this lane uses MariaDB), build the JavaScript prerequisites (the test drives the real CLI worker), then run the single integration test from packages/managed-agent-server with JDK 21:

mvn --batch-mode -Pmysql-integration \
  -Dmysql.url="jdbc:mysql://127.0.0.1:PORT/managed_agent_test?createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false" \
  -Dmysql.user=root -Dmysql.password=SECRET \
  -Dit.test=WorkspaceCsiSqlWorkerMySqlIT failsafe:integration-test

Expected: one test, zero failures, ~15 s. With the old two-argument stub the same command fails at the acquire step with 400 !== 200 and runtime_broker_invalid_request.

Evidence (Before & After)

Before (same machine, same MariaDB, only the stub reverted):

[ERROR] WorkspaceCsiSqlWorkerMySqlIT.sqlGrantExecutesOriginalWorkerAndContinuesWithRetainedHistory -- Time elapsed: 7.953 s <<< FAILURE!
/…/tool-sessions:acquire: {"error":"Runtime Broker request is invalid.","code":"runtime_broker_invalid_request","retryable":false}
400 !== 200
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0

The swallowed cause, recovered by temporarily printing the mapped exception:

java.lang.IllegalArgumentException: request is required
  at JdbcRuntimeBindingRepository.requireRequest
  at JdbcRuntimeBindingRepository.findOrCreate
  at RuntimeBrokerService.ensureBinding
  at RuntimeBrokerService.acquireNewSession

After:

[INFO] WorkspaceCsiSqlWorkerMySqlIT.sqlGrantExecutesOriginalWorkerAndContinuesWithRetainedHistory -- Time elapsed: 14.55 s
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

mvn checkstyle:check also passes.

Tested on

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

Risk & Scope

  • Main risk or tradeoff: none for product code — the change touches only the test's mock; production keeps the default delegation from the three-argument factory to the two-argument one.
  • Not validated / out of scope: the whole MariaDB lane was not run end to end locally; only the failing class was executed. Separately, the Broker still maps an internal IllegalArgumentException to a client-facing 400 without logging it, which is what hid this cause in CI — that observability gap is left for a follow-up rather than widened into this fix.
  • Breaking changes / migration notes: none.

Linked Issues

Fixes #14494

中文说明

本 PR 做了什么

修正 MariaDB 集成测试中被 mock 的 provisioner,使其响应 Runtime Broker 在 Session 获取 Tool Runtime 时实际调用的那个重载。

为什么需要

该测试在 Runtime Broker and Managed Agent MariaDB 车道上失败:tool-sessions:acquire 返回 400 runtime_broker_invalid_request。根因不是测试发送的请求,而是过期的 mock。

Broker 通过 provisioner 请求工厂的三参形式解析放置信息,使绑定 Session 的 Harness id 能单独传递。测试的 mock 只 stub 了二参重载,而 Mockito 不会执行接口默认方法,因此三参调用返回 null。Broker 把这个 null 交给绑定仓储,被以 "request is required" 拒绝;这个 IllegalArgumentException 在 acquire 路由上表现为通用的 400。

生产侧的默认实现仍然把三参形式委托给二参形式,所以所有真实 provisioner 的行为不变——只有 mock 不再匹配。改为 stub Broker 实际调用的重载,即恢复测试本意:验收链路拿到测试构造的那个请求。

评审测试计划

如何验证

启动一次性 MariaDB 10.11(该车道使用 MariaDB),构建 JavaScript 前置产物(测试会驱动真实 CLI worker),然后在 packages/managed-agent-server 下用 JDK 21 单独运行该集成测试:

mvn --batch-mode -Pmysql-integration \
  -Dmysql.url="jdbc:mysql://127.0.0.1:PORT/managed_agent_test?createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false" \
  -Dmysql.user=root -Dmysql.password=SECRET \
  -Dit.test=WorkspaceCsiSqlWorkerMySqlIT failsafe:integration-test

预期:1 个测试、0 失败、约 15 秒。若换回旧的二参 stub,同一命令会在 acquire 步骤失败,报 400 !== 200 与 runtime_broker_invalid_request。

证据(修复前后)

修复前(同机、同 MariaDB,仅把 stub 还原):

[ERROR] WorkspaceCsiSqlWorkerMySqlIT.sqlGrantExecutesOriginalWorkerAndContinuesWithRetainedHistory -- Time elapsed: 7.953 s <<< FAILURE!
/…/tool-sessions:acquire: {"error":"Runtime Broker request is invalid.","code":"runtime_broker_invalid_request","retryable":false}
400 !== 200
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0

被吞掉的根因(临时打印被映射的异常后取得):

java.lang.IllegalArgumentException: request is required
  at JdbcRuntimeBindingRepository.requireRequest
  at JdbcRuntimeBindingRepository.findOrCreate
  at RuntimeBrokerService.ensureBinding
  at RuntimeBrokerService.acquireNewSession

修复后:

[INFO] WorkspaceCsiSqlWorkerMySqlIT.sqlGrantExecutesOriginalWorkerAndContinuesWithRetainedHistory -- Time elapsed: 14.55 s
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

mvn checkstyle:check 同样通过。

测试环境

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

风险与范围

  • 主要风险或取舍:对产品代码无风险——改动只涉及测试的 mock;生产侧仍由三参工厂默认委托给二参实现。
  • 未验证 / 超出范围:本机未端到端跑完整 MariaDB 车道,只执行了失败的那个类。另外,Broker 仍会把内部的 IllegalArgumentException 映射成面向客户端的 400 且不打日志,这正是 CI 中掩盖根因的原因——该可观测性缺口留给后续处理,不并入本次修复。
  • 破坏性变更 / 迁移说明:无。

关联 Issue

Fixes #14494

… calls

#14435 made placement resolution go through the three-argument
RuntimeProvisioner.createRequest so a bound Session's Harness id travels on
its own. Mockito never runs interface default methods, so this IT's
two-argument stub stopped answering the call the Broker makes: the
provisioner returned null, findOrCreate(null) threw "request is required",
and the Broker mapped that internal failure to a 400
runtime_broker_invalid_request at tool-sessions:acquire.

Stub the overload the Broker actually calls so the acceptance chain receives
the request it constructed.
@qwen-code-review-bot

qwen-code-review-bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Qwen Triage finished — view run. See the stage comments in this thread for the result.

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

@yiliang114 yiliang114 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.

LGTM. The stub now targets the three-argument createRequest overload that RuntimeBrokerService#provisionRequest calls, and any() also matches the null isolation key path; the production default still delegates three-arg to two-arg, so the change stays test-only. Verified at 8c86b02 that the mock is only handed to RuntimeBrokerService, that service has a single createRequest call site, and the fixture request is therefore the one the binding repository receives.

@yiliang114 yiliang114 added category/core Core engine and logic scope/testing Test frameworks and cases type/bug Something isn't working as expected labels Oct 17, 2026
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR — and for writing the cause up properly in #14494 instead of just shipping a green run.

Template looks good ✓ — every required heading is present, and the Chinese section is a real translation rather than a stub.

Problem: observed, not theoretical. #14494 is a P1 with a concrete failing run and job link on the Runtime Broker / Managed Agent MariaDB lane, and you reproduced it locally and recovered the cause the 400 had swallowed (IllegalArgumentException: request is required out of JdbcRuntimeBindingRepository.requireRequest). That is exactly the evidence this gate asks for.

Direction: aligned. A red P1 CI lane, fixed in the test's mock rather than by bending product code to satisfy a stale test. There is no product-surface or mission question here, so the CHANGELOG direction signal doesn't really apply — this is internal Java test infrastructure.

Size: not applicable. One file, +1/−1, entirely test code. No Stage 0 core path is touched — packages/managed-agent-server/src/test/java/… is neither packages/core/src/** nor any of the auth / providers / models / config / tools / services trees, and the change stays inside a single package.

Approach: minimal, and I checked the diagnosis against the source rather than taking the description on faith:

  • RuntimeBrokerService.provisionRequest calls the three-argument createRequest(scope, isolationKey, harnessSessionId), and it is the only createRequest call site in that class — so the mock had to stub that overload.
  • RuntimeProvisioner's three-argument form is a default method that delegates to the two-argument one, which confirms your claim that production provisioners keep their previous behaviour. Only the mock stopped matching.
  • Mockito does not execute interface default methods, so the unstubbed three-argument call returned null — that lines up exactly with the recovered stack trace.
  • The second argument is null when the isolation class isn't session, and any() matches nulls, so the new stub still fires on that path.

I also checked whether the same stale mock exists elsewhere, since a partial fix here would just move the failure. The other five mock(RuntimeProvisioner.class) sites are all in WorkspaceRuntimeTest: two stub the two-argument form on a delegate that genuinely is called two-argument through WorkspaceRuntimeProvisioner (correct as written), and the other three never stub createRequest at all. So this is the only stale mock, and the one-line scope is complete rather than partial.

One thing worth raising before merge — not a blocker, and I agree with leaving it out of this diff: the reason this cost hours is that RuntimeBrokerHttpServer.sendError maps an internal IllegalArgumentException to a client-facing 400 with no cause attached and nothing logged. You note the follow-up in Risk & Scope, but nothing tracks it yet, and this PR closes #14494 — after which the only record of the diagnostic gap is a comment thread on a bug marked fixed. Filing it as its own issue before this merges would keep it alive.

Risk: no elevated risk signals — the Stage 1e revert-correlated path patterns match nothing in this diff.

Moving on to code review. 🔍

中文说明

感谢贡献 —— 也感谢你在 #14494 里把根因写清楚,而不是只丢一个绿色的 CI 结果。

模板:完整 ✓ —— 所有必需小节都在,中文部分是真正的翻译而不是占位。

问题:已观测到的 bug,不是理论性加固。#14494 是 P1,附带了 Runtime Broker / Managed Agent MariaDB 通道上具体的失败 run 与 job 链接;你在本地复现并还原了被 400 吞掉的根因(JdbcRuntimeBindingRepository.requireRequest 抛出的 IllegalArgumentException: request is required)。这正是本关卡要求的证据。

方向:对齐。一条红色的 P1 CI 通道,修的是测试里的 mock,而不是为了让过期测试通过去改动产品代码。这里不涉及产品面或方向性取舍,所以 CHANGELOG 方向信号并不适用 —— 属于内部 Java 测试基础设施。

规模:不适用。1 个文件,+1/−1,全部是测试代码。未触及 Stage 0 的任何核心路径 —— packages/managed-agent-server/src/test/java/… 既不是 packages/core/src/**,也不属于 auth / providers / models / config / tools / services 目录,且改动只在单个 package 内。

方案:足够精简,而且我对照源码核实了诊断,没有只采信 PR 描述:

  • RuntimeBrokerService.provisionRequest 调用的是三参形式 createRequest(scope, isolationKey, harnessSessionId),且这是该类中唯一的 createRequest 调用点 —— 所以 mock 必须 stub 这个重载。
  • RuntimeProvisioner 的三参形式是 default 方法,会委托给二参形式,这印证了你说的"生产侧 provisioner 行为不变",只有 mock 不再匹配。
  • Mockito 不会执行接口的 default 方法,因此未被 stub 的三参调用返回 null —— 与你还原出的堆栈完全吻合。
  • 当 isolation class 不是 session 时第二个参数为 null,而 any() 同样匹配 null,所以新的 stub 在该路径上依然生效。

我还确认了这个过期 mock 是否存在于别处,因为只修一半只会把失败挪个位置。另外五处 mock(RuntimeProvisioner.class) 全部在 WorkspaceRuntimeTest:其中两处是在 delegate 上 stub 二参形式,而该 delegate 确实是通过 WorkspaceRuntimeProvisioner 以二参形式被调用的(写法正确);另外三处根本没有 stub createRequest。所以这是唯一的过期 mock,一行的改动范围是完整的,不是局部修补。

有一点想在合并前提出来 —— 不是阻塞项,我也同意不要塞进这个 diff:这件事之所以花了几个小时,是因为 RuntimeBrokerHttpServer.sendError 把内部的 IllegalArgumentException 映射成面向客户端的 400,既不挂 cause 也不打日志。你在 Risk & Scope 里提到了后续处理,但目前没有任何 issue 跟踪它,而本 PR 会关闭 #14494 —— 关闭之后,这个诊断盲区唯一的记录就只剩一个已修复 bug 下的评论串。合并前单独开一个 issue,能让它不被一起埋掉。

风险:无升级风险信号 —— Stage 1e 中与回滚相关的高风险路径模式在本 diff 中没有任何命中。

进入代码审查 🔍

— Qwen Code · qwen3.8-max-2026-09-02

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

@qwen-code-review-bot

qwen-code-review-bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Code review

Before reading the diff I wrote down what I would have done from the title and the "Why it's needed" section alone: find the call site the Broker actually uses, stub that overload, leave production alone. That is what this PR does, and the one-line shape is the right one — the alternatives I considered are all worse here. Stubbing both overloads would be defensive code for a call shape nothing uses; CALLS_REAL_METHODS on the mock would pull in every other default method and change what the fixture asserts; and fixing it in JdbcRuntimeBindingRepository would mean bending product code to tolerate a null request, which is precisely the wrong direction.

I verified the three load-bearing claims against the source rather than accepting the description:

  • RuntimeBrokerService.provisionRequest calls the three-argument createRequest(scope, isolationKey, harnessSessionId), and it is that class's only createRequest call site — so the two-argument stub was genuinely dead and the three-argument one is genuinely required.
  • RuntimeProvisioner's three-argument form is a default delegating to the two-argument form, so no production provisioner changes behaviour. The blast radius really is the mock.
  • Mockito does not run interface default methods, so the unstubbed call returned null — matching the recovered requireRequest stack exactly.

One detail worth confirming, because it decides whether the new stub fires at all: the Broker passes null as the isolation key when the scope's isolation class isn't session, and any() matches nulls, so the stub still answers on that path.

No correctness, security, or regression concerns, and no AGENTS.md violations. The managed-agent module asks for mysql-integration runs on SQL/transaction changes; this touches neither — it is a fixture stub in a test that only runs under that profile — and the change stays well inside the module's ownership boundary. Scope is complete rather than partial: I checked all five other mock(RuntimeProvisioner.class) sites and none of them carries the same staleness (details in the Stage 1 comment).

Nothing here blocks merge. The one non-blocking follow-up is the untracked observability gap I raised in Stage 1 — sendError swallowing the cause is what turned a one-line mock fix into a multi-hour investigation, and closing #14494 removes its only tracker.

Test evidence — the PR's own CI, via the API

This is an unattended CI run, so per the review rules I executed nothing from this PR: no Maven, no build, no test, no checkout. The evidence below is the PR's own check-runs on the reviewed commit, read through the API, plus static reading of the diff and the surrounding source.

The headline is that this PR's CI never runs the test it fixes. Select the Managed Agent tier chose fast — managed-agent-gate.yml picks tier=fast for every pull_request event — and the mysql-integration job in sdk-java.yml carries if: inputs.tier != 'fast'. So the lane that owns WorkspaceCsiSqlWorkerMySqlIT is reported as skipped:

  • SDK Java (fast) / Runtime Broker and Managed Agent MariaDB / Java 21 → completed / skipped

That is not a defect in this PR; it is how the tiering works, and the same is true of every other SDK Java (fast) lane marked skipped below. But it does mean a green run here proves nothing about the fix.

The good news is that the gap closes on its own. The full tier — the only lane that executes this test — is triggered by the first maintainer approval of this head (pull_request_review + approved + OWNER/MEMBER/COLLABORATOR → tier=full), and the gate workflow says so itself: "Fast tier passed. On a pull request the full suite runs on the first approval of this head." Because a touched PR's fast tier reports under the name Managed Agent fast tier, the required Managed Agent gate check stays expected until that post-approval full run reports it. An approval is therefore not a merge — if this stub change is wrong, the MariaDB lane goes red after approval and the required gate never turns green.

That promotion has already started: yiliang114 approved this head while this review was running, and the pull_request_review arm of Managed Agent gate is now pending on 8c86b02. So the full-tier run that actually executes WorkspaceCsiSqlWorkerMySqlIT on MariaDB is queued — the oracle for this fix is in flight rather than hypothetical, and its result, not the fast-tier board below, is what settles the claim. That human approval is a separate vote: the bot's own commit-pinned approval still follows via the deferred marker once CI lands green.

At review time these were still in progress: Qwen Code CI, Managed Agent gate, Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox), SDK Java (fast) / ubuntu-latest / Java 21, SDK Java (fast) / Real daemon E2E / Java 11, and Build and verify reference distribution. Nothing was red, so there is no failing-job log to quote. I fetched the checks once and did not poll — the unit suite outlives any in-agent wait — so the table below is a snapshot, and the finalize job rewrites it in place once CI settles.

CI results for 8c86b02 — snapshot at review time; this table auto-updates as CI workflows complete:

Check Conclusion
Build and verify reference distribution ⏳ running
Integration Tests (no-AK, No Sandbox) ⏳ running
Lint & Static (ubuntu-latest, Node 22.x) ⏳ running
SDK Java (fast) / Real daemon E2E / Java 11 ⏳ running
SDK Java (fast) / ubuntu-latest / Java 21 ⏳ running
Test (ubuntu-latest, Node 22.x) ⏳ running
delay-automatic-review ⏳ running
triage ⏳ running
SDK Java (fast) / Runtime Broker and Managed Agent MariaDB / Java 21 ⏭️ skipped (fast tier)
SDK Java (fast) / Hosted failover E2E / Java 21 ⏭️ skipped (fast tier)
SDK Java (fast) / Hosted process fault gates / MySQL 8.4 / Java 21 ⏭️ skipped (fast tier)
SDK Java (fast) / EventTransport broker gates / RocketMQ 5.5.1 / Java 21 ⏭️ skipped (fast tier)
SDK Java (fast) / EventTransport multi-node gates / RocketMQ 5.5.1 + MySQL 8.4 / Java 21 ⏭️ skipped (fast tier)
Test (macos-latest, Node 22.x) ⏭️ skipped
Test (windows-latest, Node 22.x) ⏭️ skipped
Integration Tests (CLI, No Sandbox) ⏭️ skipped
tmux-testing ⏭️ skipped
verify ⏭️ skipped
SDK Java (fast) / Flyway migration version uniqueness ✅ success
Select the Managed Agent tier ✅ success
Classify PR ✅ success
assign ✅ success
authorize ✅ success
label ✅ success

Sandboxed verification would settle this: @qwen-code /verify — the claim that stubbing the three-argument overload turns WorkspaceCsiSqlWorkerMySqlIT.sqlGrantExecutesOriginalWorkerAndContinuesWithRetainedHistory green is not observable anywhere in this PR's CI, because the MariaDB lane is skipped on the fast tier; right now the only support for it is the author's local macOS run, which I have not re-run. /tmux does not apply — there is no TUI surface in this change. Worth noting that for this particular test the authoritative oracle is the repo's own full-tier MariaDB lane, which the first approval of this head triggers automatically, so the deferred-approval path really does end up gated on the thing in question rather than on a proxy.

Not verified: that the test passes on MariaDB. The lane is skipped on this PR's CI and I ran no PR-derived code. The before/after Maven output in the PR body — including the recovered IllegalArgumentException stack and the 14.55 s passing run — is the author's claim from a local macOS run, not evidence I reproduced. mvn verify and mvn checkstyle:check were likewise not re-run here. The pending full-tier run named above is what will close this gap.

中文说明

代码审查

在看 diff 之前,我先根据标题和「为什么需要」写下了自己会怎么做:找到 Broker 实际使用的调用点,stub 那个重载,不动产品代码。这个 PR 正是如此,一行的形态也是对的 —— 我考虑过的其他方案在这里都更差。同时 stub 两个重载是为一个没人使用的调用形态写防御代码;对 mock 用 CALLS_REAL_METHODS 会把其他所有 default 方法一起拉进来,改变 fixture 的断言语义;而在 JdbcRuntimeBindingRepository 里修,则意味着让产品代码去容忍 null request,方向完全错了。

我对照源码核实了三个关键论断,而不是采信描述:

  • RuntimeBrokerService.provisionRequest 调用三参形式 createRequest(scope, isolationKey, harnessSessionId),且这是该类唯一的 createRequest 调用点 —— 所以二参 stub 确实是死代码,三参 stub 确实必需。
  • RuntimeProvisioner 的三参形式是委托给二参形式的 default 方法,因此没有任何生产 provisioner 的行为发生变化。影响面确实只在 mock。
  • Mockito 不执行接口 default 方法,所以未被 stub 的调用返回 null —— 与还原出的 requireRequest 堆栈完全一致。

还有一个细节值得确认,因为它决定新 stub 是否会生效:当 scope 的 isolation class 不是 session 时,Broker 传入的 isolation key 是 null,而 any() 同样匹配 null,所以该路径上 stub 依然会应答。

没有正确性、安全性或回归方面的问题,也没有违反 AGENTS.md。managed-agent 模块要求 SQL/事务改动跑 mysql-integration;本 PR 两者都没碰 —— 它只是某个仅在该 profile 下运行的测试里的 fixture stub —— 且改动完全留在模块的所有权边界内。范围是完整的而非局部的:我检查了另外五处 mock(RuntimeProvisioner.class),都没有同样的过期问题(详见 Stage 1 评论)。

这里没有阻塞合并的问题。唯一的非阻塞后续项,是我在 Stage 1 提到的、尚未被跟踪的可观测性缺口 —— sendError 吞掉 cause,正是它把一行的 mock 修复变成了一次数小时的排查,而关闭 #14494 会移除它唯一的跟踪载体。

测试证据 —— 通过 API 读取本 PR 自己的 CI

这是一次无人值守的 CI 运行,因此按审查规则我没有执行本 PR 的任何代码:没有 Maven、没有构建、没有测试、没有 checkout。下面的证据是通过 API 读取的、该 commit 上本 PR 自己的 check-run 结果,外加对 diff 与周边源码的静态阅读。

关键结论是:本 PR 的 CI 从未运行它所修复的那个测试。Select the Managed Agent tier 选择了 fast —— managed-agent-gate.yml 对每个 pull_request 事件都取 tier=fast —— 而 sdk-java.yml 中的 mysql-integration job 带有 if: inputs.tier != 'fast'。因此拥有 WorkspaceCsiSqlWorkerMySqlIT 的那条通道被报告为 skipped:

  • SDK Java (fast) / Runtime Broker and Managed Agent MariaDB / Java 21 → completed / skipped

这不是本 PR 的缺陷,而是分级机制本身如此,下面其他标为 skipped 的 SDK Java (fast) 通道也是同理。但这确实意味着这里的绿色结果无法证明修复有效。

好消息是这个缺口会自行闭合。full tier —— 唯一会执行该测试的通道 —— 由对该 head 的首次维护者审批触发(pull_request_review + approved + OWNER/MEMBER/COLLABORATOR → tier=full),gate workflow 自己也这么写:「Fast tier passed. On a pull request the full suite runs on the first approval of this head.」。由于被改动 PR 的 fast tier 以 Managed Agent fast tier 这个名字上报,必需的 Managed Agent gate 检查会一直保持 expected 状态,直到审批后的 full run 上报为止。因此审批并不等于合并 —— 如果这个 stub 改动是错的,审批后 MariaDB 通道会变红,必需的 gate 永远不会转绿。

这个提级已经开始:yiliang114 在本次审查运行期间审批了该 head,Managed Agent gate 的 pull_request_review 分支目前在 8c86b02 上处于 pending。也就是说,真正会在 MariaDB 上执行 WorkspaceCsiSqlWorkerMySqlIT 的 full-tier 运行已进入队列 —— 该修复的权威判据已在运行中而非假设性的,能定论此论断的是它的结果,而不是下面这块 fast-tier 面板。那次人工审批是独立的一票:bot 自己绑定 commit 的审批仍会在 CI 全绿后通过延迟标记提交。

审查时以下检查仍在进行中:Qwen Code CI、Managed Agent gate、Test (ubuntu-latest, Node 22.x)、Lint & Static、Integration Tests (no-AK, No Sandbox)、SDK Java (fast) / ubuntu-latest / Java 21、SDK Java (fast) / Real daemon E2E / Java 11、Build and verify reference distribution。没有红色项,因此没有失败 job 的日志可引用。我只抓取了一次检查结果、没有轮询 —— 单元测试套件的耗时会超出任何 agent 内的等待预算 —— 所以下表是一个快照,CI 落定后 finalize 任务会就地重写它。

沙箱验证可以定论此事:@qwen-code /verify —— 「stub 三参重载能让 WorkspaceCsiSqlWorkerMySqlIT.sqlGrantExecutesOriginalWorkerAndContinuesWithRetainedHistory 变绿」这一论断,在本 PR 的 CI 中任何地方都观测不到,因为 MariaDB 通道在 fast tier 上被跳过;目前唯一的支持是作者本地的 macOS 运行,我并未重跑。/tmux 不适用 —— 本改动没有 TUI 界面。值得一提的是,对这个具体测试而言,权威判据是仓库自己的 full-tier MariaDB 通道,而它会由对该 head 的首次审批自动触发,所以延迟审批路径最终确实是被真正要验证的东西所把关,而不是被某个代理指标把关。

未验证:该测试在 MariaDB 上是否通过。该通道在本 PR 的 CI 上被跳过,我也没有运行任何 PR 派生代码。PR 正文中的 before/after Maven 输出 —— 包括还原出的 IllegalArgumentException 堆栈和 14.55 秒通过的运行 —— 是作者本地 macOS 运行的自述,不是我复现的证据。mvn verify 与 mvn checkstyle:check 同样未在此重跑。上面提到的、正在排队的 full-tier 运行才是补上这一缺口的东西。

— Qwen Code · qwen3.8-max-2026-09-02

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

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — the diagnosis holds up against the source and the change is one line of test mock that cannot reach product code; the reservation is that nothing in this PR's own fast-tier CI executes the test it fixes, plus one untracked follow-up.

Stepping back: this is a well-run bugfix. The author hit a red P1 lane, refused to accept the masked 400 at face value, instrumented the swallowed exception to recover the real cause, correctly located the fault in the test rather than in the Broker, and then fixed exactly that and nothing more. The write-up explains why production is unaffected — the three-argument default delegating to the two-argument one — which is the part a reviewer would otherwise have to go and establish themselves. I checked it, and it is true.

My independent proposal and the PR's approach are the same one-line change, so there is no simpler path I found that it missed. That is worth saying plainly, because it is the main thing this stage is for: I went looking for an over-engineered alternative and there isn't one. Every plausible "more robust" variant is worse — stubbing both overloads defends a call shape nothing uses, CALLS_REAL_METHODS would change what the fixture asserts, and touching JdbcRuntimeBindingRepository to tolerate a null request would be bending product code to accommodate a broken test.

On the question this gate exists to ask — does the problem actually exist? — yes, and unusually well-evidenced: a linked P1 with a specific run and job, a recovered stack trace, and a before/after that isolates the stub as the only variable. This is not theoretical hardening.

Six months from now this reads fine: a one-line fixture stub with an adjacent issue explaining the failure mode. The diff carries no unrelated edits, no drive-by refactor, and no formatting churn — the minimal-change bar is met exactly.

My one real reservation is evidential rather than technical. The lane that owns this test is gated behind inputs.tier != 'fast', and every pull_request run gets the fast tier, so a fully green board here would still not have executed WorkspaceCsiSqlWorkerMySqlIT. The before/after Maven output is the author's local macOS run, which I did not and cannot re-run under these review rules. What makes this acceptable rather than a defer is the repo's own machinery: the first maintainer approval of this head promotes the tier to full, which runs the MariaDB lane, and because a touched PR's fast tier reports under a different check name, the required Managed Agent gate stays unreported until that full run lands. Approving therefore triggers the authoritative verification instead of substituting for it — if this stub is wrong, the gate goes red and the PR cannot merge. That is a stronger guarantee than my reading the diff alone.

The non-blocking item, restated so it does not get lost: RuntimeBrokerHttpServer.sendError maps an internal IllegalArgumentException to a client-facing 400 with no cause attached and nothing logged. That is what turned a one-line mock fix into a multi-hour investigation, and this PR closes #14494 — the only place that gap is currently recorded. Filing it as its own issue before merge would keep it alive. Worth doing, not worth blocking on.

Verdict: approve. CI is still running (2 pull_request workflow runs in flight at review time), so no approval is posted in this run — approval is deferred until CI lands green on 8c86b02bf097a7408e1e1848dd29b4e13e1d8cdd, and the finalize job posts it pinned to that exact commit.

中文说明

信心:4/5 —— 诊断在源码层面站得住,改动只有一行测试 mock,触达不到产品代码;保留意见在于本 PR 自己的 fast-tier CI 中没有任何环节执行它所修复的测试,另加一个尚未被跟踪的后续项。

退一步看整体:这是一次处理得很扎实的修复。作者遇到一条红色的 P1 通道,没有把被掩盖的 400 当作表面结论接受,而是给被吞掉的异常加了打印以还原真正的根因,正确地把问题定位在测试而非 Broker,然后只修这一处、不多做别的。PR 描述解释了为什么生产侧不受影响 —— 三参 default 委托给二参形式 —— 而这正是审查者本来需要自己去确认的部分。我核实过了,确实如此。

我独立想到的方案与 PR 的方案是同一个一行改动,所以我没有找到它遗漏的更简路径。这一点值得明说,因为这本阶段的主要目的:我去找了有没有过度设计的替代方案,结果没有。所有看似「更健壮」的变体都更差 —— 同时 stub 两个重载是在为一个无人使用的调用形态做防御;CALLS_REAL_METHODS 会改变 fixture 的断言语义;而为了让 JdbcRuntimeBindingRepository 容忍 null request 去改产品代码,则是为了迁就一个坏测试而扭曲产品逻辑。

关于本关卡存在的核心问题 —— 这个问题真的存在吗? —— 存在,而且证据异常充分:一个带具体 run 与 job 的 P1 关联 issue、一段还原出的堆栈、以及一次把 stub 隔离为唯一变量的 before/after。这不是理论性加固。

六个月后再看这段代码没有问题:一行 fixture stub,旁边有一个解释失败模式的 issue。diff 中没有无关改动、没有顺手重构、没有格式化噪音 —— 精确满足最小改动的标准。

我唯一实质性的保留是证据层面的,而非技术层面的。拥有该测试的通道被 inputs.tier != 'fast' 拦住,而每次 pull_request 运行都拿到 fast tier,所以这里即使全绿,也仍然没有执行过 WorkspaceCsiSqlWorkerMySqlIT。before/after 的 Maven 输出是作者本地 macOS 的运行结果,按本次审查规则我没有也无法重跑。让这件事可以接受、而不必转为 defer 的,是仓库自身的机制:对该 head 的首次维护者审批会把 tier 提升为 full,从而运行 MariaDB 通道;并且由于被改动 PR 的 fast tier 以另一个检查名上报,必需的 Managed Agent gate 会一直保持未上报状态,直到那次 full run 落定。因此审批实际上是触发了权威验证,而不是替代它 —— 如果这个 stub 是错的,gate 会变红,PR 无法合并。这比我单靠阅读 diff 得到的保证更强。

非阻塞项,重述一遍以免被忽略:RuntimeBrokerHttpServer.sendError 把内部的 IllegalArgumentException 映射成面向客户端的 400,既不挂 cause 也不打日志。这正是把一行的 mock 修复变成数小时排查的原因,而本 PR 会关闭 #14494 —— 目前记录该缺口的唯一位置。合并前单独开一个 issue 能让它存续下去。值得做,但不值得为此阻塞。

结论:approve(通过)。CI 仍在运行(审查时有 2 个 pull_request workflow 在跑),所以本次运行不提交审批 —— 审批延迟到 CI 在 8c86b02bf097a7408e1e1848dd29b4e13e1d8cdd 上全绿之后,由 finalize 任务绑定到该确切 commit 提交。

— Qwen Code · qwen3.8-max-2026-09-02

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

@wenshao
wenshao enabled auto-merge (squash) October 17, 2026 18:14

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

评审结论:未发现本次改动引入的缺陷。

评审提交:8c86b02bf097a7408e1e1848dd29b4e13e1d8cdd(相对 f110a6b7fae47f1a5fd4f30646610a159d876103,仅 1 行测试替换)。

核对了完整测试及其调用链:这个 mock 交给 RuntimeBrokerService,其 provisionRequest 实际调用三参 createRequest(scope, isolationKey, harnessSessionId)。新 stub 对应正确的重载,继续返回原先由 SQL fixture 构造的 request;any() 也覆盖 isolation key 为 null 的情况。原有 HTTP 状态、SQL grant/settlement、文件内容、保留历史和 worker 关闭断言均保持不变。

独立验证:使用 JDK 21、项目对应的 Mockito 5.17.0 / Byte Buddy 1.17.8,编译此提交的真实 RuntimeProvisioner 及其所需类型后运行最小脚本。旧二参 stub 在三参调用时返回 null;新三参 stub 返回指定 request,含第二个参数为 null 的情况。脚本通过;git diff --check 通过。

验证边界:本次未运行完整 WorkspaceCsiSqlWorkerMySqlIT 或 mvn verify;当前工作树缺少 CLI/Core 构建产物。PR 中的 MariaDB CI 任务为 SKIPPED,作者提供的完整 MariaDB Before/After 结果未在本次评审中独立重跑。以上动态验证仅确认重载和 mock 行为,不等同于完整 SQL/HTTP/worker 链路通过。

@qqqys qqqys 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.

按 dws-review 频道的 Critical-only 流程复核 head 8c86b02bf097a7408e1e1848dd29b4e13e1d8cdd(1 文件,+1/-1,只改 WorkspaceCsiSqlWorkerMySqlIT.java 的一行 stub)。未发现可证明的 Critical。历史 Review 中没有阻塞问题:唯一一条是 APPROVED,本 PR 没有任何行内意见线程。

改动是把 when(provisioner.createRequest(any(), any())) 换成三参形式 when(provisioner.createRequest(any(), any(), any()))。这类"改 mock 重载"的一行改动,Critical 面只有一个:它是否让测试变得更空、或掩盖了生产调用契约。我把它依赖的前提在 head 上直接读了一遍,没有依赖他人结论:

  • 生产确实调用三参重载。 RuntimeBrokerService.provisionRequest(RuntimeScope scope, String harnessSessionId)(第 5346 行起)的调用是 provisioner.createRequest(scope, "session".equals(scope.getIsolationClass()) ? harnessSessionId : null, harnessSessionId),即三参形式,且第二个参数在非 session 隔离类下为 null。
  • 接口里两个重载都是 default,三参默认委派给二参。 RuntimeProvisioner 第 26 行的二参 default 直接构造 RuntimeProvisionRequest,第 37–39 行的三参 default 是 return createRequest(scope, isolationKey);。但 mock(RuntimeProvisioner.class) 会替换包括 default 在内的全部方法,委派在 mock 上并不发生——所以旧的二参 stub 对生产的三参调用完全不生效,fixture 构造的 request 并不是代码实际拿到的那个。本 PR 修的正是这一点,方向是让 stub 对齐真实调用点,而不是放松断言。
  • any() 覆盖 null 隔离键。 Mockito 的 any() 对引用类型匹配 null,因此生产在非 session 隔离类下传入的 null 也能命中该 stub,不会出现"stub 太窄导致仍返回 null"的残留问题。
  • 没有削弱任何断言。 diff 只有这一行,前后的 doAnswer(...).when(provisioner).ensureResource(any(), any(), any())、lease 的 doAnswer、以及该用例原有的 HTTP 状态、SQL grant/settlement、文件内容、保留历史与 worker 关闭断言都未改动;也没有新增 @Disabled 或跳过标记。
  • 改动是测试侧的,不影响生产契约。 接口中三参 default 委派给二参的行为、以及 WorkspaceRuntimeProvisioner / LocalProcessRuntimeProvisioner 等真实实现对两个重载的处理都未被本 PR 触及,因此真实实现上的委派语义不变。

未覆盖 / 非阻塞

WorkspaceCsiSqlWorkerMySqlIT 属于需要真实 MySQL 的 IT,sdk-java.yml 没有 pull_request 触发器,因此本 PR 的 CI 不含这条用例的执行证据;作者报告在 JDK 21 与项目对应的 Mockito/Byte Buddy 版本下编译并跑过该用例,这属于其提供的证据,我未重跑 Java/MySQL lane。我的结论建立在对 head 上生产调用点与接口重载的直接阅读上——这两点足以判定 stub 对齐的是真实调用契约。

CI:无失败项,SDK Java (fast) / Real daemon E2E / Java 11、Flyway migration version uniqueness、Build and verify reference distribution、Classify PR、Managed Agent tier 选择、assign、label、authorize、route 等均 pass;Test (ubuntu-latest, Node 22.x)、Lint & Static、Integration Tests (no-AK, No Sandbox)、SDK Java (fast) / ubuntu-latest / Java 21、triage 与 review-pr 在我发布时仍为 pending,按频道规则不等待、不轮询。

@wenshao
wenshao merged commit 39c237a into main Oct 17, 2026
120 of 122 checks passed
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Released in v0.25.3.

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

Labels

category/core Core engine and logic review/self-reported The linked issue was opened by the PR author (self-reported) scope/testing Test frameworks and cases type/bug Something isn't working as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(managed-agent): WorkspaceCsiSqlWorkerMySqlIT gets 400 runtime_broker_invalid_request from tool-sessions:acquire on the MariaDB lane

4 participants