diff --git a/spec/AUDIT_GMINOR_VOCABULARIES.md b/spec/AUDIT_GMINOR_VOCABULARIES.md new file mode 100644 index 0000000..eb5d4ef --- /dev/null +++ b/spec/AUDIT_GMINOR_VOCABULARIES.md @@ -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 15–20) 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` | 24–33 (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` | 10–15 (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` | `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` (map key) + `ObjectState` | none / none | — | `ops/src/reduce.rs:437`, `core/src/ids.rs` | +| `Snapshot` (canonical base) | ↳ `spellings: BTreeMap` | `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` 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` | **(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` | 24–33 (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` | **(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` + 24–33, `OperationPayload` 3, `ReanchorReason` 6, `PreconditionFailureReason` + 10–15) 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 (24–27) and the + schema-major-2 repeat pair (28/29). **Kinds 30–33 — `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 30–33 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` 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` + (`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` | 10–15 | 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 1–2 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.