The storage contract: coarse, semantic, individually-atomic operations.
Engine semantics that must be transactional (admission control inside claim,
workflow settlement inside ack) live inside single operations, so adapters
guarantee atomicity per call. Two adapters ship: Belay.Storage.Memory
(serialized GenServer, deterministic — the test/simulation reference) and
Belay.Storage.Postgres.
All time-dependent operations take now explicitly; adapters never read the
wall clock.
Summary
Types
@type now() :: DateTime.t()
@type queue_spec() :: %{ queue: String.t(), local_limit: pos_integer(), limit_min: pos_integer() | nil, global_limit: pos_integer() | nil, rate: %{ allowed: pos_integer(), period: pos_integer(), resource: String.t() | nil, estimate: pos_integer() } | nil, partition: {:input | :meta, String.t()} | nil, manual: boolean() }
@type ref() :: term()
@type settle_result() :: %{ job: Belay.Job.t(), released: [Belay.Job.t()], cancelled: [Belay.Job.t()] }
Callbacks
@callback ack(ref(), Belay.Job.t(), outcome(), now()) :: {:ok, settle_result()} | {:error, :stale}
@callback child_spec({Belay.Config.t(), keyword()}) :: Supervisor.child_spec()
@callback children(ref(), integer()) :: {:ok, [Belay.Job.t()]}
@callback claim( ref(), queue_spec(), demand :: pos_integer(), node_id :: String.t(), lease_ttl_ms :: pos_integer(), now() ) :: {:ok, [Belay.Job.t()]}
@callback debit_rate( ref(), bucket :: String.t(), period :: pos_integer(), amount :: integer(), now() ) :: :ok
@callback get_by_unique_key(ref(), String.t()) :: {:ok, Belay.Job.t()} | :error
@callback get_job(ref(), integer()) :: {:ok, Belay.Job.t()} | :error
@callback insert_jobs(ref(), [map()], now()) :: {:ok, [Belay.Job.t()]}
@callback list_jobs(ref(), map()) :: {:ok, [Belay.Job.t()]}
@callback prune_jobs( ref(), state :: String.t(), now(), keep_seconds :: integer(), limit :: pos_integer() ) :: {:ok, non_neg_integer()}
@callback request_cancel(ref(), integer(), now()) :: {:ok, %{ status: :cancelled | :requested | :noop, cancelled: [Belay.Job.t()], released: [Belay.Job.t()] }}
@callback resettle_parents(ref(), now()) :: {:ok, [Belay.Job.t()]}
@callback retry(ref(), integer(), now()) :: {:ok, Belay.Job.t()} | {:error, :not_retryable | :not_found}
@callback workflow_jobs(ref(), String.t()) :: {:ok, [Belay.Job.t()]}