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}