Skip to content

fix(managed-agent): Declare the tenant filter's 403 on the task read routes - #12966

Merged
wenshao merged 3 commits into
mainfrom
fix/managed-task-read-403
Sep 29, 2026
Merged

wenshao merged 3 commits into
mainfrom
fix/managed-task-read-403

Conversation

@wenshao

@wenshao wenshao commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

This settles the two items of group A in #12847 that can land at any time, ahead of the slices that map task events and cancel.

  • A10: the task read routes declare the tenant filter's 403. The tenant filter answers 403 actor_scope_mismatch on every /v1/agents/ and WebShell route when an authenticated actor belongs to another tenant than X-Qwen-Tenant-Id or has an invalid ID. Session get and list, the lifecycle routes, the operation query and the Turn reads already declare it. The task list and detail on both surfaces, partial since H0c (feat(managed-agent): Commit Stage H records and serve the task list (H0c) #12855), did not, and neither did the planned task event routes. All six now declare it, and the shared Forbidden response says that it covers this code as well as an actor that can read a resource but may not operate on it. feat(managed-agent): Track the task contract gaps deferred from the H0a review #12847 names the four partial routes; the two planned event routes are included because the filter's 403 is a fact about the filter rather than a decision H3 owes, and the planned cancel routes already declare 403. A caller that cannot read a task still gets 404. The contract becomes v1.21.0.
  • A9: the task contract note records the decision that the Legacy paused and pausing states map to waiting and that TaskState gains no state. H0c already made TaskState partial with its eight values, so a new value would now be a breaking change.
  • The error section of the task contract note lists the filter's invalid_tenant and actor_scope_mismatch and no longer says that the read routes declare no 403, in both languages.
  • The contract test probes 403 on the four served task routes, as it already does on the Session and Turn reads. The contract test only probes mapped routes, so a separate check requires the 403 on the four planned task routes (both event queries and both cancels).

Why it's needed

The server already returns this 403 on the task routes; the contract did not say so. Clients that follow the contract therefore do not expect it on a task read, and the contract test could not probe it, because it refuses a probe for a status the route does not declare. Declaring a response is additive, so #12847 notes that this can land at any time. A9 was decided in #12847 before H0c made TaskState partial, but the note still said that the adapter slice would map the Legacy states "most likely to waiting, or the design adds a state".

Reviewer Test Plan

How to verify

  • Run the Managed Agent server's tests. The contract test passes with no new gap lines.
  • Read a Session's task list or one task with an authenticated actor from another tenant: 403 with actor_scope_mismatch, which the contract now declares. Reading another tenant's Session still answers 404.
  • Regenerate the WebShell managed API types: the two WebShell task reads gain the 403 response and the description of the Forbidden response changes; nothing else does.

Evidence (Before & After)

N/A (no visible UI change).

Local results on Linux:

  • Managed Agent server: mvn test passes (179 tests), and so does Checkstyle.
  • Mutations: removing the 403 from the WebShell task detail route fails the contract test with getWebShellTask declares 403, and removing it from the planned WebShell task event query fails the planned-task check with queryWebShellTaskEvents declares 403.
  • Web Shell: the regenerated managed API types differ from main only by that 403 and description; the types test passes (2/2) and fails against the old types, and npm run typecheck passes.

Tested on

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

Environment (optional)

JDK 25 running the Java 21 build with Maven; Node 22.

Risk & Scope

Design note: English and 中文, both updated in sections 4.7 and 7.

Linked Issues

Related #12847 (A9 and A10). Part of #12827.

中文说明

这个 PR 做了什么

本 PR 处理 #12847 A 组中随时可以落地的两项,不必等映射任务事件与取消的切片。

  • A10:任务只读路由声明租户过滤器的 403。 当已认证的 actor 属于 X-Qwen-Tenant-Id 以外的租户或其 ID 非法时,租户过滤器会在每条 /v1/agents/ 与 WebShell 路由上返回 403 actor_scope_mismatch。Session get 与 list、生命周期路由、operation 查询和 Turn 读取路由都已声明它。自 H0c(feat(managed-agent): Commit Stage H records and serve the task list (H0c) #12855)起为 partial 的两个表面上的任务列表与详情没有声明,planned 的任务事件路由也没有。现在这六条路由都声明了它,共用的 Forbidden 响应也写明它既涵盖这个错误码,也涵盖能读取资源但无权操作的 actor。feat(managed-agent): Track the task contract gaps deferred from the H0a review #12847 列出的是四条 partial 路由;两条 planned 事件路由一并处理,因为过滤器的 403 是关于过滤器本身的事实,不是 H3 需要做的决定,而 planned 的取消路由已经声明了 403。无权读取任务的调用方仍然得到 404。契约版本变为 v1.21.0。
  • A9:任务契约设计说明记录了这项决定: Legacy 的 paused 与 pausing 状态映射为 waiting,TaskState 不增加状态。H0c 已把 TaskState 连同其八个值标为 partial,此后再增加一个值就是破坏性变更。
  • 任务契约设计说明的错误一节中英两版都列出了过滤器的 invalid_tenant 与 actor_scope_mismatch,不再写"只读路由不声明 403"。
  • 契约测试在四条已提供的任务路由上探测 403,与 Session 和 Turn 读取路由的做法相同。契约测试只探测已映射的路由,因此另有一项检查要求四条 planned 任务路由(两条事件查询与两条取消)声明 403。

为什么需要

服务端本来就会在任务路由上返回这个 403,只是契约没有写出来。因此按契约编写的客户端不会预期任务读取返回它,契约测试也无法探测它,因为测试拒绝探测路由未声明的状态码。声明一个响应是增量变更,所以 #12847 写明这项随时可以落地。A9 在 H0c 把 TaskState 变为 partial 之前就已在 #12847 中决定,但设计说明仍写着适配切片会把 Legacy 状态"最可能映射为 waiting,或由设计增加一个状态"。

评审测试计划

如何验证

  • 运行 Managed Agent 服务端的测试。契约测试通过,没有新增 gap 行。
  • 用另一个租户的已认证 actor 读取某个 Session 的任务列表或单个任务:返回 403 与 actor_scope_mismatch,契约现在声明了它。读取另一个租户的 Session 仍返回 404。
  • 重新生成 WebShell 的 managed API 类型:两条 WebShell 任务读取路由增加 403 响应,Forbidden 响应的说明随之更新,其余不变。

证据(前后对比)

不适用(没有可见的 UI 变化)。

在 Linux 上的本地结果:

  • Managed Agent 服务端: mvn test 通过(179 个测试),Checkstyle 也通过。
  • 变异验证: 去掉 WebShell 任务详情路由的 403,契约测试失败,报 getWebShellTask declares 403;去掉 planned 的 WebShell 任务事件查询的 403,planned 任务检查失败,报 queryWebShellTaskEvents declares 403。
  • Web Shell: 重新生成的 managed API 类型与 main 相比只差上述 403 与说明;类型测试通过(2/2),换回旧类型则失败;npm run typecheck 通过。

测试平台

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

环境(可选)

JDK 25 运行 Java 21 构建,使用 Maven;Node 22。

风险与范围

设计说明:English 与 中文,第 4.7 节与第 7 节均已更新。

关联 Issue

Related #12847(A9 与 A10)。属于 #12827。

…routes

The tenant filter answers 403 actor_scope_mismatch on every /v1/agents
and WebShell route when an authenticated actor does not belong to the
tenant. The Session, lifecycle, operation and Turn routes declare it, but
the task list, detail and event routes did not, and the task contract
note said that the read routes declare no 403. Declare it on the six task
read routes of both surfaces, say in the shared Forbidden response that
it covers this code, and move the contract to v1.21.0. The contract test
probes the 403 on the four served task routes.

The note also records the A9 decision: the Legacy paused and pausing
states map to waiting, and TaskState gains no state.

Part of #12847.

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

Reviewed. Suggestions are inline.

Not explored to full depth (tool budget reached): "agent 6b": did not run packages/web-shell/client/components/managed/managed-agent-api.test.ts — the generated-types freshness test that compares the checked-in file to ….

中文说明

已审查。 建议见行内评论。

未探索到全部深度(达到工具调用预算):"agent 6b":did not run packages/web-shell/client/components/managed/managed-agent-api.test.ts — the generated-types freshness test that compares the checked-in file to …。

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

…refix

The contract test probes the 403 only on mapped routes, so nothing
checked the declarations on the planned task event routes. A new check
in PlannedTaskContractTest requires the 403 on the planned task event
and cancel routes of both surfaces.

The tenant filter matches the /v1/agents/ prefix with its trailing slash,
so the collection path POST /v1/agents skips it. Say /v1/agents/ in the
Forbidden description and the v1.21 note, as the design note does.
@wenshao

wenshao commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the review in 982da70:

  • R1-1: the planned task routes' 403 is now pinned by PlannedTaskContractTest, since the contract test only probes mapped routes. Removing it from queryWebShellTaskEvents fails the check.
  • R1-2: the Forbidden description and the v1.21 note say /v1/agents/, matching the filter's prefix; POST /v1/agents skipping the filter is B11 of feat(managed-agent): Track the task contract gaps deferred from the H0a review #12847.
  • Triage notes: the PR body now says why six routes are declared where feat(managed-agent): Track the task contract gaps deferred from the H0a review #12847 names four, lists all 19 other filtered operations that still lack a 403 for a follow-up, and notes that the collision with feat(managed-agent): Implement private Hosted MCP runtime (H1) #12946 is textual in info.description, not only the version. The Forbidden description is left as one sentence with "or" for now, as the review suggested.
  • Red Hosted process fault gates / MySQL 8.4 / Java 21 on affe54a: ProcessCrashFaultGateTest.aRestartedBrokerAdoptsTheWorkerAndNeverRunsTheCallTwice(Window)[2] failed with 503 runtime_provision_fenced Runtime recovery claim expired in the Runtime Broker module, which this PR does not touch; in the same job the Managed Agent server suite passed 178/178. The push of 982da70 runs it again.
中文说明

已在 982da70 处理评审意见:

  • R1-1: 由于契约测试只探测已映射的路由,planned 任务路由的 403 现在由 PlannedTaskContractTest 守住。去掉 queryWebShellTaskEvents 的 403 后该检查失败。
  • R1-2: Forbidden 描述与 v1.21 的说明改为 /v1/agents/,与过滤器的前缀一致;POST /v1/agents 绕过过滤器是 feat(managed-agent): Track the task contract gaps deferred from the H0a review #12847 的 B11。
  • Triage 意见: PR 描述现在说明了为什么声明六条路由而 feat(managed-agent): Track the task contract gaps deferred from the H0a review #12847 写的是四条,列出了过滤器之后其余全部 19 个仍未声明 403 的操作留待后续处理,并写明与 feat(managed-agent): Implement private Hosted MCP runtime (H1) #12946 的冲突是 info.description 的文本冲突,而不只是版本号。按评审建议,Forbidden 描述暂时保持为用"或"连接的一句话。
  • affe54a 上红色的 Hosted process fault gates / MySQL 8.4 / Java 21: Runtime Broker 模块中的 ProcessCrashFaultGateTest.aRestartedBrokerAdoptsTheWorkerAndNeverRunsTheCallTwice(Window)[2] 失败,报 503 runtime_provision_fenced Runtime recovery claim expired,本 PR 没有改动该模块;同一个作业里 Managed Agent 服务端测试 178/178 通过。推送 982da70 后会重新运行。

@wenshao

wenshao commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

qqqys
qqqys previously approved these changes Sep 29, 2026

@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 scan — APPROVE

Head reviewed: 982da70e142c687d18e76934b11fcbce74fce2b3

Scope

Contract, documentation and test changes only. No production code is touched:

  • openapi/managed-agent-public-api.openapi.json (+21/−3) — version 1.20.0 → 1.21.0, six added 403 declarations, one rewritten Forbidden description
  • web-shell/client/components/managed/generated/managed-agent-api.ts (+3/−1) — regenerated mirror
  • two design docs (+22/−8) and the server README (+5/−2)
  • ManagedAgentApiContractTest.java (+17), PlannedTaskContractTest.java (+12)

Historical blocking issues — none exist

There is no CHANGES_REQUESTED and no bot review on this PR. The only two inline findings were filed by the author against his own earlier head affe54ab and both are marked [Suggestion]; both were fixed at this head. The sandboxed verification comment reports "✅ passed — merge-ready", and there is no [Critical] or sev:C marker anywhere in this PR's history.

What I verified

Every declaration this PR adds is now pinned by a test, on both sides of the mapped/planned split. This was the substance of the author's own R1-1, and the fix is the right shape:

  • The four mapped task routes — listSessionTasks, getSessionTask, queryWebShellTasks, getWebShellTask — gain a real exchange(drift, …, 403, …) case each in ManagedAgentApiContractTest.exchangeTasks. Each sends the correct X-Qwen-Tenant-Id with an actor principal from otherTenant, so the tenant filter's 403 actor_scope_mismatch is actually produced by a request and checked against the spec through the drift map, not merely declared.
  • The four planned routes — listSessionTaskEvents, queryWebShellTaskEvents, cancelSessionTask, cancelWebShellTask — cannot be exercised because no pass sends a request to a planned operation, so the new PlannedTaskContractTest.plannedTaskRoutesDeclareTheTenantFilterForbidden asserts the declaration directly, with a failure message naming the operation and a comment recording exactly why this test has to exist separately. Deleting any one of those 403 blocks now reddens a named test.

Together these cover the whole set the v1.21 sentence claims, so the prose and the spec cannot diverge silently.

The contract no longer over-claims. The author's R1-2 was that "every /v1/agents route" is false for the collection path, because TenantContextFilter.shouldNotFilter matches path.startsWith("/v1/agents/") with the trailing slash and so skips POST /v1/agents entirely. At this head both the Forbidden description and the v1.21 sentence read /v1/agents/, which is accurate, and the collection-path gap is recorded as item B11 of #12847 rather than being papered over by a wrong sentence. Declaring a filter behaviour the filter does not have would have been the one real defect available in a contract-only PR, and it is not present.

The generated client is in step with the spec. managed-agent-api.ts carries the identical rewritten Forbidden description and adds 403: components["responses"]["Forbidden"] to the same operations, so the file reads as regenerated rather than hand-edited, and SDKs generated from it will surface the response.

The change is additive for callers. Each hunk inserts a 403 alongside the existing 400 and 404; no response, schema, field or status code is removed, and the v1.21 sentence records that a caller who cannot read a task still gets 404 — so the existing not-found behaviour is unchanged and only a previously-undocumented status becomes visible. The whole-spec count of Forbidden references at this head is 36, six more than before, matching the six declarations.

The Session and Turn reads already declared this, so the task routes were the inconsistency; this PR removes it rather than introducing a new response class.

CI

Green at this head: Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox), web-shell E2E Smoke, Capture web-shell visuals, the full Java matrix, Runtime Broker and Managed Agent MariaDB / Java 21, Hosted process fault gates / MySQL 8.4 / Java 21 and Real daemon E2E / Java 11 all pass. Only review-pr is pending, which is not a gating check. No failure is attributable to this PR.

