Skip to content

feat(cli): Declare the v2 execute/status/cancel Managed Runtime contract - #12630

Merged
wenshao merged 5 commits into
QwenLM:mainfrom
doudouOUC:feat/managed-runtime-tool-contract
Sep 25, 2026
Merged

wenshao merged 5 commits into
QwenLM:mainfrom
doudouOUC:feat/managed-runtime-tool-contract

Conversation

@doudouOUC

@doudouOUC doudouOUC commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Defines a shared v2 contract for the owned Managed Runtime's execute, status, and cancel operations. Calls use their original reference; status is read-only and returns unknown with HTTP 200 when the Runtime has no record. A settled response must contain a result, and other states must not. The declaration manifest includes the three routes, while the raw gate continues to admit only attestation until each real handler lands.

Why it's needed

The worker and Java transport need one reviewed target before tool execution is connected. Shared fixtures pin the closed envelopes, stable error vocabulary, route-specific limits, all five states and four execution outcomes, and optional non-negative status cursors. Cancellation also accepts the Runtime's unknown answer without converting it into a retryable error.

Reviewer Test Plan

How to verify

  • Confirm execute requests are capped at 256 KiB, status/cancel requests at 16 KiB, and all three responses at 1 MiB.
  • Confirm the shared corpus rejects duplicate route suites, unknown error codes, invalid canonical or replacement header objects, settled responses without results, and malformed error envelopes. Status requests with and without a cursor should validate; negative cursors should fail.
  • Start the attestation-only worker and verify attestation succeeds, while execute/status/cancel return the gate's empty 404. Declaring a route must not expose an unimplemented handler.
  • Verify cancellation accepts unknown on both active and fenced calls. The status transport must validate its wire envelope and project it to the Broker's closed state/result shape when implemented.

Evidence (Before & After)

Before these review fixes, 11 independent contract assertions failed. After the fixes, all 17 reproduction assertions pass. The focused TypeScript contract and worker suites pass 85 tests (77 contract + 8 worker); the Java conformance and Broker suites pass 96 tests (6 + 90). A real bundled worker returns attestation 200 and empty 404 responses for all three tool routes. Full build, bundle, typecheck, and changed-file lint/format checks pass. Reverse verification rejects all 12 schema regressions and all 4 Java fixture/limit regressions.

Tested on

OS Status
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

macOS arm64, Node 22.22.2, JDK 21.

Risk & Scope

  • Main risk or tradeoff: this fixes the future tool contract without mounting tool handlers. Each handler must expand raw-gate admission in the same change that implements it.
  • Not validated / out of scope: tool handlers, the HTTP execute/status/cancel adapter, and actual tool delivery. The response-parts element shape is explicitly deferred until it can be derived from the worker's actual SDK Part path and covered in both languages. The existing unused execute stub must be replaced; the already-shipped reconciler requires the adapter to strip wire-only protocol and cursor fields.
  • Breaking changes / migration notes: attestation behavior remains intact. Tool fixtures now enforce the documented constraints. An unknown cancellation answer is accepted without claiming the call did not run.

Linked Issues

Related to #12380 and the reconciler delivered in #12655.

Design: English / 简体中文.

中文说明

这个 PR 做什么

为 owned Managed Runtime 的 execute、status、cancel 操作定义共享 v2 契约。调用按原始 reference 识别;status 只读,Runtime 没有记录时以 HTTP 200 返回 unknown。settled 响应必须携带 result,其他状态禁止携带。声明清单包含这三条路由,但在真实处理器落地之前,raw gate 仍只放行 attestation。

为什么需要

worker 与 Java transport 在接通工具执行之前需要统一的评审目标。共享 fixtures 固定封闭信封、稳定错误码、逐路由上限、五种状态与四种执行结局,以及可选的非负 status 游标。取消路径也接受 Runtime 的 unknown 应答,不再把它转换为可重试错误。

评审测试计划

如何验证

  • 确认 execute 请求上限为 256 KiB,status/cancel 请求上限为 16 KiB,三者响应上限均为 1 MiB。
  • 确认共享语料拒绝重复路由 suite、未知错误码、非法 canonical 或覆写请求头对象、缺少 result 的 settled 响应,以及非法错误信封。带或不带游标的 status 请求均应有效;负游标应失败。
  • 启动仅提供 attestation 的 worker,确认 attestation 成功,execute/status/cancel 返回 gate 的空体 404。声明路由不能暴露尚未实现的处理器。
  • 确认活跃与已隔离调用的取消路径都接受 unknown。后续 status transport 必须校验线上信封,并投影为 Broker 的封闭 state/result 形态。

前后证据

修复前,独立契约复现有 11 项断言失败;修复后 17 项全部通过。定向 TypeScript 契约与 worker 测试通过 85 项(77 + 8),Java 一致性与 Broker 测试通过 96 项(6 + 90)。真实 bundle worker 的 attestation 返回 200,三条工具路由均返回空体 404。完整 build、bundle、typecheck,以及改动文件 lint/format 检查通过。反向验证抓住全部 12 种 schema 退化和 4 种 Java fixture/上限退化。

测试平台

OS 状态
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

环境

macOS arm64、Node 22.22.2、JDK 21。

风险与范围

  • 主要风险或取舍:本变更固定未来的工具契约,不挂载工具处理器。每个处理器必须在实现的同一变更中扩展 raw gate 放行。
  • 未验证或范围外:工具处理器、HTTP execute/status/cancel 适配器与实际工具结果交付。response-parts 元素结构明确推迟到能从真实 worker 的 SDK Part 路径确定、并在两种语言中覆盖时再固定。现有未使用的 execute 占位实现需要替换;已落地的对账器要求适配器剥离仅用于线上信封的协议与游标字段。
  • 破坏性变更或迁移:attestation 行为保持不变;工具 fixtures 现在强制执行文档所述约束。取消返回 unknown 时可被接受,但不能据此认定调用没有运行。

关联

关联 #12380 与已在 #12655 交付的对账器。

设计:English / 简体中文。

@wenshao

wenshao commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Local real-environment verification: head d9f241db

Verdict: the contract slice itself is sound and the author's test claims reproduce. Before merge (or as the first line of the next slice), fix one gap the triage review did not find: the new cancel → 200 {state:"unknown"} rule turns into a retryable 503 in the existing RuntimeBrokerService. Nothing else I found blocks merge. The triage review (stage 2/3) needs one correction, covered below.

Environment: macOS arm64, Node 24.18.1, pnpm install --frozen-lockfile + npm run bundle, Zulu JDK 21. Base arm = the PR tree with only managed-runtime-attestation-contract.ts reverted to 1d30ddc9fa and re-bundled. All probes were temporary and are reverted. The worktree is clean at d9f241db.

What reproduces

Claim Result
vitest run managed-runtime-attestation-contract.test.ts: 54 pass ✅ 54/54 (plus the worker suite: 62/62 across both files)
mvn test -Dtest=ManagedRuntimeAttestationConformanceTest: 6 pass ✅ 6/6. The full runtime-broker module is 112/112 on JDK 21
"no handler mounted, the tool paths still 404" ✅ status is still 404 on the real bundled worker (Fig 1), but see F2

F1 (new, the one to fix): cancel/unknown-is-ok is rejected by the existing Broker

I fed the PR's own cancel fixtures, verbatim, as the transport response into the real RuntimeBrokerService.cancelExecution. I used both paths that actually reach transport.cancel: a live EXECUTING claim, and a lapsed claim fenced to UNKNOWN while the invocation is still running.

