G3b packet 3b: the documentation half, and the ladder closes

Kind 39 CreateMeasure and precondition reasons 16-18 reach the Binary Format's
kind table, tag table, payload layout, and reason table; graph invariant 20
reaches the core specification's enumeration, which now says twenty in all three
places it counts. Both normative listings in that document gain CreateMeasure --
earlier drafts of the contract named only the invariant, and prose fails silently.

Every version surface moves in pairs. Binary Format 0.15.0 -> 0.16.0, Operation
Catalog 0.12.0 -> 0.13.0, each with a changelog entry beside the title bump. The
Text Projection companion needed only the changelog: packet 1 bumped its header to
0.13.0 and stopped there, leaving the document claiming a version its own history
did not record. That was live from e64a4b7 until now, and no gate could see it.

Two public hooks exist that would otherwise look like leaks. epiphany-ops depends
on epiphany-core and never the reverse, so invariant 20 implements pin 6/6b's
comparable relation and musical delta a second time over the graph alone. Both
DECISIONS records name the divergence hazard that forces the duplication, name the
cross-crate agreement test as the hooks' only sanctioned use, and say so from each
side.

The monotonicity evidence chain gains only vocabulary-introducing events -- G2b
13c3d2f, G3a 6c5e69f, G3b -- and excludes G-minor and P13-S17 with the reason
stated: neither introduced an additive variant. The 2026-07-29 tie between G2b and
G3a is broken by ancestry, not timestamp.

P13-S18 (invariant 20's abstention residue) and P13-S19 (the pickup deferral) are
filed open by design. P13-S20 is recorded RESOLVED.

The genesis ladder G1 -> G2a -> G-minor -> G2b -> G3a -> G3b is CLOSED.

Executed against spec/CONTRACT_GENESIS_G3B_MEASURE.md rows 14a and 26-36,
mutation M71, which is now the contract's own guard: deleting the G3b Revision
History row fails the history test even though "genesis tranche G3b" still appears
twice in neighbouring prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
This commit is contained in:
Levi Neuwirth 2026-07-30 20:45:27 -04:00
parent acd7a7690f
commit d58eee8063
16 changed files with 507 additions and 36 deletions

View File

@ -1526,3 +1526,63 @@ behaviour change): its body already resolved a staff's group, a group's
members, a part's staves, a view's active layers, and measure/grid
time-signature references; the doc comment previously named only cross-cutting
structures and event-internal references.
## Genesis tranche G3b — graph invariant 20, and the anchor-relation oracle
## implemented twice (2026-07-29/30)
`spec/CONTRACT_GENESIS_G3B_MEASURE.md` closes the genesis ladder
(`spec/PLAN_GENESIS_OPS.md` §4: G1 → G2a → G-minor → G2b → G3a → G3b). This
crate's share is graph invariant **20** — measure/meter agreement and
boundary consistency — plus the doc-only invariant-10 note it does not
duplicate, and one deliberate, unusual export.
**Invariant 20 checks two things and nothing else** (pin 9b): that a
measure's *resolving* `time_signature` AGREES with the effective metric
grid's active signature at its start, and that consecutive measure starts
are separated by the governing signature's `measure_duration()` (BOUNDARY
consistency). It does **not** re-check signature *resolution* — invariant 10
already does that (`invariants.rs:1180``:1212`) — so a `time_signature`
that fails to resolve is invariant 10's business, not invariant 20's.
`time_signature: None` exempts only the agreement clause; the inherited
meter still governs boundary consistency, so a `None` measure landing at the
wrong distance from its predecessor is still flagged.
**It ABSTAINS rather than fails closed** (pin 7) wherever pin 6's comparable
relation cannot order two measure starts, or pin 6b's musical delta cannot
be computed between them: cross-clock offsets, differing boundary selectors,
or any anchor this prototype's `resolve_anchor` cannot place (notably a
`Measure` *end*, `invariants.rs:503``:516`). This is the deliberate
opposite of `create_measure`'s own precondition, which fails closed on the
identical incomputable cases (`epiphany-ops` `DECISIONS.md`) — base-ingested
data may predate the rule, and flagging every incomputable case would make
the invariant useless on real scores. The residue is filed as **P13-S18**,
open by design. A pickup/anacrusis first measure has no predecessor and is
therefore never flagged by the boundary clause — filed as **P13-S19**, also
open by design (the create-side mirror of this deferral lives in
`epiphany-ops`).
**The two anchor-relation hooks, and why they are a deliberate, unusual
export.** `epiphany-ops` depends on this crate and never the reverse, so
invariant 20 cannot call into `epiphany-ops`'s `Reducer` to reuse its
private pin 6/6b methods — and there is no third crate either could
delegate to instead. The normative comparable relation and musical delta
are therefore implemented **twice**: once here, over a materialized `Score`
(`GraphIndex::measure20_comparable_order` / `measure20_musical_delta`,
private to `invariants.rs`), and once in `epiphany-ops`'s `Reducer`, over
operational write chains. Two implementations of one normative relation is
a divergence hazard by construction, so this crate re-exports a narrow
oracle hook, `measure_anchor_relation` (`invariants.rs:296`ff, re-exported
from `lib.rs`), purely so `epiphany-testkit`'s cross-crate agreement test
(`g3b_measure_anchor_agreement.rs`) can drive the same anchor pairs through
both implementations and assert they agree — on the comparable-or-not
verdict, the ordering when comparable, and the musical delta. The mirror
hook, `epiphany_ops::measure_anchor_relation_for_agreement_test`, is
documented in `epiphany-ops`'s own `DECISIONS.md`.
**This is the hook's only sanctioned use.** It is not a general-purpose
anchor-comparison API: it exists solely to make the divergence-hazard test
possible from a third crate, and no other caller should reach for it. A
mutation perturbing only one implementation's boundary-selector or
cross-clock handling is caught by the agreement test going red, independent
of whether either implementation individually still passes its own unit
coverage.

View File

