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

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-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 | `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-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-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) | | 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\scshape\color{epiphanyslate}Binary Format}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt] {\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt] {\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} {\small\color{epiphanyslate}Normative for the byte layouts it defines}
\vfill \vfill
\end{titlepage} \end{titlepage}
@ -395,7 +395,10 @@ which.
\texttt{ResolvedLayoutIR} canonical output \texttt{ResolvedLayoutIR} canonical output
(Chapter~\ref{ch:noncanon}). & (Chapter~\ref{ch:noncanon}). &
Little-endian integers; \texttt{u64} little-endian counts and length 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, (c) & Identifiers and hash-derived forms: the 128-bit identifier family,
\texttt{ReplicaId}, \texttt{OperationId}, \texttt{ConflictId}, the \texttt{ReplicaId}, \texttt{OperationId}, \texttt{ConflictId}, the
\texttt{TypedObjectId} discriminant, digest truncations \texttt{TypedObjectId} discriminant, digest truncations
@ -406,14 +409,20 @@ which.
\end{longtable} \end{longtable}
\begin{rationale} \begin{rationale}
Regimes (a) and (b) differ only in prefix width, and the divergence is In schema major~0, regimes (a) and (b) differ only in prefix width, and the
\emph{tolerated rather than repaired}: the two surfaces were golden-locked divergence was \emph{tolerated rather than repaired}: the two surfaces were
independently (the barrier blobs and the resolved-layout output carry their golden-locked independently (the barrier blobs and the resolved-layout output
own literal-byte anchors), they never embed one another's framing, and carry their own literal-byte anchors), they never embed one another's framing,
unifying the prefix width now would break locked bytes for zero benefit. The and unifying the prefix width within major~0 would break locked bytes for zero
divergence is recorded as an open question for the next schema-major revision benefit. Once a major bump was scheduled for data-model reasons, part of the
(Chapter~\ref{ch:evolution}). Regime (c) is not a divergence at all: an unification became free to carry: schema major~1 moves the resolved-layout
identifier's byte form \emph{is} its sort key, so big-endian is load-bearing. 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} \end{rationale}
\section{Primitive Composition Rules} \section{Primitive Composition Rules}
@ -2078,8 +2087,13 @@ conservative treatment of unknown extensions --- are core Chapter~8
bytes only. bytes only.
These layouts are \textbf{regime (b)}: all counts and length prefixes are These layouts are \textbf{regime (b)}: all counts and length prefixes are
\texttt{u64} little-endian --- a deliberate, golden-locked divergence from \texttt{u64} little-endian --- a deliberate, golden-locked divergence from the
the \texttt{u32} regime (Section~\ref{sec:conventions:regimes}). \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} \section{Framing}
\label{sec:barriers: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 Chapter~8, \sectionsc{Schema Versioning}: \emph{``Schema evolution is
governed by the Binary Format companion specification, which defines the governed by the Binary Format companion specification, which defines the
wire encoding for each schema version''}). This chapter defines the wire 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} \section{The Chunk-Level Gate}
\label{sec:evolution:gate} \label{sec:evolution:gate}
@ -2212,7 +2228,13 @@ Every chunk declares a \texttt{SchemaVersion} (major, minor) in its
\begin{itemize} \begin{itemize}
\item \textbf{Major} = incompatible. A reader \MUST{} reject a chunk whose \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 \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: 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 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 decoder rejects trailing bytes, so manifest-body extension is
major-gated in v0 exactly like every other struct. 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 The \emph{only} minor-additive mechanism in schema major~0 is
\textbf{appending discriminants to open vocabularies}: \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 because it was semantic rather than byte-level
(Section~\ref{sec:ops:v0}); byte-level migrations belong here. (Section~\ref{sec:ops:v0}); byte-level migrations belong here.
\begin{openquestion} The \texttt{u64}/\texttt{u32} length-prefix divergence between regimes (a) and
Whether the \texttt{u64}/\texttt{u32} length-prefix divergence between (b) (Section~\ref{sec:conventions:regimes}), left open in earlier revisions for
regimes (a) and (b) (Section~\ref{sec:conventions:regimes}) gets unified at a future major, is \textbf{partly resolved in schema major~1}
the next schema major. Unification would simplify decoders and cost one (Section~\ref{sec:evolution:major1}): the first major bump, scheduled for
coordinated re-lock of the barrier-blob and resolved-layout goldens; data-model reasons, carries the unification for the independent non-canonical
leaving it is free but permanent. Decide when the first major bump is resolved-layout surface. The barrier/extension blobs remain regime~(b) because
scheduled for other reasons --- the divergence alone does not justify one. they ride the canonical manifest, which this bump keeps at major~0; their
\end{openquestion} 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} \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 The resolved layout's canonical bytes are the \textbf{byte-equal conformance
surface} of core Chapter~7: two conforming engravers given the same score, surface} of core Chapter~7: two conforming engravers given the same score,
profile, and glyph catalog must produce identical bytes. The encoding is profile, and glyph catalog must produce identical bytes. In schema major~0 the
regime (b) --- \texttt{u64} LE counts and length prefixes --- with every encoding is regime (b) (\texttt{u64} LE counts and length prefixes); schema
geometric coordinate quantized to the $1/1024$ staff-space grid as a 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}); \texttt{QuantizedCoord} (8 LE bytes; Chapter~\ref{ch:primitives});
a non-finite or out-of-range coordinate is a determinism violation and a non-finite or out-of-range coordinate is a determinism violation and
\MUST{} be rejected, never normalized. The top-level section order is: \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}, assignment changed. Semantics: Operation Catalog 0.5.0 (\sectionsc{CreateStaff},
\sectionsc{Meter and Tempo Overwrites}, \sectionsc{SetStaffLayout}, and the \sectionsc{Meter and Tempo Overwrites}, \sectionsc{SetStaffLayout}, and the
value-restoring \sectionsc{UndoTransaction} revision). \\ 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 \bottomrule
\end{longtable} \end{longtable}

Binary file not shown.

View File

@ -883,6 +883,27 @@ the other in general.
storage makes the pitch self-describing. storage makes the pitch self-describing.
\end{rationale} \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} \section{Scale Position}
\label{sec:pitch:scale-position} \label{sec:pitch:scale-position}
@ -4228,8 +4249,40 @@ pub struct Canvas {
/// system layout, page-break behavior. /// system layout, page-break behavior.
pub layout_defaults: CanvasLayoutDefaults, 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} \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} \subsection{Regions}
\begin{lstlisting}[language=Rust] \begin{lstlisting}[language=Rust]
@ -4254,6 +4307,12 @@ pub struct Region {
/// Optional tempo and tuning overrides scoped to this region. /// Optional tempo and tuning overrides scoped to this region.
pub local_tempo_map: Option<TempoMap>, pub local_tempo_map: Option<TempoMap>,
pub local_tuning_overrides: Vec<TuningOverride>, 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 { pub enum RegionContent {
@ -10452,9 +10511,17 @@ pub struct SchemaVersion {
\item Minor version changes are backward-compatible. A reader \item Minor version changes are backward-compatible. A reader
supporting major version $N$ \MUST{} accept any minor version supporting major version $N$ \MUST{} accept any minor version
$N.\textit{m}$ for $m \leq$ the reader's supported minor. $N.\textit{m}$ for $m \leq$ the reader's supported minor.
Minor version changes \MUST{} only add optional fields or Under the positional, frozen-layout wire form, a minor change
enumeration variants in a way preserving the wire format of \MUST{} only \emph{append discriminants to the append-safe
prior values. 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 \item The manifest's \texttt{manifest\_schema\_version} declares
the schema of the manifest itself. Each chunk's the schema of the manifest itself. Each chunk's
\texttt{schema\_version} field declares the schema of that \texttt{schema\_version} field declares the schema of that
@ -10465,6 +10532,26 @@ pub struct SchemaVersion {
\end{itemize} \end{itemize}
\end{requirement} \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} \section{Format Profiles}
\label{sec:format:profiles} \label{sec:format:profiles}
@ -14578,6 +14665,23 @@ layouts they own versus inherit:
score/canvas slots remain deliberately unavailable pending an score/canvas slots remain deliberately unavailable pending an
addressable root model (P12-K8). 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 \bottomrule
\end{longtable} \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}: \item the solve's constrained input $C$ (a \texttt{ConstrainedLayoutIR}:
horizontal spring slots, vertical bands, declared constraints); horizontal spring slots, vertical bands, declared constraints);
\item the declared page geometry the solve was configured with: the \item the declared page geometry the solve was configured with: the
content width $W$ and content height $H$, in staff spaces. (The score content width $W$ and content height $H$, in staff spaces. (Schema
graph has no home for page geometry yet --- the core names major~1 defines \texttt{Canvas.layout\_defaults} and its type
\texttt{Canvas.layout\_defaults} without defining it, tracked as \texttt{CanvasLayoutDefaults}, P12-I7; the reference implementation's
Pass-12 row P12-I7 --- so the geometry is a solver parameter, and the code graph home lands in a later phase, so until then the geometry is a
Reference Suite companion requires each suite entry to declare it.) solver parameter, and the Reference Suite companion requires each suite
entry to declare it.)
\end{enumerate} \end{enumerate}
Notation used throughout Chapter~\ref{ch:metrics}: 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 documented default --- A4 portrait at an 8\,mm staff height: page
$105 \times 148.5$ staff spaces, margins $7.5$ staff spaces on all $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. 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 (Schema major~1 defines \texttt{Canvas.layout\_defaults} and its type
\texttt{Canvas.layout\_defaults} without defining a type, tracked as \texttt{CanvasLayoutDefaults} (P12-I7); the reference implementation's
Pass-12 row P12-I7, so geometry is declared per entry as a solver code graph home lands in a later phase, so until then geometry is
parameter.) declared per entry as a solver parameter.)
\item the \textbf{solver configuration}: the \texttt{SolverConfig} \item the \textbf{solver configuration}: the \texttt{SolverConfig}
fields. In v0.1 every entry uses the default configuration --- the fields. In v0.1 every entry uses the default configuration --- the
\texttt{Standard} profile, an unbounded deterministic budget, and \texttt{Standard} profile, an unbounded deterministic budget, and