Fief.Authority.Postgres (Fief v0.1.0)

Copy Markdown View Source

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 exporting query/2 with 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.Supervisor passes this.)
  • :partitions — vnode-id validation bound, as in Fief.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 — a Fief.Sim.ManualClock (or {module, handle} provider, the Fief.Sim.Scheduler.clock/3 shape). When set, the adapter passes the virtual time as a bound parameter and every expiry comparison uses COALESCE($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. Mirrors Fief.Authority.Local.
  • :bootstrap (default true) — skip the init-time fief_meta ensure, so tests can start a holder against an unreachable database and observe the connection-error → :unreachable mapping.

Summary

Functions

Returns a specification to start this module under a supervisor.

Simulate arbiter unreachability for node_id (or :all). Test substrate.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

set_unreachable(store, node_id \\ :all, flag)

Simulate arbiter unreachability for node_id (or :all). Test substrate.

start_link(opts)