The tenant filter answers 403 actor_scope_mismatch for an actor from
another tenant and for an actor with an invalid ID; the Forbidden
description named only the first. The task contract note now names
both, lists the filter's 400 invalid_tenant beside it, and records the
sixth PlannedTaskContractTest test in sections 5 and 6.
@wenshao

wenshao commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2 of /review (run) finished but could not post: GitHub answered 422 "User can only have one pending review per pull request", because qwen-code-ci-bot still holds an empty pending review (5346384745) from round 1. Its findings are in the run log; I checked each one against the code and handled them in 032ea70:

  • R2-1 (fixed): sections 5 and 6 of the task contract note still counted five PlannedTaskContractTest tests. Both now record the sixth, which requires the filter's 403 on the planned task routes.
  • D2-3 (fixed): the Forbidden description named only an actor from another tenant, but the filter also answers actor_scope_mismatch for an actor with an invalid ID (!validActorId). The contract, the regenerated types and the note now name both. The sandboxed verification report flagged the same point.
  • D2-2 (fixed): section 4.7 now also lists the filter's 400 invalid_tenant, beside its 403.
  • D2-1 (not changed): the four new probes check the status but not the envelope's code. The review deferred it, and every existing 403 probe in the contract test works the same way; tightening them all fits feat(managed-agent): Declare the tenant filter's 403 on the remaining filtered routes #12976.

