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 frome64a4b7until 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 -- G2b13c3d2f, G3a6c5e69f, 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:
parent
acd7a7690f
commit
d58eee8063
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 1–3a: `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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 30–33 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 30–33 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 B2–B3). 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 16–18), 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 |
|
||||
|
|
|
|||
|
|
@ -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 **16–18**
|
||||
(`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.
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ numbering spaces are unrelated and must not be cross-read.
|
|||
| 9 | Genesis G2a | `OperationKind`/`OperationKindTag` 32–33 |
|
||||
| 10 | Genesis G2b | `OperationKind`/`OperationKindTag` 34 (`SetTuningContext`) |
|
||||
| 11 | Genesis G3a | `OperationKind`/`OperationKindTag` 35–38 (`CreateStaffGroup`, `CreatePartDefinition`, `CreateAnalysisLayer`, `CreateView`) |
|
||||
| 12 | Genesis G3b | `OperationKind`/`OperationKindTag` 39 (`CreateMeasure`); `PreconditionFailureReason` 16–18 (`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 14–15)
|
||||
> 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 32–33 enter `ops/src/payload.rs`. `55eff00` is the *subsequent
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in New Issue