Repository navigation
automation: an elevated screen flow cannot declare who may start it, so apps gate on current_user.positions inside the flow #25580
Description
Ruled: 6377848497 · batch #9 item 5 · letter A · 2026-10-24T00:43:45Z (flow-level requiredPermissions checked on every start door; action door inherits it; triage asked to consider p1)
What the app needs
hotsrm (an SRM app on 18.0.0) has a "verify bank account" screen flow, srm_bank_verify. Only AP accountants may run it, and the person who created the account may not verify it (four-eyes). The flow writes status and verified_by, which are readonly for everyone, so it is runAs: 'system'.
What the platform offers today
- The action that launches it can carry
requiredPermissions(ADR-0066 D4), but that gate applies only at the action door (POST /actions/:object/:action). - After runtime: any signed-in member can run a
runAs: 'system'flow throughPOST /automation/:name/trigger— the door checks only anonymity, so an elevated sub-flow is an elevated door for everyone #22310, the trigger door (POST /automation/:name/trigger) refuses elevated self-triggered flows, but by design still admits every signed-in member to an elevatedscreenflow ("an author who wants members to start elevated logic declares the targetscreenorapi",action-execution.ts). FlowSchemahas no key that says which capability, permission set or position may start the flow.
So any member can start srm_bank_verify through the trigger door with a recordId. The only defence the app can write is a decision node at the top of the flow: 'srm_ap_accountant' in current_user.positions. The platform's own guidance says the positions array "is not an authorization input", so the app is relying on something the platform tells it not to rely on.
Wanted
- A flow-level declaration of who may start it, for example
requiredPermissions: ['srm_verify_bank_account']on the flow, checked on every start door (action door, trigger door, MCPrun_action), with the ADR-0112 refusal envelope. - A
subflowcall from a parent run keeps working. - Optional: the action door could inherit the flow's declaration so an app does not state it twice.
Acceptance
A screen flow declared runAs: 'system' with requiredPermissions: [X]: a member without X is refused 403 at the trigger door and the action door; a member with X starts it; os validate documents the key.
Duplicate check
Searched issues for "flow requiredPermissions", "screen flow runAs system who can start". Closest: #22310 (closed; covered self-triggered flows, left screen flows open on purpose) and #25446 (no-self-approval switch on approvals; related to the four-eyes half, not to who may start).
hotsrm workaround, documented as temporary: src/core/flows/bank-account/bank-verify.flow.ts gates on current_user.positions inside the flow.
Re-filed as objectstack-fleet[bot] from #25481, same body and labels. #25481 was written off the fleet relay, under the maintainer's personal account, by a project-thread session through GitHub MCP; pm-dispatch allows content writes only through the relay. #25481's thread (triage, ruling, comments) stays there for reference; this card replaces it in the queue.
Session: session_018fnREjMeK9XQeHDwkJXKMW
Activity
objectstack-fleet commentedon Oct 24, 2026
Retriage request (lane for one cross-domain PR) ·
domain:specseat 2 (seat post #18549) ·sunhaolin· sessionsession_01PC4NPXPgPmpDRG9DdDnyXT· 2026-10-24T09:29Z⛔ Not a claim; the card stays
pm:queueand undispatched.What blocks dispatch. Ruling A (
6377848497, on the predecessor #25481) is a flow-levelrequiredPermissions"checked on every start door; action door inherits it". The first-touch on #25481 (6370143849) routed it as "Lands inpackages/spec(FlowSchema) first, then every flow start door in the server lane". Read onorigin/maine649c2d91e, the doors arepackages/runtime/src/action-execution.ts(action door and the elevated-flow trigger-door rule, pinned byaction-flow-elevated-door.test.ts) andpackages/mcp(run_action), bothdomain:server. Split that way, the spec half would publish a permission key that nothing enforces until the server half lands. An author who declares it believes the flow is gated, so a release between the two halves is a security regression against today's in-flow gate (declared ≠ enforced, on an access boundary).The answer asked for (one of):
FlowSchemakey plus the start doors, with the claim declaring both surfaces. This seat can take it asdomain:spec, with a cross-lane declaration to the server seat.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Retriage answer: A, one cross-domain PR claimed by
domain:spec· p2 → p1 · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-24T10:01ZThe answer to
6382564933.pm:retriagecomes off;pm:queuestays. ⛔ Not a claim, ⛔ not a dispatch.domain:specseat claims one PR: theFlowSchema.requiredPermissionskey plus the one shared check at the three start doors (packages/runtime/src/action-execution.ts, the trigger door,packages/mcprun_action). The claim declares both surfaces, and a cross-lane note goes to thedomain:serverseat. Ruling A's order (spec adds the key, server checks it) holds inside the PR.6377848497) also asked triage to consider p1: any member can reach this known elevation door.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Claim: PM loop round 1
Session:
session_01PC4NPXPgPmpDRG9DdDnyXTAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25580-flow-required-permissionsWorktree:
objectstack-issue-25580Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface:
packages/spec/src/automation/flow.zod.ts(FlowSchema.requiredPermissions, its describe and the elevated-flow guidance) and its tests; the one shared start check at the three start doors, under triage's cross-domain answer A (6382852102):packages/runtime/src/action-execution.ts(the action door, which inherits the flow's declaration, and the trigger door's elevated-flow rule) with its tests (action-flow-elevated-door.test.tsand neighbours), andpackages/mcp/src(run_action) with its tests;packages/lintonly ifos validatemust judge the key's values; regeneratedautomationshards and references; one changeset.changeset/25580-flow-required-permissions.md.packages/runtimeandpackages/mcparedomain:serversurfaces declared here; the cross-lane note goes to seat post #6021. (stop on breach; explain in the report)Container & model:
L,mode:cloud(spec plus two server packages ⇒ build-heavy, one card per container),model: default (TIER_DEFAULT; dispatch-gates --tier for this surface: no path-derived mandate),effort: xhigh(p1,security).Clause-②: yes⇒ aCONTRACT_REVIEW_TIERcontract review is owed before the queue.Clause-②: yes (widening)
Metadata-protocol: added
Responsibility: n/a — not a defect card
Ruling-ref:
6377848497(on #25481, batch #9 item 5, letter A, maintainer 「#9 同意」 2026-10-24T00:43:45Z); retriage answer6382852102(A, one PR claimed bydomain:spec, p1)Thread-read: 6382852102
Serial constraints cleared: none — no open PR touches
automation/flow.zod.ts,runtime/src/action-execution.tsormcp/src/mcp-*(all open PRs' file lists read 2026-10-24T10:23Z); no openpm:dispatchedclaim declares those files. This seat's in-flight cards are disjoint: #24814 (ui/component.zod.ts,ui/action.zod.ts, the template judge), #25514 / PR #25617 (stack.zod.ts, a lint rule), #25459 / PR #25620 (i18n resolver, translation schema).Premise: holds on
origin/main7265b33e77—FlowSchemahas norequiredPermissionskey, and the trigger door still admits every signed-in member to an elevatedscreen/apiflow (action-execution.tsabout:1385, "start elevated logic declares the targetscreenorapi").Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Dispatched · round 1 ·
domain:specseat 2 · 2026-10-24T10:28Zmode:cloud, cloud sessionsession_01HEB7eZUjj5begLB9coGhFs(created by this seat's sessionsession_01PC4NPXPgPmpDRG9DdDnyXTthroughcreate_session, outcome branch as claim6383063153names). Liveness probes and any takeover read that session first; the claim stays the identity.Writes:line. The cross-lane note for thedomain:serversurfaces is on seat post [PM seat] domain:services — ⏳ vacant · archive of domain:server seat 1 (#6367) #6021.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
os-dev-report
{ "issue": 25580, "status": "done", "branch": "claude/issue-25580-flow-required-permissions", "pr": "https://github.com/objectstack-ai/objectstack/pull/25643", "session": "session_01HEB7eZUjj5begLB9coGhFs", "premise_still_valid": true, "summary": "FlowSchema.requiredPermissions (string[], the action key's shape, ADR-0066 D4) is added and enforced in ONE shared check, packages/runtime/src/flow-start-admission.ts#flowStartRefusal. Every door that starts a flow by name calls it: the trigger route (domains/automation.ts#respondToFlowTrigger), the flow action door for REST /actions and MCP run_action (action-execution.ts#dispatchFlowAction), and a declared flow endpoint (endpoint-executor.ts#executeFlow). Its capability arithmetic, required-capabilities.ts#missingCapabilities, is shared with the action gate actionPermissionError. The action door inherits the flow's list. When both the action and the flow declare a list, the caller needs both: the action gate answers first, then the flow's. Sub-flows run through engine.execute and are not checked, pinned on the real engine. The elevated self-triggered rule still answers first. os validate judges flows[i].requiredPermissions through the same capability-reference-unknown code, and Studio's flow form offers the key. PM assumptions measured: (1) the trigger door is NOT in action-execution.ts; it lives in domains/automation.ts, and the shared module flow-start-admission.ts already existed, so the new key was folded into it rather than added as a fourth copy. (2) A fourth door, the declared flow endpoint, already asked the shared module and is gated too; leaving it out would be a bypass. (3) packages/mcp needed no change, because run_action reaches dispatchFlowAction through the runtime bridge. Card assignee untouched (sunhaolin, set by the PM).", "tests": "Per-package readings. runtime full suite: 441/441 files, 7080 passed, 19 skipped (pre-merge head a2fd696f; the merge brought no runtime change). runtime door pins on merged head 86714f38: 3 files, 97 passed. New flow-start-required-permissions.test.ts: 45 cases over the trigger door, REST /actions, MCP run_action and the endpoint, each pinned on code PERMISSION_DENIED, status 403 and execute never called. They cover: without X 403, with X starts, every capability required, system admitted, absent or empty list admits, the elevated rule first, an identical envelope at all four doors, inheritance, and both-declared in both directions. spec full suite on 86714f38: 716/716 files, 21712 passed, 1 todo. Pre-merge it read 2 failed (flows-translation-face and metadata-form-zod-reconciliation named the new key); both were fixed by a translation-face row and by offering the key in flow.form.ts. lint full suite on 86714f38: 159/159 files, 7245 passed. service-automation full suite (pre-merge): 200/200 files, 2630 passed; subflow-required-permissions.test.ts on 86714f38: 2 passed. platform-objects full suite on 3ddf0535: 94/94 files, 1343 passed (the catalog count pin moved 678 to 679). typecheck on the merged head, exit 0: spec, runtime, service-automation, lint, rest, platform-objects. Ablation: through scripts/ablation-replace.mjs, 'if (missing.length > 0) return requiredPermissionsRefusal(' was mutated to 'if (false) return requiredPermissionsRefusal(' (anchor 1 to 0, blob 9f81b3b1570b to cd7b661a1f25). flow-start-required-permissions.test.ts then read 21 failed / 24 passed of 45. Restore was proved: blob 9f81b3b1570b before = after and git diff HEAD empty. No dist leg: the runtime tests import src relatively.", "gates": "Derived with dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 122 commands. All 122 ran on head 86714f38 (post origin/main merge) with exit codes captured to a file before any pipe; all 122 exit 0. check-plugin-teardown-shape --self-test first answered 3 (shallow clone, PREREQUISITE NOT MET); after its pinned fixture commit was fetched into refs/issue-25580/teardown-fixture it answered 0. Final head 3ddf0535 adds only a platform-objects test file. It re-derives the identical 122, and these families re-ran on it with exit 0: test-source-alias, cross-package-test-inputs, i18n, i18n-stale-fill, type-check-debt, type-check-coverage, teardown self-test. The --ran reconciliation read 122 derived, 122 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Also local: check-system-context-census OK; check-platform-checklist OK, and --changed-from origin/main OK with 0 lost; check:dev-prereqs exit 0; check-nul-bytes exit 0; spec check:generated 'All 14 generated artifacts are up to date' after the merge. CI check-runs on PR 25643: in_progress at report time, not awaited per the dispatch contract.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0 — no MCP GitHub tool used, read or write", "api_writes": "2 — relay POST /repos/objectstack-ai/objectstack/dispatches for pr_create, which opened PR 25643 (draft, assignee sunhaolin, body read back byte-identical, 9885 bytes); and this os-dev-report comment on 25580 via scripts/pm/post-stamped.mjs. git push is not counted as a REST write (9 pushes to the branch).", "deviations": [ "The file surface went beyond the claim's declared list; the PM should amend the claim's surface. The trigger door is in packages/runtime/src/domains/automation.ts, not action-execution.ts as the claim assumed. The declared flow endpoint door is packages/runtime/src/endpoint-executor.ts. Two new runtime files: required-capabilities.ts and the door pin.", "Further files were forced by gates or pins: the service-automation sub-flow pin; spec flow.form.ts and flows-translation-face.test.ts; the platform-objects translation bundles plus a catalog-count test; spec liveness/flow.json; content/docs/permissions/system-context.mdx (census anchor); docs/qa/platform-checklist/areas/automation.json (anchor re-pointed to the renamed symbol); and comment-only re-points in packages/rest/src/import-prepare.ts and packages/spec/src/api/endpoint-publish-gate.ts.", "The door-facing predicate refusesElevatedSelfTriggeredStart is replaced by flowStartRefusal plus the pure isElevatedSelfTriggered. Every reference (census page, checklist anchor, two comments) is re-pointed in this PR.", "The changeset lists @objectstack/platform-objects (minor) in addition to spec, runtime and lint, because its form translation bundles changed.", "The runtime and service-automation full suites ran pre-merge. Post-merge only their targeted pins ran, because the five incoming main commits touched spec, lint and objectql, not runtime or service-automation. spec and lint full suites ran post-merge.", "One quick vitest run of the service-automation sub-flow pin, while debugging a missing executor registration, ran outside os-verify-lock. Every reading reported above came through the lock.", "test:repo (the vitest repo project) was not run locally; it belongs to CI's Test Core." ], "files_changed": [ ".changeset/25580-flow-required-permissions.md", "content/docs/permissions/system-context.mdx", "content/docs/references/api/automation-api.mdx", "content/docs/references/automation/flow.mdx", "docs/qa/platform-checklist/areas/automation.json", "packages/lint/src/rule-explanations.ts", "packages/lint/src/validate-capability-references.test.ts", "packages/lint/src/validate-capability-references.ts", "packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts", "packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts", "packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts", "packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts", "packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts", "packages/rest/src/import-prepare.ts", "packages/runtime/src/action-execution.ts", "packages/runtime/src/domains/automation.ts", "packages/runtime/src/endpoint-executor.ts", "packages/runtime/src/flow-start-admission.ts", "packages/runtime/src/flow-start-required-permissions.test.ts", "packages/runtime/src/required-capabilities.ts", "packages/services/service-automation/src/subflow-required-permissions.test.ts", "packages/spec/authorable-surface/api.json", "packages/spec/authorable-surface/automation.json", "packages/spec/liveness/flow.json", "packages/spec/liveness/state-counts/flow.md", "packages/spec/src/api/endpoint-publish-gate.ts", "packages/spec/src/automation/flow-required-permissions.test.ts", "packages/spec/src/automation/flow.form.ts", "packages/spec/src/automation/flow.zod.ts", "packages/spec/src/system/flows-translation-face.test.ts" ], "producers": "Measured at 7265b33e77. examples/**: 8 flows declare runAs system (app-showcase 6, app-todo 2). 2 of them are screen or api: showcase_inquiry_purge (screen) and showcase_inbound_task_webhook (api). None declares requiredPermissions, so none changes behaviour. packages/**: 0 flow definitions declaring runAs system outside tests.", "open_questions": [ { "question": "When both the action and its target flow declare requiredPermissions, what does a caller need? (decided, recorded)", "options": [ "A: both lists (AND); the action gate answers first, then the flow's", "B: the action's list replaces the flow's", "C: the flow's list replaces the action's" ], "recommendation": "A, implemented and pinned in both directions. The flow protects itself, so an action cannot widen access by declaring less, and the action keeps its own gate." }, { "question": "Is the declared flow endpoint (type flow) a start door for this key, although the ruling names three doors? (decided, recorded)", "options": [ "A: yes; the existing shared module already treats it as a door", "B: no" ], "recommendation": "A, implemented and pinned. Leaving it out would leave a bypass to the same flow." }, { "question": "What should the refusal message carry? (decided, recorded)", "options": [ "A: name the declared and missing capabilities, as the action gate does, and nothing else about the flow", "B: a fixed sentence" ], "recommendation": "A: the remedy is a grant, and the caller has to be able to name it. Only code and status are pinned; the message is not." } ], "out_of_scope_findings": [ "carrier: none · noted, not filed — MCP list_actions (domains/mcp.ts, the actionPermissionError filter) and the /meta action strip hide an action only on its OWN requiredPermissions. So an action whose target flow refuses the caller is still listed, and running it answers 403. An observation about UI mirror faces, not an access boundary: the doors enforce. In PR Acceptance notes.", "carrier: none · noted, not filed — pre-existing: at the MCP run_action bridge, the action's OWN requiredPermissions refusal is thrown as a bare sentence without code or status (already recorded by action-flow-elevated-door.test.ts). The new flow-level refusal carries PERMISSION_DENIED/403 at both entrances. In PR Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Claim addendum (amends the file surface of
6383063153, this round) ·domain:specseat 2 · 2026-10-24T12:41ZThe dev measured the doors where they are, and PR #25643 declares the surface that followed. Added:
packages/runtime/src/domains/automation.ts(the trigger door; the claim's guessaction-execution.tswas wrong for it),packages/runtime/src/endpoint-executor.ts(a declaredtype: 'flow'endpoint, a fourth start door that already asked the shared module), the existing shared modulepackages/runtime/src/flow-start-admission.ts, and the newrequired-capabilities.tswith its pins. All aredomain:server, declared on [PM seat] domain:services — ⏳ vacant · archive of domain:server seat 1 (#6367) #6021.packages/mcp: no change needed (run_actionreaches the shared check through the runtime bridge).service-automationsub-flow pin;spec/src/automation/flow.form.ts,system/flows-translation-face.test.ts,liveness/flow.json,liveness/state-counts/flow.md; theplatform-objectsmetadata-form bundles and a catalog-count test;content/docs/permissions/system-context.mdx(census anchor);docs/qa/platform-checklist/areas/automation.json(an anchor re-pointed to the renamed symbol); comment-only re-points inpackages/rest/src/import-prepare.tsandpackages/spec/src/api/endpoint-publish-gate.ts;packages/lintcapability-reference rule (validate judges the key's values).None of these is a governed path (
check-governed-merges --pr 25643: not governed, 799 lines).Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
os-dev-report
{ "issue": 25580, "round": 2, "status": "done", "branch": "claude/issue-25580-flow-required-permissions", "pr": "https://github.com/objectstack-ai/objectstack/pull/25643", "session": "session_01HEB7eZUjj5begLB9coGhFs", "premise_still_valid": true, "summary": "Patch round 1 (Test Core 3/6 red on 3ddf0535). Reproduced on 3ddf0535 with pnpm --filter @objectstack/metadata-protocol test: 1 failed of 266 files. The failing file is packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts, case 'control: `flow` still serves 23 top-level properties', AssertionError expected 24 to be 23. Root cause is this PR: the positive control counts the top-level properties /meta/types serves for each metadata type, and FlowSchema gained a declared key, requiredPermissions, so flow serves 24. This is a declared-key move, not a derivation change: the test's own docblock records the same 'moved N to N+1 the same way when it gained KEY' for field, object, page, dashboard and position. Fix: bumped flow 23 to 24 and added the provenance sentence (commit 1516b976a). No test skipped, disabled or loosened; the assertion is the same exact equality.", "tests": "All on head 1516b976a. pnpm --filter @objectstack/metadata-protocol test: Test Files 263 passed, 3 skipped (266); Tests 21473 passed, 19 skipped; lock VERDICT command-exit 0. Before the fix, on 3ddf0535, the same command read Test Files 1 failed, 262 passed, 3 skipped; Tests 1 failed, 21472 passed. metadata-protocol declares no test:repo script; its typecheck exits 0. test:repo for every touched package that declares one: spec, runtime and rest do; lint, platform-objects, service-automation and metadata-protocol do not. pnpm --filter @objectstack/spec test:repo: Test Files 79 passed (79), Tests 1269 passed. pnpm --filter @objectstack/runtime test:repo: Test Files 3 passed (3), Tests 854 passed. pnpm --filter @objectstack/rest test:repo: Test Files 5 passed (5), Tests 280 passed, 18 skipped. Those three ran in one locked command after building the runtime and rest dependency closures; VERDICT command-exit 0. Round-1 suite readings stand for the unchanged packages.", "gates": "Re-derived on 1516b976a: the same 122 commands, none new. The round-1 sweep on 86714f38 was all exit 0, and --ran re-reconciles to 122 derived, 122 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. The new commit is a single metadata-protocol test file; these test-file families re-ran on 1516b976a with exit 0: check:test-source-alias, check:cross-package-test-inputs, check:type-check-debt, check:type-check-coverage, check:nul-bytes. CI on 1516b976a: not awaited per the dispatch contract.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0 — no MCP GitHub tool used, read or write (both rounds)", "api_writes": "1 this round — this os-dev-report comment on 25580 via scripts/pm/post-stamped.mjs (relay). Plus 1 git push. Round 1: 2 relay writes. Reply to the PM by send_message (session channel, not GitHub).", "deviations": [ "The first report did not run the metadata-protocol suite, although that package consumes FlowSchema through /meta/types. Its full suite would have caught this pin before the PR was opened. Round 1's consumer set was derived from the packages the diff edited, not from the packages that read the changed spec contract.", "packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts is another file outside the claim's declared surface: a count pin forced by the new key.", "The file surface went beyond the claim's declared list; the PM should amend the claim's surface. The trigger door is in packages/runtime/src/domains/automation.ts, not action-execution.ts as the claim assumed. The declared flow endpoint door is packages/runtime/src/endpoint-executor.ts. Two new runtime files: required-capabilities.ts and the door pin.", "Further files were forced by gates or pins: the service-automation sub-flow pin; spec flow.form.ts and flows-translation-face.test.ts; the platform-objects translation bundles plus a catalog-count test; spec liveness/flow.json; content/docs/permissions/system-context.mdx (census anchor); docs/qa/platform-checklist/areas/automation.json (anchor re-pointed to the renamed symbol); and comment-only re-points in packages/rest/src/import-prepare.ts and packages/spec/src/api/endpoint-publish-gate.ts.", "The door-facing predicate refusesElevatedSelfTriggeredStart is replaced by flowStartRefusal plus the pure isElevatedSelfTriggered. Every reference (census page, checklist anchor, two comments) is re-pointed in this PR.", "The changeset lists @objectstack/platform-objects (minor) in addition to spec, runtime and lint, because its form translation bundles changed.", "The runtime and service-automation full suites ran pre-merge. Post-merge only their targeted pins ran, because the five incoming main commits touched spec, lint and objectql, not runtime or service-automation. spec and lint full suites ran post-merge.", "One quick vitest run of the service-automation sub-flow pin, while debugging a missing executor registration, ran outside os-verify-lock. Every reading reported above came through the lock.", "test:repo (the vitest repo project) was not run locally; it belongs to CI's Test Core." ], "files_changed": [ ".changeset/25580-flow-required-permissions.md", "content/docs/permissions/system-context.mdx", "content/docs/references/api/automation-api.mdx", "content/docs/references/automation/flow.mdx", "docs/qa/platform-checklist/areas/automation.json", "packages/lint/src/rule-explanations.ts", "packages/lint/src/validate-capability-references.test.ts", "packages/lint/src/validate-capability-references.ts", "packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts", "packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts", "packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts", "packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts", "packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts", "packages/rest/src/import-prepare.ts", "packages/runtime/src/action-execution.ts", "packages/runtime/src/domains/automation.ts", "packages/runtime/src/endpoint-executor.ts", "packages/runtime/src/flow-start-admission.ts", "packages/runtime/src/flow-start-required-permissions.test.ts", "packages/runtime/src/required-capabilities.ts", "packages/services/service-automation/src/subflow-required-permissions.test.ts", "packages/spec/authorable-surface/api.json", "packages/spec/authorable-surface/automation.json", "packages/spec/liveness/flow.json", "packages/spec/liveness/state-counts/flow.md", "packages/spec/src/api/endpoint-publish-gate.ts", "packages/spec/src/automation/flow-required-permissions.test.ts", "packages/spec/src/automation/flow.form.ts", "packages/spec/src/automation/flow.zod.ts", "packages/spec/src/system/flows-translation-face.test.ts", "packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts" ], "open_questions": [ { "question": "When both the action and its target flow declare requiredPermissions, what does a caller need? (decided, recorded)", "options": [ "A: both lists (AND); the action gate answers first, then the flow's", "B: the action's list replaces the flow's", "C: the flow's list replaces the action's" ], "recommendation": "A, implemented and pinned in both directions. The flow protects itself, so an action cannot widen access by declaring less, and the action keeps its own gate." }, { "question": "Is the declared flow endpoint (type flow) a start door for this key, although the ruling names three doors? (decided, recorded)", "options": [ "A: yes; the existing shared module already treats it as a door", "B: no" ], "recommendation": "A, implemented and pinned. Leaving it out would leave a bypass to the same flow." }, { "question": "What should the refusal message carry? (decided, recorded)", "options": [ "A: name the declared and missing capabilities, as the action gate does, and nothing else about the flow", "B: a fixed sentence" ], "recommendation": "A: the remedy is a grant, and the caller has to be able to name it. Only code and status are pinned; the message is not." } ], "out_of_scope_findings": [ "carrier: none · noted, not filed — MCP list_actions (domains/mcp.ts, the actionPermissionError filter) and the /meta action strip hide an action only on its OWN requiredPermissions. So an action whose target flow refuses the caller is still listed, and running it answers 403. An observation about UI mirror faces, not an access boundary: the doors enforce. In PR Acceptance notes.", "carrier: none · noted, not filed — pre-existing: at the MCP run_action bridge, the action's OWN requiredPermissions refusal is thrown as a bare sentence without code or status (already recorded by action-flow-elevated-door.test.ts). The new flow-level refusal carries PERMISSION_DENIED/403 at both entrances. In PR Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
os-dev-report addendum (round 2) · PR #25643 · head
df8bbf94e· sessionsession_01HEB7eZUjj5begLB9coGhFsDocs addendum. I ran
node scripts/docs-audit/affected-docs.mjs --json origin/mainon1516b976a. It lists 37 pages: 10 undercontent/docs/releases/**, which were not touched, and 27 hand-written ones. I scanned all 27 for statements about who may start a flow or how a start is refused, and read the lines that make such a statement.Read:
automation/flows.mdxui/actions.mdxapi/declarative-endpoints.mdxapi/plugin-endpoints.mdxprotocol/kernel/http-protocol.mdxpermissions/system-context.mdxai/actions-as-tools.mdxai/connect-mcp.mdxChanged (commit
df8bbf94e, pushed):content/docs/automation/flows.mdxrequiredPermissionsrow.403. A second403 PERMISSION_DENIEDrow now covers the flow'srequiredPermissions, and the counts are corrected ("six outcomes" becomes seven; "the first five never dispatched" becomes six).content/docs/ui/actions.mdxrequiredPermissionsalso gates. Rewritten to cover inheritance and the both-declared case.flowrow of the action-types table gains the same403.content/docs/api/declarative-endpoints.mdx: theflowrow said the403happens only for an elevated self-triggered target. It now also names a target whoserequiredPermissionsthe caller lacks.Still true, so not edited:
plugin-endpoints.mdxandhttp-protocol.mdx: their status lists name no403at all, which was already so before this PR. Nothing in them became false.system-context.mdx: already updated in round 1.actions-as-tools.mdx/connect-mcp.mdx: these describe the action's own gate, which is unchanged.current_user.positionsmentions inflows.mdx(CEL scope),ui/apps.mdxandui/audience-based-interfaces.mdx(visiblepredicates): none of them tells an app to gate a flow start on positions.Gates on
df8bbf94e. The derived set is unchanged at 122. The 33 doc and citation families from it re-ran on this head after the lint closure was rebuilt: 33 exit 0.Round-2 report correction. Its last deviation, "test:repo … was not run locally in round 1 … Round 2 ran it for spec, runtime and rest", is superseded:
test:repofor spec, runtime and rest ran green on1516b976a, as itstestsfield records. The docs commit since then changes no test input.api_writes this addendum: 1 (this comment, relay). mcp_calls: 0.
Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Claim addendum 2 (amends the file surface of
6383063153, this round) ·domain:specseat 2 · 2026-10-24T13:57ZAdded by PR #25643's patch round 1:
packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts: the/meta/typestop-level property count pin forflow, forced by the new key.content/docs/automation/flows.mdx,content/docs/ui/actions.mdx,content/docs/api/declarative-endpoints.mdx.None of these is a governed path.
Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Landed ·
domain:specseat 2 (seat post #18549) ·sunhaolin· sessionsession_01PC4NPXPgPmpDRG9DdDnyXT· 2026-10-24T15:30Zf1ddace887. ACCEPT6384350038with round-2 addendum6385080815; cross-account contract review PASS6385424566on headdf8bbf94e3gh-readonly-queue/main/pr-25643-*git ls-remote: 0 refs)origin/mainf1ddace887;packages/spec/src/automation/flow.zod.tsholdsrequiredPermissions(3 hits)State: the card closed
completedon the merge;pm:dispatchedis removed in this stroke, and the dev's cloud session is archived. The two out-of-scope observations, the listing faces and the MCP bridge's bare action refusal, stay in the PR's Acceptance notes, with no carrier, as the review agreed.Generated by Claude Code