The Postgres Authority adapter (implementation.md §3.1): Fief.StateStore
plus the Fief.Leadership.Store primitives, on five namespace-keyed tables
in the user's database (shipped as Fief.Authority.Postgres.Migrations).
Shape
Raw SQL through the user's Ecto repo — repo.query(sql, params), duck-typed
on opts[:repo], no Ecto schemas and no Ecto API calls, so this module
compiles without ecto. The supervised child (started by Fief.Supervisor
under the instance's authority name) is a small config holder: it owns
%{repo, ns, partitions, ...} and nothing else. Every operation runs in the
caller's process — pooling is the user repo's business — after a cheap
config read (a named-ETS lookup when the holder is registered, a call when
the handle is a pid).
Every statement is a single statement, never a multi-statement
transaction (design §4: PgBouncer transaction mode is the native grain).
cas_assign/cas_settle are single writes whose data-modifying CTE on
fief_meta checks-and-bumps the namespace epoch and validates/ratchets the
leader term atomically with the table write; a failed guard writes nothing.
Lease and leadership expiry compare against the database's clock in the
same statement — node clocks never appear in SQL (design §6.4).
Options
:repo(required) — the user's Ecto repo module (anything exportingquery/2with Ecto's return shape). Must be started before the fief instance.:instance— the instance name; its stringification is the namespace (ns) every statement carries. Instances sharing one database are fully independent coordination domains. (Fief.Supervisorpasses this.):partitions— vnode-id validation bound, as inFief.Authority.Local.:name— registered name (the instance's authority name); also names the protected ETS table the config is served from.
Node ids are atoms in the API and text in SQL (real node ids are node-name atoms; the conversion set is bounded by cluster membership).
{:error, :unreachable} is connection-level failure (DBConnection
connection errors). Database errors that are not connectivity — constraint
violations, SQL errors — are raised, never disguised as protocol results.
The holder's init also ensures the namespace's fief_meta row exists (epoch
0), one idempotent statement — so every later epoch op is a plain guarded
UPDATE. An unreachable database at instance start therefore fails the start
loudly, consistent with the M3 join posture (join-time validation is fatal
at init anyway).
Test seams (undocumented, test-substrate only — same status as Local's)
:sim_clock— aFief.Sim.ManualClock(or{module, handle}provider, theFief.Sim.Scheduler.clock/3shape). When set, the adapter passes the virtual time as a bound parameter and every expiry comparison usesCOALESCE($n::timestamptz, now())— the identical statement runs in production (NULL param) and under test, but the arbiter clock becomes deterministic and steppable (the contract suite's boundary-exact lease tests).set_unreachable/3— per-node (or:all) unreachability flag in the config holder; node-scoped operations return{:error, :unreachable}without touching the database. MirrorsFief.Authority.Local.:bootstrap(defaulttrue) — skip the init-timefief_metaensure, so tests can start a holder against an unreachable database and observe the connection-error →:unreachablemapping.
Summary
Functions
Returns a specification to start this module under a supervisor.
Simulate arbiter unreachability for node_id (or :all). Test substrate.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Simulate arbiter unreachability for node_id (or :all). Test substrate.