Repository navigation
Description
domain:server#1 · os-steve · session_01ADHkmPF1skfu2kx89BEXZi
Filing gate: ④ decision node, a follow-up to #31739 (ADR-0151, AI service account). It is not a claim, and it names a question the seat cannot rule on.
Business reason: PR #31867 (#31739 PR-2) holds an AI service account to its explicit grants and never lets it run as the system (ADR-0151 D3). A flow whose author declared runAs: 'system' still runs as the system when an account triggers it, because the flow author granted that run to whoever may trigger the flow. ADR-0151's Non-goals list says "a flow or job runAs naming an account" is not decided there, and ADR-0073's automation principal stays the identity of user-less runs. Whether the trigger door should refuse an account in front of a system flow is therefore a narrowing beyond the ADR's declaration.
Reader: the maintainer, through the needs-user-decision label. The fix lands on server seat 1 if the answer is A.
Dedupe: no open or closed card asks whether a runAs system flow may be triggered by an agent account.
class: c · type: Decision
Grade: p2. Nothing is exposed today: no account can be registered before #31739 PR-3, and option B gives the account no power the flow author did not already give every member allowed to trigger the flow.
维护者速读
AI 服务账号(ADR-0151)只能用显式授予的权限,永远不以系统身份运行。但如果某个流程的作者把它声明为以系统身份运行(runAs: 'system'),账号触发它时,流程照样以系统身份跑。ADR-0151 把这件事明确列为"本记录不决定"。需要你定:是在触发入口拒绝账号(A),还是保持现状、把它当作流程作者的授权(B)。
Options
- A. Refuse at the trigger door. An account that triggers a flow declared
runAs: 'system'gets a refusal by name. This narrows every automation an account may trigger, so it needs an ADR-0151 amendment (or an O6 decision) and aClause-②: no (narrowing)changeset. - B. Keep today's behaviour (seat recommends). The flow author's
runAs: 'system'is the grant. The account is the trigger, not the acting identity, and the run now carriesprincipalKind: 'agent'on its trigger facts, so O2 and O6 (the ADR's named readers of the kind) can route or hold it later. ADR-0151 gets one sentence in its Non-goals saying so when it is next amended.
Seat analysis
B is safe and adds no power: a member who may trigger the flow already gets the same system run, and the account's own grants are clamped and refused at assignment by PR #31867. A also leaves a gap, because runAs naming a person (not the system) is the same shape and is equally undecided; ruling A alone would split the two. The PR #31867 contract review (issuecomment-6735749732, ③ flag 4a) reads the same. The seat recommends B.
Activity
objectstack-fleet commentedon Nov 12, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Brief-claimed-by: session_01AavokzJ5DndAwitDXvKy4U · batch #1 · 2026-11-12T01:58Z
Presented to the maintainer in the triage seat's session, in the standing decision-batch format (
decision-analysis.md): batch #1 = #32481, #29484, #31874, cloud#4315 and #32163. ⛔ Not a claim on the work.Generated by Claude Code
objectstack-fleet commentedon Nov 12, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Ruling: batch #1 item 4 · letter C (new) · maintainer 「同意」 2026-11-12T02:29Z
Ruled in the triage seat's session on 2026-11-12. The maintainer asked for items 4 and 5 of batch #1 to be re-evaluated independently at the ceiling tier and discussed. The re-evaluation proposed a new option. Triage presented it with code evidence, and the maintainer answered 「同意」.
ai.exposed: trueopt-in. One rule on every door that can start such a flow: the MCP door already applies it, and the REST trigger route, the REST action door and declarative endpoints get the same rule. No new spec key.origin/main.runAsissystem | useronly (packages/spec/src/automation/flow.zod.ts:1243), so there is no "run as a named person" case to keep consistent. And the "same power as a member" premise holds only whereai.exposedgates the door.domain:serverPR,Clause-②: no (narrowing). The same rule goes on the doors above, with a refusal by name that tells the author which opt-in to add. Pins and an ablation. ADR-0151's Non-goals gain one sentence. The system-run branch also carriesprincipalKindon its run facts (it does not today).needs-user-decisioncomes off, and the card goes topm:queue(p2,domain:server).Generated by Claude Code
objectstack-fleet commentedon Nov 12, 2026
domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J
Claim: PM loop round 1
Session:
session_016WV4cwqTK71kjQzHae8T5JAccount:
os-elon-muskBranch:
claude/issue-31874-ai-account-system-flowuntil the card thread opens; the card thread's designatedclaude/project-thread-*branch replaces it in a whole re-posted claim before the first pushWorktree:
objectstack-issue-31874Domain:
domain:serverSeat:
domain:server#8File surface:
packages/rest/src/**(the trigger route and the action door), the declarative-endpoint door,packages/services/service-automation/src/**(the system-run branch,principalKindon run facts),packages/mcp/src/**read-only (the door that already applies the rule, to share one rule), their tests,docs/adr/0151-ai-service-account-a-headless-agent-is-a-registered-principal-of-its-own.md(one Non-goals sentence, per the ruling),.changeset/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: claude-fable-5-2(MANDATORY:dispatch-gates.mjs --tierreportsdocs/adr/**⇢ fable,oneLineExit: false, effort floor xhigh),effort: xhighClause-②: no (narrowing)
Metadata-protocol: none
Responsibility:
the REST trigger route, REST action door and declarative endpoints (objectstack) start a runAs-system flow for an AI service account without the author opt-in | the MCP door already applies the ai.exposed opt-in | any registered AI service account (ADR-0151) triggering a flow declared runAs: systemThread-read: 6812617393
Premise: holds
Serial constraints cleared:
none: #32445 (platform-objects action + service-messaging) and #32163 (platform-objects identity actions) do not name the REST trigger route, the action door or service-automationobjectstack-fleet commentedon Nov 12, 2026
domain:server#8 · os-elon-musk · session_016WV4d1G1mMRC6sZXKcRqqu
Claim: PM loop round 1
Session:
session_016WV4d1G1mMRC6sZXKcRqquAccount:
os-elon-muskBranch:
claude/project-thread-mfe3hnWorktree:
objectstack-issue-31874Domain:
domain:serverSeat:
domain:server#8File surface:
packages/runtime/src/**(the trigger route, the action door, the declared-endpoint door andflow-start-admission.ts, the one rule module the three doors ask),packages/services/service-automation/src/**(the system-run branch,principalKindon run facts),packages/mcp/src/**read-only (the door that already applies the rule, to share one rule), their tests,docs/adr/0151-ai-service-account-a-headless-agent-is-a-registered-principal-of-its-own.md(one Non-goals sentence, per the ruling),.changeset/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: CONTRACT_REVIEW_TIER(MANDATORY:dispatch-gates.mjs --tierreportsdocs/adr/**as a mandated surface,oneLineExit: false, effort floor xhigh),effort: xhighClause-②: no (narrowing)
Metadata-protocol: none
Responsibility:
the REST trigger route, REST action door and declarative endpoints (objectstack) start a runAs-system flow for an AI service account without the author opt-in | the MCP door already applies the ai.exposed opt-in | any registered AI service account (ADR-0151) triggering a flow declared runAs: systemThread-read: 6836684353
Premise: corrected — the three doors live in
packages/runtime/src/**, notpackages/rest/src/**(measured on c04aa29); the seat approved this file surface in the card threadSerial constraints cleared:
none: #32445 (platform-objects action + service-messaging) and #32163 (platform-objects identity actions) do not name the REST trigger route, the action door or service-automationThis claim replaces 6836684353 whole: the card thread's designated branch and session, and the corrected file surface.
objectstack-fleet commentedon Nov 12, 2026
domain:server#8 · os-elon-musk · session_016WV4d1G1mMRC6sZXKcRqqu
os-dev-report
{ "issue": 31874, "status": "done", "branch": "claude/project-thread-mfe3hn", "pr": "https://github.com/objectstack-ai/objectstack/pull/32912", "session": "session_016WV4d1G1mMRC6sZXKcRqqu", "premise_still_valid": true, "summary": "Ruling record 6812617393 (letter C) executed. Flow start admission (packages/runtime/src/flow-start-admission.ts#flowStartRefusal) gains one rule, asked by the three doors that start a flow by name: an AI service account starting a runAs:'system' flow is refused 403 PERMISSION_DENIED unless the action that starts it carries the author's ai.exposed:true opt-in; the trigger routes and a declared type:'flow' endpoint have no action in hand, so the account is refused there and the refusal names the action to declare. The MCP exposure gate and the new rule read the opt-in through one predicate (actionIsAiExposed). The elevated run an admitted account starts keeps principalKind:'agent' on its run facts (service-automation resolveRunDataContext system arm, seedRunGrants). ADR-0151 Non-goals gain exactly one sentence (Tier H). Premise correction, approved by the seat before the first push: the doors live in packages/runtime/src, not packages/rest/src (measured on c04aa295). The claim was re-posted whole as comment 6836939154; its `Container & model` line carries the tier constant name CONTRACT_REVIEW_TIER where 6836684353 carried a model id, because dispatch-gates' claim reader asks for the constant name. origin/main merged once before opening (5c0f113498). Where the ruling's 'the flow (or the action that starts it)' leaves a fork, reading A is implemented as the seat agreed; A/B is the first open question.", "tests": "Head 183ed607592e8bd2fcd31327778c85cc07c30380. `pnpm --filter @objectstack/runtime test`: Test Files 588 passed (588), Tests 10287 passed | 19 skipped (10306), lock VERDICT command-exit 0 (on the head's parent 20959acac0). `pnpm --filter @objectstack/service-automation test`: Test Files 278 passed (278), Tests 3811 passed (3811), VERDICT command-exit 0 (on 20959acac0). On the head: `pnpm --filter @objectstack/runtime typecheck` exit 0 (source + test layer), `pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/flow-start-required-permissions.test.ts` 81 passed (81; 45 on main), `pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2 src/run-agent-account-kind.test.ts` 9 passed (9; 5 on main). `pnpm --filter @objectstack/service-automation typecheck` exit 0 on 20959acac0. Ablations (fix committed first; `node scripts/ablation-replace.mjs`, write and restore proven on disk; both suites resolve the mutated files from src, so no build sits between mutation and reading): 1 trigger routes state aiExposed true: 4 failed | 77 passed; 2 declared endpoint states aiExposed true: 4 failed | 77 passed; 3 action door states aiExposed true whatever the action declares: 5 failed | 76 passed; 4 rule 2 removed: 11 failed | 70 passed; 5 system arm drops the kind: 1 failed | 8 passed; 6 run setup deletes the kind on a system run: 1 failed | 8 passed. Each restore line reads `ok restored: blob == HEAD … and git diff HEAD is empty`. Ablations ran on 60bea520dd; the mutated files are byte-identical at the head.", "gates": "Local, on the head 183ed60759: `node scripts/pm/dispatch-gates.mjs --commands` derived 82 commands; each was run and recorded as `CMD :: exit N` before any pipe; all 82 exit 0. `node scripts/pm/dispatch-gates.mjs --ran FILE`: `Run reconciliation — 82 derived, 82 run, 0 NOT-MEASURED, 0 UNRUN.` and `✓ dispatch-gates --ran: 82 derived famil(ies) accounted for — 82 run, 0 NOT-MEASURED`. `pnpm lint` exit 0 on the head (run because the order names it). First pass, on 20959acac0: 81 exit 0 and one red, `pnpm check:test-source-alias` (the edited runtime pin file reaches a dynamic import of @objectstack/metadata in the source under test); fixed with the gate's prescribed module-top import in 183ed60759, then the whole list was run again on the head. CI: the PR's check-runs on the head were in_progress when this report was written (24 listed: 18 in_progress, 5 skipped, 1 success); none awaited, none re-run.", "dogfood_battery": "183ed607592e8bd2fcd31327778c85cc07c30380 · os-verify-lock: VERDICT command-exit 0 · held the lock 1717s · Test Files 456 passed | 3 skipped (459) · Tests 3831 passed | 42 skipped (3873) · no failure; one run, no shard, no path, after the whole-tree build (120 of 120 tasks)", "mcp_calls": "0 — no GitHub MCP call; the card and its comments were read over the public REST API (unauthenticated GET)", "api_writes": "3 — all through the fleet-write relay: comment on #31874 (the whole re-posted claim, 6836939154); pr_create (PR #32912, draft, assignee os-elon-musk, no labels); comment on #31874 (this report). Three git pushes of claude/project-thread-mfe3hn, no other branch.", "open_questions": [ { "question": "At the two doors that start a flow by name with no action in hand (the trigger route and a declared type:'flow' endpoint), may an AI service account start a runAs:'system' flow when an action that targets the flow carries ai.exposed:true? The ruling admits the start 'only when the flow (or the action that starts it) carries' the opt-in; a flow and an endpoint declare no AI opt-in and the ruling adds no key.", "options": [ "A (implemented; the seat agreed 2026-11-12T21:50Z) — the opt-in counts only at the door where that action is run. What an operator sees: an AI service account that calls a flow's trigger URL or a custom endpoint the author declared, for a flow that runs as the system, always gets 403 with the remedy in the message; so does an account that runs a flow action without the opt-in. What the author does: declare a type:'flow' action targeting the flow with ai: { exposed: true, description } (or add that block to the action that already starts it), and point the account at POST /api/v1/actions/OBJECT/ACTION or the MCP run_action tool. The action's own guards (its requiredPermissions, its record load in the caller's scope, and on MCP the confirmation) then stand between the account and the elevated run. Starts that stop working: only an AI service account's by-name starts of runAs:'system' flows. Measured reach: hotcrm declares no elevated flow of a type that can be started by name (every runAs:'system' flow there is record_change, schedule or autolaunched, already closed to every caller but the system), so nothing in hotcrm moves; in this repo's examples two showcase flows change their answer for an account (showcase_inquiry_purge behind POST /api/v1/apps/showcase/inquiries/purge, and showcase_inbound_task_webhook at the trigger route), and no test or example starts them as an account. Unchanged: flows that run as the user, persons, OAuth agents, the HMAC inbound hook route. Cost: none beyond this PR.", "B — also admit the by-name start when at least one ai.exposed type:'flow' action targets the flow. What an operator sees: once the author has marked one such action, the account keeps calling the trigger URL or the custom endpoint; no caller change. What it costs: the account then reaches the elevated flow without passing the guards the author put on that action (its requiredPermissions, its record scope, the MCP confirmation), so an opt-in given for 'run this action' also opens two doors that ask none of them; every by-name start by an account needs a lookup over the declared actions and a rule for an action name two packages hold; and it is a derivation the ruling does not spell. Size M, a follow-up card with a contract review; the doors already hand the rule one fact, so only where that fact comes from would change.", "C — an opt-in key on the flow or on the endpoint: closed by the ruling's 'No new spec key'." ], "recommendation": "A. It admits nothing the ruling does not say, stays one rule with no lookup, and keeps the author's opt-in attached to the action whose guards the author wrote. Its whole cost falls on AI service accounts that call a trigger URL or a declared endpoint for a system-identity flow, and the 403 tells the author the exact declaration to add." }, { "question": "Should the REST actions route ask an AI service account for the action's ai.requiresConfirmation when the action starts an elevated flow? The MCP run_action tool asks it of every exposed action; the REST route asks it of no caller, a standing boundary written in action-execution.ts ('Widening the set is its own decision').", "options": [ "A (this PR) — leave the boundary: the new rule asks the opt-in alone. An account on REST runs an exposed action that declares requiresConfirmation without the confirm step, as every REST caller does today.", "B — ask it of AI service accounts on REST: a second narrowing on its own card (S), which must also say whether it covers every action an account runs on REST or only those that start an elevated flow; accounts that run such actions over REST would have to send the confirmation." ], "recommendation": "A for this PR. B is a decision about AI accounts on REST in general, not about this ruling." }, { "question": "One further item on what counts as a start door for this rule is with the seat, in the card thread; no detail on the card, per the order.", "options": [ "with the seat" ], "recommendation": "the seat routes it" } ], "security_findings": "withheld", "out_of_scope_findings": [ "carrier: none · noted, not filed — spec prose: packages/spec/src/ai/tool.zod.ts describes the REST actions route as outside the ai.exposed opt-in; after this PR that is imprecise for one pair (an AI service account starting a runAs:'system' flow). Prose only, no key or accept set moves; spec lane. dedupe words: tool.zod ai.exposed REST actions prose account", "carrier: none · noted, not filed — docs follow-up named on the PR's Skills-docs line: content/docs/automation/flows.mdx (start-refusal table), content/docs/ui/actions.mdx (door posture table), content/docs/ai/agents.mdx (AI service account paragraph); content/docs is outside the claimed file surface. dedupe words: flows.mdx refusal table AI service account runAs system", "carrier: none · noted, not filed — a run that starts on its own trigger (record change, schedule) carries no principalKind on a system run: the trigger hands the run a user id only and a system run resolves no grants. Unchanged by this PR, which carries the kind a door forwards. dedupe words: record_change schedule runAs system principalKind attribution" ], "deviations": [ "The order asks for `pnpm lint`; os-dev.md calls the repo-wide lint a CI-owned run. Followed the order: run once per head, exit 0.", "The two package suites and the service-automation typecheck ran on the head's parent (20959acac0). The head adds one module-top import line to one runtime test file; the runtime typecheck, both pin files, lint, the 82 gates and the dogfood battery were run on the head.", "A first dogfood run on 20959acac0 was stopped after about 7 minutes when the alias gate's red required a new head; the one complete run is on the head.", "`test:repo` (the vitest repo project) was not run locally: CI's.", "Skills-docs is `follow-up owed`: content/docs is outside the claimed file surface. The three pages are named on the PR's line.", "No scripts/adr-anchors entry was added (outside the claimed surface); the ADR sentence carries a path#symbol anchor that check:adr-symbol-anchors resolves." ] }objectstack-fleet commentedon Nov 12, 2026
domain:server#8 · os-elon-musk · session_016WV4d1G1mMRC6sZXKcRqqu
Claim: PM loop round 1
Session:
session_016WV4d1G1mMRC6sZXKcRqquAccount:
os-elon-muskBranch:
claude/project-thread-mfe3hnWorktree:
objectstack-issue-31874Domain:
domain:serverSeat:
domain:server#8File surface:
packages/runtime/src/**(the trigger route, the action door, the declared-endpoint door andflow-start-admission.ts, the one rule module the three doors ask),packages/services/service-automation/src/**(the system-run branch,principalKindon run facts),packages/mcp/src/**read-only (the door that already applies the rule, to share one rule), their tests,docs/adr/0151-ai-service-account-a-headless-agent-is-a-registered-principal-of-its-own.md(one Non-goals sentence, per the ruling),content/docs/automation/flows.mdx,content/docs/ui/actions.mdxandcontent/docs/ai/agents.mdx(the refusal and posture rows that teach the rule as implemented, prose only),.changeset/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: CONTRACT_REVIEW_TIER(MANDATORY:dispatch-gates.mjs --tierreportsdocs/adr/**as a mandated surface,oneLineExit: false, effort floor xhigh),effort: xhighClause-②: no (narrowing)
Metadata-protocol: none
Responsibility:
the REST trigger route, REST action door and declarative endpoints (objectstack) start a runAs-system flow for an AI service account without the author opt-in | the MCP door already applies the ai.exposed opt-in | any registered AI service account (ADR-0151) triggering a flow declared runAs: systemThread-read: 6838841457
Premise: corrected — the three doors live in
packages/runtime/src/**, notpackages/rest/src/**(measured on c04aa29); the seat approved this file surface in the card threadSerial constraints cleared:
none: #32445 (platform-objects action + service-messaging) and #32163 (platform-objects identity actions) do not name the REST trigger route, the action door or service-automationThis claim replaces 6836939154 whole: the file surface gains the three
content/docspages, so the Skills-docs rows land in PR #32912 instead of a follow-up; nothing else moves.objectstack-fleet commentedon Nov 13, 2026
domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J
Maintainer ruling on #31874's by-name doors · letter A · 2026-11-13T00:59Z
The maintainer (os-elon-musk) answered the seat's decision card in the project thread, verbatim: 「A:只认动作上的 AI 授权」 (to the question 「AI 账号启动系统身份流程,按 A 还是 B 放行?」).
Meaning: an AI service account starts a
runAs: 'system'flow only through atype: 'flow'action that declaresai.exposed: true; the trigger routes and a declaredtype: 'flow'endpoint refuse it403 PERMISSION_DENIED. This is what PR #32912 implements and what its ADR-0151 Non-goals sentence states; no change to that PR follows. Reading B (an AI-enabled action opening the by-name doors) is not adopted.