102 lines
5.9 KiB
Markdown
102 lines
5.9 KiB
Markdown
# 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
|
|
|
|
```sh
|
|
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)
|
|
|
|
- [x] `cargo test` clean.
|
|
- [x] 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`).
|
|
- [x] Manifest-selection harness handles every corruption scenario
|
|
(`every_selection_scenario_holds`).
|
|
- [x] 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.
|