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.
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
/// §"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
/// v1 `CreateRegion`; the reader treats the block bytes opaquely, so it
/// parses a higher-major block without decoding the payload). Schema major 3
/// (Push 4b tranche 3b-i) does **not** raise this role: no operation payload
/// embeds the tuning context, so no op block is ever born at v3. `Snapshot`
/// admits major 3 for the acceleration full-`Score` form (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,
/// parses a higher-major block without decoding the payload). **Schema major
/// 3 is raised by genesis tranche G2b** (`spec/CONTRACT_GENESIS_G2B_TUNING.md`):
/// `SetTuningContext` is the sole operation payload that embeds the tuning
/// context (`epiphany_core::TuningContextSettings`, born at major 3
/// unconditionally), so an op block carrying one is now born at v3 — this
/// superseded the earlier Push 4b tranche 3b-i note claiming no payload ever
/// would. `Snapshot` admits major 3 for the acceleration full-`Score` form
/// (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
/// 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.
pub fn max_supported_major(kind: ChunkKind) -> u16 {
match kind {
ChunkKind::OperationEnvelopeBlock => 2,
ChunkKind::OperationEnvelopeBlock => 3,
// The payload-polymorphic Snapshot role: the acceleration
// full-`Score` form is decoded through the core versioned seam
// (`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
// cross-cutting/staff/metadata value); every other role stays exact-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")
// raises only the snapshot role — no operation payload embeds the
// tuning context, so the op-block role's admission is untouched.
// forever. Genesis tranche G2b (`spec/CONTRACT_GENESIS_G2B_TUNING.md`)
// raises the op-block role to major 3: `SetTuningContext` is the sole
// 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::V2.major, 2);
assert_eq!(SchemaVersion::V3.major, 3);
// The op-block role admits [0, 2] — unchanged by schema major 3.
assert_eq!(max_supported_major(ChunkKind::OperationEnvelopeBlock), 2);
// (t4) The op-block role admits [0, 3] as of genesis tranche G2b.
assert_eq!(max_supported_major(ChunkKind::OperationEnvelopeBlock), 3);
// The snapshot role admits the major-3 acceleration form (decoded
// through the core versioned seam); the canonical BASE stays major 0
// per role (`mis_stamped_canonical_base`). The remaining roles stay
@ -1401,16 +1407,45 @@ mod tests {
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]
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
// forward-compat write): structural validation lets it through, but the
// 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.
//
// 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 block = StagedChunk::operation_block_versioned(
crate::block::encode_block(&[vec![1u8, 2, 3]]),
SchemaVersion::new(3, 0),
SchemaVersion::new(4, 0),
);
bundle
.commit(&[block], |ctx| {
@ -1426,7 +1461,7 @@ mod tests {
);
assert!(bundle.anomalies().iter().any(|a| matches!(
a,
IntegrityAnomaly::UnsupportedCanonicalChunkMajor { schema_major: 3 }
IntegrityAnomaly::UnsupportedCanonicalChunkMajor { schema_major: 4 }
)));
// A further commit against the now-read-only bundle is refused.
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
existing `_ => 0` catch-all in `epiphany-ops`. This is unlike G1's
`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,
StaffGroupKind, StaffInstance, StaffLineConfiguration, StemDirection, SubBeam,
TempoMapReference, TextLineDefinition, Tie, TieClass, TimeExtent, TimeSignature,
TimeSignatureDisplay, Timestamp, Tuplet, TupletRatio, UnpitchedMember, ViewDefinition, Voice,
VoiceOrigin, Volta,
TimeSignatureDisplay, Timestamp, TuningContextSettings, Tuplet, TupletRatio, UnpitchedMember,
ViewDefinition, Voice, VoiceOrigin, Volta,
};
use crate::ids::{
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
// frozen prior layouts are read by the `dec_*_v1` sub-decoders.
struct_codec!(Slur {
@ -3545,6 +3579,12 @@ canonical_value! {
// own — see the contract's "Why these two" table.
CanvasLayoutDefaults,
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)]
@ -3873,6 +3913,65 @@ mod tests {
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]
fn generator_scores_round_trip() {
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").
///
/// 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,
StaffBracketKind, StaffExtent, StaffGroup, StaffGroupKind, StaffInstance,
StaffLineConfiguration, StemDirection, SubBeam, TempoMapReference, TextLineDefinition, Tie,
TieClass, TimeExtent, TimeSignature, TimeSignatureDisplay, Timestamp, Tuplet, TupletRatio,
UnpitchedMember, ViewDefinition, Voice, VoiceOrigin, Volta,
TieClass, TimeExtent, TimeSignature, TimeSignatureDisplay, Timestamp, TuningContextSettings,
Tuplet, TupletRatio, UnpitchedMember, ViewDefinition, Voice, VoiceOrigin, Volta,
};
pub use tempo::{

View File

@ -28,7 +28,7 @@ use crate::graph::{
AnnotationAnchor, DecompositionSource, EventOrderingDAG, GestureAnchoring, KeySignature,
MetadataValue, RegionContent, RegionTimeModel, RepeatKind, ScoreTuningContext,
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_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.
// ===========================================================================

View File

@ -20,8 +20,8 @@
//! container is what the barrier's **scope** is for, and scope is matched
//! against the target's real containment, precisely.
//! * **Score-level operations** (`SetMetadata`, the transaction descriptor,
//! `SetCanvasLayoutDefaults`, `SetSpellingPrecedence`) name no graph object:
//! only a score-wide barrier (empty
//! `SetCanvasLayoutDefaults`, `SetSpellingPrecedence`, `SetTuningContext`)
//! name no graph object: only a score-wide barrier (empty
//! `affected_object_kinds`, `WholeScore`/`TuningContext`/`Registered` scope)
//! can match them.
//! * **Extension-defined operations** (`OperationKind::Registered`) carry a
@ -469,7 +469,11 @@ pub(crate) fn subjects_of(kind: &OperationKind, score: &Score) -> BarrierSubject
OperationKind::SetMetadata(_)
| OperationKind::DeclareTransaction(_)
| 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(
TypedObjectId::RepeatStructure(op.repeat_structure_id()),
repeat_context(score, &op.repeat),

View File

@ -1153,28 +1153,28 @@ mod tests {
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`
// 30, genesis G1's `CreateInstrument` 31, genesis G2a's
// `SetCanvasLayoutDefaults` 32 and `SetSpellingPrecedence` 33;
// encodings are append-only).
// `SetCanvasLayoutDefaults` 32 and `SetSpellingPrecedence` 33,
// genesis G2b's `SetTuningContext` 34; encodings are append-only).
//
// This assertion named 30 until Push 5 / P4, 31 until genesis G1, and
// 32 until genesis G2a — each time, by then, the number had become a
// real kind, so the test was pinning a bug: a barrier that prohibited
// the new operation encoded fine and would not read back. It must be
// bumped by every tranche that appends a tag, and it is deliberately a
// literal rather than `PAYLOAD_FREE.len()` so the bump is a conscious
// act.
// This assertion named 30 until Push 5 / P4, 31 until genesis G1, 32
// until genesis G2a, and 34 until genesis G2b — each time, by then,
// the number had become a real kind, so the test was pinning a bug: a
// barrier that prohibited the new operation encoded fine and would
// not read back. It must be bumped by every tranche that appends a
// tag, and it is deliberately a literal rather than
// `PAYLOAD_FREE.len()` so the bump is a conscious act.
let mut bytes = vec![0u8];
bytes.extend(set_blob(&[]));
bytes.extend(set_blob(&[vec![34u8]]));
bytes.extend(set_blob(&[vec![35u8]]));
bytes.push(0);
assert_eq!(
EditBarrier::decode_canonical_bytes(&bytes),
Err(BarrierDecodeError::InvalidTag {
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
4a, and G1; the digest moved and was re-pinned with that reasoning recorded
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,
OperationId, PitchId, PitchSpelling, RegionId, RegionTimeModel, RepeatStructureId, ReplicaId,
ScoreMetadata, SpellingPrecedence, Staff, StaffInstance, StaffInstanceId,
StaffLineConfiguration, TimeAnchor, TimeSignature, TranspositionInterval, TupletId,
TypedObjectId, Voice, VoiceId, WallClockTime,
StaffLineConfiguration, TimeAnchor, TimeSignature, TranspositionInterval,
TuningContextSettings, TupletId, TypedObjectId, Voice, VoiceId, WallClockTime,
};
use epiphany_determinism::{CanonicalDecode, CanonicalEncode};
@ -596,6 +596,9 @@ fn operation_kind(r: &mut Reader<'_>) -> Result<OperationKind> {
33 => OperationKind::SetSpellingPrecedence(SetSpellingPrecedenceOp {
precedence: value::<SpellingPrecedence>(r, "SpellingPrecedence")?,
}),
34 => OperationKind::SetTuningContext(SetTuningContextOp {
settings: value::<TuningContextSettings>(r, "TuningContextSettings")?,
}),
tag => {
return Err(EnvelopeDecodeError::InvalidTag {
kind: "OperationKind",
@ -895,6 +898,11 @@ pub(crate) mod tests {
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.
fn gen_payload(rng: &mut SplitMix64) -> OperationPayload {
let kind = match rng.below(31) {
let kind = match rng.below(32) {
0 => {
let voice = VoiceId::new(ReplicaId(7), rng.below(3));
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 {
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 {
staff_instance: StaffInstanceId::new(ReplicaId(7), rng.below(3)),
instrument_override: None,

View File

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

View File

@ -188,6 +188,8 @@ fn project_kind(kind: &OperationKind) -> V0OperationKind {
OperationKind::SetSpellingPrecedence(op) => {
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) => {
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,
RepeatStructureId, Rest, ScoreMetadata, Slur, Spanner, SpellingPrecedence, Staff, StaffId,
StaffInstance, StaffInstanceId, StaffLineConfiguration, TempoSegment, Tie, TimeAnchor,
TimeSignature, TransactionId, TranspositionInterval, TupletId, TypedObjectId, Voice, VoiceId,
TimeSignature, TransactionId, TranspositionInterval, TuningContextSettings, TupletId,
TypedObjectId, Voice, VoiceId,
};
use epiphany_determinism::{
sorted_canonical, CanonicalDecode, CanonicalEncode, CanonicalSet, DecodeError,
@ -263,6 +264,15 @@ pub enum OperationKind {
/// Overwrite the score's spelling precedence (later-in-canonical-order
/// wins).
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 {
@ -291,6 +301,13 @@ impl OperationKind {
| OperationKind::CreateStaff(_)
| OperationKind::SetMetadata(_)
| 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
// Option; None encodes byte-identically to the prior major.
OperationKind::CreateRegion(op) => {
@ -368,6 +385,9 @@ impl OperationKind {
// by the same accident as above, not by rule.
OperationKind::SetCanvasLayoutDefaults(_) => 32,
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(_) => {
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::SetCanvasLayoutDefaults(_) => OperationKindTag::SetCanvasLayoutDefaults,
OperationKind::SetSpellingPrecedence(_) => OperationKindTag::SetSpellingPrecedence,
OperationKind::SetTuningContext(_) => OperationKindTag::SetTuningContext,
}
}
}
@ -516,6 +540,7 @@ impl CanonicalEncode for OperationKind {
OperationKind::CreateInstrument(op) => op.encode_canonical(out),
OperationKind::SetCanvasLayoutDefaults(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,
/// Genesis tranche G2a.
SetSpellingPrecedence,
/// Genesis tranche G2b.
SetTuningContext,
}
/// The discriminant of [`OperationKindTag::Registered`], the one tag that
@ -684,6 +711,7 @@ operation_kind_tag_vocabulary! {
CreateInstrument = 31 => "create-instrument" @ Some(8),
SetCanvasLayoutDefaults = 32 => "set-canvas-layout-defaults" @ Some(9),
SetSpellingPrecedence = 33 => "set-spelling-precedence" @ Some(9),
SetTuningContext = 34 => "set-tuning-context" @ Some(10),
}
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
/// resolved musical position in a region's default metric grid
/// (operation_catalog §"Meter and Tempo Overwrites"). Carries the full

View File

@ -40,7 +40,7 @@ use epiphany_core::{
SpellingDirective, SpellingPrecedence, SpellingScope, SpellingSource, Staff, StaffId,
StaffInstance, StaffInstanceId, StaffLineConfiguration, TempoMap, TempoSegment, TempoShape,
TimeAnchor, TimeSignature, TimeSignatureId, TransactionId, TransposeRefusal,
TranspositionInterval, TypedObjectId, Voice, VoiceId, VoiceOrigin,
TranspositionInterval, TuningContextSettings, TypedObjectId, Voice, VoiceId, VoiceOrigin,
};
use epiphany_determinism::CanonicalEncode;
@ -63,7 +63,8 @@ use crate::payload::{
InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
OperationKind, OperationPayload, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp,
SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetTimeSignatureOp, SetUserPageBreakOp, TransposeIntervalOp, TransposeOp, TupletCompensation,
SetTimeSignatureOp, SetTuningContextOp, SetUserPageBreakOp, TransposeIntervalOp, TransposeOp,
TupletCompensation,
};
use crate::stamp::StampTuple;
use crate::support::{ObjectKind, SerializedCanonicalInputs};
@ -851,6 +852,11 @@ enum ValueRestoration {
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 {
region: RegionId,
value: Option<MetricGrid>,
@ -972,6 +978,11 @@ struct Reducer<'a> {
// the pre-operational value.
canvas_layout_defaults_chain: WriteChain<CanvasLayoutDefaults>,
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)>>,
page_break_chain: BTreeMap<(RegionId, MusicalPosition), WriteChain<(TimeAnchor, bool)>>,
// Meter/tempo overwrite chains (Phase-3 tranche): `Some` = a set/replace at
@ -1085,6 +1096,7 @@ struct WorkingSnapshot {
metadata_chain: WriteChain<ScoreMetadata>,
canvas_layout_defaults_chain: WriteChain<CanvasLayoutDefaults>,
spelling_precedence_chain: WriteChain<SpellingPrecedence>,
tuning_context_chain: WriteChain<TuningContextSettings>,
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>>>,
@ -1367,6 +1379,7 @@ impl<'a> Reducer<'a> {
metadata_chain: WriteChain::new(),
canvas_layout_defaults_chain: WriteChain::new(),
spelling_precedence_chain: WriteChain::new(),
tuning_context_chain: WriteChain::new(),
break_chain: BTreeMap::new(),
page_break_chain: BTreeMap::new(),
meter_change_chain: BTreeMap::new(),
@ -1469,6 +1482,17 @@ impl<'a> Reducer<'a> {
.seed(score.canvas.layout_defaults);
self.spelling_precedence_chain
.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 {
self.tempo_segment_chain
.entry((None, resolved_anchor_position(&segment.start)))
@ -2833,6 +2857,7 @@ impl<'a> Reducer<'a> {
self.set_canvas_layout_defaults(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::UndoTransaction(op) => self.undo_transaction(env, op),
@ -2947,6 +2972,30 @@ impl<'a> Reducer<'a> {
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(
&mut self,
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 {
if !slot_live(TypedObjectId::Region(*region)) {
continue;
@ -5574,6 +5633,24 @@ impl<'a> Reducer<'a> {
.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 } => {
self.metric_grid_chain
.entry(region)
@ -7551,6 +7628,7 @@ impl<'a> Reducer<'a> {
metadata_chain: self.metadata_chain.clone(),
canvas_layout_defaults_chain: self.canvas_layout_defaults_chain.clone(),
spelling_precedence_chain: self.spelling_precedence_chain.clone(),
tuning_context_chain: self.tuning_context_chain.clone(),
break_chain: self.break_chain.clone(),
page_break_chain: self.page_break_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.canvas_layout_defaults_chain = s.canvas_layout_defaults_chain;
self.spelling_precedence_chain = s.spelling_precedence_chain;
self.tuning_context_chain = s.tuning_context_chain;
self.break_chain = s.break_chain;
self.page_break_chain = s.page_break_chain;
self.meter_change_chain = s.meter_change_chain;
@ -10914,6 +10993,92 @@ mod tests {
0,
"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]
@ -11007,6 +11172,16 @@ mod tests {
// leak to appear on even if the base did stamp one (it does not —
// that is `OperationEnvelopeBlock`'s concern in `epiphany-bundle`,
// untouched by this packet).
//
// 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 envelopes = crate::fuzz::gen_envelope_set(&mut rng, 200);
let mut set = OperationSet::new();
@ -11016,7 +11191,7 @@ mod tests {
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(
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 ->
/// `AlreadyApplied`, and a differing second carry under the same live id
/// -> `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
/// not be on the field. `WorkingSnapshot` (`:7480`) is the transaction
/// **rollback** mechanism: snapshot before, restore on failure. But

View File

@ -15,8 +15,8 @@ use crate::payload::{
InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
OperationKind, OperationKindTag, RespellPitchOp, SetCanvasLayoutDefaultsOp, SetMetadataOp,
SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionDescriptor,
TransposeIntervalOp, TransposeOp,
SetTimeSignatureOp, SetTuningContextOp, SetUserPageBreakOp, SetUserSystemBreakOp,
TransactionDescriptor, TransposeIntervalOp, TransposeOp,
};
use crate::support::OperationKindRegistryId;
@ -229,6 +229,9 @@ impl TextValue for OperationKind {
OperationKind::SetSpellingPrecedence(op) => {
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)?,
})
}
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]
fn every_operation_kind_round_trips_with_canonical_text() {
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 {
round_trip(&sample_kind(tag));
}

View File

@ -112,6 +112,10 @@ pub enum V0OperationKind {
// every other v1-native kind above.
SetCanvasLayoutDefaults(crate::payload::SetCanvasLayoutDefaultsOp),
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

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")
}
/// 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)
/// (Phase-3 tranche): `numerator` quarter-note beat groups summing exactly to
/// 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,
));
// --- 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
}
@ -555,4 +593,33 @@ mod tests {
"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,
ResolveConflictPayload, RespellPitchOp, SerializedCanonicalInputs, SetCanvasLayoutDefaultsOp,
SetMetadataOp, SetMetricGridOp, SetSpellingPrecedenceOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransactionCategory,
TransactionDescriptor, TransposeIntervalOp, TransposeOp, TupletCompensation,
TupletCompensationKind, UndoPolicy, UndoTransactionPayload,
SetTimeSignatureOp, SetTuningContextOp, SetUserPageBreakOp, SetUserSystemBreakOp,
TransactionCategory, TransactionDescriptor, TransposeIntervalOp, TransposeOp,
TupletCompensation, TupletCompensationKind, UndoPolicy, UndoTransactionPayload,
};
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 => {
let pitches = if rng.boolean() {
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 {
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(
OperationKindRegistryId(rng.next_u64() as u128),
rng.byte_vec(0, 16),

View File

@ -686,12 +686,18 @@ mod tests {
#[test]
fn op_block_beyond_the_accept_set_opens_read_only() {
use epiphany_bundle::IntegrityAnomaly;
// A newer writer's op block, stamped schema major 3 — beyond the reader's
// op-block accept-set [0,2]. The bundle opens read-only preservation (the
// A newer writer's op block, stamped schema major 4 — beyond the reader's
// op-block accept-set [0,3]. The bundle opens read-only preservation (the
// 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(
encode_block(&[vec![1u8, 2, 3, 4]]),
SchemaVersion::new(3, 0),
SchemaVersion::new(4, 0),
);
let reopened = reopen_with_op_block(0xD2_0002, block);
assert!(
@ -700,7 +706,7 @@ mod tests {
);
assert!(reopened.anomalies().iter().any(|a| matches!(
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
// above warns about — derived list, literal total. Every tranche that
// appends a tag must bump it (genesis G1 took it from 31 to 32 by adding
// `CreateInstrument`; genesis G2a takes it from 32 to 34 by adding
// `SetCanvasLayoutDefaults` and `SetSpellingPrecedence`). It stays a
// literal on purpose: deriving it from `PAYLOAD_FREE.len()` would make the
// assertion vacuous, since that is the very list it exists to pin.
// `CreateInstrument`; genesis G2a took it from 32 to 34 by adding
// `SetCanvasLayoutDefaults` and `SetSpellingPrecedence`; genesis G2b takes
// it from 34 to 35 by adding `SetTuningContext`). It stays a literal on
// purpose: deriving it from `PAYLOAD_FREE.len()` would make the assertion
// vacuous, since that is the very list it exists to pin.
assert_eq!(
expected.len(),
34,
"33 payload-free kinds plus `Registered`"
35,
"34 payload-free kinds plus `Registered`"
);
let actual = alternatives("kind");

View File

@ -43,7 +43,13 @@ use epiphany_ops::OperationEnvelope;
/// `epiphany-layout-ir` dependency and structurally cannot decode edit-barrier
/// bytes). Holding the version while changing the grammar would leave two
/// incompatible grammars both claiming `(0 9 0)`.
pub const COMPANION_VERSION: (u32, u32, u32) = (0, 10, 0);
///
/// 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.
///

View File

@ -649,11 +649,12 @@ mod tests {
}
// Bumped with `COMPANION_VERSION` (0.7.0 → 0.8.0, genesis G1; 0.8.0 →
// 0.9.0, genesis G2a; 0.9.0 → 0.10.0, G-minor). Kept a literal because
// `projection` takes `&[&str]` and a formatted String would ripple
// through every call site; `the_test_header_tracks_the_implemented_
// version` below fails loudly if the two ever drift.
const HEADER: &str = "(text-projection (0 10 0))";
// 0.9.0, genesis G2a; 0.9.0 → 0.10.0, G-minor; 0.10.0 → 0.11.0, genesis
// G2b). Kept a literal because `projection` takes `&[&str]` and a
// formatted String would ripple through every call site;
// `the_test_header_tracks_the_implemented_version` below fails loudly if
// the two ever drift.
const HEADER: &str = "(text-projection (0 11 0))";
const DOCUMENT: &str = "(document #x00000000000000000000000000000001 (schema 0 1))";
/// 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()))
.collect();
// The rejected version must be one this crate does NOT implement. The
// G-minor rung moved `COMPANION_VERSION` to 0.10.0, which had been this
// The rejected version must be one this crate does NOT implement. Genesis
// 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 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
// migrate-on-read posture (`req:textproj:header-version`).
let wrong_version = replace_once(
minimal,
"(text-projection (0 11 0))",
"(text-projection (0 10 0))",
"(text-projection (0 9 0))",
);
vectors.push((
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
which questions must be answered before a dispatch contract can be written.
**Status:** **G1 landed** (3b09595, CI green) via
`spec/CONTRACT_GENESIS_G1_INSTRUMENT.md`. **G2a contracted**
(`spec/CONTRACT_GENESIS_G2A_SETTINGS.md`); **G-minor**, G2b, and G3 scoped, not
contracted. §6 lists what still needs ratification.
**Status:** the ladder **G1 → G2a → G-minor → G2b** is **complete**; only **G3**
remains.
* **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 29 (`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
`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} &
$\mathrm{lp}$(\texttt{SpellingPrecedence}) &
\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
\end{longtable}
\endgroup
@ -1525,6 +1532,7 @@ discriminants and wrong lengths are decode errors. Append-only past
\tablenums{28} & \texttt{CreateRepeatStructure} & \tablenums{29} & \texttt{DeleteRepeatStructure} \\
\tablenums{30} & \texttt{TransposeInterval} & \tablenums{31} & \texttt{CreateInstrument} \\
\tablenums{32} & \texttt{SetCanvasLayoutDefaults} & \tablenums{33} & \texttt{SetSpellingPrecedence} \\
\tablenums{34} & \texttt{SetTuningContext} & & \\
\bottomrule
\end{longtable}
\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
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
\tablenums{3}; the \texttt{OperationEnvelopeBlock} role's max stays
\tablenums{2} (schema major~3 embeds no operation payload, so this role
does not rise); every other role's max is \tablenums{0}. The reference
\tablenums{3}; the \texttt{OperationEnvelopeBlock} role's max is
\tablenums{3} as of genesis tranche G2b
(\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$,
$\textsc{max} = 3$), realized as these differing per-role maxima rather
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}:
\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
tranche took \tablenums{24}--\tablenums{27} under this mechanism,
the repeat pair \tablenums{28}/\tablenums{29} in the
schema-major-2 revision, Push~4a took \tablenums{30}
(\texttt{TransposeInterval}), genesis tranche G1 took \tablenums{31}
(\texttt{CreateInstrument}), and genesis tranche G2a took
(\texttt{CreateInstrument}), genesis tranche G2a took
\tablenums{32}/\tablenums{33} (\texttt{SetCanvasLayoutDefaults},
\texttt{SetSpellingPrecedence}) --- the discriminant space is one
append-only table across majors, but a kind's \emph{block stamp} follows
minimal stamping over its payload: \tablenums{28}'s payload embeds a v2
layout, so its blocks stamp major~2, Section~\ref{sec:evolution:major2});
\item \texttt{OperationKindTag}: append at ${\geq}\,\tablenums{34}$
(Requirement~\ref{req:binfmt:kind-tag}; the same ten variants as
\texttt{OperationKind}, at the same ten discriminants, appended by the
same five tranches);
\texttt{SetSpellingPrecedence}), and genesis tranche G2b took
\tablenums{34} (\texttt{SetTuningContext}) --- the discriminant space is
one append-only table across majors, but a kind's \emph{block stamp}
follows minimal stamping over its payload: \tablenums{28}'s payload
embeds a v2 layout, so its blocks stamp major~2,
Section~\ref{sec:evolution:major2}; \tablenums{34}'s payload embeds a v3
layout unconditionally, so its blocks stamp major~3,
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}$
(Section~\ref{sec:ops:payload}; \texttt{ResolveEquivocation} = 3, Push~3,
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.
\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}
\item \textbf{Snapshot-only:} \texttt{ScoreTuningContext} reaches the wire
\emph{only} through the acceleration full-\texttt{Score} snapshot. No
operation payload embeds the tuning context (core Chapter~6's operation
vocabulary carries no \texttt{ScoreTuningContext} / \texttt{TuningOverride}
/ \texttt{SmuflVersionRequirement} field anywhere), so the
\textbf{canonical operation layer is untouched}: no op payload is ``born
at v3'', and no frozen v2 op-payload decoder is needed. The
\texttt{OperationEnvelopeBlock} role's accept-set therefore stays at
major~2 (Section~\ref{sec:evolution:major2}) --- schema major~3 is the
first data-model bump under which a chunk role's max does \emph{not}
move in lockstep with the others.
\item \textbf{Reaches two sites, as of genesis tranche G2b.}
\texttt{ScoreTuningContext} still reaches the wire through the
acceleration full-\texttt{Score} snapshot, \emph{and} its authored
subset --- \texttt{TuningContextSettings}, exactly the five
wire-bearing fields, byte-identical to this section's layout
(Section~\ref{sec:ops:kinds}, \tablenums{34}
\texttt{SetTuningContext}) --- now reaches the wire through the
canonical operation layer: \texttt{SetTuningContext} is \emph{born at
v3 unconditionally} (the same shape as \texttt{CreateInstrument}'s v2
birth, Section~\ref{sec:evolution:major2}), since \texttt{smufl} and
\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
\texttt{MaterializedState} embeds no tuning context, so it remains
major~0, byte-identical across the bump --- the same keystone as
majors~1 and~2, and a conformance test \SHOULD{} assert it again.
\texttt{MaterializedState} embeds no tuning context (nor any other
\texttt{Score} field value), so it remains major~0, byte-identical
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).
\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
major-3 reader migrates a major-0 snapshot
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
major-2-only reader's op-block admission is exactly as before this bump ---
only the \texttt{Snapshot} role's accept-set widens, to \tablenums{3}
(Section~\ref{sec:evolution:gate}).
default-filling. As originally defined (Push~4b tranche 3b-i), no operation
payload changed at this major, so a major-2-only reader's op-block
admission was exactly as before the bump and only the \texttt{Snapshot}
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}
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.
SetCanvasLayoutDefaults(SetCanvasLayoutDefaultsOp),
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
SetUserSystemBreak(SetUserSystemBreakOp),
@ -11931,6 +11936,8 @@ pub enum OperationKindTag {
// Genesis tranche G2a: the two major-0 settings setters.
SetCanvasLayoutDefaults,
SetSpellingPrecedence,
// Genesis tranche G2b: name-verbatim, as the two prior additions are.
SetTuningContext,
}
pub enum BarrierScope {

Binary file not shown.

View File

@ -231,7 +231,7 @@
{\Large\scshape\color{epiphanyslate}Operation Catalog}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.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}
\vfill
\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
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}
\label{ch:framework}
@ -1247,6 +1282,79 @@ state rather than absence.
\textbf{Re-anchoring.} Not applicable: the field is a score singleton with no
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}
\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
the target transaction performed --- event and identified-pitch modification,
respelling, cross-cutting modification, metadata, canvas layout defaults,
spelling precedence, metric grid, meter change, tempo segment, staff layout,
and the user break advisories --- the reducer
spelling precedence, tuning context settings, metric grid, meter change,
tempo segment, staff layout, and the user break advisories --- the reducer
maintains, per overwritten key, the \emph{canonical-order write chain} of
(writer, value) pairs. Undoing the transaction restores each written key to
its chain-predecessor value (or its absence, where the transaction introduced

Binary file not shown.

View File

@ -234,7 +234,7 @@
{\Large\scshape\color{epiphanyslate}Text Projection}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.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}
\vfill
\end{titlepage}
@ -483,7 +483,7 @@ A projection is, in order:
\begin{requirement}
\label{req:textproj:header-version}
A parser implementing this companion \MUST{} accept exactly one header
version: \texttt{(0 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.
Multi-version acceptance and text migrate-on-read are deferred in the same
@ -534,7 +534,7 @@ projection introduces no ordering of its own.
A parser \MUST{} reject every \texttt{(blob ...)} line whose blob is
unreferenced by canonical state
(Requirement~\ref{req:textproj:canonical-blobs}). At companion
version~0.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,
and a parser \MUST{} reject every \texttt{(blob ...)} line.
\end{requirement}
@ -1061,6 +1061,7 @@ kind ::= "(insert-event " bytes " " value ")"
| "(create-instrument " value ")"
| "(set-canvas-layout-defaults " value ")"
| "(set-spelling-precedence " value ")"
| "(set-tuning-context " value ")"
tuplet-comp ::= "not-in-tuplet" | "(replace-with-rest " value ")"
| "(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:
\begin{lstlisting}
(text-projection (0 10 0))
(text-projection (0 11 0))
(document #x05050505050505050505050505050505 (schema 0 1))
(profile full (0 1 0) (constraints 67108864 (retention 1 () true)))
(canonical-base #x1f8b0000000000000000000000000000 #x 1 full (schema 0 1) #x0000)
@ -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
migrate; a stale \texttt{TextProjection} chunk is regenerated, not
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
\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_32 20
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 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 truncated tag_empty -
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 accept - set_spelling_precedence 000000000000000100000000000000030000000000000000000000000000000001000000000000000100000000000000000000010000000000000003000000000000000000002109000000050000000403020100
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 accept - empty_manifest 6f9e7d11689ab113c4a1f05faf60fe60050505050505050505050505050505050000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000400000000010000000001000000000000

View File

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