Genesis G2b: SetTuningContext, and the accept-set raise it pays for

Kind and tag 34, schema major 3, minor epoch 10. The last rung before G3, and
the one that closes P13-S13: the tuning context becomes operation-authored, so
it finally has a canonical carrier. The closure argument is the metadata
precedent, not the canonical base - the base embeds no graph values for any
field, and metadata has been durable purely through its operations since M2d.

The payload carries epiphany_core::TuningContextSettings, a five-field subset
of ScoreTuningContext, not the full graph type. ScoreTuningContext's codec
deliberately drops accidental_extensions, so a full-value payload would have
diverged between a live session, where accept stores the envelope as a value,
and the same document reloaded, where the field decodes empty. canonical_value!
could not have caught that: it compares bytes and never the originating value,
so a field that never reached the bytes is structurally invisible to it. The
subset makes the divergence unrepresentable instead of relying on a
normalization step nothing can enforce, and it costs no wire design - the
encoding is byte-identical to the existing five-field walk, which
tuning_context_settings_canonical_bytes_match_score_tuning_context asserts
directly. Reduction leaves accidental_extensions untouched.

SetTuningContext is the sole genesis payload born at major 3, because minimal
stamping is a function of each payload's value, so the accept-set raise is
charged to this one surface: OperationEnvelopeBlock 2 to 3. The doc comment
above it did not merely record the cap, it asserted that no operation payload
embeds the tuning context - a sentence this rung falsifies - so it is rewritten
rather than left beside a corrected constant.

Undo restores the seeded base settings, default or not, and the
never-authored versus authored-to-default distinction stays unobservable. An
earlier draft of the contract had that backwards; PLAN_GENESIS_OPS section 5
trap 5 withdrew it, and SetMetadata is the disproof.

Fixes two undefined references the interrupted run had not yet reached:
operation_catalog.tex referenced sec:evolution:major3, a label defined in
binary_format.tex, which LaTeX cannot resolve across documents. Replaced with
the sectionsc convention already used for every other cross-companion citation
in that file.

Gate: 1409 tests, clippy 0, fmt clean, conformance 8/8 including [7f], both
vector corpora regenerated, all four PDFs at 0 undefined references. The t5 and
t7 mutations were re-run independently and observed to fail as specified; the
remaining eight are not signed off, because the implementing run was stopped
before it reported them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
This commit is contained in:
Levi Neuwirth 2026-07-29 09:30:32 -04:00
parent 039a8ddec8
commit 13c3d2fd46
38 changed files with 1345 additions and 131 deletions

View File

