Split out of #10145 rather than folded into it, because it is a decision, not the measured defect that card fixes.
What #10145 settled, and what it deliberately did not
#10145 gates the automation-domain definition writes — POST /automation, PUT /automation/:name, DELETE /automation/:name — on manage_metadata. It deliberately left every other mutating route on the domain alone, on the rule that authoring and executing are different questions:
POST /automation/:name/toggle sits between the two classes and is the one that needs a ruling.
The question
toggleFlow(name, enabled) mutates whether a registered flow is enabled and bound to its trigger. Today it is reachable by any authenticated caller (behind the domain's anonymous floor, and nothing else).
Reading it as execution state: the domain's own _status route comment separates the two explicitly — "persisted status is metadata, but whether a flow is actually enabled + bound to its trigger is engine state". On that reading toggle is operator tooling, not authoring, and gating it on manage_metadata would be the same over-block mistake #7968 records for the paused-run screen read.
Reading it as authoring: disabling a shipped flow is functionally equivalent to deleting it for as long as it stays off — and DELETE /automation/:name is now gated. On a walled multi-organization deployment the blast radius is identical to the one #10145 measured: flow enablement is environment-scoped, so one tenant switching a flow off switches it off for every organization on the deployment. That is the same cross-tenant shape, one verb over.
⚠️ The second reading has not been measured over HTTP. This card is the request to measure it and rule, not a claim that it reproduces.
What a fix would look like if the ruling goes that way
One line: add the toggle arm to isFlowAuthoringWrite in packages/runtime/src/domains/automation.ts, which is already the single predicate for this policy. The audit block in packages/runtime/src/domains/automation-write-capability-gate.test.ts currently pins toggle as ungated, deliberately and in the open, so that pin is where the ruling would land:
it('POST /:name/toggle stays ungated — enablement is engine state, not a definition write', …)
If the ruling goes the other way, that pin stays and this card closes as not planned with the reasoning recorded — which is also a useful outcome, because right now the verdict is implicit.
Related: #10145 (the write gate this splits from), #7020 (whether the read and write capability cohorts should align).
Split out of #10145 rather than folded into it, because it is a decision, not the measured defect that card fixes.
What #10145 settled, and what it deliberately did not
#10145 gates the automation-domain definition writes —
POST /automation,PUT /automation/:name,DELETE /automation/:name— onmanage_metadata. It deliberately left every other mutating route on the domain alone, on the rule that authoring and executing are different questions:POST /automation/:name/triggerand the legacyPOST /automation/trigger/:namerun a flow. Gating them on a metadata capability would lock ordinary members out of the flows built for them.POST /automation/:name/runs/:runId/resumeruns too, and is already fail-closed through the suspended node'sresumeAuthority(automation: the generic run-resume route needs an authorization gate keyed on the suspended node #3801 / automation:resumeAuthoritydefaults to'any', so every future pausing node ships fail-open — ADR-0044 says this is "tracked separately" and nothing tracks it #5561).POST /automation/:name/togglesits between the two classes and is the one that needs a ruling.The question
toggleFlow(name, enabled)mutates whether a registered flow is enabled and bound to its trigger. Today it is reachable by any authenticated caller (behind the domain's anonymous floor, and nothing else).Reading it as execution state: the domain's own
_statusroute comment separates the two explicitly — "persistedstatusis metadata, but whether a flow is actually enabled + bound to its trigger is engine state". On that readingtoggleis operator tooling, not authoring, and gating it onmanage_metadatawould be the same over-block mistake #7968 records for the paused-run screen read.Reading it as authoring: disabling a shipped flow is functionally equivalent to deleting it for as long as it stays off — and
DELETE /automation/:nameis now gated. On a walled multi-organization deployment the blast radius is identical to the one #10145 measured: flow enablement is environment-scoped, so one tenant switching a flow off switches it off for every organization on the deployment. That is the same cross-tenant shape, one verb over.What a fix would look like if the ruling goes that way
One line: add the
togglearm toisFlowAuthoringWriteinpackages/runtime/src/domains/automation.ts, which is already the single predicate for this policy. The audit block inpackages/runtime/src/domains/automation-write-capability-gate.test.tscurrently pins toggle as ungated, deliberately and in the open, so that pin is where the ruling would land:If the ruling goes the other way, that pin stays and this card closes as
not plannedwith the reasoning recorded — which is also a useful outcome, because right now the verdict is implicit.Related: #10145 (the write gate this splits from), #7020 (whether the read and write capability cohorts should align).