diff --git a/crates/epiphany-core/DECISIONS.md b/crates/epiphany-core/DECISIONS.md index d70a697..cad85fd 100644 --- a/crates/epiphany-core/DECISIONS.md +++ b/crates/epiphany-core/DECISIONS.md @@ -1421,3 +1421,23 @@ does not exercise `Instrument` directly (the generator corpus does not walk (`epiphany-ops::vectors`) now pins a `CreateInstrument` envelope's literal bytes, which round-trips the same `Instrument` encoding through the op payload's `push_lp_bytes` wrapper — see that crate's own DECISIONS.md entry. + +## Genesis tranche G2a — `CanvasLayoutDefaults` and `SpellingPrecedence` join +## `canonical_value!` (2026-07-28) + +`spec/CONTRACT_GENESIS_G2A_SETTINGS.md` adds `SetCanvasLayoutDefaults` and +`SetSpellingPrecedence` to `epiphany-ops`, the G2a rung of the genesis ladder +(`spec/PLAN_GENESIS_OPS.md` §4). Both payloads embed the full carried value — +`CanvasLayoutDefaults` (already `Codec` via `struct_codec!`, `codec.rs:2115`) +and `SpellingPrecedence` (already a hand-written `impl Codec`, `codec.rs:1144`) +— so this crate's one required change is two more `canonical_value!` lines. No +new byte layout, same as G1's `Instrument` entry above. + +Both types are schema major 0 as standalone payloads: `SpellingPrecedence` has +never been versioned (the v0/v1/v2 decode walks and the live `Codec` all read +it through plain `Codec::dec`), and `CanvasLayoutDefaults`'s versioning lives +in the *containing* `Canvas` walk (`dec_canvas_v0` default-fills the field; +`enc_canvas_v1` writes it through the live `Codec`), not in the leaf type +itself. So neither op gains a `schema_major()` arm — both fall into the +existing `_ => 0` catch-all in `epiphany-ops`. This is unlike G1's +`CreateInstrument`, whose carried `Instrument` has mandatory major-2 appends. diff --git a/crates/epiphany-core/src/codec.rs b/crates/epiphany-core/src/codec.rs index 24ba1a8..e51aaff 100644 --- a/crates/epiphany-core/src/codec.rs +++ b/crates/epiphany-core/src/codec.rs @@ -3535,6 +3535,16 @@ canonical_value! { TuningScope, SmuflVersionRequirement, SmuflVersion, + // Genesis tranche G2a (`CONTRACT_GENESIS_G2A_SETTINGS.md`) — + // SetCanvasLayoutDefaults and SetSpellingPrecedence embed the full value, + // mirroring SetMetadata's `ScoreMetadata`. Both already have a `Codec` + // (`struct_codec!` for `CanvasLayoutDefaults` at `:2115`; the hand-written + // `impl Codec for SpellingPrecedence` at `:1144`) and already ship inside + // `Score`; this makes that existing layout reachable per-value, same as + // every other entry here. Neither carries a schema-major-1+ layout of its + // own — see the contract's "Why these two" table. + CanvasLayoutDefaults, + SpellingPrecedence, } #[cfg(test)] diff --git a/crates/epiphany-editor-core/src/barriers.rs b/crates/epiphany-editor-core/src/barriers.rs index d792c5a..e0e1525 100644 --- a/crates/epiphany-editor-core/src/barriers.rs +++ b/crates/epiphany-editor-core/src/barriers.rs @@ -19,8 +19,9 @@ //! event sits in, say) are *not* treated as edited objects; protecting a //! container is what the barrier's **scope** is for, and scope is matched //! against the target's real containment, precisely. -//! * **Score-level operations** (`SetMetadata`, the transaction descriptor) -//! name no graph object: only a score-wide barrier (empty +//! * **Score-level operations** (`SetMetadata`, the transaction descriptor, +//! `SetCanvasLayoutDefaults`, `SetSpellingPrecedence`) name no graph object: +//! only a score-wide barrier (empty //! `affected_object_kinds`, `WholeScore`/`TuningContext`/`Registered` scope) //! can match them. //! * **Extension-defined operations** (`OperationKind::Registered`) carry a @@ -465,9 +466,10 @@ pub(crate) fn subjects_of(kind: &OperationKind, score: &Score) -> BarrierSubject Some(op.staff_instance), ), ), - OperationKind::SetMetadata(_) | OperationKind::DeclareTransaction(_) => { - BarrierSubjects::ScoreWide - } + OperationKind::SetMetadata(_) + | OperationKind::DeclareTransaction(_) + | OperationKind::SetCanvasLayoutDefaults(_) + | OperationKind::SetSpellingPrecedence(_) => BarrierSubjects::ScoreWide, OperationKind::CreateRepeatStructure(op) => one( TypedObjectId::RepeatStructure(op.repeat_structure_id()), repeat_context(score, &op.repeat), diff --git a/crates/epiphany-layout-ir/src/barrier.rs b/crates/epiphany-layout-ir/src/barrier.rs index 53ac9f5..dbafacf 100644 --- a/crates/epiphany-layout-ir/src/barrier.rs +++ b/crates/epiphany-layout-ir/src/barrier.rs @@ -1100,25 +1100,28 @@ mod tests { tag: 7 }) ); - // Operation-kind tag 32 is one past the vocabulary (the Phase-3 ops + // Operation-kind tag 34 is one past the vocabulary (the Phase-3 ops // tranche appended 24..=27, the repeat pair 28/29, `TransposeInterval` - // 30, genesis G1's `CreateInstrument` 31; encodings are append-only). + // 30, genesis G1's `CreateInstrument` 31, genesis G2a's + // `SetCanvasLayoutDefaults` 32 and `SetSpellingPrecedence` 33; + // encodings are append-only). // - // This assertion named 30 until Push 5 / P4, and 31 until genesis G1 — - // each time, by then, the number had become a real kind, so the test - // was pinning a bug: a barrier that prohibited the new operation - // encoded fine and would not read back. It must be bumped by every - // tranche that appends a tag, and it is deliberately a literal rather - // than `PAYLOAD_FREE.len()` so the bump is a conscious act. + // This assertion named 30 until Push 5 / P4, 31 until genesis G1, and + // 32 until genesis G2a — each time, by then, the number had become a + // real kind, so the test was pinning a bug: a barrier that prohibited + // the new operation encoded fine and would not read back. It must be + // bumped by every tranche that appends a tag, and it is deliberately a + // literal rather than `PAYLOAD_FREE.len()` so the bump is a conscious + // act. let mut bytes = vec![0u8]; bytes.extend(set_blob(&[])); - bytes.extend(set_blob(&[vec![32u8]])); + bytes.extend(set_blob(&[vec![34u8]])); bytes.push(0); assert_eq!( EditBarrier::decode_canonical_bytes(&bytes), Err(BarrierDecodeError::InvalidTag { kind: "OperationKindTag", - tag: 32 + tag: 34 }) ); } diff --git a/crates/epiphany-ops/DECISIONS.md b/crates/epiphany-ops/DECISIONS.md index adc5235..2783360 100644 --- a/crates/epiphany-ops/DECISIONS.md +++ b/crates/epiphany-ops/DECISIONS.md @@ -1813,3 +1813,78 @@ are green (`epiphany-layout-ir` excepted, for the one stale-vocabulary test above). This is reported to the coordinator rather than worked around; see the packet's final report for the exact reproduction commands and error text. + +## Genesis tranche G2a — `SetCanvasLayoutDefaults` / `SetSpellingPrecedence`, +## kind 32/33, tag 32/33 (2026-07-28) + +`spec/CONTRACT_GENESIS_G2A_SETTINGS.md` lands the second rung of the genesis +ladder: two more `Score` fields become operation-authored, both on the +`SetMetadata` LWW pattern (`reduce.rs`) — advisory last-writer-wins, no +conflict, no idempotence short-circuit, seeded from the base for +value-restoring undo. Unlike G1's `CreateInstrument`, both new kinds are +schema major 0 *unconditionally*: neither carried type (`CanvasLayoutDefaults`, +`SpellingPrecedence`) has ever had more than one wire layout as a standalone +value, so both fall through `OperationKind::schema_major`'s existing `_ => 0` +catch-all with **no new arm** — pin 3's explicit trap (adding them to the +`=> 2` arm alongside `SetMetadata` would be the bug, since `SetMetadata` +sits there for a property of `ScoreMetadata`, not of the LWW pattern itself). +Verified by mutation: temporarily joining the `=> 2` arm kills both +`schema_majors_follow_the_minimal_stamping_rule` (`reduce.rs`) and a +dedicated `epiphany-testkit` assertion. + +**This packet does not touch `epiphany-bundle`, and the boundary crossing +this time is exactly the three sites the contract named** — `barriers.rs`, +`layout-ir/src/barrier.rs`, and `text_projection_grammar.rs` — no wider than +scoped, unlike G1's discovery that the crossing itself was mis-scoped in the +contract. Both new kinds join the existing `SetMetadata(_) | +DeclareTransaction(_)` score-wide arm in `barriers.rs::subjects_of` (they are +score-level field overwrites with no resolvable region or object, exactly +like `SetMetadata`); the layout-ir "one past the vocabulary" literal moves +32→34; the testkit grammar kind-count literal moves 32→34. + +**`ops/src/envdecode.rs::sample_kind` and `ops/src/textproj_kind.rs::parse` +are both `match` statements exhaustive over `OperationKindTag`** +(compile-enforced, unlike `OperationKind::discriminant()`), so both needed a +new arm per kind — correctly named by the contract's touch-point table +(`envdecode.rs`'s "tag→kind (:819)" is exactly `sample_kind`; +`textproj_kind.rs`'s "parse (:443)" is exactly this match), verified against +the tree rather than assumed. Both compiled to a hard error the moment the +new `OperationKindTag` variants existed without a corresponding arm, so +neither site could have been missed silently. + +**Two more hand-maintained generator sites went stale by two tranches, not +one**, confirming the contract's row-28/29 framing: `testkit/src/ +generators.rs::operation_payload`'s `rng.below(30)` bound had never grown +past the repeat pair (28/29), so `TransposeInterval` (kind 30, Push-4a debt) +and `CreateInstrument` (kind 31, G1 debt) were absent from every corpus this +generator feeds, alongside this packet's own new kinds. Widened to +`rng.below(34)` with all four appended; `testkit/src/ +layout_stub.rs::gen_operation_kind_tag` was rewritten to derive its built-in +draw from `OperationKindTag::PAYLOAD_FREE` (excluding `Registered`, appended +explicitly) rather than extended by hand — the structural fix the contract's +blast-radius note asked for, following the in-crate `all_tags()` precedent +(`payload.rs`). Five of the six hand-maintained sites the contract's +blast-radius note lists remain manual for the next append; only row 29 was +convertible to a derivation. + +**Test s7's three-times-wrong framing is now four-times right.** The +contract's own text records that two earlier framings of "transaction +rollback discards the write, not just the field" could not see their own +mutation. The third framing — author inside a failed transaction, write +again in a second transaction, undo the second, and assert the restored +value is the pre-failure predecessor rather than the rolled-back write — +was mutation-verified live: omitting `canvas_layout_defaults_chain` from +`Reducer::restore` (keeping it in `snapshot`) makes the test observe the +rolled-back transaction's value survive the undo instead of the genuine +predecessor. Confirmed to fail for exactly that reason, not incidentally. + +**The canonical-base re-pin (`the_canonical_base_is_byte_identical_across_ +data_model_majors`) is a corpus shift, not a value leak** — verified by +construction, not by re-running and hoping: `MaterializedState` embeds no +`Score` field value for any setting, `SetMetadata` included, and both new +kinds are schema major 0 unconditionally, so there is no schema-major +surface for a leak to appear on even before checking the hash. `gen_payload` +(`fuzz.rs`) widened from `rng.below(29)` to `rng.below(31)` with the two new +arms appended, reshuffling the seeded RNG stream exactly as at Phase D, Push +4a, and G1; the digest moved and was re-pinned with that reasoning recorded +in the test's own comment. diff --git a/crates/epiphany-ops/src/envdecode.rs b/crates/epiphany-ops/src/envdecode.rs index afde534..81ffa53 100644 --- a/crates/epiphany-ops/src/envdecode.rs +++ b/crates/epiphany-ops/src/envdecode.rs @@ -36,10 +36,11 @@ use epiphany_core::{ }; use epiphany_core::{CanonicalValue, TempoSegment}; use epiphany_core::{ - EventId, Instrument, InstrumentId, MetricGrid, MusicalPosition, OperationId, PitchId, - PitchSpelling, RegionId, RegionTimeModel, RepeatStructureId, ReplicaId, ScoreMetadata, Staff, - StaffInstance, StaffInstanceId, StaffLineConfiguration, TimeAnchor, TimeSignature, - TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId, WallClockTime, + CanvasLayoutDefaults, EventId, Instrument, InstrumentId, MetricGrid, MusicalPosition, + OperationId, PitchId, PitchSpelling, RegionId, RegionTimeModel, RepeatStructureId, ReplicaId, + ScoreMetadata, SpellingPrecedence, Staff, StaffInstance, StaffInstanceId, + StaffLineConfiguration, TimeAnchor, TimeSignature, TranspositionInterval, TupletId, + TypedObjectId, Voice, VoiceId, WallClockTime, }; use epiphany_determinism::{CanonicalDecode, CanonicalEncode}; @@ -589,6 +590,12 @@ fn operation_kind(r: &mut Reader<'_>) -> Result { 31 => OperationKind::CreateInstrument(CreateInstrumentOp { instrument: value::(r, "Instrument")?, }), + 32 => OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: value::(r, "CanvasLayoutDefaults")?, + }), + 33 => OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { + precedence: value::(r, "SpellingPrecedence")?, + }), tag => { return Err(EnvelopeDecodeError::InvalidTag { kind: "OperationKind", @@ -878,6 +885,16 @@ pub(crate) mod tests { instrument: valuegen::instrument(InstrumentId::new(ReplicaId(7), 1)), }) } + OperationKindTag::SetCanvasLayoutDefaults => { + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: valuegen::canvas_layout_defaults(1), + }) + } + OperationKindTag::SetSpellingPrecedence => { + OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { + precedence: valuegen::spelling_precedence(1), + }) + } } } diff --git a/crates/epiphany-ops/src/fuzz.rs b/crates/epiphany-ops/src/fuzz.rs index cd66c1e..ce3b744 100644 --- a/crates/epiphany-ops/src/fuzz.rs +++ b/crates/epiphany-ops/src/fuzz.rs @@ -90,7 +90,7 @@ fn pitch(n: u64) -> PitchId { /// Generates a random payload over the shared id space. fn gen_payload(rng: &mut SplitMix64) -> OperationPayload { - let kind = match rng.below(29) { + let kind = match rng.below(31) { 0 => { let voice = VoiceId::new(ReplicaId(7), rng.below(3)); let position = MusicalPosition(RationalTime::from_int(rng.below(4) as i32)); @@ -293,6 +293,13 @@ fn gen_payload(rng: &mut SplitMix64) -> OperationPayload { rng.below(2), )), }), + // Genesis tranche G2a: the two major-0 settings setters. + 29 => OperationKind::SetCanvasLayoutDefaults(crate::payload::SetCanvasLayoutDefaultsOp { + layout_defaults: valuegen::canvas_layout_defaults(rng.below(3) as u8), + }), + 30 => OperationKind::SetSpellingPrecedence(crate::payload::SetSpellingPrecedenceOp { + precedence: valuegen::spelling_precedence(rng.below(3) as u8), + }), _ => OperationKind::SetStaffLayout(SetStaffLayoutOp { staff_instance: StaffInstanceId::new(ReplicaId(7), rng.below(3)), instrument_override: None, diff --git a/crates/epiphany-ops/src/lib.rs b/crates/epiphany-ops/src/lib.rs index 582f620..f6204c2 100644 --- a/crates/epiphany-ops/src/lib.rs +++ b/crates/epiphany-ops/src/lib.rs @@ -130,10 +130,10 @@ pub use payload::{ DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, OperationKind, OperationKindTag, OperationPayload, PositionRemapping, ResolveConflictPayload, - ResolveEquivocationPayload, RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp, - SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, - TransactionCategory, TransactionDescriptor, TransposeIntervalOp, TransposeOp, - TupletCompensation, + ResolveEquivocationPayload, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp, + SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp, + SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionCategory, + TransactionDescriptor, TransposeIntervalOp, TransposeOp, TupletCompensation, }; pub use reduce::{ canonical_reduction_order, GraphMaterialization, MaterializedState, ObjectState, PendingReason, diff --git a/crates/epiphany-ops/src/migrate.rs b/crates/epiphany-ops/src/migrate.rs index f8cd473..1ce513f 100644 --- a/crates/epiphany-ops/src/migrate.rs +++ b/crates/epiphany-ops/src/migrate.rs @@ -181,6 +181,13 @@ fn project_kind(kind: &OperationKind) -> V0OperationKind { OperationKind::TransposeInterval(op) => V0OperationKind::TransposeInterval(op.clone()), // Genesis tranche G1: born past v0; projected verbatim. OperationKind::CreateInstrument(op) => V0OperationKind::CreateInstrument(op.clone()), + // Genesis tranche G2a: born past v0; projected verbatim. + OperationKind::SetCanvasLayoutDefaults(op) => { + V0OperationKind::SetCanvasLayoutDefaults(op.clone()) + } + OperationKind::SetSpellingPrecedence(op) => { + V0OperationKind::SetSpellingPrecedence(op.clone()) + } } } @@ -336,6 +343,13 @@ fn migrate_kind(kind: &V0OperationKind, context: &Score) -> Result OperationKind::DeleteRepeatStructure(*op), // Genesis tranche G1: identity round-trip (no lossy v0 form). V0OperationKind::CreateInstrument(op) => OperationKind::CreateInstrument(op.clone()), + // Genesis tranche G2a: identity round-trip (no lossy v0 form). + V0OperationKind::SetCanvasLayoutDefaults(op) => { + OperationKind::SetCanvasLayoutDefaults(op.clone()) + } + V0OperationKind::SetSpellingPrecedence(op) => { + OperationKind::SetSpellingPrecedence(op.clone()) + } }) } diff --git a/crates/epiphany-ops/src/payload.rs b/crates/epiphany-ops/src/payload.rs index 4c3d746..1ab6efa 100644 --- a/crates/epiphany-ops/src/payload.rs +++ b/crates/epiphany-ops/src/payload.rs @@ -32,12 +32,12 @@ //! payloads. use epiphany_core::{ - Beam, CanonicalValue, Event, EventDuration, EventId, EventPosition, IdentifiedPitch, - Instrument, InstrumentId, MetricGrid, MusicalDuration, MusicalPosition, OperationId, Pitch, - PitchId, PitchSpelling, Region, RegionId, RegionTimeModel, RepeatStructure, RepeatStructureId, - Rest, ScoreMetadata, Slur, Spanner, Staff, StaffId, StaffInstance, StaffInstanceId, - StaffLineConfiguration, TempoSegment, Tie, TimeAnchor, TimeSignature, TransactionId, - TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId, + Beam, CanonicalValue, CanvasLayoutDefaults, Event, EventDuration, EventId, EventPosition, + IdentifiedPitch, Instrument, InstrumentId, MetricGrid, MusicalDuration, MusicalPosition, + OperationId, Pitch, PitchId, PitchSpelling, Region, RegionId, RegionTimeModel, RepeatStructure, + RepeatStructureId, Rest, ScoreMetadata, Slur, Spanner, SpellingPrecedence, Staff, StaffId, + StaffInstance, StaffInstanceId, StaffLineConfiguration, TempoSegment, Tie, TimeAnchor, + TimeSignature, TransactionId, TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId, }; use epiphany_determinism::{ sorted_canonical, CanonicalDecode, CanonicalEncode, CanonicalSet, DecodeError, @@ -203,6 +203,15 @@ pub enum OperationKind { /// `CreateStaff` already demands a live `Instrument` and nothing else can /// create one. CreateInstrument(CreateInstrumentOp), + // --- Genesis tranche G2a (`spec/CONTRACT_GENESIS_G2A_SETTINGS.md`): the + // two major-0 settings setters. Discriminants extend additively past 31. + // --- + /// Overwrite the canvas's default layout advisories (later-in-canonical- + /// order wins). + SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp), + /// Overwrite the score's spelling precedence (later-in-canonical-order + /// wins). + SetSpellingPrecedence(SetSpellingPrecedenceOp), } impl OperationKind { @@ -304,6 +313,10 @@ impl OperationKind { // accident, not by rule — the two spaces are independent and // misaligned elsewhere (`RespellPitch` is kind 2 / tag 3). OperationKind::CreateInstrument(_) => 31, + // Genesis tranche G2a; appended past 31. Coincide with tags 32/33 + // by the same accident as above, not by rule. + OperationKind::SetCanvasLayoutDefaults(_) => 32, + OperationKind::SetSpellingPrecedence(_) => 33, } } @@ -349,6 +362,8 @@ impl OperationKind { // `CreateRepeatStructure`) are — the tag layer's older // Create→Insert convention is not followed here (contract pin 3). OperationKind::CreateInstrument(_) => OperationKindTag::CreateInstrument, + OperationKind::SetCanvasLayoutDefaults(_) => OperationKindTag::SetCanvasLayoutDefaults, + OperationKind::SetSpellingPrecedence(_) => OperationKindTag::SetSpellingPrecedence, } } } @@ -392,6 +407,8 @@ impl CanonicalEncode for OperationKind { OperationKind::CreateRepeatStructure(op) => op.encode_canonical(out), OperationKind::DeleteRepeatStructure(op) => op.encode_canonical(out), OperationKind::CreateInstrument(op) => op.encode_canonical(out), + OperationKind::SetCanvasLayoutDefaults(op) => op.encode_canonical(out), + OperationKind::SetSpellingPrecedence(op) => op.encode_canonical(out), } } } @@ -442,6 +459,10 @@ pub enum OperationKindTag { /// older Create→Insert convention (`InsertStaff` for `CreateStaff`) is not /// followed here, matching the two most recent additions. CreateInstrument, + /// Genesis tranche G2a. + SetCanvasLayoutDefaults, + /// Genesis tranche G2a. + SetSpellingPrecedence, } /// The discriminant of [`OperationKindTag::Registered`], the one tag that @@ -540,6 +561,8 @@ operation_kind_tag_vocabulary! { DeleteRepeatStructure = 29 => "delete-repeat-structure", TransposeInterval = 30 => "transpose-interval", CreateInstrument = 31 => "create-instrument", + SetCanvasLayoutDefaults = 32 => "set-canvas-layout-defaults", + SetSpellingPrecedence = 33 => "set-spelling-precedence", } impl CanonicalEncode for OperationKindTag { @@ -1470,6 +1493,42 @@ impl CanonicalEncode for CreateInstrumentOp { } } +// --- Genesis tranche G2a (`spec/CONTRACT_GENESIS_G2A_SETTINGS.md`): the two +// major-0 settings setters. LWW field-overwrite, exactly the `SetMetadata` +// discipline (advisory last-writer-wins, no conflict, no idempotence +// short-circuit). --- + +/// Overwrite the canvas's default layout advisories (operation_catalog +/// §SetCanvasLayoutDefaults). Carries the full [`CanvasLayoutDefaults`]; the +/// score-singleton field-overwrite is *advisory* last-writer-wins — the latest +/// write in canonical order silently wins and no conflict is recorded, exactly +/// `SetMetadata`. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct SetCanvasLayoutDefaultsOp { + pub layout_defaults: CanvasLayoutDefaults, +} + +impl CanonicalEncode for SetCanvasLayoutDefaultsOp { + fn encode_canonical(&self, out: &mut Vec) { + push_lp_bytes(out, &self.layout_defaults.canonical_bytes()); + } +} + +/// Overwrite the score's spelling precedence (operation_catalog +/// §SetSpellingPrecedence). Carries the full [`SpellingPrecedence`]; the +/// score-singleton field-overwrite is *advisory* last-writer-wins, exactly +/// `SetMetadata`. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct SetSpellingPrecedenceOp { + pub precedence: SpellingPrecedence, +} + +impl CanonicalEncode for SetSpellingPrecedenceOp { + fn encode_canonical(&self, out: &mut Vec) { + push_lp_bytes(out, &self.precedence.canonical_bytes()); + } +} + /// Set, replace, or (`None`) remove the single meter change at the anchor's /// resolved musical position in a region's default metric grid /// (operation_catalog §"Meter and Tempo Overwrites"). Carries the full diff --git a/crates/epiphany-ops/src/reduce.rs b/crates/epiphany-ops/src/reduce.rs index 7a7d995..f678ace 100644 --- a/crates/epiphany-ops/src/reduce.rs +++ b/crates/epiphany-ops/src/reduce.rs @@ -33,14 +33,14 @@ use std::collections::{BTreeMap, BTreeSet, BinaryHeap}; use epiphany_core::{ canonical_pitch_bytes, derive_promoted_voice_id, simplest_spelling, AnchorOffset, - AnnotationAnchor, CanonicalValue, Event, EventDuration, EventId, EventPosition, - GestureAnchoring, Instrument, InstrumentId, MeterChange, MetricGrid, MusicalDuration, - MusicalPosition, OperationId, Pitch, PitchId, PitchSpelling, RationalTime, RegionEdge, - RegionId, RegionTimeModel, ReplicaId, Score, ScoreMetadata, SpellingAttachment, - SpellingDirective, SpellingScope, SpellingSource, Staff, StaffId, StaffInstance, - StaffInstanceId, StaffLineConfiguration, TempoMap, TempoSegment, TempoShape, TimeAnchor, - TimeSignature, TimeSignatureId, TransactionId, TransposeRefusal, TranspositionInterval, - TypedObjectId, Voice, VoiceId, VoiceOrigin, + AnnotationAnchor, CanonicalValue, CanvasLayoutDefaults, Event, EventDuration, EventId, + EventPosition, GestureAnchoring, Instrument, InstrumentId, MeterChange, MetricGrid, + MusicalDuration, MusicalPosition, OperationId, Pitch, PitchId, PitchSpelling, RationalTime, + RegionEdge, RegionId, RegionTimeModel, ReplicaId, Score, ScoreMetadata, SpellingAttachment, + SpellingDirective, SpellingPrecedence, SpellingScope, SpellingSource, Staff, StaffId, + StaffInstance, StaffInstanceId, StaffLineConfiguration, TempoMap, TempoSegment, TempoShape, + TimeAnchor, TimeSignature, TimeSignatureId, TransactionId, TransposeRefusal, + TranspositionInterval, TypedObjectId, Voice, VoiceId, VoiceOrigin, }; use epiphany_determinism::CanonicalEncode; @@ -61,9 +61,9 @@ use crate::payload::{ CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, - OperationKind, OperationPayload, RespellPitchOp, SetMetadataOp, SetMetricGridOp, - SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp, - TransposeIntervalOp, TransposeOp, TupletCompensation, + OperationKind, OperationPayload, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp, + SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp, + SetTimeSignatureOp, SetUserPageBreakOp, TransposeIntervalOp, TransposeOp, TupletCompensation, }; use crate::stamp::StampTuple; use crate::support::{ObjectKind, SerializedCanonicalInputs}; @@ -785,6 +785,14 @@ enum ValueRestoration { Metadata { value: Option, }, + /// Genesis tranche G2a. + CanvasLayoutDefaults { + value: Option, + }, + /// Genesis tranche G2a. + SpellingPrecedence { + value: Option, + }, MetricGrid { region: RegionId, value: Option, @@ -899,6 +907,13 @@ struct Reducer<'a> { cross_cutting_modify_chain: BTreeMap>, metric_grid_chain: BTreeMap>>, metadata_chain: WriteChain, + // Genesis tranche G2a (`CONTRACT_GENESIS_G2A_SETTINGS.md` pin 6/7/8): + // score-singleton LWW chains for the two new settings setters, mirroring + // `metadata_chain` exactly — advisory last-writer-wins, seeded from the + // base so a value-restoring undo of the first operational write restores + // the pre-operational value. + canvas_layout_defaults_chain: WriteChain, + spelling_precedence_chain: WriteChain, break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>, page_break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>, // Meter/tempo overwrite chains (Phase-3 tranche): `Some` = a set/replace at @@ -1010,6 +1025,8 @@ struct WorkingSnapshot { cross_cutting_modify_chain: BTreeMap>, metric_grid_chain: BTreeMap>>, metadata_chain: WriteChain, + canvas_layout_defaults_chain: WriteChain, + spelling_precedence_chain: WriteChain, break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>, page_break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>, meter_change_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain>>, @@ -1290,6 +1307,8 @@ impl<'a> Reducer<'a> { cross_cutting_modify_chain: BTreeMap::new(), metric_grid_chain: BTreeMap::new(), metadata_chain: WriteChain::new(), + canvas_layout_defaults_chain: WriteChain::new(), + spelling_precedence_chain: WriteChain::new(), break_chain: BTreeMap::new(), page_break_chain: BTreeMap::new(), meter_change_chain: BTreeMap::new(), @@ -1383,6 +1402,15 @@ impl<'a> Reducer<'a> { // value-restoring undo of the first operational write can restore the // pre-operational state (operation_catalog §UndoTransaction). self.metadata_chain.seed(score.metadata.clone()); + // Genesis tranche G2a (contract pin 6): same discipline for the two new + // settings setters. Both are always-valued `Score` fields — like + // `metadata`, not a map key — so there is no "never authored" state to + // distinguish; restoring the seeded base default is correct whether the + // base was authored-to-default or never touched. + self.canvas_layout_defaults_chain + .seed(score.canvas.layout_defaults); + self.spelling_precedence_chain + .seed(score.spelling_precedence.clone()); for segment in &score.tempo_map.segments { self.tempo_segment_chain .entry((None, resolved_anchor_position(&segment.start))) @@ -2743,6 +2771,10 @@ impl<'a> Reducer<'a> { OperationKind::CreateRepeatStructure(op) => self.create_repeat_structure(env, op), OperationKind::DeleteRepeatStructure(op) => self.delete_repeat_structure(env, op), OperationKind::CreateInstrument(op) => self.create_instrument(env, op), + OperationKind::SetCanvasLayoutDefaults(op) => { + self.set_canvas_layout_defaults(env, op) + } + OperationKind::SetSpellingPrecedence(op) => self.set_spelling_precedence(env, op), }, OperationPayload::ResolveConflict(op) => self.resolve_conflict(env, op), OperationPayload::UndoTransaction(op) => self.undo_transaction(env, op), @@ -2823,6 +2855,40 @@ impl<'a> Reducer<'a> { OperationEffect::Applied } + /// Genesis tranche G2a (`CONTRACT_GENESIS_G2A_SETTINGS.md` pin 5): copies + /// `set_metadata` structurally — advisory LWW, no conflict, no idempotence + /// short-circuit. A re-write of an identical value is a legitimate new + /// write, not a no-op. + fn set_canvas_layout_defaults( + &mut self, + env: &OperationEnvelope, + op: &SetCanvasLayoutDefaultsOp, + ) -> OperationEffect { + self.canvas_layout_defaults_chain + .record(env.id, env.transaction, op.layout_defaults); + if let Some(score) = self.graph.as_mut() { + score.canvas.layout_defaults = op.layout_defaults; + } + OperationEffect::Applied + } + + /// Genesis tranche G2a (`CONTRACT_GENESIS_G2A_SETTINGS.md` pin 5): copies + /// `set_metadata` structurally — advisory LWW, no conflict, no idempotence + /// short-circuit. A re-write of an identical value is a legitimate new + /// write, not a no-op. + fn set_spelling_precedence( + &mut self, + env: &OperationEnvelope, + op: &SetSpellingPrecedenceOp, + ) -> OperationEffect { + self.spelling_precedence_chain + .record(env.id, env.transaction, op.precedence.clone()); + if let Some(score) = self.graph.as_mut() { + score.spelling_precedence = op.precedence.clone(); + } + OperationEffect::Applied + } + fn set_metric_grid( &mut self, env: &OperationEnvelope, @@ -5190,6 +5256,26 @@ impl<'a> Reducer<'a> { }) } } + // Genesis tranche G2a (contract pin 7): mirror `metadata_chain` for + // the two new score-singleton settings chains. + match self.canvas_layout_defaults_chain.undo_verdict(tx) { + ChainUndoVerdict::NotWritten => {} + ChainUndoVerdict::Superseded { by } => superseded.push(by), + ChainUndoVerdict::Restore(predecessor) => { + restorations.push(ValueRestoration::CanvasLayoutDefaults { + value: predecessor.map(Predecessor::into_value), + }) + } + } + match self.spelling_precedence_chain.undo_verdict(tx) { + ChainUndoVerdict::NotWritten => {} + ChainUndoVerdict::Superseded { by } => superseded.push(by), + ChainUndoVerdict::Restore(predecessor) => { + restorations.push(ValueRestoration::SpellingPrecedence { + value: predecessor.map(Predecessor::into_value), + }) + } + } for (region, chain) in &self.metric_grid_chain { if !slot_live(TypedObjectId::Region(*region)) { continue; @@ -5409,6 +5495,27 @@ impl<'a> Reducer<'a> { self.metadata_chain.record(env.id, env.transaction, value); } } + // Genesis tranche G2a (contract pin 7): mirror `Metadata`'s + // restoration-apply shape for the two new score-singleton + // settings chains. + ValueRestoration::CanvasLayoutDefaults { value } => { + if let Some(value) = value { + if let Some(score) = self.graph.as_mut() { + score.canvas.layout_defaults = value; + } + self.canvas_layout_defaults_chain + .record(env.id, env.transaction, value); + } + } + ValueRestoration::SpellingPrecedence { value } => { + if let Some(value) = value { + if let Some(score) = self.graph.as_mut() { + score.spelling_precedence = value.clone(); + } + self.spelling_precedence_chain + .record(env.id, env.transaction, value); + } + } ValueRestoration::MetricGrid { region, value } => { self.metric_grid_chain .entry(region) @@ -7384,6 +7491,8 @@ impl<'a> Reducer<'a> { cross_cutting_modify_chain: self.cross_cutting_modify_chain.clone(), metric_grid_chain: self.metric_grid_chain.clone(), metadata_chain: self.metadata_chain.clone(), + canvas_layout_defaults_chain: self.canvas_layout_defaults_chain.clone(), + spelling_precedence_chain: self.spelling_precedence_chain.clone(), break_chain: self.break_chain.clone(), page_break_chain: self.page_break_chain.clone(), meter_change_chain: self.meter_change_chain.clone(), @@ -7421,6 +7530,8 @@ impl<'a> Reducer<'a> { self.cross_cutting_modify_chain = s.cross_cutting_modify_chain; self.metric_grid_chain = s.metric_grid_chain; self.metadata_chain = s.metadata_chain; + self.canvas_layout_defaults_chain = s.canvas_layout_defaults_chain; + self.spelling_precedence_chain = s.spelling_precedence_chain; self.break_chain = s.break_chain; self.page_break_chain = s.page_break_chain; self.meter_change_chain = s.meter_change_chain; @@ -10667,6 +10778,41 @@ mod tests { 2, "CreateInstrument is unconditionally v2, even for the minimal instrument" ); + + // (s5) Genesis tranche G2a: both new setters fall into the catch-all + // `_ => 0` arm — contract pin 3 is explicit that adding them to the + // `=> 2` arm alongside `SetMetadata` would be the bug, since + // `SetMetadata` sits there because `ScoreMetadata` has mandatory + // major-2 appends, a property of *that* type and nothing else. + // Asserted on non-default values so an arm that becomes + // value-dependent in the wrong direction cannot hide behind the + // `Default`. + let non_default_layout = { + let mut d = epiphany_core::CanvasLayoutDefaults::default(); + d.page_size.width = epiphany_determinism::CanonicalF64::new(200.0).unwrap(); + d + }; + assert_eq!( + OperationKind::SetCanvasLayoutDefaults(crate::payload::SetCanvasLayoutDefaultsOp { + layout_defaults: non_default_layout, + }) + .schema_major(), + 0, + "SetCanvasLayoutDefaults stays in the major-0 catch-all" + ); + let non_default_precedence = crate::valuegen::spelling_precedence(1); + assert_ne!( + non_default_precedence, + epiphany_core::SpellingPrecedence::default() + ); + assert_eq!( + OperationKind::SetSpellingPrecedence(crate::payload::SetSpellingPrecedenceOp { + precedence: non_default_precedence, + }) + .schema_major(), + 0, + "SetSpellingPrecedence stays in the major-0 catch-all" + ); } #[test] @@ -10743,6 +10889,23 @@ mod tests { // (unconditionally, contract pin 4), but `MaterializedState` stamps // no schema major at all — that is an `OperationEnvelopeBlock` // concern in `epiphany-bundle`, which this packet does not touch. + // + // Re-pinned again at genesis tranche G2a + // (`spec/CONTRACT_GENESIS_G2A_SETTINGS.md`): `gen_payload` gained + // `SetCanvasLayoutDefaults` (arm 29) and `SetSpellingPrecedence` + // (arm 30), and `rng.below(29)` became `below(31)` — the same + // reshuffle, for the same reason: every operation id, effect, and + // conflict downstream of the first draw moves. Confirmed a corpus + // shift, not a value leak, by construction rather than inspection: + // `MaterializedState` (`reduce.rs:504`) embeds no `Score` field value + // for *any* setting, `SetMetadata` included — the base carries only + // effects, conflicts, anomalies, objects, spellings, breaks, + // page-breaks, and pending state. Neither new payload changes that + // shape, and both are schema major 0 unconditionally (pin 3: no new + // `schema_major()` arm), so there is no schema-major surface for a + // leak to appear on even if the base did stamp one (it does not — + // that is `OperationEnvelopeBlock`'s concern in `epiphany-bundle`, + // untouched by this packet). let mut rng = epiphany_determinism::fuzz::SplitMix64::new(0xBA5E); let envelopes = crate::fuzz::gen_envelope_set(&mut rng, 200); let mut set = OperationSet::new(); @@ -10752,7 +10915,7 @@ mod tests { let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect(); assert_eq!( hex, - "61af8ebbba1c4d98360ec44812e5d97a89a720738c1e3573d865f26b48addd1d" + "7fd6455a6ae304101774ba981d01702ad37c567575707b1e814dad6e153ab07d" ); } @@ -12948,4 +13111,339 @@ mod tests { "graph-aware reduction enforces the precondition base-free skipped" ); } + + // ========================================================================= + // Genesis tranche G2a (`spec/CONTRACT_GENESIS_G2A_SETTINGS.md`): the two + // major-0 settings setters, `SetCanvasLayoutDefaults` and + // `SetSpellingPrecedence`, on the `SetMetadata` LWW pattern. + // ========================================================================= + + /// (s1) From-empty authoring: through `reduce_operation_set_onto`, each + /// new setter produces its authored value in the materialized `Score`. + /// + /// **Mutation:** drop the graph write in the setter fn (`score.canvas. + /// layout_defaults = op.layout_defaults;` / `score.spelling_precedence = + /// op.precedence.clone();`) -> the field stays at its `Default` while the + /// effect still reads `Applied`. + #[test] + fn from_empty_settings_setters_materialize_the_authored_value() { + let identity = IdentityContext::new(ReplicaId(1)); + let layout = crate::valuegen::canvas_layout_defaults(1); + let precedence = crate::valuegen::spelling_precedence(1); + let set_layout = prim_env( + 1, + 0, + 10, + CausalContext::new(), + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: layout, + }), + ); + let set_precedence = prim_env( + 1, + 1, + 11, + seen_r1(0), + OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { + precedence: precedence.clone(), + }), + ); + let mut set = OperationSet::new(); + set.accept_all(vec![set_layout.clone(), set_precedence.clone()]); + let out = reduce_operation_set_onto(&set, &Score::empty(identity)); + + assert_eq!( + out.score.canvas.layout_defaults, layout, + "SetCanvasLayoutDefaults materializes in the graph" + ); + assert_eq!( + out.score.spelling_precedence, precedence, + "SetSpellingPrecedence materializes in the graph" + ); + assert_eq!(effect_at(&out.state, 0), Some(&OperationEffect::Applied)); + assert_eq!(effect_at(&out.state, 1), Some(&OperationEffect::Applied)); + } + + /// (s2) LWW: two concurrent differing writes (neither sees the other) + /// resolve to the later in canonical order, recording **no** conflict — + /// matching `SetMetadata` (`ops/tests/graph_reduction.rs:1408`). + /// + /// **Mutation:** *not* a reversed comparison — `set_canvas_layout_ + /// defaults`/`set_spelling_precedence` contain no comparison at all; they + /// record and overwrite unconditionally. Mutate setter-locally to + /// first-write-wins instead (skip the record-and-overwrite when the chain + /// already holds a write) -> the earlier value survives and this test + /// dies. + #[test] + fn concurrent_differing_settings_setters_are_advisory_lww_no_conflict() { + let identity = IdentityContext::new(ReplicaId(1)); + let layout_a = crate::valuegen::canvas_layout_defaults(1); + let layout_b = crate::valuegen::canvas_layout_defaults(2); + assert_ne!(layout_a, layout_b); + // Neither sees the other: both start from an empty causal context, so + // they are genuinely concurrent. + let a = prim_env( + 1, + 0, + 10, + CausalContext::new(), + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: layout_a, + }), + ); + let b = prim_env( + 2, + 0, + 20, + CausalContext::new(), + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: layout_b, + }), + ); + let mut set = OperationSet::new(); + set.accept_all(vec![a.clone(), b.clone()]); + let out = reduce_operation_set_onto(&set, &Score::empty(identity)); + + assert!( + out.state.conflicts.records().is_empty(), + "concurrent differing SetCanvasLayoutDefaults is advisory — no conflict" + ); + assert!( + out.state.is_clean(), + "an advisory layout-defaults edit keeps the materialized state clean" + ); + // `b` has the later physical time, so canonical order places it last; + // its value must be the one that survives. + assert_eq!( + out.score.canvas.layout_defaults, layout_b, + "the later-in-canonical-order write wins" + ); + } + + /// (s3) A re-write of an identical value is a **new write**, not a + /// no-op: assert the effect is `Applied` and not `AlreadyApplied`. This + /// test exists because pin 5 is the most likely thing for a subagent to + /// get wrong by pattern-matching on `create_staff`'s mint discipline. + /// + /// **Mutation:** add an `AlreadyApplied` short-circuit to the setter fn + /// (skip the record-and-write when the new value equals the current one) + /// -> dies. + #[test] + fn rewriting_an_identical_settings_value_is_a_new_write_not_a_no_op() { + let identity = IdentityContext::new(ReplicaId(1)); + let precedence = crate::valuegen::spelling_precedence(1); + let first = prim_env( + 1, + 0, + 10, + CausalContext::new(), + OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { + precedence: precedence.clone(), + }), + ); + let second = prim_env( + 1, + 1, + 11, + seen_r1(0), + OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { + precedence: precedence.clone(), + }), + ); + let mut set = OperationSet::new(); + set.accept_all(vec![first.clone(), second.clone()]); + let out = reduce_operation_set_onto(&set, &Score::empty(identity)); + + assert_eq!( + effect_at(&out.state, 0), + Some(&OperationEffect::Applied), + "the first write applies" + ); + assert_eq!( + effect_at(&out.state, 1), + Some(&OperationEffect::Applied), + "a byte-identical re-write is still a new Applied write, not AlreadyApplied" + ); + } + + /// (s4) Value-restoring undo reaches the seeded base — run both + /// from-empty (base = `Default`) and onto a loaded base with a + /// non-default value, so the test distinguishes "restored the base" from + /// "restored the type default". + /// + /// **Mutation:** remove the `.seed(...)` calls in `seed_from_graph` -> + /// the undo produces `Restore(None)` and the field does not move (it + /// stays at the transaction's authored value instead of reverting). + #[test] + fn value_restoring_undo_of_settings_setters_reaches_the_seeded_base() { + // Case A: from-empty. Base = Score::empty, so the seed is the type + // Default. + { + let identity = IdentityContext::new(ReplicaId(1)); + let tx = TransactionId::from_raw(1); + let authored = crate::valuegen::canvas_layout_defaults(3); + let envelopes = vec![ + declare_transaction(1, 0, 10, CausalContext::new(), tx), + tx_member( + 1, + 1, + 11, + seen_r1(0), + tx, + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: authored, + }), + ), + undo_env(1, 2, 12, seen_r1(1), tx, UndoPolicy::StrictInverse), + ]; + let mut set = OperationSet::new(); + set.accept_all(envelopes); + let out = reduce_operation_set_onto(&set, &Score::empty(identity)); + assert_eq!( + out.score.canvas.layout_defaults, + CanvasLayoutDefaults::default(), + "from-empty undo restores the seeded type Default" + ); + } + + // Case B: onto a loaded base whose value is already non-default — + // undo must restore *that* value, not the type Default. + { + let mut base = Score::empty(IdentityContext::new(ReplicaId(1))); + let base_value = crate::valuegen::spelling_precedence(1); + base.spelling_precedence = base_value.clone(); + assert_ne!(base_value, SpellingPrecedence::default()); + + let tx = TransactionId::from_raw(2); + let authored = crate::valuegen::spelling_precedence(2); + let envelopes = vec![ + declare_transaction(1, 0, 10, CausalContext::new(), tx), + tx_member( + 1, + 1, + 11, + seen_r1(0), + tx, + OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { + precedence: authored, + }), + ), + undo_env(1, 2, 12, seen_r1(1), tx, UndoPolicy::StrictInverse), + ]; + let mut set = OperationSet::new(); + set.accept_all(envelopes); + let out = reduce_operation_set_onto(&set, &base); + assert_eq!( + out.score.spelling_precedence, base_value, + "undo onto a loaded base restores the base's own value, not the type Default" + ); + } + } + + /// (s7) Transaction rollback discards the write — and the assertion must + /// not be on the field. `WorkingSnapshot` (`:7480`) is the transaction + /// **rollback** mechanism: snapshot before, restore on failure. But + /// `restore` reassigns the whole graph independently of every write + /// chain, so a setter's *field* rolls back whether or not its chain was + /// snapshotted — an assertion on the field alone cannot see a missing + /// chain in the snapshot/restore pair. + /// + /// Shaped so the stale chain is observable: author inside a **failed** + /// transaction, then perform a **successful** write in a second + /// transaction, then **undo that second transaction** and assert it + /// restores the genuine predecessor — the pre-failure value, not the + /// rolled-back one. + /// + /// **Mutation:** omit `canvas_layout_defaults_chain` / + /// `spelling_precedence_chain` from the snapshot/restore pair (`:7480` + /// `snapshot`, `:7518` `restore`) -> the failed transaction's write + /// survives in the chain and the second undo restores it instead of the + /// true (pre-failure) predecessor. + #[test] + fn transaction_rollback_discards_the_write_from_the_chain_not_just_the_field() { + let identity = IdentityContext::new(ReplicaId(1)); + let pre_failure = crate::valuegen::canvas_layout_defaults(1); + let failed_tx_value = crate::valuegen::canvas_layout_defaults(2); + let second_tx_value = crate::valuegen::canvas_layout_defaults(3); + assert_ne!(pre_failure, failed_tx_value); + assert_ne!(pre_failure, second_tx_value); + assert_ne!(failed_tx_value, second_tx_value); + + let fail_tx = TransactionId::from_raw(10); + let ok_tx = TransactionId::from_raw(11); + + // 1: a genuine, successful pre-failure write (not itself part of any + // transaction) — this is the predecessor the final undo must reach. + let pre = prim_env( + 1, + 0, + 10, + CausalContext::new(), + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: pre_failure, + }), + ); + // 2: a transaction whose only member operation targets a missing + // staff instance, so the transaction as a whole fails and rolls + // back — `WorkingSnapshot` restores the graph, but a chain missing + // from the snapshot/restore pair would leak this write into the + // chain regardless. + let missing_instance = StaffInstanceId::new(ReplicaId(1), 99); + let fail_decl = declare_transaction(1, 1, 11, seen_r1(0), fail_tx); + let fail_write = tx_member( + 1, + 2, + 12, + seen_r1(1), + fail_tx, + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: failed_tx_value, + }), + ); + let fail_doomed = tx_member( + 1, + 3, + 13, + seen_r1(2), + fail_tx, + OperationKind::SetStaffLayout(SetStaffLayoutOp { + staff_instance: missing_instance, + instrument_override: None, + staff_lines_override: None, + visible: true, + }), + ); + // 3: a second, successful transaction that writes again. + let ok_decl = declare_transaction(1, 4, 14, seen_r1(3), ok_tx); + let ok_write = tx_member( + 1, + 5, + 15, + seen_r1(4), + ok_tx, + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: second_tx_value, + }), + ); + // 4: undo the second (successful) transaction. + let undo = undo_env(1, 6, 16, seen_r1(5), ok_tx, UndoPolicy::StrictInverse); + + let mut set = OperationSet::new(); + set.accept_all(vec![ + pre, + fail_decl, + fail_write, + fail_doomed, + ok_decl, + ok_write, + undo, + ]); + let out = reduce_operation_set_onto(&set, &Score::empty(identity)); + + assert_eq!( + out.score.canvas.layout_defaults, pre_failure, + "undo of the second transaction must restore the genuine (pre-failure) \ + predecessor, not the rolled-back transaction's write" + ); + } } diff --git a/crates/epiphany-ops/src/textproj_kind.rs b/crates/epiphany-ops/src/textproj_kind.rs index 6badf5e..f210e88 100644 --- a/crates/epiphany-ops/src/textproj_kind.rs +++ b/crates/epiphany-ops/src/textproj_kind.rs @@ -13,9 +13,10 @@ use crate::payload::{ DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, - OperationKind, OperationKindTag, RespellPitchOp, SetMetadataOp, SetMetricGridOp, - SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp, - SetUserSystemBreakOp, TransactionDescriptor, TransposeIntervalOp, TransposeOp, + OperationKind, OperationKindTag, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp, + SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp, + SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionDescriptor, + TransposeIntervalOp, TransposeOp, }; use crate::support::OperationKindRegistryId; @@ -222,6 +223,12 @@ impl TextValue for OperationKind { OperationKind::CreateInstrument(op) => { production(self.tag(), vec![op.instrument.project()]) } + OperationKind::SetCanvasLayoutDefaults(op) => { + production(self.tag(), vec![op.layout_defaults.project()]) + } + OperationKind::SetSpellingPrecedence(op) => { + production(self.tag(), vec![op.precedence.project()]) + } } } @@ -543,6 +550,22 @@ impl TextValue for OperationKind { instrument: TextValue::parse(instrument)?, }) } + OperationKindTag::SetCanvasLayoutDefaults => { + let [layout_defaults] = fields(s, tag, 1)? else { + unreachable!("the arity-1 check returned one field") + }; + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: TextValue::parse(layout_defaults)?, + }) + } + OperationKindTag::SetSpellingPrecedence => { + let [precedence] = fields(s, tag, 1)? else { + unreachable!("the arity-1 check returned one field") + }; + OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { + precedence: TextValue::parse(precedence)?, + }) + } }) } } @@ -596,7 +619,7 @@ mod tests { #[test] fn every_operation_kind_round_trips_with_canonical_text() { let tags: Vec<_> = all_tags().collect(); - assert_eq!(tags.len(), 32, "the grammar has 32 kind productions"); + assert_eq!(tags.len(), 34, "the grammar has 34 kind productions"); for tag in tags { round_trip(&sample_kind(tag)); } @@ -615,6 +638,34 @@ mod tests { assert!(OperationKind::parse(&Sexp::List(items)).is_err()); } + /// (s9, round-trip half) Genesis tranche G2a: a `set-canvas-layout- + /// defaults` production carrying `set-spelling-precedence`'s field shape + /// (relabeled under the other kind's tag) must not parse — the two + /// carried types have incompatible projected shapes (`CanvasLayoutDefaults` + /// projects a 2-field struct; `SpellingPrecedence` projects a 1-field + /// struct wrapping a sequence), so mislabeling one as the other is + /// rejected rather than mis-round-tripped. + /// + /// **Mutation:** none needed to demonstrate the reject — this test *is* + /// the reject-path exercise the contract asks for; a genuine defect + /// would be a decoder that silently accepted the mismatched shape. + #[test] + fn one_settings_kind_production_under_the_others_tag_is_rejected() { + let precedence_sample = sample_kind(OperationKindTag::SetSpellingPrecedence); + let Sexp::List(mut items) = precedence_sample.project() else { + panic!("operation projection is a list") + }; + // Relabel the head symbol from `set-spelling-precedence` to + // `set-canvas-layout-defaults`, keeping SpellingPrecedence's field + // shape underneath. + items[0] = Sexp::sym(OperationKindTag::SetCanvasLayoutDefaults.catalog_name()); + let mislabeled = Sexp::List(items); + assert!( + OperationKind::parse(&mislabeled).is_err(), + "a production under the wrong kind's tag must not parse" + ); + } + #[test] fn transaction_label_projects_nfc_and_rejects_non_nfc_text() { let mut sample = sample_kind(OperationKindTag::DeclareTransaction); diff --git a/crates/epiphany-ops/src/v0.rs b/crates/epiphany-ops/src/v0.rs index cef65fd..d5ff532 100644 --- a/crates/epiphany-ops/src/v0.rs +++ b/crates/epiphany-ops/src/v0.rs @@ -107,6 +107,11 @@ pub enum V0OperationKind { // the catalog entirely), so it round-trips by identity like every other // v1-native kind above. CreateInstrument(crate::payload::CreateInstrumentOp), + // Genesis tranche G2a — born at wire-discs 32/33; no lossy v0 form (v0 + // predates the catalog entirely), so both round-trip by identity like + // every other v1-native kind above. + SetCanvasLayoutDefaults(crate::payload::SetCanvasLayoutDefaultsOp), + SetSpellingPrecedence(crate::payload::SetSpellingPrecedenceOp), } /// v0 `InsertEvent`: the event was a bare [`EventId`] plus the reduction-relevant diff --git a/crates/epiphany-ops/src/valuegen.rs b/crates/epiphany-ops/src/valuegen.rs index 08b51bf..a8b63cf 100644 --- a/crates/epiphany-ops/src/valuegen.rs +++ b/crates/epiphany-ops/src/valuegen.rs @@ -368,6 +368,47 @@ pub fn instrument(id: epiphany_core::InstrumentId) -> epiphany_core::Instrument epiphany_core::Instrument::new(id, format!("instrument-{}", id.counter())) } +/// Canvas layout defaults with an `nth`-distinct page width (genesis tranche +/// G2a) — distinct `nth` give distinct `CanvasLayoutDefaults` values so a +/// harness can drive concurrent `SetCanvasLayoutDefaults`s, an advisory LWW +/// field that resolves by canonical order with no conflict. +pub fn canvas_layout_defaults(nth: u8) -> epiphany_core::CanvasLayoutDefaults { + let mut defaults = epiphany_core::CanvasLayoutDefaults::default(); + defaults.page_size.width = epiphany_determinism::CanonicalF64::new(105.0 + f64::from(nth)) + .expect("a small positive offset from the A4 default stays finite"); + defaults +} + +/// Spelling precedence with an `nth`-distinct order (genesis tranche G2a) — for +/// even `nth`, the type default (`UserChosen > Imported > Propagated > +/// Inferred > Analytical`); for odd `nth`, that order reversed. Both are total +/// orderings over the five source kinds, so `SpellingPrecedence::new` always +/// succeeds; distinct `nth` parities give distinct values so a harness can +/// drive concurrent `SetSpellingPrecedence`s, an advisory LWW field that +/// resolves by canonical order with no conflict. +pub fn spelling_precedence(nth: u8) -> epiphany_core::SpellingPrecedence { + use epiphany_core::SpellingSourceKind; + let order = if nth % 2 == 0 { + vec![ + SpellingSourceKind::UserChosen, + SpellingSourceKind::Imported, + SpellingSourceKind::Propagated, + SpellingSourceKind::Inferred, + SpellingSourceKind::Analytical, + ] + } else { + vec![ + SpellingSourceKind::Analytical, + SpellingSourceKind::Inferred, + SpellingSourceKind::Propagated, + SpellingSourceKind::Imported, + SpellingSourceKind::UserChosen, + ] + }; + epiphany_core::SpellingPrecedence::new(order) + .expect("both listed orders are total over the five source kinds") +} + /// A well-formed `numerator`/4 [`TimeSignature`](epiphany_core::TimeSignature) /// (Phase-3 tranche): `numerator` quarter-note beat groups summing exactly to /// the measure duration, so [`epiphany_core::TimeSignature::new`]'s beat-group diff --git a/crates/epiphany-ops/src/vectors.rs b/crates/epiphany-ops/src/vectors.rs index 5e0fa71..f9c206c 100644 --- a/crates/epiphany-ops/src/vectors.rs +++ b/crates/epiphany-ops/src/vectors.rs @@ -298,6 +298,81 @@ pub fn decode_vectors() -> Vec { trailing, )); + // --- OperationEnvelope carrying SetCanvasLayoutDefaults / SetSpellingPrecedence + // (genesis tranche G2a) — same rationale as CreateInstrument above: nothing + // else in this corpus exercises either payload's decode path, and a + // round-trip check alone cannot see a self-consistent encoder/decoder + // reorder (the 3b-i lesson). + let layout_envelope = OperationEnvelope { + id: OperationId::new(ReplicaId(1), 2), + author: crate::support::AuthorId(0), + stamp: crate::stamp::OperationStamp::new( + crate::stamp::HybridLogicalClock::new(epiphany_core::WallClockTime(1), 1), + OperationId::new(ReplicaId(1), 2), + ), + causal_context: crate::causal::CausalContext::new(), + transaction: None, + payload: crate::payload::OperationPayload::Primitive( + crate::payload::OperationKind::SetCanvasLayoutDefaults( + crate::payload::SetCanvasLayoutDefaultsOp { + layout_defaults: crate::valuegen::canvas_layout_defaults(1), + }, + ), + ), + }; + let layout_envelope_bytes = layout_envelope.to_canonical_bytes(); + v.push(row( + OE, + "accept", + "-", + "set_canvas_layout_defaults", + layout_envelope_bytes.clone(), + )); + let mut layout_trailing = layout_envelope_bytes; + layout_trailing.push(0); + v.push(row( + OE, + "reject", + "trailing-bytes", + "set_canvas_layout_defaults_trailing", + layout_trailing, + )); + + let precedence_envelope = OperationEnvelope { + id: OperationId::new(ReplicaId(1), 3), + author: crate::support::AuthorId(0), + stamp: crate::stamp::OperationStamp::new( + crate::stamp::HybridLogicalClock::new(epiphany_core::WallClockTime(1), 1), + OperationId::new(ReplicaId(1), 3), + ), + causal_context: crate::causal::CausalContext::new(), + transaction: None, + payload: crate::payload::OperationPayload::Primitive( + crate::payload::OperationKind::SetSpellingPrecedence( + crate::payload::SetSpellingPrecedenceOp { + precedence: crate::valuegen::spelling_precedence(1), + }, + ), + ), + }; + let precedence_envelope_bytes = precedence_envelope.to_canonical_bytes(); + v.push(row( + OE, + "accept", + "-", + "set_spelling_precedence", + precedence_envelope_bytes.clone(), + )); + let mut precedence_trailing = precedence_envelope_bytes; + precedence_trailing.push(0); + v.push(row( + OE, + "reject", + "trailing-bytes", + "set_spelling_precedence_trailing", + precedence_trailing, + )); + v } @@ -430,4 +505,54 @@ mod tests { "the committed literal bytes must decode and re-encode injectively" ); } + + /// (s8) Genesis tranche G2a (`spec/CONTRACT_GENESIS_G2A_SETTINGS.md`): the + /// `SetCanvasLayoutDefaults` and `SetSpellingPrecedence` envelope decode + /// vectors, pinned to literal byte arrays copied from the committed + /// corpus — not derived by calling `.to_canonical_bytes()` here, for the + /// same reason as `create_instrument_envelope_decode_vector_is_pinned_to_ + /// literal_bytes` above (the 3b-i lesson: round-trip locking alone cannot + /// see a self-consistent encoder/decoder reorder). Each new payload + /// carries exactly one field, so there are no adjacent fields to swap; + /// the mutation this guards against is a swap of the two new + /// **discriminants** (32 ↔ 33) in both the encoder and the decoder — + /// self-consistent, so every round-trip test stays green, while these + /// correctly-named literal vectors die. + #[test] + fn set_canvas_layout_defaults_envelope_decode_vector_is_pinned_to_literal_bytes() { + #[rustfmt::skip] + let bytes: Vec = vec![ + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 72, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 128, 90, 64, 8, 0, 0, 0, 0, 0, 0, 0, 0, 144, 98, 64, 8, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 30, 64, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 64, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, + 64, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 64, + ]; + let result = check("ops.operation_envelope", &bytes) + .expect("ops.operation_envelope is owned by this crate"); + assert_eq!( + result, + Ok(true), + "the committed literal bytes must decode and re-encode injectively" + ); + } + + /// (s8) Same rationale as the sibling test above. + #[test] + fn set_spelling_precedence_envelope_decode_vector_is_pinned_to_literal_bytes() { + #[rustfmt::skip] + let bytes: Vec = vec![ + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 9, 0, 0, 0, 5, 0, 0, 0, 4, 3, 2, 1, 0, + ]; + let result = check("ops.operation_envelope", &bytes) + .expect("ops.operation_envelope is owned by this crate"); + assert_eq!( + result, + Ok(true), + "the committed literal bytes must decode and re-encode injectively" + ); + } } diff --git a/crates/epiphany-testkit/src/generators.rs b/crates/epiphany-testkit/src/generators.rs index 2635cf7..39ebf50 100644 --- a/crates/epiphany-testkit/src/generators.rs +++ b/crates/epiphany-testkit/src/generators.rs @@ -22,7 +22,8 @@ use epiphany_core::{ LyricLineId, MarkerId, MeasureId, MusicalDuration, MusicalPosition, ObjectKindRegistryId, OperationId, PartDefinitionId, PitchId, RationalTime, RegionId, RepeatStructureId, ReplicaId, SlurId, SpannerId, StaffGroupId, StaffId, StaffInstanceId, TieId, TimeSignatureId, - TransactionId, TupletId, TypedObjectId, ViewId, VoiceId, WallClockDuration, WallClockTime, + TransactionId, TranspositionInterval, TupletId, TypedObjectId, ViewId, VoiceId, + WallClockDuration, WallClockTime, }; use epiphany_determinism::{ CanonicalEncode, CanonicalF64, ChunkId, ContentHash, DomainTag, QuantizedCoord, Tolerance, @@ -32,22 +33,23 @@ use epiphany_ops::valuegen; use epiphany_ops::{ AnomalousReplicaSegment, AuthorId, CausalContext, ChangeRegionTimeModelOp, ConflictId, ConflictKind, ConflictKindRegistryId, ConflictRecord, ConflictRegistry, - ConflictResolutionState, CreateCrossCuttingOp, CreateRegionOp, CreateRepeatStructureOp, - CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, - DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteRepeatStructureOp, - DeleteStaffInstanceOp, DeleteVoiceOp, ExtensionPreconditionId, FieldPath, HybridLogicalClock, - InsertEventOp, InsertIdentifiedPitchOp, IntegrityAnomaly, IntegrityAnomalyKind, - IntegrityAnomalyRegistryId, MaterializedState, ModifyCrossCuttingOp, ModifyEventOp, - ModifyIdentifiedPitchOp, NoOpReason, ObjectKind, ObjectState, OperationEffect, - OperationEnvelope, OperationKind, OperationKindRegistryId, OperationPayload, OperationSet, - OperationStamp, PendingReason, PositionRemapping, PreconditionFailureReason, - PreconditionFailureRegistryId, ReanchorReason, ReanchorReasonRegistryId, ReanchorResult, - RepairKind, RepairKindRegistryId, RepairRecord, ReplicaAnomalyReason, ReplicaAnomalyRegistryId, - ResolutionAction, ResolutionRegistryId, ResolveConflictPayload, RespellPitchOp, - SerializedCanonicalInputs, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp, + ConflictResolutionState, CreateCrossCuttingOp, CreateInstrumentOp, CreateRegionOp, + CreateRepeatStructureOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp, + CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp, + DeleteRegionOp, DeleteRepeatStructureOp, DeleteStaffInstanceOp, DeleteVoiceOp, + ExtensionPreconditionId, FieldPath, HybridLogicalClock, InsertEventOp, InsertIdentifiedPitchOp, + IntegrityAnomaly, IntegrityAnomalyKind, IntegrityAnomalyRegistryId, MaterializedState, + ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, NoOpReason, ObjectKind, + ObjectState, OperationEffect, OperationEnvelope, OperationKind, OperationKindRegistryId, + OperationPayload, OperationSet, OperationStamp, PendingReason, PositionRemapping, + PreconditionFailureReason, PreconditionFailureRegistryId, ReanchorReason, + ReanchorReasonRegistryId, ReanchorResult, RepairKind, RepairKindRegistryId, RepairRecord, + ReplicaAnomalyReason, ReplicaAnomalyRegistryId, ResolutionAction, ResolutionRegistryId, + ResolveConflictPayload, RespellPitchOp, SerializedCanonicalInputs, SetCanvasLayoutDefaultsOp, + SetMetadataOp, SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionCategory, - TransactionDescriptor, TransposeOp, TupletCompensation, TupletCompensationKind, UndoPolicy, - UndoTransactionPayload, + TransactionDescriptor, TransposeIntervalOp, TransposeOp, TupletCompensation, + TupletCompensationKind, UndoPolicy, UndoTransactionPayload, }; use crate::rng::Rng; @@ -644,7 +646,7 @@ pub fn operation_payload(rng: &mut Rng, events: u64, pitches: u64) -> OperationP } _ => {} } - let kind = match rng.below(30) { + let kind = match rng.below(34) { 0 => { let pitches = if rng.boolean() { vec![obj_pitch(rng.below(pitches))] @@ -846,6 +848,29 @@ pub fn operation_payload(rng: &mut Rng, events: u64, pitches: u64) -> OperationP 28 => OperationKind::DeleteRepeatStructure(DeleteRepeatStructureOp { repeat: RepeatStructureId::new(OBJ_REPLICA, rng.below(2)), }), + // Push 4a (previously absent from this corpus — see row 28's contract + // note): the faithful transpose over the shared pitch-id space. + 29 => OperationKind::TransposeInterval(TransposeIntervalOp { + targets: (0..1 + rng.below(2)) + .map(|_| obj_pitch(rng.below(pitches))) + .collect(), + interval: TranspositionInterval { + diatonic_steps: rng.below(5) as i32 - 2, + chromatic_steps: rng.below(9) as i32 - 4, + }, + }), + // Genesis tranche G1 (previously absent from this corpus — see row + // 28's contract note): mint an instrument on the score root. + 30 => OperationKind::CreateInstrument(CreateInstrumentOp { + instrument: valuegen::instrument(InstrumentId::new(OBJ_REPLICA, rng.below(2))), + }), + // Genesis tranche G2a: the two major-0 settings setters. + 31 => OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: valuegen::canvas_layout_defaults(rng.below(3) as u8), + }), + 32 => OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { + precedence: valuegen::spelling_precedence(rng.below(3) as u8), + }), _ => OperationKind::Registered( OperationKindRegistryId(rng.next_u64() as u128), rng.byte_vec(0, 16), @@ -1802,6 +1827,86 @@ mod tests { } } + /// (s6, half) Genesis tranche G2a's boundary against G2b: both new + /// setters are schema major 0 unconditionally, so a block carrying either + /// stamps well within the `OperationEnvelopeBlock` accept-set no matter + /// where that set's ceiling sits. + /// + /// The other half of s6 — `max_supported_major(OperationEnvelopeBlock) == + /// 2` — is **not** assertable from this crate: `epiphany_bundle:: + /// max_supported_major` (`bundle.rs:67`) is defined in a private module + /// (`mod bundle;`, not `pub mod`) and is not among `epiphany-bundle`'s + /// `pub use` re-exports, so no outside crate can name it. Contract pin 4a + /// forbids touching `epiphany-bundle` in this packet (not even to add a + /// re-export), so that half of s6 is verified by reading + /// `crates/epiphany-bundle/src/bundle.rs:69` directly (`ChunkKind:: + /// OperationEnvelopeBlock => 2`, unedited by this packet) rather than by + /// a compiled assertion — a deviation from the contract's "assert it in + /// code" phrasing, reported rather than worked around. + /// + /// **Mutation:** stamp a payload carrying either new kind at a non-zero + /// major (as if it had been wrongly placed in the `=> 2` arm alongside + /// `SetMetadata`, exactly the bug pin 3 and test s5 both name) — this + /// assertion fires. + #[test] + fn the_two_new_settings_kinds_stamp_within_the_accept_set() { + for major in [ + OperationKind::SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp { + layout_defaults: valuegen::canvas_layout_defaults(1), + }) + .schema_major(), + OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { + precedence: valuegen::spelling_precedence(1), + }) + .schema_major(), + ] { + assert_eq!(major, 0, "both new kinds must stamp at major 0"); + } + } + + /// (s10, row 28) `operation_payload`'s bounded draw is a hand-maintained + /// arm list over `rng.below(N)`, which nothing forces to move when the + /// operation vocabulary grows — exactly how `TransposeInterval` (kind 30, + /// Push-4a debt) and `CreateInstrument` (kind 31, G1 debt) went missing + /// from every corpus this generator feeds despite every downstream suite + /// staying green. Assert a bounded draw actually reaches every kind + /// appended past the historically-tested range (discriminants 30..=33), + /// not just that the function does not panic. + #[test] + fn operation_payload_emits_every_appended_kind() { + let mut rng = Rng::new(17); + let (mut saw_transpose_interval, mut saw_create_instrument) = (false, false); + let (mut saw_canvas_layout_defaults, mut saw_spelling_precedence) = (false, false); + for _ in 0..2000 { + let OperationPayload::Primitive(kind) = operation_payload(&mut rng, 8, 8) else { + continue; + }; + match kind { + OperationKind::TransposeInterval(_) => saw_transpose_interval = true, + OperationKind::CreateInstrument(_) => saw_create_instrument = true, + OperationKind::SetCanvasLayoutDefaults(_) => saw_canvas_layout_defaults = true, + OperationKind::SetSpellingPrecedence(_) => saw_spelling_precedence = true, + _ => {} + } + } + assert!( + saw_transpose_interval, + "TransposeInterval (kind 30) never drawn in 2000 samples" + ); + assert!( + saw_create_instrument, + "CreateInstrument (kind 31) never drawn in 2000 samples" + ); + assert!( + saw_canvas_layout_defaults, + "SetCanvasLayoutDefaults (kind 32) never drawn in 2000 samples" + ); + assert!( + saw_spelling_precedence, + "SetSpellingPrecedence (kind 33) never drawn in 2000 samples" + ); + } + #[test] fn frontier_uses_the_real_dvv_encoding_and_preserves_gaps() { let mut rng = Rng::new(11); diff --git a/crates/epiphany-testkit/src/layout_stub.rs b/crates/epiphany-testkit/src/layout_stub.rs index 55c7e84..96f75a5 100644 --- a/crates/epiphany-testkit/src/layout_stub.rs +++ b/crates/epiphany-testkit/src/layout_stub.rs @@ -950,40 +950,25 @@ pub fn gen_vertical_band(rng: &mut Rng) -> VerticalBand { /// An operation-kind tag (every variant Agent C's type provides, including the /// registered form). +/// +/// Genesis tranche G2a (row 29): **derived**, not hand-maintained. The prior +/// hand-written `match` had already gone stale twice — it stopped at 23 until +/// the Phase-3 tranche and again omitted the repeat pair — because nothing +/// forces a hand-written arm list to move when the vocabulary grows, even +/// though this generator's own doc promises *every* variant. Built-ins are +/// drawn uniformly from `OperationKindTag::PAYLOAD_FREE` +/// (`operation_kind_tag_vocabulary!`'s single source of truth, `payload.rs`) — +/// `Registered` is excluded from `PAYLOAD_FREE` by design (it carries a +/// payload, `payload.rs:469`) and is appended here explicitly. Every future +/// built-in append then follows structurally, with no edit to this function; +/// see `OperationKind::discriminant()`'s in-crate precedent +/// (`payload.rs:2052`, `all_tags`). pub fn gen_operation_kind_tag(rng: &mut Rng) -> OperationKindTag { - match rng.below(30) { - 0 => OperationKindTag::InsertEvent, - 1 => OperationKindTag::DeleteEvent, - 2 => OperationKindTag::ModifyEvent, - 3 => OperationKindTag::RespellPitch, - 4 => OperationKindTag::Transpose, - 5 => OperationKindTag::CreateCrossCutting, - 6 => OperationKindTag::DeleteCrossCutting, - 7 => OperationKindTag::ModifyCrossCutting, - 8 => OperationKindTag::ChangeRegionTimeModel, - 9 => OperationKindTag::InsertRegion, - 10 => OperationKindTag::DeleteRegion, - 11 => OperationKindTag::InsertStaffInstance, - 12 => OperationKindTag::DeleteStaffInstance, - 13 => OperationKindTag::SetUserSystemBreak, - 14 => OperationKindTag::SetUserPageBreak, - 15 => OperationKindTag::DeclareTransaction, - 16 => OperationKindTag::InsertIdentifiedPitch, - 17 => OperationKindTag::DeleteIdentifiedPitch, - 18 => OperationKindTag::ModifyIdentifiedPitch, - 19 => OperationKindTag::CreateVoice, - 20 => OperationKindTag::DeleteVoice, - 21 => OperationKindTag::SetMetadata, - 22 => OperationKindTag::SetMetricGrid, - // The appended vocabulary: the Phase-3 tranche (24..=27) — this - // generator had gone stale at 23 — and the repeat pair (28/29). - 23 => OperationKindTag::InsertStaff, - 24 => OperationKindTag::SetTimeSignature, - 25 => OperationKindTag::SetTempoSegment, - 26 => OperationKindTag::SetStaffLayout, - 27 => OperationKindTag::CreateRepeatStructure, - 28 => OperationKindTag::DeleteRepeatStructure, - _ => OperationKindTag::Registered(epiphany_ops::OperationKindRegistryId( + // +1 slot for `Registered`, drawn last. + let index = rng.below(OperationKindTag::PAYLOAD_FREE.len() as u64 + 1) as usize; + match OperationKindTag::PAYLOAD_FREE.get(index) { + Some(tag) => *tag, + None => OperationKindTag::Registered(epiphany_ops::OperationKindRegistryId( rng.next_u64() as u128 )), } @@ -1385,6 +1370,34 @@ mod tests { use epiphany_core::TypedObjectId; use epiphany_determinism::CanonicalEncode; + /// (s10, row 29) `gen_operation_kind_tag`'s draw must cover exactly + /// `PAYLOAD_FREE` ∪ `{Registered}` — **not** merely the appended + /// discriminants 30..=33, which would pass even if the `Registered` + /// append (structurally different from every built-in: it is the one + /// variant `PAYLOAD_FREE` excludes by design, `payload.rs:469`) were + /// deleted from the generator entirely. + #[test] + fn gen_operation_kind_tag_covers_payload_free_and_registered() { + let mut rng = Rng::new(0x6E_57_A9); + let mut saw_registered = false; + let mut seen_builtins = std::collections::BTreeSet::new(); + for _ in 0..5000 { + match gen_operation_kind_tag(&mut rng) { + OperationKindTag::Registered(_) => saw_registered = true, + other => { + seen_builtins.insert(other); + } + } + } + assert!(saw_registered, "Registered never drawn in 5000 samples"); + let expected: std::collections::BTreeSet<_> = + OperationKindTag::PAYLOAD_FREE.iter().copied().collect(); + assert_eq!( + seen_builtins, expected, + "the draw's built-in coverage must be exactly PAYLOAD_FREE" + ); + } + #[test] fn edit_barrier_blob_codec_round_trips_generated_barriers() { // Property gate for the provisional manifest-blob byte form diff --git a/crates/epiphany-testkit/tests/text_projection_grammar.rs b/crates/epiphany-testkit/tests/text_projection_grammar.rs index 08e8e5f..20ab755 100644 --- a/crates/epiphany-testkit/tests/text_projection_grammar.rs +++ b/crates/epiphany-testkit/tests/text_projection_grammar.rs @@ -304,13 +304,14 @@ fn the_kind_productions_are_the_operation_vocabulary() { // The count is still hand-maintained, which is the same shape the comment // above warns about — derived list, literal total. Every tranche that // appends a tag must bump it (genesis G1 took it from 31 to 32 by adding - // `CreateInstrument`). It stays a literal on purpose: deriving it from - // `PAYLOAD_FREE.len()` would make the assertion vacuous, since that is the - // very list it exists to pin. + // `CreateInstrument`; genesis G2a takes it from 32 to 34 by adding + // `SetCanvasLayoutDefaults` and `SetSpellingPrecedence`). It stays a + // literal on purpose: deriving it from `PAYLOAD_FREE.len()` would make the + // assertion vacuous, since that is the very list it exists to pin. assert_eq!( expected.len(), - 32, - "31 payload-free kinds plus `Registered`" + 34, + "33 payload-free kinds plus `Registered`" ); let actual = alternatives("kind"); diff --git a/crates/epiphany-textproj/src/lib.rs b/crates/epiphany-textproj/src/lib.rs index 478f353..8c502bc 100644 --- a/crates/epiphany-textproj/src/lib.rs +++ b/crates/epiphany-textproj/src/lib.rs @@ -26,7 +26,12 @@ use epiphany_ops::OperationEnvelope; /// without moving this constant would leave two incompatible grammars both /// claiming `(0 7 0)`. Cached projections do not migrate: a `TextProjection` /// chunk is a non-canonical accelerator, so a stale one is regenerated. -pub const COMPANION_VERSION: (u32, u32, u32) = (0, 8, 0); +/// +/// Bumped again 0.8.0 → 0.9.0 by the genesis tranche G2a, which appended +/// `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); /// A parsed canonical Text Projection document. /// diff --git a/crates/epiphany-textproj/src/parse.rs b/crates/epiphany-textproj/src/parse.rs index 6051780..3b479ab 100644 --- a/crates/epiphany-textproj/src/parse.rs +++ b/crates/epiphany-textproj/src/parse.rs @@ -642,11 +642,12 @@ mod tests { out } - // Bumped with `COMPANION_VERSION` (0.7.0 → 0.8.0, genesis G1). 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 8 0))"; + // 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)"; /// A minimal but complete valid projection: just the two mandatory lines. @@ -851,6 +852,28 @@ 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 + /// `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. + /// + /// **Mutation:** widen the parser's version check from an exact `!= + /// COMPANION_VERSION` to accept any `(0 _ 0)` (e.g. `major == 0`) -> this + /// 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]); + assert_eq!( + parse_document(&text), + Err(TextError::NotCanonical( + "the header names a companion version other than the one this crate implements" + )) + ); + } + #[test] fn a_blob_line_is_always_rejected() { let blob_line = "(blob \"audio/wav\" () #x00)"; diff --git a/crates/epiphany-textproj/src/vectors.rs b/crates/epiphany-textproj/src/vectors.rs index b7f9edc..e708326 100644 --- a/crates/epiphany-textproj/src/vectors.rs +++ b/crates/epiphany-textproj/src/vectors.rs @@ -323,16 +323,16 @@ pub fn document_vectors() -> Vec { .collect(); // The rejected version must be one this crate does NOT implement. Genesis - // G1 moved `COMPANION_VERSION` to 0.8.0, which had been this vector's + // 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.7.0, the + // 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 // migrate-on-read posture (`req:textproj:header-version`). let wrong_version = replace_once( minimal, + "(text-projection (0 9 0))", "(text-projection (0 8 0))", - "(text-projection (0 7 0))", ); vectors.push(( SURFACE, diff --git a/spec/binary_format.pdf b/spec/binary_format.pdf index e6183c6..b760e28 100644 Binary files a/spec/binary_format.pdf and b/spec/binary_format.pdf differ diff --git a/spec/binary_format.tex b/spec/binary_format.tex index 770f0bc..303ba75 100644 --- a/spec/binary_format.tex +++ b/spec/binary_format.tex @@ -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.11.0 --- The decode vector corpus extends to the core score wire}\\[4pt] + {\normalsize\color{epiphanyink}Version 0.12.0 --- The genesis operation tranche reaches the wire (G1 + G2a)}\\[4pt] {\small\color{epiphanyslate}Normative for the byte layouts it defines} \vfill \end{titlepage} @@ -1445,6 +1445,16 @@ and the reader disagree about the field's type. \texttt{diatonic\_steps} (\texttt{i32} LE, 4) \cat{} \texttt{chromatic\_steps} (\texttt{i32} LE, 4) & \sectionsc{TransposeInterval} \\ + \tablenums{31} & \texttt{CreateInstrument} & + $\mathrm{lp}$(\texttt{Instrument}) --- the schema-major-2 layout, + unconditionally (born at v2; Section~\ref{sec:evolution:major2}) & + \sectionsc{CreateInstrument} \\ + \tablenums{32} & \texttt{SetCanvasLayoutDefaults} & + $\mathrm{lp}$(\texttt{CanvasLayoutDefaults}) & + \sectionsc{SetCanvasLayoutDefaults} \\ + \tablenums{33} & \texttt{SetSpellingPrecedence} & + $\mathrm{lp}$(\texttt{SpellingPrecedence}) & + \sectionsc{SetSpellingPrecedence} \\ \bottomrule \end{longtable} \endgroup @@ -1513,7 +1523,8 @@ discriminants and wrong lengths are decode errors. Append-only past \tablenums{24} & \texttt{InsertStaff} & \tablenums{25} & \texttt{SetTimeSignature} \\ \tablenums{26} & \texttt{SetTempoSegment} & \tablenums{27} & \texttt{SetStaffLayout} \\ \tablenums{28} & \texttt{CreateRepeatStructure} & \tablenums{29} & \texttt{DeleteRepeatStructure} \\ - \tablenums{30} & \texttt{TransposeInterval} & & \\ + \tablenums{30} & \texttt{TransposeInterval} & \tablenums{31} & \texttt{CreateInstrument} \\ + \tablenums{32} & \texttt{SetCanvasLayoutDefaults} & \tablenums{33} & \texttt{SetSpellingPrecedence} \\ \bottomrule \end{longtable} \endgroup @@ -2426,13 +2437,27 @@ The three added fields land in different chunk classes, and the major is assigned \textbf{per payload type}, not per chunk kind: \begin{itemize} - \item \texttt{Canvas.layout\_defaults} and \texttt{Instrument.range} appear - \emph{only} in the acceleration-cache full-\texttt{Score} snapshot (a - \texttt{ChunkKind::Snapshot}). No operation payload embeds a - \texttt{Canvas} or an \texttt{Instrument} value (there is no - \texttt{CreateCanvas}/\texttt{CreateInstrument}; canvas is the inline - genesis singleton and instruments live in score genesis), so these two - are confined to that one \textbf{non-canonical} chunk. + \item \texttt{Canvas.layout\_defaults} and \texttt{Instrument.range} now + also reach the \textbf{canonical operation layer}, each by a different + genesis-tranche operation. \texttt{Instrument.range}: genesis tranche G1's + \texttt{CreateInstrument} (\S\ref{sec:ops:kinds} discriminant + \tablenums{31}) embeds the full \texttt{Instrument} value, whose + schema-major-2 appends are mandatory --- so this payload always carries + the complete (major-2) \texttt{Instrument} layout, \texttt{range} + included, and a block carrying it is stamped \textbf{major~2} + unconditionally, never merely major~1. + \texttt{Canvas.layout\_defaults}: genesis tranche G2a's + \texttt{SetCanvasLayoutDefaults} (discriminant \tablenums{32}) embeds the + full \texttt{CanvasLayoutDefaults} value directly, rather than through the + containing \texttt{Canvas} walk that carries the major-1 versioning --- + \texttt{CanvasLayoutDefaults} has exactly one layout as a standalone + value, so a block carrying this operation is stamped \textbf{major~0}. + What remains confined to the \textbf{non-canonical} acceleration-cache + full-\texttt{Score} snapshot (a \texttt{ChunkKind::Snapshot}) is the + canvas \emph{object itself}: there is still no \texttt{CreateCanvas}, no + \texttt{TypedObjectId} kind for it, and no operation mints, addresses, or + deletes it --- the canvas is the inline genesis singleton, and that half + of the earlier claim survives. \item \texttt{Region.permits\_spanning\_slurs} reaches the snapshot \emph{and} the \textbf{canonical operation layer}: \texttt{CreateRegion} embeds the full \texttt{Region} value @@ -2596,19 +2621,29 @@ are defined in the same core revision; their wire layouts are below. The major is assigned per payload type, exactly as in major~1: \begin{itemize} - \item \textbf{Snapshot-only:} \texttt{Instrument} (no operation embeds - one); it reaches only the acceleration full-\texttt{Score} snapshot. - (\texttt{RepeatStructure} was snapshot-only when this section was - first ratified; the repeat-authoring pair moved it to the canonical - operation layer --- next bullet.) - \item \textbf{Canonical operation layer:} eight operation - payloads embed filled values. \texttt{CreateRepeatStructure} (this - revision --- Operation Catalog \sectionsc{Repeat Structures}, - discriminant \tablenums{28}) embeds the v2 \texttt{RepeatStructure} - layout unconditionally (\texttt{kind} and \texttt{voltas} are not - optional fields) and is \emph{born at v2}: no lower-major layout for - its payload exists, so every block carrying one stamps major~2 under - minimal stamping. Its sibling \texttt{DeleteRepeatStructure} + \item \textbf{Snapshot-only:} nothing, as of genesis tranche G1. + \texttt{Instrument} was snapshot-only when this section was first + ratified (no operation embedded one; it reached only the acceleration + full-\texttt{Score} snapshot) and \texttt{RepeatStructure} likewise, + until the repeat-authoring pair moved it to the canonical operation + layer. \texttt{CreateInstrument} (genesis tranche G1, discriminant + \tablenums{31}) moved \texttt{Instrument} the same way --- see the next + bullet, which now names it. + \item \textbf{Canonical operation layer:} nine operation + payloads embed filled values. \texttt{CreateInstrument} (genesis + tranche G1 --- Operation Catalog \sectionsc{CreateInstrument}, + discriminant \tablenums{31}) embeds the full \texttt{Instrument} value; + its schema-major-2 appends (\texttt{sound\_config}, + \texttt{default\_clef}, \texttt{default\_staff\_lines}, and the rest) + are mandatory fields, not \texttt{Option}-hidden, so it is + \emph{born at v2} exactly as \texttt{CreateRepeatStructure} below is --- + no lower-major layout for this payload exists, and every block + carrying one stamps major~2 under minimal stamping. + \texttt{CreateRepeatStructure} (Operation Catalog + \sectionsc{Repeat Structures}, discriminant \tablenums{28}) embeds the + v2 \texttt{RepeatStructure} layout unconditionally (\texttt{kind} and + \texttt{voltas} are not optional fields) and is likewise \emph{born at + v2}. Its sibling \texttt{DeleteRepeatStructure} (\tablenums{29}) carries a bare identifier --- a major-0 layout --- so minimal stamping gives its blocks major~0; the kind discriminant itself is an append-only schema-\emph{minor} vocabulary event (the @@ -2617,8 +2652,8 @@ The major is assigned per payload type, exactly as in major~1: kind's blocks \emph{stamp} always follows minimal stamping over its payload, as discriminant \tablenums{24}'s always-major-2 \texttt{CreateStaff} shows). Neither changes anything about - lower-major layouts. The seven payloads of the first ratification - embed filled values --- + lower-major layouts. The seven remaining payloads of the first + ratification embed filled values --- \texttt{CreateCrossCutting} and \texttt{ModifyCrossCutting} (a \texttt{CrossCuttingValue} embedding \texttt{Slur}, \texttt{Tie}, \texttt{Beam}, or \texttt{Spanner}); \texttt{CreateStaff} (a @@ -2634,7 +2669,9 @@ The major is assigned per payload type, exactly as in major~1: embedding the filled \texttt{StaffLineConfiguration}. (Reduction preconditions a \emph{carried} region to be empty of typed children, but the byte form is representable and preserved in the canonical - set, so the wire must define it.) + set, so the wire must define it.) That is + $1~(\texttt{CreateInstrument}) + 1~(\texttt{CreateRepeatStructure}) + + 7 = 9$ payloads total. \textbf{Minimal stamping.} A writer \MUST{} stamp each operation-envelope block with the \emph{lowest} schema major whose @@ -3475,6 +3512,35 @@ only}: implementations need not agree on an error taxonomy. order, which previously passed round-trip locking and 8/8 conformance). No wire layout changed; no existing vector's bytes moved (\texttt{spec/CONTRACT\_CORE\_DECODE\_VECTORS.md}). \\ + \today & Operation wire forms & 0.12.0 --- Genesis tranche G2a + (\texttt{spec/CONTRACT\_GENESIS\_G2A\_SETTINGS.md}), plus the repair of a + G1 omission this document's own contract wrongly declared out of scope. + Appends \texttt{OperationKind} wire discriminants \tablenums{31} + (\texttt{CreateInstrument}, genesis tranche G1 --- landed at + \texttt{3b09595} with no matching entry here), \tablenums{32} + (\texttt{SetCanvasLayoutDefaults}), and \tablenums{33} + (\texttt{SetSpellingPrecedence}), with their payload layouts, and the + matching \texttt{OperationKindTag} discriminants \tablenums{31}--\tablenums{33} + (name-verbatim projection, as the two most recent prior additions are) --- + schema-\emph{minor} evolutions under this document's own append-only + rules; no existing assignment changed. \texttt{CreateInstrument} embeds + the full \texttt{Instrument} value and is \emph{born at v2} + unconditionally (Section~\ref{sec:evolution:major2}), which the + Section~\ref{sec:evolution:major1}/\ref{sec:evolution:major2} + ``Where the changed fields reach'' bullets are corrected to name: neither + \texttt{Instrument} nor \texttt{Canvas.layout\_defaults} is + snapshot-only any longer, and the \sectionsc{Schema Major 2} canonical + operation-layer count moves from eight payloads to nine. Both new G2a + payloads --- \texttt{SetCanvasLayoutDefaultsOp} and + \texttt{SetSpellingPrecedenceOp} --- carry only major-0 layouts (neither + carried type has ever been versioned as a standalone value), so no + accept-set moves: \texttt{OperationEnvelopeBlock} stays at + \tablenums{2}. The chunk schema \emph{minor} is knowingly still not + stamped for any of \tablenums{24}--\tablenums{33} (filed as P13-S14, a + retroactive sweep sequenced after this rung). Semantics: Operation + Catalog \sectionsc{CreateInstrument} (retroactive), + \sectionsc{SetCanvasLayoutDefaults}, \sectionsc{SetSpellingPrecedence}, + 0.10.0. \\ \bottomrule \end{longtable} diff --git a/spec/core_spec.pdf b/spec/core_spec.pdf index 1fcefbd..e83bf74 100644 Binary files a/spec/core_spec.pdf and b/spec/core_spec.pdf differ diff --git a/spec/core_spec.tex b/spec/core_spec.tex index 461976b..b6f3047 100644 --- a/spec/core_spec.tex +++ b/spec/core_spec.tex @@ -5110,17 +5110,29 @@ pub struct CueEvent { The canvas is the spatial root of the score. It contains regions. -The score root and the canvas are \emph{structural givens}, not -operation products (ratified Pass~12): genesis is the creation of an -empty score together with its bundle, outside the operation set, and -neither object is ever minted, addressed, or deleted by an operation -(there is no \texttt{TypedObjectId} kind for either). This is +The score root and the canvas remain \emph{structural givens}, not +operation products (ratified Pass~12; narrowed by +\texttt{spec/RULING\_GENESIS\_PERSISTENCE.md}): neither object is ever +minted, addressed, or deleted by an operation, and there is still no +\texttt{TypedObjectId} kind for either. This is deliberate --- a document that could mint its own root would admit a genesis race under concurrent editing, and every operation would need a defined semantics against a not-yet-existing root. The decision may be revisited only if an addressable multi-canvas model is adopted at a future schema major. +What the genesis ruling reverses is the narrower claim this paragraph used +to make alongside the above: that the score's \emph{contents} also arrive +outside the operation set, as part of the same empty-score-plus-bundle +genesis act. They do not. Every mutable field of \texttt{Score} becomes +operation-authored under the genesis tranche +(\texttt{spec/PLAN\_GENESIS\_OPS.md}): a document created empty +(\texttt{Score::empty}) and given only operations can materialize a +note-bearing score, reaching \texttt{Canvas.layout\_defaults} +and \texttt{Score.spelling\_precedence} (genesis tranche G2a) among other +surfaces. The root and the canvas themselves are the load-bearing +exception that survives intact. + \begin{lstlisting}[language=Rust] pub struct Canvas { /// Regions in this canvas. Each region declares its time and @@ -6911,6 +6923,8 @@ pub enum OperationKind { // Pitch and tuning operations RespellPitch(RespellPitchOp), Transpose(TransposeOp), + // The faithful transposition (Push 4a); Transpose above is frozen. + TransposeInterval(TransposeIntervalOp), InsertIdentifiedPitch(InsertIdentifiedPitchOp), DeleteIdentifiedPitch(DeleteIdentifiedPitchOp), ModifyIdentifiedPitch(ModifyIdentifiedPitchOp), @@ -6934,6 +6948,10 @@ pub enum OperationKind { CreateVoice(CreateVoiceOp), DeleteVoice(DeleteVoiceOp), + // Genesis tranche G1: mints an abstract instrument on the score root --- + // the single missing link between Score::empty and a note. + CreateInstrument(CreateInstrumentOp), + // Metric-model operations SetTimeSignature(SetTimeSignatureOp), SetTempoSegment(SetTempoSegmentOp), @@ -6941,6 +6959,10 @@ pub enum OperationKind { // Score settings SetMetadata(SetMetadataOp), + // Genesis tranche G2a: the two major-0 settings setters, on the + // SetMetadata advisory-LWW pattern. + SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp), + SetSpellingPrecedence(SetSpellingPrecedenceOp), // Layout-semantic operations SetUserSystemBreak(SetUserSystemBreakOp), @@ -11900,6 +11922,15 @@ pub enum OperationKindTag { // its kind names verbatim, as the cross-cutting tags do. CreateRepeatStructure, DeleteRepeatStructure, + // Push 4a: the faithful transpose. Transpose above is frozen. + TransposeInterval, + // Genesis tranche G1: name-verbatim, as the two most recent prior + // additions are (the tag layer's older Create-to-Insert convention is + // not followed here). + CreateInstrument, + // Genesis tranche G2a: the two major-0 settings setters. + SetCanvasLayoutDefaults, + SetSpellingPrecedence, } pub enum BarrierScope { @@ -12183,11 +12214,17 @@ non-canonical resolved-layout length-prefix width. Its wire form and the byte-for-byte migration from major~0 are defined in the Binary Format companion. The canonical-base \texttt{MaterializedState} embeds none of these values, so it is byte-identical across the bump and stays major~0. -\texttt{Canvas.layout\_defaults} and \texttt{Instrument.range} reach only the -non-canonical acceleration (full-\texttt{Score}) snapshot; but \texttt{Region.permits\_spanning\_slurs} also reaches the \emph{canonical} operation layer, because \texttt{CreateRegion} embeds a full \texttt{Region}, so an operation-envelope block bearing a v1 \texttt{CreateRegion} is major~1. +(As of the genesis operation tranche, \texttt{Canvas.layout\_defaults} and +\texttt{Instrument.range} do too --- \texttt{SetCanvasLayoutDefaults} +(G2a) and \texttt{CreateInstrument} (G1) embed the values directly --- but +neither of those two payloads is itself major~1: the leaf value each embeds +has exactly one layout, versioned only through the containing +\texttt{Canvas}/\texttt{Instrument} walk, not through this schema-major-1 +bump. See the Binary Format companion \sectionsc{Schema Major 1} / +\sectionsc{Schema Major 2} for the operation-by-operation accounting.) Consequently a major-0-only reader opens a major-1 bundle \emph{fully} only when the bundle carries no v1 \texttt{CreateRegion} operation; otherwise it reads the major-0 canonical base and manifest but opens read-only (it cannot @@ -12204,7 +12241,7 @@ truncated bodies of the cross-cutting structures (\texttt{Slur}, Chapter~5 definitions. The same per-payload-type rules apply: the canonical-base \texttt{MaterializedState} embeds none of these values and stays major~0; the filled values reach the acceleration snapshot -\emph{and}, through the eight operation payloads that embed them +\emph{and}, through the nine operation payloads that embed them (\texttt{CreateCrossCutting}, \texttt{ModifyCrossCutting}, \texttt{InsertStaff} and \texttt{InsertStaffInstance} (the Operation Catalog's \texttt{CreateStaff} / \texttt{CreateStaffInstance}), @@ -12213,11 +12250,17 @@ Catalog's \texttt{CreateStaff} / \texttt{CreateStaffInstance}), embeds the filled \texttt{RepeatStructure} unconditionally, so it is \emph{born at v2} and every block carrying one stamps major~2; its \texttt{DeleteRepeatStructure} sibling carries a bare identifier, a -major-0 layout, and stamps major~0), and --- transitively, +major-0 layout, and stamps major~0), \texttt{CreateInstrument} (genesis +tranche G1 --- it embeds the filled \texttt{Instrument} unconditionally, so +it too is \emph{born at v2}), and --- transitively, because an embedded \texttt{Region} may carry a \texttt{StaffInstance} whose \texttt{staff\_lines\_override} embeds the filled \texttt{StaffLineConfiguration} --- \texttt{InsertRegion} -(\texttt{CreateRegion})), the canonical operation layer. A block is +(\texttt{CreateRegion})), the canonical operation layer. That is nine: +\texttt{CreateCrossCutting}, \texttt{ModifyCrossCutting}, +\texttt{InsertStaff}, \texttt{InsertStaffInstance}, \texttt{SetStaffLayout}, +\texttt{SetMetadata}, \texttt{CreateRepeatStructure}, \texttt{CreateInstrument}, +and \texttt{InsertRegion}. A block is stamped with the \emph{lowest} schema major whose layouts decode its bytes (the companion's minimal-stamping rule --- deterministic, so identical content hashes identically): any v2-dependent bytes make it @@ -16426,7 +16469,11 @@ layouts they own versus inherit: the corresponding wire discriminants. This document's \texttt{OperationKind} listing gains the four kinds. The create score/canvas slots remain deliberately unavailable pending an - addressable root model (P12-K8). + addressable root model (P12-K8). (Reversed in part by + \texttt{spec/RULING\_GENESIS\_PERSISTENCE.md}, ratified with the genesis + operation tranche: the root/canvas \emph{objects} stay unavailable as + ratified here, but the score's \emph{contents} are no longer outside the + operation set --- see \sectionsc{The Canvas}, \S\ref{sec:graph:canvas}.) \\ \today & Schema major 1 (data-model expansion) & Defines the two referenced-but-undefined types @@ -16473,7 +16520,10 @@ layouts they own versus inherit: inference-ineligible targets surface (Requirement~\ref{req:pitch:authored-uninferred}, P12-H7). Identity: system-derived intrinsic content immutable under reduction (P12-K3). Genesis - ratified outside the operation set (P12-K8). Cross-region slur permission = + ratified outside the operation set (P12-K8 --- reversed in part by + \texttt{spec/RULING\_GENESIS\_PERSISTENCE.md}, as annotated above: the + root/canvas objects stay unavailable, the score's contents no longer do). + Cross-region slur permission = AND (P12-K12). Re-anchoring: cue cascade-on-any-source, Range truncate defined, annotation orphaning sanctioned, \texttt{ReanchorReason:: SameCanvasNearer} appended (P12-C1/C2/C3/C4). Barriers: target-free and diff --git a/spec/operation_catalog.pdf b/spec/operation_catalog.pdf index 6ceb537..eecf314 100644 Binary files a/spec/operation_catalog.pdf and b/spec/operation_catalog.pdf differ diff --git a/spec/operation_catalog.tex b/spec/operation_catalog.tex index 0f2270c..591a6de 100644 --- a/spec/operation_catalog.tex +++ b/spec/operation_catalog.tex @@ -231,7 +231,7 @@ {\Large\scshape\color{epiphanyslate}Operation Catalog}\\[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 --- Space-relative CMN transposition}\\[4pt] + {\normalsize\color{epiphanyink}Version 0.10.0 --- The genesis operation tranche (G1 + G2a)}\\[4pt] {\small\color{epiphanyslate}Normative for the operation kinds it defines} \vfill \end{titlepage} @@ -343,6 +343,45 @@ resolution in Push~4b. No payload bytes change and no \texttt{PreconditionFailureReason} is appended; assignments 10 through 15 remain exactly as ratified. +\medskip + +\noindent\textbf{Version 0.10.0, first half (genesis tranche G1, +retroactively recorded).} \texttt{CreateInstrument} +(Section~\ref{sec:k0:create-instrument}) landed at commit \texttt{3b09595} +without a version bump or a changelog paragraph, against this document's own +convention above --- an omission genesis tranche G2a repairs here rather than +silently. Adds one operation kind, \texttt{CreateInstrument}, mint-only +(set-union creation, no delete in this catalogue revision) on the score root, +carrying the full \texttt{Instrument} value. It is the single missing link +between an empty score (\texttt{Score::empty}) and a note: \texttt{CreateStaff} +already demands a live \texttt{Instrument}, and nothing else could mint one. +Schema major~2 unconditionally (\texttt{Instrument}'s major-2 appends are +mandatory fields, not \texttt{Option}-hidden). + +\medskip + +\noindent\textbf{Version 0.10.0, second half (genesis tranche G2a).} Adds two +operation kinds, \texttt{SetCanvasLayoutDefaults} +(Section~\ref{sec:k0:set-canvas-layout-defaults}) and +\texttt{SetSpellingPrecedence} (Section~\ref{sec:k0:set-spelling-precedence}), +on exactly the \texttt{SetMetadata} advisory-LWW pattern +(Section~\ref{sec:k0:score-settings}): the canvas's default layout advisories +and the score's spelling-precedence configuration join the operation-authored +surfaces. Both are schema major~0 unconditionally --- neither carried type's +canonical encoding, taken as a standalone payload, has ever been versioned. +No accept-set change: the third genesis-ladder setter, +\texttt{SetTuningContext}, is schema major~3 and is deliberately deferred to +its own rung (G2b) rather than anticipated here. + +The \emph{Value restoration} passage +(Section~\ref{sec:k0:undo}) and the \emph{Retired slots} closing note both +gain a repair alongside this addition: the former's LWW-family enumeration +now names both new setters, and the latter's ``outside the operation set'' +clause is narrowed to the canvas object itself, per +\texttt{spec/RULING\_GENESIS\_PERSISTENCE.md} --- the score's \emph{contents} +are no longer outside the operation set; the canvas is still never +op-minted. + % =========================================================================== \chapter{The Catalog Framework} \label{ch:framework} @@ -1138,6 +1177,76 @@ There is no \texttt{DeleteInstrument} in this catalogue revision --- full delete/modify coverage of the genesis-authored fields is later tranche work (\texttt{spec/PLAN\_GENESIS\_OPS.md} G3). +\section{SetCanvasLayoutDefaults} +\label{sec:k0:set-canvas-layout-defaults} + +Ratified with the genesis operation tranche's G2a rung (\texttt{spec/CONTRACT\_ +GENESIS\_G2A\_SETTINGS.md}, executing \texttt{spec/RULING\_GENESIS\_ +PERSISTENCE.md}): the canvas's default layout advisories +(\texttt{Canvas.layout\_defaults}) join the operation-authored surfaces, on +exactly the \texttt{SetMetadata} pattern (Section~\ref{sec:k0:score-settings}). + +\textbf{Payload schema.} \texttt{SetCanvasLayoutDefaultsOp \{ layout\_defaults: +CanvasLayoutDefaults \}} --- the full page-size-and-margins value. + +\textbf{Canonical encoding.} The length-framed canonical bytes of +\texttt{layout\_defaults}. \texttt{CanvasLayoutDefaults} carries no +schema-major-1-or-higher appends of its own --- the versioning that labels the +type ``schema major 1'' lives in the \emph{containing} \texttt{Canvas} walk, +not in this leaf --- so, as a standalone payload, it has exactly one layout +and this operation is schema major~0 unconditionally. + +\textbf{Reduction rule.} An advisory last-writer-wins field overwrite, +identical in discipline to \texttt{SetMetadata}: the latest write in canonical +order silently wins and the operation always applies --- no working state, no +conflict, and no idempotence short-circuit (a re-write of an identical value +is a legitimate new write, not a no-op). Graph-aware reduction overwrites +\texttt{Canvas.layout\_defaults}. + +\textbf{Conflict cases.} None (advisory LWW). + +\textbf{Undo semantics.} A value-restoring field overwrite +(Section~\ref{sec:k0:undo}): the write chain is seeded from the base value at +ingest, so undoing the first operational write restores the pre-operational +state rather than absence. + +\textbf{Re-anchoring.} Not applicable: the field is a score singleton with no +tombstonable anchor. + +\section{SetSpellingPrecedence} +\label{sec:k0:set-spelling-precedence} + +Ratified with the genesis operation tranche's G2a rung, alongside +\texttt{SetCanvasLayoutDefaults} above: the score's spelling-precedence +configuration (\texttt{Score.spelling\_precedence}) joins the +operation-authored surfaces, on the same \texttt{SetMetadata} pattern. + +\textbf{Payload schema.} \texttt{SetSpellingPrecedenceOp \{ precedence: +SpellingPrecedence \}} --- the full total-ordering value over the five +spelling-source kinds. + +\textbf{Canonical encoding.} The length-framed canonical bytes of +\texttt{precedence}. \texttt{SpellingPrecedence} has never been versioned --- +every codec walk that reads it, frozen and live alike, reads it through the +plain unversioned codec --- so this operation is schema major~0 +unconditionally, exactly as \texttt{SetCanvasLayoutDefaults} is. + +\textbf{Reduction rule.} An advisory last-writer-wins field overwrite, +identical in discipline to \texttt{SetMetadata} and +\texttt{SetCanvasLayoutDefaults}: no working state, no conflict, no +idempotence short-circuit. Graph-aware reduction overwrites +\texttt{Score.spelling\_precedence}. + +\textbf{Conflict cases.} None (advisory LWW). + +\textbf{Undo semantics.} A value-restoring field overwrite +(Section~\ref{sec:k0:undo}): the write chain is seeded from the base value at +ingest, so undoing the first operational write restores the pre-operational +state rather than absence. + +\textbf{Re-anchoring.} Not applicable: the field is a score singleton with no +tombstonable anchor. + \section{Repeat Structures} \label{sec:k0:repeat-structures} @@ -1494,8 +1603,9 @@ transaction minted is tombstoned. \texttt{StrictInverse} conflicts \emph{Value restoration} (this revision): for every last-writer-wins overwrite the target transaction performed --- event and identified-pitch modification, -respelling, cross-cutting modification, metadata, metric grid, meter change, -tempo segment, staff layout, and the user break advisories --- the reducer +respelling, cross-cutting modification, metadata, canvas layout defaults, +spelling precedence, metric grid, meter change, tempo segment, staff layout, +and the user break advisories --- the reducer maintains, per overwritten key, the \emph{canonical-order write chain} of (writer, value) pairs. Undoing the transaction restores each written key to its chain-predecessor value (or its absence, where the transaction introduced @@ -1633,15 +1743,23 @@ a fresh design. \section*{Retired slots (ratified Pass~12: outside the operation set)} \begin{description} - \item[Create score / canvas --- retired, closing P12-K8] - The document root and the canvas are \emph{structural givens}, not - operation products: \texttt{TypedObjectId} has no Canvas kind, the root is - never op-minted, and genesis is normatively the empty-document constructor - plus bundle creation, outside the operation set (core specification, - Chapter~5 \sectionsc{The Canvas}). These are not ``unavailable slots'' - awaiting a design --- no operation kind will be assigned to them. The - decision is revisited only if an addressable multi-canvas model is adopted - at a future schema major. + \item[Create score / canvas --- retired, closing P12-K8; narrowed by + \texttt{spec/RULING\_GENESIS\_PERSISTENCE.md}] + The document root and the canvas remain \emph{structural givens}, not + operation products: \texttt{TypedObjectId} has no Canvas kind, and the + root is never op-minted (core specification, Chapter~5 + \sectionsc{The Canvas}). \texttt{RULING\_GENESIS\_PERSISTENCE} reverses + only the wider claim this slot's description used to carry --- that the + score's \emph{contents} arrive outside the operation set. They do not: + the genesis tranche makes every mutable \texttt{Score} field + operation-authored, \texttt{Canvas.layout\_defaults} among them + (Section~\ref{sec:k0:set-canvas-layout-defaults}). What survives, and is + load-bearing, is the narrower claim above --- the canvas \emph{itself} is + still never minted, addressed, or deleted by any operation, and no + \texttt{TypedObjectId} kind exists for it. These are not ``unavailable + slots'' awaiting a design --- no operation kind will be assigned to them. + The decision is revisited only if an addressable multi-canvas model is + adopted at a future schema major. \end{description} \begin{nongoal} diff --git a/spec/text_projection.pdf b/spec/text_projection.pdf index d0153f9..9a338a8 100644 Binary files a/spec/text_projection.pdf and b/spec/text_projection.pdf differ diff --git a/spec/text_projection.tex b/spec/text_projection.tex index fb240cb..a98d436 100644 --- a/spec/text_projection.tex +++ b/spec/text_projection.tex @@ -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.8.0 --- The genesis operation vocabulary reaches the grammar}\\[4pt] + {\normalsize\color{epiphanyink}Version 0.9.0 --- The genesis settings setters reach the grammar}\\[4pt] {\small\color{epiphanyslate}Normative for the text form it defines} \vfill \end{titlepage} @@ -467,7 +467,7 @@ A projection is, in order: \begin{requirement} \label{req:textproj:header-version} A parser implementing this companion \MUST{} accept exactly one header - version: \texttt{(0 8 0)}, the version of the companion it implements. It + version: \texttt{(0 9 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 +518,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.8.0, neither a canonical operation nor canonical reduced state can + version~0.9.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} @@ -1042,6 +1042,8 @@ kind ::= "(insert-event " bytes " " value ")" | "(delete-repeat-structure " bytes ")" | "(transpose-interval (" bytes* ") " value ")" | "(create-instrument " value ")" + | "(set-canvas-layout-defaults " value ")" + | "(set-spelling-precedence " value ")" tuplet-comp ::= "not-in-tuplet" | "(replace-with-rest " value ")" | "(rewrite-tuplets (" bytes* "))" @@ -1118,7 +1120,7 @@ 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 8 0)) +(text-projection (0 9 0)) (document #x05050505050505050505050505050505) (profile full (0 1 0) (constraints 67108864 (retention 1 () true))) (canonical-base #x1f8b0000000000000000000000000000 #x 1 full (schema 0 1) #x0000) @@ -1325,6 +1327,18 @@ absorb it, exactly as the binary decoder does. may discard, so a stale projection is regenerated from the canonical document rather than converted. Multi-version acceptance and text migrate-on-read remain deferred, unchanged. \\ + \today & Chapter 5 & 0.9.0 --- The genesis settings setters reach the + grammar. The \texttt{kind} production gains + \texttt{"(set-canvas-layout-defaults " value ")"} and + \texttt{"(set-spelling-precedence " value ")"} + (\texttt{req:textproj:operation-vocabulary}), the second appended pair since + the header was gated to a single version at 0.7.0. + + Same forcing reason as the 0.8.0 bump above: holding the version while + 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. \\ \bottomrule \end{longtable} diff --git a/spec/vectors/decode_vectors.txt b/spec/vectors/decode_vectors.txt index dae4efa..d94d835 100644 --- a/spec/vectors/decode_vectors.txt +++ b/spec/vectors/decode_vectors.txt @@ -69,8 +69,10 @@ ops.operation_kind_tag accept - tag_28 1c ops.operation_kind_tag accept - tag_29 1d ops.operation_kind_tag accept - tag_30 1e ops.operation_kind_tag accept - tag_31 1f +ops.operation_kind_tag accept - tag_32 20 +ops.operation_kind_tag accept - tag_33 21 ops.operation_kind_tag accept - registered 1000000000000000000123456789abcdef -ops.operation_kind_tag reject unknown-discriminant tag_32_one_past_the_vocabulary 20 +ops.operation_kind_tag reject unknown-discriminant tag_34_one_past_the_vocabulary 22 ops.operation_kind_tag reject unknown-discriminant tag_200 c8 ops.operation_kind_tag reject truncated tag_empty - ops.operation_kind_tag reject trailing-bytes insert_event_trailing 0000 @@ -79,6 +81,10 @@ ops.operation_kind_tag reject truncated registered_one_byte_short 10000000000000 # ops.operation_envelope ops.operation_envelope accept - create_instrument 000000000000000100000000000000010000000000000000000000000000000001000000000000000100000000000000000000010000000000000001000000000000000000001f4100000010000000000000000000000100000000000000010c000000696e737472756d656e742d31000000000000000002000508000000000000000000f03f000000000000 ops.operation_envelope reject trailing-bytes create_instrument_trailing 000000000000000100000000000000010000000000000000000000000000000001000000000000000100000000000000000000010000000000000001000000000000000000001f4100000010000000000000000000000100000000000000010c000000696e737472756d656e742d31000000000000000002000508000000000000000000f03f00000000000000 +ops.operation_envelope accept - set_canvas_layout_defaults 000000000000000100000000000000020000000000000000000000000000000001000000000000000100000000000000000000010000000000000002000000000000000000002048000000080000000000000000805a40080000000000000000906240080000000000000000001e40080000000000000000001e40080000000000000000001e40080000000000000000001e40 +ops.operation_envelope reject trailing-bytes set_canvas_layout_defaults_trailing 000000000000000100000000000000020000000000000000000000000000000001000000000000000100000000000000000000010000000000000002000000000000000000002048000000080000000000000000805a40080000000000000000906240080000000000000000001e40080000000000000000001e40080000000000000000001e40080000000000000000001e4000 +ops.operation_envelope accept - set_spelling_precedence 000000000000000100000000000000030000000000000000000000000000000001000000000000000100000000000000000000010000000000000003000000000000000000002109000000050000000403020100 +ops.operation_envelope reject trailing-bytes set_spelling_precedence_trailing 00000000000000010000000000000003000000000000000000000000000000000100000000000000010000000000000000000001000000000000000300000000000000000000210900000005000000040302010000 # bundle.manifest bundle.manifest accept - empty_manifest 6f9e7d11689ab113c4a1f05faf60fe60050505050505050505050505050505050000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000 diff --git a/spec/vectors/textproj_document_vectors.txt b/spec/vectors/textproj_document_vectors.txt index 57464bf..0da573a 100644 --- a/spec/vectors/textproj_document_vectors.txt +++ b/spec/vectors/textproj_document_vectors.txt @@ -22,16 +22,16 @@ # document bytes are normative. `` is lowercase with no separators. # textproj.document -textproj.document accept - minimal 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a -textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a -textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a -textproj.document accept - rich_document 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a -textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e2028302037203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a -textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a -textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a -textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a -textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a -textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783032303230323032303230323032303230323032303230323032303230323032290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a -textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783034303430343034303430343034303430343034303430343034303430343034290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a -textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783033303330333033303330333033303330333033303330333033303330333033290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a -textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e2028302038203029290a28646f63756d656e742023783031303130313031303130313031303130313031303130313031303130313031290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929 +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