zeph_durable/lib.rs
1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// sqlx 0.9 increases the generated Future type depth in generic async step
5// wrappers (handle.rs) beyond the default limit of 128.
6#![recursion_limit = "256"]
7
8//! Native durable execution layer for Zeph.
9//!
10//! `zeph-durable` is a Layer-0 infrastructure crate — analogous to `zeph-db` and `zeph-common` —
11//! that journals the *control flow* of an execution (individual steps, their inputs and outputs,
12//! promises, and timers) so a crashed or interrupted execution can resume at the point of failure
13//! rather than restart from scratch.
14//!
15//! # Architectural placement
16//!
17//! Consumers of this crate span several layers (`zeph-scheduler`, `zeph-subagent`,
18//! `zeph-orchestration`), so the crate must sit at Layer 0. It is a pure
19//! infrastructure primitive: it sees opaque serialized payloads, never domain types, and it
20//! MUST NOT depend on `zeph-llm`, `zeph-memory`, `zeph-core`, `zeph-sanitizer`, or any
21//! business-layer crate (INV-1). Domain meaning lives in thin adapter modules inside each
22//! consuming crate.
23//!
24//! # Module map
25//!
26//! Type-level foundation:
27//!
28//! - [`ids`] — the journal-boundary newtypes ([`ExecutionId`], [`StepId`], [`JournalSeq`],
29//! [`IdempotencyKey`], [`PromiseId`], [`TimerId`]) and the [`ExecutionKind`] discriminator.
30//! - [`journal`] — the [`Journal`] trait plus the [`JournalEntry`] / [`EntryKind`] /
31//! [`ExecutionStatus`] data model.
32//! - [`cipher`] — the [`PayloadCipher`] AEAD contract, [`PayloadAad`] binding, and the read-side
33//! `max_payload` guard. The concrete cipher lives in a consuming crate (INV-1).
34//! - [`effect`] — the [`EffectClass`] side-effect contract referenced by journal entries.
35//! - [`config`] — re-exports the pure-data [`DurableConfig`] and [`RetentionPolicy`] (which live in
36//! `zeph-config`) and owns the [`encryption_gate`] AEAD enforcement policy.
37//! - [`error`] — the crate-wide [`DurableError`].
38//!
39//! Persistence engine:
40//!
41//! - [`backend`] — the sealed [`ExecutionBackend`] trait, [`BackendCapabilities`], the
42//! [`DurableBackendEnum`] enum dispatcher, and [`LocalBackend`] (a dedicated `durable.db` pool).
43//! - [`writer`] — the background [`JournalWriter`] actor and its cloneable
44//! [`JournalWriterHandle`]: group-commit for buffered appends, flush-before-commit ACKs for
45//! exactly-once entries, and `MAX(seq)` restart resume.
46//!
47//! Execution surface:
48//!
49//! - [`step`] — the durable step typestate: [`StepDescriptor`] (with the construction-time
50//! ambiguity rule), [`StepHandle`], [`StepError`], [`StepOutcome`], and [`DurableStep`].
51//! - [`handle`] — the `&self` [`DurableContext`] front door: deterministic step ids, replay with a
52//! BLAKE3 divergence guard, the exactly-once intent/result protocol, and [`ParallelScope`] for
53//! completion-order-independent parallel batches.
54//!
55//! The promise, timer, and retention layers build on these in follow-up issues.
56//!
57//! # Schema ownership
58//!
59//! `zeph-durable` owns **no** `.sql` files and **no** `sqlx::migrate!`. All durable schema (the
60//! four `durable_*` tables) lives as numbered migration files in
61//! `zeph-db/migrations/{sqlite,postgres}/` and is applied via `zeph_db::run_migrations` against a
62//! dedicated `durable.db` pool (INV-14).
63//!
64//! # Examples
65//!
66//! ```
67//! use zeph_durable::{ExecutionId, IdempotencyKey, StepId};
68//!
69//! // Each execution gets a fresh, runtime-minted identity.
70//! let execution = ExecutionId::new();
71//!
72//! // Idempotency keys are domain-separated and deterministic for a given step.
73//! let key = IdempotencyKey::derive(execution, StepId::new(0), b"tool:read_file");
74//! assert_eq!(key, IdempotencyKey::derive(execution, StepId::new(0), b"tool:read_file"));
75//! ```
76
77mod replay;
78mod sealed;
79mod waiters;
80
81pub mod backend;
82pub mod cipher;
83pub mod config;
84pub mod effect;
85pub mod error;
86pub mod handle;
87pub mod ids;
88pub mod journal;
89pub mod promise;
90pub mod retention;
91pub mod step;
92pub mod timer;
93pub mod writer;
94
95#[doc(hidden)]
96pub use sealed::Sealed;
97
98pub use backend::{
99 BackendCapabilities, CancelOutcome, DurableBackendEnum, ExecutionBackend, ExecutionLock,
100 ExecutionSummary, LocalBackend, RedactedEntry,
101};
102pub use cipher::{
103 CipherError, EntryKindTag, PayloadAad, PayloadCipher, ensure_payload_within_limit,
104};
105pub use config::{DurableBackend, DurableConfig, EncryptionGate, RetentionPolicy, encryption_gate};
106pub use effect::{EffectClass, EffectIntentSubClass, OnAmbiguous};
107pub use error::DurableError;
108pub use handle::{DurableContext, ParallelScope};
109pub use ids::{ExecutionId, ExecutionKind, IdempotencyKey, JournalSeq, PromiseId, StepId, TimerId};
110pub use journal::{EntryKind, ExecutionStatus, Journal, JournalEntry};
111pub use promise::{DurableHandle, DurablePromise};
112pub use retention::DurableRetentionService;
113pub use step::{DurableStep, StepDescriptor, StepError, StepHandle, StepOutcome};
114pub use timer::DurableTimerService;
115pub use writer::{JournalWriter, JournalWriterHandle};