G-minor: the chunk schema minor becomes a derived record

Implements the schema-minor MUST (binary_format.tex, Schema Versioning) that no
writer had ever honoured: a writer raises the chunk minor when it emits a
discriminant appended after the minor it otherwise declares, so an
unknown-discriminant decode failure is attributable to version skew rather than
corruption.

introduced_minor() lands on the five vocabularies with post-baseline variants -
OperationKind, OperationKindTag, OperationPayload, ReanchorReason, and
PreconditionFailureReason - each exhaustive with no wildcard arm, so a future
variant cannot compile without being assigned an epoch. The tag epochs live
inside operation_kind_tag_vocabulary! rather than beside it, because a sibling
match is the parallel list that macro exists to prevent. The sentinel is
Option<u16>, not 0, since 0 is a real baseline minor for V1-V3 and conflating
them would make the max read correctly only by accident.

An envelope's required minor is the max over every discriminant it actually
emits; a block's is the max over its envelopes; major and minor derive
independently. Baselines are not normalised - V0 keeps minor 1.

The manifest seam keeps epiphany-bundle opaque: no ops or layout-ir dependency,
and the aggregate version is supplied by the producer rather than derived, with
CommitContext carrying the previous one so unchanged barrier content preserves
it. The version rides the superblock slot that already exists; Manifest gains no
field, which would have been schema-major and would have defeated the rung.
bundle.rs's superblock check stays major-only - tightening it to full-version
equality is a conformance regression, and s11 locks that.

textproj carries the manifest SchemaVersion and never derives it, so
COMPANION_VERSION moves 0.9.0 to 0.10.0 with the corpus regenerated. Not because
of op-block stamping, which remains projection-invisible. A new normative
requirement records the carry-never-derive rule in the companion itself; its
rationale names layout-ir, which textproj genuinely lacks, rather than the
operation vocabulary, which it has.

Gate [7f] adds an independent oracle over decodable in-tree barrier fixtures,
requiring exact equality rather than >=. Equality is load-bearing: >= catches
under-stamping but not stale over-stamping after the sole maximum contributor is
removed. Undecodable blobs are reported not-checkable, never as a pass.

Also repairs binary_format.tex's stale claim that OperationKind and
OperationKindTag append at 30 with a history stopping at 29, while 30-33 are
taken and the normative tables already carry them.

Gate: 1399 tests, clippy 0, fmt clean, conformance 8/8 with [7f] at four
fixtures checked and one not-checkable, 102 decode vectors byte-identical
(verified, not assumed), 13 text-projection vectors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
This commit is contained in:
Levi Neuwirth 2026-07-28 21:04:34 -04:00
parent 496dfd5640
commit ff9bd0fd06
26 changed files with 1522 additions and 143 deletions

View File

