Container major 1: the format epoch carries base provenance

FORMAT_MAJOR becomes 1 and FORMAT_MINOR restarts at 0. The decoder
stops being exact-major-only: it classifies three ways through a named
FormatEpoch carried on FixedHeader, so major 0 is decoded deliberately
as legacy rather than refused. Old readers already fail closed on an
unknown major, so that half needed no mechanism — which is why the major
is the right carrier, and why the header's immutability, fatal to
FORMAT_MINOR as a provenance field, is what makes it sound as an epoch
field.

The matrix: a major-0 bundle with no base may open; one carrying a base
is refused; one attempting to add a base is refused and told to repack.
That last row is the non-inheritance rule. Three errors, none of which
degrades to read-only: two permanent legacy/repack errors, and
ReductionAuthorityUnavailable, which is temporary, names P13-S27, and
must not say repack — a major-1 container is already the right epoch.

Until P13-S27 lands, both major-1 base boundaries are closed: opening a
major-1 bundle that already carries a base, and committing one into it.
Neither may be left open while the epoch asserts a validation that never
ran.

Text projection cannot mint a base. serialize_document staged a carried
base into a fresh bundle and build_manifest wrote it, so an old or
hand-authored document could be laundered straight through the boundary.
All three sides now refuse: projection, parsing, and a new dedicated
SerializeError variant — none of which existed to be "retained".
COMPANION_VERSION moves to 0.14.0 and the corpus is rebuilt to 20
vectors and ten rejection classes, with canonical_bases reach dropping
2 -> 0. That is a real capability loss and is recorded as one.

Corruption keeps precedence in both epochs: a corrupt major-1 base fails
as malformed, never as the temporary authority error a user would
reasonably retry.

All 11 mutations were run and observed, not reasoned about. M4 is the
signing one — with the legacy commit refusal removed, a legacy container
gains a base in place, which is exactly the counterexample that killed
FORMAT_MINOR. M11 confirms the third error is distinct while test 3
stays green, proving the mutation stayed inside the major-1 branches. M7
fails on both epoch halves. M8 falls through to
SerializeError::Bundle(ReductionAuthorityUnavailable), confirming the
text layer's own refusal is what the test asserts.

Two touch-table gaps surfaced during execution, both the same shape: a
.tex requirement addition moves hardcoded counts in
requirement_labels.rs, and the companion bump moves a second normative
version literal spelled version~0.13.0 rather than (0 13 0). Neither
file was in any touch table; the second was caught only because a test
exists for exactly that failure.

P13-S27 is unblocked — its pin 2a is resolved from outside, as its own
prohibition required — and inherits three obligations: both interim
refusals converted to validation, M8's deferred laundering
demonstration, and pin 3c's two suspended conformance assertions.
P13-S16 remains blocked on S27.

Workspace green at 1569.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
This commit is contained in:
Levi Neuwirth 2026-08-07 12:51:14 -04:00
parent 973297e1a1
commit bc06706e41
21 changed files with 1241 additions and 283 deletions

View File

@ -18,7 +18,7 @@ use crate::chunk::{
}; };
use crate::codec::DecodeError; use crate::codec::DecodeError;
use crate::error::{BundleError, IntegrityAnomaly}; use crate::error::{BundleError, IntegrityAnomaly};
use crate::header::{FixedHeader, SLOT_A_OFFSET, SLOT_B_OFFSET}; use crate::header::{FixedHeader, FormatEpoch, SLOT_A_OFFSET, SLOT_B_OFFSET};
use crate::ids::{BlobId, FileUuid, ReductionAlgorithmVersion, SchemaVersion, WallClockTime}; use crate::ids::{BlobId, FileUuid, ReductionAlgorithmVersion, SchemaVersion, WallClockTime};
use crate::manifest::{BlobRef, Manifest, ProfileDeclaration}; use crate::manifest::{BlobRef, Manifest, ProfileDeclaration};
use crate::opindex::OperationIndex; use crate::opindex::OperationIndex;
@ -408,6 +408,19 @@ impl<S: BlockStore> Bundle<S> {
"canonical base profile is not declared by the manifest", "canonical base profile is not declared by the manifest",
))); )));
} }
// Format-epoch matrix (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3,
// rows 2 and 5i). Corruption precedence runs first (both checks
// above): only once the base is structurally sound do we ask
// whether this epoch may open it at all. A legacy (major-0)
// container can never open with a base — the epoch is not
// retroactive (row 2, permanent). A major-1 container is the
// right epoch, but until P13-S27 lands there is no
// reduction-authority capability to validate it against (row 5i,
// interim — TEMPORARY, removed by P13-S27, pin 3a).
return Err(match header.epoch {
FormatEpoch::Legacy => BundleError::LegacyBundleHasCanonicalBase,
FormatEpoch::Current => BundleError::ReductionAuthorityUnavailable,
});
} }
// An unknown *required* extension forces read-only (Chapter 8 §"Behavior // An unknown *required* extension forces read-only (Chapter 8 §"Behavior
// Under Unknown Extensions"). // Under Unknown Extensions").
@ -761,6 +774,28 @@ impl<S: BlockStore> Bundle<S> {
// least one declared profile. // least one declared profile.
let active_profile = validate_emittable_manifest(&manifest)?; let active_profile = validate_emittable_manifest(&manifest)?;
// Format-epoch matrix (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3, rows 3
// and 6i): a commit may never introduce (or replace) a canonical
// base. `self.manifest.canonical_base` is always `None` here — `open`
// and `create` both already refuse ever producing a live bundle whose
// base is `Some`, so any `Some` reaching this point is necessarily a
// fresh introduction. Row 3 — a legacy (major-0) container can never
// become base-bearing in place, because its header can never say the
// base was validated (permanent; the epoch is not inheritable). Row
// 6i — a major-1 container is the right epoch, but until P13-S27
// lands there is no reduction-authority capability to validate a
// newly introduced base against (interim — TEMPORARY, removed by
// P13-S27, pin 3a). Placed after `validate_emittable_manifest` (which
// already rejects a base whose profile is undeclared) so that a
// structurally malformed base is still reported as malformed, not
// masked by this categorical epoch refusal.
if manifest.canonical_base.is_some() {
return Err(match self.header.epoch {
FormatEpoch::Legacy => BundleError::LegacyBaseIntroductionRejected,
FormatEpoch::Current => BundleError::ReductionAuthorityUnavailable,
});
}
// Before publishing, validate that every canonical root the new manifest // Before publishing, validate that every canonical root the new manifest
// declares actually resolves to a present, hash-intact chunk of the right // declares actually resolves to a present, hash-intact chunk of the right
// kind and shape (the new chunks are written and flushed; retained roots // kind and shape (the new chunks are written and flushed; retained roots
@ -1489,49 +1524,51 @@ mod tests {
// Major 2: the MaterializedState embeds no data-model-major values, // Major 2: the MaterializedState embeds no data-model-major values,
// and re-stamping byte-identical content churns its address). With // and re-stamping byte-identical content churns its address). With
// the Snapshot KIND now admitting the major-2 acceleration form, the // the Snapshot KIND now admitting the major-2 acceleration form, the
// per-role check must catch a mis-stamped base: read-only + anomaly, // per-role check (`mis_stamped_canonical_base`) must catch a
// at commit and again on reopen. // mis-stamped base.
let mut bundle = fresh_bundle(); //
let base = StagedChunk { // CONTRACT_FORMAT_EPOCH_MAJOR1.md pin 3a (the "named trap"): this test
// used to commit the mis-stamped base directly and assert read-only
// immediately, both at commit and at reopen. Neither half is
// reachable any more — pin 3a's epoch guard (rows 2/3/5i/6i, both
// exercised above in this module) now refuses *any* canonical base,
// mis-stamped or not, before `mis_stamped_canonical_base` is ever
// consulted, at both `open` and `commit`. That guard is a different
// axis (container format major) from this one (data-model schema
// major) and does not contradict pin 4 — `ReductionAuthorityUnavailable`
// must not degrade to read-only — so the fix is not to weaken pin 4
// toward this test. It is to test what remains reachable:
// `mis_stamped_canonical_base` itself, a pure function of a
// `Manifest`, independent of the now-categorical bundle-lifecycle
// refusal that sits in front of it.
let mut m = Manifest::empty(DocumentId([9; 16]));
let payload = vec![7u8, 7, 7];
let hash = chunk_content_hash(ChunkKind::Snapshot, SchemaVersion::V1, &payload);
let root = ChunkRef {
id: ChunkId(hash),
kind: ChunkKind::Snapshot, kind: ChunkKind::Snapshot,
schema_version: SchemaVersion::V1, schema_version: SchemaVersion::V1,
payload: vec![7u8, 7, 7], offset: BODY_START,
compressed_length: payload.len() as u64,
uncompressed_length: payload.len() as u64,
compression: CompressionAlgorithm::None,
hash,
}; };
bundle
.commit(&[base], |ctx| {
let mut m = ctx.previous_manifest.clone();
let root = ctx.new_chunks[0];
let mut sid = [0u8; 16];
sid.copy_from_slice(&root.hash.as_bytes()[..16]);
m.canonical_base = Some(SnapshotRef { m.canonical_base = Some(SnapshotRef {
snapshot_id: SnapshotId(sid), snapshot_id: SnapshotId([1; 16]),
covers_causal_frontier: FrontierBytes::from_bytes(vec![]), covers_causal_frontier: FrontierBytes::empty(),
reduction_algorithm_version: ReductionAlgorithmVersion(0), reduction_algorithm_version: ReductionAlgorithmVersion(0),
profile_id: ProfileId::Full, profile_id: ProfileId::Full,
hash: root.hash, hash: root.hash,
root, root,
}); });
m assert_eq!(
}) mis_stamped_canonical_base(&m),
.expect("a structurally-valid mis-stamped base is publishable"); Some(1),
assert!( "a canonical base root stamped above major 0 is still flagged by the per-role \
bundle.is_read_only(), check, even though pin 3a's epoch guard now intercepts every base before this \
"a mis-stamped canonical base forces read-only at commit" check ever runs"
); );
assert!(bundle.anomalies().iter().any(|a| matches!(
a,
IntegrityAnomaly::UnsupportedCanonicalChunkMajor { schema_major: 1 }
)));
let image = bundle.into_store().into_bytes();
let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen");
assert!(
reopened.is_read_only(),
"a mis-stamped canonical base forces read-only at open"
);
assert!(reopened.anomalies().iter().any(|a| matches!(
a,
IntegrityAnomaly::UnsupportedCanonicalChunkMajor { schema_major: 1 }
)));
} }
fn fresh_bundle() -> Bundle<MemStore> { fn fresh_bundle() -> Bundle<MemStore> {
@ -1543,6 +1580,307 @@ mod tests {
.unwrap() .unwrap()
} }
// -----------------------------------------------------------------
// CONTRACT_FORMAT_EPOCH_MAJOR1.md: format-epoch matrix (pins 1-4).
//
// After this rung no `create`/`commit` path may ever produce a
// base-bearing bundle (pin 3c): `create` already refused one, and rows
// 3/6i below now refuse committing one into either epoch, so a
// base-bearing fixture can only be built as a hand-crafted image, the
// same technique `craft_image`/`craft_image_with_manifest_bytes` already
// use further down this file.
// -----------------------------------------------------------------
/// A fixed header at `format_major`, bypassing `FixedHeader::new` (which
/// always stamps the *current* epoch) so a legacy (major-0) fixture can
/// be built too. Same technique as `craft_image` below: public fields,
/// public `encode()`.
fn header_for_major(format_major: u16, file_uuid: FileUuid) -> FixedHeader {
let epoch = if format_major == 0 {
FormatEpoch::Legacy
} else {
FormatEpoch::Current
};
FixedHeader {
format_major,
format_minor: 0,
header_length: crate::header::HEADER_LEN as u32,
superblock_a_offset: SLOT_A_OFFSET,
superblock_b_offset: SLOT_B_OFFSET,
file_uuid,
epoch,
}
}
/// A minimal valid bundle image at `format_major`, with no canonical base
/// (matrix rows 1 and 4).
fn craft_image_epoch(format_major: u16) -> Vec<u8> {
let mut m = Manifest::empty(DocumentId([1; 16]));
m.profile_declarations = vec![ProfileDeclaration::full()];
let payload = m.encode();
let mut image = vec![0u8; BODY_START as usize];
image.extend_from_slice(&payload);
let sb = Superblock {
generation: 0,
manifest_offset: BODY_START,
manifest_length: payload.len() as u64,
manifest_hash: manifest_chunk_hash(&payload),
manifest_schema_version: SchemaVersion::V0,
reduction_algorithm_version: ReductionAlgorithmVersion(0),
profile_id: ProfileId::Full,
commit_state: CommitState::Committed,
commit_timestamp: WallClockTime(0),
};
image[0..crate::header::HEADER_LEN as usize]
.copy_from_slice(&header_for_major(format_major, FileUuid([1; 16])).encode());
image[SLOT_A_OFFSET as usize..SLOT_A_OFFSET as usize + SUPERBLOCK_LEN as usize]
.copy_from_slice(&sb.encode());
image
}
/// A bundle image at `format_major` carrying a canonical base wired
/// directly into the manifest and superblock bytes, bypassing
/// `create`/`commit` entirely — the only way left to build this fixture
/// (pin 3c). `base_schema_major` mis-stamps the base's own root
/// independent of the container's format major; `base_version` and
/// `superblock_version` are equal for a self-consistent base (rows
/// 2/5i/11) and unequal for the corrupt-base precedence fixture (test 7).
fn craft_image_with_base(
format_major: u16,
base_schema_major: u16,
base_version: ReductionAlgorithmVersion,
superblock_version: ReductionAlgorithmVersion,
) -> Vec<u8> {
let base_schema = SchemaVersion::new(base_schema_major, 0);
let snap_payload = vec![7u8, 7, 7];
let snap_hash = chunk_content_hash(ChunkKind::Snapshot, base_schema, &snap_payload);
let root = ChunkRef {
id: ChunkId(snap_hash),
kind: ChunkKind::Snapshot,
schema_version: base_schema,
offset: BODY_START,
compressed_length: snap_payload.len() as u64,
uncompressed_length: snap_payload.len() as u64,
compression: CompressionAlgorithm::None,
hash: snap_hash,
};
let mut m = Manifest::empty(DocumentId([1; 16]));
m.profile_declarations = vec![ProfileDeclaration::full()];
m.canonical_base = Some(SnapshotRef {
snapshot_id: SnapshotId([1; 16]),
covers_causal_frontier: FrontierBytes::empty(),
reduction_algorithm_version: base_version,
profile_id: ProfileId::Full,
hash: root.hash,
root,
});
let manifest_payload = m.encode();
let manifest_offset = BODY_START + snap_payload.len() as u64;
let mut image = vec![0u8; BODY_START as usize];
image.extend_from_slice(&snap_payload);
image.extend_from_slice(&manifest_payload);
let sb = Superblock {
generation: 0,
manifest_offset,
manifest_length: manifest_payload.len() as u64,
manifest_hash: manifest_chunk_hash(&manifest_payload),
manifest_schema_version: SchemaVersion::V0,
reduction_algorithm_version: superblock_version,
profile_id: ProfileId::Full,
commit_state: CommitState::Committed,
commit_timestamp: WallClockTime(0),
};
image[0..crate::header::HEADER_LEN as usize]
.copy_from_slice(&header_for_major(format_major, FileUuid([1; 16])).encode());
image[SLOT_A_OFFSET as usize..SLOT_A_OFFSET as usize + SUPERBLOCK_LEN as usize]
.copy_from_slice(&sb.encode());
image
}
/// `Bundle<S>` has no `Debug` impl, so `Result::expect_err`/`unwrap_err`
/// cannot be used directly on `Bundle::open`'s result; this extracts the
/// error by hand.
fn open_err(image: Vec<u8>, context: &str) -> BundleError {
match Bundle::open(MemStore::from_bytes(image)) {
Err(e) => e,
Ok(_) => panic!("{context}"),
}
}
#[test]
fn a_legacy_major_0_bundle_without_a_base_opens() {
// Matrix row 1: a legacy container with no base opens cleanly —
// nothing unverifiable is exposed.
let image = craft_image_epoch(0);
let bundle = Bundle::open(MemStore::from_bytes(image))
.expect("a legacy bundle without a base opens");
assert!(!bundle.is_read_only());
assert!(bundle.anomalies().is_empty());
assert_eq!(bundle.header().epoch, FormatEpoch::Legacy);
}
#[test]
fn a_legacy_major_0_bundle_with_a_base_is_rejected() {
// Matrix row 2: a legacy container already carrying a
// (self-consistent) base is a hard reject at open — a pre-epoch base
// was never validated against a reduction authority.
let image = craft_image_with_base(
0,
0,
ReductionAlgorithmVersion(0),
ReductionAlgorithmVersion(0),
);
let err = open_err(image, "a legacy base-bearing bundle must not open");
assert!(matches!(err, BundleError::LegacyBundleHasCanonicalBase));
assert!(!matches!(err, BundleError::LegacyBaseIntroductionRejected));
assert!(!matches!(err, BundleError::ReductionAuthorityUnavailable));
}
#[test]
fn adding_a_base_to_a_legacy_bundle_is_rejected_and_names_repack() {
// Matrix row 3: a legacy container that opened clean under row 1
// must not become base-bearing in place — the epoch is not
// inheritable, because its header can never say the base was
// validated.
let image = craft_image_epoch(0);
let mut bundle = Bundle::open(MemStore::from_bytes(image))
.expect("a legacy bundle without a base opens");
let staged = StagedChunk {
kind: ChunkKind::Snapshot,
schema_version: SchemaVersion::V0,
payload: vec![7u8, 7, 7],
};
let err = 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([1; 16]),
covers_causal_frontier: FrontierBytes::empty(),
reduction_algorithm_version: ReductionAlgorithmVersion(0),
profile_id: ProfileId::Full,
hash: root.hash,
root,
});
m
})
.expect_err("committing a base into a legacy bundle must be refused");
assert!(matches!(err, BundleError::LegacyBaseIntroductionRejected));
assert!(!matches!(err, BundleError::LegacyBundleHasCanonicalBase));
assert!(!matches!(err, BundleError::ReductionAuthorityUnavailable));
assert!(
err.to_string().to_lowercase().contains("repack"),
"row-3 error must name repack: {err}"
);
assert_eq!(
bundle.generation(),
0,
"the refused commit did not advance the bundle"
);
}
#[test]
fn a_major_1_bundle_round_trips_and_refuses_to_introduce_a_base() {
// Matrix row 4 (round-trip half) + row 6i (refusal half). Renamed
// from "..._validates_its_base", which pin 3a forbids claiming until
// P13-S27 lands: this rung does not validate a base, it refuses one
// outright.
let mut bundle = fresh_bundle();
assert_eq!(bundle.header().epoch, FormatEpoch::Current);
// Round trip: ordinary non-base-bearing history commits and reopens.
let block = StagedChunk::operation_block(block::encode_block(&[vec![1u8, 2, 3]]));
bundle
.commit(&[block], |ctx| {
let mut m = ctx.previous_manifest.clone();
m.operation_roots.push(ctx.new_chunks[0]);
m
})
.expect("a major-1 bundle commits ordinary non-base-bearing history");
let image = bundle.into_store().into_bytes();
let mut reopened =
Bundle::open(MemStore::from_bytes(image)).expect("a major-1 bundle round trips");
assert!(!reopened.is_read_only());
assert_eq!(reopened.manifest().operation_roots.len(), 1);
// Refusal: introducing a base is refused (row 6i), distinctly from
// both legacy errors.
let staged = StagedChunk {
kind: ChunkKind::Snapshot,
schema_version: SchemaVersion::V0,
payload: vec![9u8, 9, 9],
};
let err = reopened
.commit(&[staged], |ctx| {
let mut m = ctx.previous_manifest.clone();
let root = ctx.new_chunks[0];
m.canonical_base = Some(SnapshotRef {
snapshot_id: SnapshotId([2; 16]),
covers_causal_frontier: FrontierBytes::empty(),
reduction_algorithm_version: ReductionAlgorithmVersion(0),
profile_id: ProfileId::Full,
hash: root.hash,
root,
});
m
})
.expect_err(
"a major-1 bundle must refuse to introduce a base during the S28 -> P13-S27 interval",
);
assert!(matches!(err, BundleError::ReductionAuthorityUnavailable));
assert!(!matches!(err, BundleError::LegacyBundleHasCanonicalBase));
assert!(!matches!(err, BundleError::LegacyBaseIntroductionRejected));
}
#[test]
fn a_corrupt_base_fails_as_malformed_before_any_epoch_error() {
// Pin 3's precedence rule, in BOTH epochs: a base whose version
// disagrees with its superblock's is corrupt and must fail with the
// existing malformed-bundle error — never row 2's legacy error, and
// never row 5i's `ReductionAuthorityUnavailable`. Collapsing
// tampering into staleness would erase the distinction P13-S27's
// authority check rests on. Renamed from `..._corrupt_legacy_base...`:
// the major-1 half is the one an earlier draft missed.
for format_major in [0u16, 1u16] {
let image = craft_image_with_base(
format_major,
0,
ReductionAlgorithmVersion(1),
ReductionAlgorithmVersion(2), // disagrees with the base's own version
);
let err = open_err(image, "a corrupt base must not open");
assert!(
matches!(err, BundleError::Decode(DecodeError::Malformed(_))),
"format_major {format_major}: expected the malformed-bundle error, got {err:?}"
);
assert!(!matches!(err, BundleError::LegacyBundleHasCanonicalBase));
assert!(!matches!(err, BundleError::ReductionAuthorityUnavailable));
}
}
#[test]
fn opening_a_major_1_bundle_that_already_carries_a_base_is_refused() {
// Matrix row 5i, the read-side branch pin 3a adds — an earlier draft
// omitted it entirely.
let image = craft_image_with_base(
1,
0,
ReductionAlgorithmVersion(0),
ReductionAlgorithmVersion(0),
);
let err = open_err(
image,
"a major-1 bundle already carrying a base must not open during the interval",
);
assert!(matches!(err, BundleError::ReductionAuthorityUnavailable));
assert!(!matches!(err, BundleError::LegacyBundleHasCanonicalBase));
assert!(!matches!(err, BundleError::LegacyBaseIntroductionRejected));
}
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// G-minor (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4): s10, s11, s12. // G-minor (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4): s10, s11, s12.
// ----------------------------------------------------------------- // -----------------------------------------------------------------

