Architecture Decision Record

0006. Authorization engine and the Authorizer seam

Context

The founding ADR left open whether authorization is a Wyrd trait or lives wholly in Vord (ADR-0001 open items; deferred in Wyrd ADR-0018). It is settled here.

The relationship-authorization model — "may principal P do action A on resource R", evaluated over a graph of relations (groups, ownership, sharing, org hierarchy) — is a hard, well-solved distributed-systems primitive. Google's Zanzibar is the reference, and there are mature open implementations of it. Wyrd's own doctrine applies directly: the novelty budget is spent on the differentiator, and the genuinely hard, well-understood primitives are consumed, not reinvented (the reasoning behind Wyrd adopting etcd, Wyrd ADR-0006). Vord's differentiator is the edge-verification composition, not a new authorization engine.

A single-binary / dev profile must also exist with no external dependency, the same constraint that gave Wyrd its embedded-backend pattern.

Decision

  1. Define an Authorizer trait — the policy-decision seam. It evaluates an authorization query and returns a decision, optionally fenced by a consistency token (ADR-0004) when the decision needs read-your-writes (e.g. a just-granted permission).

  2. Pluggable backends, mirroring Wyrd's redb / TiKV split.

    • Production: a Zanzibar-style relationship engine — SpiceDB or OpenFGA — run as a service behind the trait. These provide relation tuples and native consistency tokens ("zookies") that map directly onto the ADR-0004 contract.
    • Dev / single binary: an embedded engine — Cedar (Rust, embeddable) or a minimal built-in relationship evaluator — so the dev profile carries no external dependency, exactly as redb and in-memory coordination do for Wyrd.
  3. The production engine pick (SpiceDB vs OpenFGA) is deferred behind the trait — and it is a pick of an engine-plus-backing-store configuration, not an engine alone. Both engines satisfy the contract; what differs is what they run on, so the M3 decision weighs, together: the ADR-0008 tests applied to the backing datastore (PostgreSQL passes all three cleanly; CockroachDB fails the License test — BSL, then a proprietary enterprise license — despite being the horizontally-scaling option; Spanner is managed-only and fails Control-resilience); the write-scale ceiling of the license-clean PostgreSQL configurations against the V3/V4 write-storm targets (architecture §10); the fidelity of the zookie surface against the composite-token contract (ADR-0026); and native grant-expiry support (ADR-0030). If no shipped configuration passes both the governance tests and the scale bar, the Wyrd-backed storage adapter reserved in ADR-0026 §4 stops being an elegance reserve and becomes the scaling path — the M3 record states which. The choice remains a composition change behind the trait, not a refactor, so deferring it stays safe.

  4. Authorization lives entirely in Vord, not Wyrd. This resolves the Wyrd ADR-0018 deferred question: Wyrd is not in the authorization path. Vord consumes Wyrd's consistency token (ADR-0004), but the relationship store and policy engine are Vord's own.

  5. Relationship-based (ReBAC / Zanzibar) is the primary model, with attribute/role policy (Cedar-style ABAC / RBAC) available for the embedded engine and for policy that does not fit relations. The two coexist behind the trait.

  6. The relation schema evolves under a versioned migration discipline. A Zanzibar-style engine has a schema — the namespaces, relations, and rewrite rules the tuples are typed against — and changing it on a live store (adding a relation, renaming one, tightening a rule) is a known-hard operation, not a config edit. The Authorizer seam therefore carries a schema version; schema changes are additive-by-default and applied as explicit, reversible migrations (the engines' own schema-migration facilities, behind the trait); and a migration is a management-API operation (ADR-0021) and an event (ADR-0022). A breaking schema change is gated the way a token-format version bump is (ADR-0003): versioned and staged, never a silent in-place rewrite of how authorization is decided.

Consequences

  • The hardest correctness-and-scale primitive is adopted, not reinvented — the novelty budget is preserved for Vord's actual differentiator, exactly Wyrd's reasoning for etcd.
  • The dual backend keeps the single-binary dev profile dependency-free while production gets a hardened distributed engine — the same pluggability that lets Wyrd run redb or TiKV behind one trait.
  • The engine's native zookie composes with the ADR-0004 consistency token — but the engine's backing store is a second strongly-consistent domain that does not run on Wyrd's core, so the two keep separate version spaces. Naming that domain and defining the composite token that fences both is settled in ADR-0026, replacing the looser "aligns" this bullet once implied.
  • Running a Go service (SpiceDB / OpenFGA) behind the trait is consistent with ADR-0002: Vord-authored code is Rust; adopted engines are any language behind a seam.
  • The deferred production-engine choice is bounded and cheap to make later, because the trait makes it a composition change.
  • The authorization model can evolve in production without a flag-day: the schema is versioned and migrated explicitly behind the trait, so adding or reshaping relations is a staged, audited, reversible migration rather than a risky live edit of the decision logic — the same versioned-change discipline the token format gets (ADR-0003).