14 KiB
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
Bundleis unconstructible. Fixtures must be hand-built images — seecraft_image/craft_image_with_baseincrates/epiphany-bundle/src/bundle.rs, which write header, manifest, and superblock bytes directly through the publicencode()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.rswith a comment naming P13-S27. Its serialize → load → decode → reserialize cycle still runs via a directChunkRefread. Do not "restore" it by re-homing the snapshot toacceleration_snapshots— nothing inopenorverify_canonical_chunksreads 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_basesreach 2 → 0. - The base spelling survives only as the
canonical_base_presentreject vector, built through the crate-privaterender_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:
- 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.
- 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.
- Pin 3c's two suspended conformance assertions —
verify_canonical_chunks's base branch with itsbase.hash != base.root.hashcross-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 normativeversion~0.13.0; - searched for
ReductionAlgorithmVersion(constructor calls to prove no writer existed — a propagating path never constructs; - ran
grep … | head -14and 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.mdholds 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-corecrate exists with a hit-test contract and an edit loop, gated by a conformance[7c]UI-seam item. epiphany-engrave+epiphany-render-svgcarry a renderer-vs-stub scaffold.- Golden PNGs live in
crates/epiphany-editor-gui/goldens/, gated in CI by thegolden-gatefeature.
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. Recommended first moves after the move
- Reproduce the green baseline in §3.5.
- Get the other session's own handoff for §2. This one is deliberately not a substitute for it.
- 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.
- Then P13-S16, which becomes dispatchable the moment S27 lands.
- 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.