--- id: RT-GATE-15 family: RT-GATE level: extended profiles: [runtime, storage-api] posture: normative-target added: "1.0" changed: "1.0" source: https://flowdrop.io/spec/rules/rt-gate/rt-gate-15 specification: FlowDrop Workflow Specification 1.0-draft licence: CC BY 4.0 --- # RT-GATE-15 — A shipped side-effecting node type states its policy *RT-GATE (Part II) · level: extended · profiles: runtime, storage-api · added in 1.0* "Has side effects" and "an operator should approve this" are two different questions that happen to coincide for an outbound call and diverge for a memory write. Leaving a shipped node type undecided delegates a governance decision to a fail-safe. ## The rule > **Normative.** This is the rule. > > 1. Every node type an implementation ships whose executor declares that it has side effects must declare its confirmation policy explicitly, rather than leaving the requirement to be derived (RT-GATE-7). > > 2. The derivation reads "mutates persistent state" as "performs an action an operator should approve", which is not the same question. > > 3. A shipped declaration should be `ask` where the effect leaves the implementation's own boundary and `skip` where it does not. > > 4. Where an implementation delivers such declarations to sites that already exist, it must write a policy only where none is stored: an administrator's own choice, a narrowed list of allowed controls, and a value still awaiting migration must all survive untouched. ## What it means "Mutates persistent state" and "an operator should approve this" are two different questions, and a node type that ships without an answer is not defaulting to a neutral position — it is letting the gate's own fail-safe answer a governance question nobody actually decided. The two questions happen to agree for a call that leaves the implementation's own boundary, which is exactly why it is easy to assume they always agree. Once a shipped type has an answer, delivering it to a site that already exists is one-directional. The pass may fill in a policy that was never stored, but it must recognise every other shape as somebody's answer already: a stored policy, a narrowed list of controls with no policy yet, and an older tri-state waiting for a separate migration to translate all count, and none of them may be replaced by the shipped default. ## Example A confirmation mapping records a policy alongside which controls stay available. Left undecided, filling it in from what the type ships looks like this: ```json title="A node type's confirmation mapping, undecided" verdict="undecided" { "policy": null, "authorControls": ["waive", "require"], "dynamicControls": [] } ``` ```json title="The same mapping once the shipped policy is delivered" verdict="skip" { "policy": "skip", "authorControls": ["waive", "require"], "dynamicControls": [] } ``` Where a policy was already stored, the same delivery finds nothing undecided to fill in — even on a node type whose shipped default disagrees with it: ```json title="An administrator's own choice, on a type shipped with skip" verdict="untouched" { "policy": "ask", "authorControls": [], "dynamicControls": [] } ``` ## Related rules - Names: RT-GATE-1, RT-GATE-7 - Referenced by: RT-GATE-7 --- Rule identifiers are permanent and are never renumbered. This specification carries no implementation status: each implementation publishes its own standing against these rules. Licensed CC BY 4.0.