Policies
Every check evaluates the requested verb against your tenant’s policy:
a verb → mode map where each mode is allow, deny, require_grant, or
step_up. The policy is layered. The most specific layer that names the
verb decides:
- Per-principal override — one principal’s sparse verb→mode map. Highest precedence.
- Per-environment override —
dev,staging, orprod. Applies when the check names that environment. - Tenant default — the complete map; every verb falls back here.
Unnamed verbs inherit downward: a verb the principal’s override doesn’t set falls through to the environment override, then to the tenant default. An unknown environment name denies fail-closed without evaluating any policy.
The tenant default
PUT /v1/policy sets modes on the complete map; GET /v1/policy reads
it. Unlisted verbs default to require_grant (fail closed). Every edit
bumps the tenant default’s own version.
Environment overrides
PUT /v1/policies/environments/{dev|staging|prod} sets a sparse
override: only the verbs you send are stored; the rest keep inheriting
the tenant default. Each environment has its own independent version
starting at 1 on first write (version: 0 means “no override yet”).
GET /v1/policies/environments/{env} returns the sparse policy plus
effectivePolicy (tenant default with the override applied) and
policyProvenance, which names the winning layer per verb.
Per-principal overrides
PUT /v1/policies/principals/{principalId} sets the top layer: a sparse
override for one principal. Only live principals can carry policy —
setting an override on a tombstoned or disabled principal is 400
(principal_inactive), because an identity that can’t authorize checks
can’t carry policy either. Each principal’s override has its own
independent version, untouched by environment or tenant edits.
Send {"verb": "inherit"} to remove a verb from the override so it
inherits downward again.
GET /v1/policies/principals/{principalId} returns the sparse override,
the fully-resolved effectivePolicy, and policyProvenance naming the
winning layer per verb. Add ?environment=prod to fold the prod
override into the middle layer, exactly as a check naming prod would
evaluate it. Tombstoned principals still return 200 — they stay
resolvable forever — with "inert": true.
GET /v1/policies lists every policy object: tenant default first, then
configured environment overrides, then configured per-principal
overrides.
Inert overrides
Disabling or deleting a principal makes its override inert: it never
supplies a mode while the principal can’t authorize checks — checks deny
with principal disabled / principal deleted before policy is even
evaluated. The override data is kept, not wiped: re-enabling the
principal re-arms its override, and the audit record of what was
configured survives.
Receipts name the winner
Every check receipt records policyId and policyVersion — the single
policy object that supplied the verb’s mode — alongside the principal
snapshot (principalId / principalName). policyId is null only
where no policy was evaluated (e.g. unknown-environment denials).