View File

@ -116,6 +116,40 @@ pub enum BundleError {
/// extension, or a recovery/anomaly open). Chapter 8 §"Behavior Under /// extension, or a recovery/anomaly open). Chapter 8 §"Behavior Under
/// Unknown Extensions" / §"Superblock Selection". /// Unknown Extensions" / §"Superblock Selection".
ReadOnly, ReadOnly,
/// A legacy (format major 0) container's manifest already carries a
/// canonical base (`spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md` matrix row 2).
/// A pre-epoch base was never validated against a reduction authority, so
/// it is not safe materialized state — the document already contains
/// unverifiable canonical state. **Permanent**: legacy containers never
/// gain that validation retroactively. The fix is to repack into a fresh
/// major-1 bundle; that flow is not built by this rung (pin 5) and this
/// error MUST NOT degrade to a read-only open — a pre-authority base is
/// not a restricted-but-correct view.
LegacyBundleHasCanonicalBase,
/// A commit into a legacy (format major 0) container attempted to add or
/// replace the canonical base (matrix row 3). The document itself is
/// fine, but this operation cannot be performed in this container: a
/// bundle that opened clean under row 1 must not become base-bearing in
/// place, because its header can never say it was validated — the epoch
/// is not inheritable. **Permanent**, like
/// [`BundleError::LegacyBundleHasCanonicalBase`]. The fix is to repack
/// into a fresh major-1 bundle; not built by this rung (pin 5), and this
/// 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,
} }
impl core::fmt::Display for BundleError { impl core::fmt::Display for BundleError {
@ -187,6 +221,18 @@ impl core::fmt::Display for BundleError {
} }
BundleError::Decode(e) => write!(f, "decode error: {e}"), BundleError::Decode(e) => write!(f, "decode error: {e}"),
BundleError::ReadOnly => f.write_str("bundle is open read-only; edits are refused"), BundleError::ReadOnly => f.write_str("bundle is open read-only; edits are refused"),
BundleError::LegacyBundleHasCanonicalBase => f.write_str(
"legacy (format major 0) bundle carries a canonical base, which was never \
validated against a reduction authority; repack into a fresh major-1 bundle",
),
BundleError::LegacyBaseIntroductionRejected => f.write_str(
"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",
),
} }
} }
} }

View File

