Audit the G-minor reachable vocabularies: a ledger, a matrix, one stale sentence

Read-only packet. Walks each of the nine ChunkKind roles' payload encoders
transitively and records every discriminant vocabulary actually emitted,
with every variant classified baseline / post-baseline native / escape.

Two vocabularies outside the OperationKind/Tag/Payload trio have real native
appends: ReanchorReason (SameCanvasNearer = 6, Pass-12 G-pass) and
PreconditionFailureReason (10-15, four separate tranches). Fourteen further
escape-carrying enums checked and clean, recorded as evidence.

Two findings the plan's section 3 table did not name. The canonical base
reaches RepairKind, ReanchorReason, and SpellingNominal, and ReanchorReason
is one of only two vocabularies with an append. And the manifest reaches
OperationKindTag with no operation envelope in it at all, through
ExtensionDeclaration::edit_barriers -> EditBarrier::prohibited_operation_kinds
- the disposition-(c) crossing, realized in a role the plan never mentions.

binary_format.tex:2373 is recorded as stale narrative: it says OperationKind
and OperationKindTag "append at >= 30" and its history names only 24-29,
while 30-33 are taken. The normative tables at :1443 and :1526 are current
and authoritative; the .tex correction is deferred to the G-minor
implementation, which must touch that paragraph regardless.

CompressionAlgorithm is ruled out of scope for schema-minor epochs: it is
ChunkRef transport metadata, not a discriminant emitted by the payload
SchemaVersion governs, and it is deliberately excluded from chunk identity.
Recorded so it is not reopened.

The epoch space stays global, but baseline classification and introduction
events are per variant per vocabulary. The tentative ladder is incomplete -
Push 3, the Pass-12 G-pass, and M2c are unplaced - and must not be ratified
as written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
This commit is contained in:
Levi Neuwirth 2026-07-28 18:27:35 -04:00
parent 9dc48ecb70
commit c63258d32a
1 changed files with 573 additions and 0 deletions

View File