Local checks on 032ea70: the three contract tests pass 14/14, and the WebShell types test passes 2/2 after regeneration.

中文说明

第二轮 /review(运行)已完成,但没能发布:GitHub 返回 422 "User can only have one pending review per pull request",因为 qwen-code-ci-bot 仍挂着第一轮留下的一个空的 pending review(5346384745)。它的发现记录在运行日志里;我逐条对照代码核实,并在 032ea70 中处理:

  • R2-1(已修复): 任务契约设计说明第 5、6 节仍写着 PlannedTaskContractTest 有五个测试。两节现在都记录了第六个测试,它要求 planned 任务路由声明过滤器的 403。
  • D2-3(已修复): Forbidden 描述只写了来自其他租户的 actor,但对 ID 非法的 actor,过滤器同样返回 actor_scope_mismatch(!validActorId)。契约、重新生成的类型和设计说明现在都写明了这两种情况。沙箱验证报告也指出了同一点。
  • D2-2(已修复): 第 4.7 节现在在过滤器的 403 旁边也列出了它的 400 invalid_tenant。
  • D2-1(未修改): 新增的四个探测只校验状态码,不校验错误封装里的 code。评审已把它列为延后项,而契约测试里现有的所有 403 探测都是同样的写法;统一收紧它们适合放进 feat(managed-agent): Declare the tenant filter's 403 on the remaining filtered routes #12976。

