epiphany/crates/epiphany-engrave
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
..
src CI: an MSRV that could not build, and a gate that floated 2026-07-22 15:05:04 -04:00
Cargo.toml Phase 3 tranche 1: casting-off, K1 schema-fill, value-restoring undo 2026-07-02 21:55:26 -04:00
DECISIONS.md Two-sided inter-staff renegotiation, and the cascade defect it uncovered 2026-07-09 13:32:37 -04:00
README.md Phase 3 tranche 1: casting-off, K1 schema-fill, value-restoring undo 2026-07-02 21:55:26 -04:00

README.md

epiphany-engrave

Agent I's engraving constraint solver (spec Chapter 9): turns a ConstrainedLayoutIR into a ResolvedLayoutIR with real geometry. It is the production-side replacement for epiphany-layout-ir's interface-only StubSolver; the two live in separate crates so the spec's core/product boundary stays sharp.

Status: Minimal tier, with casting-off

  • Engraver runs a deterministic horizontal spacing pass (the first axis of the two-pass spring layout): each spring slot is placed left-to-right by a collision-aware advance derived from real glyph bearings.
  • A casting-off pass (Phase 3's layout track) then breaks the spaced line into systems at measure boundaries (greedy first-fit against a PageGeometry — default A4 portrait at an 8 mm staff), stacks systems vertically at the vertical-band model's inter-system gap, assigns pages by content height, and populates the real ResolvedPage/ResolvedSystem tree. Every position is baked into a single y-up world frame (pages stacked vertically), so the SVG renderer and hit-testing consume the flat glyph/stroke lists unchanged.
  • The IR's declared constraints are evaluated — geometric families against the pre-casting spaced frame, break constraints against the final break structure (hard breaks are always honoured; a pathological soft break is skipped and recorded as an IrOverride decision). Chosen breaks are recorded as EngravingDecisions with SynthesisKind::EngravedBreak targets, attributed to the user override that requested them when one did.
  • It reports SolverTier::Minimal: hard constraints (break family included) satisfied, no optimality claim — the quality-metric vector stays the honest all-worst placeholder until the Quality Metric Catalog lands.

Deferred: the vertical soft-spring solve, per-system justification/stretch, optimal break search, widow/orphan control, and casting-off caching. See DECISIONS.md.

use epiphany_engrave::Engraver;
use epiphany_layout_ir::{ConstraintSolver, SolverConfig};

let report = Engraver::default().solve(&constrained_ir, &SolverConfig::default());
assert!(report.satisfied_hard_constraints);
let resolved = report.layout; // real pages/systems; hand to epiphany-render-svg