diff --git a/spec/CONTRACT_EDITOR_T4PRE_W2_GLYPHS.md b/spec/CONTRACT_EDITOR_T4PRE_W2_GLYPHS.md new file mode 100644 index 0000000..3945438 --- /dev/null +++ b/spec/CONTRACT_EDITOR_T4PRE_W2_GLYPHS.md @@ -0,0 +1,156 @@ +# Contract: Editor T4-pre W2 — the shared typed glyph-asset seam + +Repo root `/home/jeans/Repos/active/epiphany`. Governed by +`spec/CONTRACT_EDITOR_T4PRE_IR.md` §W2 and `spec/PLAN_EDITOR_APP.md` §3.7 / +Ruling A, which names "a shared typed glyph-asset seam" as a T4 prerequisite: +a canvas tessellator needs typed vector paths from a crate both the renderer +and the app can depend on. W1 landed at `dd33b34`. + +Execution model as T1a/T2/T3/W1: Sonnet subagent, coordinator line-level +review with independent mutation re-runs, user deep-dives at contract +sign-off, any new golden baseline, and the final report. Mutation discipline +throughout: anchor-assert before substituting, restore by reversing, never +`git checkout`. + +**Parallel safety.** The genesis-operation tranche is in flight on the other +track, laddered as G1/G2/G3 in `spec/PLAN_GENESIS_OPS.md` under +`spec/RULING_GENESIS_PERSISTENCE.md`. Across its rungs it owns +`epiphany-core`, `epiphany-ops`, all `.tex` (so the requirement counts move), +testkit's requirement-label constants, vectors and fuzz generators, and — at +G2 — `epiphany-bundle` for the `OperationEnvelopeBlock` accept-set raise. +**This packet touches none of them**, at any rung. Report observed requirement +actuals; never assert stale numbers. + +--- + +## The verified starting point + +Confirm these as you go; they make the packet far smaller than §3.7's wording +suggests: + +* **The typed path type already exists, in `layout-ir`.** `PathCommand`, + `GlyphRenderData { outline: Vec, bitmap: Option }`, + and `GlyphCatalog::render_data(&self, name) -> Option` are + all defined (`layout-ir/src/glyph.rs:~285-315`). **W2 populates a seam that + is already designed; it does not design one.** `BravuraCatalog::render_data` + returns `None` today by deliberate, documented honesty — "reporting `Some` + would claim render data that does not exist." +* **The outlines exist, as SVG `d` strings**, in + `render-svg/src/outlines_generated.rs`: `BravuraOutline { name, codepoint, + path: &'static str, bbox: [f32; 4] }`, staff-space units, y-up, 4 decimals, + sorted by name. `outline()` is `pub(crate)`; `bundled_glyph_count()` and + `smufl_codepoint()` are `pub` but have **no callers outside `render-svg`** + (verified workspace-wide). +* **`Bravura.otf` is NOT in the tree.** `render-svg/tools/` holds only + `extract_bravura_outlines.py` and `OFL.txt`; the generated header pins + source SHA-256s verified *at extraction time*. Re-running the extractor + therefore requires fetching the font and **cannot be assumed**. This + determines pin 4. +* **The metrics table is conformance identity.** `BRAVURA_METRICS` lives in + `layout-ir/src/glyph.rs`; `metrics_hash_for` hashes `(name, metrics)` pairs + with the *values* participating (the crate's own note at `glyph.rs:372-375` + exists precisely to prove a field participates), and `GlyphCatalogIdentity` + is encoded into `ResolvedLayoutIR`'s canonical bytes. + +## Design pins + +1. **A new crate, `epiphany-glyphs`**, owns the glyph assets: the moved + `outlines_generated.rs`, the extractor tool, and **`OFL.txt`, which travels + with the redistributed outlines** (the generated file's license header + stays intact — Bravura is SIL OFL 1.1 and the notice is a redistribution + condition, not decoration). It depends on `epiphany-layout-ir` and exposes + a catalog type implementing `GlyphCatalog` with a **real `render_data`**. + No cycle: glyphs → layout-ir, never the reverse. +2. **`render-svg` keeps its public API and its exact output.** It depends on + `epiphany-glyphs`, and `bundled_glyph_count`/`smufl_codepoint` remain + exported from `render-svg` (delegating or re-exporting) even though nothing + outside calls them — an API that costs one `pub use` is not worth breaking. + `font_subset_generated.rs` stays in `render-svg`: an embeddable font subset + is a renderer concern, not a shared asset. +3. **The metrics table does not move and does not change.** Out of bounds + entirely. Any edit to it churns every conformance byte in the repo. +4. **Typed paths are derived from the `d` strings by an in-crate parser, not + by re-running the extractor.** The font is not in the tree, so regeneration + is not reproducible here. The parser covers exactly the grammar the + generator emits — absolute `M`/`L`/`C`/`Z`, decimal coordinates — and the + crate stays **dependency-free** (it is in the MSRV closure; no new + dependency, no `build.rs`). +5. **Equivalence is proven by round-trip, not asserted.** Parse every bundled + glyph's `d`, re-emit it in the generator's exact formatting, and compare to + the original string **byte-for-byte**. If all bundled glyphs round-trip, + the typed form provably describes the same geometry and no geometric + spot-checking is needed. *Sanctioned fallback if the generator's number + formatting cannot be reproduced exactly:* parse both the original and the + re-emitted string and compare the full coordinate sequences as exact `f32`. + **Report which was used and why** — do not silently take the weaker one. +6. **`render_data` is deterministic and side-effect-free.** Caching (a + one-time lazily built table) is permitted; it must not change results, and + two calls must return equal data. + +## Tests + minimum mutations + +* **(g1) parser round-trip** over every bundled glyph (pin 5) — the packet's + load-bearing test. *Mutation:* perturb one parsed coordinate → dies. +* **(g2) `Close` survives parsing** — *mutation:* drop the trailing `Z` → + dies. +* **(g3) every pipeline glyph has render data** — the `BRAVURA_METRICS` name + set all resolve through the new catalog's `render_data`, mirroring + `render-svg`'s existing `every_pipeline_glyph_has_a_bundled_outline`. + *Mutation:* return `None` for one bundled glyph → dies. +* **(g4) outline ink fits the declared metrics bbox** — the cross-table + consistency the tree asserts in prose and tests nowhere: `glyph.rs:107-109` + claims the metrics and outlines come from the same release "so reserved + advances/bboxes and the drawn ink agree." Compare each glyph's outline + extent (staff spaces) against its `BRAVURA_METRICS` bbox (font units, 250 + per staff space), with a stated tolerance for the outlines' 4-decimal + rounding. **Report the actual worst-case deviation.** *Mutation:* inflate + one parsed coordinate past the declared box → dies. **If a real glyph fails, + that is a finding to report — not a reason to widen the tolerance.** It + would mean engraving reserves the wrong space for that glyph, the same bug + class that twice bit the vertical metric. +* **(g5) `render-svg`'s bytes do not move** — *mutation:* emit the `d` from + the typed form instead of the stored string → the goldens die. +* **(g6) absolute, not relative** — *mutation:* treat a `C` as relative → + round-trip dies. + +## Blast radius + +New `crates/epiphany-glyphs/**` (including the moved +`outlines_generated.rs`, `tools/extract_bravura_outlines.py`, `tools/OFL.txt`, +and a `DECISIONS.md`); `crates/epiphany-render-svg/src/{lib.rs, outline.rs, +svg.rs}` and its `DECISIONS.md`; the workspace `Cargo.toml` member list. +Nothing else — no `layout-ir` edit (the seam is already there), no +`epiphany-core`, no `epiphany-ops`, no `epiphany-bundle`, no `.tex`, no CI +change (the MSRV job runs `--workspace --exclude epiphany-editor-gui`, so a +new crate joins automatically — which is exactly why it must be MSRV-clean and +dependency-free). + +## Gate (actual output) + +The standard six; conformance **9/9 with `--features golden-gate`, 8/8 +without** (both run); requirement labels reported as observed. Plus the two +W1 byte locks, run as before/after comparisons against the base commit and +reported with their actual sizes: + +* **Layout canonical bytes byte-identical** for the reference-suite fixtures + plus `ten_measure_single_staff(0)` and `ten_measure_with_slurs(0)`. (W1's + numbers, for reference: 21615 / 6948 / 3839 / 5602 / 2299 / 2299 / 21615 / + 22119.) +* **All five GUI goldens byte-identical.** Nothing here may change a pixel; + no golden is re-blessed in this packet. +* **`cargo tree -p epiphany-glyphs` shows no third-party dependency.** + +## Coordinator deliverable, alongside + +`spec/ANALYSIS_TEXT_RUN_PRIMITIVES.md` — W3, the text-run primitive decision +(Ruling A criterion 3: shaping, fallback, bidi, and metrics consistent across +canvas, SVG/PDF export, hit testing, and the accessibility tree). A decision +document, not an implementation, written by Fable while W2 runs, because +getting it wrong forecloses the toolkit choice T4's spike exists to make +freely. + +## Report + +Per packet, as every tranche: files + summary, exact asserted values +(including g4's worst-case deviation), every mutation with kill evidence, gate +output, deviations flagged explicitly. diff --git a/spec/PLAN_EDITOR_APP.md b/spec/PLAN_EDITOR_APP.md index b7d95bb..f961e0d 100644 --- a/spec/PLAN_EDITOR_APP.md +++ b/spec/PLAN_EDITOR_APP.md @@ -232,8 +232,12 @@ after the resolver tranche lands. persistence decision — RESOLVED 2026-07-24** (`spec/RULING_GENESIS_PERSISTENCE.md`): the operation set absorbs genesis, and pruning is parked until the canonical base carries graph values. The - lease/save/single-writer machinery does **not** depend on that tranche - landing and may be contracted in parallel with it; (2) the + lease/save/single-writer machinery does not *depend* on that tranche, and + its parallel-safety is **per-rung** (`spec/PLAN_GENESIS_OPS.md`): G1 + (`CreateInstrument`) needs no accept-set raise and never enters + `epiphany-bundle`, so T1b's bundle work runs beside it; **G2 spends the raise + in `bundle.rs`**, where T1b's single-writer enforcement also lands, so the + two must not fly together (corrected 2026-07-24); (2) the **versioned-decode disposition** — the migrate-on-read API in `epiphany-ops`, preferred and eventually mandatory, or the enforceable current-layout restriction (§Ruling B, blocker ii); (3) the **Ruling-D @@ -598,8 +602,11 @@ which is why T1b sequences after the resolver.)* > Push-4b-class coordinated tranche; the editor track consumes it. Two > constraints carry forward into T1b: **pruning may not be implemented until > disposition C lands** (a `MaterializedState` base cannot rebuild a score), and -> `identity`'s disposition blocks specification of that tranche. The text below -> is kept as the record of what the blocker was. +> the `identity` cursor is **ruled** in that document's §3 (it stays on `Score`, +> byte-equality claims confine to `MaterializedState`, and from-empty reduction +> derives the counter). Execution is laddered in `spec/PLAN_GENESIS_OPS.md`; +> T1b's bundle work is parallel-safe with G1 but not with G2. The text below is +> kept as the record of what the blocker was. **Blocker (i) — canonical graph-state persistence, across genesis AND pruning.** Two halves of one question. *Genesis:* "every piece of content diff --git a/spec/RULING_GENESIS_PERSISTENCE.md b/spec/RULING_GENESIS_PERSISTENCE.md index cd7456a..ba74f9c 100644 --- a/spec/RULING_GENESIS_PERSISTENCE.md +++ b/spec/RULING_GENESIS_PERSISTENCE.md @@ -237,9 +237,13 @@ existing types do not move. `spec/PLAN_EDITOR_APP.md` §Ruling B blocker (i) is **resolved**. Blocker (ii) — the version-aware envelope decoder — is unaffected and still open; its residual is one bounded ops packet. T1b's remaining runway is therefore -blocker (ii) plus the Ruling-D ownership API, and the lease/save/single-writer -machinery can be contracted in parallel with this tranche, since it does not -depend on genesis being authorable. +blocker (ii) plus the Ruling-D ownership API. The lease/save/single-writer +machinery does not depend on genesis being authorable, but its parallel-safety +is **per-rung** (`spec/PLAN_GENESIS_OPS.md`): G1 needs no accept-set raise and +never enters `epiphany-bundle`, so T1b's bundle work runs beside it; **G2 +spends the raise in `bundle.rs`**, where T1b's single-writer enforcement also +lands, so those two must not fly together. (Corrected 2026-07-24: this section +originally claimed unconditional parallel-safety.) *Related: `spec/ANALYSIS_GENESIS_PERSISTENCE.md`, `spec/PLAN_EDITOR_APP.md` §Ruling B / §3.7, `spec/PLAN_PUSH4B_TUNING.md` (the tranche mold).*