Fig 2

  • in-flight-cancel-requested and prepared-settles-cancelled are absorbed correctly on both paths.
  • unknown-is-ok fails on both paths with 503 runtime_execution_cancel_failed, retryable=true ("Runtime cancellation returned an invalid status"). The cause is RuntimeBrokerService.RUNTIME_EXECUTION_STATES = {prepared, executing, cancel_requested, settled} (line 27). The RuntimeTransport javadoc lists the same four states.
  • This is exactly the case the contract is written for: the Broker calls transport.cancel on UNKNOWN records, and a restarted Runtime will answer unknown. Today that becomes a retryable error, so the retry can never succeed.
  • Candidate fix, tested: add "unknown" to that set, and update the javadoc to match. Both rows then succeed (EXECUTING → CANCEL_REQUESTED, UNKNOWN stays UNKNOWN), and RuntimeBrokerServiceTest stays green (61/61 existing tests).
  • This is not a transport concern. The check lives in the Broker core, so no HttpRuntimeTransport slice will fix it.

F2: gate admission without a handler contradicts the attestation design's own rule

Fig 1

I ran the real dist/cli.js managed-runtime-worker with a boot payload on stdin and sent raw fetch() requests. On the PR head, the three new paths now pass the raw gate, fall through to Express's finalhandler, and come back as text/html "Cannot POST …" pages, before authentication runs, including an unauthenticated 300 KiB POST. On the base build they got the gate's empty 404. The triage review already noted that the body changed. What it did not note is that docs/design/2026-09-22-managed-runtime-attestation-contract.md forbids this change (lines 47/83/101):

"Each operation is added when its real handler is extracted, in the same change that registers it. This prevents a manifest entry from claiming that a route exists when main has no implementation."

The new doc says it follows "the manifest rule that a route joins with its real handler in the same change", while doing the opposite. The old doc still says the manifest "currently contains the one route". There is no security impact: no body is parsed, and status and classification are unchanged. But either update that rule, or keep the manifest entries out of the gate until the handler lands.

F3: the shared schema enforces fewer of the doc's rules than the prose claims (plus a candidate patch)

Fig 3

  • The PR schema accepts 7 documents that break rules stated in §3.2/§3.3. Among them: a status request carrying toolName + input (the doc says status is read-only), result on a non-settled state, and lastSequence on execute or cancel.
  • Mutation matrix over the contract artifacts, 8 mutants × both suites: with the PR schema, 3/8 survive both suites (M4 settled→unknown, M6 execute gains lastSequence, M8 ok case loses its body).
  • Candidate schema patch (+117/−2, Ajv-strict clean, fixtures untouched). It adds if state=settled then result else no result, requires a body on ok cases, and adds per-route request/response shapes via if route=…. It rejects all 7 violations, kills 8/8 mutants, and both suites stay green (54/54 TS, 6/6 Java). Taking it in this PR is optional. The alternative is to state in §3.4 that these rules are enforced only by the Java field-set assertions.

Correction to the triage review

The review says HttpRuntimeTransport.execute() is "already reachable from RuntimeBrokerService.dispatch". It is not.

  • HttpRuntimeTransport does not implement RuntimeTransport, and no production class implements that interface.
  • The only production use is LocalProcessRuntimeProvisioner calling transport.attest(...).
  • HttpRuntimeTransport.execute has zero callers, in production or in tests.

The envelope mismatch the review describes is real. I reproduced it against a Node server that enforces this PR's schema (Fig 2 B): 5 unevaluated fields, no toolName/input, and a schema-valid 20.6 KB result rejected as 413 by the 16 KiB cap. But the code involved is dead, untested preview code from #12552, so the follow-up is replacing a stub, not rewriting a live path. Deleting that method, or marking it @Deprecated with a pointer to this contract, would stop the next reviewer from rediscovering the mismatch. RuntimeTransport.execute(lease, session, reference) also has no toolName/input to send, and the Broker stores neither. The next slice has to widen that interface.

Nits

  • Error codes and messages carry attestation names, and both existing …_too_large emitters hardcode "exceeds 16 KiB" (same as the triage review).
  • No oversized-body fixture exists for status/cancel (16 KiB).
  • prepared has no fixture.
  • error is allowed on a success result.
中文说明

本地真实环境验证:head d9f241db

结论:契约切片本身成立,作者声明的测试结果可复现。合入前(或作为下一个切片的第一行改动)需要修一个 triage 评审没发现的问题:新规则 cancel → 200 {state:"unknown"} 在现有 RuntimeBrokerService 里会变成可重试的 503。 除此之外没有发现阻塞合入的问题。triage 评审(stage 2/3)有一处需要更正,见下文。

环境:macOS arm64,Node 24.18.1,pnpm install --frozen-lockfile + npm run bundle,Zulu JDK 21。base 臂 = PR 树只把 managed-runtime-attestation-contract.ts 回退到 1d30ddc9fa 后重新打包。所有探针都是临时的,已回退,工作树在 d9f241db 上是干净的。

可复现的部分

  • TS 契约测试 54/54;加上 worker 测试,两个文件共 62/62。
  • Java conformance 6/6;runtime-broker 全模块在 JDK 21 上 112/112。
  • 真实打包的 worker 上,三条新路径仍然返回 404(图 1),但见 F2。

F1(新发现,需要修):cancel/unknown-is-ok 被现有 Broker 拒绝

把 PR 自己的 cancel fixtures 原样作为 transport 响应,喂给真实的 RuntimeBrokerService.cancelExecution。两条真正会调用 transport.cancel 的路径都测了:存活的 EXECUTING 声明,以及调用仍在运行、声明已过期并被围栏为 UNKNOWN 的情况。

  • in-flight-cancel-requested 和 prepared-settles-cancelled 在两条路径上都被正确吸收。
  • unknown-is-ok 在两条路径上都抛出 503 runtime_execution_cancel_failed,且 retryable=true。 原因是 RUNTIME_EXECUTION_STATES 只有 prepared/executing/cancel_requested/settled 四个值(第 27 行),RuntimeTransport 的 javadoc 也只列了这四个。
  • 这恰恰是契约要覆盖的场景:Broker 会对 UNKNOWN 记录调用 transport.cancel,而重启过的 Runtime 会回答 unknown。现在这会变成可重试错误,重试永远不会成功。
  • 候选修复(已实测):在这个集合里加上 "unknown",并同步修改 javadoc。两行都变为成功(EXECUTING → CANCEL_REQUESTED,UNKNOWN 保持 UNKNOWN),RuntimeBrokerServiceTest 现有 61 个测试全绿。
  • 这不是 transport 层的问题。校验在 Broker 核心里,后续的 HttpRuntimeTransport 切片修不到它。

F2:没有处理器就放行,违反 attestation 设计文档自己的规则

用真实的 dist/cli.js managed-runtime-worker,从 stdin 传入 boot 数据,再发原始 fetch() 请求。在 PR head 上,三条新路径通过了原始门,落到 Express 的 finalhandler,返回 text/html 的 "Cannot POST …" 页面,而且发生在鉴权之前(未鉴权的 300 KiB POST 也一样)。base 构建返回的是门的空体 404。响应体的变化 triage 评审已经提到。它没提到的是:2026-09-22-managed-runtime-attestation-contract.md 第 47/83/101 行明确禁止这种做法——"每个操作在其真实处理器被提取时、于同一个变更中加入"。新文档声称遵守"同一变更"规则,实际做的却相反;旧文档仍写着 manifest "目前只有一条路由"。这没有安全影响(请求体不会被解析,状态码和分类都不变),但需要二选一:更新那条规则,或者在处理器落地之前不让这些条目进入门。