@ -0,0 +1,573 @@
# Audit: G-minor reachable vocabularies — a ledger and a reachability matrix
Governed by `spec/CONTRACT_GMINOR_VOCABULARY_AUDIT.md`. Read-only packet;
produces this file only. Method: walked each `ChunkKind` role's payload
encoder transitively (`encode_canonical` / `Codec::enc` / `canonical_bytes`),
confirming every discriminant-writing site actually reached from that role,
rather than trusting the type graph. `grep -rn "push_tag\|fn discriminant"`
over `crates/epiphany-core/src` and `crates/epiphany-ops/src` was used as the
starting net (and a comprehensive `grep` for `Additive|appended|append at\b`
comments across `epiphany-core`, `epiphany-ops`, `epiphany-layout-ir`,
`epiphany-bundle` was used as a second, independent net for "did this
vocabulary ever get a documented native append" — see "How completeness was
checked" at the end).
Repo state at audit time: `git status --short` showed only the pre-existing
editor-track dirt (`M Cargo.toml`, untracked `spikes/editor-toolkit/**`); see
the Gate section at the end for the before/after capture.
---
## Part 1 — the vocabulary ledger
For each vocabulary: full variant list, discriminant, classification. Unless
otherwise noted, discriminants are one byte (`u8`) and "escape" means a
`Registered`-shaped variant carrying a registry id (native extension point).
`TypedObjectId` is the one 16-bit (`u16`) exception, noted inline.
### 1.1 `OperationKind` (`ops/src/payload.rs:116`, `discriminant()` at :275)
**Baseline** (golden-locked 0..=23 — the *value-typed* v1 catalog's frozen
range; the identifier-only v0 catalog, `ops/src/v0.rs::V0OperationKind`, only
ever had discriminants 0..=7 and is entirely superseded by migration, not a
second baseline):
| # | Variant | | # | Variant |
|---|---|---|---|---|
| 0 | InsertEvent | | 12 | ModifyIdentifiedPitch |
| 1 | DeleteEvent | | 13 | DeleteCrossCutting |
| 2 | RespellPitch | | 14 | ModifyCrossCutting |
| 3 | CreateCrossCutting | | 15 | CreateRegion |
| 4 | ChangeRegionTimeModel | | 16 | DeleteRegion |
| 5 | SetUserSystemBreak | | 17 | CreateStaffInstance |
| 6 | DeclareTransaction | | 18 | DeleteStaffInstance |
| 7 | Registered (escape) | | 19 | CreateVoice |
| 8 | ModifyEvent | | 20 | DeleteVoice |
| 9 | Transpose | | 21 | SetMetadata |
| 10 | InsertIdentifiedPitch | | 22 | SetMetricGrid |
| 11 | DeleteIdentifiedPitch | | 23 | SetUserPageBreak |
**Post-baseline native** (10 variants, exactly matching P13-S14's list):
| Discriminant | Variant | Introduction event |
|---|---|---|
| 24 | CreateStaff | Phase-3 first tranche |
| 25 | SetTimeSignature | Phase-3 first tranche |
| 26 | SetTempoSegment | Phase-3 first tranche |
| 27 | SetStaffLayout | Phase-3 first tranche |
| 28 | CreateRepeatStructure | schema-major-2 repeat-authoring revision |
| 29 | DeleteRepeatStructure | schema-major-2 repeat-authoring revision |
| 30 | TransposeInterval | Push 4a |
| 31 | CreateInstrument | genesis tranche G1 |
| 32 | SetCanvasLayoutDefaults | genesis tranche G2a |
| 33 | SetSpellingPrecedence | genesis tranche G2a |
**Escape:** 7 (Registered). Out of scope per contract.
### 1.2 `OperationKindTag` (`ops/src/payload.rs:421`, macro-generated at :487)
Independent discriminant space from `OperationKind` (trap 2 confirmed:
`RespellPitch` is kind 2 / tag 3). Source of truth is the
`operation_kind_tag_vocabulary!` macro (exhaustive, no wildcard arm) — the
hand-written `OperationKind::discriminant()` match was **not** consulted for
this vocabulary; the macro table at :532 was.
**Baseline** (0..=15, 17..=23 — `Registered` sits at 16, splitting the
range):
| Disc. | Variant | | Disc. | Variant |
|---|---|---|---|---|
| 0 | InsertEvent | | 12 | DeleteStaffInstance |
| 1 | DeleteEvent | | 13 | SetUserSystemBreak |
| 2 | ModifyEvent | | 14 | SetUserPageBreak |
| 3 | RespellPitch | | 15 | DeclareTransaction |
| 4 | Transpose | | 16 | **Registered (escape)** |
| 5 | CreateCrossCutting | | 17 | InsertIdentifiedPitch |
| 6 | DeleteCrossCutting | | 18 | DeleteIdentifiedPitch |
| 7 | ModifyCrossCutting | | 19 | ModifyIdentifiedPitch |
| 8 | ChangeRegionTimeModel | | 20 | CreateVoice |
| 9 | InsertRegion | | 21 | DeleteVoice |
| 10 | DeleteRegion | | 22 | SetMetadata |
| 11 | InsertStaffInstance | | 23 | SetMetricGrid |
**Post-baseline native** (mirrors `OperationKind` 1:1, same 10 events):
| Disc. | Variant | Introduction event |
|---|---|---|
| 24 | InsertStaff | Phase-3 first tranche |
| 25 | SetTimeSignature | Phase-3 first tranche |
| 26 | SetTempoSegment | Phase-3 first tranche |
| 27 | SetStaffLayout | Phase-3 first tranche |
| 28 | CreateRepeatStructure | schema-major-2 repeat-authoring revision |
| 29 | DeleteRepeatStructure | schema-major-2 repeat-authoring revision |
| 30 | TransposeInterval | Push 4a |
| 31 | CreateInstrument | genesis tranche G1 |
| 32 | SetCanvasLayoutDefaults | genesis tranche G2a |
| 33 | SetSpellingPrecedence | genesis tranche G2a |
**Escape:** 16 (Registered). Out of scope.
### 1.3 `OperationPayload` (`ops/src/payload.rs:59`, `discriminant()` at :75)
**Baseline** (0..=2, no escape variant on this vocabulary):
| Disc. | Variant |
|---|---|
| 0 | Primitive (carries `OperationKind`) |
| 1 | ResolveConflict |
| 2 | UndoTransaction |
**Post-baseline native:**
| Disc. | Variant | Introduction event |
|---|---|---|
| 3 | ResolveEquivocation | Push 3 (2026-07); catalog §ResolveEquivocation ratified, `epiphany-ops` DECISIONS.md:620 "ResolveEquivocation + validation modes (2026-07, Push 3)"; PASS12_RATIFICATION_LOG.md:41 confirms discriminant 3, catalog 0.3.0 → 0.4.0 |
Trap 1 confirmed live here: `binary_format.tex:2384` says "append at ≥4," but
discriminant 3 is itself the one and only append this vocabulary has ever
had — the ≥N phrasing names the next free slot, not the baseline boundary,
exactly as the contract warns.
### 1.4 `ReanchorReason` (`ops/src/effect.rs:317`, `discriminant()` at :331)
**Baseline** (0..=5, escape at 5):
| Disc. | Variant |
|---|---|
| 0 | SameVoiceNearer |
| 1 | SameStaffInstanceNearer |
| 2 | SameStaffNearer |
| 3 | SameRegionNearer |
| 4 | ExplicitFallback |
| 5 | DeclaredByExtension (escape) |
**Post-baseline native:**
| Disc. | Variant | Introduction event |
|---|---|---|
| 6 | SameCanvasNearer | Pass-12 G-pass, item P12-C4 (ratified 2026-07-07; `PASS12_RATIFICATION_LOG.md:147`) |
This is the checklist's "sharpest edge" case realized: the native append (6)
sits *after* the escape variant (5) on the same enum.
### 1.5 `PreconditionFailureReason` (`ops/src/effect.rs:124`, `discriminant()` at :188)
**Baseline** (0..=9, escape at 9):
| Disc. | Variant |
|---|---|
| 0 | TargetMissing |
| 1 | TargetTombstoned |
| 2 | WrongRegionTimeModel |
| 3 | TupletCompensationInvalid |
| 4 | EventDurationInvalid |
| 5 | PositionOutsideRegion |
| 6 | PitchSpaceMismatch |
| 7 | VoiceMissing |
| 8 | ExtensionPrecondition |
| 9 | Registered (escape) |
**Post-baseline native:**
| Disc. | Variant | Introduction event |
|---|---|---|
| 10 | ContainerNotEmpty | Operation Catalog M2c ("Group 3": structural-container CRUD, Agent K Phase-2 — `ops/DECISIONS.md:165` "M2c (Group 3) — structural containers: empty-only delete") |
| 11 | TempoMapMalformed | Phase-3 first tranche (`ops/DECISIONS.md:787-796`) |
| 12 | SystemDerivedContentImmutable | Pass-12 G-pass, item P12-K3 (ratified 2026-07-07; `PASS12_RATIFICATION_LOG.md:135`) |
| 13 | RecreateContentMismatch | Pass-12 G-pass, item P12-K9 (ratified 2026-07-07; `PASS12_RATIFICATION_LOG.md:140`) |
| 14 | AcousticRealizationPinned | Push 4a |
| 15 | TranspositionOutOfRange | Push 4a |
Note the boundary mismatch with §1.1: `OperationKind`'s own baseline absorbed
the whole M2c tranche (kinds 1520) into its golden-locked 0..=23 range, but
`PreconditionFailureReason`'s baseline (0..=9) had already been fixed
*before* M2c landed, so M2c's own contribution to *this* vocabulary
(`ContainerNotEmpty` = 10) is a genuine post-baseline append even though the
`OperationKind` variants that provoke it (`CreateRegion` et al.) are
baseline. **Each vocabulary's baseline boundary is independent — this is the
concrete case that makes that true, not merely a caution from the contract.**
### 1.6 The fourteen checked-clean escape-carrying enums
Every entry below was located, its full discriminant match read, and no
comment, `DECISIONS.md` entry, or ratification-log entry recorded a native
append after its escape variant. **Negative result, recorded as evidence,**
per the contract's requirement that a clean checklist entry be stated, not
omitted.
| # | Vocabulary | Site | Discriminants | Escape at | Result |
|---|---|---|---|---|---|
| 1 | `RepairKind` | `ops/src/effect.rs:248` | 0..=7 | 7 | clean — no append |
| 4 | `IntegrityAnomalyKind` | `ops/src/anomaly.rs:143` | 0..=3 | 3 | clean — no append |
| 5 | `ReplicaAnomalyReason` | `ops/src/anomaly.rs:70` | 0..=1 | 1 | clean — no append |
| 6 | `TransactionCategory` | `ops/src/payload.rs:1007` | 0..=4 | 4 | clean — no append |
| 7 | `ResolutionAction` | `ops/src/conflict.rs:181` | 0..=5 | 5 | clean — no append |
| 8 | `ConflictKind` (via `ExtensionConflict`) | `ops/src/conflict.rs:74` | 0..=5 | 5 | clean — no append |
| 9 | `BarrierScope` | `layout-ir/src/barrier.rs:60` | 0..=7 | 7 | clean — no append |
| 10 | `BarrierCondition` | `layout-ir/src/barrier.rs:75` | 0..=6 | 6 | clean — no append |
| 11 | `TieClass` | `core/src/graph.rs:1080`, codec at `core/src/codec.rs:2302` | 0..=4 | 4 | clean — no append |
| 12 | `StaffGroupKind` | `core/src/graph.rs:1599`, codec at `core/src/codec.rs:2267` | 0..=4 | 4 | clean — no append |
| 13 | `PitchSpacePosition` | `core/src/pitch.rs:436`, codec at `core/src/codec.rs:938` | 0..=3 | 3 | clean — no append |
| 14 | `SpellingNominal` | `core/src/pitch.rs:896`, codec at `core/src/codec.rs:1066` | 0..=2 | 2 | clean — no append |
| 15 | `TypedObjectId` | `core/src/ids.rs:490`, `discriminant()` at :525 (**16-bit**, the one exception) | 0..=27 | 27 | clean — no append. **All 28 values were assigned in one batch**, `epiphany-core/DECISIONS.md:52-68` (P11-1, Pass 11, 2026-06-21): the spec named kinds only through `AnalysisLayer=21`; this crate extended the table to 27 (`Tuplet`, `RepeatStructure`, `LyricLine`, `ChordSymbol`, `View`, `Registered`) in the *same* ratifying pass, then locked it with a golden-bytes test. There is no second, later append on top of that lock. |
| 16 | barrier `ObjectKind`'s open value space | `layout-ir/src/barrier.rs:30` | n/a — see below | n/a | clean, but structurally different — see note |
**Note on #16.** This is **not** an enum with a `discriminant()` match; it is
`pub struct ObjectKind(pub u16)`, a transparent wrapper that stores whatever
`TypedObjectId::discriminant()` produced for the referenced object
(`barrier.rs:30-35`). Its "open value space" is open only in the sense that
the wire form accepts any `u16` without independently validating it against
a known set on decode (`barrier.rs:476-488`); it carries **no discriminant
vocabulary of its own** and has no append story separate from
`TypedObjectId`'s (checked clean at #15). **Naming collision flagged for the
record:** this is a *different* type from `epiphany-ops::support::ObjectKind`
(`ops/src/support.rs:172`, the `Voice`/`Pitch`/`Registered` enum used inside
`IntegrityAnomalyKind::SystemIdentifierCollision`) — same name, two
unrelated types in two crates. Checked separately below as part of
`IntegrityAnomalyKind`'s reachable set (it has no `Registered`-adjacent
append of its own either: 0..=2, escape at 2, clean).
### 1.7 Vocabularies reached with no `Registered` escape at all, checked and clean
These are the "closed value-layer unions" the contract's IN-scope clause
covers (`binary_format.tex:2386`: closed unions may still gain variants
through a ratified document revision, and such appends are minor-additive
too). Located via the reachability walk below; each was checked against the
"Additive / appended" comment net (see the completeness note) with no hits.
| Vocabulary | Site | Discriminants | Result |
|---|---|---|---|
| `OperationEffect` | `ops/src/effect.rs:29` | 0..=4 | clean — no append |
| `NoOpReason` | `ops/src/effect.rs:76` | 0..=4 | clean — no append |
| `ObjectState` | `ops/src/reduce.rs:416` | 0..=1 | clean — no append |
| `PendingReason` | `ops/src/reduce.rs:455` | 0..=4 | clean — no append |
| `ConflictResolutionState` | `ops/src/conflict.rs:230` | 0..=2 | clean — no append |
| `UndoPolicy` | `ops/src/undo.rs:32` | 0..=2 | clean — no append |
| `epiphany-ops::support::ObjectKind` | `ops/src/support.rs:172` | 0..=2 (escape 2) | clean — no append |
`BracketKind`, `MetadataValue`, `PedalKind` and similar new enums introduced
*at* schema major 2 (`core/src/graph.rs:1038,1479` etc.) were checked and are
**not** in scope: they were minted whole at a major bump, not appended-to
afterward — they have no post-baseline history of their own to record.
### 1.8 `CompressionAlgorithm` — EXCLUDED by ruling (2026-07-28)
`bundle/src/chunk.rs:119`: `None=0`, `Zstd{level}=1`, `Reserved(u8)=2`
("Reserved for future format major versions"). This audit surfaced it as a
boundary case — it is in neither the contract's checklist nor the plan's §3
table — and **it is now ruled out of scope for schema-minor epochs.**
Two independent grounds, both read from the tree:
1. **It is not a discriminant emitted by the payload that `SchemaVersion`
governs.** It is transport metadata riding `ChunkRef` alongside the chunk's
offset and length, structurally parallel to `ChunkKind` — which the
contract already excludes — rather than a value- or operation-layer
discriminant inside the payload bytes.
2. **It is deliberately outside chunk identity.** `chunk_content_hash`'s
preimage is `domain || kind || schema || uncompressed_length || payload`
(`bundle/src/chunk.rs:177`), and both the function's own doc comment and
the type's (`:110`, "**Not** part of content identity") state the exclusion
as intentional. A minor is content-addressed *because* it enters that
preimage (plan §2); compression never does, so it has no minor to raise.
Recorded here explicitly so the question is settled rather than reopened each
time the enum's `Reserved(u8)` slot is noticed. Its `Reserved` variant is a
format-**major** escape, consistent with its own doc comment.
---
## Part 2 — the reachability matrix
| Chunk role | Encoded payload type | Discriminant vocabulary | Post-baseline variants | Introduction event | Derivation site |
|---|---|---|---|---|---|
| `OperationEnvelopeBlock` | `OperationEnvelope``OperationPayload` | `OperationPayload` | `ResolveEquivocation` = 3 | Push 3 | `ops/src/payload.rs:100` (`push_tag` in `OperationPayload::encode_canonical`) |
| `OperationEnvelopeBlock` | ↳ `OperationPayload::Primitive` | `OperationKind` | 2433 (10 variants, §1.1) | Phase-3 / schema-major-2 / Push 4a / G1 / G2a | `ops/src/payload.rs:373` |
| `OperationEnvelopeBlock` | ↳ `OperationKind::DeclareTransaction``TransactionDescriptor` | `TransactionCategory` | none | — (baseline only) | `ops/src/payload.rs:1029` |
| `OperationEnvelopeBlock` | ↳ `OperationPayload::ResolveConflict``ResolveConflictPayload` | `ResolutionAction` | none | — (baseline only) | `ops/src/conflict.rs:211` |
| `OperationEnvelopeBlock` | ↳ `OperationPayload::UndoTransaction``UndoTransactionPayload` | `UndoPolicy` | none | — (baseline only) | `ops/src/undo.rs:57` |
| `OperationEnvelopeBlock` | ↳ every primitive op's embedded graph value (`Event`, `Region`, `Instrument`, `PitchSpelling`, `Tie`, `Slur`, `Beam`, `Spanner`, …) | the four escape vocabularies (`TieClass`, `StaffGroupKind`, `PitchSpacePosition`, `SpellingNominal`) + all closed value-layer unions | none found | — | `core/src/codec.rs` (per-type `Codec::enc`, embedded via `push_lp_bytes(out, &value.canonical_bytes())` at each op site, e.g. `ops/src/payload.rs:1513`, `:1528`) |
| `OperationIndex` | `(ChunkRef, Vec<(id, block, offset)>)` | — | — | — | **(b) emits no additive discriminant.** Payload is `block_count × ChunkRef` + `entry_count × {id bytes, block ordinal, offset}` (`bundle/src/opindex.rs:26-38`); the only discriminant present is each `ChunkRef`'s `ChunkKind` tag, out of scope by the contract's own exclusion. |
| `Snapshot` (canonical base) | `MaterializedState` | `OperationEffect` | none | — | `ops/src/reduce.rs:548` |
| `Snapshot` (canonical base) | ↳ `OperationEffect::AppliedWithRepair``RepairRecord` | `RepairKind` | none | — | `ops/src/effect.rs` (`RepairKind::encode_canonical`) |
| `Snapshot` (canonical base) | ↳ `RepairKind::Reanchored` | `ReanchorReason` | `SameCanvasNearer` = 6 | Pass-12 G-pass (P12-C4) | `ops/src/effect.rs:331` |
| `Snapshot` (canonical base) | ↳ `OperationEffect::NoOp` | `NoOpReason` | none | — | `ops/src/effect.rs:96` |
| `Snapshot` (canonical base) | ↳ `NoOpReason::PreconditionFailedUnderReduction` | `PreconditionFailureReason` | 1015 (6 variants, §1.5) | M2c / Phase-3 / Pass-12 G-pass ×2 / Push 4a | `ops/src/effect.rs:188` |
| `Snapshot` (canonical base) | ↳ `conflicts: ConflictRegistry``ConflictRecord` | `ConflictKind` | none | — | `ops/src/conflict.rs` (`ConflictKind::encode_canonical`) |
| `Snapshot` (canonical base) | ↳ `ConflictRecord::resolution_state` | `ConflictResolutionState` | none | — | `ops/src/conflict.rs:230` |
| `Snapshot` (canonical base) | ↳ `anomalies: Vec<IntegrityAnomaly>` | `IntegrityAnomalyKind` | none | — | `ops/src/anomaly.rs:143` |
| `Snapshot` (canonical base) | ↳ `IntegrityAnomalyKind::SystemIdentifierCollision` | `epiphany-ops::support::ObjectKind` | none | — | `ops/src/support.rs:191` |
| `Snapshot` (canonical base) | ↳ `objects: BTreeMap<TypedObjectId, ObjectState>` | `TypedObjectId` (map key) + `ObjectState` | none / none | — | `ops/src/reduce.rs:437`, `core/src/ids.rs` |
| `Snapshot` (canonical base) | ↳ `spellings: BTreeMap<PitchId, PitchSpelling>` | `SpellingNominal` | none | — | `core/src/codec.rs:1066` (via `PitchSpelling::canonical_bytes`) |
| `Snapshot` (canonical base) | ↳ `pending: Vec<(OperationId, PendingReason)>` | `PendingReason` | none | — | `ops/src/reduce.rs:494` |
| `Snapshot` (canonical base) | — | **Notably absent:** no `OperationKind`, `OperationKindTag`, or `OperationPayload` discriminant anywhere. Confirms the plan's §3 claim for this role. | | | |
| `Snapshot` (acceleration cache, full `Score`) | `Score` (whole-graph codec) | the four escape vocabularies + all closed value-layer unions | none found | — | `core/src/codec.rs` (`Score`'s `Codec` impl, transitively over every graph type) |
| `Blob` | caller-supplied bytes | — | — | — | **(d) truly producer-owned opaque bytes.** `ChunkKind::Blob` doc: "audio, image, font, ML model" (`bundle/src/chunk.rs:26`); no core encoder produces the content, only stages/verifies it (`bundle/src/bundle.rs:482,1018`). |
| `ExtensionData` | extension-supplied bytes | — | — | — | **(d) truly producer-owned opaque bytes**, per contract's own framing — no core encoder, no core derivation possible. |
| `TextProjection` | — | — | — | — | **(b) emits no additive discriminant — because no producer exists.** `ChunkKind::TextProjection` is declared (`bundle/src/chunk.rs:31,54`) and named in the text-projection kind↔string table (`textproj/src/project.rs:262`, `parse.rs:545`), but no site in this codebase ever builds a chunk of this kind. The role is reserved, unwired. |
| `LayoutCache` | — | — | — | — | **(b) emits no additive discriminant — because no producer exists.** `epiphany-layout-ir::cache::LayoutCache` (`layout-ir/src/cache.rs:82`) is a pure in-memory incremental-layout index (`DependencyIndex` + `BTreeSet<LayoutObjectId>` stage caches) with **no `CanonicalEncode` impl at all** and no wiring to `ChunkKind::LayoutCache`. Every use of `ChunkKind::LayoutCache` in `bundle/src/bundle.rs` is a test planting literal placeholder bytes (e.g. `b"layout-cache-bytes "`, `bundle.rs:2323`). |
| `IntegrityIndex` | — | — | — | — | **(b) emits no additive discriminant — because no producer exists.** Referenced only in the kind↔string round-trip table and in `testkit/src/generators.rs`'s random `ChunkRef` fuzzing (never given real integrity-index content). |
| `Manifest` | `Manifest` body (`bundle/src/manifest.rs:459`) | — (own fields: ids, `ChunkRef`s, `SemVer`s — no discriminant vocabulary) | — | — | `bundle/src/manifest.rs:459-513` |
| `Manifest` | ↳ `ExtensionDeclaration::edit_barriers: Vec<u8>` | **(c) normatively typed bytes, opaque only to the bundle layer.** Producer: `layout_ir::barrier::EditBarrier::encode_canonical` | | | `layout-ir/src/barrier.rs:387` |
| `Manifest` | ↳ ↳ `EditBarrier::scope` | `BarrierScope` | none | — | `layout-ir/src/barrier.rs:317` |
| `Manifest` | ↳ ↳ `EditBarrier::condition` | `BarrierCondition` | none | — | `layout-ir/src/barrier.rs:352` |
| `Manifest` | ↳ ↳ `EditBarrier::prohibited_operation_kinds: Vec<OperationKindTag>` | `OperationKindTag` | 2433 (10 variants, §1.2) | Phase-3 / schema-major-2 / Push 4a / G1 / G2a | `ops/src/payload.rs:499` (macro-generated `encode_canonical` at :568) |
| `Manifest` | ↳ `ExtensionDeclaration::affected_object_kinds: Vec<u8>` | **(c)**, producer: `layout_ir::barrier::ObjectKind` (u16 wrapper of `TypedObjectId::discriminant()`) | none (see §1.6 #16) | — | `layout-ir/src/barrier.rs:39` |
**Nine roles, all accounted for:** `OperationEnvelopeBlock` (a), `OperationIndex` (b), `Snapshot`/canonical-base (a), `Snapshot`/acceleration (a — clean), `Blob` (d), `ExtensionData` (d), `TextProjection` (b, unwired), `LayoutCache` (b, unwired), `IntegrityIndex` (b, unwired), `Manifest` (c, crossing into `layout-ir`'s barrier encoder).
That is ten rows of disposition against nine `ChunkKind` variants because
`Snapshot` is payload-polymorphic (trap 3) and gets one disposition per form,
as the contract requires.
---
## Completeness statement
- **Which roles emit no additive discriminant, and why:** `OperationIndex`
(its payload is ids, ordinals, and offsets — no discriminant besides the
out-of-scope `ChunkKind` tag inside each `ChunkRef`); `TextProjection`,
`LayoutCache`, `IntegrityIndex` (all three: no producer exists in this
codebase at all — the chunk kind is declared and named but unwired, which
is a different finding from "encodes nothing interesting").
- **Every escape-carrying enum from the sixteen-entry checklist, with its
result:** recorded in §1.6, all sixteen present, two with native
post-baseline appends (`ReanchorReason` #2, `PreconditionFailureReason`
#3 — both outside the sixteen but reached via #1/#4's neighborhood and
recorded in §1.4/§1.5) and fourteen clean.
**The tally:** sixteen checklist entries, sixteen checked, sixteen results
recorded. Fourteen clean (§1.6). Two with native post-baseline appends —
`ReanchorReason` (position 2, §1.4) and `PreconditionFailureReason`
(position 3, §1.5), which is why §1.6's table skips those two numbers. No
entry is unchecked and none is counted twice.
- **Variants whose introduction event could not be established:** **none.**
Every post-baseline native variant found (`OperationKind`/`OperationKindTag`
2433, `OperationPayload` 3, `ReanchorReason` 6, `PreconditionFailureReason`
1015) has a sourced introduction event from at least one of: a code
comment naming the tranche, `crates/epiphany-ops/DECISIONS.md`, or
`spec/PASS12_RATIFICATION_LOG.md`. Where more than one source existed
(`OperationPayload` 3: both `payload.rs:80` comment and
`PASS12_RATIFICATION_LOG.md:41` state Push 3 / discriminant 3), they
agreed; no dispute was found to adjudicate.
- **Where the spec and the code disagree about what was appended when: one
place, `binary_format.tex:2373`.** The bullet list at `:2373-2385` says
`OperationKind` and `OperationKindTag` "append at ${\geq}30$", and its
parenthetical history names only the Phase-3 tranche (2427) and the
schema-major-2 repeat pair (28/29). **Kinds 3033 — `TransposeInterval`
(Push 4a), `CreateInstrument` (G1), `SetCanvasLayoutDefaults` and
`SetSpellingPrecedence` (G2a) — are absent from both the number and the
narrative**, and 30 is no longer free.
This audit initially recorded "no disagreement," which was wrong, and wrong
in a way worth naming: it adopted trap 1's next-free-slot reading of "≥N"
and *then* declared the ≥30 consistent. **Both cannot hold.** Read as a
floor the sentence survives (34 ≥ 30); read as next-free-slot — the reading
this audit uses everywhere else, and the reading that made
`OperationPayload`'s "≥4 while 3 is itself the append" legible — it is
stale by four.
**Disposition: stale narrative, not table drift.** The normative kind and
tag tables *are* current — `binary_format.tex:1443-1457` carries 3033 with
their catalog cross-references and `:1526-1527` repeats them in the tag
table. **The tables are authoritative; `:2373` is prose that stopped being
maintained.** So this is not the Push-4a failure (an enumeration the
decoder contradicted) but its milder cousin: a count-and-history sentence
drifting from a listing that is correct.
**The `.tex` correction is deferred to the G-minor implementation**, which
must touch this paragraph anyway — it is the paragraph defining the
minor-additive mechanism the rung implements. Recorded here so the fix is
owed rather than rediscovered.
## Findings the plan's §3 table did not name
1. **The canonical base (`MaterializedState`) reaches more than the plan's
§3 list states.** Plan §3 lists `OperationEffect, NoOpReason,
PreconditionFailureReason, ConflictKind, IntegrityAnomalyKind,
PendingReason, ObjectState, TypedObjectId` for the canonical base. Missing
from that list, and confirmed reachable by this audit: **`RepairKind`**
(via `OperationEffect::AppliedWithRepair`), **`ReanchorReason`** (nested
inside `RepairKind::Reanchored` — and the one with a real post-baseline
append, `SameCanvasNearer` = 6), and **`SpellingNominal`** (via the
`spellings: BTreeMap<PitchId, PitchSpelling>` field). This is not a
cosmetic omission: `ReanchorReason` is one of only two vocabularies this
audit found with an actual native append, and it is missing from the
plan's inventory of what the canonical base emits.
2. **The manifest role reaches `OperationKindTag`, `BarrierScope`, and
`BarrierCondition` — none of which appear in the plan's §3 table at
all.** The plan's table (§3) enumerates only `OperationKind`,
`OperationKindTag`, `OperationPayload`, and "value-layer unions closed
under `req:binfmt:frozen-layout`" as the four floor vocabularies, framed
entirely around the *operation* layer. But `OperationKindTag` is also
reachable through the **manifest**, independently of any operation
envelope, via `ExtensionDeclaration::edit_barriers`
`EditBarrier::prohibited_operation_kinds: Vec<OperationKindTag>`
(`layout-ir/src/barrier.rs:205`). A manifest that declares an edit barrier
naming `CreateInstrument` (tag 31) needs the same G-minor stamping
discipline as an operation-envelope block that contains one — and the
manifest chunk's minor is a wholly separate stamping decision from any op
block's. This is exactly the disposition-(c) crossing the contract warned
would be "the single most important vocabulary in the whole matrix" if
skipped, realized concretely, and it lands in a role the plan's table
never mentions.
3. **Two roles are declared and named end-to-end but have no producer at
all: `TextProjection` and `LayoutCache` (chunk-kind), plus
`IntegrityIndex`.** This isn't in the plan's scope (it predates
vocabulary concerns entirely), but it matters for whoever drafts the
G-minor implementation contract next: there is currently no code path
that needs an `introduced_minor` for these three roles, because there is
no code path that writes them at all.
4. **`ConflictResolutionState` and `UndoPolicy`** are closed unions (no
`Registered` escape) reachable from the canonical base and the operation
block respectively, present in neither the plan's table nor the
contract's sixteen-entry checklist (the checklist is escape-carriers
only). Checked and clean, but worth naming since the contract's own
framing ("every reachable discriminant vocabulary") is broader than its
worked checklist.
5. **A same-named-type trap**: `layout_ir::barrier::ObjectKind` (a `u16`
wrapper around `TypedObjectId::discriminant()`, no independent vocabulary)
and `epiphany_ops::support::ObjectKind` (a three-variant enum,
`Voice`/`Pitch`/`Registered`, feeding `IntegrityAnomalyId` derivation) are
two unrelated types sharing one name across two crates. Neither the plan
nor the contract distinguishes them; this audit treats them as separate
ledger entries (§1.6 #16 and §1.7 respectively) to avoid the two being
silently merged by a later reader.
## Where this audit's own working assumptions turned out to need correction
- **A vocabulary's "baseline" is not one format-wide date.** Reading
`PreconditionFailureReason`'s baseline (locked at 0..=9, evidently before
the M2c tranche) against `OperationKind`'s baseline (locked at 0..=23,
evidently *including* M2c's `CreateRegion` et al.) shows two vocabularies
whose own golden-lock moments are not simultaneous, even though one
vocabulary's post-baseline variant (`ContainerNotEmpty` = 10) is produced
by an operation (`CreateRegion` = kind 15) that is itself baseline in the
*other* vocabulary. The contract's Part 1 definition ("baseline — present
at the format's initial ratification") reads as if there is one
initial-ratification instant; in practice each vocabulary's own
code-documented golden lock is the operative boundary, and they disagree
with each other by design (the M2c precedent shows *the same tranche* can
be baseline for one vocabulary and a genuine append for another). This
audit's ledger records introduction events per variant rather than
asserting a single global baseline date, which is the only way the two
observations above are both representable.
- **The sixteen-entry checklist, read carefully, already includes
`ReanchorReason` and `PreconditionFailureReason` at positions 2 and 3** —
an early pass through this audit almost treated the checklist as
"fourteen escape-carriers plus these two extras," which would have been a
double-count. Rereading the contract's list resolved this without any
discrepancy in the final tally (still sixteen numbered entries, all
checked); flagged here only because it was a live risk of self-inflicted
miscounting during the audit, worth naming so a future reader doesn't
repeat the confusion.
- **`OperationKindTag`'s reachability is not confined to the op layer.**
The contract frames the audit around "vocabularies reachable from an
affected chunk payload," in a document whose motivating example is entirely
about operation envelopes and `ResolveEquivocation`. It would have been
easy to conclude `OperationKindTag` is reachable *only* through
`prohibited_operation_kinds` inside an op-adjacent structure and stop
there. It is in fact reachable through the **manifest**, a chunk role with
no operation envelope in it at all. Finding 2 above is the concrete
correction; the general lesson is that "affected chunk payload" needed
checking against all nine roles independently, not inferred from the one
role (`OperationEnvelopeBlock`) the motivating example was about.
## What this audit hands to the epoch ratification (ruled 2026-07-28)
**The epoch space stays global** — one globally meaningful minor per additive
revision, as ratified in `PLAN_GMINOR_SCHEMA_MINOR.md` §4. This audit does not
reopen that.
**But baseline/post-baseline classification and introduction events are
established per variant, per vocabulary.** §1.5's M2c case is the proof rather
than the caution: the same tranche is baseline for `OperationKind` (absorbed
into the golden-locked 0..=23) and a genuine append for
`PreconditionFailureReason` (`ContainerNotEmpty` = 10, whose own lock at
0..=9 predates it). A global epoch *numbering* and a per-vocabulary *baseline
boundary* are not in tension — the epoch answers "which revision introduced
this variant," the boundary answers "was this variant present at that
vocabulary's own lock." Only the second varies by vocabulary.
**The tentative ladder is therefore incomplete.** `PLAN_GMINOR_SCHEMA_MINOR.md`
§4's Phase 3 → 2, repeats → 3, Push 4a → 4, G1 → 5, G2a → 6 was drawn against
`OperationKind` alone. Three families this audit exposed are unplaced:
| Family | Variants | Introduction event | Placement |
|---|---|---|---|
| `OperationPayload` | `ResolveEquivocation` = 3 | Push 3 | **unplaced** — Push 3 predates every rung on the tentative ladder |
| `ReanchorReason` | `SameCanvasNearer` = 6 | Pass-12 G-pass (P12-C4) | **unplaced** — Pass-12 G-pass has no rung |
| `PreconditionFailureReason` | 1015 | M2c / Phase-3 / Pass-12 G-pass ×2 / Push 4a | **partly placeable** — Phase-3 and Push 4a map to existing rungs; M2c and the Pass-12 G-pass do not |
So ratification owes at least two new epochs (Push 3, Pass-12 G-pass) and a
decision on M2c, and must confirm that Phase-3 and Push 4a rungs cover their
`PreconditionFailureReason` contributions as well as their `OperationKind`
ones. **The ladder must not be ratified as written.**
## How completeness was checked (methodology note)
Two independent nets were run and cross-checked against each other:
1. `grep -rn "push_tag\|fn discriminant"` over `epiphany-core/src` and
`epiphany-ops/src` (the contract's suggested net, ~65 hits), then every
`fn discriminant` site outside those two crates (`epiphany-layout-ir`,
`epiphany-bundle`) located separately, since the contract's net is scoped
to only two crates but `BarrierScope`/`BarrierCondition` live in
`epiphany-layout-ir`.
2. `grep -rn "Additive|appended|append at|append past|appended past"` across
`epiphany-core/src`, `epiphany-ops/src`, `epiphany-layout-ir/src`,
`epiphany-bundle/src` — every hit was read in context (§1.4, §1.5's
sourcing, plus confirmation that every schema-major-2 "appended" comment
found was a **struct field** addition, out of scope by the contract's own
"field additions are always major" rule, not a discriminant append).
Every discriminant vocabulary named in the contract's checklist, the plan's
§3 table, and everything found via nets 12 was traced to a concrete
`encode_canonical` (or `Codec::enc`) call site and confirmed reachable from
at least one `ChunkKind` role's payload before being entered in Part 2. No
vocabulary was entered on the strength of its type declaration alone.
## Gate
`git status --short` **before**:
```
M Cargo.toml
?? spikes/editor-toolkit/Cargo.lock
?? spikes/editor-toolkit/Cargo.toml
?? spikes/editor-toolkit/a11y-verifier/
?? spikes/editor-toolkit/probe-egui/
?? spikes/editor-toolkit/probe-iced/
?? spikes/editor-toolkit/probe-vello/
?? spikes/editor-toolkit/target/
```
`git status --short` **after** (expected — this file is the only addition):
```
M Cargo.toml
?? spec/AUDIT_GMINOR_VOCABULARIES.md
?? spikes/editor-toolkit/Cargo.lock
?? spikes/editor-toolkit/Cargo.toml
?? spikes/editor-toolkit/a11y-verifier/
?? spikes/editor-toolkit/probe-egui/
?? spikes/editor-toolkit/probe-iced/
?? spikes/editor-toolkit/probe-vello/
?? spikes/editor-toolkit/target/
```
No Rust, `.tex`, vector, or golden file was modified.
**`cargo` was not invoked at any point during this audit** — no build, no
test run, no `fmt`. Every fact above was established by reading source and
spec/decision-log text.
**No test result is claimed by this packet.** `cargo fmt --check` and
`cargo test --workspace` were not run, so this document asserts nothing about
their state; it neither reports them green nor relies on their being so.
What the gate actually proves is the status delta above: the packet's only
effect on the working tree is the addition of
`?? spec/AUDIT_GMINOR_VOCABULARIES.md`. No source file, build input, or
`Cargo.lock` changed, which is what makes the read-only claim checkable
without running anything.