Current section
Files
Jump to
Current section
Files
CHANGELOG.md
# Changelog
All notable changes to `docket` are documented in this file. The format
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
project follows [Semantic Versioning](https://semver.org/).
## 0.2.0 — 2026-07-25
### Removed
- The deprecated 0.1.x public lifecycle composition surface:
`Docket.Backend.transaction/2`, `Docket.Backend.commit_transition/4`,
`Docket.Backend.RunStore.insert_run/5`, `commit/3`, `mutate_run/4`, and
`Docket.Backend.EventStore.append_events/4`, together with the public
types they carried (`Docket.Backend.transaction_fun/0`,
`Docket.Backend.transaction_result/0`, and the `Docket.Backend.RunStore`
types `commit_proposal/0`, `mutation/0`, `mutation_decision/0`,
`mutation_result/0`, `checkpoint_type/0`). Lifecycle writes go through
`Docket.Backend.TransitionStore` exclusively; backend-native transactions
are private implementation details.
- The deprecated PostgreSQL delegates of that surface:
`Docket.Postgres.transaction/2`, `Docket.Postgres.commit_transition/4`,
`Docket.Postgres.RunStore.insert_run/5`, `commit/3`, `mutate_run/4`, and
`Docket.Postgres.EventStore.append_events/4`.
- `Docket.Backend.LegacyTransitionStore` and the contract-version-1 /
undeclared-backend negotiation path. Contract negotiation accepts only
contract version 2 with transition version 1 and raises a specific error
for anything else, including backends that do not export `capabilities/0`.
- The transaction-composition cases from the shared backend conformance
suite. The v2 semantic transition matrix is the lifecycle write suite;
retained read, cursor, claim, and isolation assertions now seed state
through transitions.
### Changed
- `Docket.Backend.capabilities/0` and `Docket.Backend.transitions/0` are
required callbacks. `Docket.Runtime.Supervisor` validates them with the
rest of the backend contract at startup.
- The lifecycle write span is `[:docket, :lifecycle, :transition, ...]`,
renamed from `[:docket, :lifecycle, :transaction, ...]`; measurements and
metadata are unchanged.
- The transition `schedule` type is defined on
`Docket.Backend.TransitionStore`; it previously lived on
`Docket.Backend.RunStore` for the removed commit callbacks.
- There is no database schema change: 0.2.0 runs on schema version 2 exactly
as every 0.1.x release does, and no migration needs to be generated or run.
## 0.1.2 — 2026-07-24
### Added
- `Docket.Backend.TransitionStore`, a versioned, data-only lifecycle contract
with named initialization, claimed, and optimistic unclaimed operations.
Proposals never carry callbacks, transaction handles, or
`Docket.Runtime.Moment` values; backend-native transactions are private
implementation details.
- Explicit `Docket.Backend.capabilities/0` negotiation and `transitions/0`
resolution. Contract-v2 declarations are validated at startup so partially
upgraded backends fail with the missing transition callback instead of
silently selecting a path through `function_exported?/3`.
- A 0.1.x legacy adapter that routes undeclared 0.1.0/0.1.1 backends through
the existing transaction/store composition unchanged.
- A shared v2 semantic conformance matrix covering duplicate-run conflicts,
tenancy concealment, immutable-identity and fence ordering, canonical event
idempotency, schedule variants, concurrent same-fence winners, and
zero-write rejection, run against both bundled backends.
### Changed
- Core lifecycle initialization, claimed moments, and signal/admin moments now
call semantic transitions. Core no longer composes production lifecycle
writes through public arbitrary backend transactions.
- Every claimed moment on PostgreSQL now uses the fused one-statement commit
(one round trip), previously reachable only through the optional
`commit_transition/4` callback; legacy backends that export it keep it
through the compatibility adapter. Fused statement semantics are unchanged
from 0.1.1.
- Lifecycle store-span telemetry `operation` labels follow the semantic
operations (`:transition_initialize`, `:transition_commit_claimed`,
`:transition_commit_unclaimed`, `:run_fetch_for_transition`); fence losses
keep the `:stale_fence` result label.
- Signals now use scoped fetch, pure evaluation, optimistic unclaimed commit,
and a bounded refetch/re-evaluation loop only on `:stale_checkpoint`. Signal
mutation functions may run more than once and must not perform external side
effects.
- There is no database schema change: 0.1.2 runs on schema version 2 exactly
as 0.1.0 and 0.1.1 do.
### Removed
- `mix docket.gen.migration --upgrade-from-v1`. The generator only produces
the fresh install; schema upgrades are hand-written migrations documented
in each release's upgrade guide.
### Deprecated
- Public lifecycle composition through `Docket.Backend.transaction/2`,
`Docket.Backend.RunStore.insert_run/5`, `commit/3`, and `mutate_run/4`,
`Docket.Backend.EventStore.append_events/4`, and
`Docket.Backend.commit_transition/4`. They remain available through the
0.1.x compatibility adapter and are scheduled for removal in 0.2.
## 0.1.1 — 2026-07-24
### Added
- Reproducible in-memory and PostgreSQL comparison suites against pinned
LangGraph releases, including order-balanced repeated trials, synchronous
durability, queue-drain completion percentiles, row and logical-byte
accounting, invariant gates, and owned-schema cleanup.
- An optional backend-native `Docket.Backend.commit_transition/4` callback.
Backends without a fused implementation retain the portable transaction
composition.
### Changed
- PostgreSQL now commits a successful claim-fenced run transition, its
assigned events, schedule update, and immediate wake notification with one
atomic SQL statement and one database exchange. Conflict and fence failures
preserve the existing public error distinctions and transaction rollback
semantics.
- The PostgreSQL comparison harness waits for local post-commit terminal
notifications and performs one final durable read instead of injecting
repeated `inspect_run` queries into the measured workload.
## 0.1.0 — 2026-07-23
### Added
- PostgreSQL schema version 2 adds the shared admission substrate: the
singleton claim-policy gate row with per-engine admission modes,
trigger-maintained `docket_claim_schedule` membership with exact
unfinished-run counts, `docket_claim_partitions` ownership rows, and the
scoped partial indexes serving per-scope admission reads. Run creation
atomically materializes its owner partition, and rollback leaves no
partition or wake behind. The stopped migration backfills scope membership,
supports custom prefixes and rollback to host schema V1, and makes stale
schema shapes fail closed. ClaimPolicy implementations receive the quoted
schema identifiers needed to build prefix-local plans.
- `Docket.Postgres.ClaimPolicy.WindowedInterleave`, a set-based tenant-aware
claim policy under its own `windowed` admission mode: samples active scopes
in random order, admits breadth-first across scopes for statistical
cross-tenant fairness, and keeps admission sticky within a scope so runs
already in progress are re-acquired and driven to completion before new
runs start, bounding each scope's in-flight cohort at its share of the
claim batch with no configured cap. Required PostgreSQL tenancy selects
this engine. Every engine normalizes the persisted admission mode to its
own value at startup, writing only on drift; mixed-engine fleets are
last-boot-wins and unsupported.
- `Docket.BackendTests`, a source-owned shared ExUnit suite under
`test/support`. One explicit backend matrix generates identical black-box
cases for the memory and PostgreSQL bundles; external backend projects can
load the same files from a Docket checkout pinned to their supported release.
The suite covers callback completeness, transaction and cross-store
atomicity, scopes, graph/event identity and reads, claim fencing and
recovery, checkpoint sequencing, and serialized mutation safety.
- A canonical delivery and execution guarantee matrix documenting the exact
PostgreSQL transaction boundary, replayable node attempts, external-effect
idempotency requirements, retained-event export boundary, best-effort
observer/notification semantics, and consistency behavior during partitions.
- Public saved-graph and run-query reads: `Docket.fetch_graph/3` reads the exact
tenant-owned version addressed by a `Docket.GraphRef`;
`Docket.fetch_latest_graph_ref/3` returns a graph ID's newest scoped
reference; and `Docket.list_graph_versions/3` returns newest-first
`%Docket.GraphVersionPage{}` values containing lightweight
`%Docket.GraphVersion{}` metadata. `Docket.list_runs/2` returns
tenant-scoped, newest-first `%Docket.RunPage{}` values containing lightweight
`%Docket.RunSummary{}` rows, with stable `{started_at, run_id}` keyset
pagination and graph/status filters; and `Docket.fetch_latest_run/2` returns
the newest matching summary. Graph and run reads use the same explicit owner
scope; equal graph references may be saved independently by different
tenants without granting cross-tenant access.
- `Docket.fetch_event/4` and `Docket.fetch_latest_event/3` retained-event point
reads. Exact missing or pruned sequences return `:not_found`; latest returns
`{:ok, nil}` when a visible run has no retained events, preserving the
distinction from an unknown or wrong-tenant run. Both memory and PostgreSQL
backends implement the new graph, run, and event behavior callbacks.
- `Docket.list_events/3` and the generated `list_events/2` host wrapper: a
tenant-scoped, keyset-paged reader over retained durable events, backed by a
new `Docket.Backend.EventStore.list_events/4` callback with memory and
Postgres implementations. Events return in ascending sequence order (`:after_seq`
default `0`, `:limit` default `250` in `1..1000`); a wrong tenant and an
unknown run both report `{:error, :not_found}`, invalid options are rejected
before storage, and an undecodable stored row surfaces as a typed
`%Docket.Error{type: :corrupt_event_row}`. The Postgres reader observes the
page rows, MIN/MAX retention bounds, and run state from one snapshot.
Sequence gaps from persistence filtering and pruning are legal; pages are
never promised contiguous.
- `Docket.EventPage`: the public page struct returned by the retained-event
reader — `events`, `next_after_seq`, `has_more?`, `oldest_available_seq`,
`latest_available_seq`, and `latest_seq` (the owning run's latest committed
event sequence, so a fully pruned history is detectable as `latest_seq > 0`
with `latest_available_seq == nil`), all observed from one consistent
snapshot.
- Restored authored graph map interchange: `Docket.Graph.to_map/2`,
`from_map/2`, and `from_map!/2` through a strict `Docket.Graph.Serializer`.
It produces JSON-safe, string-keyed maps with an explicit `schema_version: 1`
and strictly rejects unknown keys/enums/versions and non-portable values;
`$`-prefixed keys are reserved for tagged expressions. Executable node
implementations resolve only through an explicit host `implementations:`
registry (validated for unambiguous reverse mapping), so loading a document
never creates or reaches implementation atoms; failures are typed
`Docket.Graph.Error` codes `:invalid_registry`, `:unregistered_implementation`,
and `:unknown_implementation`. Hosts own JSON encode/decode, so there is no
Jason dependency. This is the editable AUTHORED graph and carries no
`Docket.GraphRef` hash; `save_graph` still materializes node defaults and
privately encodes and hashes the effective graph, so re-saving after defaults
change may yield a different effective reference. `Docket.Run.to_map/from_map`
and the public `Docket.Graph.hash` remain removed.
- Expanded the graph contract with nested object/list schemas, reducer-backed
fields that fold prior committed values, inline node field declarations,
graph editing helpers, and post-commit domain telemetry.
- Finite runtime-owned node attempt deadlines across Local, Task, and custom
executors. Missing node timeouts inherit the host maximum; larger explicit
graph limits are rejected before execution and rescheduled without poison,
backing off exponentially up to a configurable cap. Vehicles no longer
refresh claims during node execution.
- An operator-facing PostgreSQL correctness guide covering durable status
rationale, derived queue views, scope, claims and poison, failure recovery,
configuration defaults, checkpoint/event delivery boundaries, and the
0.0.1 cutover.
- `Docket.Postgres`: the fixed Postgres backend bundle supplying its transaction
boundary, GraphStore, RunStore, and EventStore while supervising a one-for-all
dispatcher/vehicle execution subtree, optional LISTEN/NOTIFY fast path, and
explicit-policy pruner. The host owns its Repo; schema prefixes and all
operational children derive from one backend context. Dispatcher failure
terminates untracked vehicles before restart, while notifier/pruner failures
remain isolated. `notifier: :none` provides poll-only mode, retention has no
silent deletion defaults, and individual stores cannot be mixed through the
public configuration.
- `Docket.Postgres.Pruner`: explicit, periodically supervised retention with
bounded event and terminal-run batches, transaction-scoped per-schema
advisory locking, `SKIP LOCKED` candidate selection, event-to-run cascade
accounting, and low-cardinality pass telemetry. Event retention uses the
persistence timestamp and cannot exceed run retention; only terminal runs
expire. Graph cleanup deletes only unreferenced versions older than the ten
newest publications for the same owner scope and graph ID, ordered by
immutable publication time and graph hash. Referenced versions and the
newest ten scoped revisions survive regardless of age.
- `Docket.Postgres.Notifier`: the LISTEN fast path for immediate wakes. The
Postgres RunStore announces every committed wake due at or before the
database clock with `pg_notify` on the `docket_wake` channel (payload:
schema prefix, empty when unprefixed) inside the recording transaction, so
PostgreSQL exposes the notification only after commit and drops it on
rollback. The notifier holds one dedicated LISTEN connection outside the
Repo pool, reconnects and re-subscribes on its own, and turns each
prefix-matching notification into one `Docket.Postgres.Dispatcher`
immediate poll. Polling remains correctness: a lost notification or dead
listener only costs latency, and omitting the child is poll-only operation
whose wake latency is bounded by the dispatcher poll interval alone. LISTEN
needs a session-scoped connection, so behind PgBouncer transaction or
statement pooling the notifier's `:connection` must target a direct or
session-pooled endpoint.
- `Docket.Postgres.Vehicle`: the Task-per-claim execution shell that turns a
dispatcher lease into runtime progress. A drain fetches the committed run
under the lease fence, loads and compiles the exact effective graph version
node-locally exactly once (never injecting post-publication defaults),
then loops one `Docket.Runtime.Moment` per fenced
`Docket.Lifecycle.commit_moment/5`, continuing only on `:continue` and
exiting on every park after the commit released the claim. Deterministic
pre-execution compile/decode failure abandons the claim per
`abandon_claim/5`; fence loss or event-append failure discards the moment,
releases a still-current claim, and stops; everything else crashes into
claim-expiry recovery. Node execution holds no checked-out database
connection.
- `Docket.Postgres.GraphCache`: optional node-local compiled-graph cache
keyed by storage namespace, owner scope, and store-provided
`{graph_id, graph_hash}`, and validated per read
against the local generation (`:docket` module fingerprint plus each node
implementation module's beam MD5, or an injected release identity), so a
cached graph never crosses an incompatible local generation and cache loss
only affects latency. Known-incompatible versions are negative-cached, with
a bounded TTL when the stored document could not even be decoded.
- `Docket.Backend.RunStore.abandon_claim/5` and its Postgres and shared-test
implementations: the token-and-sequence fenced, non-poisoning disposition
for a claimed run whose graph the executing node cannot compile
(deployment incompatibility). A matched abandon hands the acquisition
attempt back, counts the abandon in the new `docket_runs.claim_abandons`
column (surfaced through `Docket.RunInfo` and reset by committed progress
or poison recovery), and reschedules the run at the caller's backoff; once
the configured abandon maximum is reached it poisons the run with the
distinct `max_claim_abandons_exceeded` reason instead of retrying
unboundedly.
- `Docket.Backend`: the only public durable backend substitution boundary,
directly owning the opaque context and scope types plus `transaction/2`, and
supplying compatible graph, run-aggregate, event, and supervision
capabilities (#12).
- Substrate-neutral storage ports (#12):
- `Docket.Backend.GraphStore` — explicitly owner-scoped immutable canonical
graph save, exact fetch, latest-reference fetch, and version listing;
- `Docket.Backend.RunStore` — the run-row aggregate: insert/fetch/inspect,
atomic batched due/expired claims with poison outcomes, token-guarded
claim refresh/release, mandatory token-and-sequence fenced commit, serialized
mutation, and poison recovery;
- `Docket.Backend.EventStore` — append-only persistence of already-assigned
events.
- Explicit owner scope on graph operations and
`:system | :tenantless | {:tenant, id}` scope on run/event operations;
missing tenant input never implies privileged access (#12).
- In-memory shared-test backend exercising the full bundle contract,
including overlapping-transaction publication (test support) (#12).
- Postgres substrate scaffold behind optional dependencies: versioned
migrations (`Docket.Postgres.Migration`, v01), `docket_graph_versions` /
`docket_runs` / `docket_events` schemas, and `mix docket.gen.migration`
(#10).
- Postgres `Docket.Postgres.RunStore` atomic, demand-bounded claims over
separately indexed ready and expired paths, including `SKIP LOCKED`
dispatcher concurrency, exact claim-attempt poisoning, token-guarded
claim refresh/release, and the shared internal mandatory-commit token predicate
(#20).
- Postgres atomic runtime-moment persistence: `RunStore.commit/3` enforces the
sequence-and-claim fence while applying schedule state, and
`Docket.Postgres.EventStore` idempotently appends assigned, versioned event
facts inside the same lifecycle transaction.
- Postgres storage/read foundation: the private `Docket.Postgres.Storage`
implementation supplies the shared Repo/prefix transaction context behind
`Docket.Postgres.transaction/2`; `Docket.Postgres.GraphStore`
persists immutable content-addressed graph versions with concurrent conflict
arbitration; and the private run row codec plus scoped
`RunStore.insert_run`/`fetch_run`/`inspect_run` reconstruct the exact
committed run while keeping claim tokens out of operational projections
(#26).
- Durable graphs and opaque run/event state use a private versioned
deterministic ETF codec and PostgreSQL `bytea`. Graph identity hashes the
exact stored ETF projection; relational columns retain the facts needed for
claiming, scheduling, inspection, retention, and constraints. The public
`Docket.Graph.hash` API and persistence-only Run map codec/version were
removed with no v0.0.1 decoder or dual-write path.
Graph identity is now computed privately only from effective graph bytes
produced by compiler ingest. Graph-specific canonicalization and strict
recovered collection validation live at the compiler boundary; the generic
durable codec owns only ETF envelope and durable-term recovery safety
(#26).
- `Docket.Event`: metadata-only `:checkpoint_committed` event type and the
`types/0` helper (#12).
- `Docket.Run.Failure`: durable, JSON-safe terminal failure payload
(`code`, `message`, optional `node_id`/`details`), present exactly when a
run is `:failed` (`Run.validate_failure/1`, enforced at the wire boundary
and shared backend commits) and populated by every runtime terminal-failure
path, so a failed run retains its cause independently of retained event
history (#17).
- `Docket.RunInfo`: token-free operational projection (`run`, `wake_at`,
`claimed_at`, `claim_attempts`, paired poison facts) returned by
`Docket.Backend.RunStore.inspect_run`, documenting the `inspect_run` contract
and the poisoned `await_run` typed operational halt (#17).
- `Docket.Run.durable_statuses/0`, `durable_status?/1`, and
`valid_transition?/2`: the five durable graph statuses and the locked
transition matrix, with exhaustive transition/absorbing-state tests
(#17).
- Durable retry parking: a retryable node failure commits exactly one sync
`:retry_scheduled` checkpoint at the boundary — graph status stays
`:running`, the graph step does not advance, and the run parks until the
retry deadline instead of sleeping in the dispatcher. Crash-resume
continues at the persisted attempt with the same task and idempotency
identity, never reruns committed sibling results, and only
permanent/exhausted failure becomes `:failed` with `Run.failure`
(#18).
- Durable active-superstep state on `Docket.Run`: `active_tasks` (parked
attempts via extended `Docket.Run.TaskState`: snapshot, source versions,
accumulated failures, stable identity helpers), `pending_writes`
(`Docket.Run.PendingWrite` — completed sibling results invisible until
the barrier), and `timers` (`Docket.Run.TimerState` retry deadlines)
(#18).
- `Docket.Runtime.Moment`: the substrate-neutral pre-commit value every
runtime transition now calculates — proposed `Docket.Run`, assigned
events, checkpoint type/metadata, and an explicit core-owned disposition
(`:continue` or `{:park, :immediate | :external | {:at, timestamp} |
:terminal, reason}`). Calculation performs no storage write, checkpoint
delivery, or telemetry; `Moment.checkpoint/1`/`context/2` build the
committed checkpoint value only after the driver's commit succeeds.
- Processless moment entrypoints on the shared runtime loop:
initialization calculates exactly one `:run_initialized` moment without
invoking observers, and advancement plans, dispatches, and
applies exactly one superstep per call — one commit-boundary moment
(barrier, retry park, or terminal), never a speculative multi-step
drain. Retry moments use the durable retry control state: graph status
stays `:running` under the `:retry_scheduled` checkpoint type with a
`{:park, {:at, deadline}, :retry_backoff}` disposition.
- Runtime checkpoint identity and history allocation: every moment appends
exactly one metadata-only `:checkpoint_committed` fact after its runtime
facts, allocating all event identities from `Run.event_seq` independently
of the `checkpoint_seq` run fence. Checkpoint context and metadata expose
the committed graph step plus stable multi-task retry/attempt identity,
and committed checkpoint facts emit `[:docket, :checkpoint, :committed]`
telemetry (#22).
- Pure `Docket.Runtime.RunMutation.resolve_interrupt/5` and `cancel_run/2`
graph mutations produce deterministic pre-commit moments with explicit
dispositions: cancellation is terminal, and resolution wakes immediately
unless every active attempt is parked behind a future retry deadline, in
which case it parks at the earliest deadline so a resolved run is never
dispatched before any attempt is due. Cancellation adds the sync
`:run_cancelled` checkpoint/event fact; repeated cancellation returns an
explicit unchanged result with the stored run and consumes no sequences.
- `Docket.Lifecycle`, the substrate-neutral owner of atomic run/event start,
claim-fenced moment commit, and serialized signal/event transaction recipes,
including the complete runtime-disposition to storage-schedule mapping.
- Explicit content-addressed graph publication through `save_graph`, returning
a `Docket.GraphRef`; `start_run` accepts only that saved reference and never
writes the graph store.
- Locked versioning amendment: publication materializes node schema defaults
into the effective canonical graph before hashing. Runs pin only graph ID and
hash; later local compilation validates but never injects newly introduced
defaults. The vehicle contract requires local compilation once per claim and
reuse for its drain. Compiler ABI and distributed artifacts are deliberately
not durable run identity; the vehicle shell is specified separately.
- Unbounded cyclic graphs are valid. `max_supersteps` remains an optional graph
policy or host/runtime safety limit rather than a publication requirement.
- Durable operational facade functions: `start_run`, `fetch_run`,
`inspect_run`, `resolve_interrupt`, `cancel_run`, `retry_poisoned_run`, and
bounded `await_run`, with strict tenantless/required scope resolution and a
poisoned-run operational halt.
- `Docket.Checkpoint.Observer`, configured through separate
`checkpoint_observers:`, for isolated best-effort notification only after a
durable commit. The `0.0.1` host-owned `checkpoint:` committer is no longer
a production configuration path.
### Changed
- Backend-specific startup configuration is nested as
`backend: {BackendModule, options}`. Core runtime policy remains top-level,
unknown flat options fail early, and an optionless backend may still use its
module directly.
- PostgreSQL wall-clock injection is now a testing-only, top-level,
instance-owned option shared by facade operations, synchronous claims, and
vehicles; nested and per-call overrides are rejected or ignored. Runtime
clocks validate `DateTime` results, and the ClaimPolicy boundary normalizes
`now` to UTC microsecond precision before every implementation receives it.
- The main README now leads with the database-free graph quickstart, followed
by complete Docket.Postgres setup covering dependencies, migration,
supervision, retention, publication, and durable execution. Release
documentation consistently describes the backend-owned v0.1.0 lifecycle,
processless waiting, required tenant scope, deterministic testing modes, and
current production vehicles. HexDocs keeps the linked onboarding,
operational, migration, and example guides concise.
- Runtime dispatch now executes every node selected for a superstep
concurrently against the same committed snapshot, then collects results in
deterministic activation order before crossing the existing update barrier.
- Postgres run insertion, moment commit, signal mutation, and poison recovery
emit `pg_notify` on `docket_wake` within the same transaction whenever the
recorded wake is due at or before the database clock. Claim release and
abandonment stay silent so launch-failure retries keep the poll interval as
their backoff.
- Postgres claim, refresh, release, steal, and poison operations no longer
rewrite promoted `Docket.Run.updated_at`. Dedicated operational timestamps
now carry those transitions, so `fetch_run` remains the exact last committed
graph-run document while `inspect_run` reports delivery state (#26).
- Locked the final v0.1.0 production boundary to one required durable backend.
The `0.0.1` host-owned `checkpoint:` driver and its `run`, `resume`, and live
`get_run` facade were removed after backend assembly and deterministic
backend testing landed. Node/graph/schema/reducer/executor APIs
and Postgres-free `Docket.Test` helpers remain public; Run persistence is a
backend-private boundary and the supported adopter path is drain-and-cut-over.
- Renamed the unreleased durable runtime configuration from `storage:` to
`backend:` because the configured `Docket.Backend` owns the transaction,
graph, run, event, context, and supervision capabilities rather than naming
one storage implementation. The pre-release `storage:` option is no longer
accepted.
- One `docket` package: `Docket.Postgres.*` compiles only when the host
supplies optional `ecto_sql`/`postgrex`; the core keeps no hard Postgres
dependency (#8/#9).
- Version bumped to `0.1.0-dev` (#8).
- `Docket.Node` documentation clarifies the four failure-signaling forms and
their identical normalization (#12).
- `Docket.Run` carries terminal failure and active-superstep state directly.
Explicit durable validation rejects the private `:created` sentinel,
failure/status disagreement, malformed collections, and inconsistent active
task/timer coverage before private backend encoding and after recovery
(#17/#18); the persistence boundary was later replaced by the private
backend codec.
- The dispatcher executes exactly one node attempt per invocation; retry
waiting moved out of the durable path into shell parking (supervised
Runtime: timer wake that keeps serving reads during backoff; inline test
runtime: the injected `:sleeper` serves each committed park's wait).
Retried attempts' `:node_failed` events now ride the `:retry_scheduled`
checkpoint that recorded them instead of the eventual barrier checkpoint
(#18).
- Postgres v01 schema finalized to spec revision 8 (amended in place
before the 0.1.0 release): `claim_attempts` / `poisoned_at` / `poison_reason`
replace `attempts` / `operational_status` / `operational_error`, and
`started_at` is non-null. Lifecycle CHECK constraints make the
status/schedule/claim/poison tuple
authoritative even for raw claim SQL; a composite delete-restricted FK
keeps every retained run's exact graph version, and a delete-cascaded FK
keeps events from outliving their run. The single `wake_at` and
`operational_status` indexes become the ready-unclaimed `(wake_at, id)`,
expired-claim `(claimed_at, id)`, and poison-introspection partial
indexes behind positive dispatch eligibility (`status = 'running' AND
poisoned_at IS NULL`) (#19).
- The Postgres v01 schema makes graph versions tenant-owned without
adding a publication table: generated non-null scope keys bind runs to
`(scope_key, graph_id, graph_hash)` through a delete-restricted composite
foreign key.
- The runtime loop separates pure moment production from commitment. Durable
backends commit moments transactionally before best-effort observation;
processless helpers return read-only checkpoint values for assertions.
Checkpoints never participate in lifecycle decisions.
### Removed
- `docket_checkpoints` table and its Ecto schema: `docket_runs.checkpoint_seq`
is the run fence, recovery reads the run row, and retained events provide
history (#11).
- Loader acceptance of version-1 run documents and the serialized `created`
status (#17).
## 0.0.1 — 2026-07-08
Initial core runtime line (in-process, storage-free): typed graph
construction and compiler, superstep runtime with checkpoints and interrupts,
host-owned checkpoint committer, reducers, local and task executors,
telemetry, and deterministic test helpers.