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:
parent
973297e1a1
commit
bc06706e41
|
|
@ -18,7 +18,7 @@ use crate::chunk::{
|
|||
};
|
||||
use crate::codec::DecodeError;
|
||||
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::manifest::{BlobRef, Manifest, ProfileDeclaration};
|
||||
use crate::opindex::OperationIndex;
|
||||
|
|
@ -408,6 +408,19 @@ impl<S: BlockStore> Bundle<S> {
|
|||
"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
|
||||
// Under Unknown Extensions").
|
||||
|
|
@ -761,6 +774,28 @@ impl<S: BlockStore> Bundle<S> {
|
|||
// least one declared profile.
|
||||
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
|
||||
// declares actually resolves to a present, hash-intact chunk of the right
|
||||
// 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,
|
||||
// and re-stamping byte-identical content churns its address). With
|
||||
// the Snapshot KIND now admitting the major-2 acceleration form, the
|
||||
// per-role check must catch a mis-stamped base: read-only + anomaly,
|
||||
// at commit and again on reopen.
|
||||
let mut bundle = fresh_bundle();
|
||||
let base = StagedChunk {
|
||||
// per-role check (`mis_stamped_canonical_base`) must catch a
|
||||
// mis-stamped base.
|
||||
//
|
||||
// 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,
|
||||
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 {
|
||||
snapshot_id: SnapshotId(sid),
|
||||
covers_causal_frontier: FrontierBytes::from_bytes(vec![]),
|
||||
reduction_algorithm_version: ReductionAlgorithmVersion(0),
|
||||
profile_id: ProfileId::Full,
|
||||
hash: root.hash,
|
||||
root,
|
||||
});
|
||||
m
|
||||
})
|
||||
.expect("a structurally-valid mis-stamped base is publishable");
|
||||
assert!(
|
||||
bundle.is_read_only(),
|
||||
"a mis-stamped canonical base forces read-only at commit"
|
||||
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,
|
||||
});
|
||||
assert_eq!(
|
||||
mis_stamped_canonical_base(&m),
|
||||
Some(1),
|
||||
"a canonical base root stamped above major 0 is still flagged by the per-role \
|
||||
check, even though pin 3a's epoch guard now intercepts every base before this \
|
||||
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> {
|
||||
|
|
@ -1543,6 +1580,307 @@ mod tests {
|
|||
.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.
|
||||
// -----------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -116,6 +116,40 @@ pub enum BundleError {
|
|||
/// extension, or a recovery/anomaly open). Chapter 8 §"Behavior Under
|
||||
/// Unknown Extensions" / §"Superblock Selection".
|
||||
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 {
|
||||
|
|
@ -187,6 +221,18 @@ impl core::fmt::Display for BundleError {
|
|||
}
|
||||
BundleError::Decode(e) => write!(f, "decode error: {e}"),
|
||||
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",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,39 @@ pub const SLOT_A_OFFSET: u64 = 64;
|
|||
pub const SLOT_B_OFFSET: u64 = 320;
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
const HEADER_CRC_RANGE: usize = 60;
|
||||
|
|
@ -59,6 +88,9 @@ pub struct FixedHeader {
|
|||
pub superblock_b_offset: u64,
|
||||
/// Physical-bundle UUID, set at creation, changed on Save As.
|
||||
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 {
|
||||
|
|
@ -71,6 +103,7 @@ impl FixedHeader {
|
|||
superblock_a_offset: SLOT_A_OFFSET,
|
||||
superblock_b_offset: SLOT_B_OFFSET,
|
||||
file_uuid,
|
||||
epoch: FormatEpoch::Current,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,12 +149,20 @@ impl FixedHeader {
|
|||
let _magic = r.take_array::<8>()?;
|
||||
let format_major = r.get_u16()?;
|
||||
let format_minor = r.get_u16()?;
|
||||
if format_major != FORMAT_MAJOR {
|
||||
return Err(BundleError::UnsupportedFormatVersion {
|
||||
major: format_major,
|
||||
minor: format_minor,
|
||||
});
|
||||
}
|
||||
// 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 {
|
||||
major: format_major,
|
||||
minor: format_minor,
|
||||
})
|
||||
}
|
||||
};
|
||||
let header_length = r.get_u32()?;
|
||||
if header_length != HEADER_LEN as u32 {
|
||||
return Err(BundleError::UnsupportedHeaderLength {
|
||||
|
|
@ -147,6 +188,7 @@ impl FixedHeader {
|
|||
superblock_a_offset,
|
||||
superblock_b_offset,
|
||||
file_uuid,
|
||||
epoch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -155,6 +197,14 @@ impl FixedHeader {
|
|||
mod tests {
|
||||
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]
|
||||
fn header_round_trips() {
|
||||
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]
|
||||
fn reserved_bytes_are_ignored_on_read() {
|
||||
// A future minor version may use reserved bytes; current readers must
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ pub use crc::crc32c;
|
|||
pub use epiphany_determinism::{ChunkId, ContentHash};
|
||||
pub use error::{BundleError, IntegrityAnomaly};
|
||||
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::{
|
||||
BlobId, DocumentId, ExtensionId, FileUuid, FrontierBytes, LineageId, ManifestId,
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@
|
|||
//! 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
|
||||
//! RAM. The corpus is moderate: 1,000 generated operation envelopes packed
|
||||
//! into operation blocks plus a canonical `MaterializedState` snapshot wired
|
||||
//! as the manifest's `canonical_base`. Both
|
||||
//! into operation blocks plus a canonical `MaterializedState` snapshot. That
|
||||
//! 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.
|
||||
//! 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
|
||||
|
|
@ -34,9 +37,9 @@
|
|||
//! envelope block, i.e. block append + manifest rewrite + superblock flip,
|
||||
//! every write fsync'd. This mirrors `bundle_harness`'s commit driver.
|
||||
//! * **open_bootstrap_read** — `FileStore::open` + `Bundle::open` (superblock
|
||||
//! selection + header + manifest decode) + reading the `canonical_base`
|
||||
//! snapshot and every operation block — the bytes a first interactive frame
|
||||
//! needs.
|
||||
//! selection + header + manifest decode) + reading the cold-reduction
|
||||
//! snapshot (by direct `ChunkRef`, see above) and every operation block —
|
||||
//! the bytes a first interactive frame needs.
|
||||
//!
|
||||
//! Criterion measures; the budget gate in `main` asserts (see
|
||||
//! `epiphany_testkit::budget` for the Pass/Xfail semantics and the documented
|
||||
|
|
@ -50,9 +53,8 @@ use std::time::Duration;
|
|||
|
||||
use criterion::{BatchSize, Criterion};
|
||||
use epiphany_bundle::{
|
||||
pack_operation_blocks, Bundle, ChunkKind, CommitContext, DocumentId, FileStore, FileUuid,
|
||||
FrontierBytes, Manifest, MemStore, ProfileId, ReductionAlgorithmVersion, SchemaVersion,
|
||||
SnapshotId, SnapshotRef, StagedChunk,
|
||||
pack_operation_blocks, Bundle, ChunkKind, ChunkRef, CommitContext, DocumentId, FileStore,
|
||||
FileUuid, Manifest, MemStore, SchemaVersion, StagedChunk,
|
||||
};
|
||||
use epiphany_determinism::CanonicalEncode;
|
||||
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.
|
||||
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>,
|
||||
/// 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).
|
||||
edit: Vec<StagedChunk>,
|
||||
/// 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
|
||||
/// blocks, then their cold reduction committed as a `Snapshot` chunk wired to
|
||||
/// the manifest's `canonical_base` (the roundtrip harness's snapshot shape).
|
||||
/// blocks, then their cold reduction committed as a `Snapshot` chunk — staged
|
||||
/// but not wired into the manifest (pin 3c; see `Fixture::base_root`'s doc
|
||||
/// comment).
|
||||
fn build_fixture(dir: &Path) -> Fixture {
|
||||
let mut rng = Rng::new(0x00F1_B0DE_0001);
|
||||
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,
|
||||
payload: canonical,
|
||||
};
|
||||
let frontier = generators::frontier_bytes(&envelopes);
|
||||
let mut base_root = None;
|
||||
bundle
|
||||
.commit(&[snapshot], |ctx| {
|
||||
let mut manifest = ctx.previous_manifest.clone();
|
||||
let root = ctx.new_chunks[0];
|
||||
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
|
||||
base_root = Some(ctx.new_chunks[0]);
|
||||
ctx.previous_manifest.clone()
|
||||
})
|
||||
.expect("commit canonical-base snapshot");
|
||||
.expect("commit cold-reduction snapshot");
|
||||
let base_root = base_root.expect("commit ran the build closure");
|
||||
|
||||
Fixture {
|
||||
base_image: bundle.into_store().into_bytes(),
|
||||
base_root,
|
||||
edit: staged_blocks(&edit_envelopes),
|
||||
commit_path: dir.join("commit.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
|
||||
/// bootstrap chunks — canonical-base snapshot and every operation block.
|
||||
fn open_bootstrap_read(path: &Path) -> usize {
|
||||
/// bootstrap chunks — the cold-reduction snapshot (read directly by its
|
||||
/// `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 manifest = bundle.manifest();
|
||||
let mut bytes = 0usize;
|
||||
let base = manifest
|
||||
.canonical_base
|
||||
.as_ref()
|
||||
.expect("the fixture wires a canonical base");
|
||||
bytes += bundle
|
||||
.read_chunk(&base.root)
|
||||
.read_chunk(base_root)
|
||||
.expect("snapshot chunk reads")
|
||||
.len();
|
||||
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");
|
||||
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();
|
||||
|
|
@ -223,7 +228,7 @@ fn budget_gate(fixture: &Fixture, quick: bool) -> Vec<budget::GateReport> {
|
|||
let read_median = budget::median_time(
|
||||
if quick { 8 } else { 25 },
|
||||
|| (),
|
||||
|()| open_bootstrap_read(&fixture.read_path),
|
||||
|()| open_bootstrap_read(&fixture.read_path, &fixture.base_root),
|
||||
);
|
||||
vec![
|
||||
budget::latency_gate(
|
||||
|
|
|
|||
|
|
@ -225,14 +225,28 @@ fn canonical_score_bytes(envelopes: &[OperationEnvelope]) -> Vec<u8> {
|
|||
/// (acceptance criterion 4): the operation
|
||||
/// 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 —
|
||||
/// stored as a `Snapshot` chunk referenced by the manifest's `canonical_base`
|
||||
/// (its correct semantic home), hash-verified on reopen and read back
|
||||
/// stored as a `Snapshot` chunk, hash-verified on reopen and read back
|
||||
/// byte-identically.
|
||||
///
|
||||
/// The snapshot's `covers_causal_frontier` is the frontier the snapshot actually
|
||||
/// materializes ([`crate::generators::frontier_bytes`] over the reduced
|
||||
/// envelopes), so it is semantically consistent — not a falsely-empty frontier
|
||||
/// that would invite a replay layer to reapply already-materialized effects.
|
||||
/// **Canonical-base wiring suspended** (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin
|
||||
/// 3c, amended 2026-08-07). The snapshot's correct semantic home is the
|
||||
/// manifest's `canonical_base` — that has not changed — but pin 3a's
|
||||
/// format-epoch matrix refuses every base introduction (`Bundle::commit`) and
|
||||
/// every base-bearing open (`Bundle::open`) during the S28 → P13-S27
|
||||
/// interval, in both format epochs. So this harness cannot wire the snapshot
|
||||
/// there right now: it stages the snapshot as an ordinary chunk instead and
|
||||
/// carries the resulting `ChunkRef` across the reopen out of band, reading it
|
||||
/// back directly via [`Bundle::read_chunk`] (which hash-verifies any
|
||||
/// `ChunkRef`, not only a canonical one). The
|
||||
/// serialize → load → decode → reserialize cycle below is unchanged; only the
|
||||
/// snapshot's manifest placement is suspended. **Not** re-homed to
|
||||
/// `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
|
||||
/// [`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"
|
||||
);
|
||||
|
||||
// serialize: stage the canonical state as a real **Snapshot** chunk and
|
||||
// reference it from the manifest's `canonical_base` — its correct semantic
|
||||
// home (a materialized snapshot), with the right chunk kind.
|
||||
// serialize: stage the canonical state as a real **Snapshot** chunk.
|
||||
// P13-S27 / CONTRACT_FORMAT_EPOCH_MAJOR1.md pin 3c: the manifest's
|
||||
// `canonical_base` is the snapshot's correct semantic home, but that
|
||||
// wiring is suspended for the S28 -> P13-S27 interval (see the doc
|
||||
// comment above) — the chunk is staged but not referenced from any
|
||||
// manifest root, and its `ChunkRef` is captured directly from the commit
|
||||
// closure instead.
|
||||
let mut rng = Rng::new(seed);
|
||||
let uuid = FileUuid(rng.array16());
|
||||
let doc = DocumentId(rng.array16());
|
||||
|
|
@ -261,42 +279,26 @@ pub fn assert_reduction_serialization_stable(envelopes: &[OperationEnvelope], se
|
|||
schema_version: SchemaVersion::V0,
|
||||
payload: canonical.clone(),
|
||||
};
|
||||
let mut snapshot_root = None;
|
||||
bundle
|
||||
.commit(&[snapshot], |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 {
|
||||
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
|
||||
snapshot_root = Some(ctx.new_chunks[0]);
|
||||
ctx.previous_manifest.clone()
|
||||
})
|
||||
.expect("commit snapshot");
|
||||
let snapshot_root = snapshot_root.expect("commit ran the build closure");
|
||||
let image = bundle.into_store().into_bytes();
|
||||
|
||||
// load: reopen from exactly those bytes; the snapshot chunk is hash-verified
|
||||
// on open and read back byte-identically.
|
||||
// load: reopen from exactly those bytes; the snapshot chunk is read back
|
||||
// directly by its `ChunkRef` (not through `manifest.canonical_base`,
|
||||
// suspended above) — `read_chunk` hash-verifies any `ChunkRef`, so the
|
||||
// cycle's integrity guarantee is unchanged.
|
||||
let reopened = Bundle::open(MemStore::from_bytes(image)).expect("reopen bundle");
|
||||
reopened
|
||||
.verify_canonical_chunks()
|
||||
.expect("canonical chunks intact");
|
||||
let base = reopened
|
||||
.manifest()
|
||||
.canonical_base
|
||||
.as_ref()
|
||||
.expect("a canonical base");
|
||||
let loaded = reopened
|
||||
.read_chunk(&base.root)
|
||||
.read_chunk(&snapshot_root)
|
||||
.expect("read snapshot chunk back");
|
||||
assert_eq!(
|
||||
loaded, canonical,
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ use std::collections::{BTreeMap, BTreeSet};
|
|||
use std::fs;
|
||||
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:
|
||||
// spec/PLAN_GMINOR_SCHEMA_MINOR.md).
|
||||
const SUITE_REQUIREMENT_COUNT: usize = 283;
|
||||
const SUITE_LABEL_COUNT: usize = 283;
|
||||
// spec/PLAN_GMINOR_SCHEMA_MINOR.md); +1 for req:format:container-epoch (above).
|
||||
const SUITE_REQUIREMENT_COUNT: usize = 284;
|
||||
const SUITE_LABEL_COUNT: usize = 284;
|
||||
|
||||
/// The normative chapter-to-area assignment. Keeping this as data makes adding a
|
||||
/// requirement under the wrong chapter fail without encoding chapter names in
|
||||
|
|
|
|||
|
|
@ -56,7 +56,18 @@ use epiphany_ops::OperationEnvelope;
|
|||
/// and `create-view` to the `kind` production — the same reasoning as every
|
||||
/// prior kind append: extending the grammar without moving this constant
|
||||
/// 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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -136,6 +136,18 @@ pub fn parse_document(text: &str) -> Result<TextDocument, TextError> {
|
|||
|
||||
// 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
|
||||
// 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 →
|
||||
// 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
|
||||
// would ripple through every call site;
|
||||
// `the_test_header_tracks_the_implemented_version` below fails loudly if
|
||||
// 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))";
|
||||
|
||||
/// A minimal but complete valid projection: just the two mandatory lines.
|
||||
|
|
@ -757,28 +770,37 @@ mod tests {
|
|||
.retain_named_checkpoints
|
||||
);
|
||||
assert!(document.extensions.is_empty());
|
||||
|
||||
let base = document
|
||||
.canonical_base
|
||||
.as_ref()
|
||||
.expect("the worked example carries a canonical base");
|
||||
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]);
|
||||
// Pin 3b (`CONTRACT_FORMAT_EPOCH_MAJOR1.md`): the worked example no
|
||||
// longer carries a canonical base — the companion's own chapter
|
||||
// retired that spelling to a second, explicitly-refused listing
|
||||
// (`spec/text_projection.tex`, "A Worked Example").
|
||||
assert!(document.canonical_base.is_none());
|
||||
|
||||
assert!(document.blobs.is_empty());
|
||||
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
|
||||
// extension with preserved chunks, a canonical base, and multiple
|
||||
|
|
@ -794,8 +816,11 @@ mod tests {
|
|||
let extension = "(extension #x00000000000000000000000000000003 (1 0 0) true \
|
||||
((chunk operation-envelope-block (schema 0 1) #xaa) (chunk snapshot (schema 0 1) #xbb)) \
|
||||
#xaabb #xccdd)";
|
||||
let base =
|
||||
"(canonical-base #x00000000000000000000000000000004 #x 1 full (schema 0 1) #x0102)";
|
||||
// Pin 3b (`CONTRACT_FORMAT_EPOCH_MAJOR1.md`): this document used to
|
||||
// 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 e2 = sample_envelope(2, 1, 20);
|
||||
|
|
@ -809,7 +834,6 @@ mod tests {
|
|||
profile_full,
|
||||
profile_read_only,
|
||||
extension,
|
||||
base,
|
||||
&e1_line,
|
||||
&e2_line,
|
||||
]);
|
||||
|
|
@ -832,7 +856,7 @@ mod tests {
|
|||
vec![0xaa, 0xbb]
|
||||
);
|
||||
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]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use epiphany_bundle::{
|
||||
BlobId, BlockStore, Bundle, BundleError, ChunkKind, DocumentId, LineageId, ProfileConstraints,
|
||||
ProfileDeclaration, ProfileId, RetentionPolicy, SchemaVersion, SemVer,
|
||||
BlobId, BlockStore, Bundle, BundleError, ChunkKind, DocumentId, LineageId, Manifest,
|
||||
ProfileConstraints, ProfileDeclaration, ProfileId, RetentionPolicy, SchemaVersion, SemVer,
|
||||
};
|
||||
use epiphany_core::textvalue::Sexp;
|
||||
use epiphany_ops::{
|
||||
|
|
@ -67,6 +67,15 @@ pub enum ProjectError {
|
|||
/// A stored operation-envelope block held bytes that do not decode to a
|
||||
/// canonical [`OperationEnvelope`].
|
||||
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 {
|
||||
|
|
@ -74,6 +83,10 @@ impl core::fmt::Display for ProjectError {
|
|||
match self {
|
||||
ProjectError::Bundle(e) => write!(f, "bundle read failed: {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 {
|
||||
ProjectError::Bundle(e) => Some(e),
|
||||
ProjectError::Envelope(e) => Some(e),
|
||||
ProjectError::CanonicalBaseUnsupported => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -425,6 +439,33 @@ fn canonical_blobs<S: BlockStore>(
|
|||
// 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
|
||||
/// chunk and blob payload the projection carries inline: each extension's
|
||||
/// 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>,
|
||||
) -> Result<TextDocument, ProjectError> {
|
||||
let manifest = bundle.manifest();
|
||||
reject_canonical_base(manifest)?;
|
||||
|
||||
let mut decoded = Vec::new();
|
||||
for root in &manifest.operation_roots {
|
||||
|
|
@ -563,9 +605,9 @@ mod tests {
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use epiphany_bundle::{
|
||||
encode_block, BlobRef, CompressionAlgorithm, ExtensionDeclaration, ExtensionId, FileUuid,
|
||||
FrontierBytes, Manifest, MemStore, ProfileRegistryId, ReductionAlgorithmVersion,
|
||||
SnapshotId, SnapshotRef, StagedChunk,
|
||||
chunk_content_hash, encode_block, BlobRef, ChunkRef, CompressionAlgorithm,
|
||||
ExtensionDeclaration, ExtensionId, FileUuid, FrontierBytes, Manifest, MemStore,
|
||||
ProfileRegistryId, ReductionAlgorithmVersion, SnapshotId, SnapshotRef, StagedChunk,
|
||||
};
|
||||
use epiphany_core::textvalue::read_sexp;
|
||||
use epiphany_core::{OperationId, RegionId, ReplicaId, WallClockTime};
|
||||
|
|
@ -896,12 +938,12 @@ mod tests {
|
|||
payload: b"extension-chunk-payload".to_vec(),
|
||||
};
|
||||
|
||||
let base_chunk = StagedChunk {
|
||||
kind: ChunkKind::Snapshot,
|
||||
schema_version: SchemaVersion::V0,
|
||||
payload: b"canonical-base-payload".to_vec(),
|
||||
};
|
||||
|
||||
// `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3c: no bundle anywhere may
|
||||
// carry a canonical base for the S28 -> P13-S27 interval — `commit`
|
||||
// now refuses one outright, so this fixture used to stage a
|
||||
// `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 {
|
||||
kind: ChunkKind::Blob,
|
||||
schema_version: SchemaVersion::V0,
|
||||
|
|
@ -915,13 +957,7 @@ mod tests {
|
|||
|
||||
bundle
|
||||
.commit(
|
||||
&[
|
||||
op_block_0,
|
||||
op_block_1,
|
||||
extension_chunk,
|
||||
base_chunk,
|
||||
blob_chunk,
|
||||
],
|
||||
&[op_block_0, op_block_1, extension_chunk, blob_chunk],
|
||||
|ctx| {
|
||||
let mut manifest = ctx.previous_manifest.clone();
|
||||
manifest.operation_roots = vec![ctx.new_chunks[0], ctx.new_chunks[1]];
|
||||
|
|
@ -933,22 +969,14 @@ mod tests {
|
|||
affected_object_kinds: vec![0xAA],
|
||||
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 {
|
||||
blob_id: BlobId(ctx.new_chunks[4].hash),
|
||||
blob_id: BlobId(ctx.new_chunks[3].hash),
|
||||
media_type: "application/octet-stream".to_owned(),
|
||||
offset: ctx.new_chunks[4].offset,
|
||||
compressed_length: ctx.new_chunks[4].compressed_length,
|
||||
uncompressed_length: ctx.new_chunks[4].uncompressed_length,
|
||||
offset: ctx.new_chunks[3].offset,
|
||||
compressed_length: ctx.new_chunks[3].compressed_length,
|
||||
uncompressed_length: ctx.new_chunks[3].uncompressed_length,
|
||||
compression: CompressionAlgorithm::None,
|
||||
hash: ctx.new_chunks[4].hash,
|
||||
hash: ctx.new_chunks[3].hash,
|
||||
declared_max_uncompressed_length: None,
|
||||
}];
|
||||
manifest
|
||||
|
|
@ -995,9 +1023,9 @@ mod tests {
|
|||
assert_eq!(extension.chunks[0].kind, ChunkKind::ExtensionData);
|
||||
assert_eq!(extension.chunks[0].payload, b"extension-chunk-payload");
|
||||
|
||||
let base = document.canonical_base.as_ref().expect("base was staged");
|
||||
assert_eq!(base.snapshot_id, SnapshotId([7; 16]));
|
||||
assert_eq!(base.root_payload, b"canonical-base-payload");
|
||||
// `build_sample_bundle` no longer stages a canonical base (pin 3c: no
|
||||
// bundle anywhere may carry one during the S28 -> P13-S27 interval).
|
||||
assert!(document.canonical_base.is_none());
|
||||
|
||||
// The trap: a non-empty `blob_roots` still yields zero canonical
|
||||
// blobs, because nothing can reach one yet.
|
||||
|
|
@ -1041,7 +1069,6 @@ mod tests {
|
|||
"(lineage",
|
||||
"(profile",
|
||||
"(extension",
|
||||
"(canonical-base",
|
||||
"(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
|
||||
// canonical base, and a multi-envelope document.
|
||||
|
|
@ -1103,13 +1178,27 @@ mod tests {
|
|||
#[test]
|
||||
fn test_suite_reach_covers_extension_base_and_multi_envelope_documents() {
|
||||
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 {
|
||||
document_id: DocumentId([6; 16]),
|
||||
manifest_schema_version: SchemaVersion::V0,
|
||||
lineage_id: None,
|
||||
profiles: vec![ProfileDeclaration::full()],
|
||||
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(),
|
||||
envelopes: vec![sample_envelope(1, 1)],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,6 +70,21 @@ pub enum SerializeError {
|
|||
/// non-canonical root into the manifest or silently drop the caller's
|
||||
/// payload bytes. Neither is acceptable, so serialization refuses instead.
|
||||
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
|
||||
/// document declares no profile this implementation understands, or a
|
||||
/// staged root fails `Bundle::commit`'s structural validation.
|
||||
|
|
@ -82,6 +97,10 @@ impl fmt::Display for SerializeError {
|
|||
SerializeError::NonEmptyBlobs => f.write_str(
|
||||
"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) => {
|
||||
write!(f, "the bundle rejected the serialized document: {error}")
|
||||
}
|
||||
|
|
@ -94,6 +113,7 @@ impl std::error::Error for SerializeError {
|
|||
match self {
|
||||
SerializeError::Bundle(error) => Some(error),
|
||||
SerializeError::NonEmptyBlobs => None,
|
||||
SerializeError::CanonicalBaseUnsupported => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,8 +134,12 @@ impl From<BundleError> for SerializeError {
|
|||
/// [`ChunkRef`](epiphany_bundle::ChunkRef)s that commit assigns.
|
||||
///
|
||||
/// Returns [`SerializeError::NonEmptyBlobs`] if `document.blobs` is non-empty
|
||||
/// (see the module documentation), or [`SerializeError::Bundle`] if the bundle
|
||||
/// itself refuses the manifest or a staged root.
|
||||
/// (see the module documentation), [`SerializeError::CanonicalBaseUnsupported`]
|
||||
/// 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>(
|
||||
document: &TextDocument,
|
||||
store: S,
|
||||
|
|
@ -124,6 +148,9 @@ pub fn serialize_document<S: BlockStore>(
|
|||
if !document.blobs.is_empty() {
|
||||
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))?;
|
||||
|
||||
|
|
@ -335,11 +362,16 @@ mod tests {
|
|||
document
|
||||
}
|
||||
|
||||
/// Carries an extension, a canonical base, and many envelopes at once — the
|
||||
/// document every round-trip law needs to be checked against together.
|
||||
/// Carries an extension and many envelopes at once — the document every
|
||||
/// 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 {
|
||||
let mut document = document_with_extension(seed);
|
||||
document.canonical_base = document_with_canonical_base(seed).canonical_base;
|
||||
document.envelopes = envelopes(seed, 60);
|
||||
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]
|
||||
fn document_identity_round_trips() {
|
||||
let mut document = minimal_document(1);
|
||||
|
|
@ -393,31 +437,23 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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 reopened = serialize_and_reopen(&document);
|
||||
let expected = document
|
||||
.canonical_base
|
||||
.as_ref()
|
||||
.expect("fixture carries a canonical base");
|
||||
let base = reopened
|
||||
.manifest()
|
||||
.canonical_base
|
||||
.as_ref()
|
||||
.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);
|
||||
let result = serialize_document(&document, MemStore::new(), FileUuid([1; 16]));
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(SerializeError::CanonicalBaseUnsupported)
|
||||
));
|
||||
// M8 depends on this distinction: the dedicated variant, never a
|
||||
// `SerializeError::Bundle` passthrough from the container-layer
|
||||
// refusal this would otherwise fall through to.
|
||||
assert!(!matches!(result, Err(SerializeError::Bundle(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -533,7 +569,9 @@ mod tests {
|
|||
let document = rich_document(10);
|
||||
let reopened = serialize_and_reopen(&document);
|
||||
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().operation_roots.len(), 1);
|
||||
reopened
|
||||
|
|
@ -579,6 +617,17 @@ mod tests {
|
|||
);
|
||||
|
||||
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);
|
||||
assert_eq!(reopened.manifest().document_id, document.document_id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,16 +123,26 @@ pub struct ReachCounts {
|
|||
|
||||
/// The exact non-vacuity contract of this corpus: four accepted documents, two
|
||||
/// 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 {
|
||||
ReachCounts {
|
||||
extensions: 2,
|
||||
canonical_bases: 2,
|
||||
canonical_bases: 0,
|
||||
custom_profiles: 2,
|
||||
lineages: 2,
|
||||
multi_envelope: 2,
|
||||
reject_classes: [
|
||||
("blob-line", 1),
|
||||
("canonical-base-unsupported", 1),
|
||||
("extension-chunk-order", 1),
|
||||
("extension-declaration-order", 1),
|
||||
("missing-trailing-lf", 1),
|
||||
|
|
@ -341,7 +351,12 @@ fn accept_documents() -> Vec<(&'static str, String)> {
|
|||
blobs: Vec::new(),
|
||||
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]),
|
||||
// A non-baseline carried version, so the corpus exercises 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,
|
||||
profiles: profiles(false),
|
||||
extensions: vec![extension(1, &[1, 2])],
|
||||
canonical_base: Some(base(3)),
|
||||
canonical_base: None,
|
||||
blobs: Vec::new(),
|
||||
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 {
|
||||
document_id: DocumentId([4; 16]),
|
||||
manifest_schema_version: SchemaVersion::V0,
|
||||
lineage_id: Some(LineageId([0x14; 16])),
|
||||
profiles: profiles(true),
|
||||
extensions: vec![extension(1, &[1, 2]), extension(2, &[3])],
|
||||
canonical_base: Some(base(4)),
|
||||
canonical_base: None,
|
||||
blobs: Vec::new(),
|
||||
envelopes: vec![
|
||||
sample_envelope(4, 400),
|
||||
|
|
@ -459,7 +476,7 @@ fn accept_documents() -> Vec<(&'static str, String)> {
|
|||
),
|
||||
(
|
||||
"extension_base_two_envelopes",
|
||||
project_text_document(&extension_base_multi),
|
||||
project_text_document(&extension_two_envelopes),
|
||||
),
|
||||
("rich_document", project_text_document(&rich)),
|
||||
("create_staff_group", project_text_document(&staff_group)),
|
||||
|
|
@ -548,7 +565,7 @@ pub fn document_vectors() -> Vec<TextVector> {
|
|||
};
|
||||
let minimal = by_name("minimal");
|
||||
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 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()))
|
||||
.collect();
|
||||
|
||||
// The rejected version must be one this crate does NOT implement. Genesis
|
||||
// tranche G3b moved `COMPANION_VERSION` to 0.13.0, which had been this
|
||||
// vector's "future" version — leaving it would have made the negative
|
||||
// vector assert that the *correct* header is rejected. It now names
|
||||
// 0.12.0, the immediately superseded companion, which is the better test
|
||||
// anyway: rejecting the version right behind you is exactly the deferred
|
||||
// The rejected version must be one this crate does NOT implement.
|
||||
// `CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3b moved `COMPANION_VERSION` to
|
||||
// 0.14.0; this vector now names 0.13.0, the immediately superseded
|
||||
// companion (previously 0.12.0, when the committed version was 0.13.0) —
|
||||
// rejecting the version right behind you is exactly the deferred
|
||||
// migrate-on-read posture (`req:textproj:header-version`).
|
||||
let wrong_version = replace_once(
|
||||
minimal,
|
||||
"(text-projection (0 14 0))",
|
||||
"(text-projection (0 13 0))",
|
||||
"(text-projection (0 12 0))",
|
||||
);
|
||||
vectors.push((
|
||||
SURFACE,
|
||||
|
|
@ -590,12 +606,18 @@ pub fn document_vectors() -> Vec<TextVector> {
|
|||
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((
|
||||
SURFACE,
|
||||
"reject",
|
||||
"out-of-order-sections",
|
||||
"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((
|
||||
|
|
@ -611,7 +633,7 @@ pub fn document_vectors() -> Vec<TextVector> {
|
|||
"reject",
|
||||
"operation-envelope-order",
|
||||
"envelopes_reversed",
|
||||
swap_first_two_lines(extension_base_multi, "(envelope ").into_bytes(),
|
||||
swap_first_two_lines(extension_two_envelopes, "(envelope ").into_bytes(),
|
||||
));
|
||||
|
||||
vectors.push((
|
||||
|
|
@ -631,7 +653,7 @@ pub fn document_vectors() -> Vec<TextVector> {
|
|||
));
|
||||
|
||||
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) #x02) (chunk extension-data (schema 0 1) #x01))",
|
||||
);
|
||||
|
|
@ -653,6 +675,28 @@ pub fn document_vectors() -> Vec<TextVector> {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
@ -886,7 +930,7 @@ mod tests {
|
|||
#[test]
|
||||
fn the_reference_implementation_agrees_with_every_vector() {
|
||||
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!(
|
||||
"{} disagreement(s):\n{}",
|
||||
failures.len(),
|
||||
|
|
@ -957,20 +1001,21 @@ mod tests {
|
|||
}
|
||||
|
||||
/// (t12) Genesis tranche G3b: text projection round-trips the new
|
||||
/// `create-measure` kind, and the companion version is **0.13.0**, with
|
||||
/// the negative vector rejecting **0.12.0** (the immediately superseded
|
||||
/// `create-measure` kind, and the companion version is **0.14.0**
|
||||
/// (`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).
|
||||
///
|
||||
/// **Mutation:** drop `OperationKindTag::CreateMeasure` from
|
||||
/// `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.
|
||||
#[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!(
|
||||
crate::COMPANION_VERSION,
|
||||
(0, 13, 0),
|
||||
"the companion version must be 0.13.0"
|
||||
(0, 14, 0),
|
||||
"the companion version must be 0.14.0"
|
||||
);
|
||||
|
||||
let name = "create_measure";
|
||||
|
|
@ -988,7 +1033,7 @@ mod tests {
|
|||
);
|
||||
|
||||
// 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 superseded = rows
|
||||
.iter()
|
||||
|
|
@ -997,8 +1042,8 @@ mod tests {
|
|||
assert_eq!(superseded.verdict, "reject");
|
||||
let text = String::from_utf8(superseded.text.clone()).expect("utf8");
|
||||
assert!(
|
||||
text.contains("(text-projection (0 12 0))"),
|
||||
"the negative vector must name the immediately superseded companion 0.12.0, got: {text}"
|
||||
text.contains("(text-projection (0 13 0))"),
|
||||
"the negative vector must name the immediately superseded companion 0.13.0, got: {text}"
|
||||
);
|
||||
assert!(
|
||||
parse_document(&text).is_err(),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
# Contract — P13-S27: the reduction version gets an outside witness
|
||||
|
||||
**Status:** DRAFT — **BLOCKED on P13-S28** (the format-epoch rung). Pins 1 and
|
||||
3–10 are settled, internally consistent, and ratifiable as a plan. **Pin 2a is
|
||||
an open question this contract states rather than answers**, and it is not
|
||||
answerable inside this rung: the provenance carrier it needs is a format-epoch
|
||||
design, which P13-S28 owns.
|
||||
**Status:** DRAFT — **BLOCKED on the format-epoch rung**
|
||||
(`spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`), which is **ratified and in
|
||||
implementation** but has not yet landed. Pins 1 and 3–10 are settled, internally
|
||||
consistent, and ratifiable as a plan.
|
||||
|
||||
**This is bounded analysis, not an abandoned implementation plan.** It may not be
|
||||
dispatched. Pin 2a MUST NOT be treated as a sub-pin that drifts into this rung's
|
||||
scope — see its own prohibition.
|
||||
**Pin 2a is RESOLVED as of 2026-08-07** — from outside this rung, by that
|
||||
contract's pin 8, exactly as its prohibition required. Legacy bases are refused
|
||||
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
|
||||
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
|
||||
call sites.
|
||||
|
||||
**Does NOT by itself unblock P13-S16.** This rung installs the authority and
|
||||
validates both read and write paths. S16 additionally requires the **legacy-base
|
||||
disposition of pin 2a**, which is an open question this contract states rather
|
||||
than answers.
|
||||
**Now DOES unblock P13-S16, once it lands.** This rung installs the authority and
|
||||
validates both read and write paths; S16's remaining precondition was pin 2a's
|
||||
legacy-base disposition, and that is resolved. The chain is therefore
|
||||
format-epoch rung → **P13-S27** → **P13-S16**, with no open question left in it.
|
||||
|
||||
**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
|
||||
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
|
||||
`Bundle`.**
|
||||
|
||||
|
|
@ -404,10 +479,27 @@ Named, permanent, in `epiphany-bundle`:
|
|||
intact. A writer check that corrupts the document while refusing is worse than
|
||||
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
|
||||
*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.
|
||||
|
||||
Tests 6 and 2 stand in the same relation to each other.
|
||||
|
||||
---
|
||||
|
||||
## §4. Mutation plan
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -240,7 +240,7 @@
|
|||
{\Large\scshape\color{epiphanyslate}Binary Format}\\[6pt]
|
||||
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
|
||||
{\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}
|
||||
\vfill
|
||||
\end{titlepage}
|
||||
|
|
@ -1805,10 +1805,18 @@ format major version. All integers little-endian.
|
|||
\endhead
|
||||
\tablenums{0..8} & magic & \texttt{MUSCBND\textbackslash0} (8 bytes, ASCII,
|
||||
trailing NUL) \\
|
||||
\tablenums{8..10} & \texttt{format\_major} (\texttt{u16}) & \tablenums{0}
|
||||
in this version; readers reject other values \\
|
||||
\tablenums{10..12} & \texttt{format\_minor} (\texttt{u16}) & \tablenums{1}
|
||||
written in this version \\
|
||||
\tablenums{8..10} & \texttt{format\_major} (\texttt{u16}) & \tablenums{1}
|
||||
written in this version. The container \emph{epoch}: a major-1
|
||||
container is one whose every base-bearing commit was validated
|
||||
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{64}; readers reject other values \\
|
||||
\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
|
||||
$\rightarrow$ G3a $\rightarrow$ G3b. Semantics: Operation Catalog
|
||||
\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
|
||||
\end{longtable}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -10868,6 +10868,65 @@ pub struct FixedHeader {
|
|||
\texttt{file\_uuid}.
|
||||
\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}
|
||||
|
||||
Each superblock slot is exactly 256 bytes. The bundle has two slots
|
||||
|
|
@ -12229,6 +12288,18 @@ pub struct SchemaVersion {
|
|||
Schema versioning rules:
|
||||
|
||||
\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.
|
||||
Readers that do not support the chunk's major schema
|
||||
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
|
||||
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
|
||||
\end{longtable}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -234,7 +234,7 @@
|
|||
{\Large\scshape\color{epiphanyslate}Text Projection}\\[6pt]
|
||||
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
|
||||
{\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}
|
||||
\vfill
|
||||
\end{titlepage}
|
||||
|
|
@ -483,7 +483,7 @@ A projection is, in order:
|
|||
\begin{requirement}
|
||||
\label{req:textproj:header-version}
|
||||
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.
|
||||
|
||||
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
|
||||
unreferenced by canonical state
|
||||
(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,
|
||||
and a parser \MUST{} reject every \texttt{(blob ...)} line.
|
||||
\end{requirement}
|
||||
|
|
@ -906,6 +906,31 @@ are ratified.
|
|||
whose canonical document semantics are identical to the original's. The
|
||||
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$,
|
||||
\[
|
||||
\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.
|
||||
|
||||
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}
|
||||
(text-projection (0 13 0))
|
||||
(text-projection (0 14 0))
|
||||
(document #x05050505050505050505050505050505 (schema 0 1))
|
||||
(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))))
|
||||
\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
|
||||
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}
|
||||
\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
|
||||
\texttt{(0 12 0)} do not migrate; a stale \texttt{TextProjection} chunk is
|
||||
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
|
||||
\end{longtable}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,22 +22,23 @@
|
|||
# document bytes are normative. `<utf8-hex>` is lowercase with no separators.
|
||||
|
||||
# textproj.document
|
||||
textproj.document accept - minimal 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
|
||||
textproj.document accept - set_tuning_context 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830353035303530353035303530353035303530353035303530353035303530352028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303037202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203730302030202378303030303030303030303030303030313030303030303030303030303030303729202863617573616c2028292028292920282920287072696d697469766520287365742d74756e696e672d636f6e74657874202874756e696e672d636f6e746578742d73657474696e67732022636d6e2d31322220227465742d31322220287265666572656e63652d70697463682028636d6e2061203020342920237830303030303030303030663037623430292028736d75666c2d76657273696f6e2d726571756972656d656e742028736d75666c2d76657273696f6e2031203430292028736d75666c2d76657273696f6e20312034302929202829292929290a
|
||||
textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document accept - rich_document 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
|
||||
textproj.document accept - create_staff_group 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830363036303630363036303630363036303630363036303630363036303630362028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303038202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203830302030202378303030303030303030303030303030313030303030303030303030303030303829202863617573616c2028292028292920282920287072696d697469766520286372656174652d73746166662d67726f7570202873746166662d67726f757020237830303030303030303030303030303031303030303030303030303030303030312028736f6d65202273746166662d67726f75702d312229206772616e642d737461666620282378303030303030303030303030303030313030303030303030303030303030303129292929290a
|
||||
textproj.document accept - create_part_definition 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830373037303730373037303730373037303730373037303730373037303730372028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303039202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203930302030202378303030303030303030303030303030313030303030303030303030303030303929202863617573616c2028292028292920282920287072696d697469766520286372656174652d706172742d646566696e6974696f6e2028706172742d646566696e6974696f6e20237830303030303030303030303030303031303030303030303030303030303030312022706172742d312220282378303030303030303030303030303030313030303030303030303030303030303129292929290a
|
||||
textproj.document accept - create_analysis_layer 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830383038303830383038303830383038303830383038303830383038303830382028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303061202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313030302030202378303030303030303030303030303030313030303030303030303030303030306129202863617573616c2028292028292920282920287072696d697469766520286372656174652d616e616c797369732d6c617965722028616e616c797369732d6c61796572202378303030303030303030303030303030313030303030303030303030303030303120226c617965722d3122292929290a
|
||||
textproj.document accept - create_view 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830393039303930393039303930393039303930393039303930393039303930392028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303062202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313130302030202378303030303030303030303030303030313030303030303030303030303030306229202863617573616c2028292028292920282920287072696d697469766520286372656174652d766965772028766965772d646566696e6974696f6e20237830303030303030303030303030303031303030303030303030303030303030312022766965772d312220282378303030303030303030303030303030313030303030303030303030303030303129292929290a
|
||||
textproj.document accept - create_measure 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830613061306130613061306130613061306130613061306130613061306130612028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303063202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313230302030202378303030303030303030303030303030313030303030303030303030303030306329202863617573616c2028292028292920282920287072696d697469766520286372656174652d6d656173757265202378303030303030303030303030303030313030303030303030303030303030303120286d6561737572652023783030303030303030303030303030303130303030303030303030303030303031202877616c6c2d636c6f636b2031303030303030303030292028736f6d652023783030303030303030303030303030303130303030303030303030303030303031292028736f6d65203129206175746f292929290a
|
||||
textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e202830203132203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
|
||||
textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a
|
||||
textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a
|
||||
textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
|
||||
textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929
|
||||
textproj.document accept - minimal 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
|
||||
textproj.document accept - set_tuning_context 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830353035303530353035303530353035303530353035303530353035303530352028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303037202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203730302030202378303030303030303030303030303030313030303030303030303030303030303729202863617573616c2028292028292920282920287072696d697469766520287365742d74756e696e672d636f6e74657874202874756e696e672d636f6e746578742d73657474696e67732022636d6e2d31322220227465742d31322220287265666572656e63652d70697463682028636d6e2061203020342920237830303030303030303030663037623430292028736d75666c2d76657273696f6e2d726571756972656d656e742028736d75666c2d76657273696f6e2031203430292028736d75666c2d76657273696f6e20312034302929202829292929290a
|
||||
textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document accept - rich_document 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
|
||||
textproj.document accept - create_staff_group 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830363036303630363036303630363036303630363036303630363036303630362028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303038202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203830302030202378303030303030303030303030303030313030303030303030303030303030303829202863617573616c2028292028292920282920287072696d697469766520286372656174652d73746166662d67726f7570202873746166662d67726f757020237830303030303030303030303030303031303030303030303030303030303030312028736f6d65202273746166662d67726f75702d312229206772616e642d737461666620282378303030303030303030303030303030313030303030303030303030303030303129292929290a
|
||||
textproj.document accept - create_part_definition 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830373037303730373037303730373037303730373037303730373037303730372028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303039202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203930302030202378303030303030303030303030303030313030303030303030303030303030303929202863617573616c2028292028292920282920287072696d697469766520286372656174652d706172742d646566696e6974696f6e2028706172742d646566696e6974696f6e20237830303030303030303030303030303031303030303030303030303030303030312022706172742d312220282378303030303030303030303030303030313030303030303030303030303030303129292929290a
|
||||
textproj.document accept - create_analysis_layer 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830383038303830383038303830383038303830383038303830383038303830382028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303061202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313030302030202378303030303030303030303030303030313030303030303030303030303030306129202863617573616c2028292028292920282920287072696d697469766520286372656174652d616e616c797369732d6c617965722028616e616c797369732d6c61796572202378303030303030303030303030303030313030303030303030303030303030303120226c617965722d3122292929290a
|
||||
textproj.document accept - create_view 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830393039303930393039303930393039303930393039303930393039303930392028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303062202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313130302030202378303030303030303030303030303030313030303030303030303030303030306229202863617573616c2028292028292920282920287072696d697469766520286372656174652d766965772028766965772d646566696e6974696f6e20237830303030303030303030303030303031303030303030303030303030303030312022766965772d312220282378303030303030303030303030303030313030303030303030303030303030303129292929290a
|
||||
textproj.document accept - create_measure 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830613061306130613061306130613061306130613061306130613061306130612028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303063202378303030303030303030303030303030303030303030303030303030303030616220287374616d7020313230302030202378303030303030303030303030303030313030303030303030303030303030306329202863617573616c2028292028292920282920287072696d697469766520286372656174652d6d656173757265202378303030303030303030303030303030313030303030303030303030303030303120286d6561737572652023783030303030303030303030303030303130303030303030303030303030303031202877616c6c2d636c6f636b2031303030303030303030292028736f6d652023783030303030303030303030303030303130303030303030303030303030303031292028736f6d65203129206175746f292929290a
|
||||
textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e202830203133203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
|
||||
textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a
|
||||
textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a
|
||||
textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
|
||||
textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
|
||||
textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
|
||||
textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929
|
||||
textproj.document reject canonical-base-unsupported canonical_base_present 28746578742d70726f6a656374696f6e202830203134203029290a28646f63756d656e7420237830623062306230623062306230623062306230623062306230623062306230622028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378306230623062306230623062306230623062306230623062306230623062306220237830313032306220312066756c6c2028736368656d61203020312920237861303062290a
|
||||
|
|
|
|||
Loading…
Reference in New Issue