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) ifnode_idalready 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, perFief.Leadership.leader/1semantics, 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
@type leader_term() :: pos_integer()
@type node_id() :: term()
@type store() :: GenServer.server() | term()
Callbacks
@callback leader(store()) :: {:ok, {node_id(), leader_term()}} | {:error, :none | :unreachable}
@callback try_lead(store(), node_id(), ttl_ms :: pos_integer()) :: {:ok, leader_term()} | {:error, {:held, node_id(), leader_term()} | :unreachable}