@ -644,3 +644,45 @@ declaration order (`core_spec` §"Extension Declarations"), which had chunks bef
kinds and barriers. kinds and barriers.
Still no implementation. The companion is now complete enough to implement against. Still no implementation. The companion is now complete enough to implement against.
## Genesis tranche G2b: op-block accept-set raised to [0, 3] (2026-07-28)
`spec/CONTRACT_GENESIS_G2B_TUNING.md` charges this crate alone with the raise:
`max_supported_major(OperationEnvelopeBlock)` → 3. `SetTuningContext`
(`epiphany-ops`) is the sole genesis-tranche payload born at schema major 3 —
its carried `epiphany_core::TuningContextSettings` has mandatory (not
`Option`-hidden) appends past major 2, so a block carrying one is now born at
v3. This is the one-way door the governing plan (`PLAN_GENESIS_OPS.md` §4)
warned against burying inside routine work: G2 was split into G2a/G2b
precisely so the two major-0 setters (G2a) would not carry this raise, and it
lands alone here instead.
**The doc comment above `max_supported_major` asserted a rationale this rung
falsifies, and it had to move with the number, not just the number itself.**
The prior text ("Schema major 3 ... does not raise this role: no operation
payload embeds the tuning context, so no op block is ever born at v3") is now
false. Rewritten, and mutation-verified (`accept_set_doc_no_longer_claims_
no_payload_embeds_the_tuning_context`, `bundle.rs`) that the exact stale
sentence is absent from the source — not merely superseded by newer prose
elsewhere in the file, which a reader skimming only the doc comment could
still miss. This is the `binary_format.tex:2373` lesson applied to Rust doc
comments, not just the LaTeX companion.
**One existing test needed a value bump as a direct consequence, not a
touch-table item.** `committing_an_unsupported_major_op_root_makes_the_live_
bundle_read_only` staged a block at major 3 to exercise the "beyond the
accept-set" read-only path; major 3 is now *inside* the accept-set, so the
test silently stopped testing what its name claims (it would still pass,
vacuously, for the wrong reason) — caught by running the full bundle suite
after the raise, not by the touch table, which did not name this test.
Moved to major 4, with a comment explaining why 3 no longer works. This is
exactly the kind of collateral fix the touch table's "floor, not ceiling"
framing anticipates: a file not listed can still need an edit when a change
elsewhere makes its assumption stale.
**No `epiphany-bundle` participation in the never-authored/authored-to-default
question (pin 5).** This crate has no visibility into operation semantics —
`edit_barriers` and the op-block bytes are opaque to it — so pin 5's
seeded-undo discipline is entirely `epiphany-ops`'s concern. This crate's
only stake is that the raise is now paid for real, once, by the value that
actually needs it.

View File

@ -50,23 +50,26 @@ pub const SUPPORTED_SCHEMA_MAJOR: u16 = 0;
/// bound of its per-role accept-set `[0, max]` (Binary Format companion /// bound of its per-role accept-set `[0, max]` (Binary Format companion
/// §"Schema Major 1", "The accept-set gate"). /// §"Schema Major 1", "The accept-set gate").
/// ///
/// `OperationEnvelopeBlock` admits major 2 (schema major 2 fills the /// `OperationEnvelopeBlock` admits major 3 (schema major 2 fills the
/// cross-cutting/staff/metadata bodies its payloads embed; major 1 embedded a /// cross-cutting/staff/metadata bodies its payloads embed; major 1 embedded a
/// v1 `CreateRegion`; the reader treats the block bytes opaquely, so it /// v1 `CreateRegion`; the reader treats the block bytes opaquely, so it
/// parses a higher-major block without decoding the payload). Schema major 3 /// parses a higher-major block without decoding the payload). **Schema major
/// (Push 4b tranche 3b-i) does **not** raise this role: no operation payload /// 3 is raised by genesis tranche G2b** (`spec/CONTRACT_GENESIS_G2B_TUNING.md`):
/// embeds the tuning context, so no op block is ever born at v3. `Snapshot` /// `SetTuningContext` is the sole operation payload that embeds the tuning
/// admits major 3 for the acceleration full-`Score` form (decoded through /// context (`epiphany_core::TuningContextSettings`, born at major 3
/// the core versioned seam); the canonical BASE carried under the same kind /// unconditionally), so an op block carrying one is now born at v3 — this
/// must stay major 0, enforced per role. Every other role stays at /// superseded the earlier Push 4b tranche 3b-i note claiming no payload ever
/// [`SUPPORTED_SCHEMA_MAJOR`] until its own versioned path lands — the /// would. `Snapshot` admits major 3 for the acceleration full-`Score` form
/// layout cache, the operation index, and the manifest (carried opaquely, /// (decoded through the core versioned seam); the canonical BASE carried
/// under the same kind must stay major 0, enforced per role. Every other role
/// stays at [`SUPPORTED_SCHEMA_MAJOR`] until its own versioned path lands —
/// the layout cache, the operation index, and the manifest (carried opaquely,
/// never grows a versioned layout). A chunk above its role's max is not /// never grows a versioned layout). A chunk above its role's max is not
/// admitted; for a **canonical** role that means the bundle opens read-only /// admitted; for a **canonical** role that means the bundle opens read-only
/// (a lower-major-only reader meeting a newer op block), not a hard reject. /// (a lower-major-only reader meeting a newer op block), not a hard reject.
pub fn max_supported_major(kind: ChunkKind) -> u16 { pub fn max_supported_major(kind: ChunkKind) -> u16 {
match kind { match kind {
ChunkKind::OperationEnvelopeBlock => 2, ChunkKind::OperationEnvelopeBlock => 3,
// The payload-polymorphic Snapshot role: the acceleration // The payload-polymorphic Snapshot role: the acceleration
// full-`Score` form is decoded through the core versioned seam // full-`Score` form is decoded through the core versioned seam
// (`Score::decode_canonical_versioned`, majors {0,1,2,3}). The // (`Score::decode_canonical_versioned`, majors {0,1,2,3}). The
@ -1380,14 +1383,17 @@ mod tests {
// CreateRegion), and schema major 2 to major 2 (a block bearing a v2 // CreateRegion), and schema major 2 to major 2 (a block bearing a v2
// cross-cutting/staff/metadata value); every other role stays exact-0 // cross-cutting/staff/metadata value); every other role stays exact-0
// until its own versioned path lands, and the manifest stays major 0 // until its own versioned path lands, and the manifest stays major 0
// forever. Schema major 3 (Push 4b tranche 3b-i, §"Schema Major 3") // forever. Genesis tranche G2b (`spec/CONTRACT_GENESIS_G2B_TUNING.md`)
// raises only the snapshot role — no operation payload embeds the // raises the op-block role to major 3: `SetTuningContext` is the sole
// tuning context, so the op-block role's admission is untouched. // operation payload that embeds the tuning context, born at major 3
// unconditionally, so a block carrying one is now born at v3 — this
// supersedes the earlier Push 4b tranche 3b-i note claiming no
// payload ever would.
assert_eq!(SchemaVersion::V1.major, 1); assert_eq!(SchemaVersion::V1.major, 1);
assert_eq!(SchemaVersion::V2.major, 2); assert_eq!(SchemaVersion::V2.major, 2);
assert_eq!(SchemaVersion::V3.major, 3); assert_eq!(SchemaVersion::V3.major, 3);
// The op-block role admits [0, 2] — unchanged by schema major 3. // (t4) The op-block role admits [0, 3] as of genesis tranche G2b.
assert_eq!(max_supported_major(ChunkKind::OperationEnvelopeBlock), 2); assert_eq!(max_supported_major(ChunkKind::OperationEnvelopeBlock), 3);
// The snapshot role admits the major-3 acceleration form (decoded // The snapshot role admits the major-3 acceleration form (decoded
// through the core versioned seam); the canonical BASE stays major 0 // through the core versioned seam); the canonical BASE stays major 0
// per role (`mis_stamped_canonical_base`). The remaining roles stay // per role (`mis_stamped_canonical_base`). The remaining roles stay
@ -1401,16 +1407,45 @@ mod tests {
assert_eq!(Manifest::SCHEMA.major, 0); assert_eq!(Manifest::SCHEMA.major, 0);
} }
/// (t10) `CONTRACT_GENESIS_G2B_TUNING.md` pin 3: the doc comment above
/// `max_supported_major` used to assert a rationale G2b falsifies (no
/// operation payload reaches the tuning context, so no op block would
/// ever reach schema major 3). That claim must be gone from the source,
/// not merely superseded in prose elsewhere; a stale rationale beside a
/// corrected constant is exactly how `binary_format.tex:2373` decayed.
///
/// **Mutation:** restore the stale sentence into the doc comment; must
/// fail.
#[test]
fn accept_set_doc_no_longer_claims_no_payload_embeds_the_tuning_context() {
let source = include_str!("bundle.rs");
let stale_claim: String = ["no operation payload ", "embeds the tuning context"].concat();
let stale_consequence: String = ["no op block is ever ", "born at v3"].concat();
assert!(
!source.contains(&stale_claim),
"the doc comment above max_supported_major must not assert the falsified claim anymore"
);
assert!(
!source.contains(&stale_consequence),
"the doc comment above max_supported_major must not assert the falsified consequence anymore"
);
}
#[test] #[test]
fn committing_an_unsupported_major_op_root_makes_the_live_bundle_read_only() { fn committing_an_unsupported_major_op_root_makes_the_live_bundle_read_only() {
// A commit publishes an op block beyond this reader's accept-set (a // A commit publishes an op block beyond this reader's accept-set (a
// forward-compat write): structural validation lets it through, but the // forward-compat write): structural validation lets it through, but the
// LIVE bundle must go read-only at once — not only on the next reopen — // LIVE bundle must go read-only at once — not only on the next reopen —
// so no further commit runs against canonical history it cannot parse. // so no further commit runs against canonical history it cannot parse.
//
// Major 4, not 3: genesis tranche G2b raised the op-block accept-set
// to [0, 3] (`SetTuningContext` is born at major 3), so major 3 is now
// admitted and this test's "future major" must move past it to stay
// an actual test of the read-only-on-overflow path.
let mut bundle = fresh_bundle(); let mut bundle = fresh_bundle();
let block = StagedChunk::operation_block_versioned( let block = StagedChunk::operation_block_versioned(
crate::block::encode_block(&[vec![1u8, 2, 3]]), crate::block::encode_block(&[vec![1u8, 2, 3]]),
SchemaVersion::new(3, 0), SchemaVersion::new(4, 0),
); );
bundle bundle
.commit(&[block], |ctx| { .commit(&[block], |ctx| {
@ -1426,7 +1461,7 @@ mod tests {
); );
assert!(bundle.anomalies().iter().any(|a| matches!( assert!(bundle.anomalies().iter().any(|a| matches!(
a, a,
IntegrityAnomaly::UnsupportedCanonicalChunkMajor { schema_major: 3 } IntegrityAnomaly::UnsupportedCanonicalChunkMajor { schema_major: 4 }
))); )));
// A further commit against the now-read-only bundle is refused. // A further commit against the now-read-only bundle is refused.
let more = StagedChunk::operation_block_versioned( let more = StagedChunk::operation_block_versioned(

View File

@ -1441,3 +1441,53 @@ in the *containing* `Canvas` walk (`dec_canvas_v0` default-fills the field;
itself. So neither op gains a `schema_major()` arm — both fall into the 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 existing `_ => 0` catch-all in `epiphany-ops`. This is unlike G1's
`CreateInstrument`, whose carried `Instrument` has mandatory major-2 appends. `CreateInstrument`, whose carried `Instrument` has mandatory major-2 appends.
## Genesis tranche G2b — `TuningContextSettings`, the subset type
## (2026-07-28)
`spec/CONTRACT_GENESIS_G2B_TUNING.md` §1 (RATIFIED, resolving
`spec/PLAN_GENESIS_OPS.md` §3's open pin) adds `SetTuningContext` to
`epiphany-ops`, the G2b rung of the genesis ladder. Unlike G1 and G2a, its
carried payload is **not** the full `ScoreTuningContext` — it is a new type,
`epiphany_core::TuningContextSettings`, holding exactly the five fields
`ScoreTuningContext`'s `Codec` actually walks onto the wire
(`default_pitch_space`, `default_tuning_system`, `reference`, `smufl`,
`overrides`), in that same order.
**Why a new type, not the full value or a normalizing construction check.**
`ScoreTuningContext`'s `Codec` deliberately drops `accidental_extensions` on
encode and default-fills it to `Vec::new()` on decode (the field is staged out
of schema major 3). `OperationSet::accept` stores the authored envelope as a
**value**, so a `SetTuningContext` carrying the full `ScoreTuningContext`
would reduce with `accidental_extensions` intact on the authoring replica and
empty on any replica that received the document through serialization — the
same document in two graph states, depending only on whether you just
authored it. `canonical_value!`'s generated `decode_canonical` cannot catch
this: it compares decode → `finish()` → re-encode bytes, never the
originating value, so a field that never reaches the bytes is invisible to
it.
Normalization (clearing the field at construction) and reject-on-non-empty
were both rejected: normalization makes correctness depend on remembering to
clear a field at every construction site, enforced by nothing the compiler or
codec can see — a shape this track has been burned by twice already (four
stale literal sites at Push 4a, six found during G2a); reject-on-non-empty
turns an in-memory-only field into an authoring error for callers who never
opted into persistence. The subset type makes the divergence
**unrepresentable**: a field that does not exist cannot be set wrongly.
**No new byte layout.** `TuningContextSettings`'s `Codec` is byte-identical to
`ScoreTuningContext`'s existing five-field walk by construction — same five
fields, same order, same per-field codecs — asserted directly by
`tuning_context_settings_canonical_bytes_match_score_tuning_context`
(`codec.rs`). This is a type-level narrowing, not a new wire form, so
`canonical_value!` still applies and the G1/G2a payload template is
unchanged. One more `canonical_value!` line makes it reachable per-value.
`SetTuningContext` **does** gain a real `schema_major()` arm returning 3,
unconditionally — the opposite of G2a. `ScoreTuningContext`'s wire form is
born at schema major 3 and its appends (`smufl`, `overrides`) are mandatory,
not `Option`-hidden, so there is no lower-major layout for this payload to
fall back to. This is the sole surface among the nine genesis-tranche
settings/creates that drags `OperationEnvelopeBlock`'s accept-set from 2 to 3
(`epiphany-bundle`'s `DECISIONS.md`).

View File

@ -61,8 +61,8 @@ use crate::graph::{
SpannerKind, Staff, StaffBasedContent, StaffBracketKind, StaffExtent, StaffGroup, SpannerKind, Staff, StaffBasedContent, StaffBracketKind, StaffExtent, StaffGroup,
StaffGroupKind, StaffInstance, StaffLineConfiguration, StemDirection, SubBeam, StaffGroupKind, StaffInstance, StaffLineConfiguration, StemDirection, SubBeam,
TempoMapReference, TextLineDefinition, Tie, TieClass, TimeExtent, TimeSignature, TempoMapReference, TextLineDefinition, Tie, TieClass, TimeExtent, TimeSignature,
TimeSignatureDisplay, Timestamp, Tuplet, TupletRatio, UnpitchedMember, ViewDefinition, Voice, TimeSignatureDisplay, Timestamp, TuningContextSettings, Tuplet, TupletRatio, UnpitchedMember,
VoiceOrigin, Volta, ViewDefinition, Voice, VoiceOrigin, Volta,
}; };
use crate::ids::{ use crate::ids::{
AnalysisLayerId, AnalyticalAnnotationId, BarlineAlignmentGroupId, BeamId, ChordSymbolId, AnalysisLayerId, AnalyticalAnnotationId, BarlineAlignmentGroupId, BeamId, ChordSymbolId,
@ -1984,6 +1984,40 @@ impl Codec for ScoreTuningContext {
}) })
} }
} }
// `TuningContextSettings` (genesis tranche G2b,
// `spec/CONTRACT_GENESIS_G2B_TUNING.md` §1): the authored subset of
// `ScoreTuningContext` that `SetTuningContext` carries — exactly its five
// wire-bearing fields, in the codec's existing order. This walk is
// **byte-identical** to `ScoreTuningContext`'s `enc`/`dec` above by
// construction (same five fields, same order, same per-field codecs) — a
// type-level narrowing, not a new wire form. The identity is asserted by a
// round-trip test just below
// (`tuning_context_settings_canonical_bytes_match_score_tuning_context`).
impl Codec for TuningContextSettings {
fn enc(&self, out: &mut Vec<u8>) {
self.default_pitch_space.enc(out);
self.default_tuning_system.enc(out);
self.reference.enc(out);
self.smufl.enc(out);
self.overrides.enc(out);
}
fn dec(r: &mut Reader<'_>) -> Result<Self> {
let default_pitch_space = Codec::dec(r)?;
let default_tuning_system = Codec::dec(r)?;
let reference = Codec::dec(r)?;
let smufl = Codec::dec(r)?;
let overrides = Codec::dec(r)?;
Ok(TuningContextSettings {
default_pitch_space,
default_tuning_system,
reference,
smufl,
overrides,
})
}
}
// Schema major 2: the cross-cutting bodies filled (appended fields); the // Schema major 2: the cross-cutting bodies filled (appended fields); the
// frozen prior layouts are read by the `dec_*_v1` sub-decoders. // frozen prior layouts are read by the `dec_*_v1` sub-decoders.
struct_codec!(Slur { struct_codec!(Slur {
@ -3545,6 +3579,12 @@ canonical_value! {
// own — see the contract's "Why these two" table. // own — see the contract's "Why these two" table.
CanvasLayoutDefaults, CanvasLayoutDefaults,
SpellingPrecedence, SpellingPrecedence,
// Genesis tranche G2b (`CONTRACT_GENESIS_G2B_TUNING.md` §1) —
// SetTuningContext embeds this authored subset, not the full
// `ScoreTuningContext`. Its `Codec` above is byte-identical to
// `ScoreTuningContext`'s five-field walk; this makes that layout
// reachable per-value, exactly as `ScoreTuningContext` above is.
TuningContextSettings,
} }
#[cfg(test)] #[cfg(test)]
@ -3873,6 +3913,65 @@ mod tests {
assert!(back.accidental_extensions.is_empty()); assert!(back.accidental_extensions.is_empty());
} }
/// Genesis tranche G2b (`CONTRACT_GENESIS_G2B_TUNING.md` §1, pin/touch-row
/// 3): `TuningContextSettings`'s canonical encoding is **byte-identical**
/// to `ScoreTuningContext`'s existing five-field walk — a type-level
/// narrowing, not a new wire form. Proven directly against non-default
/// values in all five fields, not just the default.
///
/// **Mutation:** reorder two fields in `TuningContextSettings::enc` (e.g.
/// swap `smufl` and `overrides`) without making the matching change in
/// `ScoreTuningContext::enc`; must fail.
#[test]
fn tuning_context_settings_canonical_bytes_match_score_tuning_context() {
use crate::accidental::{SmuflVersion, SmuflVersionRequirement};
use crate::graph::{ScoreTuningContext, TuningContextSettings};
use crate::ids::{ReplicaId, VoiceId};
use crate::pitch::TuningSystemId;
use crate::tuning::{TuningOverride, TuningScope};
let full = ScoreTuningContext {
smufl: SmuflVersionRequirement {
minimum: SmuflVersion::from_decimal(1, "12").unwrap(),
authored_against: SmuflVersion::from_decimal(1, "18").unwrap(),
},
overrides: vec![TuningOverride {
scope: TuningScope::Voice(VoiceId::new(ReplicaId(1), 7)),
pitch_space: None,
tuning_system: Some(TuningSystemId::new("tet-19")),
reference: None,
}],
..ScoreTuningContext::default()
};
let settings = TuningContextSettings {
default_pitch_space: full.default_pitch_space.clone(),
default_tuning_system: full.default_tuning_system.clone(),
reference: full.reference.clone(),
smufl: full.smufl,
overrides: full.overrides.clone(),
};
let mut full_bytes = Vec::new();
full.enc(&mut full_bytes);
let mut settings_bytes = Vec::new();
settings.enc(&mut settings_bytes);
assert_eq!(
settings_bytes, full_bytes,
"TuningContextSettings must encode byte-identically to ScoreTuningContext's five-field walk"
);
let decoded =
TuningContextSettings::dec(&mut Reader::new(&settings_bytes)).expect("decodes");
assert_eq!(decoded.default_pitch_space, settings.default_pitch_space);
assert_eq!(
decoded.default_tuning_system,
settings.default_tuning_system
);
assert_eq!(decoded.reference, settings.reference);
assert_eq!(decoded.smufl, settings.smufl);
assert_eq!(decoded.overrides, settings.overrides);
}
#[test] #[test]
fn generator_scores_round_trip() { fn generator_scores_round_trip() {
for seed in 0..200u64 { for seed in 0..200u64 {

View File

@ -1705,6 +1705,49 @@ impl Default for ScoreTuningContext {
} }
} }
/// The **authored subset** of [`ScoreTuningContext`] that `SetTuningContext`
/// (genesis tranche G2b, `spec/CONTRACT_GENESIS_G2B_TUNING.md` §1) carries:
/// exactly the five fields `ScoreTuningContext`'s `Codec` actually walks onto
/// the wire (`codec.rs`'s hand-written `impl Codec for ScoreTuningContext`),
/// in that same order. `accidental_extensions` is **not** a field of this
/// type — it is deliberately absent, not cleared or normalized.
///
/// **Why a new type rather than reusing `ScoreTuningContext` directly.**
/// `ScoreTuningContext`'s `Codec` drops `accidental_extensions` on encode and
/// default-fills it to `Vec::new()` on decode: the field is staged out of
/// schema major 3 and stays in-memory-only. If an operation carried the full
/// `ScoreTuningContext`, `OperationSet::accept` would store the authored
/// envelope as a **value** (with `accidental_extensions` intact), while a
/// document reloaded from bytes would decode the same envelope with that
/// field reconstructed as empty — two divergent graph states from one
/// document, observable only by whether you just authored it or reloaded it.
/// A field that never reaches the wire cannot be caught by
/// `canonical_value!`'s decode → `finish()` → re-encode byte comparison,
/// because that comparison never touches the originating value.
///
/// This type makes the divergence **unrepresentable**: it has no
/// `accidental_extensions` field to diverge on. `SetTuningContext`'s
/// reduction writes exactly these five fields onto `score.tuning_context` and
/// leaves `accidental_extensions` untouched, preserving whatever the graph
/// already held. This is a **type-level narrowing, not a new wire form** —
/// `TuningContextSettings`'s canonical encoding is byte-for-byte identical to
/// `ScoreTuningContext`'s existing five-field walk (asserted in
/// `codec.rs`), so the packet designs no new layout.
///
/// When a later schema major lands `accidental_extensions` on the wire, this
/// type gains the field like any other major payload change — the same cost
/// normalization would have paid, but without making the never-authored /
/// authored-to-default distinction depend on a clearing discipline enforced
/// by nothing the compiler can see.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct TuningContextSettings {
pub default_pitch_space: PitchSpaceId,
pub default_tuning_system: TuningSystemId,
pub reference: ReferencePitch,
pub smufl: crate::accidental::SmuflVersionRequirement,
pub overrides: Vec<crate::tuning::TuningOverride>,
}
/// The root object of a score (Chapter 5 §"Top-Level Score Structure"). /// The root object of a score (Chapter 5 §"Top-Level Score Structure").
/// ///
/// This carries the full Chapter 5 top-level shape. The invariant-bearing /// This carries the full Chapter 5 top-level shape. The invariant-bearing

View File

@ -160,8 +160,8 @@ pub use graph::{
SoundConfiguration, SpaceUnit, SpanStyle, Spanner, SpannerKind, Staff, StaffBasedContent, SoundConfiguration, SpaceUnit, SpanStyle, Spanner, SpannerKind, Staff, StaffBasedContent,
StaffBracketKind, StaffExtent, StaffGroup, StaffGroupKind, StaffInstance, StaffBracketKind, StaffExtent, StaffGroup, StaffGroupKind, StaffInstance,
StaffLineConfiguration, StemDirection, SubBeam, TempoMapReference, TextLineDefinition, Tie, StaffLineConfiguration, StemDirection, SubBeam, TempoMapReference, TextLineDefinition, Tie,
TieClass, TimeExtent, TimeSignature, TimeSignatureDisplay, Timestamp, Tuplet, TupletRatio, TieClass, TimeExtent, TimeSignature, TimeSignatureDisplay, Timestamp, TuningContextSettings,
UnpitchedMember, ViewDefinition, Voice, VoiceOrigin, Volta, Tuplet, TupletRatio, UnpitchedMember, ViewDefinition, Voice, VoiceOrigin, Volta,
}; };
pub use tempo::{ pub use tempo::{

View File

@ -28,7 +28,7 @@ use crate::graph::{
AnnotationAnchor, DecompositionSource, EventOrderingDAG, GestureAnchoring, KeySignature, AnnotationAnchor, DecompositionSource, EventOrderingDAG, GestureAnchoring, KeySignature,
MetadataValue, RegionContent, RegionTimeModel, RepeatKind, ScoreTuningContext, MetadataValue, RegionContent, RegionTimeModel, RepeatKind, ScoreTuningContext,
SoundConfiguration, SpaceUnit, SpannerKind, StaffGroupKind, TieClass, TimeSignature, SoundConfiguration, SpaceUnit, SpannerKind, StaffGroupKind, TieClass, TimeSignature,
TimeSignatureDisplay, Timestamp, TupletRatio, VoiceOrigin, TimeSignatureDisplay, Timestamp, TuningContextSettings, TupletRatio, VoiceOrigin,
}; };
use crate::textvalue::{kebab, Sexp, TextError, TextValue}; use crate::textvalue::{kebab, Sexp, TextError, TextValue};
use crate::textvalue_impls::class_of; use crate::textvalue_impls::class_of;
@ -447,6 +447,38 @@ impl TextValue for ScoreTuningContext {
} }
} }
/// The **authored subset** of `ScoreTuningContext` that `SetTuningContext`
/// carries (genesis tranche G2b, `spec/CONTRACT_GENESIS_G2B_TUNING.md` §1) —
/// the same five fields, in the same order, as `ScoreTuningContext`'s
/// projection above; `accidental_extensions` has no field here to project.
impl TextValue for TuningContextSettings {
fn project(&self) -> Sexp {
Sexp::List(vec![
Sexp::Symbol(kebab("TuningContextSettings")),
self.default_pitch_space.project(),
self.default_tuning_system.project(),
self.reference.project(),
self.smufl.project(),
self.overrides.project(),
])
}
fn parse(s: &Sexp) -> Result<Self, TextError> {
let fields = s.expect_struct(&kebab("TuningContextSettings"), 5)?;
let default_pitch_space = TextValue::parse(&fields[0])?;
let default_tuning_system = TextValue::parse(&fields[1])?;
let reference = TextValue::parse(&fields[2])?;
let smufl = TextValue::parse(&fields[3])?;
let overrides = TextValue::parse(&fields[4])?;
Ok(TuningContextSettings {
default_pitch_space,
default_tuning_system,
reference,
smufl,
overrides,
})
}
}
// =========================================================================== // ===========================================================================
// Tagged unions. // Tagged unions.
// =========================================================================== // ===========================================================================

View File

@ -20,8 +20,8 @@
//! container is what the barrier's **scope** is for, and scope is matched //! container is what the barrier's **scope** is for, and scope is matched
//! against the target's real containment, precisely. //! against the target's real containment, precisely.
//! * **Score-level operations** (`SetMetadata`, the transaction descriptor, //! * **Score-level operations** (`SetMetadata`, the transaction descriptor,
//! `SetCanvasLayoutDefaults`, `SetSpellingPrecedence`) name no graph object: //! `SetCanvasLayoutDefaults`, `SetSpellingPrecedence`, `SetTuningContext`)
//! only a score-wide barrier (empty //! name no graph object: only a score-wide barrier (empty
//! `affected_object_kinds`, `WholeScore`/`TuningContext`/`Registered` scope) //! `affected_object_kinds`, `WholeScore`/`TuningContext`/`Registered` scope)
//! can match them. //! can match them.
//! * **Extension-defined operations** (`OperationKind::Registered`) carry a //! * **Extension-defined operations** (`OperationKind::Registered`) carry a
@ -469,7 +469,11 @@ pub(crate) fn subjects_of(kind: &OperationKind, score: &Score) -> BarrierSubject
OperationKind::SetMetadata(_) OperationKind::SetMetadata(_)
| OperationKind::DeclareTransaction(_) | OperationKind::DeclareTransaction(_)
| OperationKind::SetCanvasLayoutDefaults(_) | OperationKind::SetCanvasLayoutDefaults(_)
| OperationKind::SetSpellingPrecedence(_) => BarrierSubjects::ScoreWide, | OperationKind::SetSpellingPrecedence(_)
// Genesis tranche G2b (`CONTRACT_GENESIS_G2B_TUNING.md`, one-time
// authorization): another score-singleton field overwrite naming no
// resolvable region or object, exactly like `SetMetadata`.
| OperationKind::SetTuningContext(_) => BarrierSubjects::ScoreWide,
OperationKind::CreateRepeatStructure(op) => one( OperationKind::CreateRepeatStructure(op) => one(
TypedObjectId::RepeatStructure(op.repeat_structure_id()), TypedObjectId::RepeatStructure(op.repeat_structure_id()),
repeat_context(score, &op.repeat), repeat_context(score, &op.repeat),

View File

@ -1153,28 +1153,28 @@ mod tests {
tag: 7 tag: 7
}) })
); );
// Operation-kind tag 34 is one past the vocabulary (the Phase-3 ops // Operation-kind tag 35 is one past the vocabulary (the Phase-3 ops
// tranche appended 24..=27, the repeat pair 28/29, `TransposeInterval` // tranche appended 24..=27, the repeat pair 28/29, `TransposeInterval`
// 30, genesis G1's `CreateInstrument` 31, genesis G2a's // 30, genesis G1's `CreateInstrument` 31, genesis G2a's
// `SetCanvasLayoutDefaults` 32 and `SetSpellingPrecedence` 33; // `SetCanvasLayoutDefaults` 32 and `SetSpellingPrecedence` 33,
// encodings are append-only). // genesis G2b's `SetTuningContext` 34; encodings are append-only).
// //
// This assertion named 30 until Push 5 / P4, 31 until genesis G1, and // This assertion named 30 until Push 5 / P4, 31 until genesis G1, 32
// 32 until genesis G2a — each time, by then, the number had become a // until genesis G2a, and 34 until genesis G2b — each time, by then,
// real kind, so the test was pinning a bug: a barrier that prohibited // the number had become a real kind, so the test was pinning a bug: a
// the new operation encoded fine and would not read back. It must be // barrier that prohibited the new operation encoded fine and would
// bumped by every tranche that appends a tag, and it is deliberately a // not read back. It must be bumped by every tranche that appends a
// literal rather than `PAYLOAD_FREE.len()` so the bump is a conscious // tag, and it is deliberately a literal rather than
// act. // `PAYLOAD_FREE.len()` so the bump is a conscious act.
let mut bytes = vec![0u8]; let mut bytes = vec![0u8];
bytes.extend(set_blob(&[])); bytes.extend(set_blob(&[]));
bytes.extend(set_blob(&[vec![34u8]])); bytes.extend(set_blob(&[vec![35u8]]));
bytes.push(0); bytes.push(0);
assert_eq!( assert_eq!(
EditBarrier::decode_canonical_bytes(&bytes), EditBarrier::decode_canonical_bytes(&bytes),
Err(BarrierDecodeError::InvalidTag { Err(BarrierDecodeError::InvalidTag {
kind: "OperationKindTag", kind: "OperationKindTag",
tag: 34 tag: 35
}) })
); );
} }

View File

@ -1888,3 +1888,72 @@ surface for a leak to appear on even before checking the hash. `gen_payload`
arms appended, reshuffling the seeded RNG stream exactly as at Phase D, Push 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 4a, and G1; the digest moved and was re-pinned with that reasoning recorded
in the test's own comment. in the test's own comment.
## Genesis tranche G2b — `SetTuningContext`, kind/tag 34, the accept-set raise
## (2026-07-28)
`spec/CONTRACT_GENESIS_G2B_TUNING.md` lands the third rung: the sole genesis
payload born at schema major 3, and the one surface among the nine
settings/creates that drags `OperationEnvelopeBlock`'s accept-set from 2 to 3.
Its carried type is **not** the full `ScoreTuningContext` but the new
`epiphany_core::TuningContextSettings` — the authored subset of exactly the
five wire-bearing fields; see that crate's `DECISIONS.md` for why (the
never-authored / authored-to-default divergence a full-value payload would
have introduced).
`set_tuning_context` copies `set_metadata` structurally — advisory LWW, no
conflict, no idempotence short-circuit — but writes onto `score.tuning_context`
**field by field** rather than by whole-struct assignment, because the payload
carries a different (narrower) type than the field it targets:
`accidental_extensions` is left untouched in both the ordinary apply path and
undo's restoration-apply path. This is the one place in this rung's reduction
code that could not be a literal copy-paste of `set_metadata`/
`set_canvas_layout_defaults`/`set_spelling_precedence` — those three write
`score.<field> = op.<value>.clone()` in one line because the carried type
*is* the field's type; here it is a strict subset, so the assignment is five
lines, one per subset field, deliberately omitting `accidental_extensions`.
**Pin 5, verified rather than assumed.** An earlier draft of the governing
contract required undo to distinguish "never authored" from
"authored-to-default" — `spec/PLAN_GENESIS_OPS.md` §5 trap 5, withdrawn
2026-07-28 before this packet started. `tuning_context_chain` is seeded from
`Score::empty`'s default in `seed_from_graph`, exactly as `metadata_chain` is,
so the first undo of a `SetTuningContext` write yields
`Restore(Some(Predecessor::Base(seeded)))` regardless of whether the seed (or
the write) happened to equal the type default. Test t7 asserts the two cases
(default seed, non-default seed) restore identically — proving the
distinction unobservable, not merely failing to test for it.
`schema_major()` gains a real, unconditional arm returning 3 — the opposite of
G2a's "no arm" pin, because `ScoreTuningContext`'s `smufl`/`overrides` appends
are mandatory (not `Option`-hidden), so no lower-major layout for this payload
exists (a `CreateInstrument`-shaped arm, not a `CreateRegion`-shaped one).
**The accept-set raise is entirely `epiphany-bundle`'s change** (see that
crate's `DECISIONS.md`); this crate's contribution is making `SetTuningContext`
the value that requires it.
**Boundary crossings, budgeted as the contract's one-time authorization.**
`editor-core/src/barriers.rs::subjects_of` gains kind 34 in the same
score-wide `SetMetadata(_) | DeclareTransaction(_) | ...` arm G2a's two kinds
joined — a score-level field overwrite with no resolvable region or object.
`layout-ir/src/barrier.rs`'s "one past the vocabulary" literal moves 34→35.
`testkit/src/generators.rs`'s `rng.below` bound and
`testkit/tests/text_projection_grammar.rs`'s kind count both move by one.
`testkit/src/layout_stub.rs`'s derived `PAYLOAD_FREE`-based draw needed no
edit — confirmed, not assumed, per the contract's explicit ask (touch row 21)
— because it derives from the vocabulary macro rather than being hand-extended.
**Decode vector pinned to literal bytes** (`vectors.rs`,
`set_tuning_context_envelope_decode_vector_is_pinned_to_literal_bytes`), not
merely round-tripped — the plan's trap 4 (a self-consistent discriminant
reorder once passed 1283 tests and 8/8 conformance). Mutation-verified: a
one-byte corruption of the pinned discriminant produces
`InvalidTag { kind: "OperationKind", tag: 99 }`, observed directly rather than
inferred.
**No pruning or compaction is implemented, enabled, or prepared here** — pin
9's explicit non-goal. The op log is now the sole canonical carrier of the
tuning context (the canonical base embeds no graph value for any `Score`
field), which is exactly why a future prune must stay blocked on disposition
C; this packet adds no `fn prune` and no scaffolding toward one.

View File

@ -39,8 +39,8 @@ use epiphany_core::{
CanvasLayoutDefaults, EventId, Instrument, InstrumentId, MetricGrid, MusicalPosition, CanvasLayoutDefaults, EventId, Instrument, InstrumentId, MetricGrid, MusicalPosition,
OperationId, PitchId, PitchSpelling, RegionId, RegionTimeModel, RepeatStructureId, ReplicaId, OperationId, PitchId, PitchSpelling, RegionId, RegionTimeModel, RepeatStructureId, ReplicaId,
ScoreMetadata, SpellingPrecedence, Staff, StaffInstance, StaffInstanceId, ScoreMetadata, SpellingPrecedence, Staff, StaffInstance, StaffInstanceId,
StaffLineConfiguration, TimeAnchor, TimeSignature, TranspositionInterval, TupletId, StaffLineConfiguration, TimeAnchor, TimeSignature, TranspositionInterval,
TypedObjectId, Voice, VoiceId, WallClockTime, TuningContextSettings, TupletId, TypedObjectId, Voice, VoiceId, WallClockTime,
}; };
use epiphany_determinism::{CanonicalDecode, CanonicalEncode}; use epiphany_determinism::{CanonicalDecode, CanonicalEncode};
@ -596,6 +596,9 @@ fn operation_kind(r: &mut Reader<'_>) -> Result<OperationKind> {
33 => OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { 33 => OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp {
precedence: value::<SpellingPrecedence>(r, "SpellingPrecedence")?, precedence: value::<SpellingPrecedence>(r, "SpellingPrecedence")?,
}), }),
34 => OperationKind::SetTuningContext(SetTuningContextOp {
settings: value::<TuningContextSettings>(r, "TuningContextSettings")?,
}),
tag => { tag => {
return Err(EnvelopeDecodeError::InvalidTag { return Err(EnvelopeDecodeError::InvalidTag {
kind: "OperationKind", kind: "OperationKind",
@ -895,6 +898,11 @@ pub(crate) mod tests {
precedence: valuegen::spelling_precedence(1), precedence: valuegen::spelling_precedence(1),
}) })
} }
OperationKindTag::SetTuningContext => {
OperationKind::SetTuningContext(SetTuningContextOp {
settings: valuegen::tuning_context_settings(1),
})
}
} }
} }

