0026. The authorization store as a second consistency domain, and the composite consistency token
Context
The deployment view and the constitution have read, so far, as if there is exactly
one strongly-consistent store — the Wyrd core. That holds until Forseti is real. The
production authorization engines behind the Authorizer trait (ADR-0006) — SpiceDB
and OpenFGA — each bring their own strongly-consistent datastore (CockroachDB /
Spanner / PostgreSQL for SpiceDB; PostgreSQL / MySQL for OpenFGA), and neither runs
on Wyrd's metadata tier. So a production Vord has two strongly-consistent domains:
the Wyrd identity core (issuance, uniqueness, revocation) and the Forseti
relation store (group membership, ownership, sharing — the relation tuples).
This collides with two things earlier records left implicit, and the collision is worth an ADR before those records harden:
- "The only strongly-consistent component" is wrong. The deployment view said it; it stops being true the moment a real Zanzibar engine is deployed.
- The consistency token quietly spans two version spaces. ADR-0004 says the token
encodes Wyrd's
meta:version; ADR-0006 says Forseti's native zookie "aligns" with it. They do not share a clock. A decision needing read-your-writes across both a credential-state change (Wyrd core — e.g. a just-completed step-up) and a permission grant (Forseti store — e.g. a just-granted share) has two fences, not one, and "aligns" hid that seam.
Relation tuples are themselves irreducible strongly-consistent state, so this is not a breach of constitution rule 2 (ADR-0001) — it is a second instance of it. The honest reading of rule 2 is: each strongly-consistent domain holds only its irreducible facts. The Wyrd core holds the irreducible identity facts; the Forseti store holds the irreducible authorization facts. Rule 1 is untouched — verification (Vör) reads neither store on the hot path, and authorization defaults to bounded-staleness reads (ADR-0006), fencing against the store only where read-your-writes is demanded.
Decision
-
Name the two consistency domains. A production Vord has two strongly-consistent domains: the Wyrd identity core and the Forseti relation store. Each is tenant-sharded (rule 3), and each carries its own residency, backup, and fence; a tenant's relation store is co-resident with that tenant's home zone (ADR-0012; Wyrd ADR-0018 §2 placement). The deployment view (§7) and cross-cutting view (§8) are corrected to say two strongly-consistent domains, not one. The cross-domain backup-and-restore problem this creates — choosing one coherent recovery point across two independent version spaces — is settled separately in ADR-0040.
The boundary is engine-shaped, not store-shaped — worth stating because the obvious "fix" does not work: co-locating the identity core and the engine's backing database on one physical cluster would not merge the domains. The engine owns its schema, its transaction boundary, and its zookie version space behind its gRPC API; no transaction can span an identity write and a relation write, and the two fences stay incomparable regardless of what disk they share. Depending on the accident that both map to the same underlying storage timestamps would couple Vord to the engine's internal encoding — the trait-leak ADR-0006 forbids. Sharing a cluster is not sharing a clock; only the storage adapter of decision 4 actually unifies the domains.
-
The consistency token is composite. Refining ADR-0004: the opaque token MAY carry a fence for each domain it spans — a Wyrd
meta:versionhigh-water mark and/or a Forseti zookie. A read-your-writes-sensitive decision presents the composite token and is served only when every domain it fences is caught up to its component (conjunctive composition); otherwise it routes to the home zone or waits. A decision touching only one domain carries only that fence, so the common case stays single-fence and cheap. The token stays opaque (ADR-0004), so composition is an SDK concern and the encoding can evolve without a client break. -
The relation store is selected and sited as Vord's own dependency. The SpiceDB/OpenFGA backing database passes the dependency-selection criteria (ADR-0008) like any other pluggable backend, and its residency is the tenant's — not a separate operational island with its own geography.
-
Reserve the collapse to one domain. OpenFGA's storage interface is pluggable; a Wyrd-backed
Authorizerstorage adapter — relation tuples written to Wyrd's metadata tier through the client surface Vord already uses — would unify the two domains into one, reduce the composite token to a single Wyrd fence, and put all irreducible state behind one clock. This is reserved, not v1: it is a real build (a storage adapter and its consistency mapping), recorded here as the path that would restore the single-domain story, so the choice between two honest domains and one unified store is explicit rather than accidental.The reserve has hardened from an elegance option into a strategic one: the backing-store audit (ADR-0008) leaves the engines' horizontally-scaling configuration (SpiceDB on CockroachDB) failing the License test, while the license-clean PostgreSQL configurations carry a single-writer write ceiling against the V3/V4 storm targets — so this adapter may be the only configuration that is simultaneously ADR-0008-clean and hyperscale. The M3 pick weighs that explicitly (ADR-0006 decision 3).
Two facts keep the reserve honest. First, the adapter is substrate-agnostic: the same pluggable-storage interface could back tuples onto raw TiKV (one TSO version space) exactly as onto Wyrd (one
meta:versionspace) — the collapse never requires Wyrd, and Wyrd earns this role by the same metadata-model bar that earns it the identity core (the Wyrd-readiness section of proposal 0001). Second, the Wyrd-backed form has named prerequisites, today reserved rather than built: version-fenced reads at a revision (Wyrd ADR-0015's Option C — a zookie-pinned check is a snapshot read at that revision); the watch/changelog primitive (the engine's Watch API and cache invalidation — the same cross-project ask as the revocation backstop, where this adapter is the second consumer); and demonstrated fan-out read capacity (a relation check issues many point reads — a different load profile from directory writes, covered by the load scenarios before the adapter is trusted at scale).
Consequences
- The docs stop claiming one strongly-consistent component; the second domain is named, with its own residency, backup, and fence — the honest production picture.
- Read-your-writes across an identity change and a permission grant is correctly defined (two fences, conjunctively composed) rather than waved at by "aligns." V6 (read-your-writes on a just-granted permission) is the single-domain case; a step-up-then-authorize decision is the two-domain case the composite token now covers, and it earns its own quality scenario.
- The default authorization path is unchanged — bounded-staleness, edge-served (ADR-0006); only the fenced minority pays the cost, and only when it spans both domains.
- The collapse (Wyrd-backed authz storage) is a reserved option with a clear payoff (one domain, one fence); adopting it later is a storage-adapter build, not a contract change, because the token is opaque.
- Rule 4 holds: the Forseti store is Vord's own second domain, not anything pushed into Wyrd; the reserved collapse consumes Wyrd's storage surface, it does not alter Wyrd.
- The two-domain reality sharpens the write-path scaling story (ADR-0012): issuance writes hit the Wyrd core, grant writes hit the Forseti store, and both must shard per-tenant to meet the CIAM write-rate target.