Skip to main content

sim_lib_journal/
lib.rs

1//! Domain-free atomic journal behavior over content-addressed objects.
2//!
3//! A journal publishes immutable objects before making entries visible and
4//! advances its head through one fenced compare-and-swap transition. Backends
5//! implement [`JournalBackend::admit`]; callers use [`Journal`] so the gapless,
6//! closure, redelivery, and replay laws are enforced once.
7
8mod backend;
9mod datum_codec;
10mod entry;
11mod head;
12mod host;
13mod lease;
14mod memory;
15mod native_codec;
16mod object;
17mod persistent;
18mod projection;
19mod replay;
20mod snapshot;
21mod verify;
22
23pub use backend::{Admission, JournalBackend, StoredState};
24pub use entry::JournalEntry;
25pub use head::JournalHead;
26pub use host::HostDirJournalBackend;
27pub use lease::Lease;
28pub use memory::MemoryBackend;
29pub use native_codec::{
30    BackendCapabilities, EntryLocation, EntryNamespace, Failpoint, NativeFormatId,
31    NativeStateEnvelope, VerifiedNativePrefixRef,
32};
33pub use object::JournalObject;
34pub use persistent::{
35    PersistentObjectStore, PersistentSemanticObjects, StoreError, StoredDatumRef,
36};
37pub use projection::{DirProjection, ProjectionRow, TableProjection};
38pub use replay::{Replay, replay};
39pub use snapshot::VerifiedSnapshot;
40pub use verify::{JournalError, Verification};
41
42use sim_kernel::{ContentId, Symbol};
43
44/// The single state-machine implementation shared by every backend.
45pub struct Journal<B> {
46    backend: B,
47}
48
49impl<B: JournalBackend> Journal<B> {
50    /// Wraps a backend. Durable production backends must report write safety.
51    pub fn new(backend: B) -> Self {
52        Self { backend }
53    }
54
55    /// Obtains a new fencing generation, invalidating every older lease.
56    pub fn acquire_lease(&self) -> Result<Lease, JournalError> {
57        self.backend.acquire_lease()
58    }
59
60    /// Returns the current verified head.
61    pub fn head(&self) -> Result<Option<JournalHead>, JournalError> {
62        let state = self.backend.read_state()?;
63        verify::verify_state(&state).map(|v| v.head)
64    }
65
66    /// Atomically publishes objects and an ordered entry batch under one fence.
67    ///
68    /// Exact redelivery is a no-op. Any conflicting delivery, missing/corrupt
69    /// payload, stale fence, sequence gap, or wrong predecessor is rejected.
70    pub fn publish(
71        &self,
72        lease: &Lease,
73        expected: Option<&JournalHead>,
74        objects: Vec<JournalObject>,
75        entries: Vec<JournalEntry>,
76    ) -> Result<JournalHead, JournalError> {
77        if entries.is_empty() {
78            return Err(JournalError::EmptyBatch);
79        }
80        let before = self.backend.read_state()?;
81        verify::verify_state(&before)?;
82        verify::verify_batch(&before, expected, &objects, &entries)?;
83        let head = self.backend.admit(Admission {
84            fence: lease.fence,
85            expected: expected.cloned(),
86            objects,
87            entries: entries.clone(),
88        })?;
89        Ok(head)
90    }
91
92    /// Reads and verifies the complete journal closure.
93    pub fn verify(&self) -> Result<Verification, JournalError> {
94        verify::verify_state(&self.backend.read_state()?)
95    }
96
97    /// Replays verified entries in sequence order.
98    pub fn replay(&self) -> Result<Replay, JournalError> {
99        replay(self.backend.read_state()?)
100    }
101
102    /// Reads and verifies one internally consistent semantic snapshot.
103    ///
104    /// Consumers that must reduce entries together with their payload values
105    /// use this method so both come from the same backend read. The returned
106    /// value exposes no physical storage identity or mutable backend state.
107    pub fn verified_snapshot(&self) -> Result<VerifiedSnapshot, JournalError> {
108        VerifiedSnapshot::from_state(self.backend.read_state()?)
109    }
110
111    /// Creates a detached, read-only table projection.
112    pub fn table_projection(&self) -> Result<TableProjection, JournalError> {
113        Ok(TableProjection::from_verification(self.verify()?))
114    }
115
116    /// Creates a detached, read-only directory projection.
117    pub fn dir_projection(&self) -> Result<DirProjection, JournalError> {
118        Ok(DirProjection::from_verification(self.verify()?))
119    }
120
121    /// Convenience constructor for an entry with an open kind symbol.
122    pub fn entry(
123        sequence: u64,
124        previous: Option<ContentId>,
125        kind: Symbol,
126        payloads: Vec<ContentId>,
127    ) -> JournalEntry {
128        JournalEntry::new(sequence, previous, kind, payloads)
129    }
130}
131
132#[cfg(test)]
133mod tests;