pub trait JournalStore {
// Required methods
fn lock_exclusive(&self, lock_path: &Path) -> Result<Box<dyn JournalLock>>;
fn append_line(&self, path: &Path, line: &str) -> Result<()>;
fn read_lines(&self, path: &Path) -> Result<Vec<String>>;
fn read(&self, path: &Path) -> Result<Option<Vec<u8>>>;
fn write_atomic(&self, path: &Path, bytes: &[u8]) -> Result<()>;
fn list_dir(&self, dir: &Path) -> Result<Vec<String>>;
}Expand description
Durable, atomic, lockable storage for the release journal — the seam that keeps the event-sourced journal (ADR-0003) testable without touching the real filesystem, while pinning down the atomicity discipline its production impl must honor.
The append-then-apply contract (ADR-0003 §2, borrowed from octl-core) maps
onto these operations:
Self::append_linefsyncs the event so it is durable before the reducer applies it — a crash between append and apply replays as a clean no-op-or-apply, because the journal (read back bySelf::read_lines) is the single source of truth.Self::write_atomicpersists the derived manifest via temp-file → flush → atomic rename → directory fsync, so a torn write can never leave a half-written manifest (it is disposable and rebuildable regardless).Self::lock_exclusiveenforces a single active cut per repo (aflockon the releases-dir.lock): a concurrent cut/resume fails fast rather than corrupting a run.
The port is deliberately path-driven (the journal computes paths via
crate::release::journal::JournalPaths); the impl adds no policy, only the
durability guarantees documented per method.
Required Methods§
Sourcefn lock_exclusive(&self, lock_path: &Path) -> Result<Box<dyn JournalLock>>
fn lock_exclusive(&self, lock_path: &Path) -> Result<Box<dyn JournalLock>>
Take the single-active-cut exclusive lock at lock_path (creating parent
directories as needed). The returned guard holds the lock until dropped.
Err with io::ErrorKind::WouldBlock when another holder is active, so
the caller can fail fast and name the active run.
Sourcefn append_line(&self, path: &Path, line: &str) -> Result<()>
fn append_line(&self, path: &Path, line: &str) -> Result<()>
Append line (one serialized event, no embedded newline — the store adds
the single trailing \n) to the JSONL file at path, creating the file
and parent directories if absent, and fsync so it is durable before
returning. This is the append half of append-then-apply.
The write must be atomic at line granularity: on return the line is
either fully present or not present, never truncated. The production impl
opens with O_APPEND, write_alls the line + newline, and fsyncs the file
and — when it created the file or a parent directory — the containing
directory, so a newly created RunCreated survives power loss (fsyncing
only the file leaves the new directory entry non-durable). A torn final
line from a hard kill mid-write is still possible in theory; recovering it
(truncate-to-last-good under the lock) is a documented follow-up, not part
of this port yet — crate::release::journal::read_events currently
rejects any malformed line.
Sourcefn read_lines(&self, path: &Path) -> Result<Vec<String>>
fn read_lines(&self, path: &Path) -> Result<Vec<String>>
Read every line of the JSONL file at path. Ok(vec![]) when the file is
absent (a not-yet-written journal is empty, not an error).
Sourcefn read(&self, path: &Path) -> Result<Option<Vec<u8>>>
fn read(&self, path: &Path) -> Result<Option<Vec<u8>>>
Read the full contents of the (atomically-written) file at path, or
Ok(None) when it is absent. Used for the torn-free fast-path read of the
manifest.json cache; unlike Self::read_lines it returns raw bytes.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".