Schema major 1 Phase F: ratify engrave I8/I9/I10 + process trail

Spec/process only, no code. Closes the schema-major-1 track (Phases A-F): the
two data-model batch rows parked behind the frozen-layout rule (P12-I7, P12-K7)
landed via Phases C/D1, and the three engrave/layout-ir dispositions the
implementation already made are ratified into the core spec.

- core_spec: three normative requirements ratifying the implemented behavior --
  break-constraint satisfaction (req:layoutir:break-satisfaction, I8: a
  SystemBreakAt/PageBreakAt is satisfied iff the final ResolvedLayoutIR starts a
  system/page at that slot), break-override attribution via a
  ConstrainedLayoutIR.break_origins sidecar declining to widen the constraint
  record (req:layoutir:break-origin-attribution, I9), and system-continuation
  synthesis Registered(SYSTEM_CONTINUATION_SYNTHESIS) with an (original, ordinal)
  instance key (req:layoutir:continuation-synthesis, I10). Revision-history row.
  PDF rebuilt (latexmk -xelatex, 0 undefined refs).
- PASS12_RATIFICATION_LOG: schema-major-1 tranche (I7/K7 landed + I8/I9/I10
  adopt), with the open cross-region-slur item flagged.
- PASS12_BATCH: struck I7/K7/I8/I9/I10; added P12-K12 (which region governs a
  cross-region slur's spanning permission -- implemented as conservative AND).
- engrave/layout-ir DECISIONS: ratification cross-refs; the P12-I7 note reworded
  so deferred Phase C' does not read as landed.
- .gitignore: spec/*.xdv (xelatex intermediate; the tracked PDF is committed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
This commit is contained in:
Levi Neuwirth 2026-07-06 17:40:38 -04:00
parent 64de92605f
commit 7045a13a18
7 changed files with 134 additions and 8 deletions

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ spec/*.fls
spec/*.log spec/*.log
spec/*.out spec/*.out
spec/*.toc spec/*.toc
spec/*.xdv

View File

@ -116,9 +116,11 @@ inputs the solver cannot measure.
schema major. **Schema major 1 now defines the type** (`CanvasLayoutDefaults schema major. **Schema major 1 now defines the type** (`CanvasLayoutDefaults
{ page_size: CanvasSize, margins: CanvasMargins }`, staff spaces, A4/8mm { page_size: CanvasSize, margins: CanvasMargins }`, staff spaces, A4/8mm
default) and ratifies its wire form (core spec + Binary Format 0.3.0, default) and ratifies its wire form (core spec + Binary Format 0.3.0,
Phase A); the **code graph home lands in Phase C** (`Canvas` gains the Phase A); the **code graph home landed in Phase C** (`Canvas` gained the
field) and the engraver reads it in Phase C. Until then the engraver takes field). Wiring the engraver to *read* it (Phase C) is a byte-neutral
the geometry as a constructor parameter. Default arithmetic (1 staff space = staff height / 4 = 2.0 mm at follow-up **deferred** until a custom-geometry producer exists, so the
engraver still takes the geometry as a constructor parameter (every score's
`layout_defaults` is the A4 default today). Default arithmetic (1 staff space = staff height / 4 = 2.0 mm at
an 8 mm staff): A4 210 × 297 mm → **105 × 148.5** staff spaces; 15 mm an 8 mm staff): A4 210 × 297 mm → **105 × 148.5** staff spaces; 15 mm
margins → **7.5** staff spaces; content area 180 × 267 mm → **90 × 133.5** margins → **7.5** staff spaces; content area 180 × 267 mm → **90 × 133.5**
staff spaces. 90 staff spaces wraps the ten-measure hand-off fixture staff spaces. 90 staff spaces wraps the ten-measure hand-off fixture
@ -390,3 +392,14 @@ testkit's reference-suite harness.
floor). The alternative — a duration-proportional (optical) redefinition — floor). The alternative — a duration-proportional (optical) redefinition —
needs the pipeline's deferred duration-aware preferred widths and stays the needs the pipeline's deferred duration-aware preferred widths and stays the
catalog's open question. catalog's open question.
## Break-constraint satisfaction predicate (Pass 12 P12-I8, ratified)
The break-constraint satisfaction predicate — a `SystemBreakAt`/`PageBreakAt` at
`slot` is satisfied iff the final `ResolvedLayoutIR` starts a system/page at that
slot (a region-first slot trivially) — was ratified into the core spec by the
schema-major-1 track's Phase F (2026-07-06; core spec
`req:layoutir:break-satisfaction`; `spec/PASS12_RATIFICATION_LOG.md`,
schema-major-1 tranche). Satisfaction is a predicate on the output layout, not
the solver's spring state; casting-off evaluates the declared hard break
constraints as part of its tier claim.

View File

@ -445,3 +445,23 @@ all-worst `unmeasured()` vector, which a transcription test pins as excluded
by the Minimal column ("measuring is part of the Minimal claim"). The catalog by the Minimal column ("measuring is part of the Minimal claim"). The catalog
also blesses the existing `TieBreakingWeights::default()` (all 1.0) as the also blesses the existing `TieBreakingWeights::default()` (all 1.0) as the
normative defaults — pinned by test rather than re-declared. normative defaults — pinned by test rather than re-declared.
## Break-origin attribution and system-continuation synthesis (Pass 12 P12-I9/I10, ratified)
Two long-standing layout-ir dispositions were ratified into the core spec by the
schema-major-1 track's Phase F (2026-07-06; `spec/PASS12_RATIFICATION_LOG.md`,
schema-major-1 tranche):
- **P12-I9 — break-override attribution via a sidecar.** Honouring a user break
carries `DecisionSource::UserOverride(id)`, but a normalized break *constraint*
(`SystemBreakAt`/`PageBreakAt`) carries no override identity. Attribution is
threaded through a `ConstrainedLayoutIR.break_origins` sidecar populated by
`to_constrained`; the normalized constraint record is deliberately **not**
widened (attribution is a projection concern, not a solver input). Ratified as
core spec `req:layoutir:break-origin-attribution`.
- **P12-I10 — system-continuation synthesis.** A stroke spanning a system
boundary is split; the post-first segments are synthesized under
`SynthesisKind::Registered(SYSTEM_CONTINUATION_SYNTHESIS)` with a
`(original, ordinal)` `stable_semantic_instance_key`. Since `LayoutObjectId`s
are non-canonical and re-derived per layout, the key need only be stable within
a layout. Ratified as core spec `req:layoutir:continuation-synthesis`.

View File

@ -48,7 +48,7 @@ code instead is the failure mode this batch exists to prevent.
| P12-C4 | `epiphany-ops` C | `ReanchorReason` has no same-canvas variant: a rank-4 (same-canvas) survivor is recorded `ExplicitFallback` rather than appending a discriminant. Ratify a beyond-region reason or bless the fallback recording. | G / Pass 12 (re-anchoring) | | P12-C4 | `epiphany-ops` C | `ReanchorReason` has no same-canvas variant: a rank-4 (same-canvas) survivor is recorded `ExplicitFallback` rather than appending a discriminant. Ratify a beyond-region reason or bless the fallback recording. | G / Pass 12 (re-anchoring) |
| P12-K5 | `epiphany-ops` K | Equivocation resolution's third path — a profile-declared deterministic selection function (e.g. lowest-hash-wins) — is unpinned and unimplemented; the reducer has no policy hook. Referenced from the catalog §ResolveEquivocation rationale. | G / Pass 12 (equivocation) | | P12-K5 | `epiphany-ops` K | Equivocation resolution's third path — a profile-declared deterministic selection function (e.g. lowest-hash-wins) — is unpinned and unimplemented; the reducer has no policy hook. Referenced from the catalog §ResolveEquivocation rationale. | G / Pass 12 (equivocation) |
| P12-K6 | `epiphany-ops` K | `ResolveEquivocation` edge semantics to pin: cascaded resolves (a promoted candidate that is itself a resolve does not govern a further promotion — single-pass implemented, vs. fixpoint); interaction with HLC-monotonicity quarantine (may a quarantined resolve govern?); a resolve held pending by its own causal gaps still governs promotion (set-level rule) while its effect stays pending; whether the invalid-target/chosen no-op warrants a dedicated `PreconditionFailureReason` (implemented: reuses `TargetMissing`). | G / Pass 12 (equivocation) | | P12-K6 | `epiphany-ops` K | `ResolveEquivocation` edge semantics to pin: cascaded resolves (a promoted candidate that is itself a resolve does not govern a further promotion — single-pass implemented, vs. fixpoint); interaction with HLC-monotonicity quarantine (may a quarantined resolve govern?); a resolve held pending by its own causal gaps still governs promotion (set-level rule) while its effect stays pending; whether the invalid-target/chosen no-op warrants a dedicated `PreconditionFailureReason` (implemented: reuses `TargetMissing`). | G / Pass 12 (equivocation) |
| P12-K7 | `epiphany-ops` K | Advisory-precondition catalog: the spec declares the duration-boundary advisory only for InsertEvent while the implementation also applies it to ModifyEvent's replacement value — state ModifyEvent's bucket explicitly. Also: which advisory checks are blocked on graph-model completion (Instrument declared range; a Region slur-spanning permission flag; which extent shapes constitute a resolvable musical end bound). | G / Pass 12 (validation) | | ~~P12-K7~~ **RATIFIED (schema-major-1 track, 2026-07-06; see PASS12_RATIFICATION_LOG)** | `epiphany-ops` K | Advisory-precondition catalog: the spec declares the duration-boundary advisory only for InsertEvent while the implementation also applies it to ModifyEvent's replacement value — state ModifyEvent's bucket explicitly. Also: which advisory checks are blocked on graph-model completion (Instrument declared range; a Region slur-spanning permission flag; which extent shapes constitute a resolvable musical end bound). | G / Pass 12 (validation) |
| P12-I4 | `epiphany-layout-ir` I | Constraint-strength attachment: Ch9 defines `ConstraintStrength` and says the solver consumes constraints "in normalized form", but neither the normalized form nor Ch7's `LayoutConstraint` provides a channel for an instance to carry strength. Implemented rule: break strength = `BreakKind` (Hard→Required, Soft→Preferred{1.0}); other core families Required; `Registered` conservative Required. Bless the rule or add a strength field. | G / Pass 12 (solver) | | P12-I4 | `epiphany-layout-ir` I | Constraint-strength attachment: Ch9 defines `ConstraintStrength` and says the solver consumes constraints "in normalized form", but neither the normalized form nor Ch7's `LayoutConstraint` provides a channel for an instance to carry strength. Implemented rule: break strength = `BreakKind` (Hard→Required, Soft→Preferred{1.0}); other core families Required; `Registered` conservative Required. Bless the rule or add a strength field. | G / Pass 12 (solver) |
| P12-I5 | `epiphany-layout-ir` I | No renderable status exists for "constraints present but not evaluated": every renderable `SolveStatus` is documented as "all hard constraints satisfied", leaving a below-conformance passthrough solver no honest report. Implemented encoding: `SolvedWithWarnings` + `satisfied_hard_constraints == false` + a warning. Sanction it or define a non-evaluating-tier report shape. | G / Pass 12 (solver) | | P12-I5 | `epiphany-layout-ir` I | No renderable status exists for "constraints present but not evaluated": every renderable `SolveStatus` is documented as "all hard constraints satisfied", leaving a below-conformance passthrough solver no honest report. Implemented encoding: `SolvedWithWarnings` + `satisfied_hard_constraints == false` + a warning. Sanction it or define a non-evaluating-tier report shape. | G / Pass 12 (solver) |
| P12-I6 | `epiphany-layout-ir` I | The spacing pass MUST "build collision constraints" but no per-tier minimum emission set is named. Implemented floor: successive-notehead-column no-collision chains + per-glyph region containment + user-break constraints. A normative Minimal-tier floor would make the acceptance surface testable. | G / Pass 12 (solver) | | P12-I6 | `epiphany-layout-ir` I | The spacing pass MUST "build collision constraints" but no per-tier minimum emission set is named. Implemented floor: successive-notehead-column no-collision chains + per-glyph region containment + user-break constraints. A normative Minimal-tier floor would make the acceptance surface testable. | G / Pass 12 (solver) |
@ -62,10 +62,11 @@ code instead is the failure mode this batch exists to prevent.
| P12-K9 | `epiphany-ops` K | Differing-value re-creates (a live id re-carried with different content: `CreateStaff`, the carried `TimeSignature`, container creates) refuse with `TargetMissing`, which misnames the situation. Decide whether a dedicated `PreconditionFailureReason` (appended) is warranted. | G / Pass 12 (vocabulary) | | P12-K9 | `epiphany-ops` K | Differing-value re-creates (a live id re-carried with different content: `CreateStaff`, the carried `TimeSignature`, container creates) refuse with `TargetMissing`, which misnames the situation. Decide whether a dedicated `PreconditionFailureReason` (appended) is warranted. | G / Pass 12 (vocabulary) |
| P12-K10 | `epiphany-ops` K | Undo strand-blocks (StrictInverse refusing to tombstone a minted object still referenced by a live non-member, e.g. a staff with a surviving instance) reuse `ConflictKind::TransactionConflict`. Decide whether undo refusals deserve their own conflict kind. | G / Pass 12 (undo) | | P12-K10 | `epiphany-ops` K | Undo strand-blocks (StrictInverse refusing to tombstone a minted object still referenced by a live non-member, e.g. a staff with a surviving instance) reuse `ConflictKind::TransactionConflict`. Decide whether undo refusals deserve their own conflict kind. | G / Pass 12 (undo) |
| P12-K11 | `epiphany-ops` K | An undo's value restorations enter the write chains as ordinary writes by the undo op, so a second undo of the same transaction sees the first as a superseding writer (Conflicted/skip) while absence-restorations repeat idempotently — a documented asymmetry. Decide whether chain writes need distinguished undo provenance so repeated undo is uniformly idempotent. | G / Pass 12 (undo) | | P12-K11 | `epiphany-ops` K | An undo's value restorations enter the write chains as ordinary writes by the undo op, so a second undo of the same transaction sees the first as a superseding writer (Conflicted/skip) while absence-restorations repeat idempotently — a documented asymmetry. Decide whether chain writes need distinguished undo provenance so repeated undo is uniformly idempotent. | G / Pass 12 (undo) |
| P12-I7 **(IN PROGRESS — schema-major-1 track)** | `epiphany-engrave` I | Page geometry had no graph home: the spec named `Canvas.layout_defaults` ("paper size, margins") but defined no type, and adding a `Canvas` field is a schema-major change under the companion's frozen-layout rule. Casting-off therefore uses an engraver-side `PageGeometry` default (A4 at an 8mm staff: 105x148.5 ss, 7.5 ss margins). **Phase A of the schema-major-1 bump defines the type** (`CanvasLayoutDefaults { page_size: CanvasSize, margins: CanvasMargins }` in staff spaces, A4/8mm default) and ratifies its v1 wire form (Binary Format 0.3.0); the **graph home lands in code in Phase C** (`Canvas` gains the field), after which the engraver reads it (Phase C) — until fully landed, solvers MAY default. | G (graph model, schema-major) | | P12-K12 | `epiphany-ops` K | Cross-region slur spanning permission: `Region.permits_spanning_slurs` (schema major 1, P12-K7) gates the CreateCrossCutting(Slur) advisory, but which region governs a slur whose endpoints lie in *different* regions is under-specified. Implemented as conservative **AND** (a boundary is permeable only when both endpoint regions permit); "the start region governs" or "either side" are equally defensible. The advisory is authoring-only and never alters reduction, so the choice is not byte-affecting. Ratify one reading. | G / Pass 12 (validation) |
| P12-I8 | `epiphany-engrave` I | Break-constraint satisfaction predicate: implemented as "a `SystemBreakAt`/`PageBreakAt` is satisfied iff the final layout starts a system/page at that slot" (a region-first slot is trivially satisfied). Ch7/Ch9 never define satisfaction for break constraints; ratify the predicate. | G / Pass 12 (solver) | | ~~P12-I7~~ **RATIFIED (schema-major-1 track, 2026-07-06; see PASS12_RATIFICATION_LOG)** | `epiphany-engrave` I | Page geometry had no graph home: the spec named `Canvas.layout_defaults` ("paper size, margins") but defined no type, and adding a `Canvas` field is a schema-major change under the companion's frozen-layout rule. Casting-off therefore uses an engraver-side `PageGeometry` default (A4 at an 8mm staff: 105x148.5 ss, 7.5 ss margins). **Phase A of the schema-major-1 bump defines the type** (`CanvasLayoutDefaults { page_size: CanvasSize, margins: CanvasMargins }` in staff spaces, A4/8mm default) and ratifies its v1 wire form (Binary Format 0.3.0); the **graph home lands in code in Phase C** (`Canvas` gains the field), after which the engraver reads it (Phase C) — until fully landed, solvers MAY default. | G (graph model, schema-major) |
| P12-I9 | `epiphany-layout-ir` I | Honouring a user break must attribute the decision to its override (`DecisionSource::UserOverride(id)`), but constraints carry no override identity; implemented via a `ConstrainedLayoutIR.break_origins` sidecar populated by `to_constrained`. Bless the sidecar or widen the normalized constraint record. | G / Pass 12 (solver) | | ~~P12-I8~~ **RATIFIED (schema-major-1 track, 2026-07-06; core_spec `req:layoutir:break-satisfaction`)** | `epiphany-engrave` I | Break-constraint satisfaction predicate: implemented as "a `SystemBreakAt`/`PageBreakAt` is satisfied iff the final layout starts a system/page at that slot" (a region-first slot is trivially satisfied). Ch7/Ch9 never define satisfaction for break constraints; ratify the predicate. | G / Pass 12 (solver) |
| P12-I10 | `epiphany-layout-ir` I | System-spanning strokes split at system boundaries need synthesized provenance for continuation segments; implemented as `SynthesisKind::Registered(SYSTEM_CONTINUATION_SYNTHESIS)` with a deterministic `(original, ordinal)` instance key. Add a first-class continuation synthesis kind or bless the registered id. | G / Pass 12 (provenance) | | ~~P12-I9~~ **RATIFIED (schema-major-1 track, 2026-07-06; core_spec `req:layoutir:break-origin-attribution`)** | `epiphany-layout-ir` I | Honouring a user break must attribute the decision to its override (`DecisionSource::UserOverride(id)`), but constraints carry no override identity; implemented via a `ConstrainedLayoutIR.break_origins` sidecar populated by `to_constrained`. Bless the sidecar or widen the normalized constraint record. | G / Pass 12 (solver) |
| ~~P12-I10~~ **RATIFIED (schema-major-1 track, 2026-07-06; core_spec `req:layoutir:continuation-synthesis`)** | `epiphany-layout-ir` I | System-spanning strokes split at system boundaries need synthesized provenance for continuation segments; implemented as `SynthesisKind::Registered(SYSTEM_CONTINUATION_SYNTHESIS)` with a deterministic `(original, ordinal)` instance key. Add a first-class continuation synthesis kind or bless the registered id. | G / Pass 12 (provenance) |
| ~~P12-I11~~ **RESOLVED (engrave v3 widow-rebalance)** | `epiphany-engrave` I | RS-1 honestly failed the Minimal casting-off threshold under the reference engraver (measured 1.0 vs 0.90): greedy first-fit left a two-measure stub last system (width CV 0.6145 ≥ the 0.5 anchor). **Resolved the honest way — an engrave-side casting-off balance pass, no catalog change:** casting-off gained a second **widow-rebalance** phase that moves whole trailing measures from a region's penultimate system into its final one, choosing the shift that minimizes the larger of the two distribution penalties the catalog defines for the break family (width imbalance vs non-final underfill; both share the 0.5 anchor). RS-1 now casts six/four instead of eight/two: casting_off 1.0 → **0.4463**, system_break 0.254 → 0.677, every axis ≤ 0.90. `ENGRAVER_VERSION` 2 → 3; render goldens regenerated; the suite's Xfail row promoted to a plain Pass. The `casting_off` 0.5 anchor and the Minimal column stood — the engraver improved, not the threshold — so the Quality Metric Catalog is normatively unchanged. | ✅ done | | ~~P12-I11~~ **RESOLVED (engrave v3 widow-rebalance)** | `epiphany-engrave` I | RS-1 honestly failed the Minimal casting-off threshold under the reference engraver (measured 1.0 vs 0.90): greedy first-fit left a two-measure stub last system (width CV 0.6145 ≥ the 0.5 anchor). **Resolved the honest way — an engrave-side casting-off balance pass, no catalog change:** casting-off gained a second **widow-rebalance** phase that moves whole trailing measures from a region's penultimate system into its final one, choosing the shift that minimizes the larger of the two distribution penalties the catalog defines for the break family (width imbalance vs non-final underfill; both share the 0.5 anchor). RS-1 now casts six/four instead of eight/two: casting_off 1.0 → **0.4463**, system_break 0.254 → 0.677, every axis ≤ 0.90. `ENGRAVER_VERSION` 2 → 3; render goldens regenerated; the suite's Xfail row promoted to a plain Pass. The `casting_off` 0.5 anchor and the Minimal column stood — the engraver improved, not the threshold — so the Quality Metric Catalog is normatively unchanged. | ✅ done |
| ~~P12-I12~~ **RESOLVED (QMC 0.2.0: rhythmic-column spacing)** | `epiphany-engrave` I | The Standard-tier spacing floor warned on short healthy scores: 38-column entries with a wide clef/key lead measured spacing CV 0.360.41 > the 0.32 Standard floor (a spurious *diagnostic* — the scores all passed Minimal). **Resolved by the lead-aware refinement (Quality Metric Catalog 0.1.0 → 0.2.0):** `spacing_distortion` is now scoped to the system's **rhythmic columns** (slots bearing a notehead or rest), excluding the clef / key-signature / time-signature lead and treating barlines transparently (a note-to-note advance spans them) — so the furniture-width lead gap no longer inflates the CV. The three entries drop to 0.2188 / 0.0819 / 0.0856 (all below the 0.32 floor), and the axis stays honest on real irregularity (RS-3 keeps 0.2188 from a mid-line accidental). Anchor, orientation, range, thresholds, and the eight other axes unchanged; no layout/golden change (measurement-only, `ENGRAVER_VERSION` untouched). The **duration-aware optical-spacing** open question (deviation from duration-proportional spacing) stays open — it needs the pipeline's deferred duration-proportional preferred widths. | ✅ done | | ~~P12-I12~~ **RESOLVED (QMC 0.2.0: rhythmic-column spacing)** | `epiphany-engrave` I | The Standard-tier spacing floor warned on short healthy scores: 38-column entries with a wide clef/key lead measured spacing CV 0.360.41 > the 0.32 Standard floor (a spurious *diagnostic* — the scores all passed Minimal). **Resolved by the lead-aware refinement (Quality Metric Catalog 0.1.0 → 0.2.0):** `spacing_distortion` is now scoped to the system's **rhythmic columns** (slots bearing a notehead or rest), excluding the clef / key-signature / time-signature lead and treating barlines transparently (a note-to-note advance spans them) — so the furniture-width lead gap no longer inflates the CV. The three entries drop to 0.2188 / 0.0819 / 0.0856 (all below the 0.32 floor), and the axis stays honest on real irregularity (RS-3 keeps 0.2188 from a mid-line accidental). Anchor, orientation, range, thresholds, and the eight other axes unchanged; no layout/golden change (measurement-only, `ENGRAVER_VERSION` untouched). The **duration-aware optical-spacing** open question (deviation from duration-proportional spacing) stays open — it needs the pipeline's deferred duration-proportional preferred widths. | ✅ done |

View File

@ -74,3 +74,32 @@ change**: the defect was in the metric's own definition, not the engraver.
(`spacing_distortion` scoped to rhythmic columns). Core spec unchanged (the (`spacing_distortion` scoped to rhythmic columns). Core spec unchanged (the
axis's formal definition is delegated to the catalog; Chapter 9 carries only axis's formal definition is delegated to the catalog; Chapter 9 carries only
the field, which is unchanged). the field, which is unchanged).
## Schema-major-1 tranche (2026-07-06) — data-model growth + engrave ratify-only
The first binary-format schema-major bump (v0 → v1, `binary_format` 0.3.0),
built machinery-first on a small payload, landed the two data-model rows the
batch parked behind the frozen-layout rule (**P12-I7**, **P12-K7**) and ratifies
the three engrave/layout-ir dispositions the implementation already made
(**P12-I8/I9/I10**). Delivered as the schema-major-1 Phase AF commit series
(from `f4a2f1f`); this tranche records Phase F.
| Item | Disposition | Spec locus | Authority followed |
|---|---|---|---|
| P12-I7 page-geometry graph home | **landed (schema-major-1)**`Canvas` gained `layout_defaults: CanvasLayoutDefaults { page_size: CanvasSize, margins: CanvasMargins }` (staff spaces, A4/8 mm default), defined in core spec (Phase A) and added to the graph + canonical wire form at major 1 (Phase C, byte-for-byte v0→v1 migrate-on-read, zero golden churn). The engraver still defaults to the same geometry; wiring it to *read* the graph field (C) is a byte-neutral follow-up deferred until a custom-geometry producer exists | core_spec Ch5 §The Canvas (`CanvasLayoutDefaults`); binary_format §Schema Major 1 | `epiphany-core` (`graph.rs`, `codec.rs::decode_v0_score`) |
| P12-K7 advisory-precondition catalog | **landed (schema-major-1) + adopt** — the two graph-model gaps the advisory catalog named are filled: `Instrument.range: Option<PitchRange>` and `Region.permits_spanning_slurs: bool` (major 1, Phases D1/D2), so `epiphany-ops` now enforces the InsertEvent pitch-in-range advisory (frame-guarded; "if any"/indeterminate → vacuous pass) and the CreateCrossCutting(Slur) region-spanning advisory. ModifyEvent's duration-boundary bucket is stated explicitly (it carries the full replacement value). Cross-region slur permission is read as **both** endpoint regions must permit (conservative AND) — see the open item below | core_spec Ch5 §Instruments / §Regions; Ch6 §6.10 advisory buckets; `PitchRange` | `epiphany-core` (`pitch.rs::PitchRange`, `graph.rs`); `epiphany-ops` (`validate.rs`) |
| P12-I8 break-constraint satisfaction | **adopt** — Ch7 gains a normative predicate (Requirement `req:layoutir:break-satisfaction`): a `SystemBreakAt`/`PageBreakAt` at `slot` is satisfied iff the final `ResolvedLayoutIR` starts a system/page at that slot; a region-first slot is trivially satisfied; satisfaction is a predicate on the output, not the solver's spring state | core_spec Ch7 §ConstrainedLayoutIR (`req:layoutir:break-satisfaction`) | `epiphany-engrave` casting-off (evaluates hard break constraints for its tier claim) |
| P12-I9 break-override attribution | **adopt (decline widening)** — Ch7 gains Requirement `req:layoutir:break-origin-attribution`: honouring a user break carries `DecisionSource::UserOverride(id)`, threaded through a `ConstrainedLayoutIR.break_origins` sidecar populated by the logical-stage projection. The normalized constraint record is deliberately **not** widened to carry override identity — attribution is a projection concern, not a solver input | core_spec Ch7 §Engraving Overrides (`req:layoutir:break-origin-attribution`) | `epiphany-layout-ir` (`constrained.rs::break_origins`, `to_constrained`) |
| P12-I10 continuation synthesis | **adopt (bless registered id)** — Ch7 gains Requirement `req:layoutir:continuation-synthesis`: a system-spanning stroke's post-first segments are synthesized under `SynthesisKind::Registered(SYSTEM_CONTINUATION_SYNTHESIS)`, with `stable_semantic_instance_key = (original, ordinal)`; because `LayoutObjectId`s are non-canonical and re-derived per layout, the key need only be stable within a layout | core_spec Ch7 §Provenance (`req:layoutir:continuation-synthesis`) | `epiphany-layout-ir` (`provenance.rs`; `SYSTEM_CONTINUATION_SYNTHESIS`) |
**Version movements.** Binary Format companion 0.2.0 → 0.3.0 (schema major 1
wire form + migration; Phase A). Core spec: `CanvasLayoutDefaults` / `PitchRange`
/ `Region.permits_spanning_slurs` data-model additions (Phase A) and the three
engrave requirements above (Phase F); revision-history row appended.
**Open item (flagged, not resolved).** Which region governs a *cross-region*
slur's spanning permission is under-specified. The implementation chose the
conservative **AND** (a boundary is permeable only when both endpoint regions set
`permits_spanning_slurs`); a "the start region governs" or "either side" reading
is equally defensible. Tracked for ratification; the advisory is authoring-only
and never alters reduction, so the choice is not byte-affecting.

Binary file not shown.

View File

@ -8041,6 +8041,22 @@ pub struct LayoutObjectId(pub u128);
whose change should invalidate this layout object. whose change should invalidate this layout object.
\end{requirement} \end{requirement}
\begin{requirement}
\label{req:layoutir:continuation-synthesis}
\textbf{System-spanning continuation.} A line primitive (stroke) that spans a
system boundary is split into per-system segments; the segments after the
first are engraver-synthesized and \MUST{} declare a \texttt{synthesis} kind.
The reserved registered kind
\texttt{SynthesisKind::Registered(SYSTEM\_CONTINUATION\_SYNTHESIS)} names this
continuation. Its \texttt{stable\_semantic\_instance\_key} (per
Requirement~\ref{req:layoutir:object-id-derivation}) is the pair
\texttt{(original,\,ordinal)} --- the pre-split stroke's object and the
segment's order within the split --- which is stable for a fixed set of system
breaks. Because \texttt{LayoutObjectId}s are non-canonical and re-derived per
layout, this key need only be stable within a layout, not across relayouts
that move the breaks.
\end{requirement}
\begin{requirement} \begin{requirement}
\label{req:layoutir:object-id-derivation} \label{req:layoutir:object-id-derivation}
A \texttt{LayoutObjectId} \MUST{} be stable across re-layouts whose A \texttt{LayoutObjectId} \MUST{} be stable across re-layouts whose
@ -8540,6 +8556,21 @@ pub enum OverrideOrigin {
overrides without explicit user action. overrides without explicit user action.
\end{requirement} \end{requirement}
\begin{requirement}
\label{req:layoutir:break-origin-attribution}
\textbf{Break-override attribution.} When the engraver honours a user break
override, the resulting decision \MUST{} carry
\texttt{DecisionSource::UserOverride(id)} naming the override. Because a
normalized break \emph{constraint} (\texttt{SystemBreakAt} /
\texttt{PageBreakAt}, Section~\ref{sec:layoutir:constrained}) carries no
override identity, this attribution is threaded through the projection: the
\texttt{ConstrainedLayoutIR} carries a \texttt{break\_origins} sidecar,
populated when the logical stage projects the break, mapping each user-anchored
break slot to its override id. The normalized constraint record is
deliberately \emph{not} widened to carry override identity --- attribution is a
projection concern, not a constraint-solver input.
\end{requirement}
\section{ConstrainedLayoutIR} \section{ConstrainedLayoutIR}
\label{sec:layoutir:constrained} \label{sec:layoutir:constrained}
@ -8704,6 +8735,20 @@ pub enum BreakKind {
} }
\end{lstlisting} \end{lstlisting}
\begin{requirement}
\label{req:layoutir:break-satisfaction}
\textbf{Break-constraint satisfaction.} A \texttt{SystemBreakAt} at
\texttt{slot} is \emph{satisfied} iff the final resolved layout starts a
system at that slot; a \texttt{PageBreakAt} iff the final layout starts a page
there. A slot that already begins a system (respectively page) --- in
particular a region's first slot --- is trivially satisfied. Satisfaction is a
predicate on the output \texttt{ResolvedLayoutIR}, evaluable by any consumer,
not on the solver's internal spring state. A solver's tier claim
(Section~\ref{sec:layoutir:resolved}) reports which \texttt{Hard} break
constraints its layout satisfies; a \texttt{Soft} break is advisory and MAY be
left unsatisfied.
\end{requirement}
\section{ResolvedLayoutIR} \section{ResolvedLayoutIR}
\label{sec:layoutir:resolved} \label{sec:layoutir:resolved}
@ -14682,6 +14727,23 @@ layouts they own versus inherit:
byte-for-byte v0${\to}$v1 migration are ratified in the Binary Format byte-for-byte v0${\to}$v1 migration are ratified in the Binary Format
companion (0.2.0 $\rightarrow$ 0.3.0). companion (0.2.0 $\rightarrow$ 0.3.0).
\\ \\
\today & Schema major 1 Phase F (engrave ratify-only) &
Ratifies three engrave/layout-ir dispositions the implementation already made
(Pass 12 batch I8/I9/I10, now struck), with no byte-layout or architecture
change: the \emph{break-constraint satisfaction} predicate
(Requirement~\ref{req:layoutir:break-satisfaction} --- a \texttt{SystemBreakAt}
/ \texttt{PageBreakAt} is satisfied iff the final \texttt{ResolvedLayoutIR}
starts a system/page at that slot, a region-first slot trivially); \emph{break-override
attribution} threaded through a \texttt{ConstrainedLayoutIR.break\_origins}
sidecar, declining to widen the normalized constraint record
(Requirement~\ref{req:layoutir:break-origin-attribution}); and the
\emph{system-continuation} synthesis kind
\texttt{Registered(SYSTEM\_CONTINUATION\_SYNTHESIS)} with an
\texttt{(original,\,ordinal)} instance key
(Requirement~\ref{req:layoutir:continuation-synthesis}). Flags one open item:
which region governs a cross-region slur's spanning permission (implemented as
conservative AND, P12-K12).
\\
\bottomrule \bottomrule
\end{longtable} \end{longtable}