Home / Projects / Orchestrata Ultimate / Decisions / ADR index / ADR-0035
ADR-0035: Authorization is a scoped grant matched to the action instance, from two sources
| field | value |
|---|---|
| Status | Proposed |
| Date | 2026-07-24 |
| Register | factory (docs/adr, 4-digit ids) |
| Source | docs/adr/0035-scoped-authorization-two-grant-sources.md — permalink at bdafa201a (unchanged on master since that commit) |
| Wiki register entry | ADR-0035 (wiki status: Proposed) |
The record
- Status: Proposed
- Date: 2026-07-24
- Spec: SPEC-034 §4
- Relates to: ADR-0009 (loosening = human), ADR-0034 (property-derived classification), ADR-0018 (execution-identity boundary)
Context
Consent today is a single fact keyed on the category with a wildcard actor:
respects_privacy(Action, compliant) :- involves_pii(Action), human_consent_given(access_pii, _).
human_consent_given(access_pii, _) authorizes every PII action, forever, for anyone. It
cannot express the distinctions real governance needs, of which the motivating case is:
No data in production may be deleted; but test data placed there by a test run, to be cleaned up after the test, is deleted automatically.
The same verb (delete) must deny one instance (prod, human-gated) and auto-allow another
(test fixture, no human), and a category-global consent flag cannot represent either the
instance scoping or the conditional auto-authorization.
Decision
An action is authorized iff some grant's selector matches THIS action instance's attributes. Authorization is not a category flag; it is attribute-based, and comes from exactly two sources:
- Standing policy grant — a human-ratified pattern rule that auto-authorizes a scoped
class of actions with no per-instance human touch:
Adding or widening such a rule is a loosening ⇒ human ratification (ADR-0009), flagged by the B16 amendment classifier. It is a pattern in the SPEC-033 tier sense (grows by human ratification), ratified once, then automatic.auto_authorized(A) :- action(A, verb=delete), action_target(A, R), resource(R, class=test_fixture), resource(R, env=test). - Instance-scoped consent grant — a human grant bound to a specific resource (optionally
its content-version), single-use, carrying provenance and expiry:
consent_grant(GrantId, who=<human>, verb=delete, target=<resource>, scope=once, version=<v>, reason=<...>, at=<...>)
The article check composes them and the constitution consumes the result:
authorized(A) :- auto_authorized(A).
authorized(A) :- matching_consent_grant(A). % unconsumed, unexpired, selector matches
%% Article I: requires_human_consent(A), \+ authorized(A) -> deny
- A consent grant is consumed on use (marked/retracted) and the grant→action binding is recorded in the B1c enforcement ledger / B20 attestation, so "authorize once" is enforced and auditable.
- A grant carrying a
versionno longer matches once the resource's content changes — consent does not silently carry across a mutation.
Alternatives rejected
- Category-global consent (status quo) — one fact authorizes a whole category forever; cannot scope to a resource, condition, actor, or use-count.
- Only standing policy rules (no per-instance consent) — cannot express "authorize THIS prod delete once"; forces every one-off into a ratified rule (permanent) or into denial.
- Only per-instance consent (no standing policy) — makes routine, safe, high-volume actions (test-fixture cleanup) a human event each time; the throughput collapse ADR-0014 warns about.
- Role/identity-only (RBAC) — the prod-vs-test distinction is a property of the resource and context, not of who is acting; attribute-based is the model that fits.
Consequences
- Two grant stores + a matcher; the constitution's consent checks call
authorized/1instead of a category fact. - Selector correctness is load-bearing: a selector that accidentally matches production is a privilege escalation. Selectors are tested like rules, and widening one is a loosening (human). Boundary changes to what a class covers are loosening by definition (ADR-0021 shape).
- Depends on ADR-0036: a grant is only as trustworthy as the resource attributes its selector reads, so those attributes must be un-forgeable by the acting agent.
Provenance
Mohamed, 2026-07-24: "how do we tie consent to specific instances of actions? and specific categories? e.g. no data in production to be deleted but test data ... deleted automatically." — the worked example that exposed the category-global consent flag as inexpressive.
Related ADRs
Rulings cited
None.