diff --git a/crates/epiphany-determinism/src/domain.rs b/crates/epiphany-determinism/src/domain.rs index 691fceb..6f3896d 100644 --- a/crates/epiphany-determinism/src/domain.rs +++ b/crates/epiphany-determinism/src/domain.rs @@ -61,9 +61,18 @@ impl DomainTag { /// Collisions"). Reserved built-in: anomalies are core, not an extension /// concern (Pass 11, item 1.4). pub const SYSTEM_ANOMALY: DomainTag = DomainTag(*b"MUSCSANM"); + /// `LayoutObjectId` derivation (Chapter 7 §"Provenance", + /// Requirement `req:layoutir:object-id-derivation`). Like + /// [`Self::FONT_METRICS`], this is a reserved built-in but **non-canonical**: + /// layout-object ids are not document state and enter no content hash, so the + /// tag lives in the layout namespace, not among the canonical system tags + /// (Chapter 8 §"Domain-tag registry"). + pub const LAYOUT_OBJECT_ID: DomainTag = DomainTag(*b"MUSCLOID"); - /// Every built-in tag, in declaration order. The closed core vocabulary. - pub const BUILTINS: [DomainTag; 10] = [ + /// Every built-in tag, in declaration order. The closed core vocabulary + /// (the nine canonical tags plus the non-canonical layout tags + /// [`Self::FONT_METRICS`] and [`Self::LAYOUT_OBJECT_ID`]). + pub const BUILTINS: [DomainTag; 11] = [ Self::CHUNK, Self::MANIFEST, Self::BLOB, @@ -74,6 +83,7 @@ impl DomainTag { Self::SYSTEM_VOICE, Self::SYSTEM_PITCH, Self::SYSTEM_ANOMALY, + Self::LAYOUT_OBJECT_ID, ]; /// Constructs a domain tag from raw bytes, accepting only the spec's closed @@ -214,19 +224,7 @@ mod tests { #[test] fn every_tag_is_eight_ascii_bytes_starting_with_musc() { - let tags = [ - DomainTag::CHUNK, - DomainTag::MANIFEST, - DomainTag::BLOB, - DomainTag::CONFLICT, - DomainTag::ENVELOPE, - DomainTag::FONT_METRICS, - DomainTag::MANIFEST_ID, - DomainTag::SYSTEM_VOICE, - DomainTag::SYSTEM_PITCH, - DomainTag::SYSTEM_ANOMALY, - ]; - for t in tags { + for t in DomainTag::BUILTINS { assert_eq!(t.as_bytes().len(), DomainTag::LEN); assert!(t.as_bytes().starts_with(b"MUSC"), "{t:?}"); assert!(t.as_bytes().iter().all(|b| b.is_ascii()), "{t:?}"); @@ -235,18 +233,7 @@ mod tests { #[test] fn tags_are_pairwise_distinct() { - let tags = [ - DomainTag::CHUNK, - DomainTag::MANIFEST, - DomainTag::BLOB, - DomainTag::CONFLICT, - DomainTag::ENVELOPE, - DomainTag::FONT_METRICS, - DomainTag::MANIFEST_ID, - DomainTag::SYSTEM_VOICE, - DomainTag::SYSTEM_PITCH, - DomainTag::SYSTEM_ANOMALY, - ]; + let tags = DomainTag::BUILTINS; for (i, a) in tags.iter().enumerate() { for b in &tags[i + 1..] { assert_ne!(a, b, "duplicate domain tag {a:?}"); @@ -267,6 +254,7 @@ mod tests { assert_eq!(DomainTag::SYSTEM_VOICE.as_bytes(), b"MUSCSVCE"); assert_eq!(DomainTag::SYSTEM_PITCH.as_bytes(), b"MUSCSPCH"); assert_eq!(DomainTag::SYSTEM_ANOMALY.as_bytes(), b"MUSCSANM"); + assert_eq!(DomainTag::LAYOUT_OBJECT_ID.as_bytes(), b"MUSCLOID"); assert_eq!(&BUNDLE_MAGIC, b"MUSCBND\0"); assert_eq!(&SUPERBLOCK_MAGIC, b"MUSCSUPR"); } @@ -304,6 +292,13 @@ mod tests { DomainTag::from_bytes(*b"MUSCCHNK").unwrap(), DomainTag::CHUNK ); + // The layout-object-id tag is a registered (non-system) built-in. + assert_eq!( + DomainTag::from_bytes(*b"MUSCLOID").unwrap(), + DomainTag::LAYOUT_OBJECT_ID + ); + assert!(DomainTag::LAYOUT_OBJECT_ID.is_builtin()); + assert!(!DomainTag::LAYOUT_OBJECT_ID.is_system_derived()); // Extension system tag: accepted. assert!(DomainTag::from_bytes(*b"MUSCSEXT") .unwrap() diff --git a/crates/epiphany-layout-ir/DECISIONS.md b/crates/epiphany-layout-ir/DECISIONS.md index 9696159..1dd2e01 100644 --- a/crates/epiphany-layout-ir/DECISIONS.md +++ b/crates/epiphany-layout-ir/DECISIONS.md @@ -7,17 +7,19 @@ rather than improvised in code (QUICKSTART, Process notes: *"Ambiguities go into a batch, not into code … Don't open Pass 11 until you have at least three such items batched."*). -> **RATIFIED (Pass 11, 2026-06-21).** layout P11-2 (`LayoutObjectId` derivation) -> is ratified into `core_spec.tex` §"Provenance" +> **RATIFIED (Pass 11, 2026-06-21); WIRED (Pass 12, P12-I2).** layout P11-2 +> (`LayoutObjectId` derivation) is ratified into `core_spec.tex` §"Provenance" > (`req:layoutir:object-id-derivation`): the spec **pins** a `MUSCLOID`-tagged > derivation keying multiply-manifested objects on `(source, region)` and > synthesized objects on `(source, synthesis_kind, stable_semantic_instance_key)`. -> Layout ids are non-canonical, so the `MUSCLOID` tag is **flagged for Track A -> and not yet wired in code**: this crate still mints provisional ids (untagged; -> synthesized borrows `MUSCCONF`) because the frozen determinism crate exposes no -> `MUSCLOID` tag (see the `stable_layout_id` bullet below). Adopting the spec'd -> derivation is Track A (solver/renderer) work. layout P11-1 (layout→ops -> dependency) stays a crate-topology call for the G–K re-cut. See +> This is now **wired**: `epiphany-determinism` exposes the reserved built-in +> `DomainTag::LAYOUT_OBJECT_ID` (`MUSCLOID`), and all three derivations in +> `provenance.rs` route through it (`stable_layout_id`, `manifestation_layout_id`, +> `synthesized_layout_id` — the last no longer borrows `MUSCCONF`). Layout ids stay +> non-canonical (not document state, in no content hash), so realizing the +> derivation changed layout-id *values* (and the `data-prov` hex in the SVG goldens) +> but no durable or interchanged artifact. layout P11-1 (layout→ops dependency) +> stays a crate-topology call for the G–K re-cut. See > `spec/PASS11_RATIFICATION_LOG.md`. ## Scope @@ -86,17 +88,16 @@ object is covered); the provenance-preservation contract itself is unchanged. Extensions". This avoids over-prohibiting edits to objects demonstrably outside a known scope or to objects a known condition excludes. -- **`stable_layout_id` and the engraving-decision id borrow a domain tag.** A - layout object's stable id is `trunc128(BLAKE3(source.canonical_bytes()))` — a - pure function of its source, so it is invariant under insertion/removal/ - reordering of other objects (Chapter 7 §"Provenance"). It is not domain- - separated, and the engraving-decision id borrows the `MUSCCONF` tag with a - literal `engraving-decision` type prefix, because the frozen determinism crate - (Agent A) defines no layout-object domain tag. Pass 11 ratified the **target** - derivation — a `MUSCLOID`-tagged hash (`req:layoutir:object-id-derivation`) — - but adopting it is Track A work (the determinism crate must first expose the - layout-namespace tag); these ids stay provisional until then. See the header - note. +- **`stable_layout_id` and the engraving-decision id are `MUSCLOID`-tagged + (P12-I2 wired).** A layout object's stable id is a pure function of its source + (and, for manifestations/synthesized objects, the region or the synthesis + kind+instance key), so it is invariant under insertion/removal/reordering of + other objects (Chapter 7 §"Provenance"). Both ids are now domain-separated under + the reserved built-in `DomainTag::LAYOUT_OBJECT_ID` (`MUSCLOID`), the spec's + non-canonical layout namespace (`req:layoutir:object-id-derivation`); the + engraving-decision id keeps its literal `engraving-decision` discriminator prefix + so it cannot alias a layout-object id within that namespace. Neither borrows + `MUSCCONF` any longer. See the header note for the realization details. - **Repeated manifestations get per-`(source, region)` ids.** A score-graph object manifested within a region is laid out **per manifestation**: its stable @@ -238,12 +239,14 @@ object is covered); the provenance-preservation contract itself is unchanged. chapter home are in tension; the spec should either bless a layout→ops dependency for the discriminator type or relocate the edit-barrier types. -2. **Provenance / layout-object id derivation is unspecified.** Chapter 7 - declares `LayoutObjectId(pub u128)` and requires stability across relayouts but - specifies neither the derivation, whether it is domain-separated (Appendix D - §"Domain-Separated Preimages" would suggest a dedicated `MUSC*` tag), how a - multiply-manifested object (a staff in two regions) is identified — v0 keys it - on `(source, region)` — nor how synthesized objects are keyed — v0 uses - `(source, synthesis_kind, stable_semantic_instance_key)`. The spec should pin - the derivation, manifestation-context key, and synthesized-object key, and - register a layout domain tag if separation is required. +2. **Provenance / layout-object id derivation. — RESOLVED (ratified Pass 11; + wired P12-I2).** Chapter 7 originally declared `LayoutObjectId(pub u128)` and + required stability across relayouts without specifying the derivation, its + domain separation, or how multiply-manifested / synthesized objects are keyed. + Pass 11 ratified the `MUSCLOID`-tagged derivation + (`req:layoutir:object-id-derivation`) — single objects keyed on + `source.canonical_bytes()`, multiply-manifested on `(source, region)`, + synthesized on `(source, synthesis_kind, stable_semantic_instance_key)` — and + P12-I2 wired it: `epiphany-determinism` reserves the built-in + `DomainTag::LAYOUT_OBJECT_ID` and `provenance.rs` (and the engraving-decision + id) route through it. See the ratified-block note at the top of this file. diff --git a/crates/epiphany-layout-ir/src/engraving.rs b/crates/epiphany-layout-ir/src/engraving.rs index 44a5087..b271544 100644 --- a/crates/epiphany-layout-ir/src/engraving.rs +++ b/crates/epiphany-layout-ir/src/engraving.rs @@ -179,17 +179,18 @@ impl EngravingDecision { /// Derives an [`EngravingDecisionId`] from its target, kind, and source, so /// equal decisions share an id and differing ones do not. /// -/// The preimage **borrows** the `MUSCCONF` domain tag (the determinism crate, -/// frozen, defines no layout-object domain) and prefixes a literal -/// `engraving-decision` discriminator so it cannot alias a real conflict id. -/// This is *type-tag*, not *domain*, separation; a dedicated layout domain tag -/// is a Pass 11 candidate (see `DECISIONS.md`). +/// An engraving decision is a non-canonical layout-namespace object, so the +/// preimage is domain-separated under the layout tag +/// [`DomainTag::LAYOUT_OBJECT_ID`] (`MUSCLOID`) — the same tag as +/// [`crate::provenance::LayoutObjectId`] — with a literal `engraving-decision` +/// discriminator prefix so a decision id can never alias a layout-object id +/// within that namespace. fn derive_decision_id( target: LayoutObjectId, kind: &EngravingDecisionKind, source: DecisionSource, ) -> EngravingDecisionId { - let mut p = Preimage::new(DomainTag::CONFLICT); + let mut p = Preimage::new(DomainTag::LAYOUT_OBJECT_ID); p.push_bytes(b"engraving-decision"); p.push_u64_le((target.0 >> 64) as u64); p.push_u64_le(target.0 as u64); diff --git a/crates/epiphany-layout-ir/src/provenance.rs b/crates/epiphany-layout-ir/src/provenance.rs index 002014e..927a3a8 100644 --- a/crates/epiphany-layout-ir/src/provenance.rs +++ b/crates/epiphany-layout-ir/src/provenance.rs @@ -8,7 +8,7 @@ //! complete provenance of every object survives the whole pipeline unchanged. use epiphany_core::{RegionId, TypedObjectId}; -use epiphany_determinism::{blake3_256, trunc128, DomainTag, Preimage}; +use epiphany_determinism::{DomainTag, Preimage}; /// An IR object's stable identifier across re-layouts where its source is /// unchanged (Chapter 7 §"Provenance"). Carried unchanged through every stage. @@ -135,40 +135,46 @@ impl Provenance { /// This is what makes the id stable across relayouts (Chapter 7 §"Provenance": /// stable across re-layouts where the source is unchanged): inserting, /// removing, or reordering other objects cannot change any object's stable id, -/// because each depends solely on its own source's canonical bytes. v0 derives -/// it as `trunc128(BLAKE3(source.canonical_bytes()))` — a provisional, untagged -/// stand-in. The spec (`req:layoutir:object-id-derivation`) pins a -/// `MUSCLOID`-tagged derivation as the Track A target; see `DECISIONS.md`. +/// because each depends solely on its own source's canonical bytes. This is the +/// ratified derivation of `req:layoutir:object-id-derivation`: domain-separated +/// BLAKE3 truncation under the [`DomainTag::LAYOUT_OBJECT_ID`] (`MUSCLOID`) tag, +/// keyed on `source.canonical_bytes()` for a single manifestation. pub fn stable_layout_id(source: &TypedObjectId) -> LayoutObjectId { - LayoutObjectId(trunc128(&blake3_256(&source.canonical_bytes()))) + LayoutObjectId( + Preimage::new(DomainTag::LAYOUT_OBJECT_ID) + .push_bytes(&source.canonical_bytes()) + .finish_trunc128(), + ) } /// Derives the stable layout id of an object manifested **within a region**, /// from its source *and* that region (Chapter 5 §"Region Overlap and /// Concurrency"). Distinct regions give the same source distinct manifestation /// ids, so multiple manifestations of one score-graph object are distinct layout -/// objects; the id remains independent of traversal position. +/// objects; the id remains independent of traversal position. The ratified +/// `MUSCLOID` derivation keyed on the pair `(source, region)` +/// (`req:layoutir:object-id-derivation`). pub fn manifestation_layout_id(source: &TypedObjectId, region: RegionId) -> LayoutObjectId { - let mut bytes = source.canonical_bytes(); - bytes.extend_from_slice(®ion.canonical_bytes()); - LayoutObjectId(trunc128(&blake3_256(&bytes))) + LayoutObjectId( + Preimage::new(DomainTag::LAYOUT_OBJECT_ID) + .push_bytes(&source.canonical_bytes()) + .push_bytes(®ion.canonical_bytes()) + .finish_trunc128(), + ) } /// Derives the stable layout id of an **engraver-synthesized** object from its -/// `source` and its [`SynthesisKind`], so distinct synthesis kinds from one -/// source do not collide (Chapter 7 §"Provenance"). Provisionally domain-tagged -/// via the borrowed `MUSCCONF` tag with a `synthesized` discriminator prefix; the -/// determinism crate defines no `MUSCLOID` layout tag yet, and the spec'd -/// `MUSCLOID` derivation (`req:layoutir:object-id-derivation`) is the Track A -/// target — see `DECISIONS.md`. +/// `source`, its [`SynthesisKind`], and a stable semantic instance key, so +/// distinct synthesis kinds — and distinct instances of one kind — from one +/// source do not collide (Chapter 7 §"Provenance"). The ratified `MUSCLOID` +/// derivation keyed on the triple `(source, synthesis_kind, instance_key)` +/// (`req:layoutir:object-id-derivation`); the instance key is a semantically +/// stable discriminator, never a layout-position ordinal. pub fn synthesized_layout_id( source: &TypedObjectId, kind: SynthesisKind, instance: SynthesisInstanceKey, ) -> LayoutObjectId { - let mut p = Preimage::new(DomainTag::CONFLICT); - p.push_bytes(b"synthesized-layout"); - p.push_bytes(&source.canonical_bytes()); let (disc, reg) = match kind { SynthesisKind::CancellationAccidental => (0u64, 0u128), SynthesisKind::KeySignatureNatural => (1, 0), @@ -178,6 +184,8 @@ pub fn synthesized_layout_id( SynthesisKind::Cautionary => (5, 0), SynthesisKind::Registered(id) => (6, id.0), }; + let mut p = Preimage::new(DomainTag::LAYOUT_OBJECT_ID); + p.push_bytes(&source.canonical_bytes()); p.push_u64_le(disc); p.push_u64_le((reg >> 64) as u64); p.push_u64_le(reg as u64); @@ -256,4 +264,38 @@ mod tests { // And distinct from the plain source-only id. assert_ne!(cancel.stable_id, stable_layout_id(&src)); } + + #[test] + fn stable_id_uses_the_ratified_muscloid_derivation() { + use epiphany_determinism::{blake3_256, trunc128}; + let src = TypedObjectId::Event(EventId::from_raw(0x1234)); + // Exactly the MUSCLOID-tagged derivation `req:layoutir:object-id-derivation` + // pins: domain-separated BLAKE3 over the source's canonical bytes. + let expected = LayoutObjectId( + Preimage::new(DomainTag::LAYOUT_OBJECT_ID) + .push_bytes(&src.canonical_bytes()) + .finish_trunc128(), + ); + assert_eq!(stable_layout_id(&src), expected); + // ...and it is genuinely domain-separated — not the old untagged stand-in, + // so a hash for some other domain over the same bytes cannot alias a layout id. + let untagged = LayoutObjectId(trunc128(&blake3_256(&src.canonical_bytes()))); + assert_ne!(stable_layout_id(&src), untagged); + } + + #[test] + fn the_three_keying_schemes_do_not_collide() { + use epiphany_core::StaffId; + // One source, the three manifestation schemes (single / multiply-manifested / + // synthesized) → three distinct ids, so a single object's id can never alias a + // manifestation or a synthesized object derived from the same source. + let src = TypedObjectId::Staff(StaffId::from_raw(7)); + let single = stable_layout_id(&src); + let manifested = manifestation_layout_id(&src, RegionId::from_raw(3)); + let synthesized = + synthesized_layout_id(&src, SynthesisKind::GeneratedRest, SynthesisInstanceKey(0)); + assert_ne!(single, manifested); + assert_ne!(single, synthesized); + assert_ne!(manifested, synthesized); + } } diff --git a/crates/epiphany-render-svg/tests/golden/ten_measure_single_staff.engrave.svg b/crates/epiphany-render-svg/tests/golden/ten_measure_single_staff.engrave.svg index 2c3ac94..a7a82ac 100644 --- a/crates/epiphany-render-svg/tests/golden/ten_measure_single_staff.engrave.svg +++ b/crates/epiphany-render-svg/tests/golden/ten_measure_single_staff.engrave.svg @@ -3,108 +3,108 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/epiphany-render-svg/tests/golden/ten_measure_single_staff.stub.svg b/crates/epiphany-render-svg/tests/golden/ten_measure_single_staff.stub.svg index 5633961..5713f7d 100644 --- a/crates/epiphany-render-svg/tests/golden/ten_measure_single_staff.stub.svg +++ b/crates/epiphany-render-svg/tests/golden/ten_measure_single_staff.stub.svg @@ -3,108 +3,108 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/epiphany-render-svg/tests/golden/valid_score_rich.engrave.svg b/crates/epiphany-render-svg/tests/golden/valid_score_rich.engrave.svg index aa726ff..ba6e080 100644 --- a/crates/epiphany-render-svg/tests/golden/valid_score_rich.engrave.svg +++ b/crates/epiphany-render-svg/tests/golden/valid_score_rich.engrave.svg @@ -3,50 +3,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/epiphany-render-svg/tests/golden/valid_score_rich.stub.svg b/crates/epiphany-render-svg/tests/golden/valid_score_rich.stub.svg index 35e8d5d..a69b0f7 100644 --- a/crates/epiphany-render-svg/tests/golden/valid_score_rich.stub.svg +++ b/crates/epiphany-render-svg/tests/golden/valid_score_rich.stub.svg @@ -3,50 +3,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/PASS11_RATIFICATION_LOG.md b/spec/PASS11_RATIFICATION_LOG.md index 68d261e..e95ff7a 100644 --- a/spec/PASS11_RATIFICATION_LOG.md +++ b/spec/PASS11_RATIFICATION_LOG.md @@ -31,7 +31,7 @@ unchanged. The full worklist is `PASS11_WORKLIST.md`. | 2.3 `>2`-way promotion | P11-C4 | **adopt + lifted to normative** — order-independent pre-pass: bucket by voice, walk by OperationId, retain a non-overlapping set, promote each overlapping loser (lowest-id retained survivor wins); applies to **partial** interval overlaps, not just identical onsets | §"System-Promoted Voices", `req:graph:promotion-generalization` | `reduce.rs::compute_promotions` | | 2.4 Open-vocab enums | P11-C9 | **decided: pinned core sets, kept `Registered`** — `TransactionCategory ∈ {NoteEntry, Structural, Layout, Import, Registered}`; `ObjectKind ∈ {Voice, Pitch, Registered}` (narrower than the 28 object kinds: only kinds minted into the system namespace). **Discriminant bytes now pinned in spec text:** `TransactionCategory` 0–4 (was already pinned); `ObjectKind` Voice=0/Pitch=1/Registered=2 added to `req:graph:object-kind-vocab` because the byte feeds the `IntegrityAnomalyId` preimage and was golden-locked in code but absent from spec text | `req:semops:transaction-category`, `req:graph:object-kind-vocab` | `payload.rs`, `support.rs::object_kind_discriminants_are_golden` | | 2.5 `ResolveConflict` Dismissed | P11-C10 | **decided: added `ResolutionAction::Dismiss`** (code + spec) — closes the half-unreachable state machine; the Dismiss action selects the `Dismissed` state, every other action selects `Resolved`. **Discriminant bytes now pinned in spec text** (`AcceptLoser`=0 … `Dismiss`=4, `Registered`=5) in new `req:semops:resolution-action-discriminants`: the action is encoded into the operation content hash and was golden-locked in code but absent from spec text | §"Conflict Resolution Operations", `req:semops:resolution-action-discriminants` | `conflict.rs::resolution_action_discriminants_are_golden`, `reduce.rs::resolve_conflict`, `resolve_conflict_with_dismiss_reaches_dismissed_state` | -| 2.6 Layout-object id | layout P11-2 | **decided: spec pins `MUSCLOID` tag; code adoption is Track A** — the spec specifies a `MUSCLOID`-tagged derivation keying multiply-manifested objects on `(source, region)`, synthesized objects on `(source, synthesis_kind, stable_semantic_instance_key)`. Non-canonical (not document state). The v0 `layout-ir` crate still mints **provisional** ids (untagged; synthesized borrows `MUSCCONF`) because the frozen determinism crate exposes no `MUSCLOID` tag — realizing the spec'd derivation is Track A work, not done in this pass | §"Provenance", `req:layoutir:object-id-derivation` | `layout-ir` provenance (provisional) | +| 2.6 Layout-object id | layout P11-2 | **decided: spec pins `MUSCLOID` tag; code adoption is Track A** — the spec specifies a `MUSCLOID`-tagged derivation keying multiply-manifested objects on `(source, region)`, synthesized objects on `(source, synthesis_kind, stable_semantic_instance_key)`. Non-canonical (not document state). The v0 `layout-ir` crate still mints **provisional** ids (untagged; synthesized borrows `MUSCCONF`) because the frozen determinism crate exposes no `MUSCLOID` tag — realizing the spec'd derivation is Track A work, not done in this pass. **(Superseded by P12-I2: the derivation is now wired — `epiphany-determinism` reserves the built-in `MUSCLOID` tag and `layout-ir` provenance routes through it.)** | §"Provenance", `req:layoutir:object-id-derivation` | `layout-ir` provenance (wired, P12-I2) | ## Bucket 3 — Fixes (spec was contradictory or silent) diff --git a/spec/PASS12_BATCH.md b/spec/PASS12_BATCH.md index 1d8543c..69d9a61 100644 --- a/spec/PASS12_BATCH.md +++ b/spec/PASS12_BATCH.md @@ -27,7 +27,7 @@ code instead is the failure mode this batch exists to prevent. | P12-H4 | `epiphany-core` H | Decomposition simplifications: single governing meter per region (multi/mid-region meter changes deferred); region origin assumed a barline (anacrusis deferred); compound-meter beat grouping beyond the dyadic default; tuplet nesting and cross-beat tuplet members; double+ augmentation dots (`MAX_DOTS = 1`). | G (decomposition scope) | | P12-H5 | `epiphany-core` H | Automatic spelling under aleatoric regions (the spec's open question). H spells pitches region-independently but performs no region-specific aleatoric spelling; defer if the algorithm does not generalise cleanly. | G / Pass 12 (open question) | | P12-I1 | `epiphany-layout-ir` / `engrave` / `render-svg` I | The v0 `to_logical`/`to_constrained` pipeline is a **structural placeholder**: each layout object becomes one *arbitrary* glyph (`BRAVURA_METRICS[discriminant % N]`) at `y = 0`, not real notation. Chapter 7 says the *logical* stage has "engraving decisions made"; the spec should clarify which engraving decisions (glyph-by-duration selection, pitch→staff-position, clef/key/meter/barline realization, stems/beams) are core-IR construction versus solver work, so the real-notation engraving has a defined home before it is built next phase. Consequence: the QUICKSTART human visual-acceptance gate ("the SVG visually parses as standard notation") is a **next-phase** gate, *not* met by stub output — this phase's gate is renderer correctness/faithfulness. | G / Pass 12 (Ch 7 engraving boundary) | -| P12-I2 | `epiphany-render-svg` / `engrave` I | Stable layout-object id derivation (`MUSCLOID`, Pass-11 item 2.6, deferred to I) is still unwired: the frozen `epiphany-determinism` exposes no `MUSCLOID` tag, so provenance is traced via the provisional `stable_id`. Wiring the ratified derivation is Track A work (already noted in `layout-ir/DECISIONS.md`). | G (determinism tag) / Track A | +| ~~P12-I2~~ **RESOLVED (wired)** | `epiphany-determinism` / `epiphany-layout-ir` I | Stable layout-object id derivation (`MUSCLOID`, Pass-11 item 2.6). **Wired:** `epiphany-determinism` now reserves the built-in `DomainTag::LAYOUT_OBJECT_ID` (`MUSCLOID`), and `layout-ir`'s provenance derivations (single / multiply-manifested / synthesized) plus the engraving-decision id route through it (no longer borrowing `MUSCCONF`). Layout ids stay non-canonical, so only `data-prov` hex in the render goldens changed; no durable/interchanged artifact. See `layout-ir/DECISIONS.md` and `req:layoutir:object-id-derivation`. | ✅ done | | P12-I3 | `epiphany-layout-ir` I | The bundled `BRAVURA_METRICS` are *approximations* that disagree with the genuine Bravura outlines the renderer now extracts from the font (e.g. `timeSig4`: metrics bbox `[40,0,1240,2048]` vs real outline ≈ `[0.08,-1.0,1.8,1.004]` staff spaces). Real spacing needs exact metrics; regenerate the metrics table from the font or reconcile it with the outline source. | G / Pass 12 (glyph metrics) | | P12-K1 | `epiphany-ops` K | A v0 `RespellPitch` carried a `ContentHash` *fingerprint* of the spelling, not the `PitchSpelling`. The v0→v1 migration (Operation Catalog, M1) cannot invert a fingerprint, so it recovers the spelling from the score-graph context (an explicit per-pitch spelling attachment whose canonical bytes hash to the fingerprint) and returns `MigrationError::Irreversible` (bundle opens read-only) when the context lacks it. Every other representative payload migrates self-contained; this is the lone exception. Confirm the read-only fallback is the intended disposition vs. requiring a v0 corpus that preserves spelling pre-images. | G / Pass 12 (migration) | | P12-K2 | `epiphany-ops` K | The `Transpose` op (Operation Catalog, M2 Group 1) carries a minimal `chromatic_steps: i32` interval and `reduce_onto` applies it as a CMN *alteration* shift only. Faithful interval algebra (diatonic vs. chromatic intervals, octave/nominal renormalization, transposition in non-CMN pitch spaces) is the deferred Chapter 4 tuning-catalog territory. The prototype also clamps the shifted alteration to the `i8` range, so an extreme transpose silently saturates instead of renormalizing — another reason the representation needs pinning. Pin the interval representation and transposition semantics when the tuning catalog lands. | G / Pass 12 (tuning) | diff --git a/spec/core_spec.tex b/spec/core_spec.tex index 39d4c4e..90ddfdd 100644 --- a/spec/core_spec.tex +++ b/spec/core_spec.tex @@ -7961,11 +7961,13 @@ pub struct LayoutObjectId(pub u128); back-reference stability; its consumers are the solver and renderer (Track~A), not the interchange track. Because these ids never enter document state, no stored or interchanged artifact depends on this - derivation; the v0 reference crate accordingly mints layout ids with - a provisional, untagged stand-in (synthesized objects borrowing the - \texttt{"MUSCCONF"} tag), and wiring the \texttt{"MUSCLOID"} - derivation above is Track~A work rather than a property the prototype - yet realizes. + derivation. The v0 reference implementation realizes this derivation + as of Pass-12 item P12-I2: \texttt{epiphany-determinism} reserves the + built-in \texttt{"MUSCLOID"} layout tag and the \texttt{layout-ir} + provenance derivations (single, multiply-manifested, and synthesized) + route through it, the synthesized case no longer borrowing + \texttt{"MUSCCONF"}. Because the ids are non-canonical, doing so + changed layout-id values but no stored or interchanged artifact. \end{requirement} \section{The Stage Pipeline} @@ -13585,8 +13587,8 @@ and not collide with the reserved three. \section{Domain-tag registry} \label{sec:bytes:tags} -Every reserved built-in eight-byte domain tag, in one place, plus the -\texttt{MUSCLOID} Track-A target. The nine \emph{canonical} tags --- +Every reserved built-in eight-byte domain tag, in one place. The nine +\emph{canonical} tags --- whose preimages produce identifiers and content hashes that are part of the interoperable, durably persisted form every conforming implementation must reproduce bit-identically --- are @@ -13623,7 +13625,7 @@ tag (Section~\ref{sec:bytes:system-derived}). \texttt{MUSCFNTM} & Font-metrics hash for layout conformance --- \emph{non-canonical} (\S\ref{sec:layoutir:catalog-identity}) \\ \texttt{MUSCLOID} & Layout-object id --- \emph{non-canonical}, - Track-A target, not minted by the prototype + wired by the reference implementation as of P12-I2 (Requirement~\ref{req:layoutir:object-id-derivation}) \\ \bottomrule \end{longtable} @@ -13688,6 +13690,19 @@ the spec-to-code correspondence checkable. \bottomrule \end{longtable} +The \texttt{MUSCLOID} layout-object-id derivation +(Requirement~\ref{req:layoutir:object-id-derivation}), wired in P12-I2, +is anchored differently because it is \emph{non-canonical}: its ids +never enter document state or a content hash, so there is no durable +byte layout to golden-lock. It is instead pinned by a derivation +reference-lock test (\texttt{epiphany-layout-ir/src/provenance.rs}: +\texttt{stable\bul id\bul uses\bul the\bul ratified\bul muscloid\bul derivation}), +which fixes the tagged preimage; by the tag-spelling lock +(\texttt{epiphany-determinism/src/domain.rs}: +\texttt{exact\bul tag\bul spellings\bul match\bul spec}); and by the +renderer's provenance goldens, whose \texttt{data-prov} hex changes if +the derivation drifts. + \section{Layouts deferred to the companions} \label{sec:bytes:deferred} @@ -13709,11 +13724,6 @@ layouts they own versus inherit: convention baseline (Requirement~\ref{req:format:codec-conventions}). The companion inherits the baseline and formalizes these. - \item \texttt{MUSCLOID} layout-object ids - (Requirement~\ref{req:layoutir:object-id-derivation}) are - non-canonical (they are not document state) and the prototype still - mints provisional, untagged ids; realizing the \texttt{MUSCLOID} - derivation is Track-A work. \end{itemize} \chapter{Revision History} @@ -14370,9 +14380,9 @@ layouts they own versus inherit: vocabularies; added \texttt{ResolutionAction::Dismiss} so the \texttt{Dismissed} resolution state is reachable by an authored operation; and specified the (non-canonical) \texttt{LayoutObjectId} - derivation with a \texttt{MUSCLOID} tag as the Track~A target (the v0 - prototype still mints provisional layout ids; wiring \texttt{MUSCLOID} - is Track~A work). \emph{Fixes:} blob hashing + derivation with a \texttt{MUSCLOID} tag as the Track~A target (then + deferred to Track~A; subsequently wired by the reference + implementation in P12-I2). \emph{Fixes:} blob hashing is the bare \texttt{"MUSCBLOB" || payload} (deleted the contradictory ``identically to chunks'' phrasing); added the equal-generation superblock rule (\texttt{DivergentSameGeneration}