Versioned semantic write contract for durable lifecycle transitions.
A transition store receives complete, substrate-neutral proposals. It owns
the native atomic primitive used to validate and publish each proposal; core
never passes an Elixir callback, transaction handle, or
Docket.Runtime.Moment through this contract.
Version 1 defines three operations:
initialize/4creates an initial run, its schedule/supporting state, and assigned events after validating the owner-scoped graph precondition.commit_claimed/4advances a run under both the checkpoint-sequence and claim-token fences.commit_unclaimed/5applies a signal/admin proposal under an optimistic checkpoint-sequence fence and never requires a claim.
Implementations validate the complete proposal before writing. Wrong-tenant
and unknown resources both return :not_found. Validation precedes lookup,
immutable identity precedes fences, and event validation precedes
publication. A failed operation publishes no run, schedule, support, or
event changes. Events are idempotent by canonical content at
{run_id, seq}: a pre-existing identical event is accepted, and different
content at a stored sequence returns :event_conflict.
The portable error algebra is:
:not_found— the scoped graph/run is absent (including tenant concealment);:invalid_transition— malformed proposal, immutable mismatch, or invalid schedule/event identity;:stale_checkpoint— a claim-token or checkpoint fence lost; core may refetch and re-evaluate a pure unclaimed mutation;:conflict— the proposed run already exists in the owner scope;:event_conflict— an existing event sequence has different canonical content;{:retryable, reason}— an infrastructure failure that is safe to retry;{:permanent, reason}— a non-retryable infrastructure failure.
Summary
Types
Data-only claim-fenced transition proposal.
Data-only initialization proposal.
Data-only optimistic transition proposal.
Callbacks
Atomically publishes one claim-fenced transition and its assigned events.
Atomically publishes one unclaimed transition under a checkpoint CAS fence.
Atomically creates an initialized run and its assigned events.
Types
@type claimed_proposal() :: %{ run: Docket.Run.t(), expected_checkpoint_seq: non_neg_integer(), claim_token: Docket.Backend.RunStore.claim_token(), checkpoint_type: Docket.Checkpoint.type(), schedule: schedule() }
Data-only claim-fenced transition proposal.
:run— the complete next run value; itscheckpoint_seqmust equalexpected_checkpoint_seq + 1.:expected_checkpoint_seq— the committed checkpoint sequence this transition fences against.:claim_token— the non-empty claim token that must still hold the run.:checkpoint_type— the checkpoint type recorded for this transition.:schedule— the claim disposition.:retain_claimrequires a:runningrun;{:release_claim, :immediate}and{:release_claim, {:at, at}}require:running;{:release_claim, :external}requires:waiting;{:release_claim, :terminal}requires:done,:failed, or:cancelled.
@type ctx() :: Docket.Backend.ctx()
@type init_proposal() :: %{ run: Docket.Run.t(), checkpoint_type: Docket.Checkpoint.type(), wake_at: DateTime.t() }
Data-only initialization proposal.
:run— the complete initial run value; itscheckpoint_seqis at least 1.:checkpoint_type— must be:run_initialized.:wake_at— the run's first explicit schedule.
@type owner_scope() :: Docket.Backend.owner_scope()
@type result() :: {:ok, Docket.Run.t()} | {:error, error_reason()}
@type schedule() :: Docket.Backend.RunStore.schedule()
@type scope() :: Docket.Backend.scope()
@type unclaimed_proposal() :: %{ run: Docket.Run.t(), checkpoint_type: Docket.Checkpoint.type(), schedule: schedule() }
Data-only optimistic transition proposal.
Carries the same fields as claimed_proposal/0 except the claim token,
which unclaimed transitions never require, and the expected checkpoint
sequence, which commit_unclaimed/5 receives as its own argument because
it is also the compare-and-swap input core re-reads on :stale_checkpoint.
:retain_claim is not a valid unclaimed schedule.
Callbacks
@callback commit_claimed(ctx(), scope(), claimed_proposal(), [Docket.Event.t()]) :: result()
Atomically publishes one claim-fenced transition and its assigned events.
@callback commit_unclaimed( ctx(), scope(), expected_checkpoint_seq :: non_neg_integer(), unclaimed_proposal(), [Docket.Event.t()] ) :: result()
Atomically publishes one unclaimed transition under a checkpoint CAS fence.
Core may refetch and re-evaluate the pure mutation when this returns
{:error, :stale_checkpoint}. Mutation evaluation must therefore be
deterministic, bounded, and free of external side effects. No-change and
mutation errors do not invoke this callback and publish nothing.
@callback initialize(ctx(), owner_scope(), init_proposal(), [Docket.Event.t()]) :: result()
Atomically creates an initialized run and its assigned events.
Functions
@spec version() :: pos_integer()