032ea70 的本地检查:三个契约测试 14/14 通过;重新生成后 WebShell 类型测试 2/2 通过。

@wenshao

wenshao commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

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

批准

对照 032ea70 和当前 main(契约仍为 1.20.0)看过 diff、TenantContextFilter、两条契约测试和 #12847 的 A9 / A10。没有需要改的问题。

核对过的点

  • A10。 六条任务路由的 403 都指向共用的 Forbidden。四条已映射的读取(listSessionTasks、getSessionTask、queryWebShellTasks、getWebShellTask)在 exchangeTasks 里用正确的租户头,加上另一租户的 AuthenticatedTenantActor 探测。exchange() 会先要求该状态码已声明,再比对实际状态码,并按 ErrorEnvelope 校验响应体。两条 planned 事件路由发不出请求,由 plannedTaskRoutesDeclareTheTenantFilterForbidden 守住声明;两条取消路由本来就有 403,放进同一检查是回归守卫。这比 #12847 点名的四条 partial 路由多两条事件查询,理由成立:过滤器的 403 是过滤器自己的行为,而 planned 的取消路由已经声明了 403。
  • 过滤器语义。 403 的两个分支是 !tenantId.equals(actor.tenantId()) 和 !validActorId;400 invalid_tenant 是头缺失,或不匹配 ^[A-Za-z0-9._:-]{1,128}$。中英设计说明、Forbidden 描述和生成类型都写了这两种 403。/v1/agents/ 的尾斜杠与 shouldNotFilter 一致,没有把会跳过过滤器的 POST /v1/agents 说进去。
  • A9。 中英设计说明都写了 Legacy 的 paused / pausing 映射为 waiting,TaskState 不增加取值,与 #12847 A9 一致。
  • 生成物。 managed-agent-api.ts 只给两条已提供的 WebShell 任务读取(queryWebShellTasks、getWebShellTask)加了 403,Forbidden 的 @description 与契约原文一致。公共路由不在这份 WebShell 镜像里;planned 的事件与取消路由也不会进入生成类型。
  • 行为不变。 只增加响应声明。读不到任务仍然是 404(另一租户的 X-Qwen-Tenant-Id),与已认证 actor 跨租户时的 403 actor_scope_mismatch 是两条路径。
  • 与当前 main 可以干净合并。#12968 改的是 transcript 描述,和这处不重叠。契约版本仍应从 1.20.0 升到 1.21.0。

先前两轮意见已经落到代码里:planned 路由的 403 有测试守住,描述使用带尾斜杠的 /v1/agents/,并写明了非法 actor ID 这个第二触发条件以及过滤器的 400 invalid_tenant。

合入时留意

#12946 如果仍占用 1.21.0,并往同一行 info.description 追加文字,后合入的一方改成 1.22.0 并手工解决这段文本。其余 19 个未声明 403 的操作留在 #12976,不在本 PR 范围。

CI

Java 矩阵、fault gate、Test (ubuntu-latest, Node 22.x)、Lint 均已通过。提交本评审时 web-shell E2E Smoke 仍在跑。本 PR 没有 UI 行为变化,生成类型是否与契约一致已由 Node 测试覆盖。

English

Approve. Checked 032ea70 against current main (contract still 1.20.0), TenantContextFilter, both contract tests, and #12847 A9/A10. Nothing to change.

The six task routes declare 403 via the shared Forbidden response. The four mapped reads are probed with the resource tenant plus an actor from another tenant, so exchange() pins the declaration, the status, and the ErrorEnvelope. The two planned event routes are pinned by plannedTaskRoutesDeclareTheTenantFilterForbidden; the two cancels already declared 403, and including them is a regression guard. The extra event routes beyond the four partial reads named in #12847 are justified: the filter's 403 is a property of the filter, and the planned cancels already declared it.

Both 403 branches (tenant mismatch and !validActorId) and the filter's 400 invalid_tenant are named in both design notes, the Forbidden description, and the generated types. The trailing slash on /v1/agents/ matches shouldNotFilter and does not claim POST /v1/agents. A9 records paused / pausing → waiting with no new TaskState value. The generated WebShell types add 403 only on queryWebShellTasks and getWebShellTask, and the description matches the contract byte for byte. Server behavior is unchanged: an unreadable task is still 404.

The branch merges cleanly with current main. If #12946 still takes 1.21.0 and appends to the same info.description line, whichever lands second should move to 1.22.0 and resolve that text. The other 19 undeclared 403s stay in #12976.

Java matrix, fault gates, Test (ubuntu-latest, Node 22.x), and Lint are green. web-shell E2E Smoke was still running when this review was submitted; this PR has no UI behavior change, and generated-type freshness is covered by the Node test.

@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 scan — APPROVE

Head reviewed: 032ea70ee1ba3cc0e8e7a36946eb91c503cb787d

My approval of the previous head 982da70e was dismissed when this commit landed, so this is a fresh review of the current head rather than a restatement.

What changed since the dismissed approval

One commit, docs(managed-agent): Name both 403 triggers and the filter's 400, touching four files and no logic:

  • both design docs (+21/−15 between them)
  • openapi/managed-agent-public-api.openapi.json — one Forbidden description string
  • web-shell/.../generated/managed-agent-api.ts — the mirrored string

The six 403 declarations, ManagedAgentApiContractTest (+17) and PlannedTaskContractTest (+12) are byte-identical to the head I already reviewed, so the verification below carries over unchanged and I re-confirmed it against this head's file list.

The new wording is accurate against the filter

The substantive claim this commit adds is that the tenant filter's 403 covers an invalid actor ID and not only a tenant mismatch. I read TenantContextFilter at this head rather than taking the description's word: the missing-or-malformed X-Qwen-Tenant-Id path sets SC_BAD_REQUEST with the invalid_tenant envelope, which is the new 400 row the design doc adds, and the SC_FORBIDDEN / actor_scope_mismatch branch is gated on validActorId(tenantId, actorId), which constructs a WorkspaceActor — so an actor ID that does not validate reaches the same 403 as a cross-tenant actor. Both triggers named in the rewritten description are real, and the doc's new invalid_tenant row matches the filter's actual status and code.

The design doc also now records that from 1.21.0 the contract test requires the tenant filter's 403 on the four planned task routes because ManagedAgentApiContractTest cannot probe an unmapped operation. That is an accurate description of PlannedTaskContractTest.plannedTaskRoutesDeclareTheTenantFilterForbidden, so the prose and the tests no longer disagree about which routes are covered how.

Spec and generated client are in step

The Forbidden description in the OpenAPI document and the @description on the generated Forbidden component are the same rewritten sentence, so the mirror was regenerated rather than hand-edited and SDKs generated from it will carry the corrected text.

Carried-forward verification

Unchanged from the previous head and re-confirmed present here:

  • The four mapped task routes each gain a real exchange(drift, …, 403, …) case sending the correct tenant header with an actor principal from another tenant, so the 403 is produced by a request and checked against the spec through the drift map.
  • The four planned routes are pinned by declaration, each with a failure message naming the operation, so removing any one 403 block reddens a named test.
  • The change is additive for callers: each hunk inserts a 403 beside the existing 400 and 404, nothing is removed, and a caller who cannot read a task still gets 404.
  • The contract does not over-claim the filter's reach — both the description and the doc say /v1/agents/, correctly excluding the collection path the filter skips, which is recorded as item B11 of #12847.

Historical blocking issues — none

There is no CHANGES_REQUESTED in this PR's history and no inline finding other than the author's own two [Suggestion]s against an earlier head, both fixed before the head I first approved. doudouOUC approved this exact head.

CI

Green at this head: Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox), web-shell E2E Smoke, Capture web-shell visuals, the full Java matrix, Runtime Broker and Managed Agent MariaDB / Java 21, Hosted process fault gates / MySQL 8.4 / Java 21, Real daemon E2E / Java 11 and both Desktop Shell lanes all pass. Only review-pr is pending, which is not a gating check. No failure is attributable to this PR.

@wenshao
wenshao added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 92a4eca Sep 29, 2026
69 of 70 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants