epiphany/spec/HANDOFF_2026-08-07.md

18 KiB

Handoff — 2026-08-07

Written at ada751c, pushed to origin/main. Working tree clean, no stashes, no unpushed commits on any branch.

Scope note, stated first because it bounds §2. 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 the view from outside the boundary plus what to ask that session for. §0, §1, §3 and §4 are verified.


§0. Transfer manifest — what git clone does NOT carry

The good news first, because it is most of the risk retired. spikes/ is tracked (106 files), so the T4 spike moves with the repository. The editor-t4 worktree is clean — zero uncommitted, zero untracked, zero unpushed. No stashes anywhere. Every commit on every local branch is already on origin. There is no unpushed work on this machine.

What a fresh git clone will nevertheless not give you:

0.1 Gitignored files that exist locally

File Size Status
HANDOFF.md (repo root) 18 KB, last touched 2026-06-21 Not regenerable — carry it. Deliberately gitignored as "a planning briefing, not committed"
ALL_DECISIONS_AND_READMES.md 99 KB Regenerable — it is a concatenation of the tracked per-crate DECISIONS.md and README.md
out.svg 50 KB Regenerable — cargo run -p epiphany-render-svg --example render_fixture
target/, spec/*.aux etc. Build artifacts; let them rebuild

Note the root HANDOFF.md is a different, older document from this one (spec/HANDOFF_2026-08-07.md, which is tracked and will transfer normally).

0.2 Local git state that is not on the remote

  • The branch ref editor-t4-packet2b does not exist on origin. origin has only refs/heads/main. Its commits are safe — they are reachable through the f33673d merge — but the branch pointer is local. If that thread intends to keep working on that branch, push it before wiping this machine: git push -u origin editor-t4-packet2b.
  • The worktree is local by nature. Recreate on the other machine with: git worktree add .claude/worktrees/editor-t4 editor-t4-packet2b.
  • .claude/settings.local.json is untracked — local permissions/settings.

0.3 Outside the repository entirely

  • Agent memory: ~/.claude/projects/-home-jeans-Repos-active-epiphany/memory/ — 21 files, 228 KB, including the track notes both threads have accumulated (format-epoch-track, editor-app-plan-track, editor-core-editing-track, genesis-ops-track, pass12-g-pass, schema-major-1-track, and the method/lesson notes). This lives outside the repo and will not move with it. Copy the directory to the same path on the other machine.
  • Session/conversation state for both threads cannot transfer at all. That is the irreducible loss in a machine move, and it is the reason this document and the other thread's own handoff exist.

0.4 Environment (see §3 for detail)

Toolchains 1.95.0 + 1.85, a TeX Live with xelatex and fontspec, and the GUI apt packages. None of it is in the repo.


§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_VERSION0.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-S27spec/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 assertionsverify_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-S16spec/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 <doc>.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 <crate> 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. Order of operations for the move

4.1 Before wiping this machine

  1. git push -u origin editor-t4-packet2b — the only ref that exists here and not on the remote (§0.2). Its commits are safe either way; the pointer is not.
  2. Copy off the four local-only items: root HANDOFF.md, .claude/settings.local.json, and the out-of-repo ~/.claude/projects/-home-jeans-Repos-active-epiphany/memory/ directory (§0.1, §0.3). ALL_DECISIONS_AND_READMES.md and out.svg regenerate.
  3. Confirm both sessions have written their handoffs — this one covers §1; the editor/T4 thread's own is still owed (§2).

4.2 On arrival

  1. git clone, then restore the memory directory to the same path, then git worktree add .claude/worktrees/editor-t4 editor-t4-packet2b if that thread is resuming there.
  2. Install the environment in §3, including xelatexpdflatex fails outright on fontspec.
  3. Reproduce the green baseline in §3.5 before starting new work. 1570 passing. This thread's whole method depends on a known-green baseline to mutate against; if the number differs, reconcile that first.

4.3 Then, in priority order

  1. Dispatch P13-S27. The only unblocked item on the critical path, its contract is complete, and landing it discharges the three inherited obligations and ends the no-canonical-base interval that currently constrains every other piece of bundle work (§1.2).
  2. Then P13-S16, dispatchable the moment S27 lands.
  3. Take a ruling on S8 (reject one spelling vs. keep both canonical) whenever convenient — it needs a decision, not an implementation, and blocks nothing in the meantime.