@ -2117,3 +2117,172 @@ types.
**No pruning or compaction is implemented, enabled, or prepared here** — pin
9's explicit non-goal, restated: G3a adds four more authored families to the
surface a future prune must not discard.
## Genesis tranche G3b — `CreateMeasure`, kind/tag 39, closing the genesis
## ladder (2026-07-29/30, packets 13a: `e64a4b7`, `b92023a`, `b622498`,
## `314cd7a`, `acd7a76`)
`spec/CONTRACT_GENESIS_G3B_MEASURE.md` lands the fifth and final rung
(`spec/PLAN_GENESIS_OPS.md` §4: G1 → G2a → G-minor → G2b → G3a → G3b).
Unlike every G3a family, `Measure` is a **nested container child** of
`StaffInstance` (`graph.rs:611`), not a `Score`-root vector, so
`CreateMeasure` rides `CreateStaffInstance`'s parent-carrying shape
(`payload.rs:1510`, reducer `reduce.rs:4026`) rather than the four G3a
families' bare-value shape: `CreateMeasureOp { instance: StaffInstanceId,
measure: Measure }`, `measure_id()` returning `self.measure.id`.
`Measure` is schema major 0 (`struct_codec!` is a plain unversioned walk and
its only non-scalar field, `TimeAnchor`, has a hand-written `Codec` with no
version branching), so `schema_major()` gains **no** arm and
`OperationEnvelopeBlock` stays at 3 where G2b left it.
**The carried-value map must carry the owning instance** (pin 5):
`measure_values: BTreeMap<MeasureId, (StaffInstanceId, Measure)>`, not a
bare `Measure` map, because `Measure` has no back-pointer to its parent — the
graph-removal arm and the per-instance ordering check both need it. Threaded
through the same **seven** sites every carried-value map uses (declaration,
`WorkingSnapshot` declaration, initialization, base seed in
`seed_from_graph`, mint insertion, snapshot, restore). Set-union discipline:
byte-identical re-carry under the **same** parent is `AlreadyApplied`; a
differing value, **or the same measure under a different parent**, is
`RecreateContentMismatch` — the parent is part of identity here.
**Three new `PreconditionFailureReason` variants, all epoch 12:**
`MeasureMeterMismatch` (16), `MeasureOutOfOrder` (17), and
`MeasureOrderUnverifiable` (18). `create_measure`'s append-only discipline
(pin 9, `reduce.rs:5407`ff) checks, in order: the parent `StaffInstance` is
live (ungated); a resolving `time_signature` names a live `TimeSignature`
and every non-`WallClock` referent of `start` resolves (graph-aware only);
the carried `start` is comparable to the current last live measure's start
and strictly after it (`MeasureOutOfOrder`, or `MeasureOrderUnverifiable` if
incomparable); agreement against the effective grid's active signature
(`MeasureMeterMismatch`); and the boundary distance from the predecessor
equals the governing `measure_duration()` (`MeasureMeterMismatch`, or
`MeasureOrderUnverifiable` if the delta is not computable). All three
clauses are vacuous for an instance's first measure — no predecessor to
compare against — which is the pickup/anacrusis deferral filed as
**P13-S19**, open by design (core `DECISIONS.md` files the invariant-side
mirror). `CreateMeasure` fails **closed** on every incomputable case; graph
invariant 20 (core `DECISIONS.md`) **abstains** on the identical cases —
deliberately opposite postures, not an inconsistency.
**The effective-grid oracle, and the instance-grid ledger the reducer
previously lacked** (pin 6c). `StaffInstance.local_metric_grid` overrides
the region default, but the reducer retained no instance-local grid state
before this rung, so base-free reduction could not distinguish an override
from an inherited default. Disposition **(A)** is ratified: an
`instance_grid: BTreeMap<StaffInstanceId, Option<MetricGrid>>` ledger (the
same seven sites), seeded by `create_staff_instance` and by
`seed_from_graph`, **plus** a shared oracle that reconstructs the inherited
grid from `metric_grid_chain` (whole-grid writes) layered under
`meter_change_chain` (per-key changes) **in canonical write order**, folding
in prospective undo restorations, and run identically in **both** reduction
modes — so graph-aware and base-free reduction never disagree on a case this
rung can decide. Selection over a partially-comparable `meter_sequence`
follows pin 6c exactly: an incomparable change makes selection
indeterminate even from an otherwise-empty candidate set (an incomparable
change is unplaced, not absent, and might have governed); a genuinely empty
candidate set is vacuous, not a violation; multiple mutually-incomparable
maxima are indeterminate, never resolved by document order or id.
**Two repair commits, both found by tests the white-box coverage could not
see** (`b92023a`, `b622498`). Base-free c3 ordering originally trusted
`OperationId`'s `Ord` (replica, counter — authoring identity) instead of the
minters' canonical reduction-order stamps, and never checked the two
measures shared a live `StaffInstance`. An empty effective grid originally
refused instead of abstaining, because agreement collapsed `None` into a
disagreeing `Unique` and boundary distance collapsed `None` into
`Indeterminate`; they are now separate outcomes. Simultaneous prospective
whole-grid and per-key restorations had no tie-break (`Recency::Prospective`
being a unit variant made both compare equal), repaired to let the per-key
write govern on equal recency within the prospective family — matching how
`undo_transaction` actually records a whole-grid restoration before its
meter-change restoration under the same undo. The second repair rebuilt the
regression fixture itself: it never ran its own transaction, so
`compute_pending`'s missing-vector-predecessor rule (envelope counters
jumping 3 → 10 → 11 → 12 → 20) left every member permanently pending and the
test's "prediction" was checked only against itself, never against the
materialized graph.
**Undo coverage: the graph-removal arm, and the Measure strand guard across
all SEVEN inbound surface classes** (pin 10, packet 3a `acd7a76`).
`materialize_graph_tombstones` gains a `TypedObjectId::Measure(id)` arm that
reaches the owning instance through `measure_values`'s carried parent — no
search needed. The strand guard **cannot** be written against
`self.structures`: that index is event-only by construction (the base-seed
walk filters through `anchor_event_refs`, which drops every
`Measure`/`Region`/`WallClock` anchor), so a guard against it is born green
— rewriting it that way was tried and breaks fifteen tests, proving the
point rather than merely asserting it. The seven surfaces, each with its own
owner rule: spanner (`cross_cutting_modify_chain`, owned by the structure),
repeat (`repeat_values`, owned by the repeat structure — see below), another
measure's `start` (`measure_values`, owned by that measure), meter change
(`meter_change_chain`, owned by the region), system break (`break_chain`,
owned by the region), page break (`page_break_chain`, owned by the region),
and tempo segment (`tempo_segment_chain`, keyed `(Option<RegionId>,
MusicalPosition)` — a `Some(region)` entry is owned by that region;
score-level `None` entries are genuinely ownerless and block on the
reference itself). `TempoSegment` carries **two** anchor sites, `start` and
optional `end` (`tempo.rs:109`), both guarded independently. Five of the
seven surfaces are real `WriteChain`s and restoration-aware in both
directions (a restoration reinstating a reference blocks; one removing a
reference does not); `repeat_values` and `measure_values` are immutable
value maps — nothing ever rewrites a live repeat's or measure's anchors in
place, so restoration-awareness is N/A for exactly those two.
**`RepeatStructure` needed its own ledger, not the cross-cutting chain**
(pin 10.4). `CrossCuttingValue` is `Tie | Slur | Beam | Spanner`
`RepeatStructure` is not a variant, so `cross_cutting_modify_chain`
structurally cannot hold one. `repeat_values: BTreeMap<RepeatStructureId,
RepeatStructure>` carries it instead, at the same seven sites, **with no
delete site** — retention matches the ratified project discipline (no value
map is ever pruned; every reader consults `self.objects` first) and is what
makes the tombstoned-referencer test row reachable at all: a tombstoned
repeat's value stays in the map and still names the measure, so the guard
can be shown *not* blocking on a corpse only if the value is still there to
check.
**Also closes a hole that predates G3b.** `undo_strand_block`'s existing
`TimeSignature` arm consulted only `meter_change_chain`; a minted
`TimeSignature` still named by a live `Measure.time_signature` was not
checked and could be stranded by an undo. `measure_values` is an immutable
value map, so this extension is ungated exactly like the pre-existing
StaffGroup/AnalysisLayer/Instrument guards.
**`SetMetricGrid`/`SetTimeSignature` now preserve invariant 20**, both
clauses, on both forward paths and both undo restoration policies, with
every check running before any mint (`set_time_signature` previously minted
its carried `TimeSignature` before writing the meter change; a prospective
refusal appended afterward would have leaked a minted signature from a
non-transactional operation with no undo to reclaim it — restructured so
every check precedes the mint, and tested that a refusal leaves no residue
in `objects`, any carried-value map, or the graph). Restoration safety is
evaluated in **aggregate**, not per-restoration: a single undo can collect a
whole-grid restoration and one or more meter-change restorations together,
and individually-unsafe restorations can be jointly safe (or the reverse).
`StrictInverse`/`Cascade` evaluate the prospective post-undo state with the
**whole** restoration set applied and conflict if unsafe; `BestEffort`
applies the maximal safe subset under a documented deterministic
canonical-order greedy (consider restorations in canonical order, admit
each one that keeps the accumulated state clean, skip the rest — "maximal"
under this rule, not the set-theoretic maximum, which is not uniquely
defined).
**The cross-crate anchor-relation agreement test, and the hook that makes it
possible.** `epiphany-ops` depends on `epiphany-core`, never the reverse, so
invariant 20 (`epiphany-core`) cannot reuse this crate's private
`Reducer::anchors_comparable_order` / `Reducer::anchor_musical_delta`, and
implements the identical pin 6/6b relation a second time over the graph
alone. Two implementations of one normative relation is a divergence
hazard, so this crate exports a narrow oracle hook,
`measure_anchor_relation_for_agreement_test` (`reduce.rs:703`, re-exported
from `lib.rs`), whose **only** sanctioned use is
`epiphany-testkit`'s `g3b_measure_anchor_agreement.rs`, which drives a table
of anchor pairs through both this hook and core's
`measure_anchor_relation` and asserts they agree — comparable-or-not,
ordering when comparable, and musical delta. It is not a general-purpose
anchor API. This crate's `DECISIONS.md` entry and core's document the same
pair of hooks from each side.
**No `epiphany-bundle` change of any kind**, mirroring G3a: `Measure`'s one
wire layout never gained a `schema_major()` arm, so the accept-set never
moved.

View File

@ -28,21 +28,27 @@
//! — it is recorded retroactively *inside* the G2a row ("genesis tranche G1
//! --- landed at `3b09595` with no matching entry here"). Demanding a
//! principal marker for G1 would make this guard born red against a
//! document that pin B2 leaves correct, so only G2a, G-minor, G2b, and G3a
//! get principal-marker assertions here. Do not "fix" this by adding a
//! fifth marker; that rediscovers the contradiction pin B6 already resolved.
//! document that pin B2 leaves correct, so only G2a, G-minor, G2b, G3a, and
//! G3b get principal-marker assertions here. Do not "fix" this by adding a
//! sixth marker; that rediscovers the contradiction pin B6 already resolved.
//!
//! **G3b** (`spec/CONTRACT_GENESIS_G3B_MEASURE.md` pin 14) extends this same
//! guard to its own Revision History row, closing the genesis ladder: the
//! contract's own M71 deletes the G3b row, observes this guard fail, and
//! restores it by hand.
use std::fs;
use std::path::Path;
/// The rung name immediately preceded by the row's `---` separator, in the
/// exact spelling each row uses (G-minor is never prefixed "Genesis
/// tranche" in the document; the other three are).
const PRINCIPAL_MARKERS: [(&str, &str); 4] = [
/// tranche" in the document; the other four are).
const PRINCIPAL_MARKERS: [(&str, &str); 5] = [
("G2a", "--- Genesis tranche G2a"),
("G-minor", "--- G-minor"),
("G2b", "--- Genesis tranche G2b"),
("G3a", "--- Genesis tranche G3a"),
("G3b", "--- Genesis tranche G3b"),
];
fn binary_format_source() -> String {
@ -126,9 +132,9 @@ fn revision_history_has_exactly_one_principal_marker_per_rung() {
}
}
/// The four marked rungs appear in ladder order: G1 -> G2a -> G-minor ->
/// G2b -> G3a. G1 has no marker of its own (see the module comment), so
/// this checks the remaining four.
/// The five marked rungs appear in ladder order: G1 -> G2a -> G-minor ->
/// G2b -> G3a -> G3b. G1 has no marker of its own (see the module comment),
/// so this checks the remaining five.
#[test]
fn revision_history_rungs_are_strictly_ordered() {
let source = binary_format_source();
@ -156,7 +162,7 @@ fn revision_history_rungs_are_strictly_ordered() {
earlier_offset < later_offset,
"expected {earlier_rung} (offset {earlier_offset}) to precede {later_rung} \
(offset {later_offset}) in the Revision History chapter; ladder order is \
G2a < G-minor < G2b < G3a"
G2a < G-minor < G2b < G3a < G3b"
);
}
}

View File

@ -1,6 +1,6 @@
# Contract: genesis tranche G3b — `CreateMeasure`, invariant 20, and the close of the genesis ladder
**Status:** DRAFT — awaiting ratification.
**Status:** RATIFIED.
**Governs:** the final rung of `spec/PLAN_GENESIS_OPS.md`. One kind, one
invariant, three precondition reasons, one epoch.
**Predecessors:** G3a `6c5e69f`; the G3a undo repair `4b0abaf`; P13-S17

View File

@ -95,3 +95,6 @@ visible — a value with a wire form and no canonical carrier to reach it.)
| P13-S15 | **The `OperationKind` wire golden lock stops at discriminant 29, so kinds 3033 have no byte-level lock at all.** `operation_kind_wire_discriminants_are_golden` (`crates/epiphany-ops/src/payload.rs:1959`) declares `let table: [(OperationKind, u8); 30]`, covering 0..=29 and asserting for each that `kind.discriminant()` has not moved *and* that the discriminant byte truly leads the canonical encoding. **`TransposeInterval` (30, Push 4a), `CreateInstrument` (31, G1), `SetCanvasLayoutDefaults` (32) and `SetSpellingPrecedence` (33, G2a) are absent from that table**, so their wire discriminants are unlocked: a hand edit to `OperationKind::discriminant()`'s match — itself the site Push 4a got wrong — would move any of the four silently, and the encoding-leads-with-the-byte assertion never runs for them. The macro-guarded `OperationKindTag` half is unaffected (`operation_kind_tag_vocabulary!` is compile-enforced); this is the hand-written half. **The shape is the failure this project keeps rediscovering:** a hand-maintained parallel table that quietly stopped being extended, exactly like the four literal sites found at Push 4a and the six found during G2a — and the golden lock is the one guard specifically meant to catch that class, so its own staleness is the worst place for it | this file (found 2026-07-28 by the G-minor implementation while building s1's coverage; independently verified against the working tree at `ff9bd0f` — the array length literal is `30` and no arm for 3033 appears in the table) | **open.** Deliberately no code change yet. **Not a live incorrectness:** every one of the four discriminants is currently correct, and `binary_format.tex:1443-1457`/`:1526-1527` carry them normatively — the gap is the *absence of a guard*, not a wrong byte. Fixing it is mechanical (extend the table to `; 34` and add the four rows), and the mutation that proves the fix is to change one of the four discriminants and watch the extended lock fail where it previously stayed green. **Worth doing as its own rung rather than as a rider**, because a golden-lock extension should land with its mutation evidence and nothing else in the diff. Note the sibling `phase3_tag_discriminants_are_golden` should be checked for the same drift at the same time |
| P13-S16 | **`StaffGroup.members` and `Staff.group` may disagree in both directions, and nothing detects it.** The two fields encode the same relation twice. `Staff.group` (`core_spec.tex:5578`, `graph.rs:819`) names the group a staff belongs to; `StaffGroup.members` (`core_spec.tex:4231`, `graph.rs:1614`) lists the staves in a group. **The specification declares both and states neither an authority nor an agreement requirement.** Graph invariant 10 checks that each side *resolves* — a staff's group is declared (`invariants.rs:1126`), a group's members are declared (`:1135`) — and never that the two agree, so **both stale forms pass every check in the tree**: a *missing* member (`s.group == Some(g)` while `g.members` omits `s`) and a *spurious* member (`g.members` contains `s` while `s.group` is `None` or names a different group). Genesis G3a ruled `Staff.group` the sole authority and `StaffGroup.members` a non-authoritative denormalized projection (`spec/CONTRACT_GENESIS_G3A_ENTITIES.md` §1.1, disposition B, ratified 2026-07-29) — a normative semantic ruling that makes the disagreement *defined* rather than merely undetected, but does not make it *impossible*: G3a stores the projection without maintaining it | `spec/CONTRACT_GENESIS_G3A_ENTITIES.md` §1.1 (found 2026-07-29 during G3a contract review; the authorship cycle is what surfaced it — with mints only, no authoring order produces an agreeing pair, since `CreateStaff` requires its group live at `reduce.rs:4117` and `CreateStaffGroup` requires its members live) | **open.** Deliberately no code change in G3a. The fix is disposition **A**: maintain the projection under reduction — `create_staff` with `group: Some(g)` appends to `g.members` — plus a candidate **graph invariant 21** enforcing agreement in both directions, and a decision on whether a re-carried `CreateStaffGroup` compares against its *carried* `members` (empty) or the current derived state. **Consumers must read `Staff.group`, never `StaffGroup.members`, for membership** until that lands. Sequence it after G3b so an invariant append is not competing with G3b's invariant 20 |
| P13-S17 | **Binary Format revision history omitted genesis tranche G2b entirely, including the accept-set raise it performed.** `spec/binary_format.tex`'s Revision History chapter ran G2a 0.12.0 straight to G-minor 0.13.0 to G3a 0.14.0, with no row anywhere recording G2b (`spec/CONTRACT_GENESIS_G2B_TUNING.md`, kind/tag 34, `SetTuningContext`) — even though `OperationEnvelopeBlock`'s accept-set raise 2→3, the first accept-set move since genesis tranche G2a explicitly recorded staying at 2, reached the normative tables (the per-chunk-role accept-set prose at `binary_format.tex:2353``:2363`, which names the `OperationEnvelopeBlock` role's maximum as 3 "as of genesis tranche G2b", and the `OperationKind` minor-additive bullet at `:2395``:2418`, which records "genesis tranche G2b took 34 (`SetTuningContext`)") and never the history. G2b's own contract touch table row 27 required "version, Revision History row" among the four-document ritual, and the rung was signed off without it — the gate did not catch a documentation MUST because nothing tests the revision history | `spec/CONTRACT_GENESIS_G3A_UNDO_REPAIR.md` §0 (found 2026-07-29 during the G3a undo-repair contract review, verified against the working tree: `binary_format.tex:3599`, `:3628`, `:3643` before this rung's edit) | **RESOLVED in this same commit.** The chronology is restored: G2b lands as its own 0.14.0 row between G-minor and G3a, and G3a renumbers to 0.15.0 (`spec/CONTRACT_GENESIS_G3A_UNDO_REPAIR.md` Packet B, pins B2B3). A new scoped guard in `epiphany-testkit` makes the omission recurrence-detectable — a distinct principal marker per standalone-row rung (G2a, G-minor, G2b, G3a — G1 is deliberately unguarded, having no standalone row of its own), strictly ordered, with G2b's content anchored within its own row segment — so the entry is filed and closed by the same packet that finds it, unlike **P13-S15** and **P13-S16**, which stay open because their fixes are sequenced to later rungs |
| P13-S18 | **Graph invariant 20's agreement and boundary-consistency checks are partial, and nothing yet closes the residue.** Genesis tranche G3b (`spec/CONTRACT_GENESIS_G3B_MEASURE.md` pin 7) makes invariant 20 ABSTAIN — emit no violation — wherever pin 6's comparable relation cannot order two measure starts or pin 6b's musical delta cannot be computed between them: cross-clock offsets (`Musical` vs `WallClock`), differing boundary selectors (`pos`/`edge`), and any `Measure` *end* anchor (unresolvable without the deferred tempo/measure-length machinery, `invariants.rs:400`ff). This is deliberate — base-ingested data may predate the rule, so flagging every incomputable case would make the invariant useless on real scores — but it is a real gap: a score whose measure/meter disagreement happens to fall in one of these incomputable shapes passes invariant 20 silently | `spec/CONTRACT_GENESIS_G3B_MEASURE.md` pin 7 (filed 2026-07-30 during the G3b contract's own drafting, ratified as deliberate abstention rather than a defect) | **open, deliberately.** Closing it needs the deferred P11-C5 resolved-position machinery: once a `Measure` end, or an event position on a wall-clock-placed region, can be placed on a common timeline, the comparable relation and the musical delta both widen and the abstention residue shrinks. No code change is owed by G3b itself |
| P13-S19 | **`CreateMeasure`'s ordering and boundary-distance preconditions are vacuous for an instance's first measure, so pickup/anacrusis measures are unauthored by this rung.** Genesis tranche G3b (`spec/CONTRACT_GENESIS_G3B_MEASURE.md` pin 9) makes clauses 1 and 3 of `create_measure`'s append-only discipline vacuous when there is no predecessor measure, and invariant 20's boundary clause is symmetrically exempt for a first measure (core specification, invariant 20's doc comment) — a partial first measure MUST NOT be refused or flagged by either. This is a scoped deferral, not an oversight: a pickup measure's *own* internal consistency (its declared duration against its content) is a decomposition-sum question, not a measure-to-measure boundary question, and modelling it correctly needs a notion of "partial measure" this rung does not introduce | `spec/CONTRACT_GENESIS_G3B_MEASURE.md` pin 9 (filed 2026-07-30 during the G3b contract's own drafting) | **open, deliberately.** A pickup/anacrusis authoring story — whatever shape it takes — is later schema-fill work, sequenced after the genesis ladder closes |
| P13-S20 | **`decode.rs`'s `precondition_reason` decoder stopped at discriminant 13, so `PreconditionFailureReason` 14 (`AcousticRealizationPinned`) and 15 (`TranspositionOutOfRange`), both live since Push 4a, encoded but could not decode.** A materialized effect carrying either reason failed canonical round-trip in production code, undetected because `epiphany-testkit`'s `precondition_failure_reason` generator (`generators.rs:417`, then `rng.below(14)`) never drew past 13 despite its doc comment claiming "every core and registered variant" | `spec/CONTRACT_GENESIS_G3B_MEASURE.md` §0 / touch-table row 12a (found 2026-07-30 during the G3b contract's own drafting, verified against the working tree: `decode.rs:205`ff ended at 13, `generators.rs:417` drew `below(14)`) | **RESOLVED in this rung** (genesis tranche G3b packet 1, `e64a4b7`). `decode.rs`'s `precondition_reason` now decodes discriminants 14 through 18 (the pre-existing 14/15 hole plus G3b's own 1618), and `generators.rs`'s `precondition_failure_reason` now draws `below(19)` with arms for all of them — the generator's doc comment claim is true again, and the decode hole this rung found already latent in the tree, not introduced by it, is closed alongside G3b's own additions |

View File

@ -6,8 +6,9 @@ reverses Pass-12 K8 and makes every mutable field of `Score` operation-authored.
This plan is the execution scope: what the tranche touches, in what order, and
which questions must be answered before a dispatch contract can be written.
**Status:** the ladder **G1 → G2a → G-minor → G2b → G3a** is **complete**;
only **G3b** remains (ratified 2026-07-29, §4).
**Status:** the ladder **G1 → G2a → G-minor → G2b → G3a → G3b** is
**CLOSED** (`spec/CONTRACT_GENESIS_G3B_MEASURE.md`, ratified). The genesis
operation tranche is complete.
* **G1 landed** (3b09595) — `CreateInstrument`, kind/tag 31.
* **G2a landed** (7df5ca1 + 55eff00) — `SetCanvasLayoutDefaults` and
@ -32,9 +33,15 @@ only **G3b** remains (ratified 2026-07-29, §4).
this rung's own sign-off missed — `ObjectState::Tombstoned` reachable but
under-covered for these four kinds plus `Instrument` — rides after it as
`spec/CONTRACT_GENESIS_G3A_UNDO_REPAIR.md` Packet A.
* **G3b**`CreateMeasure` alone, kind/tag **39**, epoch **12**, carrying
graph invariant **20** and a new `PreconditionFailureReason` at discriminant
**16**. Scoped, not contracted.
* **G3b landed** (packet 1 `e64a4b7` + repairs `b92023a`/`b622498`, packet 2
`314cd7a`, packet 3a `acd7a76`, packet 3b — this rung) — `CreateMeasure`
alone, kind/tag **39**, epoch **12**, carrying graph invariant **20** and
three new `PreconditionFailureReason` discriminants **1618**
(`MeasureMeterMismatch`, `MeasureOutOfOrder`, `MeasureOrderUnverifiable`).
No `epiphany-bundle` change of any kind; op-block accept-set stays at 3
where G2b left it. Closes the genesis ladder. Filed **P13-S18** (invariant
20's abstention residue) and **P13-S19** (the pickup/anacrusis deferral),
both open by design.
**Deletes are deferred out of G3 entirely** (§6.1, ratified 2026-07-29). Both
packets are mints only.

View File

@ -179,6 +179,7 @@ numbering spaces are unrelated and must not be cross-read.
| 9 | Genesis G2a | `OperationKind`/`OperationKindTag` 3233 |
| 10 | Genesis G2b | `OperationKind`/`OperationKindTag` 34 (`SetTuningContext`) |
| 11 | Genesis G3a | `OperationKind`/`OperationKindTag` 3538 (`CreateStaffGroup`, `CreatePartDefinition`, `CreateAnalysisLayer`, `CreateView`) |
| 12 | Genesis G3b | `OperationKind`/`OperationKindTag` 39 (`CreateMeasure`); `PreconditionFailureReason` 1618 (`MeasureMeterMismatch`, `MeasureOutOfOrder`, `MeasureOrderUnverifiable`) |
> **Epoch 10 ratified 2026-07-28**, with G2b as the event. This is the **first
> exercise of the ladder's own growth path**: G-minor's `introduced_minor()` is
@ -193,9 +194,19 @@ numbering spaces are unrelated and must not be cross-read.
> than one epoch per kind. The ladder stays monotonic (G3a follows G2b) and
> prefix-closed.
> **Epoch 12 ratified 2026-07-30**, with G3b as the event — the final rung of
> the genesis ladder (`spec/PLAN_GENESIS_OPS.md`), closing it. One epoch for
> the rung's one kind and its three `PreconditionFailureReason` appends
> together, the Push 4a precedent (epoch 7 bundled kind 30 with reasons 1415)
> rather than a kind epoch and a reason epoch separately. The introducing
> commit is this packet's own — the committing session owns `HEAD`, not this
> packet, so the hash is recorded once committed rather than invented here.
> The ladder stays monotonic (G3b follows G3a) and prefix-closed.
**The ladder is complete against the audit** — every post-baseline variant in
`AUDIT_GMINOR_VOCABULARIES.md` appears exactly once: all fifteen kind/tag pairs,
`OperationPayload` 3, `ReanchorReason` 6, and all six
`AUDIT_GMINOR_VOCABULARIES.md` appears exactly once: all fifteen kind/tag pairs
through G3a, plus G3b's kind/tag 39 and its three reason appends,
`OperationPayload` 3, `ReanchorReason` 6, and all nine
`PreconditionFailureReason` appends.
**And it is monotonic in real time**, which is what makes a minor prefix-closed
@ -203,8 +214,18 @@ numbering spaces are unrelated and must not be cross-read.
introducing commits rather than assumed: M2c `a207077` (2026-06-25) → Push 3
`92aaccf` (07-02) → Phase-3 `0316160` (07-02) → G-pass `e4edea6` (07-07) →
repeat pair `9b5339f` (07-07) → Push 4a `2740a6c` (07-09) → G1 `3b09595`
(07-24) → **G2a `7df5ca1`** (07-28). The two events sharing 2026-07-07 are
ordered correctly: the G-pass precedes the repeat revision.
(07-24) → **G2a `7df5ca1`** (07-28) → **G2b `13c3d2f`** (07-29) → **G3a
`6c5e69f`** (07-29) → **G3b** (this packet's own commit — the committing
session owns `HEAD`, hash recorded there rather than invented here). The two
events sharing 2026-07-07 are ordered correctly: the G-pass precedes the
repeat revision. The two events sharing 2026-07-29 are ordered **by ancestry,
not timestamp**: `13c3d2f` is an ancestor of `6c5e69f` (verified with
`git merge-base --is-ancestor 13c3d2f 6c5e69f`), so G2b precedes G3a. **Only
these three additive events — G2b, G3a, G3b — extend the chain past G2a**;
G-minor (`ff9bd0f`) built the epoch machinery itself and introduced no
additive variant, and P13-S17's restoration commit (`6170015`) repaired a
document's history rather than appending vocabulary, so neither belongs in
this chain.
> **Correction 2026-07-28.** G2a's introducing commit is **`7df5ca1`**, where
> kinds/tags 3233 enter `ops/src/payload.rs`. `55eff00` is the *subsequent

View File

@ -53,7 +53,7 @@ Spec sections: Appendix D in full, Chapter 4 §4.6 (frequency units), Chapter 7
### Agent B — `epiphany-core`
Owns the score graph: the identifier family (`EventId`, `PitchId`, `VoiceId`, ..., `TypedObjectId`), the `ReplicaId::SYSTEM_DERIVED` reserved value and counter derivation (`trunc64(BLAKE3(domain_tag || canonical_inputs))`), `IdentifiedPitch`, `PitchSpelling`, `ScalePosition`, the duration union (`EventDuration` / `ConcreteDuration`), `MusicalPosition`, `WallClockTime`, `TimeAnchor`, `AnchorOffset`, the event arena, `Voice`, `Staff` and `StaffInstance` (these are distinct types — the spec is explicit), `Region`, `BarlineAlignmentGroup`, and the 19 graph invariants enumerated in Chapter 5.
Owns the score graph: the identifier family (`EventId`, `PitchId`, `VoiceId`, ..., `TypedObjectId`), the `ReplicaId::SYSTEM_DERIVED` reserved value and counter derivation (`trunc64(BLAKE3(domain_tag || canonical_inputs))`), `IdentifiedPitch`, `PitchSpelling`, `ScalePosition`, the duration union (`EventDuration` / `ConcreteDuration`), `MusicalPosition`, `WallClockTime`, `TimeAnchor`, `AnchorOffset`, the event arena, `Voice`, `Staff` and `StaffInstance` (these are distinct types — the spec is explicit), `Region`, `BarlineAlignmentGroup`, and the 20 graph invariants enumerated in Chapter 5.
**Critical**: graph invariants are property tests in CI, not runtime assertions in release builds. The testkit provides arbitrary-instance generators. For each invariant, you must have a positive generator that produces valid graphs and a negative shrinker that minimizes invariant violations to a small witness for debugging.

Binary file not shown.

View File

@ -240,7 +240,7 @@
{\Large\scshape\color{epiphanyslate}Binary Format}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.15.0 --- The genesis operation tranche completes the root-level entity mints (G3a)}\\[4pt]
{\normalsize\color{epiphanyink}Version 0.16.0 --- The genesis operation tranche closes: \texttt{CreateMeasure} (G3b)}\\[4pt]
{\small\color{epiphanyslate}Normative for the byte layouts it defines}
\vfill
\end{titlepage}
@ -1474,6 +1474,9 @@ and the reader disagree about the field's type.
\tablenums{38} & \texttt{CreateView} &
$\mathrm{lp}$(\texttt{ViewDefinition}) &
\sectionsc{CreateView} \\
\tablenums{39} & \texttt{CreateMeasure} &
\texttt{instance} (16) \cat{} $\mathrm{lp}$(\texttt{Measure}) &
\sectionsc{CreateMeasure} \\
\bottomrule
\end{longtable}
\endgroup
@ -1546,7 +1549,7 @@ discriminants and wrong lengths are decode errors. Append-only past
\tablenums{32} & \texttt{SetCanvasLayoutDefaults} & \tablenums{33} & \texttt{SetSpellingPrecedence} \\
\tablenums{34} & \texttt{SetTuningContext} & \tablenums{35} & \texttt{CreateStaffGroup} \\
\tablenums{36} & \texttt{CreatePartDefinition} & \tablenums{37} & \texttt{CreateAnalysisLayer} \\
\tablenums{38} & \texttt{CreateView} & & \\
\tablenums{38} & \texttt{CreateView} & \tablenums{39} & \texttt{CreateMeasure} \\
\bottomrule
\end{longtable}
\endgroup
@ -1633,7 +1636,9 @@ trailing bytes are decode errors.
\tablenums{10} ContainerNotEmpty;
\tablenums{11} TempoMapMalformed;
\tablenums{12} SystemDerivedContentImmutable (Pass~12, P12-K3);
\tablenums{13} RecreateContentMismatch (Pass~12, P12-K9). \\
\tablenums{13} RecreateContentMismatch (Pass~12, P12-K9);
\tablenums{16} MeasureMeterMismatch, \tablenums{17} MeasureOutOfOrder,
\tablenums{18} MeasureOrderUnverifiable (genesis tranche G3b). \\
\texttt{RepairRecord} &
(struct) \texttt{kind} (\texttt{RepairKind}) \cat{} \texttt{target}
(\texttt{TypedObjectId}). \\
@ -2396,7 +2401,7 @@ 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{39}$
\item \texttt{OperationKind}: append at ${\geq}\,\tablenums{40}$
(Requirement~\ref{req:binfmt:kind-discriminants}; the Phase-3
tranche took \tablenums{24}--\tablenums{27} under this mechanism,
the repeat pair \tablenums{28}/\tablenums{29} in the
@ -2405,22 +2410,23 @@ The \emph{only} minor-additive mechanism in schema major~0 is
(\texttt{CreateInstrument}), genesis tranche G2a took
\tablenums{32}/\tablenums{33} (\texttt{SetCanvasLayoutDefaults},
\texttt{SetSpellingPrecedence}), genesis tranche G2b took
\tablenums{34} (\texttt{SetTuningContext}), and genesis tranche G3a took
\tablenums{34} (\texttt{SetTuningContext}), genesis tranche G3a took
\tablenums{35}--\tablenums{38} (\texttt{CreateStaffGroup},
\texttt{CreatePartDefinition}, \texttt{CreateAnalysisLayer},
\texttt{CreateView}) --- the discriminant space is
\texttt{CreateView}), and genesis tranche G3b took \tablenums{39}
(\texttt{CreateMeasure}) --- 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}; \tablenums{34}'s payload embeds a v3
layout unconditionally, so its blocks stamp major~3,
Section~\ref{sec:evolution:major3} --- the sole appended kind to do so;
\tablenums{35}--\tablenums{38} are each major~0, no versioned walk
existing for any of their four carried types);
\item \texttt{OperationKindTag}: append at ${\geq}\,\tablenums{39}$
(Requirement~\ref{req:binfmt:kind-tag}; the same fifteen variants as
\texttt{OperationKind}, at the same fifteen discriminants, appended by
the same seven tranches);
\tablenums{35}--\tablenums{39} are each major~0, no versioned walk
existing for any of their five carried types);
\item \texttt{OperationKindTag}: append at ${\geq}\,\tablenums{40}$
(Requirement~\ref{req:binfmt:kind-tag}; the same sixteen variants as
\texttt{OperationKind}, at the same sixteen discriminants, appended by
the same eight tranches);
\item \texttt{OperationPayload}: append at ${\geq}\,\tablenums{4}$
(Section~\ref{sec:ops:payload}; \texttt{ResolveEquivocation} = 3, Push~3,
is the one and only append this vocabulary has had);
@ -3679,6 +3685,27 @@ only}: implementations need not agree on an error taxonomy.
\tablenums{3} where genesis tranche G2b left it. Semantics: Operation
Catalog \sectionsc{CreateStaffGroup}, \sectionsc{CreatePartDefinition},
\sectionsc{CreateAnalysisLayer}, \sectionsc{CreateView}, 0.12.0. \\
\today & Operation wire forms & 0.16.0 --- Genesis tranche G3b
(\texttt{spec/CONTRACT\_GENESIS\_G3B\_MEASURE.md}), the final rung of the
genesis operation tranche: appends \texttt{OperationKind} wire discriminant
\tablenums{39} (\texttt{CreateMeasure}) with its payload layout, and the
matching \texttt{OperationKindTag} discriminant \tablenums{39}
(name-verbatim, following the four most recent additions rather than the
tag space's older Create$\rightarrow$Insert convention) --- a
schema-\emph{minor} evolution (epoch~12,
\texttt{spec/PLAN\_GMINOR\_SCHEMA\_MINOR.md} \S4) under this document's own
append-only rules; no existing assignment changed. Also appends three
\texttt{PreconditionFailureReason} discriminants, \tablenums{16}--\tablenums{18}
(\texttt{MeasureMeterMismatch}, \texttt{MeasureOutOfOrder},
\texttt{MeasureOrderUnverifiable}), the same epoch. \texttt{Measure} has
never been versioned as a standalone value --- its only non-scalar field,
\texttt{TimeAnchor}, has a hand-written codec with no version branching of
its own --- so \texttt{CreateMeasure} is schema major~0 unconditionally and
no accept-set moves: \texttt{OperationEnvelopeBlock} stays at
\tablenums{3} where genesis tranche G2b left it. This closes the genesis
ladder: G1 $\rightarrow$ G2a $\rightarrow$ G-minor $\rightarrow$ G2b
$\rightarrow$ G3a $\rightarrow$ G3b. Semantics: Operation Catalog
\sectionsc{CreateMeasure}, 0.13.0. \\
\bottomrule
\end{longtable}

Binary file not shown.

View File

@ -6615,6 +6615,22 @@ The score graph maintains a set of structural invariants. Implementations
\texttt{Measure}s belonging to those instances. Group members
\MUSTNOT{} reference staff instances or measures across
region boundaries.
\item (Genesis tranche G3b.) A measure's declared time signature
\texttt{Measure.time\_signature}, when it resolves, AGREES with
the effective metric grid's active signature at the measure's
start; and consecutive measure starts within a staff instance
are separated by the governing signature's
\texttt{measure\_duration()} (BOUNDARY consistency).
\texttt{time\_signature: None} exempts only the agreement
clause --- the inherited meter still governs boundary
consistency. This invariant \MUSTNOT{} duplicate the
signature-\emph{resolution} check above (invariant 10): it
compares only already-resolving signatures. It ABSTAINS ---
raises no violation --- wherever the comparison or the
distance cannot be decided (base-ingested data may predate
this rule); this is deliberate abstention, not a soundness
gap. A pickup/anacrusis first measure has no predecessor and
is never flagged.
\end{enumerate}
Implementations \MUST{} reject graph configurations that violate any
@ -6623,10 +6639,14 @@ The score graph maintains a set of structural invariants. Implementations
changes that restore them within the same operation.
\end{requirement}
This enumeration contains exactly \textbf{19} invariants. (Earlier
This enumeration contains exactly \textbf{20} invariants. (Earlier
summary material, including the QUICKSTART, referred to ``18 graph
invariants''; the authoritative count is 19, matching this
enumeration and the reference implementation.) These 19 are
invariants''; a subsequent revision of this document corrected that
to 19; genesis tranche G3b
(\texttt{spec/CONTRACT\_GENESIS\_G3B\_MEASURE.md}) then appended a
20th, measure-meter consistency, above --- the authoritative count is
20, matching this enumeration and the reference implementation.)
These 20 are
\emph{runtime} invariants: they hold over every well-formed graph and
are restored by compensating changes when an edit would break them.
@ -6991,6 +7011,12 @@ pub enum OperationKind {
CreateAnalysisLayer(CreateAnalysisLayerOp),
CreateView(CreateViewOp),
// Genesis tranche G3b: the final rung of the genesis ladder. Appends a
// Measure to a live StaffInstance (StaffInstance.measures), on the
// InsertStaffInstance parent-carrying mint pattern rather than the
// bare-value shape above -- Measure has no back-pointer to its parent.
CreateMeasure(CreateMeasureOp),
// Layout-semantic operations
SetUserSystemBreak(SetUserSystemBreakOp),
SetUserPageBreak(SetUserPageBreakOp),
@ -11966,6 +11992,9 @@ pub enum OperationKindTag {
CreatePartDefinition,
CreateAnalysisLayer,
CreateView,
// Genesis tranche G3b: the final rung, name-verbatim, as the four most
// recent prior additions are. Closes the genesis ladder.
CreateMeasure,
}
pub enum BarrierScope {

Binary file not shown.

View File

@ -231,7 +231,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.12.0 --- The genesis operation tranche completes the root-level entity mints (G3a)}\\[4pt]
{\normalsize\color{epiphanyink}Version 0.13.0 --- The genesis operation tranche closes: \texttt{CreateMeasure} and graph invariant 20 (G3b)}\\[4pt]
{\small\color{epiphanyslate}Normative for the operation kinds it defines}
\vfill
\end{titlepage}
@ -476,6 +476,47 @@ No accept-set change: all four carried types have exactly one wire layout
versioned walk exists for any of the four), so none gains a
\texttt{schema\_major()} arm beyond the existing catch-all.
\medskip
\noindent\textbf{Version 0.13.0 (genesis tranche G3b,
\texttt{spec/CONTRACT\_GENESIS\_G3B\_MEASURE.md}), the final rung of the
genesis operation tranche (\texttt{spec/PLAN\_GENESIS\_OPS.md}).} Adds one
operation kind, \texttt{CreateMeasure} (Section~\ref{sec:k0:create-measure}):
\texttt{StaffInstance.measures} joins the operation-authored surfaces, on the
\texttt{CreateStaffInstance} parent-carrying mint pattern rather than the
bare-value shape the four G3a families above use --- \texttt{Measure} is a
nested container child, not a \texttt{Score}-root vector, and carries no
back-pointer to its parent. Schema major~0 unconditionally, epoch~12.
Also adds graph invariant 20 (core specification \sectionsc{Graph
Invariants}) --- measure/meter agreement and boundary consistency --- and
three \texttt{PreconditionFailureReason} appends, all epoch~12:
\texttt{MeasureMeterMismatch} (16), \texttt{MeasureOutOfOrder} (17), and
\texttt{MeasureOrderUnverifiable} (18). \texttt{CreateMeasure} itself refuses
whenever the comparison or delta invariant 20 needs is not computable
(\texttt{MeasureOrderUnverifiable}); invariant 20 instead \emph{abstains} ---
emits no violation --- over the same incomputable cases, because base-ingested
data may predate the rule. This is a deliberate split, not an inconsistency:
fail closed at the operation, abstain at the invariant.
\textbf{Two existing operations now preserve invariant 20.} \texttt{SetMetricGrid}
(Section~\ref{sec:k0:meter-tempo}) and \texttt{SetTimeSignature}
(Section~\ref{sec:k0:meter-tempo}) both gain prospective preconditions,
covering agreement and boundary consistency, on their forward paths and on
both undo restoration policies (\texttt{StrictInverse}/\texttt{Cascade}
conflict on an unsafe restoration set; \texttt{BestEffort} applies the
maximal safe subset under a documented canonical-order greedy). Every check
runs before any mint, so a refused write leaves no residue in \texttt{objects},
any carried-value map, or the graph.
No accept-set change: \texttt{Measure} has never been versioned as a
standalone value --- its only non-scalar field, \texttt{TimeAnchor}, has a
hand-written codec with no version branching of its own --- so
\texttt{OperationEnvelopeBlock} stays at \texttt{3} where genesis tranche
G2b left it. This closes the genesis ladder
(\texttt{spec/PLAN\_GENESIS\_OPS.md} \S4): G1 $\rightarrow$ G2a $\rightarrow$
G-minor $\rightarrow$ G2b $\rightarrow$ G3a $\rightarrow$ G3b.
% ===========================================================================
\chapter{The Catalog Framework}
\label{ch:framework}
@ -1585,6 +1626,102 @@ mutated.
\textbf{Re-anchoring.} Not applicable (a view mint references no tombstonable
anchor; there is no \texttt{DeleteView} in this catalogue revision).
\section{CreateMeasure}
\label{sec:k0:create-measure}
Ratified with genesis tranche G3b (\texttt{spec/CONTRACT\_GENESIS\_G3B\_MEASURE.md}),
the final rung of the genesis operation tranche
(\texttt{spec/PLAN\_GENESIS\_OPS.md}): a staff instance's measure sequence
(\texttt{StaffInstance.measures}) joins the operation-authored surfaces.
Unlike Section~\ref{sec:k0:create-staff-group} through
Section~\ref{sec:k0:create-view}, \texttt{Measure} is a \textbf{nested
container child} of \texttt{StaffInstance}, not a \texttt{Score}-root vector
--- so \texttt{CreateMeasure} rides \texttt{CreateStaffInstance}'s
parent-carrying shape (Section~\ref{sec:k0:structural-containers}), not the
four G3a families' bare-value shape.
\textbf{Payload schema.} \texttt{CreateMeasureOp \{ instance: StaffInstanceId,
measure: Measure \}} --- the owning staff instance's identifier beside the
full \texttt{Measure} value. \texttt{Measure} carries no back-pointer to its
parent, so the parent must ride along, exactly like
\texttt{CreateStaffInstanceOp::region}.
\textbf{Canonical encoding.} \texttt{instance} (canonical bytes), then the
length-framed canonical bytes of \texttt{measure}. \texttt{Measure} has never
been versioned --- its only non-scalar field, \texttt{TimeAnchor}, has a
hand-written codec with no version branching of its own --- so this operation
is schema major~0 unconditionally.
\textbf{Reduction rule.} Set-union creation, on the same discipline as
\texttt{CreateStaffInstance}: a create mints the measure live, appended to the
\textbf{end} of \texttt{instance.measures} (\texttt{Measure} order is
append-only), if its id is fresh; a repeat create carrying a byte-identical
value under the \textbf{same} owning instance reduces idempotently
(\texttt{NoOpReason::AlreadyApplied}); a create whose id is already live with a
differing value, \textbf{or the same measure value under a different owning
instance}, is a precondition no-op with \texttt{RecreateContentMismatch} ---
the owning instance is part of identity here, since a \texttt{Measure} carries
no parent reference of its own; a create naming a tombstoned id is a
precondition no-op with \texttt{TargetTombstoned}.
Referential preconditions: the parent \texttt{StaffInstance} must be live
(ungated --- checked graph-aware and base-free alike, mirroring
\texttt{CreateStaffInstance}'s own region check: a mint into a non-existent
parent has nowhere to go even base-free); graph-aware only, a resolving
\texttt{measure.time\_signature} must name a live \texttt{TimeSignature}, and
every non-\texttt{WallClock} referent of \texttt{measure.start} (an
\texttt{Event}, another \texttt{Measure}, or a \texttt{Region}) must resolve.
\textbf{Ordering and boundary-distance preconditions (core specification
\sectionsc{Graph Invariants}, invariant 20).} Graph-aware, against the current
last live measure of the same instance (vacuous for an instance's first
measure --- a pickup/anacrusis is never refused or flagged on this account):
the carried \texttt{start} must be \textbf{comparable} to the predecessor's
start and \textbf{strictly after} it (else \texttt{MeasureOutOfOrder}), and,
when a governing time signature is determinable, the \textbf{musical delta}
between the two starts must equal that signature's
\texttt{measure\_duration()} (else \texttt{MeasureMeterMismatch}). Either
comparison being \textbf{unverifiable} --- the two starts are not comparable,
or the delta is not computable --- refuses with
\texttt{MeasureOrderUnverifiable} rather than silently permitting an
unverifiable mint. A resolving \texttt{time\_signature} that \textbf{disagrees}
with the effective grid's active signature at the measure's start also refuses
with \texttt{MeasureMeterMismatch}; \texttt{time\_signature: None} skips only
this agreement check, not the boundary-distance check above.
\textbf{The effective grid} is \texttt{StaffInstance.local\_metric\_grid} when
present, else the enclosing region's default metric grid, reconstructed from
the same operational write chains (\texttt{SetMetricGrid}'s whole-grid writes
layered under \texttt{SetTimeSignature}'s per-key changes, in canonical write
order, folding in any prospective undo restoration) in both graph-aware and
base-free reduction, so the two modes never disagree.
\textbf{Conflict cases.} None at reduction time (set-union; the differing-value
re-create is a precondition gate, not a conflict).
\textbf{Undo semantics.} Undo of a create tombstones the minted measure and
removes it from the owning instance's \texttt{measures} vector
(Section~\ref{sec:k0:undo}). \texttt{StrictInverse} conflicts if a live
reference would be stranded, across \textbf{seven} inbound surface classes: a
spanner endpoint, a repeat structure's anchor sites, another measure's
\texttt{start}, a meter change, a system-break advisory, a page-break
advisory, or a tempo segment's \texttt{start} or optional \texttt{end}. Six of
the seven surfaces are owned (the referencing object must be live and not
itself part of the transaction being undone); a score-level
(\texttt{region: None}) tempo segment has no owning object and blocks on the
reference itself. Five of the seven are restoration-aware --- a prospective
undo restoration that would \emph{reinstate} a reference to the measure still
blocks, and one that would \emph{remove} a reference does not; the remaining
two (another measure's \texttt{start}, and a repeat structure's anchors) are
immutable value maps with no modify operation, so restoration-awareness does
not apply to them. Also closes a pre-existing hole: a minted
\texttt{TimeSignature} still named by a live \texttt{Measure.time\_signature}
now blocks its own undo, exactly as a live meter-change reference already did.
\textbf{Re-anchoring.} Not applicable (a measure mint references no
tombstonable anchor of its own; there is no \texttt{DeleteMeasure} in this
catalogue revision).
\section{Repeat Structures}
\label{sec:k0:repeat-structures}

Binary file not shown.

View File

@ -1404,6 +1404,18 @@ absorb it, exactly as the binary decoder does.
version while extending the grammar would leave two mutually incompatible
grammars both claiming \texttt{(0 11 0)}. Cached projections at
\texttt{(0 11 0)} do not migrate; a stale \texttt{TextProjection} chunk is
regenerated, not converted. \\
\today & Chapter 5 & 0.13.0 --- The genesis ladder closes:
\texttt{create-measure} reaches the grammar (genesis tranche G3b,
\texttt{spec/CONTRACT\_GENESIS\_G3B\_MEASURE.md}). The \texttt{kind}
production gains \texttt{"(create-measure " value ")"}
(\texttt{req:textproj:operation-vocabulary}), the fifth appended kind
event since the header was gated to a single version at 0.7.0.
Same forcing reason as every prior grammar-extending bump: holding the
version while extending the grammar would leave two mutually incompatible
grammars both claiming \texttt{(0 12 0)}. Cached projections at
\texttt{(0 12 0)} do not migrate; a stale \texttt{TextProjection} chunk is
regenerated, not converted. \\
\bottomrule
\end{longtable}