Expand description
The cooperation journal: the one replicated record of what the runtimes of a repository
said to each other. Discovery (super::registry) answers “which runtimes exist”;
the journal answers “what are they doing” — sessions, claims, handovers, reviews —
as signed events that every linked runtime stores, relays and folds into the same
state (super::state).
The guarantees are exactly these, and the tests hold each one:
- Identity. An event is written by one stream — one run of one runtime of one
node (
StreamId) — and numbered densely from 1 within it.(stream, seq)is the event’s identity everywhere. - Authenticity end to end. The origin signs every event with its node key; a relay forwards the bytes and vouches for nothing, so a relay cannot forge, alter or re-attribute an event.
- At-least-once delivery, idempotent application. An event received twice is a counted duplicate and changes nothing; the application order within a stream is its sequence, never the arrival order — an early arrival waits (bounded) for its gap.
- No loops. Replication is a comparison of high-water marks, not a flood: a runtime sends a peer only what the peer’s marks say it lacks, so an event reaches every runtime once per link and stops.
- StreamLiveness without shared clocks. Each stream carries a beat its origin raises on every heartbeat; a mark relays the beat with how long ago the sender saw it rise. A runtime computes freshness on its own monotonic clock — no two machines’ wall clocks are ever compared.
What it does not promise: exactly-once delivery, a global order across streams beyond the Lamport stamp, or consistency under partition. Two sides of a partition may both act; the fold makes the result deterministic and names the conflict (ADR 0067).
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{EventBody, Journal, SessionInfo};
use std::sync::Arc;
let a = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
let b = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000002",
"repo".into(), None).unwrap();
a.append_own(EventBody::SessionOpened { info: SessionInfo::named("s1", "docs") }).unwrap();
// replication: B tells A what it has, A answers with what B lacks, B ingests it
let missing = a.missing_for(&b.marks(), 1 << 20);
let report = b.ingest(&missing, &|_| Ok(()));
assert_eq!(report.accepted, 1);
// delivered again, it is a duplicate and nothing changes
assert_eq!(b.ingest(&missing, &|_| Ok(())).duplicate, 1);Structs§
- Appended
- An event this runtime wrote, with what its append acknowledges about the disk.
- Handover
Body - A handover as the mesh carries it: the record’s facts and its body, bounded.
- Ingest
Report - What one ingest did: how many events were stored, how many were deliveries of something already held, how many are waiting for a gap ahead of them, and what was refused and why. Every event handed in is accounted for in exactly one of those, which is what makes a sync round auditable — a peer that sends ten events and is told about ten knows nothing went missing between the wire and the store.
- Journal
- The journal of one runtime.
Mutex<BTreeMap>: a handful of events a second, and a deterministic order falls out for free. - Journal
Tallies - The journal’s counters since start: how much it holds, how much it has written,
received, absorbed, refused, held back and compacted away. They are what makes a
replication problem diagnosable from one machine — a runtime whose
receivednever rises is not linked, one whoserejectedrises is being sent something it will not take, and one whosependingstays high is missing an event ahead of what it has. - Mesh
Event - One event as the wire and the store carry it. The body is kept as JSON so that an
event of an unknown kind survives storage and relay byte-for-byte;
MeshEvent::bodyinterprets it. - Session
Info - What a session tells the mesh about itself. Every field is a public fact about work in progress; none is a path on the author’s disk, a secret or a credential.
- Stream
Id - One stream: a run of one runtime of one node.
<node 32 hex>-<runtime 16 hex>-<instance 16 hex>. The node is the machine’s key, the runtime is one checkout’s server on that machine, the instance is one process run of that server — so two worktrees on one machine are two streams, and a restart is a new stream of the same runtime. - Stream
Mark - What a runtime knows of one stream: the contiguous high-water sequence, the highest
beat with its origin’s signature, and how long ago that beat was seen to rise (
Nonewhen never).
Enums§
- Claim
Mode - Whether a claim excludes others or only tells them. Both are useful and they answer different questions: an exclusive claim is a worker saying “do not edit this while I am”, an advisory one is saying “I am here”. Exclusive is the default because a claim taken without a thought about the mode is a claim somebody expects to be respected.
- Durability
- Whether an event this runtime wrote reached this machine’s disk: what an append acknowledges, and the only thing a caller may say about it.
- Event
Body - What an event says. One vocabulary for every runtime; an event of a kind this executable does not know is stored and relayed but not interpreted, so a newer peer never breaks an older one’s replication.
- Rejection
- Why an event was not stored. A journal that silently drops what it is sent is a journal
nobody can debug, so every refusal is counted under one of these reasons and shown: a
mesh whose runtimes will not converge can be diagnosed from the numbers, because the
reasons distinguish a misconfiguration (
Repository,Untrusted) from an attack (Signature,Identity) from a limit reached (Capacity,Oversized). - Stream
Liveness - Whether a stream is speaking now, on this runtime’s clock. It is a verdict about a
stream and not about a link: a runtime reachable only through a relay is alive, and a
runtime that crashed stops beating for everyone at once.
Ownis kept apart fromLivebecause a runtime’s own stream needs no evidence — it is the thing doing the beating.
Constants§
- EVENT_
VERSION - The event format version this executable writes and reads.
- HANDOVER_
RETENTION - How long past its retention a dead stream is kept for a handover it published that nobody
has taken: a day. A handover is continuity meant to outlive the run that wrote it, so it
holds its stream longer than anything else does, but only this long: a stream held is a
slot of its node’s quota (
MAX_STREAMS_PER_NODE), and a machine whose runs each handed over something nobody took would otherwise fill its quota with the dead and have every later run refused. A handover somebody has taken holds nothing. One that lapses is still its author’s record, and publishing it again offers it again under the same id. - KNOWN_
KINDS - The event kinds this executable interprets. An event naming another kind is stored and relayed, never folded.
- MAX_
EVENTS - The most events one journal holds before dead streams are compacted away.
- MAX_
EVENTS_ PER_ NODE - The most events (held and pending) one node may occupy.
- MAX_
EVENT_ BYTES - The largest event, serialized, in bytes. A handover body is the largest payload and is bounded below this.
- MAX_
HANDOVER_ BYTES - The largest handover body an event carries, in bytes.
- MAX_
LAMPORT_ LEAD - How far ahead of this runtime’s Lamport clock a received event’s stamp may be: 2^32.
- MAX_
PENDING - The most out-of-order events held per stream while their gap is open.
- MAX_
PENDING_ TOTAL - The most out-of-order events the whole journal holds at once.
- MAX_
RELOAD_ BYTES - The most bytes of the journal file a reload reads: 64 MiB.
- MAX_
SCOPE_ PATHS - The most paths one claim or review names.
- MAX_
STREAMS - The most streams one journal tracks.
- MAX_
STREAMS_ PER_ NODE - The most streams one node may occupy: runtime and instance ids are free to invent, so a single trusted key must not be able to fill the stream table.
- NODE_
COMPACTION_ STREAMS - How many streams of one node make its dead streams due for compaction: three quarters of
MAX_STREAMS_PER_NODE. Every worktree of a machine signs with the machine’s one key and every server restart opens a new stream, so a machine reaches its quota by running, not by misbehaving — and one node alone never reaches the journal-wide bounds that otherwise start a compaction. Compacting the node at three quarters keeps the last quarter free for the runs to come. - REVIEW_
BODY_ MAX - The most a review request’s body, or a requester’s result note, carries: room for the evidence a reviewer needs without a second channel, and far under the link’s bound.
Functions§
- canonical_
json - The canonical bytes of a JSON value: object keys sorted at every depth, no whitespace. Signer and verifier compute it identically regardless of how either parser orders keys.
- causal_
key - The causal position of an event: its Lamport stamp, then its stream, then its sequence.
Type Aliases§
- Marks
- Every stream’s mark: what a sync request and answer carry.