epiphany/crates/epiphany-bundle
Levi Neuwirth 09a7f62802 Make the exhaustive tests exhaustive, and stop the record overclaiming
Audit correction. The manifest test was documented -- in its own doc comment, in
DECISIONS.md, and in P3's commit message -- as "exhaustive single-byte
perturbation". It tried three XOR deltas per byte. The claim was false as
executed, and the "guard is total" conclusion leaned on it.

Both tests now do what their names say:

  every_single_byte_replacement_of_a_manifest_is_rejected -- each byte, each of
  the 255 other values. 0.15s.

  compression_none_rejects_a_non_zero_parameter_byte -- every one of the 255
  non-zero parameter bytes, plus a round-trip of all 256 values through Zstd and
  Reserved, so the strictness is shown to be confined to None.

And the totality claim is re-seated where it belongs: on the argument, not on a
finite test. manifest_id is derived from the body, so a body edit fails the id
check and an id edit fails the derivation; encode_body sorts and deduplicates
every vector, so an out-of-order or duplicated encoding cannot round-trip. The
test is evidence for that argument over ONE constructed manifest, and is blind
to multi-byte perturbations entirely. Both the doc comment and DECISIONS.md now
say so.

Worth recording: restoring the leniency fails the codec test and the index test,
and leaves the manifest test GREEN -- the guard rejects those bytes whatever the
sub-codec does. That is not a weak test. It is the asymmetry that hid the bug,
and it locks the guard rather than the codec. A suite where every test fails on
every mutation would be telling us less.

No codec or wire-format change; the strict branch was already correct.

Gate: fmt clean, clippy 0, 30 targets / 1012 passed / 0 failed, docs 0 under
-D warnings, conformance 8/8, zero golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 19:39:30 -04:00
..
examples A B C D F 2026-06-19 12:42:31 -04:00
src Make the exhaustive tests exhaustive, and stop the record overclaiming 2026-07-09 19:39:30 -04:00
tests A B C D F 2026-06-19 12:42:31 -04:00
Cargo.toml Pushes 1+3: fix the MUST-level violations, wire the types-only machinery 2026-07-02 17:10:50 -04:00
DECISIONS.md Make the exhaustive tests exhaustive, and stop the record overclaiming 2026-07-09 19:39:30 -04:00
README.md Pushes 1+3: fix the MUST-level violations, wire the types-only machinery 2026-07-02 17:10:50 -04:00

README.md

epiphany-bundle

The Epiphany .musc file format, implementing the normative requirements of Chapter 8 (File Format) of the core specification (spec/core_spec.pdf). This is Agent D's crate per spec/QUICKSTART.md. It depends on epiphany-determinism (Agent A) and on nothing else — not on epiphany-core (Agent B) or epiphany-ops (Agent C):

bundles handle bytes, ops handles semantics. A canonical-base snapshot from the bundle's perspective is opaque bytes plus a frontier DVV; only epiphany-ops interprets it. — QUICKSTART

A bundle is a single file: a fixed 64-byte header at offset 0, two 256-byte superblock slots, then a body of immutable, content-addressed chunks. The superblocks are the only mutable on-disk objects. A commit appends new chunks, writes a new manifest chunk, then flips the active superblock by writing the inactive slot and durably flushing it — that flush is the commit point. Because commits only ever append and touch the inactive slot, a crash can never corrupt the active state.

What's here

