A shipped side-effecting node type states its policy
"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
- 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).
- The derivation reads "mutates persistent state" as "performs an action an operator should approve", which is not the same question.
- A shipped declaration should be
askwhere the effect leaves the implementation's own boundary andskipwhere it does not. - 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:
{ "policy": null, "authorControls": ["waive", "require"], "dynamicControls": [] }{ "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:
{ "policy": "ask", "authorControls": [], "dynamicControls": [] }