F3:共享 schema 实际强制的规则比文字描述少(附候选补丁)

  • PR 的 schema 接受 7 个违反 §3.2/§3.3 规则的文档。例如:status 请求携带 toolName+input(文档说 status 只读),非 settled 状态携带 result,execute/cancel 响应携带 lastSequence。
  • 对契约产物做了变异矩阵(8 个变异体 × 两套测试):用 PR 的 schema,有 3/8 的变异体两套测试都杀不掉(M4、M6、M8)。
  • 候选 schema 补丁(+117/−2,Ajv strict 下无报错,fixtures 不变):拒绝全部 7 个违规文档,变异体 8/8 被杀,两套测试仍全绿(TS 54/54,Java 6/6)。可以在本 PR 里采纳,也可以在 §3.4 说明这些规则只由 Java 字段集断言保证。

对 triage 评审的更正

评审说 HttpRuntimeTransport.execute() "已经可以从 RuntimeBrokerService.dispatch 到达"。这不成立。

  • HttpRuntimeTransport 没有实现 RuntimeTransport,生产代码里也没有任何类实现这个接口。
  • 生产代码唯一的用法是 LocalProcessRuntimeProvisioner 调用 attest()。
  • execute 没有任何调用方,生产代码和测试里都没有。

评审描述的信封不一致是真的,我已用一个强制执行本 PR schema 的 Node 服务端复现(图 2 B):多出 5 个字段、缺少 toolName/input,一个 20.6 KB 的合法结果被 16 KiB 上限拒成 413。但涉及的代码是 #12552 留下的、没有测试的预览死代码,所以后续工作是替换一个桩,而不是重写一条在用的路径。建议删掉这个方法,或者标 @Deprecated 并指向本契约,免得下一位评审再重新发现一遍。另外,RuntimeTransport.execute(lease, session, reference) 没有 toolName/input 可以发送,Broker 也不存储这两个字段,下一个切片必须扩展这个接口。

小问题

  • 错误码和消息沿用 attestation 的命名,现有两处 …_too_large 发出方都把 "exceeds 16 KiB" 写死在消息里。
  • status/cancel 没有超大请求体的 fixture(上限 16 KiB)。
  • prepared 状态没有 fixture。
  • success 结果允许携带 error。

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Addressed the real-environment verification findings:

Finding Action
F1: cancel returning unknown was rejected as retryable 503 Fixed in 1c836fd9b: accept unknown, align RuntimeTransport documentation, and cover active plus fenced cancellation paths
F2: route admission precedes handlers Deferred for author decision: update the prior design rule or defer gate admission
F3: schema is weaker than prose Deferred for author decision: strengthen the schema here or document Java-only enforcement
Remaining nits Deferred; non-blocking

Verification: git diff --check passed. The targeted Maven test could not run locally because this environment has no mvn binary and the repository has no Maven Wrapper; CI will provide the executable test result.

@wenshao

wenshao commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Round 2 verification: head 1c836fd9

Verdict: F1 is fixed and the fix is covered by tests. Nothing blocks merge. F2 and F3 remain the author's decision, as stated in the reply above. Neither one blocks merge.

Round 2

  • Same probe as round 1. I fed the PR's cancel fixtures verbatim into the real RuntimeBrokerService.cancelExecution. cancel/unknown-is-ok now succeeds on both paths that reach transport.cancel:

    • live EXECUTING → CANCEL_REQUESTED
    • lapsed claim fenced to UNKNOWN → stays UNKNOWN

    In round 1 both returned 503 retryable. The other four rows are unchanged. Once the pending execute completes, live rows settle as success and lapsed rows stay UNKNOWN, the same as before.

  • The tests are load-bearing. I removed only "unknown" from RUNTIME_EXECUTION_STATES and kept the new tests. Exactly two tests fail: cancellationAcceptsUnknownRuntimeStatus (live path) and cancellationOfAFencedRunningInvocationStillReachesTheRuntime (fenced path). That is one per path, and the other 60 tests in the class still pass.

  • Maven, since the author could not run it locally. mvn clean test for the whole runtime-broker module passes 113/113 on JDK 21 and on JDK 25 (112 in round 1, plus the new test). PR CI on 1c836fd9 is green across Java 11/17/21 on ubuntu, macOS 21, Windows 21, MariaDB, Real daemon E2E, Test, and Lint.

  • Scope. packages/cli and docs/ are byte-identical to d9f241db. So round-1 Fig 1 (F2: handler-less admission contradicts the 09-22 design rule) and Fig 3 (F3: schema looser than the prose, candidate patch) still apply as posted. Choosing either option is fine. If F3 is deferred, the one sentence in §3.4 saying where those rules are enforced is still worth adding.

  • Optional nit. The new tests build Map.of("state", "unknown") by hand. Loading cancel/unknown-is-ok from the shared fixtures file, as the conformance test does, would tie the Broker behaviour to the contract, so a later edit to the fixture can't silently diverge.

中文说明

第二轮验证:head 1c836fd9

结论:F1 已修复,修复有测试覆盖。没有阻塞合入的问题。 F2、F3 按作者回复留给作者决定,都不阻塞合入。

  • 沿用第一轮的探针。 把 PR 的 cancel fixtures 原样喂给真实的 RuntimeBrokerService.cancelExecution。cancel/unknown-is-ok 在两条会调用 transport.cancel 的路径上现在都成功:

    • 存活的 EXECUTING → CANCEL_REQUESTED
    • 过期后被围栏为 UNKNOWN 的声明 → 保持 UNKNOWN

    第一轮这两行都是可重试的 503。其余四行没有变化。挂起的 execute 完成后,存活路径结算为 success,过期路径保持 UNKNOWN,和之前一样。

  • 新测试确实起作用。 只从 RUNTIME_EXECUTION_STATES 里删掉 "unknown"、保留新测试:恰好两个测试失败,cancellationAcceptsUnknownRuntimeStatus(存活路径)和 cancellationOfAFencedRunningInvocationStillReachesTheRuntime(围栏路径),每条路径各一个;这个类里其余 60 个测试照常通过。

  • Maven(作者本地跑不了)。 runtime-broker 整个模块 mvn clean test 在 JDK 21 和 JDK 25 上都是 113/113(第一轮 112 个,加上新增的 1 个)。PR 的 CI 在 1c836fd9 上全绿。

  • 范围。 packages/cli 和 docs/ 与 d9f241db 逐字节相同,所以第一轮的图 1(F2)和图 3(F3)照旧适用。两个选项都可以;如果 F3 推迟,仍建议在 §3.4 补一句说明这些规则在哪里被强制执行。

  • 可选的小建议。 新测试是手写 Map.of("state", "unknown")。如果改成从共享 fixtures 文件读取 cancel/unknown-is-ok(conformance 测试就是这么做的),Broker 的行为就和契约绑定在一起,以后改了 fixture 也不会悄悄分叉。

doudouOUC and others added 2 commits September 24, 2026 23:51
Extend the owned managed-runtime route manifest with the three tool
operations and add their shared schema and conformance fixtures. The
wire shape fixes the evidence rules the recovery design demands: every
call is keyed by the original reference (sessionId, promptId, callId,
argsDigest) rather than any Broker-side identifier; status is read-only
and answers unknown with 200 instead of 404 or 500, because a missing
record is never evidence that a call did not run; a settled state is
the only one that may carry a result; and status additionally returns
the Runtime's own lastSequence cursor. Execute accepts up to 256 KiB
of request input, and every operation answers at most 1 MiB.

