Expand description
The cross-process journal frame envelope
(FrameHeader + FrameKind):
the typed header (kind / committed_at / run identity + totals) in front of an opaque
change payload wherever journal entries live — the hctree master’s hct_journal
payload column and the S3 archival segments (rindle-backup). Pure bytes, no deps.
See src/journal_frame.rs, designs-implemented/211-HCTREE-LEADER-CDC-DESIGN.md §4.1, and
designs-implemented/212-JOURNAL-S3-SHIPPING-DESIGN.md §2.2.
The cross-process journal frame envelope — the typed header in front of every
opaque change payload (designs-implemented/211-HCTREE-LEADER-CDC-DESIGN.md §4.1).
Today’s _rindle_change_log carries kind, run_id, run_rows, rows_cum, and
committed_at as columns, and the plane reads them as columns: the epoch fence
is a run_id point lookup at the cursor, C_grace retention reads committed_at,
and the lag-shed fold groups on kind and accounts with the run totals. Under 211
the master’s log is hct_journal(cid, query, snapshot, logptr) — no home for those
columns — so they move into this header, encoded in front of the payload bytes
handed to sqlite3_hct_journal_leader_commit. The same envelope is what the S3
shipping plane (rindle-backup, designs-implemented/212-JOURNAL-S3-SHIPPING-DESIGN.md §2.2)
frames into archival segments, so one decode serves the sender, the shipper, and
restore replay.
Deliberately not in the header:
- cid — assigned by the engine inside
leader_commit, so the producer cannot know it at encode time. The container carries it (the journal’scidcolumn; the segment framing). Keeping it out also means there is exactly one source of truth. - chunk_seq — one HCTree cid is still one atomic transaction, but its opaque payload may
use the versioned chunk container documented at
encode_payload_chunks. Chunk sequence is implicit in container order, so it does not belong in the transaction header.
Wire layout (all integers little-endian):
u8 header format version (1 = legacy, 2 = chunk container — see the constants)
u8 kind (0 = rows, 1 = ddl, 2 = empty; 3 reserved — see [`FrameKind`])
u8 flags (bit 0: run_id present, bit 1: run totals present; others reserved)
i64 committed_at — commit wall clock, unix millis, stamped by the capture path
(identical across a run, like today's column); 0 = unknown (empty frames)
[flag bit 0] u8 run_id byte-length, then that many UTF-8 bytes
[flag bit 1] u64 run_rows, u64 rows_cum — precomputed in the capture path (the
in-txn `stamp_run_totals` UPDATE is impossible once the payload is fixed at
`leader_commit` time, 211 §4.1)
… payload — version-dependent:
v1: ONE opaque legacy blob (in practice a UTF-8 JSON `RowChange[]` array)
v2: the versioned ordered-chunk container below (absent for Empty frames)The version byte is per frame, not per container: repack re-encodes old frames to the current version (212 §2.2’s aging-out policy), and a per-frame stamp is what lets one segment legally carry mixed vintages in between.
Structs§
- Frame
Header - The decoded envelope header. See the module docs for the wire layout.
- RunTotals
- The run accounting pair (
run_rows,rows_cum) — today’s stamped columns, moved into the header and precomputed in the capture path (211 §4.1).
Enums§
- Frame
Decode Error - Decode-side failures. Fail-closed by construction: unknown versions, kinds, or flag bits are errors, never best-effort skips — a frame will be replayed by a binary years younger than the one that wrote it, and that direction must be exact.
- Frame
Encode Error - Encode-side misuse. Every variant is a producer bug, not a data-reachable state — surfaced as errors (not asserts) because the encoder sits on the commit path.
- Frame
Kind - The entry kind — today’s
_rindle_change_log.kinddiscriminator plus the shapes the journal substrate adds. - Payload
Decode Error - Payload
Encode Error