P13-S27: accept reduction authority implementation

This commit is contained in:
Levi Neuwirth 2026-08-09 15:05:58 +02:00
parent 83df0a01be
commit 4df8e257f3
No known key found for this signature in database
22 changed files with 1323 additions and 251 deletions

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@
//! bundle, not as errors.
use crate::codec::DecodeError;
use crate::ids::SchemaVersion;
use crate::ids::{ReductionAlgorithmVersion, SchemaVersion};
use epiphany_determinism::ContentHash;
/// A hard bundle failure: the file is unopenable, or a canonical chunk is
@ -139,17 +139,42 @@ pub enum BundleError {
/// error MUST NOT degrade to a read-only open.
LegacyBaseIntroductionRejected,
/// A major-1 container's canonical base cannot yet be validated: matrix
/// rows 5i/6i, opening a container that already carries a base, or
/// committing one into it. The container is the **right** epoch — this is
/// not a request to repack — but no reduction-authority capability exists
/// yet to validate the base against. **Temporary**: `P13-S27` supplies
/// that capability and replaces both branches with real validation, not
/// this categorical refusal. Never mentions repack (repacking a
/// already-correct-epoch container would be wrong advice), and MUST NOT
/// degrade to a read-only open — a pre-authority base is not a
/// restricted-but-correct view.
ReductionAuthorityUnavailable,
/// A canonical base was produced under reduction semantics this build does
/// not implement, so the materialized state it carries is **the wrong
/// materialization** and must be rebuilt before use.
///
/// Raised on both boundaries a base can cross: `Bundle::open`, when a
/// container's base disagrees with the caller's
/// [`BundleCapabilities::current_reduction_version`], and
/// `commit`/`commit_versioned`, when a **newly emitted or replaced** base
/// does. Replaces the format rung's temporary
/// `ReductionAuthorityUnavailable`, which refused both boundaries
/// categorically because no authority existed to validate against; P13-S27
/// supplies that authority, so refusal becomes validation.
///
/// # Not read-only, and not an integrity anomaly
///
/// A stale base is **not a restricted-but-correct view** — it is state
/// computed under different rules. Exposing it read-only would serve
/// incorrect canonical state confidently, which is worse than refusing.
///
/// # Why `open` cannot recover
///
/// **`open` cannot rebuild.** Drop-and-replay is unsound once pruning
/// exists (`core_spec.tex:12207`, `:14701` — specified, **not
/// implemented**; no `prune` appears anywhere in this crate), because the
/// operations needed to rebuild may no longer be present. A higher-level
/// rebuild path may be authorized later **only where full pre-base history
/// is demonstrably available**; this rung authorizes none.
///
/// [`BundleCapabilities::current_reduction_version`]:
/// crate::bundle::BundleCapabilities::current_reduction_version
CanonicalBaseRequiresRebuild {
/// The version the canonical base reports for itself.
base: ReductionAlgorithmVersion,
/// The version the caller stated this build implements.
current: ReductionAlgorithmVersion,
},
}
impl core::fmt::Display for BundleError {
@ -229,9 +254,11 @@ impl core::fmt::Display for BundleError {
"cannot add or replace a canonical base in a legacy (format major 0) bundle; \
repack into a fresh major-1 bundle",
),
BundleError::ReductionAuthorityUnavailable => f.write_str(
"this major-1 container's canonical base cannot yet be validated: no \
reduction-authority capability exists until P13-S27 lands",
BundleError::CanonicalBaseRequiresRebuild { base, current } => write!(
f,
"canonical base was produced under reduction algorithm version {} but this \
build implements {}; the base must be rebuilt before use",
base.0, current.0
),
}
}

View File

