# Contract: the G3a undo repair, and the Binary Format chronology restoration **Status:** **RATIFIED 2026-07-29.** Two packets, two commits. Pins A3 (ungated ledger/map guards), A6 (no restoration lookup while those fields have no write chain — revisit only if a modifying operation is introduced), and A7 (retaining the maps is safe given A4's live-state filter and object-first recreate refusal) were ratified explicitly. Mutation budget ratified at twenty-two for Packet A and three for Packet B. **Governs:** the defect that blocks G3a sign-off (Packet A), and P13-S17 (Packet B). **Predecessor:** `spec/CONTRACT_GENESIS_G3A_ENTITIES.md`, landed at `6c5e69f`. --- ## §0. What was actually verified Every claim below was read out of the code, not relayed. **The tombstone branch is reachable.** `mint_container` (`crates/epiphany-ops/src/reduce.rs:3922`) calls `note_minted` (`:7848`), which pushes the object into `tx_minted` whenever `self.current_tx` is set. `undo_transaction` (`:5288`) reads `tx_minted` and `tombstone_undo_targets` (`:5387`) writes `ObjectState::Tombstoned` into `objects` for every target. All four G3a reducers reach `mint_container`: `create_staff_group` `:4283`, `create_part_definition` `:4340`, `create_analysis_layer` `:4384`, `create_view` `:4437`. `create_instrument` `:4214` and `create_staff` `:4165` do the same. Therefore `crates/epiphany-ops/DECISIONS.md:1986` — "`ObjectState::Tombstoned` is unreachable for any of them through the public operation API today" — is **false**, and was false for `CreateStaff` and `CreateInstrument` before this rung. It is a pre-existing wrong claim that G3a inherited and repeated. **The graph keeps ghosts.** `materialize_graph_tombstones` (`:2737`) walks `targets` at `:2768` with arms for `Pitch`, `Voice`, `Slur`, `Tie`, `Beam`, `Spanner`, `RepeatStructure`, `Staff`, `TimeSignature`, then `_ => {}` at `:2810`. There is no arm for `StaffGroup`, `PartDefinition`, `AnalysisLayer`, `View`, or `Instrument`. The ledger tombstones; `Score.staff_groups` / `.parts` / `.analysis_layers` / `.views` / `.instruments` keep the value. Ledger and graph disagree — the exact failure the `Spanner` arm's own comment at `:2793` records as having been fixed once already. **The strand guard is silent on the new families.** `undo_strand_block` (`:5458`) matches `Staff` and `TimeSignature`, then `_ => None` at `:5500`. Undoing a `StaffGroup` mint while a live `Staff.group` (`crates/epiphany-core/src/graph.rs:825`) names it strands the reference; likewise `AnalysisLayer` ← `ViewDefinition.active_layers` (`graph.rs:1652`) and `Instrument` ← `Staff.instrument` (`graph.rs:817`). `PartDefinition` and `View` need no guard: nothing in the graph references them. Their own fields (`PartDefinition.staves` `graph.rs:1636`, `ViewDefinition.active_layers`) are **outbound**, and removing the holder strands nothing. **Count check.** `spec/PLAN_GMINOR_SCHEMA_MINOR.md:198` says "all ten kind/tag pairs." The epoch table at `:170`–`:180` lists 24–27 (4), 28–29 (2), 30 (1), 31 (1), 32–33 (2), 34 (1), 35–38 (4) = **fifteen**. **Binary Format chronology.** `spec/binary_format.tex` history rows: G2a 0.12.0 (`:3599`), G-minor 0.13.0 (`:3628`), G3a 0.14.0 (`:3643`). The ladder order is G1 → G2a → G-minor → G2b → G3a, so **G2b is missing** between 0.13.0 and G3a's row, and the accept-set raise `OperationEnvelopeBlock` 2→3 that G2b performed is recorded nowhere in the revision history — G3a's row at `:3657` merely observes the block "stays at 3 where genesis tranche G2b left it." **G1 has no standalone history row, by design.** It is recorded retroactively *inside* the G2a row: the principal marker is at `:3599` (`0.12.0 --- Genesis tranche G2a`), and G1 appears nested at `:3603` as "(`CreateInstrument`, genesis tranche G1 --- landed at `3b09595` with no matching entry here)". The row is explicit that G1 never got its own entry. Pin B6 is scoped accordingly. --- # PACKET A — the undo repair ## §A1. Pins **Pin A1 — five graph-removal arms.** In `materialize_graph_tombstones` (`reduce.rs:2768`), before the `_ => {}` catch-all, add: | Target | Removal | |---|---| | `TypedObjectId::StaffGroup(id)` | `score.staff_groups.retain(\|v\| v.id != *id)` | | `TypedObjectId::PartDefinition(id)` | `score.parts.retain(\|v\| v.id != *id)` | | `TypedObjectId::AnalysisLayer(id)` | `score.analysis_layers.retain(\|v\| v.id != *id)` | | `TypedObjectId::View(id)` | `score.views.retain(\|v\| v.id != *id)` | | `TypedObjectId::Instrument(id)` | `score.instruments.retain(\|v\| v.id != *id)` | Each mirrors the existing `Staff` (`:2804`) and `TimeSignature` (`:2807`) arms exactly. **No new `RepairRecord`**: `tombstone_undo_targets` already pushes one `CascadeDeleted` per target at `:5427` before calling this function, and none of the existing arms push more. **Pin A2 — three inbound-reference guards.** In `undo_strand_block` (`:5458`), before `_ => None`: | Target | Blocked by | Read from | |---|---|---| | `StaffGroup(g)` | a live `Staff` whose `group == Some(g)` | `self.staff_values` | | `AnalysisLayer(l)` | a live `ViewDefinition` whose `active_layers` contains `l` | `self.view_values` | | `Instrument(i)` | a live `Staff` whose `instrument == i` | `self.staff_values` | Return `Some((*target, referencer_obj))` on the first match in map order. **Pin A3 — the guards are ledger-based and ungated.** They read the carried-value maps (`staff_values` `:1001`, `view_values` `:1018`), **not** `self.graph`, and are **not** wrapped in `if self.graph.is_some()`. Rationale, and it is a real decision: the *create*-side referential preconditions **are** graph-gated (`create_staff:4137`, `create_staff_group:4266`, `create_view:4420`) because base-free reduction has no universe to resolve against. The *undo* side is different — the carried-value maps are populated by mints regardless of graph presence and are seeded from base when there is one, so under base-free reduction the ledger still knows that a live staff names this group. Gating the guard would let base-free undo strand a reference the ledger can plainly see. The two existing guards (`instance_staff`, `meter_change_chain`) are likewise ungated. **Stronger than the create side, and strictly safer.** **Pin A4 — liveness is read from `objects`, not from map presence.** The value maps are insert-only; `tombstone_undo_targets` never removes from them. A guard that treated map presence as liveness would let an already-tombstoned staff block an unrelated undo forever. Each guard MUST require `matches!(self.objects.get(&referencer_obj), Some(ObjectState::Live))`. **Pin A5 — same-transaction referencers do not block.** Each guard MUST also require `!targets.contains(&referencer_obj)`, matching the `Staff` guard's `!targets.contains(&iobj)` at `:5471` and the doc comment at `:5456`. A transaction that mints a group and a staff in it must be undoable whole. **Pin A6 — `restorations` is not consulted by the three new guards.** The `TimeSignature` guard consults it (`:5483`) because `MeterChange` has a write chain that undo may restore to a prior value. `Staff.group`, `Staff.instrument` and `ViewDefinition.active_layers` have **no modify operation at all** — that is the standing §1.1 condition — so no restoration can change the prospective post-undo value. The implementer MUST NOT cargo-cult the restoration lookup; its presence would be dead code asserting a write chain that does not exist. **Pin A7 — the value maps are not pruned on undo.** `tombstone_undo_targets` leaves `staff_group_values` and siblings untouched, matching the existing `staff_values` / `time_signature_values` / `instrument_values` precedent. This is safe **because every create reducer consults `self.objects` before its value map**. The `match self.objects.get(..)` lines, not the function declarations: `create_staff_group` `:4241`, `create_part_definition` `:4298`, `create_analysis_layer` `:4356`, `create_view` `:4395`, `create_staff` `:4109`, `create_instrument` `:4186`. A re-create after undo therefore hits `Some(ObjectState::Tombstoned)` → `TargetTombstoned` and never reaches the stale value. Row family **u4** signs that ordering, and it is the only family that does — a re-create assertion bolted onto u1 would be unsigned regression coverage, since u1's arm-deletion mutation cannot reach it (§A3). **Pin A8 — Instrument is in scope as collateral.** It is the same root cause, it already has normative undo semantics, and leaving it would mean knowingly shipping a fifth instance of a defect this packet exists to fix. **Pin A9 — no new `OperationKind`, tag, epoch, discriminant, wire layout, accept-set, or companion version.** This packet changes reducer behaviour only. `spec/binary_format.tex` is **not** touched by Packet A. If any of those surfaces appears in the diff, the packet is wrong. **Pin A10 — DECISIONS.md correction of record.** Replace the false paragraph at `crates/epiphany-ops/DECISIONS.md:1986` with a correction that states plainly: the branch **is** reachable via `UndoTransaction` over a transaction containing the mint; it was reachable for `CreateStaff` and `CreateInstrument` before G3a; the claim as written was wrong; and the guards and removal arms this packet adds are what make it correct. Do not silently delete it — the wrong claim was signed off and the record must say so. ## §A2. Touch table (Packet A) | # | File | Change | |---|---|---| | 1 | `crates/epiphany-ops/src/reduce.rs` | five arms in `materialize_graph_tombstones` (pin A1) | | 2 | `crates/epiphany-ops/src/reduce.rs` | three arms in `undo_strand_block` (pins A2–A6) | | 3 | `crates/epiphany-ops/src/reduce.rs` | doc comment on `undo_strand_block` `:5452` naming the three new blocks | | 4 | `crates/epiphany-ops/src/reduce.rs` | test rows u1a–u1e, u2a–u2c, u2bf-a–c, u2tomb-a–c, u3a–u3c, u4a–u4e | | 5 | `crates/epiphany-ops/DECISIONS.md` | pin A10 correction; record the five arms, three guards, and pin A3's ruling | | 6 | `spec/PLAN_GENESIS_OPS.md:25` | "commit pending" → `6c5e69f`; note the undo repair rides after it | | 7 | `spec/PLAN_GMINOR_SCHEMA_MINOR.md:190` | "introducing commit pending" → `6c5e69f` | | 8 | `spec/PLAN_GMINOR_SCHEMA_MINOR.md:198` | "all ten kind/tag pairs" → "all fifteen kind/tag pairs" | | 9 | `spec/CONTRACT_GENESIS_G3A_UNDO_REPAIR.md` | **this file** — status line DRAFT → RATIFIED, dated, naming the ratifying decision on pins A3/A6/A7 | **Row 9 exists because the contract governing a commit must land with it.** The file is currently untracked; without this row it would sit outside both packets while §A2 says "nothing else" and §E permits staging only touch-table files — a rule that excluded the rule-book. It joins Packet A. **Nothing else.** No `epiphany-bundle`, no `epiphany-core`, no spec `.tex`, no editor-track file, no `spikes/` entry. ## §A3. Test rows Every row names the mutation that MUST be **observed** to fail. Reasoning that a mutation "would fail" does not sign a row — the G3a rung already produced two such unsigned claims, both of which I had to run myself. **Twenty-two mutations in Packet A, three in Packet B: twenty-five total.** The six row families below are u1 (5), u2 (3), u2bf (3), u2tomb (3), u3 (3), u4 (5). ### u1a–u1e — removal and tombstoning (5 mutations) One row per family: `StaffGroup`, `PartDefinition`, `AnalysisLayer`, `View`, `Instrument`. Each mints the object inside a declared transaction against a non-empty base, undoes the transaction with `StrictInverse`, and asserts: - (i) the value is **gone** from the corresponding `Score` vector; - (ii) `objects` reports `ObjectState::Tombstoned` for its `TypedObjectId`. > **Killing mutation, five independent runs:** delete that family's arm from > pin A1's table. Assertion (i) must fail. Deleting one arm must not be > observed to kill another family's row — run them one at a time and record > five separate observations. **These rows do not sign pin A7.** A re-create assertion may be included as regression coverage, but the arm-deletion mutation says nothing about it; the ordering pin is signed by u4 below, and nowhere else. ### u2a–u2c — a live outside referencer blocks the undo (3 mutations) Graph-aware (`reduce_operation_set_onto`). In each, transaction T mints the referent; a **separate, non-transactional** op mints the referencer; undo T under `StrictInverse` MUST be `Conflicted` and the referent MUST remain in its `Score` vector. | Row | T mints | Outside op mints | |---|---|---| | u2a | `StaffGroup g` | `Staff s` with `group: Some(g)` | | u2b | `AnalysisLayer l` | `ViewDefinition v` with `active_layers` ⊇ `[l]` | | u2c | `Instrument i` | `Staff s` with `instrument: i` | > **Killing mutation, three independent runs:** delete that family's arm > from pin A2. The effect becomes `Applied` and the reference strands. ### u2bf-a–u2bf-c — the guards hold base-free (3 mutations) — signs pin A3 The same three shapes reduced through **`reduce_operation_set`** (`reduce.rs:674`), with no base `Score`. Assert the effect is `Conflicted`. There is no graph to assert against — `MaterializedState` carries no score — so these rows assert the effect and the `objects` state only. Base-free reduction skips every create-side referential precondition (`create_staff:4137`, `create_view:4420`), so the referencer mints unconditionally and its value lands in `staff_values` / `view_values` regardless. That is exactly the situation pin A3 exists to cover: the ledger plainly knows the reference, and the undo must refuse. > **Killing mutation, three independent runs:** wrap that family's guard arm > in `if self.graph.is_some() { … } else { None }`. The u2bf row must go red > **while its u2 counterpart stays green** — that divergence is the whole > signature. Record both observations per run. ### u2tomb-a–u2tomb-c — a tombstoned referencer does not block (3 mutations) — signs pin A4 Graph-aware. Two transactions, undone in reverse order: 1. T1 mints the referent (`g` / `l` / `i`). 2. T2 mints the referencer naming it (`s` / `v` / `s`). 3. Undo **T2** — the referencer is tombstoned in `objects`, leaves its `Score` vector, and by pin A7 **its value is retained** in `staff_values` / `view_values`. 4. Undo **T1** — MUST be `Applied` (or `AppliedWithRepair`), and the referent MUST leave its `Score` vector. Step 3 is what creates the state pin A4 guards against: a value-map entry present while its `objects` state is `Tombstoned`. > **Killing mutation, three independent runs:** delete the > `matches!(self.objects.get(&…), Some(ObjectState::Live))` conjunct from > that family's guard. The dead referencer blocks, step 4 becomes > `Conflicted`, and the row goes red. ### u3a–u3c — same-transaction teardown is allowed (3 mutations) — signs pin A5 The mirror of u2a–u2c: put **both** the referent and the referencer inside T. Undo T MUST be `Applied` (or `AppliedWithRepair`), and **both** values MUST leave their `Score` vectors. > **Killing mutation, three independent runs:** drop the > `!targets.contains(…)` conjunct from that family's guard. The undo becomes > `Conflicted`. ### u4a–u4e — objects outranks the retained value map (5 mutations) — signs pin A7 One row per family. Take u1's state (minted in T, T undone, so the object is `Tombstoned` while its value map still holds the value by pin A7), then apply a **byte-identical re-carry** of the original `Create…`. It MUST yield `NoOpReason::TargetTombstoned` — **not** `AlreadyApplied`. Byte-identical is the load-bearing choice: it is the one carried value for which the retained map would return `identical == true` and produce `AlreadyApplied`, so it is the only re-carry that can distinguish the two orderings. A differing re-carry would yield a precondition no-op either way and sign nothing. > **Killing mutation, five independent runs:** in that family's reducer, > make the `Some(ObjectState::Tombstoned { .. })` arm fall through to the > value-map identity check instead of returning `TargetTombstoned` — i.e. > reorder so the retained value overrides the tombstone. The row must > observe `AlreadyApplied` and go red. ### Row-construction note `create_staff` preconditions instrument liveness at `:4137` and group liveness at `:4149`; `create_view` preconditions layer liveness at `:4420`. All are graph-gated, so they bind in the u2 / u2tomb / u3 / u4 rows and are skipped in u2bf. - **u2a, u3a, u2tomb-a** need a base carrying a live `Instrument`: the referencing `Staff` requires one, and the transaction under test mints a `StaffGroup`, not an instrument. - **u2c, u3c, u2tomb-c** need **no** base instrument — the instrument the staff names is the one their own transaction mints. - **u2b, u3b, u2tomb-b** need no base entity at all; `AnalysisLayer` and `ViewDefinition` reference nothing outside the pair. In every graph-aware row the referent must be minted **before** the referencing op in accepted order. A base-ingested instrument is `Live` in `objects` through `seed_from_graph` without any mint. ### Anti-traps A mutation that does not compile signs nothing. A mutation in an op that runs *before* the state under assertion cannot reach it — the t8b defect from G3a review. A mutation that leaves the row green signs nothing, and must be reported as such rather than reasoned around. Confirm each mutation produces a **red test**, then restore by editing the source back, never by `git checkout` or `git stash`. ## §A4. Gate (Packet A) - `cargo test --workspace` — full pass, count reported and compared to the 1429 baseline at `6c5e69f`, with the delta explained by the new rows. - `cargo clippy --workspace --all-targets` — zero warnings. - `cargo fmt --check` — clean. - Whitespace, per §C — **`git diff --cached --check` after staging**, then the scoped committed-range check after the commit. - All **twenty-two** Packet A mutations **observed** red and restored, each reported with its actual failing test name. u2bf additionally reports its paired u2 row staying green. --- # PACKET B — P13-S17, the Binary Format chronology Separate commit. The stack is unpublished, so the true chronology can be restored rather than patched over. ## §B1. Pins **Pin B1 — file P13-S17** in `spec/PASS13_CANDIDATES.md`: *Binary Format revision history omitted genesis tranche G2b; the accept-set raise `OperationEnvelopeBlock` 2→3 reached the normative tables but never the history.* Record that G2b's own contract touch row 27 required "version, Revision History row" and that the rung was signed off without it — the gate did not catch a documentation MUST because nothing tests the history. **P13-S17 lands RESOLVED, in the same commit that files it.** Its ledger disposition is not left open: pins B2–B4 restore the chronology and pins B5–B6 add the guard that makes the omission recurrence-detectable, so the entry is filed and closed by Packet B itself. It is filed rather than merely fixed because the candidate ledger is the record of *how the gate failed*, and a silent repair would erase that. Contrast **P13-S15** and **P13-S16**, which remain open by design because their fixes are sequenced to later rungs. **Pin B2 — restore the chronology.** Ladder order is G1 → G2a → G-minor → G2b → G3a, so: | Document version | Event | Where | |---|---|---| | 0.12.0 | G2a | `binary_format.tex:3599`, unchanged | | 0.13.0 | G-minor | `:3628`, unchanged | | **0.14.0** | **G2b — new row** | inserted after the G-minor row | | **0.15.0** | G3a | `:3643`, renumbered from 0.14.0 | The title line at `:243` moves to **0.15.0** with a description matching G3a (it already names G3a; only the number changes). **Pin B3 — the G2b row must state what G2b actually did.** Read `spec/CONTRACT_GENESIS_G2B_TUNING.md` and commit `13c3d2f` and write the row from them, not from memory. It must name at minimum: `OperationKind` / `OperationKindTag` **34** (`SetTuningContext`), epoch **10**, the payload as the five-field subset `epiphany_core::TuningContextSettings` rather than the full graph type, and — the omission that motivates this packet — the accept-set raise **`OperationEnvelopeBlock` 2→3**, the first accept-set move since G2a explicitly recorded staying at 2. **Pin B4 — regenerate `spec/binary_format.pdf`** from the amended source, using the repository's existing build path. **Pin B5 — a scoped history guard, in `epiphany-testkit`.** A new test that reads `spec/binary_format.tex` and **slices to the `\chapter{Revision History}` section only**. Follow the loading precedent in `crates/epiphany-testkit/tests/requirement_labels.rs` (`std::fs` from a path relative to the manifest dir) or `text_projection_grammar.rs`'s `include_str!` — either, but state which. **Pin B6 — presence of a rung *name* is not enough, and version numbers are forbidden.** Two constraints that pull against each other, and the guard must satisfy both. *Why bare name-presence fails:* "G2b" already occurs **inside the Revision History chapter** at `binary_format.tex:3657`, in the G3a row's sentence "`OperationEnvelopeBlock` stays at 3 where genesis tranche G2b left it." A guard asserting only that "G2b" appears in the slice stays **green after the new row is deleted**. It would be born dead. *Why version literals are also wrong:* encoding "0.14.0" would pin a number this packet is itself moving, and the next chronology correction would have to edit the guard — the stale hand-maintained parallel list failure (`PLAN_GMINOR_SCHEMA_MINOR.md:156`). *Which rungs have principal markers:* **G2a, G-minor, G2b, G3a — not G1.** G1 has no standalone row and this packet does not authorize inventing one (pin B2 lists exactly four rows, one of them new). G1 is recorded retroactively inside the G2a row at `:3603`, and that row states outright that G1 landed "with no matching entry here." A guard demanding a G1 principal marker would be **born red** against a document B2 leaves correct — the guard would be wrong, not the spec. The guard MUST therefore assert: 1. a **distinct principal marker** for each of **G2a, G-minor, G2b, G3a**, matching the row form — the rung name immediately preceded by the row's `---` separator, e.g. `--- Genesis tranche G2b`. Prose mentions cannot satisfy this: `:3657` says "where genesis tranche G2b left it" and `:3603` says "genesis tranche G1 --- landed at", where the separator *follows* the name rather than preceding it. 2. **ordering** — marker offsets strictly increasing within the slice: `G2a < G-minor < G2b < G3a`. 3. at least one **B3-specific content anchor** for the G2b row — `SetTuningContext`, discriminant `34`, and the accept-set raise to `OperationEnvelopeBlock` `3` — searched **only within the G2b row segment**, i.e. the span from the G2b marker to the next marker (or the slice end). Unbounded searching would let G3a's row, which names both `OperationEnvelopeBlock` `3` and G2b, satisfy the anchor after the G2b row is deleted — reintroducing the exact hole this pin exists to close. **G1 is deliberately unguarded.** Record that in the test's own comment, citing `:3603`, so a later reader does not "fix" the omission by adding a fifth marker assertion and rediscovering this contradiction. > **Killing mutations, three independent runs:** > (a) delete the newly added G2b row entirely — the marker assertion goes > red, *and* it must be confirmed that a name-only guard would have stayed > green here, which is the finding this pin encodes; > (b) strip the accept-set-raise clause from the G2b row, leaving its > marker — the content anchor goes red; > (c) move the G2b row after the G3a row — the ordering assertion goes red. > > All three MUST be **observed**. Packet B therefore carries **three** > mutations, not one. ## §B2. Touch table (Packet B) | # | File | Change | |---|---|---| | 1 | `spec/PASS13_CANDIDATES.md` | P13-S17 (pin B1) | | 2 | `spec/binary_format.tex` | new G2b row; G3a 0.14.0 → 0.15.0; title `:243` → 0.15.0 | | 3 | `spec/binary_format.pdf` | regenerated | | 4 | `crates/epiphany-testkit/tests/` | the scoped history guard (pins B5–B6) | **Nothing else.** Packet B touches no reducer and no crate but the testkit. ## §B3. Gate (Packet B) - Full workspace test, clippy, fmt, and the §C whitespace checks as in §A4. - All **three** pin-B5/B6 mutations **observed** red and restored, including the confirmation under (a) that a name-only guard would have stayed green. - The regenerated PDF's title page reads 0.15.0. --- ## §C. Whitespace checking — the hole this packet closes `git diff --check -- crates/ spec/` is what the G3a rung used, and it is **blind to untracked files**: it reported clean while this very contract sat untracked. It is also blind to what is already staged. Per packet, in order: 1. Stage the touch-table files explicitly. Never `git add -A`. 2. **`git diff --cached --check`** — catches whitespace in exactly what is about to be committed, untracked-and-now-staged files included. 3. Commit. 4. **`git diff --check ..HEAD -- crates/ spec/`** — a scoped committed-range check confirming the landed commit is clean. Step 4 is deliberately path-scoped. The unpushed range fails `git diff --check` at `spikes/editor-toolkit/round1-oracle/ORACLE_SUMMARY.md:147`, an editor-track file outside this packet's authorization. Scoping keeps that pre-existing failure from masking a real one in `crates/` or `spec/`, and keeps this packet from being tempted to "fix" a file it must not touch. --- ## §D. Report requirements (both packets) State, per packet: the test count before and after; every mutation with the **observed** failing test name; anything found that the contract did not anticipate. If a pin turns out to be wrong or unsatisfiable, **stop and say so** rather than working around it — pin A3 and pin A6 are rulings, not suggestions, and pin A6 in particular forbids an addition that would look like diligence. ## §E. Boundary — unchanged and absolute These MUST NOT be read, written, or staged: `spec/PLAN_EDITOR_APP.md`, `spec/CONTRACT_EDITOR_*.md`, `spec/ANALYSIS_GENESIS_PERSISTENCE.md`, `spec/ANALYSIS_TEXT_RUN_PRIMITIVES.md`, `spec/DRAFT_T4_FIXTURE_RECIPE.md`, `crates/epiphany-editor-gui/goldens/*.png`, `crates/epiphany-render-svg/**`, `crates/epiphany-glyphs/**`, `crates/epiphany-testkit/benches/editor_pipeline.rs`, the entire `spikes/` tree, the unstaged root `Cargo.toml` change, and `.claude/worktrees/`. The narrow editor authorization granted for the G3a packet (`epiphany-editor-core/src/barriers.rs`, `epiphany-layout-ir/src/barrier.rs`) was spent by that packet and **does not carry forward**. This packet authorizes no editor-crate change of any kind. Stage only the files in the two touch tables, explicitly. Never `git add -A`.