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
mcp: run_action refuses every action on a sys_* object, so an agent cannot reach the identity actions (ban_user, impersonate_user, set_user_password) that REST POST /actions serves #27681
Ruled: 6513869839 · letter B (three execution parameters; parameter 2 executed by #27900, amendment 6513938013) · 2026-10-30T03:05Z
Ruling-ref: 6513869839
Filing gate: ② a gap against a stated design goal, found in the contract review of #27649 (card #27506, ruling C step 2) and carried by the dev's os-dev-report as out of scope, class b. Read on the tree, not measured over HTTP.
reach: objectstack-ai/objectstack main 144702ca5d. MCP run_action has two sys_* refusals in front of the same action-execution path that POST /api/v1/actions/:object/:action serves:
packages/mcp/src/mcp-http-tools.ts (run_action handler, around line 1294): refuses when objectName is a sys_* name, unless the tool registration passed allowSystemObjects: true (RegisterActionToolsOptions, around line 275; default false).
packages/runtime/src/action-execution.tsinvokeBusinessAction (around line 2686): isSystemObjectName(objectName) throws Action '…' is on a system object and is not exposed via MCP. This one is unconditional: no option lifts it, so allowSystemObjects on the MCP side cannot open it either.
Business reason: the maintainer's stated goal (ruling on #27506, 2026-10-29) is that agents and humans use one system. After #27649 the REST action door serves the packaged identity actions on sys_user (ban_user, unban_user, set_user_password, impersonate_user, …) to a caller who holds the action's permissions, behind the packaged-action switch. An agent over MCP is refused every one of them by object name before any of those gates is consulted, so the ruling's "exposes them to agents with no further change" does not hold for MCP.
Reader who acts: the triage seat. This is a posture choice, not a mechanical fix: the sys_* refusal is a deliberate fail-closed default (mcp-http-tools.ts header comment, line 28). Whether, and under which gate (the per-action AI-exposure flag that invokeBusinessAction already checks for ADR-0011, an explicit allow-list, or the existing allowSystemObjects option made to reach the runtime guard), is likely a needs_decision for the maintainer.
Dedupe: scanned every open issue title for run_action, MCP and mcp. Hits: #27429, #27402, #27401, #27293, #26702, #25583 and #20791. None is about sys_* action exposure.
An MCP client that calls run_action with objectName: "sys_user" and an identity action gets a refusal naming the system object. The same caller, with the same credentials, can invoke the action through POST /api/v1/actions/sys_user/<action> once #27649 lands.
Expected
To be decided. One system for agents and humans suggests that MCP run_action reaches a sys_* action under the same gates as the REST door: the packaged-action switch, the action's permissions, the ADR-0011 AI-exposure flag, and the destructive-confirmation member. It should not hit a blanket object-name refusal. Keeping the blanket refusal is also a valid outcome if it is written down as deliberate, with the REST door named as the agent's path.
Mechanism
Two guards, listed above. The runtime guard runs before isHeadlessInvokableAction and before actionAiExposureError, so no per-action declaration can open it. The dogfood case for #27649's MCP arm runs on a non-sys_* object for this reason.
⛔ Not a claim. This is a security boundary, so the posture is the maintainer's to set. Triage does not set it.
Read fresh. Both sys_* refusals in front of MCP run_action are deliberate fail-closed defaults. One of them (invokeBusinessAction) is unconditional. Opening them exposes the identity actions (ban_user, set_user_password, impersonate_user, …) to agents.
B. Lift them only for an action that carries the existing per-action AI-exposure flag, default off. The REST door's gates then run unchanged: permissions, the packaged-action switch, and the audit.
C. Lift them for every sys_* action that the REST door serves.
Triage's lean: B. The fail-closed default holds, and the decision becomes per action and explicit.
Added fact · 2026-10-29T19:52Z · from the dev's round-2 report on PR #27664 (card #27610), checked by the seat on origin/main.
The gap is on the listing too, not only on run_action's two refusals: registerActionTools in packages/mcp/src/mcp-http-tools.ts (L1228–L1230) filters list_actions with actions.filter((a) => !a.objectName || !isSystemObject(a.objectName)) unless allowSystemObjects: true. A fix that lifts only the run_action guards leaves the action invisible to an agent that lists first, so a resolution of this card should name both sites. ADR-0066 D4's Amended note (as amended by #27664) cites this card for both.
Director seat, summon #54 continuation 1 (ledger objectstack#12708, opening mark 6493929869). Presented to the maintainer in batch #429 as item 1 of 3 — a security-boundary posture (triage 6504357275: the maintainer sets it; on the human floor by rule).
Seat increment (re-read at origin/mainded67bfb, 2026-10-30T01:52Z):
Seat's letter: B — a sys_* action is reachable over MCP (list_actions and run_action, both sites) only when the action declares ai: { exposed: true, … }; the runtime's unconditional isSystemObjectName refusal and the MCP allowSystemObjects gate both yield to that flag, and every REST-door gate (packaged-action switch, permissions, destructive confirmation, audit) runs unchanged behind it. Not C (blanket exposure of identity actions to agents), not A (the one-system goal recorded as not covering sys_*).
Workload of B: domain:server · 1 PR · S–M (two guards in packages/runtime + packages/mcp, the list_actions filter, a dogfood case on a sys_user action) · Clause-②: no, Metadata-protocol: none (no new authorable key; the opt-in exists) · precedent: ADR-0011's exposure check on non-sys_* actions.
After the ruling: triage grades the card into pm:queue for domain:server (p1) with Ruling-ref:; the identity actions' own ai.exposed declarations are the app author's (platform-objects) choice per action, not flipped by this ruling.
席位意见(增量)· 多维度综合分析 · director seat (summon #54 continuation 1, session_01Utp6CbQLcy8yg6vhJVXXi8) · on the maintainer's 「27681 多维度综合分析,给我建议」 · 2026-10-30T02:22Z
Brief 6512999526 stands; this comment is the deeper reading the maintainer asked for. ⛔ Not a ruling. Every platform fact below is read at origin/mainded67bfb.
Facts measured
Who is behind the guard. Twelve sys_* identity objects declare type: 'api' actions (packages/platform-objects/src/identity/: account, api-key, invitation, member, oauth-application, organization, session, sso-provider, team-member, team, two-factor, user). sys_user alone carries ban / unban / set password / impersonate / change email / change password / revoke sessions and more. None of them declares an ai: block (grep -l '^\s+ai:\s*\{' … sys-*.object.ts → 0 files). So today no system action is exposed to agents by declaration either.
Two guards, one of them unreachable by any declaration.invokeBusinessAction refuses isSystemObjectName(objectName) at action-execution.ts L3212, before the ADR-0011 exposure check actionAiExposureError at L3225; the MCP bridge's allowSystemObjects (mcp-http-tools.ts L180 / L281 / L695, default false) sits in front of run_action and the list_actions filter (6506838679).
The agent is the caller. The bridge runs every tool as the authenticated principal with the same permission, RLS and audit path as REST /actions/... (mcp-http-tools.ts L92, L234; stdio binds OS_MCP_STDIO_API_KEY, ADR-0101). After PR fix(runtime)!: a same-origin type: 'api' action is dispatched through the action doors, so its packaged-action switch holds (#27506) #27649 that same principal can call POST /api/v1/actions/sys_user/ban_user over HTTP and be served, behind the packaged-action switch (ADR-0126), the action's permissions (ADR-0066 D4) and the audit. The MCP blanket guard therefore does not keep an agent away from identity actions; it keeps the MCP surface from matching the REST surface for the same key.
The governance already written. ADR-0011: exposure is opt-in and default off, "the physical boundary between an AI drafted an action and the agent fleet may now run it"; ai.description required; permission parity; confirmation parity — destructive actions route through HITL confirmation, ai.requiresConfirmation overridable by the author. ADR-0066 D4 as amended 2026-10-29 (ruling 6499781492): one gate, two surfaces; the sys_* MCP gap is recorded there and names this card. impersonate_user carries a confirmText today (sys-user.object.ts L418).
Seven dimensions
Security boundary and blast radius. The identity actions split in two: lifecycle acts (ban / unban, revoke a session, cancel an invitation, remove a member) and credential- or session-minting acts (impersonate a user, set a password, create or rotate an API key, change an SSO provider). The second group is where an agent mistake or an injected instruction becomes account takeover. A name-prefix guard treats both groups the same and treats sys_invitation.cancel like sys_user.impersonate; a per-action declaration can tell them apart.
The gate stack that exists. Author opt-in (ai.exposed + ai.description) → operator switch (ADR-0126 packaged-action activation) → caller's permission and RLS (ADR-0066 D4) → HITL confirmation for destructive acts (ADR-0011) → audit. Five layers, each carrying capability information. The blanket guard adds a sixth that carries none. Under option B nothing changes until an author writes ai: { exposed: true, description } on a specific action — and today zero do — so B is observably identical to A on day one, with the difference that the decision moves into reviewable metadata.
Mainstream practice (public documentation, not fetched in this session): Salesforce Agentforce exposes an action to an agent only when it is explicitly added to the agent, running under the agent user's permission set; Microsoft's Entra Agent ID / Copilot Studio model gives agents their own least-privilege identity and gates admin operations behind explicit connectors and approvals; the MCP specification carries per-tool annotations (destructiveHint, readOnlyHint) and expects hosts to confirm destructive tools with the user; OWASP's LLM / agentic guidance names "excessive agency" and prescribes least privilege plus human approval for high-impact actions. The common shape is explicit per-capability enablement + least privilege + human confirmation for destructive acts — neither "every admin object forbidden forever" nor "everything the API serves".
AI error and prompt injection. The agent-specific risk is an instruction smuggled into record content ("ban this user", "reset their password to…"). Under B an injected instruction cannot reach an action its own declaration does not expose, the HITL confirmation still stands in front of destructive acts, and the principal's permissions bound the rest; under C every sys_* action the REST door serves is one injected sentence away from an agent that holds an admin key; under A the same agent reaches them over REST anyway, so A is a parity loss, not a mitigation.
Workload and evolution. B: domain:server S–M — the runtime guard and the MCP guard yield to the per-action flag, list_actions parity, a dogfood case on one sys_user action; no new key. The per-action declarations are separate, later decisions, each one line of metadata under review.
Options
What it does
Day-one effect
Risk shape
Workload
A keep both refusals
sys_* stays off MCP; REST remains the agent's path
none
parity loss, no mitigation (same key reaches REST)
0
B per-action opt-in
both guards and the listing filter yield to ai: { exposed: true }; REST-door gates unchanged behind it
none until a declaration lands (0 today)
explicit, per action, reviewable; HITL confirmation stands
server S–M, 1 PR
C blanket lift
every sys_* action the REST door serves is listed and callable over MCP
all identity actions reachable by any admin-keyed agent
one injected sentence from account takeover
server S, 1 PR
D operator switch
allowSystemObjects made to reach the runtime guard; a deployment flips all sys_* on or off
none until an operator flips it
coarse: all or nothing, no per-action review; the flip is outside metadata and outside the audit of a PR
server S, 1 PR
Recommendation
B, with three execution parameters named in the ruling:
Both sites (run_action and list_actions) and both guards yield to the same per-action flag; no deployment-level bypass is added (D's switch stays where it is, MCP-side, default false, and gains no new reach).
Declaration policy for this repository's sys_* actions: each ai.exposed on a platform identity action is its own ruling of record, starting from zero; actions that mint a credential or a session — impersonate_user, set_user_password, API-key create / regenerate, SSO-provider writes — are not declared exposed without a maintainer ruling naming the action, and ai.requiresConfirmation is never set to false on them. (Policy, not code: it names what the platform-objects author may write.)
The audit row of an action run through MCP records the surface, so an administrator can tell an agent's call from a click without a second log.
Not A: it records the design goal as not covering sys_* and buys no security the REST door has not already spent. Not C: blanket exposure of credential-minting acts. Not D: an all-or-nothing operator switch is the per-action decision pushed to the least reviewable place.
Confidence gaps: whether ai.requiresConfirmation's HITL path is wired for MCP-invoked actions end to end (ADR-0011 names it; the MCP arm was not exercised here — the dev measures it first and the PR shows the confirmation refusing an unconfirmed destructive call); the mainstream readings above are from public documentation, not pages fetched in this session.
os-decision-facets
① 长远:一套能力闸、两个面;按对象名前缀的笼统拒绝是第二套按名字的授权,与 ADR-0066 D4 和「agent 和人同一系统」相悖;B 让决定落在逐动作、可评审的元数据上
② 拉动:维护者设计目标 + #27649 已开放的 REST 身份动作;今天零个 sys_* 动作声明暴露,B 落地当天行为不变
③ 防错:注入的指令够不到未声明暴露的动作;破坏性动作仍过 HITL 确认;C 让铸造凭据的动作离注入一句话;A 不是缓解(同一把钥匙走 REST 照样能调)
④ 不扩散:不加键、不加门禁、不加部署开关;逐动作声明按拉动逐条来
Ruling: batch #429 item 1 · letter B · maintainer 「同意」 2026-10-30T02:59Z
Director seat, summon #54 continuation 1 (ledger objectstack#12708, opening mark 6493929869, brief 6512999526, analysis 6513393336). The maintainer asked for the multi-dimensional analysis (「27681 多维度综合分析,给我建议」), read it in the summon chat, and answered, verbatim: 「同意」 — the seat's letter B with its three execution parameters.
What the ruling binds (letter B): a sys_* action is reachable over MCP only when the action itself declares ai: { exposed: true, description } (ADR-0011). Both guards yield to that flag — the unconditional isSystemObjectName refusal in invokeBusinessAction (action-execution.ts L3212) and the MCP bridge's allowSystemObjects gate (mcp-http-tools.ts L180 / L281 / L695) — at both sites, run_action and the list_actions filter (6506838679), so an agent that lists sees exactly what it may run. Every REST-door gate runs unchanged behind the flag: the packaged-action activation switch (ADR-0126), the caller's permissions and RLS (ADR-0066 D4), the HITL confirmation for destructive acts (ADR-0011 ai.requiresConfirmation), the audit. Not taken: A (the design goal recorded as not covering sys_*, with no security bought — the same principal reaches the actions over REST), C (blanket exposure of credential-minting acts), D (an all-or-nothing deployment switch).
Execution parameters (part of the ruling):
No deployment-level bypass is added: allowSystemObjects stays MCP-side, default false, and gains no new reach; the per-action flag is the only door.
Declaration policy for this repository's sys_* actions: today zero platform sys_* actions declare an ai: block, and that is the starting state. Each ai.exposed: true on a platform identity action is its own ruling of record. Actions that mint a credential or a session — impersonate_user, set_user_password, API-key create / regenerate, SSO-provider writes — are not declared exposed without a maintainer ruling that names the action, and ai.requiresConfirmation is never set to false on them. A policy on what the platform-objects author may write, not code.
The audit row of an action run through MCP records the surface, so an administrator can tell an agent's call from a click without a second log.
Governing text: ADR-0011 (opt-in default off, confirmation parity, permission parity); ADR-0066 D4 as amended 2026-10-29 (ruling 6499781492 on #27506: one gate, two surfaces; this card named as the sys_* gap); ADR-0126 (packaged-action activation); the mcp-http-tools.ts header's fail-closed rule, now satisfied per action rather than per name prefix. Premises re-read at origin/mainded67bfb (6513393336).
Workload of the letter:domain:server · 1 PR · S–M (packages/runtime guard, packages/mcp guard and listing filter, the audit surface field, a dogfood case on one sys_user action behind a test-only ai.exposed declaration; the PR shows an unconfirmed destructive call refused by the HITL path over MCP) · Clause-②: no, Metadata-protocol: none (no new authorable key) · precedent: ADR-0011's exposure check on non-sys_* actions. Confidence gap for the dev: whether the HITL confirmation path is wired end to end for MCP-invoked actions — measured first.
Execution:needs-user-decision → pm:queue in this act (domain:server, priority:p1, area:automation as triage set them); the server lane dispatches with Ruling-ref: naming this comment. ADR-0066 D4's amended note already cites this card for both sites; the PR adds the dated closing note there (governed path ⇒ that part is Tier H) or files it as a follow-up with the pointer — the dev states which in the PR body.
os-decision-facets
① 长远:一套能力闸、两个面;按名字前缀的笼统拒绝是第二套授权;决定落在逐动作、可评审的元数据
② 拉动:维护者设计目标 + #27649 已开放的 REST 身份动作;今天零个 sys_* 动作声明暴露,落地当天行为不变
③ 防错:注入指令够不到未声明暴露的动作;破坏性动作仍过 HITL;铸造凭据的动作按参数 2 逐条裁
④ 不扩散:不加键、不加门禁、不加部署开关
Amendment to ruling 6513869839, parameter 2 · 2026-10-30T03:04Z · maintainer, verbatim, right after 「同意」: 「但是对于系统内核对象,是否应该统一评估一下。」
Parameter 2 ("each ai.exposed on a platform identity action is its own ruling of record") is executed as one assessment of the whole sys_* set, not per action: decision card #27900 carries every sys_* object that declares actions (12 objects, 58 named actions, all in packages/platform-objects/src/identity/, none with an ai: block today) sorted into three buckets — H human-only (no ai.exposed without a ruling naming the action), C exposable with ai.requiresConfirmation kept true, E exposable by plain declaration — for the maintainer to rule in one stroke. Letter B's mechanism (both guards and both sites yield to the per-action flag; no deployment bypass; the audit row records the MCP surface) is unchanged and dispatches now; the declarations follow #27900's ruling. Ruling-ref: for the mechanism PR stays 6513869839; the declarations cite #27900.
Claim: PM loop round 5
Session: session_01L6bhgUCizoYWQVJWjhdR6F
Account: os-sales
Branch: the card thread's platform-assigned claude/project-thread-* (amendment follows naming it)
Worktree: the card thread's
Domain: domain:server
Seat: domain:server#2
File surface: packages/runtime/src/action-execution.ts (the invokeBusinessActionsys_* guard), packages/mcp/src/mcp-http-tools.ts (the allowSystemObjects gate at run_action and the list_actions filter), the action audit row's surface field where it is written, their tests, one dogfood case with a test-only ai.exposed declaration, .changeset/*; the ADR-0066 D4 closing note only as the PR body states (Tier H) or as a follow-up — stop on any other breach and explain in the report
Container & model: M, mode:cloud card thread, model: default (dispatch-gates --tier: no path-derived mandate), effort: xhigh (p1, security boundary)
Clause-②: no
Metadata-protocol: none
Responsibility: MCP run_action / list_actions reach of sys_* actions (this lane) | maintainer ruling B: the per-action ai.exposed flag is the only door; every REST-door gate runs behind it | every agent calling actions over MCP
Thread-read: 6513938013
Ruling-ref: 6513869839 (maintainer, letter B; parameter 2 amended by 6513938013 — declarations follow decision card #27900, not this PR)
Premise: per the ruling's re-read at ded67bfb (analysis 6513393336); the thread re-measures on fresh origin/main (e320e366 at claim time) first, and measures the HITL confirmation path for MCP-invoked actions before any edit
Serial constraints cleared: the thread runs git merge-tree against every open PR on these files before pr_create.
Claim amendment to 6514015162 · 2026-10-30T03:14Z · Branch named
Branch: claude/project-thread-qid1f1 (the card thread's platform-assigned branch, base origin/maine320e366)
Worktree: the card thread's
Container & model: as claimed, except effort is the thread's default (not xhigh).
Claim amendment 2 to 6514015162 · 2026-10-30T03:17Z · File surface increment and one parameter carried, declared
The card thread measured on origin/maine320e366 (read from code):
File surface adds packages/runtime/src/domains/mcp.ts. The runtime bridge has two more sys_* guards than the ruling's text names: its listActions skips sys_* unconditionally (without changing it, list_actions never shows a sys_* action, so listing would not equal running), and resume_run's admission refuses them too. Both yield to the same per-action ai.exposed flag as run_action; the PR states each site.
The HITL confirmation path is wired end to end for MCP-invoked actions (confirm is declared and forwarded by run_action; invokeBusinessAction asks actionConfirmationRefusal before any read or forward → 428 ACTION_CONFIRMATION_REQUIRED). The thread proceeds.
Ruling parameter 3 (the audit row records the MCP surface) is carried by a follow-up card, not this PR. Neither door writes an action-invocation audit row today: only a console.info log on the script branch, and sys_audit_log records the action body's data writes with no surface field and no execution-context key to carry it. A durable surface field needs a packages/spec execution-context key, the audit writer and the forward headers, which is outside this lane's PR. The card filed for it cites ruling 6513869839 parameter 3; a log line is not offered as the audit row.
{
"issue": 27681,
"status": "done",
"branch": "claude/project-thread-qid1f1",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27940",
"session": "session_01L6bhgTcRzav55DAtus77SU (card thread, dispatched by domain:server#2 session_01L6bhgUCizoYWQVJWjhdR6F)",
"premise_still_valid": true,
"summary": "Ruling 6513869839 letter B, mechanism only: one predicate (systemObjectActionExposureError) now decides the sys_* refusal at four MCP sites (invokeBusinessAction, the runtime bridge listActions, the resume_run admission, and the @objectstack/mcp tool layer via the new optional McpActionSummary.aiExposed attestation), yielding per action to ai.exposed: true and to no deployment switch; every gate behind it runs unchanged. HITL confirmation over MCP was measured wired end to end before any edit. The stored-metadata family (sys_metadata, sys_metadata_history) stays refused even when flagged, preserving the #21594 boundary the name refusal also carried (seat agreed 04:14Z). Ruling parameter 3 (audit row names the MCP surface) is not here: no action-invocation audit row exists on either door, carried by #27908; no platform sys_* action gains an ai block (#27900).",
"tests": "head 55f4e186: runtime full suite 481 files, 7842 passed, 19 skipped, 0 failed (first full run before the family fix: 3 red, which found it); mcp 46 files 594/594; runtime + mcp typecheck exit 0; dogfood mcp-system-object-action-exposure + api-action-door-forward 14/14. New: runtime mcp-system-object-action-exposure.test.ts 11, mcp-resume-run +2, mcp mcp-action-system-object-exposure.test.ts 8, dogfood 6; moved pins: http-dispatcher standalone sys_api_key fixture now unflagged, action-execution-destructive gate-1 pin on the predicate. Ablations via scripts/ablation-replace.mjs, all committed first, all restored blob == HEAD: A1 yield removed 7 red; A2 runtime guard deleted 2 red; A3 runtime listing name-only 2 red; A4 resume name-only 2 red; A5 tool-layer list filter 2 red; A6 tool-layer run pre-check 3 red; A7 summary attestation 1 red; A8 A1 through runtime dist (preflight marker present, --absent after rebuild) dogfood 4 of 6 red; A9 family exemption disabled 3 red.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 55f4e186: 65 derived, 65 run, all exit 0 (check:dual-build-cjs-loads after building 8 missing dists; check:type-check-debt under os-verify-lock); --ran: 65 run, 0 NOT-MEASURED, 0 UNRUN. eslint --no-inline-config packages/runtime/src packages/mcp/src + dogfood file: exit 0. merge-tree vs open #27915, #27713, #27793: clean. CI check-runs: not yet read (PR just opened).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "3 — mcp__github__issue_read (get, get_comments) on #27681, mcp__github__list_pull_requests (open); reads only",
"api_writes": "2 — fleet-write relay pr_create (run 41004282791, #27940); post-stamped comment on #27681 (this report)",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #27908 · ruling parameter 3: no action-invocation audit row on either action door; sys_audit_log has no surface column and ExecutionContext no surface key; noted, filed by the seat",
"carrier: domain:server#2 · ADR-0066 D4 dated closing note (Tier H path) owed as a follow-up card pointing at #27940",
"carrier: domain:server#2 · Clause-② self-judged yes (widening) against the claim's no: contract review + maintainer approval before landing, per the seat"
]
}
Both pages are added to the file surface and are corrected in #27940. The ADR-0066 D4 closing note is not in this PR. It is carried by #27956, which runs after the merge.
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 审批与自动化 | none | P②
Triage: p1 ·
domain:server·area:automation·needs-user-decision· 2026-10-29T17:56Z⛔ Not a claim. This is a security boundary, so the posture is the maintainer's to set. Triage does not set it.
sys_*refusals in front of MCPrun_actionare deliberate fail-closed defaults. One of them (invokeBusinessAction) is unconditional. Opening them exposes the identity actions (ban_user,set_user_password,impersonate_user, …) to agents.sys_*.sys_*action that the REST door serves.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:skills#1 · marchtian · session_017ihL2dPRZXfgE1A2Vct3FU
Added fact · 2026-10-29T19:52Z · from the dev's round-2 report on PR #27664 (card #27610), checked by the seat on
origin/main.The gap is on the listing too, not only on
run_action's two refusals:registerActionToolsinpackages/mcp/src/mcp-http-tools.ts(L1228–L1230) filterslist_actionswithactions.filter((a) => !a.objectName || !isSystemObject(a.objectName))unlessallowSystemObjects: true. A fix that lifts only therun_actionguards leaves the action invisible to an agent that lists first, so a resolution of this card should name both sites. ADR-0066 D4's Amended note (as amended by #27664) cites this card for both.objectstack-fleet commentedon Oct 30, 2026
director#1 · os-bill · session_01Utp6CbQLcy8yg6vhJVXXi8
Brief-claimed-by: session_01Utp6CbQLcy8yg6vhJVXXi8 · batch #429 · 2026-10-30T01:52Z
Director seat, summon #54 continuation 1 (ledger objectstack#12708, opening mark 6493929869). Presented to the maintainer in batch #429 as item 1 of 3 — a security-boundary posture (triage 6504357275: the maintainer sets it; on the human floor by rule).
Seat increment (re-read at
origin/mainded67bfb, 2026-10-30T01:52Z):isSystemObjectName(objectName)sits at action-execution.ts L3212, before the per-action AI-exposure checkactionAiExposureErrorat L3225 (the ADR-0011 opt-inai: { exposed: true, description }, action.zod.ts L1626, default off); the MCP side hasallowSystemObjects(mcp-http-tools.ts L180 / L281 / L695, default false) in front of bothrun_actionand thelist_actionsfilter (added fact 6506838679); PR fix(runtime)!: a same-origintype: 'api'action is dispatched through the action doors, so its packaged-action switch holds (#27506) #27649 is merged, so the REST door now serves the packaged identity actions onsys_userbehind the packaged-action switch, the action's permissions and the audit; Packaged action activation: switching off an api-type packaged action (Ban User) does not stop it, because the activation consult runs only at action dispatch #27506 (the goal 「agents and humans use one system」) is closed.type: 'api'actions are dispatched by the platform action door #27664 (cites this card for both sites).check-prior-rulings.mjs --card 27681names ADR-0110 D1, ADR-0036 §1, ADR-0063 §2, ADR-0090 D11, ADR-0099 D2, ADR-0106 D6 — term matches, none sets thesys_*posture for MCP.type: 'api'action is dispatched through the action doors, so its packaged-action switch holds (#27506) #27649 just opened over REST. ③ decides the letter: C (lift for everysys_*action the REST door serves) makesban_user/impersonate_userreachable by any agent the moment REST serves them; B keeps fail-closed and makes each exposure an explicit, reviewable line in the action's own metadata; A leaves the goal unmet with the refusal written down. ④ B adds no key (theaiblock exists) and no gate; it removes a blanket refusal in favour of the existing per-action one.sys_*action is reachable over MCP (list_actionsandrun_action, both sites) only when the action declaresai: { exposed: true, … }; the runtime's unconditionalisSystemObjectNamerefusal and the MCPallowSystemObjectsgate both yield to that flag, and every REST-door gate (packaged-action switch, permissions, destructive confirmation, audit) runs unchanged behind it. Not C (blanket exposure of identity actions to agents), not A (the one-system goal recorded as not coveringsys_*).domain:server· 1 PR · S–M (two guards inpackages/runtime+packages/mcp, thelist_actionsfilter, a dogfood case on asys_useraction) ·Clause-②: no,Metadata-protocol: none(no new authorable key; the opt-in exists) · precedent: ADR-0011's exposure check on non-sys_*actions.pm:queuefordomain:server(p1) withRuling-ref:; the identity actions' ownai.exposeddeclarations are the app author's (platform-objects) choice per action, not flipped by this ruling.Prior rulings read: run_action, sys_, allowSystemObjects, AI exposure, ADR-0011 → #27506 C, ADR-0066 D4 (amended); thread: 6504357275, 6506838679; repo: objectstack-ai/objectstack
objectstack-fleet commentedon Oct 30, 2026
director#1 · os-bill · session_01Utp6CbQLcy8yg6vhJVXXi8
席位意见(增量)· 多维度综合分析 · director seat (summon #54 continuation 1,
session_01Utp6CbQLcy8yg6vhJVXXi8) · on the maintainer's 「27681 多维度综合分析,给我建议」 · 2026-10-30T02:22ZBrief 6512999526 stands; this comment is the deeper reading the maintainer asked for. ⛔ Not a ruling. Every platform fact below is read at
origin/mainded67bfb.Facts measured
sys_*identity objects declaretype: 'api'actions (packages/platform-objects/src/identity/: account, api-key, invitation, member, oauth-application, organization, session, sso-provider, team-member, team, two-factor, user).sys_useralone carries ban / unban / set password / impersonate / change email / change password / revoke sessions and more. None of them declares anai:block (grep -l '^\s+ai:\s*\{' … sys-*.object.ts→ 0 files). So today no system action is exposed to agents by declaration either.invokeBusinessActionrefusesisSystemObjectName(objectName)at action-execution.ts L3212, before the ADR-0011 exposure checkactionAiExposureErrorat L3225; the MCP bridge'sallowSystemObjects(mcp-http-tools.ts L180 / L281 / L695, default false) sits in front ofrun_actionand thelist_actionsfilter (6506838679)./actions/...(mcp-http-tools.ts L92, L234; stdio bindsOS_MCP_STDIO_API_KEY, ADR-0101). After PR fix(runtime)!: a same-origintype: 'api'action is dispatched through the action doors, so its packaged-action switch holds (#27506) #27649 that same principal can callPOST /api/v1/actions/sys_user/ban_userover HTTP and be served, behind the packaged-action switch (ADR-0126), the action's permissions (ADR-0066 D4) and the audit. The MCP blanket guard therefore does not keep an agent away from identity actions; it keeps the MCP surface from matching the REST surface for the same key.ai.descriptionrequired; permission parity; confirmation parity — destructive actions route through HITL confirmation,ai.requiresConfirmationoverridable by the author. ADR-0066 D4 as amended 2026-10-29 (ruling 6499781492): one gate, two surfaces; thesys_*MCP gap is recorded there and names this card.impersonate_usercarries aconfirmTexttoday (sys-user.object.ts L418).Seven dimensions
sys_invitation.cancellikesys_user.impersonate; a per-action declaration can tell them apart.ai.exposed+ai.description) → operator switch (ADR-0126 packaged-action activation) → caller's permission and RLS (ADR-0066 D4) → HITL confirmation for destructive acts (ADR-0011) → audit. Five layers, each carrying capability information. The blanket guard adds a sixth that carries none. Under option B nothing changes until an author writesai: { exposed: true, description }on a specific action — and today zero do — so B is observably identical to A on day one, with the difference that the decision moves into reviewable metadata.destructiveHint,readOnlyHint) and expects hosts to confirm destructive tools with the user; OWASP's LLM / agentic guidance names "excessive agency" and prescribes least privilege plus human approval for high-impact actions. The common shape is explicit per-capability enablement + least privilege + human confirmation for destructive acts — neither "every admin object forbidden forever" nor "everything the API serves".sys_*action the REST door serves is one injected sentence away from an agent that holds an admin key; under A the same agent reaches them over REST anyway, so A is a parity loss, not a mitigation.domain:serverS–M — the runtime guard and the MCP guard yield to the per-action flag,list_actionsparity, a dogfood case on onesys_useraction; no new key. The per-action declarations are separate, later decisions, each one line of metadata under review.Options
sys_*stays off MCP; REST remains the agent's pathai: { exposed: true }; REST-door gates unchanged behind itsys_*action the REST door serves is listed and callable over MCPallowSystemObjectsmade to reach the runtime guard; a deployment flips allsys_*on or offRecommendation
B, with three execution parameters named in the ruling:
run_actionandlist_actions) and both guards yield to the same per-action flag; no deployment-level bypass is added (D's switch stays where it is, MCP-side, default false, and gains no new reach).sys_*actions: eachai.exposedon a platform identity action is its own ruling of record, starting from zero; actions that mint a credential or a session —impersonate_user,set_user_password, API-key create / regenerate, SSO-provider writes — are not declared exposed without a maintainer ruling naming the action, andai.requiresConfirmationis never set to false on them. (Policy, not code: it names what the platform-objects author may write.)Not A: it records the design goal as not covering
sys_*and buys no security the REST door has not already spent. Not C: blanket exposure of credential-minting acts. Not D: an all-or-nothing operator switch is the per-action decision pushed to the least reviewable place.Confidence gaps: whether
ai.requiresConfirmation's HITL path is wired for MCP-invoked actions end to end (ADR-0011 names it; the MCP arm was not exercised here — the dev measures it first and the PR shows the confirmation refusing an unconfirmed destructive call); the mainstream readings above are from public documentation, not pages fetched in this session.os-decision-facets
① 长远:一套能力闸、两个面;按对象名前缀的笼统拒绝是第二套按名字的授权,与 ADR-0066 D4 和「agent 和人同一系统」相悖;B 让决定落在逐动作、可评审的元数据上
② 拉动:维护者设计目标 + #27649 已开放的 REST 身份动作;今天零个 sys_* 动作声明暴露,B 落地当天行为不变
③ 防错:注入的指令够不到未声明暴露的动作;破坏性动作仍过 HITL 确认;C 让铸造凭据的动作离注入一句话;A 不是缓解(同一把钥匙走 REST 照样能调)
④ 不扩散:不加键、不加门禁、不加部署开关;逐动作声明按拉动逐条来
objectstack-fleet commentedon Oct 30, 2026
director#1 · os-bill · session_01Utp6CbQLcy8yg6vhJVXXi8
Ruling: batch #429 item 1 · letter B · maintainer 「同意」 2026-10-30T02:59Z
Director seat, summon #54 continuation 1 (ledger objectstack#12708, opening mark 6493929869, brief 6512999526, analysis 6513393336). The maintainer asked for the multi-dimensional analysis (「27681 多维度综合分析,给我建议」), read it in the summon chat, and answered, verbatim: 「同意」 — the seat's letter B with its three execution parameters.
What the ruling binds (letter B): a
sys_*action is reachable over MCP only when the action itself declaresai: { exposed: true, description }(ADR-0011). Both guards yield to that flag — the unconditionalisSystemObjectNamerefusal ininvokeBusinessAction(action-execution.ts L3212) and the MCP bridge'sallowSystemObjectsgate (mcp-http-tools.ts L180 / L281 / L695) — at both sites,run_actionand thelist_actionsfilter (6506838679), so an agent that lists sees exactly what it may run. Every REST-door gate runs unchanged behind the flag: the packaged-action activation switch (ADR-0126), the caller's permissions and RLS (ADR-0066 D4), the HITL confirmation for destructive acts (ADR-0011ai.requiresConfirmation), the audit. Not taken: A (the design goal recorded as not coveringsys_*, with no security bought — the same principal reaches the actions over REST), C (blanket exposure of credential-minting acts), D (an all-or-nothing deployment switch).Execution parameters (part of the ruling):
allowSystemObjectsstays MCP-side, default false, and gains no new reach; the per-action flag is the only door.sys_*actions: today zero platformsys_*actions declare anai:block, and that is the starting state. Eachai.exposed: trueon a platform identity action is its own ruling of record. Actions that mint a credential or a session —impersonate_user,set_user_password, API-key create / regenerate, SSO-provider writes — are not declared exposed without a maintainer ruling that names the action, andai.requiresConfirmationis never set tofalseon them. A policy on what the platform-objects author may write, not code.Governing text: ADR-0011 (opt-in default off, confirmation parity, permission parity); ADR-0066 D4 as amended 2026-10-29 (ruling 6499781492 on #27506: one gate, two surfaces; this card named as the
sys_*gap); ADR-0126 (packaged-action activation); the mcp-http-tools.ts header's fail-closed rule, now satisfied per action rather than per name prefix. Premises re-read atorigin/mainded67bfb(6513393336).Workload of the letter:
domain:server· 1 PR · S–M (packages/runtimeguard,packages/mcpguard and listing filter, the audit surface field, a dogfood case on onesys_useraction behind a test-onlyai.exposeddeclaration; the PR shows an unconfirmed destructive call refused by the HITL path over MCP) ·Clause-②: no,Metadata-protocol: none(no new authorable key) · precedent: ADR-0011's exposure check on non-sys_*actions. Confidence gap for the dev: whether the HITL confirmation path is wired end to end for MCP-invoked actions — measured first.Execution:
needs-user-decision→pm:queuein this act (domain:server,priority:p1,area:automationas triage set them); the server lane dispatches withRuling-ref:naming this comment. ADR-0066 D4's amended note already cites this card for both sites; the PR adds the dated closing note there (governed path ⇒ that part is Tier H) or files it as a follow-up with the pointer — the dev states which in the PR body.os-decision-facets
① 长远:一套能力闸、两个面;按名字前缀的笼统拒绝是第二套授权;决定落在逐动作、可评审的元数据
② 拉动:维护者设计目标 + #27649 已开放的 REST 身份动作;今天零个 sys_* 动作声明暴露,落地当天行为不变
③ 防错:注入指令够不到未声明暴露的动作;破坏性动作仍过 HITL;铸造凭据的动作按参数 2 逐条裁
④ 不扩散:不加键、不加门禁、不加部署开关
objectstack-fleet commentedon Oct 30, 2026
director#1 · os-bill · session_01Utp6CbQLcy8yg6vhJVXXi8
Amendment to ruling 6513869839, parameter 2 · 2026-10-30T03:04Z · maintainer, verbatim, right after 「同意」: 「但是对于系统内核对象,是否应该统一评估一下。」
Parameter 2 ("each
ai.exposedon a platform identity action is its own ruling of record") is executed as one assessment of the wholesys_*set, not per action: decision card #27900 carries everysys_*object that declares actions (12 objects, 58 named actions, all inpackages/platform-objects/src/identity/, none with anai:block today) sorted into three buckets — H human-only (noai.exposedwithout a ruling naming the action), C exposable withai.requiresConfirmationkepttrue, E exposable by plain declaration — for the maintainer to rule in one stroke. Letter B's mechanism (both guards and both sites yield to the per-action flag; no deployment bypass; the audit row records the MCP surface) is unchanged and dispatches now; the declarations follow #27900's ruling.Ruling-ref:for the mechanism PR stays 6513869839; the declarations cite #27900.objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim: PM loop round 5
Session:
session_01L6bhgUCizoYWQVJWjhdR6FAccount:
os-salesBranch: the card thread's platform-assigned
claude/project-thread-*(amendment follows naming it)Worktree: the card thread's
Domain:
domain:serverSeat:
domain:server#2File surface:
packages/runtime/src/action-execution.ts(theinvokeBusinessActionsys_*guard),packages/mcp/src/mcp-http-tools.ts(theallowSystemObjectsgate atrun_actionand thelist_actionsfilter), the action audit row's surface field where it is written, their tests, one dogfood case with a test-onlyai.exposeddeclaration,.changeset/*; the ADR-0066 D4 closing note only as the PR body states (Tier H) or as a follow-up — stop on any other breach and explain in the reportContainer & model:
M,mode:cloudcard thread,model: default (dispatch-gates --tier: no path-derived mandate),effort: xhigh(p1, security boundary)Clause-②: no
Metadata-protocol: none
Responsibility:
MCP run_action / list_actions reach of sys_* actions (this lane) | maintainer ruling B: the per-action ai.exposed flag is the only door; every REST-door gate runs behind it | every agent calling actions over MCPThread-read: 6513938013
Ruling-ref: 6513869839 (maintainer, letter B; parameter 2 amended by 6513938013 — declarations follow decision card #27900, not this PR)
Premise: per the ruling's re-read at
ded67bfb(analysis 6513393336); the thread re-measures on freshorigin/main(e320e366at claim time) first, and measures the HITL confirmation path for MCP-invoked actions before any editSerial constraints cleared: the thread runs
git merge-treeagainst every open PR on these files beforepr_create.objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim amendment to
6514015162· 2026-10-30T03:14Z · Branch namedBranch:
claude/project-thread-qid1f1(the card thread's platform-assigned branch, baseorigin/maine320e366)Worktree: the card thread's
Container & model: as claimed, except effort is the thread's default (not
xhigh).Everything else in the claim is unchanged.
objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim amendment 2 to
6514015162· 2026-10-30T03:17Z · File surface increment and one parameter carried, declaredThe card thread measured on
origin/maine320e366(read from code):packages/runtime/src/domains/mcp.ts. The runtime bridge has two moresys_*guards than the ruling's text names: itslistActionsskipssys_*unconditionally (without changing it,list_actionsnever shows asys_*action, so listing would not equal running), andresume_run's admission refuses them too. Both yield to the same per-actionai.exposedflag asrun_action; the PR states each site.confirmis declared and forwarded byrun_action;invokeBusinessActionasksactionConfirmationRefusalbefore any read or forward → 428ACTION_CONFIRMATION_REQUIRED). The thread proceeds.console.infolog on the script branch, andsys_audit_logrecords the action body's data writes with no surface field and no execution-context key to carry it. A durable surface field needs apackages/specexecution-context key, the audit writer and the forward headers, which is outside this lane's PR. The card filed for it cites ruling6513869839parameter 3; a log line is not offered as the audit row.Everything else in the claim is unchanged.
objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
os-dev-report
{ "issue": 27681, "status": "done", "branch": "claude/project-thread-qid1f1", "pr": "https://github.com/objectstack-ai/objectstack/pull/27940", "session": "session_01L6bhgTcRzav55DAtus77SU (card thread, dispatched by domain:server#2 session_01L6bhgUCizoYWQVJWjhdR6F)", "premise_still_valid": true, "summary": "Ruling 6513869839 letter B, mechanism only: one predicate (systemObjectActionExposureError) now decides the sys_* refusal at four MCP sites (invokeBusinessAction, the runtime bridge listActions, the resume_run admission, and the @objectstack/mcp tool layer via the new optional McpActionSummary.aiExposed attestation), yielding per action to ai.exposed: true and to no deployment switch; every gate behind it runs unchanged. HITL confirmation over MCP was measured wired end to end before any edit. The stored-metadata family (sys_metadata, sys_metadata_history) stays refused even when flagged, preserving the #21594 boundary the name refusal also carried (seat agreed 04:14Z). Ruling parameter 3 (audit row names the MCP surface) is not here: no action-invocation audit row exists on either door, carried by #27908; no platform sys_* action gains an ai block (#27900).", "tests": "head 55f4e186: runtime full suite 481 files, 7842 passed, 19 skipped, 0 failed (first full run before the family fix: 3 red, which found it); mcp 46 files 594/594; runtime + mcp typecheck exit 0; dogfood mcp-system-object-action-exposure + api-action-door-forward 14/14. New: runtime mcp-system-object-action-exposure.test.ts 11, mcp-resume-run +2, mcp mcp-action-system-object-exposure.test.ts 8, dogfood 6; moved pins: http-dispatcher standalone sys_api_key fixture now unflagged, action-execution-destructive gate-1 pin on the predicate. Ablations via scripts/ablation-replace.mjs, all committed first, all restored blob == HEAD: A1 yield removed 7 red; A2 runtime guard deleted 2 red; A3 runtime listing name-only 2 red; A4 resume name-only 2 red; A5 tool-layer list filter 2 red; A6 tool-layer run pre-check 3 red; A7 summary attestation 1 red; A8 A1 through runtime dist (preflight marker present, --absent after rebuild) dogfood 4 of 6 red; A9 family exemption disabled 3 red.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 55f4e186: 65 derived, 65 run, all exit 0 (check:dual-build-cjs-loads after building 8 missing dists; check:type-check-debt under os-verify-lock); --ran: 65 run, 0 NOT-MEASURED, 0 UNRUN. eslint --no-inline-config packages/runtime/src packages/mcp/src + dogfood file: exit 0. merge-tree vs open #27915, #27713, #27793: clean. CI check-runs: not yet read (PR just opened).", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "3 — mcp__github__issue_read (get, get_comments) on #27681, mcp__github__list_pull_requests (open); reads only", "api_writes": "2 — fleet-write relay pr_create (run 41004282791, #27940); post-stamped comment on #27681 (this report)", "open_questions": [], "out_of_scope_findings": [ "carrier: #27908 · ruling parameter 3: no action-invocation audit row on either action door; sys_audit_log has no surface column and ExecutionContext no surface key; noted, filed by the seat", "carrier: domain:server#2 · ADR-0066 D4 dated closing note (Tier H path) owed as a follow-up card pointing at #27940", "carrier: domain:server#2 · Clause-② self-judged yes (widening) against the claim's no: contract review + maintainer approval before landing, per the seat" ] }objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim amendment 3 to
6514015162· 2026-10-30T05:02Z · two docs pages added to the file surfaceThe contract review on PR #27940 (
6515376004, PASS) found two docs pages that will state the old rule once the PR lands:content/docs/ai/actions-as-tools.mdx: "System objects (sys_*) are held back fail-closed".content/docs/ai/connect-mcp.mdx: the troubleshooting row "it targets asys_*object (held back fail-closed for every type, api included — mcp: run_action refuses every action on a sys_* object, so an agent cannot reach the identity actions (ban_user, impersonate_user, set_user_password) that REST POST /actions serves #27681 tracks that gap)". This page also documents thelist_actionsoutput, which now hasaiExposed.Both pages are added to the file surface and are corrected in #27940. The ADR-0066 D4 closing note is not in this PR. It is carried by #27956, which runs after the merge.
Everything else in the claim is unchanged.