@ -158,6 +158,15 @@ pub struct CommitContext<'a> {
pub new_chunks: &'a [ChunkRef],
/// The generation the new manifest must declare (active + 1).
pub generation: u64,
/// The schema version the *previous* manifest was stamped with (G-minor,
/// `spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 6.2): new plumbing, not a
/// read-through — [`CommitContext::previous_manifest`] is a `&Manifest`,
/// and [`Manifest`] carries no schema-version field at all (it lives in
/// the superblock, `Superblock::manifest_schema_version`). A build
/// closure whose commit preserves the complete barrier content unchanged
/// reads this to preserve the carried version exactly (pin 6.6), rather
/// than recomputing it from scratch.
pub previous_manifest_version: SchemaVersion,
}
/// An open bundle over a block store.
@ -183,10 +192,29 @@ impl<S: BlockStore> Bundle<S> {
/// A crash *during creation* may leave a half-formed file that [`Bundle::open`]
/// rejects as corrupt — acceptable, since the file is not yet a bundle. The
/// crash-safety guarantee is about *commits to an existing bundle*.
pub fn create(
///
/// Stamps the manifest chunk at the **baseline** [`Manifest::SCHEMA`]
/// version. A manifest created here declares no canonical roots or
/// blobs (enforced below), so it cannot yet name an edit barrier whose
/// tag requires a higher epoch — see [`Bundle::create_versioned`] for a
/// caller (e.g. a repack seeding a non-empty initial manifest through a
/// different path) that must supply the version explicitly.
pub fn create(store: S, file_uuid: FileUuid, manifest: Manifest) -> Result<Self, BundleError> {
Self::create_versioned(store, file_uuid, manifest, Manifest::SCHEMA)
}
/// As [`Bundle::create`], but the manifest chunk is stamped at the given
/// `manifest_schema_version` rather than the baseline [`Manifest::SCHEMA`]
/// (G-minor pin 6.1: "producers supply the aggregate manifest
/// `SchemaVersion` explicitly"). `epiphany-bundle` itself never derives
/// this value — it has no dependency on `epiphany-ops` or
/// `epiphany-layout-ir` and cannot decode `prohibited_operation_kinds` —
/// so the caller (a producer with the right dependencies) computes it.
pub fn create_versioned(
mut store: S,
file_uuid: FileUuid,
mut manifest: Manifest,
manifest_schema_version: SchemaVersion,
) -> Result<Self, BundleError> {
manifest.generation = 0;
manifest.manifest_id = manifest.derive_id();
@ -216,8 +244,11 @@ impl<S: BlockStore> Bundle<S> {
let manifest_payload = manifest.encode();
enforce_limit(manifest_payload.len() as u64, MAX_MANIFEST_BYTES)?;
let manifest = Manifest::decode(&manifest_payload)?;
let manifest_hash =
chunk_content_hash(ChunkKind::Manifest, Manifest::SCHEMA, &manifest_payload);
let manifest_hash = chunk_content_hash(
ChunkKind::Manifest,
manifest_schema_version,
&manifest_payload,
);
store.write_at(BODY_START, &manifest_payload)?;
store.flush()?;
@ -226,7 +257,7 @@ impl<S: BlockStore> Bundle<S> {
manifest_offset: BODY_START,
manifest_length: manifest_payload.len() as u64,
manifest_hash,
manifest_schema_version: Manifest::SCHEMA,
manifest_schema_version,
reduction_algorithm_version: reduction_version_for(&manifest),
profile_id: active_profile.profile_id,
commit_state: CommitState::Committed,
@ -605,10 +636,32 @@ impl<S: BlockStore> Bundle<S> {
/// appended bytes. On an error *at* the commit-point flush the durable result
/// is indeterminate (the new superblock may or may not have landed); the
/// bundle is poisoned read-only and the caller must reopen from storage.
///
/// Stamps the new manifest chunk at the **baseline** [`Manifest::SCHEMA`]
/// version. See [`Bundle::commit_versioned`] for a caller that must
/// supply a non-baseline aggregate (e.g. because the manifest the
/// closure builds names an edit barrier prohibiting a post-baseline
/// `OperationKindTag`).
pub fn commit(
&mut self,
new_chunks: &[StagedChunk],
build: impl FnOnce(&CommitContext) -> Manifest,
) -> Result<(), BundleError> {
self.commit_versioned(new_chunks, Manifest::SCHEMA, build)
}
/// As [`Bundle::commit`], but the new manifest chunk is stamped at the
/// given `manifest_schema_version` rather than the baseline
/// [`Manifest::SCHEMA`] (G-minor pin 6.1). The build closure reads
/// [`CommitContext::previous_manifest_version`] if it needs to decide
/// whether the previous aggregate is still exact (pin 6.6: an ordinary
/// repack preserving the complete barrier content preserves the carried
/// version exactly).
pub fn commit_versioned(
&mut self,
new_chunks: &[StagedChunk],
manifest_schema_version: SchemaVersion,
build: impl FnOnce(&CommitContext) -> Manifest,
) -> Result<(), BundleError> {
if self.read_only {
return Err(BundleError::ReadOnly);
@ -675,6 +728,7 @@ impl<S: BlockStore> Bundle<S> {
previous_manifest: &previous,
new_chunks: &new_refs,
generation: next_generation,
previous_manifest_version: self.superblock.manifest_schema_version,
});
// Extension-root preservation (Chapter 8 §"Behavior Under Unknown
@ -720,8 +774,11 @@ impl<S: BlockStore> Bundle<S> {
let manifest_payload = manifest.encode();
enforce_limit(manifest_payload.len() as u64, MAX_MANIFEST_BYTES)?;
let manifest = Manifest::decode(&manifest_payload)?;
let manifest_hash =
chunk_content_hash(ChunkKind::Manifest, Manifest::SCHEMA, &manifest_payload);
let manifest_hash = chunk_content_hash(
ChunkKind::Manifest,
manifest_schema_version,
&manifest_payload,
);
let manifest_offset = cursor;
self.store.write_at(manifest_offset, &manifest_payload)?;
cursor += manifest_payload.len() as u64;
@ -734,7 +791,7 @@ impl<S: BlockStore> Bundle<S> {
manifest_offset,
manifest_length: manifest_payload.len() as u64,
manifest_hash,
manifest_schema_version: Manifest::SCHEMA,
manifest_schema_version,
reduction_algorithm_version: reduction_version_for(&manifest),
profile_id: active_profile.profile_id,
commit_state: CommitState::Committed,
@ -1293,11 +1350,22 @@ fn verified_slot(
// Re-exported helper for harnesses that build raw images: the body start offset
// and the content hash of a manifest payload.
/// The content hash of a manifest chunk payload (Chapter 8 §"Content Hashing").
/// The content hash of a manifest chunk payload at the **baseline**
/// [`Manifest::SCHEMA`] version (Chapter 8 §"Content Hashing"). See
/// [`manifest_chunk_hash_versioned`] for a manifest stamped at a
/// non-baseline aggregate (G-minor, `spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4,
/// pin 7).
pub fn manifest_chunk_hash(payload: &[u8]) -> ContentHash {
chunk_content_hash(ChunkKind::Manifest, Manifest::SCHEMA, payload)
}
/// As [`manifest_chunk_hash`], but against the given `schema` rather than the
/// baseline [`Manifest::SCHEMA`] — the hash a manifest naming a post-baseline
/// edit-barrier tag must be content-addressed under.
pub fn manifest_chunk_hash_versioned(payload: &[u8], schema: SchemaVersion) -> ContentHash {
chunk_content_hash(ChunkKind::Manifest, schema, payload)
}
#[cfg(test)]
mod tests {
use super::*;
@ -1431,6 +1499,123 @@ mod tests {
.unwrap()
}
// -----------------------------------------------------------------
// G-minor (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4): s10, s11, s12.
// -----------------------------------------------------------------
#[test]
fn s10_a_repack_with_unchanged_barrier_content_preserves_the_carried_version() {
// A "repack" here: a second commit whose closure carries forward the
// manifest completely unchanged (no barrier content changed), reading
// the exact prior aggregate from `CommitContext::previous_manifest_
// version` (pin 6.2/6.6) rather than recomputing it. The carried
// version must survive byte-for-byte.
let mut bundle = Bundle::create_versioned(
MemStore::new(),
FileUuid([9; 16]),
Manifest::empty(DocumentId([9; 16])),
SchemaVersion::new(0, 8),
)
.unwrap();
assert_eq!(
bundle.superblock().manifest_schema_version,
SchemaVersion::new(0, 8)
);
let mut observed_previous = None;
bundle
.commit_versioned(&[], SchemaVersion::new(0, 8), |ctx| {
observed_previous = Some(ctx.previous_manifest_version);
ctx.previous_manifest.clone()
})
.unwrap();
assert_eq!(
observed_previous,
Some(SchemaVersion::new(0, 8)),
"CommitContext must expose the previous manifest version for the closure to read"
);
assert_eq!(
bundle.superblock().manifest_schema_version,
SchemaVersion::new(0, 8),
"an unchanged-content repack preserves the carried version exactly"
);
}
#[test]
fn s11_bundle_rs_301_accepts_a_manifest_minor_mismatch_at_the_same_major() {
// `open`'s gate at :301 compares `.major` only. A bundle whose
// manifest minor differs from `Manifest::SCHEMA`'s (but whose major
// matches) must still open — the v0 rule that the minor is a record,
// not a gate (pin 7). This is the exact conformance regression pin 7
// warns tightening the comparison to full-version equality would
// cause.
let bundle = Bundle::create_versioned(
MemStore::new(),
FileUuid([10; 16]),
Manifest::empty(DocumentId([10; 16])),
SchemaVersion::new(0, 8), // differs from Manifest::SCHEMA's minor (1)
)
.unwrap();
assert_ne!(SchemaVersion::new(0, 8), Manifest::SCHEMA);
assert_eq!(SchemaVersion::new(0, 8).major, Manifest::SCHEMA.major);
let image = bundle.into_store().into_bytes();
let reopened = Bundle::open(MemStore::from_bytes(image))
.expect("a major-matching minor mismatch opens");
assert!(!reopened.is_read_only());
assert_eq!(
reopened.superblock().manifest_schema_version,
SchemaVersion::new(0, 8)
);
}
#[test]
fn s12_a_raised_minor_changes_the_chunk_id_and_therefore_the_manifest_id() {
// Two operation blocks with byte-identical payloads but different
// schema minors must have different `ChunkId`s — the minor enters the
// hash preimage (`canonical_bytes`: major then minor). Naming that
// `ChunkRef` from a manifest's `operation_roots` then changes the
// manifest body, and therefore the `ManifestId`.
let payload = vec![1u8, 2, 3];
let id_v0 = crate::chunk::chunk_id(
ChunkKind::OperationEnvelopeBlock,
SchemaVersion::V0,
&payload,
);
let id_minor_8 = crate::chunk::chunk_id(
ChunkKind::OperationEnvelopeBlock,
SchemaVersion::new(0, 8),
&payload,
);
assert_ne!(
id_v0, id_minor_8,
"raising the minor must change the ChunkId"
);
let make_manifest = |chunk_id: epiphany_determinism::ChunkId, schema: SchemaVersion| {
let mut m = Manifest::empty(DocumentId([11; 16]));
m.operation_roots = vec![ChunkRef {
id: chunk_id,
kind: ChunkKind::OperationEnvelopeBlock,
schema_version: schema,
offset: 0,
compressed_length: payload.len() as u64,
uncompressed_length: payload.len() as u64,
compression: CompressionAlgorithm::None,
hash: chunk_id.content_hash(),
}];
m
};
let manifest_v0 = make_manifest(id_v0, SchemaVersion::V0);
let manifest_minor_8 = make_manifest(id_minor_8, SchemaVersion::new(0, 8));
assert_ne!(
manifest_v0.derive_id(),
manifest_minor_8.derive_id(),
"a changed ChunkRef in operation_roots must change the ManifestId"
);
}
#[test]
fn create_then_open_round_trips() {
let bundle = fresh_bundle();

View File

@ -201,12 +201,22 @@ impl SchemaVersion {
SchemaVersion { major, minor }
}
/// The current schema version at a given major: [`Self::V0`] for major 0,
/// [`Self::V1`] for major 1, [`Self::V2`] for major 2, [`Self::V3`] for
/// major 3, and `{major, 0}` for any higher (future) major. A writer maps
/// a chunk's derived schema major to a version this way — e.g. an
/// operation-envelope block stamps the max over its operations'
/// `schema_major()`.
/// The **baseline** schema version at a given major: [`Self::V0`] for
/// major 0, [`Self::V1`] for major 1, [`Self::V2`] for major 2,
/// [`Self::V3`] for major 3, and `{major, 0}` for any higher (future)
/// major. A writer maps a chunk's derived schema major to a version this
/// way — e.g. an operation-envelope block stamps the max over its
/// operations' `schema_major()`.
///
/// This yields the **baseline** minor only (G-minor,
/// `spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 4): a writer whose payload
/// emits a post-baseline discriminant (an appended `OperationKind`,
/// `OperationKindTag`, `OperationPayload`, `ReanchorReason`, or
/// `PreconditionFailureReason` variant) must use
/// [`Self::for_major_at_epoch`] instead, which raises the minor to the
/// epoch that variant requires. This constructor is **not** replaced —
/// `for_major_at_epoch` needs exactly the baseline this computes as its
/// floor (pin 3: `max(baseline_minor(major), epoch_max)`).
#[inline]
pub const fn for_major(major: u16) -> Self {
match major {
@ -218,6 +228,29 @@ impl SchemaVersion {
}
}
/// The schema version a writer stamps when its payload's discriminants
/// may require more than `major`'s own baseline minor (G-minor, pin 3):
/// `major` as given, and the minor raised to `epoch_max` when it exceeds
/// [`Self::for_major`]'s baseline minor for that major. `epoch_max` is
/// the maximum [`OperationKind::introduced_minor`](crate)-style epoch
/// actually emitted, or `None` for "no additive requirement" — the
/// distinct sentinel pin 3 requires (never `0`, which is a real baseline
/// minor for `V1``V3`). Baselines are never lowered: an `epoch_max` at
/// or below the baseline, or `None`, yields the baseline unchanged.
#[inline]
pub const fn for_major_at_epoch(major: u16, epoch_max: Option<u16>) -> Self {
let baseline = SchemaVersion::for_major(major);
if let Some(epoch) = epoch_max {
if epoch > baseline.minor {
return SchemaVersion {
major,
minor: epoch,
};
}
}
baseline
}
/// Canonical 4 bytes for the hash preimage (Chapter 8
/// §"Domain-Separated Preimages"): major then minor, little-endian.
#[inline]
@ -381,6 +414,49 @@ mod tests {
assert_ne!(a, c, "a different generation derives a different id");
}
#[test]
fn for_major_at_epoch_keeps_the_baseline_when_no_epoch_applies() {
// s3 anchor: a major-0 payload emitting only baseline vocabulary
// stamps {0, 1} — V0's baseline — never {0, 0}.
assert_eq!(
SchemaVersion::for_major_at_epoch(0, None),
SchemaVersion::V0
);
assert_eq!(SchemaVersion::for_major_at_epoch(0, None).minor, 1);
}
#[test]
fn for_major_at_epoch_raises_the_minor_when_the_epoch_exceeds_baseline() {
assert_eq!(
SchemaVersion::for_major_at_epoch(0, Some(8)),
SchemaVersion::new(0, 8)
);
}
#[test]
fn s6_major_and_minor_combine_independently_kind_28_example() {
// A major-2 block containing `CreateRepeatStructure` (kind 28: schema
// major 2, G-minor epoch 6) stamps exactly {2, 6} — the major comes
// from `schema_major()`, the minor from `introduced_minor()`, and
// this constructor combines them without either influencing the
// other (`epiphany-ops`' own `s6_major_and_minor_derive_
// independently` test establishes the two inputs; this closes the
// loop by combining them the way a real writer does).
assert_eq!(
SchemaVersion::for_major_at_epoch(2, Some(6)),
SchemaVersion::new(2, 6)
);
}
#[test]
fn for_major_at_epoch_never_lowers_the_baseline() {
// V1's baseline minor is 0; an epoch below it must not lower it.
assert_eq!(
SchemaVersion::for_major_at_epoch(1, Some(0)),
SchemaVersion::V1
);
}
#[test]
fn schema_version_canonical_bytes_are_major_then_minor_le() {
let v = SchemaVersion::new(0x0102, 0x0304);

View File

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

View File

@ -587,7 +587,17 @@ impl Manifest {
Ok(manifest)
}
/// The schema version manifests are encoded against in this crate.
/// The **baseline** schema version manifests are encoded against in this
/// crate. Not the universally-emitted version (G-minor,
/// `spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 7): a manifest naming an
/// edit barrier that prohibits a post-baseline `OperationKindTag`
/// (2433) must stamp that tag's epoch instead — see
/// `epiphany_layout_ir::barrier::edit_barriers_introduced_minor` for the
/// aggregate derivation, and `Bundle::create_versioned` /
/// `Bundle::commit_versioned` for the producer-supplied seam. This
/// constant remains the correct value for a manifest declaring only
/// baseline barrier tags (or none), and the manifest **major** always
/// stays 0 regardless of the minor stamped.
pub const SCHEMA: SchemaVersion = SchemaVersion::V0;
/// The *chunk-typed* canonical roots: the operation blocks and the canonical

View File

@ -773,6 +773,27 @@ pub fn decode_edit_barriers(bytes: &[u8]) -> DecodeResult<Vec<EditBarrier>> {
Ok(barriers)
}
/// The G-minor schema-minor epoch a set of **decoded** edit barriers requires
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 6; audit §5.1 Part 2): the
/// maximum [`OperationKindTag::introduced_minor`] over every tag named in any
/// barrier's `prohibited_operation_kinds`, or `None` if every named tag is
/// baseline (including when `barriers` is empty).
///
/// `epiphany-layout-ir` is the only crate that can both decode `EditBarrier`
/// and reach `OperationKindTag`'s epoch table (it depends on `epiphany-ops`;
/// `epiphany-bundle` deliberately does not, pin 6's "the bundle stays
/// opaque"). Callers with undecodable barrier bytes (a foreign extension's
/// blob, say) cannot call this at all — they must fail closed rather than
/// guess (pin 6.4): decode first with [`decode_edit_barriers`], and treat a
/// decode error as "the exact epoch cannot be established", not as baseline.
pub fn edit_barriers_introduced_minor(barriers: &[EditBarrier]) -> Option<u16> {
barriers
.iter()
.flat_map(|barrier| barrier.prohibited_operation_kinds.iter())
.filter_map(|tag| tag.introduced_minor())
.max()
}
/// Encodes a set of object kinds to the canonical blob stored in
/// `ExtensionDeclaration.affected_object_kinds` (the same `push_set` framing
/// as [`encode_edit_barriers`]; each element is the kind's 2 LE bytes).
@ -802,6 +823,38 @@ mod tests {
TypedObjectId::Event(EventId::from_raw(raw))
}
// -----------------------------------------------------------------
// G-minor (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 6.5): s9.
// -----------------------------------------------------------------
#[test]
fn s9_removing_the_sole_max_contributing_barrier_lowers_the_aggregate() {
let max_barrier = EditBarrier {
scope: BarrierScope::WholeScore,
affected_object_kinds: Vec::new(),
prohibited_operation_kinds: vec![OperationKindTag::CreateInstrument], // epoch 8
condition: BarrierCondition::Always,
};
let baseline_barrier = EditBarrier {
scope: BarrierScope::WholeScore,
affected_object_kinds: Vec::new(),
prohibited_operation_kinds: vec![OperationKindTag::InsertEvent], // baseline
condition: BarrierCondition::Always,
};
let with_both = vec![max_barrier, baseline_barrier.clone()];
assert_eq!(
edit_barriers_introduced_minor(&with_both),
Some(8),
"the sole max contributor sets the aggregate"
);
let after_removal = vec![baseline_barrier];
assert_eq!(
edit_barriers_introduced_minor(&after_removal),
None,
"removing the sole max contributor must lower the aggregate, not retain it"
);
}
#[test]
fn prohibits_matching_op_within_object_set() {
let target = ev(1);

View File

@ -83,10 +83,10 @@ pub mod time_axis;
pub mod vertical_band;
pub use barrier::{
decode_affected_object_kinds, decode_edit_barriers, encode_affected_object_kinds,
encode_edit_barriers, AlwaysLiveOracle, BarrierCondition, BarrierConditionRegistryId,
BarrierDecodeError, BarrierScope, BarrierScopeRegistryId, EditBarrier, EditContext, EditOracle,
ExtensionRef, ObjectKind, MAX_CONDITION_DEPTH,
decode_affected_object_kinds, decode_edit_barriers, edit_barriers_introduced_minor,
encode_affected_object_kinds, encode_edit_barriers, AlwaysLiveOracle, BarrierCondition,
BarrierConditionRegistryId, BarrierDecodeError, BarrierScope, BarrierScopeRegistryId,
EditBarrier, EditContext, EditOracle, ExtensionRef, ObjectKind, MAX_CONDITION_DEPTH,
};
pub use cache::{
ConstrainedRegionCache, DependencyIndex, FineLayoutCache, LayoutCache, LogicalRegionCache,

View File

@ -214,6 +214,39 @@ impl PreconditionFailureReason {
}
}
impl PreconditionFailureReason {
/// The G-minor schema-minor epoch this variant requires
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 1's ratified table), or
/// `None` for a baseline (0..=9) variant — the distinct "no additive
/// requirement" sentinel pin 3 requires, never `0`. Exhaustive with no
/// wildcard arm (pin 2): a future variant cannot compile without an
/// epoch assignment.
pub fn introduced_minor(&self) -> Option<u16> {
match self {
PreconditionFailureReason::TargetMissing
| PreconditionFailureReason::TargetTombstoned
| PreconditionFailureReason::WrongRegionTimeModel
| PreconditionFailureReason::TupletCompensationInvalid
| PreconditionFailureReason::EventDurationInvalid
| PreconditionFailureReason::PositionOutsideRegion
| PreconditionFailureReason::PitchSpaceMismatch
| PreconditionFailureReason::VoiceMissing
| PreconditionFailureReason::ExtensionPrecondition(_)
| PreconditionFailureReason::Registered(_) => None,
// Minor 2 (M2c).
PreconditionFailureReason::ContainerNotEmpty => Some(2),
// Minor 4 (Phase-3 first tranche).
PreconditionFailureReason::TempoMapMalformed => Some(4),
// Minor 5 (Pass-12 G-pass).
PreconditionFailureReason::SystemDerivedContentImmutable => Some(5),
PreconditionFailureReason::RecreateContentMismatch => Some(5),
// Minor 7 (Push 4a).
PreconditionFailureReason::AcousticRealizationPinned => Some(7),
PreconditionFailureReason::TranspositionOutOfRange => Some(7),
}
}
}
impl CanonicalEncode for PreconditionFailureReason {
fn encode_canonical(&self, out: &mut Vec<u8>) {
push_tag(out, self.discriminant());
@ -343,6 +376,25 @@ impl ReanchorReason {
}
}
impl ReanchorReason {
/// The G-minor schema-minor epoch this variant requires, or `None` for a
/// baseline (0..=5) variant. See
/// [`PreconditionFailureReason::introduced_minor`] for the sentinel and
/// exhaustiveness discipline this mirrors.
pub fn introduced_minor(&self) -> Option<u16> {
match self {
ReanchorReason::SameVoiceNearer
| ReanchorReason::SameStaffInstanceNearer
| ReanchorReason::SameStaffNearer
| ReanchorReason::SameRegionNearer
| ReanchorReason::ExplicitFallback
| ReanchorReason::DeclaredByExtension(_) => None,
// Minor 5 (Pass-12 G-pass, P12-C4).
ReanchorReason::SameCanvasNearer => Some(5),
}
}
}
impl CanonicalEncode for ReanchorReason {
fn encode_canonical(&self, out: &mut Vec<u8>) {
push_tag(out, self.discriminant());

View File

@ -124,14 +124,14 @@ pub use envelope::{
pub use migrate::{migrate_v0_envelope, project_v1_to_v0, MigrationError};
pub use opset::{AcceptOutcome, OperationSet};
pub use payload::{
ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp,
CreateRepeatStructureOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp,
CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp,
DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp,
InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
OperationKind, OperationKindTag, OperationPayload, PositionRemapping, ResolveConflictPayload,
ResolveEquivocationPayload, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp,
SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp,
operation_block_introduced_minor, ChangeRegionTimeModelOp, CreateCrossCuttingOp,
CreateInstrumentOp, CreateRegionOp, CreateRepeatStructureOp, CreateStaffInstanceOp,
CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp,
DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp,
DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp,
ModifyIdentifiedPitchOp, OperationKind, OperationKindTag, OperationPayload, PositionRemapping,
ResolveConflictPayload, ResolveEquivocationPayload, RespellPitchOp, SetCanvasLayoutDefaultsOp,
SetMetadataOp, SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionCategory,
TransactionDescriptor, TransposeIntervalOp, TransposeOp, TupletCompensation,
};

View File

@ -45,7 +45,7 @@ use epiphany_determinism::{
use crate::conflict::{ConflictId, ResolutionAction};
use crate::encode::{push_canon, push_lp_bytes, push_seq, push_str, push_tag, push_u8_bool};
use crate::envelope::EnvelopeHash;
use crate::envelope::{EnvelopeHash, OperationEnvelope};
use crate::support::OperationKindRegistryId;
use crate::undo::UndoTransactionPayload;
@ -93,6 +93,23 @@ impl OperationPayload {
| OperationPayload::ResolveEquivocation(_) => 0,
}
}
/// The G-minor schema-minor epoch this payload's own **outer**
/// discriminant requires (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 1),
/// or `None` for "no additive requirement" — the distinct sentinel pin 3
/// requires, never `0`. This is the outer `OperationPayload` variant
/// only; a `Primitive`'s nested `OperationKind` epoch is a separate
/// contribution an envelope's derivation combines with this one (see
/// `OperationEnvelope::introduced_minor`, below).
pub fn introduced_minor(&self) -> Option<u16> {
match self {
OperationPayload::Primitive(_)
| OperationPayload::ResolveConflict(_)
| OperationPayload::UndoTransaction(_) => None,
// Minor 3 (Push 3).
OperationPayload::ResolveEquivocation(_) => Some(3),
}
}
}
impl CanonicalEncode for OperationPayload {
@ -107,6 +124,40 @@ impl CanonicalEncode for OperationPayload {
}
}
impl OperationEnvelope {
/// The G-minor schema-minor epoch this envelope's canonical bytes require
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 3): the maximum over its
/// outer [`OperationPayload::introduced_minor`] and — when the payload is
/// [`OperationPayload::Primitive`] — the nested
/// [`OperationKind::introduced_minor`]. `None` when neither imposes a
/// requirement (both are baseline). No other nested vocabulary an
/// envelope's encoder reaches (`OperationEnvelope::encode_canonical`)
/// carries a post-baseline append: the embedded graph values ride
/// `epiphany-core`'s codec, which the G-minor audit found clean
/// (`spec/AUDIT_GMINOR_VOCABULARIES.md` Part 2).
pub fn introduced_minor(&self) -> Option<u16> {
let payload_epoch = self.payload.introduced_minor();
let kind_epoch = match &self.payload {
OperationPayload::Primitive(kind) => kind.introduced_minor(),
OperationPayload::ResolveConflict(_)
| OperationPayload::UndoTransaction(_)
| OperationPayload::ResolveEquivocation(_) => None,
};
payload_epoch.into_iter().chain(kind_epoch).max()
}
}
/// An operation-envelope block's required G-minor schema-minor epoch (pin 3):
/// the maximum [`OperationEnvelope::introduced_minor`] over every envelope it
/// carries, or `None` if the block carries no envelope or every envelope is
/// baseline. Callers combine this with the block's schema **major** (an
/// independent derivation, `OperationEnvelope::schema_major`) via
/// `epiphany_bundle::SchemaVersion::for_major_at_epoch` — major and minor
/// never influence each other (pin 3).
pub fn operation_block_introduced_minor(envelopes: &[OperationEnvelope]) -> Option<u16> {
envelopes.iter().filter_map(|e| e.introduced_minor()).max()
}
/// The catalog of primitive operation kinds reduced by this crate (Chapter 6
/// §"Operation Envelopes", representative subset of §6.10).
// `InsertEvent` carries a whole `Event`, so this variant is intentionally larger
@ -320,6 +371,62 @@ impl OperationKind {
}
}
/// The G-minor schema-minor epoch this kind's own discriminant requires
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 1's ratified table), or
/// `None` for a baseline (golden-locked 0..=23) variant — the distinct
/// "no additive requirement" sentinel pin 3 requires, never `0`.
///
/// **Deliberately a separate match from [`Self::discriminant`]** (pin 2):
/// that hand-written match is the site Push 4a's `TransposeInterval`
/// append went wrong by being consulted for only one of the vocabulary's
/// several responsibilities. Exhaustive here too, with no wildcard arm,
/// so a future variant cannot compile without an epoch assignment.
pub fn introduced_minor(&self) -> Option<u16> {
match self {
OperationKind::InsertEvent(_)
| OperationKind::DeleteEvent(_)
| OperationKind::RespellPitch(_)
| OperationKind::CreateCrossCutting(_)
| OperationKind::ChangeRegionTimeModel(_)
| OperationKind::SetUserSystemBreak(_)
| OperationKind::DeclareTransaction(_)
| OperationKind::Registered(..)
| OperationKind::ModifyEvent(_)
| OperationKind::Transpose(_)
| OperationKind::InsertIdentifiedPitch(_)
| OperationKind::DeleteIdentifiedPitch(_)
| OperationKind::ModifyIdentifiedPitch(_)
| OperationKind::DeleteCrossCutting(_)
| OperationKind::ModifyCrossCutting(_)
| OperationKind::CreateRegion(_)
| OperationKind::DeleteRegion(_)
| OperationKind::CreateStaffInstance(_)
| OperationKind::DeleteStaffInstance(_)
| OperationKind::CreateVoice(_)
| OperationKind::DeleteVoice(_)
| OperationKind::SetMetadata(_)
| OperationKind::SetMetricGrid(_)
| OperationKind::SetUserPageBreak(_) => None,
// Minor 4 (Phase-3 first tranche).
OperationKind::CreateStaff(_)
| OperationKind::SetTimeSignature(_)
| OperationKind::SetTempoSegment(_)
| OperationKind::SetStaffLayout(_) => Some(4),
// Minor 6 (schema-major-2 repeat-authoring revision).
OperationKind::CreateRepeatStructure(_) | OperationKind::DeleteRepeatStructure(_) => {
Some(6)
}
// Minor 7 (Push 4a).
OperationKind::TransposeInterval(_) => Some(7),
// Minor 8 (Genesis tranche G1).
OperationKind::CreateInstrument(_) => Some(8),
// Minor 9 (Genesis tranche G2a).
OperationKind::SetCanvasLayoutDefaults(_) | OperationKind::SetSpellingPrecedence(_) => {
Some(9)
}
}
}
/// The discriminator-only [`OperationKindTag`] for this kind. Used by edit
/// barriers (Chapter 7/8 `prohibited_operation_kinds`) to name a kind
/// without its payload.
@ -485,7 +592,7 @@ pub const REGISTERED_TAG_DISCRIMINANT: u8 = 16;
/// separate hand-maintained lists — two of them asserting the tag was *unknown*
/// — stayed green (Push 5 / P4).
macro_rules! operation_kind_tag_vocabulary {
($($variant:ident = $disc:literal => $catalog:literal),+ $(,)?) => {
($($variant:ident = $disc:literal => $catalog:literal @ $epoch:expr),+ $(,)?) => {
impl OperationKindTag {
/// Every payload-free tag, in discriminant order. [`Registered`]
/// is excluded: it carries an id and has no bare encoding.
@ -525,44 +632,58 @@ macro_rules! operation_kind_tag_vocabulary {
OperationKindTag::Registered(_) => "registered",
}
}
/// The G-minor schema-minor epoch this tag requires
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 1), or `None` for
/// "no additive requirement" — never `0`, a real baseline minor
/// for `V1``V3` (pin 3). **Co-located inside this macro** (pin
/// 2), not a sibling match: the macro is the compile-enforced
/// source of truth for this vocabulary, exhaustive over every
/// generated arm with no wildcard, including `Registered`.
pub fn introduced_minor(&self) -> Option<u16> {
match self {
$(OperationKindTag::$variant => $epoch,)+
OperationKindTag::Registered(_) => None,
}
}
}
};
}
operation_kind_tag_vocabulary! {
InsertEvent = 0 => "insert-event",
DeleteEvent = 1 => "delete-event",
ModifyEvent = 2 => "modify-event",
RespellPitch = 3 => "respell-pitch",
Transpose = 4 => "transpose",
CreateCrossCutting = 5 => "create-cross-cutting",
DeleteCrossCutting = 6 => "delete-cross-cutting",
ModifyCrossCutting = 7 => "modify-cross-cutting",
ChangeRegionTimeModel = 8 => "change-region-time-model",
InsertRegion = 9 => "create-region",
DeleteRegion = 10 => "delete-region",
InsertStaffInstance = 11 => "create-staff-instance",
DeleteStaffInstance = 12 => "delete-staff-instance",
SetUserSystemBreak = 13 => "set-user-system-break",
SetUserPageBreak = 14 => "set-user-page-break",
DeclareTransaction = 15 => "declare-transaction",
InsertIdentifiedPitch = 17 => "insert-identified-pitch",
DeleteIdentifiedPitch = 18 => "delete-identified-pitch",
ModifyIdentifiedPitch = 19 => "modify-identified-pitch",
CreateVoice = 20 => "create-voice",
DeleteVoice = 21 => "delete-voice",
SetMetadata = 22 => "set-metadata",
SetMetricGrid = 23 => "set-metric-grid",
InsertStaff = 24 => "create-staff",
SetTimeSignature = 25 => "set-time-signature",
SetTempoSegment = 26 => "set-tempo-segment",
SetStaffLayout = 27 => "set-staff-layout",
CreateRepeatStructure = 28 => "create-repeat-structure",
DeleteRepeatStructure = 29 => "delete-repeat-structure",
TransposeInterval = 30 => "transpose-interval",
CreateInstrument = 31 => "create-instrument",
SetCanvasLayoutDefaults = 32 => "set-canvas-layout-defaults",
SetSpellingPrecedence = 33 => "set-spelling-precedence",
InsertEvent = 0 => "insert-event" @ None,
DeleteEvent = 1 => "delete-event" @ None,
ModifyEvent = 2 => "modify-event" @ None,
RespellPitch = 3 => "respell-pitch" @ None,
Transpose = 4 => "transpose" @ None,
CreateCrossCutting = 5 => "create-cross-cutting" @ None,
DeleteCrossCutting = 6 => "delete-cross-cutting" @ None,
ModifyCrossCutting = 7 => "modify-cross-cutting" @ None,
ChangeRegionTimeModel = 8 => "change-region-time-model" @ None,
InsertRegion = 9 => "create-region" @ None,
DeleteRegion = 10 => "delete-region" @ None,
InsertStaffInstance = 11 => "create-staff-instance" @ None,
DeleteStaffInstance = 12 => "delete-staff-instance" @ None,
SetUserSystemBreak = 13 => "set-user-system-break" @ None,
SetUserPageBreak = 14 => "set-user-page-break" @ None,
DeclareTransaction = 15 => "declare-transaction" @ None,
InsertIdentifiedPitch = 17 => "insert-identified-pitch" @ None,
DeleteIdentifiedPitch = 18 => "delete-identified-pitch" @ None,
ModifyIdentifiedPitch = 19 => "modify-identified-pitch" @ None,
CreateVoice = 20 => "create-voice" @ None,
DeleteVoice = 21 => "delete-voice" @ None,
SetMetadata = 22 => "set-metadata" @ None,
SetMetricGrid = 23 => "set-metric-grid" @ None,
InsertStaff = 24 => "create-staff" @ Some(4),
SetTimeSignature = 25 => "set-time-signature" @ Some(4),
SetTempoSegment = 26 => "set-tempo-segment" @ Some(4),
SetStaffLayout = 27 => "set-staff-layout" @ Some(4),
CreateRepeatStructure = 28 => "create-repeat-structure" @ Some(6),
DeleteRepeatStructure = 29 => "delete-repeat-structure" @ Some(6),
TransposeInterval = 30 => "transpose-interval" @ Some(7),
CreateInstrument = 31 => "create-instrument" @ Some(8),
SetCanvasLayoutDefaults = 32 => "set-canvas-layout-defaults" @ Some(9),
SetSpellingPrecedence = 33 => "set-spelling-precedence" @ Some(9),
}
impl CanonicalEncode for OperationKindTag {
@ -1688,6 +1809,121 @@ mod tests {
use super::*;
use epiphany_core::{RegionId, ReplicaId, SlurId};
fn envelope(id: u64, payload: OperationPayload) -> OperationEnvelope {
use crate::causal::CausalContext;
use crate::stamp::{HybridLogicalClock, OperationStamp};
use crate::support::AuthorId;
use epiphany_core::WallClockTime;
let oid = OperationId::new(ReplicaId(1), id);
OperationEnvelope {
id: oid,
author: AuthorId(1),
stamp: OperationStamp::new(HybridLogicalClock::new(WallClockTime(id as i64), 0), oid),
causal_context: CausalContext::new(),
transaction: None,
payload,
}
}
fn create_instrument_envelope(id: u64) -> OperationEnvelope {
envelope(
id,
OperationPayload::Primitive(OperationKind::CreateInstrument(CreateInstrumentOp {
instrument: crate::valuegen::instrument(epiphany_core::InstrumentId::new(
ReplicaId(1),
id,
)),
})),
)
}
fn set_user_page_break_envelope(id: u64) -> OperationEnvelope {
// Baseline kind 23 (golden-locked 0..=23): no additive requirement.
envelope(
id,
OperationPayload::Primitive(OperationKind::SetUserPageBreak(SetUserPageBreakOp {
region: RegionId::new(ReplicaId(1), id),
anchor: crate::valuegen::region_start_anchor(
RegionId::new(ReplicaId(1), id),
MusicalPosition::origin(),
),
present: true,
})),
)
}
fn resolve_equivocation_envelope(id: u64) -> OperationEnvelope {
envelope(
id,
OperationPayload::ResolveEquivocation(ResolveEquivocationPayload {
target: OperationId::new(ReplicaId(1), id),
chosen: EnvelopeHash([0; 32]),
}),
)
}
fn create_repeat_structure_envelope(id: u64) -> OperationEnvelope {
let repeat_id = RepeatStructureId::new(ReplicaId(1), id);
let a = EventId::new(ReplicaId(1), id * 10 + 1);
let b = EventId::new(ReplicaId(1), id * 10 + 2);
envelope(
id,
OperationPayload::Primitive(OperationKind::CreateRepeatStructure(
CreateRepeatStructureOp {
repeat: crate::valuegen::repeat_structure(repeat_id, a, b),
},
)),
)
}
// -----------------------------------------------------------------
// G-minor block/envelope derivation: s4, s5, s6.
// -----------------------------------------------------------------
#[test]
fn s4_a_block_containing_kind_31_stamps_minor_8() {
let envelopes = vec![create_instrument_envelope(1)];
assert_eq!(operation_block_introduced_minor(&envelopes), Some(8));
}
#[test]
fn s5_mixing_an_old_kind_23_with_resolve_equivocation_stamps_3_not_1() {
// `SetUserPageBreak` is kind 23 (baseline, no additive requirement);
// `ResolveEquivocation` is the outer payload appended at minor 3. The
// block's minor must be the *epoch* max (3), never the highest
// *discriminant* (23) — the rejected policy pin 3 names explicitly.
let envelopes = vec![
set_user_page_break_envelope(1),
resolve_equivocation_envelope(2),
];
assert_eq!(operation_block_introduced_minor(&envelopes), Some(3));
}
#[test]
fn s6_major_and_minor_derive_independently() {
// `CreateRepeatStructure` (kind 28) is schema-major 2 (born at v2) and
// epoch 6 (schema-major-2 repeat-authoring revision). A major-2 block
// containing it must stamp minor 6 regardless of major — the two
// derivations must never influence each other.
let envelopes = vec![create_repeat_structure_envelope(1)];
let major = envelopes.iter().map(|e| e.schema_major()).max().unwrap();
assert_eq!(
major, 2,
"major derives from schema_major, independent of epoch"
);
let epoch_max = operation_block_introduced_minor(&envelopes);
assert_eq!(
epoch_max,
Some(6),
"minor epoch derives from introduced_minor, independent of major"
);
// `SchemaVersion::for_major_at_epoch(2, Some(6))` (tested directly in
// `epiphany-bundle`'s `ids.rs`, which this crate does not depend on)
// combines these two independent values into `{2, 6}` — this test's
// job is only to show the two inputs to that combination are each
// correct and mutually uninfluenced.
}
#[test]
fn operation_kind_wire_discriminants_are_golden() {
// GOLDEN LOCK: the discriminant byte leads every canonically-encoded
@ -2162,6 +2398,137 @@ mod tests {
}
}
// -----------------------------------------------------------------
// G-minor (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 1's ratified
// epoch table): s1, s2.
// -----------------------------------------------------------------
#[test]
fn s1_operation_kind_tag_epochs_match_pin1s_table() {
// Every post-baseline `OperationKindTag` epoch, transcribed from pin
// 1's ratified table.
for (tag, expected) in [
(OperationKindTag::InsertStaff, 4u16),
(OperationKindTag::SetTimeSignature, 4),
(OperationKindTag::SetTempoSegment, 4),
(OperationKindTag::SetStaffLayout, 4),
(OperationKindTag::CreateRepeatStructure, 6),
(OperationKindTag::DeleteRepeatStructure, 6),
(OperationKindTag::TransposeInterval, 7),
(OperationKindTag::CreateInstrument, 8),
(OperationKindTag::SetCanvasLayoutDefaults, 9),
(OperationKindTag::SetSpellingPrecedence, 9),
] {
assert_eq!(
tag.introduced_minor(),
Some(expected),
"{tag:?} must require epoch {expected}"
);
}
}
#[test]
fn s1_operation_kind_epochs_match_tag_epochs_across_every_generated_kind() {
// `OperationKind`'s own epoch table mirrors `OperationKindTag`'s (same
// ten events); rather than hand-build all thirty-four payloads, this
// checks every kind the corpus generator reaches agrees with its own
// tag's already pin-1-verified epoch (s1 above) — so a divergence in
// either table is caught.
use crate::fuzz::gen_envelope_set;
use epiphany_determinism::fuzz::SplitMix64;
let mut rng = SplitMix64::new(0xC0FFEE);
let envelopes = gen_envelope_set(&mut rng, 400);
let mut seen_post_baseline = false;
for envelope in &envelopes {
if let OperationPayload::Primitive(kind) = &envelope.payload {
assert_eq!(
kind.introduced_minor(),
kind.tag().introduced_minor(),
"{:?}: OperationKind and OperationKindTag epochs disagree",
kind.tag()
);
seen_post_baseline |= kind.introduced_minor().is_some();
}
}
assert!(
seen_post_baseline,
"fixture reach: the generated set never reached a post-baseline kind"
);
}
#[test]
fn s1_operation_payload_and_reanchor_and_precondition_epochs_match_pin1() {
assert_eq!(
OperationPayload::ResolveEquivocation(ResolveEquivocationPayload {
target: OperationId::new(epiphany_core::ReplicaId(1), 1),
chosen: EnvelopeHash([0; 32]),
})
.introduced_minor(),
Some(3)
);
assert_eq!(
crate::effect::ReanchorReason::SameCanvasNearer.introduced_minor(),
Some(5)
);
for (reason, expected) in [
(
crate::effect::PreconditionFailureReason::ContainerNotEmpty,
2u16,
),
(
crate::effect::PreconditionFailureReason::TempoMapMalformed,
4,
),
(
crate::effect::PreconditionFailureReason::SystemDerivedContentImmutable,
5,
),
(
crate::effect::PreconditionFailureReason::RecreateContentMismatch,
5,
),
(
crate::effect::PreconditionFailureReason::AcousticRealizationPinned,
7,
),
(
crate::effect::PreconditionFailureReason::TranspositionOutOfRange,
7,
),
] {
assert_eq!(reason.introduced_minor(), Some(expected), "{reason:?}");
}
}
#[test]
fn s2_every_baseline_variant_returns_no_additive_requirement() {
// A representative baseline member of each of the five vocabularies.
assert_eq!(OperationKindTag::InsertEvent.introduced_minor(), None);
assert_eq!(OperationKindTag::SetMetricGrid.introduced_minor(), None);
assert_eq!(
OperationPayload::Primitive(OperationKind::InsertEvent(InsertEventOp {
staff_instance: StaffInstanceId::new(epiphany_core::ReplicaId(1), 1),
event: crate::valuegen::insert_event_value(
EventId::new(epiphany_core::ReplicaId(1), 2),
VoiceId::new(epiphany_core::ReplicaId(1), 3),
MusicalPosition::origin(),
MusicalDuration::whole(),
&[],
),
}))
.introduced_minor(),
None
);
assert_eq!(
crate::effect::ReanchorReason::SameVoiceNearer.introduced_minor(),
None
);
assert_eq!(
crate::effect::PreconditionFailureReason::TargetMissing.introduced_minor(),
None
);
}
#[test]
fn reassign_remapping_is_order_independent() {
let e1 = EventId::new(ReplicaId(1), 1);

View File

@ -607,6 +607,64 @@ impl MaterializedState {
pub fn is_clean(&self) -> bool {
self.conflicts.is_empty() && self.anomalies.is_empty() && self.pending.is_empty()
}
/// The canonical base's own G-minor schema-minor epoch (pin 5): the
/// maximum over every later-added discriminant its **own bytes** emit —
/// today, [`ReanchorReason::SameCanvasNearer`] (nested inside
/// [`RepairKind::Reanchored`], itself inside
/// [`OperationEffect::AppliedWithRepair`]) and any post-baseline
/// [`PreconditionFailureReason`] (nested inside
/// [`NoOpReason::PreconditionFailedUnderReduction`], itself inside
/// [`OperationEffect::NoOp`]). `None` if every effect is baseline.
///
/// **Never `OperationKind`, `OperationKindTag`, or `OperationPayload`**
/// (pin 5): `MaterializedState`'s encoder
/// (`MaterializedState::canonical_bytes`) never emits any of those three
/// vocabularies, so a newer *source* operation alone — e.g. one whose
/// primitive kind is `CreateInstrument` (minor 8) — never moves this
/// value merely by having been reduced; only the effect it *produces*
/// can.
pub fn introduced_minor(&self) -> Option<u16> {
self.effects
.iter()
.filter_map(|(_, effect)| effect_introduced_minor(effect))
.max()
}
}
/// The G-minor epoch one [`OperationEffect`] contributes to
/// [`MaterializedState::introduced_minor`] (pin 5): walks the two nested
/// vocabularies the canonical base's encoder actually reaches —
/// `RepairKind::Reanchored`'s [`ReanchorReason`] and
/// `NoOpReason::PreconditionFailedUnderReduction`'s
/// [`PreconditionFailureReason`] — and returns their maximum, or `None` if
/// neither applies or both are baseline.
fn effect_introduced_minor(effect: &OperationEffect) -> Option<u16> {
match effect {
OperationEffect::AppliedWithRepair { repairs } => repairs
.iter()
.filter_map(|repair| match &repair.kind {
RepairKind::Reanchored { reason, .. } => reason.introduced_minor(),
RepairKind::SpannerTruncated { .. }
| RepairKind::Orphaned
| RepairKind::CascadeDeleted
| RepairKind::AttachmentTombstoned
| RepairKind::VoicePromoted { .. }
| RepairKind::TupletCompensated { .. }
| RepairKind::Registered(_) => None,
})
.max(),
OperationEffect::NoOp { reason } => match reason {
NoOpReason::PreconditionFailedUnderReduction { reason } => reason.introduced_minor(),
NoOpReason::TargetTombstoned
| NoOpReason::AlreadyApplied
| NoOpReason::SupersededByLaterOperation { .. }
| NoOpReason::TransactionConflict => None,
},
OperationEffect::Applied
| OperationEffect::Conflicted { .. }
| OperationEffect::TombstonedTarget { .. } => None,
}
}
/// Reduces an [`OperationSet`] to its canonical [`MaterializedState`].
@ -7729,6 +7787,49 @@ mod tests {
MusicalPosition(RationalTime::from_int(n as i32))
}
// -----------------------------------------------------------------
// G-minor (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pin 5): s7.
// -----------------------------------------------------------------
#[test]
fn s7_a_canonical_base_whose_effects_include_same_canvas_nearer_stamps_5() {
use epiphany_core::EventId;
let from = TypedObjectId::Event(EventId::new(ReplicaId(1), 1));
let to = TypedObjectId::Event(EventId::new(ReplicaId(1), 2));
let state = MaterializedState {
effects: vec![(
OperationId::new(ReplicaId(1), 1),
OperationEffect::AppliedWithRepair {
repairs: vec![RepairRecord {
kind: RepairKind::Reanchored {
from,
to,
reason: ReanchorReason::SameCanvasNearer,
},
target: from,
}],
},
)],
..Default::default()
};
assert_eq!(state.introduced_minor(), Some(5));
}
#[test]
fn s7_a_canonical_base_without_the_effect_stamps_baseline_even_from_kind_33_sources() {
// The base's own bytes carry only a plain `Applied` effect — never an
// `OperationKind`, `OperationKindTag`, or `OperationPayload` — so even
// though the *source operation* that produced it (not modeled in this
// state at all, by construction: pin 5) might have been kind 33
// (`SetSpellingPrecedence`, epoch 9), the base itself must not be
// dragged to epoch 9. It has no additive requirement.
let state = MaterializedState {
effects: vec![(OperationId::new(ReplicaId(1), 1), OperationEffect::Applied)],
..Default::default()
};
assert_eq!(state.introduced_minor(), None);
}
fn insert(
replica: u64,
counter: u64,

View File

@ -12,8 +12,8 @@
//! first violation.
use epiphany_testkit::{
bundle_harness, convergence, corpus, editloop, equivocation, fixtures, generators, layout_stub,
negative, prepass_harness, roundtrip, textproj, Rng,
bundle_harness, convergence, corpus, editloop, equivocation, fixtures, generators, gminor,
layout_stub, negative, prepass_harness, roundtrip, textproj, Rng,
};
/// The suite's total gate count, printed in every `[N/TOTAL_GATES]` line and
@ -210,6 +210,23 @@ fn main() {
}
}
// 7f. G-minor (spec/PLAN_GMINOR_SCHEMA_MINOR.md §4, pin 11): the
// independent oracle over the manifest's carried schema minor, built
// from known, decodable, in-tree fixtures — never routed through
// `TOTAL_GATES`, the same convention 7b-7e already use (this is a
// sub-gate of gate 7, not a new top-level numbered gate). It is NOT
// evidence that `epiphany-textproj` validates arbitrary hand edits
// (pin 11.4): that layer stays a preserving producer by ruled design
// (a), carrying whatever SchemaVersion a document declares verbatim.
eprintln!("[7f ] G-minor: manifest schema-minor oracle over in-tree fixtures");
{
let (checked, not_checkable) = gminor::run_gate();
eprintln!(
" {checked} fixture(s) checked (equality oracle), {not_checkable} \
reported not-checkable (never counted as a pass)"
);
}
// 8. T2 W3 — the [9/9] golden conformance gate (`golden-gate` feature
// only; absent without it, and the line above prints "8/8" unchanged).
// Re-derives the three T1a golden states headlessly and rasterizes

View File

@ -20,15 +20,19 @@ use epiphany_bundle::{
Tear,
};
use epiphany_determinism::CanonicalEncode;
use epiphany_ops::{peek_operation_id, OperationEnvelope};
use epiphany_ops::{operation_block_introduced_minor, peek_operation_id, OperationEnvelope};
/// Stages real operation envelopes into a single op-envelope block, **deriving**
/// the block's schema version from its operations: the block major is the max
/// over `OperationEnvelope::schema_major` under minimal stamping (a v1
/// `CreateRegion` → major 1; a v2 cross-cutting/staff/metadata value →
/// major 2), mapped to a version by [`SchemaVersion::for_major`]. This is the
/// writer-side derivation every real-envelope staging path must use so a
/// block carrying a versioned payload is never mis-stamped major 0.
/// major 2), and the block minor is the max over
/// `OperationEnvelope::introduced_minor` (G-minor,
/// `spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4) — the block's actually-emitted
/// discriminants, never a fixed baseline. Both are mapped to a version by
/// [`SchemaVersion::for_major_at_epoch`]. This is the writer-side derivation
/// every real-envelope staging path must use so a block carrying a versioned
/// payload is never mis-stamped.
pub fn stage_operation_block(envelopes: &[OperationEnvelope]) -> StagedChunk {
let payloads: Vec<Vec<u8>> = envelopes.iter().map(|e| e.to_canonical_bytes()).collect();
let major = envelopes
@ -36,7 +40,11 @@ pub fn stage_operation_block(envelopes: &[OperationEnvelope]) -> StagedChunk {
.map(|e| e.schema_major())
.max()
.unwrap_or(0);
StagedChunk::operation_block_versioned(encode_block(&payloads), SchemaVersion::for_major(major))
let epoch_max = operation_block_introduced_minor(envelopes);
StagedChunk::operation_block_versioned(
encode_block(&payloads),
SchemaVersion::for_major_at_epoch(major, epoch_max),
)
}
use crate::generators;

View File

@ -0,0 +1,348 @@
//! The `[7f]` conformance gate (G-minor, `spec/PLAN_GMINOR_SCHEMA_MINOR.md`
//! §4, pin 11): an independent oracle over the manifest's carried schema
//! minor, built from **known, decodable, in-tree artifacts**.
//!
//! `epiphany-textproj` deliberately never decodes edit-barrier bytes (pin 8:
//! it has no `epiphany-layout-ir` dependency), so it cannot itself check
//! whether a manifest's carried `SchemaVersion` matches what its declared
//! edit barriers require. This module is the independent check that *can*:
//! `epiphany-testkit` depends on `epiphany-layout-ir`, so it can decode
//! `ExtensionDeclaration::edit_barriers`, walk every barrier's
//! `prohibited_operation_kinds`, and recompute the exact aggregate minor the
//! manifest should carry (`epiphany_layout_ir::barrier::edit_barriers_introduced_minor`).
//!
//! **What this gate is not.** It is not evidence that `epiphany-textproj`
//! validates arbitrary hand-edited documents — pin 11's ruled design (a) is
//! that `textproj` stays a *preserving* producer, carrying whatever
//! `SchemaVersion` a document declares verbatim, and a hand-edited document
//! whose barrier bytes changed while its carried version did not is
//! undetectable at that layer by construction. This gate validates a
//! separate, narrower claim: that *this crate's own fixtures*, built directly
//! against `epiphany-bundle`, are exactly and correctly stamped. An
//! undecodable barrier blob (a foreign extension's bytes, a corrupt encoding)
//! is reported as **not-checkable**, never silently counted as a pass.
use epiphany_bundle::{
Bundle, DocumentId, ExtensionDeclaration, ExtensionId, FileUuid, Manifest, MemStore,
SchemaVersion, SemVer,
};
use epiphany_layout_ir::{
decode_edit_barriers, edit_barriers_introduced_minor, encode_edit_barriers, BarrierCondition,
BarrierScope, EditBarrier,
};
use epiphany_ops::OperationKindTag;
/// The oracle's verdict for one manifest.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GminorVerdict {
/// Every barrier blob decoded; `expected` is the recomputed aggregate,
/// `actual` is the carried superblock value, and `matches` is whether
/// they are **exactly equal** — not `>=` (pin 11.1: equality alone
/// catches over-stamping after a contributing barrier is removed).
Checked {
expected: SchemaVersion,
actual: SchemaVersion,
matches: bool,
},
/// At least one barrier blob failed to decode (a foreign extension, or
/// deliberately corrupt bytes). The exact epoch cannot be established, so
/// this is reported as not-checkable — **never** as a pass (pin 11.3).
NotCheckable(String),
}
/// A single edit barrier naming `tags`, with an otherwise-trivial
/// whole-score, unconditional scope — the minimum shape needed to exercise
/// `prohibited_operation_kinds`.
fn barrier(tags: &[OperationKindTag]) -> EditBarrier {
EditBarrier {
scope: BarrierScope::WholeScore,
affected_object_kinds: Vec::new(),
prohibited_operation_kinds: tags.to_vec(),
condition: BarrierCondition::Always,
}
}
fn extension_declaration(id_byte: u8, barriers: &[EditBarrier]) -> ExtensionDeclaration {
ExtensionDeclaration {
extension_id: ExtensionId([id_byte; 16]),
version: SemVer::new(1, 0, 0),
required: false,
preserved_chunk_roots: Vec::new(),
affected_object_kinds: Vec::new(),
edit_barriers: encode_edit_barriers(barriers),
}
}
/// Builds a bundle whose manifest declares `extensions` and is stamped at
/// `stamped_version` — the two independent knobs every fixture below varies.
fn build_bundle(
seed: u8,
extensions: Vec<ExtensionDeclaration>,
stamped_version: SchemaVersion,
) -> Bundle<MemStore> {
let mut manifest = Manifest::empty(DocumentId([seed; 16]));
manifest.extension_declarations = extensions;
Bundle::create_versioned(
MemStore::new(),
FileUuid([seed; 16]),
manifest,
stamped_version,
)
.expect("fixture manifest is emittable")
}
/// The independent oracle (pin 11): recomputes the exact aggregate minor a
/// bundle's manifest should carry from its decodable edit barriers, and
/// compares it **by equality** to the carried superblock value.
pub fn check(bundle: &Bundle<MemStore>) -> GminorVerdict {
let mut all_barriers = Vec::new();
for declaration in &bundle.manifest().extension_declarations {
match decode_edit_barriers(&declaration.edit_barriers) {
Ok(mut decoded) => all_barriers.append(&mut decoded),
Err(error) => {
return GminorVerdict::NotCheckable(format!(
"extension {:?}: edit_barriers did not decode: {error:?}",
declaration.extension_id
))
}
}
}
let epoch_max = edit_barriers_introduced_minor(&all_barriers);
let expected = SchemaVersion::for_major_at_epoch(0, epoch_max);
let actual = bundle.superblock().manifest_schema_version;
GminorVerdict::Checked {
expected,
actual,
matches: expected == actual,
}
}
/// Fixture: a manifest naming barrier tag 31 (`CreateInstrument`, epoch 8),
/// correctly stamped at exactly that epoch. Positive: the oracle must find
/// `matches: true`.
pub fn fixture_correctly_stamped_tag_31() -> Bundle<MemStore> {
let declarations = vec![extension_declaration(
1,
&[barrier(&[OperationKindTag::CreateInstrument])],
)];
build_bundle(1, declarations, SchemaVersion::new(0, 8))
}
/// Fixture: a manifest naming only baseline tags, correctly stamped at the
/// baseline `{0, 1}`. Positive.
pub fn fixture_baseline_only() -> Bundle<MemStore> {
let declarations = vec![extension_declaration(
2,
&[barrier(&[
OperationKindTag::InsertEvent,
OperationKindTag::DeleteEvent,
])],
)];
build_bundle(2, declarations, SchemaVersion::V0)
}
/// Negative fixture (pin 11.2, required #1): a blob naming tag 31 (epoch 8),
/// but the manifest carries the **baseline** version — under-stamped. The
/// oracle must find `matches: false`.
pub fn fixture_understamped() -> Bundle<MemStore> {
let declarations = vec![extension_declaration(
3,
&[barrier(&[OperationKindTag::CreateInstrument])],
)];
build_bundle(3, declarations, SchemaVersion::V0)
}
/// Negative fixture (pin 11.2, required #2): two barriers, one naming tag 31
/// (epoch 8, the sole max contributor) and one baseline-only. The barrier
/// contributing the maximum is then **removed** (only the baseline barrier
/// remains), but the manifest retains the old aggregate `{0, 8}` — exactly
/// the over-stamp pin 6.5 warns "blindly retaining the previous aggregate"
/// produces. The oracle must find `matches: false`.
pub fn fixture_overstamped_after_barrier_removal() -> Bundle<MemStore> {
let declarations = vec![extension_declaration(
4,
&[barrier(&[OperationKindTag::InsertEvent])],
)];
build_bundle(4, declarations, SchemaVersion::new(0, 8))
}
/// Fixture: a manifest declaring an extension whose `edit_barriers` blob is
/// deliberately corrupt (not a valid canonical `EditBarrier` set encoding).
/// The oracle must report `NotCheckable`, never a pass (pin 11.3).
pub fn fixture_undecodable_barrier_blob() -> Bundle<MemStore> {
let declaration = ExtensionDeclaration {
extension_id: ExtensionId([5; 16]),
version: SemVer::new(1, 0, 0),
required: false,
preserved_chunk_roots: Vec::new(),
affected_object_kinds: Vec::new(),
// Not a canonical edit-barrier-set encoding: garbage bytes that a
// real foreign/corrupt extension could plausibly carry.
edit_barriers: vec![0xFF, 0x00, 0x13, 0x37, 0xAB],
};
build_bundle(5, vec![declaration], SchemaVersion::V0)
}
/// Runs the whole `[7f]` gate: every fixture, checked against its expected
/// verdict shape. Returns `(checked, not_checkable)` — the two counts the
/// conformance suite reports (pin 11.4: describing the gate's actual reach
/// honestly, not implying it validates arbitrary edits).
pub fn run_gate() -> (usize, usize) {
let mut checked = 0usize;
let mut not_checkable = 0usize;
let positive_correct = check(&fixture_correctly_stamped_tag_31());
assert_eq!(
positive_correct,
GminorVerdict::Checked {
expected: SchemaVersion::new(0, 8),
actual: SchemaVersion::new(0, 8),
matches: true,
},
"a manifest naming tag 31, correctly stamped at minor 8, must check as matching"
);
checked += 1;
let positive_baseline = check(&fixture_baseline_only());
assert_eq!(
positive_baseline,
GminorVerdict::Checked {
expected: SchemaVersion::V0,
actual: SchemaVersion::V0,
matches: true,
},
"a manifest naming only baseline tags must check as matching its baseline stamp"
);
checked += 1;
let negative_understamped = check(&fixture_understamped());
assert_eq!(
negative_understamped,
GminorVerdict::Checked {
expected: SchemaVersion::new(0, 8),
actual: SchemaVersion::V0,
matches: false,
},
"an under-stamped manifest (tag 31 present, baseline carried) must be caught"
);
checked += 1;
let negative_overstamped = check(&fixture_overstamped_after_barrier_removal());
assert_eq!(
negative_overstamped,
GminorVerdict::Checked {
expected: SchemaVersion::V0,
actual: SchemaVersion::new(0, 8),
matches: false,
},
"an over-stamped manifest (max contributor removed, old aggregate retained) must be caught"
);
checked += 1;
match check(&fixture_undecodable_barrier_blob()) {
GminorVerdict::NotCheckable(_) => not_checkable += 1,
other => panic!("an undecodable barrier blob must report NotCheckable, got {other:?}"),
}
(checked, not_checkable)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn s8_manifest_naming_tag_31_stamps_8_baseline_tags_stamp_baseline() {
assert_eq!(
check(&fixture_correctly_stamped_tag_31()),
GminorVerdict::Checked {
expected: SchemaVersion::new(0, 8),
actual: SchemaVersion::new(0, 8),
matches: true,
}
);
assert_eq!(
check(&fixture_baseline_only()),
GminorVerdict::Checked {
expected: SchemaVersion::V0,
actual: SchemaVersion::V0,
matches: true,
}
);
}
#[test]
fn s15_the_gate_fails_the_understamped_fixture() {
let verdict = check(&fixture_understamped());
assert_eq!(
verdict,
GminorVerdict::Checked {
expected: SchemaVersion::new(0, 8),
actual: SchemaVersion::V0,
matches: false,
}
);
}
#[test]
fn s16_the_gate_fails_the_overstamped_fixture() {
let verdict = check(&fixture_overstamped_after_barrier_removal());
assert_eq!(
verdict,
GminorVerdict::Checked {
expected: SchemaVersion::V0,
actual: SchemaVersion::new(0, 8),
matches: false,
}
);
}
#[test]
fn s17_an_undecodable_blob_is_reported_not_checkable_never_a_pass() {
assert!(matches!(
check(&fixture_undecodable_barrier_blob()),
GminorVerdict::NotCheckable(_)
));
}
#[test]
fn the_gate_reports_four_checked_and_one_not_checkable() {
assert_eq!(run_gate(), (4, 1));
}
#[test]
fn correctly_stamped_fixtures_match() {
assert!(matches!(
check(&fixture_correctly_stamped_tag_31()),
GminorVerdict::Checked { matches: true, .. }
));
assert!(matches!(
check(&fixture_baseline_only()),
GminorVerdict::Checked { matches: true, .. }
));
}
#[test]
fn understamped_fixture_is_caught() {
assert!(matches!(
check(&fixture_understamped()),
GminorVerdict::Checked { matches: false, .. }
));
}
#[test]
fn overstamped_fixture_is_caught() {
assert!(matches!(
check(&fixture_overstamped_after_barrier_removal()),
GminorVerdict::Checked { matches: false, .. }
));
}
#[test]
fn undecodable_barrier_blob_is_not_checkable_not_a_pass() {
assert!(matches!(
check(&fixture_undecodable_barrier_blob()),
GminorVerdict::NotCheckable(_)
));
}
}

View File

@ -123,6 +123,7 @@ pub mod negative;
pub mod bundle_harness;
pub mod gminor;
pub mod layout_stub;
// The editing-loop vertical slice: hit-test → score object → operation → reduce →

View File

@ -9,7 +9,7 @@
//! intentionally regenerates layout, collapses duplicate blobs, and omits
//! non-canonical accelerators; none of those changes operation semantics.
use epiphany_bundle::{DocumentId, FileUuid, MemStore, ProfileDeclaration};
use epiphany_bundle::{DocumentId, FileUuid, MemStore, ProfileDeclaration, SchemaVersion};
use epiphany_ops::{OperationEnvelope, OperationSet};
use epiphany_textproj::parse::parse_document;
use epiphany_textproj::project::{document_from_bundle, project_bundle};
@ -36,6 +36,7 @@ pub fn assert_semantics_preserved(seed: u64) {
let source = TextDocument {
document_id: DocumentId(seed.to_le_bytes().repeat(2).try_into().expect("16 bytes")),
manifest_schema_version: SchemaVersion::V0,
lineage_id: None,
profiles: vec![ProfileDeclaration::full()],
extensions: Vec::new(),

View File

@ -10,8 +10,10 @@ use std::fs;
use std::path::{Path, PathBuf};
const CORE_REQUIREMENT_COUNT: usize = 212;
const SUITE_REQUIREMENT_COUNT: usize = 282;
const SUITE_LABEL_COUNT: usize = 282;
// +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;
/// The normative chapter-to-area assignment. Keeping this as data makes adding a
/// requirement under the wrong chapter fail without encoding chapter names in

View File

@ -31,7 +31,19 @@ use epiphany_ops::OperationEnvelope;
/// `set-canvas-layout-defaults` and `set-spelling-precedence` to the `kind`
/// production — the same reasoning: extending the grammar without moving this
/// constant would leave two incompatible grammars both claiming `(0 8 0)`.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 9, 0);
///
/// Bumped again 0.9.0 → 0.10.0 by the G-minor rung
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md`), which added the carried manifest
/// [`SchemaVersion`] to the `document` production
/// (`document ::= "(document " bytes " " schema ")"`). **Not** because of
/// operation-block schema-minor stamping — block schemas are discarded during
/// projection and stay projection-invisible, exactly as before — but because
/// the manifest's aggregate `SchemaVersion` becomes a carried `TextDocument`
/// attribute that this companion cannot derive (`epiphany-textproj` has no
/// `epiphany-layout-ir` dependency and structurally cannot decode edit-barrier
/// bytes). Holding the version while changing the grammar would leave two
/// incompatible grammars both claiming `(0 9 0)`.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 10, 0);
/// A parsed canonical Text Projection document.
///
@ -49,6 +61,14 @@ pub const COMPANION_VERSION: (u32, u32, u32) = (0, 9, 0);
pub struct TextDocument {
/// Logical identity of the projected document.
pub document_id: DocumentId,
/// The manifest's aggregate G-minor `SchemaVersion`
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pins 8/11): carried verbatim,
/// never derived. This companion has no `epiphany-layout-ir` dependency
/// and cannot decode `ExtensionDeclaration::edit_barriers`, so it cannot
/// recompute this value from the document's other fields — the document
/// author is responsible for updating it when hand-editing barrier bytes
/// that change what tags they name (pin 11's ruled design (a)).
pub manifest_schema_version: SchemaVersion,
/// Optional shared-ancestor identity used for document genealogy.
pub lineage_id: Option<LineageId>,
/// Profile declarations in canonical manifest order.

View File

@ -96,11 +96,11 @@ pub fn parse_document(text: &str) -> Result<TextDocument, TextError> {
)?;
// document: mandatory, immediately after the header.
let document_id = require_line(
let (document_id, manifest_schema_version) = require_line(
&mut lines,
"document",
"a projection must carry a document line immediately after its header",
parse_document_id_line,
parse_document_line,
)?;
// lineage?
@ -176,6 +176,7 @@ pub fn parse_document(text: &str) -> Result<TextDocument, TextError> {
Ok(TextDocument {
document_id,
manifest_schema_version,
lineage_id,
profiles,
extensions,
@ -404,13 +405,18 @@ fn parse_header(s: &Sexp) -> Result<(), TextError> {
Ok(())
}
/// `document ::= "(document " bytes ")"`.
fn parse_document_id_line(s: &Sexp) -> Result<DocumentId, TextError> {
let fields = s.expect_struct("document", 1)?;
Ok(DocumentId(parse_id16(
&fields[0],
"a DocumentId is exactly 16 bytes",
)?))
/// `document ::= "(document " bytes " " schema ")"`.
///
/// The `schema` field is the carried manifest `SchemaVersion` (G-minor,
/// `spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pins 8/11; companion 0.10.0):
/// parsed back verbatim, never re-derived — this companion has no
/// `epiphany-layout-ir` dependency and cannot decode edit-barrier bytes to
/// check it.
fn parse_document_line(s: &Sexp) -> Result<(DocumentId, SchemaVersion), TextError> {
let fields = s.expect_struct("document", 2)?;
let document_id = DocumentId(parse_id16(&fields[0], "a DocumentId is exactly 16 bytes")?);
let manifest_schema_version = parse_schema_version(&fields[1])?;
Ok((document_id, manifest_schema_version))
}
/// `lineage ::= "(lineage " bytes ")"`.
@ -643,12 +649,12 @@ mod tests {
}
// Bumped with `COMPANION_VERSION` (0.7.0 → 0.8.0, genesis G1; 0.8.0 →
// 0.9.0, genesis G2a). 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 9 0))";
const DOCUMENT: &str = "(document #x00000000000000000000000000000001)";
// 0.9.0, genesis G2a; 0.9.0 → 0.10.0, G-minor). 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 10 0))";
const DOCUMENT: &str = "(document #x00000000000000000000000000000001 (schema 0 1))";
/// A minimal but complete valid projection: just the two mandatory lines.
fn minimal_valid_document() -> String {
@ -852,10 +858,10 @@ mod tests {
assert!(parse_document(&minimal_valid_document()).is_ok());
}
/// (s9) Genesis tranche G2a (`spec/CONTRACT_GENESIS_G2A_SETTINGS.md`):
/// `(0 8 0)` — the version this crate implemented *before* this
/// packet's `kind` grammar extension — must now be rejected, not merely
/// "some other version". This is the specific case
/// G-minor (`spec/PLAN_GMINOR_SCHEMA_MINOR.md`, s14): `(0 9 0)` — the
/// version this crate implemented *before* this packet's `document`
/// grammar extension — must now be rejected, not merely "some other
/// version". This is the specific case
/// `req:textproj:header-version`'s reject-all-others clause exists to
/// guard, and it is the one a lenient "any `(0 x 0)`" parser would still
/// pass.
@ -865,7 +871,7 @@ mod tests {
/// test dies, since `(0 8 0)` would then parse.
#[test]
fn the_immediately_superseded_companion_version_is_rejected() {
let text = projection(&["(text-projection (0 8 0))", DOCUMENT]);
let text = projection(&["(text-projection (0 9 0))", DOCUMENT]);
assert_eq!(
parse_document(&text),
Err(TextError::NotCanonical(
@ -874,6 +880,17 @@ mod tests {
);
}
/// s14: the committed companion version parses, and the immediately
/// superseded version is rejected — restated explicitly under its
/// contract label (`spec/CONTRACT_GMINOR_IMPLEMENTATION.md`), on top of
/// the two tests immediately above which already exercise both halves.
#[test]
fn s14_the_committed_corpus_parses_at_0_10_0_and_0_9_0_is_rejected() {
assert!(parse_document(&minimal_valid_document()).is_ok());
let superseded = projection(&["(text-projection (0 9 0))", DOCUMENT]);
assert!(parse_document(&superseded).is_err());
}
#[test]
fn a_blob_line_is_always_rejected() {
let blob_line = "(blob \"audio/wav\" () #x00)";

View File

@ -171,11 +171,15 @@ pub fn project_header() -> Sexp {
])
}
/// `document ::= "(document " bytes ")" LF`.
pub fn project_document(document_id: &DocumentId) -> Sexp {
/// `document ::= "(document " bytes " " schema ")" LF`.
///
/// The `schema` field is the carried manifest `SchemaVersion` (G-minor,
/// `spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4, pins 8/11; companion 0.10.0).
pub fn project_document(document_id: &DocumentId, manifest_schema_version: &SchemaVersion) -> Sexp {
Sexp::List(vec![
Sexp::sym("document"),
Sexp::Bytes(document_id.as_bytes().to_vec()),
project_schema(manifest_schema_version),
])
}
@ -491,6 +495,9 @@ pub fn document_from_bundle<S: BlockStore>(
Ok(TextDocument {
document_id: manifest.document_id,
// Carried from the superblock, never derived (pin 8/11): `Manifest`
// itself has no schema-version field (it lives in the superblock).
manifest_schema_version: bundle.superblock().manifest_schema_version,
lineage_id: manifest.lineage_id,
// `Manifest::decode`'s own re-encode check (see `epiphany-bundle`)
// guarantees a bundle's `profile_declarations` are already the
@ -518,7 +525,10 @@ pub fn document_from_bundle<S: BlockStore>(
pub fn project_text_document(document: &TextDocument) -> String {
let mut lines: Vec<Sexp> = Vec::new();
lines.push(project_header());
lines.push(project_document(&document.document_id));
lines.push(project_document(
&document.document_id,
&document.manifest_schema_version,
));
if let Some(lineage_id) = &document.lineage_id {
lines.push(project_lineage(lineage_id));
}
@ -587,8 +597,8 @@ mod tests {
fn document_id_matches_the_worked_example() {
let id = DocumentId([0x05; 16]);
assert_eq!(
project_document(&id).render(),
"(document #x05050505050505050505050505050505)"
project_document(&id, &SchemaVersion::V0).render(),
"(document #x05050505050505050505050505050505 (schema 0 1))"
);
}
@ -1095,6 +1105,7 @@ mod tests {
let rich = document_from_bundle(&build_sample_bundle()).expect("bundle reads cleanly");
let minimal = TextDocument {
document_id: DocumentId([6; 16]),
manifest_schema_version: SchemaVersion::V0,
lineage_id: None,
profiles: vec![ProfileDeclaration::full()],
extensions: Vec::new(),

View File

@ -57,7 +57,7 @@ use epiphany_bundle::{
FileUuid, Manifest, SchemaVersion, SnapshotRef, StagedChunk,
};
use epiphany_determinism::CanonicalEncode;
use epiphany_ops::OperationEnvelope;
use epiphany_ops::{operation_block_introduced_minor, OperationEnvelope};
use crate::TextDocument;
@ -150,7 +150,12 @@ pub fn serialize_document<S: BlockStore>(
}
staged.push(stage_operation_envelope_block(document));
bundle.commit(&staged, |ctx| build_manifest(document, ctx))?;
// The carried manifest SchemaVersion is supplied explicitly (G-minor pin
// 6.1/8/11): this companion never derives it, it round-trips exactly
// what the document declares.
bundle.commit_versioned(&staged, document.manifest_schema_version, |ctx| {
build_manifest(document, ctx)
})?;
Ok(bundle)
}
@ -186,7 +191,11 @@ fn stage_operation_envelope_block(document: &TextDocument) -> StagedChunk {
.map(OperationEnvelope::schema_major)
.max()
.unwrap_or(0);
StagedChunk::operation_block_versioned(encode_block(&payloads), SchemaVersion::for_major(major))
let epoch_max = operation_block_introduced_minor(&document.envelopes);
StagedChunk::operation_block_versioned(
encode_block(&payloads),
SchemaVersion::for_major_at_epoch(major, epoch_max),
)
}
/// Builds the committed manifest from the previous (empty) manifest and the
@ -279,6 +288,7 @@ mod tests {
fn minimal_document(seed: u64) -> TextDocument {
TextDocument {
document_id: DocumentId([seed as u8; 16]),
manifest_schema_version: SchemaVersion::V0,
lineage_id: None,
profiles: vec![base_profile()],
extensions: Vec::new(),
@ -341,6 +351,20 @@ mod tests {
Bundle::open(MemStore::from_bytes(image)).expect("the serialized bundle reopens")
}
#[test]
fn s13_text_document_round_trips_the_carried_manifest_schema_version() {
let mut document = minimal_document(42);
document.manifest_schema_version = SchemaVersion::new(0, 8);
let reopened = serialize_and_reopen(&document);
let bundle_document =
crate::project::document_from_bundle(&reopened).expect("bundle reads cleanly");
assert_eq!(
bundle_document.manifest_schema_version,
SchemaVersion::new(0, 8),
"the carried manifest SchemaVersion must round-trip exactly, never the baseline"
);
}
#[test]
fn document_identity_round_trips() {
let mut document = minimal_document(1);
@ -470,11 +494,12 @@ mod tests {
expected_major > 0,
"fixture must include a schema-major-bearing operation to exercise the derivation"
);
let expected_epoch_max = operation_block_introduced_minor(&document.envelopes);
let reopened = serialize_and_reopen(&document);
let root = reopened.manifest().operation_roots[0];
assert_eq!(
root.schema_version,
SchemaVersion::for_major(expected_major)
SchemaVersion::for_major_at_epoch(expected_major, expected_epoch_max)
);
}

View File

@ -202,6 +202,7 @@ fn base(snapshot_byte: u8) -> TextCanonicalBase {
fn accept_documents() -> Vec<(&'static str, String)> {
let minimal = TextDocument {
document_id: DocumentId([1; 16]),
manifest_schema_version: SchemaVersion::V0,
lineage_id: None,
profiles: profiles(false),
extensions: Vec::new(),
@ -211,6 +212,7 @@ fn accept_documents() -> Vec<(&'static str, String)> {
};
let lineage_custom = TextDocument {
document_id: DocumentId([2; 16]),
manifest_schema_version: SchemaVersion::V0,
lineage_id: Some(LineageId([0x12; 16])),
profiles: profiles(true),
extensions: Vec::new(),
@ -220,6 +222,10 @@ fn accept_documents() -> Vec<(&'static str, String)> {
};
let extension_base_multi = 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
// ubiquitous baseline (G-minor pin 8/11).
manifest_schema_version: SchemaVersion::new(0, 8),
lineage_id: None,
profiles: profiles(false),
extensions: vec![extension(1, &[1, 2])],
@ -229,6 +235,7 @@ fn accept_documents() -> Vec<(&'static str, String)> {
};
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])],
@ -322,17 +329,17 @@ 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
// G2a moved `COMPANION_VERSION` to 0.9.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.8.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. The
// G-minor rung moved `COMPANION_VERSION` to 0.10.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.9.0, the immediately superseded companion, which is the better test
// anyway: 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 10 0))",
"(text-projection (0 9 0))",
"(text-projection (0 8 0))",
);
vectors.push((
SURFACE,

Binary file not shown.

View File

@ -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.12.0 --- The genesis operation tranche reaches the wire (G1 + G2a)}\\[4pt]
{\normalsize\color{epiphanyink}Version 0.13.0 --- The chunk schema minor becomes a derived record (G-minor)}\\[4pt]
{\small\color{epiphanyslate}Normative for the byte layouts it defines}
\vfill
\end{titlepage}
@ -2370,24 +2370,54 @@ The \emph{only} minor-additive mechanism in schema major~0 is
\textbf{appending discriminants to open vocabularies}:
\begin{itemize}
\item \texttt{OperationKind}: append at ${\geq}\,\tablenums{30}$
\item \texttt{OperationKind}: append at ${\geq}\,\tablenums{34}$
(Requirement~\ref{req:binfmt:kind-discriminants}; the Phase-3
tranche took \tablenums{24}--\tablenums{27} under this mechanism,
and the repeat pair \tablenums{28}/\tablenums{29} in the
schema-major-2 revision --- the discriminant space is one append-only
table across majors, but a kind's \emph{block stamp} follows minimal
stamping over its payload: \tablenums{28}'s payload embeds a v2
layout, so its blocks stamp major~2,
Section~\ref{sec:evolution:major2});
\item \texttt{OperationKindTag}: append at ${\geq}\,\tablenums{30}$
(Requirement~\ref{req:binfmt:kind-tag});
the repeat pair \tablenums{28}/\tablenums{29} in the
schema-major-2 revision, Push~4a took \tablenums{30}
(\texttt{TransposeInterval}), genesis tranche G1 took \tablenums{31}
(\texttt{CreateInstrument}), and genesis tranche G2a took
\tablenums{32}/\tablenums{33} (\texttt{SetCanvasLayoutDefaults},
\texttt{SetSpellingPrecedence}) --- the discriminant space is one
append-only table across majors, but a kind's \emph{block stamp} follows
minimal stamping over its payload: \tablenums{28}'s payload embeds a v2
layout, so its blocks stamp major~2, Section~\ref{sec:evolution:major2});
\item \texttt{OperationKindTag}: append at ${\geq}\,\tablenums{34}$
(Requirement~\ref{req:binfmt:kind-tag}; the same ten variants as
\texttt{OperationKind}, at the same ten discriminants, appended by the
same five tranches);
\item \texttt{OperationPayload}: append at ${\geq}\,\tablenums{4}$
(Section~\ref{sec:ops:payload});
(Section~\ref{sec:ops:payload}; \texttt{ResolveEquivocation} = 3, Push~3,
is the one and only append this vocabulary has had);
\item the value-layer unions enumerated as closed in
Requirement~\ref{req:binfmt:frozen-layout} may gain appended variants
only through a ratified revision of this document, also minor-additive.
\end{itemize}
\paragraph{The schema-minor derivation (G-minor).} Every appended
discriminant above is annotated with a \emph{global additive epoch}: one
schema-minor value shared across every vocabulary, assigned in the order the
revision that introduced it landed
(\texttt{spec/PLAN\_GMINOR\_SCHEMA\_MINOR.md}~\S4). An envelope's required
minor is the maximum epoch over every discriminant it actually emits --- its
outer \texttt{OperationPayload} variant, its primitive \texttt{OperationKind}
when \texttt{Primitive}, and any nested additive variant its encoder reaches;
a block's is the maximum over its envelopes. The emitted version is
$\{\text{major}, \max(\text{baseline\_minor}(\text{major}),
\text{epoch\_max})\}$: existing baselines (\tablenums{1} for major~0,
\tablenums{0} for majors~1--3) are never lowered, and a block emitting only
baseline vocabulary keeps its major's baseline minor rather than being
normalized to zero. This is content-minimal per-role stamping, not a single
document-wide counter: the canonical base, the manifest, and each operation
block are each stamped from only the discriminants their own bytes emit
(Section~\ref{sec:evolution:additive}'s per-chunk framing extends
unchanged). The manifest reaches \texttt{OperationKindTag} independently of
any operation envelope, through
\texttt{ExtensionDeclaration.edit\_barriers} $\to$
\texttt{EditBarrier.prohibited\_operation\_kinds}: a manifest naming a
barrier on a post-baseline tag takes that tag's epoch, and the manifest
major stays~0 regardless.
\texttt{ChunkKind} is \textbf{closed} --- it has no \texttt{Registered}
variant and no append story inside major~0, because its discriminant enters
every chunk's hash preimage; a new chunk kind is a format-major event.
@ -3541,6 +3571,21 @@ only}: implementations need not agree on an error taxonomy.
Catalog \sectionsc{CreateInstrument} (retroactive),
\sectionsc{SetCanvasLayoutDefaults}, \sectionsc{SetSpellingPrecedence},
0.10.0. \\
\today & Schema Evolution & 0.13.0 --- G-minor
(\texttt{spec/CONTRACT\_GMINOR\_IMPLEMENTATION.md},
\texttt{spec/PLAN\_GMINOR\_SCHEMA\_MINOR.md}): implements the minor
\MUST{} of Section~\ref{sec:evolution:gate} for the first time
(\texttt{binary\_format.tex:2330}, unchanged in substance, wired to a real
writer-side derivation for the first time). Adds the global additive-epoch
derivation (this chapter, \sectionsc{What ``Additive'' Means Here}) and
repairs the stale \tablenums{30} next-free-slot narrative this section
previously carried, which omitted \tablenums{30}--\tablenums{33}
(\texttt{TransposeInterval}, \texttt{CreateInstrument},
\texttt{SetCanvasLayoutDefaults}, \texttt{SetSpellingPrecedence}) from both
the number and the history; the normative kind and tag tables were already
current. No discriminant, wire layout, or accept-set changes: this is a
correction to prose and a new writer-side derivation, not a new appended
variant. \\
\bottomrule
\end{longtable}

Binary file not shown.

View File

@ -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.9.0 --- The genesis settings setters reach the grammar}\\[4pt]
{\normalsize\color{epiphanyink}Version 0.10.0 --- The carried manifest schema version reaches the document line}\\[4pt]
{\small\color{epiphanyslate}Normative for the text form it defines}
\vfill
\end{titlepage}
@ -454,8 +454,10 @@ A projection is, in order:
\begin{enumerate}
\item a \texttt{(text-projection <version>)} header line, naming the version of
\emph{this companion} the text conforms to;
\item a \texttt{(document \#x<document-id>)} line, and a
\texttt{(lineage \#x<lineage-id>)} line if the manifest declares one;
\item a \texttt{(document \#x<document-id> (schema <major> <minor>))} line ---
the manifest's carried \texttt{SchemaVersion} is projected alongside the
document id, verbatim and never re-derived (Requirement~\ref{req:textproj:manifest-schema-carried}) ---
and a \texttt{(lineage \#x<lineage-id>)} line if the manifest declares one;
\item zero or more \texttt{(profile ...)} lines, in canonical order;
\item zero or more \texttt{(extension ...)} lines, in canonical order;
\item at most one \texttt{(canonical-base ...)} line;
@ -464,10 +466,24 @@ A projection is, in order:
(core specification Appendix~D).
\end{enumerate}
\begin{requirement}
\label{req:textproj:manifest-schema-carried}
The \texttt{document} line \MUST{} carry the manifest's aggregate
\texttt{SchemaVersion} (core specification Chapter~8,
\sectionsc{Schema Versioning}) as its second field. A binary-to-text
projector \MUST{} copy this value verbatim from the bundle superblock, and
a text-to-binary serializer \MUST{} use the value declared by the document
line verbatim as the bundle's manifest schema version. This companion
\MUSTNOT{} derive or validate the value from
\texttt{ExtensionDeclaration.edit\_barriers}; those bytes are opaque at
this layer. A document author editing opaque barrier bytes is responsible
for updating the field to match.
\end{requirement}
\begin{requirement}
\label{req:textproj:header-version}
A parser implementing this companion \MUST{} accept exactly one header
version: \texttt{(0 9 0)}, the version of the companion it implements. It
version: \texttt{(0 10 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
@ -518,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.9.0, neither a canonical operation nor canonical reduced state can
version~0.10.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}
@ -960,7 +976,8 @@ projection ::= header document lineage? profile* extension*
header ::= "(text-projection " version ")" LF
version ::= "(" integer " " integer " " integer ")"
document ::= "(document " bytes ")" LF
document ::= "(document " bytes " " schema ")" LF
; id, then the carried manifest SchemaVersion (G-minor)
lineage ::= "(lineage " bytes ")" LF
profile ::= "(profile " profile-id " " version " " constraints ")" LF
@ -1120,8 +1137,8 @@ A document of one operation --- a transposition of two pitches up a perfect
fifth, over a compacted base --- projects to five lines:
\begin{lstlisting}
(text-projection (0 9 0))
(document #x05050505050505050505050505050505)
(text-projection (0 10 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))))
@ -1338,6 +1355,22 @@ absorb it, exactly as the binary decoder does.
extending the grammar would leave two mutually incompatible grammars both
claiming \texttt{(0 8 0)}. Cached projections at \texttt{(0 8 0)} do not
migrate; a stale \texttt{TextProjection} chunk is regenerated, not
converted. \\
\today & Chapters 8, 5 & 0.10.0 --- The carried manifest schema version
reaches the \texttt{document} line (the G-minor rung,
\texttt{spec/PLAN\_GMINOR\_SCHEMA\_MINOR.md}). The \texttt{document}
production gains a second field: the manifest's aggregate
\texttt{SchemaVersion}, carried verbatim and never derived
(\texttt{req:textproj:manifest-schema-carried}).
This bump is caused by the manifest attribute alone, \emph{not} by
operation-block schema-minor stamping: an operation block's physical schema
is discarded during projection exactly as before, and a reader must not
infer that op-block minors reach the text surface from this entry. The reasoning is the
same forcing argument as every prior grammar-extending bump: holding the
version while adding a field would leave two mutually incompatible grammars
both claiming \texttt{(0 9 0)}. Cached projections at \texttt{(0 9 0)} do not
migrate; a stale \texttt{TextProjection} chunk is regenerated, not
converted. \\
\bottomrule
\end{longtable}

View File

@ -22,16 +22,16 @@
# document bytes are normative. `<utf8-hex>` is lowercase with no separators.
# textproj.document
textproj.document accept - minimal 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document accept - rich_document 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a
textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a
textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929
textproj.document accept - minimal 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document accept - rich_document 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a
textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a
textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929