From ada751cc9f241bcbefeed26c077cd4fdf3b7f9cf Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Fri, 7 Aug 2026 13:20:52 -0400 Subject: [PATCH] Handoff for the machine move, and two stale ledger openers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spec/HANDOFF_2026-08-07.md: state of the spec / Pass-13 / format-epoch thread at be244df, the live constraint that no bundle may carry a canonical base until P13-S27 lands, the S28 -> S27 -> S16 chain with S27's three inherited obligations, the working agreements that are not derivable from the code, and the environment notes the other machine needs (xelatex not pdflatex, the 1.95.0/1.85 toolchain pins, the cargo fmt --all trap and why its --check form is safe). Section 2 covers the parallel editor/T4 thread and is explicitly bounded: those files were out of bounds for this session all along, so it records only what shared git history shows plus leads to verify, and says plainly that it is not a substitute for that session's own handoff. It does name the one place the threads can collide — the canonical-base interval — which neither side can see from its own side. Ledger: P13-S27 and P13-S28 both still opened with "open" while their resolutions sat further down the cell. S27 is UNBLOCKED and dispatchable; S28 is IMPLEMENTED. The cells are appended to rather than rewritten, so an opener can lag the truth by several rungs; the handoff records that as a reading hazard. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV --- spec/HANDOFF_2026-08-07.md | 292 +++++++++++++++++++++++++++++++++++++ spec/PASS13_CANDIDATES.md | 4 +- 2 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 spec/HANDOFF_2026-08-07.md diff --git a/spec/HANDOFF_2026-08-07.md b/spec/HANDOFF_2026-08-07.md new file mode 100644 index 0000000..8fc4431 --- /dev/null +++ b/spec/HANDOFF_2026-08-07.md @@ -0,0 +1,292 @@ +# Handoff — 2026-08-07 + +Written at `be244df`, pushed to `origin/main`. Working tree clean. + +**Scope note, stated first because it bounds everything below.** This handoff is +written from the **spec / Pass-13 / format-epoch** session. A second session has +been committing to this repository in parallel, and its files were placed +explicitly out of bounds for this one — `spec/PLAN_EDITOR_APP.md`, +`spec/CONTRACT_EDITOR_*.md`, `spec/ANALYSIS_*.md`, `spec/DRAFT_T4_FIXTURE_RECIPE.md`, +the whole `spikes/` tree, `crates/epiphany-editor-gui/**`, +`crates/epiphany-render-svg/**`, `crates/epiphany-glyphs/**`, and the root +`Cargo.toml`. **§2 is therefore not an assessment of that work.** It is what is +visible from outside the boundary, plus a precise list of what to ask that +session for. Do not treat §2 as authoritative the way §1 is. + +--- + +## §1. This thread — spec, Pass 13, format epoch + +### 1.1 What just landed + +The container format major became **1**, and the header now carries a +provenance **epoch** rather than only a wire-layout discriminator. + +| Commit | What | +|---|---| +| `79a2507` | P13-S27 contract (DRAFT, blocked) | +| `818a16f` | P13-S28 filed as the critical path | +| `8bb917d` | Format-epoch contract ratified | +| `973297e` | Amended pre-dispatch: pin 3c, touch rows 10/11, gate 8 | +| `bc06706` | **The rung**: container major 1 | +| `be244df` | Pin 3b's projection refusal made symmetric | + +`FORMAT_MAJOR` 0 → 1, `FORMAT_MINOR` 1 → 0. `FixedHeader::decode` classifies +three ways through a named `FormatEpoch` (legacy / current / unsupported) carried +on the header, so major 0 is decoded **deliberately as legacy**, not rejected. + +**Why the major carries it.** The header never changes after creation +(`req:format:fixed-header-integrity`), so the epoch is a property of the file's +creation that no later commit can confer. That immutability is exactly what +disqualified `FORMAT_MINOR` as a provenance field — a legacy bundle committing a +freshly validated base keeps its old minor forever — and exactly what makes the +major sound. Old readers already fail closed on an unknown major, so that half +needed no new mechanism. + +Normative text: `core_spec.tex` §"The Container Epoch" + +`req:format:container-epoch`; `binary_format.tex` header table (0.17.0); +`text_projection.tex` `req:textproj:roundtrip` (0.14.0). + +### 1.2 The live constraint — read this before touching bases + +**Until P13-S27 lands, no bundle anywhere may carry a canonical base** — not in +production, not in tests, not in the conformance suite. Four rules compose to +that: `Bundle::create` already rejected a base-bearing manifest; matrix row 3 +refuses committing one into major 0; row 6i refuses committing one into major 1; +rows 2 and 5i refuse *opening* one in either epoch. + +Consequences you will hit immediately if you forget: + +- A base-bearing `Bundle` is **unconstructible**. Fixtures must be hand-built + images — see `craft_image` / `craft_image_with_base` in + `crates/epiphany-bundle/src/bundle.rs`, which write header, manifest, and + superblock bytes directly through the public `encode()`s. +- Two refusal tests are unit tests over `Manifest`, not end-to-end, for exactly + this reason, and say so in their doc comments. +- Conformance criterion 4's canonical-base wiring is **suspended**, marked in + `crates/epiphany-testkit/src/roundtrip.rs` with a comment naming P13-S27. Its + serialize → load → decode → reserialize cycle still runs via a direct + `ChunkRef` read. **Do not "restore" it by re-homing the snapshot to + `acceleration_snapshots`** — nothing in `open` or `verify_canonical_chunks` + reads that field, so it would verify nothing while looking like coverage. Pin + 3c forbids it explicitly. + +Three errors exist, none of which may degrade to read-only: +`LegacyBundleHasCanonicalBase` and `LegacyBaseIntroductionRejected` (permanent, +both name **repack**), and `ReductionAuthorityUnavailable` (temporary, names +**P13-S27**, must **not** say repack — a major-1 container is already the right +epoch). + +### 1.3 Text projection lost a capability, deliberately + +Base-bearing documents no longer round-trip through text. All three sides refuse +— projection, parsing, and a dedicated `SerializeError::CanonicalBaseUnsupported`. +None of the three pre-existed; an earlier contract draft wrongly claimed +serialization already refused. + +- `COMPANION_VERSION` → **0.14.0** (`crates/epiphany-textproj/src/lib.rs`). +- Corpus rebuilt: **20 vectors**, 10 accept / 10 reject, **ten** rejection + classes, `canonical_bases` reach **2 → 0**. +- The base spelling survives only as the `canonical_base_present` reject vector, + built through the crate-private `render_text_document`. **That function is the + one intentional hole in the refusal** and exists solely so a negative vector + can contain the spelling it asserts is refused. Keep it private. + +### 1.4 Chain state + +``` +format-epoch rung (LANDED) → P13-S27 (UNBLOCKED, dispatchable) → P13-S16 (blocked on S27) +``` + +**P13-S27** — `spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md`, still DRAFT, now +dispatchable. Its pin 2a was **resolved from outside** by the format rung's pin +8, exactly as pin 2a's own prohibition required: *legacy bases are refused by +container epoch, never by version arithmetic.* The number collision it feared — +a pre-S27 base carrying `1` versus a rebuilt S16 base carrying `1` — never has to +be adjudicated, because the two can never meet: the former exists only in a +major-0 container, refused before any version is compared. + +S27 **owes three inherited items**, written as required tests in its contract so +they cannot be discharged in prose: + +1. Convert **both** interim refusals (open-side and commit-side) to real + capability validation. Converting one leaves the hole the format rung's own + review found. +2. **M8's deferred laundering demonstration** — with pin 3b's text refusal + removed, show that a base-bearing document whose raw version happens to match + the authority serializes into a major-1 container indistinguishable from a + validated one. Never observed, only reasoned about. +3. **Pin 3c's two suspended conformance assertions** — `verify_canonical_chunks`'s + base branch with its `base.hash != base.root.hash` cross-check, and the + reopened manifest carrying the base. Restoring them means **deleting the + suspension marker**; if the marker is still in the tree when S27 reports, the + restoration did not happen. + +**P13-S16** — `spec/CONTRACT_P13S16_PROJECTION.md`, complete and ratifiable as a +plan, blocked purely on sequencing now rather than on an open question, for the +first time since it was filed. + +### 1.5 Other open Pass-13 candidates + +Ledger: `spec/PASS13_CANDIDATES.md`. Ten rows still read **open**: S4, S8, S9, +S16, S18, S19, S23, S24, S25, S26. Beyond those, **S27 is UNBLOCKED and +dispatchable** and **S28 is IMPLEMENTED** — both status openers were corrected in +this handoff commit, because each still began "open" while its resolution sat +further down the cell. Worth knowing the shape of that staleness: the ledger's +status cells are appended to rather than rewritten, so **the opener can lag the +truth by several rungs**. Scan the whole cell, not its first clause. + +**S8 is the one needing a ruling, not an implementation.** It is a semantic fork: +two byte spellings are currently both accepted. Normalizing one on encode +violates `req:binfmt:decode-vectors`' injectivity rule, so the repair must either +**reject one spelling** or **explicitly retain both as distinct canonical +values**. Nothing else in the ledger is waiting on a decision like this. + +### 1.6 Working agreements that must survive the move + +These were established through this session and are not derivable from the code: + +- **Contracts are ratified through adversarial review rounds before dispatch, + and frozen after.** A defect found during execution is **reported, not patched + in place** — that is how pin 3c came to exist rather than being absorbed + silently. +- **Subagent claims are verified independently before anything is committed.** + This session's agents were substantively accurate but got three things wrong: + one misattributed four test failures to "a concurrent session" (it was this + session's own spec edit), one missed a fifth failing suite entirely, and the + implementation left pin 3b's guard on the unreachable path. All three surfaced + only because the work was re-run rather than relayed. +- **Mutation-first.** Every regression is verified by re-introducing its bug and + *observing* the failure. Reasoning that a mutation would fail signs nothing. A + **compile error is not a test failure**. +- **Staging is explicit, never `git add -A`**, and the touch table is the staging + allowlist — which is why a file that must change but isn't listed silently + drops out of the commit. + +### 1.7 The recurring defect, named so it can be watched for + +**Searching one spelling and concluding about all sites.** Four instances in this +rung alone: + +- grepped a **hex-encoded** corpus for plaintext, got 0, nearly reported it + unaffected; +- grepped `(0 13 0)`, missed a normative `version~0.13.0`; +- searched for `ReductionAlgorithmVersion(` **constructor** calls to prove no + writer existed — a *propagating* path never constructs; +- ran `grep … | head -14` and asserted a universal negative from truncated output. + +Before believing a zero, ask **what this search would miss if the claim were +false**, and never pipe through `head` when the conclusion is a universal +negative. + +**And its sibling:** a guard placed on the path the spec sentence *names* rather +than on every path a caller can reach. In `be244df` the named path +(`document_from_bundle`) was additionally the unreachable one, so the only +reachable entry point was unguarded — and the new corpus vector was being built +straight through the hole. + +--- + +## §2. The other thread — editor / T4 spike (BOUNDED VIEW ONLY) + +**I could not read any of this thread's files.** What follows is inferred from +shared git history and from notes carried in earlier sessions. Verify all of it +against that session before relying on any of it. + +Visible in shared history: a `f33673d` merge of `editor-t4-packet2b` ("the T4 +spike's Round 2 text packet"), `e2979df` ("Packet 2B: both candidates consume the +frozen text apparatus"), and `694d135` ("Packet 2B apparatus: the neutral +candidatekit and the check-5 oracle"). That reads as a **toolkit-selection spike +running two candidates against a neutral harness** with a frozen text apparatus +and scored checks. + +Carried notes (**stale, from before this session**; treat as leads only): + +- `spec/PLAN_EDITOR_APP.md` holds the rulings and a tranche ladder T1a → T4, with + all three Ruling-A prerequisites (W1/W2/W3) discharged and **T4 the toolkit + spike** as the next item. +- An `epiphany-editor-core` crate exists with a hit-test contract and an edit + loop, gated by a conformance `[7c]` UI-seam item. +- `epiphany-engrave` + `epiphany-render-svg` carry a renderer-vs-stub scaffold. +- Golden PNGs live in `crates/epiphany-editor-gui/goldens/`, gated in CI by the + `golden-gate` feature. + +**Ask that session for**, since none of it is visible from here: the current T4 +candidate scores and whether a selection was made; whether Packet 2B closed or +has open rounds; what `spikes/` currently contains and whether any of it is meant +to graduate into `crates/`; and whether anything there depends on the +canonical-base interval in §1.2 — that is the one place the two threads can +collide, and neither side can see the collision from its own side. + +**One known cross-thread interaction, already handled:** this thread's spec edits +move hardcoded counts in `crates/epiphany-testkit/tests/requirement_labels.rs` +(`CORE_REQUIREMENT_COUNT`, `SUITE_REQUIREMENT_COUNT`, `SUITE_LABEL_COUNT`). Any +`.tex` requirement added by either thread breaks four tests there until those +constants move. It is in no contract's touch table. + +--- + +## §3. Reproducing the environment on the other machine + +### 3.1 Toolchains + +`.github/workflows/ci.yml` pins **stable 1.95.0** for lint/GUI/conformance and +**1.85** as the MSRV floor. The MSRV job runs +`cargo test --workspace --exclude epiphany-editor-gui --all-targets` plus +`--doc`; excluding the GUI crate is what keeps `resvg`'s raster stack out of the +MSRV dependency closure. + +### 3.2 LaTeX — the one non-obvious dependency + +The spec builds with **`xelatex`, not `pdflatex`**. `pdflatex` fails immediately: +`fontspec` requires XeTeX or LuaTeX. Build with: + +``` +cd spec && latexmk -xelatex -interaction=nonstopmode .tex +``` + +`core_spec.tex` needed a **second pass** before newly added labels resolved; +check the log for `undefined references` rather than trusting the first exit +code. Six PDFs are tracked in git and must be rebuilt whenever their `.tex` +changes. + +### 3.3 GUI build deps (Linux) + +`libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev +libssl-dev`. + +### 3.4 The formatting trap + +**Never run `cargo fmt --all`** while a parallel spike workspace is present — it +reaches the root crates through path dependencies and reformats across +workspaces. Use `cargo fmt -p ` for writes. Note the asymmetry: CI runs +`cargo fmt --all -- --check`, and the **check** form is safe; it is the *writing* +form that crosses. + +### 3.5 Green baseline to reproduce first + +``` +cargo test --workspace # 1570 passing, 0 failed +cargo clippy --workspace --all-targets -- -D warnings # clean +cargo fmt -p epiphany-bundle -p epiphany-textproj -p epiphany-testkit --check +``` + +If `cargo test --workspace` is not 1570 on arrival, reconcile that **before** +starting new work — this thread's whole method depends on a known-green baseline +to mutate against. + +--- + +## §4. Recommended first moves after the move + +1. Reproduce the green baseline in §3.5. +2. Get the other session's own handoff for §2. This one is deliberately not a + substitute for it. +3. **Dispatch P13-S27.** It is the only unblocked item on the critical path, its + contract is complete, and it discharges the three inherited obligations plus + ends the no-canonical-base interval that currently constrains every other + piece of bundle work. +4. Then **P13-S16**, which becomes dispatchable the moment S27 lands. +5. Take a ruling on **S8** (reject one spelling vs. keep both canonical) whenever + convenient — it needs a decision, not an implementation, and it blocks nothing + in the meantime. diff --git a/spec/PASS13_CANDIDATES.md b/spec/PASS13_CANDIDATES.md index 3d4d8ec..0b117cc 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) | **open, BLOCKED on P13-S28, and blocking P13-S16.** **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-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) | **open. The critical path — P13-S27 and P13-S16 are both 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 | +| 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-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 |