Commit Graph

11 Commits

Author SHA1 Message Date
Levi Neuwirth 496dfd5640 T4 round 0: iced is eliminated, and the probe that cleared it proved nothing
Round 0 is the ladder's cheapest hard gate: for each candidate, a desk survey
and a demonstrated accessibility route -- a window exposing one node with a
role and a name, read back through the platform adapter. It is first precisely
so that a candidate with no accessibility story costs one round instead of
four.

C1 (egui/eframe 0.35.0) passes on a first-party route: egui-winit's accesskit
feature is literally dep:accesskit_winit, so the chain runs eframe ->
egui-winit -> accesskit_winit -> accesskit_unix and inherits that crate's
window-lifecycle handling. C2 (vello 0.9.0 + winit 0.30.13) passes on the
manual accesskit_winit route the contract names, wired into the same
ApplicationHandler that owns the vello renderer.

C3 (iced 0.14.0) FAILS, and the interesting part is that it first reported as
a pass. The probe registered a hand-built two-node tree through
accesskit_unix::Adapter, which takes no window handle at all -- only handlers
-- and registers with AT-SPI from process identity. It read back cleanly. It
also happened to label its button exactly as iced's own view() labelled a
button, so the transcript looked as though iced had produced it. Deleting iced
from that probe would produce the identical readback. Round 0 asks whether the
CANDIDATE exposes a route; a process-level side channel answers a different
question.

The verdict is recorded with dual attribution, because two distinct things
went wrong. The probe-design defect is the false positive above. The candidate
limitation -- which alone fails the round -- is that iced 0.14 ships no
accessibility integration anywhere (accesskit appears in no iced crate
manifest) and its stock runner hands application code neither the winit
ActiveEventLoop nor a pre-visibility Window, both of which every
accesskit_winit::Adapter constructor requires. That scoping matters and is
deliberate: iced_winit documents a conversion module for users implementing a
custom event loop, so a hand-built shell remains conceivable but unproven, and
would mean owning the shell. Upstream iced #552 is still open.

The evidence file keeps the verifier's factual READBACK: PASS beneath a
ROUND-0 RESULT: FAIL annotation rather than being rewritten. A corrected record
that erases the false positive teaches nobody why it was false.

Two findings carried forward. C1's frame node is unnamed -- its readback path
is application:'probe-egui' / frame:'' / button:'...' where C2 names its frame
-- which is non-disqualifying here but means a screen-reader user hears an
unnamed window, and round 3 must check it. And AT-SPI application registration
is gated behind two settings that are off by default; without both, probes
connect to the bus and enumerate zero applications, which would read as a
candidate failure rather than the environment absence it is.

The spike workspace lives outside the root workspace with its own lockfile;
the root gains one line, exclude = ["spikes"], and nothing else. Round 0's
probes depend on no epiphany crate, which is what makes this round independent
of the pinned source baseline the later rounds need.

Root gate unchanged: fmt clean, clippy -D warnings clean, 1371 tests passed 0
failed. Spike workspace gated separately: fmt clean, all probes build --locked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 19:01:51 -04:00
Levi Neuwirth 24f8c8099a Editor T4-pre W2: the glyph outlines become a shared typed seam
A canvas tessellator needs typed vector paths, and the outlines were private to
render-svg as SVG `d` strings. They move to a new epiphany-glyphs crate, which
implements layout-ir's GlyphCatalog with a real render_data.

The seam was already designed and merely unpopulated: PathCommand,
GlyphRenderData, and GlyphCatalog::render_data all existed, and BravuraCatalog
returned None by deliberate documented honesty -- reporting Some would claim
render data that does not exist. Like W1, this packet fills a structure rather
than building one.

Typed paths are parsed from the `d` strings rather than regenerated, because
Bravura.otf is not vendored -- tools/ carries only the extractor and its OFL
notice, and the generated header pins source hashes verified at extraction
time. Equivalence is therefore proven rather than assumed: parse every bundled
glyph, re-emit, compare byte-for-byte. All thirty-seven round-trip exactly, so
the sanctioned coordinate-comparison fallback was not needed. The real grammar
is wider than the contract's summary claimed -- absolute V/H lineto shorthand
appears in twenty-three of thirty-seven glyphs, and numbers carry nought to
three fractional digits with trailing zeros stripped, never a fixed four. The
parser lowers V/H to LineTo and the emitter reconstructs the shorthand from
geometry.