View File

@ -90,7 +90,7 @@ fn pitch(n: u64) -> PitchId {
/// Generates a random payload over the shared id space. /// Generates a random payload over the shared id space.
fn gen_payload(rng: &mut SplitMix64) -> OperationPayload { fn gen_payload(rng: &mut SplitMix64) -> OperationPayload {
let kind = match rng.below(31) { let kind = match rng.below(32) {
0 => { 0 => {
let voice = VoiceId::new(ReplicaId(7), rng.below(3)); let voice = VoiceId::new(ReplicaId(7), rng.below(3));
let position = MusicalPosition(RationalTime::from_int(rng.below(4) as i32)); let position = MusicalPosition(RationalTime::from_int(rng.below(4) as i32));
@ -300,6 +300,10 @@ fn gen_payload(rng: &mut SplitMix64) -> OperationPayload {
30 => OperationKind::SetSpellingPrecedence(crate::payload::SetSpellingPrecedenceOp { 30 => OperationKind::SetSpellingPrecedence(crate::payload::SetSpellingPrecedenceOp {
precedence: valuegen::spelling_precedence(rng.below(3) as u8), precedence: valuegen::spelling_precedence(rng.below(3) as u8),
}), }),
// Genesis tranche G2b: the sole genesis payload born at schema major 3.
31 => OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: valuegen::tuning_context_settings(rng.below(3) as u8),
}),
_ => OperationKind::SetStaffLayout(SetStaffLayoutOp { _ => OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: StaffInstanceId::new(ReplicaId(7), rng.below(3)), staff_instance: StaffInstanceId::new(ReplicaId(7), rng.below(3)),
instrument_override: None, instrument_override: None,

View File

@ -132,8 +132,9 @@ pub use payload::{
ModifyIdentifiedPitchOp, OperationKind, OperationKindTag, OperationPayload, PositionRemapping, ModifyIdentifiedPitchOp, OperationKind, OperationKindTag, OperationPayload, PositionRemapping,
ResolveConflictPayload, ResolveEquivocationPayload, RespellPitchOp, SetCanvasLayoutDefaultsOp, ResolveConflictPayload, ResolveEquivocationPayload, RespellPitchOp, SetCanvasLayoutDefaultsOp,
SetMetadataOp, SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp, SetMetadataOp, SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionCategory, SetTimeSignatureOp, SetTuningContextOp, SetUserPageBreakOp, SetUserSystemBreakOp,
TransactionDescriptor, TransposeIntervalOp, TransposeOp, TupletCompensation, TransactionCategory, TransactionDescriptor, TransposeIntervalOp, TransposeOp,
TupletCompensation,
}; };
pub use reduce::{ pub use reduce::{
canonical_reduction_order, GraphMaterialization, MaterializedState, ObjectState, PendingReason, canonical_reduction_order, GraphMaterialization, MaterializedState, ObjectState, PendingReason,

View File

@ -188,6 +188,8 @@ fn project_kind(kind: &OperationKind) -> V0OperationKind {
OperationKind::SetSpellingPrecedence(op) => { OperationKind::SetSpellingPrecedence(op) => {
V0OperationKind::SetSpellingPrecedence(op.clone()) V0OperationKind::SetSpellingPrecedence(op.clone())
} }
// Genesis tranche G2b: born past v0; projected verbatim.
OperationKind::SetTuningContext(op) => V0OperationKind::SetTuningContext(op.clone()),
} }
} }
@ -350,6 +352,8 @@ fn migrate_kind(kind: &V0OperationKind, context: &Score) -> Result<OperationKind
V0OperationKind::SetSpellingPrecedence(op) => { V0OperationKind::SetSpellingPrecedence(op) => {
OperationKind::SetSpellingPrecedence(op.clone()) OperationKind::SetSpellingPrecedence(op.clone())
} }
// Genesis tranche G2b: identity round-trip (no lossy v0 form).
V0OperationKind::SetTuningContext(op) => OperationKind::SetTuningContext(op.clone()),
}) })
} }

View File

@ -37,7 +37,8 @@ use epiphany_core::{
OperationId, Pitch, PitchId, PitchSpelling, Region, RegionId, RegionTimeModel, RepeatStructure, OperationId, Pitch, PitchId, PitchSpelling, Region, RegionId, RegionTimeModel, RepeatStructure,
RepeatStructureId, Rest, ScoreMetadata, Slur, Spanner, SpellingPrecedence, Staff, StaffId, RepeatStructureId, Rest, ScoreMetadata, Slur, Spanner, SpellingPrecedence, Staff, StaffId,
StaffInstance, StaffInstanceId, StaffLineConfiguration, TempoSegment, Tie, TimeAnchor, StaffInstance, StaffInstanceId, StaffLineConfiguration, TempoSegment, Tie, TimeAnchor,
TimeSignature, TransactionId, TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId, TimeSignature, TransactionId, TranspositionInterval, TuningContextSettings, TupletId,
TypedObjectId, Voice, VoiceId,
}; };
use epiphany_determinism::{ use epiphany_determinism::{
sorted_canonical, CanonicalDecode, CanonicalEncode, CanonicalSet, DecodeError, sorted_canonical, CanonicalDecode, CanonicalEncode, CanonicalSet, DecodeError,
@ -263,6 +264,15 @@ pub enum OperationKind {
/// Overwrite the score's spelling precedence (later-in-canonical-order /// Overwrite the score's spelling precedence (later-in-canonical-order
/// wins). /// wins).
SetSpellingPrecedence(SetSpellingPrecedenceOp), SetSpellingPrecedence(SetSpellingPrecedenceOp),
// --- Genesis tranche G2b (`spec/CONTRACT_GENESIS_G2B_TUNING.md`): the
// sole genesis payload born at schema major 3. Discriminant extends
// additively past 33.
// ---
/// Overwrite the score's tuning context settings (later-in-canonical-
/// order wins). Carries [`TuningContextSettings`], the authored subset of
/// `ScoreTuningContext` — `accidental_extensions` is not on the wire and
/// is left untouched by reduction (contract §1).
SetTuningContext(SetTuningContextOp),
} }
impl OperationKind { impl OperationKind {
@ -291,6 +301,13 @@ impl OperationKind {
| OperationKind::CreateStaff(_) | OperationKind::CreateStaff(_)
| OperationKind::SetMetadata(_) | OperationKind::SetMetadata(_)
| OperationKind::CreateInstrument(_) => 2, | OperationKind::CreateInstrument(_) => 2,
// Genesis tranche G2b (contract pin 2): unconditionally 3.
// `ScoreTuningContext`'s `smufl`/`overrides` appends are
// mandatory, not `Option`-hidden, so there is no lower-major
// layout for this payload — a `CreateInstrument`-shaped arm, not
// a `CreateRegion`-shaped one. The sole surface among the nine
// genesis settings/creates that raises the op-block accept-set.
OperationKind::SetTuningContext(_) => 3,
// Value-dependent: the embedded StaffLineConfiguration rides an // Value-dependent: the embedded StaffLineConfiguration rides an
// Option; None encodes byte-identically to the prior major. // Option; None encodes byte-identically to the prior major.
OperationKind::CreateRegion(op) => { OperationKind::CreateRegion(op) => {
@ -368,6 +385,9 @@ impl OperationKind {
// by the same accident as above, not by rule. // by the same accident as above, not by rule.
OperationKind::SetCanvasLayoutDefaults(_) => 32, OperationKind::SetCanvasLayoutDefaults(_) => 32,
OperationKind::SetSpellingPrecedence(_) => 33, OperationKind::SetSpellingPrecedence(_) => 33,
// Genesis tranche G2b; appended past 33. Coincides with tag 34 by
// the same accident as G1/G2a, not by rule.
OperationKind::SetTuningContext(_) => 34,
} }
} }
@ -424,6 +444,9 @@ impl OperationKind {
OperationKind::SetCanvasLayoutDefaults(_) | OperationKind::SetSpellingPrecedence(_) => { OperationKind::SetCanvasLayoutDefaults(_) | OperationKind::SetSpellingPrecedence(_) => {
Some(9) Some(9)
} }
// Minor 10 (Genesis tranche G2b), ratified
// `spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4.
OperationKind::SetTuningContext(_) => Some(10),
} }
} }
@ -471,6 +494,7 @@ impl OperationKind {
OperationKind::CreateInstrument(_) => OperationKindTag::CreateInstrument, OperationKind::CreateInstrument(_) => OperationKindTag::CreateInstrument,
OperationKind::SetCanvasLayoutDefaults(_) => OperationKindTag::SetCanvasLayoutDefaults, OperationKind::SetCanvasLayoutDefaults(_) => OperationKindTag::SetCanvasLayoutDefaults,
OperationKind::SetSpellingPrecedence(_) => OperationKindTag::SetSpellingPrecedence, OperationKind::SetSpellingPrecedence(_) => OperationKindTag::SetSpellingPrecedence,
OperationKind::SetTuningContext(_) => OperationKindTag::SetTuningContext,
} }
} }
} }
@ -516,6 +540,7 @@ impl CanonicalEncode for OperationKind {
OperationKind::CreateInstrument(op) => op.encode_canonical(out), OperationKind::CreateInstrument(op) => op.encode_canonical(out),
OperationKind::SetCanvasLayoutDefaults(op) => op.encode_canonical(out), OperationKind::SetCanvasLayoutDefaults(op) => op.encode_canonical(out),
OperationKind::SetSpellingPrecedence(op) => op.encode_canonical(out), OperationKind::SetSpellingPrecedence(op) => op.encode_canonical(out),
OperationKind::SetTuningContext(op) => op.encode_canonical(out),
} }
} }
} }
@ -570,6 +595,8 @@ pub enum OperationKindTag {
SetCanvasLayoutDefaults, SetCanvasLayoutDefaults,
/// Genesis tranche G2a. /// Genesis tranche G2a.
SetSpellingPrecedence, SetSpellingPrecedence,
/// Genesis tranche G2b.
SetTuningContext,
} }
/// The discriminant of [`OperationKindTag::Registered`], the one tag that /// The discriminant of [`OperationKindTag::Registered`], the one tag that
@ -684,6 +711,7 @@ operation_kind_tag_vocabulary! {
CreateInstrument = 31 => "create-instrument" @ Some(8), CreateInstrument = 31 => "create-instrument" @ Some(8),
SetCanvasLayoutDefaults = 32 => "set-canvas-layout-defaults" @ Some(9), SetCanvasLayoutDefaults = 32 => "set-canvas-layout-defaults" @ Some(9),
SetSpellingPrecedence = 33 => "set-spelling-precedence" @ Some(9), SetSpellingPrecedence = 33 => "set-spelling-precedence" @ Some(9),
SetTuningContext = 34 => "set-tuning-context" @ Some(10),
} }
impl CanonicalEncode for OperationKindTag { impl CanonicalEncode for OperationKindTag {
@ -1650,6 +1678,30 @@ impl CanonicalEncode for SetSpellingPrecedenceOp {
} }
} }
// --- Genesis tranche G2b (`spec/CONTRACT_GENESIS_G2B_TUNING.md`): the sole
// genesis payload born at schema major 3. LWW field-overwrite, the same
// `SetMetadata` discipline. ---
/// Overwrite the score's tuning context settings (operation_catalog
/// §SetTuningContext). Carries [`TuningContextSettings`] — the **authored
/// subset** of `ScoreTuningContext` (contract §1): exactly the five
/// wire-bearing fields, in the codec's existing order.
/// `accidental_extensions` is not a field of the carried type and is never
/// touched by reduction, which writes only these five fields onto
/// `score.tuning_context`. 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 SetTuningContextOp {
pub settings: TuningContextSettings,
}
impl CanonicalEncode for SetTuningContextOp {
fn encode_canonical(&self, out: &mut Vec<u8>) {
push_lp_bytes(out, &self.settings.canonical_bytes());
}
}
/// Set, replace, or (`None`) remove the single meter change at the anchor's /// Set, replace, or (`None`) remove the single meter change at the anchor's
/// resolved musical position in a region's default metric grid /// resolved musical position in a region's default metric grid
/// (operation_catalog §"Meter and Tempo Overwrites"). Carries the full /// (operation_catalog §"Meter and Tempo Overwrites"). Carries the full

