Authorize as Caller, Execute as System

A control-plane operation separates AUTHORIZATION from EXECUTION. Check the caller's access EXPLICITLY — when the request is written, and again immediately before it executes — and refuse fail-closed, naming the missing permission. Then execute under System. Never let the caller's row-level-security reach decide, mid-execution, whether an operation that was already authorized can finish. Policy authorize-as-caller-execute-as-system (register).

What it covers

A control-plane / system operation is work the platform performs on behalf of a request that a person (or a service identity) filed, where the work itself touches records that belong to the platform rather than to that person:

The records these operations read and write — a GitSynced Deployments/<name>, a registry entry, an operator status node — are system-owned. The person who filed or approved the request is authorized to ask for the operation; that does not mean they can read every record the operation must read to carry it out, and it should not have to.

Operations nobody requested

A webhook-triggered sync (a push imports at its pushed SHA), a periodic reconcile that repairs lost deliveries, a scheduled governance pass: these have no caller — no person wrote a request node, so there is no identity whose access could be checked twice. Their authority is the platform's own declaration (the sync source, the schedule) plus the authenticity of the trigger (the webhook's signature), checked where the trigger arrives; they execute as System from the start and record the trigger, not a requester. The two checkpoints below apply to every operation that a person or a service identity asked for — including a manual sync or import, and the approval of any of them.

The two checkpoints

1. When the request is WRITTEN

The create/update of the request node is validated, and an unauthorized request is refused by name at that write — it never gets parked as a pending request that nobody can execute.

2. Immediately BEFORE executing

The caller's access is checked again, explicitly, right before the operation runs. Between the request and its execution the requester or the approver may have lost the right (a revoked grant, a removed membership, a role change). The check uses the Permission API against the identity recorded on the request, not the ambient context of whatever thread picked it up — and it checks every identity the operation's authority rests on: the requester's right to request it, and, for an operation that needed approval (the approval re-plan included), the recorded approver's right to approve it. A requester who still holds the right does not carry an approver who lost it.

Then: execute as System

Once both checkpoints pass, everything the operation does — reading the records it acts on, rendering plans, writing results and status — runs as System. The operation's success must not depend on what the caller can read.

// 1) explicit authorization of EVERY identity the action's authority rests on — fail closed
var requester = hub.CheckPermissionOutcome(request.DeploymentPath, request.RequestedBy, Permission.Update)
    .Select(outcome => (who: request.RequestedBy, what: "Update", outcome));
var approver = request.ApprovedBy is null
    ? Observable.Return((who: (string?)null, what: "", outcome: PermissionCheckOutcome.Granted))
    : hub.CheckPermissionOutcome(request.DeploymentPath, request.ApprovedBy, ApprovePermission)
        .Select(outcome => (who: request.ApprovedBy, what: "Approve", outcome));

requester.Zip(approver, (r, a) => r.outcome.IsGranted ? a : r)   // the first check that did not grant
    .SelectMany(check => access.RunAsSystemFor(
        governedBy: request.Path, onBehalfOf: request.RequestedBy,
        // 2) everything after the checks runs as System — the action, and the refusal's status
        //    write alike: the status node is system-owned too
        () => check.outcome.IsGranted
            ? ExecuteAction(hub, request)
            : RecordRefusal(hub, request, check.outcome.IsUndetermined
                // could not decide is NOT a denial (PermissionCheckOutcome) — say which it was
                ? $"{check.what} permission of '{check.who}' on '{request.DeploymentPath}' could not be checked: {check.outcome.UndeterminedReason}"
                : $"User '{check.who}' lacks {check.what} permission on '{request.DeploymentPath}'")))
    .Subscribe(_ => { }, ex => logger.LogWarning(ex, "Action {Path} failed", request.Path));

ApprovePermission stands for whatever right the action's approval policy requires; the shape is the point — one explicit check per identity, the refusal naming which identity and which right, and nothing after the checks running as the caller.

What is NOT allowed

Evidence

On the control instance (control.systemorph.com), the action Ops/Actions/provision-control-registry-20261008 was approved and then refused at 2026-10-08 07:39:40Z:

the Hosting/Deployment record 'Deployments/control' is listed by the index but could not be read — User 'rbuergi' lacks Read permission on 'Deployments/control'

The approval re-plan ran as the approver. The approver was authorized to approve the action; the deployment record is GitSynced and system-owned, and the approver held no Read grant on it. The operation failed after authorization, on a read its executor should have performed as System. Under this rule the re-plan checks the approver's right to approve explicitly, then renders the plan as System.