Skip to main content

Registry

Trait Registry 

Source
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:

  • claim selects atomically, admits only an eligible pact, and rotates the retainer. It returns only a pact lifecycle::is_claimable would admit and mints a fresh retainer, all in one atomic step. A durable backend expresses this as a native, full-scan-free selection.
  • apply is load → decide → store in one atomic scope. It loads the state held by the retainer, computes the next state through the passed lifecycle decision, 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§

Source

type Error: Error

Error returned by the registry implementation.

Required Methods§

Source

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.

Source

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.

Source

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§

Source

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.

Source

fn fulfill(&self, retainer: &Retainer) -> Result<(), Self::Error>

Mark the pact as successfully fulfilled. Rejected when the retainer is not the current holder.

Source

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.

Source

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".

Implementors§