View File

@ -40,7 +40,7 @@ use epiphany_core::{
SpellingDirective, SpellingPrecedence, SpellingScope, SpellingSource, Staff, StaffId, SpellingDirective, SpellingPrecedence, SpellingScope, SpellingSource, Staff, StaffId,
StaffInstance, StaffInstanceId, StaffLineConfiguration, TempoMap, TempoSegment, TempoShape, StaffInstance, StaffInstanceId, StaffLineConfiguration, TempoMap, TempoSegment, TempoShape,
TimeAnchor, TimeSignature, TimeSignatureId, TransactionId, TransposeRefusal, TimeAnchor, TimeSignature, TimeSignatureId, TransactionId, TransposeRefusal,
TranspositionInterval, TypedObjectId, Voice, VoiceId, VoiceOrigin, TranspositionInterval, TuningContextSettings, TypedObjectId, Voice, VoiceId, VoiceOrigin,
}; };
use epiphany_determinism::CanonicalEncode; use epiphany_determinism::CanonicalEncode;
@ -63,7 +63,8 @@ use crate::payload::{
InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
OperationKind, OperationPayload, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp, OperationKind, OperationPayload, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp,
SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp, SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetTimeSignatureOp, SetUserPageBreakOp, TransposeIntervalOp, TransposeOp, TupletCompensation, SetTimeSignatureOp, SetTuningContextOp, SetUserPageBreakOp, TransposeIntervalOp, TransposeOp,
TupletCompensation,
}; };
use crate::stamp::StampTuple; use crate::stamp::StampTuple;
use crate::support::{ObjectKind, SerializedCanonicalInputs}; use crate::support::{ObjectKind, SerializedCanonicalInputs};
@ -851,6 +852,11 @@ enum ValueRestoration {
SpellingPrecedence { SpellingPrecedence {
value: Option<SpellingPrecedence>, value: Option<SpellingPrecedence>,
}, },
/// Genesis tranche G2b. The subset type — `accidental_extensions` is not
/// part of it and is never touched by restoration.
TuningContext {
value: Option<TuningContextSettings>,
},
MetricGrid { MetricGrid {
region: RegionId, region: RegionId,
value: Option<MetricGrid>, value: Option<MetricGrid>,
@ -972,6 +978,11 @@ struct Reducer<'a> {
// the pre-operational value. // the pre-operational value.
canvas_layout_defaults_chain: WriteChain<CanvasLayoutDefaults>, canvas_layout_defaults_chain: WriteChain<CanvasLayoutDefaults>,
spelling_precedence_chain: WriteChain<SpellingPrecedence>, spelling_precedence_chain: WriteChain<SpellingPrecedence>,
// Genesis tranche G2b (`CONTRACT_GENESIS_G2B_TUNING.md` pin 5): same
// discipline, carrying the **subset** type — `accidental_extensions`
// never participates in undo, because no operation ever writes it and
// undo must leave it exactly as it found it.
tuning_context_chain: WriteChain<TuningContextSettings>,
break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>, break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>,
page_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 // Meter/tempo overwrite chains (Phase-3 tranche): `Some` = a set/replace at
@ -1085,6 +1096,7 @@ struct WorkingSnapshot {
metadata_chain: WriteChain<ScoreMetadata>, metadata_chain: WriteChain<ScoreMetadata>,
canvas_layout_defaults_chain: WriteChain<CanvasLayoutDefaults>, canvas_layout_defaults_chain: WriteChain<CanvasLayoutDefaults>,
spelling_precedence_chain: WriteChain<SpellingPrecedence>, spelling_precedence_chain: WriteChain<SpellingPrecedence>,
tuning_context_chain: WriteChain<TuningContextSettings>,
break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>, break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>,
page_break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>, page_break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>,
meter_change_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<Option<MeterChange>>>, meter_change_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<Option<MeterChange>>>,
@ -1367,6 +1379,7 @@ impl<'a> Reducer<'a> {
metadata_chain: WriteChain::new(), metadata_chain: WriteChain::new(),
canvas_layout_defaults_chain: WriteChain::new(), canvas_layout_defaults_chain: WriteChain::new(),
spelling_precedence_chain: WriteChain::new(), spelling_precedence_chain: WriteChain::new(),
tuning_context_chain: WriteChain::new(),
break_chain: BTreeMap::new(), break_chain: BTreeMap::new(),
page_break_chain: BTreeMap::new(), page_break_chain: BTreeMap::new(),
meter_change_chain: BTreeMap::new(), meter_change_chain: BTreeMap::new(),
@ -1469,6 +1482,17 @@ impl<'a> Reducer<'a> {
.seed(score.canvas.layout_defaults); .seed(score.canvas.layout_defaults);
self.spelling_precedence_chain self.spelling_precedence_chain
.seed(score.spelling_precedence.clone()); .seed(score.spelling_precedence.clone());
// Genesis tranche G2b (contract pin 5): same discipline, seeded with
// only the five wire-bearing fields — the subset the chain's value
// type carries. `accidental_extensions` is not part of the seed and
// is never touched by undo.
self.tuning_context_chain.seed(TuningContextSettings {
default_pitch_space: score.tuning_context.default_pitch_space.clone(),
default_tuning_system: score.tuning_context.default_tuning_system.clone(),
reference: score.tuning_context.reference.clone(),
smufl: score.tuning_context.smufl,
overrides: score.tuning_context.overrides.clone(),
});
for segment in &score.tempo_map.segments { for segment in &score.tempo_map.segments {
self.tempo_segment_chain self.tempo_segment_chain
.entry((None, resolved_anchor_position(&segment.start))) .entry((None, resolved_anchor_position(&segment.start)))
@ -2833,6 +2857,7 @@ impl<'a> Reducer<'a> {
self.set_canvas_layout_defaults(env, op) self.set_canvas_layout_defaults(env, op)
} }
OperationKind::SetSpellingPrecedence(op) => self.set_spelling_precedence(env, op), OperationKind::SetSpellingPrecedence(op) => self.set_spelling_precedence(env, op),
OperationKind::SetTuningContext(op) => self.set_tuning_context(env, op),
}, },
OperationPayload::ResolveConflict(op) => self.resolve_conflict(env, op), OperationPayload::ResolveConflict(op) => self.resolve_conflict(env, op),
OperationPayload::UndoTransaction(op) => self.undo_transaction(env, op), OperationPayload::UndoTransaction(op) => self.undo_transaction(env, op),
@ -2947,6 +2972,30 @@ impl<'a> Reducer<'a> {
OperationEffect::Applied OperationEffect::Applied
} }
/// Genesis tranche G2b (`CONTRACT_GENESIS_G2B_TUNING.md` pin 5): copies
/// `set_metadata` structurally — advisory LWW, no conflict, no
/// idempotence short-circuit. Writes exactly the five subset fields onto
/// `score.tuning_context` and **leaves `accidental_extensions`
/// untouched** — the carried payload has no such field, and preserving
/// whatever the graph already held is the ruling (contract §1).
fn set_tuning_context(
&mut self,
env: &OperationEnvelope,
op: &SetTuningContextOp,
) -> OperationEffect {
self.tuning_context_chain
.record(env.id, env.transaction, op.settings.clone());
if let Some(score) = self.graph.as_mut() {
score.tuning_context.default_pitch_space = op.settings.default_pitch_space.clone();
score.tuning_context.default_tuning_system = op.settings.default_tuning_system.clone();
score.tuning_context.reference = op.settings.reference.clone();
score.tuning_context.smufl = op.settings.smufl;
score.tuning_context.overrides = op.settings.overrides.clone();
// accidental_extensions is intentionally not touched here.
}
OperationEffect::Applied
}
fn set_metric_grid( fn set_metric_grid(
&mut self, &mut self,
env: &OperationEnvelope, env: &OperationEnvelope,
@ -5334,6 +5383,16 @@ impl<'a> Reducer<'a> {
}) })
} }
} }
// Genesis tranche G2b (contract pin 5): mirror the same shape.
match self.tuning_context_chain.undo_verdict(tx) {
ChainUndoVerdict::NotWritten => {}
ChainUndoVerdict::Superseded { by } => superseded.push(by),
ChainUndoVerdict::Restore(predecessor) => {
restorations.push(ValueRestoration::TuningContext {
value: predecessor.map(Predecessor::into_value),
})
}
}
for (region, chain) in &self.metric_grid_chain { for (region, chain) in &self.metric_grid_chain {
if !slot_live(TypedObjectId::Region(*region)) { if !slot_live(TypedObjectId::Region(*region)) {
continue; continue;
@ -5574,6 +5633,24 @@ impl<'a> Reducer<'a> {
.record(env.id, env.transaction, value); .record(env.id, env.transaction, value);
} }
} }
// Genesis tranche G2b (contract pin 5): mirror `Metadata`'s
// restoration-apply shape; writes only the five subset
// fields, leaving `accidental_extensions` untouched.
ValueRestoration::TuningContext { value } => {
if let Some(value) = value {
if let Some(score) = self.graph.as_mut() {
score.tuning_context.default_pitch_space =
value.default_pitch_space.clone();
score.tuning_context.default_tuning_system =
value.default_tuning_system.clone();
score.tuning_context.reference = value.reference.clone();
score.tuning_context.smufl = value.smufl;
score.tuning_context.overrides = value.overrides.clone();
}
self.tuning_context_chain
.record(env.id, env.transaction, value);
}
}
ValueRestoration::MetricGrid { region, value } => { ValueRestoration::MetricGrid { region, value } => {
self.metric_grid_chain self.metric_grid_chain
.entry(region) .entry(region)
@ -7551,6 +7628,7 @@ impl<'a> Reducer<'a> {
metadata_chain: self.metadata_chain.clone(), metadata_chain: self.metadata_chain.clone(),
canvas_layout_defaults_chain: self.canvas_layout_defaults_chain.clone(), canvas_layout_defaults_chain: self.canvas_layout_defaults_chain.clone(),
spelling_precedence_chain: self.spelling_precedence_chain.clone(), spelling_precedence_chain: self.spelling_precedence_chain.clone(),
tuning_context_chain: self.tuning_context_chain.clone(),
break_chain: self.break_chain.clone(), break_chain: self.break_chain.clone(),
page_break_chain: self.page_break_chain.clone(), page_break_chain: self.page_break_chain.clone(),
meter_change_chain: self.meter_change_chain.clone(), meter_change_chain: self.meter_change_chain.clone(),
@ -7590,6 +7668,7 @@ impl<'a> Reducer<'a> {
self.metadata_chain = s.metadata_chain; self.metadata_chain = s.metadata_chain;
self.canvas_layout_defaults_chain = s.canvas_layout_defaults_chain; self.canvas_layout_defaults_chain = s.canvas_layout_defaults_chain;
self.spelling_precedence_chain = s.spelling_precedence_chain; self.spelling_precedence_chain = s.spelling_precedence_chain;
self.tuning_context_chain = s.tuning_context_chain;
self.break_chain = s.break_chain; self.break_chain = s.break_chain;
self.page_break_chain = s.page_break_chain; self.page_break_chain = s.page_break_chain;
self.meter_change_chain = s.meter_change_chain; self.meter_change_chain = s.meter_change_chain;
@ -10914,6 +10993,92 @@ mod tests {
0, 0,
"SetSpellingPrecedence stays in the major-0 catch-all" "SetSpellingPrecedence stays in the major-0 catch-all"
); );
// (t2) Genesis tranche G2b: the opposite of G2a's pin — SetTuningContext
// is *unconditionally* 3, the same "no lower-major layout exists" shape
// as CreateInstrument above (not a value-dependent Option-hidden
// embedding). Asserted on the minimal (default) settings specifically,
// so an arm that becomes value-dependent is caught immediately.
assert_eq!(
OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: crate::valuegen::tuning_context_settings(0),
})
.schema_major(),
3,
"SetTuningContext is unconditionally v3, even for the minimal settings"
);
}
/// (t1) Genesis tranche G2b: kind and tag are both 34, and the
/// discriminant byte leads the canonical encoding of the op payload.
///
/// **Mutation:** move either `OperationKind::SetTuningContext`'s
/// discriminant or `OperationKindTag::SetTuningContext`'s tag to 35;
/// must fail.
#[test]
fn t1_set_tuning_context_kind_and_tag_are_both_34() {
use crate::payload::{OperationKindTag, SetTuningContextOp};
let op = OperationKind::SetTuningContext(SetTuningContextOp {
settings: crate::valuegen::tuning_context_settings(1),
});
assert_eq!(op.tag(), OperationKindTag::SetTuningContext);
assert_eq!(op.tag().discriminant(), 34);
let mut bytes = Vec::new();
op.encode_canonical(&mut bytes);
assert_eq!(
bytes[0], 34,
"the discriminant byte must lead the canonical encoding"
);
}
/// (t3) Genesis tranche G2b: a block containing exactly one
/// `SetTuningContext` stamps schema major 3.
///
/// **Mutation:** make `schema_major` ignore the `SetTuningContext` arm
/// (fold it into the catch-all `_ => 0`); must fail.
#[test]
fn t3_a_block_with_one_set_tuning_context_stamps_major_3() {
let op = OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: crate::valuegen::tuning_context_settings(1),
});
let envelopes = [op.clone()];
let major = envelopes.iter().map(|k| k.schema_major()).max().unwrap();
assert_eq!(
major, 3,
"a block carrying only SetTuningContext stamps major 3"
);
}
/// (t8) Genesis tranche G2b: kind 34 carries epoch 10
/// (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4), and a block containing it
/// stamps schema minor 10.
///
/// **Mutation:** assign epoch 9 instead of 10 in `introduced_minor`'s
/// `SetTuningContext` arm; must fail.
#[test]
fn t8_set_tuning_context_carries_epoch_10() {
let op = OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: crate::valuegen::tuning_context_settings(1),
});
assert_eq!(op.introduced_minor(), Some(10));
assert_eq!(op.tag().introduced_minor(), Some(10));
let env = OperationEnvelope {
id: OperationId::new(ReplicaId(1), 1),
author: crate::support::AuthorId(0),
stamp: crate::stamp::OperationStamp::new(
crate::stamp::HybridLogicalClock::new(epiphany_core::WallClockTime(1), 1),
OperationId::new(ReplicaId(1), 1),
),
causal_context: crate::causal::CausalContext::new(),
transaction: None,
payload: OperationPayload::Primitive(op),
};
assert_eq!(
crate::payload::operation_block_introduced_minor(&[env]),
Some(10),
"a block containing kind 34 stamps minor 10"
);
} }
#[test] #[test]
@ -11007,6 +11172,16 @@ mod tests {
// leak to appear on even if the base did stamp one (it does not — // leak to appear on even if the base did stamp one (it does not —
// that is `OperationEnvelopeBlock`'s concern in `epiphany-bundle`, // that is `OperationEnvelopeBlock`'s concern in `epiphany-bundle`,
// untouched by this packet). // untouched by this packet).
//
// Re-pinned again at genesis tranche G2b
// (`spec/CONTRACT_GENESIS_G2B_TUNING.md`): `gen_payload` gained
// `SetTuningContext` (arm 31), and `rng.below(31)` became
// `below(32)` — the same reshuffle, same reasoning. `SetTuningContext`
// is schema major **3**, unlike its siblings, but that is
// `OperationEnvelopeBlock`'s concern (`epiphany-bundle`), not
// `MaterializedState`'s: the canonical base still embeds no `Score`
// field value for any setting, so there remains no schema-major
// surface on this type for a leak to appear on.
let mut rng = epiphany_determinism::fuzz::SplitMix64::new(0xBA5E); let mut rng = epiphany_determinism::fuzz::SplitMix64::new(0xBA5E);
let envelopes = crate::fuzz::gen_envelope_set(&mut rng, 200); let envelopes = crate::fuzz::gen_envelope_set(&mut rng, 200);
let mut set = OperationSet::new(); let mut set = OperationSet::new();
@ -11016,7 +11191,7 @@ mod tests {
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect(); let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!( assert_eq!(
hex, hex,
"7fd6455a6ae304101774ba981d01702ad37c567575707b1e814dad6e153ab07d" "116e88b4013a18864dcd1b09489ab297aa332e14800cf4ca00be9dfa100b4a08"
); );
} }
@ -12952,6 +13127,55 @@ mod tests {
); );
} }
/// (t9) Regression guard: the from-empty spine still reaches a note, now
/// with a `SetTuningContext` authored alongside it — genesis tranche G2b
/// does not disturb the spine G1 established. Mutation is t2's (the
/// `schema_major` arm), which this test does not re-verify on its own;
/// its job is to prove the spine and the new op coexist cleanly.
#[test]
fn from_empty_spine_reaches_a_note_with_a_tuning_context_authored() {
let spine = genesis_spine_envelopes();
let settings = crate::valuegen::tuning_context_settings(1);
let set_tuning = prim_env(
1,
12,
70,
CausalContext::new(),
OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: settings.clone(),
}),
);
let mut envelopes = spine.envelopes;
envelopes.push(set_tuning);
let mut set = OperationSet::new();
set.accept_all(envelopes);
let identity = IdentityContext::new(ReplicaId(1));
let out = reduce_operation_set_onto(&set, &Score::empty(identity));
for counter in [0, 2, 4, 6, 8, 10, 12] {
assert_eq!(
effect_at(&out.state, counter),
Some(&OperationEffect::Applied),
"spine operation at counter {counter} must apply"
);
}
assert!(
out.score.events.contains(spine.event_id),
"the spine still reaches a note"
);
assert_eq!(
out.score.tuning_context.default_pitch_space,
settings.default_pitch_space
);
assert_eq!(
out.score.tuning_context.default_tuning_system,
settings.default_tuning_system
);
assert_eq!(out.score.tuning_context.reference, settings.reference);
assert_eq!(out.score.tuning_context.smufl, settings.smufl);
assert_eq!(out.score.tuning_context.overrides, settings.overrides);
}
/// (i2) Re-carry idempotence: the same `CreateInstrument` twice -> /// (i2) Re-carry idempotence: the same `CreateInstrument` twice ->
/// `AlreadyApplied`, and a differing second carry under the same live id /// `AlreadyApplied`, and a differing second carry under the same live id
/// -> `RecreateContentMismatch` — exactly `create_staff`'s discipline /// -> `RecreateContentMismatch` — exactly `create_staff`'s discipline
@ -13511,6 +13735,245 @@ mod tests {
} }
} }
// =========================================================================
// Genesis tranche G2b (`spec/CONTRACT_GENESIS_G2B_TUNING.md`): the sole
// genesis payload born at schema major 3, `SetTuningContext`.
// =========================================================================
/// (t5) **The pin's whole reason for existing** (contract §1): an op
/// authored **live** and the same op reloaded **from bytes** must
/// reduce to identical graph states, with a pre-existing non-empty
/// `accidental_extensions` preserved across both. This is what the
/// subset type (pin 1) makes structurally impossible to get wrong — a
/// full-value `SetTuningContext(ScoreTuningContext)` would diverge here,
/// because `OperationSet::accept` stores the authored envelope as a
/// **value** (live path keeps whatever `accidental_extensions` the
/// constructor set) while a decoded envelope always reconstructs it as
/// empty (the wire drops it).
///
/// **Mutation:** make the payload carry the full `ScoreTuningContext`
/// instead of the subset `TuningContextSettings`; must fail. Performed
/// and reversed live for this rung (see the report) rather than left in
/// the tree, since it requires the type-level change pin 1 forbids.
#[test]
fn t5_live_authored_and_reloaded_from_bytes_agree_with_extensions_preserved() {
use epiphany_core::{AccidentalRegistryId, ScoreAccidentalExtensions};
// A base graph with a pre-existing, non-empty accidental_extensions —
// the field the op never carries and reduction must leave untouched.
let mut base = Score::empty(IdentityContext::new(ReplicaId(1)));
let pre_existing = vec![ScoreAccidentalExtensions {
base: AccidentalRegistryId::new("heji"),
additions: Vec::new(),
overrides: Vec::new(),
}];
base.tuning_context.accidental_extensions = pre_existing.clone();
let settings = crate::valuegen::tuning_context_settings(3);
let live_env = prim_env(
1,
0,
10,
CausalContext::new(),
OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: settings.clone(),
}),
);
// Path A: authored live — the envelope object itself, never touching
// bytes.
let mut live_set = OperationSet::new();
live_set.accept_all(vec![live_env.clone()]);
let live_out = reduce_operation_set_onto(&live_set, &base);
// Path B: the identical envelope, round-tripped through canonical
// bytes and decoded back before reduction.
let bytes = live_env.to_canonical_bytes();
let reloaded_env = crate::envdecode::decode_envelope(&bytes).expect("the envelope decodes");
let mut reloaded_set = OperationSet::new();
reloaded_set.accept_all(vec![reloaded_env]);
let reloaded_out = reduce_operation_set_onto(&reloaded_set, &base);
// The two graph states are identical.
assert_eq!(
live_out.score.tuning_context, reloaded_out.score.tuning_context,
"an op authored live and the same op reloaded from bytes must reduce identically"
);
// Both apply the authored five-field settings...
assert_eq!(
live_out.score.tuning_context.default_pitch_space,
settings.default_pitch_space
);
assert_eq!(live_out.score.tuning_context.reference, settings.reference);
// ...and both preserve the base's pre-existing accidental_extensions,
// untouched — the field the payload cannot carry.
assert_eq!(
live_out.score.tuning_context.accidental_extensions, pre_existing,
"live path: accidental_extensions preserved"
);
assert_eq!(
reloaded_out.score.tuning_context.accidental_extensions, pre_existing,
"reloaded path: accidental_extensions preserved"
);
}
/// (t6) Undo restores the previous (chain-predecessor) five-field value —
/// not the seeded base, and not the type default. Two `SetTuningContext`
/// writes in two separate transactions; undoing the *second* must restore
/// the *first*'s authored value.
///
/// **Mutation:** in the `TuningContext` restoration-apply arm, restore
/// the default `TuningContextSettings` instead of the chain predecessor
/// (or drop the field writes entirely); must fail.
#[test]
fn t6_undo_restores_the_chain_predecessor_tuning_settings() {
let identity = IdentityContext::new(ReplicaId(1));
let first = crate::valuegen::tuning_context_settings(1);
let second = crate::valuegen::tuning_context_settings(2);
assert_ne!(first, second);
let tx2 = TransactionId::from_raw(2);
let envelopes = vec![
prim_env(
1,
0,
10,
CausalContext::new(),
OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: first.clone(),
}),
),
declare_transaction(1, 1, 20, seen_r1(0), tx2),
tx_member(
1,
2,
21,
seen_r1(1),
tx2,
OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: second,
}),
),
undo_env(1, 3, 22, seen_r1(2), tx2, 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.tuning_context.default_pitch_space,
first.default_pitch_space
);
assert_eq!(
out.score.tuning_context.default_tuning_system,
first.default_tuning_system
);
assert_eq!(out.score.tuning_context.reference, first.reference);
assert_eq!(out.score.tuning_context.smufl, first.smufl);
assert_eq!(
out.score.tuning_context.overrides, first.overrides,
"undo of the second write restores the first write's value"
);
}
/// (t7) Undo of the **first** authoring restores the **seeded base**
/// settings, identically whether the seed was the type default or a
/// non-default value — the never-authored / authored-to-default
/// distinction stays unobservable (contract pin 5, and plan trap 5's
/// withdrawal). `accidental_extensions` is untouched throughout: it is
/// not part of the carried subset type, so undo cannot move it, and this
/// test proves the base's own extensions survive both the write and the
/// undo unchanged.
///
/// **Mutation:** skip `tuning_context_chain.seed(...)` in
/// `seed_from_graph` -> the first undo sees `NotWritten` (no predecessor
/// recorded) instead of `Restore(Base(seeded))`, and the field is left at
/// the authored value instead of reverting; must fail.
#[test]
fn t7_undo_of_first_authoring_restores_the_seeded_base_default_or_not() {
// Both cases run through the exact same assertion shape below — no
// branch distinguishes "the seed happened to be the type default"
// from "the seed was a genuine non-default value". That uniformity is
// itself the proof the distinction is unobservable, per pin 5's
// explicit instruction not to write code that tells them apart.
// A pre-existing, non-empty `accidental_extensions` fixture, built
// directly (its constructor is `pub(crate)` in `epiphany-core`, so
// this crate cannot reuse the core fixture helper).
let default_extensions = || {
vec![epiphany_core::ScoreAccidentalExtensions {
base: epiphany_core::AccidentalRegistryId::new("heji"),
additions: Vec::new(),
overrides: Vec::new(),
}]
};
for (case, base) in [
("case A: from-empty, default seed", {
let mut b = Score::empty(IdentityContext::new(ReplicaId(1)));
b.tuning_context.accidental_extensions = default_extensions();
b
}),
("case B: loaded base, non-default seed", {
let mut b = Score::empty(IdentityContext::new(ReplicaId(1)));
let non_default = crate::valuegen::tuning_context_settings(9);
b.tuning_context.default_pitch_space = non_default.default_pitch_space;
b.tuning_context.default_tuning_system = non_default.default_tuning_system;
b.tuning_context.reference = non_default.reference;
b.tuning_context.smufl = non_default.smufl;
b.tuning_context.overrides = non_default.overrides;
b.tuning_context.accidental_extensions = default_extensions();
b
}),
] {
let seeded = base.tuning_context.clone();
let tx = TransactionId::from_raw(3);
let authored = crate::valuegen::tuning_context_settings(20);
let envelopes = vec![
declare_transaction(1, 0, 10, CausalContext::new(), tx),
tx_member(
1,
1,
11,
seen_r1(0),
tx,
OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: 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.tuning_context.default_pitch_space, seeded.default_pitch_space,
"{case}: undo restores the seeded default_pitch_space"
);
assert_eq!(
out.score.tuning_context.default_tuning_system, seeded.default_tuning_system,
"{case}: undo restores the seeded default_tuning_system"
);
assert_eq!(
out.score.tuning_context.reference, seeded.reference,
"{case}: undo restores the seeded reference"
);
assert_eq!(
out.score.tuning_context.smufl, seeded.smufl,
"{case}: undo restores the seeded smufl"
);
assert_eq!(
out.score.tuning_context.overrides, seeded.overrides,
"{case}: undo restores the seeded overrides"
);
assert_eq!(
out.score.tuning_context.accidental_extensions, seeded.accidental_extensions,
"{case}: accidental_extensions is untouched throughout — it is not part \
of the carried subset type, so undo cannot move it"
);
}
}
/// (s7) Transaction rollback discards the write — and the assertion must /// (s7) Transaction rollback discards the write — and the assertion must
/// not be on the field. `WorkingSnapshot` (`:7480`) is the transaction /// not be on the field. `WorkingSnapshot` (`:7480`) is the transaction
/// **rollback** mechanism: snapshot before, restore on failure. But /// **rollback** mechanism: snapshot before, restore on failure. But

View File

@ -15,8 +15,8 @@ use crate::payload::{
InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
OperationKind, OperationKindTag, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp, OperationKind, OperationKindTag, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp,
SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp, SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionDescriptor, SetTimeSignatureOp, SetTuningContextOp, SetUserPageBreakOp, SetUserSystemBreakOp,
TransposeIntervalOp, TransposeOp, TransactionDescriptor, TransposeIntervalOp, TransposeOp,
}; };
use crate::support::OperationKindRegistryId; use crate::support::OperationKindRegistryId;
@ -229,6 +229,9 @@ impl TextValue for OperationKind {
OperationKind::SetSpellingPrecedence(op) => { OperationKind::SetSpellingPrecedence(op) => {
production(self.tag(), vec![op.precedence.project()]) production(self.tag(), vec![op.precedence.project()])
} }
OperationKind::SetTuningContext(op) => {
production(self.tag(), vec![op.settings.project()])
}
} }
} }
@ -566,6 +569,14 @@ impl TextValue for OperationKind {
precedence: TextValue::parse(precedence)?, precedence: TextValue::parse(precedence)?,
}) })
} }
OperationKindTag::SetTuningContext => {
let [settings] = fields(s, tag, 1)? else {
unreachable!("the arity-1 check returned one field")
};
OperationKind::SetTuningContext(SetTuningContextOp {
settings: TextValue::parse(settings)?,
})
}
}) })
} }
} }
@ -619,7 +630,7 @@ mod tests {
#[test] #[test]
fn every_operation_kind_round_trips_with_canonical_text() { fn every_operation_kind_round_trips_with_canonical_text() {
let tags: Vec<_> = all_tags().collect(); let tags: Vec<_> = all_tags().collect();
assert_eq!(tags.len(), 34, "the grammar has 34 kind productions"); assert_eq!(tags.len(), 35, "the grammar has 35 kind productions");
for tag in tags { for tag in tags {
round_trip(&sample_kind(tag)); round_trip(&sample_kind(tag));
} }

View File

@ -112,6 +112,10 @@ pub enum V0OperationKind {
// every other v1-native kind above. // every other v1-native kind above.
SetCanvasLayoutDefaults(crate::payload::SetCanvasLayoutDefaultsOp), SetCanvasLayoutDefaults(crate::payload::SetCanvasLayoutDefaultsOp),
SetSpellingPrecedence(crate::payload::SetSpellingPrecedenceOp), SetSpellingPrecedence(crate::payload::SetSpellingPrecedenceOp),
// Genesis tranche G2b — born at wire-disc 34; no lossy v0 form (v0
// predates the catalog entirely), so it round-trips by identity like
// every other v1-native kind above.
SetTuningContext(crate::payload::SetTuningContextOp),
} }
/// v0 `InsertEvent`: the event was a bare [`EventId`] plus the reduction-relevant /// v0 `InsertEvent`: the event was a bare [`EventId`] plus the reduction-relevant

View File

@ -409,6 +409,30 @@ pub fn spelling_precedence(nth: u8) -> epiphany_core::SpellingPrecedence {
.expect("both listed orders are total over the five source kinds") .expect("both listed orders are total over the five source kinds")
} }
/// Tuning context settings with an `nth`-distinct reference frequency
/// (genesis tranche G2b) — the carried [`epiphany_core::TuningContextSettings`]
/// of `SetTuningContext`. Distinct `nth` give distinct values so a harness can
/// drive concurrent `SetTuningContext`s, an advisory LWW field that resolves
/// by canonical order with no conflict.
pub fn tuning_context_settings(nth: u8) -> epiphany_core::TuningContextSettings {
use epiphany_core::{CmnNominal, PitchSpacePosition, ReferencePitch};
epiphany_core::TuningContextSettings {
default_pitch_space: epiphany_core::PitchSpaceId::new("cmn-12"),
default_tuning_system: epiphany_core::TuningSystemId::new("tet-12"),
reference: ReferencePitch::new(
PitchSpacePosition::Cmn {
nominal: CmnNominal::A,
alteration: 0,
octave: 4,
},
440.0 + f64::from(nth),
)
.expect("a small positive offset from A440 stays finite and positive"),
smufl: epiphany_core::SmuflVersionRequirement::default(),
overrides: Vec::new(),
}
}
/// A well-formed `numerator`/4 [`TimeSignature`](epiphany_core::TimeSignature) /// A well-formed `numerator`/4 [`TimeSignature`](epiphany_core::TimeSignature)
/// (Phase-3 tranche): `numerator` quarter-note beat groups summing exactly to /// (Phase-3 tranche): `numerator` quarter-note beat groups summing exactly to
/// the measure duration, so [`epiphany_core::TimeSignature::new`]'s beat-group /// the measure duration, so [`epiphany_core::TimeSignature::new`]'s beat-group

View File

@ -373,6 +373,44 @@ pub fn decode_vectors() -> Vec<DecodeVector> {
precedence_trailing, precedence_trailing,
)); ));
// --- OperationEnvelope carrying SetTuningContext (genesis tranche G2b) —
// the sole genesis payload born at schema major 3. Same rationale as the
// siblings above: nothing else in this corpus exercises this payload's
// decode path, and a round-trip check alone cannot see a self-consistent
// encoder/decoder reorder (the 3b-i lesson).
let tuning_envelope = OperationEnvelope {
id: OperationId::new(ReplicaId(1), 4),
author: crate::support::AuthorId(0),
stamp: crate::stamp::OperationStamp::new(
crate::stamp::HybridLogicalClock::new(epiphany_core::WallClockTime(1), 1),
OperationId::new(ReplicaId(1), 4),
),
causal_context: crate::causal::CausalContext::new(),
transaction: None,
payload: crate::payload::OperationPayload::Primitive(
crate::payload::OperationKind::SetTuningContext(crate::payload::SetTuningContextOp {
settings: crate::valuegen::tuning_context_settings(1),
}),
),
};
let tuning_envelope_bytes = tuning_envelope.to_canonical_bytes();
v.push(row(
OE,
"accept",
"-",
"set_tuning_context",
tuning_envelope_bytes.clone(),
));
let mut tuning_trailing = tuning_envelope_bytes;
tuning_trailing.push(0);
v.push(row(
OE,
"reject",
"trailing-bytes",
"set_tuning_context_trailing",
tuning_trailing,
));
v v
} }
@ -555,4 +593,33 @@ mod tests {
"the committed literal bytes must decode and re-encode injectively" "the committed literal bytes must decode and re-encode injectively"
); );
} }
/// (s8 analogue) Genesis tranche G2b (`spec/CONTRACT_GENESIS_G2B_TUNING.md`
/// touch row 11): the `SetTuningContext` envelope decode vector, pinned to
/// a literal byte array copied from the committed corpus — not derived by
/// calling `.to_canonical_bytes()` here, for the same reason as the
/// sibling tests above (the 3b-i lesson: round-trip locking alone cannot
/// see a self-consistent encoder/decoder reorder). The mutation this
/// guards against is a swap of discriminant 34 with any neighboring
/// discriminant in both the encoder and the decoder — self-consistent, so
/// every round-trip test stays green, while this correctly-named literal
/// vector dies.
#[test]
fn set_tuning_context_envelope_decode_vector_is_pinned_to_literal_bytes() {
#[rustfmt::skip]
let bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 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, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 48, 0, 0, 0, 6, 0, 0, 0, 99, 109, 110,
45, 49, 50, 6, 0, 0, 0, 116, 101, 116, 45, 49, 50, 0, 5, 0, 4, 8, 0, 0, 0, 0, 0, 0,
0, 0, 144, 123, 64, 1, 0, 40, 0, 1, 0, 40, 0, 0, 0, 0, 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"
);
}
} }