@ -35,7 +35,7 @@
//! bundle images by hand and asserts the Chapter 8 §"Superblock Selection" rule
//! across every corruption scenario the QUICKSTART enumerates.
use crate::bundle::{Bundle, CommitContext, StagedChunk, BODY_START};
use crate::bundle::{Bundle, BundleCapabilities, CommitContext, StagedChunk, BODY_START};
use crate::chunk::{ChunkKind, ChunkRef, CompressionAlgorithm};
use crate::error::IntegrityAnomaly;
use crate::header::FixedHeader;
@ -101,14 +101,21 @@ fn staged_blocks(envelope_payloads: &[Vec<u8>]) -> Vec<StagedChunk> {
fn check_recovery(base_image: &[u8], base_gen: u64, chunks: &[StagedChunk], crash: CrashPoint) {
// Open the bundle over a fault store; `open` only reads, so it never
// consumes crash budget and always succeeds on a valid base image.
let mut bundle = Bundle::open(FaultStore::new(base_image.to_vec(), crash))
.expect("base image must open before the commit");
let mut bundle = Bundle::open(
FaultStore::new(base_image.to_vec(), crash),
BundleCapabilities::synthetic_for_fixture(0),
)
.expect("base image must open before the commit");
let committed = bundle.commit(chunks, append_roots).is_ok();
let store = bundle.into_store();
// Recover: reopen from exactly the bytes that survived the crash.
let durable = store.durable_image();
let recovered = Bundle::open(MemStore::from_bytes(durable.clone())).unwrap_or_else(|e| {
let recovered = Bundle::open(
MemStore::from_bytes(durable.clone()),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap_or_else(|e| {
panic!(
"crash at {crash:?} left an UNOPENABLE bundle (base gen {base_gen}): {e}\n\
durable image length {}",
@ -164,7 +171,13 @@ fn check_recovery(base_image: &[u8], base_gen: u64, chunks: &[StagedChunk], cras
fn build_base(rng: &mut SplitMix64, commits: u64) -> (Vec<u8>, u64) {
let doc = DocumentId([(rng.next_u64() & 0xff) as u8; 16]);
let uuid = FileUuid([(rng.next_u64() & 0xff) as u8; 16]);
let mut bundle = Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).unwrap();
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
for _ in 0..commits {
let n = rng.below(3) as usize; // 0..2 envelopes
let envelopes: Vec<Vec<u8>> = (0..n)
@ -289,7 +302,11 @@ pub fn run_wire_decode_fuzz(iters: u64, seed: u64) -> WireFuzzCoverage {
let mut manifests: Vec<Vec<u8>> = Vec::new();
for commits in 0..4u64 {
let (image, _) = build_base(&mut rng, commits);
let bundle = Bundle::open(MemStore::from_bytes(image.clone())).expect("valid image opens");
let bundle = Bundle::open(
MemStore::from_bytes(image.clone()),
BundleCapabilities::synthetic_for_fixture(0),
)
.expect("valid image opens");
manifests.push(bundle.manifest().encode());
images.push(image);
}
@ -347,7 +364,10 @@ pub fn run_wire_decode_fuzz(iters: u64, seed: u64) -> WireFuzzCoverage {
// 1. Whole-image open. Must never panic; an Ok manifest must re-encode.
let pick = (rng.next_u64() as usize) % images.len();
let image = mutate_image(&mut rng, &images[pick]);
match Bundle::open(MemStore::from_bytes(image)) {
match Bundle::open(
MemStore::from_bytes(image),
BundleCapabilities::synthetic_for_fixture(0),
) {
Ok(bundle) => {
cov.opens_ok += 1;
let encoded = bundle.manifest().encode();
@ -532,10 +552,18 @@ pub fn exhaustive_crash_check(base_image: &[u8], base_gen: u64, envelope_payload
// Learn the commit's total syscall count (and confirm the clean commit
// recovers to G+1) via a no-fault run.
let total = {
let mut bundle = Bundle::open(FaultStore::no_fault(base_image.to_vec())).unwrap();
let mut bundle = Bundle::open(
FaultStore::no_fault(base_image.to_vec()),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
bundle.commit(&chunks, append_roots).unwrap();
let store = bundle.into_store();
let recovered = Bundle::open(store.recover()).unwrap();
let recovered = Bundle::open(
store.recover(),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
assert_eq!(recovered.generation(), base_gen + 1);
store.syscalls_issued()
};
@ -646,7 +674,8 @@ pub fn run_manifest_selection_harness() {
b.set_slot(Slot::A, &sb_a);
b.set_slot(Slot::B, &sb_b);
b.corrupt_slot(Slot::A);
let bundle = Bundle::open(b.store()).expect("slot B is valid; bundle must open");
let bundle = Bundle::open(b.store(), BundleCapabilities::synthetic_for_fixture(0))
.expect("slot B is valid; bundle must open");
assert_eq!(bundle.active_slot(), Slot::B);
assert_eq!(bundle.generation(), 1);
assert!(bundle.anomalies().is_empty());
@ -661,7 +690,8 @@ pub fn run_manifest_selection_harness() {
b.set_slot(Slot::A, &sb_a);
b.set_slot(Slot::B, &sb_b);
b.corrupt_slot(Slot::B);
let bundle = Bundle::open(b.store()).expect("slot A is valid; bundle must open");
let bundle = Bundle::open(b.store(), BundleCapabilities::synthetic_for_fixture(0))
.expect("slot A is valid; bundle must open");
assert_eq!(bundle.active_slot(), Slot::A);
assert_eq!(bundle.generation(), 7);
assert!(bundle.anomalies().is_empty());
@ -675,7 +705,7 @@ pub fn run_manifest_selection_harness() {
let sb_b = b.add_manifest(5, &m);
b.set_slot(Slot::A, &sb_a);
b.set_slot(Slot::B, &sb_b);
let bundle = Bundle::open(b.store()).unwrap();
let bundle = Bundle::open(b.store(), BundleCapabilities::synthetic_for_fixture(0)).unwrap();
assert_eq!(bundle.generation(), 5);
assert_eq!(bundle.active_slot(), Slot::B);
assert!(bundle.anomalies().is_empty());
@ -688,7 +718,7 @@ pub fn run_manifest_selection_harness() {
let sb = b.add_manifest(9, &m);
b.set_slot(Slot::A, &sb);
b.set_slot(Slot::B, &sb);
let bundle = Bundle::open(b.store()).unwrap();
let bundle = Bundle::open(b.store(), BundleCapabilities::synthetic_for_fixture(0)).unwrap();
assert_eq!(bundle.active_slot(), Slot::A);
assert_eq!(bundle.generation(), 9);
assert!(bundle.anomalies().is_empty());
@ -707,7 +737,7 @@ pub fn run_manifest_selection_harness() {
sb_b.generation = 9;
b.set_slot(Slot::A, &sb_a);
b.set_slot(Slot::B, &sb_b);
let bundle = Bundle::open(b.store()).unwrap();
let bundle = Bundle::open(b.store(), BundleCapabilities::synthetic_for_fixture(0)).unwrap();
assert_eq!(
bundle.anomalies(),
&[IntegrityAnomaly::DivergentSameGeneration { generation: 9 }]
@ -726,7 +756,7 @@ pub fn run_manifest_selection_harness() {
sb_b.generation = 9;
b.set_slot(Slot::A, &sb_a);
b.set_slot(Slot::B, &sb_b);
let bundle = Bundle::open(b.store()).unwrap();
let bundle = Bundle::open(b.store(), BundleCapabilities::synthetic_for_fixture(0)).unwrap();
assert_eq!(bundle.generation(), 9);
assert_eq!(
bundle.anomalies(),
@ -749,7 +779,7 @@ pub fn run_manifest_selection_harness() {
b.corrupt_slot(Slot::A);
b.corrupt_slot(Slot::B);
assert!(matches!(
Bundle::open(b.store()),
Bundle::open(b.store(), BundleCapabilities::synthetic_for_fixture(0)),
Err(crate::BundleError::NoValidSuperblock)
));
}
@ -764,7 +794,7 @@ pub fn run_manifest_selection_harness() {
sb_b.commit_state = CommitState::Reserved(1);
b.set_slot(Slot::A, &sb_a);
b.set_slot(Slot::B, &sb_b);
let bundle = Bundle::open(b.store()).unwrap();
let bundle = Bundle::open(b.store(), BundleCapabilities::synthetic_for_fixture(0)).unwrap();
// Slot B is not committed -> excluded; A (gen 3) is selected. The
// non-committed slot is surfaced as an anomaly, but this is ordinary
// fallback (the next commit overwrites the bad slot), so the bundle is
@ -787,7 +817,8 @@ pub fn run_manifest_selection_harness() {
sb_b.manifest_hash = manifest_chunk_hash(b"not the manifest");
b.set_slot(Slot::A, &sb_a);
b.set_slot(Slot::B, &sb_b);
let bundle = Bundle::open(b.store()).expect("slot A is valid");
let bundle = Bundle::open(b.store(), BundleCapabilities::synthetic_for_fixture(0))
.expect("slot A is valid");
assert_eq!(bundle.active_slot(), Slot::A);
assert_eq!(bundle.generation(), 5);
}

View File

@ -285,8 +285,22 @@ impl SchemaVersion {
/// The reduction-algorithm version that produced a canonical-base snapshot
/// (Chapter 8): a snapshot may serve as a canonical base only if this matches
/// the active superblock's value. Modeled as an opaque monotonically-versioned
/// `u32` (the algorithm catalog itself lives in `epiphany-ops`).
/// **the semantics the running build implements**, which the caller states via
/// `BundleCapabilities` at `open` and `create`.
///
/// Modeled as an opaque monotonically-versioned `u32`. **The authoritative
/// number lives in `epiphany-ops` as
/// `epiphany_ops::CURRENT_REDUCTION_ALGORITHM_VERSION`, a plain `u32`; this
/// wrapper type is constructed at the composition boundary** by whichever crate
/// depends on both. `epiphany-bundle` deliberately does **not** depend on
/// `epiphany-ops` — its only workspace dependency is `epiphany-determinism` —
/// so it cannot read that constant itself, which is exactly why the capability
/// is injected rather than looked up.
///
/// This doc comment previously claimed "the algorithm catalog itself lives in
/// `epiphany-ops`" while nothing of the kind existed there — a doc asserting a
/// false fact about another module, and as written **unimplementable from where
/// the check must run**. P13-S27 pin 8 is the rung that made it true.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct ReductionAlgorithmVersion(pub u32);

View File

@ -67,8 +67,9 @@ pub use block::{
MAX_BLOCK_DEFAULT,
};
pub use bundle::{
manifest_chunk_hash, manifest_chunk_hash_versioned, Bundle, CommitContext, StagedChunk,
BODY_START, MAX_BLOB_BYTES, MAX_CHUNK_BYTES, MAX_MANIFEST_BYTES, SUPPORTED_SCHEMA_MAJOR,
manifest_chunk_hash, manifest_chunk_hash_versioned, Bundle, BundleCapabilities, CommitContext,
StagedChunk, BODY_START, MAX_BLOB_BYTES, MAX_CHUNK_BYTES, MAX_MANIFEST_BYTES,
SUPPORTED_SCHEMA_MAJOR,
};
pub use chunk::{
chunk_content_hash, chunk_id, content_hash_for, ChunkKind, ChunkRef, CompressionAlgorithm,

View File

@ -10,6 +10,7 @@
//! against an actual durable-flush primitive, not only the simulator.
use epiphany_bundle::fuzz::{exhaustive_crash_check, run_crash_recovery_fuzz, SplitMix64};
use epiphany_bundle::BundleCapabilities;
use epiphany_bundle::{Bundle, DocumentId, FileUuid, Manifest, MemStore, StagedChunk};
/// The headline gate: 10,000 randomized crash scenarios. Every one must recover
@ -51,8 +52,13 @@ fn exhaustive_sweep_across_base_states_and_commit_shapes() {
/// its image and generation. (Mirrors the fuzzer's own base builder.)
fn build_base(rng: &mut SplitMix64, commits: u64) -> (Vec<u8>, u64) {
let doc = DocumentId([(rng.next_u64() & 0xff) as u8; 16]);
let mut bundle =
Bundle::create(MemStore::new(), FileUuid([7; 16]), Manifest::empty(doc)).unwrap();
let mut bundle = Bundle::create(
MemStore::new(),
FileUuid([7; 16]),
Manifest::empty(doc),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
for i in 0..commits {
let payload = epiphany_bundle::encode_block(&[vec![i as u8; 16]]);
bundle
@ -83,6 +89,7 @@ fn file_store_real_fsync_round_trip() {
store,
FileUuid([0xAB; 16]),
Manifest::empty(DocumentId([1; 16])),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
for i in 1..=2u64 {
@ -99,7 +106,11 @@ fn file_store_real_fsync_round_trip() {
}
// Reopen from disk in a fresh handle: the committed state is durable.
let reopened = Bundle::open(FileStore::open(&path).unwrap()).unwrap();
let reopened = Bundle::open(
FileStore::open(&path).unwrap(),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
assert_eq!(reopened.generation(), 2);
assert_eq!(reopened.manifest().operation_roots.len(), 2);
reopened.verify_canonical_chunks().unwrap();

View File

@ -9,6 +9,7 @@
//! real commit path.
use epiphany_bundle::fuzz::run_manifest_selection_harness;
use epiphany_bundle::BundleCapabilities;
use epiphany_bundle::{Bundle, DocumentId, FileUuid, Manifest, MemStore, Slot, StagedChunk};
#[test]
@ -25,6 +26,7 @@ fn commit_then_corrupt_active_slot_falls_back() {
MemStore::new(),
FileUuid([3; 16]),
Manifest::empty(DocumentId([4; 16])),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
// Commit once: slot A holds gen 0, slot B holds gen 1 (active).
@ -44,7 +46,11 @@ fn commit_then_corrupt_active_slot_falls_back() {
image[320 + 80] ^= 0xFF;
// Recovery falls back to slot A (the previous generation), cleanly.
let recovered = Bundle::open(MemStore::from_bytes(image)).unwrap();
let recovered = Bundle::open(
MemStore::from_bytes(image),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
assert_eq!(recovered.active_slot(), Slot::A);
assert_eq!(recovered.generation(), 0);
assert!(recovered.anomalies().is_empty());

View File

@ -104,6 +104,53 @@ pub mod valuegen;
pub mod fuzz;
pub mod vectors;
/// The reduction semantics **this build implements**, as a bare number.
///
/// `core_spec.tex` §"Canonical Document Identity" is normative: *snapshots
/// produced under an earlier algorithm version cannot be used as canonical
/// bases under a later one without rebuilding*. Enforcing that needs a value
/// naming what the running implementation actually does — and before P13-S27
/// no such value existed anywhere. `ReductionAlgorithmVersion`
/// (`epiphany-bundle`) was a wire field whose reader compared it only against
/// the superblock that same value had seeded, so the check was a tautology for
/// every conformingly-written document.
///
/// # The bump discipline — this is the whole guarantee
///
/// **Any change to a canonical reduction verdict, or to canonical reduced
/// state, MUST bump this constant and record the change in the list below.**
///
/// **No mechanism can detect a semantics change.** A golden test over reduction
/// outputs can *prompt* the question — outputs moved, did semantics? — but it
/// can never answer it: a deliberate semantics change and an accidental
/// regression look identical from outside. **The discipline is the guarantee;
/// there is no backstop.**
///
/// # Why `0`, and why that is a decision
///
/// Bundles written to date carry `0` when they have no canonical base, and
/// bases self-report whatever they were stamped with. Starting anywhere but `0`
/// would make every existing base-bearing document fail to open **without any
/// semantics having changed** — the check would manufacture the breakage it
/// exists to detect. `0` is therefore a decision, **not "unset"**.
///
/// # Bumps
///
/// * `0` — the baseline. The semantics `canonical_reduction_order` and
/// `reduce_onto` implement as of P13-S27 (2026-08-08). No earlier version
/// exists; nothing predates this constant.
///
/// The first real bump belongs to **P13-S16**, which changes
/// `CreateStaffGroup`'s reduction verdict and must move this to `1`.
///
/// # Layering
///
/// This is a plain `u32`, and `epiphany-ops` **MUST NOT** gain a dependency on
/// `epiphany-bundle` in order to use that crate's `ReductionAlgorithmVersion`
/// wrapper. The wrapper is constructed at the composition boundary by whoever
/// depends on both (P13-S27 pin 1, §0.3).
pub const CURRENT_REDUCTION_ALGORITHM_VERSION: u32 = 0;
pub use anomaly::{
AnomalousReplicaSegment, IntegrityAnomaly, IntegrityAnomalyKind, ReplicaAnomalyReason,
};

View File

@ -124,8 +124,13 @@ fn build_fixture(dir: &Path) -> Fixture {
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create base bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
epiphany_testkit::production_caps(),
)
.expect("create base bundle");
bundle
.commit(&staged_blocks(&envelopes), append_roots)
.expect("commit base operation blocks");
@ -160,7 +165,11 @@ fn build_fixture(dir: &Path) -> Fixture {
/// Un-timed setup for the commit row: restore the base image and open it.
fn restore_and_open(path: &Path, image: &[u8]) -> Bundle<FileStore> {
fs::write(path, image).expect("restore base image");
Bundle::open(FileStore::open(path).expect("open store")).expect("open bundle")
Bundle::open(
FileStore::open(path).expect("open store"),
epiphany_testkit::production_caps(),
)
.expect("open bundle")
}
/// The timed commit: block append + manifest rewrite + superblock flip, fsync'd.
@ -176,7 +185,11 @@ fn typical_edit_commit(mut bundle: Bundle<FileStore>, edit: &[StagedChunk]) -> u
/// `ChunkRef`; see `Fixture::base_root`'s doc comment for why it is not
/// `manifest.canonical_base` right now) and every operation block.
fn open_bootstrap_read(path: &Path, base_root: &ChunkRef) -> usize {
let bundle = Bundle::open(FileStore::open(path).expect("open store")).expect("open bundle");
let bundle = Bundle::open(
FileStore::open(path).expect("open store"),
epiphany_testkit::production_caps(),
)
.expect("open bundle");
let manifest = bundle.manifest();
let mut bytes = 0usize;
bytes += bundle

View File

@ -81,8 +81,13 @@ fn staged(payloads: &[Vec<u8>]) -> Vec<StagedChunk> {
pub fn build_base(rng: &mut Rng, commits: u64) -> (Vec<u8>, u64) {
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
for _ in 0..commits {
let n = rng.range_usize(0, 2);
let payloads: Vec<Vec<u8>> = (0..n).map(|_| rng.byte_vec(1, 40)).collect();
@ -104,16 +109,20 @@ pub fn assert_recovers(
crash: CrashPoint,
) {
let blocks = staged(envelope_payloads);
let mut bundle = Bundle::open(FaultStore::new(base_image.to_vec(), crash))
.expect("the base image must open before the commit");
let mut bundle = Bundle::open(
FaultStore::new(base_image.to_vec(), crash),
crate::production_caps(),
)
.expect("the base image must open before the commit");
let committed = bundle.commit(&blocks, append_roots).is_ok();
let store = bundle.into_store();
// Recover from exactly the bytes that survived the crash.
let durable = store.durable_image();
let recovered = Bundle::open(MemStore::from_bytes(durable)).unwrap_or_else(|e| {
panic!("crash at {crash:?} left an UNOPENABLE bundle (base gen {base_gen}): {e}")
});
let recovered = Bundle::open(MemStore::from_bytes(durable), crate::production_caps())
.unwrap_or_else(|e| {
panic!("crash at {crash:?} left an UNOPENABLE bundle (base gen {base_gen}): {e}")
});
let g = recovered.generation();
assert!(
@ -159,10 +168,14 @@ pub fn exhaustive_crash_sweep(base_image: &[u8], base_gen: u64, envelope_payload
// Learn the commit's total syscall count via a no-fault run (and confirm the
// clean commit reaches G+1).
let total = {
let mut bundle = Bundle::open(FaultStore::no_fault(base_image.to_vec())).unwrap();
let mut bundle = Bundle::open(
FaultStore::no_fault(base_image.to_vec()),
crate::production_caps(),
)
.unwrap();
bundle.commit(&blocks, append_roots).unwrap();
let store = bundle.into_store();
let recovered = Bundle::open(store.recover()).unwrap();
let recovered = Bundle::open(store.recover(), crate::production_caps()).unwrap();
assert_eq!(recovered.generation(), base_gen + 1);
store.syscalls_issued()
};
@ -232,8 +245,13 @@ pub fn assert_selection_through_commits(seed: u64) {
let mut rng = Rng::new(seed);
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
// Generation 0 lives in slot A; each commit flips the active slot.
assert_eq!(bundle.active_slot(), Slot::A);
let commits = 5u64;
@ -249,7 +267,8 @@ pub fn assert_selection_through_commits(seed: u64) {
let image = bundle.into_store().into_bytes();
// Reopen: selection picks the highest committed generation, no anomaly.
let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen");
let reopened =
Bundle::open(MemStore::from_bytes(image), crate::production_caps()).expect("reopen");
assert_eq!(reopened.generation(), commits);
assert!(reopened.anomalies().is_empty());
assert!(!reopened.is_read_only());
@ -373,8 +392,13 @@ pub fn assert_operation_index_end_to_end(seed: u64) {
let envelopes = generators::operation_envelopes(&mut rng, 36, 3, 8, 8);
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
bundle
.commit(&staged_envelope_blocks(&envelopes, 12), append_roots)
.expect("commit operation blocks");
@ -392,7 +416,8 @@ pub fn assert_operation_index_end_to_end(seed: u64) {
// Reader side: reopen from the durable image; the fresh index is usable
// and locates every operation.
let image = bundle.into_store().into_bytes();
let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen");
let reopened =
Bundle::open(MemStore::from_bytes(image), crate::production_caps()).expect("reopen");
let usable = reopened
.usable_operation_index()
.expect("a fresh, covering index is usable");
@ -413,8 +438,13 @@ pub fn assert_stale_operation_index_rejected_and_rebuilt(seed: u64) {
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
bundle
.commit(&staged_envelope_blocks(first, 12), append_roots)
.expect("commit first blocks");
@ -444,7 +474,8 @@ pub fn assert_stale_operation_index_rejected_and_rebuilt(seed: u64) {
// The same verdict from a cold reopen; then rebuild from blocks.
let image = bundle.into_store().into_bytes();
let mut reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen");
let mut reopened =
Bundle::open(MemStore::from_bytes(image), crate::production_caps()).expect("reopen");
assert!(reopened.usable_operation_index().is_none());
let rebuilt = scan_rebuild_operation_index(&reopened);
commit_index(&mut reopened, &rebuilt);
@ -466,8 +497,13 @@ pub fn assert_corrupt_operation_index_is_not_bundle_corruption(seed: u64) {
let envelopes = generators::operation_envelopes(&mut rng, 24, 3, 8, 8);
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
bundle
.commit(&staged_envelope_blocks(&envelopes, 8), append_roots)
.expect("commit operation blocks");
@ -486,7 +522,7 @@ pub fn assert_corrupt_operation_index_is_not_bundle_corruption(seed: u64) {
)
.expect("commit garbage index chunk");
let image = bundle.into_store().into_bytes();
let mut reopened = Bundle::open(MemStore::from_bytes(image))
let mut reopened = Bundle::open(MemStore::from_bytes(image), crate::production_caps())
.expect("a defective index must not prevent opening");
assert!(reopened.anomalies().is_empty());
assert!(!reopened.is_read_only());
@ -512,14 +548,18 @@ pub fn assert_corrupt_operation_index_is_not_bundle_corruption(seed: u64) {
// (b) On-disk corruption of the now-valid index chunk's payload region.
let valid_image = reopened.into_store().into_bytes();
let probe = Bundle::open(MemStore::from_bytes(valid_image.clone())).expect("reopen");
let probe = Bundle::open(
MemStore::from_bytes(valid_image.clone()),
crate::production_caps(),
)
.expect("reopen");
let root = probe
.manifest()
.operation_index_root
.expect("the index is referenced");
let mut corrupt = valid_image;
corrupt[(root.offset + 3) as usize] ^= 0xFF;
let reopened = Bundle::open(MemStore::from_bytes(corrupt))
let reopened = Bundle::open(MemStore::from_bytes(corrupt), crate::production_caps())
.expect("index-region corruption must not prevent opening");
assert!(reopened.anomalies().is_empty());
reopened
@ -595,8 +635,13 @@ pub fn run_barrier_declaration_roundtrip(seed: u64) {
// Commit a manifest carrying the declaration; reopen from the raw image.
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
bundle
.commit(&staged(&[rng.byte_vec(1, 40)]), |ctx| {
let mut m = append_roots(ctx);
@ -605,7 +650,8 @@ pub fn run_barrier_declaration_roundtrip(seed: u64) {
})
.expect("commit the declaration");
let image = bundle.into_store().into_bytes();
let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen");
let reopened =
Bundle::open(MemStore::from_bytes(image), crate::production_caps()).expect("reopen");
// The bundle preserved the opaque blobs verbatim ...
let decl = reopened

View File

@ -87,6 +87,7 @@ fn build_bundle(
FileUuid([seed; 16]),
manifest,
stamped_version,
crate::production_caps(),
)
.expect("fixture manifest is emittable")
}

View File

@ -130,4 +130,24 @@ pub mod layout_stub;
// re-layout → re-render → re-resolve selection, across the ops/layout/render seams.
pub mod editloop;
use epiphany_bundle::{BundleCapabilities, ReductionAlgorithmVersion};
pub use rng::Rng;
/// The capabilities a **production composition path** supplies: the reduction
/// semantics this build actually implements, wrapped at the composition
/// boundary.
///
/// This is the "real authority" side of pin 3b's split. Fixtures deliberately
/// exercising arbitrary wire values use
/// [`BundleCapabilities::synthetic_for_fixture`] instead, so that a fixture
/// asserting behaviour at version `7` keeps asserting it when the authority
/// moves. **Never use `synthetic_for_fixture` on a production path.**
#[must_use]
pub fn production_caps() -> BundleCapabilities {
BundleCapabilities {
current_reduction_version: ReductionAlgorithmVersion(
epiphany_ops::CURRENT_REDUCTION_ALGORITHM_VERSION,
),
}
}

View File

@ -195,8 +195,13 @@ pub fn committed_manifest(seed: u64) -> Manifest {
let mut rng = Rng::new(seed);
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
for _ in 0..3 {
let n = rng.range_usize(1, 3);
let payloads: Vec<Vec<u8>> = (0..n).map(|_| rng.byte_vec(1, 80)).collect();
@ -228,25 +233,21 @@ fn canonical_score_bytes(envelopes: &[OperationEnvelope]) -> Vec<u8> {
/// stored as a `Snapshot` chunk, hash-verified on reopen and read back
/// byte-identically.
///
/// **Canonical-base wiring suspended** (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin
/// 3c, amended 2026-08-07). The snapshot's correct semantic home is the
/// manifest's `canonical_base` — that has not changed — but pin 3a's
/// format-epoch matrix refuses every base introduction (`Bundle::commit`) and
/// every base-bearing open (`Bundle::open`) during the S28 → P13-S27
/// interval, in both format epochs. So this harness cannot wire the snapshot
/// there right now: it stages the snapshot as an ordinary chunk instead and
/// carries the resulting `ChunkRef` across the reopen out of band, reading it
/// back directly via [`Bundle::read_chunk`] (which hash-verifies any
/// `ChunkRef`, not only a canonical one). The
/// serialize → load → decode → reserialize cycle below is unchanged; only the
/// snapshot's manifest placement is suspended. **Not** re-homed to
/// **Canonical-base wiring RESTORED by P13-S27** (test 7 / inherited obligation
/// 3). The format rung's pin 3c suspended it for the S28 → P13-S27 interval,
/// because pin 3a refused every base introduction and every base-bearing open
/// while no reduction authority existed to validate against. P13-S27 supplies
/// that authority, so a base-bearing container is constructible again and the
/// snapshot is wired to its correct semantic home: the manifest's
/// `canonical_base`.
///
/// The two assertions that lapsed are back, and they are the point of the
/// restoration: **`verify_canonical_chunks` covers the base branch again**
/// (including its `base.hash != base.root.hash` cross-check), and **the
/// reopened manifest actually carries the base**. It was never re-homed to
/// `acceleration_snapshots` — that field is verified nowhere in
/// `epiphany-bundle` (not in `open`, not in `verify_canonical_chunks`), so a
/// reference there would verify nothing while looking like preserved
/// coverage. Two assertions lapse until P13-S27 restores the wiring:
/// `verify_canonical_chunks`'s base branch (its `base.hash != base.root.hash`
/// cross-check), and the reopened manifest actually carrying the base. Both
/// are owed back in `spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md`.
/// `epiphany-bundle`, so a reference there would have verified nothing while
/// looking like preserved coverage.
///
/// After reopen, the snapshot payload is decoded through
/// [`MaterializedState::decode_canonical`], compared structurally with the
@ -262,18 +263,21 @@ pub fn assert_reduction_serialization_stable(envelopes: &[OperationEnvelope], se
"re-reduction changed the canonical score bytes"
);
// serialize: stage the canonical state as a real **Snapshot** chunk.
// P13-S27 / CONTRACT_FORMAT_EPOCH_MAJOR1.md pin 3c: the manifest's
// `canonical_base` is the snapshot's correct semantic home, but that
// wiring is suspended for the S28 -> P13-S27 interval (see the doc
// comment above) — the chunk is staged but not referenced from any
// manifest root, and its `ChunkRef` is captured directly from the commit
// closure instead.
// serialize: stage the canonical state as a real **Snapshot** chunk and
// wire it to its correct semantic home, the manifest's `canonical_base`.
// Restored by P13-S27 (test 7): the base's version is the authority this
// session states, so pin 3a validates the introduction rather than
// refusing it.
let mut rng = Rng::new(seed);
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
let snapshot = StagedChunk {
kind: ChunkKind::Snapshot,
schema_version: SchemaVersion::V0,
@ -282,21 +286,45 @@ pub fn assert_reduction_serialization_stable(envelopes: &[OperationEnvelope], se
let mut snapshot_root = None;
bundle
.commit(&[snapshot], |ctx| {
snapshot_root = Some(ctx.new_chunks[0]);
ctx.previous_manifest.clone()
let root = ctx.new_chunks[0];
snapshot_root = Some(root);
let mut m = ctx.previous_manifest.clone();
m.canonical_base = Some(SnapshotRef {
snapshot_id: SnapshotId(rng.array16()),
covers_causal_frontier: FrontierBytes::empty(),
reduction_algorithm_version: ReductionAlgorithmVersion(
epiphany_ops::CURRENT_REDUCTION_ALGORITHM_VERSION,
),
profile_id: ProfileId::Full,
hash: root.hash,
root,
});
m
})
.expect("commit snapshot");
.expect("commit snapshot as the canonical base");
let snapshot_root = snapshot_root.expect("commit ran the build closure");
let image = bundle.into_store().into_bytes();
// load: reopen from exactly those bytes; the snapshot chunk is read back
// directly by its `ChunkRef` (not through `manifest.canonical_base`,
// suspended above) — `read_chunk` hash-verifies any `ChunkRef`, so the
// cycle's integrity guarantee is unchanged.
let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen bundle");
// load: reopen from exactly those bytes. `verify_canonical_chunks` now
// covers the **base branch** again — RESTORED ASSERTION 1 of 2 — including
// its `base.hash != base.root.hash` cross-check.
let reopened =
Bundle::open(MemStore::from_bytes(image), crate::production_caps()).expect("reopen bundle");
reopened
.verify_canonical_chunks()
.expect("canonical chunks intact");
// RESTORED ASSERTION 2 of 2: the reopened manifest actually carries the
// base. Without this the harness passes on a base-free bundle and the
// restoration would be cosmetic.
let reopened_base = reopened
.manifest()
.canonical_base
.as_ref()
.expect("the reopened manifest must carry the canonical base");
assert_eq!(
reopened_base.root, snapshot_root,
"the reopened base must point at the snapshot chunk that was staged"
);
let loaded = reopened
.read_chunk(&snapshot_root)
.expect("read snapshot chunk back");
@ -347,8 +375,13 @@ pub fn assert_score_serialization_stable(score: &Score, frontier: &[u8], seed: u
let mut rng = Rng::new(seed);
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
let snapshot = StagedChunk {
kind: ChunkKind::Snapshot,
schema_version: SchemaVersion::for_major(3),
@ -377,7 +410,8 @@ pub fn assert_score_serialization_stable(score: &Score, frontier: &[u8], seed: u
// load: reopen (read-write — an acceleration snapshot at the current
// major is within the snapshot role's accept-set), hash-verify, read the
// referenced chunk back byte-identically.
let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen bundle");
let reopened =
Bundle::open(MemStore::from_bytes(image), crate::production_caps()).expect("reopen bundle");
assert!(
!reopened.is_read_only(),
"a current-major acceleration snapshot must not force read-only"
@ -525,8 +559,13 @@ pub fn assert_operation_block_summary_survives_storage(envelopes: &[OperationEnv
let mut rng = Rng::new(seed);
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create bundle");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create bundle");
// A real operation block (opaque payload bytes) carrying the summary.
let blocks: Vec<StagedChunk> = pack_operation_blocks(&[rng.byte_vec(4, 64)])
.into_iter()
@ -544,7 +583,8 @@ pub fn assert_operation_block_summary_survives_storage(envelopes: &[OperationEnv
// Reopen and select the summary by block id — no block payload is decoded.
let image = bundle.into_store().into_bytes();
let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen bundle");
let reopened =
Bundle::open(MemStore::from_bytes(image), crate::production_caps()).expect("reopen bundle");
let root_id = reopened.manifest().operation_roots[0].id;
assert_eq!(
reopened.manifest().operation_block_summary(root_id),
@ -599,6 +639,7 @@ mod tests {
MemStore::new(),
FileUuid(rng.array16()),
Manifest::empty(DocumentId(rng.array16())),
crate::production_caps(),
)
.expect("create bundle");
bundle
@ -609,7 +650,7 @@ mod tests {
})
.expect("commit op block");
let image = bundle.into_store().into_bytes();
Bundle::open(MemStore::from_bytes(image)).expect("reopen bundle")
Bundle::open(MemStore::from_bytes(image), crate::production_caps()).expect("reopen bundle")
}
#[test]
@ -788,8 +829,13 @@ mod tests {
// A real committed superblock from a live bundle.
let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16());
let mut bundle =
Bundle::create(MemStore::new(), uuid, Manifest::empty(doc)).expect("create");
let mut bundle = Bundle::create(
MemStore::new(),
uuid,
Manifest::empty(doc),
crate::production_caps(),
)
.expect("create");
bundle
.commit(
&[StagedChunk::operation_block(encode_block(&[vec![1u8; 8]]))],
@ -816,4 +862,95 @@ mod tests {
// Strong sensitivity: same identities, changed content → different bytes.
assert_content_mutation_changes_serialization();
}
/// P13-S27 test 10b — the test M5b breaks, and **the only place in the rung
/// where the real authority meets a canonical base**. In `epiphany-testkit`,
/// which may reach the real constant.
///
/// # Two provably independent operands
///
/// The fixture is built with `synthetic_for_fixture(0)` and commits a base
/// carrying the **literal** `ReductionAlgorithmVersion(0)`; the reopen then
/// supplies `production_caps()`, which wraps the real constant. One operand
/// is a literal written into a fixture, the other is the authority read at
/// the reopen — neither derived from the other.
///
/// **Round 3 caught the alternative**: if both the supplied capability and
/// the base version descended from `CURRENT_REDUCTION_ALGORITHM_VERSION`,
/// both would move together under M5b's mutation and the comparison would
/// pass for every value — §0.1's own tautology, reproduced inside the
/// mutation built to detect it. **Do not tidy either literal into the
/// constant** (§7 item 4b).
///
/// # Both `Result` arms are written deliberately
///
/// Round 5 pinned this: "assert it opens" was not enough, because under M5b
/// the reopen returns `Err` and **a `#[test]` returning `Err` asserts
/// nothing about that error's fields**. The `Err` arm below runs only under
/// mutation, and it is what makes M5b's required two-field observation a
/// *verified* one rather than a stack trace. The third arm exists so a
/// *different* error under mutation is reported rather than read as success.
#[test]
fn a_base_bearing_bundle_reopened_under_the_real_authority_validates() {
use epiphany_bundle::{BundleCapabilities, BundleError};
let mut bundle = Bundle::create(
MemStore::new(),
FileUuid([0x5E; 16]),
Manifest::empty(DocumentId([0x5E; 16])),
BundleCapabilities::synthetic_for_fixture(0),
)
.expect("fixture bundle creates");
let staged = StagedChunk {
kind: ChunkKind::Snapshot,
schema_version: SchemaVersion::V0,
payload: vec![5u8, 5, 5],
};
bundle
.commit(&[staged], |ctx| {
let mut m = ctx.previous_manifest.clone();
let root = ctx.new_chunks[0];
m.canonical_base = Some(SnapshotRef {
snapshot_id: SnapshotId([0x5E; 16]),
covers_causal_frontier: FrontierBytes::empty(),
// A deliberate LITERAL — not the constant. See above.
reduction_algorithm_version: ReductionAlgorithmVersion(0),
profile_id: ProfileId::Full,
hash: root.hash,
root,
});
m
})
.expect("committing the base under the matching synthetic capability succeeds");
let image = bundle.into_store().into_bytes();
match Bundle::open(MemStore::from_bytes(image), crate::production_caps()) {
Ok(reopened) => {
assert!(
reopened.manifest().canonical_base.is_some(),
"the base must survive the reopen, or this asserts nothing"
);
assert_eq!(
reopened
.manifest()
.canonical_base
.as_ref()
.unwrap()
.reduction_algorithm_version,
ReductionAlgorithmVersion(0)
);
}
Err(BundleError::CanonicalBaseRequiresRebuild { base, current }) => {
// Reached only under M5b. Assert both fields, then fail loudly
// quoting them — that is the mutation's required observation.
assert_eq!(base, ReductionAlgorithmVersion(0));
panic!(
"M5b observation: base={} current={} — the authority is load-bearing here",
base.0, current.0
);
}
Err(other) => panic!("unexpected error, not the authority verdict: {other:?}"),
}
}
}

View File

@ -48,13 +48,18 @@ fn a_bundle_round_trips_through_bytes_back_into_a_reduced_score() {
MemStore::new(),
FileUuid([3; 16]),
Manifest::empty(DocumentId([9; 16])),
epiphany_testkit::production_caps(),
)
.expect("create");
bundle.commit(&staged, append_roots).expect("commit");
let image = bundle.into_store().into_bytes();
// Close it, reopen it from nothing but the bytes.
let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen");
let reopened = Bundle::open(
MemStore::from_bytes(image),
epiphany_testkit::production_caps(),
)
.expect("reopen");
let mut recovered = Vec::new();
for chunk in reopened.manifest().operation_roots.clone() {

View File

@ -12,11 +12,11 @@ use std::path::{Path, PathBuf};
// +1 for req:format:container-epoch (the format-epoch rung, pin 7:
// spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md) — the container major becomes an epoch,
// and Chapter 8 states the classification and the epoch matrix normatively.
const CORE_REQUIREMENT_COUNT: usize = 213;
const CORE_REQUIREMENT_COUNT: usize = 214;
// +1 for req:textproj:manifest-schema-carried (G-minor, pins 8/11:
// spec/PLAN_GMINOR_SCHEMA_MINOR.md); +1 for req:format:container-epoch (above).
const SUITE_REQUIREMENT_COUNT: usize = 284;
const SUITE_LABEL_COUNT: usize = 284;
const SUITE_REQUIREMENT_COUNT: usize = 285;
const SUITE_LABEL_COUNT: usize = 285;
/// The normative chapter-to-area assignment. Keeping this as data makes adding a
/// requirement under the wrong chapter fail without encoding chapter names in

View File

@ -9,6 +9,8 @@ pub mod project;
pub mod serialize;
pub mod vectors;
use epiphany_bundle::BundleCapabilities;
use epiphany_bundle::{
ChunkKind, DocumentId, ExtensionId, FrontierBytes, LineageId, ProfileDeclaration, ProfileId,
ReductionAlgorithmVersion, SchemaVersion, SemVer, SnapshotId,
@ -172,3 +174,21 @@ pub struct TextBlob {
/// Uncompressed blob payload carried inline.
pub payload: Vec<u8>,
}
/// The capabilities a **production composition path** supplies: the reduction
/// semantics this build actually implements, wrapped at the composition
/// boundary.
///
/// This is the "real authority" side of pin 3b's split. Fixtures deliberately
/// exercising arbitrary wire values use
/// [`BundleCapabilities::synthetic_for_fixture`] instead, so that a fixture
/// asserting behaviour at version `7` keeps asserting it when the authority
/// moves. **Never use `synthetic_for_fixture` on a production path.**
#[must_use]
pub(crate) fn production_caps() -> BundleCapabilities {
BundleCapabilities {
current_reduction_version: ReductionAlgorithmVersion(
epiphany_ops::CURRENT_REDUCTION_ALGORITHM_VERSION,
),
}
}

View File

@ -980,8 +980,13 @@ mod tests {
let mut initial = Manifest::empty(DocumentId([3; 16]));
initial.lineage_id = Some(LineageId([4; 16]));
let mut bundle = Bundle::create(MemStore::new(), FileUuid([1; 16]), initial)
.expect("a freshly created bundle with no canonical roots is valid");
let mut bundle = Bundle::create(
MemStore::new(),
FileUuid([1; 16]),
initial,
crate::production_caps(),
)
.expect("a freshly created bundle with no canonical roots is valid");
bundle
.commit(
@ -1119,7 +1124,13 @@ mod tests {
fn a_corrupt_operation_envelope_is_a_typed_error_not_a_panic() {
let mut initial = Manifest::empty(DocumentId([5; 16]));
initial.lineage_id = None;
let mut bundle = Bundle::create(MemStore::new(), FileUuid([2; 16]), initial).unwrap();
let mut bundle = Bundle::create(
MemStore::new(),
FileUuid([2; 16]),
initial,
crate::production_caps(),
)
.unwrap();
let garbage_block = StagedChunk::operation_block(encode_block(&[vec![0xFF; 4]]));
bundle
.commit(&[garbage_block], |ctx| {
@ -1144,7 +1155,7 @@ mod tests {
let corrupt_at = extension_root.offset as usize;
image[corrupt_at] ^= 0xFF;
let corrupted = Bundle::open(MemStore::from_bytes(image))
let corrupted = Bundle::open(MemStore::from_bytes(image), crate::production_caps())
.expect("corrupting a non-canonical chunk's payload does not stop the bundle opening");
match document_from_bundle(&corrupted) {
Err(ProjectError::Bundle(_)) => {}

View File

@ -152,7 +152,12 @@ pub fn serialize_document<S: BlockStore>(
return Err(SerializeError::CanonicalBaseUnsupported);
}
let mut bundle = Bundle::create(store, file_uuid, empty_manifest(document))?;
let mut bundle = Bundle::create(
store,
file_uuid,
empty_manifest(document),
crate::production_caps(),
)?;
let mut staged = Vec::new();
if let Some(base) = &document.canonical_base {
@ -380,7 +385,8 @@ mod tests {
let bundle = serialize_document(document, MemStore::new(), FileUuid([1; 16]))
.expect("a well-formed document serializes");
let image = bundle.into_store().into_bytes();
Bundle::open(MemStore::from_bytes(image)).expect("the serialized bundle reopens")
Bundle::open(MemStore::from_bytes(image), crate::production_caps())
.expect("the serialized bundle reopens")
}
#[test]
@ -632,4 +638,32 @@ mod tests {
assert_eq!(reopened.manifest().document_id, document.document_id);
}
}
/// P13-S27 test 10a — the test M5a breaks. **In `epiphany-textproj`**,
/// because `epiphany-bundle` must not depend on `epiphany-ops` (pin 1, §0.3)
/// and so no test there can reach the real authority.
///
/// # The `0` is a deliberate LITERAL, and that is load-bearing
///
/// Comparing against `CURRENT_REDUCTION_ALGORITHM_VERSION` would compare the
/// constant with itself laundered through one function call: mutate the
/// constant and **both sides move**, so the assertion would hold for every
/// value and M5a could not break it. **Do not "tidy" this into the
/// constant** — doing so makes M5a vacuous while leaving every test green,
/// a failure invisible to the suite (contract §7 item 4b exists to catch it).
///
/// **This test is expected to fail when P13-S16 bumps the authority**, and
/// that is correct: the literal is a tripwire on the production wiring, and
/// S16 updating it is S16 stating that the authority moved.
#[test]
fn serialize_document_supplies_the_real_reduction_authority() {
let document = minimal_document(42);
let bundle = serialize_document(&document, MemStore::new(), FileUuid([1; 16]))
.expect("a base-free document serializes");
assert_eq!(
bundle.capabilities().current_reduction_version,
ReductionAlgorithmVersion(0),
"the production writer must supply the real authority, not a literal of its own"
);
}
}

View File

@ -10,8 +10,9 @@ them, having gone stale in two consecutive rounds by doing so.
execution is **reported, not patched in place** — if it needs a pin change, that
is its own amendment with its own review round.
**IMPLEMENTED 2026-08-09, STAGED, and NOT YET ACCEPTED.** The implementation remains
staged and uncommitted; only the amendments are committed.
**IMPLEMENTED AND ACCEPTED 2026-08-09, by the repository owner after execution review
8.** The implementation was staged for the required independent review and is committed
with this acceptance record.
**How many post-ratification reviews have closed, which amendment each produced, and
what each found are THE HISTORY TABLE'S ROWS. This block does not restate them —
@ -1046,9 +1047,10 @@ history table, and it shows that every independent round before this one found s
- **Every finding since execution has been in this contract, not in the 21 staged
files** — across all eight reviews.
**What remains is the owner's acceptance decision.** This document does not make it.
Round 1's ratification was claimed by the author after a single round and withdrawn;
**that precedent is why this block records the evidence and stops.**
**The repository owner accepted the implementation on 2026-08-09, after execution
review 8.** That decision is recorded here; it does not recast the evidence as proof of
correctness. Round 1's author-claimed ratification was withdrawn, which is why the
evidence and the owner's decision remain distinct.
**Review round 19 — 2026-08-08, independent, against the round-17/18 working
tree. ZERO FINDINGS. The first clean round in nineteen.**
@ -3059,7 +3061,8 @@ begin it. No `create_staff`, `create_staff_group`, or invariant change.
**Do not bump `CURRENT_REDUCTION_ALGORITHM_VERSION` past 0** — pin 2. The bump
to 1 belongs to S16.
**The executing agent MUST NOT commit.** Leave the work staged.
**Execution boundary (SATISFIED):** the executing agent MUST NOT commit and left the
work staged for independent review.
**Execution is AUTHORISED as of ratification, 2026-08-08.** *(This read "no
execution work may begin at all until this contract is ratified"; ratification has
@ -3067,9 +3070,10 @@ happened.)* The boundaries above are unchanged and remain binding — **stage on
§2's files by explicit path, never `git add -A`, re-check `HEAD` before staging
and before committing, and never `git reset`/`restore`/`checkout`/`stash`.**
**Leave the work STAGED. Do not commit.** The execution report is then subject to
**independent review before completion is accepted**, covering in particular
**M7's three observations and its control**.
**The executing agent left the work STAGED and did not commit.** The execution report
was then subject to **independent review before completion was accepted**, covering in
particular **M7's three observations and its control**. The repository owner accepted
the reviewed staged implementation on 2026-08-09.
---

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -11733,6 +11733,61 @@ pub struct SnapshotRef {
ignored or rebuilt if they disagree with the canonical document.
\end{requirement}
\begin{requirement}
\label{req:format:reduction-authority}
An implementation \MUST{} name the reduction semantics it
implements, and \MUST{} validate a canonical base against that
value rather than against a value the base itself supplied.
Comparing a base's \texttt{reduction\_algorithm\_version} only
against the active superblock's, as
Requirement~\ref{req:format:canonical-document-reduction}
requires, is necessary but \emph{not} sufficient: a writer that
seeds the superblock from the base's own self-report makes that
comparison an identity, so it holds for every conformingly
written document and detects only tampering. The semantics the
running implementation actually implements is a third value, and
it \MUST{} participate.
Accordingly:
\begin{itemize}
\item An implementation \MUST{} expose the reduction-algorithm
version it implements, and every reader and writer of a
canonical base \MUST{} state which semantics it implements
rather than infer it from the document.
\item A reader \MUST{} refuse to open a document whose
canonical base's \texttt{reduction\_algorithm\_version}
differs from the semantics the reader implements. A document
with no canonical base \MUST{} open regardless, since it
carries no reduced state that could be stale.
\item A writer \MUST{} refuse to emit or replace a canonical
base whose \texttt{reduction\_algorithm\_version} differs
from the semantics the writer implements. A commit that does
not emit or replace the base \MUSTNOT{} be refused on this
ground.
\item The refusal \MUSTNOT{} degrade to a read-only open and
\MUSTNOT{} be reported as an integrity anomaly. A stale base
is not a restricted-but-correct view of the document; it is
state computed under different rules, and serving it
read-only would present incorrect canonical state as
authoritative.
\item A refusal on this ground \MUST{} remain distinguishable
from a malformed-document failure. A base whose version
disagrees with its own superblock is corrupt, and corruption
\MUST{} be reported as such: the two conditions detect
different faults, and collapsing them loses the distinction
this requirement rests on.
\end{itemize}
Rebuilding is out of scope for opening. An implementation
\MUSTNOT{} silently rebuild a stale base during open, because
the envelopes required to rebuild may have been pruned
(Requirement~\ref{req:format:pruning-state-preservation}); a rebuild
path is sound only
where the full pre-base history is demonstrably present.
\end{requirement}
\subsection{Pruning}
\begin{requirement}
@ -16820,6 +16875,28 @@ layouts they own versus inherit:
staleness. \sectionsc{Schema Versioning} gains the corresponding
distinction between the schema axis and the container axis.
\\
\today & Ch.~\ref{ch:format} & \sectionsc{The Canonical Document Identity}
gains Requirement~\ref{req:format:reduction-authority}: an implementation
\MUST{} name the reduction semantics it implements and validate a canonical
base against \emph{that}, not against a value the base itself supplied.
Comparing the base's \texttt{reduction\_algorithm\_version} only against
the active superblock's is necessary but not sufficient, because a writer
that seeds the superblock from the base's own self-report makes the
comparison an identity --- so it holds for every conformingly written
document and detects only tampering. The requirement therefore introduces
the implementation's own semantics as a third participant, required at both
boundaries: a reader refuses a base that disagrees with the semantics it
implements, and a writer refuses to emit or replace one. A document with no
canonical base opens regardless, carrying no reduced state that could be
stale, and a commit that does not touch the base is not refused on this
ground. The refusal may not degrade to a read-only open nor be reported as
an integrity anomaly --- a stale base is not a restricted-but-correct view
but state computed under different rules --- and it stays distinguishable
from the malformed-document failure a base disagreeing with its own
superblock produces, since the two detect different faults. Rebuilding
during open is prohibited outright, because the envelopes a rebuild would
need may have been pruned.
\\
\bottomrule
\end{longtable}