epiphany/spec/HANDOFF_2026-08-07.md

29 KiB
Raw Blame History

Handoff — 2026-08-07

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

POST-S27 UPDATE — 2026-08-09. Read this before the rest of the document.

P13-S27 landed and was accepted at 4df8e25, after eight independent reviews of the staged execution. This handoff is a dated snapshot of 2026-08-07 and is preserved as written; each section S27 invalidated is marked at its own site, and this block is the only place their new state is given — the sites point here rather than restating it.

Section Said Now
§1.2 the live constraint no bundle anywhere may carry a canonical base LIFTED. A base is accepted when its reduction_algorithm_version matches the running authority and refused with CanonicalBaseRequiresRebuild { base, current } when it does not — read side in open, write side in commit/commit_versioned
§1.2 conformance criterion 4 canonical-base wiring suspended RESTORED. Both lapsed assertions are back and the suspension marker is gone from testkit/src/roundtrip.rs. §1.2's warning against re-homing the snapshot to acceleration_snapshots stands permanently — it was never about the suspension
§1.2 the third error ReductionAuthorityUnavailable, temporary DELETED, replaced by CanonicalBaseRequiresRebuild { base, current }. The two permanent repack errors are unchanged, and §1.2's "none may degrade to read-only" rule carries to the new error verbatim — a stale base is the wrong materialization, not a restricted-but-correct view
§1.4 chain state P13-S27 (UNBLOCKED) → P13-S16 (blocked on S27) P13-S27 (LANDED)P13-S16 (UNBLOCKED, DRAFT — needs ratification)
§2.6, §4.3 collision T1b and P13-S27 must not fly together RESOLVED — S27 has landed. T1b is NOT thereby free: it remains blocked on Ruling B blocker (ii), versioned decode — see §2.3, which is still accurate
§3.5, §4.2, §4.3 baseline 1570 passing Moved when S27 landed. The count is in CLAUDE.md's Green baseline and is deliberately not stated here, or anywhere else in this document — it was kept in three places and went stale in all three
§4.3 item 7 dispatch P13-S27 done
§4.3 item 9 P13-S16 dispatchable the moment S27 lands unblocked — but its contract is a DRAFT and needs ratification before dispatch

The authority is epiphany_ops::CURRENT_REDUCTION_ALGORITHM_VERSION, currently 0. Any change to a canonical reduction verdict MUST bump it, and no mechanism can detect a semantics change — the discipline is the entire guarantee. P13-S16 is the first rung that must move it to 1.

Scope. Written from the spec / Pass-13 / format-epoch session. A second session has been committing to this repository in parallel on the editor / T4 track, and its files were out of bounds for this one for most of the work. That boundary was lifted on 2026-08-07 for the purpose of this handoff, so §2 is now read from the sources rather than inferred — but it is still written by someone who did not do that work, and it is not a substitute for that session's own account of its reasoning. Every section is verified against the tree or the documents; where a claim is second-hand, it says so.


§0a. If you are an agent picking this up cold

Read CLAUDE.md at the repo root first — it carries the standing rules (contract discipline, mutation-first, the git and cargo fmt constraints, the green baseline, the one live constraint). This document is the state; that one is the method.

Then: §4 for what to do, §1 or §2 for whichever track you are on, §3 only if the environment is not already working.

Two things that do not arrive via git clone and that change how much you know: the agent memory directory (§0.3 — it holds the accumulated track notes and method lessons) and this session's conversation. If the memory directory was not restored, treat CLAUDE.md and this file as the whole inheritance and say so rather than guessing at intent.


§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 exists only locally. Resolved 2026-08-07 — pushed; origin now carries both refs. Recorded because it was a real exposure: its commits were safe via the f33673d merge, but the pointer was not, and a wiped machine would have lost the branch name the spike's rounds are organised under.
  • 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 — LIFTED 2026-08-09 by P13-S27 (4df8e25)

This section is a dated record, not a live rule. The constraint it states held only until S27 landed, and S27 has landed. The rest of §1.2 — down to §1.3 — describes the pre-S27 world and must not be followed. What replaced it is in the POST-S27 UPDATE at the top, which is the only place that state is given; it is not restated here.

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 — SUPERSEDED 2026-08-09 by P13-S27 (4df8e25)

This whole section is a dated record. The chain below, S27's status, and the three inherited items it lists all describe the pre-S27 tree; the three items are discharged. The current chain is in the POST-S27 UPDATE at the top and is not restated here.

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. (Row states below are as of 2026-08-07 and are superseded for S27 and S16 — see the POST-S27 UPDATE at the top. The ledger itself is authoritative for every row.) 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 — the editor track and the T4 toolkit spike