No handler is mounted yet: per the manifest rule the routes join in the
same line of slices as the execute extraction that implements them. The
TypeScript gate test proves the owned-route gate admits exactly these
paths, and the Java consumer pins the route contract, every outcome
classification, and the closed field sets, so the worker and transport
slices that follow share one reviewed target.

Related to QwenLM#12380.
@doudouOUC
doudouOUC force-pushed the feat/managed-runtime-tool-contract branch from 1c836fd to 37fd360 Compare September 24, 2026 15:52
@github-actions

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

The route manifest declares the v2 execute/status/cancel contract, but the
raw gate treated every declaration as admitted, so three unimplemented
operations reached Express ahead of any handler. The gate now admits only the
attestation route it serves, and each handler expands admission in the change
that mounts it.

The shared schema also enforced less than the documented contract: one
permissive request body covered all three routes, and nothing tied result to a
settled state or lastSequence to status. Each route now binds its own closed
request body, every ok case must carry a response body, result outside settled
is rejected, and lastSequence is rejected outside status. Mutation tests break
one rule at a time to prove each constraint is load-bearing, and the Java
conformance test reads the renamed definitions.

Both design doc pairs now separate contract declaration from gate admission.

Co-authored-by: Qwen-Coder <[email protected]>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Addressed F2 and F3 from the real-environment verification; both are fixed in this PR rather than deferred.

Finding Action
F2: route admission precedes handlers Fixed in 241c87292. The raw gate now admits only the attestation route it actually serves, so the three declared tool routes 404 before Express even when a handler is mounted behind them. The manifest keeps the declarations as the shared wire contract, and each handler expands admission in the change that mounts it. The stale "one manifest drives admission" rule is corrected in both language versions of the attestation design, and the tool design now states that declaration is not admission.
F3: schema is weaker than prose Fixed in 241c87292. The shared schema binds each route to its own closed request body (canonical request and per-case body override alike), requires a response body on every ok case, rejects result outside settled, and rejects lastSequence outside status. Ten mutation tests break exactly one rule each. I printed the Ajv error path for all ten to confirm each fails on the intended keyword rather than incidentally; the three mutations that previously survived are now rejected by required / additionalProperties on the route-specific body. The Java conformance test reads the renamed definitions.

Verification on the pushed head 241c87292 (rebased onto current main):

  • npx vitest run src/serve/managed-runtime-attestation-contract.test.ts src/serve/managed-runtime-attestation-worker.test.ts → 72/72 pass (64 contract + 8 worker).
  • npm run typecheck → pass. npx prettier --check and npx eslint on every changed file → clean.
  • npm run build passed on identical content before the rebase; the rebase touches none of the eight changed files and no dependency manifest.
  • Maven still cannot run in this environment (no mvn binary and no Maven Wrapper). The Java change is test-only — it reads the renamed $defs — so CI supplies the executable result.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Recorded deferral (non-blocking, from the round-2 verification): loading cancel/unknown-is-ok from the shared fixtures into the two Broker cancellation tests, instead of hand-building Map.of("state", "unknown"), is deferred to a follow-up rather than landed here, so this change stays focused on F2 and F3.

记录推迟:第二轮验证中提到的可选建议——让 Broker 的两个取消测试从共享 fixtures 读取 cancel/unknown-is-ok,而不是手写 Map.of("state", "unknown")——推迟到后续变更处理,本次改动只聚焦 F2 与 F3。

@wenshao

wenshao commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

…ntract

Main gave the execution lookup its own state and field vocabulary while
this branch taught cancellation to accept an unknown state. Both survive
the merge: cancel validation keeps unknown, and the lookup keeps its
separate sets, so neither contract narrows the other.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Conflict resolution — PR #12630 ← main

Root cause. main's #12655 (3413e8c, UNKNOWN-execution reconciliation) added a status lookup to RuntimeTransport and a separate Runtime-state vocabulary in RuntimeBrokerService — on the two spots this PR's 37fd3607e2 Accept unknown cancellation status had already edited: the RUNTIME_EXECUTION_STATES block and the javadoc listing cancel states.

Semantic, not just adjacent. Both sides redefined which state values the broker accepts, for different operations; taking either verbatim would revert the other:

private static final Set<String> RUNTIME_EXECUTION_STATES = Set.of(
        "prepared", "executing", "cancel_requested", "settled",
        "unknown");          // ← this PR: gates the cancel path
private static final Set<String> RUNTIME_STATUS_STATES = Set.of(
        "prepared", "executing", "cancel_requested", "settled",
        "unknown");          // ← main: gates the lookup path
private static final Set<String> RUNTIME_STATUS_FIELDS = Set.of(
        "state", "result");  // ← main

Load-bearing. The two state sets are now element-identical but must stay separate declarations: RUNTIME_EXECUTION_STATES gates only absorbCancellationStatus, RUNTIME_STATUS_STATES/RUNTIME_STATUS_FIELDS only absorbRuntimeStatus. Collapsing them couples the cancel and lookup contracts, so narrowing one would silently change the other. Each is pinned by a test that survived the auto-merge: cancellationAcceptsUnknownRuntimeStatus (PR), invalidLookupResponseKeepsTheExecutionUnknown (main). main's javadoc "the same states plus unknown" likewise became "…the same states".

Could not verify. No build or tests ran. RuntimeBrokerServiceTest.java auto-merged with tests from both sides; I checked for duplicate method names, not compilation. Also, in files that did not conflict and that I left alone: this PR's managed-runtime-tool-v2.schema.json and fixtures let a status response carry protocolVersion and lastSequence, while main's absorbRuntimeStatus rejects any field outside {state, result} as runtime_execution_status_invalid (502). Latent (no transport overrides the 501 default yet), but once one does, a Runtime answering per this PR's contract is rejected. Decide in the PR: widen RUNTIME_STATUS_FIELDS, or drop those fields.

中文说明

冲突根因。 main 的 #12655(3413e8cc57,UNKNOWN 执行对账)为 RuntimeTransport 增加 status 查询,并在 RuntimeBrokerService 引入独立状态词表,正是本 PR 37fd3607e2 改过之处。

属于语义冲突。 双方各自重新定义了 broker 接受哪些 state、用于哪个操作,照抄任一方都会回退另一方(见上方代码)。

关键约束。 两个状态集合元素已相同,但必须保持独立声明:前者只约束 absorbCancellationStatus,后两者只约束 absorbRuntimeStatus;合并成一个常量会耦合 cancel 与查询契约,收窄其一将静默影响另一。两侧各由自动合并后保留的测试锁定:cancellationAcceptsUnknownRuntimeStatus(本 PR)、invalidLookupResponseKeepsTheExecutionUnknown(main)。javadoc 中 main 的 "plus unknown" 相应改为 "the same states"。

未能验证。 未执行构建与测试。RuntimeBrokerServiceTest.java 自动合并了双方新增用例,我只检查了重复方法名,未编译。另有未冲突文件(故未改)的分歧:本 PR 的 v2 schema 与 fixtures 允许 status 响应携带 protocolVersion 与 lastSequence,而 main 的 absorbRuntimeStatus 只接受 {state, result},否则报 runtime_execution_status_invalid(502)。目前潜在(无 transport 覆写 501 默认实现),一旦实现,按本 PR 契约应答的 Runtime 会被拒绝;请在 PR 内决定放宽 RUNTIME_STATUS_FIELDS 还是删除这两个字段。

@wenshao

wenshao commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Round 3 verification: head 53a3faac

