Skip to content

feat(managed-agent): broker authentication and broker-provisioned writer credentials - #13210

Merged
wenshao merged 23 commits into
QwenLM:mainfrom
wenshao:feat/managed-agent-broker-auth
Oct 5, 2026
Merged

wenshao merged 23 commits into
QwenLM:mainfrom
wenshao:feat/managed-agent-broker-auth

Conversation

@wenshao

@wenshao wenshao commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Adds the authentication/credential layer for the Managed Agent Runtime Broker that #13180 asks for, with the bilingual design doc at docs/design/managed-agent-broker-auth.md / managed-agent-broker-auth.zh-CN.md:

  • Authenticated principals on the public surface. A new signed mode (qwen.managed-agent.auth.mode=signed) has the broker verify an HMAC signature over METHOD + path + query + tenant + actor + timestamp + SHA-256(body) + Idempotency-Key itself (headers X-Qwen-Actor-Id, X-Qwen-Signature-Timestamp, X-Qwen-Signature), installing the AuthenticatedTenantActor principal without any external gateway. auto (the default) resolves to the existing open behavior on loopback and refuses to start on a non-loopback address, so a production listen address can never come up unauthenticated. Startup guards also reject a weak signing key, a contradictory trusted-actor-header configuration, a non-root server.servlet.context-path or spring.mvc.servlet.path (which would silently bypass the path-prefix filters), a plaintext non-loopback session-store.base-url or tool-publication.service-base-url, an internal listener on the same port number as the public one (the surface routing is port-based), and reusing the signing key as the writer binding key. Signed requests buffer their body under qwen.managed-agent.auth.max-signed-body-bytes (default 10 MiB) before verification — an over-limit body answers 413 payload_too_large and a repeated Idempotency-Key header answers 400, both before the signature comparison.
  • Broker-provisioned writer credentials. With qwen.managed-agent.session-store.binding-key set (>= 32 bytes), the writer token becomes an HMAC over (tenantId, workspaceId, sessionId) issued through the harness attach payload (ManagedSessionStoreConnection.writerToken), and every session-store entry point rejects self-minted tokens with 403 writer_credential_invalid — including during a free lease window. The credential deliberately excludes the writer id, so a rebooted harness re-acquires with the same credential after the old lease lapses. Without the key, loopback deployments keep the legacy first-writer-wins behavior.
  • A separate internal listener. qwen.managed-agent.internal-server.port/.address bind /internal/** to their own connector; a routing filter answers 404 for the wrong surface on either port. Leaving loopback — public or internal — requires signed mode or a configured binding key respectively (allow-insecure-bind is the documented escape hatch).
  • Durable approval ownership. V40 adds managed_agent_session.creator_actor_key, recorded from the authenticated actor at creation for both hosted and workspace Sessions. requireOwner resolves the creator column, then the legacy workspace creation row, then (neither recorded) falls back to the tenant-scoped semantics non-workspace Sessions already had — so hosted approvals can finally be answered over HTTP, and workspace approvals bind to the authenticated actor instead of a guessable header value.
  • A plaintext-transport guard in the TS client. createHttpManagedSessionStores refuses a non-loopback http:// base URL unless the broker opts in (qwen.managed-agent.session-store.allow-insecure-http=true, forwarded through the attach payload as allowInsecureHttp).

Why it's needed

Without a gateway in front, the pre-change arrangement composes into cross-tenant read/write of any hosted Session's durable transcript: assert a tenant header, wait out a writer lease window (up to 300 s), acquire the writer with a self-minted token, and rewrite history — verified end-to-end in this PR's reproduction (a forged requested → decided: allow approval lifecycle was committed into a victim Session's journal and served back by the public actions API). The broker is being built toward multi-tenant hosted deployment; this PR installs the perimeter the controls assumed. Default loopback dev/E2E topologies keep working with zero new configuration.

Reviewer Test Plan

How to verify

  • Java (needs JDK 21 + Maven; use -Dgpg.skip=true): cd packages/sdk-java/managed-agent-server && mvn test — the new coverage: Issue13180SignedModeTest (unsigned/wrong-key/stale → 401, signed works, cross-tenant 404), Issue13180InternalPortTest (each surface 404s on the other's port) and Issue13180HardenedVerificationTest (the original attack chain re-run against the hardened broker: takeover 403, forged commit 403, cross-tenant replay 403, hosted approval answered by its creator), ManagedSessionStoreBindingTest (self-minted refused at every store entry point, reboot re-acquire), BrokerSecurityTest (mode resolution + every startup guard), SignatureAuthFilterTest, WriterCredentialPolicyTest, ManagedActionsTest#hostedSessionsAnswerThroughTheirRecordedCreator (ownership fallback chain).
  • TS: cd packages/core && npx vitest run src/managed-runtime/http-managed-session-store.test.ts (plaintext guard table incl. 127.example.com not counting as loopback), cd packages/acp-bridge && npx vitest run src/bridgeTypes.test.ts, cd packages/cli && npx vitest run src/serve/hosted-harness-session.test.ts (payload passthrough + unusable-descriptor 400).
  • Expected: all pass. Two pre-existing ToolPublicationStoreTest timing cases fail on this machine both before and after the change (measured on the unmodified base; unrelated claim-expiry probes).

Evidence (Before & After)

N/A — non-UI server/SDK change. Before/after behavior is pinned by Issue13180HardenedVerificationTest (attacks fail in the hardened configuration); the before-half is reproduced publicly by the sandboxed verification lane's three-arm A/B, whose arm A drives the #13180 chain against the base build and lands the forged commit (e.g. run 37254095813).

Tested on

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

Environment (optional)

OpenJDK 21, Maven 3.9, H2 (MySQL mode) for the broker tests; Node 22 for the TS suites.

Risk & Scope

  • Main risk or tradeoff: the fix is configuration-gated by design — holes close where the hardened configuration is enabled (and non-loopback binds now force it), while loopback open mode intentionally keeps legacy behavior, with one disclosed exception: an anonymous hosted Session (no recorded creator and no recorded create command) is tenant-owned for approvals, as design doc §4.4 tier 3 specifies.
  • Not validated / out of scope: the runtime-broker's own single-token HTTP server (tracked by a separate issue); the MySQL-flavored failsafe ITs (-Pmysql-integration) need a database service and were not run locally; the 401 refusal is declared per route on the covered surface, and 413 on every covered route that declares a request body.
  • Breaking changes / migration notes: two upgrade-order constraints — (1) a CLI-only upgrade against a broker whose session-store URL is non-loopback plaintext http now refuses the attach (upgrade the broker and set allow-insecure-http, or move the store to https/loopback); (2) enabling binding-key on a new broker requires harnesses on this CLI version or later (older clients reject the provisioned writerToken field). Existing databases migrate via V40; pre-migration Sessions keep working through the legacy ownership fallback.
  • Design docs (English + Chinese, kept in sync): docs/design/managed-agent-broker-auth.md, docs/design/managed-agent-broker-auth.zh-CN.md.

Linked Issues

Closes #13180

中文说明

本 PR 做什么

为 Managed Agent Runtime Broker 实现 #13180 要求的认证/凭证层,双语设计文档见 docs/design/managed-agent-broker-auth.md / managed-agent-broker-auth.zh-CN.md:

  • 公网面的已认证主体。 新的 signed 模式(qwen.managed-agent.auth.mode=signed)让 broker 自行校验覆盖 METHOD + path + query + tenant + actor + timestamp + SHA-256(body) + Idempotency-Key 的 HMAC 签名(头 X-Qwen-Actor-Id、X-Qwen-Signature-Timestamp、X-Qwen-Signature),无需外部网关即可安装 AuthenticatedTenantActor principal。auto(默认)在回环地址上保持现有 open 行为,在非回环地址上拒绝启动,生产监听地址不可能在无认证的情况下起来。启动守卫还会拒绝过弱的签名密钥、与 trusted-actor-header 的矛盾配置、非根 server.servlet.context-path 或 spring.mvc.servlet.path(它会静默绕过路径前缀过滤器)、非回环明文的 session-store.base-url 或 tool-publication.service-base-url、与公网同号的内部监听端口(路由按端口分类),以及签名密钥与绑定密钥复用。签名请求在验签前按 qwen.managed-agent.auth.max-signed-body-bytes(默认 10 MiB)缓冲请求体——超限应答 413 payload_too_large,重复 Idempotency-Key 头应答 400,均先于签名比对。
  • Broker 下发的 writer 凭证。 配置 qwen.managed-agent.session-store.binding-key(≥32 字节)后,writer token 变为对 (tenantId, workspaceId, sessionId) 的 HMAC,经 harness attach 载荷(ManagedSessionStoreConnection.writerToken)下发;会话存储的所有入口都以 403 writer_credential_invalid 拒绝自铸 token——包括租约空窗期。凭证刻意不含 writer id,harness 重启后可在旧租约失效后用同一凭证重新 acquire。未配置时,回环部署保持既有的首写者赢行为。
  • 独立的内部监听器。 qwen.managed-agent.internal-server.port/.address 把 /internal/** 绑到独立连接器;路由过滤器对两个端口上的错面请求一律 404。离开回环——公网或内部——分别要求 signed 模式或已配置的 binding-key(allow-insecure-bind 是文档化的逃生门)。
  • 持久的审批属主。 V40 新增 managed_agent_session.creator_actor_key,hosted 与 workspace 会话在创建时都会从已认证 actor 记录。requireOwner 依次解析 creator 列、遗留的 workspace 创建行,最后(都无记录时)回退到非 workspace 会话已有的租户级语义——hosted 审批终于能通过 HTTP 应答,workspace 审批绑定到已认证 actor 而非可猜测的头值。
  • TS 客户端的明文传输守卫。 createHttpManagedSessionStores 拒绝非回环的 http:// base URL,除非 broker 显式 opt-in(qwen.managed-agent.session-store.allow-insecure-http=true,经 attach 载荷以 allowInsecureHttp 转发)。

为什么需要

没有网关时,改动前的安排可组合成对任意 hosted 会话持久 transcript 的跨租户读写:伪造租户头、等一个租约空窗(最长 300 秒)、用自铸 token 夺取 writer、重写历史——本 PR 的复现端到端验证了这条链(伪造的 requested → decided: allow 审批被写入受害者会话的持久 journal,并经公共 API 读回)。该特性正走向多租户外托管部署,本 PR 把这些控制所假设的边界真正建立起来。默认的回环 dev/E2E 拓扑零新配置继续可用。

评审者测试计划

如何验证

  • Java(需要 JDK 21 + Maven;加 -Dgpg.skip=true):cd packages/sdk-java/managed-agent-server && mvn test——新增覆盖:Issue13180SignedModeTest(未签名/错密钥/过期 → 401,签名可用,跨租户 404)、Issue13180InternalPortTest(两个面在对方端口 404)、Issue13180HardenedVerificationTest(对加固后的 broker 重放原始攻击链:夺取 403、伪造提交 403、跨租户重放 403、hosted 审批可被创建者应答)、ManagedSessionStoreBindingTest(自铸 token 在每个存储入口被拒、重启后重新 acquire)、BrokerSecurityTest(模式解析 + 全部启动守卫)、SignatureAuthFilterTest、WriterCredentialPolicyTest、ManagedActionsTest#hostedSessionsAnswerThroughTheirRecordedCreator(属主回退链)。
  • TS:cd packages/core && npx vitest run src/managed-runtime/http-managed-session-store.test.ts(明文守卫表,含 127.example.com 不算回环)、cd packages/acp-bridge && npx vitest run src/bridgeTypes.test.ts、cd packages/cli && npx vitest run src/serve/hosted-harness-session.test.ts(载荷透传 + 不可用描述符 400)。
  • 预期:全部通过。ToolPublicationStoreTest 有两个既有的计时用例在本机改动前后都失败(已在未改动的基线上实测;与本 PR 无关的 claim 过期探针)。

证据(前后对比)

N/A——非 UI 的服务端/SDK 改动。前后行为由 Issue13180HardenedVerificationTest(加固配置下攻击失败)钉住;before 半侧由沙箱验证通道的三臂 A/B 公开复现——臂 A 在 base 构建上驱动 #13180 攻击链并成功落地伪造提交(见 run 37254095813)。

测试平台

操作系统 状态
🍏 macOS ✅
🪟 Windows N/A
🐧 Linux N/A

环境(可选)

OpenJDK 21、Maven 3.9、H2(MySQL 模式)跑 broker 测试;Node 22 跑 TS 套件。

风险与范围

  • 主要风险/取舍:修复按设计由配置门控——启用加固配置的地方漏洞关闭(非回环绑定现在会强制要求),回环 open 模式有意保持既有行为,除一处已披露例外:无记录创建者且无记录创建命令的匿名 hosted Session 在审批上归租户共有,如设计文档 §4.4 第三级所述。
  • 未验证/范围外:runtime-broker 自身的单 token HTTP server(由另一 issue 跟踪);MySQL 版 failsafe IT(-Pmysql-integration)需要数据库服务,本地未跑;401 拒绝已在覆盖面的每条路由上逐一声明,413 覆盖所有声明了请求体的覆盖路由。
  • 破坏性变更/迁移说明:两个升级顺序约束——(1) 只升级 CLI 而 broker 的 session-store URL 是非回环明文 http 时,attach 会被拒绝(升级 broker 并设置 allow-insecure-http,或把 store 改为 https/回环);(2) 在新 broker 上启用 binding-key 要求 harness 使用本版或更新的 CLI(旧客户端会拒绝下发的 writerToken 字段)。现有数据库经 V40 迁移;迁移前的会话通过遗留属主回退继续工作。
  • 设计文档(中英双语,保持同步):docs/design/managed-agent-broker-auth.md、docs/design/managed-agent-broker-auth.zh-CN.md。

关联 Issue

Closes #13180

@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Oct 2, 2026
@wenshao
wenshao force-pushed the feat/managed-agent-broker-auth branch from afa75f3 to 92298a4 Compare October 2, 2026 06:37
@github-actions

github-actions Bot commented Oct 2, 2026

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)为单个提交。

@wenshao

wenshao commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the review round in 3648e4b:

Criticals

  • R1-1: the equal-port guard now resolves an unset server.port to the Spring Boot default 8080, so the collision it refuses cannot slip through.
  • R1-3: the signature canonical string now also covers the raw query string, a SHA-256 of the raw body, and the Idempotency-Key header — a captured signature authorizes exactly one request, not any same-path request inside the drift window. (This changes the v1 recipe pre-merge; docs, OpenAPI, README and all three signers updated together.)
  • R1-4: the trustedTenant/trustedActor scheme descriptions now describe both topologies (gateway vs signed) instead of asserting gateway-only trust.

Triage items: requireOwner reads the creator row null-tolerantly (no more 500 on a missing row); the writer credential policy is a required injection (fails closed).

Suggestions: fixed — bound-mode test for the publication-writer path, workspace term of the MAC pinned, drift window validated (>=1s) and exercised, unparsable timestamp 401 pinned, store base-url/internal-port coherence guard, plaintext non-loopback harness URL refused, envelope-shaped 404 from the routing filter, connector address acceptance checked, TLS/plaintext split warned, signed-hosted-create replay test, WebShell creator recording test, CLI refusal body now carries the reason, producer omit-when-unset test, passthrough assertion covers all six fields, lowercase-hex and undecoded-path/raw-query documented, stale self-mint prose corrected, loopback trust-boundary caveat added, spec wording scoped to covered routes and the missing-tenant 400.

Intentionally unchanged (design decisions, now documented): ownerless Sessions (anonymous open-mode or pre-V31) answer approvals tenant-scoped, matching their read ACL (§8.4 amended); the attach payload fields stay additive without a protocol bump — the upgrade order (CLI before broker config) is called out in Risk & Scope.

中文说明

已在 3648e4b 处理本轮评审:

  • 严重项:等端口守卫对未设 server.port 解析为默认 8080;签名规范串新增查询串、请求体 SHA-256 与幂等键(捕获的签名只对原请求有效);两个身份方案描述按网关/签名双拓扑改写。
  • triage 项:requireOwner 容忍缺行(不再 500);凭证策略改为必需注入(fail-closed)。
  • 建议项:已全部修复——覆盖发布面凭证检查、MAC 的 workspace 项、漂移窗口校验与实测、非法时间戳 401、store 地址与内部端口一致性、明文 harness 地址拒绝、路由过滤器标准错误信封、连接器地址确认、TLS/明文并存告警、签名创建重放测试、WebShell 属主记录测试、CLI 400 携带原因、生产者缺省省略测试、透传全字段断言、文档口径修正。
  • 有意不改(已写入文档):无属主会话按租户域应答(与读 ACL 一致,§8.4 已修订);attach 载荷字段保持增量不加协议版本——升级顺序已在 Risk & Scope 注明。

wenshao and others added 7 commits October 3, 2026 01:46
…ter credentials

Close the five gateway-free security gaps from QwenLM#13180:

- signed auth mode: the broker authenticates the tenant/actor header pair
  itself via HMAC (X-Qwen-Signature + timestamp), with startup guards that
  refuse non-loopback binds, weak keys, a contradictory trusted-actor
  stand-in, and a context path that would bypass the path filters
- writer credentials are broker-provisioned HMAC bindings over
  (tenant, workspace, session) and enforced at every session-store entry
  point; the harness receives them through the attach payload
- /internal/** can bind to its own loopback connector with 404 routing
  between the two surfaces
- sessions record their creator (V28), so hosted approvals are answerable
  over HTTP and workspace approvals bind to the authenticated actor
- the TS client refuses plaintext http:// broker URLs on non-loopback
  hosts unless the broker opts in via the attach payload

Includes the bilingual design doc (docs/design/managed-agent-broker-auth)
and verification coverage: signed-mode E2E, dual-port routing E2E, the
binding sweep over every store entry point, and the hosted ownership
chain.

Co-authored-by: Qwen-Coder <[email protected]>
- refuse internal-server.port equal to server.port at startup: the surface
  routing filter classifies by local port, and equal port numbers on
  different addresses would serve /internal/** on the public address
- refuse reusing the signing key as the writer binding key: the two HMAC
  keys protect different domains
- log the resolved auth mode, internal listener and writer binding state
  at startup, as the design doc promises
- log the refused managed session store descriptor in the hosted harness
  so the transport guard's remedy stays discoverable

Co-authored-by: Qwen-Coder <[email protected]>
main gained its own V28 and V30 migrations while this branch was in
review; move the creator column to V31 to keep the Flyway sequence
unambiguous.

Co-authored-by: Qwen-Coder <[email protected]>
The v1.28 Unauthorized response description changed; the generated
client must match the contract.

Co-authored-by: Qwen-Coder <[email protected]>
- sign the request body, query string and Idempotency-Key too, so a
  captured signature authorizes exactly one request (review R1-3)
- resolve the public port to the Spring Boot default 8080 when
  server.port is unset, so the equal-port guard cannot slip (R1-1)
- read the session creator row null-tolerantly instead of 500ing on a
  missing row (triage #3), and make the writer credential policy a
  required injection so it fails closed (triage #5)
- startup guards: allowed-drift >= 1s, loopback store base-url must name
  the internal listener's port, and an enabled harness over plaintext
  non-loopback http is refused (the attach payload carries credentials)
- the routing filter answers the standard error envelope, the internal
  connector checks its address property was accepted, and a TLS public
  listener beside a plaintext internal one is warned about
- docs: OpenAPI scheme descriptions, README recipes and both design docs
  now match the implementation; the ownerless fallback is documented as
  tenant-scoped for anonymous and pre-migration Sessions
- tests pin the drift window, unparsable timestamps, body/query/key
  coverage, the workspace term of the writer MAC, WebShell creator
  recording, the publication-writer credential check, and the producer's
  omit-when-unset payload

Co-authored-by: Qwen-Coder <[email protected]>
main took V31 (workspace recovery bundle) and V32 (workspace session
close) while the review round was in flight; move the creator column to
V33.

Co-authored-by: Qwen-Coder <[email protected]>
main took V33 (agent definitions) during the latest rebase. Also make
WorkspaceRecoveryStoreTest compare the session table byte[]-tolerantly:
creator_actor_key is the first VARBINARY column in that table, and byte[]
compares by reference inside Map.equals, which failed the immutability
assertion on MySQL.

Co-authored-by: Qwen-Coder <[email protected]>
@wenshao
wenshao force-pushed the feat/managed-agent-broker-auth branch from 21ff724 to 201051a Compare October 2, 2026 17:49
…olumn set

The retention IT pins the schema at V31 and then creates a session, but
the store insert now always names creator_actor_key (V34), which a
database at the prerequisite version does not have yet. Write the
pre-upgrade session with the V31-era column set directly, then let the
close and retirement flow run through the store as before.

Co-authored-by: Qwen-Coder <[email protected]>
…tract gaps

- Cover the bare POST /v1/agents route and normalized path spellings by
  deciding filter coverage on the routed path (shared PublicSurface
  predicate), while the canonical string keeps signing the raw URI.
- Bound the buffered signed body (auth.max-signed-body-bytes, default
  10 MiB) and answer 413 before the signature comparison; refuse a
  repeated Idempotency-Key header with 400.
- Classify the internal surface on the routed path as well, so encoded or
  parameter-carrying spellings cannot cross the listener boundary; the
  routing filter now shares the injected ObjectMapper.
- Refuse a plaintext session-store base URL whose host falls outside the
  client's literal-only loopback set, guard spring.mvc.servlet.path
  alongside context-path, and enumerate the guards allow-insecure-bind
  skips in the startup posture line.
- Qualify the creator-only responder wording in the OpenAPI contract with
  the ownerless fall-through, declare the 401 refusals per route, and pin
  both with contract tests.
- Restore the transaction boundary on the legacy 9-arg insertSessionCommand
  default, share one test signer between the integration tests, and carry
  writer_credential_invalid in the shared error fixture.

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

wenshao commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the R2 round in 8dc87e3 (plus the MariaDB IT fix in e86c2a5: the retention IT pins the schema at V31, so its pre-upgrade session is now written with the legacy column set instead of the current store insert).

Criticals

  • R2-1: filter coverage is decided on the routed path through a shared PublicSurface predicate used by both the tenant and signature filters, so the bare POST /v1/agents and normalized spellings (/%61gents, ;jsessionid) require a signature; the canonical string still signs the raw request URI. Pinned by SignatureAuthFilterTest#coversTheBareCollectionRouteAndNormalizedSpellings and Issue13180SignedModeTest#theBareCollectionRouteRequiresASignature (unsigned → 401 on all three spellings, correctly signed POST /v1/agents → 202).
  • R2-2: the routing filter classifies on the same routed-path view, so /%69nternal/... and /internal;/... answer the envelope 404 on the public connector while the internal connector still serves its own surface; InternalSurfaceConfigurationTest#classifiesNormalizedSpellingsOnTheRoutedPath pins both directions, and the listener-direction test stays green.
  • R2-3: the buffered body is bounded (qwen.managed-agent.auth.max-signed-body-bytes, default 10 MiB): an over-limit Content-Length is refused 413 payload_too_large before any read, and a bounded reader enforces the same cap on chunked or under-declared bodies; the wrapper still re-exposes the buffered bytes. SignatureAuthFilterTest#boundsTheBufferedBody pins the 413 and the under-limit path.
  • R2-4 (resolves R1-2 via its option b): the twelve creator-only sentences, the trustedActor "mandatory in either mode" clause and the v1.30 note now state the ownerless fall-through, and ManagedAgentApiContractTest#theCreatorOnlyResponderContractNamesTheOwnerlessFallThrough pins the qualification so the contract and requireOwner cannot drift apart silently.

Suggestions

  • R2-5 query term pinned (signsTheQueryString: matching query installs the principal, the same signature against another query → 401); R2-6 the routing filter takes the shared ObjectMapper; R2-7 the posture line enumerates the guards allow-insecure-bind skipped (skipped=public-bind,internal-binding-key,harness-transport); R2-8 startup refuses a plaintext session-store.base-url outside the client's literal loopback set unless allow-insecure-http (incl. the 127.example.com case); R2-10 one shared BrokerSignatures helper for the two integration tests (the filter test keeps its own copy on purpose); R2-11 the README recipe is a fenced code block; R2-12 the absent direction is pinned (attachOmitsTheCredentialAndOptInWhenNeitherIsConfigured); R2-13 both replay ids are non-blank-guarded; R2-14 a repeated Idempotency-Key answers 400 invalid_request; R2-15 the publication-writer accept direction is pinned on a migrated schema; R2-16 the parse arm logs the cause and returns it as the 400 message; R2-17 the 9-arg default carries @Transactional (pinned on both arities); R2-18 writer_credential_invalid joins the shared fixture, the ManagedSessionStoreModels constants, the throw site and both fixture suites.
  • Carried items: R1-7 the 401 refusal is now declared per route on all 49 covered operations (the 7 inline artifact 401s untouched); R1-29 spring.mvc.servlet.path is refused alongside context-path; R1-30 the qwenSignature description explains the timestamp header belongs to the scheme (apiKey names one header); R1-31 the internal listener logs its resolved bind address (the setProperty return was already checked); R1-32 the lease-lapse leg polls instead of trusting one sleep; R1-36 the writer-token length bounds are pinned against the shared fixture on both sides; R1-39 requiresEachSignatureHeader omits one header at a time.

Already in place / reply-only

  • R1-10: the internal-port test context already sets session-store.binding-key and drives the positive acquire with an issued credential (Issue13180InternalPortTest: issued token → 200, self-minted → 403 writer_credential_invalid), so the credential check is observable on that listener.
  • R1-27: a second Tomcat connector cannot inherit server.ssl — that namespace is scoped to the main connector in Spring Boot — so the configuration warns at startup instead (the conditional warning is in place); a dedicated internal TLS option is a separate feature.
  • R1-35: the static per-Session credential is the documented compromise (design §6): folding the writer id into the credential would break reboot re-acquisition under the same scope, and the lease fencing (lease_token_hash) still rejects a superseded writer's mutations.
  • R2-9: the absence direction is now pinned on the producer (attachOmitsTheCredentialAndOptInWhenNeitherIsConfigured) and on the wire (connectionOmitsUnsetOptionalCredentials).

Design docs (EN + zh-CN) updated for the new fail-closed details, guards and limits; the OpenAPI 401 sweep is reflected in the risk section.

中文说明

已在 8dc87e3 处理 R2 轮(另有 e86c2a5 修复 MariaDB IT:保留 IT 把 schema 钉在 V31,其升级前会话改用以当时列集直写)。

严重项

  • R2-1:过滤器覆盖改由路由后路径判定(两个过滤器共享 PublicSurface),裸 POST /v1/agents 与归一化拼写(/%61gents、;jsessionid)都要求签名;规范串仍签原始 URI。SignatureAuthFilterTest 与 Issue13180SignedModeTest 双向钉住(未签名 → 401,正确签名的裸路由 → 202)。
  • R2-2:路由过滤器同样按路由后路径分类,/%69nternal/...、/internal;/... 在公网连接器 404,内部连接器正常服务;InternalSurfaceConfigurationTest 钉住两个方向。
  • R2-3:缓冲请求体加上限(auth.max-signed-body-bytes,默认 10 MiB):超限 Content-Length 直接 413,有界读取对分块/虚报同效;wrapper 照常回暴露字节。
  • R2-4(按其选项 b 一并了结 R1-2):十二处"仅创建者可应答"措辞、trustedActor 的"mandatory in either mode"及 v1.30 说明均补上无属主回退;新增契约测试防止文字与 requireOwner 静默漂移。

建议项:R2-5 查询串签名钉住;R2-6 共享 ObjectMapper;R2-7 posture 行枚举被跳过的守卫;R2-8 启动拒绝客户端回环集之外的明文 store URL(含 127.example.com);R2-10 两个集成测试共享 BrokerSignatures(过滤器测试刻意保留独立副本);R2-11 README 配方改为围栏代码块;R2-12 缺省方向钉住;R2-13 重放 id 非空守卫;R2-14 重复幂等键 400;R2-15 发布面凭证接受方向在已迁移 schema 上钉住;R2-16 解析臂记录原因并随 400 返回;R2-17 9 参默认方法补 @Transactional;R2-18 writer_credential_invalid 进入共享 fixture、常量、抛出点与两侧套件。遗留项:R1-7 全部 49 个覆盖操作逐路由声明 401;R1-29 守卫 spring.mvc.servlet.path;R1-30 方案描述说明时间戳头归属;R1-31 启动日志记录内部监听器解析后的绑定地址;R1-32 租约失效腿改为轮询;R1-36 token 长度边界对共享 fixture 钉住;R1-39 逐头缺失用例。

已有/仅答复

  • R1-10:内部端口测试上下文已配置 binding key 并用下发凭证驱动正向 acquire(下发 → 200,自铸 → 403),凭证检查在该监听器上可观测。
  • R1-27:第二个 Tomcat 连接器无法继承 server.ssl(该命名空间属主连接器),改为启动告警;内部面独立 TLS 属另一特性。
  • R1-35:静态会话级凭证是设计 §6 记载的取舍:并入 writer id 会破坏同范围重启重 acquire,租约围栏(lease_token_hash)仍拒绝被取代写者的写入。
  • R2-9:缺省方向已在生产者侧与线上载荷双侧钉住。

设计文档(中英)已同步新的 fail-closed 细节、守卫与上限;OpenAPI 401 逐路由声明已反映到风险节。

main landed V34 as managed_tool_output_collection; move the creator
column to V35 and update the design docs, the OpenAPI wording and the
test references.

Co-authored-by: Qwen-Coder <[email protected]>
wenshao and others added 4 commits October 3, 2026 11:41
…oker-auth

# Conflicts:
#	packages/sdk-java/managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json
The first merge commit swept in a locally regenerated NOTICES.txt whose
content depends on the host's installed tree; CI regenerates it on Linux
and expects the upstream content.

Co-authored-by: Qwen-Coder <[email protected]>
- Narrow the replay and body-bound claims to what the code guarantees
  (captured signatures stay fixed to method/path/query/tenant/actor/body;
  the body bound is per request, with the worker pool as the aggregate
  ceiling).
- Presize the signed-body buffer, pin the raw-URI signing split and the
  chunked bound, and add a route-table coverage test for the shared
  public-surface predicate.
- Guard a plaintext non-loopback tool-publication service URL, normalize a
  blank internal address, warn for a non-loopback internal listener
  without TLS, and stop classifying a bare 127 as loopback.
- Declare 413 per operation with a shared PayloadTooLarge component, state
  the ownerless approval fall-through's create-command step, and parse the
  shared fixture structurally in the qwencode test.

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

wenshao commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

Sandbox verification round 4 (run 37254095813) disposition / 第四轮沙箱验证处置:

Finding #1 (413 contract gap) → fixed by narrowing the claim. The PR body now reads: "the 401 refusal is declared per route on the covered surface, and 413 on every covered route that declares a request body" — which is exactly true at head (401: 58/58; 413: 32/32 on requestBody-declaring operations). Both language halves updated; no code or contract change.

Finding #2 (unreachable .qwen/issues citation) → fixed. The Evidence section now points at this lane's own arm A (the #13180 chain reproduced on the base build, forged commit landed) instead of the gitignored local file. Both language halves updated.

Finding #3 (the timeout class is unpinned) → deferred, with reasoning. A source-scanning guard test over vi.waitFor spans is brittle relative to what it buys: the class is measured complete at head (59/59 HTTP polls carry an explicit timeout, confirmed by your independent census), the flakiness gate is a deterministic tripwire that re-runs this file on every future PR that touches it, and the file's own convention (explicit { timeout: 10_000 } at every HTTP settle wait) is now uniform enough that a bare-default HTTP poll stands out in review. Recorded on the deferral list; if a future regression ever slips through, the guard test is the follow-up.

The 415 observation (unsupported media type answers 500 — pre-existing, ApiExceptionHandler untouched by this PR) — recorded as a follow-up issue candidate; out of scope here.

Noted with appreciation: the 12-cell charset A/B (including the +json subtypes and the quoted/uppercase/extra-parameter spellings) and the vacuity check on the new regression test are exactly the measurements this fix needed.

中文:发现 #1 已通过收窄主张修复(正文改为「401 逐路由声明,413 覆盖所有声明请求体的覆盖路由」,与 head 实测完全一致,双语已同步);发现 #2 已修复(证据段改引本通道臂 A 的公开复现 run);发现 #3 延后并说明理由(当前 59/59 全覆盖有独立普查实测,抖动门是确定性绊线,自扫描守卫测试相对收益偏脆——记入延后清单,若有回归漏网再补);415 观察为 PR 外既有问题,记入跟进 issue 候选。12 格 charset A/B 与回归测试的非空验证正是该修复所需的度量,致谢。

@wenshao

wenshao commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

@qqqys One pointer for the next pass's gate #4: the wire-level regression the round-6 review asked for exists — Issue13180SignedModeTest#signedJsonDecodesAsUtf8RegardlessOfTheDeclaredCharset (not SignatureAuthFilterTest, which is the unit-level suite). It signs a UTF-8 café body, replays the identical bytes and signature with charset=ISO-8859-1 over real HTTP against embedded Tomcat, and asserts the persisted content digest equals a fresh UTF-8 control plus idempotent replay. It was verified red on the pre-fix head (the replay leg answered 409 idempotency_conflict), and the sandbox verify lane independently re-proved it non-vacuous by reverting only the R6-1 hunk and watching it go red with the intended behavioural mismatch (run 37254095813, "vacuity check"). Your gates #1–#3 are fairly stated as unconfirmed-at-this-head rather than broken — no objection.

中文:给下一轮的门 #4 指路——R6 要求的 wire 级回归测试在 Issue13180SignedModeTest(非 SignatureAuthFilterTest):真实 HTTP 下以 UTF-8 签名 café 请求体、以 ISO-8859-1 声明重放同字节同签名,断言持久化 digest 与 UTF-8 对照一致且幂等重放;修复前曾跑红(重放腿 409),沙箱验证通道亦通过单点还原 R6-1 hunk 独立证明其非空转。门 #1–#3 如实记录为「本 head 未确认」而非「已破坏」,无异议。

@wenshao

wenshao commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /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 at 64fe514, reviewed against the threat model. All five goals check out in code: SignatureAuthFilter verifies HMAC over method+URI+query+tenant+actor+timestamp+body-hash+idempotency-key with MessageDigest.isEqual, bounded buffered body, repeated-key refusal, and the routed-path coverage rule (so percent-encoding can't slip past); auto mode refuses non-loopback binds without a key; the writer credential is broker-issued HMAC over the scope (>=32-byte key enforced, checked before any state lookup, legacy TOFU preserved when unbound); the TS client refuses plaintext non-loopback with a literal-only loopback set (no DNS resolution to rebound through); and V40's creator_actor_key gives hosted sessions a durable owner. The startup guards enumerate every skipped check. Migrations don't collide with in-flight V36-V39.

@wenshao
wenshao added this pull request to the merge queue Oct 5, 2026
Merged via the queue into QwenLM:main with commit 29fd4c1 Oct 5, 2026
161 of 162 checks passed
@wenshao

wenshao commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

Sandbox verification round 5 (run 37261576399) disposition — post-merge / 第五轮(合并后)处置:

61/61 assertions, flakiness gate ✅, and the correction is accepted with thanks — the narrowed 413 sentence now reads true as measured (32/32 on requestBody-declaring covered operations). That correction closed three rounds of a finding that was really a denominator mismatch.

Finding #1 (approval-ownership wording) → fixed in the body. The Risk & Scope sentence now carries the qualifier in both languages: loopback open mode keeps legacy behavior except that an anonymous hosted Session (no recorded creator, no recorded create command) is tenant-owned for approvals, exactly as design doc §4.4 tier 3 specifies. The bounding analysis is right: in signed mode the signing key already confers tenant-wide authority, so tier 3 grants nothing new there; the widening exists only on the default loopback posture, where caller-chosen tenant headers are the trust model by design.

Finding #2 (waitFor timeout class unpinned) → deferred, disposition unchanged from round 4 — the measured mutation (M1 survives green) is exactly the property we accepted: the class is complete at head (59/59) and the flakiness gate remains the deterministic tripwire for any future regression on this file.

Both language halves of the body were updated and the local draft is in sync. With the PR merged at 29fd4c1a, this closes the verification loop on my side; the 415 observation (ApiExceptionHandler missing HttpMediaTypeNotSupportedException, pre-existing on base) stays on the follow-up list.

中文:61/61 断言全过、抖动门 ✅,并接受更正——收窄后的 413 表述经实测为真(32/32),三轮旧发现实为分母口径差异。发现 #1 已在正文修复(双语补上限定:回环 open 模式除「匿名 hosted Session 的审批归租户共有」外保持既有行为,与设计文档 §4.4 第三级一致);边界分析正确——signed 模式下签名密钥本就有租户级权限,放宽只存在于默认回环姿态(其信任模型本就是调用方自选租户头)。发现 #2 维持第四轮延后处置(M1 存活正是我们接受的属性:类已完整,抖动门是未来的确定性绊线)。PR 已合并,验证闭环完成;415 观察保留在跟进清单。

wenshao added a commit that referenced this pull request Oct 5, 2026
Absorbs main's broker authentication (#13210) and the cold-load refusal
hardening (#13361). Two conflict hunks in hosted-harness-session.ts keep
both semantics: main's restore-refusal diagnostics reuse the single
pre-close verdict read, and the typed checkpoint_blocked decline now
fires only for a drive takeover — a bare load or a cancellation-only
load keeps the baseline retriable 409 (main's new witness pins the bare
shape; a new drive-shape witness pins the decline).

R10 (qwen-code-ci-bot review 5410379695, four Criticals) shared one root
cause: a plain attach was minted while the journal held an unsettled
Turn with no live owner of the wait.

- R10-1: the attached cancellation redrive of a parked no-tool Turn no
  longer mints a plain attach; no kernel is consulted on that arm, so
  there was no inapplicable to mirror — the load keeps the cold arm's
  retriable refusal, drive keeps its typed decline.
- R10-2: the prompt route guards the journal itself — a fresh promptId
  is refused 409 hosted_prompt_recovery_required whenever any input is
  unsettled, whatever the takeover answered; no admission can stack on a
  parked Turn's mid-flight checkpoint.
- R10-3: all three plain-attach epoch migrations (plus the recovered
  path's) move ONLY the epoch and keep the consumed watermark, so a
  committed-but-undelivered tail — including turn.settled — replays
  instead of being skipped behind the exclusive cursor; the plain cancel
  route answers an honest 409 when nothing live can be aborted while the
  journal is unsettled, and the CANCELLING arm adopts + streams on that
  coded refusal instead of re-issuing a no-op cancel.
- R10-4: a null epoch proves only that the admission REPLY was lost; the
  withdraw arm resubmits once, and on the coded duplicate-admission 409
  adopts the attach epoch via a split-expectation recordRecoveryAdmission
  (turn epoch vs session epoch — a session can still carry an earlier
  Turn's), keeping the watermark; without a prior mark the refusal names
  a different Turn's work, so it burns the retry budget and records the
  daemon's own code on exhaustion.

Also: recordRecoveryAdmission's epoch CAS predicates are null-safe with
split turn/session expectations (a real-store adoption witness proves
`IS NULL` matches and a wrong expected session epoch still refuses);
the R9 journal-replay's detached async writer now answers a failed
identity read with the retriable refusal instead of hanging the request;
the re-prove identity guard gains its workspaceId-mismatch witness; the
sibling wholesale store mock exports ManagedSessionStoreHttpError.

Witnesses: CLI suites 243/243 across the three hosted files (new R10
tests plus the merge-restored main witness), HarnessCoordinatorTest
51/51, ManagedAgentServerIntegrationTest 29/29 (real H2), full module
suites managed-agent-server 744/744 and qwencode 181/181, npm run
build, typecheck, eslint.
yiliang114 added a commit to yiliang114/qwen-code that referenced this pull request Oct 5, 2026
…#13434)

The baseUrl guard added in QwenLM#13210 rejects non-loopback plaintext hosts
unless allowInsecureHttp is set. Nine construction sites in
http-managed-session-store.test.ts still used the fake host
http://session-store.test without the opt-in, so 12 tests have failed on
main since that merge and now block CI on every PR that includes latest
main. Pass allowInsecureHttp: true to those fixtures; the dedicated
guard-negative test is untouched.
qwen-code-dev-bot pushed a commit that referenced this pull request Oct 5, 2026
- pin both halves of the parseLine pair: the record half now asserts the
  normalized message (so a raw-as-record substitution goes red), and a new
  case keeps one slot per physical record that fails validation
- witness the non-object owner-payload disjunct that stands between a
  primitive systemPayload on disk and a TypeError from the 'in' operator
- widen the restore-head grant table to all five disjuncts and the
  journal-vs-head agreement test to all three, via disjoint headOverrides
- make the fake store honour the requested page limit so the two-page
  count derives from production's limit=100 request
- merge resolution: opt the fake-host store constructions into
  allowInsecureHttp after main's plaintext-HTTP guard (#13210)

Each new row was mutation-probed: it goes red when its own disjunct (or
the limit) is removed and stays green on pristine source.

Co-authored-by: Qwen-Coder <[email protected]>
qwen-code-dev-bot added a commit that referenced this pull request Oct 5, 2026
Resolve the overlap with the broker-auth work (#13210):

- bridgeTypes.test.ts (add/add): union of both suites, plus the
  127-prefixed DNS-name reject rows the loopback predicate's per-octet
  numeric test is pinned by, and a row covering the opt-in below.
- bridgeTypes.ts: the HTTPS-off-loopback rule predates the
  broker-provisioned allowInsecureHttp opt-in that arrived with this
  merge; honor it (mirroring http-managed-session-store.ts) so the field
  stays reachable through the bridge parser, and correct the loopback
  predicate's JSDoc premise — a four-label name whose labels are not all
  numeric survives the URL parser verbatim.
- hosted-harness-session.test.ts: keep main's loopback baseUrl in the
  bounded-store actual call; the factory-refusal test's descriptor now
  carries allowInsecureHttp so the store factory is what rejects it.

Co-authored-by: Qwen-Coder <[email protected]>
qwen-code-dev-bot pushed a commit that referenced this pull request Oct 5, 2026
Resolve the http-managed-session-store.test.ts conflict by keeping both
sides' new suites (this PR's six witness tests and main's #13341 fake-server
override suite) and adopting main's bootStoresAndSession helper.

Review round: opt the five new createHttpManagedSessionStores call sites
into allowInsecureHttp so the six tests they set up actually run (R4-1) —
plaintext guard from #13210 reached this branch through the merge. Drop
this branch's per-call-site CommonJS module-scope pins in
terminal-image-renderer.test.ts and test-efficacy.integration.test.ts now
that main pins the same shims at suite/builder level (R4-2); verified green
under an ambient {"type":"module"} TMPDIR, and mermaidImageRenderer
returns to 13 failed if main's builder pin is removed.

Co-authored-by: Qwen-Coder <[email protected]>
ShadyAV pushed a commit to ShadyAV/qwen-code that referenced this pull request Oct 5, 2026
QwenLM#13341)

* test(core): close QwenLM#12693 post-merge review test and hygiene gaps

Second batch of QwenLM#12693 post-merge review follow-ups — the test-coverage and
hygiene findings that remained after the correctness PR:

- The HTTP store suite never drove a failing or inconsistent server: the fake
  now takes page/receipt overrides and stored-integrity edits, pinning the
  multi-page read, contiguous-revision, bytes-vs-metadata, empty-page,
  nextRevision, echo-receipt mismatch and 409 resource-missing paths.
- A sealed Managed session can now be seen to survive daemon archive through
  the sealed-writer fallback (red if that arm is removed; mutation-checked).
- The managed-resume rejection test stubs QWEN_RUNTIME_DIR — ambient exports
  outrank setRuntimeBaseDir and redirected the hook to the ambient runtime.
- sink success-path coverage: session_source and file_history_snapshot domain
  round-trips, carried system subtypes through the message channel, and the
  remaining three turn_result guard disjuncts; the unmapped-refusal fixture
  now uses a genuinely unmapped subtype (rewind instead of the mapped
  file_history_snapshot).
- The live-vs-cold projection difference is documented at both sites and
  pinned by a test asserting the deliberate narrower live door.
- parseLine returns the validation it already ran, so transcript indexing no
  longer validates every record twice; the accumulator's four unavailability
  reasons and the empty-transcript default gain a collocated suite.
- isLockRecord's managed-sealed branch reuses isValidCommitProof instead of
  re-implementing it; the misplaced recoverUncommittedTail safety contract
  now sits on the method that truncates; the unused de-facto-private export
  of buildManagedSessionRestoreProjection is dropped.

* test(core): cover the restore head mismatch and align the 409 comment

- The journal-vs-durable-head check now has a witness: a server head that
  overstates committedSequence is refused after the page loop.
- The staged-survival assertion's comment no longer overclaims a literal
  retry of the same missing resource (review R1-3).

* test(core): address the round-1 review on the test batch

Addressing the seven Suggestion findings from the auto-review, each with the
mutation witness the review asked for:

- The restore-journal head guards now have witnesses too, via headOverrides:
  a storageVersion/recoveryStatus mismatch is refused by the grant check, an
  overstated committedSequence fails journal-vs-head, a stale eventsDigest
  fails metadata-vs-records, and a page that promises the end while the head
  is ahead is refused (the 'did not advance' branch was already pinned).
- The 409 test drives a real retry of the same transaction with a staged ref
  it carries, asserting the retried commit body still posts bytesBase64 —
  exactly the regression that would appear if a failure path ever released
  staged entries.
- SessionExecutionEngineAccumulator.parseLine's returned
  `{ value, record }` pair is pinned independently on both halves, proving
  the raw line value survives where the normalized record drops fields.
- The hot/wide projection distinction is documented as width, not lifecycle —
  `projectManagedSessionRecords` is named as the reader-facing list used on
  live paths too (`readActiveTranscriptChain`) — and the pin test's title
  and inline comment say the same.
- `turn_result` case rejection now asserts both halves of the
  canCarry/write agreement on the same record object.
- Carried system subtypes assert their message channel (one
  message.committed, zero domain.committed) before the cold round-trip.
- QWEN_RUNTIME_DIR is deleted once in each package's setup file (preload of
  every suite) instead of two per-test stubs, closing the class: 15 ambient
  failures in session-transcript-reader and 4 ACP lock failures on
  writer-lease reproduce on ambient machines and now pass.

* test(core): address the round-2 review on the test batch

- pin both halves of the parseLine pair: the record half now asserts the
  normalized message (so a raw-as-record substitution goes red), and a new
  case keeps one slot per physical record that fails validation
- witness the non-object owner-payload disjunct that stands between a
  primitive systemPayload on disk and a TypeError from the 'in' operator
- widen the restore-head grant table to all five disjuncts and the
  journal-vs-head agreement test to all three, via disjoint headOverrides
- make the fake store honour the requested page limit so the two-page
  count derives from production's limit=100 request
- merge resolution: opt the fake-host store constructions into
  allowInsecureHttp after main's plaintext-HTTP guard (QwenLM#13210)

Each new row was mutation-probed: it goes red when its own disjunct (or
the limit) is removed and stays green on pristine source.

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

* test(cli): scope fake renderer scripts as CommonJS under a poisoned tmpdir

Deterministic verification rejected the round-2 commit: 15 packages/cli
tests in mermaidImageRenderer, terminal-image-renderer and test-efficacy
failed with renderer results of kind 'unavailable'. The fake mmdc/chafa/git
executables those suites write under os.tmpdir() are extensionless CommonJS
scripts, and the runner's /tmp/package.json carries "type": "module", so
node executed them as ES modules and they crashed on require/__dirname
('require is not defined in ES module scope'), which the renderers then
report as chafa being unavailable.

Write a {"type":"commonjs"} package.json next to each fake so the
nearest-scope lookup pins the module system regardless of what sits above
the host's temp directory. Verified both ways: the three files are green
with the poisoning in place and stay green with TMPDIR pointed at a clean
directory.

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

---------

Co-authored-by: qwen-code-ci-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
wenshao added a commit that referenced this pull request Oct 5, 2026
Conflicts resolved:
- managed-agent-public-api.openapi.json: #13210 took v1.30.0 with its
  gateway-free qwenSignature scheme this morning; the W2 cwd contract now
  ships v1.31.0, with both changelog sentences kept and the contract's
  two (v1.30) feature references re-anchored to (v1.31).
- README.md: keep both feature sections - main's broker authentication
  and writer credentials first, the W2 controlled cwd change after, with
  the settle retry budget recorded in its refusal paragraph.
- web-shell generated client: regenerated from the resolved contract,
  so it carries the v1.31 text and main's 413 payload_too_large arm.
The cwd migration is V45 after upstream took V40-V44 this morning.
wenshao added a commit to wenshao/qwen-code that referenced this pull request Oct 7, 2026
…#13565)

Move the merged-PR history of the Managed Agent dual-path proposal
(QwenLM#12380) out of the issue body into a bilingual ledger under
docs/design/. The issue body had come within 10 KB of GitHub's 256 KiB
limit, so the body will keep only the delivery snapshot and the open
PRs, and merged rows move here rewritten to their merged final state.

The ledger carries every merged row the issue tracked up to its
2026-10-03 reconcile (adding the missing merge commit to the five
earliest rows and normalising the Chinese state cells), rewrites the
eight rows the issue still listed as open although their PRs had merged
(QwenLM#13141, QwenLM#13166, QwenLM#13174, QwenLM#13210, QwenLM#13214, QwenLM#13217, QwenLM#13218, QwenLM#13247), and
adds rows for the 48 managed-agent PRs merged between that reconcile and
main 0c13502 that had no row yet. PRs closed without merging (QwenLM#13087,
QwenLM#13336) sit in their own table. Later merges land at the next reconcile.

Co-authored-by: wenshao <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature(managed-agent): broker authentication and broker-provisioned writer credentials

4 participants