Schema major 1 Phase A: ratify the v1 wire form + migration (spec only)

The first phase of the first-ever binary-format schema-major bump (v0 -> v1),
the machinery-first minimal major. Spec-only: ratifies the contract that
Phases B-F build against; no code changes.

core_spec.tex: defines the two referenced-but-undefined types that were the
P12-I7 / P12-K7 gaps -- CanvasLayoutDefaults (with core geometry primitives
CanvasSize/CanvasMargins in staff spaces via CanonicalF64, A4/8mm default,
since core has no geometry types and must not depend on layout-ir) and
PitchRange (advisory pitch compass used by Instrument.range and
IndeterminacyHints) -- and adds Region.permits_spanning_slurs (default false).
Records schema major 1 as the first data-model expansion major and tightens
the minor-version rule (a field add, even Option, is major; minor = append
discriminants to the companion's append-safe vocabularies only).

binary_format.tex -> 0.3.0: the full "Schema Major 1" section --
  - Where the changed fields reach: Canvas.layout_defaults and
    Instrument.range are snapshot-only (no CreateCanvas/CreateInstrument op),
    but Region.permits_spanning_slurs also reaches the CANONICAL CreateRegion
    operation payload (CreateRegion embeds the full Region). The canonical-base
    MaterializedState embeds none of these and stays major 0, byte-identical.
  - Cross-major reader rules: discard-and-regenerate non-canonical chunks;
    parse-or-read-only for canonical ones, so a major-0 reader opens a bundle
    carrying v1 CreateRegion ops read-only.
  - Accept-set gate [min,max] (rejects majors outside the set); per-payload-
    type major assignment; the changed v1 value layouts (the wire form ratifies
    the reduced reference-code layout, not the fuller data model); the total
    default-filling v0->v1 migration table (including the CreateRegion payload).
  - Length-prefix unification NARROWED to the resolved-layout (its own
    non-canonical LayoutCache): the barrier/extension blobs stay regime (b) u64
    because they ride the canonical manifest, which stays major 0.

Two review passes hardened this checkpoint. The first caught that Region is a
canonical operation payload (not cache-only, as the architecture analysis had
assumed) -- user chose to embrace it and build the canonical op-payload
migration. It also surfaced the barrier-blobs-in-manifest constraint that
narrows the unification. The second refined the minor-version delegation, the
accept-set outside-[min,max] semantics, and stale "no defined type" text in
the reference-suite / quality-metric companions and the engrave DECISIONS.

P12-I7 moved to IN PROGRESS (spec type defined here; code graph home lands in
Phase C). Both companions and the engrave DECISIONS reworded accordingly. All
four affected PDFs rebuild clean (0 undefined references).

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-05 18:05:38 -04:00
parent 9d28dd6615
commit f4a2f1fdf0
10 changed files with 370 additions and 44 deletions

View File

@ -111,12 +111,14 @@ inputs the solver cannot measure.
be the spec's intent. Break constraints are evaluated against the **final
break structure**.
4. **Page geometry is an engraver parameter (`PageGeometry`), defaulted to A4
at an 8 mm staff.** The spec names `Canvas.layout_defaults` ("paper size,
margins") but never defines the type, and core does not implement it;
adding a graph field now would violate the companion's frozen-layout rule,
so the graph home (`CanvasLayoutDefaults`) is **staged to the data-model
schema major** and the engraver takes the geometry as a constructor
parameter. Default arithmetic (1 staff space = staff height / 4 = 2.0 mm at
at an 8 mm staff.** Adding a `Canvas` graph field is a schema-major change
under the companion's frozen-layout rule, so it was staged to the data-model
schema major. **Schema major 1 now defines the type** (`CanvasLayoutDefaults
{ page_size: CanvasSize, margins: CanvasMargins }`, staff spaces, A4/8mm
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
field) and the engraver reads it in Phase C. Until then the engraver takes
the geometry as a constructor parameter. 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
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

View File

@ -62,7 +62,7 @@ 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-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-I7 | `epiphany-engrave` I | Page geometry has no graph home: the spec names `Canvas.layout_defaults` ("paper size, margins") but defines 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, documented arithmetic). Define `CanvasLayoutDefaults` with the data-model schema-major; until then solvers MAY default. | G (graph model, schema-major) |
| 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-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-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-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) |

Binary file not shown.

View File

@ -235,7 +235,7 @@
{\Large\scshape\color{epiphanyslate}Binary Format}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.2.0 --- Phase 2/3 (canonical wire format: primitives through bundle physical layout + K0 and Phase-3-tranche payload framing)}\\[4pt]
{\normalsize\color{epiphanyink}Version 0.3.0 --- Phase 2/3 (canonical wire format: primitives through bundle physical layout + K0 and Phase-3-tranche payload framing; schema major~1 data-model expansion)}\\[4pt]
{\small\color{epiphanyslate}Normative for the byte layouts it defines}
\vfill
\end{titlepage}
@ -395,7 +395,10 @@ which.
\texttt{ResolvedLayoutIR} canonical output
(Chapter~\ref{ch:noncanon}). &
Little-endian integers; \texttt{u64} little-endian counts and length
prefixes. Deliberate, golden-locked divergence from regime (a). \\
prefixes --- a golden-locked divergence from regime (a). Schema major~1
moves the \emph{resolved-layout} surface to \texttt{u32}; the
manifest-embedded barrier blobs stay \texttt{u64}
(Section~\ref{sec:evolution:major1}). \\
(c) & Identifiers and hash-derived forms: the 128-bit identifier family,
\texttt{ReplicaId}, \texttt{OperationId}, \texttt{ConflictId}, the
\texttt{TypedObjectId} discriminant, digest truncations
@ -406,14 +409,20 @@ which.
\end{longtable}
\begin{rationale}
Regimes (a) and (b) differ only in prefix width, and the divergence is
\emph{tolerated rather than repaired}: the two surfaces were golden-locked
independently (the barrier blobs and the resolved-layout output carry their
own literal-byte anchors), they never embed one another's framing, and
unifying the prefix width now would break locked bytes for zero benefit. The
divergence is recorded as an open question for the next schema-major revision
(Chapter~\ref{ch:evolution}). Regime (c) is not a divergence at all: an
identifier's byte form \emph{is} its sort key, so big-endian is load-bearing.
In schema major~0, regimes (a) and (b) differ only in prefix width, and the
divergence was \emph{tolerated rather than repaired}: the two surfaces were
golden-locked independently (the barrier blobs and the resolved-layout output
carry their own literal-byte anchors), they never embed one another's framing,
and unifying the prefix width within major~0 would break locked bytes for zero
benefit. Once a major bump was scheduled for data-model reasons, part of the
unification became free to carry: schema major~1 moves the resolved-layout
surface (an independent non-canonical \texttt{LayoutCache}) to regime (a)'s
\texttt{u32} prefixes and re-locks its goldens. The barrier blobs cannot follow
in the same step --- they are carried opaquely inside the \emph{canonical}
manifest, which stays major~0 --- so they keep regime (b) until a manifest-
revising major (Chapter~\ref{ch:evolution}). Regime (c) is not a divergence at
all: an identifier's byte form \emph{is} its sort key, so big-endian is
load-bearing.
\end{rationale}
\section{Primitive Composition Rules}
@ -2078,8 +2087,13 @@ conservative treatment of unknown extensions --- are core Chapter~8
bytes only.
These layouts are \textbf{regime (b)}: all counts and length prefixes are
\texttt{u64} little-endian --- a deliberate, golden-locked divergence from
the \texttt{u32} regime (Section~\ref{sec:conventions:regimes}).
\texttt{u64} little-endian --- a deliberate, golden-locked divergence from the
\texttt{u32} regime (Section~\ref{sec:conventions:regimes}). They \textbf{keep
this form in schema major~1}: unlike the resolved-layout surface, these blobs
are carried \emph{opaquely inside the canonical manifest}
(Section~\ref{sec:bundle:manifest}), which stays major~0, so a major-0 reader
must continue to parse them as \texttt{u64}. Their unification waits for a
future major that revises the manifest (Section~\ref{sec:evolution:major1}).
\section{Framing}
\label{sec:barriers:framing}
@ -2202,7 +2216,9 @@ The core specification delegates schema evolution to this document (core
Chapter~8, \sectionsc{Schema Versioning}: \emph{``Schema evolution is
governed by the Binary Format companion specification, which defines the
wire encoding for each schema version''}). This chapter defines the wire
rules for schema major~0.
rules for schema evolution. Chapters~\ref{ch:values}--\ref{ch:barriers}
specify the schema-major-0 layouts; Section~\ref{sec:evolution:major1}
specifies the schema-major-1 delta and the migration between them.
\section{The Chunk-Level Gate}
\label{sec:evolution:gate}
@ -2212,7 +2228,13 @@ Every chunk declares a \texttt{SchemaVersion} (major, minor) in its
\begin{itemize}
\item \textbf{Major} = incompatible. A reader \MUST{} reject a chunk whose
schema major it does not support. The current major is~\tablenums{0}.
schema major it does not support. A reader supports a contiguous accept-set
$[\textsc{min}, \textsc{max}]$ of majors and rejects any chunk whose major
falls \emph{outside} $[\textsc{min}, \textsc{max}]$ --- too new (above
\textsc{max}) or, once \textsc{min} rises past a retired major, too old
(Section~\ref{sec:evolution:major1}). Two majors are defined:
\tablenums{0} and \tablenums{1}; the reference implementation's accept-set
is $\{0, 1\}$ ($\textsc{min} = 0$, $\textsc{max} = 1$).
\item \textbf{Minor} = additive. v0 readers verify the major only; the
minor is a \emph{record}, not a gate --- but it is a mandatory record:
a writer \MUST{} raise the chunk schema minor when it emits any
@ -2232,6 +2254,15 @@ fourteenth field within major~0}. The manifest is a positional struct whose
decoder rejects trailing bytes, so manifest-body extension is
major-gated in v0 exactly like every other struct.
Adding a field is a \textsc{major} change \emph{regardless of its type}: an
\texttt{Option} field is not ``optional'' in the wire sense --- it still
occupies a new positional slot (a presence byte, then the payload when
present), which shifts every subsequent field and rejects under the
trailing-bytes rule. There is no ``downgrade to minor'' for an \texttt{Option}
addition. The first exercise of this is schema major~1
(Section~\ref{sec:evolution:major1}), which adds \texttt{Instrument.range}
(an \texttt{Option}) among other fields.
The \emph{only} minor-additive mechanism in schema major~0 is
\textbf{appending discriminants to open vocabularies}:
@ -2273,14 +2304,181 @@ Operation Catalog (\sectionsc{v0 $\rightarrow$ v1 Payload Migration})
because it was semantic rather than byte-level
(Section~\ref{sec:ops:v0}); byte-level migrations belong here.
\begin{openquestion}
Whether the \texttt{u64}/\texttt{u32} length-prefix divergence between
regimes (a) and (b) (Section~\ref{sec:conventions:regimes}) gets unified at
the next schema major. Unification would simplify decoders and cost one
coordinated re-lock of the barrier-blob and resolved-layout goldens;
leaving it is free but permanent. Decide when the first major bump is
scheduled for other reasons --- the divergence alone does not justify one.
\end{openquestion}
The \texttt{u64}/\texttt{u32} length-prefix divergence between regimes (a) and
(b) (Section~\ref{sec:conventions:regimes}), left open in earlier revisions for
a future major, is \textbf{partly resolved in schema major~1}
(Section~\ref{sec:evolution:major1}): the first major bump, scheduled for
data-model reasons, carries the unification for the independent non-canonical
resolved-layout surface. The barrier/extension blobs remain regime~(b) because
they ride the canonical manifest, which this bump keeps at major~0; their
unification stays open for a manifest-revising major.
\section{Schema Major 1}
\label{sec:evolution:major1}
Schema major~1 is the first data-model expansion major, defined here in full:
the changed value layouts, the length-prefix unification, and the
byte-for-byte migration from major~0. It bundles every change that was
deferred to ``the next major'' so that one coordinated migration discharges
them together.
\subsection{Where the changed fields reach}
The three added fields land in different chunk classes, and the major is
assigned \textbf{per payload type}, not per chunk kind:
\begin{itemize}
\item \texttt{Canvas.layout\_defaults} and \texttt{Instrument.range} appear
\emph{only} in the acceleration-cache full-\texttt{Score} snapshot (a
\texttt{ChunkKind::Snapshot}). No operation payload embeds a
\texttt{Canvas} or an \texttt{Instrument} value (there is no
\texttt{CreateCanvas}/\texttt{CreateInstrument}; canvas is the inline
genesis singleton and instruments live in score genesis), so these two
are confined to that one \textbf{non-canonical} chunk.
\item \texttt{Region.permits\_spanning\_slurs} reaches the snapshot
\emph{and} the \textbf{canonical operation layer}:
\texttt{CreateRegion} embeds the full \texttt{Region} value
(Section~\ref{sec:ops:payload}), so its v1 payload carries the v1
\texttt{Region} layout. An operation-envelope block containing a
\texttt{CreateRegion} therefore encodes v1 bytes and is stamped
\textbf{major~1}; a block with no such operation stays \textbf{major~0}.
Operation-envelope blocks are canonical, so this is a canonical
operation-layer change, not merely a cache change.
\item \texttt{ChunkKind::Snapshot} is itself payload-polymorphic: the same
kind carries \emph{either} an acceleration-cache full \texttt{Score} (as
above, stamped major~1) \emph{or} the canonical-base
\texttt{MaterializedState}, disambiguated by manifest role. The canonical
base embeds no \texttt{Canvas}, \texttt{Instrument}, or \texttt{Region}
value, so it is unchanged and stays \textbf{major~0}. Its content-hash
preimage (Section~\ref{sec:bundle:chunks}) includes the schema version, so
re-stamping the unchanged base at major~1 would change its chunk id and
churn every \texttt{SnapshotRef} for zero layout benefit. A writer
\MUSTNOT{} do so; a conformance test \SHOULD{} assert the canonical base
is byte-identical across the bump.
\end{itemize}
\subsection{Cross-major reader behaviour}
Two rules follow from the chunk classes above and the core specification's
\sectionsc{Schema Versioning} canonical/non-canonical distinction:
\begin{itemize}
\item \textbf{Non-canonical chunks --- discard and regenerate.} The layout
caches and the acceleration snapshot are non-canonical. A reader meeting a
foreign-major one \MAY{} discard and regenerate it rather than decode it: a
major-1 reader regenerates a major-0 layout cache instead of carrying a
frozen old-width decoder, and it migrates a major-0 acceleration
\texttt{Score} snapshot on read (cheaper than replaying the operation log).
\item \textbf{Canonical chunks --- parse or open read-only.} The manifest and
the canonical base stay major~0 and are always parseable. A major-1 op
block is canonical: a major-0-only reader \MUSTNOT{} discard it (that would
fork canonical state) --- it opens the bundle in read-only preservation
mode, having read the major-0 base and manifest but being unable to replay
the v1 \texttt{CreateRegion} operations. A major-1 reader migrates such a
block on read (below).
\end{itemize}
So a major-0-only reader opens a major-1 bundle \emph{fully} only when the
bundle contains no v1 \texttt{CreateRegion} operation; otherwise it opens
read-only. It always reads the canonical base and manifest, and always
discards the higher-major non-canonical caches.
\subsection{The accept-set gate}
A reader supports a contiguous set of majors $[\textsc{min}, \textsc{max}]$
(the reference implementation: $\{0, 1\}$, i.e. $\textsc{min} = 0$,
$\textsc{max} = 1$). The chunk-level gate
(Section~\ref{sec:evolution:gate}) rejects a chunk whose major falls outside
$[\textsc{min}, \textsc{max}]$ --- too new above \textsc{max}, or too old
below \textsc{min} once a reader drops support for a retired major; it no
longer rejects on inequality with a single supported major.
\subsection{Changed and new value layouts}
These extend Chapter~\ref{ch:values}'s regime~(a) rules. As there, \textbf{the
wire form is the reference implementation's struct layout} --- which is a
reduced subset of the core specification's fuller data model (the code's
\texttt{Instrument} carries a subset of the model's fields, etc.). The wire
form ratifies the code, not the model; a struct's v1 layout \textbf{appends}
its new field(s) after its existing major-0 fields:
\begin{itemize}
\item \texttt{Canvas} $=$ \texttt{regions} \cat{} \texttt{layout\_defaults},
where \texttt{CanvasLayoutDefaults} $=$ \texttt{page\_size}
(\texttt{CanvasSize} $=$ \texttt{width} \cat{} \texttt{height}, two
\texttt{CanonicalF64} leaves) \cat{} \texttt{margins}
(\texttt{CanvasMargins} $=$ four \texttt{CanonicalF64} leaves: top,
right, bottom, left).
\item \texttt{Instrument} $=$ \texttt{id} \cat{} \texttt{name} \cat{}
\texttt{range} (\texttt{Option<PitchRange>}: a presence byte, then for
\texttt{Some} a \texttt{PitchRange} $=$ \texttt{lowest} \cat{}
\texttt{highest}, each a \texttt{Pitch} under this chapter's rules).
\item \texttt{Region} appends \texttt{permits\_spanning\_slurs}, one bare
bool byte (\tablenums{0}/\tablenums{1}), after its major-0 fields.
\item \textbf{Operation layer:} the \texttt{CreateRegion} payload embeds a
length-prefixed \texttt{Region} (Section~\ref{sec:ops:payload}), so its v1
form carries the v1 \texttt{Region} layout (the appended bool inside the
embedded value). This is the one \emph{canonical} byte change in major~1;
the operation envelope's own field order is unchanged, only its embedded
\texttt{Region} grows.
\end{itemize}
Every embedded leaf keeps this chapter's framing (a \texttt{CanonicalF64} is a
\texttt{u32} LE length \cat{} 8 bytes $= 12$; a bool is one bare byte).
\subsection{Length-prefix unification (partial)}
Regime~(b) (Section~\ref{sec:conventions:regimes}) is \emph{narrowed}, not
fully retired, in major~1. The \texttt{ResolvedLayoutIR} output --- an
independent, non-canonical \texttt{LayoutCache} chunk --- moves from
\texttt{u64} to \texttt{u32} LE counts and length prefixes, matching
regime~(a), and its golden anchors re-lock in the same step. Because it is
non-canonical, a major-1 reader regenerates a major-0 resolved-layout cache
rather than decoding its \texttt{u64} form --- no frozen \texttt{u64} decoder
is required.
The extension-declaration and edit-barrier blobs (Chapter~\ref{ch:barriers})
\textbf{stay regime~(b)}: they are carried opaquely inside the canonical
manifest (Section~\ref{sec:bundle:manifest}), which stays major~0, so a
major-0 reader must keep parsing them as \texttt{u64}. Unifying them would
require bumping the manifest itself, which this data-model bump deliberately
avoids; that unification waits for a manifest-revising major.
\subsection{Migration from major~0}
The v0${\to}$v1 translation is total and default-filling: every new field has
a canonical default, so no value is unrecoverable --- including the
\texttt{CreateRegion} operation payload, whose embedded \texttt{Region}
default-fills structurally. This is unlike the \emph{semantic},
context-dependent operation-payload migration of Section~\ref{sec:ops:v0}
(which could be irreversible); the major-1 migration needs no score context.
\begin{center}
\begin{tabular}{p{1.9in} p{3.4in}}
\toprule
\textbf{major-0 form} & \textbf{major-1 form} \\
\midrule
\texttt{Canvas} $=$ \texttt{regions} &
append \texttt{layout\_defaults} $=$ the A4/8\,mm default (page
$105 \times 148.5$, margins $7.5$ staff spaces) \\
\texttt{Instrument} $=$ \texttt{id}, \texttt{name} &
append \texttt{range} $=$ \texttt{None} (presence byte \tablenums{0}) \\
\texttt{Region} $= \ldots$ (snapshot) &
append \texttt{permits\_spanning\_slurs} $=$ \tablenums{0}
(\texttt{false}) \\
\texttt{CreateRegion} op payload &
default-fill the embedded \texttt{Region}'s appended field
(\texttt{permits\_spanning\_slurs} $=$ \tablenums{0}); \emph{canonical} ---
a v0 op block migrates on read, and a block bearing a v1 \texttt{CreateRegion}
is major~1 (a major-0-only reader opens the bundle read-only) \\
\texttt{ResolvedLayoutIR} \texttt{u64} prefixes &
non-canonical \texttt{LayoutCache}: discard and regenerate at major~1
under \texttt{u32} (no in-place byte translation) \\
barrier / extension-declaration blobs &
stay regime~(b) \texttt{u64} (manifest-embedded, manifest stays major~0);
unchanged in this bump \\
canonical-base \texttt{MaterializedState} &
unchanged, byte-identical, stays major~0 \\
\bottomrule
\end{tabular}
\end{center}
A reader migrates a major-0 acceleration \texttt{Score} snapshot on read
(cheaper than replaying the operation log); a writer emits only major-1 forms
for the changed payloads and leaves the canonical base at major~0.
% ===========================================================================
\chapter{Non-Canonical Pinned Encodings}
@ -2321,9 +2519,13 @@ $\texttt{LayoutObjectId} = \mathrm{trunc128}(\mathrm{BLAKE3}(
The resolved layout's canonical bytes are the \textbf{byte-equal conformance
surface} of core Chapter~7: two conforming engravers given the same score,
profile, and glyph catalog must produce identical bytes. The encoding is
regime (b) --- \texttt{u64} LE counts and length prefixes --- with every
geometric coordinate quantized to the $1/1024$ staff-space grid as a
profile, and glyph catalog must produce identical bytes. In schema major~0 the
encoding is regime (b) (\texttt{u64} LE counts and length prefixes); schema
major~1 moves it to regime (a)'s \texttt{u32} prefixes with the rest of the
unification (Section~\ref{sec:evolution:major1}), re-locking this surface's
goldens. Because the surface is non-canonical, a reader regenerates a
foreign-major layout cache rather than decoding it. Either way every geometric
coordinate is quantized to the $1/1024$ staff-space grid as a
\texttt{QuantizedCoord} (8 LE bytes; Chapter~\ref{ch:primitives});
a non-finite or out-of-range coordinate is a determinism violation and
\MUST{} be rejected, never normalized. The top-level section order is:
@ -2556,6 +2758,23 @@ layouts of Section~\ref{sec:values:representative}.
assignment changed. Semantics: Operation Catalog 0.5.0 (\sectionsc{CreateStaff},
\sectionsc{Meter and Tempo Overwrites}, \sectionsc{SetStaffLayout}, and the
value-restoring \sectionsc{UndoTransaction} revision). \\
\today & Schema evolution / Graph value layouts & 0.3.0 --- Defines
\textbf{schema major~1}, the first data-model expansion major
(Section~\ref{sec:evolution:major1}): appends \texttt{Canvas.layout\_defaults}
(new \texttt{CanvasLayoutDefaults}/\texttt{CanvasSize}/\texttt{CanvasMargins}
layouts, snapshot-only), \texttt{Instrument.range} (\texttt{Option<PitchRange>},
snapshot-only), and \texttt{Region.permits\_spanning\_slurs} (also inside the
\emph{canonical} \texttt{CreateRegion} operation payload); narrows regime~(b)
by unifying the non-canonical resolved-layout length prefixes to \texttt{u32}
(the manifest-embedded barrier blobs stay \texttt{u64}, partly resolving the
standing open question); pins per-payload-type major assignment (the
canonical-base \texttt{MaterializedState} stays major~0, byte-identical),
the accept-set gate ($[\textsc{min},\textsc{max}]$), the cross-major reader
rules (discard-and-regenerate non-canonical chunks; parse-or-read-only for
canonical ones, so a major-0 reader opens a bundle with v1 \texttt{CreateRegion}
ops read-only), and the total default-filling v0${\to}$v1 migration table.
Clarifies that any field add is a major change regardless of
\texttt{Option}-ness. \\
\bottomrule
\end{longtable}

Binary file not shown.

View File

@ -883,6 +883,27 @@ the other in general.
storage makes the pitch self-describing.
\end{rationale}
\begin{lstlisting}[language=Rust]
/// A closed range of pitches, used for advisory declarations: an
/// instrument's playable compass and an indeterminate event's pitch hint.
/// Both bounds are full pitches.
pub struct PitchRange {
pub lowest: Pitch,
pub highest: Pitch,
}
\end{lstlisting}
A \texttt{PitchRange} is \emph{advisory}. Whether a given pitch lies within
it is decidable only when the pitch and both bounds share a pitch space that
defines an order (the common CMN case); cross-space or partially-ordered
comparisons are treated as ``not out of range'' --- a sound-but-incomplete
check, as with region time overlap. A range is well-formed when
\texttt{lowest} does not sort above \texttt{highest} in a common ordered
space. A solver \MAY{} flag an out-of-range pitch but \MUSTNOT{} treat the
range as a hard constraint. The type is defined with schema major~1, closing
the gap in which \texttt{IndeterminacyHints} and \texttt{Instrument} named a
\texttt{PitchRange} that no section defined.
\section{Scale Position}
\label{sec:pitch:scale-position}
@ -4228,8 +4249,40 @@ pub struct Canvas {
/// system layout, page-break behavior.
pub layout_defaults: CanvasLayoutDefaults,
}
/// Canvas-level layout defaults. Page size and margins are in staff
/// spaces; a solver reads them as its default page geometry and MAY
/// override per solve. The type is defined with schema major 1 (its field
/// was named on the canvas from the first draft; only its type was
/// undefined).
pub struct CanvasLayoutDefaults {
pub page_size: CanvasSize,
pub margins: CanvasMargins,
}
/// A page size in staff spaces (1 staff space = staff height / 4).
pub struct CanvasSize {
pub width: f64,
pub height: f64,
}
/// Page margins in staff spaces.
pub struct CanvasMargins {
pub top: f64,
pub right: f64,
pub bottom: f64,
pub left: f64,
}
\end{lstlisting}
The default is \textbf{A4 portrait at an 8\,mm staff} (1 staff space =
2\,mm): page $105 \times 148.5$ staff spaces, $7.5$-staff-space margins,
hence a $90 \times 133.5$ content area. Each staff-space scalar is realized
canonically as a \texttt{CanonicalF64} leaf; the reference engraver maps a
\texttt{CanvasLayoutDefaults} onto its internal page geometry. This closes
the long-standing gap in which the canvas named \texttt{layout\_defaults} but
no chapter defined \texttt{CanvasLayoutDefaults}.
\subsection{Regions}
\begin{lstlisting}[language=Rust]
@ -4254,6 +4307,12 @@ pub struct Region {
/// Optional tempo and tuning overrides scoped to this region.
pub local_tempo_map: Option<TempoMap>,
pub local_tuning_overrides: Vec<TuningOverride>,
/// Whether spanners (slurs first of all) may cross this region's
/// boundary into an adjacent region. Default false: cross-region
/// spanning is not permitted unless a region opts in. Added with
/// schema major 1.
pub permits_spanning_slurs: bool,
}
pub enum RegionContent {
@ -10452,9 +10511,17 @@ pub struct SchemaVersion {
\item Minor version changes are backward-compatible. A reader
supporting major version $N$ \MUST{} accept any minor version
$N.\textit{m}$ for $m \leq$ the reader's supported minor.
Minor version changes \MUST{} only add optional fields or
enumeration variants in a way preserving the wire format of
prior values.
Under the positional, frozen-layout wire form, a minor change
\MUST{} only \emph{append discriminants to the append-safe
vocabularies the Binary Format companion defines} (its open
\texttt{Registered} vocabularies, plus ratified appends to
\texttt{OperationKind}, \texttt{OperationKindTag},
\texttt{OperationPayload}, and the closed value-layer unions), which
leaves the bytes of every prior value untouched; it \MUSTNOT{} add or
reorder a struct field --- adding a field, even an \texttt{Option},
occupies a new positional slot and shifts subsequent bytes, so it is a
\emph{major} change (Binary Format companion,
\sectionsc{What ``Additive'' Means Here}).
\item The manifest's \texttt{manifest\_schema\_version} declares
the schema of the manifest itself. Each chunk's
\texttt{schema\_version} field declares the schema of that
@ -10465,6 +10532,26 @@ pub struct SchemaVersion {
\end{itemize}
\end{requirement}
Schema major~1 is the first data-model expansion major: it adds
\texttt{Canvas.layout\_defaults}, \texttt{Instrument.range}, and
\texttt{Region.permits\_spanning\_slurs} to the graph, and unifies the
non-canonical resolved-layout length-prefix width. Its wire form and the
byte-for-byte migration from major~0 are defined in the Binary Format
companion. The canonical-base \texttt{MaterializedState} embeds none of these
values, so it is byte-identical across the bump and stays major~0.
\texttt{Canvas.layout\_defaults} and \texttt{Instrument.range} reach only the
non-canonical acceleration (full-\texttt{Score}) snapshot; but
\texttt{Region.permits\_spanning\_slurs} also reaches the \emph{canonical}
operation layer, because \texttt{CreateRegion} embeds a full \texttt{Region},
so an operation-envelope block bearing a v1 \texttt{CreateRegion} is major~1.
Consequently a major-0-only reader opens a major-1 bundle \emph{fully} only
when the bundle carries no v1 \texttt{CreateRegion} operation; otherwise it
reads the major-0 canonical base and manifest but opens read-only (it cannot
replay the v1 canonical operations), per the canonical/non-canonical rules
above. It always discards the higher-major non-canonical caches. A major-1
reader migrates a major-0 acceleration snapshot and a major-0
\texttt{CreateRegion} payload on read (default-filling the new fields).
\section{Format Profiles}
\label{sec:format:profiles}
@ -14578,6 +14665,23 @@ layouts they own versus inherit:
score/canvas slots remain deliberately unavailable pending an
addressable root model (P12-K8).
\\
\today & Schema major 1 (data-model expansion) &
Defines the two referenced-but-undefined types
\texttt{CanvasLayoutDefaults} (with \texttt{CanvasSize} /
\texttt{CanvasMargins}, staff-space page geometry, A4/8\,mm default;
closes P12-I7) and \texttt{PitchRange} (advisory pitch compass, used by
\texttt{Instrument.range} and \texttt{IndeterminacyHints}); adds
\texttt{Region.permits\_spanning\_slurs} (default false, P12-K7). Records
that schema major~1 is the first data-model expansion major: the
canonical-base \texttt{MaterializedState} embeds none of these values and
stays major~0, byte-identical, but \texttt{Region.permits\_spanning\_slurs}
reaches the \emph{canonical} \texttt{CreateRegion} operation payload, so a
major-0 reader opens a bundle carrying v1 \texttt{CreateRegion} ops
read-only. The wire form, the resolved-layout length-prefix unification
(the manifest-embedded barrier blobs stay \texttt{u64}), and the
byte-for-byte v0${\to}$v1 migration are ratified in the Binary Format
companion (0.2.0 $\rightarrow$ 0.3.0).
\\
\bottomrule
\end{longtable}

Binary file not shown.

View File

@ -515,11 +515,12 @@ assembles its \texttt{SolveReport}:
\item the solve's constrained input $C$ (a \texttt{ConstrainedLayoutIR}:
horizontal spring slots, vertical bands, declared constraints);
\item the declared page geometry the solve was configured with: the
content width $W$ and content height $H$, in staff spaces. (The score
graph has no home for page geometry yet --- the core names
\texttt{Canvas.layout\_defaults} without defining it, tracked as
Pass-12 row P12-I7 --- so the geometry is a solver parameter, and the
Reference Suite companion requires each suite entry to declare it.)
content width $W$ and content height $H$, in staff spaces. (Schema
major~1 defines \texttt{Canvas.layout\_defaults} and its type
\texttt{CanvasLayoutDefaults}, P12-I7; the reference implementation's
code graph home lands in a later phase, so until then the geometry is a
solver parameter, and the Reference Suite companion requires each suite
entry to declare it.)
\end{enumerate}
Notation used throughout Chapter~\ref{ch:metrics}:

Binary file not shown.

View File

@ -410,10 +410,10 @@ under:
documented default --- A4 portrait at an 8\,mm staff height: page
$105 \times 148.5$ staff spaces, margins $7.5$ staff spaces on all
four sides, hence a content area of $90 \times 133.5$ staff spaces.
(The score graph has no home for page geometry yet: the core names
\texttt{Canvas.layout\_defaults} without defining a type, tracked as
Pass-12 row P12-I7, so geometry is declared per entry as a solver
parameter.)
(Schema major~1 defines \texttt{Canvas.layout\_defaults} and its type
\texttt{CanvasLayoutDefaults} (P12-I7); the reference implementation's
code graph home lands in a later phase, so until then geometry is
declared per entry as a solver parameter.)
\item the \textbf{solver configuration}: the \texttt{SolverConfig}
fields. In v0.1 every entry uses the default configuration --- the
\texttt{Standard} profile, an unbounded deterministic budget, and