View File

@ -47,9 +47,9 @@ use epiphany_ops::{
ReplicaAnomalyReason, ReplicaAnomalyRegistryId, ResolutionAction, ResolutionRegistryId, ReplicaAnomalyReason, ReplicaAnomalyRegistryId, ResolutionAction, ResolutionRegistryId,
ResolveConflictPayload, RespellPitchOp, SerializedCanonicalInputs, SetCanvasLayoutDefaultsOp, ResolveConflictPayload, RespellPitchOp, SerializedCanonicalInputs, SetCanvasLayoutDefaultsOp,
SetMetadataOp, SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp, SetMetadataOp, SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionCategory, SetTimeSignatureOp, SetTuningContextOp, SetUserPageBreakOp, SetUserSystemBreakOp,
TransactionDescriptor, TransposeIntervalOp, TransposeOp, TupletCompensation, TransactionCategory, TransactionDescriptor, TransposeIntervalOp, TransposeOp,
TupletCompensationKind, UndoPolicy, UndoTransactionPayload, TupletCompensation, TupletCompensationKind, UndoPolicy, UndoTransactionPayload,
}; };
use crate::rng::Rng; use crate::rng::Rng;
@ -646,7 +646,7 @@ pub fn operation_payload(rng: &mut Rng, events: u64, pitches: u64) -> OperationP
} }
_ => {} _ => {}
} }
let kind = match rng.below(34) { let kind = match rng.below(35) {
0 => { 0 => {
let pitches = if rng.boolean() { let pitches = if rng.boolean() {
vec![obj_pitch(rng.below(pitches))] vec![obj_pitch(rng.below(pitches))]
@ -871,6 +871,10 @@ pub fn operation_payload(rng: &mut Rng, events: u64, pitches: u64) -> OperationP
32 => OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp { 32 => OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp {
precedence: valuegen::spelling_precedence(rng.below(3) as u8), precedence: valuegen::spelling_precedence(rng.below(3) as u8),
}), }),
// Genesis tranche G2b: the sole genesis payload born at schema major 3.
33 => OperationKind::SetTuningContext(SetTuningContextOp {
settings: valuegen::tuning_context_settings(rng.below(3) as u8),
}),
_ => OperationKind::Registered( _ => OperationKind::Registered(
OperationKindRegistryId(rng.next_u64() as u128), OperationKindRegistryId(rng.next_u64() as u128),
rng.byte_vec(0, 16), rng.byte_vec(0, 16),

View File

@ -686,12 +686,18 @@ mod tests {
#[test] #[test]
fn op_block_beyond_the_accept_set_opens_read_only() { fn op_block_beyond_the_accept_set_opens_read_only() {
use epiphany_bundle::IntegrityAnomaly; use epiphany_bundle::IntegrityAnomaly;
// A newer writer's op block, stamped schema major 3 — beyond the reader's // A newer writer's op block, stamped schema major 4 — beyond the reader's
// op-block accept-set [0,2]. The bundle opens read-only preservation (the // op-block accept-set [0,3]. The bundle opens read-only preservation (the
// canonical base and manifest still read) rather than hard-rejecting. // canonical base and manifest still read) rather than hard-rejecting.
//
// Major 4, not 3: genesis tranche G2b raised the op-block accept-set
// to [0, 3] (`SetTuningContext` is born at major 3), so major 3 is
// now admitted and this test's "beyond the accept-set" major must
// move past it to stay an actual test of the read-only-on-overflow
// path.
let block = StagedChunk::operation_block_versioned( let block = StagedChunk::operation_block_versioned(
encode_block(&[vec![1u8, 2, 3, 4]]), encode_block(&[vec![1u8, 2, 3, 4]]),
SchemaVersion::new(3, 0), SchemaVersion::new(4, 0),
); );
let reopened = reopen_with_op_block(0xD2_0002, block); let reopened = reopen_with_op_block(0xD2_0002, block);
assert!( assert!(
@ -700,7 +706,7 @@ mod tests {
); );
assert!(reopened.anomalies().iter().any(|a| matches!( assert!(reopened.anomalies().iter().any(|a| matches!(
a, a,
IntegrityAnomaly::UnsupportedCanonicalChunkMajor { schema_major: 3 } IntegrityAnomaly::UnsupportedCanonicalChunkMajor { schema_major: 4 }
))); )));
} }

View File

@ -304,14 +304,15 @@ fn the_kind_productions_are_the_operation_vocabulary() {
// The count is still hand-maintained, which is the same shape the comment // The count is still hand-maintained, which is the same shape the comment
// above warns about — derived list, literal total. Every tranche that // 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 // appends a tag must bump it (genesis G1 took it from 31 to 32 by adding
// `CreateInstrument`; genesis G2a takes it from 32 to 34 by adding // `CreateInstrument`; genesis G2a took it from 32 to 34 by adding
// `SetCanvasLayoutDefaults` and `SetSpellingPrecedence`). It stays a // `SetCanvasLayoutDefaults` and `SetSpellingPrecedence`; genesis G2b takes
// literal on purpose: deriving it from `PAYLOAD_FREE.len()` would make the // it from 34 to 35 by adding `SetTuningContext`). It stays a literal on
// assertion vacuous, since that is the very list it exists to pin. // purpose: deriving it from `PAYLOAD_FREE.len()` would make the assertion
// vacuous, since that is the very list it exists to pin.
assert_eq!( assert_eq!(
expected.len(), expected.len(),
34, 35,
"33 payload-free kinds plus `Registered`" "34 payload-free kinds plus `Registered`"
); );
let actual = alternatives("kind"); let actual = alternatives("kind");

View File

@ -43,7 +43,13 @@ use epiphany_ops::OperationEnvelope;
/// `epiphany-layout-ir` dependency and structurally cannot decode edit-barrier /// `epiphany-layout-ir` dependency and structurally cannot decode edit-barrier
/// bytes). Holding the version while changing the grammar would leave two /// bytes). Holding the version while changing the grammar would leave two
/// incompatible grammars both claiming `(0 9 0)`. /// incompatible grammars both claiming `(0 9 0)`.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 10, 0); ///
/// Bumped again 0.10.0 → 0.11.0 by the genesis tranche G2b
/// (`spec/CONTRACT_GENESIS_G2B_TUNING.md`), which appended
/// `set-tuning-context` to the `kind` production — the same reasoning as G1
/// and G2a: extending the grammar without moving this constant would leave
/// two incompatible grammars both claiming `(0 10 0)`.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 11, 0);
/// A parsed canonical Text Projection document. /// A parsed canonical Text Projection document.
/// ///

View File

@ -649,11 +649,12 @@ mod tests {
} }
// Bumped with `COMPANION_VERSION` (0.7.0 → 0.8.0, genesis G1; 0.8.0 → // Bumped with `COMPANION_VERSION` (0.7.0 → 0.8.0, genesis G1; 0.8.0 →
// 0.9.0, genesis G2a; 0.9.0 → 0.10.0, G-minor). Kept a literal because // 0.9.0, genesis G2a; 0.9.0 → 0.10.0, G-minor; 0.10.0 → 0.11.0, genesis
// `projection` takes `&[&str]` and a formatted String would ripple // G2b). Kept a literal because `projection` takes `&[&str]` and a
// through every call site; `the_test_header_tracks_the_implemented_ // formatted String would ripple through every call site;
// version` below fails loudly if the two ever drift. // `the_test_header_tracks_the_implemented_version` below fails loudly if
const HEADER: &str = "(text-projection (0 10 0))"; // the two ever drift.
const HEADER: &str = "(text-projection (0 11 0))";
const DOCUMENT: &str = "(document #x00000000000000000000000000000001 (schema 0 1))"; const DOCUMENT: &str = "(document #x00000000000000000000000000000001 (schema 0 1))";
/// A minimal but complete valid projection: just the two mandatory lines. /// A minimal but complete valid projection: just the two mandatory lines.

View File

@ -329,17 +329,17 @@ pub fn document_vectors() -> Vec<TextVector> {
.map(|(name, text)| (SURFACE, "accept", "-", *name, text.as_bytes().to_vec())) .map(|(name, text)| (SURFACE, "accept", "-", *name, text.as_bytes().to_vec()))
.collect(); .collect();
// The rejected version must be one this crate does NOT implement. The // The rejected version must be one this crate does NOT implement. Genesis
// G-minor rung moved `COMPANION_VERSION` to 0.10.0, which had been this // tranche G2b moved `COMPANION_VERSION` to 0.11.0, which had been this
// vector's "future" version — leaving it would have made the negative // vector's "future" version — leaving it would have made the negative
// vector assert that the *correct* header is rejected. It now names // vector assert that the *correct* header is rejected. It now names
// 0.9.0, the immediately superseded companion, which is the better test // 0.10.0, the immediately superseded companion, which is the better test
// anyway: rejecting the version right behind you is exactly the deferred // anyway: rejecting the version right behind you is exactly the deferred
// migrate-on-read posture (`req:textproj:header-version`). // migrate-on-read posture (`req:textproj:header-version`).
let wrong_version = replace_once( let wrong_version = replace_once(
minimal, minimal,
"(text-projection (0 11 0))",
"(text-projection (0 10 0))", "(text-projection (0 10 0))",
"(text-projection (0 9 0))",
); );
vectors.push(( vectors.push((
SURFACE, SURFACE,

File diff suppressed because one or more lines are too long

View File

@ -6,10 +6,28 @@ reverses Pass-12 K8 and makes every mutable field of `Score` operation-authored.
This plan is the execution scope: what the tranche touches, in what order, and This plan is the execution scope: what the tranche touches, in what order, and
which questions must be answered before a dispatch contract can be written. which questions must be answered before a dispatch contract can be written.
**Status:** **G1 landed** (3b09595, CI green) via **Status:** the ladder **G1 → G2a → G-minor → G2b** is **complete**; only **G3**
`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md`. **G2a contracted** remains.
(`spec/CONTRACT_GENESIS_G2A_SETTINGS.md`); **G-minor**, G2b, and G3 scoped, not
contracted. §6 lists what still needs ratification. * **G1 landed** (3b09595) — `CreateInstrument`, kind/tag 31.
* **G2a landed** (7df5ca1 + 55eff00) — `SetCanvasLayoutDefaults` and
`SetSpellingPrecedence`, kinds/tags 32/33, both major 0.
* **G-minor landed** (ff9bd0f) — the chunk schema minor became a derived
record; epoch ladder ratified at minors 2–9 (`spec/PLAN_GMINOR_SCHEMA_MINOR.md`
§4), governed by `spec/AUDIT_GMINOR_VOCABULARIES.md`. Closed **P13-S14**.
* **G2b landed** — `SetTuningContext`, kind/tag **34**, schema major **3**,
minor epoch **10**. Carried the sole accept-set raise
(`OperationEnvelopeBlock` 2→3) and rewrote the `bundle.rs` rationale it
falsified. Payload is the five-field subset
`epiphany_core::TuningContextSettings`, **not** the full graph type — §5
trap 7's holdout, resolved in the contract as *subset over normalization*.
Closed **P13-S13**.
* **G3** — the five remaining mint families, plus the delete/modify coverage
design. Scoped, not contracted.
§6 lists what still needs ratification. **Standing constraint, now with real
teeth:** pruning MUST NOT be implemented until disposition C lands — after G2b
it would discard *authored* genesis state, not merely re-derivable state.
**G1 shipped documentation debt** — five normative falsehoods across **G1 shipped documentation debt** — five normative falsehoods across
`binary_format.tex`, `core_spec.tex`, and `operation_catalog.tex`, because its `binary_format.tex`, `core_spec.tex`, and `operation_catalog.tex`, because its

Binary file not shown.

View File

@ -1455,6 +1455,13 @@ and the reader disagree about the field's type.
\tablenums{33} & \texttt{SetSpellingPrecedence} & \tablenums{33} & \texttt{SetSpellingPrecedence} &
$\mathrm{lp}$(\texttt{SpellingPrecedence}) & $\mathrm{lp}$(\texttt{SpellingPrecedence}) &
\sectionsc{SetSpellingPrecedence} \\ \sectionsc{SetSpellingPrecedence} \\
\tablenums{34} & \texttt{SetTuningContext} &
$\mathrm{lp}$(\texttt{TuningContextSettings}) --- the \textbf{authored
subset} of \texttt{ScoreTuningContext}: exactly its five wire-bearing
fields, in the same order, byte-identical to the schema-major-3
\texttt{ScoreTuningContext} layout (Section~\ref{sec:evolution:major3});
born at v3 unconditionally &
\sectionsc{SetTuningContext} \\
\bottomrule \bottomrule
\end{longtable} \end{longtable}
\endgroup \endgroup
@ -1525,6 +1532,7 @@ discriminants and wrong lengths are decode errors. Append-only past
\tablenums{28} & \texttt{CreateRepeatStructure} & \tablenums{29} & \texttt{DeleteRepeatStructure} \\ \tablenums{28} & \texttt{CreateRepeatStructure} & \tablenums{29} & \texttt{DeleteRepeatStructure} \\
\tablenums{30} & \texttt{TransposeInterval} & \tablenums{31} & \texttt{CreateInstrument} \\ \tablenums{30} & \texttt{TransposeInterval} & \tablenums{31} & \texttt{CreateInstrument} \\
\tablenums{32} & \texttt{SetCanvasLayoutDefaults} & \tablenums{33} & \texttt{SetSpellingPrecedence} \\ \tablenums{32} & \texttt{SetCanvasLayoutDefaults} & \tablenums{33} & \texttt{SetSpellingPrecedence} \\
\tablenums{34} & \texttt{SetTuningContext} & & \\
\bottomrule \bottomrule
\end{longtable} \end{longtable}
\endgroup \endgroup
@ -2332,9 +2340,13 @@ Every chunk declares a \texttt{SchemaVersion} (major, minor) in its
role}, not one shared bound (Section~\ref{sec:evolution:major1}'s ``raised role}, not one shared bound (Section~\ref{sec:evolution:major1}'s ``raised
per chunk role'' rule, sharpened by major~3, the first bump under which the per chunk role'' rule, sharpened by major~3, the first bump under which the
roles' maxima genuinely diverge): the \texttt{Snapshot} role's max is roles' maxima genuinely diverge): the \texttt{Snapshot} role's max is
\tablenums{3}; the \texttt{OperationEnvelopeBlock} role's max stays \tablenums{3}; the \texttt{OperationEnvelopeBlock} role's max is
\tablenums{2} (schema major~3 embeds no operation payload, so this role \tablenums{3} as of genesis tranche G2b
does not rise); every other role's max is \tablenums{0}. The reference (\texttt{spec/CONTRACT\_GENESIS\_G2B\_TUNING.md} ---
\texttt{SetTuningContext}, \tablenums{34}, is born at v3
unconditionally; the role stayed at \tablenums{2} from Push~4b tranche
3b-i, when schema major~3 embedded no operation payload, until this
rung); every other role's max is \tablenums{0}. The reference
implementation's overall envelope is $\{0, 1, 2, 3\}$ ($\textsc{min} = 0$, implementation's overall envelope is $\{0, 1, 2, 3\}$ ($\textsc{min} = 0$,
$\textsc{max} = 3$), realized as these differing per-role maxima rather $\textsc{max} = 3$), realized as these differing per-role maxima rather
than a single shared bound. than a single shared bound.
@ -2370,22 +2382,26 @@ The \emph{only} minor-additive mechanism in schema major~0 is
\textbf{appending discriminants to open vocabularies}: \textbf{appending discriminants to open vocabularies}:
\begin{itemize} \begin{itemize}
\item \texttt{OperationKind}: append at ${\geq}\,\tablenums{34}$ \item \texttt{OperationKind}: append at ${\geq}\,\tablenums{35}$
(Requirement~\ref{req:binfmt:kind-discriminants}; the Phase-3 (Requirement~\ref{req:binfmt:kind-discriminants}; the Phase-3
tranche took \tablenums{24}--\tablenums{27} under this mechanism, tranche took \tablenums{24}--\tablenums{27} under this mechanism,
the repeat pair \tablenums{28}/\tablenums{29} in the the repeat pair \tablenums{28}/\tablenums{29} in the
schema-major-2 revision, Push~4a took \tablenums{30} schema-major-2 revision, Push~4a took \tablenums{30}
(\texttt{TransposeInterval}), genesis tranche G1 took \tablenums{31} (\texttt{TransposeInterval}), genesis tranche G1 took \tablenums{31}
(\texttt{CreateInstrument}), and genesis tranche G2a took (\texttt{CreateInstrument}), genesis tranche G2a took
\tablenums{32}/\tablenums{33} (\texttt{SetCanvasLayoutDefaults}, \tablenums{32}/\tablenums{33} (\texttt{SetCanvasLayoutDefaults},
\texttt{SetSpellingPrecedence}) --- the discriminant space is one \texttt{SetSpellingPrecedence}), and genesis tranche G2b took
append-only table across majors, but a kind's \emph{block stamp} follows \tablenums{34} (\texttt{SetTuningContext}) --- the discriminant space is
minimal stamping over its payload: \tablenums{28}'s payload embeds a v2 one append-only table across majors, but a kind's \emph{block stamp}
layout, so its blocks stamp major~2, Section~\ref{sec:evolution:major2}); follows minimal stamping over its payload: \tablenums{28}'s payload
\item \texttt{OperationKindTag}: append at ${\geq}\,\tablenums{34}$ embeds a v2 layout, so its blocks stamp major~2,
(Requirement~\ref{req:binfmt:kind-tag}; the same ten variants as Section~\ref{sec:evolution:major2}; \tablenums{34}'s payload embeds a v3
\texttt{OperationKind}, at the same ten discriminants, appended by the layout unconditionally, so its blocks stamp major~3,
same five tranches); Section~\ref{sec:evolution:major3} --- the sole appended kind to do so);
\item \texttt{OperationKindTag}: append at ${\geq}\,\tablenums{35}$
(Requirement~\ref{req:binfmt:kind-tag}; the same eleven variants as
\texttt{OperationKind}, at the same eleven discriminants, appended by
the same six tranches);
\item \texttt{OperationPayload}: append at ${\geq}\,\tablenums{4}$ \item \texttt{OperationPayload}: append at ${\geq}\,\tablenums{4}$
(Section~\ref{sec:ops:payload}; \texttt{ResolveEquivocation} = 3, Push~3, (Section~\ref{sec:ops:payload}; \texttt{ResolveEquivocation} = 3, Push~3,
is the one and only append this vocabulary has had); is the one and only append this vocabulary has had);
@ -2977,24 +2993,39 @@ layout ahead of a real consumer is exactly what the frozen-layout rule
major~1. major~1.
\subsection{Where the changed fields reach} \subsection{Where the changed fields reach}
Only one payload type changes, and it changes at exactly one site:
\textbf{As first defined (Push~4b tranche 3b-i), no operation payload
embedded the tuning context} --- \texttt{ScoreTuningContext} reached the
wire only through the acceleration full-\texttt{Score} snapshot, and the
\texttt{OperationEnvelopeBlock} role's accept-set stayed at major~2. This
changed at genesis tranche G2b
(\texttt{spec/CONTRACT\_GENESIS\_G2B\_TUNING.md}), which corrects the claim
below:
\begin{itemize} \begin{itemize}
\item \textbf{Snapshot-only:} \texttt{ScoreTuningContext} reaches the wire \item \textbf{Reaches two sites, as of genesis tranche G2b.}
\emph{only} through the acceleration full-\texttt{Score} snapshot. No \texttt{ScoreTuningContext} still reaches the wire through the
operation payload embeds the tuning context (core Chapter~6's operation acceleration full-\texttt{Score} snapshot, \emph{and} its authored
vocabulary carries no \texttt{ScoreTuningContext} / \texttt{TuningOverride} subset --- \texttt{TuningContextSettings}, exactly the five
/ \texttt{SmuflVersionRequirement} field anywhere), so the wire-bearing fields, byte-identical to this section's layout
\textbf{canonical operation layer is untouched}: no op payload is ``born (Section~\ref{sec:ops:kinds}, \tablenums{34}
at v3'', and no frozen v2 op-payload decoder is needed. The \texttt{SetTuningContext}) --- now reaches the wire through the
\texttt{OperationEnvelopeBlock} role's accept-set therefore stays at canonical operation layer: \texttt{SetTuningContext} is \emph{born at
major~2 (Section~\ref{sec:evolution:major2}) --- schema major~3 is the v3 unconditionally} (the same shape as \texttt{CreateInstrument}'s v2
first data-model bump under which a chunk role's max does \emph{not} birth, Section~\ref{sec:evolution:major2}), since \texttt{smufl} and
move in lockstep with the others. \texttt{overrides} are mandatory appends with no lower-major layout for
this payload to fall back to. The \texttt{OperationEnvelopeBlock}
role's accept-set therefore \textbf{widens to major~3}
(Section~\ref{sec:evolution:gate}) --- schema major~3 stopped being the
data-model bump under which a chunk role's max does not move in
lockstep with the others; that distinction now belongs to no major.
\item \textbf{The canonical base is unchanged.} The \item \textbf{The canonical base is unchanged.} The
\texttt{MaterializedState} embeds no tuning context, so it remains \texttt{MaterializedState} embeds no tuning context (nor any other
major~0, byte-identical across the bump --- the same keystone as \texttt{Score} field value), so it remains major~0, byte-identical
majors~1 and~2, and a conformance test \SHOULD{} assert it again. across the bump --- the same keystone as majors~1 and~2, and a
conformance test \SHOULD{} assert it again. This is unaffected by G2b:
the canonical carrier of the tuning context is the operation log, not
the base, so the base's own stamping never moves.
\item The manifest stays major~0 (nothing here touches it). \item The manifest stays major~0 (nothing here touches it).
\end{itemize} \end{itemize}
@ -3005,10 +3036,14 @@ or migrate it on read (the reference implementation migrates). Canonical
chunks parse or the bundle opens read-only. Migration \emph{composes}: a chunks parse or the bundle opens read-only. Migration \emph{composes}: a
major-3 reader migrates a major-0 snapshot major-3 reader migrates a major-0 snapshot
v0${\to}$v1${\to}$v2${\to}$v3 in one read, each step total and v0${\to}$v1${\to}$v2${\to}$v3 in one read, each step total and
default-filling. Because no operation payload changes at this major, a default-filling. As originally defined (Push~4b tranche 3b-i), no operation
major-2-only reader's op-block admission is exactly as before this bump --- payload changed at this major, so a major-2-only reader's op-block
only the \texttt{Snapshot} role's accept-set widens, to \tablenums{3} admission was exactly as before the bump and only the \texttt{Snapshot}
(Section~\ref{sec:evolution:gate}). role's accept-set widened, to \tablenums{3}
(Section~\ref{sec:evolution:gate}). Genesis tranche G2b (above) changes
this: a major-2-only reader now meets a possible major-3 op block
(\texttt{SetTuningContext}) and opens read-only, exactly the one-way door
Section~\ref{sec:evolution:gate} describes for every other accept-set raise.
\subsection{Changed value layout} \subsection{Changed value layout}
As in majors~1 and~2, the wire form appends new fields \emph{after} the As in majors~1 and~2, the wire form appends new fields \emph{after} the

Binary file not shown.

View File

@ -6963,6 +6963,11 @@ pub enum OperationKind {
// SetMetadata advisory-LWW pattern. // SetMetadata advisory-LWW pattern.
SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp), SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp),
SetSpellingPrecedence(SetSpellingPrecedenceOp), SetSpellingPrecedence(SetSpellingPrecedenceOp),
// Genesis tranche G2b: the sole genesis payload born at schema major 3,
// on the same advisory-LWW pattern. Carries TuningContextSettings, the
// authored subset of ScoreTuningContext (accidental_extensions is not
// on the wire and is left untouched by reduction).
SetTuningContext(SetTuningContextOp),
// Layout-semantic operations // Layout-semantic operations
SetUserSystemBreak(SetUserSystemBreakOp), SetUserSystemBreak(SetUserSystemBreakOp),
@ -11931,6 +11936,8 @@ pub enum OperationKindTag {
// Genesis tranche G2a: the two major-0 settings setters. // Genesis tranche G2a: the two major-0 settings setters.
SetCanvasLayoutDefaults, SetCanvasLayoutDefaults,
SetSpellingPrecedence, SetSpellingPrecedence,
// Genesis tranche G2b: name-verbatim, as the two prior additions are.
SetTuningContext,
} }
pub enum BarrierScope { pub enum BarrierScope {

Binary file not shown.

View File

@ -231,7 +231,7 @@
{\Large\scshape\color{epiphanyslate}Operation Catalog}\\[6pt] {\Large\scshape\color{epiphanyslate}Operation Catalog}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt] {\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt] {\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.10.0 --- The genesis operation tranche (G1 + G2a)}\\[4pt] {\normalsize\color{epiphanyink}Version 0.11.0 --- The genesis operation tranche reaches schema major 3 (G2b)}\\[4pt]
{\small\color{epiphanyslate}Normative for the operation kinds it defines} {\small\color{epiphanyslate}Normative for the operation kinds it defines}
\vfill \vfill
\end{titlepage} \end{titlepage}
@ -382,6 +382,41 @@ clause is narrowed to the canvas object itself, per
are no longer outside the operation set; the canvas is still never are no longer outside the operation set; the canvas is still never
op-minted. op-minted.
\medskip
\noindent\textbf{Version 0.11.0 (genesis tranche G2b,
\texttt{spec/CONTRACT\_GENESIS\_G2B\_TUNING.md}).} Adds one operation kind,
\texttt{SetTuningContext} (Section~\ref{sec:k0:set-tuning-context}), the
third and final genesis-ladder setter deferred at 0.10.0. Carries
\texttt{TuningContextSettings}, the authored subset of
\texttt{ScoreTuningContext} --- exactly its five wire-bearing fields, byte-
identical to the existing schema-major-3 wire walk (Binary Format companion
\sectionsc{Schema Major 3}) --- rather than the full graph type, so that
\texttt{accidental\_extensions} (staged off the wire) cannot diverge between
a live-authored session and the same document reloaded from bytes.
\textbf{The sole accept-set-raising surface among the nine genesis
settings/creates.} \texttt{SetTuningContext} is schema major~3
\emph{unconditionally} (\texttt{ScoreTuningContext}'s \texttt{smufl}/
\texttt{overrides} appends are mandatory, so no lower-major layout for this
payload exists at any value), which is why it alone --- not
\texttt{CreateInstrument} at major~2, and not either G2a setter at major~0 ---
drags the \texttt{OperationEnvelopeBlock} accept-set from \texttt{[0, 2]} to
\texttt{[0, 3]} (Binary Format companion \sectionsc{The Chunk-Level Gate}).
This is the reason the genesis ladder split \texttt{SetTuningContext} into
its own rung rather than landing it alongside \texttt{SetCanvasLayoutDefaults}
/ \texttt{SetSpellingPrecedence}: a one-way accept-set door is not something
to bury inside two unrelated major-0 setters.
Undo restores the \textbf{seeded base} settings --- default or non-default
alike --- exactly as \texttt{SetMetadata} does; the never-authored /
authored-to-default distinction stays unobservable
(Section~\ref{sec:k0:set-tuning-context}). This closes P13-S13: the
canonical operation log is now the sole canonical carrier of the tuning
context (the canonical base embeds no \texttt{Score} field value for any
setting, \texttt{metadata} included), on the same reasoning that already
applied to \texttt{SetMetadata}.
% =========================================================================== % ===========================================================================
\chapter{The Catalog Framework} \chapter{The Catalog Framework}
\label{ch:framework} \label{ch:framework}
@ -1247,6 +1282,79 @@ state rather than absence.
\textbf{Re-anchoring.} Not applicable: the field is a score singleton with no \textbf{Re-anchoring.} Not applicable: the field is a score singleton with no
tombstonable anchor. tombstonable anchor.
\section{SetTuningContext}
\label{sec:k0:set-tuning-context}
Ratified with the genesis operation tranche's G2b rung (\texttt{spec/
CONTRACT\_GENESIS\_G2B\_TUNING.md}, executing \texttt{spec/
RULING\_GENESIS\_PERSISTENCE.md}): the score's tuning context settings
(\texttt{Score.tuning\_context}) join the operation-authored surfaces, on
the same \texttt{SetMetadata} pattern as \texttt{SetCanvasLayoutDefaults}
and \texttt{SetSpellingPrecedence} above. Unlike either sibling,
\texttt{SetTuningContext} is the \textbf{sole genesis payload born at
schema major~3} and is the surface that raises the
\texttt{OperationEnvelopeBlock} accept-set from \texttt{[0, 2]} to
\texttt{[0, 3]} (Binary Format companion \sectionsc{The Chunk-Level Gate}).
\textbf{Payload schema.} \texttt{SetTuningContextOp \{ settings:
TuningContextSettings \}}. \texttt{TuningContextSettings} is the
\textbf{authored subset} of \texttt{ScoreTuningContext}: exactly its five
wire-bearing fields (\texttt{default\_pitch\_space},
\texttt{default\_tuning\_system}, \texttt{reference}, \texttt{smufl},
\texttt{overrides}), in the codec's existing order --- not the full
six-field graph type. \texttt{accidental\_extensions} is deliberately
\textbf{not} a field of the carried payload: it is staged out of schema
major~3 (Binary Format companion \sectionsc{Schema Major 3}) and
\texttt{OperationSet::accept} stores the authored envelope as a
\emph{value}, so a payload carrying the full \texttt{ScoreTuningContext}
would let \texttt{accidental\_extensions} reach the graph on the authoring
replica while decoding to empty on every replica that received the
document through serialization --- the same document in two graph states,
depending only on whether it was just authored or just reloaded. The
subset type makes this divergence unrepresentable: a field the payload
does not carry cannot be set inconsistently.
\textbf{Canonical encoding.} The length-framed canonical bytes of
\texttt{settings}. \texttt{TuningContextSettings}'s encoding is
\textbf{byte-identical} to \texttt{ScoreTuningContext}'s existing
five-field wire walk --- a type-level narrowing, not a new wire form. Since
\texttt{ScoreTuningContext}'s \texttt{smufl} and \texttt{overrides} appends
are mandatory (not \texttt{Option}-hidden), no lower-major layout for this
payload exists at any value: \texttt{SetTuningContext} is \textbf{born at
schema major~3 unconditionally}, the same shape as \texttt{CreateInstrument}'s
unconditional birth at major~2 (Section~\ref{sec:k0:create-instrument}), not
the value-dependent shape \texttt{CreateRegion}/\texttt{SetStaffLayout} use.
\textbf{Reduction rule.} An advisory last-writer-wins field overwrite,
identical in discipline to \texttt{SetMetadata}: no working state, no
conflict, and no idempotence short-circuit. Graph-aware reduction writes
exactly the five subset fields onto \texttt{Score.tuning\_context} and
\textbf{leaves \texttt{accidental\_extensions} untouched} --- preserving
whatever the graph already held, since the operation carries no
information about that field and normalizing it away (clearing it at
construction, or rejecting a populated in-memory value) would either make
correctness depend on a clearing discipline the compiler cannot see, or
turn an in-memory-only field into an authoring error for callers who never
opted into its persistence.
\textbf{Conflict cases.} None (advisory LWW).
\textbf{Undo semantics.} A value-restoring field overwrite
(Section~\ref{sec:k0:undo}), with the chain's value type the \textbf{subset}
\texttt{TuningContextSettings}, not \texttt{ScoreTuningContext}: the write
chain is seeded from the base's five wire-bearing fields at ingest, so
undoing the first operational write restores the pre-operational settings
rather than absence, identically whether the base was seeded with the type
default or a genuine non-default value --- the never-authored /
authored-to-default distinction is unobservable and stays so, since
\texttt{ScoreTuningContext} is an always-valued \texttt{Score} field, like
\texttt{metadata}, with no absent state to return to. \texttt{accidental\_
extensions} never participates in undo: no operation ever writes it, so
undo leaves it exactly as it found it.
\textbf{Re-anchoring.} Not applicable: the field is a score singleton with no
tombstonable anchor.
\section{Repeat Structures} \section{Repeat Structures}
\label{sec:k0:repeat-structures} \label{sec:k0:repeat-structures}
@ -1604,8 +1712,8 @@ transaction minted is tombstoned. \texttt{StrictInverse} conflicts
\emph{Value restoration} (this revision): for every last-writer-wins overwrite \emph{Value restoration} (this revision): for every last-writer-wins overwrite
the target transaction performed --- event and identified-pitch modification, the target transaction performed --- event and identified-pitch modification,
respelling, cross-cutting modification, metadata, canvas layout defaults, respelling, cross-cutting modification, metadata, canvas layout defaults,
spelling precedence, metric grid, meter change, tempo segment, staff layout, spelling precedence, tuning context settings, metric grid, meter change,
and the user break advisories --- the reducer tempo segment, staff layout, and the user break advisories --- the reducer
maintains, per overwritten key, the \emph{canonical-order write chain} of maintains, per overwritten key, the \emph{canonical-order write chain} of
(writer, value) pairs. Undoing the transaction restores each written key to (writer, value) pairs. Undoing the transaction restores each written key to
its chain-predecessor value (or its absence, where the transaction introduced its chain-predecessor value (or its absence, where the transaction introduced

Binary file not shown.

View File

@ -234,7 +234,7 @@
{\Large\scshape\color{epiphanyslate}Text Projection}\\[6pt] {\Large\scshape\color{epiphanyslate}Text Projection}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt] {\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt] {\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.10.0 --- The carried manifest schema version reaches the document line}\\[4pt] {\normalsize\color{epiphanyink}Version 0.11.0 --- The genesis tuning-context setter reaches the grammar}\\[4pt]
{\small\color{epiphanyslate}Normative for the text form it defines} {\small\color{epiphanyslate}Normative for the text form it defines}
\vfill \vfill
\end{titlepage} \end{titlepage}
@ -483,7 +483,7 @@ A projection is, in order:
\begin{requirement} \begin{requirement}
\label{req:textproj:header-version} \label{req:textproj:header-version}
A parser implementing this companion \MUST{} accept exactly one header A parser implementing this companion \MUST{} accept exactly one header
version: \texttt{(0 10 0)}, the version of the companion it implements. It version: \texttt{(0 11 0)}, the version of the companion it implements. It
\MUST{} reject any other version at line one. \MUST{} reject any other version at line one.
Multi-version acceptance and text migrate-on-read are deferred in the same Multi-version acceptance and text migrate-on-read are deferred in the same
@ -534,7 +534,7 @@ projection introduces no ordering of its own.
A parser \MUST{} reject every \texttt{(blob ...)} line whose blob is A parser \MUST{} reject every \texttt{(blob ...)} line whose blob is
unreferenced by canonical state unreferenced by canonical state
(Requirement~\ref{req:textproj:canonical-blobs}). At companion (Requirement~\ref{req:textproj:canonical-blobs}). At companion
version~0.10.0, neither a canonical operation nor canonical reduced state can version~0.11.0, neither a canonical operation nor canonical reduced state can
carry a \texttt{BlobId}; canonical state therefore cannot reference a blob, carry a \texttt{BlobId}; canonical state therefore cannot reference a blob,
and a parser \MUST{} reject every \texttt{(blob ...)} line. and a parser \MUST{} reject every \texttt{(blob ...)} line.
\end{requirement} \end{requirement}
@ -1061,6 +1061,7 @@ kind ::= "(insert-event " bytes " " value ")"
| "(create-instrument " value ")" | "(create-instrument " value ")"
| "(set-canvas-layout-defaults " value ")" | "(set-canvas-layout-defaults " value ")"
| "(set-spelling-precedence " value ")" | "(set-spelling-precedence " value ")"
| "(set-tuning-context " value ")"
tuplet-comp ::= "not-in-tuplet" | "(replace-with-rest " value ")" tuplet-comp ::= "not-in-tuplet" | "(replace-with-rest " value ")"
| "(rewrite-tuplets (" bytes* "))" | "(rewrite-tuplets (" bytes* "))"
@ -1137,7 +1138,7 @@ A document of one operation --- a transposition of two pitches up a perfect
fifth, over a compacted base --- projects to five lines: fifth, over a compacted base --- projects to five lines:
\begin{lstlisting} \begin{lstlisting}
(text-projection (0 10 0)) (text-projection (0 11 0))
(document #x05050505050505050505050505050505 (schema 0 1)) (document #x05050505050505050505050505050505 (schema 0 1))
(profile full (0 1 0) (constraints 67108864 (retention 1 () true))) (profile full (0 1 0) (constraints 67108864 (retention 1 () true)))
(canonical-base #x1f8b0000000000000000000000000000 #x 1 full (schema 0 1) #x0000) (canonical-base #x1f8b0000000000000000000000000000 #x 1 full (schema 0 1) #x0000)
@ -1372,6 +1373,18 @@ absorb it, exactly as the binary decoder does.
both claiming \texttt{(0 9 0)}. Cached projections at \texttt{(0 9 0)} do not both claiming \texttt{(0 9 0)}. Cached projections at \texttt{(0 9 0)} do not
migrate; a stale \texttt{TextProjection} chunk is regenerated, not migrate; a stale \texttt{TextProjection} chunk is regenerated, not
converted. \\ converted. \\
\today & Chapter 5 & 0.11.0 --- The genesis tuning-context setter reaches
the grammar (genesis tranche G2b,
\texttt{spec/CONTRACT\_GENESIS\_G2B\_TUNING.md}). The \texttt{kind}
production gains \texttt{"(set-tuning-context " value ")"}
(\texttt{req:textproj:operation-vocabulary}), the third appended kind since
the header was gated to a single version at 0.7.0.
Same forcing reason as every prior grammar-extending bump: holding the
version while extending the grammar would leave two mutually incompatible
grammars both claiming \texttt{(0 10 0)}. Cached projections at
\texttt{(0 10 0)} do not migrate; a stale \texttt{TextProjection} chunk is
regenerated, not converted. \\
\bottomrule \bottomrule
\end{longtable} \end{longtable}

View File

@ -71,8 +71,9 @@ ops.operation_kind_tag accept - tag_30 1e
ops.operation_kind_tag accept - tag_31 1f ops.operation_kind_tag accept - tag_31 1f
ops.operation_kind_tag accept - tag_32 20 ops.operation_kind_tag accept - tag_32 20
ops.operation_kind_tag accept - tag_33 21 ops.operation_kind_tag accept - tag_33 21
ops.operation_kind_tag accept - tag_34 22
ops.operation_kind_tag accept - registered 1000000000000000000123456789abcdef ops.operation_kind_tag accept - registered 1000000000000000000123456789abcdef
ops.operation_kind_tag reject unknown-discriminant tag_34_one_past_the_vocabulary 22 ops.operation_kind_tag reject unknown-discriminant tag_35_one_past_the_vocabulary 23
ops.operation_kind_tag reject unknown-discriminant tag_200 c8 ops.operation_kind_tag reject unknown-discriminant tag_200 c8
ops.operation_kind_tag reject truncated tag_empty - ops.operation_kind_tag reject truncated tag_empty -
ops.operation_kind_tag reject trailing-bytes insert_event_trailing 0000 ops.operation_kind_tag reject trailing-bytes insert_event_trailing 0000
@ -85,6 +86,8 @@ ops.operation_envelope accept - set_canvas_layout_defaults 000000000000000100000
ops.operation_envelope reject trailing-bytes set_canvas_layout_defaults_trailing 000000000000000100000000000000020000000000000000000000000000000001000000000000000100000000000000000000010000000000000002000000000000000000002048000000080000000000000000805a40080000000000000000906240080000000000000000001e40080000000000000000001e40080000000000000000001e40080000000000000000001e4000 ops.operation_envelope reject trailing-bytes set_canvas_layout_defaults_trailing 000000000000000100000000000000020000000000000000000000000000000001000000000000000100000000000000000000010000000000000002000000000000000000002048000000080000000000000000805a40080000000000000000906240080000000000000000001e40080000000000000000001e40080000000000000000001e40080000000000000000001e4000
ops.operation_envelope accept - set_spelling_precedence 000000000000000100000000000000030000000000000000000000000000000001000000000000000100000000000000000000010000000000000003000000000000000000002109000000050000000403020100 ops.operation_envelope accept - set_spelling_precedence 000000000000000100000000000000030000000000000000000000000000000001000000000000000100000000000000000000010000000000000003000000000000000000002109000000050000000403020100
ops.operation_envelope reject trailing-bytes set_spelling_precedence_trailing 00000000000000010000000000000003000000000000000000000000000000000100000000000000010000000000000000000001000000000000000300000000000000000000210900000005000000040302010000 ops.operation_envelope reject trailing-bytes set_spelling_precedence_trailing 00000000000000010000000000000003000000000000000000000000000000000100000000000000010000000000000000000001000000000000000300000000000000000000210900000005000000040302010000
ops.operation_envelope accept - set_tuning_context 00000000000000010000000000000004000000000000000000000000000000000100000000000000010000000000000000000001000000000000000400000000000000000000223000000006000000636d6e2d3132060000007465742d313200050004080000000000000000907b40010028000100280000000000
ops.operation_envelope reject trailing-bytes set_tuning_context_trailing 00000000000000010000000000000004000000000000000000000000000000000100000000000000010000000000000000000001000000000000000400000000000000000000223000000006000000636d6e2d3132060000007465742d313200050004080000000000000000907b4001002800010028000000000000
# bundle.manifest # bundle.manifest
bundle.manifest accept - empty_manifest 6f9e7d11689ab113c4a1f05faf60fe60050505050505050505050505050505050000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000 bundle.manifest accept - empty_manifest 6f9e7d11689ab113c4a1f05faf60fe60050505050505050505050505050505050000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000

View File

@ -22,16 +22,16 @@
# document bytes are normative. `<utf8-hex>` is lowercase with no separators. # document bytes are normative. `<utf8-hex>` is lowercase with no separators.
# textproj.document # textproj.document
textproj.document accept - minimal 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a textproj.document accept - minimal 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a textproj.document accept - lineage_custom_profile 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a textproj.document accept - extension_base_two_envelopes 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document accept - rich_document 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a textproj.document accept - rich_document 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e2028302039203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a textproj.document reject wrong-header-version superseded_companion_version 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a
textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a textproj.document reject blob-line unreferenced_blob 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28626c6f6220226170706c69636174696f6e2f6f637465742d73747265616d222028292023783031290a
textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a textproj.document reject out-of-order-sections canonical_base_before_extension 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a textproj.document reject repeated-singular-section lineage_repeated 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a textproj.document reject operation-envelope-order envelopes_reversed 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a
textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a textproj.document reject profile-declaration-order profiles_reversed 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830323032303230323032303230323032303230323032303230323032303230322028736368656d612030203129290a286c696e656167652023783132313231323132313231323132313231323132313231323132313231323132290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303031202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203130302030202378303030303030303030303030303030313030303030303030303030303030303129202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030312929290a
textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a textproj.document reject extension-declaration-order extensions_reversed 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830343034303430343034303430343034303430343034303430343034303430342028736368656d612030203129290a286c696e656167652023783134313431343134313431343134313431343134313431343134313431343134290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a2870726f66696c652028637573746f6d20237863636363636363636363636363636363636363636363636363636363636363632920283120322033292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303230323032303230323032303230323032303230323032303230323032303220283120302032292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830332929202378303220237830323033290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303430343034303430343034303430343034303430343034303430343034303420237830313032303420312066756c6c2028736368656d61203020312920237861303034290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303034202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203430302030202378303030303030303030303030303030313030303030303030303030303030303429202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030342929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303035202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203530302030202378303030303030303030303030303030313030303030303030303030303030303529202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030352929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303036202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203630302030202378303030303030303030303030303030313030303030303030303030303030303629202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030362929290a
textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a textproj.document reject extension-chunk-order extension_chunks_reversed 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830333033303330333033303330333033303330333033303330333033303330332028736368656d612030203829290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e203120282920747275652929290a28657874656e73696f6e202378303130313031303130313031303130313031303130313031303130313031303120283120302031292066616c73652028286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830322920286368756e6b20657874656e73696f6e2d646174612028736368656d61203020312920237830312929202378303120237830313032290a2863616e6f6e6963616c2d62617365202378303330333033303330333033303330333033303330333033303330333033303320237830313032303320312066756c6c2028736368656d61203020312920237861303033290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303032202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203230302030202378303030303030303030303030303030313030303030303030303030303030303229202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030322929290a28656e76656c6f70652023783030303030303030303030303030303130303030303030303030303030303033202378303030303030303030303030303030303030303030303030303030303030616220287374616d70203330302030202378303030303030303030303030303030313030303030303030303030303030303329202863617573616c2028292028292920282920287072696d6974697665202864656c6574652d726567696f6e20237830303030303030303030303030303031303030303030303030303030303030332929290a
textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e202830203130203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929 textproj.document reject missing-trailing-lf final_lf_missing 28746578742d70726f6a656374696f6e202830203131203029290a28646f63756d656e7420237830313031303130313031303130313031303130313031303130313031303130312028736368656d612030203129290a2870726f66696c652066756c6c20283020312030292028636f6e73747261696e74732036373130383836342028726574656e74696f6e20312028292074727565292929