Boundary lifted 2026-08-07; this section is now read from the sources, not inferred. Primary documents: spec/PLAN_EDITOR_APP.md (1,024 lines — the plan and Rulings AE), spec/CONTRACT_EDITOR_T4_SPIKE.md (Revision 6, 16 design pins), spikes/editor-toolkit/DECISIONS.md (the running findings log), and the per-tranche CONTRACT_EDITOR_T* set.

2.1 Where the track is

The editor's model is built; its shell is a demo. epiphany-editor-core is ~6.7k lines with 93 tests behind conformance gate [7c]; the hit-test provenance contract and the SVG renderer are golden-locked; epiphany-editor-gui is 598 lines of self-described demo binary, disposable by design. The discipline is that every capability lands headless in editor-core first, proven at the IR boundary, before any GUI wiring.

Ladder: T1a (visual golden harness) landed; T1b (document layer) is blocked; T2 selection v2, T3 note-entry caret, T4 the rendering tranche (open now, via the spike), T4b incremental materialization, T5 playback.

Rulings: A (direct vector canvas over ResolvedLayoutIR) granted; C (golden harness) granted; D (new epiphany-editor-app crate created at T4) granted conditionally; E (clipboard fragment format) granted; B (document/persistence) still not granted — see 2.3.

All three Ruling-A prerequisites are discharged: W1 IR primitive ownership (dd33b34), W2 the epiphany-glyphs seam (24f8c80), W3 the text-run primitive ruling (f639919).

2.2 The T4 spike — exactly where it stands

An elimination experiment producing one deliverable, spec/RULING_EDITOR_TOOLKIT.md, which does not exist yet. Candidates: C1 modern egui + lyon meshes, C2 vello behind winit, C3 iced.

Round Subject State
0 Accessibility route + desk survey COMPLETE. C3 (iced) ELIMINATED.
1 Compound-path fill correctness COMPLETE. C1 PASS, C2 PASS (27 points × 2 adapters each)
2 Text (5 checks) APPARATUS ONLY. Packet 2A (fixtures/emitter/differential) and 2B (candidatekit + check-5 oracle) landed; no candidate results yet — there is no round2-evidence/
3 Accessibility semantics not started
3b Damage correctness (untimed) not started
4 Staged latency (ranking) not started
5 Foreclosure probes not started