@ -36,10 +36,39 @@ pub const SLOT_A_OFFSET: u64 = 64;
pub const SLOT_B_OFFSET: u64 = 320; pub const SLOT_B_OFFSET: u64 = 320;
/// The format major version this crate writes and understands. /// The format major version this crate writes and understands.
pub const FORMAT_MAJOR: u16 = 0; ///
/// **Format epoch** (`spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`): a major-1
/// container is one whose every base-bearing commit was validated against a
/// supplied reduction authority. Major 0 is the pre-epoch **legacy** format —
/// still decoded, deliberately, but never trusted to carry a canonical base
/// (see [`FormatEpoch`]). A new major restarts minor numbering, which is why
/// [`FORMAT_MINOR`] resets to `0` here rather than continuing from legacy's
/// `1`.
pub const FORMAT_MAJOR: u16 = 1;
/// The format minor version this crate writes. /// The format minor version this crate writes.
pub const FORMAT_MINOR: u16 = 1; ///
/// Reset to `0` by the major-1 epoch bump (`spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`
/// pin 1): a new major restarts minor numbering.
pub const FORMAT_MINOR: u16 = 0;
/// Which format-epoch a decoded header belongs to
/// (`spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 2). A named enum rather than a
/// boolean so the legacy case is a value the type system carries — every
/// consumer of the epoch matches this, rather than re-deriving
/// `format_major == 0` at each use site.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum FormatEpoch {
/// Format major 0: decoded deliberately, for backward compatibility, but
/// never trusted to carry a canonical base (the epoch is not retroactive
/// — `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3, row 2/3).
Legacy,
/// Format major 1: the current epoch. Every base-bearing commit is meant
/// to be validated against a supplied reduction authority — the
/// capability itself lands with P13-S27; until then this rung refuses
/// base introduction outright (pin 3a).
Current,
}
/// Byte range covered by the header CRC: everything before the CRC field. /// Byte range covered by the header CRC: everything before the CRC field.
const HEADER_CRC_RANGE: usize = 60; const HEADER_CRC_RANGE: usize = 60;
@ -59,6 +88,9 @@ pub struct FixedHeader {
pub superblock_b_offset: u64, pub superblock_b_offset: u64,
/// Physical-bundle UUID, set at creation, changed on Save As. /// Physical-bundle UUID, set at creation, changed on Save As.
pub file_uuid: FileUuid, pub file_uuid: FileUuid,
/// The format epoch this header's major classifies to (pin 2). Carried
/// on the header rather than re-derived at each call site.
pub epoch: FormatEpoch,
} }
impl FixedHeader { impl FixedHeader {
@ -71,6 +103,7 @@ impl FixedHeader {
superblock_a_offset: SLOT_A_OFFSET, superblock_a_offset: SLOT_A_OFFSET,
superblock_b_offset: SLOT_B_OFFSET, superblock_b_offset: SLOT_B_OFFSET,
file_uuid, file_uuid,
epoch: FormatEpoch::Current,
} }
} }
@ -116,12 +149,20 @@ impl FixedHeader {
let _magic = r.take_array::<8>()?; let _magic = r.take_array::<8>()?;
let format_major = r.get_u16()?; let format_major = r.get_u16()?;
let format_minor = r.get_u16()?; let format_minor = r.get_u16()?;
if format_major != FORMAT_MAJOR { // Pin 2's explicit three-way classification: 0 is legacy (decoded
// deliberately, marked as such), FORMAT_MAJOR is current, anything
// else is unsupported — unchanged from the pre-epoch exact-major
// rejection for that third arm.
let epoch = match format_major {
0 => FormatEpoch::Legacy,
FORMAT_MAJOR => FormatEpoch::Current,
_ => {
return Err(BundleError::UnsupportedFormatVersion { return Err(BundleError::UnsupportedFormatVersion {
major: format_major, major: format_major,
minor: format_minor, minor: format_minor,
}); })
} }
};
let header_length = r.get_u32()?; let header_length = r.get_u32()?;
if header_length != HEADER_LEN as u32 { if header_length != HEADER_LEN as u32 {
return Err(BundleError::UnsupportedHeaderLength { return Err(BundleError::UnsupportedHeaderLength {
@ -147,6 +188,7 @@ impl FixedHeader {
superblock_a_offset, superblock_a_offset,
superblock_b_offset, superblock_b_offset,
file_uuid, file_uuid,
epoch,
}) })
} }
} }
@ -155,6 +197,14 @@ impl FixedHeader {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn format_epoch_constants_are_major_1_minor_0() {
// Gate 7 (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 1): the epoch bump,
// asserted in a test rather than only by reading the constants.
assert_eq!(FORMAT_MAJOR, 1);
assert_eq!(FORMAT_MINOR, 0);
}
#[test] #[test]
fn header_round_trips() { fn header_round_trips() {
let h = FixedHeader::new(FileUuid([0xAB; 16])); let h = FixedHeader::new(FileUuid([0xAB; 16]));
@ -192,6 +242,21 @@ mod tests {
)); ));
} }
#[test]
fn an_unknown_major_is_still_unsupported_format_version() {
// Pin 2's third arm: neither 0 (legacy) nor FORMAT_MAJOR (current) —
// still hard-rejected, exactly as the pre-epoch exact-major check was
// (`CONTRACT_FORMAT_EPOCH_MAJOR1.md`).
let mut bytes = FixedHeader::new(FileUuid::ZERO).encode();
bytes[8..10].copy_from_slice(&2u16.to_le_bytes()); // format_major = 2
let crc = crc32c(&bytes[0..60]);
bytes[60..64].copy_from_slice(&crc.to_le_bytes());
assert!(matches!(
FixedHeader::decode(&bytes),
Err(BundleError::UnsupportedFormatVersion { major: 2, minor: 0 })
));
}
#[test] #[test]
fn reserved_bytes_are_ignored_on_read() { fn reserved_bytes_are_ignored_on_read() {
// A future minor version may use reserved bytes; current readers must // A future minor version may use reserved bytes; current readers must

View File

@ -81,7 +81,7 @@ pub use crc::crc32c;
pub use epiphany_determinism::{ChunkId, ContentHash}; pub use epiphany_determinism::{ChunkId, ContentHash};
pub use error::{BundleError, IntegrityAnomaly}; pub use error::{BundleError, IntegrityAnomaly};
pub use header::{ pub use header::{
FixedHeader, FORMAT_MAJOR, FORMAT_MINOR, HEADER_LEN, SLOT_A_OFFSET, SLOT_B_OFFSET, FixedHeader, FormatEpoch, FORMAT_MAJOR, FORMAT_MINOR, HEADER_LEN, SLOT_A_OFFSET, SLOT_B_OFFSET,
}; };
pub use ids::{ pub use ids::{
BlobId, DocumentId, ExtensionId, FileUuid, FrontierBytes, LineageId, ManifestId, BlobId, DocumentId, ExtensionId, FileUuid, FrontierBytes, LineageId, ManifestId,

View File

@ -17,8 +17,11 @@
//! deliberately not `std::env::temp_dir()`, which is commonly tmpfs on Linux, //! deliberately not `std::env::temp_dir()`, which is commonly tmpfs on Linux,
//! where fsync is a near-no-op and the commit budget would be measured against //! where fsync is a near-no-op and the commit budget would be measured against
//! RAM. The corpus is moderate: 1,000 generated operation envelopes packed //! RAM. The corpus is moderate: 1,000 generated operation envelopes packed
//! into operation blocks plus a canonical `MaterializedState` snapshot wired //! into operation blocks plus a canonical `MaterializedState` snapshot. That
//! as the manifest's `canonical_base`. Both //! snapshot's manifest placement (`canonical_base`) is suspended for the S28
//! -> P13-S27 interval (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3c) — see
//! `Fixture::base_root`'s doc comment — so it is staged and read back
//! directly by `ChunkRef` instead. Both
//! are expected to **Pass** today, so a regression fails `cargo bench` loudly. //! are expected to **Pass** today, so a regression fails `cargo bench` loudly.
//! The read row's honesty note: the spec sizes its 200 ms against a 100-page //! The read row's honesty note: the spec sizes its 200 ms against a 100-page
//! orchestral score; no such corpus generator exists yet, so this row is the //! orchestral score; no such corpus generator exists yet, so this row is the
@ -34,9 +37,9 @@
//! envelope block, i.e. block append + manifest rewrite + superblock flip, //! envelope block, i.e. block append + manifest rewrite + superblock flip,
//! every write fsync'd. This mirrors `bundle_harness`'s commit driver. //! every write fsync'd. This mirrors `bundle_harness`'s commit driver.
//! * **open_bootstrap_read** — `FileStore::open` + `Bundle::open` (superblock //! * **open_bootstrap_read** — `FileStore::open` + `Bundle::open` (superblock
//! selection + header + manifest decode) + reading the `canonical_base` //! selection + header + manifest decode) + reading the cold-reduction
//! snapshot and every operation block — the bytes a first interactive frame //! snapshot (by direct `ChunkRef`, see above) and every operation block —
//! needs. //! the bytes a first interactive frame needs.
//! //!
//! Criterion measures; the budget gate in `main` asserts (see //! Criterion measures; the budget gate in `main` asserts (see
//! `epiphany_testkit::budget` for the Pass/Xfail semantics and the documented //! `epiphany_testkit::budget` for the Pass/Xfail semantics and the documented
@ -50,9 +53,8 @@ use std::time::Duration;
use criterion::{BatchSize, Criterion}; use criterion::{BatchSize, Criterion};
use epiphany_bundle::{ use epiphany_bundle::{
pack_operation_blocks, Bundle, ChunkKind, CommitContext, DocumentId, FileStore, FileUuid, pack_operation_blocks, Bundle, ChunkKind, ChunkRef, CommitContext, DocumentId, FileStore,
FrontierBytes, Manifest, MemStore, ProfileId, ReductionAlgorithmVersion, SchemaVersion, FileUuid, Manifest, MemStore, SchemaVersion, StagedChunk,
SnapshotId, SnapshotRef, StagedChunk,
}; };
use epiphany_determinism::CanonicalEncode; use epiphany_determinism::CanonicalEncode;
use epiphany_ops::{OperationEnvelope, OperationSet}; use epiphany_ops::{OperationEnvelope, OperationSet};
@ -71,8 +73,21 @@ const EDIT_ENVELOPES: usize = 4;
/// Everything the two rows measure against, built once from a fixed seed. /// Everything the two rows measure against, built once from a fixed seed.
struct Fixture { struct Fixture {
/// The committed base image (blocks + canonical-base snapshot). /// The committed base image (blocks + the corpus's cold-reduction
/// snapshot).
base_image: Vec<u8>, base_image: Vec<u8>,
/// The cold-reduction snapshot's `ChunkRef`.
///
/// **Suspended manifest placement** (`CONTRACT_FORMAT_EPOCH_MAJOR1.md`
/// pin 3c, amended 2026-08-07): this used to be `manifest.canonical_base`
/// — its correct semantic home — but pin 3a's format-epoch matrix refuses
/// every base introduction and every base-bearing open during the S28 ->
/// P13-S27 interval. The ref is carried here instead and read back
/// directly via `Bundle::read_chunk`, exactly as
/// `epiphany_testkit::roundtrip::assert_reduction_serialization_stable`
/// now does. **Not** re-homed to `acceleration_snapshots` — nothing in
/// `epiphany-bundle` verifies that field.
base_root: ChunkRef,
/// The typical edit, staged (one small operation block). /// The typical edit, staged (one small operation block).
edit: Vec<StagedChunk>, edit: Vec<StagedChunk>,
/// Temp-dir file paths: one per row so commit growth never skews reads. /// Temp-dir file paths: one per row so commit growth never skews reads.
@ -99,8 +114,9 @@ fn staged_blocks(envelopes: &[OperationEnvelope]) -> Vec<StagedChunk> {
} }
/// Builds the moderate base corpus: 1,000 envelopes committed as operation /// Builds the moderate base corpus: 1,000 envelopes committed as operation
/// blocks, then their cold reduction committed as a `Snapshot` chunk wired to /// blocks, then their cold reduction committed as a `Snapshot` chunk — staged
/// the manifest's `canonical_base` (the roundtrip harness's snapshot shape). /// but not wired into the manifest (pin 3c; see `Fixture::base_root`'s doc
/// comment).
fn build_fixture(dir: &Path) -> Fixture { fn build_fixture(dir: &Path) -> Fixture {
let mut rng = Rng::new(0x00F1_B0DE_0001); let mut rng = Rng::new(0x00F1_B0DE_0001);
let envelopes = generators::operation_envelopes(&mut rng, BASE_ENVELOPES, 3, 40, 40); let envelopes = generators::operation_envelopes(&mut rng, BASE_ENVELOPES, 3, 40, 40);
@ -123,27 +139,18 @@ fn build_fixture(dir: &Path) -> Fixture {
schema_version: SchemaVersion::V0, schema_version: SchemaVersion::V0,
payload: canonical, payload: canonical,
}; };
let frontier = generators::frontier_bytes(&envelopes); let mut base_root = None;
bundle bundle
.commit(&[snapshot], |ctx| { .commit(&[snapshot], |ctx| {
let mut manifest = ctx.previous_manifest.clone(); base_root = Some(ctx.new_chunks[0]);
let root = ctx.new_chunks[0]; ctx.previous_manifest.clone()
let mut sid = [0u8; 16];
sid.copy_from_slice(&root.hash.as_bytes()[..16]);
manifest.canonical_base = Some(SnapshotRef {
snapshot_id: SnapshotId(sid),
covers_causal_frontier: FrontierBytes::from_bytes(frontier.clone()),
reduction_algorithm_version: ReductionAlgorithmVersion(0),
profile_id: ProfileId::Full,
hash: root.hash,
root,
});
manifest
}) })
.expect("commit canonical-base snapshot"); .expect("commit cold-reduction snapshot");
let base_root = base_root.expect("commit ran the build closure");
Fixture { Fixture {
base_image: bundle.into_store().into_bytes(), base_image: bundle.into_store().into_bytes(),
base_root,
edit: staged_blocks(&edit_envelopes), edit: staged_blocks(&edit_envelopes),
commit_path: dir.join("commit.epb"), commit_path: dir.join("commit.epb"),
read_path: dir.join("read.epb"), read_path: dir.join("read.epb"),
@ -165,17 +172,15 @@ fn typical_edit_commit(mut bundle: Bundle<FileStore>, edit: &[StagedChunk]) -> u
} }
/// The timed read: open (superblock selection + manifest decode) + the /// The timed read: open (superblock selection + manifest decode) + the
/// bootstrap chunks — canonical-base snapshot and every operation block. /// bootstrap chunks — the cold-reduction snapshot (read directly by its
fn open_bootstrap_read(path: &Path) -> usize { /// `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")).expect("open bundle");
let manifest = bundle.manifest(); let manifest = bundle.manifest();
let mut bytes = 0usize; let mut bytes = 0usize;
let base = manifest
.canonical_base
.as_ref()
.expect("the fixture wires a canonical base");
bytes += bundle bytes += bundle
.read_chunk(&base.root) .read_chunk(base_root)
.expect("snapshot chunk reads") .expect("snapshot chunk reads")
.len(); .len();
for root in &manifest.operation_roots { for root in &manifest.operation_roots {
@ -206,7 +211,7 @@ fn criterion_measurements(criterion: &mut Criterion, fixture: &Fixture, quick: b
fs::write(&fixture.read_path, &fixture.base_image).expect("write read-row image"); fs::write(&fixture.read_path, &fixture.base_image).expect("write read-row image");
group.bench_function("open_bootstrap_read", |b| { group.bench_function("open_bootstrap_read", |b| {
b.iter(|| open_bootstrap_read(&fixture.read_path)) b.iter(|| open_bootstrap_read(&fixture.read_path, &fixture.base_root))
}); });
group.finish(); group.finish();
@ -223,7 +228,7 @@ fn budget_gate(fixture: &Fixture, quick: bool) -> Vec<budget::GateReport> {
let read_median = budget::median_time( let read_median = budget::median_time(
if quick { 8 } else { 25 }, if quick { 8 } else { 25 },
|| (), || (),
|()| open_bootstrap_read(&fixture.read_path), |()| open_bootstrap_read(&fixture.read_path, &fixture.base_root),
); );
vec![ vec![
budget::latency_gate( budget::latency_gate(

View File

@ -225,14 +225,28 @@ fn canonical_score_bytes(envelopes: &[OperationEnvelope]) -> Vec<u8> {
/// (acceptance criterion 4): the operation /// (acceptance criterion 4): the operation
/// set reduces to canonical bytes; re-reducing the same set yields byte-identical /// set reduces to canonical bytes; re-reducing the same set yields byte-identical
/// bytes; and those bytes survive content-addressed storage in a real bundle — /// bytes; and those bytes survive content-addressed storage in a real bundle —
/// stored as a `Snapshot` chunk referenced by the manifest's `canonical_base` /// stored as a `Snapshot` chunk, hash-verified on reopen and read back
/// (its correct semantic home), hash-verified on reopen and read back
/// byte-identically. /// byte-identically.
/// ///
/// The snapshot's `covers_causal_frontier` is the frontier the snapshot actually /// **Canonical-base wiring suspended** (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin
/// materializes ([`crate::generators::frontier_bytes`] over the reduced /// 3c, amended 2026-08-07). The snapshot's correct semantic home is the
/// envelopes), so it is semantically consistent — not a falsely-empty frontier /// manifest's `canonical_base` — that has not changed — but pin 3a's
/// that would invite a replay layer to reapply already-materialized effects. /// 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
/// `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`.
/// ///
/// After reopen, the snapshot payload is decoded through /// After reopen, the snapshot payload is decoded through
/// [`MaterializedState::decode_canonical`], compared structurally with the /// [`MaterializedState::decode_canonical`], compared structurally with the
@ -248,9 +262,13 @@ pub fn assert_reduction_serialization_stable(envelopes: &[OperationEnvelope], se
"re-reduction changed the canonical score bytes" "re-reduction changed the canonical score bytes"
); );
// serialize: stage the canonical state as a real **Snapshot** chunk and // serialize: stage the canonical state as a real **Snapshot** chunk.
// reference it from the manifest's `canonical_base` — its correct semantic // P13-S27 / CONTRACT_FORMAT_EPOCH_MAJOR1.md pin 3c: the manifest's
// home (a materialized snapshot), with the right chunk kind. // `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.
let mut rng = Rng::new(seed); let mut rng = Rng::new(seed);
let uuid = FileUuid(rng.array16()); let uuid = FileUuid(rng.array16());
let doc = DocumentId(rng.array16()); let doc = DocumentId(rng.array16());
@ -261,42 +279,26 @@ pub fn assert_reduction_serialization_stable(envelopes: &[OperationEnvelope], se
schema_version: SchemaVersion::V0, schema_version: SchemaVersion::V0,
payload: canonical.clone(), payload: canonical.clone(),
}; };
let mut snapshot_root = None;
bundle bundle
.commit(&[snapshot], |ctx| { .commit(&[snapshot], |ctx| {
let mut m = ctx.previous_manifest.clone(); snapshot_root = Some(ctx.new_chunks[0]);
let root = ctx.new_chunks[0]; ctx.previous_manifest.clone()
let mut sid = [0u8; 16];
sid.copy_from_slice(&root.hash.as_bytes()[..16]);
m.canonical_base = Some(SnapshotRef {
snapshot_id: SnapshotId(sid),
// The frontier the snapshot actually materializes (covering every
// reduced envelope), not a falsely-empty one.
covers_causal_frontier: FrontierBytes::from_bytes(generators::frontier_bytes(
envelopes,
)),
reduction_algorithm_version: ReductionAlgorithmVersion(0),
profile_id: ProfileId::Full,
hash: root.hash,
root,
});
m
}) })
.expect("commit snapshot"); .expect("commit snapshot");
let snapshot_root = snapshot_root.expect("commit ran the build closure");
let image = bundle.into_store().into_bytes(); let image = bundle.into_store().into_bytes();
// load: reopen from exactly those bytes; the snapshot chunk is hash-verified // load: reopen from exactly those bytes; the snapshot chunk is read back
// on open and read back byte-identically. // 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"); let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen bundle");
reopened reopened
.verify_canonical_chunks() .verify_canonical_chunks()
.expect("canonical chunks intact"); .expect("canonical chunks intact");
let base = reopened
.manifest()
.canonical_base
.as_ref()
.expect("a canonical base");
let loaded = reopened let loaded = reopened
.read_chunk(&base.root) .read_chunk(&snapshot_root)
.expect("read snapshot chunk back"); .expect("read snapshot chunk back");
assert_eq!( assert_eq!(
loaded, canonical, loaded, canonical,

View File

@ -9,11 +9,14 @@ use std::collections::{BTreeMap, BTreeSet};
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
const CORE_REQUIREMENT_COUNT: usize = 212; // +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;
// +1 for req:textproj:manifest-schema-carried (G-minor, pins 8/11: // +1 for req:textproj:manifest-schema-carried (G-minor, pins 8/11:
// spec/PLAN_GMINOR_SCHEMA_MINOR.md). // spec/PLAN_GMINOR_SCHEMA_MINOR.md); +1 for req:format:container-epoch (above).
const SUITE_REQUIREMENT_COUNT: usize = 283; const SUITE_REQUIREMENT_COUNT: usize = 284;
const SUITE_LABEL_COUNT: usize = 283; const SUITE_LABEL_COUNT: usize = 284;
/// The normative chapter-to-area assignment. Keeping this as data makes adding a /// The normative chapter-to-area assignment. Keeping this as data makes adding a
/// requirement under the wrong chapter fail without encoding chapter names in /// requirement under the wrong chapter fail without encoding chapter names in

View File

@ -56,7 +56,18 @@ use epiphany_ops::OperationEnvelope;
/// and `create-view` to the `kind` production — the same reasoning as every /// and `create-view` to the `kind` production — the same reasoning as every
/// prior kind append: extending the grammar without moving this constant /// prior kind append: extending the grammar without moving this constant
/// would leave two incompatible grammars both claiming `(0 11 0)`. /// would leave two incompatible grammars both claiming `(0 11 0)`.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 13, 0); ///
/// Bumped again 0.12.0 → 0.13.0 by the genesis tranche G3b, which appended
/// `create-measure` to the `kind` production.
///
/// Bumped again 0.13.0 → 0.14.0 by `spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`
/// pin 3b: a canonical base can no longer be projected, parsed, or
/// serialized by this companion at all — refusing a document the companion
/// previously serialized is a semantic change, so the bump is load-bearing,
/// not cosmetic. The `canonical-base` grammar production is retained (it is
/// what the refusal is defined against, and what a future rebuild/repack
/// flow will emit), but no document containing it round-trips.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 14, 0);
/// A parsed canonical Text Projection document. /// A parsed canonical Text Projection document.
/// ///

View File

@ -136,6 +136,18 @@ pub fn parse_document(text: &str) -> Result<TextDocument, TextError> {
// canonical-base? // canonical-base?
let canonical_base = take_line(&mut lines, "canonical-base", parse_canonical_base)?; let canonical_base = take_line(&mut lines, "canonical-base", parse_canonical_base)?;
// `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b: text projection cannot mint a
// canonical base. The grammar still defines the production — it is what
// this refusal is defined against, and what a future rebuild/repack flow
// will emit — but no document containing it parses. `NotCanonical`
// matches the existing `(blob ...)` rejection just below: syntactically
// well-formed, but never a text this companion can accept.
if canonical_base.is_some() {
return Err(TextError::NotCanonical(
"a (canonical-base ...) line cannot be canonical text: no reduction-authority \
validation exists for text projection to have produced it",
));
}
// blob*: collected only so the rejection below can fire; see the module // blob*: collected only so the rejection below can fire; see the module
// documentation and `req:textproj:reject-unreferenced-blobs`. // documentation and `req:textproj:reject-unreferenced-blobs`.
@ -650,12 +662,13 @@ mod tests {
// Bumped with `COMPANION_VERSION` (0.7.0 → 0.8.0, genesis G1; 0.8.0 → // Bumped with `COMPANION_VERSION` (0.7.0 → 0.8.0, genesis G1; 0.8.0 →
// 0.9.0, genesis G2a; 0.9.0 → 0.10.0, G-minor; 0.10.0 → 0.11.0, genesis // 0.9.0, genesis G2a; 0.9.0 → 0.10.0, G-minor; 0.10.0 → 0.11.0, genesis
// G2b; 0.11.0 → 0.12.0, genesis G3a; 0.12.0 → 0.13.0, genesis G3b). Kept // G2b; 0.11.0 → 0.12.0, genesis G3a; 0.12.0 → 0.13.0, genesis G3b;
// 0.13.0 → 0.14.0, `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b). Kept
// a literal because `projection` takes `&[&str]` and a formatted String // a literal because `projection` takes `&[&str]` and a formatted String
// would ripple through every call site; // would ripple through every call site;
// `the_test_header_tracks_the_implemented_version` below fails loudly if // `the_test_header_tracks_the_implemented_version` below fails loudly if
// the two ever drift. // the two ever drift.
const HEADER: &str = "(text-projection (0 13 0))"; const HEADER: &str = "(text-projection (0 14 0))";
const DOCUMENT: &str = "(document #x00000000000000000000000000000001 (schema 0 1))"; const DOCUMENT: &str = "(document #x00000000000000000000000000000001 (schema 0 1))";
/// A minimal but complete valid projection: just the two mandatory lines. /// A minimal but complete valid projection: just the two mandatory lines.
@ -757,28 +770,37 @@ mod tests {
.retain_named_checkpoints .retain_named_checkpoints
); );
assert!(document.extensions.is_empty()); assert!(document.extensions.is_empty());
// Pin 3b (`CONTRACT_FORMAT_EPOCH_MAJOR1.md`): the worked example no
let base = document // longer carries a canonical base — the companion's own chapter
.canonical_base // retired that spelling to a second, explicitly-refused listing
.as_ref() // (`spec/text_projection.tex`, "A Worked Example").
.expect("the worked example carries a canonical base"); assert!(document.canonical_base.is_none());
assert_eq!(
base.snapshot_id,
SnapshotId([0x1f, 0x8b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
);
assert_eq!(base.covers_causal_frontier, FrontierBytes::empty());
assert_eq!(
base.reduction_algorithm_version,
ReductionAlgorithmVersion(1)
);
assert_eq!(base.profile_id, ProfileId::Full);
assert_eq!(base.root_schema_version, SchemaVersion::V0);
assert_eq!(base.root_payload, vec![0u8, 0u8]);
assert!(document.blobs.is_empty()); assert!(document.blobs.is_empty());
assert_eq!(document.envelopes.len(), 1); assert_eq!(document.envelopes.len(), 1);
} }
/// The base-bearing spelling `the_worked_example_parses_to_its_documented_fields`
/// used to exercise, before pin 3b retired it from the worked example
/// itself: still grammar-valid, no longer canonical text. Pinned as a
/// literal here (rather than re-reading the spec's second listing) so
/// this test does not depend on the exact prose the orchestrator chose to
/// wrap it in.
#[test]
fn parsing_base_bearing_text_is_refused() {
let base =
"(canonical-base #x1f8b0000000000000000000000000000 #x 1 full (schema 0 1) #x0000)";
let text = projection(&[HEADER, DOCUMENT, base]);
let err = parse_document(&text).expect_err("base-bearing text must be refused");
assert_eq!(
err,
TextError::NotCanonical(
"a (canonical-base ...) line cannot be canonical text: no reduction-authority \
validation exists for text projection to have produced it",
)
);
}
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// A synthetic document exercising lineage, multiple profiles, an // A synthetic document exercising lineage, multiple profiles, an
// extension with preserved chunks, a canonical base, and multiple // extension with preserved chunks, a canonical base, and multiple
@ -794,8 +816,11 @@ mod tests {
let extension = "(extension #x00000000000000000000000000000003 (1 0 0) true \ let extension = "(extension #x00000000000000000000000000000003 (1 0 0) true \
((chunk operation-envelope-block (schema 0 1) #xaa) (chunk snapshot (schema 0 1) #xbb)) \ ((chunk operation-envelope-block (schema 0 1) #xaa) (chunk snapshot (schema 0 1) #xbb)) \
#xaabb #xccdd)"; #xaabb #xccdd)";
let base = // Pin 3b (`CONTRACT_FORMAT_EPOCH_MAJOR1.md`): this document used to
"(canonical-base #x00000000000000000000000000000004 #x 1 full (schema 0 1) #x0102)"; // also carry a `(canonical-base ...)` line here, exercising every
// section at once. Base-bearing text is refused outright now — see
// `parsing_base_bearing_text_is_refused` — so this fixture drops it
// and keeps every other feature it was written to exercise together.
let e1 = sample_envelope(1, 1, 10); let e1 = sample_envelope(1, 1, 10);
let e2 = sample_envelope(2, 1, 20); let e2 = sample_envelope(2, 1, 20);
@ -809,7 +834,6 @@ mod tests {
profile_full, profile_full,
profile_read_only, profile_read_only,
extension, extension,
base,
&e1_line, &e1_line,
&e2_line, &e2_line,
]); ]);
@ -832,7 +856,7 @@ mod tests {
vec![0xaa, 0xbb] vec![0xaa, 0xbb]
); );
assert_eq!(document.extensions[0].edit_barriers, vec![0xcc, 0xdd]); assert_eq!(document.extensions[0].edit_barriers, vec![0xcc, 0xdd]);
assert!(document.canonical_base.is_some()); assert!(document.canonical_base.is_none());
assert_eq!(document.envelopes, vec![e1, e2]); assert_eq!(document.envelopes, vec![e1, e2]);
} }

View File

@ -41,8 +41,8 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use epiphany_bundle::{ use epiphany_bundle::{
BlobId, BlockStore, Bundle, BundleError, ChunkKind, DocumentId, LineageId, ProfileConstraints, BlobId, BlockStore, Bundle, BundleError, ChunkKind, DocumentId, LineageId, Manifest,
ProfileDeclaration, ProfileId, RetentionPolicy, SchemaVersion, SemVer, ProfileConstraints, ProfileDeclaration, ProfileId, RetentionPolicy, SchemaVersion, SemVer,
}; };
use epiphany_core::textvalue::Sexp; use epiphany_core::textvalue::Sexp;
use epiphany_ops::{ use epiphany_ops::{
@ -67,6 +67,15 @@ pub enum ProjectError {
/// A stored operation-envelope block held bytes that do not decode to a /// A stored operation-envelope block held bytes that do not decode to a
/// canonical [`OperationEnvelope`]. /// canonical [`OperationEnvelope`].
Envelope(EnvelopeDecodeError), Envelope(EnvelopeDecodeError),
/// The bundle's manifest carries a canonical base
/// (`spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b: "text projection
/// cannot mint a canonical base"). Symmetric with
/// [`crate::serialize::SerializeError::CanonicalBaseUnsupported`] and
/// [`crate::parse`]'s own refusal — `req:textproj:roundtrip` quantifies
/// over every bundle and every valid text, so all three sides move
/// together: a document this companion cannot produce from text must
/// also never be produced *as* text.
CanonicalBaseUnsupported,
} }
impl core::fmt::Display for ProjectError { impl core::fmt::Display for ProjectError {
@ -74,6 +83,10 @@ impl core::fmt::Display for ProjectError {
match self { match self {
ProjectError::Bundle(e) => write!(f, "bundle read failed: {e}"), ProjectError::Bundle(e) => write!(f, "bundle read failed: {e}"),
ProjectError::Envelope(e) => write!(f, "operation envelope failed to decode: {e}"), ProjectError::Envelope(e) => write!(f, "operation envelope failed to decode: {e}"),
ProjectError::CanonicalBaseUnsupported => f.write_str(
"the bundle's manifest carries a canonical base, which this companion cannot \
project: text projection cannot mint a canonical base",
),
} }
} }
} }
@ -83,6 +96,7 @@ impl std::error::Error for ProjectError {
match self { match self {
ProjectError::Bundle(e) => Some(e), ProjectError::Bundle(e) => Some(e),
ProjectError::Envelope(e) => Some(e), ProjectError::Envelope(e) => Some(e),
ProjectError::CanonicalBaseUnsupported => None,
} }
} }
} }
@ -425,6 +439,33 @@ fn canonical_blobs<S: BlockStore>(
// Bundle<S> -> TextDocument. // Bundle<S> -> TextDocument.
// =========================================================================== // ===========================================================================
/// The check [`document_from_bundle`] and [`project_bundle`] apply before
/// doing any work (pin 3b): a base-bearing bundle is refused, symmetric with
/// [`crate::serialize::serialize_document`]'s and [`crate::parse::parse_document`]'s
/// own refusals.
///
/// Exposed as its own function, over a bare [`Manifest`] rather than inlined
/// into `document_from_bundle`, so it is independently unit-testable. That
/// indirection is load-bearing, not stylistic: during the S28 → P13-S27
/// interval, `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3a's bundle-layer
/// refusals (rows 2/3/5i/6i of its epoch matrix) close *every* path —
/// `create`, `open`, and `commit` alike — that could ever produce a live
/// `Bundle` whose manifest carries a canonical base, in any crate, not only
/// this one (pin 3c: "no bundle anywhere may carry a canonical base ... in
/// production, in tests, or in the conformance suite"). So this predicate
/// cannot be driven end-to-end through a real `Bundle::open`/`create`/`commit`
/// call right now — there is no way to construct the `&Bundle<S>` argument
/// `document_from_bundle` would need. `projecting_a_base_bearing_bundle_is_refused`
/// (below) tests this function directly against a hand-built `Manifest`
/// instead, and its own doc comment records this as a contract-execution
/// finding, not something patched around.
fn reject_canonical_base(manifest: &Manifest) -> Result<(), ProjectError> {
if manifest.canonical_base.is_some() {
return Err(ProjectError::CanonicalBaseUnsupported);
}
Ok(())
}
/// Builds a [`TextDocument`] from a bundle's current manifest, reading every /// Builds a [`TextDocument`] from a bundle's current manifest, reading every
/// chunk and blob payload the projection carries inline: each extension's /// chunk and blob payload the projection carries inline: each extension's
/// preserved chunks, the canonical base's root chunk, and (today, always /// preserved chunks, the canonical base's root chunk, and (today, always
@ -440,6 +481,7 @@ pub fn document_from_bundle<S: BlockStore>(
bundle: &Bundle<S>, bundle: &Bundle<S>,
) -> Result<TextDocument, ProjectError> { ) -> Result<TextDocument, ProjectError> {
let manifest = bundle.manifest(); let manifest = bundle.manifest();
reject_canonical_base(manifest)?;
let mut decoded = Vec::new(); let mut decoded = Vec::new();
for root in &manifest.operation_roots { for root in &manifest.operation_roots {
@ -563,9 +605,9 @@ mod tests {
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use epiphany_bundle::{ use epiphany_bundle::{
encode_block, BlobRef, CompressionAlgorithm, ExtensionDeclaration, ExtensionId, FileUuid, chunk_content_hash, encode_block, BlobRef, ChunkRef, CompressionAlgorithm,
FrontierBytes, Manifest, MemStore, ProfileRegistryId, ReductionAlgorithmVersion, ExtensionDeclaration, ExtensionId, FileUuid, FrontierBytes, Manifest, MemStore,
SnapshotId, SnapshotRef, StagedChunk, ProfileRegistryId, ReductionAlgorithmVersion, SnapshotId, SnapshotRef, StagedChunk,
}; };
use epiphany_core::textvalue::read_sexp; use epiphany_core::textvalue::read_sexp;
use epiphany_core::{OperationId, RegionId, ReplicaId, WallClockTime}; use epiphany_core::{OperationId, RegionId, ReplicaId, WallClockTime};
@ -896,12 +938,12 @@ mod tests {
payload: b"extension-chunk-payload".to_vec(), payload: b"extension-chunk-payload".to_vec(),
}; };
let base_chunk = StagedChunk { // `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3c: no bundle anywhere may
kind: ChunkKind::Snapshot, // carry a canonical base for the S28 -> P13-S27 interval — `commit`
schema_version: SchemaVersion::V0, // now refuses one outright, so this fixture used to stage a
payload: b"canonical-base-payload".to_vec(), // `base_chunk` and wire it into `manifest.canonical_base` here can no
}; // longer do so. The base-bearing scenario is exercised separately, at
// the `Manifest` level, by `projecting_a_base_bearing_bundle_is_refused`.
let blob_chunk = StagedChunk { let blob_chunk = StagedChunk {
kind: ChunkKind::Blob, kind: ChunkKind::Blob,
schema_version: SchemaVersion::V0, schema_version: SchemaVersion::V0,
@ -915,13 +957,7 @@ mod tests {
bundle bundle
.commit( .commit(
&[ &[op_block_0, op_block_1, extension_chunk, blob_chunk],
op_block_0,
op_block_1,
extension_chunk,
base_chunk,
blob_chunk,
],
|ctx| { |ctx| {
let mut manifest = ctx.previous_manifest.clone(); let mut manifest = ctx.previous_manifest.clone();
manifest.operation_roots = vec![ctx.new_chunks[0], ctx.new_chunks[1]]; manifest.operation_roots = vec![ctx.new_chunks[0], ctx.new_chunks[1]];
@ -933,22 +969,14 @@ mod tests {
affected_object_kinds: vec![0xAA], affected_object_kinds: vec![0xAA],
edit_barriers: vec![0xBB, 0xCC], edit_barriers: vec![0xBB, 0xCC],
}]; }];
manifest.canonical_base = Some(SnapshotRef {
snapshot_id: SnapshotId([7; 16]),
covers_causal_frontier: FrontierBytes::from_bytes(vec![1, 2, 3]),
reduction_algorithm_version: ReductionAlgorithmVersion(1),
profile_id: ProfileId::Full,
root: ctx.new_chunks[3],
hash: ctx.new_chunks[3].hash,
});
manifest.blob_roots = vec![BlobRef { manifest.blob_roots = vec![BlobRef {
blob_id: BlobId(ctx.new_chunks[4].hash), blob_id: BlobId(ctx.new_chunks[3].hash),
media_type: "application/octet-stream".to_owned(), media_type: "application/octet-stream".to_owned(),
offset: ctx.new_chunks[4].offset, offset: ctx.new_chunks[3].offset,
compressed_length: ctx.new_chunks[4].compressed_length, compressed_length: ctx.new_chunks[3].compressed_length,
uncompressed_length: ctx.new_chunks[4].uncompressed_length, uncompressed_length: ctx.new_chunks[3].uncompressed_length,
compression: CompressionAlgorithm::None, compression: CompressionAlgorithm::None,
hash: ctx.new_chunks[4].hash, hash: ctx.new_chunks[3].hash,
declared_max_uncompressed_length: None, declared_max_uncompressed_length: None,
}]; }];
manifest manifest
@ -995,9 +1023,9 @@ mod tests {
assert_eq!(extension.chunks[0].kind, ChunkKind::ExtensionData); assert_eq!(extension.chunks[0].kind, ChunkKind::ExtensionData);
assert_eq!(extension.chunks[0].payload, b"extension-chunk-payload"); assert_eq!(extension.chunks[0].payload, b"extension-chunk-payload");
let base = document.canonical_base.as_ref().expect("base was staged"); // `build_sample_bundle` no longer stages a canonical base (pin 3c: no
assert_eq!(base.snapshot_id, SnapshotId([7; 16])); // bundle anywhere may carry one during the S28 -> P13-S27 interval).
assert_eq!(base.root_payload, b"canonical-base-payload"); assert!(document.canonical_base.is_none());
// The trap: a non-empty `blob_roots` still yields zero canonical // The trap: a non-empty `blob_roots` still yields zero canonical
// blobs, because nothing can reach one yet. // blobs, because nothing can reach one yet.
@ -1041,7 +1069,6 @@ mod tests {
"(lineage", "(lineage",
"(profile", "(profile",
"(extension", "(extension",
"(canonical-base",
"(envelope", "(envelope",
"(envelope", "(envelope",
], ],
@ -1095,6 +1122,54 @@ mod tests {
} }
} }
#[test]
fn projecting_a_base_bearing_bundle_is_refused() {
// Pin 3b's projection side: `document_from_bundle`/`project_bundle`
// refuse a bundle whose manifest carries a canonical base.
//
// This cannot be driven end-to-end through a live `Bundle<S>` right
// now: `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3a's refusals (this same
// rung, `epiphany-bundle`) close *every* path that could produce
// one — `Bundle::create` already rejected an initial base before this
// rung, and `Bundle::open`/`Bundle::commit` now refuse one
// categorically in both format epochs (matrix rows 2/3/5i/6i) — so no
// crate, including this one, can construct a `Bundle` whose manifest
// carries a canonical base during the S28 -> P13-S27 interval (pin
// 3c: "no bundle anywhere may carry a canonical base ... in
// production, in tests, or in the conformance suite"). That is
// reported as a contract-execution finding, not patched around here.
//
// What IS independently checkable, without a live `Bundle`, is the
// exact predicate `document_from_bundle` guards on
// (`reject_canonical_base`): a `Manifest` — a public, freestanding
// type — carrying `canonical_base = Some(...)`.
let mut manifest = Manifest::empty(DocumentId([1; 16]));
let payload = b"snapshot".to_vec();
let hash = chunk_content_hash(ChunkKind::Snapshot, SchemaVersion::V0, &payload);
let root = ChunkRef {
id: epiphany_bundle::ChunkId(hash),
kind: ChunkKind::Snapshot,
schema_version: SchemaVersion::V0,
offset: epiphany_bundle::BODY_START,
compressed_length: payload.len() as u64,
uncompressed_length: payload.len() as u64,
compression: CompressionAlgorithm::None,
hash,
};
manifest.canonical_base = Some(SnapshotRef {
snapshot_id: SnapshotId([1; 16]),
covers_causal_frontier: FrontierBytes::empty(),
reduction_algorithm_version: ReductionAlgorithmVersion(0),
profile_id: ProfileId::Full,
hash: root.hash,
root,
});
assert!(matches!(
reject_canonical_base(&manifest),
Err(ProjectError::CanonicalBaseUnsupported)
));
}
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// Suite reach: count, don't just claim, coverage of an extension, a // Suite reach: count, don't just claim, coverage of an extension, a
// canonical base, and a multi-envelope document. // canonical base, and a multi-envelope document.
@ -1103,13 +1178,27 @@ mod tests {
#[test] #[test]
fn test_suite_reach_covers_extension_base_and_multi_envelope_documents() { fn test_suite_reach_covers_extension_base_and_multi_envelope_documents() {
let rich = document_from_bundle(&build_sample_bundle()).expect("bundle reads cleanly"); let rich = document_from_bundle(&build_sample_bundle()).expect("bundle reads cleanly");
// `build_sample_bundle` no longer carries a canonical base (pin 3c),
// so `with_canonical_base` reach is exercised here instead, directly
// on a `TextDocument` value — no live `Bundle` is involved, so pin
// 3a's bundle-layer refusal does not apply (this is not projection,
// parsing, or serialization; see
// `projecting_a_base_bearing_bundle_is_refused`, which covers the
// refusal itself).
let minimal = TextDocument { let minimal = TextDocument {
document_id: DocumentId([6; 16]), document_id: DocumentId([6; 16]),
manifest_schema_version: SchemaVersion::V0, manifest_schema_version: SchemaVersion::V0,
lineage_id: None, lineage_id: None,
profiles: vec![ProfileDeclaration::full()], profiles: vec![ProfileDeclaration::full()],
extensions: Vec::new(), extensions: Vec::new(),
canonical_base: None, canonical_base: Some(TextCanonicalBase {
snapshot_id: SnapshotId([6; 16]),
covers_causal_frontier: FrontierBytes::empty(),
reduction_algorithm_version: ReductionAlgorithmVersion(1),
profile_id: ProfileId::Full,
root_schema_version: SchemaVersion::V0,
root_payload: b"reach-only-canonical-base".to_vec(),
}),
blobs: Vec::new(), blobs: Vec::new(),
envelopes: vec![sample_envelope(1, 1)], envelopes: vec![sample_envelope(1, 1)],
}; };

View File

@ -70,6 +70,21 @@ pub enum SerializeError {
/// non-canonical root into the manifest or silently drop the caller's /// non-canonical root into the manifest or silently drop the caller's
/// payload bytes. Neither is acceptable, so serialization refuses instead. /// payload bytes. Neither is acceptable, so serialization refuses instead.
NonEmptyBlobs, NonEmptyBlobs,
/// The document carries a canonical base
/// (`spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b: "text projection cannot
/// mint a canonical base"). `TextDocument` has no container-major or epoch
/// field, and `parse` accepts an unbounded reduction-algorithm version, so
/// an old or hand-authored text document could otherwise be laundered
/// straight through the format-epoch boundary into a brand-new major-1
/// container. A dedicated variant, not a `SerializeError::Bundle`
/// passthrough: the container-layer refusal
/// (`epiphany_bundle::BundleError::ReductionAuthorityUnavailable`) is
/// temporary and interim-only, while this refusal is a permanent property
/// of what the text medium can prove — a text format cannot carry
/// unforgeable provenance, so refusal is the only rule it can actually
/// enforce. This is real capability loss: base-bearing documents stop
/// round-tripping through text until a repack flow exists.
CanonicalBaseUnsupported,
/// The bundle itself rejected the manifest or a staged chunk — e.g. the /// The bundle itself rejected the manifest or a staged chunk — e.g. the
/// document declares no profile this implementation understands, or a /// document declares no profile this implementation understands, or a
/// staged root fails `Bundle::commit`'s structural validation. /// staged root fails `Bundle::commit`'s structural validation.
@ -82,6 +97,10 @@ impl fmt::Display for SerializeError {
SerializeError::NonEmptyBlobs => f.write_str( SerializeError::NonEmptyBlobs => f.write_str(
"the document carries a populated blobs vector, but no blob can be canonical today", "the document carries a populated blobs vector, but no blob can be canonical today",
), ),
SerializeError::CanonicalBaseUnsupported => f.write_str(
"the document carries a canonical base, which this companion cannot serialize: \
text projection cannot mint a canonical base",
),
SerializeError::Bundle(error) => { SerializeError::Bundle(error) => {
write!(f, "the bundle rejected the serialized document: {error}") write!(f, "the bundle rejected the serialized document: {error}")
} }
@ -94,6 +113,7 @@ impl std::error::Error for SerializeError {
match self { match self {
SerializeError::Bundle(error) => Some(error), SerializeError::Bundle(error) => Some(error),
SerializeError::NonEmptyBlobs => None, SerializeError::NonEmptyBlobs => None,
SerializeError::CanonicalBaseUnsupported => None,
} }
} }
} }
@ -114,8 +134,12 @@ impl From<BundleError> for SerializeError {
/// [`ChunkRef`](epiphany_bundle::ChunkRef)s that commit assigns. /// [`ChunkRef`](epiphany_bundle::ChunkRef)s that commit assigns.
/// ///
/// Returns [`SerializeError::NonEmptyBlobs`] if `document.blobs` is non-empty /// Returns [`SerializeError::NonEmptyBlobs`] if `document.blobs` is non-empty
/// (see the module documentation), or [`SerializeError::Bundle`] if the bundle /// (see the module documentation), [`SerializeError::CanonicalBaseUnsupported`]
/// itself refuses the manifest or a staged root. /// if `document.canonical_base` is present (pin 3b: text projection cannot
/// mint a canonical base — checked as defence for a directly constructed
/// `TextDocument`, since `parse` also refuses one on the way in), or
/// [`SerializeError::Bundle`] if the bundle itself refuses the manifest or a
/// staged root.
pub fn serialize_document<S: BlockStore>( pub fn serialize_document<S: BlockStore>(
document: &TextDocument, document: &TextDocument,
store: S, store: S,
@ -124,6 +148,9 @@ pub fn serialize_document<S: BlockStore>(
if !document.blobs.is_empty() { if !document.blobs.is_empty() {
return Err(SerializeError::NonEmptyBlobs); return Err(SerializeError::NonEmptyBlobs);
} }
if document.canonical_base.is_some() {
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))?;
@ -335,11 +362,16 @@ mod tests {
document document
} }
/// Carries an extension, a canonical base, and many envelopes at once — the /// Carries an extension and many envelopes at once — the document every
/// document every round-trip law needs to be checked against together. /// round-trip law needs to be checked against together.
///
/// Used to carry a canonical base too (hence the name); pin 3b's
/// base-bearing exclusion means that capability moved to
/// `document_with_canonical_base` alone, so this fixture keeps every
/// other feature and drops the base
/// (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b).
fn rich_document(seed: u64) -> TextDocument { fn rich_document(seed: u64) -> TextDocument {
let mut document = document_with_extension(seed); let mut document = document_with_extension(seed);
document.canonical_base = document_with_canonical_base(seed).canonical_base;
document.envelopes = envelopes(seed, 60); document.envelopes = envelopes(seed, 60);
document document
} }
@ -365,6 +397,18 @@ mod tests {
); );
} }
#[test]
fn text_projection_serialize_produces_a_major_1_container() {
// Pin 6: every writer path stamps the epoch, including text
// projection. `serialize_document` (via `build_manifest`) is the one
// production writer that reaches a canonical base — `project.rs:936`
// is a `#[cfg(test)]` fixture writer, not production.
let document = minimal_document(11);
let bundle = serialize_document(&document, MemStore::new(), FileUuid([1; 16]))
.expect("a well-formed document serializes");
assert_eq!(bundle.header().format_major, epiphany_bundle::FORMAT_MAJOR);
}
#[test] #[test]
fn document_identity_round_trips() { fn document_identity_round_trips() {
let mut document = minimal_document(1); let mut document = minimal_document(1);
@ -393,31 +437,23 @@ mod tests {
} }
#[test] #[test]
fn canonical_base_round_trips_snapshot_id_and_payload() { fn serializing_a_text_document_with_a_canonical_base_is_refused() {
// Pin 3b: text projection cannot mint a canonical base. Built from
// the existing base-bearing fixture (`document_with_canonical_base`),
// which used to round-trip through `serialize_document` /
// `document_from_bundle` before this rung — that capability is a
// real, intentional loss (base-bearing documents stop round-tripping
// through text until a repack flow exists).
let document = document_with_canonical_base(4); let document = document_with_canonical_base(4);
let reopened = serialize_and_reopen(&document); let result = serialize_document(&document, MemStore::new(), FileUuid([1; 16]));
let expected = document assert!(matches!(
.canonical_base result,
.as_ref() Err(SerializeError::CanonicalBaseUnsupported)
.expect("fixture carries a canonical base"); ));
let base = reopened // M8 depends on this distinction: the dedicated variant, never a
.manifest() // `SerializeError::Bundle` passthrough from the container-layer
.canonical_base // refusal this would otherwise fall through to.
.as_ref() assert!(!matches!(result, Err(SerializeError::Bundle(_))));
.expect("canonical base present after reopen");
assert_eq!(base.snapshot_id, expected.snapshot_id);
assert_eq!(base.covers_causal_frontier, expected.covers_causal_frontier);
assert_eq!(
base.reduction_algorithm_version,
expected.reduction_algorithm_version
);
assert_eq!(base.profile_id, expected.profile_id);
assert_eq!(base.root.kind, ChunkKind::Snapshot);
assert_eq!(base.root.schema_version, expected.root_schema_version);
let payload = reopened
.read_chunk(&base.root)
.expect("root chunk reads and verifies");
assert_eq!(payload, expected.root_payload);
} }
#[test] #[test]
@ -533,7 +569,9 @@ mod tests {
let document = rich_document(10); let document = rich_document(10);
let reopened = serialize_and_reopen(&document); let reopened = serialize_and_reopen(&document);
assert_eq!(reopened.manifest().document_id, document.document_id); assert_eq!(reopened.manifest().document_id, document.document_id);
assert!(reopened.manifest().canonical_base.is_some()); // `rich_document` no longer carries a base (pin 3b) — every other
// root it carries still round-trips together.
assert!(reopened.manifest().canonical_base.is_none());
assert_eq!(reopened.manifest().extension_declarations.len(), 1); assert_eq!(reopened.manifest().extension_declarations.len(), 1);
assert_eq!(reopened.manifest().operation_roots.len(), 1); assert_eq!(reopened.manifest().operation_roots.len(), 1);
reopened reopened
@ -579,6 +617,17 @@ mod tests {
); );
for document in &documents { for document in &documents {
if document.canonical_base.is_some() {
// Pin 3b: a base-bearing document is refused outright, not
// round-tripped — the one round-trip law this suite must now
// except.
let result = serialize_document(document, MemStore::new(), FileUuid([1; 16]));
assert!(matches!(
result,
Err(SerializeError::CanonicalBaseUnsupported)
));
continue;
}
let reopened = serialize_and_reopen(document); let reopened = serialize_and_reopen(document);
assert_eq!(reopened.manifest().document_id, document.document_id); assert_eq!(reopened.manifest().document_id, document.document_id);
} }

View File

@ -123,16 +123,26 @@ pub struct ReachCounts {
/// The exact non-vacuity contract of this corpus: four accepted documents, two /// The exact non-vacuity contract of this corpus: four accepted documents, two
/// reaching each optional/rich feature, and one actually rejected vector for /// reaching each optional/rich feature, and one actually rejected vector for
/// each of the nine distinct rejection classes implemented by this layer. /// each of the ten distinct rejection classes implemented by this layer.
///
/// `canonical_bases` is pinned at **0**, not two:
/// `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b refuses projecting, parsing, and
/// serializing any base-bearing document, so a canonical base is no longer
/// reachable through text at all. The two formerly base-bearing accepts
/// (`extension_base_two_envelopes`, `rich_document`) keep every other feature
/// they exercised but lose their base, and the base-bearing spelling survives
/// only as the new `canonical_base_present` reject vector (class
/// `canonical-base-unsupported`).
pub fn expected_reach() -> ReachCounts { pub fn expected_reach() -> ReachCounts {
ReachCounts { ReachCounts {
extensions: 2, extensions: 2,
canonical_bases: 2, canonical_bases: 0,
custom_profiles: 2, custom_profiles: 2,
lineages: 2, lineages: 2,
multi_envelope: 2, multi_envelope: 2,
reject_classes: [ reject_classes: [
("blob-line", 1), ("blob-line", 1),
("canonical-base-unsupported", 1),
("extension-chunk-order", 1), ("extension-chunk-order", 1),
("extension-declaration-order", 1), ("extension-declaration-order", 1),
("missing-trailing-lf", 1), ("missing-trailing-lf", 1),
@ -341,7 +351,12 @@ fn accept_documents() -> Vec<(&'static str, String)> {
blobs: Vec::new(), blobs: Vec::new(),
envelopes: vec![sample_envelope(1, 100)], envelopes: vec![sample_envelope(1, 100)],
}; };
let extension_base_multi = TextDocument { // `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b: text projection can no longer
// carry a canonical base at all, so this document (still named for the
// two envelopes it exercises, not for a base it no longer carries) keeps
// its extension and non-baseline schema version but drops the base that
// used to make it `extension_base_multi`.
let extension_two_envelopes = TextDocument {
document_id: DocumentId([3; 16]), document_id: DocumentId([3; 16]),
// A non-baseline carried version, so the corpus exercises the // A non-baseline carried version, so the corpus exercises the
// `document` line's schema field at a value other than the // `document` line's schema field at a value other than the
@ -350,17 +365,19 @@ fn accept_documents() -> Vec<(&'static str, String)> {
lineage_id: None, lineage_id: None,
profiles: profiles(false), profiles: profiles(false),
extensions: vec![extension(1, &[1, 2])], extensions: vec![extension(1, &[1, 2])],
canonical_base: Some(base(3)), canonical_base: None,
blobs: Vec::new(), blobs: Vec::new(),
envelopes: vec![sample_envelope(2, 200), sample_envelope(3, 300)], envelopes: vec![sample_envelope(2, 200), sample_envelope(3, 300)],
}; };
// Base removed (pin 3b); lineage, custom profile, two extensions, and
// multiple envelopes are all retained.
let rich = TextDocument { let rich = TextDocument {
document_id: DocumentId([4; 16]), document_id: DocumentId([4; 16]),
manifest_schema_version: SchemaVersion::V0, manifest_schema_version: SchemaVersion::V0,
lineage_id: Some(LineageId([0x14; 16])), lineage_id: Some(LineageId([0x14; 16])),
profiles: profiles(true), profiles: profiles(true),
extensions: vec![extension(1, &[1, 2]), extension(2, &[3])], extensions: vec![extension(1, &[1, 2]), extension(2, &[3])],
canonical_base: Some(base(4)), canonical_base: None,
blobs: Vec::new(), blobs: Vec::new(),
envelopes: vec![ envelopes: vec![
sample_envelope(4, 400), sample_envelope(4, 400),
@ -459,7 +476,7 @@ fn accept_documents() -> Vec<(&'static str, String)> {
), ),
( (
"extension_base_two_envelopes", "extension_base_two_envelopes",
project_text_document(&extension_base_multi), project_text_document(&extension_two_envelopes),
), ),
("rich_document", project_text_document(&rich)), ("rich_document", project_text_document(&rich)),
("create_staff_group", project_text_document(&staff_group)), ("create_staff_group", project_text_document(&staff_group)),
@ -548,7 +565,7 @@ pub fn document_vectors() -> Vec<TextVector> {
}; };
let minimal = by_name("minimal"); let minimal = by_name("minimal");
let lineage_custom = by_name("lineage_custom_profile"); let lineage_custom = by_name("lineage_custom_profile");
let extension_base_multi = by_name("extension_base_two_envelopes"); let extension_two_envelopes = by_name("extension_base_two_envelopes");
let rich = by_name("rich_document"); let rich = by_name("rich_document");
let mut vectors: Vec<TextVector> = accepts let mut vectors: Vec<TextVector> = accepts
@ -556,17 +573,16 @@ pub fn document_vectors() -> Vec<TextVector> {
.map(|(name, text)| (SURFACE, "accept", "-", *name, text.as_bytes().to_vec())) .map(|(name, text)| (SURFACE, "accept", "-", *name, text.as_bytes().to_vec()))
.collect(); .collect();
// The rejected version must be one this crate does NOT implement. Genesis // The rejected version must be one this crate does NOT implement.
// tranche G3b moved `COMPANION_VERSION` to 0.13.0, which had been this // `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b moved `COMPANION_VERSION` to
// vector's "future" version — leaving it would have made the negative // 0.14.0; this vector now names 0.13.0, the immediately superseded
// vector assert that the *correct* header is rejected. It now names // companion (previously 0.12.0, when the committed version was 0.13.0) —
// 0.12.0, the immediately superseded companion, which is the better test // rejecting the version right behind you is exactly the deferred
// anyway: rejecting the version right behind you is exactly the deferred
// migrate-on-read posture (`req:textproj:header-version`). // migrate-on-read posture (`req:textproj:header-version`).
let wrong_version = replace_once( let wrong_version = replace_once(
minimal, minimal,
"(text-projection (0 14 0))",
"(text-projection (0 13 0))", "(text-projection (0 13 0))",
"(text-projection (0 12 0))",
); );
vectors.push(( vectors.push((
SURFACE, SURFACE,
@ -590,12 +606,18 @@ pub fn document_vectors() -> Vec<TextVector> {
blob.into_bytes(), blob.into_bytes(),
)); ));
// Re-expressed on a non-base section pair (pin 3b: neither accept carries
// a canonical base to invert against any more). `projection`'s order is
// `header document lineage? profile* extension* canonical-base? blob*
// envelope*`, so a profile/extension inversion reaches
// `out-of-order-sections` exactly as the old canonical-base/extension
// inversion did, without a base.
vectors.push(( vectors.push((
SURFACE, SURFACE,
"reject", "reject",
"out-of-order-sections", "out-of-order-sections",
"canonical_base_before_extension", "canonical_base_before_extension",
swap_first_lines(extension_base_multi, "(extension ", "(canonical-base ").into_bytes(), swap_first_lines(extension_two_envelopes, "(profile ", "(extension ").into_bytes(),
)); ));
vectors.push(( vectors.push((
@ -611,7 +633,7 @@ pub fn document_vectors() -> Vec<TextVector> {
"reject", "reject",
"operation-envelope-order", "operation-envelope-order",
"envelopes_reversed", "envelopes_reversed",
swap_first_two_lines(extension_base_multi, "(envelope ").into_bytes(), swap_first_two_lines(extension_two_envelopes, "(envelope ").into_bytes(),
)); ));
vectors.push(( vectors.push((
@ -631,7 +653,7 @@ pub fn document_vectors() -> Vec<TextVector> {
)); ));
let chunks_reversed = replace_once( let chunks_reversed = replace_once(
extension_base_multi, extension_two_envelopes,
"((chunk extension-data (schema 0 1) #x01) (chunk extension-data (schema 0 1) #x02))", "((chunk extension-data (schema 0 1) #x01) (chunk extension-data (schema 0 1) #x02))",
"((chunk extension-data (schema 0 1) #x02) (chunk extension-data (schema 0 1) #x01))", "((chunk extension-data (schema 0 1) #x02) (chunk extension-data (schema 0 1) #x01))",
); );
@ -653,6 +675,28 @@ pub fn document_vectors() -> Vec<TextVector> {
missing_lf, missing_lf,
)); ));
// NEW (pin 3b): a base-bearing text, refused by the parse-side check —
// built from the pre-change base-bearing spelling, so the corpus keeps a
// base-bearing text as a negative rather than losing the spelling
// entirely.
let canonical_base_present = TextDocument {
document_id: DocumentId([11; 16]),
manifest_schema_version: SchemaVersion::V0,
lineage_id: None,
profiles: profiles(false),
extensions: Vec::new(),
canonical_base: Some(base(11)),
blobs: Vec::new(),
envelopes: Vec::new(),
};
vectors.push((
SURFACE,
"reject",
"canonical-base-unsupported",
"canonical_base_present",
project_text_document(&canonical_base_present).into_bytes(),
));
vectors vectors
} }
@ -886,7 +930,7 @@ mod tests {
#[test] #[test]
fn the_reference_implementation_agrees_with_every_vector() { fn the_reference_implementation_agrees_with_every_vector() {
match verify(COMMITTED) { match verify(COMMITTED) {
Ok(count) => assert_eq!(count, 19, "the corpus has unexpectedly thinned"), Ok(count) => assert_eq!(count, 20, "the corpus has unexpectedly thinned"),
Err(failures) => panic!( Err(failures) => panic!(
"{} disagreement(s):\n{}", "{} disagreement(s):\n{}",
failures.len(), failures.len(),
@ -957,20 +1001,21 @@ mod tests {
} }
/// (t12) Genesis tranche G3b: text projection round-trips the new /// (t12) Genesis tranche G3b: text projection round-trips the new
/// `create-measure` kind, and the companion version is **0.13.0**, with /// `create-measure` kind, and the companion version is **0.14.0**
/// the negative vector rejecting **0.12.0** (the immediately superseded /// (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b bumped it from 0.13.0), with
/// the negative vector rejecting **0.13.0** (the immediately superseded
/// companion). /// companion).
/// ///
/// **Mutation:** drop `OperationKindTag::CreateMeasure` from /// **Mutation:** drop `OperationKindTag::CreateMeasure` from
/// `OperationKind::parse` in `textproj_kind.rs`; must fail. Separately, /// `OperationKind::parse` in `textproj_kind.rs`; must fail. Separately,
/// leave `COMPANION_VERSION` at `(0, 12, 0)`; the negative vector must /// leave `COMPANION_VERSION` at `(0, 13, 0)`; the negative vector must
/// fail. /// fail.
#[test] #[test]
fn t12_g3b_kinds_round_trip_and_companion_is_0_13_0_rejecting_0_12_0() { fn t12_g3b_kinds_round_trip_and_companion_is_0_14_0_rejecting_0_13_0() {
assert_eq!( assert_eq!(
crate::COMPANION_VERSION, crate::COMPANION_VERSION,
(0, 13, 0), (0, 14, 0),
"the companion version must be 0.13.0" "the companion version must be 0.14.0"
); );
let name = "create_measure"; let name = "create_measure";
@ -988,7 +1033,7 @@ mod tests {
); );
// The negative vector must reject exactly the immediately superseded // The negative vector must reject exactly the immediately superseded
// companion, 0.12.0. // companion, 0.13.0.
let rows = parse(COMMITTED).expect("the committed corpus parses"); let rows = parse(COMMITTED).expect("the committed corpus parses");
let superseded = rows let superseded = rows
.iter() .iter()
@ -997,8 +1042,8 @@ mod tests {
assert_eq!(superseded.verdict, "reject"); assert_eq!(superseded.verdict, "reject");
let text = String::from_utf8(superseded.text.clone()).expect("utf8"); let text = String::from_utf8(superseded.text.clone()).expect("utf8");
assert!( assert!(
text.contains("(text-projection (0 12 0))"), text.contains("(text-projection (0 13 0))"),
"the negative vector must name the immediately superseded companion 0.12.0, got: {text}" "the negative vector must name the immediately superseded companion 0.13.0, got: {text}"
); );
assert!( assert!(
parse_document(&text).is_err(), parse_document(&text).is_err(),

View File

@ -1,14 +1,21 @@
# Contract — P13-S27: the reduction version gets an outside witness # Contract — P13-S27: the reduction version gets an outside witness
**Status:** DRAFT — **BLOCKED on P13-S28** (the format-epoch rung). Pins 1 and **Status:** DRAFT — **BLOCKED on the format-epoch rung**
3–10 are settled, internally consistent, and ratifiable as a plan. **Pin 2a is (`spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`), which is **ratified and in
an open question this contract states rather than answers**, and it is not implementation** but has not yet landed. Pins 1 and 3–10 are settled, internally
answerable inside this rung: the provenance carrier it needs is a format-epoch consistent, and ratifiable as a plan.
design, which P13-S28 owns.
**This is bounded analysis, not an abandoned implementation plan.** It may not be **Pin 2a is RESOLVED as of 2026-08-07** — from outside this rung, by that
dispatched. Pin 2a MUST NOT be treated as a sub-pin that drifts into this rung's contract's pin 8, exactly as its prohibition required. Legacy bases are refused
scope — see its own prohibition. by container epoch, never by version arithmetic; see the resolution block under
pin 2a. This contract additionally **inherits three obligations** from that rung
(the two interim refusals it must convert to validation, M8's deferred laundering
demonstration, and pin 3c's two suspended conformance assertions) — recorded in
the same place.
**It becomes dispatchable when the format rung lands, not before.** Until then
this remains bounded analysis. Pin 2a's original prohibition stands for the
record: it was never amended into a disposition from inside this contract.
**Rung type:** **capability + API change.** No wire bytes move and no schema **Rung type:** **capability + API change.** No wire bytes move and no schema
major or minor changes — `BundleError` has no discriminant and no encoder major or minor changes — `BundleError` has no discriminant and no encoder
@ -16,10 +23,10 @@ major or minor changes — `BundleError` has no discriminant and no encoder
change is `Bundle::open`'s **and `Bundle::create`'s** signatures, at 57 and 32 change is `Bundle::open`'s **and `Bundle::create`'s** signatures, at 57 and 32
call sites. call sites.
**Does NOT by itself unblock P13-S16.** This rung installs the authority and **Now DOES unblock P13-S16, once it lands.** This rung installs the authority and
validates both read and write paths. S16 additionally requires the **legacy-base validates both read and write paths; S16's remaining precondition was pin 2a's
disposition of pin 2a**, which is an open question this contract states rather legacy-base disposition, and that is resolved. The chain is therefore
than answers. format-epoch rung → **P13-S27** → **P13-S16**, with no open question left in it.
**Rulings already made (2026-07-31), not re-opened here:** **Rulings already made (2026-07-31), not re-opened here:**
@ -246,6 +253,74 @@ dispatchable merely because S27 lands.** This pin deliberately remains an open
question. It is not to be amended into a disposition without its own ratification question. It is not to be amended into a disposition without its own ratification
round. round.
### Pin 2a — **RESOLVED 2026-08-07 by the format-epoch rung.**
The ratification round this pin demanded is the one the format-epoch contract
had: `spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`, ratified after four adversarial
review rounds, whose **pin 8** exists to resolve this pin from outside it. The
open analysis above is retained verbatim as the reasoning that produced the
answer, not superseded prose.
**The disposition is none of (i), (ii) or (iii): it is the container epoch.**
> **Reduction-version authority is meaningful only in major-1 containers.
> Legacy bases are refused by container epoch, never by version arithmetic.**
That is why the collision this pin identified never has to be adjudicated. A
pre-S27 base carrying `1` and a legitimately rebuilt S16 base carrying `1` are
indeed indistinguishable **as numbers** — and they never meet, because the
pre-S27 base can only exist in a major-0 container, which is refused at the
epoch boundary before any version is compared. The `u32` never has to carry
provenance, because the container already does.
Each rejected option, and why the epoch beats it: **(i)** normalizing the corpus
would have to find every artifact, and a missed one is silently wrong forever;
**(ii)** a non-colliding epoch value is a convention a hand-authored document can
simply declare — `parse.rs:591` accepts an unbounded `u32`; **(iii)** widening
the type makes the wire meaning richer and buys nothing the container property
does not already give. All three try to make a number carry provenance. The
epoch makes the *file* carry it.
**S27's own baseline stays `0`** (pin 2 is unchanged). What changes is that the
question "what about a base older than the authority?" is no longer S27's to
answer.
### Inherited from the format-epoch rung — obligations S27 must discharge
The format rung lands **before** this one and closes two things temporarily,
naming S27 as what reopens them. Both are owed work here, not optional:
1. **The interim refusals become real validation.** The format rung's pin 3a
temporarily refuses **both** major-1 base boundaries — opening a major-1
container that already carries a base, and committing a base into one —
through a third, temporary error (`ReductionAuthorityUnavailable`) that is
distinct from its two legacy/repack errors. S27 replaces **both** branches
with capability validation. Replacing only one leaves a hole exactly where
the format rung's own review found one.
2. **The deferred laundering demonstration** (format-rung M8). That rung could
not demonstrate the text-import laundering path end to end, because pin 3a
refuses every major-1 base commit categorically, so the "a base-bearing text
document really does serialize into a major-1 container" observation is
unreachable there. Under S27 a base commit succeeds or fails **on its
version**, so the demonstration becomes performable and is owed: with pin 3b's
text refusal removed, show that a base-bearing document whose raw version
happens to match the current authority serializes into a major-1 container
indistinguishable from a validated one. That is the false provenance the text
refusal exists to prevent, and it has never been observed — only reasoned
about.
3. **Two conformance assertions come back** (format-rung pin 3c). Criterion 4's
bookkeeping-projection counterpart, `assert_reduction_serialization_stable`
(`testkit/src/roundtrip.rs:241`), keeps its serialize → load → decode →
reserialize cycle through the interval but loses exactly two
canonical-base-specific assertions: `verify_canonical_chunks`'s base branch
(`bundle.rs:613`–`:621`, including the `base.hash != base.root.hash`
cross-check), and the reopened manifest actually carrying the base
(`roundtrip.rs:293`–`:297`). S27 restores both, since a base-bearing container
becomes constructible again the moment validation replaces refusal. The
harness carries a marker naming this contract at the suspension point.
**Pin 3 — `BundleCapabilities`, required at both constructors, carried on the **Pin 3 — `BundleCapabilities`, required at both constructors, carried on the
`Bundle`.** `Bundle`.**
@ -404,10 +479,27 @@ Named, permanent, in `epiphany-bundle`:
intact. A writer check that corrupts the document while refusing is worse than intact. A writer check that corrupts the document while refusing is worse than
no check. no check.
**Added 2026-08-07 with pin 2a's resolution — the inherited obligations, stated
as tests so they cannot be discharged by prose:**
6. **`a_major_1_bundle_carrying_a_base_opens_when_the_authority_matches`** — the
read-side half of the format rung's pin 3a, converted from temporary refusal
to real validation. Its sibling is test 2, which is the same path when the
authority *disagrees*. Both branches must exist; the format rung's own review
found a draft that closed only one.
7. **The two restored conformance assertions** (format-rung pin 3c), in
`assert_reduction_serialization_stable` (`testkit/src/roundtrip.rs:241`):
`verify_canonical_chunks` covering the base again, and the reopened manifest
carrying it. **Restoring them means deleting the suspension marker** that
names this contract — if the marker is still in the tree when this rung
reports, the restoration did not happen.
Tests 2 and 3 must be **paired in review**: each asserts the other's error is Tests 2 and 3 must be **paired in review**: each asserts the other's error is
*not* produced. A test that only checks its own variant cannot show the two *not* produced. A test that only checks its own variant cannot show the two
paths are distinguishable, which is the whole point of pin 6. paths are distinguishable, which is the whole point of pin 6.
Tests 6 and 2 stand in the same relation to each other.
--- ---
## §4. Mutation plan ## §4. Mutation plan

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -240,7 +240,7 @@
{\Large\scshape\color{epiphanyslate}Binary Format}\\[6pt] {\Large\scshape\color{epiphanyslate}Binary Format}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt] {\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt] {\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.16.0 --- The genesis operation tranche closes: \texttt{CreateMeasure} (G3b)}\\[4pt] {\normalsize\color{epiphanyink}Version 0.17.0 --- The container format major becomes 1: the header carries an epoch}\\[4pt]
{\small\color{epiphanyslate}Normative for the byte layouts it defines} {\small\color{epiphanyslate}Normative for the byte layouts it defines}
\vfill \vfill
\end{titlepage} \end{titlepage}
@ -1805,10 +1805,18 @@ format major version. All integers little-endian.
\endhead \endhead
\tablenums{0..8} & magic & \texttt{MUSCBND\textbackslash0} (8 bytes, ASCII, \tablenums{0..8} & magic & \texttt{MUSCBND\textbackslash0} (8 bytes, ASCII,
trailing NUL) \\ trailing NUL) \\
\tablenums{8..10} & \texttt{format\_major} (\texttt{u16}) & \tablenums{0} \tablenums{8..10} & \texttt{format\_major} (\texttt{u16}) & \tablenums{1}
in this version; readers reject other values \\ written in this version. The container \emph{epoch}: a major-1
\tablenums{10..12} & \texttt{format\_minor} (\texttt{u16}) & \tablenums{1} container is one whose every base-bearing commit was validated
written in this version \\ against a supplied reduction authority. \tablenums{0} is
\emph{legacy} and \MUST{} be decoded deliberately as such, not
rejected; any other value is rejected. A legacy container \MAY{}
be opened only while it carries no canonical base, and \MUSTNOT{}
acquire one --- the epoch is fixed at creation and cannot be
inherited by a later commit (Core Specification,
\sectionsc{The Container Epoch}) \\
\tablenums{10..12} & \texttt{format\_minor} (\texttt{u16}) & \tablenums{0}
written in this version; minor numbering restarts at a new major \\
\tablenums{12..16} & \texttt{header\_length} (\texttt{u32}) & \tablenums{12..16} & \texttt{header\_length} (\texttt{u32}) &
\tablenums{64}; readers reject other values \\ \tablenums{64}; readers reject other values \\
\tablenums{16..24} & \texttt{superblock\_a\_offset} (\texttt{u64}) & \tablenums{16..24} & \texttt{superblock\_a\_offset} (\texttt{u64}) &
@ -3715,6 +3723,28 @@ only}: implementations need not agree on an error taxonomy.
ladder: G1 $\rightarrow$ G2a $\rightarrow$ G-minor $\rightarrow$ G2b ladder: G1 $\rightarrow$ G2a $\rightarrow$ G-minor $\rightarrow$ G2b
$\rightarrow$ G3a $\rightarrow$ G3b. Semantics: Operation Catalog $\rightarrow$ G3a $\rightarrow$ G3b. Semantics: Operation Catalog
\sectionsc{CreateMeasure}, 0.13.0. \\ \sectionsc{CreateMeasure}, 0.13.0. \\
\today & The Fixed Header & 0.17.0 --- The container format major becomes
\tablenums{1}, and \texttt{format\_minor} restarts at \tablenums{0}
(\texttt{spec/CONTRACT\_FORMAT\_EPOCH\_MAJOR1.md}). This is the first
\emph{container} major boundary, and it is not a change of wire layout: no
field moves, no offset changes, and every byte range in the header table is
what it was. What changes is what the major \emph{means}. A major-1 container
is one whose every base-bearing commit was validated against a supplied
reduction authority; major \tablenums{0} is legacy and \MUST{} be decoded
deliberately as such rather than rejected.
The major is the carrier precisely because the header never changes after
creation. A superblock field could not do this job: a legacy container that
later commits a validated base would keep carrying whatever its creation
stamped, so provenance would be inheritable by a document that never earned
it. A minor bump could not do it either --- readers ignore minor, and this
document's own rules limit a minor change to appending append-safe
discriminants, whereas making a previously-valid canonical base newly
rejectable is a semantic acceptance change. The full epoch matrix, including
the rule that a legacy container may open but \MUSTNOT{} acquire a canonical
base, is normative in the Core Specification,
\sectionsc{The Container Epoch}. \\
\bottomrule \bottomrule
\end{longtable} \end{longtable}

Binary file not shown.

View File

@ -10868,6 +10868,65 @@ pub struct FixedHeader {
\texttt{file\_uuid}. \texttt{file\_uuid}.
\end{requirement} \end{requirement}
\subsubsection{The Container Epoch}
\label{sec:format:container-epoch}
The \texttt{format\_major} field is not only a wire-layout
discriminator. It is the container's \emph{epoch}: the statement of
which guarantees held at the moment the file was created. Because the
header never changes after creation, the epoch is a property of the
file's creation and cannot drift as the document is committed to ---
which is precisely what makes it able to carry a provenance guarantee
that a later generation must not be able to inherit.
Major \tablenums{1} is the first epoch to use that capacity. A major-1
container is one whose every base-bearing commit was validated against
a supplied reduction authority; a major-0 container carries no such
guarantee, and no sequence of commits can promote one into the other.
\begin{requirement}
\label{req:format:container-epoch}
A reader \MUST{} classify \texttt{format\_major} three ways rather
than testing it for equality with the version it writes:
\tablenums{0} is \emph{legacy} and \MUST{} be decoded deliberately as
such; \tablenums{1} is \emph{current}; any other value \MUST{} be
rejected as an unsupported format version. The classification
\MUST{} be carried as a value the reader can act on, not re-derived
by comparison at each use site.
Behaviour is then determined by the container's epoch together with
the presence of a canonical base:
\begin{itemize}
\item \emph{Legacy, no canonical base} --- the bundle \MAY{} be
opened. Nothing whose validity cannot be established is exposed.
\item \emph{Legacy, canonical base present} --- the bundle
\MUST{} be refused. A base predating any reduction authority is
not safe materialized state, and \MUSTNOT{} be served in a
read-only or otherwise restricted mode: a restricted view of
unverifiable canonical state is still unverifiable canonical
state, presented confidently.
\item \emph{Legacy, a commit attempts to add or replace a
canonical base} --- the commit \MUST{} be refused, and the
implementation \MUST{} direct the writer to repack into a fresh
major-1 container. This is the rule that makes the epoch
non-inheritable, and it is the reason a header field can carry
this guarantee where a superblock field could not.
\item \emph{Current, no canonical base} --- the bundle \MAY{} be
opened, and \MAY{} be committed to.
\item \emph{Current, canonical base present or introduced} ---
the base \MUST{} be validated against the reduction authority
the opening implementation supplies.
\end{itemize}
Where a container's canonical base disagrees with its active
superblock, that disagreement is \emph{corruption} and \MUST{} be
reported as a malformed bundle \emph{before} any epoch rule above is
applied, in either epoch. Ordering the epoch rules first would
collapse tampering into staleness and erase the distinction the
reduction authority depends on.
\end{requirement}
\subsection{The Superblock Slots} \subsection{The Superblock Slots}
Each superblock slot is exactly 256 bytes. The bundle has two slots Each superblock slot is exactly 256 bytes. The bundle has two slots
@ -12229,6 +12288,18 @@ pub struct SchemaVersion {
Schema versioning rules: Schema versioning rules:
\begin{itemize} \begin{itemize}
\item These rules govern \emph{schema} versions --- the manifest's
\texttt{manifest\_schema\_version} and each chunk's
\texttt{schema\_version}. The container's
\texttt{format\_major} is a separate axis, and a change to it
now means more than a change of wire layout: it declares a new
container \emph{epoch}, with guarantees that a reader may rely
on and that no later commit can confer on a container created
before them (Section~\ref{sec:format:container-epoch}). A
container format major boundary is therefore not satisfied by
re-encoding bytes; a document produced under an older epoch
acquires the new epoch's guarantees only by being repacked into
a newly created container.
\item Major version changes are non-backward-compatible. \item Major version changes are non-backward-compatible.
Readers that do not support the chunk's major schema Readers that do not support the chunk's major schema
version \MUST{} handle it according to the chunk's role: version \MUST{} handle it according to the chunk's role:
@ -16720,6 +16791,35 @@ layouts they own versus inherit:
metadata: it enters no content hash and no canonical encoding, so no metadata: it enters no content hash and no canonical encoding, so no
wire form and no companion version moves. wire form and no companion version moves.
\\ \\
\midrule
\today & The Fixed Header (container epoch) &
Chapter~8 gains \sectionsc{The Container Epoch} and
\ref{req:format:container-epoch}
(\texttt{spec/CONTRACT\_FORMAT\_EPOCH\_MAJOR1.md}).
\texttt{format\_major} becomes \tablenums{1} and \texttt{format\_minor}
restarts at \tablenums{0}. A reader classifies the major three ways ---
legacy \tablenums{0}, current \tablenums{1}, unsupported otherwise ---
and carries the classification as a value rather than re-deriving it by
comparison, so a legacy container is decoded deliberately instead of
refused.
The epoch exists because a canonical base is only meaningful when it has
been validated against a reduction authority, and nothing in a document
could previously attest that: the reduction-algorithm version is a bare
integer that a writer copies from the base's own self-report, so the one
check that would detect a stale base necessarily passes for a base that
was propagated conformingly. The header can attest it because the header
never changes after creation --- which makes the epoch a property of the
file's creation that no later commit can confer. A legacy container may
therefore open while it carries no base, \MUSTNOT{} acquire one, and is
refused outright if it already has one; a repack into a freshly created
major-1 container is the only path forward, and read-only degradation is
explicitly not offered, since a restricted view of unverifiable canonical
state is still unverifiable canonical state. Corruption keeps precedence
over every epoch rule in both epochs, so tampering is never reported as
staleness. \sectionsc{Schema Versioning} gains the corresponding
distinction between the schema axis and the container axis.
\\
\bottomrule \bottomrule
\end{longtable} \end{longtable}

Binary file not shown.

View File

@ -234,7 +234,7 @@
{\Large\scshape\color{epiphanyslate}Text Projection}\\[6pt] {\Large\scshape\color{epiphanyslate}Text Projection}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt] {\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt] {\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.13.0 --- The genesis ladder closes: \texttt{create-measure} reaches the grammar}\\[4pt] {\normalsize\color{epiphanyink}Version 0.14.0 --- Canonical bases leave the companion: the container epoch carries what text cannot}\\[4pt]
{\small\color{epiphanyslate}Normative for the text form it defines} {\small\color{epiphanyslate}Normative for the text form it defines}
\vfill \vfill
\end{titlepage} \end{titlepage}
@ -483,7 +483,7 @@ A projection is, in order:
\begin{requirement} \begin{requirement}
\label{req:textproj:header-version} \label{req:textproj:header-version}
A parser implementing this companion \MUST{} accept exactly one header A parser implementing this companion \MUST{} accept exactly one header
version: \texttt{(0 13 0)}, the version of the companion it implements. It version: \texttt{(0 14 0)}, the version of the companion it implements. It
\MUST{} reject any other version at line one. \MUST{} reject any other version at line one.
Multi-version acceptance and text migrate-on-read are deferred in the same Multi-version acceptance and text migrate-on-read are deferred in the same
@ -534,7 +534,7 @@ projection introduces no ordering of its own.
A parser \MUST{} reject every \texttt{(blob ...)} line whose blob is A parser \MUST{} reject every \texttt{(blob ...)} line whose blob is
unreferenced by canonical state unreferenced by canonical state
(Requirement~\ref{req:textproj:canonical-blobs}). At companion (Requirement~\ref{req:textproj:canonical-blobs}). At companion
version~0.13.0, neither a canonical operation nor canonical reduced state can version~0.14.0, neither a canonical operation nor canonical reduced state can
carry a \texttt{BlobId}; canonical state therefore cannot reference a blob, carry a \texttt{BlobId}; canonical state therefore cannot reference a blob,
and a parser \MUST{} reject every \texttt{(blob ...)} line. and a parser \MUST{} reject every \texttt{(blob ...)} line.
\end{requirement} \end{requirement}
@ -906,6 +906,31 @@ are ratified.
whose canonical document semantics are identical to the original's. The whose canonical document semantics are identical to the original's. The
bundle's physical layout, chunking, and compression \MAY{} differ. bundle's physical layout, chunking, and compression \MAY{} differ.
\textbf{Documents carrying a canonical base are outside this requirement's
domain, and are refused rather than round-tripped.} The exclusion is stated
here, in the requirement's own terms, because the equations below quantify
universally and a reader checking them against an implementation would
otherwise find a conforming implementation failing them. The companion
\MUST{} refuse a base-bearing document at every one of its three boundaries:
projecting a base-bearing bundle to text, parsing text that declares a
canonical base, and serializing a directly constructed document that carries
one.
The reason is that this format cannot carry the guarantee the base needs. A
canonical base is only meaningful when it has been validated against a
reduction authority, and that validation is attested by the \emph{container}
epoch (Core Specification, \sectionsc{The Container Epoch}), which a text
document has no way to hold: every field a text format defines can be typed
by hand, so any provenance marker it carried would reduce to trusting its
author. Serializing a base-bearing text document into a freshly created
container would therefore mint a container asserting a validation that never
occurred. A canonical base enters a container only through an explicit
rebuild or repack flow, never through text import.
This is a real loss of capability and is stated as one: a base-bearing bundle
does not round-trip through text. The quantifiers below range over documents
that carry no canonical base.
Equivalently, and more usefully to an implementer: for every bundle $B$, Equivalently, and more usefully to an implementer: for every bundle $B$,
\[ \[
\textrm{semantics}(\textrm{parse}(\textrm{project}(B))) = \textrm{semantics}(\textrm{parse}(\textrm{project}(B))) =
@ -1140,13 +1165,12 @@ an example that cannot be parsed teaches the wrong lesson. The conformance vecto
that pin \emph{real} bytes are a deliverable of the implementation. that pin \emph{real} bytes are a deliverable of the implementation.
A document of one operation --- a transposition of two pitches up a perfect A document of one operation --- a transposition of two pitches up a perfect
fifth, over a compacted base --- projects to five lines: fifth --- projects to four lines:
\begin{lstlisting} \begin{lstlisting}
(text-projection (0 13 0)) (text-projection (0 14 0))
(document #x05050505050505050505050505050505 (schema 0 1)) (document #x05050505050505050505050505050505 (schema 0 1))
(profile full (0 1 0) (constraints 67108864 (retention 1 () true))) (profile full (0 1 0) (constraints 67108864 (retention 1 () true)))
(canonical-base #x1f8b0000000000000000000000000000 #x 1 full (schema 0 1) #x0000)
(envelope #x00000000000000070000000000000001 #x00000000000000000000000011223344 (stamp 42 7 #x00000000000000070000000000000001) (causal ((#x0000000000000001 3)) (#x00000000000000020000000000000009)) (some #x00000000000000070000000000000005) (primitive (transpose-interval (#x00000000000000070000000000000001 #x00000000000000070000000000000002) (transposition-interval 4 7)))) (envelope #x00000000000000070000000000000001 #x00000000000000000000000011223344 (stamp 42 7 #x00000000000000070000000000000001) (causal ((#x0000000000000001 3)) (#x00000000000000020000000000000009)) (some #x00000000000000070000000000000005) (primitive (transpose-interval (#x00000000000000070000000000000001 #x00000000000000070000000000000002) (transposition-interval 4 7))))
\end{lstlisting} \end{lstlisting}
@ -1155,6 +1179,19 @@ The envelope's targets are a \emph{set}: strictly increasing, no duplicates
$\mathrm{seq}^{\Uparrow}$). A parser \MUST{} reject a duplicate rather than $\mathrm{seq}^{\Uparrow}$). A parser \MUST{} reject a duplicate rather than
absorb it, exactly as the binary decoder does. absorb it, exactly as the binary decoder does.
Earlier revisions of this companion showed the same document over a compacted
base, as five lines. The fifth line is retained here as the spelling a parser
now \emph{refuses}:
\begin{lstlisting}
(canonical-base #x1f8b0000000000000000000000000000 #x 1 full (schema 0 1) #x0000)
\end{lstlisting}
The grammar still defines the section --- it is what a refusal is defined
against, and what a future rebuild or repack flow will emit --- but no document
containing it parses, projects, or serializes
(\texttt{req:textproj:roundtrip}).
% =========================================================================== % ===========================================================================
\chapter{Revision History} \chapter{Revision History}
\label{ch:history} \label{ch:history}
@ -1417,6 +1454,27 @@ absorb it, exactly as the binary decoder does.
grammars both claiming \texttt{(0 12 0)}. Cached projections at grammars both claiming \texttt{(0 12 0)}. Cached projections at
\texttt{(0 12 0)} do not migrate; a stale \texttt{TextProjection} chunk is \texttt{(0 12 0)} do not migrate; a stale \texttt{TextProjection} chunk is
regenerated, not converted. \\ regenerated, not converted. \\
\today & Chapters 4, 5 & 0.14.0 --- Canonical bases leave the companion
(\texttt{spec/CONTRACT\_FORMAT\_EPOCH\_MAJOR1.md}, pin 3b). The container
format major becomes an \emph{epoch} attesting that every base-bearing commit
was validated against a reduction authority (Core Specification,
\sectionsc{The Container Epoch}); a text document cannot hold that attestation,
because every field this format defines can be typed by hand. Serializing a
base-bearing document into a freshly created container would therefore mint a
container asserting a validation that never happened.
\texttt{req:textproj:roundtrip} accordingly excludes base-bearing documents in
its own terms, and all three boundaries refuse them: projection, parsing, and
serialization.
The first two of those refusals are \emph{new}; serialization had none to
retain. This is a real capability loss and is recorded as one --- a
base-bearing bundle does not round-trip through text until a rebuild or repack
flow exists. The forcing reason for the version bump is the usual one in
reverse: refusing a document the companion previously serialized is a semantic
change, and holding the version would leave two mutually incompatible readings
of \texttt{(0 13 0)}. The \texttt{canonical-base} production is retained in the
grammar --- a refusal is defined against it, and a repack flow will emit it. \\
\bottomrule \bottomrule
\end{longtable} \end{longtable}

View File

@ -22,22 +22,23 @@
# document bytes are normative. `<utf8-hex>` is lowercase with no separators. # document bytes are normative. `<utf8-hex>` is lowercase with no separators.
# textproj.document # textproj.document
textproj.document accept - minimal 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a textproj.document accept - minimal 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document accept - set_tuning_context 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830353035303530353035303530353035303530353035303530353035303530352028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303037202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203730302030202378303030303030303030303030303030313030303030303030303030303030303729202863617573616c2028292028292920282920287072696d697469766520287365742d74756e696e672d636f6e74657874202874756e696e672d636f6e746578742d73657474696e67732022636d6e2d31322220227465742d31322220287265666572656e63652d70697463682028636d6e2061203020342920237830303030303030303030663037623430292028736d75666c2d76657273696f6e2d726571756972656d656e742028736d75666c2d76657273696f6e2031203430292028736d75666c2d76657273696f6e20312034302929202829292929290a textproj.document accept - set_tuning_context 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830353035303530353035303530353035303530353035303530353035303530352028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303037202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203730302030202378303030303030303030303030303030313030303030303030303030303030303729202863617573616c2028292028292920282920287072696d697469766520287365742d74756e696e672d636f6e74657874202874756e696e672d636f6e746578742d73657474696e67732022636d6e2d31322220227465742d31322220287265666572656e63652d70697463682028636d6e2061203020342920237830303030303030303030663037623430292028736d75666c2d76657273696f6e2d726571756972656d656e742028736d75666c2d76657273696f6e2031203430292028736d75666c2d76657273696f6e20312034302929202829292929290a
textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document accept - rich_document 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a textproj.document accept - rich_document 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document accept - create_staff_group 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830363036303630363036303630363036303630363036303630363036303630362028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303038202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203830302030202378303030303030303030303030303030313030303030303030303030303030303829202863617573616c2028292028292920282920287072696d697469766520286372656174652d73746166662d67726f7570202873746166662d67726f757020237830303030303030303030303030303031303030303030303030303030303030312028736f6d65202273746166662d67726f75702d312229206772616e642d737461666620282378303030303030303030303030303030313030303030303030303030303030303129292929290a textproj.document accept - create_staff_group 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830363036303630363036303630363036303630363036303630363036303630362028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303038202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203830302030202378303030303030303030303030303030313030303030303030303030303030303829202863617573616c2028292028292920282920287072696d697469766520286372656174652d73746166662d67726f7570202873746166662d67726f757020237830303030303030303030303030303031303030303030303030303030303030312028736f6d65202273746166662d67726f75702d312229206772616e642d737461666620282378303030303030303030303030303030313030303030303030303030303030303129292929290a
textproj.document accept - create_part_definition 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830373037303730373037303730373037303730373037303730373037303730372028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303039202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203930302030202378303030303030303030303030303030313030303030303030303030303030303929202863617573616c2028292028292920282920287072696d697469766520286372656174652d706172742d646566696e6974696f6e2028706172742d646566696e6974696f6e20237830303030303030303030303030303031303030303030303030303030303030312022706172742d312220282378303030303030303030303030303030313030303030303030303030303030303129292929290a textproj.document accept - create_part_definition 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830373037303730373037303730373037303730373037303730373037303730372028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303039202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203930302030202378303030303030303030303030303030313030303030303030303030303030303929202863617573616c2028292028292920282920287072696d697469766520286372656174652d706172742d646566696e6974696f6e2028706172742d646566696e6974696f6e20237830303030303030303030303030303031303030303030303030303030303030312022706172742d312220282378303030303030303030303030303030313030303030303030303030303030303129292929290a
textproj.document accept - create_analysis_layer 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830383038303830383038303830383038303830383038303830383038303830382028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303061202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313030302030202378303030303030303030303030303030313030303030303030303030303030306129202863617573616c2028292028292920282920287072696d697469766520286372656174652d616e616c797369732d6c617965722028616e616c797369732d6c61796572202378303030303030303030303030303030313030303030303030303030303030303120226c617965722d3122292929290a textproj.document accept - create_analysis_layer 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830383038303830383038303830383038303830383038303830383038303830382028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303061202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313030302030202378303030303030303030303030303030313030303030303030303030303030306129202863617573616c2028292028292920282920287072696d697469766520286372656174652d616e616c797369732d6c617965722028616e616c797369732d6c61796572202378303030303030303030303030303030313030303030303030303030303030303120226c617965722d3122292929290a
textproj.document accept - create_view 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830393039303930393039303930393039303930393039303930393039303930392028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303062202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313130302030202378303030303030303030303030303030313030303030303030303030303030306229202863617573616c2028292028292920282920287072696d697469766520286372656174652d766965772028766965772d646566696e6974696f6e20237830303030303030303030303030303031303030303030303030303030303030312022766965772d312220282378303030303030303030303030303030313030303030303030303030303030303129292929290a textproj.document accept - create_view 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830393039303930393039303930393039303930393039303930393039303930392028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303062202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313130302030202378303030303030303030303030303030313030303030303030303030303030306229202863617573616c2028292028292920282920287072696d697469766520286372656174652d766965772028766965772d646566696e6974696f6e20237830303030303030303030303030303031303030303030303030303030303030312022766965772d312220282378303030303030303030303030303030313030303030303030303030303030303129292929290a
textproj.document accept - create_measure 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830613061306130613061306130613061306130613061306130613061306130612028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303063202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313230302030202378303030303030303030303030303030313030303030303030303030303030306329202863617573616c2028292028292920282920287072696d697469766520286372656174652d6d656173757265202378303030303030303030303030303030313030303030303030303030303030303120286d6561737572652023783030303030303030303030303030303130303030303030303030303030303031202877616c6c2d636c6f636b2031303030303030303030292028736f6d652023783030303030303030303030303030303130303030303030303030303030303031292028736f6d65203129206175746f292929290a textproj.document accept - create_measure 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830613061306130613061306130613061306130613061306130613061306130612028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303063202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313230302030202378303030303030303030303030303030313030303030303030303030303030306329202863617573616c2028292028292920282920287072696d697469766520286372656174652d6d656173757265202378303030303030303030303030303030313030303030303030303030303030303120286d6561737572652023783030303030303030303030303030303130303030303030303030303030303031202877616c6c2d636c6f636b2031303030303030303030292028736f6d652023783030303030303030303030303030303130303030303030303030303030303031292028736f6d65203129206175746f292929290a
textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e202830203132203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a
textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a
textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929 textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929
textproj.document reject canonical-base-unsupported canonical_base_present 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830623062306230623062306230623062306230623062306230623062306230622028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378306230623062306230623062306230623062306230623062306230623062306220237830313032306220312066756c6c2028736368656d61203020312920237861303062290a