Contract the G-minor vocabulary audit: a matrix, read-only
The gating work before G-minor can be contracted. Deliverable is one file, spec/AUDIT_GMINOR_VOCABULARIES.md, holding a reachability matrix keyed chunk role -> encoded payload type -> discriminant vocabulary -> post-baseline variants -> introduction event -> derivation site. Method is pinned to walking the encoder rather than the type, because a field can exist and never reach the wire -- ScoreTuningContext::accidental_extensions is the standing proof -- and the matrix's purpose is to say what is emitted. Inclusions cover nested additive variants at any depth and, the sharpest edge, later native additions to enums that also carry a Registered escape: the escape is out of scope while a new native variant on the same enum is not, and binary_format names fifteen such carriers. ChunkKind is excluded as closed. The trap most likely to corrupt the matrix: the spec's "append at >= N" phrasing names the next free slot, not the baseline boundary. OperationPayload is documented as appending at >= 4 while discriminant 3 is itself an append, and OperationKind at >= 30 while 24-29 were appends too. Post-baseline membership must come from revision history, not from those numbers. Epoch numbers are explicitly not ratified here; column 5 records the introduction event. Completeness is enforced by requiring a per-role accounting including roles that produce no rows, and negative results recorded for every escape-carrying enum -- an unlisted role being indistinguishable from an overlooked one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
This commit is contained in:
parent
82ac636982
commit
6145efecd9
|
|
@ -0,0 +1,169 @@
|
|||
# Contract: G-minor vocabulary audit — a reachability matrix, and nothing else
|
||||
|
||||
Repo root `/home/jeans/Repos/active/epiphany`. Governed by
|
||||
`spec/PLAN_GMINOR_SCHEMA_MINOR.md` (§3 and §5.1; policy ratified 2026-07-28)
|
||||
and `spec/PASS13_CANDIDATES.md` (P13-S14).
|
||||
|
||||
**This is a READ-ONLY packet.** It produces one document. It changes no Rust,
|
||||
no `.tex`, no vectors, and no goldens.
|
||||
|
||||
**Parallel safety.** The editor track owns `crates/epiphany-editor-gui/**`,
|
||||
`crates/epiphany-render-svg/**`, `crates/epiphany-glyphs/**`,
|
||||
`spec/PLAN_EDITOR_APP.md`, every `spec/CONTRACT_EDITOR_*.md`,
|
||||
`spec/DRAFT_T4_FIXTURE_RECIPE.md`, the entire `spikes/` tree, and the current
|
||||
unstaged root `Cargo.toml` change. **All of it is out of scope**, and since
|
||||
this packet writes only one new file, none of it should ever be staged.
|
||||
|
||||
---
|
||||
|
||||
## Why this packet exists
|
||||
|
||||
The G-minor rung cannot be contracted until every append-only discriminant
|
||||
reachable from an affected chunk payload is known. A first attempt scoped the
|
||||
problem as per-`OperationKind` and recommended a policy that could not work —
|
||||
an envelope also emits the outer `OperationPayload` discriminant, and
|
||||
`ResolveEquivocation` carries no `OperationKind` at all. **That scoping error
|
||||
is the reason this audit is a separate packet rather than a paragraph in a
|
||||
contract.**
|
||||
|
||||
## The deliverable
|
||||
|
||||
One new file, `spec/AUDIT_GMINOR_VOCABULARIES.md`, whose core is a
|
||||
**reachability matrix** with exactly these columns:
|
||||
|
||||
| chunk role | encoded payload type | discriminant vocabulary | post-baseline variants | introduction event | derivation site |
|
||||
|
||||
* **chunk role** — a `ChunkKind` variant (`bundle/src/chunk.rs:18`; nine of
|
||||
them). Every role gets at least one row or an explicit "emits no additive
|
||||
discriminant" row with the reason.
|
||||
* **encoded payload type** — the Rust type whose canonical encoding *is* that
|
||||
role's payload. Some roles are payload-polymorphic (`Snapshot` is the
|
||||
canonical-base `MaterializedState` *or* the acceleration full-`Score`); those
|
||||
get one row per form, distinguished.
|
||||
* **discriminant vocabulary** — the enum whose tag is written.
|
||||
* **post-baseline variants** — the specific variants appended after the
|
||||
format's initial ratification, listed individually with their discriminant
|
||||
values. Not a count.
|
||||
* **introduction event** — the tranche or revision that appended it (e.g.
|
||||
"Phase-3 first tranche", "schema-major-2 repeat revision", "Push 4a",
|
||||
"genesis G1", "genesis G2a"). **Name the event, not an epoch number.**
|
||||
* **derivation site** — `file.rs:line` of the code that writes the tag, so the
|
||||
G-minor implementation knows where `introduced_minor` has to live.
|
||||
|
||||
## Method — walk the encoder, never the type
|
||||
|
||||
**The ground truth is the encoder, not the struct definition.** A field can
|
||||
exist on a type and never reach the wire: `ScoreTuningContext::accidental_extensions`
|
||||
is staged out of schema major 3 and its `Codec` deliberately drops it
|
||||
(`core/src/codec.rs:1939`). A type-driven walk would record a vocabulary that
|
||||
is not actually emitted, and the matrix's whole purpose is to say what is
|
||||
*emitted*.
|
||||
|
||||
So: start from each role's payload type, walk its `encode_canonical` /
|
||||
`Codec::enc` implementation transitively, and record every site that writes a
|
||||
tag or discriminant. `grep -rn "push_tag\|fn discriminant"` over
|
||||
`crates/epiphany-core/src` and `crates/epiphany-ops/src` returns ~65 hits
|
||||
across 11 files and is a reasonable starting net — but it is a *net*, not the
|
||||
method. Confirm reachability from a role by following the encoder.
|
||||
|
||||
## Inclusions and exclusions
|
||||
|
||||
**IN scope:**
|
||||
|
||||
* Native variants appended to any open vocabulary after baseline.
|
||||
* **Nested** additive variants — a vocabulary reached only through another
|
||||
value's encoder counts, at whatever depth.
|
||||
* Later native additions to enums that *also* carry a `Registered` escape.
|
||||
**This is the audit's sharpest edge**: the escape variant is out of scope,
|
||||
but a new native variant on the same enum is in scope, and the two are easy
|
||||
to conflate. `binary_format.tex` names fifteen escape carriers
|
||||
(`RepairKind`, `ReanchorReason`, `PreconditionFailureReason`,
|
||||
`IntegrityAnomalyKind`, `ReplicaAnomalyReason`, `TransactionCategory`,
|
||||
`ResolutionAction`, `ConflictKind` via `ExtensionConflict`,
|
||||
`BarrierScope`/`BarrierCondition`, `TieClass`, `StaffGroupKind`,
|
||||
`PitchSpacePosition`, `SpellingNominal`, `TypedObjectId`, and the barrier
|
||||
`ObjectKind`); every one needs checking for native appends.
|
||||
|
||||
**OUT of scope:**
|
||||
|
||||
* **`Registered` payload values.** Extension through an escape variant is not a
|
||||
schema change at all — the wire form is already defined.
|
||||
* **`ChunkKind` itself.** It is closed: no `Registered` variant, and its
|
||||
discriminant enters every chunk's hash preimage, so a new chunk kind is a
|
||||
format-**major** event.
|
||||
* Anything requiring a schema-major bump. Field additions are major
|
||||
*regardless of type* — an `Option` still occupies a positional slot
|
||||
(`binary_format.tex:2360`). Only discriminant appends are minor-additive.
|
||||
|
||||
## Traps
|
||||
|
||||
1. **The spec's "append at ≥N" phrasing names the next free slot, not the
|
||||
baseline boundary.** `binary_format.tex:2369` says `OperationPayload`
|
||||
appends at ≥4 — yet discriminant **3** (`ResolveEquivocation`) is itself an
|
||||
append, as both the spec (`:1291`) and the code comment
|
||||
(`payload.rs:80`, "the ratified 0..=2 stay stable") state. Likewise
|
||||
`OperationKind` "appends at ≥30" while 24–29 were also appends. **Do not
|
||||
derive post-baseline membership from the ≥N numbers.** Establish each
|
||||
variant's introduction event from the spec's revision history, the
|
||||
`DECISIONS.md` files, and `git log` — and say which source settled it when
|
||||
they disagree.
|
||||
2. **`OperationKind` and `OperationKindTag` are independent, misaligned
|
||||
spaces.** `RespellPitch` is kind 2 / tag 3. They are two rows, never one.
|
||||
3. **Payload-polymorphic roles.** `Snapshot` carries both the canonical-base
|
||||
`MaterializedState` and the acceleration full-`Score`; they emit different
|
||||
vocabularies and must not be merged. The canonical base notably does **not**
|
||||
emit `OperationKind` at all.
|
||||
4. **A hand-written `discriminant()` match is not evidence of completeness.**
|
||||
`OperationKind::discriminant()` is exactly the site Push 4a got wrong. Where
|
||||
a macro-generated vocabulary exists (`operation_kind_tag_vocabulary!`),
|
||||
prefer it as the source of truth and note the divergence if the hand-written
|
||||
sibling disagrees.
|
||||
|
||||
## What this packet must NOT do
|
||||
|
||||
* **Do not ratify epoch numbers.** Column 5 records the introduction *event*.
|
||||
Assigning epochs is a later ruling that depends on this matrix being
|
||||
complete.
|
||||
* **Do not write or modify any Rust**, including tests, and do not add
|
||||
`introduced_minor` anywhere.
|
||||
* **Do not modify any `.tex`**, vectors, or goldens.
|
||||
* **Do not draft the G-minor implementation contract.**
|
||||
* Do not touch `spikes/`, the root `Cargo.toml`, or any editor-track file.
|
||||
|
||||
## Completeness — how to know the matrix is done
|
||||
|
||||
The audit's failure mode is a missed vocabulary, and it is silent. So the
|
||||
report must state, for each of the nine roles, either the rows it produced or
|
||||
why it produces none — an unlisted role is indistinguishable from an
|
||||
overlooked one.
|
||||
|
||||
State explicitly:
|
||||
|
||||
* which roles emit no additive discriminant, and why;
|
||||
* every escape-carrying enum checked for native appends, **including those that
|
||||
turned out to have none** — a negative result recorded is evidence, a
|
||||
negative result omitted is a gap;
|
||||
* any variant whose introduction event could not be established from the
|
||||
sources in trap 1, flagged as **unresolved** rather than guessed;
|
||||
* any place where the spec and the code disagree about what was appended when.
|
||||
|
||||
**A guess presented as a finding is the worst possible output here**, because
|
||||
the epoch mapping will be ratified on top of it and a wrong epoch is a wrong
|
||||
wire record that then freezes.
|
||||
|
||||
## Gate
|
||||
|
||||
`cargo fmt --check` and `cargo test --workspace` are expected to be **untouched
|
||||
and green** — that is the check that this packet really was read-only. Report
|
||||
`git status --short` verbatim so the file list can be seen to contain exactly
|
||||
one new file.
|
||||
|
||||
## Report
|
||||
|
||||
The matrix itself, plus: the completeness statement above; the sources used to
|
||||
establish introduction events and which settled disputes; anything found that
|
||||
the plan's §3 four-vocabulary table did not name; and any place where this
|
||||
contract's own assumptions turned out to be wrong. That last one is not
|
||||
politeness — the plan this packet serves already had to be revised once for a
|
||||
scoping error of exactly this kind, and the contract's four-vocabulary starting
|
||||
table is a floor, not a ceiling.
|
||||
Loading…
Reference in New Issue