C3's elimination is worth reading before anything else — it is the methodological centre of the whole spike. It first reported PASS, and the PASS was an artefact: probe-iced registered a hand-built static AT-SPI tree through accesskit_unix, which takes no window handle and registers from process identity. The tree was decoupled from iced's window, focus and event lifecycle, and view() happened to label its button identically — so the transcript read as though iced produced it. Deleting iced from the probe would have produced the identical readback. Corrected to FAIL on coordinator review, with the false positive preserved rather than rewritten. The same shape recurs in Round 1 (C1's first run drew nothing: egui-wgpu silently skips primitives whose texture id is unregistered, so a blank target passed every background point) — whence the standing rule: every round must keep at least one assertion that can only succeed if ink was actually deposited.

2.3 What blocks what

  • T1b is blocked on Ruling B blocker (ii), versioned decode. Blocker (i) (graph-state persistence) was resolved 2026-07-24 by spec/RULING_GENESIS_PERSISTENCE.md — the operation set absorbs genesis, Pass-12 K8 reversed — which is the same genesis-ops track §1 has been interleaved with. Blocker (ii) needs the migrate-on-read API in epiphany-ops: decode_envelope is current-layout-only while the format requires migrate-on-read, and the "written by the current writer" restriction is unenforceable because minimal stamping means a current writer legitimately emits major-0, -1 and -2 blocks.
  • T4b's trigger is measured, not guessed. benches/editor_pipeline.rs found reduce is the only depth-scaling stage and is superlinear (~n^1.4), breaking the frame budget between 3,000 and 5,000 edits — the wall is ~4,500, and note entry mints one operation per note. Reopen is full replay, so nothing resets that depth until T4b's checkpoint machinery exists.

2.4 Two machine-specific facts that a move puts at risk

These are the reason this section matters for a hardware change, and neither is recoverable from the documents alone.

  1. The spike's measurement protocol requires two GPU adapter classes. Round 1's harness refuses to report PASS unless it finds one DiscreteGpu and one IntegratedGpu Vulkan adapter, and pin 4 fixes the deciding configuration across candidates. This machine has a discrete Navi 31 and an integrated Granite Ridge. The integrated adapter's figures are the ones that decide Round 4 — "a budget met on a Navi 31 says nothing about the machine most users have." If the other box does not present both classes, Round 1's completed results are not comparable to anything measured there, and Round 4 cannot be run as specified. Check adapter enumeration from the spike's own process before assuming continuity, and if the hardware differs, treat it as a contract question rather than re-running quietly on whatever enumerates.
  2. Accessibility rounds need two settings that are off by default. On this machine (sway + AT-SPI2), Atspi.get_desktop(0) enumerates zero applications — even with a probe alive, rendering, and connected to the AT-SPI bus — unless both of these have been set:
    gsettings set org.gnome.desktop.interface toolkit-accessibility true
    gdbus call --session --dest org.a11y.Bus --object-path /org/a11y/bus \
      --method org.freedesktop.DBus.Properties.Set org.a11y.Status \
      ScreenReaderEnabled "<true>"
    
    Without them, Rounds 0 and 3 produce a NOT RUN that looks like a candidate limitation and is not one. The spike's own log flags this for exactly this situation: any later round run in a fresh sandbox or session must redo both steps before trusting a NOT RUN verdict on accessibility.

Also environment-shaped: a11y-verifier/verify.py uses gi.repository.Atspi (Python GObject introspection), so the other machine needs that binding, not just the Rust toolchain.

2.5 Frozen-baseline discipline

Spike pin 12 runs every round inside a git worktree at a pinned baseline commit, so path dependencies resolve to frozen sources rather than the live tree — which has had §1's work moving through it continuously. Round 1 is anchored to root baseline 0a35697d8e48e65d62cd96c19eec2431e414359c, oracle commit the same, candidate harness c20bc93, with oracle.json hashing to b3fc017b…. If the baseline moves, the affected round is re-run in full; partially re-run rounds are not reported. Any round resumed on the other machine must either reuse that baseline or re-run whole.

2.6 Cross-thread collision surface — checked, and clean

The one place the threads could have collided is §1.2's constraint that no bundle may carry a canonical base. Verified: it cannot reach the editor track. The spike path-depends on epiphany-layout-ir, -core, -glyphs, -determinism and -opsnot on epiphany-bundle — and none of epiphany-editor-core, -engrave, -layout-ir or -glyphs depends on epiphany-bundle either. cargo metadata resolves in the spike workspace against the current tree. §1's format-epoch work therefore has no path into T4's measurements.

The remaining shared surfaces are the ones already named: .tex requirement counts in testkit/tests/requirement_labels.rs, and — when T1b eventually opens — epiphany-bundle, which is exactly where §1's rungs live. T1b and P13-S27 both land in bundle.rs and must not fly together.

SUPERSEDED 2026-08-09 — see the collision row of the POST-S27 UPDATE at the top. §2.3's account of what blocks T1b is unaffected and still current.

§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                                   # count: see CLAUDE.md
cargo clippy --workspace --all-targets -- -D warnings     # clean
cargo fmt -p epiphany-bundle -p epiphany-textproj -p epiphany-testkit --check

The expected count lives in CLAUDE.md's Green baseline and is NOT repeated here — changed 2026-08-09. This document stated it in three places and S27 moved it, so all three went stale together; a figure kept in four documents goes stale in three of them. If the observed count differs from CLAUDE.md's, 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. Push editor-t4-packet2bDONE 2026-08-07. origin now carries both main and editor-t4-packet2b.
  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. Have the editor/T4 session add its own account of §2's reasoning — this document reads its artefacts, not its intent. Specifically: whether Round 2's checks are ready to run, and whether the hardware change in §2.4 changes the spike's plan.

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 xelatex (pdflatex fails outright on fontspec) and, for the spike, the gi.repository.Atspi Python binding plus §2.4's two accessibility settings. 5b. Enumerate GPU adapters from the spike's own process (§2.4). If the new machine does not present both a discrete and an integrated Vulkan adapter, stop and raise it — Round 1's results were taken under that configuration and Round 4's deciding figures are defined by it.
  3. Reproduce the green baseline in §3.5 before starting new work. The expected count is in CLAUDE.md's Green baselinenot restated here, and it moved when S27 landed. 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

The two threads are independent right now (§2.6) and can resume in parallel. They collide only when T1b opens, since it and P13-S27 both land in bundle.rs. Superseded 2026-08-09 — see the collision row of the POST-S27 UPDATE.

  1. Spec thread — dispatch P13-S27. DONE — see the POST-S27 UPDATE.
  2. Editor thread — run Round 2's checks, whose apparatus is built and whose candidates are wired (§2.2); then Rounds 3, 3b, 4, 5 and the RULING_EDITOR_TOOLKIT.md verdict.
  3. Then P13-S16dispatchable the moment S27 lands superseded 2026-08-09; see the POST-S27 UPDATE for its current state, and spec/CONTRACT_P13S16_PROJECTION.md for what it now requires.
  4. 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.