0071. system is a reserved key of the ActionContext; policies decide what it admits
0071. system is a reserved key of the ActionContext; policies decide what it admits
Status
Accepted. Amends ADR-0059 (a second reserved key) and complements ADR-0055 (no bypass).
Date
2026-10-10
Deciders
operator (Saulo Vallory), accepting roadmap revision 5 on 2026-10-10 at 09:05, decision D11 with its recommended option.
Context
Hyper’s bootstrap, lease sweeper and 11 nested change modules call actions with authorize?: false and keep the human actor (70 call sites in lib/, pipeline/changes.ex:26-31). ADR-0055 has no bypass and forbids any action no policy covers. Without a rule, an internal write either cannot pass or needs a flag no record names (gap G20). ADR-0068 makes nested calls checked by the called action’s policies, so the question is how a cascade or a timer is admitted.
Decision
ActionContext has one more reserved key, system, a boolean the runtime declares optional (system?: boolean).
- Mesh does not act on it. No policy is skipped: a policy admits an internal write by testing the key, with
isSystem(context)in the project’s own helper, inauthorize-if. - The application sets it, for the bootstrap, a timer, or a call it makes on the system’s behalf. It is never read from input. It is as trustworthy as the code that builds the context.
- The
actionshandle of arunstep carries the caller’s whole context into a nested call,systemincluded, and the humanactorstays on it, so an event written by a cascade still names the person who caused it. - A write policy of an internal-only action (a Completion recorded by a cascade) is
authorize-if=({ actor, context }) => hasRole(actor, ["owner"]) || isSystem(context).
Options considered
- A reserved
systemkey on the context (chosen). One documented key, the human actor kept. - A marker on the actor (
actor.system). A nested call must swap the actor, so events lose the human unless a second key (onBehalfOf) is added. - A call-level exemption: a separate
internalbinding of the action functions that skips policies, mirroringauthorize?: false. No clause in the policies, but policies no longer say who can reach an action, and it is a bypass by API where ADR-0055 rejected one by syntax. - No exemption: nested calls checked as the human, and cascade-only actions admit whoever cascades. Assignment, Completion and Event become writable by any worker unless the transport hides them.
Trade-off analysis
Option 1 keeps the rule that access is declared in policies and the human actor on events, for one reserved key and a clause in each write policy of an internal-only action. If that clause repeats too much, shared checks (after 1.0) are the fix, not a bypass. Option 3 is shorter to write and gives up the property that reading the policies tells you who can reach an action.
Consequences
- Using your domain and Configuration document the key.
- A restore or import write that skips checks, if one is built after 1.0, requires
systemand is exported from its own entry that#meshdoes not re-export.
Action items
- M8:
system?: booleanin the runtime’sActionContext; the policy tests of M8 cover a nested call.