Status: Accepted (2026-07-24); implemented in
corium-authz with the design in
docs/design/auth.md. Extends
ADR-0012, which fixed the request-scoped authz
seam this fills in.
ADR-0012 gave the network surfaces a per-request Principal and an async
Authorizer that maps (Principal, Access) onto
Allow / AllowFiltered(ViewFilter) / Deny. It deliberately shipped only
two authorizers: permit-all, and a role→grant table configured in Rust. Neither
scales past a single operator — a role table cannot express "everyone in the
engineering group writes the databases owned by their tenant" without
enumerating it — and changing either means restarting a server.
The obvious next step is a relationship-based (ReBAC) model in the Zanzibar
tradition: relationship tuples plus derivation rules, with a Check that walks
them. The question is where the tuples live. Running an external policy
service (OpenFGA, Auth0 FGA) is a supported answer — the async Authorizer was
shaped for it — but it costs a second service, a second consistency boundary,
and a second cache layer for every deployment, including the single-cluster
ones that make up most of them.
Corium is already a database whose model is a close fit: tuples are entities,
policy is small and read-mostly, snapshots are immutable values, every change
gets a monotonic basis t, and history is retained. Storing policy in Corium
means backup, restore, fork, as-of, and the log API apply to policy for free.
Store relationship policy in an ordinary Corium database — corium_authz by
default — with a reserved, tuple-oriented schema, and answer checks from a
compiled in-memory snapshot of it.
owner, writer, viewer,
member, parent) are strings a deployment invents. The fixed vocabulary is
the existing Action/ActionClass API contract, which policy maps onto
relations through permission entities. The tuple shape stays flat and
string-keyed so it can be imported from, or exported to, an OpenFGA-like
service.CreateDatabase, edited with
transactions, and read through the same snapshot machinery. Access to it is
governed by the policy it contains.Policy keyed by its basis t and answers checks with a breadth-first walk
bounded by depth, visited goals, and a visited set that doubles as cycle
detection. Compilation happens off the request path, driven by the source's
change signal; the request path touches only in-memory indexes.authz_t appears in the decision, the
audit event, and the check-result cache key — one number that makes
decisions reproducible and invalidation free.warn on every use.PolicySource impls,
so an embedded or test deployment can hand over a database value directly.corium authz init, some grants,
and --authz-db on the servers.as-of the
authz_t in the audit line.--authz-fresh-writes), and the transactor's own snapshot makes write
admission consistent where it is serialized.Exhausted) rather
than a hang, at the price of policies deeper than the limit silently not
granting — the limit is configurable and logged.corium authz check exists to make the answer — and the matched path — inspectable before
enforcement is turned on.AllowFiltered decisions are expressible but not yet servable: no read path
applies a ViewFilter, so a peer server rejects a filtered decision with
UNIMPLEMENTED rather than returning unfiltered data. That keeps the failure
safe, and leaves the query-engine hook (ADR-0012's largest deferred piece) as
the remaining work.Authorizer is still the seam,
and a deployment can point it at OpenFGA instead, or export Corium's tuples
to one.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |