From 39287f8f4341bab4493b591f900fee69602e6349 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Fri, 7 Aug 2026 22:55:26 +0200 Subject: [PATCH] P13-S27 ratified: review round 1, nine findings, four blocking The contract had reached "dispatchable" with zero ratification rounds on record, against the standing rule that contracts go through adversarial review before dispatch. The format-epoch rung had four, and its fourth is what produced pin 3c. This is S27's first. Blocking: Inherited obligation 2 -- M8's laundering demonstration -- was in neither the test section nor the mutation plan, while that section's preamble claimed all three inherited obligations were "stated as tests so they cannot be discharged by prose". It was also ambiguous between temporarily lifting the text refusal and permanently restoring the capability, which differ by four touch rows and a COMPANION_VERSION bump. Ruled a mutation: the refusal is permanent, and the demonstration is now M7, with its expected outcome recorded as success rather than failure. Section 0.4 claimed "commit has 57 sites (including 2 in epiphany-editor-core)". That crate depends on core, ops and layout-ir -- not bundle -- and the word Bundle appears in its lib.rs zero times. The two hits are self.commit(...) resolving to its own method. A textual .commit( grep counted a same-named method in a crate that cannot reach Bundle. That is the third instrument failure recorded in that one section, and it was committed in the same paragraph as the method note warning about the second. Three independent stale list-counts: the test section's header said "names all four" over seven items, gate 1 said "four tests added", and three report items named five mutations, seven gate results and four tests. All replaced with "every item in section N". The delta was never a simple addition anyway -- three tests convert or extend existing format-rung tests, which nets zero. testkit/tests/requirement_labels.rs was absent from the touch table while pin 9 may move CORE_REQUIREMENT_COUNT from 213. Pin 9 must now decide explicitly whether it mints a label; touch row 12 carries the file conditionally. This is the escapee CLAUDE.md names, and it escaped the format-epoch rung too. Non-blocking: locator drift since 381c498 (bc06706 grew bundle.rs by 338 lines; correction table added, and pin 5's own :396-:399 confirmed unmoved); pin 2a's corpus evidence superseded by the 2 -> 0 rebuild; Bundle::open( 57 -> 60, create confirmed still 32; gate 6a widened to epiphany-testkit, which touch row 7 gives the real authority; and a missing commit-side positive test, added as test 8 -- obligation 1 warns that converting one branch leaves a hole, and that branch had none. Pins are now frozen. Documentation only; no code reads .md. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x --- spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md | 290 ++++++++++++++++++-- spec/PASS13_CANDIDATES.md | 2 +- 2 files changed, 263 insertions(+), 29 deletions(-) diff --git a/spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md b/spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md index 146d789..e3f4505 100644 --- a/spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md +++ b/spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md @@ -1,11 +1,37 @@ # Contract — P13-S27: the reduction version gets an outside witness -**Status:** DRAFT — **UNBLOCKED and dispatchable as of 2026-08-07**; **AMENDED -2026-08-07 (pin 10)**, before dispatch and before ratification. (Was: BLOCKED on -the format-epoch rung, `spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`, which at the time -was ratified and in implementation but had not yet landed.) **That rung landed at -`bc06706`**, with its pin-3b follow-up at `be244df`. Pins 1 and 3–10 are settled, -internally consistent, and ratifiable as a plan. +**Status:** **RATIFIED 2026-08-07** after **review round 1**, which returned nine +findings — four of them blocking — all now carried in the document; **AMENDED +2026-08-07** twice, both before dispatch: **pin 10** (the unsatisfiable escape +clause) and **round 1's nine**. **Not yet implemented. The pins are now frozen — +they may be executed, not edited.** A defect found during execution is +**reported, not patched in place**. + +(Was: DRAFT, BLOCKED on the format-epoch rung, +`spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`, which at the time was ratified and in +implementation but had not yet landed. **That rung landed at `bc06706`**, with +its pin-3b follow-up at `be244df`.) + +**Review round 1 — 2026-08-07, at `96b40b2`.** Run because this contract had +reached "dispatchable" with **zero** ratification rounds on record, against a +standing rule that contracts go through adversarial review *before* dispatch — +the format-epoch rung had four, and its fourth is what produced pin 3c. What +round 1 returned: + +| # | Finding | Disposition | +|---|---|---| +| 1 | Inherited obligation 2 was in **neither** §3 nor §4, though §3's preamble claimed all obligations were stated as tests | **M7** added; ruled a mutation, not a capability restoration | +| 2 | §0.4's *"`commit` has 57 sites (including 2 in `epiphany-editor-core`)"* — that crate has no `epiphany-bundle` dependency and the word `Bundle` appears in its `lib.rs` **zero** times | Corrected; recorded as the **third** instrument failure in §0.4 | +| 3 | §3's header, gate 1 and §7 items 1/2/4 each carried a stale count of the same lists | All replaced with "every item in §N"; gate 1 now reports three buckets | +| 4 | `requirement_labels.rs` absent from §2 while pin 9 may move `CORE_REQUIREMENT_COUNT` | Pin 9 must decide explicitly; **touch row 12** added, conditional | +| 5 | Locators verified at `381c498`; `bc06706` grew `bundle.rs` by 338 lines | Correction table in §0; pin 5's own `:396`–`:399` confirmed **unmoved** | +| 6 | Pin 2a cites `vectors.rs:353`/`:363`; the corpus was rebuilt to `canonical_bases` 2 → 0 | Evidence updated; disposition unchanged | +| 7 | `Bundle::open(` is **60**, not 57 | §0.4 table corrected; `create` confirmed still 32 | +| 8 | Gate 6a checked `textproj` only, while touch row 7 gives `testkit` the real authority | Scope widened to both | +| 9 | No **commit-side positive** test, though obligation 1 warns that converting one branch leaves a hole | **Test 8** added | + +**Pins 1 and 3–10 are settled and internally consistent.** Pin 2a is resolved +from outside (below); pin 2 is unchanged. **The pin 10 amendment**, recorded here so it is not read as the original: pin 10 made P13-S16's opening conditional on pin 2a being *"ratified and tested **within @@ -56,6 +82,30 @@ format-epoch rung → **P13-S27** → **P13-S16**, with no open question left in Read out of the working tree at `381c498`. Every line number confirmed by reading the line. +> **Locators re-verified 2026-08-07 in review round 1, at `96b40b2`.** The +> format-epoch rung (`bc06706`) landed *after* `381c498` and grew `bundle.rs` by +> **338 lines**, so the inline citations throughout this contract are as-of +> `381c498`. **This table is authoritative where the two disagree.** +> +> | Cited | Actual at `96b40b2` | Symbol | +> |---|---|---| +> | `bundle.rs:989` | **`:1024`** | `fn reduction_version_for` | +> | `bundle.rs:798` | **`:833`** | `commit_versioned`'s superblock stamp | +> | `bundle.rs:613`–`:621` | **`:622`ff** | `fn verify_canonical_chunks` | +> | `bundle.rs:939` | **`:974`** | `fn profile_is_understood` | +> | `bundle.rs:233`–`:240` | **`:205`ff** | `fn create` (base-bearing refusal) | +> | `error.rs:253` | **`:306`** | `UnsupportedCanonicalChunkMajor` | +> | `serialize.rs:119` | **`:143`** | `fn serialize_document` | +> | `serialize.rs:212`, `:219`–`:222` | **`:239`ff** | `fn build_manifest` | +> | `serialize.rs:347` | **`:379`** | `fn serialize_and_reopen` | +> | `roundtrip.rs:241` | **`:255`** | `assert_reduction_serialization_stable` | +> | `parse.rs:591` | **`:603`** | unbounded `u32` parse | +> +> **Confirmed still exact, and not to be "corrected":** `bundle.rs:396`–`:399` +> (**pin 5's own insertion point**), `:301` (`open`), `:87` +> (`SUPPORTED_PROFILE_MAJOR`), `:389` (`UnsupportedProfile`), `ids.rs:289`, +> `generators.rs:1628`/`:1651`. + ### 0.1 The defect is a tautology, not an absence `reduction_version_for` (`bundle.rs:989`) sets a new superblock's version from @@ -125,11 +175,13 @@ open, which is the gap this revision closes (pin 3a, pin 4a). > exists to fix, committed while scoping it: **an observation that cannot > support the claim drawn from it.** -**Reader surface — 57 `Bundle::open(` sites across 10 files:** +**Reader surface — 60 `Bundle::open(` sites across 10 files.** *(Was 57 at +`381c498`; the format-epoch rung added 3 in `bundle.rs`. Re-counted 2026-08-07 at +`96b40b2`.)* | Crate | Sites | |---|---| -| `epiphany-bundle` (`bundle.rs` 20, `fuzz.rs` 15) | 35 | +| `epiphany-bundle` (`bundle.rs` **23**, `fuzz.rs` 15) | **38** | | `epiphany-testkit` (`bundle_harness.rs` 11, `roundtrip.rs` 4, `benches/bundle.rs` 2, `tests/bundle_reopen.rs` 1) | 18 | | `epiphany-textproj` (`serialize.rs`, `project.rs`) | 2 | | `epiphany-bundle/tests/` | 2 | @@ -139,9 +191,28 @@ open, which is the gap this revision closes (pin 3a, pin 4a). `bundle/tests/crash_recovery.rs` 2, `textproj/serialize.rs` 1, `testkit/tests/bundle_reopen.rs` 1, `testkit/benches/bundle.rs` 1, `bundle/tests/manifest_selection.rs` 1, `bundle/fuzz.rs` 1. +*Re-counted 2026-08-07 at `96b40b2`: still 32, and every per-file figure above +still holds.* -**`commit` has 57 sites** (including 2 in `epiphany-editor-core`) — pin 3's -design keeps every one of them unchanged. +**`commit` sites** — pin 3's design keeps every one of them unchanged. + +> **CORRECTED 2026-08-07 in review round 1. The claim this paragraph made was +> *"`commit` has 57 sites (including 2 in `epiphany-editor-core`)"*, and the +> parenthetical is false.** `epiphany-editor-core` depends on `epiphany-core`, +> `epiphany-ops` and `epiphany-layout-ir` — **not** on `epiphany-bundle` — and +> the string `Bundle` does not appear in its `lib.rs` at all. Its two hits are +> `self.commit(...)` (`editor-core/src/lib.rs:1593`, `:1709`), resolving to its +> own `fn commit(&mut self, new: Vec) -> Result EditorError>` (`:1404`). A textual `.commit(` grep counted a same-named method +> in a crate that cannot reach `Bundle`. +> +> **This is the third instrument failure recorded in this section, and it was +> committed in the same paragraph as the method note above.** The first could not +> see a propagating path; the second asserted a universal negative from `head`- +> truncated output; this one resolved a method name without resolving the type it +> belongs to. Same defect, three shapes: **an observation that cannot support the +> claim drawn from it.** The executing agent MUST count `Bundle`-typed receivers, +> not the token `.commit(`. **Which capability each site supplies is NOT decided by crate dependency.** `epiphany-testkit` and `epiphany-textproj` depend on `epiphany-ops`, but that @@ -194,6 +265,17 @@ committed text-projection vectors carrying the same; `generators.rs:1628`/`:1651 emit `rng.range(0, 8)`. Injecting the real authority `0` at every site would reject part of the present corpus. +> **Evidence updated 2026-08-07 in review round 1 — the disposition is unchanged, +> the corpus is not.** `vectors.rs:353`/`:363` no longer exist. The format-epoch +> rung rebuilt the text-projection corpus to 20 vectors with `canonical_bases` +> reach **2 → 0**, so the two base-carrying vectors this pin cites are gone; the +> single surviving occurrence is `vectors.rs:326`, inside the +> `canonical_base_present` **reject** vector. Pin 2a's conclusion stands (it was +> settled from outside by the container epoch), but **pin 3b's fixture-surface +> reasoning must be re-derived against the current corpus** rather than against +> the two vectors named here. `generators.rs:1628`/`:1651` are unaffected and +> still emit `rng.range(0, 8)`. + Pin 3b handles the *fixtures* — they take named synthetic capabilities. It does **not** handle the real problem: @@ -321,6 +403,25 @@ naming S27 as what reopens them. Both are owed work here, not optional: refusal exists to prevent, and it has never been observed — only reasoned about. + > **RULED 2026-08-07 in review round 1: the removal is a MUTATION, not a + > capability restoration.** As written, *"with pin 3b's text refusal removed"* + > was ambiguous between temporarily lifting the refusal to observe what it + > prevents, and permanently restoring base-bearing text round-trip. The + > sentence's own next clause settles it — *"that is the false provenance the + > text refusal exists to prevent"* — and a guard is not permanently deleted in + > order to demonstrate why it is needed. **The format rung's text refusal is + > permanent and S27 does not lift it.** The demonstration is therefore + > **M7** in §4, removed and restored by hand-editing, and this obligation is + > discharged there rather than by a test. + > + > **Consequences of the ruling, stated so they are not re-litigated:** + > `COMPANION_VERSION` stays **0.14.0**; `parse.rs`, `vectors.rs`, + > `textproj/src/lib.rs` and `spec/text_projection.tex` are **NOT** touched by + > this rung and are deliberately absent from §2; and the corpus keeps + > `canonical_bases` reach **0**. A future rung may restore the capability — + > that is its own contract, with the four touch rows and the companion-version + > bump this one declines. + 3. **Two conformance assertions come back** (format-rung pin 3c). Criterion 4's bookkeeping-projection counterpart, `assert_reduction_serialization_stable` (`testkit/src/roundtrip.rs:241`), keeps its serialize → load → decode → @@ -437,6 +538,23 @@ behaviour normatively, and a Revision History row. There is **no** existing "requires rebuild" error language in either document — verified — so this is new prose, not an amendment. +**Whether this mints a new `\label{req:...}` MUST be decided explicitly, and +stated in the report. AMENDED 2026-08-07 in review round 1.** The pin said "add +the rejection behaviour normatively" without saying whether the behaviour gets +its own requirement label, and the two readings have different touch tables: + +- **If it mints a label**, `core_spec.tex`'s requirement count moves 213 → 214 + and `crates/epiphany-testkit/tests/requirement_labels.rs` **must** change — + `CORE_REQUIREMENT_COUNT = 213` (`:15`) is hardcoded and currently matches the + tree exactly. **Touch row 12 carries it.** +- **If it does not** — the prose lands under an existing requirement — row 12 is + unused and the report says so. + +**This is the escapee `CLAUDE.md` names by name**, and it escaped the +format-epoch rung's touch table too. A file that must change but is not listed +**silently drops out of the commit**, and the resulting failure surfaces on +someone else's branch. + **Pin 10 — the ledger. AMENDED 2026-08-07, before dispatch, on a finding from the machine-move review.** `spec/PASS13_CANDIDATES.md`: P13-S27 → RESOLVED, recording both rulings, the @@ -488,10 +606,27 @@ row may not record S16 as open until those land with this rung**; ratification o | 9 | `crates/epiphany-textproj/src/{serialize,project}.rs` | call sites, real authority | | 10 | `spec/core_spec.tex` (+ `.pdf`) | pin 9 | | 11 | `spec/PASS13_CANDIDATES.md` | pin 10 | +| 12 | `crates/epiphany-testkit/tests/requirement_labels.rs` | **conditional** — pin 9, *only if* it mints a new `\label{req:...}`; `CORE_REQUIREMENT_COUNT` (`:15`) then moves 213 → 214. Added in review round 1. If pin 9 mints no label, leave unmodified and say so in the report | + +**Row 12 is conditional, and that is deliberate.** `CLAUDE.md` names this file as +a recurring escapee, and it escaped the format-epoch rung's table. Carrying it +conditionally costs nothing if unused; omitting it costs a silent drop-out. + +**`spec/text_projection.tex`, `crates/epiphany-textproj/src/{parse,vectors}.rs` +and `crates/epiphany-textproj/src/lib.rs` are deliberately ABSENT** — see the +ruling under inherited obligation 2. M7 edits the refusals temporarily and +restores them by hand; nothing there is staged. **If any of those files shows up +in `git diff --cached`, M7 was not restored** and gate 4 must fail. --- -## §3. Required tests (pin 4's ruling names all four) +## §3. Required tests + +> **Header corrected 2026-08-07 in review round 1.** It read *"(pin 4's ruling +> names all four)"* while the list below carried seven items. The count went +> stale twice — test 5 was added when the first draft's writer-path omission was +> found, then tests 6 and 7 with pin 2a's resolution — and gate 1 inherited the +> stale figure. **Do not reconcile against a fixed number**; see gate 1. Named, permanent, in `epiphany-bundle`: @@ -514,8 +649,11 @@ Named, permanent, in `epiphany-bundle`: intact. A writer check that corrupts the document while refusing is worse than no check. -**Added 2026-08-07 with pin 2a's resolution — the inherited obligations, stated -as tests so they cannot be discharged by prose:** +**Added 2026-08-07 with pin 2a's resolution — inherited obligations 1 and 3, +stated as tests so they cannot be discharged by prose.** *(Corrected in review +round 1: this preamble previously claimed **all** the inherited obligations were +stated as tests. Obligation 2 was in neither §3 nor §4 — it is now **M7**, by +the ruling recorded beside it.)* 6. **`a_major_1_bundle_carrying_a_base_opens_when_the_authority_matches`** — the read-side half of the format rung's pin 3a, converted from temporary refusal @@ -529,11 +667,43 @@ as tests so they cannot be discharged by prose:** names this contract — if the marker is still in the tree when this rung reports, the restoration did not happen. +8. **`committing_a_canonical_base_succeeds_when_the_authority_matches`** — + **ADDED 2026-08-07 in review round 1.** Obligation 1 warns that converting + only one branch "leaves a hole exactly where the format rung's own review + found one", and the write-side **positive** branch had no test: test 1 is + read-positive, test 2 read-negative, test 5 write-negative. The missing branch + is precisely where the format rung's temporary refusal sits + (`bundle.rs:795` → `ReductionAuthorityUnavailable`, asserted by + `a_major_1_bundle_round_trips_and_refuses_to_introduce_a_base` at `:1787`). + Without this test, an implementation that converts the read side and leaves + `commit` refusing categorically passes every other test in this section. + Tests 2 and 3 must be **paired in review**: each asserts the other's error is *not* produced. A test that only checks its own variant cannot show the two paths are distinguishable, which is the whole point of pin 6. -Tests 6 and 2 stand in the same relation to each other. +Tests 6 and 2 stand in the same relation to each other. **So do tests 8 and 5**, +on the write side. + +**Several of these CONVERT existing tests rather than adding new ones — found in +review round 1, and the reason gate 1 no longer names a number.** The format rung +left two tests asserting the interim refusal, and S27 turns each into a matched +pair: + +| Existing test | Becomes | +|---|---| +| `opening_a_major_1_bundle_that_already_carries_a_base_is_refused` (`bundle.rs:1866`) | tests **6** and **2** | +| `a_major_1_bundle_round_trips_and_refuses_to_introduce_a_base` (`:1787`) | tests **8** and **5** | +| `a_corrupt_base_fails_as_malformed_before_any_epoch_error` (`:1840`) | extended into test **3** (adds the "*not* `CanonicalBaseRequiresRebuild`" assertion) | + +**And `ReductionAuthorityUnavailable` is deleted by this rung**, so every site +naming it must move or the crate will not compile: `error.rs:152` (variant), +`:232` (Display arm), `bundle.rs:422` and `:795` (construction), the doc comments +at `:1538` and `:1844`, and **five test assertions** at `:1740`, `:1774`, +`:1834`, `:1861`, `:1879`. The three negative assertions (`:1740`, `:1774`, +`:1861`) exist to prove the legacy and corrupt paths do **not** produce it — they +must be re-pointed at the error that replaces it, **not** deleted, or the +distinction they were written to hold is lost. --- @@ -569,12 +739,50 @@ must fail. Then narrow it to refuse *any* stale inherited base rather than only newly emitted or replaced one, and confirm an unrelated commit on an already-open bundle starts failing — signing that pin 3a's scope is deliberate. +**M7 — the laundering the text refusal prevents, finally observed. ADDED +2026-08-07 in review round 1; this discharges inherited obligation 2.** + +The format rung reasoned about this path and could never run it: its own pin 3a +refused every major-1 base commit categorically, so the observation was +unreachable. Under S27 a base commit succeeds or fails on its version, so it +becomes reachable for the first time. + +Temporarily remove the format rung's pin-3b text refusal — **all three sides**, +since removing one leaves the others refusing and the document never reaches the +writer: `serialize.rs:152` (`SerializeError::CanonicalBaseUnsupported`), +`project.rs`'s `project_text_document` refusal (`:579`), and the parser's +(`parse.rs:138`–`:147`). Then construct a base-bearing `TextDocument` whose raw +`reduction_algorithm_version` **happens to equal** +`CURRENT_REDUCTION_ALGORITHM_VERSION`, serialize it, and **observe** that the +resulting major-1 container is byte-indistinguishable from one whose base was +genuinely validated. + +**What the observation must show, or it has not been made:** that the capability +check does **not** fire — the document launders precisely *because* its number +matches, and no check can tell a coincidence from a rebuild. That is the whole +argument for the refusal, and it has never been run. + +**Restore all three refusals by hand-editing back**, never with git. Record the +result as a **demonstration**, not a guard: nothing in the shipped tree changes, +and the refusal is permanent (see the ruling under inherited obligation 2). + +**This is a mutation whose expected outcome is SUCCESS, not failure.** Every +other mutation here breaks a test; this one makes a refused path succeed, and +the finding is that it succeeds *silently*. Do not report it as a passing gate. + --- ## §5. Gate -1. `cargo test --workspace` — full pass; report the new total and the delta with - its cause (four tests added). +1. `cargo test --workspace` — full pass. **Report the new total and account for + the delta by category — do NOT reconcile against a fixed number.** *(Corrected + in review round 1: this item read "(four tests added)", a figure already stale + twice over, and §3's own header carried the same wrong count. The delta is not + a simple addition: three of §3's tests **convert or extend** existing + format-rung tests, which nets zero, while others are new.)* The baseline is + **1570**. Give the count in three buckets — net-new, converted-from-existing, + restored-assertions — and if they do not sum to the observed delta, **that is + a finding, not an arithmetic error to be papered over**. 2. `cargo clippy --workspace --all-targets -- -D warnings` → clean. 3. `cargo fmt -p epiphany-ops -p epiphany-bundle -p epiphany-testkit -p epiphany-textproj --check` → clean. **`cargo fmt --all` is forbidden.** @@ -586,10 +794,17 @@ already-open bundle starts failing — signing that pin 3a's scope is deliberate `grep -rnE "impl +Default +for +BundleCapabilities|derive\\([^)]*\\bDefault\\b[^)]*\\)[[:space:]]*(pub )?struct BundleCapabilities" crates/epiphany-bundle/src/` → **0 matches.** Run it against production source only; report the count, not a verdict. -6a. **No production composition path uses the fixture constructor:** - `grep -rn "synthetic_for_fixture" crates/epiphany-textproj/src/` shows matches - **only** inside `#[cfg(test)]` modules. Report each match with its enclosing - item. +6a. **No production composition path uses the fixture constructor.** *(Scope + widened in review round 1: this checked `epiphany-textproj` only, while touch + row 7 gives **`epiphany-testkit`** the real authority too — so a + `synthetic_for_fixture` leak there was ungated.)* Run over **both**: + `grep -rn "synthetic_for_fixture" crates/epiphany-textproj/src/ crates/epiphany-testkit/src/` + Report each match with its enclosing item. In `epiphany-textproj` every match + must be inside a `#[cfg(test)]` module. In `epiphany-testkit`, which is a + fixture crate whose non-test code legitimately builds fixtures, each match + must instead be justified against §0.4's rule: **only production composition + paths wrap the real constant**, and `roundtrip.rs` / `bundle_harness.rs` carry + both kinds. Name which kind each site is; an unclassified site is a finding. 7. `spec/vectors/decode_vectors.txt` unmodified; no schema major/minor moved. --- @@ -622,11 +837,30 @@ to 1 belongs to S16. ## §7. Report requirements -1. The five mutations, each with verbatim output (M4 as a recorded prohibition). -2. The seven gate results, each with its command. -3. The staged file list and the test-count delta with its cause. -4. The four required tests by name, each passing, with tests 2 and 3 shown to - produce *different* errors. -5. A count of call sites updated per crate, against §0.4's table — any - discrepancy is a finding. -6. Anything contradicting this contract. +*Counts corrected 2026-08-07 in review round 1 — items 1, 2 and 4 each named a +figure the document had outgrown. This is the same drift as §3's header and gate +1: **three independent stale counts of the same three lists.** Prefer "every item +in §N" to a number.* + +1. **Every mutation in §4** — currently **seven** (M1–M7), each with verbatim + output. **M4 is a recorded prohibition** (observed to compile, then reverted) + and **M7 is a demonstration whose expected outcome is success**. Neither is a + passing guard; do not report them as one. +2. **Every gate item in §5** — currently **eight** (1, 2, 3, 4, 5, 6, 6a, 7), + each with the command that produced it. +3. The staged file list, and the test-count delta in gate 1's three buckets. +4. **Every required test in §3** — currently **eight** — by name, each passing, + with tests 2 and 3 shown to produce *different* errors, and the same for + tests 6/2 and 8/5. +5. A count of call sites updated per crate, against §0.4's table **as corrected + in review round 1** (open **60**, create **32**) — any discrepancy is a + finding. Count `Bundle`-typed receivers, not the token `.commit(`. +6. **Confirmation that M7's three text refusals were restored**, and that none of + `text_projection.tex`, `textproj/src/parse.rs`, `textproj/src/vectors.rs` or + `textproj/src/lib.rs` appears in the staged diff. +7. **Whether pin 9 minted a new requirement label**, and therefore whether touch + row 12 was used. +8. **Confirmation that the pin-3c suspension marker naming this contract is gone + from `testkit/src/roundtrip.rs`.** If it is still in the tree, obligation 3's + restoration did not happen, whatever the prose says. +9. Anything contradicting this contract. diff --git a/spec/PASS13_CANDIDATES.md b/spec/PASS13_CANDIDATES.md index 0b117cc..675e0bc 100644 --- a/spec/PASS13_CANDIDATES.md +++ b/spec/PASS13_CANDIDATES.md @@ -122,5 +122,5 @@ evidence in isolation. | P13-S23 | **No filed candidate owns "place any anchor pair on a common timeline and measure musical distance along it" — P13-S18 previously mis-cited a narrower capability as its gate.** Two disjoint deficiencies, both owned by this candidate. (1) **No ordering.** The pair is not comparable under any of `measure20_comparable_order`'s five shapes c1-c5 (`invariants.rs:2457`) at all — whether the failure is in the **referent** (distinct `Event` ids; distinct `Measure` ids outside c3's `Start`+`Zero` restriction), the **variant or selector** (`Event` against `Measure`, `Measure` against `Region`, differing `pos`/`edge`), or the **clock** (`Musical` against `WallClock`, including inside `measure20_offset_order`, `:2419`) — this is what invariant 20's A4 and B4 are made of. (2) **Ordering without a usable delta.** The pair IS comparable and still yields no musical distance: c3 supplies a vector index (an order, never a distance), and c5 compares two `WallClock`s, and `measure20_musical_delta` (`:2522`) never returns a `WallClock` delta (`:2527`) — this is what invariant 20's B5 is made of. Scoping this as merely "anchors of differing shapes" or "not directly comparable under c1-c5" would exclude B5 entirely — S5 (distinct-id `Measure` `Start`/`Zero`) is c3-comparable and S1 (`WallClock` measures, `WallClock` meter changes) is c5-comparable, and both still reach B5 — an earlier draft of this filing made exactly that narrower mistake. **Explicitly broader than P11-C5**: P11-C5 (`PASS11_WORKLIST.md:159`) is a re-anchoring proximity metric that resolves "when the graph-mutation phase tracks resolved positions", and covers narrowly the two-distinct-`Event`s case (`CONTRACT_GENESIS_G3B_MEASURE.md:223`, `effect.rs:139`-`:142`'s `PositionOutsideRegion` Reserved note); P13-S23 is the timeline itself, whatever positions get placed on it. Names its dependents: invariant 20's A4, B4 and B5, and `PositionOutsideRegion`'s Reserved status | `spec/CONTRACT_P13S18_MATRIX.md` pin 10 (filed 2026-07-31 during the same rung that corrected P13-S18's over-narrow P11-C5 citation) | **open.** No code owed by this rung. Closing it needs the deferred common-timeline/duration machinery — once a `Measure` end, a distinct-id `Measure`/`Event` referent, or an `Event` position on a wall-clock-placed region can be placed on a common timeline with a musical distance, invariant 20's A4/B4/B5 residue and `PositionOutsideRegion`'s Reserved status shrink together | | P13-S25 | **The committed decode corpus's numbered tag rows lock byte→byte, not variant→byte — one row already has the property the other thirty-nine lack.** `ops/src/vectors.rs:206`–`:209` emits one row per tag as `format!("tag_{:02}", tag.discriminant())` carrying `[discriminant]`: **both the name and the payload derive from the value alone**, so `tag_32` asserts that `0x20` round-trips and never that `SetCanvasLayoutDefaults` is 32. The `Registered` row (`:210`–`:217`) is different — its name is the hard-coded string `"registered"` while its bytes are computed from the variant, so the frozen literal at `spec/vectors/decode_vectors.txt:80` binds the association. **Disposition B of P13-S22:** give the numbered rows the same property. It **does** catch the coordinated permutation — by exactly the `Registered` mechanism, with the committed text serving as the independent statement — and it propagates the property to every implementation that reads the cross-impl corpus, which an in-crate Rust test cannot do | `spec/CONTRACT_P13S22_TAGLOCK.md` (disposition B, considered and deferred during the 2026-07-31 ruling; filed rather than left as a closing remark, per the same discipline that moved P13-S22 out of P13-S15's resolved row) | **open. Complementary to P13-S22, not a replacement for it, and not a re-litigation of it.** P13-S22 landed disposition A (`tag_wire_discriminants_are_golden`, `payload.rs:2730`), which fails **by variant name inside the crate**. B cannot supply that: its failure is still *"spec/vectors/decode_vectors.txt is stale. Regenerate: …"* (`testkit/src/vectors.rs:224`) — the misleading diagnosis P13-S22 was filed about — even though the diff text would now name variants. **What B buys is cross-implementation reach; what it costs is churn in a committed artifact other implementations pin.** Both are wanted; neither substitutes for the other. Sequencing note: run B's own signing mutation as the coordinated permutation (literals *and* declaration lines), since the literal-only form is caught today by row ordering and proves nothing | | P13-S26 | **A doc comment in shipped code claims a specification repair that never landed, and the claim is guarded on the code side and nowhere on the specification side.** `crates/epiphany-core/src/invariants.rs:69`–`:71` enumerates invariant 10's four reference classes and states that *“genesis tranche G3a repairs this prose to name what the check body already enforced”*. **It did not.** `core_spec.tex:6570`–`:6572`, the normative enumeration item 10, still reads only *“Every cross-cutting structure's references resolve to extant objects in the graph, except where explicit re-anchoring rules permit transient dangling states during edits”* — naming neither a staff's declared instrument, a staff's group, a staff group's members, a part's staves, a view's active layers, nor any of the meter/time-signature references the Rust doc lists and the check body enforces. The repair landed in the Rust doc comment only. **The asymmetry is the defect's sharp edge:** the Rust doc block is protected by a grep-assert, `t12_invariant_10_doc_comment_names_the_four_reference_classes` (`invariants.rs:4554`, needles at `:4562`–`:4566`), so the side that is *wrong about the other* is the side that is **locked**, while the side that is actually stale is unguarded | this file (found 2026-07-31 during P13-S16 reconnaissance, while verifying that row's invariant-10 citations; no ledger entry covered it) | **open.** **Not a live incorrectness** — the check body is correct and enforces every class; only the normative prose under-describes it, and only the doc comment lies about that. **A P13-S9 instance**, and filed deliberately as one: the loud form (a dangling citation) is caught by `requirement_labels.rs`, and this quiet form — a *true-sounding claim about another document's state* — is caught by nothing. **`invariants.rs:69`–`:71` MUST NOT be “corrected” on its own.** It is currently the only artifact in the tree pointing at the `core_spec.tex` gap; softening the Rust claim in isolation would make the specification defect invisible and convert a caught defect into an uncaught one — which is P13-S9's stated failure mode verbatim. **Repair both sides in one rung**, and consider whether the LaTeX enumeration deserves the grep-assert its Rust mirror already has | -| P13-S27 | **The reduction-algorithm-version machinery is self-referential, so the one check that would detect a canonical-semantics change necessarily passes.** `core_spec.tex:11614`–`:11617` is normative — *"Snapshots produced under an earlier algorithm version cannot be used as canonical bases under a later one without rebuilding"* — and `:14369`–`:14372` states that replicas at differing versions *"may produce different canonical states from the same operation set."* The machinery to enforce it appears to exist: `ReductionAlgorithmVersion` (`bundle/src/ids.rs:291`) is a superblock wire field (bytes `68..72`, `superblock.rs:20`); `reduction_version_for` (`bundle.rs:989`) sets a new superblock's value; and `open` (`bundle.rs:396`–`:399`) rejects a mismatch. **But the writer sources the value from the canonical base's own self-report** (mapping the base's `reduction_algorithm_version` through `unwrap_or_default()`), **and the reader compares it only against the superblock that value seeded.** Nothing compares either against the semantics the running implementation actually implements. **The check is not vacuous** — it catches a corrupt or tampered base whose version disagrees with its superblock — but it **necessarily passes for a conformingly propagated stale base**, which is precisely the case the requirement exists to prevent. Supporting: **no constant or accessor anywhere names the implementation's current reduction semantics**, and `ids.rs:288`–`:289` states that *"the algorithm catalog itself lives in `epiphany-ops`"* while nothing of the kind exists in that crate — **a second instance of P13-S26's pattern**, a doc comment asserting a false fact about another module | `spec/CONTRACT_P13S16_PROJECTION.md` pin 0 (found 2026-07-31 while scoping P13-S16, which is a canonical reduction-semantics change and therefore the first rung to need this guarantee; filed in the same ledger edit as the row it blocks) | **UNBLOCKED 2026-08-07 — the format-epoch rung landed; dispatchable, and still blocking P13-S16.** (Was: open, BLOCKED on P13-S28.) **Scoped 2026-07-31 as `spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md` (DRAFT, not dispatchable).** Rulings taken: a typed `BundleCapabilities` required at both `Bundle::open` and `Bundle::create` and carried on the `Bundle` — no default, so every caller states the semantics it implements — and outright rejection on mismatch via a new `CanonicalBaseRequiresRebuild` error, not read-only and not an integrity anomaly. Storing the capability keeps all 57 `commit` sites unchanged; only `open` (57 sites) and `create` (32) move. **The scoping also falsified this row's first reading that the writer path was test-only:** `epiphany-textproj`'s `serialize_document` (`serialize.rs:119`) and `project.rs:936` are production paths that copy a base's `reduction_algorithm_version` verbatim into a fresh `SnapshotRef`, which `commit_versioned` then stamps into the superblock (`bundle.rs:798`) — so production mints self-consistent stale documents **without ever calling `open`**, and the capability must govern writers too. **What blocks it:** contract pin 2a. Baseline authority `0` does not preserve the corpus (`serialize.rs:327` stamps `1` and round-trips it; `vectors.rs:353`/`:363` likewise), and once P13-S16 moves the authority to `1`, a pre-S27 base that happens to carry `1` is **indistinguishable from a legitimately rebuilt one** — a raw `u32` carries no provenance. Four dispositions are recorded there; `FORMAT_MINOR` as a provenance carrier was proposed and **rejected** (the header never changes after creation, `core_spec.tex:10799`, so a legacy bundle committing a freshly validated base keeps its old minor forever; and a minor change may only append append-safe discriminants, `:12258`, not alter acceptance semantics). The surviving requirement — provenance must ride a container property **old readers cannot silently accept** and **a later commit cannot inherit unchanged** — is a format-epoch design, filed as **P13-S28**. **Scope of the claim, deliberately narrow:** this establishes that the **current implementation** has no detection mechanism. It does **not** establish that no reduction-semantics change in the project's history was ever detectable — that needs a history audit not yet done, and the stronger sentence is deliberately not written here. **What closing it requires:** an authority naming the semantics this build implements, and a rejection-or-rebuild path when a base disagrees with it. Until then any rung changing canonical reduction semantics can record its break in prose but cannot make stale bases unusable — which is why P13-S16's contract is complete, ratifiable as a plan, and **not dispatchable**. **Method note:** an earlier draft of S16's pin 0 claimed no writer path existed at all. That was false, and the way it was false is the point — the search behind it looked for `ReductionAlgorithmVersion(` constructor calls, which cannot find a path that propagates an existing value without constructing one. The instrument could not observe the thing it was used to rule out. **UNBLOCKED 2026-08-07:** the format-epoch rung landed and its pin 8 **resolves pin 2a** — reduction-version authority is meaningful only in major-1 containers, so legacy bases are refused by container epoch and never by version arithmetic. The collision pin 2a identified never has to be adjudicated: a pre-S27 base carrying `1` and a rebuilt S16 base carrying `1` are indistinguishable as numbers but can never meet, because the former exists only in a major-0 container, refused at the epoch boundary before any version is compared. The `u32` never has to carry provenance because the container does. **S27 now additionally owes three inherited items** (both interim refusals converted to validation, M8's deferred laundering demonstration, pin 3c's two suspended conformance assertions), recorded in its contract as required tests | +| P13-S27 | **The reduction-algorithm-version machinery is self-referential, so the one check that would detect a canonical-semantics change necessarily passes.** `core_spec.tex:11614`–`:11617` is normative — *"Snapshots produced under an earlier algorithm version cannot be used as canonical bases under a later one without rebuilding"* — and `:14369`–`:14372` states that replicas at differing versions *"may produce different canonical states from the same operation set."* The machinery to enforce it appears to exist: `ReductionAlgorithmVersion` (`bundle/src/ids.rs:291`) is a superblock wire field (bytes `68..72`, `superblock.rs:20`); `reduction_version_for` (`bundle.rs:989`) sets a new superblock's value; and `open` (`bundle.rs:396`–`:399`) rejects a mismatch. **But the writer sources the value from the canonical base's own self-report** (mapping the base's `reduction_algorithm_version` through `unwrap_or_default()`), **and the reader compares it only against the superblock that value seeded.** Nothing compares either against the semantics the running implementation actually implements. **The check is not vacuous** — it catches a corrupt or tampered base whose version disagrees with its superblock — but it **necessarily passes for a conformingly propagated stale base**, which is precisely the case the requirement exists to prevent. Supporting: **no constant or accessor anywhere names the implementation's current reduction semantics**, and `ids.rs:288`–`:289` states that *"the algorithm catalog itself lives in `epiphany-ops`"* while nothing of the kind exists in that crate — **a second instance of P13-S26's pattern**, a doc comment asserting a false fact about another module | `spec/CONTRACT_P13S16_PROJECTION.md` pin 0 (found 2026-07-31 while scoping P13-S16, which is a canonical reduction-semantics change and therefore the first rung to need this guarantee; filed in the same ledger edit as the row it blocks) | **UNBLOCKED 2026-08-07 — the format-epoch rung landed; dispatchable, and still blocking P13-S16.** (Was: open, BLOCKED on P13-S28.) **Scoped 2026-07-31 as `spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md` (DRAFT, not dispatchable).** Rulings taken: a typed `BundleCapabilities` required at both `Bundle::open` and `Bundle::create` and carried on the `Bundle` — no default, so every caller states the semantics it implements — and outright rejection on mismatch via a new `CanonicalBaseRequiresRebuild` error, not read-only and not an integrity anomaly. Storing the capability keeps all 57 `commit` sites unchanged; only `open` (57 sites) and `create` (32) move. **The scoping also falsified this row's first reading that the writer path was test-only:** `epiphany-textproj`'s `serialize_document` (`serialize.rs:119`) and `project.rs:936` are production paths that copy a base's `reduction_algorithm_version` verbatim into a fresh `SnapshotRef`, which `commit_versioned` then stamps into the superblock (`bundle.rs:798`) — so production mints self-consistent stale documents **without ever calling `open`**, and the capability must govern writers too. **What blocks it:** contract pin 2a. Baseline authority `0` does not preserve the corpus (`serialize.rs:327` stamps `1` and round-trips it; `vectors.rs:353`/`:363` likewise), and once P13-S16 moves the authority to `1`, a pre-S27 base that happens to carry `1` is **indistinguishable from a legitimately rebuilt one** — a raw `u32` carries no provenance. Four dispositions are recorded there; `FORMAT_MINOR` as a provenance carrier was proposed and **rejected** (the header never changes after creation, `core_spec.tex:10799`, so a legacy bundle committing a freshly validated base keeps its old minor forever; and a minor change may only append append-safe discriminants, `:12258`, not alter acceptance semantics). The surviving requirement — provenance must ride a container property **old readers cannot silently accept** and **a later commit cannot inherit unchanged** — is a format-epoch design, filed as **P13-S28**. **Scope of the claim, deliberately narrow:** this establishes that the **current implementation** has no detection mechanism. It does **not** establish that no reduction-semantics change in the project's history was ever detectable — that needs a history audit not yet done, and the stronger sentence is deliberately not written here. **What closing it requires:** an authority naming the semantics this build implements, and a rejection-or-rebuild path when a base disagrees with it. Until then any rung changing canonical reduction semantics can record its break in prose but cannot make stale bases unusable — which is why P13-S16's contract is complete, ratifiable as a plan, and **not dispatchable**. **Method note:** an earlier draft of S16's pin 0 claimed no writer path existed at all. That was false, and the way it was false is the point — the search behind it looked for `ReductionAlgorithmVersion(` constructor calls, which cannot find a path that propagates an existing value without constructing one. The instrument could not observe the thing it was used to rule out. **UNBLOCKED 2026-08-07:** the format-epoch rung landed and its pin 8 **resolves pin 2a** — reduction-version authority is meaningful only in major-1 containers, so legacy bases are refused by container epoch and never by version arithmetic. The collision pin 2a identified never has to be adjudicated: a pre-S27 base carrying `1` and a rebuilt S16 base carrying `1` are indistinguishable as numbers but can never meet, because the former exists only in a major-0 container, refused at the epoch boundary before any version is compared. The `u32` never has to carry provenance because the container does. **S27 now additionally owes three inherited items** (both interim refusals converted to validation, M8's deferred laundering demonstration, pin 3c's two suspended conformance assertions), recorded in its contract as required tests. **RATIFIED 2026-08-07 after review round 1** — run because this contract had reached "dispatchable" with **zero** ratification rounds on record, against the standing rule that contracts go through adversarial review before dispatch. Round 1 returned **nine findings, four blocking**, all now carried in the contract. **Correcting this row's own preceding clause:** the three inherited items were *not* all "recorded as required tests" — obligation 2, M8's laundering demonstration, appeared in **neither** the contract's test section nor its mutation plan, while that section's preamble claimed all of them were tests. It is now **M7**, and ruled a **mutation** rather than a capability restoration: the format rung's text refusal is permanent, `COMPANION_VERSION` stays 0.14.0, and the text-projection corpus keeps `canonical_bases` reach 0. The other blocking three: §0.4's `commit`-site count counted a same-named method in `epiphany-editor-core`, **a crate with no `epiphany-bundle` dependency at all** — the third instrument failure recorded in that one section; three independent stale list-counts (the test-section header, gate 1, and three report items) all naming figures the document had outgrown; and `testkit/tests/requirement_labels.rs` missing from the touch table while pin 9 may move `CORE_REQUIREMENT_COUNT` 213 → 214 — **the escapee `CLAUDE.md` names by name**, which also escaped the format-epoch rung. Non-blocking: locator drift since `381c498` (`bc06706` grew `bundle.rs` by 338 lines; pin 5's own `:396`–`:399` confirmed unmoved), pin 2a's corpus evidence superseded by the 2 → 0 rebuild, `Bundle::open(` 57 → **60**, gate 6a's scope widened to `epiphany-testkit`, and a missing **commit-side positive** test now added as test 8. **Pins are frozen; dispatchable** | | P13-S28 | **No container property distinguishes a document produced under a validated reduction authority from one produced before any authority existed — and the two candidates that look like they would, cannot.** P13-S27 installs an authority and validates it at read and write time, but cannot state what to do with a canonical base that predates the authority: a raw `ReductionAlgorithmVersion` is a bare `u32` (`bundle/src/ids.rs:291`) carrying no provenance, and the text-projection parser accepts an unbounded one from a document (`textproj/src/parse.rs:591`), so no numeric convention — including a deliberately high epoch — is safe from a hand-authored or third-party document declaring it. **`FORMAT_MINOR` does not work either, for two independent reasons:** the header *"never changes after the file is created"* (`core_spec.tex:10799`–`:10800`) and `commit_versioned` publishes only a superblock (`bundle.rs:791`), so a legacy bundle that commits a base S27 just validated keeps its old minor **permanently** — rejecting minor-≤1 bases would then reject a base the authority itself accepted, and accepting them leaves S16's `1` ambiguous; and `core_spec.tex:12258`–`:12262` limits a minor change to appending append-safe discriminants and calls it backward-compatible, whereas making a previously-valid base newly rejectable is a **semantic acceptance change**, with current readers ignoring minor entirely (`header.rs:119` gates on major alone) so the boundary would bind only readers that already comply. **The requirement that survives:** provenance MUST ride a container property that **old readers cannot silently accept** and that **a later commit cannot inherit unchanged** | `spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md` pin 2a (filed 2026-07-31; the disposition S27 cannot make from inside itself) | **IMPLEMENTED 2026-08-07 (`bc06706`, fix `be244df`). Was the critical path; both P13-S27 and P13-S16 were blocked on it.** **This rung must own all five, and none may be deferred into S27:** (1) an **old-reader rejection boundary** — pre-boundary readers must fail closed rather than silently open a document whose safety check they do not run; (2) **provenance that survives commits correctly**, i.e. is not inherited unchanged by a later generation and is not lost by one; (3) **legacy-base rebuild/repack behaviour**, stated for real artifacts rather than assumed away; (4) **every writer path, including text projection** — `serialize_document`, `project.rs`, and the committed `.txt` vectors, since a text document can declare any version; (5) **the exact format-version and compatibility consequences**, most plausibly a **major**-version boundary or a generation-scoped attestation paired with an incompatibility boundary. **Not a sub-pin of S27 and must not drift into it** — S27's pin 2a carries an explicit prohibition against being amended into a disposition without its own ratification round. **Scoped and RATIFIED 2026-07-31 as `spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`** after four adversarial review rounds — 11 pins, 11 tests, 11 mutations, 15 touch rows, 7 gate items. **This row is now a dependency record only; the work lives there and P13-S28 does not execute as a Pass 13 rung.** Rulings taken: the carrier is the **format major** (`FORMAT_MAJOR` 0 → 1, `FORMAT_MINOR` 1 → 0), decoded three ways through a named `FormatEpoch` rather than a bool, with **no** generation-scoped attestation in this epoch; legacy resolves to **hard rejection, not read-only**; and an eight-row epoch matrix in which a major-0 bundle with no base may open, one carrying a base is rejected, and one attempting to *add* a base is rejected and told to repack — **the non-inheritance rule that `FORMAT_MINOR` could not express**. All five things this row required the rung to own are pinned: old-reader boundary (pin 2), commit-surviving provenance (pin 3), legacy repack (pins 4, 5), every writer path including text projection (pins 3b, 6), and the exact format/compatibility consequences (pins 1, 7). **Three findings from the review rounds that changed the rung's shape**, none of them visible at filing: (1) **it cannot stamp major 1 before S27's writer enforcement exists**, so pin 3a temporarily refuses *both* boundaries — opening a major-1 bundle already carrying a base, and committing one into it — through a third, temporary `ReductionAuthorityUnavailable` error that must name P13-S27 and must **not** name repack; (2) **text projection launders provenance straight through the boundary** (`serialize_document` stages a carried base into a fresh bundle and `build_manifest` writes it), resolved as **symmetric document-level refusal** — projection, parsing and a new dedicated `SerializeError` variant, none of which existed to be "retained" — which forces `COMPANION_VERSION` 0.13.0 → **0.14.0** and rebuilds the committed corpus to **20 vectors, ten rejection classes, `canonical_bases` reach 2 → 0**, a real and stated capability loss; (3) **corruption precedence binds in both epochs** — a corrupt major-1 base must still fail as malformed, never as the *temporary* authority error a user would reasonably retry. **IMPLEMENTED 2026-08-07** — amended once before dispatch (pin 3c, touch rows 10/11, gate 8) after reconnaissance found pin 3a's refusals reaching a conformance criterion through a file the touch table did not carry. All 11 tests landed under their contract names, all 11 mutations run and observed, workspace green at 1569. **P13-S27 is unblocked and P13-S16 remains blocked on S27** — pin 8 resolved S27's open pin 2a (legacy bases are refused by container epoch, never by version arithmetic), and S27 additionally inherits three obligations recorded in its own contract: converting **both** interim refusals to validation, M8's deferred laundering demonstration, and pin 3c's two suspended conformance assertions. **Two touch-table gaps found during execution, both of the same shape** — a `.tex` requirement addition moves hardcoded counts in `testkit/tests/requirement_labels.rs`, and a companion-version bump moves a second normative version literal spelled `version~0.13.0` rather than `(0 13 0)`; neither file was in any touch table, and the second was caught only because `requirements_name_only_this_companion_version` exists. **A third gap was caught in review, after the rung was committed:** pin 3b's projection refusal had been implemented only on the **bundle** side (`document_from_bundle`), leaving the public `project_text_document` free to emit a `(canonical-base ...)` line for a directly constructed `TextDocument` — text the parser then rejects. A projector that can produce what the parser refuses is exactly the asymmetry pin 3b exists to close, and the refusal is unreachable through a `Bundle` during the interval anyway, so the *only* reachable half was the unguarded one. The public projector now returns `Result` and refuses; a crate-private `render_text_document` retains the base spelling for the one legitimate caller, the `canonical_base_present` negative vector. **The lesson is the rung's own recurring one:** a guard placed on the path that happened to be named, rather than on every path a caller can reach |