This head is rebased onto 3413e8cc57 (#12655) and includes the /resolve merge.

Verdict: F1, F2 and F3 are all fixed, and the /resolve merge compiles and passes. Nothing blocks merge. There is one new latent finding (F4), which confirms the unverified concern the /resolve bot raised. It is worth deciding in this PR or recording before the status transport slice.

Round 3

Findings from earlier rounds

Round 1 Now How verified
F2 gate admission new paths → Express HTML 404, pre-auth all three → the gate's empty 404, same as base fresh pnpm install + bundle, real dist/cli.js managed-runtime-worker, same 8 raw requests
F3 schema vs prose accepted 7/7 violating documents; 5/8 mutants killed all 7 rejected; 8/8 killed same Ajv-strict probe and the same 8 mutants. Fixtures are byte-identical to round 1, so the gain comes entirely from the schema
F1 cancel unknown 503 retryable 6/6 cancel fixtures OK on the merged code same probe against the real RuntimeBrokerService

The design-doc rewrite ("declaration does not imply admission") is consistent in both languages and matches the gate code.

Build: the /resolve merge was never compiled, so I compiled it

  • runtime-broker mvn clean test passes 156/156 on JDK 21 and on JDK 25.
  • The TS contract and worker suites pass 72/72, matching the author's number.
  • PR CI is green (review-pr and web-shell E2E Smoke were still running when I checked).
  • The two separate state sets the bot kept (RUNTIME_EXECUTION_STATES for cancel, RUNTIME_STATUS_STATES/RUNTIME_STATUS_FIELDS for lookup) are both exercised: the cancel probe for the first, the status probe below for the second.

F4 (new, latent): the wire status response doesn't fit the Broker's closed lookup shape

I fed this PR's three status fixtures into main's real reconcileExecution through FakeTransport.status:

fixture verbatim, as the wire schema defines it transport strips protocolVersion + lastSequence
status/unknown-is-ok 502 runtime_execution_status_invalid, non-retryable UNRESOLVED, record stays UNKNOWN ✅
status/executing 502, non-retryable UNRESOLVED ✅
status/settled-with-result 502, non-retryable RESOLVED → SETTLED/success ✅
  • Cause. feat(java): Reconcile UNKNOWN tool executions from Runtime evidence #12655 closes the lookup answer to RUNTIME_STATUS_FIELDS = {state, result}, and its javadoc says so. This PR's schema requires protocolVersion on every response and allows lastSequence on status.
  • Impact once wired. A transport that passes the wire body through would turn every valid Runtime answer into a permanent 502. The execution then stays UNKNOWN, which is exactly the stuck state feat(java): Reconcile UNKNOWN tool executions from Runtime evidence #12655 exists to clear.
  • Why it is latent. No production class implements RuntimeTransport.status, and HttpRuntimeTransport has no status method yet. So this does not block merge.
  • The inconsistency. The cancel path has no closed field check, so protocolVersion passes through there. Status rejects it.
  • What the probe shows. The two contracts compose correctly once the transport validates protocolVersion == 2 and strips it.
  • The open question is lastSequence. The tool design (§3.3) justifies it as the cursor a reconciler uses "to advance without replaying", but the Broker's lookup has no field for it and never reads it. There are two options. Either add one sentence to §5 and the status javadoc saying the HTTP transport validates and strips the envelope and that lastSequence is dropped until a consumer exists. Or widen RUNTIME_STATUS_FIELDS and consume it. A Java test that pipes the shared status fixtures through that adapter would pin the decision.

Still open from earlier rounds (non-blocking, author-acknowledged)

  • HttpRuntimeTransport.execute is still the dead pre-contract stub.
  • The Broker cancellation tests still hand-build Map.of("state", "unknown"); the author has deferred this.
中文说明

第三轮验证:head 53a3faac

这个 head 已 rebase 到 3413e8cc57(#12655),并包含 /resolve 机器人的合并。

结论:F1、F2、F3 都已修复,/resolve 的合并能编译、测试全过,没有阻塞合入的问题。 有一个新的潜在问题 F4,证实了 /resolve 机器人提出但没有验证的担忧。建议在本 PR 里定下来,或者在实现 status transport 的切片之前记录下来。

之前各轮的问题

  • F2:用全新安装加打包的真实 worker 重放同样 8 个请求。三条工具路径都回到了门的空体 404,与 base 一致,不再返回 Express 的 HTML 页面。
  • F3:同一套 Ajv strict 探针和 8 个变异体。7 个违规文档全部被拒(第一轮全部被接受);8 个变异体全部被杀(第一轮 5/8)。fixtures 与第一轮逐字节相同,所以提升完全来自 schema。
  • F1:在合并后的代码上重放 6 个 cancel fixtures,全部成功,没有回退。
  • attestation 设计文档的改写("声明不等于放行")中英文一致,也与门的代码相符。

构建:/resolve 的合并没有编译过,由我补上

runtime-broker 的 mvn clean test 在 JDK 21 和 JDK 25 上都是 156/156。TS 契约和 worker 测试 72/72,与作者给出的数字一致。PR 的 CI 全绿(我查看时 review-pr 和 web-shell E2E Smoke 还在运行)。机器人保留的两套独立状态集合都被覆盖到了:cancel 探针覆盖前一套,下面的 status 探针覆盖后一套。

F4(新发现,潜在问题):线上的 status 响应与 Broker 封闭的查询形态不匹配

把本 PR 的三个 status fixtures 原样经 FakeTransport.status 喂给 main 真实的 reconcileExecution,全部返回 502 runtime_execution_status_invalid,且不可重试。transport 剥掉 protocolVersion 和 lastSequence 之后,三者分别得到正确结果:UNRESOLVED、UNRESOLVED、RESOLVED→SETTLED/success。

  • 原因:feat(java): Reconcile UNKNOWN tool executions from Runtime evidence #12655 把查询结果封闭为 {state, result}(javadoc 里也是这么写的),而本 PR 的 schema 要求每个响应都带 protocolVersion,并允许 status 带 lastSequence。
  • 接上真实 transport 后的影响:如果 transport 把线上响应原样转交,每个合法的 Runtime 回答都会变成永久 502,执行一直停在 UNKNOWN,而这正是 feat(java): Reconcile UNKNOWN tool executions from Runtime evidence #12655 要解决的卡死状态。
  • 为什么说是潜在问题:目前没有任何生产类实现 RuntimeTransport.status,HttpRuntimeTransport 也还没有 status 方法,所以不阻塞合入。
  • 不一致之处:cancel 路径不检查封闭字段集,protocolVersion 可以通过;status 路径则会拒绝它。
  • 探针结果:只要 transport 校验 protocolVersion == 2 后把它剥掉,两份契约就能正确衔接。
  • 待决问题是 lastSequence:工具设计 §3.3 说它是对账方"免重放推进"的游标,但 Broker 的查询结果里没有这个字段,也从不读取它。二选一:在 §5 和 status 的 javadoc 里写明"HTTP transport 负责校验并剥掉外层字段,lastSequence 在有消费方之前被丢弃";或者放宽 RUNTIME_STATUS_FIELDS 并真正消费它。再加一个 Java 测试,把共享的 status fixtures 经这个适配器跑一遍,就能把这个决定固定下来。

之前各轮仍然存在的问题(不阻塞,作者已确认)

  • HttpRuntimeTransport.execute 仍是契约之前的死代码桩。
  • Broker 的取消测试仍手写 Map.of("state","unknown"),作者已推迟处理。

@wenshao

wenshao commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Thanks for merging main (53a3faac81). I used the same resolution for RuntimeBrokerService.java and RuntimeTransport.java when merging main into #12637 (ec97ca28ac), so the two branches stay in step.

Two text points relative to #12655, which is now on main:

  1. The comment on RUNTIME_STATUS_STATES no longer draws a contrast. It reads "A lookup may also report that the Runtime holds no record at all." Now that RUNTIME_EXECUTION_STATES (used for cancel) also carries unknown, the two sets are identical. Suggested comments:
    • on RUNTIME_EXECUTION_STATES: // A cancellation answers with one of these states.
    • on RUNTIME_STATUS_STATES: // A lookup answers with the same states as a cancellation.
    • on RUNTIME_STATUS_FIELDS: // A lookup answer carries nothing but these fields.
  2. Tool-contract §5 (EN and zh-CN) is out of date. It says "The UNKNOWN execution reconciler consumes status (tracked on proposal(serve): Define Managed Agent dual-path architecture and staged delivery #12380)", but feat(java): Reconcile UNKNOWN tool executions from Runtime evidence #12655 shipped the reconciler. What remains is to wire HttpRuntimeTransport in as the RuntimeTransport and project each status answer to {state, result}. Without that projection, reconcileExecution rejects protocolVersion and lastSequence with a non-retryable 502.
中文说明

感谢合入 main(53a3faac81)。我在把 main 合进 #12637(ec97ca28ac)时,RuntimeBrokerService.java 和 RuntimeTransport.java 采用了同样的解法,两个分支保持一致。

相对已经合入 main 的 #12655,有两处文字需要调整:

  1. RUNTIME_STATUS_STATES 上的注释已经没有对比意义。 原文是 "A lookup may also report that the Runtime holds no record at all."。现在 RUNTIME_EXECUTION_STATES(cancel 用)也包含 unknown,两个集合完全相同。建议改为:
    • RUNTIME_EXECUTION_STATES 上:// A cancellation answers with one of these states.
    • RUNTIME_STATUS_STATES 上:// A lookup answers with the same states as a cancellation.
    • RUNTIME_STATUS_FIELDS 上:// A lookup answer carries nothing but these fields.
  2. tool-contract §5(中英文)已经过时。 原文是 "The UNKNOWN execution reconciler consumes status (tracked on proposal(serve): Define Managed Agent dual-path architecture and staged delivery #12380)",但对账器已在 feat(java): Reconcile UNKNOWN tool executions from Runtime evidence #12655 实现。剩下的工作是把 HttpRuntimeTransport 接为 RuntimeTransport,并把每个 status 应答投影为 {state, result}。不做这个投影,reconcileExecution 会以不可重试的 502 拒绝 protocolVersion 和 lastSequence。

@wenshao

wenshao commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /review

@github-actions

Copy link
Copy Markdown
Contributor

Qwen Code review request accepted. Review is queued for an available runner; follow the workflow run for progress. A command-triggered review is not listed under the checks of this PR; the result is posted here as a review when it finishes.

@doudouOUC
doudouOUC enabled auto-merge September 25, 2026 01:43

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed. Suggestions are inline.

Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": none — the whole chunk (zh-CN doc + 318-line fixtures file) and both consumers were read in full; the JSON was also parsed for duplicate keys, which were absent….

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.

中文说明

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

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)":none — the whole chunk (zh-CN doc + 318-line fixtures file) and both consumers were read in full; the JSON was also parsed for duplicate keys, which were absent…。

未审查:反向审计——在 5 轮的反审轮数上限内未收敛。

— DeepSeek/deepseek-v4.1-flash@2d473174 via Qwen Code /review (v0.24.5)

Comment thread packages/cli/src/serve/contracts/managed-runtime-tool-v2.schema.json Outdated
Comment thread packages/cli/src/serve/contracts/managed-runtime-tool-v2.schema.json Outdated
Comment thread packages/cli/src/serve/managed-runtime-attestation-contract.test.ts
Comment thread packages/cli/src/serve/contracts/managed-runtime-tool-v2.schema.json Outdated
Comment thread packages/cli/src/serve/contracts/managed-runtime-tool-v2.schema.json Outdated
wenshao
wenshao previously approved these changes Sep 25, 2026
@wenshao

wenshao commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao
wenshao disabled auto-merge September 25, 2026 02:31
@wenshao

wenshao commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Review follow-up pushed as 401d214. All ten inline suggestions have a concrete response. The general comments about the cancellation/lookup state comments and the already-shipped reconciler are also addressed; both design languages and the status interface document the required wire-envelope projection.

Before committing, I completed three open-ended / presume-wrong audit passes. The first found a new test's readonly-cast typecheck/lint issue, which was corrected; the final two consecutive passes found no further issue. Reverse checks used isolated copies of the actual suites: all 12 schema mutants and all 4 Java fixture/limit mutants were caught by assertion failures, with green baselines.

Validation on macOS arm64, Node 22.22.2 and JDK 21:

  • Full build, bundle and typecheck passed; changed-file ESLint/Prettier and diff checks passed.
  • TypeScript contract + worker suites: 85/85 (77 + 8).
  • Java conformance + Runtime Broker suites: 96/96 (6 + 90).
  • Independent reproduction: 17/17 assertions pass after the correction; 11 failed on the original contract.

E2E report: the globally installed qwen did not expose a usable managed-runtime-worker startup for the baseline, so contract reproduction used a direct Ajv test script. The locally built node dist/cli.js managed-runtime-worker started successfully: attestation returned 200 with the exact expected body and Cache-Control: no-store; execute/status/cancel all returned the raw gate's empty 404. No tool handler was claimed or tested as implemented.

R1-10 uses the review's explicit documentation option: responseParts element structure remains deferred until the worker/transport slice derives it from the actual SDK Part path and adds shared coverage. The fixture header closure applies to fixture objects, not arbitrary transport-added HTTP headers. The request caps remain 256 KiB for execute and 16 KiB for status/cancel, with 1 MiB responses.

中文说明

已推送提交 401d214,逐条回应十条行内建议,同时处理状态注释和已落地对账器的说明。双语设计与 status 接口文档明确了线上信封向 Broker 形态的投影要求。

提交前进行了三轮无方向通读与反向核验。第一轮发现新增测试的只读类型转换与 lint 问题并修正;随后连续两轮未发现新问题。对实际测试文件的隔离副本撤掉约束或破坏语料,12 种 schema 退化和 4 种 Java fixture/上限退化全部触发断言失败,对照组保持通过。

macOS arm64、Node 22.22.2、JDK 21 上,完整 build、bundle、typecheck 通过,改动文件 ESLint/Prettier 和 diff 检查通过;TypeScript 测试 85/85,Java 测试 96/96。独立复现从原契约的 11 项失败转为 17/17 通过。

E2E 报告:全局 qwen 无法启动可用的 managed-runtime-worker,因此基线契约问题用直接 Ajv 脚本复现。本地 bundle 的 worker 启动成功,attestation 返回 200、精确预期响应和 no-store;execute/status/cancel 均返回 raw gate 的空体 404。未声称工具处理器已实现或已验证。

R1-10 按评审明确允许的文档方案处理:responseParts 元素结构推迟到 worker/transport 切片,从实际 SDK Part 路径确定后补齐共享覆盖。请求头封闭规则约束 fixture 对象,不禁止 HTTP 层正常添加的头。请求上限保持 execute 256 KiB、status/cancel 16 KiB,响应均为 1 MiB。

@wenshao

wenshao commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Round 4 verification: head 401d2149 (review follow-up R1-1…R1-10)

Verdict: all ten review follow-ups hold up under independent probes. F1–F3 stay fixed, and F4 is now documented as a required transport projection. Nothing blocks merge. Two fixture-consistency nits survive both suites; both are optional.

Round 4

The R1 follow-ups, checked independently rather than against the author's own mutants

  • Schema items (R1-1/2/4/6/7/9). For each item I built one targeted mutation of the shipped fixtures, and the strict Ajv schema rejects every one:

    • a duplicated suite in place of status
    • a misspelled error code
    • a settled response without result
    • an extra header, in the canonical headers and in a per-case override
    • a malformed authorization value
    • the execute request cap set to 16 KiB, and a 2 MiB response cap

    The shipped fixtures still validate, and the round-0 violations stay rejected.

  • R1-5 and R1-8 (new fixtures). without-cursor, settled-with-error and settled-not-started are now covered, so the corpus spans all five states and all four execution statuses.

  • R1-3 and R1-10 (docs only). The PR description (EN and zh) now describes the attest-only gate. The deferral of responseParts element structure is written down in §3.3.

  • Builds.

    • TS contract and worker tests pass 85/85, matching the author's number, and tsc --noEmit is clean.
    • runtime-broker mvn clean test passes 156/156 on JDK 21 and JDK 25. Worth noting because the author validated only JDK 21.
    • The gate code and lockfile are unchanged since round 3, so the round-3 real-worker result (tool paths get the gate's empty 404) carries over.

Mutation matrix: 13 mutants

  • The original M1–M8 are 8/8 killed.
  • I added five mutants aimed at shapes the schema still accepts. The schema does not bind a route's key to its path, and routes has no "exactly one per key" rule, even though suites now does. Those two (A1, A2) are still killed by both suites, because the TS manifest toEqual and the Java path == /v2/<key> check catch them. So that schema gap is harmless.
  • error result without an error object (A4): killed by the Java pin.
  • Survivors (optional nits):
    • A3: a success result carrying an error object. Same as the round-1 nit.
    • A5: a suite's canonical x-qwen-managed-lease-id disagreeing with identity.leaseId. Nothing checks this until a handler materializes the tool suites.

F4 (status projection), now across the whole corpus

I sent every ok status fixture, including the three new ones, through main's real reconcileExecution, and all six cancel cases through cancelExecution (live and lapsed paths).

  • Verbatim, all six status bodies still get 502 runtime_execution_status_invalid, non-retryable.
  • Projected to {state, result}, all six are absorbed correctly: unknown-is-ok / executing / without-cursor → UNRESOLVED; settled-with-result → SETTLED/success; settled-with-error → SETTLED/error; settled-not-started → SETTLED/not_started. So the new fixtures are consumable by the shipped reconciler.
  • Cancel is 6/6 OK.
  • Documentation. The projection is now required in tool design §5 and in the RuntimeTransport.status javadoc, and it stays latent because no production RuntimeTransport exists. The one thing still not pinned is a test: when the transport slice lands, piping the shared status fixtures through its adapter would turn that sentence into a check.

Notes

  • CI on 401d2149 is green so far. Test (ubuntu-latest, Node 22.x) and review-pr were still running when I checked.
  • The sandboxed /triage verify run started at 02:32, about 5 minutes before 401d2149 was pushed at 02:37. It may be reporting on 53a3faac.
中文说明

第四轮验证:head 401d2149(评审跟进 R1-1…R1-10)

结论:十条评审跟进都经得起独立探针检验。F1–F3 保持修复,F4 现在已写成文档要求的 transport 投影。没有阻塞合入的问题。 有两个 fixtures 内部一致性的小问题两套测试都没抓到,可改可不改。

R1 跟进:独立验证,而不是复用作者自己的变异体

  • schema 类(R1-1/2/4/6/7/9):每条各构造一个针对性的 fixtures 变异,strict 模式的 Ajv schema 全部拒绝:

    • 用重复的 suite 顶替 status
    • 错拼的错误码
    • 不带 result 的 settled 响应
    • 规范请求头里、以及单个用例覆盖的请求头里多出的键
    • 格式错误的 authorization
    • 把 execute 请求上限改成 16 KiB、把响应上限改成 2 MiB

    原样的 fixtures 仍然通过,第零轮的违规文档仍被拒绝。

  • R1-5、R1-8(新增 fixtures):新增了 without-cursor、settled-with-error、settled-not-started,语料覆盖了全部 5 种状态和 4 种执行结果。

  • R1-3、R1-10(仅文档):PR 描述(中英文)已改为只放行 attest 的门;responseParts 元素结构推迟处理这一点已写入 §3.3。

  • 构建:

    • TS 契约和 worker 测试 85/85,与作者一致;tsc --noEmit 无报错。
    • runtime-broker 的 mvn clean test 在 JDK 21 和 JDK 25 上都是 156/156(作者只在 JDK 21 上验证过)。
    • 门的代码和 lockfile 自第三轮以来没有变化,第三轮真实 worker 的结果(工具路径返回门的空体 404)依然成立。

变异矩阵:13 个变异体

  • 原来的 M1–M8 全部被杀。
  • 新增 5 个变异体,专门针对 schema 仍然接受的形态。schema 没有把路由的 key 和 path 绑定,routes 也没有"每个 key 恰好一个"的规则(suites 现在有)。这两个(A1、A2)两套测试都能抓到:TS 的 manifest toEqual 和 Java 的 path == /v2/<key> 检查。所以这个 schema 缺口无害。
  • error 结果却不带 error 对象(A4):被 Java 的断言抓到。
  • 存活的变异体(可选的小问题):
    • A3:success 结果带 error 对象,与第一轮提过的小问题相同。
    • A5:suite 规范请求头里的 x-qwen-managed-lease-id 与 identity.leaseId 不一致。在处理器真正使用这些工具 suite 之前,没有任何代码检查它。

F4(status 投影):覆盖全部语料

把每个 ok 的 status fixture(含三个新增的)送进 main 真实的 reconcileExecution,并把 6 个 cancel 用例在存活和过期两条路径上送进 cancelExecution。

  • 原样转交:6 个 status 响应仍然全部返回 502 runtime_execution_status_invalid,且不可重试。
  • 投影为 {state, result} 后:全部被正确吸收。unknown-is-ok、executing、without-cursor → UNRESOLVED;settled-with-result → SETTLED/success;settled-with-error → SETTLED/error;settled-not-started → SETTLED/not_started。新增的 fixtures 可以被已上线的对账器消费。
  • cancel:6/6 成功。
  • 文档:这个投影现在是工具设计 §5 和 RuntimeTransport.status javadoc 的明确要求;因为还没有生产环境的 RuntimeTransport 实现,问题仍然只是潜在的。唯一还没被测试固定的是这条要求本身:等 transport 切片落地时,把共享的 status fixtures 经它的适配器跑一遍,就能把这句文档变成检查。

备注

  • 401d2149 上的 CI 目前全绿;我查看时 Test (ubuntu-latest, Node 22.x) 和 review-pr 还在运行。
  • 沙箱 /triage 验证在 02:32 启动,比 401d2149 的推送时间(02:37)早约 5 分钟,它报告的可能是 53a3faac。

@wenshao
wenshao enabled auto-merge September 25, 2026 03:04

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

Independent Critical-only review — head 401d2149

Declares the v2 execute/status/cancel Managed Runtime contract: a JSON schema (+475) and fixture set (+384), a conformance test (+142), four design docs, and 43 production lines across three files — RuntimeBrokerService.java (+5/-2), RuntimeTransport.java (+8/-4) and managed-runtime-attestation-contract.ts (+30/-3).

No historical blocker

No Critical has ever been filed against this PR. The only ledger, round 1 at 53a3faac, records floor:"o" with ten findings, all sev:"S", and that review has been dismissed; head has since moved to 401d2149, which carries a maintainer approval. All ten review threads are resolved with none outstanding, and there is no CHANGES_REQUESTED standing against this head.

The one behavioural change is a widening that can only fail safe

RUNTIME_EXECUTION_STATES gains "unknown", making it identical to RUNTIME_STATUS_STATES:

private static final Set<String> RUNTIME_EXECUTION_STATES = Set.of(
        "prepared", "executing", "cancel_requested", "settled", "unknown");

A set like this is only as safe as its use sites, so I enumerated them rather than reasoning from the name. There are exactly two, one per constant: RUNTIME_EXECUTION_STATES at :1076 and RUNTIME_STATUS_STATES at :1166. The first is the allowlist in absorbCancellationStatus:

Object state = status == null ? null : status.get("state");
if (!(state instanceof String) || !RUNTIME_EXECUTION_STATES.contains(state)) {
    throw unavailable("runtime_execution_cancel_failed", "Runtime cancellation returned an invalid status");
}
if (!"settled".equals(state)) {
    return;
}

Accepting "unknown" therefore changes one thing: a cancel answer of unknown becomes a well-formed no-op instead of a 502. It falls straight into the !"settled" early return, so it absorbs nothing, settles nothing and leaves the record exactly as requestCancel left it. The widening cannot reach the only branch that writes. The second consumer already treated unknown this way, so both now agree — which is what the new comments and the interface javadoc state ("A lookup answers with the same states as a cancellation" / "Status results use the same states"), and it is the alignment an HTTP adapter needs to project one envelope onto both operations.

This is also semantically right rather than merely permissive: unknown means the Runtime holds no record of the reference, which is a legitimate answer to a cancel of something it never saw, and the sibling contract already establishes that it is never evidence the call did not run — so absorbing it without settling is the correct handling.

RuntimeTransport.java is javadoc-only: it restates the cancel states to include unknown and adds that an HTTP adapter must validate the wire envelope and project it to this shape, stripping protocolVersion and lastSequence because the Broker does not consume the Runtime's response cursor yet. No behaviour change, and it documents the adapter's obligation at the interface an adapter implements.

The new route declarations are bounded and non-cacheable

OWNED_MANAGED_RUNTIME_ROUTES gains three frozen entries under the existing /internal/managed-runtime/ prefix, all at protocolVersion: 2 and all cacheControl: 'no-store':

route method request limit response limit
execute POST 256 KiB (new MANAGED_RUNTIME_TOOL_REQUEST_BODY_LIMIT_BYTES) 1 MiB (new MANAGED_RUNTIME_TOOL_RESULT_BODY_LIMIT_BYTES)
status POST 16 KiB (the existing attestation limit) 1 MiB
cancel POST 16 KiB (the existing attestation limit) 1 MiB

The sizing is proportionate: execute carries a tool reference and arguments so it gets the larger request bound, while status and cancel carry only a reference and reuse the 16 KiB attestation bound. Every route has an explicit request and response bound, so no path reads an unbounded body, and no-store keeps execution results and cancellation answers out of any intermediary cache — the right posture for a control plane. The limits are exported named constants rather than inline literals, and the entries are frozen inside a frozen array, so the declaration cannot be mutated at runtime.

Scope disclosure

The schema and fixture files are declarative contract data, and the ten Suggestions from round 1 are all about how tightly they pin things — the envelope limits declared as any integer above 1024 rather than exact values, expected.code as a free non-empty string, request headers left open with additionalProperties: true, responseParts declared as an array with no item schema, a response allOf encoding only one direction, and the suites array pinned to exactly three items. Per Critical-only scope I did not gate on any of them; they are contract-tightness questions for the maintainer, and a permissive schema in a conformance fixture weakens what the test proves without mis-certifying runtime behaviour.

One production hunk I did not read: the ~2-line net change to isOwnedManagedRuntimeRoute at :311, the matcher over the route table. I ran out of budget before opening it. It is exercised by the +344-line attestation-contract test and the +142-line conformance test, and the lanes that completed are green, but I am naming it because it is the one piece of production code in this diff I cannot speak to.

CI

review-pr and web-shell E2E Smoke (ubuntu-latest, Node 22.x) were in progress at head and a route check shows cancelled from superseded runs; no failure is attributable to this diff and CI state is not a gate. Two of my API calls hit transient network timeouts during this review and were retried successfully, so nothing above rests on a failed read.

Verdict: APPROVE — No Critical found. The single behavioural change admits unknown as a cancel answer at its one use site, where it becomes a no-op that cannot reach the settling branch; the transport change is documentation; and the three new route declarations are bounded on both request and response and marked no-store. One small matcher hunk is disclosed above as unread.

@wenshao
wenshao added this pull request to the merge queue Sep 25, 2026
Merged via the queue into QwenLM:main with commit 177c2f6 Sep 25, 2026
360 of 370 checks passed
wenshao added a commit to doudouOUC/qwen-code that referenced this pull request Sep 25, 2026
…s-p0-p8

Brings in main through 2686cad. The conflicts are all in
runtime-broker, where QwenLM#12627 upstreamed this branch's durable binding
recovery onto main's asynchronous service.

The branch keeps its own RuntimeBrokerService, as in earlier merges, and
takes main's refinements of the shared data model: the provision seed
binds its lease to the provisional Runtime instance, resource handles
compare by their canonical JSON, the encryption contexts use a
fixed-width digest of the binding id, provisioner_kind widens to 512
characters, and the binding repositories gain releaseOperation.
placement_domain and runtime_template_digest stay in the request
identity.

Main's behaviors the branch service lacked are ported, with tests in the
branch's API:
- a release settles a Session that still pins a LOST generation, and a
  LOST binding that is still referenced no longer caches its failure, so
  the generation is reclaimed once the last reference settles (QwenLM#12627);
- only identity evidence (managed_runtime_identity_conflict,
  managed_runtime_unauthorized) blocks the re-attestation of a restored
  or health-refreshed binding; a throttled or malformed attestation
  leaves it READY for the next attempt (QwenLM#12627);
- a repository failure thrown while driving a binding no longer leaves
  it started with nothing driving it (QwenLM#12627);
- a cancellation may answer unknown, and the recorded intent stands
  (QwenLM#12630).

Main's DurableRuntimeRecoveryTest cases run against the branch service;
the ones tied to main's separate provision step are expressed through
the branch's ensure-then-reconcile path. Two auto-merge artifacts are
fixed: a duplicated hasActiveByBinding and a releaseOperation override
spliced into LocalProcessRuntimeProvisionerTest.
wenshao added a commit to doudouOUC/qwen-code that referenced this pull request Sep 25, 2026
…s-p0-p8

Brings in main through 688e8af. The conflicts are in the Runtime
Broker's HTTP transport, where QwenLM#12637 adds execute, status and cancel to
main's attestation-only HttpRuntimeTransport against the QwenLM#12630 v2 tool
contract. The branch's transport implements the whole RuntimeTransport
against the branch's own worker, whose request and response shapes that
contract does not accept yet, so the branch versions of the transport,
its test and LocalProcessRuntimeProvisionerTest stand. Converging the
worker and transport on the declared contract belongs with the split
that upstreams them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants