Frames are 4096-byte pages tagged with metadata; the WAL is a sequence of frames followed by an optional uncommitted tail. Each batch ends with a frame that has the commit bit set; recovery on open scans forward and accepts only frames whose commit batch is complete.
Layout ------ The WAL begins with a 24-byte header containing a magic identifier, a 64-bit salt assigned at open time when the WAL is empty, and a 64-bit seed used by the per-frame checksum. Frame i occupies header_size + i * frame_size_bytes bytes.
Crash-safety guarantee ---------------------- A frame is considered durable only if (a) its checksum verifies and (b) some later frame in the same forward scan has the commit bit set without an intervening checksum failure. Partial trailing batches are silently discarded on recovery and overwritten by the next append.
This module is purely an append-only frame store; integration with the pager (read-from-WAL routing, checkpointing back to the main DB) lives in the Granary_store.Store module.
Size of one WAL frame for the DEFAULT 4096-byte geometry (24-byte frame header + 4096-byte page = 4120). A WAL opened with a non-default page_size uses a proportionally larger frame internally (#95).
frame_idx of a structurally-present frame that failed integrity: a bad checksum on a directly-addressed read, or (on an encrypted WAL) a frame whose checksum passed but whose GCM tag failed — i.e. tampering (#219)
Open a WAL over the given byte-addressable callbacks. If the device is empty (or smaller than header_size_bytes) the WAL is initialised with a fresh salt and seed. Otherwise the header is read, and a forward scan recovers the index of every page in the last contiguous committed batch. A trailing partial batch is silently discarded.
page_size (default Geometry.default's 4096) sets the frame's page payload size; it must match the main DB geometry (#95).
cipher (default None): when supplied, the page payload of each frame is AES-256-GCM encrypted; the 24-byte meta header stays plaintext so keyless recovery forward-scans still work. The FNV checksum covers the whole payload region (ciphertext + nonce + tag). Encrypted frames are Crypto.overhead (32) bytes larger than plaintext frames.
Snapshot-aware lookup: most recent frame index for page_id strictly less than max_frame. Used by RO snapshots so a reader captured at committed_frames = max_frame does not see frames written after. Returns None if no frame for page_id satisfies the bound.
Read the decrypted page bytes at a given frame index.
Ownership (#246): the returned Cstruct may be a cache-shared buffer that the WAL retains in its decrypted-frame cache and hands to other readers. The caller MUST treat it as read-only — cstruct_dup it before any in-place mutation, or only borrow it read-only and drop it before the next reset/checkpoint. Mutating it in place would corrupt the cache and every concurrent borrower. (The buffer is immutable for the life of the WAL generation; the cache is dropped wholesale on reset.)
Read the full frame metadata AND decrypted page bytes at a given committed frame index. Like read_frame but also returns the page_id and is_commit flag so the caller can reconstruct the full frame record (e.g. for incremental backup / frame capture).
Raises Corrupt_frame for indices outside 0, committed_frames).
Append a batch of pages; the LAST entry in the list is automatically marked as the commit frame. Performs a single sync at the end and only then updates the in-memory index. If sync fails the index is left unchanged so the partial batch is invisible to readers.
Append a batch of pages but skip the trailing sync. The in-memory index and committed_frames are bumped immediately so concurrent writers (still under the store's rw_mutex) can locate the newly written frames and subsequent appends place their frames at the correct base. Durability is deferred to a separate flush_sync call by the group-commit coordinator. Sync failure is treated as fatal by upstream callers — see Granary_store.Store.commit.
Reset the WAL: discards all committed frames and the in-memory index. Used by checkpointing to truncate the log after migrating its contents to the main DB. The on-disk WAL is not physically truncated; later appends overwrite from the beginning. Bumps epoch so that replication consumers can detect that their snapshot has been invalidated.
WAL generation counter — bumped every time reset is called (i.e. after each checkpoint). Starts at 0 on open. Replication can use this to detect whether a checkpoint expired its snapshot.
Sourceval iter_index : t->(int64 ->int -> unit)-> unit
Iterate over every (page_id, frame_idx) currently in the WAL index. Order is unspecified.