Skip to main content

Module journal

Module journal 

Source
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.
HandoverBody
A handover as the mesh carries it: the record’s facts and its body, bounded.
IngestReport
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.
JournalTallies
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 received never rises is not linked, one whose rejected rises is being sent something it will not take, and one whose pending stays high is missing an event ahead of what it has.
MeshEvent
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::body interprets it.
SessionInfo
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.
StreamId
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.
StreamMark
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 (None when never).

Enums§

ClaimMode
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.
EventBody
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).
StreamLiveness
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. Own is kept apart from Live because 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.