Nothing rendered moves. The outline data is byte-identical to its previous home
(the only edit is BravuraOutline's visibility), render-svg keeps emitting the
stored strings and re-exports bundled_glyph_count and smufl_codepoint
unchanged, and BRAVURA_METRICS is untouched -- it feeds metrics_hash and hence
GlyphCatalogIdentity, which is inside the resolved layout's canonical bytes.
Verified: all eight reference fixtures produce byte-identical canonical layouts,
and all five GUI goldens are unchanged.

The cross-table test locks what glyph.rs asserted only in prose: every glyph's
declared metrics bbox contains its drawn ink, re-derived through true
cubic-bezier extrema rather than control points. Worst-case deviation across the
whole table is exactly zero, at noteheadBlack's left edge. Stated honestly, this
is a rounding-chain lock rather than an independent cross-check -- the same
extractor emits both tables and deliberately rounds the metric box outward so it
always contains the ink -- but it catches a unit-conversion error, a
regeneration mismatch, or a future inward rounding, none of which had a test
before.

Five of six mutations killed, all coordinator-re-verified independently. The
sixth is an honest survivor and a defect in the contract rather than the work:
it asked for proof that re-emitting from the typed form breaks the goldens,
which the round-trip pin makes a no-op. The goldens were re-proven live a
different way -- perturbing one glyph's stored path by one digit flips one pixel
of 279,992 and fails all five.

Gate: 35 suites / 1351 tests / 0 failed, conformance 9/9 with golden-gate and
8/8 without, requirement labels 6/6, clippy 0, cargo tree showing exactly one
dependency and no third-party crate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 18:54:30 -04:00
Levi Neuwirth e6a118e1c6 CI: an MSRV that could not build, and a gate that floated
First remote CI run went red twice, neither from the change that triggered it.

The declared MSRV was false. Cargo.toml claimed 1.77 and all ten crates
inherited it, but the committed lockfile carries blake3 1.8.5, which ships
edition 2024 and cannot be parsed by any Cargo before 1.85. The job died in
fifteen seconds, before compiling a line. The claim had simply never been
tested -- there was no remote to test it.

Raised to 1.85, which is measured rather than picked: over the Linux-target
dependency closure minus the GUI crate, the highest declared rust-version is
constant_time_eq at 1.85.0, pulled in by blake3 itself. The comment on the
field says so, and says not to raise it again to make a build pass.

Raising it woke six clippy lints that MSRV-gating had suppressed: map_or(true,
f) is is_none_or(f) as of 1.82. Rewritten. Five of the six sites are covered --
verified by flipping each to is_some_and and confirming a named test dies. The
sixth, invariants.rs:1335, survives the flip: nothing constructs a Constant
tempo segment with end_tempo None, so that branch has no test that can see it.
The rewrite is safe by the standard library's own equivalence, but the gap is
real and is not created by this commit.

The blocking jobs no longer float on stable. CI ran 1.97.1; this machine has
1.95.0 with no rustup, so the second failure -- float_literal_f32_fallback on
a 2.0 literal in the GUI crate -- was invisible and unreproducible here. With
-D warnings and a floating toolchain, a Rust release turns the gate red with
no repo change, and a gate that fails for reasons the developer cannot
reproduce stops being a gate. All four blocking jobs now pin PINNED_STABLE;
the nightly soak keeps floating, so drift surfaces on a schedule instead of in
an unrelated push. The GUI literal is suffixed anyway, so the eventual
deliberate bump is clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 15:05:04 -04:00
Levi Neuwirth 244bd6bd96 Text Projection: the document layer, and the equation closes
`epiphany-textproj` projects a bundle to its canonical text and reads it back:
the header, identity, profile, extension, canonical-base and blob productions,
plus the pipeline that joins them to the operation layer already in place.

Free functions, not `TextValue` impls. `epiphany-bundle` does not depend on
`epiphany-core`, so the trait and the manifest types are both foreign to this
crate and the orphan rule forbids the impls. That looked like it would force a new
dependency edge until the productions were read properly: no document line
contains a `value` position, so none is needed. The bundle stays independent of
the music model, as designed.

Blobs are handled on both sides and neither is the obvious one. The emit side
filters `blob_roots` through a real reachability predicate that today provably
returns empty -- never the field wholesale, which is the plausible wrong answer.
The parse side rejects any blob line outright, while still parsing the production
in isolation so both halves are ready together. A trip-wire source-scans
epiphany-core and epiphany-ops for `BlobId` and names both obligations when it
fires; it asserts it actually scanned files, so it cannot pass vacuously.

Two rejections were added on the agents' own reading of the spec rather than the
brief, and both are right. Envelopes must already be in canonical reduction order
(text_projection.tex:458) -- without that, two texts differing only in envelope
order would denote one document. And a hand-built `TextDocument` carrying blobs is
refused rather than silently dropped, since serializing it would produce a bundle
whose next projection loses those bytes.

Section order is enforced as a sequence, not a set: a repeated or out-of-order
section is a rejection, never something the parser sorts back into place.
`derived-ordering` sorts and de-duplicates blob lines and extension chunk roots by
*rendered* form, proved against fixtures that are unsorted in memory rather than
already canonical -- the blind spot that let five outbound normalizations ship
untested in the operation layer.

Serialization stages payloads and lets `commit` assign every offset, hash and
chunk id, which is exactly what `req:textproj:derive-or-carry` assumes; nothing is
hashed by hand. The emitted bundle deliberately carries no accelerators.

And the three directions compose: `project(serialize(parse(T))) == T` holds
end-to-end over the companion's own worked example. Eight checks re-mutated
independently of the agents' reports, each killed by its own named test; all eight
document productions diffed against the grammar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:05:28 -04:00
Levi Neuwirth 3e91a8302a Push 4: Binary Format companion, F1 benches, subquadratic reduction order
The audit's fourth push: the biggest outstanding Phase-2 item plus the
performance gate. 793 workspace tests pass; clippy -D warnings, fmt,
and rustdoc (deny-warnings) clean; all three spec documents build with
zero undefined references.

Binary Format companion (spec/binary_format.tex, v0.1.0 — Agent J's
deliverable, 43 pages):
- Twelve chapters transcribed from the golden-locked implementation:
  encoding conventions (the three prefix/endianness regimes, a
  normative no-varint rule, reject-never-normalize decode discipline),
  identifiers imported from the core spec's Canonical Byte-Layout
  Reference, primitive value encodings, the whole-Score positional
  codec ratified as the schema-major-0 wire form, operation wire
  forms (envelope field order with the normative id-leads property,
  the OperationPayload 0..=3 and OperationKind 0..=23 tables,
  effects/conflict/anomaly/MaterializedState vocabulary), the bundle
  physical layout (64-byte header, 256-byte superblock, chunk
  preimages and framing, ChunkRef, manifest body order), the
  operation-index payload, and the extension-blob/edit-barrier byte
  forms.
- Ratifies P12-D1 (req:binfmt:opindex), P12-E1 (req:binfmt:ext-blobs),
  P12-E2 (req:binfmt:condition-depth, MAX_CONDITION_DEPTH = 64
  normative), and P12-E3 (req:binfmt:object-kind-open) — batch rows
  struck through; discharges the provisional-codec notes in core
  (P11-4), ops, and bundle (P11-D2/D4/D5) DECISIONS with ratification
  cross-references.
- Pins the frozen-layout schema-evolution keystone: within schema
  major 0 every positional struct layout is frozen; a field-set change
  is a schema-major change with migration — formally grounding the
  data-model-expansion staging decision. Open questions kept honest
  in-document: SnapshotId derivation, index-refresh threshold, u64/u32
  prefix unification at the next major.
- Not yet delivered from J's charter: the cross-implementation decoder
  test and the wire-format fuzzer (follow-up harnesses).

F1 benches (crates/epiphany-testkit/benches/, per the F0 decision):
- criterion 0.5.1 (workspace dev-dependency; MSRV 1.77 respected with
  documented transitive pins: clap 4.5.53, half 2.4.1).
- reduction bench at 1K/10K/50K envelopes with the Chapter-10 budget
  (>10,000 envelopes/second cold) written in the bench as a Pass/Xfail
  gate; bundle benches for the typical-edit commit (<=50 ms; measured
  ~14.7 ms on real disk after catching that tmpfs neuters fsync) and
  the open/bootstrap read (<=200 ms; measured ~60 us).
- CI: quick budget gates in the conformance job, full gates nightly.

Subquadratic canonical_reduction_order (the F-surfaces/K-fixes
handshake, closing K's 10K-envelope acceptance gate):
- The bench documented the failure (50K at ~1.7K env/s, a 29 s cold
  reduction; two O(n^2) loops); the fix replaces pair enumeration with
  threshold/frontier readiness per replica plus explicit-dot dependent
  lists and a stamp-tuple binary heap — O((n + sum(context)) log n),
  never materializing covered pairs.
- Byte-identical order: same edge relation, same ready predicate, same
  total order; the old implementation is retained as a test-only
  oracle with element-for-element order-equality property tests over
  fuzz sets, adversarial sets, and directed shapes (2,000-envelope
  full-coverage chains, dot cycles, duplicate-id stamp ties),
  mutation-tested for sensitivity.
- Measured: 1K 155K->674K env/s, 10K 12.5K->257K, 50K 1.7K->87K; all
  three scale points now pass and the 50K row is promoted from Xfail.

Also: fixed nine rustdoc private/unresolved intra-doc links that had
accumulated across the pushes (the CI deny-doc-warnings job would have
failed on them).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-02 19:02:07 -04:00
Levi Neuwirth 92aaccf7e2 Pushes 1+3: fix the MUST-level violations, wire the types-only machinery
Two audit pushes whose code edits interleave line-by-line in the same
files (reduce.rs, bundle.rs, the DECISIONS logs), committed together so
the tree at every commit builds. Gate: 784 workspace tests pass, clippy
-D warnings clean, fmt clean.

Push 1 — the true MUST violations, all fixed:
- bundle: zstd read support on both read paths, output bounded by the
  declared uncompressed_length, typed decompression errors, explicit
  CompressedManifest rejection (zstd 0.13 workspace dep; write path
  stays uncompressed per the Phase-3 deferral).
- ops: system-derived counter collision check — mint registry seeded
  from the base graph, canonical-order pre-walk, halt via the new
  PendingReason::HaltedBySystemCollision (discriminant 4, additive)
  with transaction-atomicity and causal-dependent closure; neither
  input set occupies a collided counter. canonical_pitch_bytes made
  pub in core for the MUSCSPCH preimage.
- ops: Transpose skips tombstoned targets per the catalog; missing
  targets still refuse the whole operation.
- ops: marker re-anchoring recorded as a RepairRecord in the
  triggering operation's effect; ResolveConflict meta-conflicts name
  both resolvers; base-free pitch-id freshness; reserved effect
  vocabulary annotated.
- core: decomposition pre-pass honors authored attachments
  (resolve_decomposition, spec-default precedence); inversion
  tolerance typed as a TempoIntegration-class Tolerance.
- CONFORMANCE.md: the determinism conformance statement required by
  Appendix D — all seven declarations.

Push 3 — wiring the types-only machinery:
- layout-ir/engrave: to_constrained emits real constraints (successive
  notehead no-collision chains, per-glyph region containment, soft
  user-break constraints); ConstraintStrength{Required, Preferred}
  with strength-by-rule; Preferred violations surface as warnings, not
  failures; StubSolver reworked honest-but-renderable. SVG goldens
  byte-identical; snapshot constraint counts regenerated (0->90/15).
- layout-ir: to_logical projects user system/page breaks as anchored
  EngravingOverrides with paired UserOverride-sourced decisions
  (OverrideKind::SystemBreak/PageBreak carry TimeAnchor, ratified in
  the spec alongside).
- layout-ir/ops/editor-core: edit-barrier bridge — decode mirrors for
  the whole barrier tree (reject-never-normalize, NFC revalidation,
  MAX_CONDITION_DEPTH = 64), golden-locked blob codec for the
  ExtensionDeclaration fields, a barrier gate in apply and
  apply_transaction backed by a Score oracle and real containment
  contexts, and apply_unsafe recording the crossed extensions in
  extensions_requiring_tombstone() for the next bundle write.
- ops: ResolveEquivocation meta-operation per the newly ratified
  catalog entry — payload discriminant 3 (appended), set-level
  earliest-resolve-governs promotion, ResolveConflict-mirrored
  meta-conflicts, permutation-invariance fuzz; the missing golden
  locks on the OperationKind/OperationPayload wire tables added.
- ops/editor-core: validation modes — ValidationMode + a non-canonical
  advisory layer (validate.rs), an authoring gate before minting, and
  reduction pinned as replay mode by construction (canonical bytes
  untouched).
- bundle: the operation index (opindex.rs) — provisional golden-locked
  payload, binary-search locate, staleness defined as full-ChunkRef
  set equality against operation_roots, and the reject-and-rebuild
  discipline (a defective index is never bundle corruption).
- ops: re-anchoring rule table completed — the four-key "nearest"
  ordering computed from base-free ledger indices; markers re-anchor
  to the nearest live event in the same staff instance (replacing the
  Push-1 region-start stand-in); cue-source cascade; graphic-gesture
  Events/Range/Free rows; comment and analytical-annotation orphaning.
  Zero appended discriminants.

Spec enablers ratified with Push 3: catalog §ResolveEquivocation
(0.3.0 -> 0.4.0) and anchored break overrides; 16 new Pass-12 rows
filed (C1-C4, K5-K7, I4-I6, D1, E1-E5). The data-model payload
expansion (SlurKind, beam geometry, voltas, instrument bodies,
metadata) is deliberately staged to the Binary Format companion — the
positional graph codec has no value-level versioning, so filling those
structs is a schema-major break that should land once, with J.

Also carries the pre-existing editor-track increment: the atomic
tuplet overwrite (CascadeDeleteTuplets prunes decomposition
attachments naming the cascaded tuplet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-02 17:10:50 -04:00
Levi Neuwirth c56ea4845f editor-gui: a thin native (egui) editor over the editor-core seam
A new demo binary crate that proves the EditorSession seam end to end. It renders
the session's resolved layout with epiphany-render-svg, rasterizes that SVG with
resvg into an egui texture, resolves clicks back to staff-space world coordinates
to select, and drives all six note-editing intents from a toolbar and keyboard
(delete, chromatic transpose, staff-step move, add chord note, insert-after). A
debug panel shows the selection and the last applied op, proving the op log is
usable. Intentionally narrow: no duration editing, empty-space insert, or undo UI.

The one piece of nontrivial logic — the screen<->world map a click depends on — is
a pure ViewMap that inverts render-svg's translate(-min_x, max_y) scale(1 -1)
transform, unit-tested for round-trip, the y-up corners, and the logical-vs-ceiled
size mapping (the texture is displayed at the SVG's logical size with a uv crop, so
the click plane is not stretched by the pixmap's rounded-up dimensions). rerender
updates view box, logical size, and texture together on success and clears the
texture on a rasterization failure, so the pixels never disagree with the click
plane.

eframe + resvg are the workspace's first heavy external deps. eframe is pinned to
0.29 for the classic App::update(ctx) + panel API (0.35 redesigned App around
ui()). The crate needs a newer toolchain than the workspace MSRV, so CI excludes
it from the pinned-1.77 workspace commands and checks it in a separate
current-stable editor-gui job; the rest of the workspace is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAtEiJtt9yKVV1zjKYmZhS
2026-06-29 11:47:25 -04:00
Levi Neuwirth a28d888c8e epiphany-editor-core: a headless editor API + a conformance UI-seam gate
Packages the proven editing-loop vertical slice as the API a GUI calls -- no UI,
no rendering backend of its own (it produces a RenderIR). EditorSession owns:

  - selection state (Selection { source, layout_object }): click(point) selects the
    topmost hit, select(id) restores a selection, selection()/clear_selection();
  - render/hit-test query: render() and hit_test() for the GUI to draw and resolve
    clicks/drags;
  - operation minting -- the ergonomics gap the harness exposed, closed before UI
    depends on it: the caller passes an OperationKind to apply() (or an intent like
    transpose_selection(+1)) and the session assembles the OperationEnvelope (id,
    author, stamp, causal context). A GUI never hand-rolls envelope bookkeeping;
  - apply/re-render -- ATOMIC: a minted op the reducer rejects (e.g. a reserved
    replica identity) returns Err(RejectedOperation), not a silent no-op, and a
    diagnostic-only layout returns Err(NotRenderable); on any error nothing mutates,
    operation counter included (the candidate id is committed only on success);
  - selection preservation: the selection is re-resolved against the new layout,
    kept when its layout object survives and cleared when it is gone.

The session is solver-agnostic (Box<dyn ConstraintSolver>), so a GUI plugs in the
Engraver, the stub, or any conformant solver. EditorError implements Display/Error.
epiphany-ops now re-exports AcceptOutcome (accept()'s return type, previously
unreachable) so a caller can inspect a rejection.

Also wires the edit-loop harness into the conformance suite as the [7c] UI-seam
gate: over both fixtures (ten_measure_single_staff and valid_score_rich) every seed
must drive a click->sharpen->re-render cycle whose selection survives the relayout
-- the contract a GUI's correctness rests on.

Full gate green: build, fmt, clippy, 606 tests, conformance scale 1 (incl. [7c]).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:47:34 -04:00
Levi Neuwirth ac7c076e73 Phase 2 (Agent I): visible-slice scaffold — Bravura SVG renderer vs stub
Lands the renderer-against-stub slice of Agent I's visible engraving work
(spec/PHASE2_QUICKSTART.md). Two new crates; prerequisites (G Pass 11, H
spelling/decomposition) are in place. Real engraving + Minimal-tier solver
follow next phase.

epiphany-render-svg (the deliverable this phase):
- Renders a ResolvedLayoutIR to well-formed SVG 1.1, drawing each glyph as a
  GENUINE Bravura SMuFL outline <path>. Outlines are extracted reproducibly
  from the official OFL Bravura.otf by a committed generator
  (tools/extract_bravura_outlines.py, OFL.txt); the font is not vendored, only
  the generated Rust (src/outlines_generated.rs). Staff-space/y-up coords with
  one global y-flip wrapper; viewBox in staff spaces, px scale on the root.
- Non-overreach: every element traces to a ResolvedGlyph (data-prov) or a
  declared wrapper; a glyph lacking an outline is surfaced as a diagnostic and
  drawn as a fallback rect, never silently dropped.
- Hand-rolled xml::check_well_formed (no XML dep); acceptance tests cross-check
  with system xmllint when present.
- examples/render_fixture.rs demo (fixture name -> SVG stdout, --solver=stub|real).
- Golden-locked machine acceptance snapshot + full-SVG golden for
  ten_measure_single_staff and valid_score_rich; deterministic output.

epiphany-engrave (honest scaffold):
- Engraver: a deterministic horizontal-spacing pass (first axis of the planned
  two-pass spring layout). Reports SolverTier::Stub — NOT Minimal — until it
  evaluates the declared hard constraints, guarded by a regression test. The
  demo's --solver=real exercises it end to end.

Honesty notes (recorded as Pass-12 candidates P12-I1..I3 in spec/PASS12_BATCH.md
and the crates' DECISIONS.md): the v0 to_logical/to_constrained pipeline is a
structural placeholder (arbitrary glyph per object, y=0), so stub output is not
yet recognizable notation and the QUICKSTART human visual gate is a next-phase
gate; MUSCLOID layout-id derivation stays unwired; bundled BRAVURA_METRICS are
approximations that disagree with the real outlines.

Gates: cargo fmt + clippy -D warnings clean; cargo test --workspace 504 passed,
0 failed, 0 ignored (criterion 6 layout round-trip still green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:01:40 -04:00
Levi Neuwirth 3d1c55d73e Land epiphany-layout-ir (Agent E): layout IR + solver interface
Implements Agent E per spec/QUICKSTART.md — the layout intermediate
representation (Chapter 7) and the constraint-solver interface (Chapter 9):

  * Four IR stages: LogicalLayoutIR -> ConstrainedLayoutIR ->
    ResolvedLayoutIR -> RenderIR (interface only), with the composite-object
    taxonomy, spring slots/constraints, vertical-band model, pages/systems,
    engraving decisions + overrides, and the incremental dependency/cache model.
  * TimeAxisModel tagged enum (Metric/Proportional/Aleatoric/Registered).
  * Provenance back-references with manifestation- and synthesis-aware ids
    ((source, region) and (source, kind, ordinal)), so multiply-manifested and
    synthesized objects never collide.
  * In-tree Bravura GlyphCatalog (Send+Sync, metrics + render-data interface),
    MUSCFNTM-tagged metrics hash with anchors hashed as a name-keyed map.
  * Edit-barrier types keyed on OperationKindTag, with precise EditContext /
    EditOracle scope/condition evaluation.
  * StubSolver: returns SolveStatus::Solved with the input geometry verbatim;
    spec-compliant SolveReport, Minimal tier + all-worst (unmeasured) metric
    vector (no false conformance claim); rejects ill-formed input.
  * f32 staff-space IR coordinates, quantized to the 1/1024 grid only at
    canonical ResolvedLayoutIR serialization (Appendix D); non-finite geometry
    is rejected, not normalized. Canonical encoding is injective in glyph
    identity, provenance, engraving decisions, and catalog identity.

Re-points Agent F's testkit layout harness from its in-tree stub to the real
crate (v0 acceptance criterion 6) and expands its generators to E's public
surface. Expands Agent C's OperationKindTag to the full normative variant set
so edit barriers can prohibit every operation class.

Workspace gates green: fmt, clippy -D warnings, 377 tests, doc tests, rustdoc
-D warnings, and the conformance suite at scale 1. Decisions and Pass 11
candidates recorded in crates/epiphany-layout-ir/DECISIONS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:58:01 -04:00
Levi Neuwirth a2e9ec32f6 A B C D F 2026-06-19 12:42:31 -04:00