Skip to content

feat(managed-agent): enforce workspace actor roles across the bound-Session surface - #13545

Merged
wenshao merged 30 commits into
mainfrom
feat/13535-roles-enforcement
Oct 9, 2026
Merged

wenshao merged 30 commits into
mainfrom
feat/13535-roles-enforcement

Conversation

@wenshao

@wenshao wenshao commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Enforces the Workspace actor roles across the bound-Session surface (contract v1.36), advancing #13535. On top of the registry slice and the role-storage slice, the three former creator checks become role/owner checks: Turn submit, cancel, rename and cwd change now admit any caller holding OPERATOR on the bound Workspace — under the unchanged shape gates and while the Session's creator-keyed execution facts hold (the Registry still backs the binding and stays ACTIVE, and the actor recorded by the Workspace create command keeps OPERATOR or above), because the admitted work executes under the creator's grants; crossing that line answers the family's domain 409 workspace_unavailable synchronously at admission instead of returning a 202 for a Turn that could only fail asynchronously. Approval (Action) respond admits the Session's recorded owner or any OPERATOR, under no shape gate — so a second operator on the same Workspace can now answer a pending approval that blocks a Turn, the product behaviour the issue names as blocked. Close/archive/unarchive/delete admit the Session's recorded owner (owner_actor_key, with the pre-V40 creator/create-command fallback). The refusal vocabulary normalises to the #12867 contract: below the read grant answers 404 (session_not_found, invisible as before); a readable actor below OPERATOR answers 403 session_operation_forbidden on the submitter family (was 409), 403 action_forbidden on respond. The WebShell workspaceTurns capability mirrors server admission exactly: caller OPERATOR plus the same creator-keyed facts; the page's former create-command batch read becomes a creator-facts batch read, so the page stays a constant number of queries (pinned by Issue13181QueryBudgetTest). The surface registry flips cwd and Action respond OWNER→OPERATOR with the enforcement, its probes pin the new matrix on both surfaces, the contract changelog records v1.36, and the generated TypeScript types are regenerated.

Deliberately preserved: legacy (unbound) Sessions stay tenant-wide; execution (authorizePassiveAttachment) and cwd settlement still re-verify the recorded creator's grant set — execution authority semantics are unchanged, and admission now certifies the same facts synchronously on every family that runs under them; a cwd operation admitted before its initiator's own demotion still commits (the operation row stores actor_digest, not the actor key — the W2 revocation sentence is scoped to the recorded actor, documented in contract text and the design); legacy open-mode Action answering stays tenant-owned for unbound Sessions only; internal writer routes are untouched.

Why it's needed

#13535's R1: every bound-Session operation admitted only its creator, so a Hosted approval could only be answered by the Session's creator (a second operator could not unblock a Turn) and nothing could express an owner distinct from the creator. #12867 section 10 fixed the vocabulary (reader/operator/owner, 404-below-read/403-below-operate); this PR enforces it per route and flips the registry it landed on, under the build gate that makes future routes unable to skip the check.

R1 review round (this push)

The round-1 review's two Criticals and 32 Suggestions are resolved; each thread carries a reply naming the code/test that addresses it. Headline changes since the reviewed head:

  • Caller/authority correlation restored (R1-1, Critical). Submitter-family admission now requires the creator-keyed execution facts as well as the caller's OPERATOR role (hasExecutionRegistryFacts / batched sessionsWithExecutionRegistryFacts), so a demoted creator means a synchronous 409 rather than an accepted Turn that asynchronously dies in the execution authority. HostedPublicWorkspaceIT pins the IT acceptance (second OPERATOR: 409, no PENDING row, no model request, no tool execution) and the headline widening end to end (a non-creator OPERATOR submits a later Turn and it executes, cancels, and renames).
  • cwd contract narrowed to the shipped gate (R1-2, Critical). Its changelog sentence and both cwd descriptions now state the recorded create-command actor's grant is required at admission and settlement, and that the settlement re-verifies the recorded actor rather than the initiator (an operation admitted before the initiator's demotion commits; pinned by ManagedCwdChangeOperationTest.settlementKeepsAnAdmittedChangeWhenOnlyTheInitiatorDropped and the design's D4 note).
  • Replay before mutable role (R1-15). On every family whose replay is actor-scoped, the invisibility check runs first, then the idempotency replay, then the mutable role refusal — so a role revoked after admission still resolves a retry to the caller's own operation (aDemotedOperatorStillReplaysTheirAdmittedChange; requireOwner split into identity/role arms with the role arm after the replay).
  • Docs and contract truthfulness. The seven false normative claims, the malformed §2 table (escaped pipes, both languages), the stale "21 internal" provenance, the registry javadoc for the OPERATOR/INTERNAL rule classes, and the responder ownerless fall-through (now unbound-only in all 8 description strings, pinned by the extended contract test).
  • Suite load-bearingness. New or tightened pins: pre-V40 NULL/NULL owner fallback; OWNER-role fixture arms; SESSION_LIST bound-absence; owner-READER lifecycle admission; publicationTokenDecidesThroughTheStoredGrantComparison and receipts/commit 403; unconditional null-actor probes; method-agnostic @RequestMapping fails the gate (SurfaceRegistryGateUnconstrainedTest); the dual-purpose events route's ordinal-first capability; operatorAnswered.hasSize(2) in the approvals IT; the WebShell forbidden latch now survives Session navigation but re-probes on retry, with EN/zh strings matching the server's rule.

Deferred deliberately, now tracked: #13617 (Session handover command), #13618 (legacy-Session hardening), #13619 (submitter-family command idempotency actor scope).

Reviewer Test Plan

How to verify

With JDK 21 and Maven 3.9+: mvn -q -DskipTests install in packages/sdk-java/runtime-broker, then mvn -B test in packages/sdk-java/managed-agent-server. Key witnesses: (1) SurfaceAdmissionAcceptanceTest — the full post-v1.36 matrix including OWNER-rank arms and null-actor probes, plus secondOperatorAnswersPendingApprovalsOnBothSurfaces; (2) ManagedCwdChangeOperationTest — creator-only demotion → 409 at admission, initiator demotion → committed settlement, demoted-operator replays; (3) HostedPublicWorkspaceIT (real signed HTTP) — non-creator OPERATOR executes a later Turn, 409 when only the creator is demoted, operatorAnswered.hasSize(2); (4) Issue13181QueryBudgetTest — constant page queries with the new facts batch; (5) SurfaceRegistryGateTest + â€ĶNegativeTest + â€ĶUnconstrainedTest — the gate fails close on unregistered, handler-less, and method-agnostic routes; (6) ManagedWorkspaceAdmissionTest.aPreV40BoundSessionFallsBackToTheCreateCommandOwner. Cross-check the contract: openapi version 1.36.0 with the v1.36 changelog sentence; packages/web-shell npm run generate:managed-agent-api produces no drift; use-managed-actions.test.tsx covers the retry re-probe.

Evidence (Before & After)

Before: submitter family readable non-creator → 409 workspace_unavailable; approval respond limited to the creator (403 action_forbidden); cwd limited to the creator (403). After: any OPERATOR admitted on all three families while the creator-keyed facts hold; readable-below-OPERATOR → 403 on all three; owner keeps lifecycle exactly as the creator had it; a demoted creator stops the widened family synchronously with 409 instead of an async failure. The acceptance-probe diff in this PR is the human-readable before/after matrix.

Tested on

OS Status
🍏 macOS ✅
🊟 Windows ⚠ïļ
🐧 Linux ⚠ïļ

Environment (optional)

JDK 21 (Zulu 21.0.11), Maven 3.9.11, H2 MySQL-mode; suites: runtime-broker green; managed-agent-server full suite green — 1404 passed, 0 failures (1 pre-existing skip). mvn checkstyle:check spotbugs:check green on both modules. Hosted MySQL failsafe profiles left to CI (the local harness environment cannot warm a runtime — the IT aborts at runtime_warm before the first Turn, independent of this diff).

Risk & Scope

Linked Issues

Refs #13535 — this PR delivers the vocabulary enforcement it names (second-operator approval handoff through the role column, plus route-level role checks and owner records driving every former creator check); the two behaviours it defers land as #13617 (Session handover command) and #13618 (legacy-Session hardening). Also resolves #12867's roles follow-up vocabulary enforcement (Q4 answered in the design doc). The submitter-family command idempotency gap the review surfaced is tracked as #13619.

Stacked delivery for #13535: #13543 (surface registry + gate, R2, merged) and #13544 (V53 role storage, merged) precede this PR; with #13544 merged this PR is retargeted to main and the CI lanes run again (the feature-branch base had matched no pull_request branch filter). Design docs: docs/design/2026-10-07-managed-agent-actor-roles.{md,zh-CN.md}.

äļ­æ–‡čŊŽ

朎 PR 做äŧ€äđˆ

åœĻįŧ‘åۚ Session éĒäļŠåžšåˆķæ‰§čĄŒ Workspace actor 角č‰ēåĨ‘įšĶ v1.36æŽĻčŋ› #13535。åœĻæģĻ册čĄĻåˆ‡į‰‡äļŽč§’č‰ē存å‚Ļåˆ‡į‰‡äđ‹äļŠïžŒäļ‰äļŠåŽŸåˆ›åŧšč€…æ ĄéŠŒå˜äļšč§’č‰ē/owner æ ĄéŠŒïžšTurn 提äšĪ、取æķˆã€æ”đ名äļŽ cwd 变æ›īįŽ°åœĻ准å…Ĩäŧŧä―•åœĻčŊĨ Workspace 持有 OPERATOR įš„č°ƒį”Ļæ–đ —— å―Ēæ€é—Ļäļå˜ïžŒäļ” Session įš„åˆ›åŧšč€…é”Ūæ‰§čĄŒäš‹åŪžåŋ…éĄŧ成įŦ‹ïžˆRegistry äŧæ”Ŋ撑čŊĨįŧ‘åۚåđķåĪ„äšŽ ACTIVEWorkspace 创åŧšå‘―äŧĪčŪ°å―•įš„ actor äŋæŒ OPERATOR 及äŧĨäļŠïž‰ïžŒå› äļščĒŦ准å…Ĩįš„å·Ĩä―œäŧĨ创åŧšč€…įš„æŽˆæƒæ‰§čĄŒïž›čķŠčŋ‡čŋ™æĄįšŋåœĻ准å…Ĩæ—ķ同æ­Ĩå›žį­”æ—įš„åŸŸåįš§ 409 workspace_unavailableïžŒč€Œäļæ˜Ŋįŧ™äļ€äļŠåŠäžšåž‚æ­ĨåĪąčīĨįš„ Turn čŋ”回 202。åŪĄæ‰đActionïž‰å›žį­”å‡†å…Ĩ选 Session įš„å·ēčŪ°å―• owner 或äŧŧä―• OPERATORäļæŒ‚å―Ēæ€é—Ļ —— 同äļ€ Workspace äļŠįš„įŽŽäšŒäļŠ operator įŽ°åœĻčƒ―å›žį­”é˜ŧåĄž Turn įš„åū…į­”åŪĄæ‰đåģ issue į‚đ名čĒŦé˜ŧåĄžįš„äš§å“čĄŒäļšã€‚close/archive/unarchive/delete 准å…Ĩ Session įš„å·ēčŪ°å―• ownerowner_actor_keyåļĶ V40 äđ‹å‰įš„ creator/create-command 回退。拒įŧčŊčĄĻå―’äļ€åˆ° #12867 åĨ‘įšĶïžšä―ŽäšŽčŊŧæŽˆæƒå›žį­” 404session_not_foundäļåŊč§æ€§äļå˜ïž‰ïž›åŊčŊŧä―†ä―ŽäšŽ OPERATOR įš„ actor åœĻ submitter æ—å›žį­” 403 session_operation_forbidden原äļš 409åœĻå›žį­”č·Ŋį”ąäļš 403 action_forbidden。WebShell workspaceTurns čƒ―åŠ›įēūįĄŪé•œåƒæœåŠĄįŦŊ准å…Ĩïžšč°ƒį”Ļæ–đ OPERATOR 加äļŠåŒæ ·įš„创åŧšč€…é”Ū之åŪžïž›éĄĩéĒ原æĨįš„ create-command æ‰đ量čŊŧ取æĒ成创åŧšč€…äš‹åŪžæ‰đ量čŊŧ取æŸĨčŊĒæ•°äŋæŒåļļ量Issue13181QueryBudgetTest é’‰ä―ïž‰ã€‚surface æģĻ册čĄĻéšæ‰§čĄŒæŠŠ cwd äļŽ Action respond äŧŽ OWNER įŋŧ到 OPERATORæŽĒ针åœĻäļĪäļŠéĒäļŠé’‰ä―æ–°įŸĐé˜ĩåĨ‘įšĶ changelog čŪ°å―• v1.36ïžŒį”Ÿæˆįš„ TypeScript įąŧ型å·ēé‡æ–°į”Ÿæˆã€‚

åˆŧ意äŋæŒäļå˜ïžšlegacy朊įŧ‘åŪšïž‰Session äŋæŒį§Ÿæˆ·įš§ïž›æ‰§čĄŒïžˆauthorizePassiveAttachmentäļŽ cwd įŧ“įŪ—äŧåĪæ ļå·ēčŪ°å―•åˆ›åŧšč€…įš„æŽˆæƒé›†åˆ —— æ‰§čĄŒæƒčŊ­äđ‰äļå˜ïžŒč€Œå‡†å…ĨįŽ°åœĻåœĻæŊæĄäŧĨčŊĨæŽˆæƒæ‰§čĄŒįš„æ—äļŠåŒæ­Ĩ担äŋåŒäļ€įŧ„äš‹åŪžïž›å‘čĩ·č€…朎䚚准å…Ĩ后čĒŦé™įš§įš„ cwd æ“ä―œäŧäžšæäšĪïžˆæ“ä―œčĄŒåŠå­˜ actor_digest —— W2 æ’Ī销åĨįš„é€‚į”Ļ范å›ī限åۚäļščŊĨčŪ°å―• actoråĨ‘įšĶ文朎äļŽčŪūčŪĄå‡å·ē写明legacy 垀æ”ūæĻĄåžįš„ Action å›žį­”äŧ…åŊđæœŠįŧ‘åۚ Session äŋæŒį§Ÿæˆ·å…ąæœ‰ïž›å†…éƒĻ writer č·Ŋį”ąäļåŠĻ。

äļšäŧ€äđˆéœ€č́

#13535 įš„ R1ïžščŋ„äŧŠäļšæ­ĒæŊäļŠįŧ‘åۚ Session æ“ä―œåŠå‡†å…Ĩ创åŧšč€…因æ­Ī Hosted åŪĄæ‰đåŠčƒ―į”ą Session 创åŧšč€…å›žį­”ïžˆįŽŽäšŒäļŠ operator 无æģ•č§Ģé™Īé˜ŧåĄžįš„ TurnäđŸæēĄæœ‰äŧŧä―•äļœčĨŋčƒ―čĄĻčūūäļŽåˆ›åŧšč€…äļåŒįš„ owner。#12867 įŽŽ 10 节钉äļ‹äš†čŊčĄĻreader/operator/ownerïžŒä―ŽäšŽčŊŧ者 404ã€ä―ŽäšŽæ“ä―œč€… 403朎 PR 逐č·Ŋį”ąåžšåˆķæ‰§čĄŒåŪƒïžŒåđķįŋŧč―Žå…ķč―åœ°įš„æģĻ册čĄĻį―Ū䚎ä―ŋ朊æĨč·Ŋį”ąæ— æģ•įŧ•čŋ‡æ ĄéŠŒįš„æž„åŧšé—Ļį́äđ‹äļ‹ã€‚

R1 čŊ„åŪĄč―ŪïžˆæœŽæŽĄæŽĻ送

įŽŽäļ€č―ŪčŊ„åŪĄįš„ 2 æĄ Critical äļŽ 32 æĄ Suggestion å·ēåĪ„į―ŪïžŒé€æĄ thread 有回åĪčŊŽæ‰ŋč――įš„äŧĢ᠁/æĩ‹čŊ•。į›ļåŊđčĒŦčŊ„åŪĄåĪīįš„åĪīæĄå˜åŒ–ïžš

  • æĒåĪč°ƒį”Ļæ–đäļŽæ‰§čĄŒæƒįš„į›ļå…ģR1-1Criticalsubmitter 族准å…ĨåœĻ调į”Ļæ–đ OPERATOR äđ‹åĪ–åŒæ—ķčĶæą‚åˆ›åŧšč€…é”Ūæ‰§čĄŒäš‹åŪžïžˆhasExecutionRegistryFacts äļŽæ‰đ量 sessionsWithExecutionRegistryFacts创åŧšč€…čĒŦé™įš§åģ同æ­Ĩ 409äļå†å‡šįްčĒŦæŽĨ受åīæģĻåۚåœĻæ‰§čĄŒæŽˆæƒåĪ„åž‚æ­Ĩæ­ŧæŽ‰įš„ Turn。HostedPublicWorkspaceIT é’‰ä― IT 驌æ”ķïžˆįŽŽäšŒäļŠ OPERATORïžš409、无 PENDING čĄŒã€æ— æĻĄåž‹čŊ·æą‚、无å·Ĩå…·æ‰§čĄŒïž‰äļŽåĪīæĄæ”ūåŪ―įš„įŦŊ到įŦŊ非创åŧšč€… OPERATOR 提äšĪ后įŧ­ Turn åđķᜟæ­Ģæ‰§čĄŒïžŒéšåŽå–æķˆäļŽæ”đ名。
  • cwd åĨ‘įšĶæ”ķįŠ„åˆ°å·ē发čī§įš„é—ĻįĶïžˆR1-2CriticalåŊđåš”åĨ‘įšĶåĨäļŽäļĪäļŠ cwd 描čŋ°įްåœĻ写明创åŧšå‘―äŧĪčŪ°å―• actor įš„æŽˆæƒåœĻ准å…ĨäļŽįŧ“įŪ—éƒ―éœ€čĶïž›įŧ“įŪ—åĪæ ļčŪ°å―• actor č€Œéžå‘čĩ·č€…发čĩ·č€…准å…Ĩ后čĒŦé™įš§įš„æ“ä―œäŧäžšæäšĪïž›ManagedCwdChangeOperationTest.settlementKeepsAnAdmittedChangeWhenOnlyTheInitiatorDropped äļŽčŪūčŪĄ D4 æģĻé’‰ä―ïž‰ã€‚
  • 重æ”ū先䚎åŊå˜č§’č‰ēR1-15ïž‰ïžšå‡Ąé‡æ”ū按 actor 域限åŪšįš„æ—ïžŒéĄšåšįŧŸäļ€äļšäļåŊč§æ€§ → åđ‚į­‰é‡æ”ū → åŊå˜č§’č‰ē拒įŧ —— 准å…Ĩ后čĒŦæ’Īé”€įš„č§’č‰ēåœĻ重čŊ•æ—ķäŧčƒ―č§Ģæžåˆ°æœŽč°ƒį”Ļæ–đč‡Šå·ąįš„æ“ä―œïžˆaDemotedOperatorStillReplaysTheirAdmittedChangeïž›requireOwner 拆äļščšŦäŧ―臂/角č‰ēč‡‚ïžŒč§’č‰ē臂åœĻ重æ”ūäđ‹åŽïž‰ã€‚
  • 文æĄĢäļŽåĨ‘įšĶåĶ‚åŪžïžšäĄåĪąåŪžč§„čŒƒæ€§é™ˆčŋ°ã€Â§2 į•ļå―ĒčĄĻ栞äļĪčŊ­čĻ€č―Žäđ‰įŪĄé“įŽĶ、「21 internal」čŋ‡æ—ķįš„å‡šåĪ„ã€OPERATOR/INTERNAL č§„åˆ™įąŧįš„ registry javadoc、responder įš„ ownerless 回退å…ĻéƒĻ 8 åĪ„æčŋ°é™åۚäļšäŧ…朊įŧ‘åŪšïžŒæ‰Đåą•åŽįš„åĨ‘įšĶæĩ‹čŊ•é’‰ä―ïž‰ã€‚
  • åĨ—äŧķæ‰ŋ重新åĒžæˆ–æ”ķįī§įš„é’‰į‚đïžšV40 前 NULL/NULL owner 回退OWNER įš§č§’č‰ēåĪđå…·č‡‚ïž›SESSION_LIST įŧ‘åŪščĄŒäļåŊč§ïž›owner äļš READER æ—ķįš„į”Ÿå‘―å‘Ļ期准å…Ĩïž›publicationTokenDecidesThroughTheStoredGrantComparison äļŽ receipts/commit 403无 actor įš„å…ĻįĻ‹æŽĒ针æ–đæģ•æ— å…ģ @RequestMapping ä―ŋé—Ļį́åĪąčīĨSurfaceRegistryGateUnconstrainedTest双į”Ļ途 events č·Ŋį”ąįš„éĶ–äļŠ capability 断čĻ€ïž›approvals IT įš„ operatorAnswered.hasSize(2)ïž›WebShell 拒įŧ latch č·Ļ Session åŊžčˆŠäŋæŒã€æ˜ū垏 retry æ—ķ重æŽĒïž›EN/zh æ–‡æĄˆäļŽæœåŠĄå™Ļč§„åˆ™äļ€č‡ī。

有意åŧķ后åđķå·ēįŦ‹éĄđïžš#13617Session į§ŧäšĪå‘―äŧĪ、#13618legacy Session æ”ķįī§ïž‰ã€#13619submitter æ—å‘―äŧĪåđ‚į­‰é”Ūįš„ actor 域。

čŊ„åŪĄæĩ‹čŊ•čŪĄåˆ’

åĶ‚ä―•éŠŒčŊ

