spec: Pass 12 tranche 1 — align spec text with ratified dispositions
The 2026-07 compliance audit's spec-side batch: every place where normative text trailed a disposition the project had already made (the ratified Operation Catalog, a crate DECISIONS entry, or the PHASE2_QUICKSTART model the implementation follows). No byte-layout or architecture changes. - Re-anchoring table: the Slur/Spanner rows now state the catalog's surviving-endpoint semantics (re-anchor while >=1 endpoint survives, cascade-delete only when none does). - RewriteTuplets adopts the catalog's id-only v1 payload (TupletRewrite removed); graph-aware reduction MUST refuse the variant rather than fabricate rewritten values. - Operation-envelope blocks are pure envelope vectors (a content- addressed chunk cannot embed its own id); per-block summaries (dvv_summary/min_stamp/max_stamp) relocated to the manifest's operation_block_summaries map; the order-blocks-by-min_stamp SHOULD dropped (the canonical encoding sorts chunk refs by encoded form). - Ch2+Ch3 pre-passes respecified as canonical derived annotations — deterministic functions of (graph, profile, versioned algorithm id), recomputed on materialization, never stored; the incremental-re-run MUST demoted to MAY with unobservability required. - Spelling precedence ties break by canonical attachment order (the creation-timestamp tie-break deleted; attachments carry none). - ConflictRegistry moved off the Score root into canonical materialized state, matching Chapter 6. - SolverTier gains the non-conformance Stub variant. - The drafted ModifyEvent M2e text landed in the catalog (0.2.0 -> 0.3.0): metric placement changes materialize behind a voice-occupancy placement precondition. Dispositions logged in spec/PASS12_RATIFICATION_LOG.md; the Pass 12 batch itself stays open (no P12-H/K rows resolved). Also carries the audit-filed batch rows P12-H6/H7/K3/K4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
This commit is contained in:
parent
7f8d1a1f95
commit
80e3fed99c
|
|
@ -10,6 +10,13 @@ surfaced five candidates, crossing the ≥3 threshold. This file is the running
|
|||
collection; G ratifies (or defers/rejects) the batch when Phase 2's open
|
||||
questions are resolved. F collects, F does not resolve.
|
||||
|
||||
**Pass 12 tranche 1 landed (2026-07-02):** the audit *spec-alignment* set —
|
||||
places where spec text trailed an already-made disposition (catalog, crate
|
||||
DECISIONS, PHASE2_QUICKSTART) — was ratified directly into `core_spec.tex` /
|
||||
`operation_catalog.tex` (0.2.0 → 0.3.0); dispositions in
|
||||
`PASS12_RATIFICATION_LOG.md`. That tranche resolves **none** of the rows
|
||||
below; they remain open for G.
|
||||
|
||||
## How an item lands here
|
||||
|
||||
When implementation hits a behavior the ratified spec does not determine, the
|
||||
|
|
@ -31,10 +38,15 @@ code instead is the failure mode this batch exists to prevent.
|
|||
| ~~P12-I3~~ **RESOLVED (I-4a)** | `epiphany-layout-ir` I | The bundled `BRAVURA_METRICS` were *approximations* disagreeing with the renderer's genuine outlines. **Resolved by I-4a:** the metrics table is re-extracted from the **same** SHA-pinned `bravura-1.392` font the outlines come from, with bboxes rounded *outward* so each metric box contains the drawn ink (a `render-svg` test proves containment); `BRAVURA_VERSION = SemVer(1, 392, 0)`. A coupled barline-placement bug it surfaced (bottom-origin glyph floated) was fixed in the same increment. | ✅ done |
|
||||
| P12-K1 | `epiphany-ops` K | A v0 `RespellPitch` carried a `ContentHash` *fingerprint* of the spelling, not the `PitchSpelling`. The v0→v1 migration (Operation Catalog, M1) cannot invert a fingerprint, so it recovers the spelling from the score-graph context (an explicit per-pitch spelling attachment whose canonical bytes hash to the fingerprint) and returns `MigrationError::Irreversible` (bundle opens read-only) when the context lacks it. Every other representative payload migrates self-contained; this is the lone exception. Confirm the read-only fallback is the intended disposition vs. requiring a v0 corpus that preserves spelling pre-images. | G / Pass 12 (migration) |
|
||||
| P12-K2 | `epiphany-ops` K | The `Transpose` op (Operation Catalog, M2 Group 1) carries a minimal `chromatic_steps: i32` interval and `reduce_onto` applies it as a CMN *alteration* shift only. Faithful interval algebra (diatonic vs. chromatic intervals, octave/nominal renormalization, transposition in non-CMN pitch spaces) is the deferred Chapter 4 tuning-catalog territory. The prototype also clamps the shifted alteration to the `i8` range, so an extreme transpose silently saturates instead of renormalizing — another reason the representation needs pinning. Pin the interval representation and transposition semantics when the tuning catalog lands. | G / Pass 12 (tuning) |
|
||||
| P12-H6 | `epiphany-core` H | Decomposition-attachment precedence: the spec says the decomposition pre-pass uses the "same precedence machinery" as spelling, but spelling's machinery is per-score *configurable* with `priority`/timestamp tie-breaks while `DecompositionAttachment` carries no `priority` and the graph has no `DecompositionPrecedence` field. The audit fix implements the fixed default order (`UserChosen > Imported > Propagated > Inferred`, canonical attachment order as tie-break). Decide whether decomposition precedence is configurable (a canonical-codec change) or fixed. | G / Pass 12 (decomposition) |
|
||||
| P12-H7 | `epiphany-core` H | Authored decompositions for inference-ineligible events: an authored attachment is exactly how a user would notate an *ungriddable* event, but the derived-annotation surface (mirroring spelling, which likewise ignores attachments on spelling-unavailable pitches) only resolves overrides where inferred output exists. Needs a spec answer for both pre-passes. | G / Pass 12 (pre-passes) |
|
||||
| P12-K3 | `epiphany-ops` K | Content modification of a `SYSTEM_DERIVED` pitch: `ModifyEvent`/`ModifyIdentifiedPitch` can rewrite a synthetic pitch's intrinsic content in place, silently invalidating the id's content-derivation (Invariant 11). The new reduction-time collision check deliberately does not treat in-place rewrites as mints. Decide whether reduction must refuse them outright. | G / Pass 12 (identity) |
|
||||
| P12-K4 | `epiphany-ops` K | `ResolveConflict` beyond the concurrent case: the spec pins outcomes only for *concurrent* differing resolves; the implementation applies the same rule to causally-later resolves (so an intentional re-resolution cannot supersede) and reads `AlreadyApplied` for any resolve against a `Dismissed` conflict. Also: the meta-conflict record cannot name the contested conflict in `affected_objects` because `TypedObjectId` has no Conflict kind. Pin the causally-later semantics and decide whether conflict records need an addressable object kind. | G / Pass 12 (conflict resolution) |
|
||||
|
||||
## Not yet open elsewhere
|
||||
|
||||
Agent I (Track A) has contributed P12-I1..I3 above. Track B's Agent K has
|
||||
contributed P12-K1 (Operation Catalog M1). Agent J (Binary Format companion) has
|
||||
not yet contributed; when it does, append rows — the batch is already open, so it
|
||||
joins directly (no new threshold).
|
||||
contributed P12-K1..K4; H has contributed P12-H1..H7 (H6/H7 from the 2026-07
|
||||
spec-compliance audit follow-up, alongside K3/K4). Agent J (Binary Format
|
||||
companion) has not yet contributed; when it does, append rows — the batch is
|
||||
already open, so it joins directly (no new threshold).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# Pass 12 — Ratification Log
|
||||
|
||||
## Tranche 1 (2026-07-02): audit spec-alignment
|
||||
|
||||
The 2026-07 six-agent spec-compliance audit sorted its findings into code-side
|
||||
MUST fixes (landed separately; see the crate `DECISIONS.md` files and
|
||||
`CONFORMANCE.md`) and places where the *spec text* trailed a disposition the
|
||||
project had already made — a documented crate decision, the ratified Operation
|
||||
Catalog, or the PHASE2_QUICKSTART model the implementation follows. This
|
||||
tranche ratifies that second set. **No byte-layout changes; architecture
|
||||
unchanged.** The main Pass 12 batch (`PASS12_BATCH.md`) stays open: the
|
||||
P12-H/K rows await G's ratification alongside Phase 2's open questions and are
|
||||
*not* resolved here.
|
||||
|
||||
`adopt` = blessed the implementation/catalog behavior as normative spec text.
|
||||
`fixed` = the spec text was contradictory or stale and changed.
|
||||
|
||||
| Item | Disposition | Spec locus | Authority followed |
|
||||
|---|---|---|---|
|
||||
| ModifyEvent metric placement | **adopt** — the catalog's §ModifyEvent reduction rule now states: a *metric* move (different `Musical` position/duration) is materialised, re-sorting the owning voice (id-tiebroken), behind a **placement precondition** read from the reducer's canonical voice-occupancy index — a non-positive span or an overlap with another live event refuses as a clean `EventDurationInvalid` no-op; a materialised move updates the occupancy index; non-metric moves and tuplet-member trimming stay deferred; same-placement field edits apply in place | operation_catalog §ModifyEvent | ops DECISIONS "ModifyEvent materializes metric placement changes (trim/move)"; `reduce.rs::modify_event` |
|
||||
| Slur/Spanner re-anchoring | **fixed (core-spec↔catalog contradiction)** — the rule-table rows now read: re-anchor to the nearest surviving endpoint/anchor while ≥ 1 survives, cascade-delete only when none does; a two-endpoint slur collapses onto its sole survivor (reference-clean degenerate form); proximity-aware re-targeting and per-spanner-kind bounds are deferred refinements (P11-C5 stand-in). Previously the Slur row said "nearest forward event in same voice; cascade if fewer than two members survive" | core_spec §The Re-Anchoring Rule Table | operation_catalog §DeleteEvent Re-anchoring; `reduce.rs` ledger + `materialize_graph_delete` (kept consistent per ops DECISIONS "the graph follows the ledger") |
|
||||
| RewriteTuplets payload | **adopt (id-only v1)** — core spec drops `TupletRewrite { tuplet, new_ratio, new_members }` for the catalog's `RewriteTuplets { tuplets: Vec<TupletId> }`, and states normatively that graph-aware reduction MUST refuse the variant as an ill-formed compensation rather than fabricate rewritten values; a future value-carrying payload revision reopens it (`ReplaceWithRest`/`CascadeDeleteTuplets` are the applicable compensations until then). Keeps v1 bytes stable; the functionality hole is documented instead of contradictory | core_spec §DeleteEvent (payload, preconditions, effect) | operation_catalog §DeleteEvent; `reduce.rs` refusal (`TupletCompensationInvalid`) |
|
||||
| Operation-block summaries | **adopt (bundle M4)** — an `OperationEnvelopeBlock` chunk payload is the envelope vector only (the spec'd `block_id` field was unrealizable: a content-addressed chunk cannot embed its own id); `OperationBlockSummary { dvv_summary, min_stamp, max_stamp }` lives in the **manifest**, keyed by the block's `ChunkId`, as opaque ops-supplied, non-canonical, optional metadata; the "writers SHOULD order blocks by min_stamp" line is dropped — the canonical manifest encoding sorts chunk references ascending by encoded form (Appendix D), so stamp-ordered scanning goes through the summaries | core_spec Ch8 §Operation Envelope Blocks + §Manifest | bundle DECISIONS "Operation-envelope block summary metadata is carried (M4 follow-up)"; `manifest.rs::OperationBlockSummary` |
|
||||
| Pre-pass output model | **fixed (stale Ch2/Ch3 text)** — the spelling and decomposition pre-passes produce **canonical derived annotations**: deterministic functions of (materialized graph, profile, versioned algorithm id), recomputed on materialization, never stored as graph state or serialized into canonical chunks — replacing "the pre-pass MUST produce / write `Inferred`-source attachments" and "stored as an attachment on the event". The pre-pass now MUST NOT mint attachments; promotion to a stored `UserChosen` attachment happens only via explicit editing operations. The incremental-re-run MUST is demoted to MAY (cache/incremental recompute permitted; neither observable in output; cache invalidates on derivation-key change). Ch1 design-principle retitled "notational rhythm is *derived*" | core_spec Ch2 §Spelling Source/Precedence/Pre-Pass + rationale; Ch3 §Notational Decomposition + §Decomposition Pre-Pass; Ch1 design principles | PHASE2_QUICKSTART "Canonical model: derived annotations, not stored objects"; `prepass.rs::derive_annotations` |
|
||||
| Spelling tie-break | **adopt** — conflicts break by precedence, then attachment `priority` (higher wins), then **canonical attachment order** (earliest in the score's canonical serialization wins); the "attachment creation timestamp" tie-break is deleted — attachments carry no timestamp, and one would hang resolution on non-canonical state | core_spec Ch2 §Configurable Precedence | `prepass.rs::resolve_spelling` |
|
||||
| Conflict-registry location | **fixed** — `Score.conflicts: ConflictRegistry` removed from the graph root; the registry is a component of canonical **materialized state** (which Ch6's requirement already said), i.e. a reduction product, not authored content; the hierarchy table, chapter overview, and §Conflict Records prose updated to match | core_spec Ch5 §Score root; Ch6 §Conflict Records / §The Conflict Registry | `reduce.rs::MaterializedState.conflicts` |
|
||||
| SolverTier::Stub | **adopt** — the spec enum gains the code's `Stub` variant: not a conformance tier, an interface-only/passthrough solver ordered below every conformant tier; declaring it makes no conformance claim and satisfies no minimum-tier requirement. Never canonically serialized, so no byte impact | core_spec Ch7 §Conformance Tiers | `layout-ir/solver.rs::SolverTier` |
|
||||
|
||||
**Version movements.** Operation Catalog 0.2.0 → 0.3.0 (ModifyEvent
|
||||
reduction-rule behavior text). Core spec: revision-history row "Pass 12
|
||||
tranche 1 (audit spec-alignment)" appended.
|
||||
|
||||
**Deliberately not touched here** (still open in `PASS12_BATCH.md`): every
|
||||
P12-H/K row (algorithm-id ratification, decomposition precedence
|
||||
configurability, authored decompositions for ineligible events, system-pitch
|
||||
content modification, ResolveConflict beyond the concurrent case, RespellPitch
|
||||
v0 migration fallback, Transpose interval algebra), plus the audit's Push-3
|
||||
wiring tracks (constraints, overrides, validation modes, edit barriers,
|
||||
operation index) — those are code work, not spec alignment.
|
||||
Binary file not shown.
|
|
@ -487,7 +487,7 @@ identity.
|
|||
\endhead
|
||||
Score & --- & Document root. Carries metadata, instruments,
|
||||
global staves, staff groups, tuning context, tempo map,
|
||||
canvas, conflict registry. \\
|
||||
canvas. \\
|
||||
Canvas & --- & Spatial root. Partitioned into regions. \\
|
||||
Region & \texttt{RegionId} & Spatial-temporal container.
|
||||
Declares time model (metric/proportional/aleatoric) and
|
||||
|
|
@ -661,7 +661,7 @@ orthogonal to file-format profiles
|
|||
\ref{ch:graph} & Score graph, event arena, cross-cutting
|
||||
structures, regions, staff identity (Staff vs.
|
||||
StaffInstance), polymeter via per-staff metric grids, voice
|
||||
origin, conflict registry, graph invariants. \\
|
||||
origin, graph invariants. \\
|
||||
\ref{ch:semops} & Operation framework, canonical reduction,
|
||||
DVV causal contexts, operation effects, tombstones, conflict
|
||||
records, transactions, re-anchoring, undo, LWW discipline. \\
|
||||
|
|
@ -1221,8 +1221,9 @@ pub enum SpellingSource {
|
|||
UserChosen,
|
||||
|
||||
/// Inferred by the spelling pre-pass from key signature and
|
||||
/// context. Lower precedence; may be revised by re-running the
|
||||
/// pre-pass.
|
||||
/// context. Lowest default precedence. The pre-pass does not
|
||||
/// store attachments (its output is a derived annotation);
|
||||
/// this variant ranks that output in the precedence order.
|
||||
Inferred,
|
||||
|
||||
/// Imported from a foreign format.
|
||||
|
|
@ -1240,9 +1241,13 @@ pub enum SpellingSource {
|
|||
Editing operations that respell a pitch \MUST{} produce attachments
|
||||
with source \texttt{UserChosen}. Editing operations that move or
|
||||
transpose a pitch \MUST{} produce attachments with source
|
||||
\texttt{Propagated}. The spelling pre-pass (Section~\ref{sec:pitch:prepass})
|
||||
\MUST{} produce attachments with source \texttt{Inferred}. Foreign-format
|
||||
importers \MUST{} produce attachments with source \texttt{Imported}.
|
||||
\texttt{Propagated}. Foreign-format importers \MUST{} produce
|
||||
attachments with source \texttt{Imported}. The spelling pre-pass
|
||||
(Section~\ref{sec:pitch:prepass}) \MUSTNOT{} mint attachments: its
|
||||
inferred spellings are derived annotations reported with
|
||||
\texttt{Inferred} provenance, promoted to a stored
|
||||
\texttt{UserChosen} attachment only by an explicit editing
|
||||
operation.
|
||||
\end{requirement}
|
||||
|
||||
\subsection{Configurable Precedence}
|
||||
|
|
@ -1266,17 +1271,29 @@ precedence is:
|
|||
default; for example, a workflow that treats MusicXML imports as
|
||||
authoritative may rank \texttt{Imported} above \texttt{UserChosen}.
|
||||
Conflicts are broken first by precedence, then by the \texttt{priority}
|
||||
field of the attachment, then by attachment creation timestamp.
|
||||
field of the attachment (higher wins), then by canonical attachment
|
||||
order (the attachment appearing earliest in the score's canonical
|
||||
serialization wins). Attachments carry no creation timestamp: a
|
||||
timestamp tie-break would hang resolution on non-canonical state.
|
||||
\end{requirement}
|
||||
|
||||
\subsection{The Spelling Pre-Pass}
|
||||
\label{sec:pitch:prepass}
|
||||
|
||||
When a score is loaded, edited, or about to be rendered, the spelling
|
||||
pre-pass produces \texttt{Inferred}-source attachments for every pitch
|
||||
that lacks a higher-precedence attachment. The pre-pass \MUST{} be
|
||||
deterministic: given the same score graph and the same precedence
|
||||
configuration, it \MUST{} produce identical attachments.
|
||||
When a score is materialized (on load, after an edit, or ahead of
|
||||
rendering), the spelling pre-pass computes an inferred spelling for
|
||||
every spelling-eligible pitch. Its output is a \emph{canonical
|
||||
derived annotation}: a deterministic function of the materialized
|
||||
score graph, the active profile, and the versioned
|
||||
\texttt{SpellingAlgorithmId}, recomputed on materialization and
|
||||
never stored as graph state or serialized into canonical chunks.
|
||||
Where a stored attachment targets the pitch, the resolved annotation
|
||||
reports that attachment's spelling and provenance per the precedence
|
||||
configuration (Section~\ref{sec:pitch:precedence}); the pre-pass
|
||||
supplies the \texttt{Inferred}-provenance spelling that applies when
|
||||
no attachment outranks it. The pre-pass \MUST{} be deterministic:
|
||||
given the same materialized graph, precedence configuration, and
|
||||
algorithm version, it \MUST{} produce identical annotations.
|
||||
|
||||
The pre-pass operates as follows:
|
||||
|
||||
|
|
@ -1293,19 +1310,30 @@ The pre-pass operates as follows:
|
|||
\item The harmonic context (concurrent pitches in other voices).
|
||||
\item Any active scope-level \texttt{Rule} attachments.
|
||||
\end{enumerate}
|
||||
\item Write an \texttt{Inferred}-source attachment for each pitch.
|
||||
\item Report an \texttt{Inferred}-provenance derived annotation for
|
||||
each pitch.
|
||||
\end{enumerate}
|
||||
|
||||
The pre-pass \MUST{} be incremental: an edit to a single pitch \MUST{}
|
||||
re-run the pre-pass only on the affected measure and voice (and forward
|
||||
through measures whose context is altered, until the context stabilizes).
|
||||
Because derived annotations are not stored, a change to the algorithm
|
||||
version deterministically invalidates them without any state
|
||||
migration. Implementations \MAY{} cache derived annotations and
|
||||
\MAY{} recompute incrementally (re-running only the affected measure
|
||||
and voice, and forward through measures whose context is altered,
|
||||
until the context stabilizes); a cache \MUST{} be invalidated when
|
||||
the derivation key --- graph, profile, algorithm version --- changes,
|
||||
and neither caching nor incrementality may be observable in the
|
||||
annotations produced.
|
||||
|
||||
\begin{rationale}
|
||||
Storing inferred spellings as attachments---rather than computing them
|
||||
at draw time---makes spellings inspectable in the UI, editable by
|
||||
promotion to \texttt{UserChosen}, and deterministic for serialization
|
||||
diffs. The cost is one attachment per unspelled pitch, which is
|
||||
negligible.
|
||||
Deriving inferred spellings---rather than storing them as
|
||||
attachments---keeps algorithm output out of canonical state: two
|
||||
replicas at the same (graph, profile, algorithm version) agree on
|
||||
annotations byte-for-byte without ever exchanging them, an
|
||||
algorithm upgrade cannot strand stale inferred state, and
|
||||
serialization never churns on re-inference. Inferred spellings
|
||||
remain inspectable in the UI and editable by promotion: accepting
|
||||
one mints a \texttt{UserChosen} attachment through an editing
|
||||
operation, which then outranks the pre-pass.
|
||||
\end{rationale}
|
||||
|
||||
\begin{openquestion}
|
||||
|
|
@ -1491,11 +1519,11 @@ proportional, and aleatoric time models simultaneously.
|
|||
interchangeable. The algebra (position $+$ duration $\rightarrow$ position;
|
||||
position $-$ position $\rightarrow$ duration; position $+$ position is
|
||||
undefined) is enforced at the type level.
|
||||
\item[Sounding duration is data; notational rhythm is attached.]
|
||||
\item[Sounding duration is data; notational rhythm is derived.]
|
||||
A note's sounding duration is intrinsic; its notated decomposition
|
||||
(notehead values, ties, dots, tuplet membership) is produced by a
|
||||
deterministic pre-pass analogous to the spelling pre-pass and is
|
||||
overridable per-event.
|
||||
overridable per-event by an authored attachment.
|
||||
\item[Multiple time models coexist.] A score \MUST{} support metric,
|
||||
proportional, and aleatoric regions, possibly concurrent on
|
||||
different staves. The region's declared time model determines the
|
||||
|
|
@ -2097,9 +2125,12 @@ Its \emph{notational decomposition} is the sequence of notehead values,
|
|||
augmentation dots, and ties used to draw that duration on the staff.
|
||||
|
||||
The decomposition is computed from the sounding duration by a
|
||||
deterministic pre-pass and stored as an attachment on the event. The
|
||||
attachment is overridable by the user; the pre-pass produces an
|
||||
inferred decomposition that the user may accept or replace.
|
||||
deterministic pre-pass as a \emph{canonical derived annotation} ---
|
||||
recomputed on materialization, never stored graph state --- under the
|
||||
same output model as the spelling pre-pass
|
||||
(Section~\ref{sec:pitch:prepass}). The user may accept the inferred
|
||||
decomposition or override it by authoring a decomposition attachment,
|
||||
which takes precedence.
|
||||
|
||||
\subsection{The Notational Decomposition}
|
||||
|
||||
|
|
@ -2141,9 +2172,10 @@ machinery, same pre-pass discipline.
|
|||
|
||||
\subsection{The Decomposition Pre-Pass}
|
||||
|
||||
When a score is loaded or edited, the decomposition pre-pass produces
|
||||
inferred decompositions for events that lack a higher-precedence
|
||||
attachment. The algorithm is, in outline:
|
||||
When a score is materialized, the decomposition pre-pass computes
|
||||
inferred decompositions, reported as derived annotations; an authored
|
||||
higher-precedence attachment overrides the inferred value in the
|
||||
resolved annotation. The algorithm is, in outline:
|
||||
|
||||
\begin{enumerate}
|
||||
\item For each event in time order within a voice and measure,
|
||||
|
|
@ -2161,11 +2193,14 @@ attachment. The algorithm is, in outline:
|
|||
\end{enumerate}
|
||||
|
||||
\begin{requirement}
|
||||
The decomposition pre-pass \MUST{} be deterministic: given identical
|
||||
score graph and identical configuration, it \MUST{} produce identical
|
||||
decompositions. The pre-pass \MUST{} be incremental: an edit to one
|
||||
event \MUST{} re-run the pre-pass only on the affected voice and
|
||||
measure, with propagation only as far as the context is altered.
|
||||
The decomposition pre-pass \MUST{} be deterministic: given
|
||||
identical materialized graph, configuration, and algorithm
|
||||
version, it \MUST{} produce identical decompositions. Its output
|
||||
is a derived annotation, never stored graph state. Implementations
|
||||
\MAY{} cache and \MAY{} recompute incrementally (re-running only
|
||||
the affected voice and measure, with propagation only as far as
|
||||
the context is altered); neither may be observable in the
|
||||
annotations produced.
|
||||
\end{requirement}
|
||||
|
||||
\begin{openquestion}
|
||||
|
|
@ -3298,7 +3333,12 @@ Chapters~\ref{ch:pitch}--\ref{ch:tuning} into a coherent whole.
|
|||
\label{sec:graph:score}
|
||||
|
||||
The score is the root object. All other objects in the specification
|
||||
exist within or reference into a score.
|
||||
exist within or reference into a score. Conflict records are
|
||||
deliberately \emph{not} part of the score graph: the
|
||||
\texttt{ConflictRegistry} produced by canonical reduction is a
|
||||
component of the canonical \emph{materialized state}
|
||||
(Chapter~\ref{ch:semops}), recomputed with it, and persisting there
|
||||
until resolved or dismissed by explicit operations.
|
||||
|
||||
\begin{lstlisting}[language=Rust]
|
||||
pub struct Score {
|
||||
|
|
@ -3326,11 +3366,6 @@ pub struct Score {
|
|||
/// graphic gestures, comments, and analytical annotations.
|
||||
pub cross_cutting: CrossCuttingRegistry,
|
||||
|
||||
/// Conflict records produced by the canonical reduction
|
||||
/// (Chapter~\ref{ch:semops}). Conflict records persist until
|
||||
/// resolved or dismissed by explicit operations.
|
||||
pub conflicts: ConflictRegistry,
|
||||
|
||||
/// Tuning systems, pitch spaces, and reference pitch.
|
||||
pub tuning_context: ScoreTuningContext,
|
||||
|
||||
|
|
@ -6349,9 +6384,9 @@ yields a deletion record rather than the original object.
|
|||
|
||||
When an operation cannot apply cleanly, the reduction produces a
|
||||
\texttt{Conflicted} effect referencing a \texttt{ConflictRecord} in
|
||||
the score's conflict registry. Conflict records are first-class
|
||||
graph objects: stable, addressable, visible to users, and
|
||||
addressable by subsequent operations.
|
||||
the canonical conflict registry (a component of materialized state;
|
||||
see below). Conflict records are first-class: stable, addressable,
|
||||
visible to users, and addressable by subsequent operations.
|
||||
|
||||
\subsection{Conflict Record Type}
|
||||
|
||||
|
|
@ -6503,8 +6538,9 @@ pub enum ResolutionAction {
|
|||
|
||||
\subsection{The Conflict Registry}
|
||||
|
||||
The score graph carries a top-level conflict registry alongside the
|
||||
cross-cutting registry:
|
||||
Canonical materialized state carries a top-level conflict registry
|
||||
(the score graph does not --- conflict records are a reduction
|
||||
product, not authored content):
|
||||
|
||||
\begin{lstlisting}[language=Rust]
|
||||
pub struct ConflictRegistry {
|
||||
|
|
@ -6876,9 +6912,11 @@ table is normative.
|
|||
\endhead
|
||||
|
||||
Slur & Endpoint event &
|
||||
Re-anchor to nearest forward event in same voice; cascade-delete
|
||||
if fewer than two members survive &
|
||||
proximity max: same voice; direction: forward preferred \\
|
||||
Re-anchor to the nearest surviving endpoint; cascade-delete only
|
||||
when no endpoint survives &
|
||||
a two-endpoint slur collapses onto its sole survivor (a
|
||||
reference-clean degenerate form); proximity-aware re-targeting
|
||||
is a deferred refinement \\
|
||||
|
||||
Slur & Interior event &
|
||||
No action &
|
||||
|
|
@ -6899,9 +6937,10 @@ table is normative.
|
|||
--- \\
|
||||
|
||||
Spanner & Anchor &
|
||||
Re-anchor to nearest surviving anchor of same kind; cascade-delete
|
||||
if no surviving anchor within proximity bound &
|
||||
proximity max: same staff instance; bound: per-spanner-kind \\
|
||||
Re-anchor to the nearest surviving anchor; cascade-delete only
|
||||
when no anchor survives &
|
||||
as for slurs (surviving-endpoint collapse); per-spanner-kind
|
||||
proximity bounds are a deferred refinement \\
|
||||
|
||||
Marker & Anchor &
|
||||
Re-anchor to nearest event in same staff instance &
|
||||
|
|
@ -7324,19 +7363,15 @@ pub enum TupletCompensation {
|
|||
ReplaceWithRest { new_rest: Rest },
|
||||
|
||||
/// Rewrite the enclosing tuplet(s) to remain structurally
|
||||
/// consistent. The list specifies the rewritten tuplets in full.
|
||||
RewriteTuplets { rewrites: Vec<TupletRewrite> },
|
||||
/// consistent. The v1 payload names the affected tuplets by
|
||||
/// id only; it does not carry rewritten values (see the
|
||||
/// precondition below).
|
||||
RewriteTuplets { tuplets: Vec<TupletId> },
|
||||
|
||||
/// Cascade-delete the entire tuplet group(s) containing the
|
||||
/// target event. The listed tuplet ids are cascaded.
|
||||
CascadeDeleteTuplets { tuplets: Vec<TupletId> },
|
||||
}
|
||||
|
||||
pub struct TupletRewrite {
|
||||
pub tuplet: TupletId,
|
||||
pub new_ratio: TupletRatio,
|
||||
pub new_members: Vec<EventId>,
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\textbf{Invariant preconditions:}
|
||||
|
|
@ -7354,9 +7389,17 @@ pub struct TupletRewrite {
|
|||
\item For \texttt{ReplaceWithRest}: the replacement rest's
|
||||
duration \MUST{} equal the deleted event's duration; the rest's
|
||||
voice and position \MUST{} match the deleted event.
|
||||
\item For \texttt{RewriteTuplets}: every listed tuplet's resulting
|
||||
member-duration sum \MUST{} match the tuplet's
|
||||
structurally-required total.
|
||||
\item For \texttt{RewriteTuplets}: the v1 payload carries only
|
||||
tuplet ids, not the rewritten ratio and member values a reducer
|
||||
would need to demonstrate that the resulting structure preserves
|
||||
the tuplet consistency invariant. Graph-aware reduction \MUST{}
|
||||
therefore refuse the variant as an ill-formed compensation (a
|
||||
precondition no-op) rather than fabricate rewritten values. A
|
||||
future payload revision carrying full rewrites (per-tuplet ratio
|
||||
and members, each of whose member-duration sums \MUST{} match
|
||||
the tuplet's structurally-required total) reopens the variant;
|
||||
until then \texttt{ReplaceWithRest} and
|
||||
\texttt{CascadeDeleteTuplets} are the applicable compensations.
|
||||
\item For \texttt{CascadeDeleteTuplets}: every tuplet listed
|
||||
\MUST{} contain the target event as a member.
|
||||
\end{itemize}
|
||||
|
|
@ -7366,8 +7409,9 @@ voice's event list. The event's \texttt{EventId} and every contained
|
|||
\texttt{PitchId} are tombstoned in their respective identity
|
||||
indexes: they remain resolvable to ``deleted'' markers and \MUSTNOT{}
|
||||
be reused for new objects. The chosen tuplet compensation is applied
|
||||
in the same delta: a replacement rest is inserted, the tuplet is
|
||||
rewritten, or the tuplets are cascade-deleted as specified.
|
||||
in the same delta: a replacement rest is inserted or the tuplets are
|
||||
cascade-deleted as specified (a \texttt{RewriteTuplets} compensation
|
||||
is refused at precondition check; see above).
|
||||
Cross-cutting structures referencing the event or its pitches are
|
||||
re-anchored per the rule table (Section~\ref{sec:semops:reanchor}).
|
||||
Live attachments targeting the event or its pitches transition to
|
||||
|
|
@ -9286,10 +9330,22 @@ pub struct Manifest {
|
|||
|
||||
/// Operation-envelope blocks defining the canonical document.
|
||||
/// Block ordering is irrelevant for canonical state (envelopes
|
||||
/// are a set), but writers SHOULD order blocks by min_stamp
|
||||
/// for predictable scanning.
|
||||
/// are a set), and the canonical manifest encoding sorts chunk
|
||||
/// references ascending by their encoded form (Appendix D), so
|
||||
/// a writer cannot order this list by stamp; stamp-ordered
|
||||
/// scanning goes through operation_block_summaries below.
|
||||
pub operation_roots: Vec<ChunkRef>,
|
||||
|
||||
/// Per-block summary metadata (Section on operation-envelope
|
||||
/// blocks below), keyed by the block's ChunkId in canonical
|
||||
/// ascending order. Supplied by the operation layer and carried
|
||||
/// opaquely by the storage layer; non-canonical and optional (a
|
||||
/// block need not have an entry). Lets a reader select or skip
|
||||
/// a block by causal frontier or stamp range without decoding
|
||||
/// its envelopes.
|
||||
pub operation_block_summaries:
|
||||
BTreeMap<ChunkId, OperationBlockSummary>,
|
||||
|
||||
/// Optional operation index chunk: maps OperationId to
|
||||
/// (block, offset) for fast random access. If absent,
|
||||
/// implementations rebuild on demand by scanning blocks.
|
||||
|
|
@ -9735,20 +9791,30 @@ pub struct ChunkId(pub ContentHash);
|
|||
\label{sec:format:opblocks}
|
||||
|
||||
The canonical document is stored as operation-envelope blocks. Each
|
||||
block is a chunk of kind \texttt{OperationEnvelopeBlock} containing
|
||||
a vector of envelopes, with summary metadata.
|
||||
block is a chunk of kind \texttt{OperationEnvelopeBlock} whose
|
||||
payload is a vector of envelopes. A block is identified by its
|
||||
\texttt{ChunkId}; because chunks are content-addressed, that
|
||||
identifier cannot appear inside the block payload itself. Summary
|
||||
metadata for a block travels in the \emph{manifest}
|
||||
(\texttt{operation\_block\_summaries}), keyed by the block's chunk
|
||||
id: the summary is computed by the operation layer from the block's
|
||||
envelopes, and the storage layer carries it as opaque bytes without
|
||||
interpreting it.
|
||||
|
||||
\begin{lstlisting}[language=Rust]
|
||||
pub struct OperationEnvelopeBlock {
|
||||
/// Block identifier (same as its ChunkId in the chunk store).
|
||||
pub block_id: ChunkId,
|
||||
|
||||
/// Operation envelopes in this block. Order within a block is
|
||||
/// not normative for canonical reduction (envelopes are a set)
|
||||
/// but writers SHOULD preserve authoring order within a block
|
||||
/// for diagnostic purposes.
|
||||
pub envelopes: Vec<OperationEnvelope>,
|
||||
}
|
||||
|
||||
/// Manifest-side summary of one block, keyed by the block's
|
||||
/// ChunkId. Non-canonical and optional: a reader MUST NOT rely
|
||||
/// on a summary being present, and canonical state never depends
|
||||
/// on one.
|
||||
pub struct OperationBlockSummary {
|
||||
/// DVV summary covering the envelopes in this block. Enables
|
||||
/// readers to determine causal coverage without parsing every
|
||||
/// envelope.
|
||||
|
|
@ -11099,6 +11165,11 @@ and the two axes are independent.
|
|||
|
||||
\begin{lstlisting}[language=Rust]
|
||||
pub enum SolverTier {
|
||||
/// Not a conformance tier: an interface-only / passthrough
|
||||
/// solver that evaluates no constraints and computes no
|
||||
/// quality metrics. Ordered below every conformant tier.
|
||||
Stub,
|
||||
|
||||
/// Minimal Layout Solver.
|
||||
Minimal,
|
||||
|
||||
|
|
@ -11110,6 +11181,11 @@ pub enum SolverTier {
|
|||
}
|
||||
\end{lstlisting}
|
||||
|
||||
The \texttt{Stub} variant exists so scaffolding, harnesses, and
|
||||
not-yet-conformant pipelines can label themselves honestly: it makes
|
||||
no conformance claim, and a document requiring any conformant tier
|
||||
is never satisfied by a solver declaring \texttt{Stub}.
|
||||
|
||||
\subsection{Minimal Layout Solver}
|
||||
|
||||
\begin{requirement}
|
||||
|
|
@ -14405,6 +14481,43 @@ layouts they own versus inherit:
|
|||
operation content hash) were golden-locked in code but unpinned in
|
||||
spec text.
|
||||
\\
|
||||
\today & Pass 12 tranche 1 (audit spec-alignment) &
|
||||
Aligned spec text with dispositions the project had already made
|
||||
and the reference implementation exercises; no byte-layout or
|
||||
architecture change. The re-anchoring rule table's Slur and
|
||||
Spanner rows now state the Operation Catalog's surviving-endpoint
|
||||
semantics (re-anchor while $\geq 1$ endpoint survives,
|
||||
cascade-delete only when none does; proximity-aware re-targeting
|
||||
deferred). \texttt{TupletCompensation::RewriteTuplets} adopts the
|
||||
catalog's id-only v1 payload (\texttt{TupletRewrite} removed):
|
||||
graph-aware reduction refuses the variant rather than fabricate
|
||||
rewritten values, and a value-carrying payload revision reopens
|
||||
it. Operation-envelope blocks are pure envelope vectors (a
|
||||
content-addressed chunk cannot embed its own id); the per-block
|
||||
summary metadata
|
||||
(\texttt{dvv\_summary}/\texttt{min\_stamp}/\texttt{max\_stamp})
|
||||
relocated to the manifest's \texttt{operation\_block\_summaries}
|
||||
map (opaque ops-supplied bytes, non-canonical, ChunkId-keyed),
|
||||
and the order-blocks-by-\texttt{min\_stamp} \SHOULD{} was dropped
|
||||
because the canonical manifest encoding sorts chunk references by
|
||||
encoded form. The spelling and decomposition pre-passes are now
|
||||
specified as producing \emph{canonical derived annotations} ---
|
||||
deterministic functions of (materialized graph, profile,
|
||||
versioned algorithm id), recomputed on materialization and never
|
||||
stored --- replacing the stored-\texttt{Inferred}-attachment
|
||||
model; the incremental re-run requirement is demoted from
|
||||
\MUST{} to \MAY{} with caching and incrementality required to be
|
||||
unobservable. Spelling-precedence ties now break by canonical
|
||||
attachment order (attachments carry no creation timestamp). The
|
||||
\texttt{ConflictRegistry} moved off the \texttt{Score} root into
|
||||
canonical materialized state, matching Chapter~6.
|
||||
\texttt{SolverTier} gains the non-conformance \texttt{Stub}
|
||||
variant. Companion: Operation Catalog 0.2.0 $\rightarrow$ 0.3.0
|
||||
(ModifyEvent materializes metric placement changes behind a
|
||||
voice-occupancy placement precondition). Dispositions logged in
|
||||
\texttt{spec/PASS12\_RATIFICATION\_LOG.md}; the Pass 12 batch
|
||||
itself remains open.
|
||||
\\
|
||||
\bottomrule
|
||||
\end{longtable}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -226,7 +226,7 @@
|
|||
{\Large\scshape\color{epiphanyslate}Operation Catalog}\\[6pt]
|
||||
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
|
||||
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
|
||||
{\normalsize\color{epiphanyink}Version 0.2.0 --- Phase 2 (K0 representative + broad-K0 M2 groups)}\\[4pt]
|
||||
{\normalsize\color{epiphanyink}Version 0.3.0 --- Phase 2 (K0 representative + broad-K0 M2 groups)}\\[4pt]
|
||||
{\small\color{epiphanyslate}Normative for the operation kinds it defines}
|
||||
\vfill
|
||||
\end{titlepage}
|
||||
|
|
@ -493,13 +493,23 @@ is read from the value.
|
|||
Precondition: the event is live. The resolved value is the one carried by the
|
||||
operation latest in canonical order; two causally ordered modifications overwrite
|
||||
intentionally, and two \emph{concurrent} modifications with \emph{equal} value
|
||||
reduce idempotently. Graph-aware reduction overwrites the event in place. A
|
||||
modification that \emph{moves} the event (a different region-local position or
|
||||
duration) is recorded in the bookkeeping but its placement change is \emph{not}
|
||||
applied to the graph: re-sorting a voice on a placement change is a deferred
|
||||
refinement, and an in-place move would break \texttt{VoiceEventsSortedNonOverlap}
|
||||
(Chapter~5 invariant~3). A malformed (empty pitched) replacement is likewise
|
||||
recorded but not materialised.
|
||||
reduce idempotently. Graph-aware reduction overwrites the event in place, and a
|
||||
modification that \emph{moves} a metric event (a different region-local
|
||||
\texttt{Musical} position or duration) is \emph{materialised}: the owning voice
|
||||
is re-sorted by ascending position (id-tiebroken --- the same order an insert
|
||||
maintains), preserving \texttt{VoiceEventsSortedNonOverlap} (Chapter~5
|
||||
invariant~3). To keep that invariant, a placement change carries a
|
||||
\emph{placement precondition}, read from the reducer's canonical voice-occupancy
|
||||
index (graph-independent, so graph-free and graph-aware reduction agree): a move
|
||||
with a non-positive span, or one that would overlap another live event in the
|
||||
voice, is refused as a clean precondition no-op (\texttt{EventDurationInvalid})
|
||||
rather than skipped silently. A materialised move updates the occupancy index,
|
||||
so a later insert sees the freed or changed span. A placement change of a
|
||||
\emph{non-metric} event is recorded in the bookkeeping but not applied to the
|
||||
graph (re-sorting a non-metric voice is a deferred refinement); a malformed
|
||||
(empty pitched) replacement is likewise recorded but not materialised;
|
||||
same-placement field edits apply in place, preserving voice membership. Partial
|
||||
trimming of a tuplet member remains a later refinement.
|
||||
|
||||
\textbf{Conflict cases.} Two concurrent modifications of one event with
|
||||
\emph{differing} values produce a \texttt{StructuralFieldCollision} on the field
|
||||
|
|
|
|||
Loading…
Reference in New Issue