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 |