3298 lines
160 KiB
TeX
3298 lines
160 KiB
TeX
% !TEX program = xelatex
|
|
%
|
|
% Epiphany --- Binary Format (companion specification)
|
|
% Companion to the Core Specification. Compile with XeLaTeX.
|
|
%
|
|
% This document is versioned independently of the Core Specification
|
|
% (independent semver; see the Versioning note in the front matter). Its preamble
|
|
% is intentionally a self-contained copy of the core specification's preamble so
|
|
% the two documents build independently; factoring a shared preamble file is a
|
|
% later cleanup, not a v0.1 deliverable.
|
|
|
|
\documentclass[11pt,letterpaper]{report}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Packages
|
|
% ---------------------------------------------------------------------------
|
|
\usepackage{fontspec}
|
|
\usepackage{geometry}
|
|
\geometry{
|
|
letterpaper,
|
|
top=1.05in,
|
|
bottom=1.05in,
|
|
left=1.15in,
|
|
right=1.15in,
|
|
headheight=15pt
|
|
}
|
|
|
|
\usepackage[english]{babel}
|
|
\usepackage{microtype}
|
|
\usepackage{parskip}
|
|
\usepackage{xcolor}
|
|
\usepackage{hyperref}
|
|
\usepackage{enumitem}
|
|
\usepackage{titlesec}
|
|
\usepackage{fancyhdr}
|
|
\usepackage{booktabs}
|
|
\usepackage{array}
|
|
\usepackage{longtable}
|
|
\usepackage{listings}
|
|
\usepackage{amsmath}
|
|
\usepackage{amssymb}
|
|
\usepackage{tcolorbox}
|
|
\tcbuselibrary{breakable, skins}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Color palette (shared with the core specification)
|
|
% ---------------------------------------------------------------------------
|
|
\definecolor{epiphanyteal}{HTML}{1A4044}
|
|
\definecolor{epiphanygold}{HTML}{8E6E2E}
|
|
\definecolor{epiphanyink}{HTML}{1F1B16}
|
|
\definecolor{epiphanyslate}{HTML}{6B6660}
|
|
\definecolor{epiphanycream}{HTML}{F8F4ED}
|
|
\definecolor{epiphanymist}{HTML}{ECE8E0}
|
|
\definecolor{epiphanycode}{HTML}{2A2520}
|
|
\definecolor{epiphanycrimson}{HTML}{7A2424}
|
|
|
|
\hypersetup{
|
|
colorlinks=true,
|
|
linkcolor=epiphanyteal,
|
|
citecolor=epiphanyteal,
|
|
urlcolor=epiphanygold,
|
|
pdftitle={Epiphany --- Binary Format},
|
|
pdfauthor={The Epiphany Project},
|
|
pdfsubject={Binary Format companion for the Epiphany music notation platform},
|
|
pdfkeywords={music notation, binary format, serialization, wire format, canonical encoding},
|
|
bookmarksnumbered=true,
|
|
bookmarksopen=true
|
|
}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Typography (shared with the core specification)
|
|
% ---------------------------------------------------------------------------
|
|
\setmainfont{TeX Gyre Pagella}[Numbers={OldStyle, Proportional}, Ligatures={TeX, Common}]
|
|
\setsansfont{TeX Gyre Heros}[Scale=0.94, Ligatures={TeX, Common}]
|
|
\setmonofont{TeX Gyre Cursor}[Scale=0.88, Ligatures={TeX}]
|
|
\newfontfamily\titlefont{TeX Gyre Pagella}[Numbers={OldStyle}, Ligatures={TeX, Common}]
|
|
\newcommand{\tablenums}[1]{{\addfontfeatures{Numbers={Lining,Tabular}}#1}}
|
|
\newcommand{\sectionsc}[1]{{\addfontfeatures{Letters=SmallCaps}#1}}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Section styling (shared with the core specification)
|
|
% ---------------------------------------------------------------------------
|
|
\titleformat{\chapter}[display]
|
|
{\normalfont\filright}
|
|
{\raggedright\color{epiphanygold}\fontsize{14pt}{16pt}\selectfont
|
|
\scshape Chapter\ \thechapter}
|
|
{16pt}
|
|
{\raggedright\color{epiphanyteal}\fontsize{32pt}{36pt}\selectfont\bfseries}
|
|
[\vspace{4pt}{\color{epiphanygold}\rule{2in}{0.6pt}}]
|
|
\titlespacing*{\chapter}{0pt}{-20pt}{30pt}
|
|
\titleformat{\section}
|
|
{\normalfont\Large\bfseries\color{epiphanyteal}}
|
|
{\color{epiphanygold}\thesection}{1em}{}
|
|
\titleformat{\subsection}
|
|
{\normalfont\large\bfseries\color{epiphanyteal}}
|
|
{\color{epiphanygold}\thesubsection}{1em}{}
|
|
\titleformat{\subsubsection}
|
|
{\normalfont\normalsize\bfseries\color{epiphanyink}}
|
|
{\thesubsubsection}{1em}{}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Headers and footers (shared with the core specification)
|
|
% ---------------------------------------------------------------------------
|
|
\pagestyle{fancy}
|
|
\fancyhf{}
|
|
\renewcommand{\headrulewidth}{0pt}
|
|
\renewcommand{\footrulewidth}{0pt}
|
|
\fancyhead[L]{\small\scshape\color{epiphanyslate}Epiphany --- Binary Format}
|
|
\fancyhead[R]{\small\itshape\color{epiphanyslate}\leftmark}
|
|
\fancyfoot[C]{\small\color{epiphanyslate}\thepage}
|
|
\renewcommand{\headrule}{
|
|
\color{epiphanygold!50}\hrule width\headwidth height 0.4pt
|
|
\vspace{1pt}
|
|
\color{epiphanygold!30}\hrule width\headwidth height 0.2pt
|
|
}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Code listing style (shared with the core specification)
|
|
% ---------------------------------------------------------------------------
|
|
\lstdefinelanguage{Rust}{
|
|
keywords={fn,let,mut,pub,struct,enum,impl,trait,for,in,if,else,match,return,
|
|
use,mod,crate,self,Self,as,where,move,async,await,const,static,
|
|
ref,type,unsafe,extern,dyn,box,break,continue,loop,while},
|
|
keywordstyle=\color{epiphanyteal}\bfseries,
|
|
ndkeywords={i8,i16,i32,i64,i128,u8,u16,u32,u64,u128,f32,f64,bool,char,str,
|
|
String,Vec,Option,Result,Box,Rc,Arc,HashMap,BTreeMap,
|
|
NonZeroU16,NonZeroU32,NonZeroU64,Duration,Timestamp},
|
|
ndkeywordstyle=\color{epiphanygold}\bfseries,
|
|
sensitive=true,
|
|
comment=[l]{//},
|
|
morecomment=[s]{/*}{*/},
|
|
commentstyle=\color{epiphanyslate}\itshape,
|
|
stringstyle=\color{epiphanycrimson},
|
|
morestring=[b]",
|
|
morestring=[b]'
|
|
}
|
|
\lstset{
|
|
basicstyle=\ttfamily\small\color{epiphanycode},
|
|
backgroundcolor=\color{epiphanycream},
|
|
frame=leftline,
|
|
rulecolor=\color{epiphanygold!60},
|
|
framesep=8pt,
|
|
framerule=1.5pt,
|
|
xleftmargin=10pt,
|
|
xrightmargin=4pt,
|
|
breaklines=true,
|
|
showstringspaces=false,
|
|
numberstyle=\tiny\color{epiphanyslate},
|
|
numbersep=10pt,
|
|
captionpos=b,
|
|
aboveskip=10pt,
|
|
belowskip=10pt,
|
|
language=Rust
|
|
}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Custom environments (shared with the core specification)
|
|
% ---------------------------------------------------------------------------
|
|
\newtcolorbox{openquestion}[1][]{
|
|
enhanced, breakable,
|
|
colback=epiphanymist, colframe=epiphanycrimson,
|
|
fonttitle=\bfseries\color{white}, title={\scshape\hspace{2pt}Open Question},
|
|
coltitle=white, colbacktitle=epiphanycrimson,
|
|
arc=1pt, boxrule=0pt, leftrule=2pt,
|
|
left=10pt, right=10pt, top=8pt, bottom=8pt,
|
|
attach boxed title to top left={xshift=0pt, yshift=0pt},
|
|
boxed title style={arc=0pt, sharp corners, boxrule=0pt, left=6pt, right=8pt, top=2pt, bottom=2pt},
|
|
#1
|
|
}
|
|
\newtcolorbox{rationale}[1][]{
|
|
enhanced, breakable,
|
|
colback=epiphanymist, colframe=epiphanyteal,
|
|
fonttitle=\bfseries\color{white}, title={\scshape\hspace{2pt}Rationale},
|
|
coltitle=white, colbacktitle=epiphanyteal,
|
|
arc=1pt, boxrule=0pt, leftrule=2pt,
|
|
left=10pt, right=10pt, top=8pt, bottom=8pt,
|
|
attach boxed title to top left={xshift=0pt, yshift=0pt},
|
|
boxed title style={arc=0pt, sharp corners, boxrule=0pt, left=6pt, right=8pt, top=2pt, bottom=2pt},
|
|
#1
|
|
}
|
|
\newtcolorbox{requirement}[1][]{
|
|
enhanced, breakable,
|
|
colback=white, colframe=epiphanygold,
|
|
fonttitle=\bfseries\color{white}, title={\scshape\hspace{2pt}Requirement},
|
|
coltitle=white, colbacktitle=epiphanygold,
|
|
arc=1pt, boxrule=0pt, leftrule=2pt,
|
|
left=10pt, right=10pt, top=8pt, bottom=8pt,
|
|
attach boxed title to top left={xshift=0pt, yshift=0pt},
|
|
boxed title style={arc=0pt, sharp corners, boxrule=0pt, left=6pt, right=8pt, top=2pt, bottom=2pt},
|
|
#1
|
|
}
|
|
\newtcolorbox{nongoal}[1][]{
|
|
enhanced, breakable,
|
|
colback=epiphanymist, colframe=epiphanyslate,
|
|
fonttitle=\bfseries\color{white}, title={\scshape\hspace{2pt}Non-Goal},
|
|
coltitle=white, colbacktitle=epiphanyslate,
|
|
arc=1pt, boxrule=0pt, leftrule=2pt,
|
|
left=10pt, right=10pt, top=8pt, bottom=8pt,
|
|
attach boxed title to top left={xshift=0pt, yshift=0pt},
|
|
boxed title style={arc=0pt, sharp corners, boxrule=0pt, left=6pt, right=8pt, top=2pt, bottom=2pt},
|
|
#1
|
|
}
|
|
|
|
\newcommand{\MUST}{\textbf{MUST}}
|
|
\newcommand{\MUSTNOT}{\textbf{MUST}\nobreak\ \textbf{NOT}}
|
|
\newcommand{\SHOULD}{\textbf{SHOULD}}
|
|
\newcommand{\SHOULDNOT}{\textbf{SHOULD}\nobreak\ \textbf{NOT}}
|
|
\newcommand{\MAY}{\textbf{MAY}}
|
|
|
|
% Byte concatenation within a wire layout.
|
|
\newcommand{\cat}{\ensuremath{\,\Vert\,}}
|
|
% A breakable underscore for long monospace anchor names.
|
|
\newcommand{\ub}{\_\allowbreak}
|
|
% Long monospace tokens (requirement names, test paths) cannot hyphenate;
|
|
% allow loose lines instead of overfull ones.
|
|
\emergencystretch=3em
|
|
\tolerance=2000
|
|
|
|
\setlist[itemize]{topsep=2pt, itemsep=3pt, parsep=0pt}
|
|
\setlist[enumerate]{topsep=2pt, itemsep=3pt, parsep=0pt}
|
|
\setlist[description]{topsep=2pt, itemsep=5pt, parsep=0pt}
|
|
\AtBeginDocument{\color{epiphanyink}}
|
|
|
|
% ---------------------------------------------------------------------------
|
|
% Document
|
|
% ---------------------------------------------------------------------------
|
|
\begin{document}
|
|
|
|
\begin{titlepage}
|
|
\thispagestyle{empty}
|
|
\centering
|
|
\vspace*{2.2in}
|
|
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[18pt]
|
|
{\titlefont\fontsize{34pt}{38pt}\selectfont\color{epiphanyteal}\bfseries Epiphany}\\[10pt]
|
|
{\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.9.0 --- The decode vector corpus is ratified (the cross-implementation decoder test is no longer deferred)}\\[4pt]
|
|
{\small\color{epiphanyslate}Normative for the byte layouts it defines}
|
|
\vfill
|
|
\end{titlepage}
|
|
|
|
\tableofcontents
|
|
|
|
% ===========================================================================
|
|
\chapter{About This Companion}
|
|
\label{ch:about}
|
|
|
|
The \emph{Binary Format} document is a companion to the Epiphany Core
|
|
Specification. It fulfils the core specification's delegation in Chapter~8,
|
|
\sectionsc{Binary Format Companion} (the \texttt{sec:format:binary} section):
|
|
the byte-level encoding --- integer conventions, exact field widths, record
|
|
alignment, endianness, string encoding details, struct layouts --- is delivered
|
|
here. In the core specification's words: \emph{``The Binary Format document is
|
|
normative; an implementation cannot conform to the file format specification
|
|
without conforming to the Binary Format specification.''}
|
|
|
|
This release (v0.1.0) delivers the full \textbf{K-independent} encoding
|
|
surface --- identifier and derivation layouts, primitive value encodings, the
|
|
whole-\texttt{Score} composite value codec, the operation-layer wire forms,
|
|
the bundle physical layout, and the extension-declaration blob forms ---
|
|
\emph{plus} the \textbf{K0/M2 operation-payload wire framing}. The Operation
|
|
Catalog's K0 set (the representative primitives and the M2 broad-K0 groups,
|
|
catalog version 0.4.0) now exists in ratified form, so the historical ``waits
|
|
for Agent~K'' clause in the Phase-2 charter is discharged: every operation
|
|
kind the catalog defines has its literal wire form pinned in
|
|
Chapter~\ref{ch:ops}.
|
|
|
|
This document does \emph{not} cover:
|
|
|
|
\begin{itemize}
|
|
\item the canonical s-expression form --- that is the \emph{Text Projection}
|
|
companion's (version~0.1.0, which now exists);
|
|
\item per-profile feature lists --- the \emph{Profile Conformance}
|
|
companion's;
|
|
\item the Chapter-4 tuning-catalog values (pitch-space and tuning-system
|
|
registries), which are undelivered Track-C work and have no wire form yet;
|
|
\item the wire-format fuzzer, an \emph{implementation} deliverable tracked
|
|
in the Phase-2 process documents rather than part of this document's
|
|
normative text; Chapter~\ref{ch:goldens} records the golden anchors any
|
|
such harness must reproduce.
|
|
\end{itemize}
|
|
|
|
It \emph{does} cover the cross-implementation decoder test, which is no longer
|
|
deferred: Section~\ref{sec:goldens:decode-vectors} ratifies the literal-byte
|
|
vector corpus and the accept/reject verdict a conforming decoder owes it.
|
|
|
|
\section{Relationship to the Core Specification and the Operation Catalog}
|
|
\label{sec:about:relationship}
|
|
|
|
This companion \emph{inherits} --- it never re-derives --- the core
|
|
specification's ratified convention baseline: core specification Chapter~8,
|
|
\sectionsc{Binary Format Companion}, requirement
|
|
\texttt{req:format:codec-conventions}, together with the
|
|
\sectionsc{Canonical Byte-Layout Reference} appendix (\texttt{app:bytes}).
|
|
That appendix consolidates every discriminant table, derivation preimage, and
|
|
primitive encoding Pass~11 and Pass~12 ratified, precisely so that this
|
|
document could import it as a starting point rather than recovering the
|
|
layouts from the crates. Chapter~\ref{ch:ids} therefore reproduces only
|
|
\emph{compact} normative tables, each with a citation back to the appendix
|
|
row and requirement that governs it; where this document and a ratified core
|
|
requirement disagree, \textbf{the core requirement governs} and the
|
|
discrepancy is a defect in this document.
|
|
|
|
What this document adds is the completion of the appendix's deferred list
|
|
(core specification, \sectionsc{Layouts deferred to the companions},
|
|
\texttt{sec:bytes:deferred}): the \texttt{OperationKind} and
|
|
\texttt{OperationKindTag} discriminants and full per-operation payload
|
|
encodings; the full composite struct layouts (whole-\texttt{Score}, operation
|
|
envelopes, the manifest body's field order); schema-version wire evolution;
|
|
and the varint question, which Chapter~\ref{ch:conventions} settles.
|
|
|
|
The division of labour with the \emph{Operation Catalog} is exact: the
|
|
catalog fixes each operation's \textbf{field set and order} (Operation
|
|
Catalog, \sectionsc{Per-Primitive Schema Template}) and its semantics; this
|
|
document pins the \textbf{bytes} of those fields. Chapter~\ref{ch:ops} cites
|
|
the catalog section for every payload rather than restating what the fields
|
|
mean. Likewise, graph \emph{types} are the core specification's Chapters 2--5,
|
|
and the structural layer of the bundle --- what a header, superblock, chunk
|
|
graph, or manifest \emph{is} and how it behaves --- is core Chapter~8; this
|
|
document specifies only how those structures' bits are laid out.
|
|
|
|
\begin{rationale}
|
|
\textbf{Versioning.} This companion is versioned independently of the core
|
|
specification (independent semver), like the Operation Catalog --- but tied
|
|
tighter to it: the binary-format \textsc{major} version tracks the core
|
|
specification's major version (both are pre-1.0 working drafts today, hence
|
|
v0.1.0), while \textsc{minor} versions diverge freely. Byte layouts change on
|
|
a slower, more deliberate cadence than prose; a core-spec minor revision that
|
|
touches no wire fact requires no revision here, and an additive discriminant
|
|
append here (Chapter~\ref{ch:evolution}) requires no core-spec revision.
|
|
\end{rationale}
|
|
|
|
\section{Conformance}
|
|
\label{sec:about:conformance}
|
|
|
|
The byte layouts in this document are \textbf{normative}. An implementation
|
|
conforms to the Epiphany binary format if and only if, for every layout this
|
|
document defines, it produces exactly the specified bytes when encoding and
|
|
accepts exactly the specified language of byte strings when decoding.
|
|
|
|
Decode discipline is \textbf{reject, never normalize} --- the workspace-wide
|
|
convention the core specification establishes in Appendix~D (canonical
|
|
serialization determinism and ordered iteration). Concretely, each of the
|
|
following is a \emph{decode error}, never an input to be repaired:
|
|
|
|
\begin{itemize}
|
|
\item an unknown discriminant in any tagged union;
|
|
\item a set- or map-valued field whose elements are not in strictly
|
|
ascending canonical order, or which contains duplicates;
|
|
\item text that is not in Unicode NFC where NFC is required (catalog
|
|
identifiers, barrier pitch-space names, operation labels);
|
|
\item trailing bytes after a complete top-level value
|
|
(Requirement~\ref{req:binfmt:trailing-bytes});
|
|
\item recursion beyond a normative depth bound
|
|
(Requirement~\ref{req:binfmt:condition-depth}).
|
|
\end{itemize}
|
|
|
|
A decoder \MUSTNOT{} silently sort, deduplicate, NFC-fold, truncate, or
|
|
otherwise canonicalize non-canonical input: two implementations that disagree
|
|
on repair would disagree on canonical state.
|
|
|
|
The \textbf{golden anchor registry} (Chapter~\ref{ch:goldens}) is the
|
|
conformance contract binding this text to the reference implementation: every
|
|
layout in this document is anchored by at least one named test in the
|
|
reference crates, and a change that breaks an anchor is either a defect or a
|
|
deliberate, versioned format revision (Chapter~\ref{ch:evolution}).
|
|
|
|
% ===========================================================================
|
|
\chapter{Encoding Conventions}
|
|
\label{ch:conventions}
|
|
|
|
This chapter restates the ratified convention baseline (core specification,
|
|
requirement \texttt{req:format:codec-conventions}) as normative fact and
|
|
completes it: the three prefix/endianness regimes, the primitive composition
|
|
rules, the varint disposition, and the trailing-bytes rule.
|
|
|
|
\section{The Three Encoding Regimes}
|
|
\label{sec:conventions:regimes}
|
|
|
|
Three internally consistent regimes coexist in the format. Every layout in
|
|
this document belongs to exactly one of them, and each layout's chapter says
|
|
which.
|
|
|
|
\begin{longtable}{p{0.55in} p{2.25in} p{2.85in}}
|
|
\toprule
|
|
\textbf{Regime} & \textbf{Surfaces} & \textbf{Rules} \\
|
|
\midrule
|
|
\endhead
|
|
(a) & Canonical document encodings: the core value codec
|
|
(Chapter~\ref{ch:values}), the operation layer
|
|
(Chapter~\ref{ch:ops}), the bundle codec
|
|
(Chapter~\ref{ch:bundle}). &
|
|
Little-endian fixed-width integers; \texttt{u32} little-endian counts
|
|
and length prefixes on every variable-width field. \\
|
|
(b) & Layout-side pinned encodings: the extension-declaration blobs and
|
|
edit-barrier trees (Chapter~\ref{ch:barriers}) and the
|
|
\texttt{ResolvedLayoutIR} canonical output
|
|
(Chapter~\ref{ch:noncanon}). &
|
|
Little-endian integers; \texttt{u64} little-endian counts and length
|
|
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
|
|
(Chapter~\ref{ch:ids}). &
|
|
Big-endian fixed width. Numeric order equals canonical byte order,
|
|
which is what Appendix~D's ordered iteration requires. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\begin{rationale}
|
|
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}
|
|
\label{sec:conventions:primitives}
|
|
|
|
The following rules apply throughout regime (a); regimes (b) and (c) state
|
|
their deltas in their own chapters.
|
|
|
|
\begin{description}
|
|
\item[Booleans.] A single byte: \texttt{0} = false, \texttt{1} = true. Any
|
|
other value is a decode error.
|
|
\item[Integers.] Fixed-width little-endian two's complement
|
|
(\texttt{u8}/\texttt{i8} through \texttt{u128}), except within regime (c).
|
|
\item[\texttt{Option<T>}.] A presence byte: \texttt{0} = absent (no further
|
|
bytes), \texttt{1} = present, followed by \texttt{T}'s encoding. A
|
|
presence byte greater than~1 is a decode error.
|
|
\item[Sequences, sets, maps.] A \texttt{u32} little-endian element count,
|
|
then each element (for maps: key then value). Set- and map-valued fields
|
|
are emitted in canonical iteration order --- ascending by the key's
|
|
canonical byte form --- and decoders reject out-of-order or duplicated
|
|
elements wherever the field is a set or map (as opposed to an
|
|
order-significant list).
|
|
\item[Free text.] A \texttt{u32} little-endian byte length, then the raw
|
|
UTF-8 bytes. Free-text fields (e.g.\ a score title) are \textbf{never}
|
|
NFC-folded by the codec: $\mathrm{decode}(\mathrm{encode}(x)) = x$ is the
|
|
round-trip identity, and folding would break it. By contrast,
|
|
\emph{catalog identifiers} (pitch-space ids, tuning-system ids, and the
|
|
other \texttt{catalog\_id} newtypes) are NFC-normalized \emph{at
|
|
construction}, so their stored form is already NFC; the codec encodes the
|
|
stored string verbatim. Operation-layer text fields (the transaction
|
|
label, conflict field paths) are NFC-normalized at encode time
|
|
(Chapter~\ref{ch:ops}).
|
|
\item[Structs.] Positional, unframed concatenation of the fields' encodings
|
|
in declaration order. No field tags, no per-field lengths, no padding, no
|
|
alignment. (This is the layout the frozen-layout rule,
|
|
Requirement~\ref{req:binfmt:frozen-layout}, freezes.)
|
|
\item[Tagged unions.] A single discriminant byte, then the selected
|
|
variant's payload encoded positionally. Two documented exceptions:
|
|
\texttt{TypedObjectId} uses a \emph{16-bit big-endian} discriminant
|
|
(core requirement \texttt{req:graph:typed-object-id-discriminants};
|
|
Chapter~\ref{ch:ids}), and the bundle's \texttt{ProfileId} uses a
|
|
\texttt{u32} little-endian discriminant (core requirement
|
|
\texttt{req:format:profileid-discriminants}; Chapter~\ref{ch:bundle}).
|
|
\item[Determinism-canonical leaves.] Where a composite codec embeds a value
|
|
whose canonical byte form is owned by the determinism layer or the
|
|
identifier layer (a graph identifier, \texttt{ContentHash},
|
|
\texttt{CanonicalF64}, \texttt{RationalTime}, a wall-clock integer), the
|
|
core value codec frames it as a \texttt{u32} little-endian length prefix
|
|
followed by the leaf's own canonical bytes --- even when the leaf is
|
|
fixed-width. The leaf's bytes are then self-describing under its own
|
|
decoder. (The operation layer instead embeds fixed-width leaves
|
|
\emph{raw}, unprefixed; Chapter~\ref{ch:ops} states this explicitly.)
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:no-varint}
|
|
\textbf{No varint.} Schema major~0 defines \emph{no} variable-length integer
|
|
encoding. Every integer field in every layout in this document is
|
|
fixed-width. The core specification's ``varint conventions'' language
|
|
(Chapter~8, \sectionsc{Binary Format Companion}) is hereby discharged as
|
|
\emph{none}: an encoder \MUSTNOT{} emit, and a decoder \MUSTNOT{} accept, any
|
|
LEB128-style or otherwise variable-width integer under schema major~0.
|
|
Variable width in this format comes only from explicit \texttt{u32}/%
|
|
\texttt{u64} length prefixes and from \texttt{RationalTime}'s length-prefixed
|
|
big-integer magnitudes (Chapter~\ref{ch:primitives}).
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:trailing-bytes}
|
|
\textbf{Trailing bytes.} Every top-level decode entry point --- a
|
|
whole-\texttt{Score} value, a per-value \texttt{CanonicalValue} decode, an
|
|
\texttt{OperationKindTag}, a \texttt{MaterializedState}, a manifest payload,
|
|
an operation-index payload, a block payload, an extension-declaration blob, a
|
|
fixed-width primitive --- \MUST{} reject input with bytes remaining after the
|
|
complete value has been decoded. Trailing bytes are a decode error, never
|
|
ignored padding.
|
|
\end{requirement}
|
|
|
|
% ===========================================================================
|
|
\chapter{Identifiers and Derivations}
|
|
\label{ch:ids}
|
|
|
|
This chapter is an \emph{import-by-citation} of the core specification's
|
|
\sectionsc{Canonical Byte-Layout Reference} appendix (\texttt{app:bytes}):
|
|
that appendix and its golden anchors govern every table in this chapter, and
|
|
the tables here are compact restatements for self-containedness, not a second
|
|
source of truth. All layouts in this chapter are regime (c): big-endian,
|
|
fixed width.
|
|
|
|
\section{The 128-Bit Identifier Family}
|
|
\label{sec:ids:family}
|
|
|
|
\begin{description}
|
|
\item[\texttt{ReplicaId}.] 8 bytes, big-endian \texttt{u64}. The reserved
|
|
system namespace is \texttt{ReplicaId::SYSTEM\ub DERIVED} =
|
|
\texttt{0xFFFF\ub FFFF\ub FFFF\ub FFFF}; user-authored replicas
|
|
\MUSTNOT{} use it.
|
|
\item[Typed graph identifiers.] Every typed 128-bit graph identifier
|
|
(\texttt{EventId}, \texttt{PitchId}, \texttt{VoiceId},
|
|
\texttt{RegionId}, \texttt{StaffInstanceId}, \texttt{TransactionId},
|
|
\ldots{} --- the \texttt{graph\_id} family) packs the 64-bit replica
|
|
into the high half and the counter into the low half, and encodes as
|
|
exactly 16~bytes: replica big-endian (8) \cat{} counter big-endian (8).
|
|
Numeric order, canonical byte order, and Appendix-D iteration order
|
|
coincide.
|
|
\item[\texttt{OperationId}.] The same 16-byte form: replica big-endian (8)
|
|
\cat{} counter big-endian (8). Ordering is $(\mathit{replica},
|
|
\mathit{counter})$ lexicographic, which equals byte order.
|
|
\item[Registry and author identifiers (operation layer).] The opaque
|
|
128-bit registry newtypes (\texttt{OperationKind\allowbreak RegistryId},
|
|
\texttt{ConflictKind\allowbreak RegistryId},
|
|
\texttt{Resolution\allowbreak RegistryId},
|
|
\texttt{RepairKind\allowbreak RegistryId},
|
|
\texttt{ReanchorReason\allowbreak RegistryId},
|
|
\texttt{ReplicaAnomaly\allowbreak RegistryId},
|
|
\texttt{IntegrityAnomaly\allowbreak RegistryId},
|
|
\texttt{Extension\allowbreak PreconditionId},
|
|
\texttt{PreconditionFailure\allowbreak RegistryId}) and \texttt{AuthorId}
|
|
all encode as 16 big-endian bytes.
|
|
\end{description}
|
|
|
|
\section{\texttt{TypedObjectId}}
|
|
\label{sec:ids:typed-object-id}
|
|
|
|
Canonical form: a \textbf{16-bit big-endian discriminant} \cat{} the variant
|
|
payload (core requirement \texttt{req:graph:typed-object-id-discriminants}).
|
|
Non-\texttt{Registered} variants carry one 16-byte identifier, for a total of
|
|
\textbf{18~bytes}; \texttt{Registered} carries the
|
|
\texttt{ObjectKindRegistryId} (16 big-endian bytes) \cat{} the extension's
|
|
own raw \texttt{u128} (16 big-endian bytes), for a total of \textbf{34~bytes}.
|
|
An unknown discriminant is a decode error.
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{0.5in} p{1.6in} p{0.5in} p{1.6in}}
|
|
\toprule
|
|
\textbf{Disc} & \textbf{Variant} & \textbf{Disc} & \textbf{Variant} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{0} & \texttt{Event} & \tablenums{14} & \texttt{Spanner} \\
|
|
\tablenums{1} & \texttt{Pitch} & \tablenums{15} & \texttt{Marker} \\
|
|
\tablenums{2} & \texttt{Voice} & \tablenums{16} & \texttt{AnalyticalAnnotation} \\
|
|
\tablenums{3} & \texttt{Staff} & \tablenums{17} & \texttt{Comment} \\
|
|
\tablenums{4} & \texttt{StaffInstance} & \tablenums{18} & \texttt{GraphicObject} \\
|
|
\tablenums{5} & \texttt{StaffGroup} & \tablenums{19} & \texttt{GraphicGesture} \\
|
|
\tablenums{6} & \texttt{Region} & \tablenums{20} & \texttt{TimeSignature} \\
|
|
\tablenums{7} & \texttt{Instrument} & \tablenums{21} & \texttt{AnalysisLayer} \\
|
|
\tablenums{8} & \texttt{PartDefinition} & \tablenums{22} & \texttt{Tuplet} \\
|
|
\tablenums{9} & \texttt{Measure} & \tablenums{23} & \texttt{RepeatStructure} \\
|
|
\tablenums{10} & \texttt{BarlineAlignmentGroup} & \tablenums{24} & \texttt{LyricLine} \\
|
|
\tablenums{11} & \texttt{Slur} & \tablenums{25} & \texttt{ChordSymbol} \\
|
|
\tablenums{12} & \texttt{Tie} & \tablenums{26} & \texttt{View} \\
|
|
\tablenums{13} & \texttt{Beam} & \tablenums{27} & \texttt{Registered} \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\section{System-Derived Counters and Digest Truncations}
|
|
\label{sec:ids:derivations}
|
|
|
|
All content derivation uses BLAKE3-256 over a domain-separated preimage: the
|
|
8-byte domain tag is always the first bytes hashed. Two truncations recur,
|
|
both taking the digest's \emph{leading} bytes big-endian:
|
|
|
|
\begin{itemize}
|
|
\item $\mathrm{trunc64}(d) = \mathtt{u64::from\_be\_bytes}(d[0..8])$ ---
|
|
the counter of a system-derived identifier;
|
|
\item $\mathrm{trunc128}(d) = \mathtt{u128::from\_be\_bytes}(d[0..16])$ ---
|
|
content-derived 128-bit identifiers.
|
|
\end{itemize}
|
|
|
|
The system-derived counter function is
|
|
$\mathrm{trunc64}(\mathrm{BLAKE3}(\mathit{tag} \cat \mathit{inputs}))$; the
|
|
resulting identifier is
|
|
$\texttt{from\_parts}(\texttt{ReplicaId::SYSTEM\_DERIVED}, \mathit{counter})$.
|
|
Only a system domain tag (prefix \texttt{MUSCS}) may seed it. The ratified
|
|
instances (governed by \texttt{app:bytes} and the cited requirements):
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{1.35in} p{0.7in} p{3.5in}}
|
|
\toprule
|
|
\textbf{Identifier} & \textbf{Tag} & \textbf{Canonical inputs} \\
|
|
\midrule
|
|
\endhead
|
|
Promoted \texttt{VoiceId} & \texttt{MUSCSVCE} &
|
|
64-byte preimage: \texttt{staff\_instance} (16 BE) \cat{}
|
|
\texttt{original\_voice} (16 BE) \cat{} \texttt{winning\_op} (16 BE)
|
|
\cat{} \texttt{losing\_op} (16 BE). Core \sectionsc{System-Promoted
|
|
Voices}. \\
|
|
System \texttt{PitchId} & \texttt{MUSCSPCH} &
|
|
The pitch's intrinsic canonical bytes (core requirement
|
|
\texttt{req:graph:system-derived-pitch-id}). \\
|
|
\texttt{IntegrityAnomalyId} & \texttt{MUSCSANM} &
|
|
The anomaly kind's canonical bytes (Chapter~\ref{ch:ops}; core
|
|
requirement \texttt{req:graph:integrity-anomaly-id}). \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
The 128-bit content-derived identifiers:
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{1.1in} p{0.75in} p{3.7in}}
|
|
\toprule
|
|
\textbf{Identifier} & \textbf{Tag} & \textbf{Preimage after the tag} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{ConflictId} & \texttt{MUSCCONF} &
|
|
\texttt{kind.canonical\_bytes()} \cat{} causing operations (each 16 BE,
|
|
sorted by canonical bytes) \cat{} affected objects (each
|
|
\texttt{TypedObjectId} canonical bytes, sorted); $\mathrm{trunc128}$.
|
|
Wire form of the id itself: 16 big-endian bytes. \\
|
|
\texttt{ManifestId} & \texttt{MUSCMNIF} &
|
|
\texttt{document\_id} (16) \cat{} \texttt{generation} (\texttt{u64} LE)
|
|
\cat{} manifest body bytes (body excludes the \texttt{manifest\_id}
|
|
field); $\mathrm{trunc128}$. Restated normatively in
|
|
Chapter~\ref{ch:bundle} (core requirement
|
|
\texttt{req:format:manifest-id}). Note the \emph{on-disk} field encodes
|
|
little-endian (Chapter~\ref{ch:bundle}). \\
|
|
\texttt{EnvelopeHash} & \texttt{MUSCENVH} &
|
|
The envelope's complete canonical bytes; the full 32-byte digest (no
|
|
truncation). Chapter~\ref{ch:ops}. \\
|
|
\texttt{LayoutObjectId} & \texttt{MUSCLOID} &
|
|
Non-canonical; three preimage shapes, Chapter~\ref{ch:noncanon}
|
|
(core requirement \texttt{req:layoutir:object-id-derivation}). \\
|
|
\texttt{BlobId} & \texttt{MUSCBLOB} &
|
|
The blob payload, bare (\texttt{req:format:blob-hash-shape}); full
|
|
32-byte digest. Chapter~\ref{ch:primitives}. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\section{The Domain-Tag Registry}
|
|
\label{sec:ids:tags}
|
|
|
|
Every domain tag is exactly 8 printable-ASCII bytes beginning \texttt{MUSC};
|
|
the vocabulary is closed (governed by \texttt{app:bytes},
|
|
\sectionsc{Domain-tag registry}). Extension system tags \MUST{} begin
|
|
\texttt{MUSCS}, be exactly 8 bytes, and collide with neither the built-ins
|
|
nor the file magics.
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{1.05in} p{1.55in} p{3.0in}}
|
|
\toprule
|
|
\textbf{Tag} & \textbf{Name} & \textbf{Use} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{MUSCCHNK} & chunk & non-manifest chunk hash preimages \\
|
|
\texttt{MUSCMANI} & manifest & manifest chunk hash preimages \\
|
|
\texttt{MUSCBLOB} & blob & \texttt{BlobId} (bare $\mathit{tag} \cat \mathit{payload}$) \\
|
|
\texttt{MUSCCONF} & conflict & \texttt{ConflictId} derivation \\
|
|
\texttt{MUSCENVH} & envelope & \texttt{EnvelopeHash} \\
|
|
\texttt{MUSCFNTM} & font metrics & glyph-catalog metrics hash (non-canonical) \\
|
|
\texttt{MUSCMNIF} & manifest id & \texttt{ManifestId} derivation \\
|
|
\texttt{MUSCSVCE} & system voice & promoted-voice counters \\
|
|
\texttt{MUSCSPCH} & system pitch & system-derived pitch counters \\
|
|
\texttt{MUSCSANM} & system anomaly & \texttt{IntegrityAnomalyId} \\
|
|
\texttt{MUSCLOID} & layout object id & \texttt{LayoutObjectId} (non-canonical) \\
|
|
\midrule
|
|
\texttt{MUSCBND\textbackslash0} & bundle magic & file magic, \emph{not} a hash tag (note the trailing NUL) \\
|
|
\texttt{MUSCSUPR} & superblock magic & slot magic, \emph{not} a hash tag \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\section{\texttt{SnapshotId}}
|
|
\label{sec:ids:snapshot-id}
|
|
|
|
The core specification's support-type identity table says \emph{``the Binary
|
|
Format companion defines the exact derivation''} of \texttt{SnapshotId}. v0
|
|
has no snapshot producer --- pruning and canonical-base creation are deferred
|
|
--- so there is nothing to derive \emph{from} yet.
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:snapshot-id-opaque}
|
|
In schema major~0, \texttt{SnapshotId} is an \textbf{opaque 16-byte
|
|
identifier}: 16 raw bytes on the wire (Chapter~\ref{ch:primitives}), with no
|
|
normative derivation function. Readers \MUST{} treat two
|
|
\texttt{SnapshotId}s as equal iff their 16 bytes are equal and \MUSTNOT{}
|
|
attempt to derive or verify a snapshot id from snapshot content.
|
|
\end{requirement}
|
|
|
|
\begin{openquestion}
|
|
The \texttt{SnapshotId} derivation (presumably a content derivation in the
|
|
\texttt{trunc128} family, over the snapshot's canonical payload and its
|
|
covering frontier) is deferred together with the pruning/garbage-collection
|
|
track that will produce the first snapshots. It must be pinned before any
|
|
writer emits a \texttt{canonical\_base}; until then the opaque reading above
|
|
is complete.
|
|
\end{openquestion}
|
|
|
|
% ===========================================================================
|
|
\chapter{Primitive Value Encodings}
|
|
\label{ch:primitives}
|
|
|
|
The leaf encodings owned by the determinism layer and the small fixed
|
|
identifier types. These byte forms are what the framings of
|
|
Chapters~\ref{ch:values}--\ref{ch:bundle} embed.
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{>{\raggedright\arraybackslash}p{1.7in} p{0.5in} p{3.45in}}
|
|
\toprule
|
|
\textbf{Type} & \textbf{Width} & \textbf{Encoding} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{CanonicalF64} & 8 &
|
|
IEEE-754 binary64, little-endian. $-0.0$ is canonicalized to $+0.0$ at
|
|
construction, so the negative-zero bit pattern never appears in canonical
|
|
bytes. Decode rejects NaN and $\pm\infty$ (non-finite floats are
|
|
corruption). \\
|
|
\texttt{QuantizedCoord} & 8 &
|
|
\texttt{i64} little-endian, in units of $1/1024$ staff space
|
|
(\texttt{STAFF\_SPACE\_GRID} = 1024). Construction from a float rounds
|
|
ties-to-even and rejects non-finite or out-of-range input. \\
|
|
\texttt{ContentHash} & 32 & Raw BLAKE3-256 digest bytes. \\
|
|
\texttt{ChunkId} & 32 & Raw digest bytes (a \texttt{ContentHash} newtype). \\
|
|
\texttt{DomainTag} & 8 &
|
|
The raw ASCII tag bytes. Decode validates against the closed vocabulary
|
|
of Section~\ref{sec:ids:tags}; foreign bytes are a decode error. \\
|
|
\texttt{WallClockTime} & 8 &
|
|
\texttt{i64} little-endian, nanoseconds. Floating-point wall-clock time
|
|
is forbidden in stored data. \\
|
|
\texttt{WallClockDuration} & 8 & \texttt{i64} little-endian, nanoseconds. \\
|
|
\texttt{SchemaVersion} & 4 &
|
|
\texttt{major} \texttt{u16} LE \cat{} \texttt{minor} \texttt{u16} LE. \\
|
|
\texttt{SemVer} & 12 &
|
|
\texttt{major} \texttt{u32} LE \cat{} \texttt{minor} \texttt{u32} LE
|
|
\cat{} \texttt{patch} \texttt{u32} LE. Canonical \emph{ordering} of
|
|
\texttt{SemVer}-keyed collections is \textbf{numeric} on
|
|
$(\mathit{major},\mathit{minor},\mathit{patch})$, never byte-lexicographic
|
|
on the little-endian encoding (which would order 256.0.0 before 1.0.0). \\
|
|
\texttt{Reduction\allowbreak AlgorithmVersion} & 4 & \texttt{u32}
|
|
little-endian. \\
|
|
\texttt{FileUuid}, \texttt{DocumentId}, \texttt{LineageId},
|
|
\texttt{SnapshotId}, \texttt{ExtensionId}, \texttt{ProfileRegistryId} & 16 &
|
|
16 raw opaque bytes each. No internal structure is interpreted;
|
|
equality is byte equality. \\
|
|
\texttt{BlobId} & 32 &
|
|
Raw digest bytes: $\mathrm{BLAKE3}(\texttt{MUSCBLOB} \cat
|
|
\mathit{payload})$, the only bare $\mathit{tag} \cat \mathit{payload}$
|
|
content hash in the format (core requirement
|
|
\texttt{req:format:blob-hash-shape}). \\
|
|
\texttt{FrontierBytes} & var &
|
|
\texttt{u32} LE length \cat{} opaque bytes (an ops-computed causal
|
|
frontier the bundle carries verbatim). \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\section{\texttt{RationalTime}}
|
|
\label{sec:primitives:rationaltime}
|
|
|
|
Restating core requirement \texttt{req:format:rationaltime-encoding}, which
|
|
governs. \texttt{RationalTime} is an arbitrary-precision rational, always
|
|
stored \emph{reduced} (equal rationals encode identically; the sign lives on
|
|
the numerator; the denominator is strictly positive). Its canonical byte
|
|
form:
|
|
|
|
\begin{center}
|
|
\begin{tabular}{l l}
|
|
\toprule
|
|
\textbf{Field} & \textbf{Encoding} \\
|
|
\midrule
|
|
sign & 1 byte: \tablenums{0} = zero, \tablenums{1} = positive,
|
|
\tablenums{2} = negative \\
|
|
numerator & \texttt{u32} LE magnitude byte length \cat{} big-endian
|
|
magnitude bytes \\
|
|
denominator & \texttt{u32} LE magnitude byte length \cat{} big-endian
|
|
magnitude bytes \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
Decode rejects a sign byte greater than~2, a zero denominator, and trailing
|
|
bytes. \texttt{MusicalPosition} and \texttt{MusicalDuration} are newtypes
|
|
whose canonical bytes are exactly their inner \texttt{RationalTime}'s.
|
|
Wall-clock integers deliberately do \emph{not} use this form: they are the
|
|
fixed-width little-endian integers of the table above, matching
|
|
\texttt{QuantizedCoord}'s convention.
|
|
|
|
% ===========================================================================
|
|
\chapter{Graph Value Layouts}
|
|
\label{ch:values}
|
|
|
|
This chapter defines the schema-major-0 wire form of every value reachable
|
|
from a \texttt{Score} --- the whole-document composite codec. The core
|
|
specification's \texttt{DECISIONS} record (P11-4) held this codec
|
|
\emph{provisional} pending this companion;
|
|
Requirement~\ref{req:binfmt:score-envelope} and
|
|
Requirement~\ref{req:binfmt:frozen-layout} \textbf{ratify} it: the reference
|
|
implementation's layout, as specified here, \emph{is} the schema-major-0 wire
|
|
form, and P11-4's provisional status is discharged.
|
|
|
|
All layouts in this chapter are regime (a): little-endian, \texttt{u32}
|
|
prefixes.
|
|
|
|
\section{The \texttt{Score} Envelope}
|
|
\label{sec:values:score}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:score-envelope}
|
|
The canonical bytes of a \texttt{Score} are the bare positional
|
|
concatenation of its nineteen fields' encodings, in exactly this order:
|
|
|
|
\begin{enumerate}
|
|
\item \texttt{metadata}
|
|
\item \texttt{canvas}
|
|
\item \texttt{instruments}
|
|
\item \texttt{staves}
|
|
\item \texttt{staff\_groups}
|
|
\item \texttt{parts}
|
|
\item \texttt{cross\_cutting}
|
|
\item \texttt{time\_signatures}
|
|
\item \texttt{tuning\_context}
|
|
\item \texttt{tempo\_map}
|
|
\item \texttt{events}
|
|
\item \texttt{spelling\_attachments}
|
|
\item \texttt{decomposition\_attachments}
|
|
\item \texttt{spelling\_precedence}
|
|
\item \texttt{analysis\_layers}
|
|
\item \texttt{views}
|
|
\item \texttt{identity}
|
|
\item \texttt{tombstoned\_pitches}
|
|
\item \texttt{tombstoned\_events}
|
|
\end{enumerate}
|
|
|
|
There is \textbf{no magic} and \textbf{no version prefix} at the value
|
|
level: format identification lives in the bundle's fixed header
|
|
(Chapter~\ref{ch:bundle}) and versioning lives at the chunk layer
|
|
(\texttt{SchemaVersion} in every \texttt{ChunkRef} and superblock;
|
|
Chapter~\ref{ch:evolution}). Decoders \MUST{} reject trailing bytes after
|
|
the nineteenth field.
|
|
\end{requirement}
|
|
|
|
\section{Composition Rules as Normative Encoding Facts}
|
|
\label{sec:values:composition}
|
|
|
|
The reference codec is built from a small set of conventions; each is a
|
|
normative statement about the bytes, not an implementation detail:
|
|
|
|
\begin{description}
|
|
\item[Positional structs.] Every struct in this chapter encodes as its
|
|
fields in declaration order, unframed
|
|
(Section~\ref{sec:conventions:primitives}). Zero-field structs encode as
|
|
zero bytes.
|
|
\item[C-style enums.] A fieldless enum is a single discriminant byte
|
|
(tables in Section~\ref{sec:values:discriminants}).
|
|
\item[Tagged unions.] A discriminant byte, then the variant's fields
|
|
positionally.
|
|
\item[Leaf framing.] The determinism-canonical leaf types ---
|
|
\texttt{ReplicaId}, \texttt{OperationId}, every embedded typed graph
|
|
identifier (\texttt{EventId}, \texttt{PitchId}, \texttt{VoiceId},
|
|
\texttt{StaffId}, \texttt{StaffInstanceId}, \texttt{StaffGroupId},
|
|
\texttt{RegionId}, \texttt{InstrumentId}, \texttt{PartDefinitionId},
|
|
\texttt{MeasureId}, \texttt{BarlineAlignmentGroupId}, \texttt{SlurId},
|
|
\texttt{TieId}, \texttt{BeamId}, \texttt{SpannerId}, \texttt{TupletId},
|
|
\texttt{MarkerId}, \texttt{AnalyticalAnnotationId}, \texttt{CommentId},
|
|
\texttt{RepeatStructureId}, \texttt{LyricLineId}, \texttt{ChordSymbolId},
|
|
\texttt{GraphicObjectId}, \texttt{GraphicGestureId},
|
|
\texttt{TimeSignatureId}, \texttt{AnalysisLayerId}, \texttt{ViewId}) ---
|
|
plus \texttt{ContentHash}, \texttt{CanonicalF64}, \texttt{RationalTime},
|
|
\texttt{MusicalPosition}, \texttt{MusicalDuration},
|
|
\texttt{WallClockTime}, and \texttt{WallClockDuration}, are each emitted
|
|
as \texttt{u32} LE length \cat{} the leaf's canonical bytes
|
|
(Chapters~\ref{ch:ids} and \ref{ch:primitives}). An embedded 16-byte
|
|
identifier therefore occupies 20 bytes inside a \texttt{Score}. Decode
|
|
runs the leaf's own validating decoder over the framed region.
|
|
\item[Catalog identifiers.] The \texttt{catalog\_id} newtypes
|
|
(\texttt{PitchSpaceId}, \texttt{TuningSystemId}, \texttt{AccidentalId},
|
|
registry-name ids, \ldots) encode as their stored NFC string under the
|
|
String rule (\texttt{u32} LE length \cat{} UTF-8).
|
|
\item[\texttt{EventArena}.] \texttt{u32} LE event count, then each
|
|
\texttt{Event} in ascending-\texttt{EventId} order (identity travels
|
|
inside each event; the arena adds no per-event framing).
|
|
\item[Validated reconstruction.] Types with checked constructors decode
|
|
through them: \texttt{TupletRatio} rejects degenerate ratios,
|
|
\texttt{KeySignature} encodes its \texttt{fifths()} as \texttt{i8} and
|
|
rejects out-of-range values, \texttt{TimeSignature} re-validates that
|
|
beat groups sum to the measure duration, \texttt{NonZeroU16} rejects
|
|
zero, \texttt{EventOrderingDAG} rejects cyclic edge maps,
|
|
\texttt{ReferencePitch}, \texttt{Tempo}, and
|
|
\texttt{SpellingPrecedence} re-validate their invariants. Structurally
|
|
well-formed bytes that fail a type invariant are a decode error
|
|
(reject-never-normalize).
|
|
\end{description}
|
|
|
|
\section{Discriminant Tables}
|
|
\label{sec:values:discriminants}
|
|
|
|
The complete discriminant assignment for schema major~0. Every table is
|
|
governed by Requirement~\ref{req:binfmt:frozen-layout}: an unknown
|
|
discriminant is a decode error; changes are gated by
|
|
Chapter~\ref{ch:evolution}.
|
|
|
|
\subsection{C-style enums (one byte, no payload)}
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{1.9in} p{3.7in}}
|
|
\toprule
|
|
\textbf{Type} & \textbf{Discriminants} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{MeasurePosition} & \tablenums{0} Start, \tablenums{1} End \\
|
|
\texttt{RegionEdge} & \tablenums{0} Start, \tablenums{1} End \\
|
|
\texttt{CmnNominal} & \tablenums{0} C, \tablenums{1} D, \tablenums{2} E,
|
|
\tablenums{3} F, \tablenums{4} G, \tablenums{5} A, \tablenums{6} B \\
|
|
\texttt{SpellingSourceKind} & \tablenums{0} UserChosen,
|
|
\tablenums{1} Imported, \tablenums{2} Propagated, \tablenums{3} Inferred,
|
|
\tablenums{4} Analytical \\
|
|
\texttt{TempoShape} & \tablenums{0} Constant, \tablenums{1} Linear,
|
|
\tablenums{2} Exponential, \tablenums{3} Curve \\
|
|
\texttt{StemDirection} & \tablenums{0} Up, \tablenums{1} Down \\
|
|
\texttt{MeasureNumberVisibility} & \tablenums{0} Auto,
|
|
\tablenums{1} Always, \tablenums{2} Never \\
|
|
\texttt{Aleatoric\allowbreak AnchoringDiscipline} & \tablenums{0} Musical,
|
|
\tablenums{1} WallClock, \tablenums{2} EitherPerEvent,
|
|
\tablenums{3} FreelyMixed \\
|
|
\texttt{NoteValue} & \tablenums{0} Whole, \tablenums{1} Half,
|
|
\tablenums{2} Quarter, \tablenums{3} Eighth, \tablenums{4} Sixteenth,
|
|
\tablenums{5} ThirtySecond, \tablenums{6} SixtyFourth \\
|
|
\texttt{ClefShape} & \tablenums{0} G, \tablenums{1} F, \tablenums{2} C,
|
|
\tablenums{3} Percussion \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\subsection{Tagged unions (one byte, then the variant payload)}
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{1.9in} p{3.7in}}
|
|
\toprule
|
|
\textbf{Type} & \textbf{Discriminants} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{AnchorOffset} & \tablenums{0} Musical, \tablenums{1} WallClock,
|
|
\tablenums{2} Zero \\
|
|
\texttt{TimeAnchor} & \tablenums{0} Event, \tablenums{1} Measure,
|
|
\tablenums{2} Region, \tablenums{3} WallClock
|
|
(Section~\ref{sec:values:representative}) \\
|
|
\texttt{EventPosition} & \tablenums{0} Musical, \tablenums{1} WallClock \\
|
|
\texttt{ConcreteDuration} & \tablenums{0} Musical, \tablenums{1} WallClock \\
|
|
\texttt{EventDuration} & \tablenums{0} Musical, \tablenums{1} WallClock,
|
|
\tablenums{2} Indeterminate \\
|
|
\texttt{TimeBounds} & \tablenums{0} MusicalRange,
|
|
\tablenums{1} WallClockRange, \tablenums{2} Unbounded \\
|
|
\texttt{PitchSpacePosition} & \tablenums{0} Cmn, \tablenums{1} Integer,
|
|
\tablenums{2} JiVector, \tablenums{3} Registered \\
|
|
\texttt{TuningReference} & \tablenums{0} Inherit, \tablenums{1} Explicit \\
|
|
\texttt{AcousticRealization} & \tablenums{0} Implicit,
|
|
\tablenums{1} CentsOffset, \tablenums{2} AbsoluteHz \\
|
|
\texttt{SpellingNominal} & \tablenums{0} Cmn, \tablenums{1} Integer,
|
|
\tablenums{2} Registered \\
|
|
\texttt{SpellingSource} & \tablenums{0} UserChosen, \tablenums{1} Inferred,
|
|
\tablenums{2} Imported, \tablenums{3} Propagated,
|
|
\tablenums{4} Analytical \\
|
|
\texttt{SpellingScope} & \tablenums{0} Pitch, \tablenums{1} Range \\
|
|
\texttt{SpellingDirective} & \tablenums{0} Explicit, \tablenums{1} Rule \\
|
|
\texttt{VoiceSelector} & \tablenums{0} All, \tablenums{1} Voices \\
|
|
\texttt{GraceKind} & \tablenums{0} Acciaccatura, \tablenums{1} Appoggiatura,
|
|
\tablenums{2} Unmeasured, \tablenums{3} MeasuredFraction \\
|
|
\texttt{IndeterminacyKind} & \tablenums{0} Pitch, \tablenums{1} Duration,
|
|
\tablenums{2} Choice, \tablenums{3} Compound \\
|
|
\texttt{TrajectoryEndpoint} & \tablenums{0} EventPitch,
|
|
\tablenums{1} ExplicitPitch \\
|
|
\texttt{TrajectoryShape} & \tablenums{0} Linear, \tablenums{1} Exponential,
|
|
\tablenums{2} Curve, \tablenums{3} Stepwise \\
|
|
\texttt{Event} & \tablenums{0} Pitched, \tablenums{1} Unpitched,
|
|
\tablenums{2} Rest, \tablenums{3} Indeterminate, \tablenums{4} Trajectory,
|
|
\tablenums{5} Graphic, \tablenums{6} Cue
|
|
(Section~\ref{sec:values:representative}) \\
|
|
\texttt{RegionTimeModel} & \tablenums{0} Metric, \tablenums{1} Proportional,
|
|
\tablenums{2} Aleatoric \\
|
|
\texttt{RegionContent} & \tablenums{0} StaffBased, \tablenums{1} FreeGraphic,
|
|
\tablenums{2} Hybrid \\
|
|
\texttt{VoiceOrigin} & \tablenums{0} UserDeclared, \tablenums{1} Imported,
|
|
\tablenums{2} SystemPromoted \\
|
|
\texttt{StaffGroupKind} & \tablenums{0} GrandStaff, \tablenums{1} Bracket,
|
|
\tablenums{2} SubBracket, \tablenums{3} Choral, \tablenums{4} Registered \\
|
|
\texttt{TieClass} & \tablenums{0} Standard, \tablenums{1} Editorial,
|
|
\tablenums{2} CrossVoice, \tablenums{3} LaissezVibrer,
|
|
\tablenums{4} Registered \\
|
|
\texttt{AnnotationAnchor} & \tablenums{0} Event, \tablenums{1} Range,
|
|
\tablenums{2} Region \\
|
|
\texttt{GestureAnchoring} & \tablenums{0} Events, \tablenums{1} Range,
|
|
\tablenums{2} Free \\
|
|
\texttt{DecompositionSource} & \tablenums{0} UserChosen,
|
|
\tablenums{1} Inferred, \tablenums{2} Imported, \tablenums{3} Propagated \\
|
|
\texttt{TimeSignatureDisplay} & \tablenums{0} Standard,
|
|
\tablenums{1} Compound, \tablenums{2} Irrational,
|
|
\tablenums{3} MixedDenominators, \tablenums{4} None,
|
|
\tablenums{5} Symbolic \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:spelling-source-order}
|
|
\textbf{The \texttt{SpellingSource} / \texttt{SpellingSourceKind} trap.}
|
|
These two vocabularies name the same five provenance concepts but assign
|
|
them \emph{different} discriminants: \texttt{SpellingSourceKind} (the
|
|
C-style precedence-list element) is \tablenums{0}~UserChosen,
|
|
\tablenums{1}~Imported, \tablenums{2}~Propagated, \tablenums{3}~Inferred,
|
|
\tablenums{4}~Analytical, while \texttt{SpellingSource} (the payload-carrying
|
|
attachment provenance) is \tablenums{0}~UserChosen, \tablenums{1}~Inferred,
|
|
\tablenums{2}~Imported, \tablenums{3}~Propagated, \tablenums{4}~Analytical.
|
|
The orders differ (\texttt{Inferred} is 3 in one and 1 in the other), both
|
|
are golden-locked, and an implementation \MUSTNOT{} share one discriminant
|
|
function between them.
|
|
\end{requirement}
|
|
|
|
\section{Representative Complete Layouts}
|
|
\label{sec:values:representative}
|
|
|
|
The following layouts are spelled byte-by-byte both as worked examples of the
|
|
composition rules and as normative fact. Recall that inside this chapter's
|
|
codec every leaf is framed (\texttt{u32} LE length \cat{} bytes), so an
|
|
embedded 16-byte identifier occupies $4+16=20$ bytes.
|
|
|
|
\subsection{\texttt{RationalTime}}
|
|
As a leaf inside a composite: \texttt{u32} LE length \cat{} the canonical
|
|
form of Section~\ref{sec:primitives:rationaltime} (sign \cat{}
|
|
length-prefixed big-endian numerator magnitude \cat{} length-prefixed
|
|
big-endian denominator magnitude).
|
|
|
|
\subsection{\texttt{TimeAnchor}}
|
|
One discriminant byte, then:
|
|
\begin{description}
|
|
\item[\tablenums{0} Event] \texttt{id} (\texttt{EventId} leaf, 20) \cat{}
|
|
\texttt{offset} (\texttt{AnchorOffset}: tag byte, then for Musical a
|
|
\texttt{MusicalDuration} leaf, for WallClock a
|
|
\texttt{WallClockDuration} leaf ($4+8=12$), for Zero nothing).
|
|
\item[\tablenums{1} Measure] \texttt{id} (\texttt{MeasureId} leaf, 20)
|
|
\cat{} \texttt{position} (\texttt{MeasurePosition}, 1) \cat{}
|
|
\texttt{offset} (\texttt{AnchorOffset}).
|
|
\item[\tablenums{2} Region] \texttt{id} (\texttt{RegionId} leaf, 20) \cat{}
|
|
\texttt{edge} (\texttt{RegionEdge}, 1) \cat{} \texttt{offset}
|
|
(\texttt{AnchorOffset}).
|
|
\item[\tablenums{3} WallClock] \texttt{time} (\texttt{WallClockTime} leaf,
|
|
12).
|
|
\end{description}
|
|
|
|
\subsection{\texttt{Pitch}}
|
|
Positional struct: \texttt{scale\_position} \cat{} \texttt{acoustic}.
|
|
\begin{itemize}
|
|
\item \texttt{ScalePosition} = \texttt{space} (\texttt{PitchSpaceId}: NFC
|
|
string, \texttt{u32} LE length \cat{} UTF-8) \cat{} \texttt{position}
|
|
(\texttt{PitchSpacePosition}: tag byte, then Cmn = \texttt{nominal}
|
|
(\texttt{CmnNominal}, 1) \cat{} \texttt{alteration} (\texttt{i8}, 1)
|
|
\cat{} \texttt{octave} (\texttt{i8}, 1); Integer =
|
|
\texttt{space\_size} (\texttt{u16} LE, 2) \cat{} \texttt{index}
|
|
(\texttt{i32} LE, 4); JiVector = \texttt{u32} LE count \cat{} that many
|
|
\texttt{i32} LE components; Registered = a catalog-id string).
|
|
\item \texttt{AcousticPitch} = \texttt{tuning} (\texttt{TuningReference}:
|
|
tag byte; Explicit carries a \texttt{TuningSystemId} catalog-id string)
|
|
\cat{} \texttt{realization} (\texttt{AcousticRealization}: tag byte;
|
|
CentsOffset and AbsoluteHz each carry a \texttt{CanonicalF64} leaf,
|
|
$4+8=12$).
|
|
\end{itemize}
|
|
|
|
\subsection{\texttt{Event}}
|
|
One discriminant byte (\tablenums{0}--\tablenums{6}), then the variant struct
|
|
positionally. All seven variants share the prefix \texttt{id}
|
|
(\texttt{EventId} leaf) \cat{} \texttt{voice} (\texttt{VoiceId} leaf) \cat{}
|
|
\texttt{position} (\texttt{EventPosition}) \cat{} \texttt{duration}
|
|
(\texttt{EventDuration}); the remaining fields, in order:
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{0.35in} p{1.35in} p{3.8in}}
|
|
\toprule
|
|
\textbf{Disc} & \textbf{Variant} & \textbf{Fields after the common prefix} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{0} & \texttt{Pitched} &
|
|
\texttt{pitches} \cat{} \texttt{articulations} \cat{} \texttt{dynamic}
|
|
\cat{} \texttt{ornaments} \cat{} \texttt{stem} \cat{} \texttt{grace} \\
|
|
\tablenums{1} & \texttt{Unpitched} &
|
|
\texttt{staff\_position} \cat{} \texttt{instrument\_member} \cat{}
|
|
\texttt{articulations} \cat{} \texttt{dynamic} \cat{} \texttt{stem}
|
|
\cat{} \texttt{grace} \\
|
|
\tablenums{2} & \texttt{Rest} &
|
|
\texttt{vertical\_position} \cat{} \texttt{visible} \\
|
|
\tablenums{3} & \texttt{Indeterminate} &
|
|
\texttt{indeterminacy} \cat{} \texttt{hints} \\
|
|
\tablenums{4} & \texttt{Trajectory} &
|
|
\texttt{start} \cat{} \texttt{end} \cat{} \texttt{shape} \cat{}
|
|
\texttt{display} \\
|
|
\tablenums{5} & \texttt{Graphic} &
|
|
\texttt{graphics} \cat{} \texttt{playback\_bindings} \\
|
|
\tablenums{6} & \texttt{Cue} &
|
|
\texttt{source} \cat{} \texttt{rendering} \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
Each field encodes under this chapter's rules for its type (vectors are
|
|
\texttt{u32}-counted, options carry a presence byte, embedded identifiers are
|
|
leaves, unions carry their tag byte).
|
|
|
|
\subsection{\texttt{Slur}}
|
|
Positional struct of three leaves: \texttt{id} (\texttt{SlurId} leaf, 20)
|
|
\cat{} \texttt{start\_event} (\texttt{EventId} leaf, 20) \cat{}
|
|
\texttt{end\_event} (\texttt{EventId} leaf, 20) --- 60 bytes total.
|
|
|
|
\section{The \texttt{CanonicalValue} Seam}
|
|
\label{sec:values:canonical-value}
|
|
|
|
The public per-value codec --- the seam between this document and the
|
|
Operation Catalog's value-typed payloads (Operation Catalog,
|
|
\sectionsc{Value-Typed Payloads}, requirement
|
|
\texttt{req:catalog:value-encoding}) --- is defined for exactly these
|
|
twenty-three types (the Phase-3 operation tranche added
|
|
\texttt{Staff}/\texttt{TimeSignature}/\texttt{TempoSegment}/%
|
|
\texttt{StaffLineConfiguration}; the repeat-authoring revision added
|
|
\texttt{RepeatStructure}):
|
|
|
|
\begin{quote}
|
|
\texttt{Event}, \texttt{Rest}, \texttt{Pitch}, \texttt{IdentifiedPitch},
|
|
\texttt{PitchSpelling}, \texttt{Tie}, \texttt{Slur}, \texttt{Beam},
|
|
\texttt{Spanner}, \texttt{RegionTimeModel}, \texttt{TimeAnchor},
|
|
\texttt{Region}, \texttt{StaffInstance}, \texttt{Voice},
|
|
\texttt{DecompositionAttachment}, \texttt{SpellingSourceKind},
|
|
\texttt{ScoreMetadata}, \texttt{MetricGrid}, \texttt{Staff},
|
|
\texttt{TimeSignature}, \texttt{TempoSegment},
|
|
\texttt{StaffLineConfiguration}, \texttt{RepeatStructure}.
|
|
\end{quote}
|
|
|
|
The seam introduces \textbf{no new bytes}: a value's stand-alone canonical
|
|
bytes are byte-for-byte the bytes the whole-\texttt{Score} codec embeds for
|
|
that value. Stand-alone decode applies the same validation and rejects
|
|
trailing bytes.
|
|
|
|
\section{The Frozen-Layout Rule}
|
|
\label{sec:values:frozen}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:frozen-layout}
|
|
\textbf{Frozen positional layouts (the schema-evolution keystone).} Within
|
|
schema major~0, every positional struct layout in this chapter is
|
|
\textbf{frozen}: there is no field-addition mechanism at the value level ---
|
|
no per-field framing, no optional-field encoding, no trailing-field
|
|
tolerance. Any change to any struct's field \emph{set} or field \emph{order}
|
|
is a schema-\textsc{major} change and requires a documented migration in this
|
|
document's revision history (Chapter~\ref{ch:evolution}).
|
|
|
|
Appending a \emph{new variant} to an \textbf{open} discriminant vocabulary is
|
|
a schema-\textsc{minor} change under the rules of
|
|
Chapter~\ref{ch:evolution}. At the value layer, the open vocabularies are
|
|
those with a \texttt{Registered} escape variant ---
|
|
\texttt{PitchSpacePosition}, \texttt{SpellingNominal},
|
|
\texttt{StaffGroupKind}, \texttt{TieClass} --- through which extensions
|
|
attach without any wire change at all; every \emph{other} union in
|
|
Section~\ref{sec:values:discriminants} (including \texttt{Event},
|
|
\texttt{TimeAnchor}, \texttt{TimeSignatureDisplay}) is closed in v0 and may
|
|
grow only by an append ratified in a revision of this document.
|
|
|
|
This rule ratifies the project's staging decision that data-model payload
|
|
expansion --- \texttt{SlurKind}, beam geometry, voltas, instrument bodies,
|
|
score-metadata growth, and their kin --- lands as a coordinated
|
|
schema-\textsc{major} revision of this document, not as ad-hoc field
|
|
insertion.
|
|
\end{requirement}
|
|
|
|
% ===========================================================================
|
|
\chapter{Operation Wire Forms}
|
|
\label{ch:ops}
|
|
|
|
This chapter pins the operation layer: envelopes, payloads, stamps, causal
|
|
contexts, effects, and materialized state. It \textbf{ratifies} the
|
|
encodings the ops crate's \texttt{DECISIONS} record held as ``provisional
|
|
canonical encoding'' (mirroring core P11-4); that provisional status is
|
|
discharged.
|
|
|
|
Regime (a) applies, with one composition difference from
|
|
Chapter~\ref{ch:values}: the operation layer embeds \emph{fixed-width}
|
|
canonical leaves (identifiers, hashes, \texttt{ConflictId}) \textbf{raw},
|
|
without a length prefix (\texttt{push\_canon}); only variable-width parts
|
|
carry \texttt{u32} LE length prefixes (\texttt{push\_lp\_bytes}). Sequences
|
|
(\texttt{push\_seq}) are a \texttt{u32} LE count, then each element
|
|
individually length-prefixed (\texttt{u32} LE), regardless of element width.
|
|
Text (\texttt{push\_str}) is NFC-normalized at encode time, then
|
|
length-prefixed UTF-8. Embedded graph values are the
|
|
\texttt{CanonicalValue} bytes of Section~\ref{sec:values:canonical-value},
|
|
framed by a \texttt{u32} LE length prefix (Operation Catalog requirement
|
|
\texttt{req:catalog:value-encoding}).
|
|
|
|
\section{The Operation Envelope}
|
|
\label{sec:ops:envelope}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:envelope}
|
|
An \texttt{OperationEnvelope}'s canonical bytes are, in order:
|
|
|
|
\begin{center}
|
|
\begin{tabular}{p{1.35in} p{3.9in}}
|
|
\toprule
|
|
\textbf{Field} & \textbf{Encoding} \\
|
|
\midrule
|
|
\texttt{id} & \texttt{OperationId}, 16 big-endian bytes, raw \\
|
|
\texttt{author} & \texttt{AuthorId}, 16 big-endian bytes, raw \\
|
|
\texttt{stamp} & \texttt{OperationStamp}, 28 bytes
|
|
(Section~\ref{sec:ops:stamp}) \\
|
|
\texttt{causal\_context} & \texttt{CausalContext}
|
|
(Section~\ref{sec:ops:causal}) \\
|
|
\texttt{transaction} & option tag byte (\tablenums{0} absent /
|
|
\tablenums{1} present), then \texttt{TransactionId} 16 BE if present \\
|
|
\texttt{payload} & \texttt{OperationPayload}
|
|
(Section~\ref{sec:ops:payload}) \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
\textbf{Id-leads property.} The envelope's first 16 bytes \emph{are} the
|
|
\texttt{OperationId}'s canonical bytes. This is load-bearing: the operation
|
|
index (Chapter~\ref{ch:bundle}, Section~\ref{sec:bundle:opindex}) keys its
|
|
entries on those leading 16 bytes without decoding envelopes, and the
|
|
sanctioned partial read (\emph{peek}) of an envelope is exactly its leading
|
|
16 bytes. Any future envelope revision \MUST{} preserve id-leads or revise
|
|
the operation index in the same schema-major step.
|
|
\end{requirement}
|
|
|
|
$\texttt{EnvelopeHash} = \mathrm{BLAKE3}(\texttt{MUSCENVH} \cat
|
|
\mathit{envelope\ canonical\ bytes})$ --- the full 32-byte digest.
|
|
|
|
Envelopes are \textbf{encode-only} at this layer, deliberately: once
|
|
committed, an envelope is stored and transported as opaque bytes and is
|
|
never reconstructed into typed form by the storage layer; the peek above is
|
|
the only sanctioned partial read. (Reducers decode payloads they authored or
|
|
received through the ops layer's own typed surface; the wire contract is
|
|
that stored envelope bytes are preserved verbatim, since
|
|
\texttt{EnvelopeHash} commits to them.)
|
|
|
|
\section{\texttt{OperationPayload} and the Meta-Operations}
|
|
\label{sec:ops:payload}
|
|
|
|
\texttt{OperationPayload}: one discriminant byte, then the variant.
|
|
|
|
\begin{center}
|
|
\small
|
|
\begin{tabular}{p{0.35in} p{1.55in} p{3.3in}}
|
|
\toprule
|
|
\textbf{Disc} & \textbf{Variant} & \textbf{Payload} \\
|
|
\midrule
|
|
\tablenums{0} & \texttt{Primitive} & an \texttt{OperationKind}
|
|
(Section~\ref{sec:ops:kinds}) \\
|
|
\tablenums{1} & \texttt{ResolveConflict} & \texttt{target}
|
|
(\texttt{ConflictId}, 16 BE) \cat{} \texttt{action}
|
|
(\texttt{ResolutionAction}) \\
|
|
\tablenums{2} & \texttt{UndoTransaction} & \texttt{target}
|
|
(\texttt{TransactionId}, 16 BE) \cat{} \texttt{policy}
|
|
(\texttt{UndoPolicy}, 1 byte) \\
|
|
\tablenums{3} & \texttt{ResolveEquivocation} & \texttt{target}
|
|
(\texttt{OperationId}, 16 BE) \cat{} \texttt{chosen}
|
|
(\texttt{EnvelopeHash}, 32) \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
Discriminants \tablenums{0}--\tablenums{2} are the ratified v1 values;
|
|
\tablenums{3} was appended by the catalog's \sectionsc{ResolveEquivocation
|
|
(meta-operation)} entry. The vocabulary is append-only: new meta-operations
|
|
take $\geq 4$.
|
|
|
|
\texttt{UndoPolicy} is one byte: \tablenums{0} StrictInverse,
|
|
\tablenums{1} BestEffort, \tablenums{2} Cascade.
|
|
\texttt{ResolutionAction} is one byte then an optional payload
|
|
(\texttt{app:bytes}, core requirement
|
|
\texttt{req:semops:\allowbreak resolution-action-discriminants}): \tablenums{0}
|
|
AcceptLoser, \tablenums{1} KeepWinner, \tablenums{2} Override
|
|
(\cat{} \texttt{OperationId} 16 BE), \tablenums{3} Reanchor (\cat{}
|
|
\texttt{TypedObjectId}), \tablenums{4} Dismiss, \tablenums{5} Registered
|
|
(\cat{} \texttt{ResolutionRegistryId} 16 BE).
|
|
|
|
\section{\texttt{OperationKind}: the Primitive Wire Discriminants}
|
|
\label{sec:ops:kinds}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:kind-discriminants}
|
|
The \texttt{OperationKind} wire discriminant is one byte, golden-locked to
|
|
the table below. The vocabulary is \textbf{append-only}: new primitive kinds
|
|
take discriminants past \tablenums{29}; the assignments below never change.
|
|
\end{requirement}
|
|
|
|
Each row also pins the payload's byte layout; the field \emph{set and order}
|
|
is the Operation Catalog's (the cited section governs semantics).
|
|
``$\mathrm{lp}(T)$'' means the \texttt{u32}-LE-length-prefixed
|
|
\texttt{CanonicalValue} bytes of a \texttt{T}
|
|
(Section~\ref{sec:values:canonical-value}); bare identifiers are raw 16
|
|
big-endian bytes; ``$\mathrm{seq}^{\uparrow}$'' is a \texttt{push\_seq}
|
|
sequence sorted ascending by canonical bytes, and
|
|
``$\mathrm{seq}^{\Uparrow}$'' is the same sequence constrained to be
|
|
\emph{strictly} increasing --- a set, not a multiset. A decoder \MUST{}
|
|
reject a $\mathrm{seq}^{\Uparrow}$ containing a duplicate rather than
|
|
normalize it away, since the duplicate is the only evidence that the writer
|
|
and the reader disagree about the field's type.
|
|
|
|
\begingroup\footnotesize
|
|
\begin{longtable}{p{0.32in} >{\raggedright\arraybackslash}p{1.5in} >{\raggedright\arraybackslash}p{2.35in} >{\raggedright\arraybackslash}p{1.35in}}
|
|
\toprule
|
|
\textbf{Disc} & \textbf{Kind} & \textbf{Payload layout} &
|
|
\textbf{Catalog section} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{0} & \texttt{InsertEvent} &
|
|
\texttt{staff\_instance} (16) \cat{} $\mathrm{lp}$(\texttt{Event}) &
|
|
\sectionsc{InsertEvent} \\
|
|
\tablenums{1} & \texttt{DeleteEvent} &
|
|
\texttt{event} (16) \cat{} \texttt{TupletCompensation} &
|
|
\sectionsc{DeleteEvent} \\
|
|
\tablenums{2} & \texttt{RespellPitch} &
|
|
\texttt{pitch} (16) \cat{} $\mathrm{lp}$(\texttt{PitchSpelling}) &
|
|
\sectionsc{RespellPitch} \\
|
|
\tablenums{3} & \texttt{CreateCrossCutting} &
|
|
\texttt{CrossCuttingValue} &
|
|
\sectionsc{CreateCrossCutting} \\
|
|
\tablenums{4} & \texttt{ChangeRegionTimeModel} &
|
|
\texttt{region} (16) \cat{} $\mathrm{lp}$(\texttt{RegionTimeModel})
|
|
\cat{} $\mathrm{seq}^{\uparrow}$(\texttt{EventId}) \cat{}
|
|
\texttt{PositionRemapping} &
|
|
\sectionsc{ChangeRegion\allowbreak TimeModel} \\
|
|
\tablenums{5} & \texttt{SetUserSystemBreak} &
|
|
\texttt{region} (16) \cat{} $\mathrm{lp}$(\texttt{TimeAnchor}) \cat{}
|
|
\texttt{present} (bool, 1) &
|
|
\sectionsc{SetUserSystemBreak} \\
|
|
\tablenums{6} & \texttt{DeclareTransaction} &
|
|
\texttt{id} (16) \cat{} \texttt{label} (NFC string) \cat{} option tag
|
|
\cat{} \texttt{TransactionCategory} if present &
|
|
\sectionsc{DeclareTransaction} \\
|
|
\tablenums{7} & \texttt{Registered} &
|
|
\texttt{OperationKindRegistryId} (16) \cat{}
|
|
$\mathrm{lp}$(opaque bytes) &
|
|
\sectionsc{K1 --- Framework Slots} \\
|
|
\tablenums{8} & \texttt{ModifyEvent} &
|
|
$\mathrm{lp}$(\texttt{Event}) &
|
|
\sectionsc{ModifyEvent} \\
|
|
\tablenums{9} & \texttt{Transpose} &
|
|
$\mathrm{seq}^{\uparrow}$(\texttt{PitchId}) \cat{}
|
|
\texttt{chromatic\_steps} (\texttt{i32} LE, 4). Frozen: the sequence is a
|
|
multiset, \emph{not} $\mathrm{seq}^{\Uparrow}$ &
|
|
\sectionsc{Transpose} \\
|
|
\tablenums{10} & \texttt{InsertIdentifiedPitch} &
|
|
\texttt{event} (16) \cat{} $\mathrm{lp}$(\texttt{IdentifiedPitch}) &
|
|
\sectionsc{Identified-Pitch Operations} \\
|
|
\tablenums{11} & \texttt{DeleteIdentifiedPitch} &
|
|
\texttt{pitch} (16) &
|
|
\sectionsc{Identified-Pitch Operations} \\
|
|
\tablenums{12} & \texttt{ModifyIdentifiedPitch} &
|
|
\texttt{pitch} (16) \cat{} $\mathrm{lp}$(\texttt{Pitch}) &
|
|
\sectionsc{Identified-Pitch Operations} \\
|
|
\tablenums{13} & \texttt{DeleteCrossCutting} &
|
|
\texttt{structure} (\texttt{TypedObjectId}, 18/34) &
|
|
\sectionsc{DeleteCrossCutting} \\
|
|
\tablenums{14} & \texttt{ModifyCrossCutting} &
|
|
\texttt{CrossCuttingValue} &
|
|
\sectionsc{ModifyCrossCutting} \\
|
|
\tablenums{15} & \texttt{CreateRegion} &
|
|
$\mathrm{lp}$(\texttt{Region}) &
|
|
\sectionsc{Structural Containers} \\
|
|
\tablenums{16} & \texttt{DeleteRegion} &
|
|
\texttt{region} (16) &
|
|
\sectionsc{Structural Containers} \\
|
|
\tablenums{17} & \texttt{CreateStaffInstance} &
|
|
\texttt{region} (16) \cat{} $\mathrm{lp}$(\texttt{StaffInstance}) &
|
|
\sectionsc{Structural Containers} \\
|
|
\tablenums{18} & \texttt{DeleteStaffInstance} &
|
|
\texttt{staff\_instance} (16) &
|
|
\sectionsc{Structural Containers} \\
|
|
\tablenums{19} & \texttt{CreateVoice} &
|
|
\texttt{staff\_instance} (16) \cat{} $\mathrm{lp}$(\texttt{Voice}) &
|
|
\sectionsc{Structural Containers} \\
|
|
\tablenums{20} & \texttt{DeleteVoice} &
|
|
\texttt{voice} (16) &
|
|
\sectionsc{Structural Containers} \\
|
|
\tablenums{21} & \texttt{SetMetadata} &
|
|
$\mathrm{lp}$(\texttt{ScoreMetadata}) &
|
|
\sectionsc{Score Settings} \\
|
|
\tablenums{22} & \texttt{SetMetricGrid} &
|
|
\texttt{region} (16) \cat{} option tag (\tablenums{0}/\tablenums{1})
|
|
\cat{} $\mathrm{lp}$(\texttt{MetricGrid}) if present &
|
|
\sectionsc{Score Settings} \\
|
|
\tablenums{23} & \texttt{SetUserPageBreak} &
|
|
\texttt{region} (16) \cat{} $\mathrm{lp}$(\texttt{TimeAnchor}) \cat{}
|
|
\texttt{present} (bool, 1) &
|
|
\sectionsc{Score Settings} \\
|
|
\tablenums{24} & \texttt{CreateStaff} &
|
|
$\mathrm{lp}$(\texttt{Staff}) &
|
|
\sectionsc{CreateStaff} \\
|
|
\tablenums{25} & \texttt{SetTimeSignature} &
|
|
\texttt{region} (16) \cat{} $\mathrm{lp}$(\texttt{TimeAnchor}) \cat{}
|
|
option tag (\tablenums{0}/\tablenums{1}) \cat{}
|
|
$\mathrm{lp}$(\texttt{TimeSignature}) if present &
|
|
\sectionsc{Meter and Tempo Overwrites} \\
|
|
\tablenums{26} & \texttt{SetTempoSegment} &
|
|
option tag \cat{} \texttt{region} (16) if present \cat{}
|
|
$\mathrm{lp}$(\texttt{TimeAnchor}) \cat{} option tag \cat{}
|
|
$\mathrm{lp}$(\texttt{TempoSegment}) if present &
|
|
\sectionsc{Meter and Tempo Overwrites} \\
|
|
\tablenums{27} & \texttt{SetStaffLayout} &
|
|
\texttt{staff\_instance} (16) \cat{} option tag \cat{}
|
|
\texttt{instrument\_override} (16) if present \cat{} option tag \cat{}
|
|
$\mathrm{lp}$(\texttt{StaffLineConfiguration}) if present \cat{}
|
|
\texttt{visible} (bool, 1) &
|
|
\sectionsc{SetStaffLayout} \\
|
|
\tablenums{28} & \texttt{CreateRepeatStructure} &
|
|
$\mathrm{lp}$(\texttt{RepeatStructure}) --- the schema-major-2
|
|
layout, unconditionally (born at v2;
|
|
Section~\ref{sec:evolution:major2}) &
|
|
\sectionsc{Repeat Structures} \\
|
|
\tablenums{29} & \texttt{DeleteRepeatStructure} &
|
|
\texttt{repeat} (16) &
|
|
\sectionsc{Repeat Structures} \\
|
|
\tablenums{30} & \texttt{TransposeInterval} &
|
|
$\mathrm{seq}^{\Uparrow}$(\texttt{PitchId}) \cat{}
|
|
\texttt{diatonic\_steps} (\texttt{i32} LE, 4) \cat{}
|
|
\texttt{chromatic\_steps} (\texttt{i32} LE, 4) &
|
|
\sectionsc{TransposeInterval} \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
Sub-vocabularies used above (one discriminant byte, then the listed
|
|
payload):
|
|
|
|
\begin{description}
|
|
\item[\texttt{TupletCompensation}.] \tablenums{0} NotInTuplet (empty);
|
|
\tablenums{1} ReplaceWithRest \cat{} $\mathrm{lp}$(\texttt{Rest});
|
|
\tablenums{2} RewriteTuplets \cat{}
|
|
$\mathrm{seq}^{\uparrow}$(\texttt{TupletId});
|
|
\tablenums{3} CascadeDeleteTuplets \cat{}
|
|
$\mathrm{seq}^{\uparrow}$(\texttt{TupletId}).
|
|
\item[\texttt{CrossCuttingValue}.] \tablenums{0} Tie, \tablenums{1} Slur,
|
|
\tablenums{2} Beam, \tablenums{3} Spanner; in every case the tag is
|
|
followed by $\mathrm{lp}$(the typed structure's \texttt{CanonicalValue}
|
|
bytes).
|
|
\item[\texttt{PositionRemapping}.] \tablenums{0} PreserveTime (empty);
|
|
\tablenums{1} Reassign \cat{} \texttt{u32} LE entry count \cat{}
|
|
entries, each \texttt{EventId} (16, raw) \cat{}
|
|
$\mathrm{lp}$(\texttt{MusicalPosition}), ascending by \texttt{EventId}.
|
|
\item[\texttt{TransactionCategory}.] \tablenums{0} NoteEntry,
|
|
\tablenums{1} Structural, \tablenums{2} Layout, \tablenums{3} Import,
|
|
\tablenums{4} Registered \cat{} \texttt{OperationKindRegistryId} (16 BE)
|
|
--- governed by \texttt{app:bytes} (core requirement
|
|
\texttt{req:semops:transaction-category}).
|
|
\end{description}
|
|
|
|
\texttt{OperationKind} is encode-only at the storage boundary, like the
|
|
envelope that carries it.
|
|
|
|
\section{\texttt{OperationKindTag}: a Separate Discriminant Space}
|
|
\label{sec:ops:kind-tag}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:kind-tag}
|
|
\texttt{OperationKindTag} --- the payload-free projection of an operation
|
|
kind, stored by edit barriers
|
|
(Chapter~\ref{ch:barriers}) --- has its \textbf{own} discriminant space,
|
|
assigned independently of Section~\ref{sec:ops:kinds}, with both encode and
|
|
validating decode. One byte; only \texttt{Registered} carries a payload (its
|
|
\texttt{OperationKindRegistryId}, 16 big-endian bytes, total 17). Unknown
|
|
discriminants and wrong lengths are decode errors. Append-only past
|
|
\tablenums{29}.
|
|
\end{requirement}
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{0.5in} p{2.0in} p{0.5in} p{2.0in}}
|
|
\toprule
|
|
\textbf{Disc} & \textbf{Tag} & \textbf{Disc} & \textbf{Tag} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{0} & \texttt{InsertEvent} & \tablenums{12} & \texttt{DeleteStaffInstance} \\
|
|
\tablenums{1} & \texttt{DeleteEvent} & \tablenums{13} & \texttt{SetUserSystemBreak} \\
|
|
\tablenums{2} & \texttt{ModifyEvent} & \tablenums{14} & \texttt{SetUserPageBreak} \\
|
|
\tablenums{3} & \texttt{RespellPitch} & \tablenums{15} & \texttt{DeclareTransaction} \\
|
|
\tablenums{4} & \texttt{Transpose} & \tablenums{16} & \texttt{Registered} \\
|
|
\tablenums{5} & \texttt{CreateCrossCutting} & \tablenums{17} & \texttt{InsertIdentifiedPitch} \\
|
|
\tablenums{6} & \texttt{DeleteCrossCutting} & \tablenums{18} & \texttt{DeleteIdentifiedPitch} \\
|
|
\tablenums{7} & \texttt{ModifyCrossCutting} & \tablenums{19} & \texttt{ModifyIdentifiedPitch} \\
|
|
\tablenums{8} & \texttt{ChangeRegionTimeModel} & \tablenums{20} & \texttt{CreateVoice} \\
|
|
\tablenums{9} & \texttt{InsertRegion} & \tablenums{21} & \texttt{DeleteVoice} \\
|
|
\tablenums{10} & \texttt{DeleteRegion} & \tablenums{22} & \texttt{SetMetadata} \\
|
|
\tablenums{11} & \texttt{InsertStaffInstance} & \tablenums{23} & \texttt{SetMetricGrid} \\
|
|
\tablenums{24} & \texttt{InsertStaff} & \tablenums{25} & \texttt{SetTimeSignature} \\
|
|
\tablenums{26} & \texttt{SetTempoSegment} & \tablenums{27} & \texttt{SetStaffLayout} \\
|
|
\tablenums{28} & \texttt{CreateRepeatStructure} & \tablenums{29} & \texttt{DeleteRepeatStructure} \\
|
|
\tablenums{30} & \texttt{TransposeInterval} & & \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\textbf{Naming mapping.} The kind-to-tag projection renames three pairs:
|
|
\texttt{OperationKind::\allowbreak CreateRegion} projects to
|
|
\texttt{OperationKindTag::\allowbreak InsertRegion}, and
|
|
\texttt{OperationKind::\allowbreak CreateStaffInstance} projects to
|
|
\texttt{OperationKindTag::\allowbreak InsertStaffInstance}, and
|
|
\texttt{OperationKind::\allowbreak CreateStaff} projects to
|
|
\texttt{OperationKindTag::\allowbreak InsertStaff}. The mismatch is
|
|
intentional
|
|
(the tag space predates the M2c naming) and is pinned here so barrier
|
|
authors target the right tag. The repeat-structure pair projects
|
|
name-verbatim (\texttt{CreateRepeatStructure}/%
|
|
\texttt{DeleteRepeatStructure}), as the cross-cutting tags do.
|
|
|
|
\section{Stamps and Causal Contexts}
|
|
\label{sec:ops:stamp}
|
|
|
|
\texttt{HybridLogicalClock}: \texttt{physical\_time} (\texttt{WallClockTime},
|
|
\texttt{i64} LE, 8) \cat{} \texttt{logical\_counter} (\texttt{u32} LE, 4).
|
|
\texttt{OperationStamp}: \texttt{hlc} (12) \cat{} \texttt{id}
|
|
(\texttt{OperationId}, 16 BE) --- \textbf{28 bytes} total, fixed width. The
|
|
canonical reduction tuple orders stamps as $(\mathit{physical\_time},
|
|
\mathit{logical\_counter}, \mathit{replica}, \mathit{counter})$, ascending;
|
|
the replica comparison equals big-endian byte order.
|
|
|
|
\label{sec:ops:causal}
|
|
\texttt{CausalContext} (dotted version vector):
|
|
|
|
\begin{center}
|
|
\begin{tabular}{p{0.7in} p{4.55in}}
|
|
\toprule
|
|
\textbf{Field} & \textbf{Encoding} \\
|
|
\midrule
|
|
vector & \texttt{u32} LE entry count \cat{} entries, each
|
|
\texttt{replica} (8 BE) \cat{} \texttt{counter} (\texttt{u64} LE, 8),
|
|
ascending by replica \\
|
|
dots & \texttt{u32} LE count \cat{} \texttt{OperationId}s (16 BE each),
|
|
ascending \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
The vector is \emph{zero-based-floor} (P11-C7): an entry $(r, c)$ covers
|
|
counters $0..=c$ of replica $r$; absence of a replica covers nothing.
|
|
|
|
\section{Effects, Conflicts, Anomalies, and Materialized State}
|
|
\label{sec:ops:effects}
|
|
|
|
These types are canonical \emph{materialized} facts: they appear in
|
|
\texttt{MaterializedState}'s canonical bytes, so their discriminants and
|
|
layouts are normative wire facts even though they are never stored inside
|
|
envelopes. All decode validation is performed by the ops layer's validating
|
|
decoder; unknown discriminants, bad lengths, non-canonical orders, and
|
|
trailing bytes are decode errors.
|
|
|
|
\begingroup\footnotesize
|
|
\begin{longtable}{>{\raggedright\arraybackslash}p{1.6in} p{4.1in}}
|
|
\toprule
|
|
\textbf{Type} & \textbf{Layout (tag byte, then payload)} \\
|
|
\midrule
|
|
\endhead
|
|
\texttt{OperationEffect} &
|
|
\tablenums{0} Applied (empty);
|
|
\tablenums{1} AppliedWithRepair \cat{} seq(\texttt{RepairRecord});
|
|
\tablenums{2} Conflicted \cat{} \texttt{ConflictId} (16 BE);
|
|
\tablenums{3} TombstonedTarget \cat{} \texttt{TypedObjectId};
|
|
\tablenums{4} NoOp \cat{} \texttt{NoOpReason}. \\
|
|
\texttt{NoOpReason} &
|
|
\tablenums{0} TargetTombstoned; \tablenums{1} AlreadyApplied;
|
|
\tablenums{2} SupersededByLaterOperation \cat{} \texttt{OperationId}
|
|
(16 BE); \tablenums{3} PreconditionFailedUnderReduction \cat{}
|
|
\texttt{PreconditionFailureReason}; \tablenums{4} TransactionConflict. \\
|
|
\texttt{Precondition\allowbreak FailureReason} &
|
|
\tablenums{0} TargetMissing; \tablenums{1} TargetTombstoned;
|
|
\tablenums{2} WrongRegionTimeModel;
|
|
\tablenums{3} TupletCompensationInvalid;
|
|
\tablenums{4} EventDurationInvalid; \tablenums{5} PositionOutsideRegion;
|
|
\tablenums{6} PitchSpaceMismatch; \tablenums{7} VoiceMissing;
|
|
\tablenums{8} ExtensionPrecondition \cat{} id (16 BE);
|
|
\tablenums{9} Registered \cat{} id (16 BE);
|
|
\tablenums{10} ContainerNotEmpty;
|
|
\tablenums{11} TempoMapMalformed;
|
|
\tablenums{12} SystemDerivedContentImmutable (Pass~12, P12-K3);
|
|
\tablenums{13} RecreateContentMismatch (Pass~12, P12-K9). \\
|
|
\texttt{RepairRecord} &
|
|
(struct) \texttt{kind} (\texttt{RepairKind}) \cat{} \texttt{target}
|
|
(\texttt{TypedObjectId}). \\
|
|
\texttt{RepairKind} &
|
|
\tablenums{0} Reanchored \cat{} \texttt{from} (\texttt{TypedObjectId})
|
|
\cat{} \texttt{to} (\texttt{TypedObjectId}) \cat{}
|
|
\texttt{ReanchorReason};
|
|
\tablenums{1} SpannerTruncated \cat{} seq(\texttt{TypedObjectId});
|
|
\tablenums{2} Orphaned; \tablenums{3} CascadeDeleted;
|
|
\tablenums{4} AttachmentTombstoned;
|
|
\tablenums{5} VoicePromoted \cat{} \texttt{from} (\texttt{VoiceId},
|
|
16 BE) \cat{} \texttt{to} (\texttt{VoiceId}, 16 BE);
|
|
\tablenums{6} TupletCompensated \cat{} \texttt{TupletCompensationKind};
|
|
\tablenums{7} Registered \cat{} id (16 BE). \\
|
|
\texttt{ReanchorReason} &
|
|
\tablenums{0} SameVoiceNearer; \tablenums{1} SameStaffInstanceNearer;
|
|
\tablenums{2} SameStaffNearer; \tablenums{3} SameRegionNearer;
|
|
\tablenums{4} ExplicitFallback;
|
|
\tablenums{5} DeclaredByExtension \cat{} id (16 BE);
|
|
\tablenums{6} SameCanvasNearer (Pass~12, P12-C4: semantically the
|
|
rank-4 proximity variant; appended at 6 because 5 was already
|
|
owned by DeclaredByExtension). \\
|
|
\texttt{TupletCompensationKind} &
|
|
\tablenums{0} ReplaceWithRest; \tablenums{1} RewriteTuplets;
|
|
\tablenums{2} CascadeDeleteTuplets. (No payload; distinct from the
|
|
payload-carrying \texttt{TupletCompensation} of
|
|
Section~\ref{sec:ops:kinds}.) \\
|
|
\texttt{PendingReason} &
|
|
tag \cat{} blocker (\texttt{OperationId}, 16 BE) in every variant:
|
|
\tablenums{0} MissingCausalPredecessor; \tablenums{1}
|
|
DependsOnEquivocated; \tablenums{2} DependsOnExcluded;
|
|
\tablenums{3} DependsOnPending; \tablenums{4} HaltedBySystemCollision. \\
|
|
\texttt{ConflictKind} &
|
|
\tablenums{0} StructuralFieldCollision \cat{} \texttt{winner} (16 BE)
|
|
\cat{} \texttt{loser} (16 BE) \cat{} \texttt{FieldPath} (NFC string);
|
|
\tablenums{1} TransactionConflict \cat{} \texttt{transaction} (16 BE)
|
|
\cat{} $\mathrm{seq}^{\uparrow}$(\texttt{OperationId});
|
|
\tablenums{2} TombstonedTarget \cat{} \texttt{TypedObjectId} \cat{}
|
|
\texttt{OperationId} (16 BE);
|
|
\tablenums{3} ReanchorFailure \cat{} \texttt{original\_referent}
|
|
(\texttt{TypedObjectId}) \cat{} \texttt{referencing\_object}
|
|
(\texttt{TypedObjectId});
|
|
\tablenums{4} TimeModelMigrationFailure \cat{} \texttt{region} (16 BE)
|
|
\cat{} $\mathrm{seq}^{\uparrow}$(\texttt{TypedObjectId});
|
|
\tablenums{5} ExtensionConflict \cat{} \texttt{kind\_id} (16 BE) \cat{}
|
|
$\mathrm{lp}$(opaque details). \\
|
|
\texttt{ConflictResolutionState} &
|
|
\tablenums{0} Unresolved; \tablenums{1} Resolved \cat{} \texttt{by}
|
|
(16 BE) \cat{} \texttt{ResolutionAction}; \tablenums{2} Dismissed \cat{}
|
|
\texttt{by} (16 BE). \\
|
|
\texttt{ConflictRecord} &
|
|
(struct) \texttt{id} (\texttt{ConflictId}, 16 BE) \cat{}
|
|
seq(\texttt{caused\_by}: \texttt{OperationId}, stored sorted) \cat{}
|
|
\texttt{kind} \cat{} seq(\texttt{affected\_objects}:
|
|
\texttt{TypedObjectId}, stored sorted) \cat{}
|
|
\texttt{resolution\_state}. \\
|
|
\texttt{IntegrityAnomaly} &
|
|
(struct) \texttt{id} (\texttt{IntegrityAnomalyId}, 16 BE) \cat{}
|
|
\texttt{kind}. \\
|
|
\texttt{IntegrityAnomalyKind} &
|
|
\tablenums{0} SystemIdentifierCollision \cat{} \texttt{ObjectKind}
|
|
\cat{} \texttt{colliding\_counter} (\texttt{u64} LE) \cat{} the two
|
|
input-set blobs, each $\mathrm{lp}$(bytes), emitted lo${}\leq{}$hi by
|
|
byte comparison;
|
|
\tablenums{1} OperationSlotEquivocated \cat{} \texttt{OperationId}
|
|
(16 BE);
|
|
\tablenums{2} ReplicaStreamQuarantined \cat{} \texttt{replica} (8 BE)
|
|
\cat{} \texttt{first\_bad\_counter} (\texttt{u64} LE);
|
|
\tablenums{3} Registered \cat{} id (16 BE). \\
|
|
\texttt{AnomalousReplicaSegment} &
|
|
(struct) \texttt{replica} (8 BE) \cat{} \texttt{first\_bad\_counter}
|
|
(\texttt{u64} LE) \cat{} \texttt{ReplicaAnomalyReason}
|
|
(\tablenums{0} HlcMonotonicityViolation \cat{} two
|
|
\texttt{OperationId}s; \tablenums{1} Registered \cat{} id 16 BE)
|
|
\cat{} seq(\texttt{excluded}: \texttt{OperationId}, ascending counter). \\
|
|
\texttt{FieldPath} &
|
|
an NFC string (\texttt{u32} LE length \cat{} UTF-8). \\
|
|
\texttt{ObjectState} &
|
|
\tablenums{0} Live (empty); \tablenums{1} Tombstoned \cat{}
|
|
\texttt{deleted\_by} (\texttt{OperationId}, 16 BE) \cat{}
|
|
\texttt{minted\_by} (\texttt{OperationId}, 16 BE). \\
|
|
\texttt{ObjectKind} (ops) &
|
|
\tablenums{0} Voice; \tablenums{1} Pitch; \tablenums{2} Registered
|
|
\cat{} \texttt{OperationKindRegistryId} (16 BE) --- governed by
|
|
\texttt{app:bytes} (core requirement
|
|
\texttt{req:graph:object-kind-vocab}). Distinct from the barrier layer's
|
|
2-byte \texttt{ObjectKind} (Chapter~\ref{ch:barriers}). \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\subsection{\texttt{MaterializedState}}
|
|
\label{sec:ops:materialized}
|
|
|
|
The canonical bytes of a reduction's materialized state are eight sections,
|
|
concatenated; every section is \texttt{u32}-LE-counted (the conflict
|
|
registry carries its own count) and in the stated normative order, so the
|
|
bytes are identical across any permutation of the input operation set:
|
|
|
|
\begin{enumerate}
|
|
\item \textbf{effects} --- count, then per entry \texttt{OperationId}
|
|
(16 BE, raw) \cat{} $\mathrm{lp}$(\texttt{OperationEffect}); in
|
|
canonical reduction order;
|
|
\item \textbf{conflict registry} --- a seq of
|
|
$\mathrm{lp}$(\texttt{ConflictRecord}), ascending \texttt{ConflictId};
|
|
\item \textbf{anomalies} --- count, then each
|
|
$\mathrm{lp}$(\texttt{IntegrityAnomaly}), ascending
|
|
\texttt{IntegrityAnomalyId};
|
|
\item \textbf{objects} --- count, then per entry \texttt{TypedObjectId}
|
|
(raw) \cat{} \texttt{ObjectState}, ascending \texttt{TypedObjectId};
|
|
\item \textbf{spellings} --- count, then per entry \texttt{PitchId}
|
|
(16 BE, raw) \cat{} $\mathrm{lp}$(\texttt{PitchSpelling}), ascending
|
|
\texttt{PitchId};
|
|
\item \textbf{breaks} --- count, then per entry \texttt{RegionId}
|
|
(16 BE, raw) \cat{} \texttt{MusicalPosition} (raw \texttt{RationalTime}
|
|
form, self-describing, \emph{not} leaf-framed) \cat{} \texttt{present}
|
|
(bool, 1); ascending $(\mathit{region}, \mathit{position})$;
|
|
\item \textbf{page breaks} --- same shape and order as breaks;
|
|
\item \textbf{pending} --- count, then per entry \texttt{OperationId}
|
|
(16 BE, raw) \cat{} \texttt{PendingReason}, ascending
|
|
\texttt{OperationId}.
|
|
\end{enumerate}
|
|
|
|
Decode is fully validating (orders, tags, lengths, trailing bytes) and
|
|
rejects non-canonical input.
|
|
|
|
\section{v0 Payloads Have No Wire Form}
|
|
\label{sec:ops:v0}
|
|
|
|
The prototype's v0 identifier-projection payload types are an
|
|
\textbf{in-memory migration guard only}: they carry no canonical encoding,
|
|
and no v0 wire form was ever shipped. The v0${\to}$v1 migration is
|
|
\emph{semantic} --- typed value reconstruction, specified in the Operation
|
|
Catalog's \sectionsc{v0 $\rightarrow$ v1 Payload Migration} chapter --- not a
|
|
byte-level translation, and nothing in this document defines bytes for a v0
|
|
payload.
|
|
|
|
% ===========================================================================
|
|
\chapter{Bundle Physical Layout}
|
|
\label{ch:bundle}
|
|
|
|
The physical file: the fixed prelude, chunks, blocks, the manifest, and the
|
|
operation index. This chapter \textbf{ratifies} the bundle crate's
|
|
provisional encodings (its \texttt{DECISIONS} entries P11-D2, P11-D4, and
|
|
P11-D5 pending-companion status is discharged) and the operation-index
|
|
byte form (P12-D1). Structural \emph{semantics} --- superblock selection,
|
|
commit protocol, recovery, retention --- are core specification Chapter~8;
|
|
this chapter pins bytes only. Regime (a) applies except where a table says
|
|
otherwise.
|
|
|
|
\section{The Fixed Header (64 bytes)}
|
|
\label{sec:bundle:header}
|
|
|
|
Offset 0 of every bundle. Written at creation, never rewritten within a
|
|
format major version. All integers little-endian.
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{0.8in} p{1.7in} p{3.1in}}
|
|
\toprule
|
|
\textbf{Range} & \textbf{Field} & \textbf{Value / rule} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{0..8} & magic & \texttt{MUSCBND\textbackslash0} (8 bytes, ASCII,
|
|
trailing NUL) \\
|
|
\tablenums{8..10} & \texttt{format\_major} (\texttt{u16}) & \tablenums{0}
|
|
in this version; readers reject other values \\
|
|
\tablenums{10..12} & \texttt{format\_minor} (\texttt{u16}) & \tablenums{1}
|
|
written in this version \\
|
|
\tablenums{12..16} & \texttt{header\_length} (\texttt{u32}) &
|
|
\tablenums{64}; readers reject other values \\
|
|
\tablenums{16..24} & \texttt{superblock\_a\_offset} (\texttt{u64}) &
|
|
\tablenums{64}; readers reject other values \\
|
|
\tablenums{24..32} & \texttt{superblock\_b\_offset} (\texttt{u64}) &
|
|
\tablenums{320}; readers reject other values \\
|
|
\tablenums{32..48} & \texttt{file\_uuid} & 16 opaque bytes \\
|
|
\tablenums{48..60} & reserved & written zero; ignored on read \\
|
|
\tablenums{60..64} & \texttt{header\_crc} & CRC-32C of bytes
|
|
\tablenums{0..60}, \texttt{u32} LE \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
Readers \MUST{} verify the magic and the CRC before consulting any other part
|
|
of the file.
|
|
|
|
\section{The Superblock Slots (256 bytes each)}
|
|
\label{sec:bundle:superblock}
|
|
|
|
Two slots, at offsets 64 (A) and 320 (B); the only mutable on-disk objects.
|
|
Selection \emph{semantics} (highest valid committed generation, the
|
|
equal-generation rule, torn-write fallback) are core Chapter~8,
|
|
\sectionsc{Superblock Selection}; the bytes:
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{0.9in} p{2.15in} p{2.55in}}
|
|
\toprule
|
|
\textbf{Range} & \textbf{Field} & \textbf{Encoding} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{0..8} & magic & \texttt{MUSCSUPR} (8 ASCII bytes) \\
|
|
\tablenums{8..16} & \texttt{generation} & \texttt{u64} LE \\
|
|
\tablenums{16..24} & \texttt{manifest\_offset} & \texttt{u64} LE \\
|
|
\tablenums{24..32} & \texttt{manifest\_length} & \texttt{u64} LE (also the
|
|
uncompressed length; the manifest is mandatorily uncompressed) \\
|
|
\tablenums{32..64} & \texttt{manifest\_hash} & 32 raw digest bytes \\
|
|
\tablenums{64..68} & \texttt{manifest\_schema\_version} &
|
|
\texttt{SchemaVersion} (\texttt{u16} LE \cat{} \texttt{u16} LE) \\
|
|
\tablenums{68..72} & \texttt{reduction\_algorithm\_version} &
|
|
\texttt{u32} LE \\
|
|
\tablenums{72..92} & \texttt{profile\_id} & 20 bytes: \texttt{u32} LE
|
|
discriminant (\tablenums{0} Full, \tablenums{1} ReadOnly,
|
|
\tablenums{2} Lite, \tablenums{3} Custom) \cat{}
|
|
\texttt{ProfileRegistryId} (16 raw bytes; writers \MUST{} emit zero
|
|
unless Custom, readers ignore it unless Custom). Core requirement
|
|
\texttt{req:format:profileid-discriminants}. \\
|
|
\tablenums{92..100} & \texttt{commit\_state} & two \texttt{u32} LE words:
|
|
Committed $= (\tablenums{0}, \tablenums{0})$; any tag
|
|
${}\neq \tablenums{0}$ decodes as Reserved(value) and the slot is
|
|
invalid for ordinary selection. Writers in this version \MUST{} produce
|
|
only Committed. \\
|
|
\tablenums{100..108} & \texttt{commit\_timestamp} & \texttt{i64} LE
|
|
nanoseconds; advisory --- selection never consults it \\
|
|
\tablenums{108..252} & reserved & written zero; ignored on read \\
|
|
\tablenums{252..256} & \texttt{superblock\_crc} & CRC-32C of bytes
|
|
\tablenums{0..252}, \texttt{u32} LE \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\section{Chunks}
|
|
\label{sec:bundle:chunks}
|
|
|
|
The bundle body begins at
|
|
$\texttt{BODY\_START} = 320 + 256 = \tablenums{576}$. A chunk's on-disk form
|
|
is its raw (possibly compressed) payload bytes at a byte offset ---
|
|
\textbf{no per-chunk length prefix, magic, or trailer on disk}; the offset,
|
|
lengths, compression, and hash live in the referencing \texttt{ChunkRef}. A
|
|
chunk reference whose offset is below \texttt{BODY\_START} is malformed.
|
|
|
|
\textbf{Content hash preimage} (core Chapter~8, \sectionsc{Domain-Separated
|
|
Preimages}; governs identity):
|
|
\[
|
|
\mathrm{BLAKE3}\bigl(\mathit{domain} \cat \mathit{kind}\,(1) \cat
|
|
\mathit{schema}\,(4) \cat \mathit{uncompressed\_length}\,
|
|
(\texttt{u64}\ \mathrm{LE}) \cat \mathit{payload}\bigr)
|
|
\]
|
|
where $\mathit{domain}$ is \texttt{MUSCMANI} for \texttt{Manifest} chunks and
|
|
\texttt{MUSCCHNK} otherwise. Compression is \emph{not} in the preimage.
|
|
Blob chunks are addressed by the bare \texttt{BlobId} form instead
|
|
(Chapter~\ref{ch:primitives}).
|
|
|
|
\textbf{\texttt{ChunkKind}} is a single byte, a \textbf{closed} vocabulary
|
|
(no \texttt{Registered} variant; core requirement
|
|
\texttt{req:format:chunkkind-discriminants}): \tablenums{0}
|
|
OperationEnvelopeBlock, \tablenums{1} OperationIndex, \tablenums{2}
|
|
Snapshot, \tablenums{3} Blob, \tablenums{4} ExtensionData, \tablenums{5}
|
|
TextProjection, \tablenums{6} LayoutCache, \tablenums{7} IntegrityIndex,
|
|
\tablenums{8} Manifest.
|
|
|
|
\textbf{\texttt{CompressionAlgorithm}} is a fixed two bytes, discriminant
|
|
\cat{} parameter: None $= (\tablenums{0},\tablenums{0})$;
|
|
Zstd$\{$level$\}$ $= (\tablenums{1}, \mathit{level})$; Reserved$(v)$ $=
|
|
(\tablenums{2}, v)$. \texttt{None} is \emph{not} a bare tag --- its zero
|
|
parameter byte is always present.
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:compression-none-parameter}
|
|
A decoder \MUST{} reject a \texttt{CompressionAlgorithm} whose discriminant
|
|
is \tablenums{0} (\texttt{None}) and whose parameter byte is non-zero. It
|
|
\MUSTNOT{} ignore the byte, and \MUSTNOT{} normalize it to zero.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
Ignoring it made the codec \emph{lenient}, and therefore non-injective:
|
|
$(\tablenums{0}, \tablenums{255})$ and $(\tablenums{0}, \tablenums{0})$
|
|
decoded to one value, which re-encodes to the latter. Every structure
|
|
embedding a \texttt{ChunkRef} inherited that. A whole-value
|
|
re-encode-and-compare guard hides it --- the manifest has one --- but the
|
|
operation index does not, and so accepted two distinct payloads for one index
|
|
while its own contract promised to reject non-canonical bytes rather than
|
|
normalize them. Content-addressing rests on distinct bytes meaning distinct
|
|
values.
|
|
|
|
No conforming writer emits a non-zero parameter for \texttt{None}, so this
|
|
rejects only corrupt or adversarial input; no existing file changes meaning.
|
|
Found by the wire-decode fuzzer (Push~5, P3); supersedes the core
|
|
specification's earlier ``present but zero, and ignored on read''.
|
|
\end{rationale}
|
|
|
|
\textbf{Read rules.} Writers in this format version emit \texttt{None} only;
|
|
\emph{reading} zstd at any level is a conformance \MUST{}. A zstd payload is
|
|
decompressed into a buffer sized \emph{exactly} by the declared
|
|
\texttt{uncompressed\_length} (checked against the reader's resource policy
|
|
before allocation): a stream that would exceed the declaration, ends short of
|
|
it, or carries trailing garbage is corruption. After decompression the
|
|
content hash is verified over the uncompressed bytes, and
|
|
$\mathit{id} = \mathit{hash}$ is checked. A chunk whose declared schema
|
|
major differs from the reader's supported major is rejected
|
|
(Chapter~\ref{ch:evolution}). \texttt{Reserved} compression is rejected.
|
|
The \textbf{manifest chunk is mandatorily uncompressed}: a compressed
|
|
manifest reference is rejected before any bytes are read. The reference
|
|
reader bounds any single chunk at 256\,MiB
|
|
(\texttt{MAX\_CHUNK\_BYTES}); the bound is reader policy, not a wire fact.
|
|
|
|
\subsection{\texttt{ChunkRef} (95 bytes)}
|
|
\label{sec:bundle:chunkref}
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{0.9in} p{1.9in} p{2.8in}}
|
|
\toprule
|
|
\textbf{Range} & \textbf{Field} & \textbf{Encoding} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{0..32} & \texttt{id} & 32 raw digest bytes \\
|
|
\tablenums{32..33} & \texttt{kind} & \texttt{ChunkKind} byte \\
|
|
\tablenums{33..37} & \texttt{schema\_version} & \texttt{u16} LE \cat{}
|
|
\texttt{u16} LE \\
|
|
\tablenums{37..45} & \texttt{offset} & \texttt{u64} LE \\
|
|
\tablenums{45..53} & \texttt{compressed\_length} & \texttt{u64} LE \\
|
|
\tablenums{53..61} & \texttt{uncompressed\_length} & \texttt{u64} LE \\
|
|
\tablenums{61..63} & \texttt{compression} & 2 bytes \\
|
|
\tablenums{63..95} & \texttt{hash} & 32 raw digest bytes (restates
|
|
\texttt{id}) \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
The canonical sort key for chunk-reference collections is
|
|
$(\mathit{kind\ discriminant}, \mathit{hash}, \mathit{offset})$, ascending
|
|
--- the Appendix-D chunk-reference order. Every set-valued
|
|
\texttt{ChunkRef} field in this chapter is sorted by it and deduplicated
|
|
before encoding.
|
|
|
|
\section{Operation-Envelope Block Payloads}
|
|
\label{sec:bundle:blocks}
|
|
|
|
The uncompressed payload of an \texttt{OperationEnvelopeBlock} chunk:
|
|
|
|
\begin{center}
|
|
\texttt{u32} LE envelope count \cat{} per envelope
|
|
\{\texttt{u32} LE byte length \cat{} envelope bytes\}
|
|
\end{center}
|
|
|
|
Envelope bytes are opaque to the bundle (their internal form is
|
|
Chapter~\ref{ch:ops}). Each envelope's offset --- the byte offset of its
|
|
\emph{first content byte} within the uncompressed block payload, with its
|
|
length prefix at $\mathit{offset}-4$ --- is deterministically recoverable
|
|
from this framing alone; it is the coordinate the operation index records.
|
|
Writers \SHOULD{} begin a new block rather than let an uncompressed payload
|
|
exceed 1\,MiB (an individual envelope larger than that occupies its own
|
|
block); readers \MUST{} accept blocks up to the active profile's bound
|
|
(default 64\,MiB). Block boundaries are storage artifacts: the envelope set
|
|
is the union across blocks.
|
|
|
|
\section{The Manifest}
|
|
\label{sec:bundle:manifest}
|
|
|
|
On-disk manifest chunk payload:
|
|
|
|
\begin{center}
|
|
\texttt{manifest\_id} (16 bytes, \texttt{u128} \emph{little-endian}) \cat{}
|
|
\emph{body}
|
|
\end{center}
|
|
|
|
(The little-endian on-disk field is a deliberate, golden-locked quirk: the
|
|
\emph{derivation} truncates big-endian like every content-derived id, but
|
|
the bundle codec serializes the resulting \texttt{u128} under its
|
|
little-endian integer rule.)
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:manifest-id}
|
|
Restating core requirement \texttt{req:format:manifest-id}, which governs:
|
|
\[
|
|
\texttt{ManifestId} = \mathrm{trunc128}\bigl(\mathrm{BLAKE3}(
|
|
\texttt{MUSCMNIF} \cat \mathit{document\_id} \cat
|
|
\mathit{generation} \cat \mathit{body}\bigr)\bigr)
|
|
\]
|
|
with $\mathit{document\_id}$ as its 16 raw bytes, $\mathit{generation}$ as
|
|
\texttt{u64} little-endian, and $\mathit{body}$ the complete manifest body
|
|
below (which itself opens with \texttt{document\_id} and
|
|
\texttt{generation} --- the double commitment is intentional and locked).
|
|
Writers re-derive the id from the body at encode time; decoders verify it.
|
|
\end{requirement}
|
|
|
|
\textbf{The body} is a positional struct of thirteen fields:
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{0.3in} p{1.9in} p{3.45in}}
|
|
\toprule
|
|
\textbf{\#} & \textbf{Field} & \textbf{Encoding and canonical order} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{1} & \texttt{document\_id} & 16 raw bytes \\
|
|
\tablenums{2} & \texttt{lineage\_id} & option tag \cat{} 16 raw bytes if
|
|
present \\
|
|
\tablenums{3} & \texttt{generation} & \texttt{u64} LE \\
|
|
\tablenums{4} & \texttt{operation\_roots} & \texttt{u32} LE count \cat{}
|
|
\texttt{ChunkRef}s, sorted by the canonical chunk-reference key,
|
|
deduplicated \\
|
|
\tablenums{5} & \texttt{operation\_block\_summaries} & \texttt{u32} LE
|
|
count \cat{} entries \{\texttt{chunk\_id} (32) \cat{}
|
|
\texttt{OperationBlockSummary}\}, ascending \texttt{chunk\_id} \\
|
|
\tablenums{6} & \texttt{operation\_index\_root} & option tag \cat{}
|
|
\texttt{ChunkRef} \\
|
|
\tablenums{7} & \texttt{canonical\_base} & option tag \cat{}
|
|
\texttt{SnapshotRef} \\
|
|
\tablenums{8} & \texttt{acceleration\_snapshots} & \texttt{u32} LE count
|
|
\cat{} \texttt{SnapshotRef}s, sorted ascending by their full encoded
|
|
bytes, deduplicated \\
|
|
\tablenums{9} & \texttt{blob\_roots} & \texttt{u32} LE count \cat{}
|
|
\texttt{BlobRef}s, sorted ascending by encoded bytes, deduplicated \\
|
|
\tablenums{10} & \texttt{profile\_declarations} & \texttt{u32} LE count
|
|
\cat{} \texttt{ProfileDeclaration}s, sorted by the key
|
|
$(\texttt{profile\_id}, \texttt{version})$ with \texttt{SemVer} compared
|
|
\emph{numerically}, deduplicated \\
|
|
\tablenums{11} & \texttt{extension\_declarations} & \texttt{u32} LE count
|
|
\cat{} \texttt{ExtensionDeclaration}s, sorted by
|
|
$(\texttt{extension\_id}, \texttt{version})$, \texttt{SemVer} numeric,
|
|
deduplicated \\
|
|
\tablenums{12} & \texttt{text\_projection\_root} & option tag \cat{}
|
|
\texttt{ChunkRef} \\
|
|
\tablenums{13} & \texttt{integrity\_root} & option tag \cat{}
|
|
\texttt{ChunkRef} \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
Sub-records, positionally:
|
|
|
|
\begin{description}
|
|
\item[\texttt{OperationBlockSummary}] = \texttt{dvv\_summary}
|
|
(\texttt{FrontierBytes}: \texttt{u32}-LE-prefixed opaque bytes) \cat{}
|
|
\texttt{min\_stamp} (\texttt{u32}-LE-prefixed opaque stamp bytes) \cat{}
|
|
\texttt{max\_stamp} (same). The contents are ops-computed
|
|
(Chapter~\ref{ch:ops}); the bundle carries them verbatim.
|
|
\item[\texttt{SnapshotRef}] = \texttt{snapshot\_id} (16 raw) \cat{}
|
|
\texttt{covers\_causal\_frontier} (\texttt{FrontierBytes}) \cat{}
|
|
\texttt{reduction\_algorithm\_version} (\texttt{u32} LE) \cat{}
|
|
\texttt{profile\_id} (20 bytes, as in the superblock) \cat{}
|
|
\texttt{root} (\texttt{ChunkRef}, 95) \cat{} \texttt{hash} (32 raw).
|
|
\item[\texttt{BlobRef}] = \texttt{blob\_id} (32 raw) \cat{}
|
|
\texttt{media\_type} (\texttt{u32}-LE-prefixed ASCII; decode validates a
|
|
well-formed RFC~6838 \texttt{type/subtype} restricted name) \cat{}
|
|
\texttt{offset} (\texttt{u64} LE) \cat{} \texttt{compressed\_length}
|
|
(\texttt{u64} LE) \cat{} \texttt{uncompressed\_length} (\texttt{u64} LE)
|
|
\cat{} \texttt{compression} (2) \cat{} \texttt{hash} (32 raw) \cat{}
|
|
option tag \cat{} \texttt{declared\_max\_uncompressed\_length}
|
|
(\texttt{u64} LE) if present.
|
|
\item[\texttt{ProfileDeclaration}] = \texttt{profile\_id} (20) \cat{}
|
|
\texttt{version} (\texttt{SemVer}, 12) \cat{} \texttt{constraints}
|
|
(\texttt{ProfileConstraints} =
|
|
\texttt{max\_uncompressed\_block\_size} (\texttt{u64} LE) \cat{}
|
|
\texttt{RetentionPolicy}). \texttt{RetentionPolicy} =
|
|
\texttt{retain\_previous\_manifests} (\texttt{u32} LE) \cat{} option tag
|
|
\cat{} \texttt{retain\_duration} (\texttt{i64} LE) if present \cat{}
|
|
\texttt{retain\_named\_checkpoints} (bool, 1).
|
|
\item[\texttt{ExtensionDeclaration}] = \texttt{extension\_id} (16 raw)
|
|
\cat{} \texttt{version} (\texttt{SemVer}, 12) \cat{} \texttt{required}
|
|
(bool, 1) \cat{} \texttt{u32} LE count \cat{}
|
|
\texttt{preserved\_chunk\_roots} (\texttt{ChunkRef}s, canonical order,
|
|
deduplicated) \cat{} \texttt{affected\_object\_kinds}
|
|
(\texttt{u32}-LE-prefixed opaque blob) \cat{} \texttt{edit\_barriers}
|
|
(\texttt{u32}-LE-prefixed opaque blob). The two blobs' internal form is
|
|
Chapter~\ref{ch:barriers}; the bundle preserves them verbatim.
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:manifest-canonical}
|
|
\textbf{Reject non-canonical manifests.} A decoder \MUST{} accept a manifest
|
|
payload only if re-encoding the decoded manifest reproduces the input
|
|
byte-for-byte. This single check subsumes: the stored \texttt{manifest\_id}
|
|
matching the body-derived id, every set-valued vector being in its canonical
|
|
order with no duplicates, and every optional field using presence byte
|
|
\tablenums{0}/\tablenums{1}. Unsorted, duplicated, or wrongly-identified
|
|
manifest bytes are malformed --- never re-sorted or repaired.
|
|
\end{requirement}
|
|
|
|
\section{The Operation Index}
|
|
\label{sec:bundle:opindex}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:opindex}
|
|
\textbf{Operation-index payload (ratifies P12-D1).} The uncompressed payload
|
|
of an \texttt{OperationIndex} chunk is:
|
|
|
|
\begin{center}
|
|
\texttt{u32} LE \texttt{block\_count} \cat{} that many \texttt{ChunkRef}s
|
|
(95 bytes each, strictly ascending canonical order) \cat{}
|
|
\texttt{u32} LE \texttt{entry\_count} \cat{} that many entries, each
|
|
\{\texttt{id} (16 raw operation-id bytes) \cat{} \texttt{block}
|
|
(\texttt{u32} LE ordinal into the block vector) \cat{} \texttt{offset}
|
|
(\texttt{u32} LE)\}, strictly ascending by \texttt{id} bytes.
|
|
\end{center}
|
|
|
|
The empty index is exactly 8 zero bytes. \texttt{offset} is the byte offset
|
|
of the envelope's \emph{first content byte} within the uncompressed payload
|
|
of the named block (the envelope's \texttt{u32} length prefix sits at
|
|
$\mathit{offset}-4$) --- well-defined by the id-leads property
|
|
(Requirement~\ref{req:binfmt:envelope}) and the block framing
|
|
(Section~\ref{sec:bundle:blocks}).
|
|
|
|
\textbf{One slot per id.} Strict ascent makes duplicate ids unrepresentable:
|
|
an index payload containing two entries with equal id bytes is malformed.
|
|
Equivocation candidates (multiple envelopes claiming one
|
|
\texttt{OperationId}) live \emph{inside} the operation set's slot model
|
|
(core Chapter~6), never as duplicate index rows.
|
|
|
|
\textbf{Staleness.} An index \emph{covers} a manifest iff its block vector
|
|
equals the manifest's deduplicated, canonically sorted
|
|
\texttt{operation\_roots} under \textbf{full \texttt{ChunkRef} equality} ---
|
|
every field, not just the content hash, since the index hands out its stored
|
|
references for reading. A reader that finds the index stale \MUST{} reject
|
|
it and rebuild from the blocks.
|
|
|
|
A rejected or stale index is \textbf{never bundle corruption}: the index is
|
|
a non-canonical accelerator, and rebuild-from-blocks is always sound.
|
|
|
|
Decoders \MUST{} reject: unsorted or duplicated blocks or entry ids, a block
|
|
reference whose kind is not \texttt{OperationEnvelopeBlock}, an entry whose
|
|
block ordinal is out of range, and trailing bytes.
|
|
\end{requirement}
|
|
|
|
The core specification's commit-time \SHOULD{} --- refresh the index when
|
|
the operation log has ``grown significantly'' since it was built --- has an
|
|
\textbf{implementation-defined} threshold in v0: this document deliberately
|
|
does not pin a number, and conforming writers may use any policy (including
|
|
always or never refreshing at commit), because the index is non-canonical.
|
|
|
|
\begin{openquestion}
|
|
Whether to pin a normative refresh threshold (e.g.\ a block-count or
|
|
byte-size ratio) or leave it permanently implementation-defined. Leaving it
|
|
open costs only lookup performance on stale-index bundles; pinning it too
|
|
early would constrain writers for no interoperability gain. Revisit when a
|
|
second writer implementation exists.
|
|
\end{openquestion}
|
|
|
|
% ===========================================================================
|
|
\chapter{Extension Declaration Blobs and Edit Barriers}
|
|
\label{ch:barriers}
|
|
|
|
The two opaque blobs a manifest \texttt{ExtensionDeclaration} carries ---
|
|
\texttt{affected\_object\_kinds} and \texttt{edit\_barriers} --- have their
|
|
internal byte form pinned here. This chapter \textbf{ratifies} P12-E1
|
|
(the blob and barrier-tree byte form), P12-E2 (the recursion bound), and
|
|
P12-E3 (the barrier \texttt{ObjectKind} representation). Barrier
|
|
\emph{semantics} --- scope matching, prohibited-edit evaluation,
|
|
conservative treatment of unknown extensions --- are core Chapter~8
|
|
(\sectionsc{Forward Compatibility and Edit Barriers}); this chapter pins
|
|
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}). 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}).
|
|
|
|
\begin{openquestion}
|
|
\textbf{Unsafe-edit tombstone encoding (from P12-E5).} Core Chapter~8 now
|
|
ratifies the unsafe-edit tombstone \emph{semantics} (immediate deactivation of
|
|
the crossed extension's remaining barriers; a durable record \MUST{} land at
|
|
the next commit; \texttt{required = true} interaction). The manifest-side
|
|
\emph{byte encoding} of that record is unresolved --- and constrained: the
|
|
manifest schema is frozen at major~0, so the record cannot be a new manifest
|
|
field. Candidate channels: a marker inside the extension-declaration blob
|
|
layer (this chapter's regime-(b) surface), or a dedicated chunk kind
|
|
referenced from \texttt{preserved\_chunk\_roots}. To be designed in the next
|
|
bundle-format tranche; the editor-side producer
|
|
(\texttt{extensions\_requiring\_tombstone()}) already exists.
|
|
\end{openquestion}
|
|
|
|
\section{Framing}
|
|
\label{sec:barriers:framing}
|
|
|
|
\begin{description}
|
|
\item[element] $\mathrm{elem}(x)$ = \texttt{u64} LE byte length \cat{}
|
|
$x$'s canonical bytes.
|
|
\item[set] $\mathrm{set}(xs)$ = elements' canonical byte strings, sorted
|
|
ascending byte-lexicographic and \emph{deduplicated}; then \texttt{u64}
|
|
LE element count \cat{} each element as $\mathrm{elem}$. Order and
|
|
repetition in the source collection cannot affect the bytes; decoders
|
|
reject unsorted or duplicated elements.
|
|
\item[list] $\mathrm{list}(xs)$ = \texttt{u64} LE count \cat{} each
|
|
element as $\mathrm{elem}$, order-significant (used only for condition
|
|
subtrees).
|
|
\end{description}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:ext-blobs}
|
|
\textbf{The two blobs (ratifies P12-E1).} The
|
|
\texttt{affected\_object\_kinds} blob is $\mathrm{set}$ of barrier
|
|
\texttt{ObjectKind} elements; the \texttt{edit\_barriers} blob is
|
|
$\mathrm{set}$ of \texttt{EditBarrier} elements. Both decode under
|
|
reject-never-normalize with trailing bytes forbidden.
|
|
\end{requirement}
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:object-kind-open}
|
|
\textbf{Barrier \texttt{ObjectKind} (ratifies P12-E3).} A barrier
|
|
\texttt{ObjectKind} is the \texttt{TypedObjectId} discriminant of
|
|
Section~\ref{sec:ids:typed-object-id}, encoded as \textbf{2 little-endian
|
|
bytes}. Decode is \textbf{open-value}: every \texttt{u16} decodes
|
|
successfully --- an unknown kind (a future core kind or an
|
|
extension-registered kind) is a \emph{value}, not a decode branch, and
|
|
simply never matches any object the reader knows. This is the format's
|
|
forward-compatibility stance for barrier kinds: unknown kinds are preserved
|
|
and re-emitted verbatim, never dropped or rejected.
|
|
\end{requirement}
|
|
|
|
\section{\texttt{EditBarrier}}
|
|
\label{sec:barriers:barrier}
|
|
|
|
An \texttt{EditBarrier}'s canonical bytes, positionally:
|
|
|
|
\begin{center}
|
|
\texttt{scope} \cat{} $\mathrm{set}$(\texttt{affected\_object\_kinds}:
|
|
\texttt{ObjectKind}) \cat{}
|
|
$\mathrm{set}$(\texttt{prohibited\_operation\_kinds}:
|
|
\texttt{OperationKindTag}, Section~\ref{sec:ops:kind-tag}) \cat{}
|
|
\texttt{condition}
|
|
\end{center}
|
|
|
|
\texttt{BarrierScope}: one discriminant byte, then the payload:
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{0.35in} p{1.35in} p{3.8in}}
|
|
\toprule
|
|
\textbf{Disc} & \textbf{Variant} & \textbf{Payload} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{0} & \texttt{WholeScore} & (none) \\
|
|
\tablenums{1} & \texttt{Region} & \texttt{RegionId}, 16 big-endian bytes \\
|
|
\tablenums{2} & \texttt{StaffInstance} & \texttt{StaffInstanceId}, 16 BE \\
|
|
\tablenums{3} & \texttt{AnalysisLayer} & \texttt{AnalysisLayerId}, 16 BE \\
|
|
\tablenums{4} & \texttt{ObjectSet} & $\mathrm{set}$ of
|
|
\texttt{TypedObjectId} canonical bytes (18/34 each) \\
|
|
\tablenums{5} & \texttt{PitchSpace} & \texttt{u64} LE byte length \cat{}
|
|
the pitch-space id's NFC UTF-8 bytes; decoders reject non-NFC input \\
|
|
\tablenums{6} & \texttt{TuningContext} & (none) \\
|
|
\tablenums{7} & \texttt{Registered} & \texttt{BarrierScopeRegistryId},
|
|
\texttt{u128} \emph{little-endian} (16 bytes) \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
\texttt{BarrierCondition}: one discriminant byte, then the payload; the tree
|
|
is recursive:
|
|
|
|
\begingroup\small
|
|
\begin{longtable}{p{0.35in} p{1.85in} p{3.3in}}
|
|
\toprule
|
|
\textbf{Disc} & \textbf{Variant} & \textbf{Payload} \\
|
|
\midrule
|
|
\endhead
|
|
\tablenums{0} & \texttt{Always} & (none) \\
|
|
\tablenums{1} & \texttt{ObjectExists} & \texttt{TypedObjectId} \\
|
|
\tablenums{2} & \texttt{ObjectHasExtensionData} & \texttt{object}
|
|
(\texttt{TypedObjectId}) \cat{} \texttt{extension}
|
|
(\texttt{ExtensionRef}, \texttt{u128} LE, 16) \\
|
|
\tablenums{3} & \texttt{All} & $\mathrm{list}$ of conditions \\
|
|
\tablenums{4} & \texttt{Any} & $\mathrm{list}$ of conditions \\
|
|
\tablenums{5} & \texttt{Not} & one $\mathrm{elem}$-framed condition \\
|
|
\tablenums{6} & \texttt{Registered} &
|
|
\texttt{BarrierConditionRegistryId}, \texttt{u128} LE (16) \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
Note the endianness: the barrier layer's registry identifiers
|
|
(\texttt{BarrierScopeRegistryId}, \texttt{BarrierConditionRegistryId},
|
|
\texttt{ExtensionRef}) encode \texttt{u128} \emph{little-endian}, unlike the
|
|
big-endian identifier family of Chapter~\ref{ch:ids}. This is part of the
|
|
golden-locked regime-(b) surface.
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:condition-depth}
|
|
\textbf{Recursion bound (ratifies P12-E2).}
|
|
$\texttt{MAX\_CONDITION\_DEPTH} = \tablenums{64}$ is the normative bound on
|
|
\texttt{BarrierCondition} nesting. Decoders \MUST{} reject a condition tree
|
|
nested deeper than 64 levels of \texttt{All}/\texttt{Any}/\texttt{Not};
|
|
writers \MUSTNOT{} emit one. The bound exists so adversarial bytes cannot
|
|
drive unbounded recursion; real barriers are depth 1--2.
|
|
\end{requirement}
|
|
|
|
% ===========================================================================
|
|
\chapter{Schema Evolution}
|
|
\label{ch:evolution}
|
|
|
|
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 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, and
|
|
Section~\ref{sec:evolution:major2} the schema-major-2 delta and its
|
|
migration from major~1.
|
|
|
|
\section{The Chunk-Level Gate}
|
|
\label{sec:evolution:gate}
|
|
|
|
Every chunk declares a \texttt{SchemaVersion} (major, minor) in its
|
|
\texttt{ChunkRef} and the superblock declares the manifest's. The gate:
|
|
|
|
\begin{itemize}
|
|
\item \textbf{Major} = incompatible. A reader \MUST{} reject a chunk whose
|
|
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}). Three majors are defined:
|
|
\tablenums{0}, \tablenums{1} (Section~\ref{sec:evolution:major1}), and
|
|
\tablenums{2} (Section~\ref{sec:evolution:major2}); the reference
|
|
implementation's accept-set is $\{0, 1, 2\}$ ($\textsc{min} = 0$,
|
|
$\textsc{max} = 2$).
|
|
\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
|
|
discriminant appended after the minor it otherwise declares, so that a
|
|
decode failure on an unknown appended discriminant is attributable to a
|
|
version skew rather than corruption.
|
|
\end{itemize}
|
|
|
|
\section{What ``Additive'' Means Here}
|
|
\label{sec:evolution:additive}
|
|
|
|
Under the frozen-layout rule (Requirement~\ref{req:binfmt:frozen-layout}),
|
|
positional struct layouts admit \emph{no} additive change: there is no
|
|
optional-field or trailing-field mechanism at the value level. In
|
|
particular, and stated honestly: \textbf{the manifest body cannot grow a
|
|
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}:
|
|
|
|
\begin{itemize}
|
|
\item \texttt{OperationKind}: append at ${\geq}\,\tablenums{30}$
|
|
(Requirement~\ref{req:binfmt:kind-discriminants}; the Phase-3
|
|
tranche took \tablenums{24}--\tablenums{27} under this mechanism,
|
|
and the repeat pair \tablenums{28}/\tablenums{29} in the
|
|
schema-major-2 revision --- the discriminant space is one append-only
|
|
table across majors, but a kind's \emph{block stamp} follows minimal
|
|
stamping over its payload: \tablenums{28}'s payload embeds a v2
|
|
layout, so its blocks stamp major~2,
|
|
Section~\ref{sec:evolution:major2});
|
|
\item \texttt{OperationKindTag}: append at ${\geq}\,\tablenums{30}$
|
|
(Requirement~\ref{req:binfmt:kind-tag});
|
|
\item \texttt{OperationPayload}: append at ${\geq}\,\tablenums{4}$
|
|
(Section~\ref{sec:ops:payload});
|
|
\item the value-layer unions enumerated as closed in
|
|
Requirement~\ref{req:binfmt:frozen-layout} may gain appended variants
|
|
only through a ratified revision of this document, also minor-additive.
|
|
\end{itemize}
|
|
|
|
\texttt{ChunkKind} is \textbf{closed} --- it has no \texttt{Registered}
|
|
variant and no append story inside major~0, because its discriminant enters
|
|
every chunk's hash preimage; a new chunk kind is a format-major event.
|
|
|
|
Separately, many vocabularies carry a \texttt{Registered} \emph{escape
|
|
variant} (\texttt{RepairKind}, \texttt{ReanchorReason},
|
|
\texttt{PreconditionFailureReason}, \texttt{IntegrityAnomalyKind},
|
|
\texttt{ReplicaAnomalyReason}, \texttt{TransactionCategory},
|
|
\texttt{ResolutionAction}, \texttt{ConflictKind} via
|
|
\texttt{ExtensionConflict}, \texttt{BarrierScope}/\texttt{BarrierCondition},
|
|
\texttt{TieClass}, \texttt{StaffGroupKind}, \texttt{PitchSpacePosition},
|
|
\texttt{SpellingNominal}, \texttt{TypedObjectId}, and the barrier
|
|
\texttt{ObjectKind}'s open value space). Extension through an escape variant
|
|
is \textbf{not} a schema change at all: the wire form is already defined.
|
|
|
|
\section{Migration Discipline}
|
|
\label{sec:evolution:migration}
|
|
|
|
A schema-\textsc{major} bump \MUST{} ship with a migration documented in
|
|
this document's revision history (Chapter~\ref{ch:history}): what changed,
|
|
byte-for-byte, and the deterministic translation from the old form. The
|
|
precedent is the v0${\to}$v1 operation-payload migration, which lives in the
|
|
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.
|
|
|
|
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}]$
|
|
(at the major-1 revision the reference implementation's set was $\{0, 1\}$;
|
|
the \emph{current} set is stated once, in
|
|
Section~\ref{sec:evolution:gate}, and is not repeated here). The
|
|
chunk-level 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.
|
|
|
|
\section{Schema Major 2}
|
|
\label{sec:evolution:major2}
|
|
|
|
Schema major~2 is the second data-model expansion major: it fills the
|
|
truncated bodies the v0 baseline deliberately staged to this companion ---
|
|
the cross-cutting structures (\texttt{Slur}, \texttt{Tie}, \texttt{Beam},
|
|
\texttt{Spanner}), \texttt{RepeatStructure}, \texttt{Staff} and
|
|
\texttt{StaffLineConfiguration}, \texttt{Instrument}, and
|
|
\texttt{ScoreMetadata} --- to the shapes the core specification's Chapter~5
|
|
ratifies (schema-major-2 revision). The leaf types those shapes reference
|
|
are defined in the same core revision; their wire layouts are below.
|
|
|
|
\subsection{Where the changed fields reach}
|
|
The major is assigned per payload type, exactly as in major~1:
|
|
|
|
\begin{itemize}
|
|
\item \textbf{Snapshot-only:} \texttt{Instrument} (no operation embeds
|
|
one); it reaches only the acceleration full-\texttt{Score} snapshot.
|
|
(\texttt{RepeatStructure} was snapshot-only when this section was
|
|
first ratified; the repeat-authoring pair moved it to the canonical
|
|
operation layer --- next bullet.)
|
|
\item \textbf{Canonical operation layer:} eight operation
|
|
payloads embed filled values. \texttt{CreateRepeatStructure} (this
|
|
revision --- Operation Catalog \sectionsc{Repeat Structures},
|
|
discriminant \tablenums{28}) embeds the v2 \texttt{RepeatStructure}
|
|
layout unconditionally (\texttt{kind} and \texttt{voltas} are not
|
|
optional fields) and is \emph{born at v2}: no lower-major layout for
|
|
its payload exists, so every block carrying one stamps major~2 under
|
|
minimal stamping. Its sibling \texttt{DeleteRepeatStructure}
|
|
(\tablenums{29}) carries a bare identifier --- a major-0 layout ---
|
|
so minimal stamping gives its blocks major~0; the kind discriminant
|
|
itself is an append-only schema-\emph{minor} vocabulary event (the
|
|
same append mechanism the Phase-3 tranche used for
|
|
\tablenums{24}--\tablenums{27} --- a mechanism claim only: what a
|
|
kind's blocks \emph{stamp} always follows minimal stamping over its
|
|
payload, as discriminant \tablenums{24}'s always-major-2
|
|
\texttt{CreateStaff} shows). Neither changes anything about
|
|
lower-major layouts. The seven payloads of the first ratification
|
|
embed filled values ---
|
|
\texttt{CreateCrossCutting} and \texttt{ModifyCrossCutting} (a
|
|
\texttt{CrossCuttingValue} embedding \texttt{Slur}, \texttt{Tie},
|
|
\texttt{Beam}, or \texttt{Spanner}); \texttt{CreateStaff} (a
|
|
\texttt{Staff}, which also embeds the filled
|
|
\texttt{StaffLineConfiguration}); \texttt{CreateStaffInstance} (a
|
|
\texttt{StaffInstance}, whose \texttt{staff\_lines\_override} embeds
|
|
it); \texttt{SetStaffLayout} (whose \emph{own}
|
|
\texttt{staff\_lines\_override} field --- an op-payload field, not
|
|
part of a \texttt{StaffInstance} --- embeds it); \texttt{SetMetadata}
|
|
(a \texttt{ScoreMetadata}); and \texttt{CreateRegion}
|
|
\emph{transitively} --- its embedded \texttt{Region} may carry
|
|
\texttt{StaffInstance}s (\texttt{content.staff\_instances}), each
|
|
embedding the filled \texttt{StaffLineConfiguration}. (Reduction
|
|
preconditions a \emph{carried} region to be empty of typed children,
|
|
but the byte form is representable and preserved in the canonical
|
|
set, so the wire must define it.)
|
|
|
|
\textbf{Minimal stamping.} A writer \MUST{} stamp each
|
|
operation-envelope block with the \emph{lowest} schema major whose
|
|
layouts decode the block's bytes to the encoded values. The stamp
|
|
is therefore a pure function of the block's values --- two
|
|
conforming writers stamp identical content identically, which the
|
|
content hash requires (the schema version is in the chunk-hash
|
|
preimage). This is the same principle the major-1 section applies
|
|
to the canonical base: byte-identical content \MUSTNOT{} be
|
|
re-stamped at a higher major. A payload whose higher-major encoding
|
|
coincides byte-for-byte with its lower-major form takes the lower
|
|
major; a hidden embedding site (an \texttt{Option} that is
|
|
\texttt{None}, an empty \texttt{Vec}) contributes nothing.
|
|
Concretely, the per-payload minimal majors at this revision:
|
|
\texttt{CreateCrossCutting} / \texttt{ModifyCrossCutting} /
|
|
\texttt{CreateStaff} / \texttt{SetMetadata} $\Rightarrow$ always
|
|
\textbf{2} (their v2 fills are mandatory appended fields);
|
|
\texttt{CreateRegion} $\Rightarrow$ \textbf{2} iff a carried staff
|
|
instance's \texttt{staff\_lines\_override} is \texttt{Some}, else
|
|
\textbf{1} (the major-1 bool is always present);
|
|
\texttt{CreateStaffInstance} / \texttt{SetStaffLayout}
|
|
$\Rightarrow$ \textbf{2} iff their \texttt{staff\_lines\_override}
|
|
is \texttt{Some}, else \textbf{0};
|
|
\texttt{CreateRepeatStructure} $\Rightarrow$ always \textbf{2}
|
|
(born at v2); \texttt{DeleteRepeatStructure} $\Rightarrow$
|
|
\textbf{0} (a bare identifier under a minor kind append);
|
|
\texttt{TransposeInterval} $\Rightarrow$ \textbf{0} (a sequence of
|
|
identifiers and two \texttt{i32}s --- every constituent is a major-0
|
|
layout, so the kind append is a schema \emph{minor}, and a
|
|
\texttt{TransposeInterval} rides in a major-0 block); every
|
|
other payload keeps its prior major. A block stamps the maximum
|
|
over its payloads.
|
|
(Operation names are the Operation Catalog's; core Chapter~6's
|
|
enum spells the creates \texttt{InsertStaff} /
|
|
\texttt{InsertStaffInstance} / \texttt{InsertRegion}.)
|
|
\item \textbf{The canonical base is unchanged.} The
|
|
\texttt{MaterializedState} embeds none of the filled types (its
|
|
values are effects, conflicts, anomalies, object states, pitch
|
|
spellings, break keys, and pending records), so it remains major~0,
|
|
byte-identical across the bump --- the same keystone as major~1, and
|
|
a conformance test \SHOULD{} assert it again.
|
|
\item The manifest stays major~0 (nothing here touches it).
|
|
\end{itemize}
|
|
|
|
\subsection{Cross-major reader behaviour}
|
|
The major-1 rules extend unchanged: non-canonical chunks at a foreign
|
|
major are discarded and regenerated (or migrated on read, for the
|
|
acceleration snapshot); canonical chunks parse or the bundle opens
|
|
read-only. Migration \emph{composes}: a major-2 reader migrates a major-0
|
|
snapshot v0${\to}$v1${\to}$v2 in one read, each step total and
|
|
default-filling. A major-1-only reader opens a bundle read-only iff it
|
|
carries an op block bearing a v2 value; a major-0-only reader
|
|
additionally on v1 \texttt{CreateRegion} blocks, as before.
|
|
|
|
\emph{Staging note.} As with major~1, the reference implementation
|
|
raises chunk admission \emph{per role} as the implementation phases of
|
|
this revision's push land (snapshot decode, then the canonical
|
|
operation layer); the accept-set stated in
|
|
Section~\ref{sec:evolution:gate} is the ratified end-state, not a claim
|
|
that every intermediate commit already admits it.
|
|
|
|
\subsection{Changed value layouts}
|
|
As in major~1, \textbf{the wire form ratifies the code, not the model's
|
|
presentational field order}: a struct's v2 layout appends its new fields
|
|
\emph{after its existing major-0/major-1 fields}, even where Chapter~5's
|
|
listing interleaves them. Concretely (every leaf under
|
|
Chapter~\ref{ch:values} regime~(a) rules: LE integers, one discriminant
|
|
byte per union, \texttt{u32} counts and length prefixes, one presence
|
|
byte per \texttt{Option}, one bare byte per bool):
|
|
|
|
\begin{itemize}
|
|
\item \texttt{Slur} $=$ \texttt{id} \cat{} \texttt{start\_event} \cat{}
|
|
\texttt{end\_event} \cat{} \textbf{\texttt{kind}} \cat{}
|
|
\textbf{\texttt{curvature\_override}} \cat{} \textbf{\texttt{style}}.
|
|
\item \texttt{Tie} $=$ (major-0 fields) \cat{} \textbf{\texttt{style}}.
|
|
\item \texttt{Beam} $=$ \texttt{id} \cat{} \texttt{events} \cat{}
|
|
\texttt{level} \cat{} \textbf{\texttt{sub\_beams}} \cat{}
|
|
\textbf{\texttt{geometry\_override}}.
|
|
\item \texttt{Spanner} $=$ \texttt{id} \cat{} \texttt{start} \cat{}
|
|
\texttt{end} \cat{} \texttt{staves} \cat{} \textbf{\texttt{kind}}
|
|
\cat{} \textbf{\texttt{style}} (the kind appends \emph{after}
|
|
\texttt{staves}; Chapter~5 lists it second, presentationally).
|
|
\item \texttt{RepeatStructure} $=$ \texttt{id} \cat{} \texttt{start}
|
|
\cat{} \texttt{end} \cat{} \textbf{\texttt{kind}} \cat{}
|
|
\textbf{\texttt{voltas}}.
|
|
\item \texttt{Staff} $=$ \texttt{id} \cat{} \texttt{name} \cat{}
|
|
\texttt{abbreviation} \cat{} \texttt{instrument} \cat{}
|
|
\texttt{default\_staff\_lines} \cat{} \texttt{group} \cat{}
|
|
\textbf{\texttt{default\_clef}} (appended last; Chapter~5 lists it
|
|
mid-struct).
|
|
\item \texttt{StaffLineConfiguration} $=$ \texttt{line\_count} \cat{}
|
|
\textbf{\texttt{line\_spacing}} \cat{} \textbf{\texttt{line\_style}}
|
|
\cat{} \textbf{\texttt{bracket}}. This value is embedded by
|
|
\texttt{Staff} \emph{and} by
|
|
\texttt{StaffInstance.staff\_lines\_override}, which is how
|
|
\texttt{CreateStaffInstance}, \texttt{SetStaffLayout}, and
|
|
(transitively, through a carried region's staff instances)
|
|
\texttt{CreateRegion} become v2 payloads without gaining fields of
|
|
their own (the \texttt{CreateRegion}-in-major-1 pattern).
|
|
\item \texttt{Instrument} $=$ \texttt{id} \cat{} \texttt{name} \cat{}
|
|
\texttt{range} \cat{} \textbf{\texttt{abbreviation}} \cat{}
|
|
\textbf{\texttt{sound\_config}} \cat{}
|
|
\textbf{\texttt{transposition}} \cat{}
|
|
\textbf{\texttt{default\_clef}} \cat{}
|
|
\textbf{\texttt{default\_staff\_lines}} \cat{}
|
|
\textbf{\texttt{unpitched\_members}} (appended after the major-1
|
|
order \texttt{id}/\texttt{name}/\texttt{range}; Chapter~5's listing
|
|
order is presentational).
|
|
\item \texttt{ScoreMetadata} $=$ \texttt{title} \cat{}
|
|
\texttt{composer} \cat{} \texttt{copyright} \cat{}
|
|
\textbf{\texttt{subtitle}} \cat{} \textbf{\texttt{lyricist}} \cat{}
|
|
\textbf{\texttt{arranger}} \cat{}
|
|
\textbf{\texttt{creation\_timestamp}} \cat{}
|
|
\textbf{\texttt{modification\_timestamp}} \cat{}
|
|
\textbf{\texttt{additional}} (appended after the major-0 order).
|
|
\end{itemize}
|
|
|
|
\subsection{New leaf-type layouts}
|
|
\begin{itemize}
|
|
\item \texttt{SlurKind}: one byte --- \tablenums{0} Legato,
|
|
\tablenums{1} Phrase, \tablenums{2} Articulation,
|
|
\tablenums{3} Editorial.
|
|
\item \texttt{CurveDirection}: one byte --- \tablenums{0} Above,
|
|
\tablenums{1} Below.
|
|
\item \texttt{CurvatureOverride} $=$ \texttt{direction}
|
|
(\texttt{Option<CurveDirection>}) \cat{} \texttt{height}
|
|
(\texttt{Option<SpaceUnit>}).
|
|
\item \texttt{LineStyle}: one byte --- \tablenums{0} Solid,
|
|
\tablenums{1} Dashed, \tablenums{2} Dotted.
|
|
\item \texttt{SpanStyle} (one shared record: \texttt{Slur.style},
|
|
\texttt{Tie.style}, \texttt{Spanner.style}) $=$ \texttt{line}
|
|
(\texttt{LineStyle}) \cat{} \texttt{thickness}
|
|
(\texttt{Option<SpaceUnit>}).
|
|
\item \texttt{SubBeam} $=$ \texttt{level} (one byte) \cat{}
|
|
\texttt{events} (\texttt{u32} count \cat{} \texttt{EventId}
|
|
\emph{leaves}, each \texttt{u32}-length-framed 16 canonical bytes
|
|
$= 20$ bytes --- this chapter's leaf framing, exactly as
|
|
\texttt{Beam.events} encodes today).
|
|
\item \texttt{BeamGeometryOverride} $=$ \texttt{slope}
|
|
(\texttt{Option<CanonicalF64>}; dimensionless) \cat{}
|
|
\texttt{offset} (\texttt{Option<SpaceUnit>}).
|
|
\item \texttt{SpannerKind}: discriminant byte \cat{} payload ---
|
|
\tablenums{0} Generic; \tablenums{1} Hairpin \cat{}
|
|
\texttt{HairpinDirection} (one byte: \tablenums{0} Crescendo,
|
|
\tablenums{1} Diminuendo); \tablenums{2} OctaveLine \cat{}
|
|
\texttt{OctaveOffset} (one \texttt{i8}); \tablenums{3} PedalLine
|
|
\cat{} \texttt{PedalKind} (one byte: \tablenums{0} Sustain,
|
|
\tablenums{1} Sostenuto, \tablenums{2} UnaCorda);
|
|
\tablenums{4} TrillExtension; \tablenums{5} Glissando;
|
|
\tablenums{6} Portamento; \tablenums{7} TextLine \cat{}
|
|
\texttt{TextLineDefinition} ($=$ one length-prefixed raw-UTF-8
|
|
string); \tablenums{8} Bracket \cat{} \texttt{BracketKind} (one
|
|
byte: \tablenums{0} Square).
|
|
\item \texttt{RepeatKind}: discriminant byte \cat{} payload ---
|
|
\tablenums{0} SimpleRepeat \cat{} \texttt{count} (\texttt{u32} LE);
|
|
\tablenums{1} DaCapo \cat{} \texttt{end\_target}
|
|
(\texttt{TimeAnchor}); \tablenums{2} DalSegno \cat{} \texttt{segno}
|
|
\cat{} \texttt{end\_target}; \tablenums{3} Volta.
|
|
\item \texttt{Volta} $=$ \texttt{endings} (\texttt{u32} count \cat{}
|
|
bare \texttt{u32} LE values --- fixed-width primitives are
|
|
unframed) \cat{} \texttt{start} \cat{} \texttt{end}
|
|
(\texttt{TimeAnchor}s).
|
|
\item \texttt{SpaceUnit} $=$ one \texttt{CanonicalF64}, leaf-framed
|
|
($12$ bytes) --- byte-identical to how \texttt{CanonicalF64} itself
|
|
encodes; the newtype adds no bytes.
|
|
\item \texttt{StaffBracketKind}: one byte --- \tablenums{0} Brace,
|
|
\tablenums{1} Bracket.
|
|
\item \texttt{SoundConfiguration} $=$ \texttt{u32} length \cat{} raw
|
|
bytes (opaque; never interpreted by the core).
|
|
\item \texttt{TranspositionInterval} $=$ \texttt{diatonic\_steps}
|
|
(\texttt{i32} LE) \cat{} \texttt{chromatic\_steps} (\texttt{i32} LE).
|
|
\item \texttt{UnpitchedMember} $=$ \texttt{member} (bare \texttt{u32}
|
|
LE --- \texttt{UnpitchedMemberId} is an instrument-scoped small
|
|
value, \emph{not} a 128-bit object identifier, so the
|
|
identifier-leaf framing does not apply; this matches how
|
|
\texttt{UnpitchedEvent.instrument\_member} has encoded since
|
|
major~0) \cat{} \texttt{name} (string) \cat{}
|
|
\texttt{staff\_position} (bare \texttt{i16} LE, likewise).
|
|
\item \texttt{Timestamp} $=$ one \texttt{i64} LE (nanoseconds since
|
|
the Unix epoch, UTC).
|
|
\item \texttt{MetadataEntry} $=$ \texttt{key} (string) \cat{}
|
|
\texttt{value}; \texttt{MetadataValue}: discriminant byte \cat{}
|
|
payload --- \tablenums{0} Text \cat{} string; \tablenums{1} Integer
|
|
\cat{} \texttt{i64} LE; \tablenums{2} Flag \cat{} bool.
|
|
\item \texttt{Clef}, \texttt{ClefShape}, \texttt{KeySignature},
|
|
\texttt{ClefChange}, \texttt{KeySignatureChange} are \emph{not new
|
|
wire forms}: they have carried major-0 layouts since the
|
|
visible-slice revision (empty sequences left \texttt{Score} bytes
|
|
unchanged). Major~2 only \emph{embeds} \texttt{Clef} in two more
|
|
places (\texttt{Staff.default\_clef},
|
|
\texttt{Instrument.default\_clef}). Its layout, for the record:
|
|
\texttt{shape} (one byte: \tablenums{0} G, \tablenums{1} F,
|
|
\tablenums{2} C, \tablenums{3} Percussion) \cat{} \texttt{line}
|
|
(\texttt{i8}) \cat{} \texttt{octave\_shift} (\texttt{i8});
|
|
\texttt{KeySignature} $=$ \texttt{fifths} (\texttt{i8}; decoders
|
|
\MUST{} reject $|\texttt{fifths}| > 7$).
|
|
\end{itemize}
|
|
|
|
As in major~1: every embedded \emph{leaf} keeps this chapter's framing
|
|
(an identifier is a \texttt{u32} LE length \cat{} its 16 canonical
|
|
bytes $= 20$; a \texttt{CanonicalF64} is a \texttt{u32} LE length
|
|
\cat{} 8 bytes $= 12$); fixed-width primitive integers and bools inside
|
|
structs are bare; every \texttt{Option} is one presence byte then the
|
|
payload when present.
|
|
|
|
\subsection{Migration from major 1}
|
|
Total and default-filling, needing no score context; composes after the
|
|
major-1 migration for major-0 input. The defaults reproduce today's
|
|
rendering exactly: a migrated document draws byte-identically.
|
|
|
|
\begin{center}
|
|
\begin{tabular}{p{1.9in} p{3.4in}}
|
|
\toprule
|
|
\textbf{major-1 form} & \textbf{major-2 form} \\
|
|
\midrule
|
|
\texttt{Slur} & append \texttt{kind} $=$ Legato,
|
|
\texttt{curvature\_override} $=$ \texttt{None}, \texttt{style} $=$
|
|
(Solid, \texttt{None}) \\
|
|
\texttt{Tie} & append \texttt{style} $=$ (Solid, \texttt{None}) \\
|
|
\texttt{Beam} & append \texttt{sub\_beams} $=$ empty,
|
|
\texttt{geometry\_override} $=$ \texttt{None} \\
|
|
\texttt{Spanner} & append \texttt{kind} $=$ Generic, \texttt{style}
|
|
$=$ (Solid, \texttt{None}) \\
|
|
\texttt{RepeatStructure} & append \texttt{kind} $=$ SimpleRepeat
|
|
\{count $=$ 2\} (a v1 repeat \emph{meant} a repeat), \texttt{voltas}
|
|
$=$ empty \\
|
|
\texttt{Staff} & append \texttt{default\_clef} $=$ treble
|
|
(G, line 2, shift 0) \\
|
|
\texttt{StaffLineConfiguration} & append \texttt{line\_spacing} $=$
|
|
1.0, \texttt{line\_style} $=$ Solid, \texttt{bracket} $=$
|
|
\texttt{None} --- at \emph{every} embedding site:
|
|
\texttt{Staff.default\_staff\_lines},
|
|
\texttt{Instrument.default\_staff\_lines} (v2-new),
|
|
\texttt{StaffInstance.staff\_lines\_override}, and the
|
|
\texttt{SetStaffLayout} op payload's own
|
|
\texttt{staff\_lines\_override} field \\
|
|
\texttt{Instrument} & append \texttt{abbreviation} $=$ \texttt{None},
|
|
\texttt{sound\_config} $=$ empty, \texttt{transposition} $=$
|
|
\texttt{None}, \texttt{default\_clef} $=$ treble,
|
|
\texttt{default\_staff\_lines} $=$ the complete v2 default
|
|
(\texttt{line\_count} $=$ 5, \texttt{line\_spacing} $=$ 1.0,
|
|
\texttt{line\_style} $=$ Solid, \texttt{bracket} $=$ \texttt{None}),
|
|
\texttt{unpitched\_members} $=$ empty \\
|
|
\texttt{ScoreMetadata} & append \texttt{subtitle} / \texttt{lyricist}
|
|
/ \texttt{arranger} $=$ \texttt{None}, both timestamps $=$
|
|
\tablenums{0} (the unset convention), \texttt{additional} $=$
|
|
empty \\
|
|
the seven embedding op payloads (incl.\ \texttt{CreateRegion}'s
|
|
transitively embedded staff instances) & default-fill the embedded
|
|
value(s) as above; \emph{canonical} --- a v0/v1 op block migrates on
|
|
read, and a block bearing any v2-dependent bytes is major~2 (a
|
|
lower-major-only reader opens the bundle read-only) \\
|
|
canonical-base \texttt{MaterializedState} & unchanged, byte-identical,
|
|
stays major~0 \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
% ===========================================================================
|
|
\chapter{Non-Canonical Pinned Encodings}
|
|
\label{ch:noncanon}
|
|
|
|
\textbf{Non-canonical} means: never part of document identity. Nothing in
|
|
this chapter enters a content hash that canonical state depends on, and all
|
|
of it can be discarded and rebuilt without changing the document. The
|
|
encodings are pinned for \emph{reproducibility} --- byte-equal conformance
|
|
claims and cache correctness --- not for durability.
|
|
|
|
\section{\texttt{LayoutObjectId} (\texttt{MUSCLOID})}
|
|
\label{sec:noncanon:muscloid}
|
|
|
|
Governed by core requirement \texttt{req:layoutir:object-id-derivation}.
|
|
$\texttt{LayoutObjectId} = \mathrm{trunc128}(\mathrm{BLAKE3}(
|
|
\texttt{MUSCLOID} \cat \mathit{inputs}))$, with three preimage shapes:
|
|
|
|
\begin{description}
|
|
\item[stable] $\mathit{inputs}$ = the source
|
|
\texttt{TypedObjectId}'s canonical bytes.
|
|
\item[manifestation] $\mathit{inputs}$ = source canonical bytes \cat{}
|
|
\texttt{RegionId} canonical bytes (16 BE) --- distinct regions give one
|
|
source distinct manifestation ids.
|
|
\item[synthesized] $\mathit{inputs}$ = source canonical bytes \cat{}
|
|
five \texttt{u64} LE words: the \texttt{SynthesisKind} discriminant,
|
|
the registry id's high and low 64 bits (zero unless
|
|
\texttt{Registered}), and the instance key's high and low 64 bits.
|
|
\end{description}
|
|
|
|
\texttt{SynthesisKind} discriminants: \tablenums{0} CancellationAccidental,
|
|
\tablenums{1} KeySignatureNatural, \tablenums{2} GeneratedRest,
|
|
\tablenums{3} EngravedBreak, \tablenums{4} MultimeasureRest,
|
|
\tablenums{5} Cautionary, \tablenums{6} Registered.
|
|
|
|
\section{\texttt{ResolvedLayoutIR} Canonical Output}
|
|
\label{sec:noncanon:resolved}
|
|
|
|
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. 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:
|
|
source score version \cat{} pages \cat{} glyphs \cat{} strokes \cat{}
|
|
engraving decisions \cat{} glyph-catalog identity; within each section,
|
|
elements carry their provenance (built on \texttt{MUSCLOID} ids) and their
|
|
quantized geometry. The full leaf grammar is pinned by the reference
|
|
implementation's golden and round-trip anchors (Chapter~\ref{ch:goldens})
|
|
rather than restated field-by-field here, because the surface is
|
|
non-canonical and evolves with the engraving feature set.
|
|
|
|
\section{Layout Caches and Font Metrics}
|
|
\label{sec:noncanon:caches}
|
|
|
|
\texttt{LayoutCache} chunks (\texttt{ChunkKind} \tablenums{6}) hold cached
|
|
layout artifacts; they are \textbf{always discardable}, and a reader
|
|
\MUSTNOT{} treat a missing, stale, or undecodable layout cache as bundle
|
|
corruption. The glyph-metrics identity hash is domain-separated under
|
|
\texttt{MUSCFNTM} (a reserved built-in, non-canonical tag): it names the
|
|
metrics a layout was computed against, so caches and conformance runs can
|
|
detect font drift; it never enters canonical state.
|
|
|
|
% ===========================================================================
|
|
\chapter{Golden Anchor Registry}
|
|
\label{ch:goldens}
|
|
|
|
The conformance contract binding this document to the reference
|
|
implementation. Three lock classes:
|
|
|
|
\begin{description}
|
|
\item[literal-byte] the test asserts exact expected bytes or exact
|
|
discriminant literals --- the strongest lock;
|
|
\item[round-trip] the test asserts $\mathrm{decode} \circ \mathrm{encode} =
|
|
\mathrm{id}$ and re-encode byte-stability over a corpus;
|
|
\item[canonical-equality] the test asserts that semantically equal values
|
|
encode identically (order-independence, reduction identities).
|
|
\end{description}
|
|
|
|
The first block restates the anchors already recorded in the core
|
|
specification's \texttt{app:bytes} \sectionsc{Reference-implementation
|
|
locks} table (which governs them); the second block adds the anchors this
|
|
document introduces, which \texttt{app:bytes} predates.
|
|
|
|
Each anchor names its crate file, then the test on a second line.
|
|
|
|
\begingroup\footnotesize
|
|
\begin{longtable}{>{\raggedright\arraybackslash}p{1.5in} >{\raggedright\arraybackslash}p{3.0in} p{1.0in}}
|
|
\toprule
|
|
\textbf{Layout} & \textbf{Anchor} & \textbf{Class} \\
|
|
\midrule
|
|
\endhead
|
|
\multicolumn{3}{l}{\emph{Imported from \texttt{app:bytes}:}} \\
|
|
\addlinespace[2pt]
|
|
\texttt{TypedObjectId} &
|
|
\texttt{epiphany-core/src/ids.rs}\newline
|
|
\texttt{::typed\ub object\ub id\ub byte\ub form\ub is\ub locked} &
|
|
literal-byte \\
|
|
Promoted \texttt{VoiceId} &
|
|
\texttt{epiphany-core/src/graph.rs}\newline
|
|
\texttt{::promoted\ub voice\ub id\ub byte\ub form\ub is\ub locked} &
|
|
literal-byte \\
|
|
System \texttt{PitchId} &
|
|
\texttt{epiphany-core/src/pitch.rs}\newline
|
|
\texttt{::system\ub pitch\ub id\ub byte\ub form\ub is\ub locked} &
|
|
literal-byte \\
|
|
\texttt{IntegrityAnomalyId} &
|
|
\texttt{epiphany-ops/src/anomaly.rs}\newline
|
|
\texttt{::integrity\ub anomaly\ub id\ub byte\ub form\ub is\ub locked} &
|
|
literal-byte \\
|
|
\texttt{ObjectKind} (ops) &
|
|
\texttt{epiphany-ops/src/support.rs}\newline
|
|
\texttt{::object\ub kind\ub discriminants\ub are\ub golden} &
|
|
literal-byte \\
|
|
\texttt{ChunkKind} &
|
|
\texttt{epiphany-bundle/src/chunk.rs}\newline
|
|
\texttt{::chunk\ub kind\ub discriminants\ub are\ub golden} &
|
|
literal-byte \\
|
|
\texttt{Compression\allowbreak Algorithm} &
|
|
\texttt{epiphany-bundle/src/chunk.rs}\newline
|
|
\texttt{::compression\ub algorithm\ub encoding\ub is\ub golden} &
|
|
literal-byte \\
|
|
\texttt{ProfileId} &
|
|
\texttt{epiphany-bundle/src/superblock.rs}\newline
|
|
\texttt{::profile\ub id\ub discriminants\ub are\ub golden} &
|
|
literal-byte \\
|
|
\texttt{ManifestId} &
|
|
\texttt{epiphany-bundle/src/ids.rs}\newline
|
|
\texttt{::manifest\ub id\ub is\ub content\ub derived\ub and\ub deterministic} &
|
|
canonical-equality \\
|
|
\texttt{TransactionCategory} &
|
|
\texttt{epiphany-ops/src/payload.rs}\newline
|
|
\texttt{::transaction\ub category\ub discriminants\ub are\ub golden} &
|
|
literal-byte \\
|
|
\texttt{ResolutionAction} &
|
|
\texttt{epiphany-ops/src/conflict.rs}\newline
|
|
\texttt{::resolution\ub action\ub discriminants\ub are\ub golden} &
|
|
literal-byte \\
|
|
\texttt{BlobId} &
|
|
\texttt{epiphany-determinism/src/hash.rs}\newline
|
|
\texttt{ContentHash::of\ub blob} tests &
|
|
round-trip \\
|
|
\texttt{RationalTime} &
|
|
\texttt{epiphany-core/src/time.rs}\newline
|
|
\texttt{::equal\ub rationals\ub encode\ub identically} &
|
|
canonical-equality \\
|
|
\texttt{MUSCLOID} derivation &
|
|
\texttt{epiphany-layout-ir/src/provenance.rs}\newline
|
|
\texttt{::stable\ub id\ub uses\ub the\ub ratified\ub muscloid\ub derivation} &
|
|
literal-byte \\
|
|
Domain-tag spellings &
|
|
\texttt{epiphany-determinism/src/domain.rs}\newline
|
|
\texttt{::exact\ub tag\ub spellings\ub match\ub spec} &
|
|
literal-byte \\
|
|
\addlinespace[4pt]
|
|
\multicolumn{3}{l}{\emph{Added by this document:}} \\
|
|
\addlinespace[2pt]
|
|
\texttt{OperationKind} discriminants (\S\ref{sec:ops:kinds}) &
|
|
\texttt{epiphany-ops/src/payload.rs}\newline
|
|
\texttt{::operation\ub kind\ub wire\ub discriminants\ub are\ub golden} &
|
|
literal-byte \\
|
|
\texttt{OperationPayload} discriminants (\S\ref{sec:ops:payload}) &
|
|
\texttt{epiphany-ops/src/payload.rs}\newline
|
|
\texttt{::operation\ub payload\ub discriminants\ub are\ub golden} &
|
|
literal-byte \\
|
|
\texttt{OperationKindTag} (\S\ref{sec:ops:kind-tag}) &
|
|
\texttt{epiphany-ops/src/payload.rs}\newline
|
|
\texttt{::operation\ub kind\ub tag\ub decode\ub mirrors\ub encode\ub exactly} &
|
|
round-trip \\
|
|
\texttt{ResolveEquivocation} payload (\S\ref{sec:ops:payload}) &
|
|
\texttt{epiphany-ops/src/payload.rs}\newline
|
|
\texttt{::resolve\ub equivocation\ub payload\ub encodes\ub target\ub then\ub hash} &
|
|
literal-byte \\
|
|
\texttt{OperationStamp} (\S\ref{sec:ops:stamp}) &
|
|
\texttt{epiphany-ops/src/stamp.rs}\newline
|
|
\texttt{::stamp\ub encode\ub is\ub stable} &
|
|
literal-byte \\
|
|
\texttt{CausalContext} (\S\ref{sec:ops:causal}) &
|
|
\texttt{epiphany-ops/src/causal.rs}\newline
|
|
\texttt{::canonical\ub encoding\ub is\ub build\ub order\ub independent} &
|
|
canonical-equality \\
|
|
\texttt{MaterializedState} (\S\ref{sec:ops:materialized}) &
|
|
\texttt{epiphany-ops/src/decode.rs}\newline
|
|
\texttt{::reduced\ub states\ub decode\ub and\ub reencode} &
|
|
round-trip \\
|
|
Operation-index payload (\S\ref{sec:bundle:opindex}) &
|
|
\texttt{epiphany-bundle/src/opindex.rs}\newline
|
|
\texttt{::payload\ub encoding\ub is\ub golden} &
|
|
literal-byte \\
|
|
Fixed header (\S\ref{sec:bundle:header}) &
|
|
\texttt{epiphany-bundle/src/header.rs}\newline
|
|
\texttt{::header\ub round\ub trips} &
|
|
round-trip \\
|
|
Superblock (\S\ref{sec:bundle:superblock}) &
|
|
\texttt{epiphany-bundle/src/superblock.rs}\newline
|
|
\texttt{::superblock\ub round\ub trips\ub through\ub 256\ub bytes} &
|
|
round-trip \\
|
|
Chunk hash preimage (\S\ref{sec:bundle:chunks}) &
|
|
\texttt{epiphany-bundle/src/chunk.rs}\newline
|
|
\texttt{::hash\ub preimage\ub matches\ub the\ub spec\ub layout} &
|
|
literal-byte \\
|
|
\texttt{ChunkRef} (\S\ref{sec:bundle:chunkref}) &
|
|
\texttt{epiphany-bundle/src/chunk.rs}\newline
|
|
\texttt{::chunk\ub ref\ub round\ub trips} &
|
|
round-trip \\
|
|
Manifest body (\S\ref{sec:bundle:manifest}) &
|
|
\texttt{epiphany-bundle/src/manifest.rs}\newline
|
|
\texttt{::re\ub encode\ub is\ub byte\ub identical},
|
|
\texttt{::manifest\ub round\ub trips},
|
|
\texttt{::duplicate\ub roots\ub collapse\ub on\ub encode},
|
|
\texttt{::semver\ub orders\ub numerically\ub not\ub byte\ub wise} &
|
|
round-trip + canonical-equality \\
|
|
Barrier kinds blob (\S\ref{sec:barriers:framing}) &
|
|
\texttt{epiphany-layout-ir/src/barrier.rs}\newline
|
|
\texttt{::affected\ub object\ub kinds\ub blob\ub bytes\ub are\ub golden} &
|
|
literal-byte \\
|
|
Barriers blob (\S\ref{sec:barriers:barrier}) &
|
|
\texttt{epiphany-layout-ir/src/barrier.rs}\newline
|
|
\texttt{::edit\ub barriers\ub blob\ub bytes\ub are\ub golden} &
|
|
literal-byte \\
|
|
Barrier variants (\S\ref{sec:barriers:barrier}) &
|
|
\texttt{epiphany-layout-ir/src/barrier.rs}\newline
|
|
\texttt{::every\ub scope\ub and\ub condition\ub variant\ub round\ub trips\ub byte\ub identically} &
|
|
round-trip \\
|
|
Whole-\texttt{Score} codec (\S\ref{sec:values:score}) &
|
|
\texttt{epiphany-core/src/codec.rs}\newline
|
|
\texttt{::generator\ub scores\ub round\ub trip},
|
|
\texttt{::trailing\ub and\ub truncated\ub bytes\ub are\ub rejected} &
|
|
round-trip \\
|
|
\texttt{CanonicalValue} seam (\S\ref{sec:values:canonical-value}) &
|
|
\texttt{epiphany-core/src/codec.rs}\newline
|
|
\texttt{::value\ub types\ub round\ub trip\ub over\ub generator\ub corpus} &
|
|
round-trip \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
\endgroup
|
|
|
|
Struct \emph{bodies} in Chapter~\ref{ch:values} are round-trip-locked rather
|
|
than literal-byte-locked: their byte identity follows from the frozen
|
|
positional rule plus the literal-byte locks on every discriminant and leaf
|
|
they embed.
|
|
|
|
\section{The Decode Vector Corpus}
|
|
\label{sec:goldens:decode-vectors}
|
|
|
|
The cross-implementation decoder test is \textbf{delivered}, as a corpus of
|
|
literal byte strings each paired with the verdict a decoder owes it. The
|
|
reference implementation carries it at \texttt{spec/vectors/decode\_vectors.txt}
|
|
and regenerates it from a committed generator, so a change to any wire form
|
|
moves the file.
|
|
|
|
Each line is \texttt{surface\ \ verdict\ \ class\ \ name\ \ hex}. The
|
|
\texttt{verdict} is \texttt{accept} or \texttt{reject}. The \texttt{class}
|
|
names \emph{why} a rejected vector is rejected and is \textbf{informative
|
|
only}: implementations need not agree on an error taxonomy.
|
|
|
|
\begin{requirement}
|
|
\label{req:binfmt:decode-vectors}
|
|
A conforming decoder \MUST{} accept every \texttt{accept} vector for a
|
|
surface it implements, and \MUST{} reject every \texttt{reject} vector for
|
|
that surface. A value decoded from an \texttt{accept} vector \MUST{}
|
|
re-encode to exactly that vector's bytes.
|
|
|
|
Accepting a \texttt{reject} vector and then normalizing it is
|
|
\emph{accepting} it, and does not satisfy this requirement. Canonical decode
|
|
is injective: distinct byte strings denote distinct values, which is what
|
|
content-addressing rests on.
|
|
\end{requirement}
|
|
|
|
\begin{rationale}
|
|
The corpus is not a restatement of this document; it is what a second
|
|
implementation can be run against. Every rejection class in it is one the
|
|
reference implementation shipped a bug in, or one whose check no injectivity
|
|
fuzzer can see: a whole-value re-encode guard catches the fields a decoder
|
|
\emph{normalizes} and is blind to order-preserving sequence fields, which need
|
|
per-site order checks; and a guard on an outer value can \emph{mask} a lenient
|
|
inner codec rather than fix it (Push~5).
|
|
|
|
A future revision should extend the corpus to the representative struct
|
|
layouts of Section~\ref{sec:values:representative}, which remain round-trip
|
|
locked rather than literal-byte locked.
|
|
\end{rationale}
|
|
|
|
% ===========================================================================
|
|
\chapter{Revision History}
|
|
\label{ch:history}
|
|
|
|
\begin{longtable}{p{2cm} p{2.5cm} p{9cm}}
|
|
\toprule
|
|
\textbf{Date} & \textbf{Section} & \textbf{Change} \\
|
|
\midrule
|
|
\endhead
|
|
\today & All & 0.1.0 --- Initial companion: ratifies the previously
|
|
provisional whole-\texttt{Score} value codec (core P11-4), operation-layer
|
|
wire forms (ops), bundle physical layout (bundle P11-D2/D4/D5),
|
|
operation-index payload (P12-D1), and extension-blob/edit-barrier byte
|
|
forms (P12-E1/E2/E3); pins the no-varint rule, the frozen-positional
|
|
schema-evolution keystone, and the id-leads envelope property;
|
|
\texttt{SnapshotId} derivation deferred (open question). \\
|
|
\today & Operation wire forms & 0.2.0 --- Phase-3 first tranche: appended
|
|
\texttt{OperationKind} wire discriminants \tablenums{24}--\tablenums{27}
|
|
(\texttt{CreateStaff}, \texttt{SetTimeSignature},
|
|
\texttt{SetTempoSegment}, \texttt{SetStaffLayout}) with their payload
|
|
layouts, the matching \texttt{OperationKindTag} discriminants
|
|
\tablenums{24}--\tablenums{27} (kind-to-tag naming gains
|
|
\texttt{CreateStaff}~$\rightarrow$~\texttt{InsertStaff}), and
|
|
\texttt{PreconditionFailureReason} \tablenums{11}
|
|
(\texttt{TempoMapMalformed}) --- a schema-\emph{minor} evolution under this
|
|
document's own append-only rules (Chapter~\ref{ch:evolution}); no existing
|
|
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. \\
|
|
\today & Effect vocabularies / Extension blobs & 0.4.0 --- Pass-12
|
|
G-ratification: appends \texttt{PreconditionFailureReason}
|
|
\tablenums{12} (\texttt{SystemDerivedContentImmutable}, P12-K3) and
|
|
\tablenums{13} (\texttt{RecreateContentMismatch}, P12-K9), and
|
|
\texttt{ReanchorReason} \tablenums{6} (\texttt{SameCanvasNearer},
|
|
P12-C4) --- schema-\emph{minor} appends under Chapter~\ref{ch:evolution};
|
|
no existing assignment changed. Adds the unsafe-edit tombstone
|
|
\emph{encoding} open question (from P12-E5; semantics ratified in core
|
|
Chapter~8). Semantics: Operation Catalog 0.6.0. \\
|
|
\today & Schema evolution / Graph value layouts & 0.5.0 --- Defines
|
|
\textbf{schema major~2}, the second data-model expansion major
|
|
(Section~\ref{sec:evolution:major2}): fills the staged truncations ---
|
|
\texttt{Slur} (kind / curvature override / style), \texttt{Tie} (style),
|
|
\texttt{Beam} (sub-beams / geometry override), \texttt{Spanner} (kind,
|
|
with the leading \texttt{Generic} migration default / style),
|
|
\texttt{RepeatStructure} (kind / voltas), \texttt{Staff}
|
|
(\texttt{default\_clef}), \texttt{StaffLineConfiguration} (spacing /
|
|
line style / bracket), \texttt{Instrument} (six appended fields), and
|
|
\texttt{ScoreMetadata} (six appended fields incl.\ the
|
|
strictly-authored timestamps) --- with layouts for the new leaf types
|
|
and the total default-filling v1${\to}$v2 migration table. Assigns the
|
|
major per payload type: snapshot-only for \texttt{Instrument} /
|
|
\texttt{RepeatStructure}; \emph{canonical} for the seven embedding op
|
|
payloads (\texttt{Create}/\texttt{ModifyCrossCutting},
|
|
\texttt{CreateStaff}, \texttt{CreateStaffInstance},
|
|
\texttt{SetStaffLayout}, \texttt{SetMetadata}, and
|
|
\texttt{CreateRegion} transitively through carried staff instances);
|
|
the canonical base and manifest stay major~0, byte-identical.
|
|
Accept-set widens to $\{0, 1, 2\}$ (staged per role as the
|
|
implementation phases land). Semantics: core specification schema-major-2 revision
|
|
(leaf-type definitions, clef/key content-model ratification,
|
|
\texttt{ClefId} retirement, strictly-authored timestamp requirement). \\
|
|
\today & Operation vocabularies & 0.6.0 --- Repeat authoring
|
|
(schema-major-2 revision, Phase D): appends \texttt{OperationKind}
|
|
\tablenums{28} (\texttt{CreateRepeatStructure},
|
|
$\mathrm{lp}$(\texttt{RepeatStructure}), born at v2 --- every block
|
|
carrying one stamps major~2 under minimal stamping) and \tablenums{29}
|
|
(\texttt{DeleteRepeatStructure}, bare identifier --- minimal stamping
|
|
over its major-0 payload layout gives major~0; the discriminant itself
|
|
a schema-\emph{minor} vocabulary append), with the
|
|
matching \texttt{OperationKindTag} \tablenums{28}/\tablenums{29}
|
|
(name-verbatim projection). No existing assignment changed;
|
|
Section~\ref{sec:evolution:major2} restates the per-payload stamping
|
|
with the pair. Semantics: Operation Catalog \sectionsc{Repeat
|
|
Structures}, 0.7.0; re-anchoring row (nearest-surviving-anchor across
|
|
every anchor site, cascade when none): core Chapter~6 rule table. \\
|
|
\today & Operation vocabularies & 0.7.0 --- Transpose algebra (Push~4a):
|
|
appends \texttt{OperationKind} \tablenums{30}
|
|
(\texttt{TransposeInterval}: $\mathrm{seq}^{\Uparrow}$(\texttt{PitchId})
|
|
\cat{} two \texttt{i32}s) and the matching \texttt{OperationKindTag}
|
|
\tablenums{30}. Every constituent is a major-0 layout, so the payload
|
|
stamps major~\tablenums{0} and the discriminant is a schema-\emph{minor}
|
|
vocabulary append --- no major~3. Introduces the
|
|
$\mathrm{seq}^{\Uparrow}$ notation (strictly increasing; a decoder
|
|
\MUST{} reject a duplicate rather than normalize it) and marks
|
|
\tablenums{9} (\texttt{Transpose}) frozen, its
|
|
$\mathrm{seq}^{\uparrow}$ being a multiset. No existing assignment
|
|
changed. Semantics: Operation Catalog \sectionsc{TransposeInterval},
|
|
0.8.0; algebra: core Chapter~2, \sectionsc{Transposition and the
|
|
Interval Type}. \\
|
|
\today & Chunk references & 0.8.0 --- Strict \texttt{CompressionAlgorithm}
|
|
decode (Push~5, P3): \texttt{req:binfmt:compression-none-parameter} \MUST{}
|
|
reject a non-zero parameter byte on the \texttt{None} discriminant. Ignoring
|
|
it made the codec non-injective --- two byte strings, one value --- which
|
|
every structure embedding a \texttt{ChunkRef} inherited; the operation index,
|
|
having no whole-value re-encode guard, accepted both. Found by the wire-decode
|
|
fuzzer. Supersedes the core specification's ``ignored on read''; no wire
|
|
layout changed and no conforming writer is affected. \\
|
|
\today & Golden anchors & 0.9.0 --- Ratifies the decode vector corpus
|
|
(Push~5, P4): \texttt{req:binfmt:decode-vectors} and
|
|
Section~\ref{sec:goldens:decode-vectors}. The cross-implementation decoder
|
|
test moves out of the \sectionsc{About This Companion} exclusion list and out
|
|
of the \sectionsc{Golden Anchor Registry}'s ``deferred conformance harness''
|
|
note: literal byte vectors with accept/reject verdicts now exist
|
|
(\texttt{spec/vectors/decode\_vectors.txt}), and a conforming decoder owes them
|
|
the verdict. Accepting a \texttt{reject} vector and normalizing it is
|
|
\emph{accepting} it. The wire-format fuzzer remains an implementation
|
|
deliverable. No wire layout changed. \\
|
|
\bottomrule
|
|
\end{longtable}
|
|
|
|
\end{document}
|