Area Items Spec
Prelude FixedHeader (64 B, CRC-32C), Superblock/CommitState (256 B, CRC-32C), select_active Ch. 8 §"The Bundle Layout", §"Superblock Selection"
Atomic commit Bundle::create/open/commit, the 7-step protocol, cold-open path Ch. 8 §"The Atomic Write Protocol", §"Streaming Reads"
Content addressing chunk_content_hash/chunk_id, ChunkRef, ChunkKind, CompressionAlgorithm, domain separation Ch. 8 §"Content Hashing", §"Chunks"
Manifest Manifest (canonical_base ≠ acceleration_snapshots), SnapshotRef, BlobRef, ProfileDeclaration, ExtensionDeclaration Ch. 8 §"The Manifest"
Retention RetentionPolicy (first-class), ProfileConstraints Ch. 8 §"Garbage Collection and Retention"
Op blocks pack_operation_blocks (1 MiB soft target), encode_block/decode_block Ch. 8 §"Operation Envelope Blocks"
Storage BlockStore, MemStore, FileStore (real fsync), FaultStore (crash sim) Ch. 8 §"Durable Writes"
Gates fuzz::run_crash_recovery_fuzz, fuzz::exhaustive_crash_check, fuzz::run_manifest_selection_harness QUICKSTART acceptance

The crash-recovery contract (the acceptance gate)

Kill the process between any two syscalls in the commit protocol; reopen; the bundle must be valid in 100% of runs, and must recover to the previous generation when the crash precedes the durable flush. This is the most important single test in the entire prototype. — QUICKSTART, Agent D

Killing a real process between syscalls cannot be made deterministic, so the fuzzer drives the commit against a FaultStore that distinguishes live (page-cache) bytes from durable (survives-a-crash) bytes and can crash after any chosen syscall — optionally tearing the in-flight superblock write, the case the slot CRC must catch. After every simulated crash the bundle is reopened from the durable image and must:

  1. open successfully (never corrupt);
  2. be at the previous generation or the new one, never anything else;
  3. if the commit returned Ok, be at the new generation; and if the crash was clean (the in-flight flush persisted nothing) and the commit did not complete, be at the previous generation — the exact "recover to the previous generation when the crash precedes the durable flush" property. (A torn final flush may at a full prefix legitimately persist the whole superblock — the genuine post-commit case — so the torn branch admits either generation.)
  4. report no integrity anomaly;
  5. have every canonical chunk present and hash-intact.

Two drivers exercise this: a randomized 10,000-iteration sweep, and an exhaustive per-commit sweep that tests every syscall boundary crossed with every tear point (clean, and torn at prefixes around the 252-byte CRC offset and the 256-byte slot size). The second leaves no step of the protocol untested.

The companion manifest_selection gate asserts the Chapter 8 superblock- selection rule across every corruption scenario the QUICKSTART enumerates: slot A corrupt + B valid (and vice versa), both valid at generation+1, both valid at the same generation (equivalent, and divergent), a generation gap > 1, a non-committed slot, a manifest-hash mismatch, and neither valid.

Building and testing

cargo test -p epiphany-bundle                              # unit + the two gates
cargo clippy -p epiphany-bundle --all-targets -- -D warnings
cargo run --release --example fuzz_crash -- 1000000        # extended crash soak

Hand-off criteria (QUICKSTART, Agent D)

  • cargo test clean.
  • Crash-recovery fuzzer passes 10,000 iterations (crash_recovery_fuzz_ten_thousand_iterations, two seeds; extended soak via the example binary; exhaustive per-syscall sweep in exhaustive_sweep_across_base_states_and_commit_shapes).
  • Manifest-selection harness handles every corruption scenario (every_selection_scenario_holds).
  • Real-filesystem fsync round-trip (file_store_real_fsync_round_trip).

Scope boundaries (per QUICKSTART "Don't do these")

v0 writes only uncompressed chunks (compression on the write path is deferred), but reading zstd-compressed chunks and blobs is supported, per the spec's §Compression MUST (the manifest is mandatory-uncompressed regardless, and a compressed manifest is rejected). It carries the text-projection root but does not implement the s-expression projection content, and it preserves extension declarations and chunks but does not evaluate edit barriers — barrier operands (OperationKindTag, ObjectKind, EditBarrier) are owned by Agents C and E. Operation envelopes, snapshots, and causal frontiers are opaque bytes here.

See DECISIONS.md for the prototype byte-layout choices that anticipate the deferred Binary Format companion, and the batched Pass 11 candidates.