You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
runtime(mcp): an OAuth-token agent's api action forward carries a bearer the target door does not accept (acceptOAuthAccessToken matches /mcp paths only), so the target likely resolves the caller as anonymous #28178
Filing gate: ② a finding a dev report carried out of scope (#27908's dev, draft PR #28171, out-of-scope finding 1). Filed by domain:spec seat 4 · marchtian · session session_017ihL2fi9zfJhUTubyJYb6V. ⛔ Not a claim. No grade and no lane: triage sets both (the code is packages/runtime).
Reader: the lane triage assigns.
Dedupe (open AND closed): semantic search "OAuth access token agent api action forward target anonymous acceptOAuthAccessToken mcp path only" → 8 hits. #24137 and #24166 are the OAuth delegator's organization; #16549 is the OAuth ceiling; #27681 is run_action on sys_* objects. None is the forward's bearer. New.
reach: read in code on origin/maina3d24c991f, NOT measured through a public door. Any agent connected over OAuth (not an API key) whose run_action reaches an api-type action that forwards in-process to a non-/mcp route, for example the sys_* identity actions #27681 opened to MCP.
What the code says
HttpDispatcher accepts an OAuth access token only on /mcp paths: acceptOAuthAccessToken: /^(?:\/environments\/[^/]+)?\/mcp(?:[/?]|$)/.test(cleanPath) (packages/runtime/src/http-dispatcher.ts:753).
forwardApiAction (packages/runtime/src/action-execution.ts:1529) relays the caller's authorization header (the forwarded header list, action-execution.ts:1167) to the action's target route, which is not under /mcp.
So an OAuth bearer that /mcp accepted reaches a door that does not accept it. The likely result is that the target resolves the caller as anonymous and refuses (401) or serves an anonymous answer, while the same action over an API key works.
Wanted
Measure first: drive run_action over an OAuth token against an api action whose target needs a signed-in caller (e.g. ban_user on examples/app-showcase), and compare with the same call over an API key.
If confirmed: the in-process forward carries an identity the target accepts, without widening where OAuth tokens are accepted from outside callers. Prefer a server-constructed forward credential to loosening the path match.
Related: #27681, #27908 / PR #28171 (whose rows are written by the door, so they are unaffected).
Read fresh. This is read in code, not measured. It reads as fail-closed: the forward's target does not accept the bearer, so the OAuth agent gets less than it is entitled to, not more. That makes it a broken function, not a hole.
Step 1 is the card's measurement: an OAuth token against an API key on the same api action. If it is not reproduced, close the card with the measurement.
⛔ The fix must not widen where an outside caller's OAuth token is accepted. A fix that needs a change to that boundary comes back as a decision card before any code.
Claim: PM loop round 2 · 2026-10-30T16:50Z
Session: session_016WV4d1MkJvadehWmrLUUT1 (card thread; seat session_016WV4czuTWNgKR22fKv7jia)
Account: os-elon-musk
Branch: claude/project-thread-o9ppaa (the card thread's platform-assigned branch — the recorded platform exception; amended by the seat when the thread reported it)
Worktree: objectstack-issue-28178
Domain: domain:server
Seat: domain:server#3
File surface: amended under the maintainer's option-B ruling (6540286612): packages/runtime/src/action-execution.ts (the credential-kind × target-door table, its shared predicate, the run_action refusal by name, and the #28217 identity-route check moved onto the table), packages/runtime/src/domains/mcp.ts (listActions reads the predicate and marks the action), packages/mcp/src/mcp-http-tools.ts (McpActionSummary gains one optional mark member, and the list_actions description says what it means), tests beside each, an OAuth-lane dogfood pin, content/docs/**/actions-as-tools.mdx, .changeset/*. Clause-② expected yes (widening) (an answer member added), so a CONTRACT_REVIEW_TIER review is owed.
Container & model: M, mode:thread, model: opus (TIER_DEFAULT), effort: high
Clause-②: no for the measurement; the fix declares its own (expected no)
Metadata-protocol: none
Responsibility: an OAuth-token agent's run_action on an api action reaching its target with an identity the target accepts, as the same call over an API key does, without widening where OAuth tokens are accepted from outside.
Thread-read: 6527276576
Premise: read in code on main (filer, a3d24c99), not measured; the dev measures OAuth vs API key on the same api action on main @ 6fd87b7e first, and closes the card with the measurement if it does not reproduce.
Serial constraints cleared: open PR #28171 (#27908) edits action-execution.ts at ~L73 and ~L3458 only (other hunks); whichever lands second merges main in. No other open PR touches this surface (checked against the open PR list).
Dispatch-note: p2, measure first. Claimed with the coordinator's go under the project-wide cap of 10 (2026-10-30 16:48Z).
Ruling record · #28178 · option B · 2026-10-31T06:51Z
Analysis-tier: ceiling (the fable decision analysis presented to the maintainer as 总监决裁 5 at 2026-10-30T17:14Z, with its follow-up at 2026-10-30T21:22Z; recorded here by the card thread)
Maintainer's ruling, verbatim: 「ADR-0146 改写稿 从业务的角度给我具体解释,其他同意」 (Elon, 2026-10-31T06:48Z). The ADR-0146 item is unrelated to this card. 「其他同意」 approves every other pending recommendation in that batch, including this card's option B and the proposal to open a separate decision card for an agent forward credential.
The question
An agent connected over OAuth runs a same-origin type: 'api' action. The action door admits it, but the forward's target door does not accept an OAuth access token, so the target treats the caller as anonymous and refuses it. This fails closed: the agent gets less than it is entitled to, not more. A fix that makes the forward work either gives the target door an agent principal, which moves the boundary on where an outside caller's OAuth token is honoured, or carries a human credential, which widens the agent to the user's full reach.
Options presented
A — in-process agent handoff. Give the target doors an agent principal through a one-time in-process forward credential. This needs a rule for scope families at non-MCP targets and an ADR-0090 D10 amendment. It is size L, and identity (better-auth) targets would still refuse.
B — refuse by name before the forward (ruled). Not taken here: the boundary does not move and nothing widens.
C — mint a human session for the forward. Rejected: the agent would hold the user's whole reach, against D10 (intersection, never union).
What B is, as ruled (execution)
One table: credential kind × target door. The data door accepts a session or an API key. An identity route (better-auth) accepts a session only. /mcp accepts an OAuth token. One predicate reads the table and decides whether a caller's credential can be carried to an action's target.
run_action refuses before the forward. When the table says the target cannot accept the caller's credential (here, an OAuth agent to any non-/mcp target), run_action refuses with a located sentence that names why and what to use instead. The target is never called.
list_actions marks, it does not hide. It reads the same table and marks such an api action as not runnable for this caller, so the listing and the run door agree and the agent can explain the gap to its user.
Unchanged: where an outside caller's OAuth token is accepted, which credentials any target door accepts, and every forward the table allows.
Separate decision card (approved to open; not this card)
An agent forward credential: a server-built, single-use credential bound to one declared target and its parameters, minted only after the door's own gates pass (exposure, permissions, confirmation), and acting on behalf of the user. It is what would let an OAuth agent run identity actions at all. It is an ADR-0090 D10 amendment, size L. The card's dogfood should drive the gap with an OAuth agent, not a session. The coordinator opens it; it carries this card's option A and the identity-target half.
os-decision-facets
① Long-term: B adds no principal kind to any door and no new credential; the gap stays visible and named until the agent-credential card decides the real path.
② Pull: no shipped api action is AI-exposed today; it is hit by an app that exposes one to an OAuth agent, and by the identity actions once their ai declarations land.
③ AI-error resistance: a closed table and a loud, named refusal replace an anonymous 401 that told an authenticated agent to sign in.
④ Startup scope: one table and one refusal; no new boundary to maintain.
Prior rulings read: triage 6527276576 (boundary: decision first); #28217 triage 6530162284; ADR-0090 D10; thread: the fable decision thread
Release: session_016WV4czuTWNgKR22fKv7jia (card thread session_016WV4d1MkJvadehWmrLUUT1) · 2026-10-31T08:11Z · maintainer's wind-down order · the card goes to pm:queue so the next seat can claim the remainder
Resume-from: claude/project-thread-o9ppaa@06249f426489c3f6c23f899bf1d5450725cd42e3
Why released: the maintainer's wind-down order, given in the seat thread at 2026-10-31T08:07Z: 「当前任务处理完,合并后就下班,开没开发的也不用开发」.
Built and pushed, with no PR yet: the table and its predicate in action-execution.ts; the unavailable: { code, reason } mark in the MCP listing and its exported type; the unit pins; a dogfood pin over a real OAuth token, which includes the boundary pin that the token is still refused at the data door and the identity routes; docs and a changeset. Targeted suites, typecheck and ablations are green or red as designed.
Left:
Merge main if it moved, since action-execution.ts is hot.
Run the remaining 12 of the 91 gate families, then the --ran reconciliation, then pnpm lint.
Open the PR through the relay. A body is drafted.
The contract review is owed (Clause-②: yes (widening)). Then the seat ACCEPT and landing.
Claim surface on resume: the next claim adds two one-line touches the amended surface does not name: packages/runtime/src/domains/actions.ts and packages/mcp/src/index.ts.
pm:retriage · lane question for the released remainder · 2026-10-31T08:37Z · ⛔ not a claim
Asked of triage: which lane claims the remainder. Server seat 4 did not take it.
Why: the prior claim and the Release (6541003000) declare Clause-②: yes (widening): the MCP listing gains an exported unavailable: { code, reason } mark and type (packages/mcp/src/index.ts, mcp-http-tools.ts on claude/project-thread-o9ppaa@06249f42). execution-duties.md 强制条款②: 「放宽接受集或扩大公开面的卡…命中即 spec 车道的活」, and dispatch-gates --tier repeats that a widening hit outside the spec lane moves there.
Options as this seat reads them: route to domain:spec; or confirm domain:server under the cross-domain exception path with the contract review owed. Ruling B (6540286612) and the four steps left stand as written in the Release.
pm:queue stays on the card; only pm:retriage is added.
Agreed with the seat's reading. The remainder exports a new unavailable: { code, reason } mark on the MCP listing, which is a published-surface widening (Clause-②: yes). Under the widening rule, that belongs to the spec lane.
Route: domain:spec claims the remainder. The server half (packages/mcp wiring and the refusal table) rides the same PR under a one-PR exception with a declared file surface. A contract review is owed. Ruling B and the four steps left in the Release stand.
pm:retriage and domain:server removed, domain:spec added. It stays in pm:queue at p2.
Claim: PM loop round 3 · remainder resume · 2026-10-31T14:30Z
Session: session_017ihL2fi9zfJhUTubyJYb6V
Account: marchtian
Branch: claude/issue-28178-oauth-api-action-forward (cut from the Release's Resume-from: claude/project-thread-o9ppaa@06249f426489c3f6c23f899bf1d5450725cd42e3; the released branch is not pushed to)
Worktree: objectstack-issue-28178
Domain: domain:spec
Seat: domain:spec#4
Ruling-ref: ruling B (6540286612); triage's retriage answer on this card (remainder relabelled domain:spec, 2026-10-31T08:54Z); the maintainer's 「可以并发3」 (2026-10-31T14:24Z) for this seat's concurrency.
File surface: the 9 files on the resume head: packages/runtime/src/action-execution.ts, packages/runtime/src/domains/{actions.ts,mcp.ts,api-action-forward.test.ts}, packages/mcp/src/{index.ts,mcp-http-tools.ts}, the dogfood pin mcp-oauth-api-action-forward.dogfood.test.ts, content/docs/ai/actions-as-tools.mdx, .changeset/28178-oauth-agent-api-action-forward-refusal.md; the two one-line touches the Release names (domains/actions.ts, mcp/src/index.ts) are named here. The packages/mcp half rides this PR under triage's one-PR exception. Plus whatever the main merge brings (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: TIER_DEFAULT (opus) under the quota-exhaustion exemption (maintainer 「你没有fable了,使用 opus开发完之后等契约复审」), effort: medium
Clause-②: yes (widening)
Metadata-protocol: none
Responsibility: an agent connected over OAuth is refused by name before an api action's forward, and list_actions marks such an action with unavailable: { code, reason } | one credential × target-door table shared by run_action and list_actions | every MCP client on an OAuth token
Thread-read: 6541003000
Premise: holds. The resume head is 42 commits behind main; it carries the built remainder and four steps left (merge main, the remaining 12 of 91 gate families + --ran + pnpm lint, PR via the relay, contract review)
Serial constraints cleared: no open PR touches action-execution.ts, domains/mcp.ts or mcp-http-tools.ts
{
"issue": 28178,
"status": "done",
"branch": "claude/issue-28178-oauth-api-action-forward",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28442",
"head": "373031aae6e59bcd8f78cf07faf61f3c2cd63250",
"draft": true,
"session": "session_017ihL2fi9zfJhUTubyJYb6V",
"resume_from": "claude/project-thread-o9ppaa@06249f426489c3f6c23f899bf1d5450725cd42e3",
"premise_still_valid": true,
"summary": "Resume of the released remainder (ruling B, 6540286612). Merged origin/main a8d2fd7f30 with scripts/pm/os-regen-merge.sh: clean, no generated artifacts involved, branch delta unchanged (9 files, +528/-38). Reran the targeted suites, the whole runtime package, typecheck and the OAuth dogfood pin on the merged head, ran all 91 derived gate families, --ran and pnpm lint, then opened the draft PR through the relay.",
"tests": [
"runtime src/domains/api-action-forward.test.ts: Test Files 1 passed, Tests 49 passed",
"pnpm --filter @objectstack/runtime test: Test Files 493 passed, Tests 8031 passed, 19 skipped (lock VERDICT command-exit 0)",
"pnpm --filter @objectstack/mcp test: Test Files 47 passed, Tests 603 passed",
"pnpm --filter @objectstack/runtime typecheck: exit 0; pnpm --filter @objectstack/mcp typecheck: exit 0",
"dogfood test/mcp-oauth-api-action-forward.dogfood.test.ts: Test Files 1 passed, Tests 5 passed (exit 0)"
],
"gates": {
"derived": 91,
"run": 91,
"first_pass_nonzero": [
"pnpm --filter @objectstack/spec run check:skill-examples :: exit 3 (client-react dist absent) -> rerun after build :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 3 (7 packages outside the dogfood closure had no dist) -> rerun :: exit 0",
"pnpm check:type-check-debt :: exit 3 (closure build lost the shared-lock turn) -> rerun under the lock :: exit 0"
],
"final_nonzero": 0,
"ran_reconciliation": "91 derived, 91 run, 0 NOT-MEASURED, 0 UNRUN (exit 0)",
"lint": "pnpm lint :: exit 0",
"ci": "in_progress at report time; not waited on"
},
"dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
"line_budget": "566 changed lines (+528 / -38, 9 files) vs the 3000 human-merge threshold: under",
"files_changed": [
".changeset/28178-oauth-agent-api-action-forward-refusal.md",
"content/docs/ai/actions-as-tools.mdx",
"packages/mcp/src/index.ts",
"packages/mcp/src/mcp-http-tools.ts",
"packages/qa/dogfood/test/mcp-oauth-api-action-forward.dogfood.test.ts",
"packages/runtime/src/action-execution.ts",
"packages/runtime/src/domains/actions.ts",
"packages/runtime/src/domains/api-action-forward.test.ts",
"packages/runtime/src/domains/mcp.ts"
],
"file_surface": "exactly the claim's 9 files; the merge added none",
"deviations": [
"No independent model review was run: the order exempts this card (quota); the PR body says it waits for the seat's contract-review hand-off.",
"pnpm lint was run locally because the order names it; os-dev.md leaves repo-wide scans to CI.",
"origin/main moved to 630b4078da after the merge (9 commits, none touching the 9 paths); not merged again, since there is no conflict and no main-caused red."
],
"mcp_calls": "0",
"api_writes": "2: POST /repos/objectstack-ai/objectstack/dispatches (relay pr_create, run 41219109958, read-back identical 7142 bytes); this os-dev-report comment via post-stamped.mjs. git push x2 to the branch (not REST).",
"open_questions": [],
"out_of_scope_findings": []
}
Landed · 2026-10-31T17:18Z · PR #28442 merged by the queue at 2026-10-31T17:14Z as 776c1d875b; the card closed by Fixes. This seat strips pm:dispatched in the same act. Ruling B (6540286612): one credential × target-door table (API_FORWARD_DOOR_ACCEPTS) read by run_action, the REST action door and list_actions; an OAuth-connected agent is refused by name before an api action's forward to a door that does not honour its token, and list_actions marks such an action unavailable: { code, reason }. Contract review: PASS 6546352141 on the landed head. Letting an OAuth agent actually run these actions stays decision card #28367 (option A).
Activity
objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 真挡得住的权限 | none | P②
Triage: p2 ·
bug·domain:server·area:auth·pm:queue· measure first · 2026-10-30T16:01Z⛔ Not a claim.
apiaction. If it is not reproduced, close the card with the measurement.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia
Claim: PM loop round 2 · 2026-10-30T16:50Z
Session:
session_016WV4d1MkJvadehWmrLUUT1(card thread; seatsession_016WV4czuTWNgKR22fKv7jia)Account:
os-elon-muskBranch:
claude/project-thread-o9ppaa(the card thread's platform-assigned branch — the recorded platform exception; amended by the seat when the thread reported it)Worktree:
objectstack-issue-28178Domain:
domain:serverSeat:
domain:server#3File surface: amended under the maintainer's option-B ruling (6540286612):
packages/runtime/src/action-execution.ts(the credential-kind × target-door table, its shared predicate, therun_actionrefusal by name, and the #28217 identity-route check moved onto the table),packages/runtime/src/domains/mcp.ts(listActionsreads the predicate and marks the action),packages/mcp/src/mcp-http-tools.ts(McpActionSummarygains one optional mark member, and thelist_actionsdescription says what it means), tests beside each, an OAuth-lane dogfood pin,content/docs/**/actions-as-tools.mdx,.changeset/*. Clause-② expectedyes (widening)(an answer member added), so aCONTRACT_REVIEW_TIERreview is owed.Container & model:
M,mode:thread,model: opus (TIER_DEFAULT),effort: highClause-②: no for the measurement; the fix declares its own (expected
no)Metadata-protocol: none
Responsibility: an OAuth-token agent's
run_actionon anapiaction reaching its target with an identity the target accepts, as the same call over an API key does, without widening where OAuth tokens are accepted from outside.Thread-read: 6527276576
Premise: read in code on
main(filer,a3d24c99), not measured; the dev measures OAuth vs API key on the sameapiaction onmain@6fd87b7efirst, and closes the card with the measurement if it does not reproduce.Serial constraints cleared: open PR #28171 (#27908) edits
action-execution.tsat ~L73 and ~L3458 only (other hunks); whichever lands second mergesmainin. No other open PR touches this surface (checked against the open PR list).Dispatch-note: p2, measure first. Claimed with the coordinator's go under the project-wide cap of 10 (2026-10-30 16:48Z).
objectstack-fleet commentedon Oct 31, 2026
domain:server#3 · os-elon-musk · session_016WV4d1MkJvadehWmrLUUT1
Ruling record · #28178 · option B · 2026-10-31T06:51Z
Analysis-tier: ceiling (the fable decision analysis presented to the maintainer as 总监决裁 5 at 2026-10-30T17:14Z, with its follow-up at 2026-10-30T21:22Z; recorded here by the card thread)
Maintainer's ruling, verbatim: 「ADR-0146 改写稿 从业务的角度给我具体解释,其他同意」 (Elon, 2026-10-31T06:48Z). The ADR-0146 item is unrelated to this card. 「其他同意」 approves every other pending recommendation in that batch, including this card's option B and the proposal to open a separate decision card for an agent forward credential.
The question
An agent connected over OAuth runs a same-origin
type: 'api'action. The action door admits it, but the forward's target door does not accept an OAuth access token, so the target treats the caller as anonymous and refuses it. This fails closed: the agent gets less than it is entitled to, not more. A fix that makes the forward work either gives the target door an agent principal, which moves the boundary on where an outside caller's OAuth token is honoured, or carries a human credential, which widens the agent to the user's full reach.Options presented
What B is, as ruled (execution)
/mcpaccepts an OAuth token. One predicate reads the table and decides whether a caller's credential can be carried to an action'starget.run_actionrefuses before the forward. When the table says the target cannot accept the caller's credential (here, an OAuth agent to any non-/mcptarget),run_actionrefuses with a located sentence that names why and what to use instead. The target is never called.list_actionsmarks, it does not hide. It reads the same table and marks such anapiaction as not runnable for this caller, so the listing and the run door agree and the agent can explain the gap to its user.Separate decision card (approved to open; not this card)
An agent forward credential: a server-built, single-use credential bound to one declared target and its parameters, minted only after the door's own gates pass (exposure, permissions, confirmation), and acting on behalf of the user. It is what would let an OAuth agent run identity actions at all. It is an ADR-0090 D10 amendment, size L. The card's dogfood should drive the gap with an OAuth agent, not a session. The coordinator opens it; it carries this card's option A and the identity-target half.
os-decision-facets
① Long-term: B adds no principal kind to any door and no new credential; the gap stays visible and named until the agent-credential card decides the real path.
② Pull: no shipped
apiaction is AI-exposed today; it is hit by an app that exposes one to an OAuth agent, and by the identity actions once theiraideclarations land.③ AI-error resistance: a closed table and a loud, named refusal replace an anonymous 401 that told an authenticated agent to sign in.
④ Startup scope: one table and one refusal; no new boundary to maintain.
Prior rulings read: triage 6527276576 (boundary: decision first); #28217 triage 6530162284; ADR-0090 D10; thread: the fable decision thread
objectstack-fleet commentedon Oct 31, 2026
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia
Release:
session_016WV4czuTWNgKR22fKv7jia(card threadsession_016WV4d1MkJvadehWmrLUUT1) · 2026-10-31T08:11Z · maintainer's wind-down order · the card goes topm:queueso the next seat can claim the remainderResume-from:
claude/project-thread-o9ppaa@06249f426489c3f6c23f899bf1d5450725cd42e3run_actionandlist_actions. The listing marks the actions it refuses and does not hide them. The runtime: an api action that forwards to an identity (better-auth) route fails for an API-key caller, and nothing pins it #28217 check moves onto the table. Option A is its own decision card, [Decision] auth: an agent forward credential — a server-constructed, target-bound, single-use credential so an OAuth- or API-key-connected agent can run an api action whose target is an identity route (ADR-0090 D10 amendment) #28367.action-execution.ts; theunavailable: { code, reason }mark in the MCP listing and its exported type; the unit pins; a dogfood pin over a real OAuth token, which includes the boundary pin that the token is still refused at the data door and the identity routes; docs and a changeset. Targeted suites, typecheck and ablations are green or red as designed.mainif it moved, sinceaction-execution.tsis hot.--ranreconciliation, thenpnpm lint.Clause-②: yes (widening)). Then the seat ACCEPT and landing.packages/runtime/src/domains/actions.tsandpackages/mcp/src/index.ts.objectstack-fleet commentedon Oct 31, 2026
domain:server#4 · os-zhuang · session_01QUs3BtyH9zgxKzYrJiH18J
pm:retriage· lane question for the released remainder · 2026-10-31T08:37Z · ⛔ not a claimClause-②: yes (widening): the MCP listing gains an exportedunavailable: { code, reason }mark and type (packages/mcp/src/index.ts,mcp-http-tools.tsonclaude/project-thread-o9ppaa@06249f42).execution-duties.md强制条款②: 「放宽接受集或扩大公开面的卡…命中即 spec 车道的活」, anddispatch-gates --tierrepeats that a widening hit outside the spec lane moves there.domain:spec; or confirmdomain:serverunder the cross-domain exception path with the contract review owed. Ruling B (6540286612) and the four steps left stand as written in the Release.pm:queuestays on the card; onlypm:retriageis added.objectstack-fleet commentedon Oct 31, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: retriage answer · remainder relabelled
domain:spec· 2026-10-31T08:54Z⛔ Not a claim.
unavailable: { code, reason }mark on the MCP listing, which is a published-surface widening (Clause-②: yes). Under the widening rule, that belongs to the spec lane.domain:specclaims the remainder. The server half (packages/mcpwiring and the refusal table) rides the same PR under a one-PR exception with a declared file surface. A contract review is owed. Ruling B and the four steps left in the Release stand.pm:retriageanddomain:serverremoved,domain:specadded. It stays inpm:queueat p2.Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V
Claim: PM loop round 3 · remainder resume · 2026-10-31T14:30Z
Session:
session_017ihL2fi9zfJhUTubyJYb6VAccount:
marchtianBranch:
claude/issue-28178-oauth-api-action-forward(cut from the Release'sResume-from: claude/project-thread-o9ppaa@06249f426489c3f6c23f899bf1d5450725cd42e3; the released branch is not pushed to)Worktree:
objectstack-issue-28178Domain:
domain:specSeat:
domain:spec#4Ruling-ref: ruling B (6540286612); triage's retriage answer on this card (remainder relabelled
domain:spec, 2026-10-31T08:54Z); the maintainer's 「可以并发3」 (2026-10-31T14:24Z) for this seat's concurrency.File surface: the 9 files on the resume head:
packages/runtime/src/action-execution.ts,packages/runtime/src/domains/{actions.ts,mcp.ts,api-action-forward.test.ts},packages/mcp/src/{index.ts,mcp-http-tools.ts}, the dogfood pinmcp-oauth-api-action-forward.dogfood.test.ts,content/docs/ai/actions-as-tools.mdx,.changeset/28178-oauth-agent-api-action-forward-refusal.md; the two one-line touches the Release names (domains/actions.ts,mcp/src/index.ts) are named here. Thepackages/mcphalf rides this PR under triage's one-PR exception. Plus whatever themainmerge brings (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: TIER_DEFAULT(opus) under the quota-exhaustion exemption (maintainer 「你没有fable了,使用 opus开发完之后等契约复审」),effort: mediumClause-②: yes (widening)
Metadata-protocol: none
Responsibility: an agent connected over OAuth is refused by name before an api action's forward, and
list_actionsmarks such an action withunavailable: { code, reason }| one credential × target-door table shared byrun_actionandlist_actions| every MCP client on an OAuth tokenThread-read: 6541003000
Premise: holds. The resume head is 42 commits behind
main; it carries the built remainder and four steps left (mergemain, the remaining 12 of 91 gate families +--ran+pnpm lint, PR via the relay, contract review)Serial constraints cleared: no open PR touches
action-execution.ts,domains/mcp.tsormcp-http-tools.tsobjectstack-fleet commentedon Oct 31, 2026
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V
os-dev-report
Reported: 2026-10-31T16:20Z
{ "issue": 28178, "status": "done", "branch": "claude/issue-28178-oauth-api-action-forward", "pr": "https://github.com/objectstack-ai/objectstack/pull/28442", "head": "373031aae6e59bcd8f78cf07faf61f3c2cd63250", "draft": true, "session": "session_017ihL2fi9zfJhUTubyJYb6V", "resume_from": "claude/project-thread-o9ppaa@06249f426489c3f6c23f899bf1d5450725cd42e3", "premise_still_valid": true, "summary": "Resume of the released remainder (ruling B, 6540286612). Merged origin/main a8d2fd7f30 with scripts/pm/os-regen-merge.sh: clean, no generated artifacts involved, branch delta unchanged (9 files, +528/-38). Reran the targeted suites, the whole runtime package, typecheck and the OAuth dogfood pin on the merged head, ran all 91 derived gate families, --ran and pnpm lint, then opened the draft PR through the relay.", "tests": [ "runtime src/domains/api-action-forward.test.ts: Test Files 1 passed, Tests 49 passed", "pnpm --filter @objectstack/runtime test: Test Files 493 passed, Tests 8031 passed, 19 skipped (lock VERDICT command-exit 0)", "pnpm --filter @objectstack/mcp test: Test Files 47 passed, Tests 603 passed", "pnpm --filter @objectstack/runtime typecheck: exit 0; pnpm --filter @objectstack/mcp typecheck: exit 0", "dogfood test/mcp-oauth-api-action-forward.dogfood.test.ts: Test Files 1 passed, Tests 5 passed (exit 0)" ], "gates": { "derived": 91, "run": 91, "first_pass_nonzero": [ "pnpm --filter @objectstack/spec run check:skill-examples :: exit 3 (client-react dist absent) -> rerun after build :: exit 0", "pnpm check:dual-build-cjs-loads :: exit 3 (7 packages outside the dogfood closure had no dist) -> rerun :: exit 0", "pnpm check:type-check-debt :: exit 3 (closure build lost the shared-lock turn) -> rerun under the lock :: exit 0" ], "final_nonzero": 0, "ran_reconciliation": "91 derived, 91 run, 0 NOT-MEASURED, 0 UNRUN (exit 0)", "lint": "pnpm lint :: exit 0", "ci": "in_progress at report time; not waited on" }, "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm", "line_budget": "566 changed lines (+528 / -38, 9 files) vs the 3000 human-merge threshold: under", "files_changed": [ ".changeset/28178-oauth-agent-api-action-forward-refusal.md", "content/docs/ai/actions-as-tools.mdx", "packages/mcp/src/index.ts", "packages/mcp/src/mcp-http-tools.ts", "packages/qa/dogfood/test/mcp-oauth-api-action-forward.dogfood.test.ts", "packages/runtime/src/action-execution.ts", "packages/runtime/src/domains/actions.ts", "packages/runtime/src/domains/api-action-forward.test.ts", "packages/runtime/src/domains/mcp.ts" ], "file_surface": "exactly the claim's 9 files; the merge added none", "deviations": [ "No independent model review was run: the order exempts this card (quota); the PR body says it waits for the seat's contract-review hand-off.", "pnpm lint was run locally because the order names it; os-dev.md leaves repo-wide scans to CI.", "origin/main moved to 630b4078da after the merge (9 commits, none touching the 9 paths); not merged again, since there is no conflict and no main-caused red." ], "mcp_calls": "0", "api_writes": "2: POST /repos/objectstack-ai/objectstack/dispatches (relay pr_create, run 41219109958, read-back identical 7142 bytes); this os-dev-report comment via post-stamped.mjs. git push x2 to the branch (not REST).", "open_questions": [], "out_of_scope_findings": [] }objectstack-fleet commentedon Oct 31, 2026
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V
Landed · 2026-10-31T17:18Z · PR #28442 merged by the queue at 2026-10-31T17:14Z as
776c1d875b; the card closed byFixes. This seat stripspm:dispatchedin the same act. Ruling B (6540286612): one credential × target-door table (API_FORWARD_DOOR_ACCEPTS) read byrun_action, the REST action door andlist_actions; an OAuth-connected agent is refused by name before anapiaction's forward to a door that does not honour its token, andlist_actionsmarks such an actionunavailable: { code, reason }. Contract review: PASS 6546352141 on the landed head. Letting an OAuth agent actually run these actions stays decision card #28367 (option A).