Fief.Leadership.Store behaviour (Fief v0.1.0)

Copy Markdown View Source

The storage primitives a StateStore-colocated leadership seat exposes (design §5.1's default Leadership implementation — one row, one statement). Fief.Leadership.FromStore is the notify-loop adapter campaigning through these; any store module implementing them (Fief.Authority.Local today, the Postgres adapter at M7 — its fief_leader UPDATE is exactly try_lead/3) plugs in unchanged. This keeps the adapter generic without coupling it to Fief.Authority.Local: the adapter depends on this two-callback behaviour, never on a concrete store.

Semantics:

  • try_lead/3 — one campaign step. Grants a fresh monotonic term if the seat is empty or expired; renews (same term) if node_id already holds it; {:error, {:held, holder, term}} otherwise. Terms are per-namespace and never reissued lower — that monotonicity is what makes StateStore term validation a fence against zombie planners.
  • leader/1 — the current holder, per Fief.Leadership.leader/1 semantics, judged on the arbiter's clock.

Leadership needs none of the lease layer's clock conservatism (design §5.1): it gates only epoch/term-fenced writes, so no margins, no self-fencing — expiry here costs a leaderless interval, never safety.

Summary

Types

leader_term()

@type leader_term() :: pos_integer()

node_id()

@type node_id() :: term()

store()

@type store() :: GenServer.server() | term()

Callbacks

leader(store)

@callback leader(store()) ::
  {:ok, {node_id(), leader_term()}} | {:error, :none | :unreachable}

try_lead(store, node_id, ttl_ms)

@callback try_lead(store(), node_id(), ttl_ms :: pos_integer()) ::
  {:ok, leader_term()}
  | {:error, {:held, node_id(), leader_term()} | :unreachable}