JDK 21 äļŽ Maven 3.9+ïžšåœĻ packages/sdk-java/runtime-broker æ‰§čĄŒ mvn -q -DskipTests installį„ķ后åœĻ packages/sdk-java/managed-agent-server æ‰§čĄŒ mvn -B test。å…ģé”Ū见čŊïžšïžˆ1SurfaceAdmissionAcceptanceTest —— v1.36 后å…ĻįŸĐé˜ĩOWNER įš§č§’č‰ēč‡‚ã€æ—  actor æŽĒ针加 secondOperatorAnswersPendingApprovalsOnBothSurfaces2ManagedCwdChangeOperationTest —— äŧ…创åŧšč€…é™įš§ → 准å…Ĩ 409发čĩ·č€…é™įš§ → įŧ“įŪ—äŧæäšĪïž›é™įš§ operator įš„é‡æ”ū3HostedPublicWorkspaceITïžˆįœŸåŪžį­ū名 HTTP—— 非创åŧšč€… OPERATOR æ‰§čĄŒåŽįŧ­ Turnïž›äŧ…创åŧšč€…é™įš§æ—ķ 409ïž›operatorAnswered.hasSize(2)4Issue13181QueryBudgetTest —— æ–°äš‹åŪžæ‰đæŽĄäļ‹éĄĩéĒæŸĨčŊĒæ•°åļļ量5SurfaceRegistryGateTest + â€ĶNegativeTest + â€ĶUnconstrainedTest —— 朊æģĻ册、无 handler、æ–đæģ•æ— å…ģč·Ŋį”ąäļ‰č‡‚均åĪąčīĨå…ģ闭6ManagedWorkspaceAdmissionTest.aPreV40BoundSessionFallsBackToTheCreateCommandOwner。æ ļåŊđåĨ‘įšĶïžšopenapi version äļš 1.36.0 äļ”åļĶ v1.36 changelog åĨïž›packages/web-shell įš„ npm run generate:managed-agent-api æ— æž‚į§ŧïž›use-managed-actions.test.tsx č͆ᛖ retry 重æŽĒ。

čŊæŪ前后åŊđį…§ïž‰

äđ‹å‰ïžšsubmitter 族åŊčŊŧ非创åŧšč€… → 409 workspace_unavailableïž›åŪĄæ‰đå›žį­”äŧ…限创åŧšč€…403 action_forbiddencwd äŧ…限创åŧšč€…403。äđ‹åŽïžšåˆ›åŧšč€…é”Ū之åŪžæˆįŦ‹æœŸé—īäļ‰äļŠæ—éƒ―准å…Ĩäŧŧä―• OPERATORïž›åŊčŊŧä―†ä―ŽäšŽ OPERATOR → äļ‰äļŠæ—éƒ― 403ïž›owner 原样äŋæœ‰åˆ›åŧšč€…æ›ūæœ‰įš„į”Ÿå‘―å‘Ļ期创åŧšč€…čĒŦé™įš§ä―ŋæ”ūåŪ―åŽįš„æ—åŒæ­Ĩ 409 而äļæ˜Ŋåž‚æ­ĨåĪąčīĨ。朎 PR äļ­éЌæ”ķæŽĒé’ˆįš„ diff å°ąæ˜ŊåŊčŊŧįš„å‰åŽįŸĐé˜ĩ。

æĩ‹čŊ•åđģ台

macOS ✅Windows ⚠ïļïž›Linux ⚠ïļã€‚

įŽŊåĒƒïžˆåŊ选

JDK 21Zulu 21.0.11Maven 3.9.11MySQL æĻĄåž H2ïž›åĨ—äŧķïžšruntime-broker įŧŋïž›managed-agent-server å…ĻåĨ—įŧŋ —— 1404 通čŋ‡ã€0 åĪąčīĨ1 äļŠæ—Ē有č·ģčŋ‡ïž‰ã€‚mvn checkstyle:check spotbugs:check äļĪæĻĄå—įŧŋ。Hosted MySQL failsafe profile äšĪįŧ™ CI朎朚 harness įŽŊåĒƒæ— æģ•éĒ„įƒ­ runtime —— IT åœĻéĶ–äļŠ Turn å‰įš„ runtime_warm åĪ„äļ­æ­ĒäļŽæœŽ diff 无å…ģ。

éĢŽé™ĐäļŽčŒƒå›ī

  • äļŧč́éĢŽé™Đæˆ–å–čˆïžšåŪĒæˆ·įŦŊåŊč§įš„æ‹’įŧå˜åŒ– —— 非创åŧšč€…įš„ submitter æ—č°ƒį”ĻåœĻ角č‰ēäŧĨäļ‹åū—到 403 而非 409æ”ūåŪ―åŽįš„æäšĪéĒåœĻ准å…Ĩæ—ķéĒåĪ–æ‹…äŋåˆ›åŧšč€…é”Ū之åŪžïžŒčĒŦčŋ™äš›äš‹åŪžæ‹Ķäļ‹įš„ OPERATOR 同æ­Ĩåū—åˆ°åŸŸåįš§ 409。äļĪč€…åŒäļš issue 意å›ūåđķčŪ°å―•äšŽåĨ‘įšĶ v1.36按įēūįĄŪ᠁åŒđé…įš„åŪĒæˆ·įŦŊåš”æ ļåŊđ changelog。
  • 朊銌čŊ / 范å›īåĪ–ïžšSession handover command: transfer a bound Session's recorded owner between actors #13617 į§ŧäšĪå‘―äŧĪ、Harden legacy (unbound) Sessions to the actor-role vocabulary #13618 legacy Session æ”ķįī§ã€managed_agent_command idempotency key is not actor-scoped on the newly widened submitter family #13619 å‘―äŧĪé”Ū actor 域、HTTP 授权į―ŪåĪ‡ïžˆactor_manager、朎朚 MySQL profile。
  • į ī坏性变化 / čŋį§ŧčŊŽïžšč§’č‰ē存å‚Ļå·ē随 feat(managed-agent): persist workspace actor roles and session owners #13544 äŧĨ V53 č―åœ°ïžˆå• role åˆ—ïžŒįĄŽåŒ–å›žåĄŦåœĻå•į‰ˆæœŽįšŠåū‹ä”ķįž– can_read/can_create朎 PR 及新åĒž V56managed_agent_operation.actor_keyNULL å­˜é‡čĄŒį…§æ—§įŧ“įŪ—ïž‰ïž›äļŠčŋ°åĨ‘įšĶ v1.36 拒įŧį å―’äļ€ã€‚

å…ģ联 issue

Refs #13535 —— 朎 PR äšĪäŧ˜å…ķäļ­į‚đåįš„čŊčĄĻåžšåˆķæ‰§čĄŒïžˆįŧč§’č‰ē列åŪžįŽ°įš„įŽŽäšŒ operator åŪĄæ‰đäšĪæŽĨäŧĨ及逐č·Ŋį”ąįš„č§’č‰ēæ ĄéŠŒäļŽéĐąåŠĻæŊäļŠåŽŸåˆ›åŧšč€…æ ĄéŠŒįš„ owner čŪ°å―•ïž‰ïž›åۃåŧķåŽįš„äļĪéĄđ行äļšåˆ†åˆŦč―åœ°äļš #13617Session į§ŧäšĪå‘―äŧĪäļŽ #13618legacy Session æ”ķįī§ïž‰ã€‚同æ—ķč§Ģå†ģ #12867 įš„č§’č‰ē后įŧ­čŊčĄĻåžšåˆķæ‰§čĄŒïžˆQ4 å·ēåœĻčŪūčŪĄæ–‡æĄĢå›žį­”ïž‰ã€‚čŊ„åŪĄæšīéœēįš„ submitter æ—å‘―äŧĪåđ‚į­‰įžšåĢ跟čŋ›äļš #13619。

#13535 įš„å †å äšĪäŧ˜ïžš#13543surface æģĻ册čĄĻ + é—ĻįĶïžŒR2å·ē合å…ĨäļŽ #13544V53 角č‰ē存å‚Ļå·ē合å…Ĩïž‰å…ˆčĄŒïž›#13544 合å…Ĩ后朎 PR å·ē重åŪšå‘åˆ° mainCI 通道å·ēæĒåĪïžˆæ­Ī前į‰đ性分æ”Ŋ base äļåŒđ配äŧŧä―• pull_request 分æ”Ŋčŋ‡æŧĪ。čŪūčŪĄæ–‡æĄĢïžšdocs/design/2026-10-07-managed-agent-actor-roles.{md,zh-CN.md}。

Review disposition and post-merge audit (2026-10-09)

R1 (34 threads) was dispositioned at b9f90eb; R2 (38 threads) is fully dispositioned across 8c1db10, 3c646ff and the audit-pass commits bf93110 — ba1e8d7: every thread carries an inline reply naming its fix and its witness, and all 72 threads are resolved. The main merge had silently dropped five pre-existing ManagedCwdChangeOperationTest arms (retained-runtime ×2, migration fence ×2, confirmed-release) — restored in 5f117de with the full class at 46 green. A multi-round audit of the whole PR (unbiased production and test reads, adversarial probes on auth ladders, replay ordering, merge double-adds, doc parity and contract wording) then landed: the respond creator-facts gate now covers every bound admission (the identity arm had the same 202-into-a-dead-arbiter hole; pinned by a two-armed witness plus its !admitted && mutation witness), a superseded dead session-mutation replay probe is removed, the workspaceTurns field, the contract changelog and both design languages no longer bundle cancellation into the facts conjunct (cancel admits on role and shape alone; every bound respond certifies the facts), and the README no longer tells a public caller to gate on a WebShell-only field. Final state: mvn test on managed-agent-server is 1404 green with zero failures at ba1e8d7; hosted MySQL profiles stay with CI per the environment note. Contract story after the next CI cycles: main's merged H5b/H5c channel runtime (#13572) took v1.35, so this slice finalises the contract at v1.36 and the actor-key migration as V56 (V54 is #13550's lineage; V55 is #13572's channel instance binding); #13545 is rebased over both without code overlap.

čŊ„åŪĄåĪ„į―ŪäļŽåˆåđķ后åŪĄčŪĄïžˆ2026-10-09

R134 æĄïž‰åœĻ b9f90eb åĪ„į―ŪåیæŊ•ïž›R238 æĄïž‰įŧ 8c1db10、3c646ff äļŽåŪĄčŪĄč―ŪæäšĪ bf93110 — ba1e8d7 å…ĻéƒĻåĪ„į―ŪïžšæŊæĄ thread éƒ―æœ‰æŒ‡æ˜ŽäŋŪåĪäļŽč§čŊįš„čĄŒå†…å›žåĪïžŒ72 æĄå…ĻéƒĻ resolved。main 合åđķæ›ū静éŧ˜äļǿމ 5 äļŠæ—Ēæœ‰įš„ ManagedCwdChangeOperationTest č‡‚ïžˆretained-runtime ×2、čŋį§ŧ栅栏 ×2、įĄŪčŪĪ释æ”ū—— å·ēåœĻ 5f117de æĒåĪïžŒæ•īäļŠįąŧ 46 įŧŋ。随后åŊđæ•ī PR įš„åΚč―ŪåŪĄčŪĄïžˆį”Ÿäš§äļŽæĩ‹čŊ• diff įš„æ— æ–đ向通čŊŧ、授权é˜ķæĒŊ/重æ”ūæŽĄåš/merge 双加/文æĄĢ双čŊ­/åĨ‘įšĶ掊čūžįš„åŊđæŠ—æŽĒé’ˆïž‰č―åœ°ïžšrespond įš„åˆ›åŧšč€…é”Ū之åŪžé—ļįŽ°åœĻč͆ᛖæŊæĄįŧ‘åŪšå‡†å…Ĩidentity 臂æ›ū同样有 202 čŋ›å…ĨäļåŊčūūåˆĪåŪšč€…įš„æīžïž›åŒč‡‚见čŊåŠ  !admitted && å˜åž‚č§čŊé’‰æ­ŧ删é™Ī乆čĒŦäļĪįš§åŒ–å–äŧĢįš„æ­ŧ replay æŽĒ针workspaceTurns 字æŪĩ、åĨ‘įšĶ changelog äļŽåŒčŊ­čŪūčŪĄäļå†æŠŠå–æķˆåđķå…Ĩ之åŪžåˆå–ïžˆå–æķˆåŠéœ€č§’č‰ēäļŽå―Ēæ€ïž›æŊäļŠįŧ‘åۚ respond éƒ―æ‹…äŋäš‹åŪžïž‰ïžŒREADME äļå†æŒ‡åž•å…Žåž€č°ƒį”Ļæ–đåŽŧį­‰äļ€äļŠäŧ… WebShell æ‰æœ‰įš„å­—æŪĩ。įŧˆæ€ïžšba1e8d7 äļŠ managed-agent-server mvn test 1404 įŧŋ、0 åĪąčīĨïž›hosted MySQL profile 按įŽŊåǃčŊŽäšĪ CI。 åĨ‘įšĶ后įŧ­į›˜į‚đïžšmain å·ē合å…Ĩįš„ H5b/H5c 通道čŋčĄŒæ—ķ#13572占䚆 v1.35ïžŒæœŽåˆ‡į‰‡įš„åĨ‘įšĶ因æ­Īæ”ķå°ūäļš v1.36、actor_key čŋį§ŧæ”đ号 V56V54 å―’ #13550 įš„ lineageV55 å―’ #13572 įš„ channel instance binding#13545 å·ēč·Ļčŋ‡äļĪæŽĄäļŧåđē前čŋ›äļ”äŧĢ᠁éĒé›ķ重叠。

wenshao and others added 7 commits October 7, 2026 04:51
Slice B of #13535 (storage only, behavior-invisible):

- V48 migration replaces managed_workspace_access.can_read/can_create
  with a single role column (READER/OPERATOR/OWNER, CHECK-enforced),
  backfilling can_create -> OPERATOR and can_read-only -> READER, and
  adds managed_agent_session.owner_actor_key backfilled from
  creator_actor_key.
- WorkspaceAccess becomes NONE/READER/OPERATOR/OWNER (ordered, with an
  atLeast helper); NONE remains the no-row decision, never storable.
- Every grant reader (canRead, findReadable, listReadable,
  readableDefault, resolveForCreation, authorizePassiveAttachment, the
  cwd-change registry-facts join, the listSessions EXISTS filter) keeps
  its current truth table, re-derived from role.
- insertSession writes owner_actor_key = creator on both bound and
  legacy creation paths; anonymous legacy creation leaves both NULL.
- Test fixtures move to the role column; revocations that previously
  zeroed booleans now delete the grant row (the honest no-access
  equivalent under the CHECK); a (FALSE,TRUE) fixture state that role
  ordering makes unrepresentable becomes a grant-row deletion.
- New tests: V47->V48 migration shape (backfill, owner:=creator, CHECK
  rejects NONE/invalid, post-V48 store-path creation writes owner) and
  session-owner store test (bound/legacy owner write, anonymous NULL).

The full managed-agent-server suite differs from baseline only in the
new tests; ManagedSessionStoreIntegrationTest#holdsRestorePages
InsideThePerPageByteBudget errors identically on unmodified HEAD
(verified in a clean worktree at ac497ae).
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
â€Ķn admission gate

Slice A of #13535 (D5/D6): SurfaceRegistry over today's rules for all 78
mounted handler methods (32 public + 24 WebShell + 22 internal), the
Spring-scanned correspondence gate that fails a route without a registry
entry or a stale entry, and the parametrized acceptance walk that pins
today's per-rule-class refusals on both surfaces plus both arms of the
internal writer credential. Purely additive; no production edits.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
â€Ķackfill

A (can_read=FALSE) row grants nothing under the booleans, but the role relabel would have given it READER (or OPERATOR with can_create). Dropping such rows keeps the migration a pure relabel for every reachable grant; pins the drop for (FALSE,TRUE) and (FALSE,FALSE) shapes in the migration test.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
â€Ķession surface

Closes #13535.

Turn submit/cancel/rename, cwd change and Action respond now admit any
caller holding the OPERATOR role on the bound Workspace (Action respond
additionally keeps the Session-owner arm), so a second operator can
answer a pending approval — the blocked handoff of R1. Lifecycle (close,
archive, unarchive, delete) admits the Session's recorded owner
(owner_actor_key, with the pre-V40 creator fallback) holding a read
grant: exactly today's caller set.

The submitter family's readable-but-below-OPERATOR refusal normalizes
from 409 workspace_unavailable to 403 session_operation_forbidden;
all other refusal vocabularies are unchanged and the shape gates
(deployment files opt-in, ACTIVE qwen-code Session, frozen execution
profile) keep their domain 409 for an admitted OPERATOR.

The SurfaceRegistry flips SESSION_CWD_CHANGE and ACTION_RESPOND from
OWNER to OPERATOR on both surfaces, the D6 probes pin the post-flip
matrix with a non-owner OPERATOR admitted on every OPERATOR-family
route, and the WebShell workspaceTurns capability is computed from the
caller's role instead of creator identity. Contract v1.34 records the
role vocabulary, the refusal normalization and the capability rule; the
generated WebShell types are refreshed with it.
@qwen-code-review-bot

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

Copy link
Copy Markdown
Collaborator

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

✅ Qwen Triage å·ēåŪŒæˆ —— æŸĨįœ‹čŋčĄŒã€‚įŧ“æžœč§æœŽįšŋįĻ‹äļ­įš„各é˜ķæŪĩčŊ„čŪšã€‚

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — every required heading is present and filled in, including the Tested-on matrix and the Risk & Scope bullets.

Problem: real and named, not theoretical. #13535 is open and states the block concretely: every bound-Session operation merged so far admits its creator only, so a Hosted approval can only be answered by the Session's creator and a second operator on the same Workspace cannot unblock a Turn that is waiting on it. That is an observed product gap with a tracker, plus #12380 naming actor/tenant isolation as a production-enablement requirement. Not a "could theoretically" PR.

Direction: aligned. #12867 §10 already fixed the vocabulary (reader/operator/owner, 404-below-read, 403-below-operate); this enforces that existing contract per route rather than inventing a parallel one, and it lands under the surface registry + build gate so a newly added route cannot silently skip the check. Enforcing a contract the repo already agreed to is the right shape for this.

Size: 3,162 changed lines, broken down:

Bucket Lines
Production logic (main Java) 836
Tests 1,535
Contract + generated (openapi.json, managed-agent-api.ts) 82
Design docs (EN + zh-CN) 709

You have admin on this repo, so the two-tier core gate in AGENTS.md does not apply to a maintainer-authored PR — recording the 836 production lines for the trail rather than as an escalation. Worth knowing though: 574 of those 836 are SurfaceRegistry.java, which is #13543's file, so the enforcement slice's own production code is 262 lines. Under the 1000-line advisory either way.

Approach — one thing that needs correcting before review. The description says "this branch is based on feat/13535-role-storage so the diff shows only the enforcement slice", and that the design docs "land with #13543". Neither holds. I diffed all six files this PR shares with #13543 against feat/13535-surface-registry:

File 13543 vs 13545
SurfaceRegistryGateTest.java identical
SurfaceRegistryGateNegativeTest.java identical
SurfaceRegistry.java differs (73 lines)
SurfaceAdmissionAcceptanceTest.java differs (156 lines)
â€Ķactor-roles.md differs (234 lines)
â€Ķactor-roles.zh-CN.md differs (226 lines)

Because #13544's branch does not contain #13543, this PR re-adds all of #13543 — 2,695 of its 2,985 additions (90%). The actual enforcement slice is 290 additions / 177 deletions. Two consequences: a reviewer following the description will review the wrong 10%, and since four of the six files diverge, whichever of #13543/#13545 merges second hits an add/add conflict on files that already exist on main. Rebasing feat/13535-roles-enforcement on top of #13543 (or merging #13543 first and retargeting) would make the diff match the description. The SurfaceRegistry divergence itself looks right — it is exactly the documented cwd + Action-respond OWNER→OPERATOR flip — it just should not be arriving as a second copy.

