pub struct Ledger { /* private fields */ }Expand description
The append-only ledger of one checkout: the canonical record of what happened, written only by Majordomus and in the order the commands ran.
use majordomus_cli::session::Ledger;
use std::path::Path;
assert!(Ledger::of(Path::new("/r")).path().ends_with("ledger.jsonl"));Implementations§
Source§impl Ledger
impl Ledger
Sourcepub fn of(root: &Path) -> Self
pub fn of(root: &Path) -> Self
The ledger of the repository rooted at root.
use majordomus_cli::session::Ledger;
use std::path::Path;
assert!(Ledger::of(Path::new("/r")).path().ends_with("ledger.jsonl"));Sourcepub fn at(path: impl Into<PathBuf>) -> Self
pub fn at(path: impl Into<PathBuf>) -> Self
The ledger at an explicit path, for a test or a store that is not a repository root.
use majordomus_cli::session::Ledger;
let dir = tempfile::tempdir().unwrap();
let ledger = Ledger::at(dir.path().join("elsewhere.jsonl"));
assert!(ledger.read().events.is_empty(), "a ledger nobody has written is empty");Sourcepub fn path(&self) -> &Path
pub fn path(&self) -> &Path
Where it is. A reader that wants to name the file in a diagnostic asks here rather than composing the path a second time.
use majordomus_cli::session::Ledger;
use std::path::Path;
assert!(Ledger::of(Path::new("/r")).path().starts_with("/r"));Sourcepub fn append(
&self,
event: &LedgerEvent,
vocabulary: &Vocabulary,
envelope: &Envelope,
) -> Result<(), LedgerError>
pub fn append( &self, event: &LedgerEvent, vocabulary: &Vocabulary, envelope: &Envelope, ) -> Result<(), LedgerError>
Append one rendered line, under an exclusive lock, opened O_APPEND.
The local half is never tracked, so a second worktree of one repository starts
without it and the first write creates the directory — the same thing
mj_ledger_append does, for the same reason.
use majordomus_cli::session::{Envelope, Ledger, LedgerEvent, Vocabulary};
let dir = tempfile::tempdir().unwrap();
let ledger = Ledger::at(dir.path().join("ledger.jsonl"));
let v = Vocabulary::parse("version: 1\nevents:\n - id: session.started\n requires: [owner]\n public: true\n").unwrap();
let envelope = Envelope {
ts: "2026-09-11T11:02:35Z".into(), head: "1a651e64".into(),
branch: "master".into(), by: "majordomus/0.5.0".into(), session: None,
};
ledger.append(&LedgerEvent::new("session.started").with("owner", "korczis"), &v, &envelope).unwrap();
ledger.append(&LedgerEvent::new("session.started").with("owner", "korczis"), &v, &envelope).unwrap();
let read = ledger.read();
assert_eq!(read.events.len(), 2);
assert!(read.corrupt.is_empty());Sourcepub fn read(&self) -> LedgerRead
pub fn read(&self) -> LedgerRead
Read the whole ledger, reporting what did not parse rather than failing on it.
An absent ledger reads as empty: a fresh clone has never run the lifecycle, and that is not a fault.
use majordomus_cli::session::Ledger;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("ledger.jsonl");
std::fs::write(
&path,
"{\"ts\":\"a\",\"event\":\"session.started\"}\nnot json at all\n{\"ts\":\"b\",\"event\":\"session.closed\"}\n{\"ts\":\"c\",\"event\"\n",
).unwrap();
let read = Ledger::at(&path).read();
assert_eq!(read.events.len(), 2, "everything that parsed");
assert_eq!(read.corrupt.len(), 2, "and both lines that did not, by number");
assert_eq!(read.corrupt[0].line, 2);
assert_eq!(read.corrupt[1].line, 4, "a truncated tail is one bad line, not a failed read");
// absence is an answer
assert!(Ledger::at(dir.path().join("nothing.jsonl")).read().events.is_empty());