pub trait Registry {
type Error: Error;
// Required methods
fn claim(
&self,
dockets: &[&str],
now: Timestamp,
) -> Result<Option<Claim>, Self::Error>;
fn lease_millis(&self) -> u64;
fn apply(
&self,
retainer: &Retainer,
transition: &Transition<'_>,
) -> Result<(), Self::Error>;
// Provided methods
fn heartbeat(
&self,
retainer: &Retainer,
now: Timestamp,
) -> Result<(), Self::Error> { ... }
fn fulfill(&self, retainer: &Retainer) -> Result<(), Self::Error> { ... }
fn breach(&self, retainer: &Retainer) -> Result<(), Self::Error> { ... }
fn release(
&self,
retainer: &Retainer,
reclaimable_at: Timestamp,
) -> Result<(), Self::Error> { ... }
}Expand description
The Registry is the durable lifecycle-authority port: it preserves pacts and decides claim,
lease, and settlement authority over them. It is not itself the pure state machine — the pure,
colorless machine is lifecycle, which every backend composes over; a Registry
implementation is the I/O-owning port that persists that machine’s states and enforces its
authority (the async twin is [AsyncRegistry]).
A backend implements three primitives — a native claim selection, a
lease_millis accessor, and an atomic apply
transition port — and inherits heartbeat, fulfill, breach, and release as defaults over apply.
The obligations mirror the async binding exactly:
claimselects atomically, admits only an eligible pact, and rotates the retainer. It returns only a pactlifecycle::is_claimablewould admit and mints a fresh retainer, all in one atomic step. A durable backend expresses this as a native, full-scan-free selection.applyisload → decide → storein one atomic scope. It loads the state held by the retainer, computes the next state through the passedlifecycledecision, and stores it atomically; a non-atomic load-to-store window lets two workers both write and breaks exactly-once and retainer fencing.- Reclaim — not mere expiry — rotates settlement authority. A holder whose lease lapsed but
whose pact no one reclaimed is still the current holder and can still settle; authority rotates
only when the pact is actually reclaimed (or released). A transition against a pact the retainer
no longer holds surfaces as a not-current-holder error through the backend’s
Error.
pacta-conformance proves the behavioral half of this (eligibility, transitions, lapse/reclaim
rotation, and — via its contention checks — at-most-once claim and settlement). It does not
prove the query-shape obligation that claim is full-scan-free; a sequential functional suite
cannot observe query cost, so that stays a backend obligation established by review.
Time is injected: claim and heartbeat take the
current time as a parameter, and the registry reads no ambient clock. Settlement takes no time
because a rotated retainer already tells a stale holder apart from the current one.
The backend type itself need not be Send or Sync. A single-threaded backend may stay
local; a caller that moves or shares a registry across threads adds Send + Sync at that
concurrency boundary. In particular, generic code that needs thread shareability must write
R: Registry + Send + Sync rather than treating it as implied by this trait.
Required Associated Types§
Required Methods§
Sourcefn claim(
&self,
dockets: &[&str],
now: Timestamp,
) -> Result<Option<Claim>, Self::Error>
fn claim( &self, dockets: &[&str], now: Timestamp, ) -> Result<Option<Claim>, Self::Error>
Claim a pact for execution from one of the requested dockets, using now
to set the new lease and to reclaim any pact whose lease already expired
without settlement — a lapse, realized through this normal claim path.
Obligation: select — atomically — only a pact lifecycle::is_claimable would admit
(available, a lapsed hold, or a deferred pact past its instant; never a settled one) and mint
a fresh retainer, so reclaiming rotates authority and the prior holder can no longer settle. A
durable backend expresses this as a native, full-scan-free selection (for example SQL
SKIP LOCKED), not by loading the whole docket to filter in memory.
Sourcefn lease_millis(&self) -> u64
fn lease_millis(&self) -> u64
The backend’s lease duration in milliseconds, used by heartbeat
to compute the extended lease. Lease sizing is the backend’s; the contract supplies the
mechanism, not a constant.
Sourcefn apply(
&self,
retainer: &Retainer,
transition: &Transition<'_>,
) -> Result<(), Self::Error>
fn apply( &self, retainer: &Retainer, transition: &Transition<'_>, ) -> Result<(), Self::Error>
Apply a lifecycle transition to the pact held by retainer, within the backend’s own
atomic scope. transition is the pure kernel decision (a lifecycle on_X): the
backend loads the held state, computes the next state through transition, and applies
it atomically — it never decides the transition itself, so the lifecycle semantics stay
single-sourced in the kernel and cannot drift. A transition applied against a pact the
retainer no longer holds resolves to a not-current-holder error (the kernel’s
NotCurrentHolder surfaces through transition). This is
the one transition port; the four transition operations below are provided over it.
The backend owns how the scope is made atomic (a lock, a transaction, a native conditional write, or compare-and-set); the contract mandates no concurrency-control mechanism.
Provided Methods§
Sourcefn heartbeat(
&self,
retainer: &Retainer,
now: Timestamp,
) -> Result<(), Self::Error>
fn heartbeat( &self, retainer: &Retainer, now: Timestamp, ) -> Result<(), Self::Error>
Extend the retainer’s lease using now. A heartbeat presented after the
lease already expired is rejected: the holder must claim again rather than
revive a lapsed lease, so two holders never both hold settlement authority.
Sourcefn fulfill(&self, retainer: &Retainer) -> Result<(), Self::Error>
fn fulfill(&self, retainer: &Retainer) -> Result<(), Self::Error>
Mark the pact as successfully fulfilled. Rejected when the retainer is not the current holder.
Sourcefn breach(&self, retainer: &Retainer) -> Result<(), Self::Error>
fn breach(&self, retainer: &Retainer) -> Result<(), Self::Error>
Mark the pact as breached. Rejected when the retainer is not the current
holder. Shares the settlement transition with fulfill — the
lifecycle records that the obligation concluded, not which outcome concluded it.
Sourcefn release(
&self,
retainer: &Retainer,
reclaimable_at: Timestamp,
) -> Result<(), Self::Error>
fn release( &self, retainer: &Retainer, reclaimable_at: Timestamp, ) -> Result<(), Self::Error>
Release the claim without concluding the obligation, making the pact
reclaimable again only at or after reclaimable_at.
This is non-terminal: unlike fulfill and
breach, it settles nothing — the pact is left to be
attempted again. The registry computes no delay: reclaimable_at is a
consumer-supplied instant, honored exactly as the injected now is honored
(compared, never computed), so backoff policy stays with the caller and Pact
carries no delay. A reclaimable_at at or before now makes the pact immediately
claimable, as a voluntary lapse. Release rotates authority like a lapse, so the
prior retainer can no longer settle or heartbeat. Rejected when the retainer is
not the current holder.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".