Risk: no elevated Stage 1e signals — none of the changed files match the revert-correlated path list. The real risk is evidentiary and I have left the detail in the code-review comment: ci.yml and sdk-java.yml both gate their pull_request trigger on branches: [main, release/**, â€Ķ], so with feat/13535-role-storage as the base no CI fires on this PR at all. The Test Plan's "Hosted MySQL failsafe profiles left to CI" cannot be satisfied as filed.

Flagging the stack ordering and the CI gap for discussion; the enforcement code itself is in the next comment. 🔍

äļ­æ–‡čŊŽ

感č°ĒčīĄįŒŪ

æĻĄæŋåیæ•ī ✓ —— 所有åŋ…åĄŦå°čŠ‚éƒ―åœĻäļ”å·ēåĄŦ写包拎æĩ‹čŊ•åđģ台įŸĐé˜ĩäļŽã€ŒéĢŽé™ĐäļŽčŒƒå›ī」。

é—ŪéĒ˜ïžšįœŸåŪžäļ”å·ēį‚đ名äļæ˜ŊᐆčŪšæ€§įš„ã€‚ #13535 åĪ„äšŽ open įŠķ态åđķå…·ä―“čŊŽäš†é˜ŧåĄžį‚đïžščŋ„äŧŠæŊäļŠįŧ‘åۚ Session æ“ä―œåŠå‡†å…Ĩå…ķ创åŧšč€…因æ­Ī Hosted åŪĄæ‰đåŠčƒ―į”ą Session 创åŧšč€…å›žį­”ïžŒåŒäļ€ Workspace äļŠįš„įŽŽäšŒäļŠ operator 无æģ•č§Ģé™ĪčĒŦåŪĄæ‰đé˜ŧåĄžįš„ Turn。čŋ™æ˜Ŋ有 tracker įš„å·ē观æĩ‹äš§å“įžšåĢ加äļŠ #12380 把 actor/tenant 隔įĶŧį‚đ名äļšį”Ÿäš§åŊį”Ļč́äŧķ。äļæ˜Ŋã€Œį†čŪšäļŠåŊčƒ―ã€įš„ PR。

æ–đ向åŊđé―ã€‚ #12867 įŽŽ 10 节å·ē钉äļ‹čŊčĄĻreader/operator/ownerïžŒä―ŽäšŽčŊŧ者 404ã€ä―ŽäšŽæ“ä―œč€… 403朎 PR æ˜Ŋ逐č·Ŋį”ąåžšåˆķæ‰§čĄŒčŋ™äŧ―äŧ“åš“å·ēįŧčŪĪåŊįš„æ—Ē有åĨ‘įšĶïžŒč€Œäļæ˜ŊåĶ造äļ€åĨ—åđķäļ”č―åœĻ surface æģĻ册čĄĻ + 构åŧšé—Ļį́äđ‹äļ‹ïžŒä―ŋ新åĒžč·Ŋį”ąæ— æģ•静éŧ˜č·ģčŋ‡æ ĄéŠŒã€‚

规æĻĄïžš å…ą 3,162 čĄŒå˜æ›ī拆分åĶ‚äļ‹ïžšį”Ÿäš§é€ŧčū‘main Java836 čĄŒã€æĩ‹čŊ• 1,535 čĄŒã€åĨ‘įšĶäļŽį”Ÿæˆæ–‡äŧķopenapi.json、managed-agent-api.ts82 čĄŒã€čŪūčŪĄæ–‡æĄĢäļ­č‹ąïž‰709 čĄŒã€‚

ä― åœĻ朎äŧ“嚓有 admin 权限因æ­Ī AGENTS.md įš„äļĪįš§æ ļåŋƒé—Ļį́äļé€‚į”Ļ䚎įŧĪč€…æœŽäššæäšĪįš„ PR —— čŋ™é‡ŒčŪ°å―• 836 čĄŒį”Ÿäš§äŧĢį åŠæ˜Ŋį•™į—•ïžŒäļæ˜Ŋå‡įš§åĪ„į†ã€‚äļčŋ‡éœ€č́įŸĨ道čŋ™ 836 čĄŒé‡Œæœ‰ 574 行æ˜Ŋ SurfaceRegistry.javaåģ #13543 įš„æ–‡äŧķ所äŧĨåžšåˆķæ‰§čĄŒåˆ‡į‰‡č‡ŠčšŦįš„į”Ÿäš§äŧĢ᠁æ˜Ŋ 262 čĄŒã€‚äļĪį§åĢåū„éƒ―åœĻ 1000 行åΧ PR åŧščŪŪįšŋäŧĨäļ‹ã€‚

æ–đæĄˆ —— 有äļ€åĪ„éœ€čĶå…ˆæ›īæ­Ģ再čŊ„åŪĄã€‚ 描čŋ°é‡Œå†™ã€ŒæœŽåˆ†æ”Ŋ埚䚎 feat/13535-role-storagediff 及åŦåžšåˆķæ‰§čĄŒåˆ‡į‰‡ã€ïžŒåđķčŊīčŪūčŪĄæ–‡æĄĢ「随 #13543 č―åœ°ã€ã€‚äļĪæĄéƒ―äļæˆįŦ‹ã€‚我把朎 PR äļŽ #13543 å…ąæœ‰įš„å…­äļŠæ–‡äŧķ逐äļ€åŊđ feat/13535-surface-registry 做䚆 diffïžšSurfaceRegistryGateTest.java äļŽ SurfaceRegistryGateNegativeTest.java åیå…Ļį›ļ同SurfaceRegistry.java å·Ū 73 čĄŒã€SurfaceAdmissionAcceptanceTest.java å·Ū 156 čĄŒã€č‹ąæ–‡čŪūčŪĄæ–‡æĄĢå·Ū 234 čĄŒã€äļ­æ–‡čŪūčŪĄæ–‡æĄĢå·Ū 226 čĄŒã€‚

因äļš #13544 įš„åˆ†æ”ŊäļåŦ #13543朎 PR 把 #13543 æ•īäļŠé‡æ–°åŠ äš†äļ€é —— 2,985 čĄŒæ–°åĒžé‡Œæœ‰ 2,695 čĄŒïžˆ90%æĨ臩 #13543ïžŒįœŸæ­Ģįš„åžšåˆķæ‰§čĄŒåˆ‡į‰‡æ˜Ŋ 290 čĄŒæ–°åĒž / 177 čĄŒåˆ é™Ī。äļĪäļŠåŽæžœïžšæŒ‰æčŋ°čŊ„åŪĄįš„äššäžšįœ‹é”™é‚Ģ 10%ïž›č€Œäļ”å…­äļŠæ–‡äŧķ里有四äļŠå†…åŪđ分叉#13543 äļŽ #13545 äļ­åŽåˆåđķįš„é‚ĢäļŠäžšåœĻ main äļŠå·ē存åœĻįš„æ–‡äŧķä’ž add/add å†ēįŠã€‚æŠŠ feat/13535-roles-enforcement 变埚到 #13543 äđ‹äļŠïžˆæˆ–先合åđķ #13543 再æ”đ basediff å°ąäžšäļŽæčŋ°äļ€č‡ī。SurfaceRegistry įš„åˆ†å‰æœŽčšŦæ˜ŊåŊđįš„ —— æ­Ģæ˜Ŋ文æĄĢ所čŋ°įš„ cwd äļŽ Action-respond į”ą OWNER įŋŧ到 OPERATOR —— 及æ˜ŊäļčŊĨäŧĨįŽŽäšŒäŧ―拷čīįš„å―Ē垏čŋ›æĨ。

éĢŽé™Đïžš Stage 1e æ— å‡įš§äŋĄå· —— 变æ›ī文äŧķéƒ―äļå‘―äļ­äļŽå›žæŧšį›ļå…ģįš„č·Ŋåū„æļ…å•ã€‚įœŸæ­Ģįš„éĢŽé™ĐåœĻčŊæŪåą‚éĒįŧ†čЂᕙåœĻäŧĢ᠁åŪĄæŸĨčŊ„čŪšé‡Œïžšci.yml äļŽ sdk-java.yml įš„ pull_request č§Ķå‘éƒ―é™åۚ branches: [main, release/**, â€Ķ]因æ­Ī base æ˜Ŋ feat/13535-role-storage æ—ķ朎 PR åیå…Ļäļäžšč§Ķ发äŧŧä―• CI。æĩ‹čŊ•čŪĄåˆ’é‡Œã€ŒHosted MySQL failsafe profile äšĪįŧ™ CIã€æŒ‰å―“å‰æäšĪæ–đ垏无æģ•å…‘įŽ°ã€‚

å †å éĄšåšäļŽ CI įžšåĢ先提凚æĨčŪĻčŪšïž›åžšåˆķæ‰§čĄŒéƒĻåˆ†įš„äŧĢ᠁åŪĄæŸĨ见äļ‹äĄčŊ„čŪšã€‚ 🔍

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

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

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Code review

My independent read first. Given only the title and #13535's R1, I would have: put the role on the Workspace binding (a Session-keyed role table multiplies rows and needs a fan-out insert per creation), collapse the two grant booleans into one ordered column, funnel every bound-Session mutation through one shared "resolve caller role → compare to the family's minimum" helper so a new route cannot skip it, keep owner distinct from creator with a fallback for pre-V40 rows, and pin the route→role matrix in a registry under a build gate. That is what this stack does, and the registry-plus-gate framing is better than my "one shared helper" — it makes the enumeration itself the test subject instead of trusting each call site. No simpler path that I can see.

The enforcement slice holds up. I checked the parts that would be easy to get wrong:

  • WorkspaceSummary.canCreateSession is role â‰Ĩ OPERATOR && state == ACTIVE (ManagedWorkspaceRegistry.summary), so maySubmitWorkspaceTurn keeps the ACTIVE gate rather than trading it for the role. Correct.
  • accessOf ends in WorkspaceAccess.valueOf(roles.getFirst()) outside its IllegalArgumentException catch, which looked like a 500 waiting for a dirty row. It isn't: V48 adds CHECK (role IN ('READER','OPERATOR','OWNER')), and all three are enum constants. NONE stays the no-grant decision only, as its javadoc claims.
  • Placeholder order in the two new queries matches — accessOf and the requireOwner role arm both bind tenantId, workspaceId, tenantId, workspaceId, key against tenant_id = ?, workspace_id = ?, the two BINARY(513) collation guards, then actor_id = ?. The BINARY(513) guard and the hardcoded role list are the established convention in this file (canRead, listReadable, findReadable all do it), so the inline query is consistent rather than inventive.
  • managed_agent_session.workspace_id (V7), creator_actor_key (V40) and owner_actor_key (V48) all exist, so the widened requireOwner SELECT is schema-valid.
  • Dropping the createdSessions batch read from webShellSessions is what takes the page budget 5→4, and submitterGrants catching IllegalArgumentException preserves the old degradation (an actor id the registry key cannot encode advertises no workspaceTurns instead of erroring the page). The singular twin catches the same way. Behaviour preserved.
  • Generated TS types match the OpenAPI edits one-for-one — no drift between the contract and managed-agent-api.ts.

One consistency note, not a blocker. The OPERATOR threshold is now expressed twice: as accessOf(...).atLeast(WorkspaceAccess.OPERATOR) on the service/agent-store paths, and as a literal role IN ('OPERATOR', 'OWNER') in ManagedActionStore.requireOwner. ManagedActionStore only holds JdbcTemplate and AgentStateStore, so reusing the registry would add a store→store dependency — a defensible reason to inline it, and the raw-SQL shape matches house convention. Flagging only because the ordering now lives in two places and would drift silently if a role is ever inserted above OPERATOR.

Coverage gap against your own validation plan. Design §7 promises slice-C targeted tests for "second-operator answers a pending approval on both surfaces, OPERATOR submits/cancels/renames/changes cwd". What lands:

Arm Covered by Positive non-owner-OPERATOR admission?
Action respond secondOperatorAnswersPendingApprovalsOnBothSurfaces (202 both surfaces, READER 403) + HostedPublicWorkspaceIT over real signed HTTP (operator2) ✅ yes
cwd change ManagedCwdChangeOperationTest — operator-colleague reaches PENDING; on an archived Session it reaches 409 session_state_conflict, proving the role check passed ✅ yes
Turn submit / cancel / rename probe matrix + IT refusal arms (STRANGER 404, READER 403, demoted owner 403) ⚠ïļ no — the OPERATOR arm asserts 409 workspace_unavailable because the files opt-in is off, and operator2 is only inserted when approvals is true, so no test drives a successful submit/cancel/rename by an OPERATOR who is not the owner

The role check is clearly reached (403 became 404/403/409 in the right places) and Issue13181QueryBudgetTest pins the advertisement side (workspaceTurns flips from index < 3 to index != 3 && index != 6, i.e. true for the two non-creator OPERATOR sessions). So the gap is narrow — but submit/cancel/rename is the widest authorization surface in the change and it is the one arm with no admitted-and-succeeded witness.

The probe matrix itself is the strong part: belowReadAndBelowFamilyProbesFollowTheRuleClass is parameterized over the registry and asserts the per-family refusal code (action_forbidden for ACTION_RESPOND, session_operation_forbidden otherwise), and case OWNER asserts a non-owner OPERATOR gets 403 on lifecycle. Combined with SurfaceRegistryGateTest's bijection against the mounted handlers, that is a real gate rather than a snapshot.

Docs defect. Section 2's table is malformed in both language versions (EN lines 50–54, zh-CN lines 28–32). The cell carrying POST â€Ķ/close|archive|unarchive has raw pipes, which GFM reads as cell separators: the header declares 4 columns, the separator row declares 6, and the Lifecycle row's archive, unarchive, DELETE and the requireWorkspaceCreator / 403 session_operation_forbidden cells all spill into columns the header does not name. #13543 has the same unescaped pipes but in compact form; running a formatter here baked the split into the separator row, so this copy renders worse. Escaping them (close\|archive\|unarchive) fixes both. Separately, EN line 40–41 lost the 2-space continuation indent on the managed_workspace_access(...) bullet. Structure and content are otherwise properly synchronized across EN and zh-CN — sections 1–10 and D1–D7 line up one-for-one, and §8's six acceptance criteria match in both.

Refusal vocabulary as implemented

Caller on the bound Workspace submit / cancel / rename cwd change Action respond close / archive / unarchive / delete
no read grant 404 session_not_found 404 session_not_found 403 action_forbidden 404 session_not_found
READER 403 session_operation_forbidden (was 409) 403 session_operation_forbidden 403 action_forbidden 403 session_operation_forbidden
OPERATOR, not the Session owner admitted → shape gates admitted → state checks 202 (new) 403 session_operation_forbidden
recorded owner admitted → shape gates admitted → state checks 202 admitted
OPERATOR past the role check but blocked by a shape gate 409 workspace_unavailable 409 domain — —

This matches contract v1.34's changelog sentence and the SurfaceRegistry RuleClass javadoc. The 409→403 normalization on the submitter family is client-visible; you have it under Risk & Scope, which is the right place.

Files changed (15 of 15 shown — grouped by which slice they belong to)

#13543's slice, re-added here because the base branch does not contain it (2,695 additions, 0 deletions):

File What changed
docs/design/2026-10-07-managed-agent-actor-roles.md New EN design doc; diverges from #13543's copy by 234 lines, mostly formatter padding plus the malformed section-2 table
docs/design/2026-10-07-managed-agent-actor-roles.zh-CN.md New zh-CN design doc; structurally synced with EN, diverges from #13543 by 226 lines, same table defect
â€Ķ/managedagent/api/SurfaceRegistry.java Route→rule-class registry; differs from #13543 by the cwd + Action-respond OWNER→OPERATOR flip and updated javadoc
â€Ķ/managedagent/SurfaceAdmissionAcceptanceTest.java Probe matrix over the registry; +74 lines vs #13543 for the OPERATOR/OWNER arms
â€Ķ/managedagent/api/SurfaceRegistryGateTest.java Bijection gate against the mounted Spring handlers — byte-identical to #13543
â€Ķ/managedagent/api/SurfaceRegistryGateNegativeTest.java Gate fails on an unregistered route — byte-identical to #13543

The enforcement slice this PR is actually about (290 additions, 177 deletions):

File What changed
â€Ķ/managedagent/service/ManagedAgentService.java Submitter check moves from creator identity to role; webShellSessions drops the createdSessions batch read; requireLegacyWorkspace splits 404 / 403 / 409
â€Ķ/managedagent/store/ManagedActionStore.java requireOwner reads owner_actor_key with the creator/create-command fallback, then falls through to a workspace OPERATOR/OWNER arm instead of refusing
â€Ķ/managedagent/store/ManagedAgentStore.java isSessionCreator → isSessionOwner for lifecycle; requireCwdChangeActor stops delegating to the owner gate and checks OPERATOR directly
â€Ķ/managedagent/store/ManagedWorkspaceRegistry.java isSessionCreator → isSessionOwner; createdSessions batch replaced by accessOf returning the WorkspaceAccess enum
â€Ķ/resources/openapi/managed-agent-public-api.openapi.json Version 1.33.0 → 1.34.0, v1.34 changelog sentence, route descriptions moved off creator language
â€Ķ/managedagent/HostedPublicWorkspaceIT.java Real signed HTTP: reader is now always READER, new operator2 OPERATOR answers the first approval per Session, 409 expectations flip to 403
â€Ķ/managedagent/ManagedCwdChangeOperationTest.java Adds the READER-403 and admitted-non-owner-OPERATOR arms; demoted owner now 403 instead of 409
â€Ķ/managedagent/service/Issue13181QueryBudgetTest.java Page budget 5 → 4 queries, create_command assertion dropped, workspaceTurns expectation flips to role-based
packages/web-shell/client/components/managed/generated/managed-agent-api.ts Regenerated from the OpenAPI edit; matches it one-for-one

Testing

This comment carries no independent test evidence, and none is obtainable while the base is a feature branch. Both ci.yml and sdk-java.yml gate their pull_request trigger on branches: ['main', 'release/**'(, 'omni-experiment')]. This PR's base is feat/13535-role-storage, so neither workflow fires. Verified three ways: actions/runs?head_sha=3c649d5fâ€Ķ returns only pull_request_target runs; actions/runs?branch=feat/13535-roles-enforcement returns the same four and zero pull_request-event runs; and the commit's 16 check-runs are bot orchestration plus skipped gates. The SDK Java workflow — the only lane that would run the Maven suites the Test Plan names — is therefore unreachable here, and "Hosted MySQL failsafe profiles left to CI" cannot be satisfied as filed.

The JDK 21 / Maven 3.9.11 / H2 results in the PR description, including the holdsRestorePagesInsideThePerPageByteBudget break being pre-existing (#13542, which I confirmed is an open issue against main), are the author's claim and are not re-run or corroborated here. Static review only; per the triage rules I did not build or execute anything from this PR.

Check Conclusion
assign success
authorize (×2) success
label success
triage in_progress (this run)
delay-automatic-review waiting
precheck-pr (×2) skipped
review-config skipped
ack-review-request skipped
resolve-pr skipped
verify · publish-verify skipped
tmux-testing · publish-tmux skipped
publish-resolution skipped
Qwen Code CI (ci.yml) never triggered — base branch not in the pull_request branch filter
SDK Java (sdk-java.yml) never triggered — same filter; this is the lane that runs the Maven suites

No red checks to triage; the absence of the two build lanes is the finding, not noise. Retargeting this PR onto main after #13543 and #13544 land would bring both back, and with them the only mechanical proof that the 1,535 test lines pass.

Sandboxed verification would settle this: @qwen-code /verify. The central claim is behavioural — a non-owner OPERATOR can now answer an approval that gates tool execution — and nothing currently substantiates it beyond the author's local run. A /verify pass is what would show the suite is load-bearing rather than incidentally green: specifically, that SurfaceAdmissionAcceptanceTest.belowReadAndBelowFamilyProbesFollowTheRuleClass and secondOperatorAnswersPendingApprovalsOnBothSurfaces fail when the accessOf(...).atLeast(OPERATOR) check in requireCwdChangeActor / requireLegacyWorkspace is dropped, or when the workspace-role arm in ManagedActionStore.requireOwner is removed. As filed the suite has never executed anywhere I can observe, so I cannot tell a pinned assertion from a vacuous one. You have write access, so /tmux is also available, but there is no TUI surface here — /verify is the lane that matches the claim.

äļ­æ–‡čŊŽ

äŧĢ᠁åŪĄæŸĨ

先čŊ‘č‡Šå·ąįš„į‹ŽįŦ‹æ–đæĄˆïžšåŠįœ‹æ ‡éǘäļŽ #13535 įš„ R1ïžŒæˆ‘äžšæŠŠč§’č‰ēæ”ūåœĻ Workspace įŧ‘åۚäļŠïžˆSession įš§č§’č‰ēčĄĻ䞚成倍åĒžåŠ čĄŒåđķåœĻæŊæŽĄåˆ›åŧšæ—ķ插å…Ĩä‰đæœåŠĄįŦŊ无æģ•æžšäļūįš„ actor把äļĪäļŠæŽˆæƒåļƒå°”合åđķäļšäļ€äļŠæœ‰åšåˆ—čŪĐæŊäļŠįŧ‘åۚ Session 变æ›īéƒ―čĩ°åŒäļ€äļŠã€Œč§Ģæžč°ƒį”Ļæ–đ角č‰ē → äļŽčŊĨæ—æœ€ä―ŽčĶæą‚æŊ”čūƒã€įš„å…ąäšŦčū…åŠĐæ–đæģ•äŧĨ免新č·Ŋį”ąæžæĢ€ïžŒčŪĐ owner äļŽ creator 分įĶŧåđķäļš V40 äđ‹å‰įš„čĄŒį•™å›žé€€ïžŒåđķæŠŠã€Œč·Ŋį”ą → 角č‰ē」įŸĐé˜ĩ钉åœĻåļĶæž„åŧšé—ĻįĶįš„æģĻ册čĄĻ里。čŋ™åĨ—å †å åšįš„æ­Ģæ˜Ŋčŋ™äš›ïžŒč€Œäļ”「æģĻ册čĄĻ + é—ĻįĶã€įš„æĄ†åۚæŊ”æˆ‘įš„ã€Œäļ€äļŠå…ąäšŦčū…åŠĐæ–đæģ•」æ›īåĨ― —— åۃčŪĐæžšäļū朎čšŦ成äļščĒŦæĩ‹åŊđčąĄïžŒč€Œäļæ˜ŊäŋĄäŧŧæŊäļŠč°ƒį”Ļį‚đã€‚æˆ‘įœ‹äļå‡šæ›īįŪ€įš„č·Ŋåū„。

åžšåˆķæ‰§čĄŒåˆ‡į‰‡æ˜ŊįŦ™åū—ä―įš„ã€‚æˆ‘æ ļåŊđ乆åŪđæ˜“å‡šé”™įš„å‡ åĪ„ïžšWorkspaceSummary.canCreateSession æ˜Ŋ role â‰Ĩ OPERATOR && state == ACTIVE所äŧĨ maySubmitWorkspaceTurn äŋį•™äš† ACTIVE é—Ļ而äļæ˜Ŋį”Ļ角č‰ē把åۃæĒ掉accessOf æœŦå°ūįš„ WorkspaceAccess.valueOf åœĻåŪƒįš„ IllegalArgumentException catch äđ‹åĪ–ïžŒįœ‹į€åƒäļ€äļŠį­‰č„čĄŒč§Ķå‘įš„ 500ïžŒä―† V48 加䚆 CHECK (role IN ('READER','OPERATOR','OWNER')) äļ”äļ‰č€…éƒ―æ˜Ŋ枚äļūåļļ量所äŧĨäļäžšïž›äļĪæĄæ–°æŸĨčŊĒįš„å ä―įŽĶéĄšåšéƒ―åŊđåū—äļŠïžˆtenantId, workspaceId, tenantId, workspaceId, keyBINARY(513) æŽ’åšč§„åˆ™åۈåŦäļŽįĄŽįž–᠁角č‰ēæļ…单æ˜Ŋ朎文äŧķæ—Ēæœ‰æƒŊäū‹ïžˆcanRead、listReadable、findReadable éƒ―čŋ™äđˆå†™ïž‰ïž›managed_agent_session įš„ workspace_id(V7)、creator_actor_key(V40)、owner_actor_key(V48) éƒ―å­˜åœĻæ”ūåŪ―åŽįš„ requireOwner SELECT åœĻ schema äœ‰æ•ˆïž›webShellSessions åŽŧ掉 createdSessions æ‰đ量čŊŧæ­Ģæ˜ŊéĄĩéĒéĒ„įŪ— 5→4 įš„åŽŸå› ïžŒč€Œ submitterGrants æ•čŽ· IllegalArgumentException äŋį•™äš†åŽŸæĨįš„é™įš§čĄŒäļšïžˆæģĻ册čĄĻé”Ū无æģ•įž–į įš„ actor id 及åđŋ告æēĄæœ‰ workspaceTurnsïžŒč€Œäļæ˜ŊčŪĐæ•īéĄĩæŠĨé”™ïž‰ïž›į”Ÿæˆįš„ TS įąŧ型äļŽ OpenAPI æ”đåŠĻé€æĄåŊđ嚔无枂į§ŧ。

äļ€åĪ„äļ€č‡ī性提醒äļæ˜Ŋé˜ŧåĄžéĄđ。 OPERATOR é˜ˆå€žįŽ°åœĻčĒŦčĄĻčūū乆äļĪæŽĄïžšservice/agent-store č·Ŋåū„äļŠįš„ accessOf(...).atLeast(WorkspaceAccess.OPERATOR)äŧĨ及 ManagedActionStore.requireOwner 里字éĒįš„ role IN ('OPERATOR', 'OWNER')。ManagedActionStore 及持有 JdbcTemplate äļŽ AgentStateStoreåĪį”ĻæģĻ册čĄĻäžšåž•å…Ĩ store→store äūčĩ– —— 内联æ˜ŊåŊäŧĨčūĐæŠĪįš„ïžŒSQL å―Ēæ€äđŸįŽĶ合朎äŧ“æƒŊäū‹ã€‚提凚æĨ及因äļšč§’č‰ēéĄšåšįŽ°åœĻ存åœĻäļĪåĪ„ïžŒå°†æĨč‹ĨåœĻ OPERATOR äđ‹ä’å…Ĩ角č‰ē侚静éŧ˜åˆ†å‰ã€‚

į›ļåŊđä― č‡Šå·ąéŠŒčŊčŪĄåˆ’įš„čĶ†į›–įžšåĢ。 čŪūčŪĄæ–‡æĄĢ §7 æ‰ŋčŊšåˆ‡į‰‡ C įš„åŪšå‘æĩ‹čŊ•包åŦã€ŒįŽŽäšŒäļŠ operator åœĻäļĪäļŠéĒäļŠå›žį­”åū…į­”åŪĄæ‰đ、OPERATOR 提äšĪ/取æķˆ/æ”đ名/æ”đ cwd」。åŪžé™…č―åœ°ïžšAction å›žį­”æœ‰æ­Ģ向见čŊïžˆsecondOperatorAnswersPendingApprovalsOnBothSurfaces äļĪéĒ 202、READER 403加äļŠ HostedPublicWorkspaceIT ᜟåŪžį­ū名 HTTP įš„ operator2cwd 变æ›īäđŸæœ‰ïžˆoperator-colleague 到čūū PENDINGåœĻå·ēå―’æĄĢ Session äļŠåˆ°čūū 409 session_state_conflictčŊæ˜Žč§’č‰ēæĢ€æŸĨå·ē通čŋ‡ïž‰ã€‚ä―†æēĄæœ‰äŧŧä―•æĩ‹čŊ•čŪĐäļ€äļŠéž owner įš„ OPERATOR 成功提äšĪ/取æķˆ/æ”đ名 —— æŽĒ针įŸĐé˜ĩįš„ OPERATOR č‡‚æ–­čĻ€įš„æ˜Ŋ 409 workspace_unavailablefiles opt-in å…ģé—­ïž‰ïžŒč€Œ operator2 及åœĻ approvals äļšįœŸæ—ķ插å…Ĩã€‚č§’č‰ēæĢ€æŸĨæ˜ūį„ķčĒŦ到čūū䚆403 åœĻæ­ĢįĄŪįš„ä―į―Ū变成 404/403/409Issue13181QueryBudgetTest äđŸé’‰ä―äš†åđŋ告äū§ïžˆworkspaceTurns į”ą index < 3 įŋŧäļš index != 3 && index != 6所äŧĨįžšåĢåūˆįŠ„ —— ä―†æäšĪ/取æķˆ/æ”đ名æ˜ŊæœŽæŽĄå˜æ›īäļ­æœ€åŪ―įš„æŽˆæƒéĒ偏偏æ˜Ŋå”Ŋäļ€æēĄæœ‰ã€Œå‡†å…ĨäˆåŠŸã€č§čŊįš„äļ€č‡‚。

æŽĒ针įŸĐé˜ĩ朎čšŦæ˜Ŋæœ€æœ‰åŠ›įš„éƒĻ分belowReadAndBelowFamilyProbesFollowTheRuleClass äŧĨæģĻ册čĄĻäļšå‚数化æĨ暐逐族断čĻ€æ‹’įŧį ïžˆACTION_RESPOND äļš action_forbiddenå…ķä―™äļš session_operation_forbiddenäļ” case OWNER 断čĻ€éž owner įš„ OPERATOR åœĻį”Ÿå‘―å‘Ļ期äļŠåū—到 403。配合 SurfaceRegistryGateTest äļŽå·ēæŒ‚č―― handler įš„åŒå°„æ ĄéŠŒïžŒčŋ™æ˜Ŋᜟæ­Ģįš„é—ĻįĶč€Œäļæ˜ŊåŋŦį…§ã€‚

文æĄĢįžšé™·ã€‚ įŽŽ 2 čŠ‚įš„čĄĻæ žåœĻäļ­č‹ąäļĪį‰ˆéƒ―æ˜Ŋåįš„ïžˆč‹ąæ–‡ 50–54 čĄŒïžŒäļ­æ–‡ 28–32 čĄŒïž‰ã€‚å†™į€ POST â€Ķ/close|archive|unarchive įš„é‚ĢäļŠå•元栞åļĶæœ‰čĢļįŪĄé“įŽĶGFM 䞚把åŪƒå―“ä―œå•å…ƒæ žåˆ†éš”įŽĶïžščĄĻåĪīåĢ°æ˜Ž 4 åˆ—ïžŒåˆ†éš”čĄŒåĢ°æ˜Ž 6 列Lifecycle čĄŒįš„ archive、unarchive、DELETE äŧĨ及 requireWorkspaceCreator / 403 session_operation_forbidden éƒ―æšĒ到čĄĻåĪīæēĄæœ‰å‘―åįš„åˆ—é‡Œã€‚#13543 æœ‰åŒæ ·æœŠč―Žäđ‰įš„įŪĄé“įŽĶä―†æ˜Ŋįī§å‡‘å―Ē垏čŋ™é‡Œč·‘čŋ‡æ žåžåŒ–后把分čĢ‚å›šåŒ–čŋ›äš†åˆ†éš”čĄŒïžŒæ‰€äŧĨčŋ™äļ€äŧ―æļ翟“æ›īįģŸã€‚č―Žäđ‰æˆ close\|archive\|unarchive åŊ同æ—ķäŋŪåĨ―äļĪį‰ˆã€‚åĶåĪ–č‹ąæ–‡ 40–41 čĄŒįš„ managed_workspace_access(...) 列čĄĻéĄđäļĒ乆 2 įĐšæ žįŧ­čĄŒįžĐčŋ›ã€‚é™Īæ­Īäđ‹åĪ–äļ­č‹ąæ–‡æĄĢåœĻįŧ“æž„äļŽå†…åŪđä˜Ŋ同æ­Ĩįš„ —— įŽŽ 1–10 节äļŽ D1–D7 äļ€äļ€åŊđ嚔§8 įš„å…­æĄéŠŒæ”ķ标准äļĪį‰ˆäļ€č‡ī。

æĩ‹čŊ•

æœŽæĄčŊ„čŪšäļæšåļĶäŧŧä―•į‹ŽįŦ‹æĩ‹čŊ•čŊæŪïžŒč€Œäļ”åœĻ base æ˜Ŋį‰đ性分æ”Ŋ期é—īäđŸæ— æģ•获åū—。 ci.yml äļŽ sdk-java.yml įš„ pull_request č§Ķå‘éƒ―é™åۚ branches: ['main', 'release/**'(, 'omni-experiment')]朎 PR įš„ base æ˜Ŋ feat/13535-role-storageäļĪäļŠ workflow éƒ―äļäžšč§Ķ发。äļ‰į§æ–đ垏銌čŊčŋ‡ïžšactions/runs?head_sha=3c649d5fâ€Ķ 及čŋ”回 pull_request_target čŋčĄŒïž›actions/runs?branch=feat/13535-roles-enforcement čŋ”å›žåŒæ ·įš„å››æĄã€é›ķæĄ pull_request 之äŧķčŋčĄŒïž›čŊĨ commit įš„ 16 æĄ check-run å…Ļæ˜Ŋ朚å™Ļäššįž–æŽ’äļŽč·ģčŋ‡įš„é—ĻįĶã€‚SDK Java —— å”Ŋäļ€äžšč·‘æĩ‹čŊ•čŪĄåˆ’æ‰€åˆ— Maven åĨ—äŧķįš„é€šé“ —— 因æ­ĪåœĻčŋ™é‡ŒäļåŊčūū「Hosted MySQL failsafe profile äšĪįŧ™ CIã€æŒ‰å―“å‰æäšĪæ–đ垏无æģ•å…‘įŽ°ã€‚

PR 描čŋ°é‡Œįš„ JDK 21 / Maven 3.9.11 / H2 įŧ“果åŦ holdsRestorePagesInsideThePerPageByteBudget įš„į īååąžäšŽ main æ—Ē有é—ŪéĒ˜ïžŒ#13542 —— 我å·ēįĄŪčŪĪé‚Ģæ˜Ŋäļ€äļŠé’ˆåŊđ main įš„ open issueæ˜Ŋä―œč€…įš„åĢ°æ˜ŽïžŒæ­ĪåĪ„æœŠåĪč·‘äđŸæœŠä―čŊã€‚äŧ…静态åŪĄæŸĨ按 triage č§„åˆ™æˆ‘æēĄæœ‰æž„åŧšæˆ–æ‰§čĄŒæœŽ PR įš„äŧŧä―•äŧĢį ã€‚

CI čĄĻ见ä–đč‹ąæ–‡éƒĻåˆ†įš„æœšå™ĻåŊčŊŧ匚域čĄĻæ žäļé‡åĪįŋŧčŊ‘。æēĄæœ‰įšĒæĢ€æŸĨ需čĶå―’å› ïž›įžšåĪąįš„äļĪæĄæž„åŧšé€šé“朎čšŦå°ąæ˜Ŋįŧ“čŪšïžŒäļæ˜Ŋ噊éŸģã€‚į­‰ #13543 äļŽ #13544 č―åœ°åŽæŠŠæœŽ PR įš„ base æ”đ回 mainäļĪæĄé€šé“å°ąäžšå›žæĨ同æ—ķåļĶæĨ「1,535 行æĩ‹čŊ•įĄŪåŪžé€ščŋ‡ã€įš„å”ŊäœšæĒ°čŊæ˜Žã€‚

æē™įŪąéŠŒčŊåŊäŧĨåŪšæĄˆïžš@qwen-code /verify。 æ ļåŋƒäļŧåž æ˜Ŋ行äļšæ€§įš„ —— 非 owner įš„ OPERATOR įŽ°åœĻčƒ―å›žį­”äĄé—Ļ控å·Ĩå…·æ‰§čĄŒįš„åŪĄæ‰đ —— 而į›Ū前é™Īä―œč€…æœŽæœščŋčĄŒåĪ–æēĄæœ‰äŧŧä―•äļœčĨŋä―čŊåŪƒã€‚/verify čƒ―čŊæ˜ŽåĨ—äŧķæ˜Ŋæ‰ŋé‡įš„č€ŒéžįĒ°å·§å˜įŧŋïžšå…·ä―“čŊīïžŒå―“ requireCwdChangeActor / requireLegacyWorkspace é‡Œįš„ accessOf(...).atLeast(OPERATOR) æĢ€æŸĨčĒŦåŽŧ掉、或 ManagedActionStore.requireOwner é‡Œįš„ workspace 角č‰ē臂čĒŦį§ŧé™Īæ—ķSurfaceAdmissionAcceptanceTest.belowReadAndBelowFamilyProbesFollowTheRuleClass äļŽ secondOperatorAnswersPendingApprovalsOnBothSurfaces åš”å―“åĪąčīĨã€‚æŒ‰å―“å‰æäšĪæ–đ垏čŋ™åĨ—æĩ‹čŊ•äŧŽæœŠåœĻæˆ‘čƒ―č§‚åŊŸåˆ°įš„äŧŧä―•åœ°æ–đæ‰§čĄŒčŋ‡ïžŒæ‰€äŧĨ我无æģ•åŒšåˆ†ã€Œé’‰ä―įš„æ–­čĻ€ã€äļŽã€ŒįĐšč―Žįš„æ–­čĻ€ã€ã€‚ä― æœ‰å†™æƒé™ïžŒ/tmux äđŸåŊį”ĻïžŒä―†čŋ™é‡ŒæēĄæœ‰ TUI éĒ —— äļŽäļŧåž åŒđé…įš„æ˜Ŋ /verify。

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

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

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — the enforcement code is good and I would not change its shape, but I cannot verify a line of it runs, and the PR cannot merge as filed.

Going back to the proposal I wrote before reading the diff: this exceeds it. I would have reached for one shared admission helper and trusted each call site to call it; the registry-plus-build-gate framing makes the enumeration itself the thing under test, so a route added next quarter cannot quietly skip the check. That is the better design and it is the part I would most want to keep. The refusal vocabulary is coherent — 404 below read, 403 below operate, 409 only for a shape gate an admitted OPERATOR hits — and it is applied consistently enough that I could reconstruct the whole matrix from the code and have it match contract v1.34 and the RuleClass javadoc. The comments explain why rather than restating the code, which is the house rule and is not common in a 3,000-line diff. If I picked this up in six months I would thank whoever wrote it.

So the reservations are not about the code's shape. Three things stop me approving:

I have no way to verify it, and that is structural rather than a timing accident. ci.yml and sdk-java.yml both filter pull_request on branches: [main, release/**, â€Ķ], so with feat/13535-role-storage as the base neither fires — the 16 check-runs on this head are bot orchestration and skipped gates, and there are zero pull_request-event workflow runs. Normally I would hand the approval to the finalize job with a deferred-approval marker and let CI settle it; there is nothing for it to settle, and no green will ever arrive on this base. What this change does is widen authorization on routes that gate tool execution — a second operator can now answer an approval that unblocks a Turn — which is exactly the class of change where "the suite has never executed anywhere I can observe" is not a comfortable place to sign off from. 1,535 test lines is a serious investment and I cannot currently distinguish a pinned assertion from a vacuous one. Your local JDK 21 / Maven / H2 results are credible and I have no reason to doubt them, but they are your claim, and substituting them for evidence is the specific thing this gate is not supposed to do.

The diff does not match the description, and that misdirection is load-bearing. "The diff shows only the enforcement slice" and "design docs land with #13543" are both false as filed: 2,695 of 2,985 additions are #13543's six files, four of them divergent copies. I reviewed the 290/177 enforcement slice on its merits and it stands up — but a maintainer who trusts the description would spend their attention on the wrong 10%, and the add/add conflict waiting on whichever of #13543/#13545 merges second is a real cost, not a tidiness complaint. This is the part I would fix first, because it also fixes the CI problem: once #13543 and #13544 land and this retargets main, the diff becomes the slice the description claims, SDK Java fires, and a deferred approval becomes meaningful again.

One arm of your own validation plan did not arrive. §7 promises targeted tests that an OPERATOR submits, cancels, renames and changes cwd. cwd has a genuine admitted-and-succeeded witness (operator-colleague reaching PENDING, and 409 session_state_conflict on an archived Session proving the role check passed), and approval respond has one on both surfaces over real signed HTTP. Submit/cancel/rename do not — the probe matrix asserts 409 workspace_unavailable there because the files opt-in is off, and operator2 is only inserted in approvals mode, so no test drives a successful submit by a non-owner OPERATOR. Narrow gap, and the refusal arms plus the workspaceTurns advertisement flip (index < 3 → index != 3 && index != 6) cover most of the reasoning. But it is the widest authorization surface in the change and it is the one arm resting on inference.

Also worth fixing while you are in the file: §2's table is malformed in both language versions — the raw pipes in the POST â€Ķ/close|archive|unarchive cell are read as cell separators, so the header declares 4 columns, the separator declares 6, and the Lifecycle row spills across columns the header never names. Escape them and it renders. EN/zh-CN are otherwise properly synchronized (sections 1–10, D1–D7, and all six §8 acceptance criteria line up).

Am I being a pushover, or the reverse? The pull here is real — it is a maintainer's own PR, the code is clean, and "LGTM" is free. But an approval would be attesting to a suite result that does not exist, on a PR that cannot merge until two others land first. That is not a judgement I should make on the author's behalf by waving it through, and I found no code defect that would justify requesting changes either. So: deferring, not rejecting.

Asks, in the order I would do them:

  1. Land feat(managed-agent): enumerate the public and WebShell surface with an admission gate #13543 and feat(managed-agent): persist workspace actor roles and session owners #13544, then retarget this onto main so the diff is genuinely the enforcement slice and SDK Java / Qwen Code CI fire.
  2. Once CI is reachable, @qwen-code /verify — specifically to show belowReadAndBelowFamilyProbesFollowTheRuleClass and secondOperatorAnswersPendingApprovalsOnBothSurfaces fail when the OPERATOR check or the requireOwner workspace-role arm is removed.
  3. Add the missing positive witness for a non-owner OPERATOR submitting (or cancelling, or renaming) with the files opt-in on.
  4. Escape the pipes in the §2 table in both design docs; restore the EN line 40–41 bullet indent.
  5. Consider whether the OPERATOR threshold should live in one place rather than as atLeast(OPERATOR) here and role IN ('OPERATOR','OWNER') in ManagedActionStore.

One open question from design §9 that I do not have the context to answer and that a human should: lifecycle stays Session-owner-only, so a workspace-OWNER grant does not admit lifecycle on Sessions bound to that workspace. That is a defensible line, but it means an OWNER can be locked out of deleting a Session in their own workspace — worth confirming that is the intended admin model before v1.34 is treated as settled, since flipping it later is a client-visible change.

On the escalation mention: the deterministic resolver could not produce one. $QWEN_MAINTAINER_HANDLE is unset, the PR carries no labels (so no area in .github/issue-owners.json matches — its requireLabels gate is need-discussion), there are no human reviews, and no assignees. The node runtime needed to run the resolver script is also blocked in this environment. Per the skill I am not guessing a login, so this is posted unmentioned — @wenshao, as the author and the first-listed owner of the core area, you are the accountable maintainer here and the asks above are addressed to you directly.

äļ­æ–‡čŊŽ

äŋĄåŋƒïžš3/5 —— åžšåˆķæ‰§čĄŒéƒĻåˆ†įš„äŧĢ᠁æ˜ŊåĨ―įš„ïžŒæˆ‘äļäžšåŽŧæ”đåŪƒįš„å―Ēæ€ïž›ä―†æˆ‘æ— æģ•éЌčŊå…ķäļ­äŧŧä―•äļ€čĄŒč·‘åū—čĩ·æĨïžŒč€ŒäœŽ PR æŒ‰å―“å‰æäšĪæ–đ垏无æģ•合åđķ。

回到我åœĻčŊŧ diff äđ‹å‰å†™äļ‹įš„æ–đæĄˆïžšæœŽ PR čķ…凚䚆åŪƒã€‚æˆ‘æœŽæĨ侚选äļ€äļŠå…ąäšŦįš„å‡†å…Ĩčū…åŠĐæ–đæģ•åđķäŋĄäŧŧæŊäļŠč°ƒį”Ļį‚đåŽŧ调į”ĻåŪƒïž›č€Œã€ŒæģĻ册čĄĻ + 构åŧšé—ĻįĶã€įš„æĄ†åۚčŪĐæžšäļū朎čšŦ成äļščĒŦæĩ‹åŊđčąĄïžŒäļ‹äļŠå­ĢåšĶ新åĒžįš„č·Ŋį”ąæ— æģ•静éŧ˜č·ģčŋ‡æ ĄéŠŒã€‚čŋ™æ˜Ŋæ›īåĨ―įš„čŪūčŪĄïžŒäđŸæ˜Ŋ我最æƒģäŋį•™įš„éƒĻ分。拒įŧčŊčĄĻæ˜Ŋ臩æī―įš„ —— ä―ŽäšŽčŊŧ授权 404ã€ä―ŽäšŽæ“ä―œæŽˆæƒ 403、及有å·ē准å…Ĩįš„ OPERATOR 撞äļŠå―Ēæ€é—Ļ才 409 —— 而äļ”åš”į”Ļåū—čķģåΟäļ€č‡īäŧĨč‡ģäšŽæˆ‘čƒ―äŧŽäŧĢį åæŽĻ凚æ•īåž įŸĐé˜ĩåđķäļŽåĨ‘įšĶ v1.34 和 RuleClass javadoc åŊđäļŠã€‚æģĻ释åœĻč§Ģ释「äļšäŧ€äđˆã€č€Œäļæ˜ŊåĪčŋ°äŧĢį ïžŒčŋ™æ˜Ŋ朎äŧ“įš„č§„įŸĐïžŒč€ŒåœĻäļ€äŧ― 3,000 čĄŒįš„ diff 里åđķäļåļļč§ã€‚å…­äļŠæœˆåŽæˆ‘æŽĨ手čŋ™äŧ―äŧĢį ïžŒäžšæ„Ÿč°Ē写åŪƒįš„äššã€‚

所äŧĨæˆ‘įš„äŋį•™æ„č§äļåœĻäŧĢ᠁å―Ēæ€äļŠã€‚有äļ‰äŧķ之čŪĐæˆ‘äļæ‰đ准

我æēĄæœ‰åŠžæģ•éЌčŊåŪƒïžŒč€Œäļ”čŋ™æ˜Ŋįŧ“æž„æ€§įš„ïžŒäļæ˜Ŋæ—ķ嚏äļŠįš„åķį„ķ。 ci.yml äļŽ sdk-java.yml įš„ pull_request éƒ―é™åۚ branches: [main, release/**, â€Ķ]因æ­Ī base æ˜Ŋ feat/13535-role-storage æ—ķäļĪč€…éƒ―äļč§Ķ发 —— čŊĨ head äļŠįš„ 16 æĄ check-run å…Ļæ˜Ŋ朚å™Ļäššįž–æŽ’äļŽč·ģčŋ‡įš„é—ĻįĶïžŒpull_request 之äŧķįš„ workflow čŋčĄŒæ•°äļšé›ķ。通åļ￈‘äžšį”Ļäļ€äļŠåŧķčŋŸæ‰đ准标čŪ°æŠŠæ‰đ准äšĪįŧ™ finalize ä―œäļšïžŒčŪĐ CI åŽŧåŪšæĄˆïž›ä―†čŋ™é‡ŒæēĄæœ‰äŧŧä―•äļœčĨŋåŊäū›åۃåŪšæĄˆïžŒåœĻčŋ™äļŠ base ä°ļčŋœäļäžšå‡šįްįŧŋč‰ēã€‚æœŽæŽĄå˜æ›īåšįš„æ˜Ŋæ”ūåŪ―æŽˆæƒïžŒä˜ŊåœĻé—Ļ控å·Ĩå…·æ‰§čĄŒįš„č·Ŋį”ąäļŠ â€”â€” įŽŽäšŒäļŠ operator įŽ°åœĻčƒ―å›žį­”äĄč§Ģé™Ī Turn é˜ŧåĄžįš„åŪĄæ‰đ —— čŋ™æ°åĨ―æ˜Ŋ「čŋ™åĨ—æĩ‹čŊ•äŧŽæœŠåœĻæˆ‘čƒ―č§‚åŊŸåˆ°įš„äŧŧä―•åœ°æ–đæ‰§čĄŒčŋ‡ã€æœ€äļé€‚合į­ū字æ”ūčĄŒįš„äļ€įąŧæ”đåŠĻ。1,535 行æĩ‹čŊ•æ˜ŊåūˆčŪĪįœŸįš„æŠ•å…ĨïžŒč€Œæˆ‘į›Ū前无æģ•åŒšåˆ†é’‰ä―įš„æ–­čĻ€äļŽįĐšč―Žįš„æ–­čĻ€ã€‚ä― æœŽæœšįš„ JDK 21 / Maven / H2 įŧ“æžœæ˜ŊåŊäŋĄįš„我æēĄæœ‰į†į”࿀€į–‘ïžŒä―†é‚Ģæ˜Ŋä― įš„åĢ°æ˜Žïž›į”ĻåĢ°æ˜Žæ›ŋäŧĢčŊæŪæ­Ģæ˜Ŋčŋ™äļŠé—Ļį́äļčŊĨåšįš„äš‹ã€‚

diff äļŽæčŋ°äļįŽĶïžŒč€Œčŋ™äļŠčŊŊåŊžæ˜Ŋæ‰ŋé‡įš„ã€‚ 「diff 及åŦåžšåˆķæ‰§čĄŒåˆ‡į‰‡ã€äļŽã€ŒčŪūčŪĄæ–‡æĄĢ随 #13543 č―åœ°ã€æŒ‰å―“å‰æäšĪæ–đåžéƒ―äļæˆįŦ‹ïžš2,985 čĄŒæ–°åĒžé‡Œæœ‰ 2,695 行æ˜Ŋ #13543 įš„å…­äļŠæ–‡äŧķå…ķäļ­å››äļŠæ˜Ŋ分叉å‰Ŋ朎。我按å…ķåŪžčīĻåŪĄäš†é‚Ģ 290/177 čĄŒįš„åžšåˆķæ‰§čĄŒåˆ‡į‰‡ïžŒåۃįŦ™åū—ä― —— ä―†äļ€ä―äŋĄäŧŧ描čŋ°įš„įŧĪč€…äžšæŠŠæģĻæ„åŠ›čŠąé”™åœĻé‚Ģ 10% äļŠïž›č€Œ #13543 äļŽ #13545 äļ­åŽåˆåđķč€…å°†æ’žäļŠįš„ add/add å†ēįŠæ˜ŊåŪžé™…äŧĢäŧ·ïžŒäļæ˜Ŋæ•īæīæ€§é—ŪéĒ˜ã€‚čŋ™éƒĻ分我䞚最先äŋŪ因äļšåۃ同æ—ķäđŸäŋŪåĨ―乆 CI é—ŪéĒ˜ïžšį­‰ #13543 äļŽ #13544 č―åœ°ã€æœŽ PR æ”đ base 到 maindiff å°ąå˜æˆæčŋ°æ‰€į§°įš„é‚ĢäļŠåˆ‡į‰‡ïžŒSDK Java 侚č§Ķ发åŧķčŋŸæ‰đ准äđŸé‡æ–°å˜åū—有意äđ‰ã€‚

ä― č‡Šå·ąéŠŒčŊčŪĄåˆ’é‡Œįš„äļ€č‡‚æēĄæœ‰åˆ°ä―ã€‚ §7 æ‰ŋčŊšæœ‰åŪšå‘æĩ‹čŊ•č͆ᛖ OPERATOR 提äšĪ、取æķˆã€æ”đ名äļŽæ”đ cwd。cwd æœ‰įœŸæ­Ģįš„ã€Œå‡†å…ĨäˆåŠŸã€č§čŊïžˆoperator-colleague 到čūū PENDINGåœĻå·ēå―’æĄĢ Session äļŠåˆ°čūū 409 session_state_conflictčŊæ˜Žč§’č‰ēæĢ€æŸĨå·ē通čŋ‡ïž‰ïžŒåŪĄæ‰đå›žį­”åœĻäļĪäļŠéĒäļŠäđŸæœ‰ã€äļ”čĩ°įœŸåŪžį­ū名 HTTP。提äšĪ/取æķˆ/æ”đ名æēĄæœ‰ —— æŽĒ针įŸĐé˜ĩåœĻé‚Ģ里断čĻ€ 409 workspace_unavailablefiles opt-in å…ģé—­ïž‰ïžŒč€Œ operator2 及åœĻ approvals æĻĄåžä’å…Ĩ所äŧĨæēĄæœ‰æĩ‹čŊ•čŪĐ非 owner įš„ OPERATOR 成功提äšĪã€‚įžšåĢåūˆįŠ„ïžŒæ‹’įŧč‡‚加äļŠ workspaceTurns åđŋ告įŋŧč―Žïžˆindex < 3 → index != 3 && index != 6čĶ†į›–äš†åΧéƒĻ分æŽĻį†ã€‚ä―†åۃæ˜ŊæœŽæŽĄå˜æ›īäļ­æœ€åŪ―įš„æŽˆæƒéĒ偏偏æ˜Ŋå”Ŋäļ€é æŽĻ断æ”Ŋæ’‘įš„äļ€č‡‚。

éĄšæ‰‹äđŸå€žåū—äŋŪ§2 įš„čĄĻæ žåœĻäļ­č‹ąäļĪį‰ˆéƒ―æ˜Ŋåįš„ —— POST â€Ķ/close|archive|unarchive å•å…ƒæ žé‡Œįš„čĢļįŪĄé“įŽĶčĒŦå―“ä―œå•å…ƒæ žåˆ†éš”įŽĶ䚎æ˜ŊčĄĻåĪīåĢ°æ˜Ž 4 åˆ—ã€åˆ†éš”čĄŒåĢ°æ˜Ž 6 列Lifecycle 行æšĒ到čĄĻåĪīäŧŽæœŠå‘―åįš„åˆ—é‡Œã€‚č―Žäđ‰åŽå°ąčƒ―æ­Ģåļļæļ翟“。é™Īæ­Īäđ‹åĪ–äļ­č‹ąæ–‡æĄĢ同æ­Ĩč‰ŊåĨ―ïžˆįŽŽ 1–10 čŠ‚ã€D1–D7、äŧĨ及 §8 å…ĻéƒĻå…­æĄéŠŒæ”ķæ ‡å‡†éƒ―äļ€äļ€åŊđ嚔。

我æ˜ŊåœĻå―“åĨ―åĨ―å…ˆį”ŸïžŒčŋ˜æ˜Ŋį›ļ反čŋ™é‡Œįš„æ‹‰åŠ›æ˜ŊᜟåŪžįš„ —— čŋ™æ˜ŊįŧĪč€…æœŽäššįš„ PRäŧĢ᠁åđēå‡€ïžŒč€Œã€ŒLGTM」æ˜Ŋ免čīđįš„ã€‚ä―†äļ€æŽĄæ‰đ准将æ˜ŊåŊđäļ€äļŠåđķäļå­˜åœĻįš„åĨ—äŧķįŧ“æžœä―œäŋïžŒč€Œä˜ŊåœĻäļ€äļŠåŋ…éĄŧį­‰åĶåĪ–äļĪäļŠ PR å…ˆč―åœ°æ‰čƒ―åˆåđķįš„ PR äļŠã€‚čŋ™äļæ˜Ŋ我čŊĨæ›ŋä―œč€…æŒĨ手æ”ūčĄŒįš„åˆĪ断同æ—ķ我äđŸæēĄæœ‰å‘įŽ°čķģäŧĨæ”Ŋ撑「čŊ·æą‚变æ›īã€įš„äŧĢį įžšé™·ã€‚æ‰€äŧĨïžšæŽĻčŋŸïžŒč€ŒéžåĶå†ģ。

åŧščŪŪ之éĄđïžŒæŒ‰æˆ‘äžšæ‰§čĄŒįš„éĄšåšïžš

  1. å…ˆč―åœ° feat(managed-agent): enumerate the public and WebShell surface with an admission gate #13543 äļŽ feat(managed-agent): persist workspace actor roles and session owners #13544再把朎 PR æ”đ base 到 mainčŪĐ diff ᜟæ­Ģ成äļšåžšåˆķæ‰§čĄŒåˆ‡į‰‡ïžŒåđķčŪĐ SDK Java / Qwen Code CI č§Ķ发。
  2. CI åŊčūūåŽč·‘ @qwen-code /verify —— å…·ä―“į”Ļ䚎čŊæ˜Žïžšį§ŧé™Ī OPERATOR æĢ€æŸĨ或 requireOwner įš„ workspace 角č‰ēč‡‚åŽïžŒbelowReadAndBelowFamilyProbesFollowTheRuleClass äļŽ secondOperatorAnswersPendingApprovalsOnBothSurfaces 侚åĪąčīĨ。
  3. äļšã€Œfiles opt-in 垀åŊæ—ķ非 owner įš„ OPERATOR 成功提äšĪ或取æķˆã€æ”đ名」čĄĨäļŠįžšåĪąįš„æ­Ģ向见čŊã€‚
  4. č―Žäđ‰äļĪäŧ―čŪūčŪĄæ–‡æĄĢ §2 čĄĻæ žé‡Œįš„įŪĄé“įŽĶïž›æĒåĪč‹ąæ–‡ 40–41 čĄŒįš„åˆ—čĄĻéĄđįžĐčŋ›ã€‚
  5. 考虑 OPERATOR 阈倞æ˜ŊåĶ嚔及存åœĻäļ€åĪ„ïžŒč€Œäļæ˜Ŋčŋ™é‡Œį”Ļ atLeast(OPERATOR)、ManagedActionStore 里į”Ļ role IN ('OPERATOR','OWNER')。

čŪūčŪĄæ–‡æĄĢ §9 里有äļ€äļŠæˆ‘æēĄæœ‰äļŠä–‡å›žį­”、需čĶäššæĨåŪšįš„åž€æ”ūé—ŪéĒ˜ïžšį”Ÿå‘―å‘Ļ期äŋæŒäŧ… Session owner因æ­Ī workspace äļŠįš„ OWNER 授权äļå‡†å…ĨčŊĨ workspace äļ‹ Session įš„į”Ÿå‘―å‘ĻæœŸæ“ä―œã€‚čŋ™æĄįšŋæ˜ŊåŊäŧĨčūĐæŠĪįš„ïžŒä―†åŪƒæ„å‘ģį€äļ€äļŠ OWNER åŊčƒ―čĒŦæŒĄåœĻ删é™Īč‡Šå·ą workspace 内 Session äđ‹åĪ– —— 倞åū—åœĻ v1.34 čĒŦå―“ä―œåŪšæĄˆäđ‹å‰įĄŪčŪĪčŋ™æ˜ŊéĒ„æœŸįš„įŪĄį†æĻĄåž‹ïžŒå› äļšå°†æĨįŋŧč―Žåۃæ˜ŊåŪĒæˆ·įŦŊåŊč§įš„å˜æ›ī。

å…ģäšŽå‡įš§ @ 提及įĄŪåŪšæ€§č§Ģ析å™Ļįŧ™äļå‡šäššé€‰ã€‚$QWEN_MAINTAINER_HANDLE 朊čŪūį―Ū朎 PR æēĄæœ‰æ ‡į­ū因æ­Ī .github/issue-owners.json 里æēĄæœ‰ area čƒ―åŒđ配 —— å…ķ requireLabels é—Ļį́æ˜Ŋ need-discussionæēĄæœ‰äššįąŧ reviewäđŸæēĄæœ‰ assigneeïž›čŋčĄŒč§Ģ析å™Ļč„šæœŽæ‰€éœ€įš„ node čŋčĄŒæ—ķåœĻ朎įŽŊåǃäļ­äđŸčĒŦį́į”Ļ。按 skill č§„åˆ™æˆ‘äļįŒœį™ŧå―•åïžŒå› æ­ĪæœŽæĄčŊ„čŪšäļåļĶ @ 提及发凚 —— @wenshaoïžŒä―œäļšä―œč€…äŧĨ及 core area éĶ–ä―åˆ—å‡šįš„ ownerïžŒä― åœĻčŋ™é‡Œå°ąæ˜ŊčīŸčīĢįš„įŧĪč€…ïžŒäļŠéĒįš„åŧščŪŪ之éĄđį›īæŽĨå‘ä― æå‡šã€‚

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

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

wenshao and others added 2 commits October 7, 2026 08:17
â€Ķstry javadoc

The RuleClass summary said a caller below OPERATOR everywhere answers 403, dropping the qualifier the per-class entries carry: below the read grant every Session route answers 404 session_not_found, and only a readable actor below OPERATOR gets the normalized 403 — the #12867 split the sentence claims to summarize. Review finding F4 on PR #13545.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
â€Ķ's own language

The Chinese section-10 note sent readers to D4's English 'Rule today' column; the Chinese table calls it 'äŧŠåĪĐ'. Review finding F2 on PR #13545.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: end-to-end integration layer — HostedPublicWorkspaceIT and the two MySQL ITs compile but were never executed in this review (the failsafe profile needs MySQL plus a bundled dist/cli.js), so the findings that rest on their fixtures were traced, not measured.

Not reviewed: web-shell client unit tests — the vitest suite was never run (no node_modules in the review worktree); the one changed web-shell file is generated and its regeneration gate was reproduced byte-identical, so the changed file is covered, but no client behaviour was executed.

Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": could not execute a GFM/Prettier render of docs/design/2026-10-07-managed-agent-actor-roles.md lines 49-53 — no node_modules in this worktree (no prettier binarâ€Ķ; "agent reverse-audit (round 1)": byte-exact regeneration check — openapi-typescript is not installed in the review worktree (no node_modules ), so managed-agent-api.test.ts 's toBe(await râ€Ķ; "agent reverse-audit (round 3)": did not execute the managed-agent-server suite, so section 7 Slice C's "updated probes and contract tests pin the new matrix" stays unverified for ManagedAgentâ€Ķ; "agent reverse-audit (round 2)": section 8's "Full managed-agent-server suite green on H2" — I test-compiled the module clean offline ( mvn -o -q test-compile , exit 0) but could not establish â€Ķ.

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

[Critical] Re-check of a blocker already filed on this PR by the earlier triage round (comments 6027112673 and 6027112927) - STILL STANDS at 215999c. The base is still feat/13535-role-storage and the diff still re-adds all six files that #13543 also adds: git diff --name-status 7a6c52bd..HEAD reports A for both design docs, api/SurfaceRegistry.java, SurfaceAdmissionAcceptanceTest.java, SurfaceRegistryGateTest.java and SurfaceRegistryGateNegativeTest.java. Only two commits landed since that triage (aab53c1 a zh-CN matrix note, 215999c a registry javadoc qualifier), both documentation-only, so nothing about the stacking changed. Whichever of #13543 and #13545 merges second meets an add/add conflict on all six, and the description's 'this branch is based on feat/13535-role-storage so the diff shows only the enforcement slice' together with 'Design docs (EN/zh-CN) land with #13543' still describe a rebase that has not happened.

[Critical] Re-check of the same triage round's second blocker - STILL STANDS structurally at 215999c. .github/workflows/ci.yml filters pull_request on branches [main, release/, omni-experiment] and .github/workflows/sdk-java.yml on [main, release/], so with this base neither lane fires; the pre-submission check sees 3 checks and none of them is the Java test lane. The evidentiary half of that blocker is discharged by this review rather than by CI: the module was built and tested at this commit (mvn test -> Tests run: 1208, Failures: 0, Errors: 1, and the single error was measured identical at the merge base 7a6c52b, so it is pre-existing in a file this diff does not touch). What remains open is that the PR itself carries no CI signal, and HostedPublicWorkspaceIT plus the two MySQL ITs were never executed in this review because the failsafe profile needs MySQL and a bundled dist/cli.js.

äļ­æ–‡čŊŽ

äŧ…åŪŒæˆéƒĻ分åŪĄæŸĨåŪĄæŸĨįžšåĢå·ēæŠŦéœē。

朊åŪĄæŸĨ原文äļšč‹ąæ–‡ïž‰ïžšend-to-end integration layer — HostedPublicWorkspaceIT and the two MySQL ITs compile but were never executed in this review (the failsafe profile needs MySQL plus a bundled dist/cli.js), so the findings that rest on their fixtures were traced, not measured.

朊åŪĄæŸĨ原文äļšč‹ąæ–‡ïž‰ïžšweb-shell client unit tests — the vitest suite was never run (no node_modules in the review worktree); the one changed web-shell file is generated and its regeneration gate was reproduced byte-identical, so the changed file is covered, but no client behaviour was executed.

朊æŽĒįīĒ到å…ĻéƒĻæ·ąåšĶčūū到å·Ĩå…·č°ƒį”ĻéĒ„įŪ—ïž‰ïžš"agent reverse-audit (round 2)"ïžšcould not execute a GFM/Prettier render of docs/design/2026-10-07-managed-agent-actor-roles.md lines 49-53 — no node_modules in this worktree (no prettier binarâ€Ķïž›"agent reverse-audit (round 1)"ïžšbyte-exact regeneration check — openapi-typescript is not installed in the review worktree (no node_modules ), so managed-agent-api.test.ts 's toBe(await râ€Ķïž›"agent reverse-audit (round 3)"ïžšdid not execute the managed-agent-server suite, so section 7 Slice C's "updated probes and contract tests pin the new matrix" stays unverified for ManagedAgentâ€Ķïž›"agent reverse-audit (round 2)"ïžšsection 8's "Full managed-agent-server suite green on H2" — I test-compiled the module clean offline ( mvn -o -q test-compile , exit 0) but could not establish â€Ķ。

朊åŪĄæŸĨ反向åŪĄčŪĄâ€”â€”čŊ„åŪĄæ—ķé—īéĒ„įŪ—äļčķģïžŒæœŠčƒ―åž€å§‹įŽŽ 4 č―Ū。

[Critical] Re-check of a blocker already filed on this PR by the earlier triage round (comments 6027112673 and 6027112927) - STILL STANDS at 215999c. The base is still feat/13535-role-storage and the diff still re-adds all six files that #13543 also adds: git diff --name-status 7a6c52bd..HEAD reports A for both design docs, api/SurfaceRegistry.java, SurfaceAdmissionAcceptanceTest.java, SurfaceRegistryGateTest.java and SurfaceRegistryGateNegativeTest.java. Only two commits landed since that triage (aab53c1 a zh-CN matrix note, 215999c a registry javadoc qualifier), both documentation-only, so nothing about the stacking changed. Whichever of #13543 and #13545 merges second meets an add/add conflict on all six, and the description's 'this branch is based on feat/13535-role-storage so the diff shows only the enforcement slice' together with 'Design docs (EN/zh-CN) land with #13543' still describe a rebase that has not happened.

[Critical] Re-check of the same triage round's second blocker - STILL STANDS structurally at 215999c. .github/workflows/ci.yml filters pull_request on branches [main, release/, omni-experiment] and .github/workflows/sdk-java.yml on [main, release/], so with this base neither lane fires; the pre-submission check sees 3 checks and none of them is the Java test lane. The evidentiary half of that blocker is discharged by this review rather than by CI: the module was built and tested at this commit (mvn test -> Tests run: 1208, Failures: 0, Errors: 1, and the single error was measured identical at the merge base 7a6c52b, so it is pre-existing in a file this diff does not touch). What remains open is that the PR itself carries no CI signal, and HostedPublicWorkspaceIT plus the two MySQL ITs were never executed in this review because the failsafe profile needs MySQL and a bundled dist/cli.js.

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

Comment thread docs/design/2026-10-07-managed-agent-actor-roles.md
Comment thread docs/design/2026-10-07-managed-agent-actor-roles.md
Comment thread docs/design/2026-10-07-managed-agent-actor-roles.md Outdated
Comment thread packages/web-shell/client/components/managed/generated/managed-agent-api.ts Outdated
â€Ķyed execution facts

The widened OPERATOR admission of the submitter family certified runs
the execution authority still keys on the create-command actor, so a
demoted creator meant an accepted 202 that could only fail. Admission
now also requires the creator-keyed execution facts (registry ACTIVE
and backing the binding, create-command actor keeps OPERATOR or
above), synchronously from the service and the WebShell advertisement,
and the cwd contract names the recorded actor at admission and
settlement. Actor-scoped idempotency replays now run before any
mutable role check on every family, so a role revoked after admission
still resolves a retry to the callers own operation. The v1.34
contract and design docs state the new matrix truthfully, and the
follow-up gaps are tracked: #13617 handover, #13618 legacy hardening,
#13619 command-key actor scope.
@wenshao

wenshao commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification — PR #13545 (Workspace actor roles enforcement, contract v1.34)

Verdict: findings — the enforcement design verifies end-to-end at the current head (A/B flip on the real signed-HTTP stack, every live guard mutation-killed, gates live and green), but the PR's own committed full-stack test has a deterministically-failing arm (wrong turn_id in the new operator2-cancel probe; one-line fix verified below), the PR cannot merge as filed (stack ordering; 7 merge conflicts with current main), and no CI lane has ever run on this base. 47/48 scripted checks passed; the 1 failure is the test bug, measured and fixed locally.

  • Verified head: b9f90ebb6e8ae245f7e64298607a26400d3faff6 (feat/13535-roles-enforcement, tip at verify time)
  • Verified base: 7a6c52bd9e7ff3688fe9e92ebdd92a056470508d (feat/13535-role-storage tip as filed)
  • An earlier round of this verification ran at 215999c8; the author pushed b9f90ebb ("correlate bound-Session admission with creator-keyed execution facts") mid-round, so everything below was re-measured at the new head. Round-1 evidence is preserved in the artifact dir.
  • Environment: macOS arm64, OpenJDK 21 (Homebrew), Maven 3.9.16, Node v22.23.1, H2 MySQL-mode; per-tree isolated Maven repositories.
äļ­æ–‡æ‘˜č́

įŧ“čŪš:findings —— 新 head(b9f90ebb)äļ‹åžšåˆķæ‰§čĄŒčŪūčŪĄäļŽåŪžįŽ°æœŽčšŦ驌čŊé€ščŋ‡(ᜟåŪžį­ū名 HTTP 协čŪŪæ ˆ A/B įŋŧč―ŽæˆįŦ‹ã€æŊäļŠæīŧä―“åۈåŦ均čĒŦ变垂å‡ŧ杀、checkstyle/spotbugs æīŧ性čŊæ˜ŽåŽå…Ļįŧŋ、web-shell 84/84 įŧŋã€į”Ÿæˆį‰Đé›ķæž‚į§ŧ),ä―†æœ‰äļ‰äŧķ之é˜ŧæ­Ēį›īæŽĨ合åđķ:

  1. PR 臩åļĶįš„ full-stack æĩ‹čŊ•有įĄŪåŪšæ€§ bug(æĩ‹čŊ•äŧĢ᠁,éžį”Ÿäš§äŧĢ᠁):HostedPublicWorkspaceIT 新åĒžįš„ operator2-cancel 臂åĪį”Ļ䚆指向å·ēčĒŦ取æķˆįš„ owner turn įš„ cancel map(L581,turn_id=heldTurn),č€ŒįœŸæ­Ģį­‰åū…įŧ“įŪ—įš„æ˜Ŋ L671 įš„ heldByOperator —— 取æķˆå‘é”™åŊđ蹥,čĒŦį­‰ turn æ°ļčŋœ RUNNING,35 į§’čķ…æ—ķ,æ–đæģ• 64s ERROR。owner äļŽ operator2 调į”Ļč€…åŒæ ·æŒ‚(控åˆķåŪžéŠŒ),äļŽč§’č‰ē无å…ģ,įšŊæĩ‹čŊ•变量垕į”Ļ错čŊŊ。äļ€čĄŒäŋŪåĪ(构造 cancelHeldByOperator 指向 heldByOperator)å·ēåŪžæĩ‹:单æ–đæģ• 12.1s įŧŋ、æ•īäļŠ IT įąŧ 4/4 įŧŋã€‚č§ 04 å›ūäļŽ Findings F1。
  2. 无æģ•æŒ‰å―“å‰å―Ēæ€åˆåđķ:base æ˜Ŋäŧ open įš„ feat(managed-agent): persist workspace actor roles and session owners #13544 分æ”Ŋ;äļŽå―“前 main čŊ•合åđķæœ‰ 7 åĪ„å†ēįŠ(5 åĪ„ add/add æĨ臩å·ē合å…Ĩįš„ feat(managed-agent): enumerate the public and WebShell surface with an admission gate #13543,2 å΄内åŪđå†ēįŠæĨ臩 main äļŠįš„ feat(managed-agent): add W1c offline workspace migration #13260/feat(managed-agent): adopt the next Hosted Harness generation (G3) #13174);ci.yml / sdk-java.yml įš„ pull_request 分æ”Ŋčŋ‡æŧĪåŊžč‡ī朎 PR äŧŽæœŠč·‘čŋ‡ CI。
  3. æŪ‹į•™ nit:äļĪäŧ―čŪūčŪĄæ–‡æĄĢ §2 čĄĻæ žįš„æœŠč―Žäđ‰įŪĄé“įŽĶåœĻ新 head äŧåœĻ(EN įŽŽ 54 čĄŒã€zh-CN įŽŽ 30 行);ManagedActionStore.requireOwner åœĻ fix 提äšĪ后成äļšæ­ŧäŧĢ᠁(å…Ļäŧ“æ— č°ƒį”Ļæ–đ,æīŧä―“åš”į­”é—Ļį́åœĻ admit()),javadoc äŧč‡Šį§° "The responder gate"。

新提äšĪčŊ­äđ‰æ ļåŪž:b9f90ebb 把提äšĪ者åŪķæ—å‡†å…Ĩæ”đäļšåŒč”æĄäŧķ(调į”Ļæ–đ OPERATOR 角č‰ē ∧ 创åŧšč€… keyed æ‰§čĄŒäš‹åŪžäŧæˆįŦ‹),åđķæŠŠåđ‚į­‰é‡æ”ū提到åŊå˜č§’č‰ēæĢ€æŸĨäđ‹å‰ã€‚äļĪäļŠæ–°č”æĄäŧķ均čĒŦ变垂įŸĐé˜ĩčŊæ˜ŽåļĶčīŸč――:M7(删é™Īæ‰§čĄŒäš‹åŪžč”éĄđ)čĒŦ committed IT įš„ demoted-creator 409 臂å‡ŧ杀;回æ”ūäž˜å…ˆéĄšåšį”ąæ–°åĒžįš„ aDemotedOperatorStillReplaysTheirAdmittedChange / settlementKeepsAnAdmittedChangeWhenOnlyTheInitiatorDropped é’‰ä―ã€‚čŊ„åŪĄ(S1)æå‡šįš„"įžšéž owner OPERATOR æ­Ģ向见čŊ"å·ēį”ąä―œč€…åœĻ朎提äšĪäļ­čĄĨčŋ› committed IT(operator2-later/hold/cancel/rename 202 臂)——我åœĻįŽŽäļ€č―Ūį”Ļ scratch č‡‚å…ˆį‹ŽįŦ‹čŊæ˜Žčŋ‡åŒäļ€äš‹åŪžã€‚

A/B įŧ“čŪš:单元åĨ—äŧķ head 1215 č·‘ã€0 非įŽŊåǃåĪąčīĨ;base 1041;äļ‰äļŠé”™čŊŊäļĪæ ‘é€å­—čŠ‚åŒå(#13542 äļŽæœŽæœš ToolPublication æ—ķ嚏)。IT åŊđį…§:base äļŠ reader 提äšĪ 409↔403、operator2 åŪĄæ‰đåš”į­” 403(åĄæ­ŧ)、operator2 后įŧ­ Turn 提äšĪ 409——å…ĻéƒĻåœĻ head 变įŧŋ。

朊č͆ᛖ:Linux/Windows č…ŋ(äļĪ台čŋœįĻ‹æœšå™Ļ SSH å…Žé’Ĩ始įŧˆæœŠæŽˆæƒ);MySQL failsafe profile;durableClose(macOS č·ģčŋ‡,Linux-only);runtime-broker å…Ļ量åĨ—äŧķ(æĻĄå—朊čĒŦč§Ķįǰ,åĒžé‡é—­åŒ…å·ēæ ļåŪžäļšįĐš)。

Round-1 findings carried forward (re-measured at b9f90ebb)

# item (from triage stage-3 / round 1) status at b9f90ebb
1 Land #13543/#13544, retarget to main stands, and grew: #13544 still open; trial merge now shows 7 conflicts (5 add/add from merged #13543 + 2 content conflicts in ManagedActionStore/ManagedAgentStore from main's #13260, #13174)
2 Show the acceptance/approval arms fail without the role checks done — mutation matrix below (M1â€ē, M2, M4 etc.)
3 Missing positive witness: non-owner OPERATOR submit succeeds fixed by author — committed IT arms (operator2-later/hold/cancel/rename → 202) now exist; independently pre-proven by my round-1 scratch arm (kept in artifacts)
4 Escape the §2 table pipes in both design docs stands — still malformed at b9f90ebb (EN line 54, zh-CN line 30; mechanical scan, both files)
5 Consider one home for the OPERATOR threshold stands — atLeast(OPERATOR) in service/store vs role IN ('OPERATOR','OWNER') in ManagedActionStore unchanged by the fix commit

Central claim and A/B proof

Central claim (as amended by b9f90ebb): on the bound-Session surface, Turn submit/cancel/rename and cwd change admit any Workspace OPERATOR and certify the creator-keyed execution facts (registry ACTIVE and backing the binding, create-command actor still OPERATOR+); Action respond admits the recorded owner or any OPERATOR; lifecycle admits the recorded owner; actor-scoped idempotency replays precede the mutable role checks; refusal vocabulary 404/403/409 per v1.34.

Unit suite (managed-agent-server, mvn -B test, isolated .m2repo per tree)

cell tests outcome
head b9f90ebb 1215 0 failures, 3 errors — all three byte-identical to base's pre-existing environmental failures
base 7a6c52bd 1041 0 failures, 3 errors (same three names)
round-1 head 215999c8 1208 same 3 + 1 load flake (HarnessCoordinatorTestâ€Ķ[2], green 33/33 on isolated rerun; unrelated to this PR)

Pre-existing error names on both trees: holdsRestorePagesInsideThePerPageByteBudget (the PR-declared #13542 break), ToolPublicationStoreTest.streamsLargeOutputAndReadsItsTailAfterStoreReplacement(Path), â€ĶrenewsTheOriginalClaimWhileScanningSlowObjectBytes (known macOS-local timing failures).

Witness suites at head, all green: SurfaceAdmissionAcceptanceTest 164/164 (the v1.34 refusal matrix), ManagedCwdChangeOperationTest 34/34 (incl. the new demotion/replay arms), Issue13181QueryBudgetTest 30/30 (role-advertised workspaceTurns, one fewer page query), SurfaceRegistryGateTest 3/3 + â€ĶNegativeTest 2/2 + â€ĶUnconstrainedTest 1/1 (the build gate), ManagedWorkspaceRolesMigrationTest 1/1 (V48 invariants). Web Shell: the two touched suites 84/84 green; generate:managed-agent-api produces zero drift; prettier clean.

HostedPublicWorkspaceIT — real signed HTTP, spawned CLI + server, H2

test HEAD (committed) HEAD + one-line fix BASE (same tests)
ownerAnswersHostedApprovalsThroughBothSurfaces PASS (9.0s) PASS ERROR — operator2 respond 403 on base → Turn stuck RUNNING
workspaceCwdChangeSettlesThroughBothSurfaces PASS PASS PASS
publicCreationRunsFilesThroughProductionWorkspaceBinding ERROR (64.5s) — test bug F1 PASS (9.3s) FAIL — READER later-submit 409↔403 flip
durableCloseStopsOriginalWorkersAndRetainsHistoryAndFiles SKIP (macOS) SKIP SKIP

Captures: 01-r2-suite-and-it.png (three-column cells), 04-r2-testbug-fix.png (the bug and its measured fix).

Mutation matrix @ b9f90ebb (single-point mutants, head tree)

mut guard removed result killed by
M1 requireOwner workspace-role arm survived — method has no callers (F5) —
M1â€ē admit(): the live respond-gate role arm KILLED secondOperatorAnswersPendingApprovalsOnBothSurfaces 202→403
M2 maySubmitWorkspaceTurn admission → false KILLED 2 ManagedWorkspaceAdmissionTest rename tests (owner path)
M6 = M2, driven through the committed full-stack IT KILLED operator2 later-Turn 202 arms
M7 creator-keyed facts conjunct dropped KILLED the IT's demoted-creator arm: expected 409, got 202 (mut2-m7b.log; the first M7 run was masked by the F1 test bug, so it was re-run against the fixed IT — evidence discipline)
M3 requireCwdChangeActor OPERATOR gate KILLED ManagedCwdChangeOperationTest ×3
M4 isSessionOwner lifecycle gate KILLED refusal matrix, 8 probes flip 403→409
M5 CONTROL: corrupt the 409 code string KILLED 5 acceptance probes (harness liveness)

Tree state after the matrix: production sources byte-identical to b9f90ebb. Capture: 02-r2-mutation-matrix.png.

Findings

F1 — the committed full-stack test cannot pass: operator2-cancel arm targets the wrong Turn (test bug, deterministic). In HostedPublicWorkspaceIT.runFiles, the cancel body is built once at L581 with turn_id = heldTurn — the owner's hold-turn, already cancelled and settled at L587–590. The new operator2 arm (L671–683) submits a second held turn (heldByOperator) but sends the stale map: the cancel lands on the already-CANCELLED owner turn (a no-op replay, 202), while the awaited heldByOperator stays RUNNING until the 35s awaitility cap (state dump at timeout: status=RUNNING, epoch/dispatch lease present — nothing ever requested its cancellation). Both callers hang identically (owner-cancel control run: same 64–68s ERROR), so it is the variable, not the role path. Untouched by environment: reproduces with zero concurrency. This means the PR's own "operator cancels a running later Turn" witness has never executed anywhere. Measured fix (kept as fix-operator2-cancel.patch):

Map<String, Object> cancelHeldByOperator = Map.of("type",
        "agent.session.cancel", "turn_id", heldByOperator);
request("POST", "/v1/agents/sessions/" + session + "/events",
        cancelHeldByOperator, "operator2-cancel-" + workspace, "operator2", 202);

With exactly this change: single method PASS in 12.1s; whole IT class 4/4 green, BUILD SUCCESS (it-head-fixed.log). Zero collateral — no other arm's expectations move.

F2 — merge as filed is blocked (process, mechanically confirmed). Base branch feat/13535-role-storage is open PR #13544; the filed diff still carries the stacked slices (2,932 of 3,755 additions are the six #13543-overlapping files). Trial merge onto current main: 7 conflicts — 5 add/add (both design docs, SurfaceAdmissionAcceptanceTest, both gate tests; all from the merged #13543) + 2 content conflicts in ManagedActionStore.java and ManagedAgentStore.java (main moved under the base via #13260 and #13174). Post-resolution hazard: #13543 merged a test-tree api/SurfaceRegistry.java; this PR adds the main-tree class with the same FQN — the conflict resolution must delete the test-tree copy or the gate tests compile against a shadow. Neither ci.yml nor sdk-java.yml fires on this base (pull_request filters main/release/** only — confirmed in YAML and in the PR's check list). Capture: 03-r2-merge-tree.png.

F3 — ManagedActionStore.requireOwner is dead code at this head (nit). The fix commit moved respond admission into admit() (owner arms → replay → role arm) and left requireOwner with zero callers anywhere in packages/sdk-java (only two stale javadoc references in ManagedAgentApiContractTest comments). Its javadoc still claims it is "The responder gate". My M1 mutant removed its role arm and the full probe suite stayed green — nothing observes it. Delete it or re-point the comment.

F4 — design-doc §2 table still malformed in both languages (nit, carried). â€Ķactor-roles.md L54 and â€Ķzh-CN.md L30: the close | archive | unarchive cell's raw pipes render 6 cells against a 4-column header (mechanical pipe-count scan, both files).

Confirmed intent, not a defect: lifecycle stays Session-owner-only — a workspace-OWNER who is not the Session owner gets 403 (M4's kill proves enforcement). The stage-3 open question (can an OWNER be locked out of deleting a Session in their own workspace?) is a product call for v1.34, not an implementation bug.

Not covered

  • Linux/Windows legs: the two offered LAN machines (192.168.0.54/.75) rejected the verifier's SSH key for the whole session (publickey denied), so macOS is the only execution leg; the Linux-only durableCloseâ€Ķ arm was skipped everywhere it ran.
  • MySQL failsafe profiles (hosted-harness-mysql against real MySQL): not run; H2 MySQL-mode, matching the author's stated local setup.
  • runtime-broker full suite: the module's input closure is untouched by the PR (re-verified empty at b9f90ebb); round-1 checkstyle/spotbugs gates on it were green and carry forward on that basis.
  • Per-commit attribution (verified as the aggregate base..head diff); the trial-merge tree (un-compilable until the 7 conflicts resolve); web-shell runtime UI behavior (types/latch logic covered by its vitest suites, no browser drive).

Methodology

Two detached worktrees at the metadata OIDs, isolated per-tree Maven repositories (clonefile-seeded). Round 2 re-measured every round-1 cell at the new head. The full-stack IT runs the real server in-JVM plus a spawned Node CLI harness and a loopback model server; the base cell ran the head version of the test file (its assertions are the post-v1.34 matrix, so base reds are the expected-failure control cells); the same dist/cli.js (built from head) served both arms — the PR touches no CLI production code. Mutations are single-point source edits applied/restored by script with per-cell targeted suites; M5 is the can-fail control. The test-bug diagnosis used three independent cuts: an owner-caller control (same hang ⇒ not actor-specific), a state dump at timeout (target turn never left RUNNING, no cancel requested against it), and the measured one-line fix. All raw logs, both mutation scripts, the probe test source, frozen failsafe XMLs (head committed / head+fix / base), and the fix patch are under tmp/pr13545-verify-20261007-224357/.

Evidence captures

  • 01-r2-suite-and-it.png — A/B cells: unit suite, witness suites, HostedPublicWorkspaceIT (HEAD / HEAD+fix / BASE):

    01-r2-suite-and-it.png — unit-suite counts, witness suites, and the three-column IT table (HEAD / HEAD+fix / BASE).

  • 02-r2-mutation-matrix.png — Mutation matrix: every live guard pinned; the dead requireOwner exposed:

    02-r2-mutation-matrix.png — eight mutation cells, incl. the dead-requireOwner exposure and the M1→M1â€ē correction.

  • 03-r2-merge-tree.png — Trial merge onto main: 5 add/add + 2 content conflicts:

    03-r2-merge-tree.png — the seven conflicts against current main.

  • 04-r2-testbug-fix.png — F1: the wrong-turn_id cancel bug, timeout dump, measured one-line fix:

    04-r2-testbug-fix.png — F1's wrong-variable bug, the timeout state dump, and the measured fix.


Local maintainer verification requested by @wenshao. Round-1 head 215999c8 evidence is retained in the same artifact dir; every number above was re-measured at b9f90ebb. Nothing here approves or blocks the merge — the four findings are the actionable set.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Unresolved, please confirm:

  • [Critical] Issue-level triage comment 6027112416 - the context render cut this body at the section budget and it was not fetched within this round's remaining time budget, so whether it asserts a blocker beyond the two triage blockers re-checked aboveâ€Ķ

Not reviewed: build-and-test — the MySQL-backed integration lane never ran: hosted-harness-mysql (HostedPublicWorkspaceIT and the V48 migration ITs) needs a MySQL 8.4 service plus a bundled dist/cli.js, and no CI lane fires at all on this feature-branch base, so those findings are traced, not measured.

Not explored to full depth (tool budget reached): "agent reverse-audit (round 1)": dynamic execution of the three new SurfaceRegistryGate*Test classes and the rewritten boundWebShellPageBatchesTheRoleSubmitCapability — not run; mvn -o tesâ€Ķ; "agent reverse-audit (round 1)": did not compile or run packages/sdk-java/managed-agent-server (Maven dependency resolution offline unverified), so every conclusion above is from source readiâ€Ķ; "agent reverse-audit (round 2)": the zh-CN section-2 claim "į”ąåäļŠ Spring controller åŪžįŽ°" — my controller-count probe resolved a path outside the api package and returned 0; the per-surface routeâ€Ķ; "agent reverse-audit (round 2)": did not open SurfaceAdmissionAcceptanceTest / the SSE-revocation test to confirm §7 Slice C's named probes ("second-operator answers a pending approval on botâ€Ķ; "agent reverse-audit (round 1)": did not execute ManagedWorkspaceRolesMigrationTest or any Java suite — the V48 backfill, the CHECK-constraint rejections and the narrowed responder fall-throuâ€Ķ, and 4 more.

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

Not reviewed: "agent verify" — pointed at diff lines it never opened: it made tool calls, but none of them read the diff.

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

  • docs/design/2026-10-07-managed-agent-actor-roles.md:87 — [probe] D1 defines the Workspace OWNER rank as "OPERATOR plusâ€Ķ
  • docs/design/2026-10-07-managed-agent-actor-roles.md:235 — [review] The RuleClass.OWNER arm of the acceptance walk fires noâ€Ķ
  • docs/design/2026-10-07-managed-agent-actor-roles.md:316 — [review] Section 6's deferral and open question 3 (line 374) bothâ€Ķ
  • docs/design/2026-10-07-managed-agent-actor-roles.zh-CN.md:18 — [review] §2 names the identity contract AuthenticatedActor , aâ€Ķ
  • packages/sdk-java/managed-agent-server/src/main/java/com/alibaba/qwen/code/managedagent/store/ManagedWorkspaceRegistry.java:70 — [review] This PR writes the "recorded Session owner" resolutionâ€Ķ
  • packages/sdk-java/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/SurfaceAdmissionAcceptanceTest.java:83 — [review] The class javadoc states the walk probes theâ€Ķ
  • packages/sdk-java/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/SurfaceAdmissionAcceptanceTest.java:1072 — [review] bodyFor ignores the probe actor, so the WebShellâ€Ķ
  • packages/sdk-java/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/api/SurfaceRegistryGateNegativeTest.java:11 — [review] TestConfiguration (line 11) and Bean (line 13) areâ€Ķ

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

[Critical] Re-check of the blocker filed by the earlier triage round (comments 6027112673 / 6027112927) and re-asserted by round 1 - STILL STANDS at b9f90eb. The base is still feat/13535-role-storage and git diff --name-status 7a6c52bd..HEAD still reports A for all seven paths PR 13543 also added (both design docs, api/SurfaceRegistry.java, SurfaceAdmissionAcceptanceTest.java and the three SurfaceRegistryGate* classes). PR 13543 has since MERGED to main, and four of those paths now differ from origin/main while two are absent from it, so this branch merges second into an add/add conflict. The description half of the round-1 blocker is fixed - the body no longer promises a rebase that has not happened; it now states 13543 merged and that this PR retargets to main once 13544 lands. What remains open is that the retarget has not happened, so the conflict is still live at this head.

[Critical] Re-check of the same triage round's second blocker - STILL STANDS at b9f90eb. .github/workflows/ci.yml filters pull_request on branches [main, release/, omni-experiment] and .github/workflows/sdk-java.yml on [main, release/], so with a feature-branch base neither lane fires: gh pr checks 13545 lists only the review/assign/label/authorize jobs and no Java test lane, and gh run list --commit b9f90ebb shows five runs, all pull_request_target. The description now discloses this and names the retarget as what restores the lanes. The evidentiary half is discharged by this review rather than by CI: the module was built and tested at this commit (web-shell 412 files / 10900 tests green; managed-agent-server surefire 1214 tests with one error measured identical at the merge base, so pre-existing). What remains open is that the PR itself carries no CI signal, and HostedPublicWorkspaceIT plus the MySQL ITs were never executed here.

äļ­æ–‡čŊŽ

äŧ…åŪŒæˆéƒĻ分åŪĄæŸĨåŪĄæŸĨįžšåĢå·ēæŠŦéœē。

朊å†ģčŊ·įĄŪčŪĪïžšå…ą 1 æĄïžˆåŽŸæ–‡æœŠįŋŧčŊ‘列čĄĻ见ä–đč‹ąæ–‡éƒĻ分。

朊åŪĄæŸĨ原文äļšč‹ąæ–‡ïž‰ïžšbuild-and-test — the MySQL-backed integration lane never ran: hosted-harness-mysql (HostedPublicWorkspaceIT and the V48 migration ITs) needs a MySQL 8.4 service plus a bundled dist/cli.js, and no CI lane fires at all on this feature-branch base, so those findings are traced, not measured.

朊æŽĒįīĒ到å…ĻéƒĻæ·ąåšĶčūū到å·Ĩå…·č°ƒį”ĻéĒ„įŪ—ïž‰ïžš"agent reverse-audit (round 1)"ïžšdynamic execution of the three new SurfaceRegistryGate*Test classes and the rewritten boundWebShellPageBatchesTheRoleSubmitCapability — not run; mvn -o tesâ€Ķïž›"agent reverse-audit (round 1)"ïžšdid not compile or run packages/sdk-java/managed-agent-server (Maven dependency resolution offline unverified), so every conclusion above is from source readiâ€Ķïž›"agent reverse-audit (round 2)"ïžšthe zh-CN section-2 claim "į”ąåäļŠ Spring controller åŪžįŽ°" — my controller-count probe resolved a path outside the api package and returned 0; the per-surface routeâ€Ķïž›"agent reverse-audit (round 2)"ïžšdid not open SurfaceAdmissionAcceptanceTest / the SSE-revocation test to confirm §7 Slice C's named probes ("second-operator answers a pending approval on botâ€Ķïž›"agent reverse-audit (round 1)"ïžšdid not execute ManagedWorkspaceRolesMigrationTest or any Java suite — the V48 backfill, the CHECK-constraint rejections and the narrowed responder fall-throuâ€ĶåĶ有 4 æĄã€‚

朊åŪĄæŸĨ反向åŪĄčŪĄâ€”â€”čŊ„åŪĄæ—ķé—īéĒ„įŪ—äļčķģïžŒæœŠčƒ―åž€å§‹įŽŽ 3 č―Ū。

朊åŪĄæŸĨïžš"agent verify"——åŊåŠĻ prompt äļšåŪƒæŒ‡åŪšäš† diff äļ­įš„čĄŒïžŒä―†åۃäŧŽæœŠæ‰“垀有å·Ĩå…·č°ƒį”ĻåīæēĄæœ‰äļ€æŽĄčŊŧ取 diff。

æ”ķ敛å§ŋ态äļ‹åŧķåŽïžˆįŽŽ 2 č―Ū非é˜ŧ断——å·ēčŪ°å―•ïžŒæœŽč―ŪäļčĶæą‚äŋŪæ”đïžšå…ą 8 æĄïžˆåŽŸæ–‡æœŠįŋŧčŊ‘列čĄĻ见ä–đč‹ąæ–‡éƒĻ分。

朚åˆķåĨ嚷朎č―ŪæœŠčƒ―åđē净æ”ķå°ūïžŒå› č€Œæ‰Ģį•™äš†åĒžé‡é”šį‚đïžŒč€ŒåۃæĒåĪåˆ°įš„é‚Ģäļ€č―ŪäđŸæēĄæœ‰į•™äœŽč―ŪåŊį”Ļįš„é”šį‚đ——č́äđˆåیå…ĻæēĄæœ‰ã€č́äđˆæēĄæœ‰čŪĪčŊč€…、č́äđˆį”ąæœŽč―ŪčŋčĄŒčšŦäŧ―äđ‹åĪ–įš„čšŦäŧ―čŪĪčŊã€č́äđˆčĒŦ朎č―Ūįš„čŽ·å–æ‹’įŧæˆ–č§Ģ析äļšåĪīæäšĪ——因æ­Īäļ‹äļ€æŽĄčŊ„åŪĄå°†é‡čŊŧæ•īäļŠ diffé™Ī非æĒåĪæĩįĻ‹æŠŠæœŽč―Ūčƒ―ä―ŋį”Ļįš„æ›īæ—Đč‡Šæœ‰é”šį‚đåŦæŽĨ到朎č―Ūį•™äļ‹įš„åیæ•īå·Ĩä―œæļ…单äļŠïž›åđķ侚äļ€į›īåĶ‚æ­Īį›ī到某äļ€č―Ūįš„æ ‡čŪ°é‡æ–°åļĶäļŠé”šį‚đïžŒæˆ–č―åœ°įš„åŦæŽĨčƒ―čĒŦčŋčĄŒčŊĨč―Ūįš„čŊ„åŪĄä―ŋį”Ļ。äŧ…陈čŋ°ïžŒäļæŪæ­Ī行åŠĻ——čŋ™äļæ”đ变朎č―Ū发åļƒįš„äŧŧä―•å†…åŪđ。

[Critical] Re-check of the blocker filed by the earlier triage round (comments 6027112673 / 6027112927) and re-asserted by round 1 - STILL STANDS at b9f90eb. The base is still feat/13535-role-storage and git diff --name-status 7a6c52bd..HEAD still reports A for all seven paths PR 13543 also added (both design docs, api/SurfaceRegistry.java, SurfaceAdmissionAcceptanceTest.java and the three SurfaceRegistryGate* classes). PR 13543 has since MERGED to main, and four of those paths now differ from origin/main while two are absent from it, so this branch merges second into an add/add conflict. The description half of the round-1 blocker is fixed - the body no longer promises a rebase that has not happened; it now states 13543 merged and that this PR retargets to main once 13544 lands. What remains open is that the retarget has not happened, so the conflict is still live at this head.

[Critical] Re-check of the same triage round's second blocker - STILL STANDS at b9f90eb. .github/workflows/ci.yml filters pull_request on branches [main, release/, omni-experiment] and .github/workflows/sdk-java.yml on [main, release/], so with a feature-branch base neither lane fires: gh pr checks 13545 lists only the review/assign/label/authorize jobs and no Java test lane, and gh run list --commit b9f90ebb shows five runs, all pull_request_target. The description now discloses this and names the retarget as what restores the lanes. The evidentiary half is discharged by this review rather than by CI: the module was built and tested at this commit (web-shell 412 files / 10900 tests green; managed-agent-server surefire 1214 tests with one error measured identical at the merge base, so pre-existing). What remains open is that the PR itself carries no CI signal, and HostedPublicWorkspaceIT plus the MySQL ITs were never executed here.

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

Comment thread packages/sdk-java/managed-agent-server/README.md Outdated
@wenshao

wenshao commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /resolve

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

åĒžé‡åĪéŠŒåŪŒæˆïžŒåŸšäšŽæœ€æ–° head ffd7b2255902096c593a9ebc413cfe95930acfcb埚įšŋ 4a3b8f3c084a1aa82af6cd75684f26123cdc64be。

äļŠč―Ū P2 å·ēäŋŪåĪïž›æœŽč―Ūæ–°åĒžå·Ūåž‚æœŠå‘įŽ°æ–°įš„åŊæŠĨ告é—ŪéĒ˜ã€‚ a3022f13 æĒåĪįš„ generation 臩åŠĻæŽĨįŪĄäļŽ rename čŊŽã€SurfaceRegistry čŊŽã€big-output 和 W1c čŋį§ŧæŪĩč―å‡äļŽå―“前埚įšŋ逐字äļ€č‡ī。原先“åŋ…éĄŧ重åŊ Javaâ€įš„é”™čŊŊčŊŽå·ē删é™ĪäŋŪåĪæēĄæœ‰čĒŦ后įŧ­åˆåđķčĶ†į›–ã€‚

朎č―Ū重į‚đåĪæŸĨ乆äļŧåđē合åđķåŽįš„åیæ•§ïžšč§’č‰ēį”Ÿäš§é€ŧčū‘įš„ PR å·Ūåž‚åŧķįŧ­äļŠč―ŪåŪžįŽ°ïž›actor-key čŋį§ŧ调æ•īäļš V56äŋį•™äļŧåđē V55 channel čŋį§ŧïž›åĨ‘įšĶæ›ī新äļš v1.37åیæ•īäŋį•™äļŧåđē 58 æĄ OpenAPI č·Ŋåū„、å…ĻéƒĻ schema 和å·ē有 changelogã€‚į”Ÿæˆ TypeScript äļŽåĨ‘įšĶäļ€č‡ī。

驌čŊįŧ“果

  • æļ…į†æ—§æž„åŧšäš§į‰ĐåŽæ‰§čĄŒ Java åŪšå‘æĩ‹čŊ•ïžš325 éĄđ通čŋ‡ïžŒ0 åĪąčīĨ/错čŊŊ/č·ģčŋ‡ã€‚čĶ†į›–æƒé™įŸĐé˜ĩ、cwd 发čĩ·č€…æ’Ī权、Workspace admission、åĨ‘įšĶ/č·Ŋį”ą gate、æŸĨčŊĒéĒ„įŪ—ã€æ–°åˆåđķįš„ channel service/contractäŧĨ及 generation æŽĨįŪĄæĩ‹čŊ•。H2 æ—Ĩåŋ—įĄŪčŪĪ V55 channel → V56 actor-key 均成功嚔į”Ļ。
  • WebShell éĄĩéĒ、åŪĄæ‰đ hook、API įąŧåž‹į”Ÿæˆæĩ‹čŊ•ïžš89 éĄđ通čŋ‡ã€‚
  • Flyway 静态æĢ€æŸĨïžš56 äļŠčŋį§ŧįž–å·å‡å”Ŋäļ€ïž›git diff --check 通čŋ‡ã€‚

朎č―ŪæēĄæœ‰åœĻæœŽåœ°é‡č·‘ MySQL/Hosted čŋ›įĻ‹įŦŊ到įŦŊäđŸæœŠčŋčĄŒå…Ļäŧ“æĩ‹čŊ•。发åļƒå‰ïžŒCI įš„ Java、MariaDB、WebShell E2E į­‰å·ēåŪŒæˆæĢ€æŸĨ均无åĪąčīĨïž›Hosted process fault gates / MySQL 8.4 / Java 21 äŧåœĻčŋčĄŒã€‚PR äŧäļš OPEN。

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

Reviewed head ffd7b2255902096c593a9ebc413cfe95930acfcb (base 4a3b8f3c084a1aa82af6cd75684f26123cdc64be) — the enforcement slice of #13535, now reconciled over current main (H4b #13550, H5b/H5c #13572, replay-floor #13621). No merge blocker found; approving.

What I re-verified on this head:

  • The out-of-enum role read is now fail-closed: accessOf, resolveForCreation, initiatorKeepsOperate and WorkspaceExecutionStore all add the role IN ('READER','OPERATOR','OWNER') filter, with a settlement witness (settlementFailsClosedWhenTheInitiatorsStoredRoleIsOutOfEnum) and an admission witness. That closes the note from the previous round.
  • The actor-key migration is renumbered to V56 (V54 is #13550's lineage, V55 is #13572's channel instance binding), and the Flyway uniqueness check passes against main. Note for the merge window: V56 is co-claimed by seven other open PRs (#13526 #13530 #13554 #13598 #13642 #13654 #13682), so if one of them lands first this needs the same renumber dance the storage slice went through (V48→â€Ķ→V53). Not a blocker here.
  • isSessionOwner precedence (owner_actor_key → create-command actor → creator_actor_key) matches the design's owner-of-record resolution and the H4b child cases; the registry, design doc and contract now agree at v1.37 (openapi version 1.37.0; design acceptance says "Contract v1.37").
  • All 75 review threads are resolved; CI on this head is green apart from delay-automatic-review still pending.

Three non-blocking notes: the ownerRow null-contract nit is inline; SurfaceAdmissionAcceptanceTest still omits the positive-admission arm (expectAdmitted), so its walk catches a registration stronger than its handler but not a whole family declared weaker; and the PR body still says "contract v1.36" / "openapi version 1.36.0", while the head artifact is v1.37.0 (main's replay-floor took v1.36) — worth refreshing the body so the test plan matches the tree. None of these blocks merge.

// back execution, so a demoted creator moves an answer to 409
// rather than letting it be queued where the arbiter can never
// reach — whichever arm admitted the caller.
if (ownerRow.workspaceId() != null

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.

Same non-blocking note as the previous round: the two admits helpers just above tolerate a null OwnerRow (session == null ? null : ..., session != null && ...), but this gate dereferences ownerRow.workspaceId() unconditionally. It is safe today because the FOR UPDATE existence probe earlier in this method throws when the Session row is gone, so ownerRow cannot be null here — ownerRow != null && ownerRow.workspaceId() != null && ... would just keep the gate self-consistent with the arms above.

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

Critical-only pass at head ffd7b2255902096c593a9ebc413cfe95930acfcb. Comment, not approval: the two most load-bearing historical Criticals are verifiably fixed at this head, but five further round-2 Criticals and both round-1 Criticals could not be confirmed inside this run's budget, so the approve gate is not met.

Verified fixed at this head, in source

R2-1 — the submitter-family role gate moved behind the idempotency replay. This was the security-critical finding: if the mutable role check ran after the actor-unscoped replay, a revoked actor could still collect a recorded answer. It does not run after it. In ManagedAgentService.java, submit orders:

297:  validateIdempotencyKey(idempotencyKey);
300:  requireSubmitterRole(session, actorId);
304:  Admission replay = replay(tenantId, SUBMIT, idempotencyKey, ...
306:  if (replay != null) { dispatch(...); return response(replay); }
311:  requireSubmitterFacts(session, actorId);

requireSubmitterRole (:959-967) tests the caller's live rank — workspaces.accessOf(...).atLeast(WorkspaceAccess.OPERATOR) — and precedes the replay, so a below-OPERATOR caller never reaches a recorded key. requireSubmitterFacts (:969-975) follows the replay, and the comment at :954-958 states the split deliberately: the facts gate "certifies new work only, so a recorded completed outcome always answers — a Workspace re-registration, a DRAINING row or a creator demotion must not retire it." Cancel keeps its own narrower rule in requireCanceller (:980-988), which requires OPERATOR plus the executable shape and re-checks no grants, consistent with the W2 semantics. The ordering is correct and the reason for it is documented at the site.

R1-30 — the "wrong" publication token was byte-identical to the real one, which made the refusal probes unable to fail. At this head SurfaceAdmissionAcceptanceTest.java:123-124 declares WRONG_TOKEN = "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", a fixed literal appearing once in the file, while the admitted token is issued per test at :954 by credentials.issue(tenant, "ws", session). The probes now pair genuinely distinct values — :966, :996, :1031 and :1241 send WRONG_TOKEN, :989 and :1006 send the issued token — so an issued HMAC and a 32-character q literal cannot collide and the refusal arm asserts something.

The V56 migration is safe. V56__managed_operation_actor_key.sql adds one nullable column and nothing else:

ALTER TABLE managed_agent_operation
    ADD COLUMN actor_key VARBINARY(2048) NULL;

with the header comment recording that pre-V56 rows stay NULL and settle on the creator-keyed facts alone. No backfill, no rewrite of existing rows, no data-loss path. I also checked the version namespace rather than trusting it: main currently tops out at V55__managed_channel_instance_binding.sql, and this head carries exactly one V56, so the renumbering the author describes did preserve main's V55. Flyway migration version uniqueness is green. One forward-looking note, not a finding against this PR: #13598 is open and also claims V56 (V56__managed_automation_ledger.sql), so whichever of the two merges second will need to renumber — the uniqueness gate runs against each PR's own merge base and cannot see the other branch today.

Not confirmed at this head

Five round-2 Criticals and both round-1 Criticals remain unruled by me:

  • R2-4 — widening cwd-change admission from requireWorkspaceCreator to "any OPERATOR". This is the other authorization-widening question on the PR and I did not reach it: the cwd path is not in ManagedAgentService.java, so tracing it needs the store and contract together.
  • R2-2, R2-5, R1-2 — the published responder contract for a bound Session with no owner record, the changeSessionCwd "Errors in pipeline order" sentence, and the v1.34 contract sentence describing who cwd change admits. These are the mis-certification class: contract prose that does not match what the handler does. The contract is now v1.37 and the author reports main's 58 paths and all schemas preserved, but I did not read the sentences against the handlers.
  • R2-3 — the README cwd paragraph promising behaviour around "the initâ€Ķ". Not read.
  • R2-6 — the operator-cancel probe at HostedPublicWorkspaceIT.java:675 reusing the cancel payload declared at :581, which can make the probe assert on the wrong request. Not read.
  • R1-1 — turn submit/cancel/rename admission certifying on the caller's OPERATOR role. R2-1's ordering verification covers the submit arm's role gate directly; I did not separately rule the cancel and rename arms.

I stopped on the short-circuit rule once unconfirmed blocking findings remained, rather than enumerating equivalent evidence for the two I had already closed.

Scope

Of ~2,000 added lines across 30 files I read the submit/cancel admission path and its two predicates, the wrong-token probes, the migration, and the version namespace. ManagedActionStore, ManagedAgentStore, ManagedWorkspaceRegistry, the web-shell ManagedSessionsPage and use-managed-actions changes, and the generated TypeScript client are unexamined by this pass. That is a scope statement, not a clean ruling on those areas. The prior approving pass at ba1e8d7e reports all review threads resolved and both bot CHANGES_REQUESTED verdicts sitting on superseded heads; resolved threads are not code, so I did not treat that as discharging the findings above.

CI at this head

Green: 23 checks succeeded — Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests, both desktop shells, all four Java lanes, Hosted process fault gates / MySQL 8.4 / Java 21, Runtime Broker and Managed Agent MariaDB / Java 21, Real daemon E2E / Java 11, Flyway migration version uniqueness and the web-shell lanes — with only delay-automatic-review queued and no failures. Note the author's report at this head said the MySQL fault-gate lane was still running; it has since landed green.

Next step

Point at per-finding rulings for R2-2, R2-3, R2-4, R2-5, R2-6, R1-1 and R1-2 at this head — R2-4 above all, since it is the remaining authorization-widening question — or re-request review so they can be read against a settled commit. The two fixes I verified and the migration are good as they stand.

@wenshao
wenshao added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 5b1c701 Oct 9, 2026
117 of 118 checks passed
@wenshao

wenshao commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: findings (post-merge confirmation). 551/552 scripted assertions passed, at verified head ffd7b2255902096c593a9ebc413cfe95930acfcb.

  • The PR was merged at 16:18Z as 5b1c701 while this round ran, after approvals from yiliang114 (16:11Z) and qqqys (16:17Z, following the critical-only pass whose open items are ruled below). The landed tree 9394bab is byte-identical to git merge-tree e3c5360 ffd7b22. In packages/sdk-java, cli, core and the managed panel it is also identical to my trial merge 8ea2e76 (main f20ed55 + ffd7b22). So every result below describes what is on main now.
  • The role enforcement holds on the real stack, CI was green, and every earlier blocker stays fixed.
  • The one failed assertion is a new finding, F6. The responder gate resolves an ownerless bound row (H4b child shape) through creator_actor_key first, but the v1.37 contract and the PR's own lifecycle gate use the create command. It is non-blocking and not a regression versus main's old order. A +10/−4 candidate is verified.
  • These are now main follow-ups, not merge blockers: F6, the registry-walk gap (F4), and stale PR-description text.
äļ­æ–‡į‰ˆ

įŧ“čŪšïžšfindings合å…Ĩ后įĄŪčŪĪïž‰ã€‚č„šæœŽåŒ–æ–­čĻ€ 551/552 通čŋ‡ïžŒå·ē驌čŊ head ffd7b22。

  • 朎 PR åœĻ朎č―Ū驌čŊæœŸé—ī16:18ZäŧĨ 5b1c701 合å…Ĩæ­Ī前 yiliang11416:11Z和 qqqys16:17ZåœĻå…ķ critical-only čŊ„åŪĄäđ‹åŽïžŒį›ļå…ģ朊å†ģéĄđ见ä–đč́å†ģå·ēæ‰đ准。合å…ĨåŽįš„æ ‘ 9394bab äļŽ git merge-tree e3c5360 ffd7b22 é€å­—čŠ‚į›ļ同åœĻ sdk-java、cli、core 和 managed éĒæŋč·Ŋåū„äļŠïžŒäđŸäļŽæˆ‘įš„čŊ•合åđķ 8ea2e76main f20ed55 + ffd7b22į›ļ同。所äŧĨäļ‹éĒ所有įŧ“果描čŋ°įš„å°ąæ˜ŊįŽ°åœĻ main äļŠįš„äŧĢį ã€‚
  • 角č‰ēåžšåˆķæ‰§čĄŒåœĻᜟåŪžæ ˆäˆįŦ‹ïžŒCI å…Ļįŧŋäđ‹å‰įš„é˜ŧåĄžéĄđéƒ―äŋæŒäŋŪåĪã€‚
  • å”Ŋäļ€åĪąčīĨįš„æ–­čĻ€æ˜Ŋæ–°å‘įŽ° F6ïžšåŪĄæ‰đå›žį­”é—ļé—ĻåŊđ「无 owner čŪ°å―•įš„įŧ‘åŪščĄŒã€ïžˆH4b 子䞚čŊå―Ēæ€ïž‰å…ˆæŒ‰ creator_actor_key čŪĪ ownerïžŒč€Œ v1.37 åĨ‘įšĶ和朎 PR č‡Šå·ąįš„į”Ÿå‘―å‘Ļ期é—ļé—Ļéƒ―æŒ‰åˆ›åŧšå‘―äŧĪčŪĪ。äļé˜ŧåĄžïžŒį›ļåŊđ main æ—§čĄŒäļšäđŸäļæ˜Ŋå›žå―’ïž›é™„ +10/−4 候选å·ē驌čŊã€‚
  • äŧĨäļ‹įްåœĻéƒ―æ˜Ŋ main äļŠįš„č·Ÿčŋ›éĄđäļæ˜Ŋ合å…Ĩé˜ŧåĄžïžšF6、registry éåŽ†įžšåĢF4、PR 描čŋ°é‡Œčŋ‡æ—ķįš„æ–‡å­—ã€‚

F6 įŧ†čŠ‚ïžš

  • 按 insertChildSessionCommand įš„å†™æģ•造äļ€čĄŒïžšowner äļš NULLcreator åĪåˆķ臩įˆķ侚čŊįš„ cr创åŧšå‘―äŧĪčŪ°åœĻ child:<parent> 名äļ‹ã€‚
  • cr 发čĩ·åŪĄæ‰đ后čĒŦ降äļš READERåۃäŧčƒ―å›žį­”čŋ™æĄåŪĄæ‰đïžš202ïžŒæ‰§čĄŒåŪŒæˆïžŒæ–‡äŧķå·ē写凚。
  • åĨ‘įšĶå’Œį”Ÿå‘―å‘ĻæœŸéƒ―åˆĪåۃ 403archive äđŸįĄŪåŪžčŋ”回 403。
  • 原因åœĻæ™Ū通侚čŊäļŠïžŒé™įš§įš„创åŧšč€…同æ—ķå°ąæ˜Ŋ创åŧšå‘―äŧĪ actor䚋åŪžé—ļ䞚拒409åœĻ子䞚čŊčĄŒäļŠïžŒäš‹åŪžé—ļæĢ€æŸĨįš„æ˜Ŋ合成 actor åĪåˆķæĨįš„æŽˆæƒïžŒé™įš§įǰäļåˆ°åŪƒã€‚
  • 候选äŋŪæģ•ïžšownerIdentityAdmits æ”đį”ĻäļŽ isSessionOwner į›ļåŒįš„éĄšåšïžˆowner → 创åŧšå‘―äŧĪ â†’ creator。įŧ“æžœæ˜Ŋčŋ™äļ€čĄŒå˜ 403H2 1522 äļŠ 0 åĪąčīĨA+B ᜟåŪžæ ˆ 87/87 äļå˜ã€‚įŽ°æœ‰æĩ‹čŊ•åŊđäļĪį§éĄšåšéƒ―é€ščŋ‡ïžŒčŊŽæēĄæœ‰æĩ‹čŊ•é’‰ä―åŪƒã€‚

qqqys čŊ„åŪĄæœŠčƒ―įĄŪčŪĪįš„ 7 æĄ Critical朎č―Ūį”ĻåŪžæĩ‹č́å†ģïžš

  • R2-4cwd æ”ūåŪ―åˆ° OPERATOR成įŦ‹ã€‚
  • R1-1cancel/rename成įŦ‹ã€‚
  • R2-5cwd æŠĨé”™éĄšåšïž‰ïžš6 į§éĄšåšå…ĻéƒĻ按åĨ‘įšĶ。
  • R1-2、R2-3åĨ‘įšĶäļŽ README įš„ cwd 掊čūžïž‰äļŽåŪžæĩ‹äļ€č‡ī。
  • R2-6 å·ēäŋŪ。
  • R2-2 åģ F6。

äđ‹å‰å‘įŽ°įš„įŠķ态

  • įŽŽ 1–6、9 éĄđïžšå·ēäŋŪäŋæŒã€‚
  • įŽŽ 7 éĄđPR 描čŋ°é‡Œįš„ cwd é‚ĢåĨ朊æ”đ。
  • įŽŽ 8 éĄđF4朊æ”đ。
  • įŽŽ 10 éĄđæ­Ģæ–‡å†™įš„æ˜ŊåĨ‘įšĶ v1.36åŪžé™…æ˜Ŋ 1.37.0朊æ”đ。

įŽŊåǃčŊŽïžš ManagedAgentMySqlIT įš„ hook į”Ļäū‹ 60 s čķ…æ—ķïžŒå―“å‰ main åœĻ同样čīŸč――äļ‹äŧĨ同样æ–đ垏åĪąčīĨCI č·‘čŋ™äļŠį”Ļäū‹æ˜Ŋįŧŋįš„ã€‚æŒ‰įŽŊåĒƒåŽŸå› åĪ„į†ïžŒäļčŪĄå…ĨåĪąčīĨ。

Previous-finding status (re-measured at ffd7b22)

Previous rounds:

Rows 6 and 10 come from reviews 5468003961 and 5472551247.

r3-01-status

New: F6, responder owner resolution disagrees with the contract and with lifecycle

ManagedActionStore.ownerIdentityAdmits resolves an ownerless row as owner → creator_actor_key → create command. ManagedWorkspaceRegistry.isSessionOwner (after 5e3d39f) and the respondToSessionAction / respondWebShellAction contract ("â€Ķa bound Session without an owner record answers through the create-command actor recorded for it, or through the Workspace role arm") both use the create command first.

The two orders only disagree on rows where the creator and the create-command actor differ, which is exactly the H4b child shape:

  • insertChildSessionCommand leaves owner_actor_key NULL;
  • it copies the parent's creator_actor_key;
  • it records the create command, plus a copied grant, under child:<parent>.

On such a row, measured on the real stack:

  • I demoted the parent creator to READER after they raised an approval.
  • They still answered it: 202, the operation completed, and the approved write_file ran.
  • Archive by that same actor on that same row correctly answered 403.

On an ordinary Session the same demotion gives 409, because the facts gate checks the demoted actor's own grant (state B). On a child row, the facts gate checks the synthetic actor's copied grant, which no demotion touches.

main's old requireOwner was creator-first as well, so this is not a regression. But the contract this PR published, and its own lifecycle gate, now promise the other order.

candidate-f6-responder-owner-order.patch (+10/−4) gives ownerIdentityAdmits the isSessionOwner order. With it:

  • the copied creator gets 403;
  • H2 stays at 1522 run, 0 failures, 0 errors;
  • the real-stack A+B cells stay 87/87 identical.

No existing test distinguishes the two orders. A lineage-row respond probe should be added along with the fix.

r3-05-f6-responder-owner

The Criticals that review 5472588463 left unconfirmed, ruled by measurement

item ruling evidence
R2-4 holds cwd widened to OPERATOR, bounded by the creator facts and the V56 initiator re-check
R1-1 holds the cancel and rename arms
R2-5 holds the six pipeline orderings each answer in the contract's order (probe Q)
R1-2 holds the v1.37 cwd sentence matches the measured behaviour
R2-3 holds the README cwd paragraph matches the measured behaviour
R2-6 fixed the operator-cancel probe now targets its own Turns
R2-2 stands this is F6, above

r3-04-critical-rulings

What changed since round 2, and what was re-measured

P = a3022f1, H = ffd7b22. The history between them:

git show --remerge-diff isolates the hand-written parts:

  • contract version/changelog, docs, the V56 rename, and test text;
  • 3 Java comment lines.

So the PR's own production logic is unchanged since b8f004f. main's production code under it moved, so the whole matrix was re-run, together with the upgrade from current main and the trial merge.

r3-02-real-stack

r3-03-suites

Follow-ups for main:

Environment, not counted as a failure: ManagedAgentMySqlIT.admitsHookExecutionsWithoutReadingTheirHistoryOnMySql timed out at about 60.1 s ("writer grant is stale") at host load 30–80. main f20ed55 fails identically back to back, and CI's MariaDB lane runs it green.

Not covered:

  • Windows.
  • A live H4b child cascade. F6 uses a lineage-shaped row written the way insertChildSessionCommand writes one, not a relay-created child.
  • Fresh UI captures: the managed-panel code is unchanged since round 2.

Assets: pr13545/r3, with the ledger, raw cells, logs, rig and patches.

doudouOUC added a commit that referenced this pull request Oct 9, 2026
Main's #13545 moved cwd admission from creator identity to
atLeast(OPERATOR) while this branch was in review. The capability
predicate now mirrors exactly that: one role grant read covers the
workspaces of the cwd- and Turn-shaped candidate union, the
execution-facts read covers the union of both id sets, and the facts
batch is skipped when the caller holds no OPERATOR grant — the same
zero-query shape the submit path has.

The OpenAPI entry becomes v1.38 (main took v1.37 for the roles slice).
Capability tests move to the role vocabulary: a non-creator OPERATOR now
sees cwdChange while a READER does not, and the no-facts-read cases run
under a READER-only caller. Design docs record the convergence.
doudouOUC added a commit that referenced this pull request Oct 9, 2026
Textual auto-merge with #13545 concatenated this branch's fail-closed
default methods with main's abstract declarations of the same two
AgentStateStore methods, breaking compilation. Main's abstract
declarations win: ManagedAgentStore is the only implementation and
overrides both. The design docs' fail-closed-default note goes with the
removed methods.
doudouOUC added a commit that referenced this pull request Oct 10, 2026
â€Ķmodel

The field description still said the caller must be the Session creator,
contradicting the role-based predicate shipped in the same diff; the
WebShell design docs still recorded #13545 as open with a pre-rebase
baseline. Both now match the converged role model, and the generated
client is refreshed. The composeSignals fallback test also pins the
hook-lifetime half of the composition, not just the caller signal.

R1-2, R1-3, R1-22.
doudouOUC added a commit that referenced this pull request Oct 10, 2026
Main's #13545 moved cwd admission from creator identity to
atLeast(OPERATOR) while this branch was in review. The capability
predicate now mirrors exactly that: one role grant read covers the
workspaces of the cwd- and Turn-shaped candidate union, the
execution-facts read covers the union of both id sets, and the facts
batch is skipped when the caller holds no OPERATOR grant — the same
zero-query shape the submit path has.

The OpenAPI entry becomes v1.38 (main took v1.37 for the roles slice).
Capability tests move to the role vocabulary: a non-creator OPERATOR now
sees cwdChange while a READER does not, and the no-facts-read cases run
under a READER-only caller. Design docs record the convergence.
doudouOUC added a commit that referenced this pull request Oct 10, 2026
Textual auto-merge with #13545 concatenated this branch's fail-closed
default methods with main's abstract declarations of the same two
AgentStateStore methods, breaking compilation. Main's abstract
declarations win: ManagedAgentStore is the only implementation and
overrides both. The design docs' fail-closed-default note goes with the
removed methods.
doudouOUC added a commit that referenced this pull request Oct 10, 2026
â€Ķmodel

The field description still said the caller must be the Session creator,
contradicting the role-based predicate shipped in the same diff; the
WebShell design docs still recorded #13545 as open with a pre-rebase
baseline. Both now match the converged role model, and the generated
client is refreshed. The composeSignals fallback test also pins the
hook-lifetime half of the composition, not just the caller signal.

R1-2, R1-3, R1-22.
qwen-code-dev-bot pushed a commit that referenced this pull request Oct 10, 2026
#13325)

Merge origin/main's role/facts actor model (#13545) and re-express the
replay-after-delete machinery on it: the visibility fence moves below the
replay while the role gate (whose 404 arm is the read grant) stays above,
and the creator half gates the replay arm on key presence, before the
digest comparison can leak a live key to a non-creator. Renumber the
created_at list index to V58 (main spent V56/V57) and extend the MariaDB
upgrade canary to match. Case-fold the lock-order canary's SQL matching
and pin the store's turn-row population exactly, with synthetic witnesses
for a lowercase lock and a turn-only writer. Fold C0 controls and DEL to
spaces in the WebShell titleFor and the channel bindRoute title
derivation, so the server's control-character rejection no longer breaks
either shipped caller.
doudouOUC added a commit that referenced this pull request Oct 10, 2026
Main's #13545 moved cwd admission from creator identity to
atLeast(OPERATOR) while this branch was in review. The capability
predicate now mirrors exactly that: one role grant read covers the
workspaces of the cwd- and Turn-shaped candidate union, the
execution-facts read covers the union of both id sets, and the facts
batch is skipped when the caller holds no OPERATOR grant — the same
zero-query shape the submit path has.

The OpenAPI entry becomes v1.38 (main took v1.37 for the roles slice).
Capability tests move to the role vocabulary: a non-creator OPERATOR now
sees cwdChange while a READER does not, and the no-facts-read cases run
under a READER-only caller. Design docs record the convergence.
doudouOUC added a commit that referenced this pull request Oct 10, 2026
Textual auto-merge with #13545 concatenated this branch's fail-closed
default methods with main's abstract declarations of the same two
AgentStateStore methods, breaking compilation. Main's abstract
declarations win: ManagedAgentStore is the only implementation and
overrides both. The design docs' fail-closed-default note goes with the
removed methods.
doudouOUC added a commit that referenced this pull request Oct 10, 2026
â€Ķmodel

The field description still said the caller must be the Session creator,
contradicting the role-based predicate shipped in the same diff; the
WebShell design docs still recorded #13545 as open with a pre-rebase
baseline. Both now match the converged role model, and the generated
client is refreshed. The composeSignals fallback test also pins the
hook-lifetime half of the composition, not just the caller signal.

R1-2, R1-3, R1-22.
shenyankm pushed a commit to shenyankm/qwen-code that referenced this pull request Oct 11, 2026
* feat(web-shell): support managed session cwd changes

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* feat(managed-agent): advertise WebShell cwd change capability

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* chore(managed-agent): consolidate W2 WebShell delivery

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): compose abort signals without AbortSignal.any below Chrome 116

refreshSummary composed the caller signal with the hook lifetime via an
unconditional AbortSignal.any, which is Chrome 116 / Safari 17.4 while the
package floor is Chrome 111 / Safari 16.4. On those browsers a successful
cwd change threw TypeError on the completion readback, was booked as
unconfirmed, and left the composer locked for the lifetime of the tab.

Mirror the composeAbortSignals feature-detect from acpTransportUtils and
rebase onto main: the cwdChange capability becomes OpenAPI v1.36 (v1.34 is
reserved for actor roles, v1.35 landed first).

* refactor(managed-agent): address triage nits

- managed.cwd.unconfirmedForbidden now tells the user sending stays
  blocked until the server settles the operation or a new tab is opened.
- cwdShaped collects to HashSet explicitly since retainAll mutates it;
  Collectors.toSet makes no mutability promise.
- Drop the never-read cwd field from ManagedSessionCwdControl basis state.

* fix(web-shell): repair CI after rebase

Prettier rejected the conflict-resolved OpenAPI JSON (the closing brace
stayed glued to the description line), and the transcript entry grew past
its byte ceiling: main measures 1,320,579 and this branch 1,323,120
against a 1,323,000 gate. The growth is the cwd slice's shared i18n
strings; no interactive cwd code enters the transcript graph. Raise the
ceiling to 1,324,000 with the measurements recorded, matching the gate's
documented ratchet practice.

* chore(managed-agent): renumber cwd capability to OpenAPI v1.37

Main took v1.36 for the replay-floor pass (QwenLM#13621) while this branch was
in review, so the cwdChange capability moves to v1.37 and the design docs
follow. Also drop the pre-rebase SHA from the size-gate docblock; the
measurement is unchanged at 1,323,120 on the rebased branch.

* feat(managed-agent): converge cwd capability on the QwenLM#13545 role model

Main's QwenLM#13545 moved cwd admission from creator identity to
atLeast(OPERATOR) while this branch was in review. The capability
predicate now mirrors exactly that: one role grant read covers the
workspaces of the cwd- and Turn-shaped candidate union, the
execution-facts read covers the union of both id sets, and the facts
batch is skipped when the caller holds no OPERATOR grant — the same
zero-query shape the submit path has.

The OpenAPI entry becomes v1.38 (main took v1.37 for the roles slice).
Capability tests move to the role vocabulary: a non-creator OPERATOR now
sees cwdChange while a READER does not, and the no-facts-read cases run
under a READER-only caller. Design docs record the convergence.

* fix(managed-agent): drop duplicate registry-facts interface declarations

Textual auto-merge with QwenLM#13545 concatenated this branch's fail-closed
default methods with main's abstract declarations of the same two
AgentStateStore methods, breaking compilation. Main's abstract
declarations win: ManagedAgentStore is the only implementation and
overrides both. The design docs' fail-closed-default note goes with the
removed methods.

* fix(web-shell): release a parked cwd intent on definitive replay refusals

A lost-ACK intent has no operationId, so Confirm is the only way out —
but definitive was gated on initialSubmission, so a confirm replay could
never classify a refusal as final and the composer stayed locked for the
tab's lifetime. The store's actor-scoped idempotency lookup runs before
the role, revision and busy gates, so a 4xx behind it proves the original
submit was never recorded. Drop the initialSubmission conjunct and
release with the real refusal code; 401 and the pre-lookup read-visibility
404 stay non-definitive (they prove nothing about recording), as do
transient 408/429. The query path starts accepted=true, so transient
mid-poll refusals still retain an admitted operation's intent.

R1-12.

* docs(managed-agent): align the cwdChange contract text with the role model

The field description still said the caller must be the Session creator,
contradicting the role-based predicate shipped in the same diff; the
WebShell design docs still recorded QwenLM#13545 as open with a pre-rebase
baseline. Both now match the converged role model, and the generated
client is refreshed. The composeSignals fallback test also pins the
hook-lifetime half of the composition, not just the caller signal.

R1-2, R1-3, R1-22.

* chore(managed-agent): renumber cwd capability to OpenAPI v1.39

Main took v1.38 for the automation runtime (QwenLM#13598); the cwdChange entry
moves to v1.39 and the design docs follow. Transcript entry measures
1,323,162 against the 1,324,000 gate on the rebased branch.

* codex: address PR review feedback (QwenLM#13760)

* codex: address PR review feedback (QwenLM#13760)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Released in v0.25.1.

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.

feat(managed-agent): Stage D follow-ups for durable lifecycle, Turns, Actions, durable admission and AgentDefinition

6 participants