feat(wal): add Wal::append + frame format §6.2.2 (PR-M5) - #69
Conversation
Second WAL implementation slice. The frame module pins the exact §6.2.2 byte layout (4 B len LE u32 + 1 B kind + 3 B _pad + 4 B CRC32-C LE u32 + payload) at the bytes-on-disk boundary; Wal::append composes against it without knowing the layout. CRC32-C covers kind || _pad || payload, matching Kafka's record-batch shape. payload.len() > MAX_FRAME_BYTES is rejected at append time before any I/O. The returned WalOffset points at the start of the new frame, recorded via metadata().len() rather than stream_position to dodge the O_APPEND cursor-sync issue. 9 colocated unit tests in frame.rs pin every RFC0008.5 corruption sub-case at the helper level (CrcMismatch, UnknownKind, NonZeroPad, OversizeLen) plus header layout, both round-trips, empty payload, and truncated header/payload. 4 integration tests in tests/append.rs pin on-disk layout, tight packing across appends, MAX_FRAME_BYTES boundary, and reopen extending the existing segment. RFC0008.X integration tests stay #[ignore]'d — they need sync or replay, which land in follow-up slices. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughImplements the RFC 0008 §6.2.2 WAL frame byte format with CRC32-C validation and read/write helpers, integrates frame serialization into ChangesWAL Frame Format and Append Implementation
Sequence Diagram(s)sequenceDiagram
participant ComponentA
participant ComponentB
ComponentA->>ComponentB: observable interaction
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Implements the next WAL slice by adding frame (de)serialization per RFC 0008 §6.2.2 and wiring Wal::append to write frames and return a WalOffset for the newly appended record.
Changes:
- Add
framemodule implementing the §6.2.2 frame header/payload layout with CRC32-C validation and structured read errors. - Implement
Wal::appendto enforceMAX_FRAME_BYTES, write frames to the current segment, and return a(segment_uuid, byte_offset)WalOffset. - Add integration tests that pin on-disk layout/offset behavior for
append.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| crates/ourios-wal/src/lib.rs | Implements Wal::append and introduces frame module usage + updated offset semantics docs. |
| crates/ourios-wal/src/frame.rs | Adds frame format implementation (write/read + CRC checks) and colocated unit tests. |
| crates/ourios-wal/tests/append.rs | Adds integration tests asserting append layout, offsets, and reopen behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
What
Second WAL implementation slice —
Wal::append+ the §6.2.2 frame format. Builds on PR-M4 (Wal::open+ segment header).crates/ourios-wal/src/frame.rs—pub(crate)boundary between bytes-on-disk and(FrameKind, payload)tuples per RFC 0008 §6.2.2:FRAME_HEADER_LEN = 12(4 BlenLE u32 + 1 Bkind+ 3 B_pad+ 4 Bcrc32LE u32).write_frame— single-buffer header build, thenwrite_all(header)+write_all(payload).read_frame— validateslen ≤ MAX_FRAME_BYTES,kind ∈ {0x01, 0x02},_pad == [0, 0, 0], CRC32-C match.FrameError::{CrcMismatch, UnknownKind, NonZeroPad, OversizeLen, Io}withDisplay+Errorimpls — one variant per RFC0008.5 sub-case.Wal::appendreal impl inlib.rs:payload.len() > MAX_FRAME_BYTEScheck →AppendError::TooLarge { len, limit }before any bytes are written.metadata().len()as the frame's start byte (notstream_position—O_APPENDdoesn't guarantee cursor synchronisation on all platforms; same lesson as PR-M4 §1.6.3).WalOffset { segment: current_segment_uuid, byte: pre_write_len }pointing at the start of the new frame.Test coverage
9 colocated unit tests in
src/frame.rs:frame_byte_layout_matches_rfc_6_2_2— pins exact 12 B header bytes.frame_round_trips_through_write_then_read— both kinds × three payload sizes.empty_payload_frame_is_header_only—len = 0edge.read_frame_rejects_crc_mismatch— RFC0008.5 sub-case 1.read_frame_rejects_unknown_kind— RFC0008.5 sub-case 2 (hand-crafted bytes with CRC that matches0xFEso the kind check fires first).read_frame_rejects_non_zero_pad— RFC0008.5 sub-case 3.read_frame_rejects_oversize_len— RFC0008.5 sub-case 4; checks length is rejected before any payload allocation.read_frame_rejects_truncated_header/_truncated_payload— RFC0008.4 / 0008.5 disambiguation (variant-level; recovery driver disambiguates by segment position in a future slice).4 integration tests in
tests/append.rs:one_append_writes_one_frame_after_the_segment_header— pins on-disk layout (24 B header + 12 B frame header + payload, no double-write), pinsWalOffset.byte == 24and.segment == file stem UUID.consecutive_appends_pack_tight_with_monotonic_offsets— second offset = first + 12 + first_payload.len; tight packing; both in the same segment.max_frame_bytes_is_accepted_one_more_is_rejected— boundary atMAX_FRAME_BYTES+ theTooLarge { len, limit }arm.append_after_reopen_extends_the_existing_segment— PR-M4 reopen contract preserved across an append.Workspace totals: 321 passed / 0 failed / 44 ignored (was 308 / 44 on
main— +13 live).RFC0008.X status
The §5 RFC0008.X integration tests stay
#[ignore]'d this slice — they needsync(RFC0008.1, 0008.8, 0008.9) orreplay(RFC0008.4, 0008.5). The corruption sub-cases that drive RFC0008.5's audit-event reasons are covered at the function-level in the colocatedframe.rstests — same pattern as PR-M4's segment-header colocated tests.Invariants touched
appendis the first half of the contract; the durability half lands withsyncin the next slice.appendreturningOk(offset)still means "frame in OS page cache", not "acked-durable". Receiver MUST NOT ack untilsync(offset)returns.frame_byte_layout_matches_rfc_6_2_2test.MAX_FRAME_BYTESenforced atappendtime before any I/O.CLAUDE.md §6.6
cargo fmt --all --check— clean.cargo clippy --all-targets --all-features -- -D warnings— clean.cargo test --all-features— 321 passed / 0 failed / 44 ignored.mdbook build— clean.Out of scope (next slices)
Wal::sync—fdatasync(orF_FULLFSYNCon macOS), the durability gate the receiver acks against.Wal::replay— segment scan + frame-by-frame walk; lights up RFC0008.4 + RFC0008.5 integration tests.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests