Commit Graph
2 Commits
Author SHA1 Message Date
Omar SobhandClaude Opus 4.7 0cf00e5954 Phase 4d: WAL segment rotation
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 17s
Turns the single-file Phase 4c WAL into a segmented log so it
can grow past a single file safely. This unblocks every
downstream Phase 4d/4e integration — reconnect + push loop
can't rely on an unbounded single file.

Layout change:
  <root>/segment-<20-digit-first-seq>.bin

20-digit zero-padded first-seq means lex sort == numeric sort,
so `read_dir + sort_by_key` recovers the natural order.

Rotation policy:
  * `max_segment_bytes` default 8 MiB, overridable via
    `open_with_options`.
  * `append` rolls to a fresh segment BEFORE writing when the
    current tail is non-empty AND at/above the cap. A single
    oversize record always lands in one segment — we never split
    a record.

Truncation across segments:
  * whole segments with `last_seq <= watermark` are `unlink`'d
  * the boundary segment (if any) is rewritten in place via
    `tempfile-in-parent + rename` + parent-dir fsync
  * full truncation resets head/tail to 0 and the next append
    creates a fresh segment

Legacy compat: on open, if a pre-4d `log.bin` is present and
no `segment-*.bin` files exist, it is scanned for its first
seq and renamed to the correct segment name. Refuses to
silently overwrite on filename collision.

Tests (18, all green): rotation-happens-at-cap, reopen-
enumerates-all-segments, truncate-drops-whole-segments,
truncate-partial-rewrites-boundary, oversize-record-still-
fits-one-segment, legacy-log.bin-migration, plus the full
Phase 4c suite (fresh open, append, iter partial ranges,
reopen recovers tail, torn-write truncation, corruption is
hard error, full truncation appendable, below-head no-op,
large payload, empty payload, append-after-reopen).

942-line file, comfortably under the 1300-line ceiling.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-14 00:47:19 -07:00
Omar SobhandClaude Opus 4.7 dd1a37fe7e Phase 4c: Write-Ahead Log primitives
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 16s
Foundation for the roaming/offline-client story described in
Architecture v2 ("Roaming client (full R/W, offline queue)"):
mutating ops append to a durable local log before hitting the
network, and are replayed at reconnect. This PR ships the
primitive; RPC/reconnect wiring lands in Phase 4d.

Segment format (single-file for now — rotation is 4d):

  seq   : u64 LE   (8 bytes)
  len   : u32 LE   (4 bytes)   payload length
  csum  : [u8; 8]  (8 bytes)   first 8 bytes of
                              BLAKE3(seq || len || payload)
  bytes : [u8; len]

On open, the log is scanned linearly. A short read or truncated
tail is treated as "clean crash boundary" — the file is
size-truncated to the last fully-fsynced record, no error.
A checksum mismatch on a full-length record is fatal (real
corruption, don't silently swallow data).

Public API:
  WriteAheadLog::open(root) -> Self
  wal.append(&[u8]) -> Result<u64>        // durable, fsynced
  wal.iter_from(start_seq) -> Vec<WalRecord>
  wal.truncate_up_to(watermark) -> ()     // atomic rewrite via
                                          // tempfile-in-parent + rename
  wal.head_seq() / wal.tail_seq() / wal.is_empty()

Tests (12, all green): fresh open, monotonic seq, replay full &
partial ranges, reopen-recovers-tail, torn-write truncation on
open, corruption is hard error, prefix truncation, full
truncation leaves appendable, below-head no-op, 1 MiB payload
roundtrip, empty payload roundtrip, append-after-reopen.

No new deps — BLAKE3 (already a dep) supplies the checksum.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-13 18:04:37 -07:00