docs(results): production saturation binds via the class cap, not the iteration cap (68.4% of 193 kernels, stop reason typed) - #1087
Conversation
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
🤖 Hi @jppittman, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
jppittman
commented
Sep 1, 2026
Picked this up in the open-PR sweep. Two red checks, both mechanical — I fixed one, the other needs a push from you.
let soft:Vec<&Row> = rows
.iter().filter(|r| r.anomaly.is_some() && !r.fatal).collect();println!("\nnon-fatal anomalies (more saturation extracted worse): {}",
soft.len());Everything else is green, including both test jobs, the ISA matrix and the feature matrix. One thing worth deciding before this landsThis PR and #1083 ( More broadly, five open PRs now touch the same saturation surface — #1083, #1084, #1085, #1087, and #1044 (whose I did not touch the branch itself, and I have no view on the integrity note beyond reading it — flagging only that the Generated by Claude Code |
jppittman
commented
Sep 1, 2026
Correction to my previous comment: I said I re-ran the failed jobs to confirm, and it failed again with the old title in the log:
No extra work for you: the Generated by Claude Code |
b2d1e48 to
9594f92Compare9594f92 to
a3f4d9dCompare…inds, not iteration cap optimize_runtime_arena_uncached computes a SaturationResult and discards it (runtime.rs:128-133), so production couldn't say whether a real core-term kernel quiesces, hits the class cap, hits the iteration cap, or hits the 200ms wall clock. Adds three #[ignore]d measurement tests — no public API change, no production behavior change: - pixelflow-core: dump the real packed cell-grid arena at core-term's startup/resize geometries (cell_grid.rs) - pixelflow-graphics: dump the real per-glyph arenas via Font::glyph_kernel_scaled for all 95 printable-ASCII chars at both bake densities (new tests/production_glyph_arena_dump.rs) - pixelflow-search: replay optimize_runtime_arena_uncached's exact calls on every dumped arena, plus two unceilinged references (4x iterations, and 4x iterations + 4x class cap) to distinguish class-cap-bound from genuinely-converged trajectories (runtime.rs) - pixelflow-compiler: measure the winding kernel's separate macro-time Dwrt e-graph (ir_bridge.rs) — finding it never runs for this kernel at all (BitAnd has no e-graph Op, so differentiate_in_optimizer's representable guard bails before constructing an EGraph) Result (173/193 kernels measured; the remaining 20 are density-2.0 duplicates of already-measured characters, see the doc for why the run didn't finish): only 11/173 (6.4%) quiesce under production's budget; 117/173 (67.6%) are class-cap-bound; 45/173 (26.0%) hit the 200ms wall clock (inflated by exceptional machine contention during this run — flagged per-row as machine_dependent). Truncation cost is median 0%, p90 ~16-19%, worst case 47.2%. The cell-grid kernel — compiled at every core-term startup and resize — is class-cap-bound but shows 0% loss. 23/173 rows show more saturation extracting worse code under the static cost model, a real caveat for "more budget is strictly better." Full writeup, per-kernel CSV, and an integrity note (a mid-run attempt by another agent to inject a production API change was reverted) in docs/results/2026-09-01-production-saturation-telemetry.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… not from reference runs The telemetry harness classified production's stop by inference: two extra unceilinged runs per kernel, compared by iteration count and trajectory signature (`classify` / `classify_reference` / `classify_lifted`). That is exactly the "infer quiescence from counts" mistake the typed `SaturationStopReason` exists to close. The harness now records `SaturationResult::stop_reason` for the production run and for both generous runs; the three classify functions are gone. The winding-kernel Dwrt test in ir_bridge.rs carried the same inference (`stopped_on_its_own` / `cap_bound`, unreachable in practice because the representable guard bails first) and now reads `SaturationStats::stop_reason` too. The generous runs stay, but only as the truncation-LOSS measurement: `ref` (4x iterations, same class cap, no clock) isolates the 200ms wall clock's bite; `lifted` (4x iterations, 4x class cap, no clock) is the whole budget's. Whether lifting the cap changed the trajectory is kept as a plain column (`cap_lift_changed`), a measured fact rather than a classification. Also: - a per-KERNEL wall-clock ceiling (`PIXELFLOW_TELEMETRY_KERNEL_CEILING_S`, default 1200s) shared by the two generous runs, enforced by the loop's own deadline and reported: a cut generous run shows `Timeout`, the row's loss against it is `NA`, and the row is listed under "loss unmeasured" — never skipped, never a panic mid-table; - the 0/0 loss of the 1-node space glyph is `NA`, excluded from quartiles (the old harness would have produced NaN and panicked in `median`); - host load averages recorded at start and end (`<out>.meta`) because the wall-clock stop is the one verdict that depends on the host; - the deterministic-replay check is now stated against the type: when production stopped `Converged` or `ClassLimit` (no clock involved), the same-cap generous run must retrace it exactly, or the row is fatal; - `cargo fmt` applied to the three telemetry test files the measurement commit left unformatted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`EGraph::saturate_with_limits` has four ways to return — converged,
iteration cap, class cap, wall clock — and reported none of them:
`SaturationStats` carried only `iterations` and `total_unions`, and
`SaturationResult::saturated` reads `iterations < max || total_unions == 0`,
which calls a class-cap or timeout stop "saturated". Every consumer that
needed the reason (guide_headroom.rs, the production-telemetry harness in
runtime.rs) has been inferring it from counts or from extra reference runs.
Adds `SaturationStopReason { Converged, IterationLimit, ClassLimit, Timeout }`
and a `stop_reason` field on `SaturationStats` and `SaturationResult`, set at
each break site of the one loop that decides when to stop. No behavior
change; `saturated` is kept as is.
This is exactly the graph.rs / mod.rs / saturate.rs hunk set of
origin/claude/saturation-telemetry-flag cd9dcdf (identical in 586d9cf),
applied as a git patch of those three files; the feature-flagged JSONL
telemetry in that commit is not brought in. `SaturationStats` loses its
`Default` derive (no default stop reason is honest); nothing used it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>…scence
apply_rule_at_index_timed truncates its own scan at the class budget or
deadline and keeps classes.len() at or under max_classes, so the outer
`classes.len() > max_classes` check in saturate_with_limits essentially
never fires: a class-capped run ends with a zero-union sweep and was
labeled Converged. (The production cell-grid kernel settles at 4,768
classes under the 5,000 cap this way.)
ApplyResult now carries `truncated`, and the loop reads it: a zero-union
sweep is Quiesced only if every rule scanned to completion; otherwise it
is Timeout (deadline passed) or ClassCap. Read off the loop, not inferred
from counts.
Also converges the type onto the names the in-flight Phase 3 branch
independently chose for the same thing — SaturationStop { Quiesced,
ClassCap, IterationCeiling, Timeout }, field `stop` — so its rebase is a
duplicate-delete rather than a rename.
Pinned by tests/saturation_stop.rs: the busy expression hits ClassCap at
production's 10,000-class budget, x + y is Quiesced, 0 iterations is
IterationCeiling, Duration::ZERO is Timeout.
This is origin/claude/saturation-telemetry-flag faeaffc restricted to
graph.rs / mod.rs / saturate.rs / tests/saturation_stop.rs (its
telemetry.rs hunk belongs to the feature flag, which this branch does not
carry). It replaces an equivalent fix this branch had written independently
(`ApplyResult::stop: ScanStop`), dropped in favour of the reviewed one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>c96e0b04 converged the stop-reason type onto the Phase 3 names
(`SaturationStop { Quiesced, ClassCap, IterationCeiling, Timeout }`, field
`stop`) but left runtime.rs and ir_bridge.rs on the earlier
`SaturationStopReason` / `stop_reason` spelling, so the crate's tests did
not compile. Rename only; no logic change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>…els, class cap binds on 68.4% (lower bound) Re-measures every production kernel with SaturationResult::stop read off the loop instead of inferred from reference runs. Typed census: 12 Quiesced (6.2%), 132 ClassCap (68.4%), 49 Timeout (25.4%), 0 IterationCeiling. Class-cap cost on the 132 ClassCap rows: median 8.66%, p90 13.22%, max 15.07% vs the 4x-cap reference, itself a lower bound (that reference is ClassCap on 128/132). Clock cost on the 49 Timeout rows: median 11.01%, p90 35.51%, max 47.17%. Cell grid: ClassCap at all three geometries, 0% loss. Against Round 1 (173 rows, inferred labels, kept as *-round1-inferred.csv): 167/173 production labels agree; the 6 that differ are ClassCap<->Timeout flips on a loaded host, 2 of which were Round 1 inference errors for their own run; 39 of Round 1's 56 'lifted quiesced' labels were budget-truncated runs. Every deterministic column reproduces to the digit on all 128 clock-free rows. Run labelled loaded: the <4 load gate never opened in the bounded 40-minute wait (63 one-minute polls across three launches), load 5-15 throughout. Integrity note rewritten from the reflog by commit; Verification section added (harness == production call path; public surface = SaturationStop, stop x2, ApplyResult::truncated; fmt/clippy -D warnings/workspace tests/both no_std checks clean after rebase onto origin/main 7d7eabf). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
a3f4d9d to
41201efCompare…cted (#1089) ## Summary Second pass over the open PRs, continuing #1086. That PR did the rebase sweep and wrote recommendations; this one records what was **executed** against them and re-derives the board rows that have since gone stale. Adds one document: `docs/results/2026-09-01-open-pr-sweep-followup.md`. ## What was executed | Action | PR | Basis | |---|---|---| | Merged | #1051 — `cost.rs` mutation gaps | test + docs only, 0 unresolved threads, all blocking checks green | | Merged | #1049 — `graph.rs` test renames | test + docs only, 0 unresolved threads, all blocking checks green | | Closed | #1050 — `regalloc.rs` mutation gaps | target code verified absent from `main` and from its own branch | | Reviewed | #1044 | `shepherd` label; found it had never been reviewed | | Retitled | #1087 | `CL metadata` wanted a conventional-commit prefix | After merging, all 11 remaining open PRs were re-checked with `git merge-tree --write-tree` against the new `main` — **all still merge cleanly**, so the merges introduced no conflicts. ## Corrections to #1086's board #1086 was accurate when written and is now one step behind. The one that moved against the trend: **#1054 went from green to red.** #1082 removed `X86Backend::prologue`/`::epilogue` and #1081 changed the error type, so its tests no longer compile (`E0599` ×4, `E0308`); Clippy, both test jobs and the ISA matrix are red. This is the *second* encoder refactor to invalidate the branch — the first is already in its own history as `4435869f`. Recommendation is to hold it until that file stops moving rather than fund a third re-close pass. ## Three things #1086 could not have seen - **The saturation collision has five participants, not three.** #1044's `variants.rs` calls `eg.saturate_with_limit(64)` (lines 229, 262), which #1085 deletes. Disjoint files, so git merges clean and the *build* breaks on whichever lands second. - **#1083 and #1087 are two mechanisms for one fact** — a real `SaturationStopReason` field vs. outside-in inference — on confusingly adjacent branch names. #1083 went green during the sweep. One should be picked before either lands. - **#1044's zero unresolved threads is an artifact.** Codex hit its usage limit on 2026-08-28 before reviewing it, so thread count ranks an unreviewed 3060-line diff as the cleanest thing in the set. ## Verified rather than relayed #1072's call-overhead P1 is real: `bench_extraction_3way.rs:2607` aggregates `bench.ns * normalization` while `adjusted_ns` is only serialized at `:2591`. Worth noting the direction — adding a constant to both arms pulls the ratio toward 1, so the true regression is *larger* than the reported 1.0153. The qualitative verdict survives; the intervals do not. ## What this pass could not do The remaining gap to "no unresolved comments, no CI failures" is entirely commits belonging on other branches, which this session is not scoped to push. The four outstanding fixes (#1083 `saturation-telemetry = ["std"]`; #1053 stale script header; #1044 `saturate_with_limits`; #1087 `cargo fmt`) are written down in the doc with exact locations. Roughly 60 unresolved threads remain across 8 PRs, 13 of them P1, all filed by `chatgpt-codex-connector` — no human review is unaddressed. ## Test plan Documentation only — no code changes, so `cargo` gates are unaffected. Branch state claims were produced from `git merge-tree --write-tree` and `git rev-list --count` per branch; CI and review-thread claims from the GitHub check-run and review-thread APIs; the `#1072` and `#1044` source claims by reading the files at those branches' heads. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01BAj1WWiTMvAJ6qK42LNyD6 --- _Generated by [Claude Code](https://claude.ai/code/session_01BAj1WWiTMvAJ6qK42LNyD6)_ Co-authored-by: Claude <noreply@anthropic.com>
…, guide, reranker, observer (#1108) ## Summary #1085 unified the three production optimizer tiers **by hand**: they now agree on the rule set, the budget, the cost model and the extractor. What they still lack is a *place* where those four choices live — which is why the divergence #1085 fixed went unnoticed for months, and why the next one would too. `Optimizer` is that place. One entry point, five levers, all defaulting to what production does today: ```rust let mut optimizer = Optimizer::production().for_lattice(shape); let mut eg = optimizer.egraph(); // carries the rule set let root = /* insert your term */; let out = optimizer.run(&mut eg, root, node_count); let (arena, arena_root) = out.to_arena(&eg, root); ``` `optimize.rs` (macro tier), `runtime.rs` (runtime tier) and `ir_bridge.rs` (`Dwrt` expansion tier) all call exactly this. Design doc: `docs/plans/2026-09-02-optimizer-api.md` (§6 added here records what shipped, what did not, and why). ## The laws The doc audits five proposed laws against the code; three needed correcting. The one this API stands on is **L4, policy neutrality**: 1. Every rewrite rule preserves denotation, so an e-class **is** a semantic equivalence class (L1). 2. Saturation only ever adds equalities, so any policy that orders or truncates can only make the graph hold a *subset* of the equalities an exhaustive run would hold — never a different one (L2). 3. Extraction picks one node from the root's class, and by (1) every node in that class denotes the root's function. ∴ the extracted term denotes the same function under **any** ordering policy and **any** budget. A policy changes cost and compile time, never meaning — so a future policy PR owes a *quality* measurement, not a correctness suite. `pixelflow-search/tests/optimizer_laws.rs` pins this rather than leaving it as an argument. Seven tests: | test | law | |---|---| | `every_ordering_policy_extracts_the_same_denotation` | **L4** — 4 rule orderings (incl. reversed) × 6 budgets, identical denotation via the reference interpreter. Denotation is asserted; **cost is not**, because a policy that could not change cost would not be worth having | | `a_larger_budget_refines_the_partition` | L2 — a budget ladder, every pair equal at *b* still equal at *b′* > *b* | | `a_starved_budget_still_denotes_the_input` | L2 — 0/1/2/3/5/13/100 applications, denotation intact | | `an_extracted_term_re_adds_into_its_own_class` | L3a — membership, as a test and deliberately **not** as public API | | `the_same_budget_extracts_the_same_term` | determinism, 8 runs | | `the_stop_reason_names_which_limit_bound` | the typed stop, and that the application cap actually bounds | | `observation_is_optional_and_does_not_move_the_budget` | G4 — production records nothing; the observer sees every application; the extraction is unchanged either way | Also `every_production_rule_has_a_distinct_id` (G5) and `family_names_alias_but_labels_do_not`, which pins the bug as a fact so it cannot come back quietly. ## The gap table | | gap | status after this PR | |---|---|---| | **G1** | Guide | **field NOT added — see below.** Everything around it is: the struct, `Budget::Applications` as the matched-budget currency, `RuleId` for its checkpoint, and the L4 test whose `POLICIES` table it extends | | **G2** | Schedule-cost residual | **closed.** `Reranker` is a real field, honored through `IncrementalExtractor`; `.rerank(Some(..))` is the whole injection | | **G3** | Deterministic application budgets | **closed.** `Budget::Applications(n)`, enforced inside the scan; `SaturationStop::ApplicationBudget`; `OptimizerStats.applications` | | **G4** | Observation / labels | **closed.** `Observer` + enriched `ApplicationRecord`; provenance is opt-in and the counter is independent of it | | **G5** | Rule identity — *critical* | **closed.** `RuleId` from `(name, specialization)`; 62 rules → 62 distinct ids, pinned | | **G6** | One schema, fingerprinted | **partial.** `RuleSet::fingerprint()` exists and covers content *and* order; no consumer keys on it yet | | **G7** | Whole-lattice kernel | **no API change needed**, as the doc predicted — `Budget` is expressed in iterations/classes/applications, so a 10× arena changes the *values*, not the types | | **G8** | Configuration fingerprint in the cache | **half.** `runtime.rs`'s cache now keys on it; `pixelflow-codegen::jit_cache` still does not | ### G1: why the `guide` field is not here The trait is trivial; the loop that reads it is not, and it lives on `claude/phase3-guide`. A `guide: Option<..>` on an optimizer whose saturation loop never consults it would accept a policy and **ignore it** — a silent failure, which this codebase forbids outright. It lands with its loop, in #1084, as a field plus a trait, not a re-plumbing. §3's "one-line injection" for G1 was optimistic; §6.2 says so. ## Equivalence proof `optimize_runtime_arena` over the twelve `shader_bench` kernels, release build, digesting the extracted arena — a stronger check than cost, since equal arenas have equal cost under *every* model while equal costs can hide a different term. | arm | combined digest | runs | |---|---|---| | `origin/main` @ `c1afd4b9` | `66efbe1a7133c5f4` | 3/3 identical | | this branch | `66efbe1a7133c5f4` | 3/3 identical | **All twelve match individually**, not just in the fold. Per-kernel, with the budget actually spent (from the new `OptimizerStats`): | kernel | in | out | applications | classes | stop | |---|---:|---:|---:|---:|---| | cosine_palette | 40 | 26 | 2 614 | 1 767 | ClassCap | | smooth_min_scene | 43 | 38 | 2 722 | 1 219 | ClassCap | | mandelbrot_distance | 152 | 109 | 15 303 | 3 716 | ClassCap | | star_sdf | 66 | 57 | 7 997 | 3 828 | ClassCap | | gyroid_slice | 44 | 35 | 8 652 | 779 | Quiesced | | plasma | 41 | 31 | 3 310 | 1 822 | ClassCap | | domain_warp_fbm | 84 | 59 | 6 976 | 4 577 | ClassCap | | kaleidoscope_fold | 46 | 40 | 601 | 131 | Quiesced | | metaballs | 62 | 48 | 10 090 | 2 584 | ClassCap | | julia_set | 122 | 122 | 14 870 | 4 527 | ClassCap | | smoothstep_vignette | 64 | 45 | 1 596 | 283 | Quiesced | | torus_slice | 42 | 37 | 4 697 | 1 291 | ClassCap | **Not one `Timeout`.** Nine stop on the class cap, three quiesce — which is why dropping the wall clock costs nothing here and the output is unchanged. That independently reproduces #1087's finding (class cap binding on 68 % of its kernels; 75 % here) on a different corpus. Per-kernel timings are the same order on both arms (main 9.5–101 ms, branch 4.2–122 ms, on a machine at load ≈ 21 where that spread is noise). Two things not to over-read, both in §6.3: 1. **This is not proof the clock can never bind.** The same harness against `origin/main` @ `6336a0c2`, *before* #1085's `ScanStop` work, produced **five different digests in five identical runs** and truncated three of the twelve mid-saturation. The clock is a live hazard the moment a sweep outruns it; removing it makes that unrepresentable rather than unlikely. 2. **Applications range 601–15 303, median ≈ 5 800.** That is the calibration table for `Budget::Applications` when two policies must be held to the same spend — the number G3 was missing. Reproduce: `cargo run --release -p pixelflow-pipeline --example optimizer_equivalence` ## Migration for the in-flight branches The diff is mechanical and the names are stable, so a rebase is a rename, not a redesign. | branch / PR | what changes | |---|---| | `claude/phase3-guide` **#1084** | add `guide: Option<Box<dyn SaturationGuide>>` to `Optimizer` + the loop that reads it; drop the parallel budget plumbing (now `Budget::Applications`); `CandidateSummary.rule_idx` → `RuleId`; extend `POLICIES` in `optimizer_laws.rs` instead of re-arguing L4 | | `claude/saturation-telemetry-flag` **#1083** (merged) | already rebased here: `SaturationInvocation.result: &SaturationResult` → `stats: &OptimizerStats`; the JSONL `hard_timeout_us` key becomes `max_applications` (`null` when uncapped) — the budget carries no clock any more | | `claude/rule-order` | `build_rule_set` becomes `RuleSet::new(..)`; `NUMERIC_FIRST_ORDER` keeps working, and the reorder is now *safe by construction* because nothing durable is keyed by index | | `claude/phase3-round2` **#1088**, `phase3-r2g` **#1096**, `phase3-label-constfold` **#1095** | re-key per-rule tables and JSON by `RuleId`; credit definitions move to the research crate consuming `ApplicationRecord`s | | `claude/phase3-domain-shift` **#1091** | results doc, no code | | anything calling `env_extraction_policy()` / `saturate_for_extraction()` | both deleted → `Optimizer::production()` + `.run(..)` | ## Not in this PR * **The `HARD_CLASS_LIMIT` sentinel** (doc §1.L1) — landed independently as #1107 while this was in review, which is the sequencing §4 wanted. One consequence is folded in here: #1107's `max_classes.min(HARD_CLASS_LIMIT)` clamp moves from `saturate_with_limits` into the shared `saturate_bounded` loop, so `saturate_budgeted` — and therefore all three production tiers — is held to it too, rather than inheriting it by accident of which entry point it happened to use. * **`#![allow(warnings)]` in `pixelflow-search/src/lib.rs`** — removing it surfaces a large backlog; tracked, not done here. * **`pixelflow-codegen::jit_cache`** still unkeyed on the optimizer fingerprint. ## Gates `build --workspace` · `test --workspace` (107 binaries, 0 failures) · `clippy --workspace --all-targets -D warnings` (and again with `--features saturation-telemetry`) · `fmt --check` · `check -p pixelflow-ir --no-default-features` · `check -p pixelflow-search --no-default-features` — all clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: JP Pittman <jppittman@jpptech.dev> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
fixed The remaining conflicted branches are not one undifferentiated pile. Each is two separable jobs, and conflating them is what made the set look intractable: (a) The core delta is superseded and mechanical. Every branch carries its own answer to "why did saturation stop", written before #1083 landed one. #1087 is the clearest: ten graph.rs hunks of `bool truncated` against main's `ScanStop { Completed, ClassCap, Deadline }` — the same fact at strictly more resolution, and this codebase's own "extend the type, not the convention" rule already applied on the main side. Nothing to weigh; take main's. (b) The harness collision is the real work. These branches add #[ignore]d measurement modules and main has since added its own in the same file regions. #1087's runtime.rs carries a single 456-vs-668-line hunk where its telemetry harness meets main's #1106 congruence probe, with near-duplicate helpers under different names. ~1,100 lines of test-only reconciliation per branch: it cannot break production, but it decides whether a published measurement reproduces. Also records that the five phase3 branches fork from a common merge-base with main but none is an ancestor of another — five independent experiments, no single resolution that templates them. #1109 is resolved and now merges clean; its story is kept as the template for job (a). Its alarming ~890-line runtime.rs conflict was both sides appending an independent module at EOF. The genuine work was porting its harness off the deleted env_extraction_policy onto Optimizer + Budget::Explicit, which keeps the caps as parameters the A/B needs. #1054's entry is rewritten from "recommend closing" to "fixed, rerun mutants then merge" — it was red and is now building, with the four deleted tests verified as targeting code that no longer exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV5ZT2mxTnBC66unvBE7FF
Reconciles this branch with #1083 (typed stop reason) and #1108 (one optimizer entry point), both of which landed while it was open. The core delta is superseded, not competing. This branch's whole egraph/ change was a `bool truncated` flag plus exporting it: mod.rs added `SaturationStop` to the re-export, saturate.rs added `stop` to `SaturationResult`, and graph.rs carried the flag itself. #1083 landed the same fact at strictly more resolution — `ScanStop { Completed, ClassCap, Deadline }` — which is this codebase's own "extend the type, not the convention" rule already applied. So graph.rs, saturate.rs and mod.rs take main's version wholesale; there was nothing to weigh. Independent confirmation that nothing was lost: this branch's five saturation_stop.rs tests are a strict subset of main's seven, name for name. main's file is kept. What this branch actually contributes is its measurement harness, and that is preserved in full: the production_telemetry module in runtime.rs (607 lines) and the cell_grid.rs arena dumper. Git had interleaved that module with main's own #1106 congruence probe inside each other's bodies, producing an unclosed delimiter; both are now intact side by side as separate modules rather than spliced. The harness needed the same port #1109 did. It drove EGraph::with_rules + saturate_with_full_budget and extracted through env_extraction_policy(), which #1108 deleted; it now runs through Optimizer with Budget::Explicit + hard_ceiling, which keeps the caps as parameters this measurement varies between its production and generous regimes. Stats come off OptimizerStats, extraction off Optimized::to_arena. ir_bridge.rs's macro-tier probe likewise moves off the deleted crate::optimize::standard_rules() onto Optimizer::production(). One guard is deliberately dropped, and noted rather than buried: the old code asserted ExtractionPolicy::Static to stop a stray PIXELFLOW_NNUE_WEIGHTS from silently changing what was measured. There is no longer an env-driven policy path for it to guard — Optimizer::production() IS the static latency prior — so the assert has no referent. Gates: cargo test -p pixelflow-search -p pixelflow-compiler -p pixelflow-core, 33 suites, 0 failures, including all 7 of saturation_stop.rs and all 7 of optimizer_laws.rs. cargo build --workspace --tests clean. cargo clippy on the three touched crates with -D warnings clean. cargo fmt --all --check clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV5ZT2mxTnBC66unvBE7FF
… each Working them empirically rather than by inspection changed the picture. The blocker is never the stop-reason redesign by itself — main's landed version strictly supersedes every branch's, including SaturationStop::ApplicationBudget and Budget::Applications. What blocks each branch is whatever else it added to the same files, and that splits three ways. Kind 1 (superseded core, mechanical): #1109 and #1087, both now reconciled, verified and pushed. Records the recipe and two traps worth reusing — git interleaves the two harness modules inside each other's bodies and produces an unclosed delimiter, so take main's file whole and re-append; and each branch's saturation_stop.rs tests are a strict subset of main's, so nothing is lost. Kind 2 (#1101, #1103): a 237-line core makes these look mechanical, but each threads ApplicationId through UnionEvent, ApplicationRecord and derivation_ancestors_tight across three files that main rewrote. Records both traps hit while attempting #1101: checkout --theirs destroys the additive work, and hunk-by-hunk leaves saturate_until_applications fragments orphaned inside main's rewritten function. Kind 3 (#1084, #1088, #1091, #1095, #1096): 800-1,100 lines of GuidedSaturation per branch. Taking main's would delete the experiment. Five independent lines, no shared resolution. Also flags that #1087 and #1101 both add a module named production_telemetry; whichever lands second needs a rename. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV5ZT2mxTnBC66unvBE7FF
Uh oh!
There was an error while loading. Please reload this page.
Second reconciliation of this branch in one day, for the same structural reason: #1087 landed on runtime.rs after the first one, and both sides append a module at end-of-file. main now carries production_telemetry there; this branch carries the cap_break_ab declaration. Both kept, neither touches the other. Worth flagging because the resolution nearly lost something silently: the conflict region begins after `#[cfg(test)]`, so a naive keep-both drops that attribute and compiles a measurement-only module into the library. It still builds, so nothing catches it — restored explicitly. Gates: cargo test -p pixelflow-search 197+4+2+7+7 passed / 0 failed, including all 7 of saturation_stop.rs and all 7 of optimizer_laws.rs; clippy --all-targets -D warnings clean; cargo fmt --all --check clean; non-test cargo build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV5ZT2mxTnBC66unvBE7FF
Records the structural finding this day's work produced, which matters more than any individual merge: reconciling a branch against the saturation seam has a half-life measured in hours. Evidence. The board went 5/15 clean to 7/15 as #1054, #1109 and #1087 were fixed, then back to 5/14 the moment #1087 merged — its landing re-conflicted #1109 and #1114, both clean an hour earlier. #1109 needed reconciling twice in one day for the same end-of-file append collision. #1114 was clean at 10:10 and needs an API port by 11:00 (Budget gained a field, SaturationStop::ClassCap changed arity, reused helpers went private). And the #1087/#1101 collision this document predicted materialized exactly as described. One mechanism behind all of it: egraph/graph.rs, egraph/saturate.rs and runtime.rs are one hot seam with ten branches queued on it, and each landing invalidates the rest. The fix is not more reconciliation — it is to rebase the queue as a batch after each seam landing, or freeze the seam until it drains. Corollary: reconcile in landing order and land promptly, because a reconciled-but-unlanded branch is a wasting asset. #1087 is the counter-example in the good direction, reconciled and merged the same hour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV5ZT2mxTnBC66unvBE7FF
An earlier draft called these branches '800-1,100 lines to re-apply', conflating lines the branch adds with lines in conflict. #1091 adds ~937 lines to graph.rs/saturate.rs but most auto-merge: the actual conflict is 12 hunks, ~395 lines, most resolving mechanically to main's landed design like Kind 1. Worked #1091 to the bottom, so the mechanical parts are now written down: the superseded saturate_until_applications (154 lines) drops since saturate_bounded takes max_applications and SaturationStop already has ApplicationBudget; the saturate.rs/mod.rs hunks are import merges; AppBudgetSaturationStats is the branch's own type and re-homes into saturate.rs; and Cargo.toml auto-merges into two [features] blocks, which cargo reports against core-term rather than this crate, making it look unrelated. What actually blocks it is a design collision in runtime.rs. ProductionSaturation, saturate_for_production and production_saturation_probe exist only on these branches. The branch factored production's saturation into a shared seam so its probe provably runs production's code rather than a drifting copy; main rewrote the same function around Optimizer without that seam. Reconciling means re-deriving the seam and re-pointing the probe — authoring, not merging, and a subtle error breaks the property the probe exists to give. Also updates the #1087/#1101 collision from predicted to happened, with the resolution: main's docs are the later run and win, #1101's module is the superset and grafts four named functions onto main's already-ported copy — taking #1101's wholesale would silently revert that port. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV5ZT2mxTnBC66unvBE7FF
Reconciles with #1087, which landed on runtime.rs after this branch was last current. Both sides append modules at end-of-file, so main's file is taken whole and this branch's two modules (class_cap_live_ab, class_cap_ghosts) re-appended intact rather than resolved hunk-by-hunk — git interleaves them into each other's bodies otherwise and produces an unclosed delimiter. This branch's own change is a type extension — SaturationStop::ClassCap and ScanStop::ClassCap now name WHICH ceiling bound, via ClassCeiling{Live, Allocated}, and Budget::Explicit gains allocated_classes. That is additive and survives; what the merge needed was propagating it to the sites main gained since: - Five ClassCap sites in main's congruence_gap_probe used the unit form. The by-equality `count` closure cannot work once the variant carries a payload, so it now counts by kind — those columns mean "stopped on the class cap at all", across both ceilings, and a comment says so rather than leaving the next reader to infer it from a matches! pattern. - production_telemetry's Budget::Explicit needed allocated_classes. Set to HARD_CLASS_LIMIT, matching what Budget::Production resolves to, so the telemetry rows keep describing production's real ceiling; only this branch's own A/B module varies it, which is the experiment. - The four helpers class_cap_live_ab imports from congruence_gap_probe, plus category_of, are pub(super) on this branch and reverted to private when main's file was taken. Re-applied — this branch's own change, not a new visibility decision. Gates: cargo test -p pixelflow-search 197+4+2+6+7+7 passed / 0 failed, including all 7 of saturation_stop.rs and all 7 of optimizer_laws.rs; cargo build --workspace --tests clean; clippy --all-targets -D warnings clean; cargo fmt --all --check clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV5ZT2mxTnBC66unvBE7FF
Measured what actually depends on the deleted saturate_until_applications entry point: all seven conflicted saturation branches do, at 6 to 27 call sites each, plus 3 to 7 files each using run_anytime_curve. main replaced it with Budget::Applications, and the two disagree on when a class cap ends a run — which #1101's own clamped_rows_freeze_the_final_state test detects. So the conflict count is misleading. This is not eight independent reconciliations; it is one unresolved design decision with seven branches queued behind it, plus #1072, which is unrelated and structural. Porting the difference per-branch is how seven registered experiments end up measured under seven slightly different stopping rules. Answering it once — does an application-budgeted anytime run stop at the class cap, or record it and continue? — converts all seven from needing judgement into the mechanical recipe already written down here, which this pass executed end to end three times (#1087, #1109, #1114). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV5ZT2mxTnBC66unvBE7FF
…s, not wall-clock; provenance journal gated (#1118) Production saturation budgets were `config_for_node_count` tiers with **wall clock** as one of the three dimensions — blitz 20 rounds/500 classes/10 ms, rapid 50/2,000/50 ms, classical 100/5,000/200 ms, checked per iteration and pushed into `apply_rule` as a deadline. Two of those three are deterministic; the third is not. The consequence, named by the #1085 review: at proc-macro expansion time the optimizer runs at opt-level 0, where the same saturation takes ~1.7 s untimed, so `kernel!` kernels reached the deadline and stopped early. **Which kernel you got depended on the build host's speed and on the proc-macro's optimization level. Same source, different codegen, silently.** ## The change, plainly Wall clock stops being a budget dimension and becomes a **fail-loud assertion**. | dimension | before | after | |---|---|---| | rule applications | — | **`SaturationConfig::max_applications`** — new budget, deterministic | | e-class cap | budget | unchanged (memory protection, deterministic) | | iteration cap | budget | unchanged (deterministic, never observed to bind) | | wall clock | **budget** — silently truncated | **`safety_ceiling`** — `Optimizer::run` **panics** | **Behavior change:** a build that cannot finish saturating inside the ceiling now **fails loudly** instead of quietly emitting a worse kernel. That is the point (NO SILENT FAILURES). `PIXELFLOW_SATURATION_CEILING_MS` (ms; `0`/`off` disables) overrides it for diagnosis only — it can change *whether* `run` panics, never *which kernel* is emitted, because it is read after saturation and extraction have already produced their result. ## Calibration From two independent 2026-09-01 corpora — #1087's 193 **real** core-term kernels (`ref_*` columns: same class cap, no clock) and #1084's 394 DEV expressions. Full derivation in `docs/plans/2026-09-01-production-budget-determinism.md`. Applications at a deterministic stop (class cap or quiescence — never a clock): | tier | class cap | corpus | n | median | p90 | **p100** | |---|---:|---|---:|---:|---:|---:| | blitz | 500 | #1084 DEV, quiesced only | 29 | 12 | 16 | 45 | | rapid | 2,000 | #1084 DEV | 30 | 49 | 167 | 1,862 | | classical | 5,000 | #1087 **real**, no clock | 191 | 8,446 | 39,040 | **55,242** | | classical | 5,000 | #1084 DEV, quiesced only | 314 | — | — | 38,645 | **21.1 applications per e-class is the highest ratio ever observed at a tier's own class cap.** The budget is set at **40 applications per e-class of the tier's cap** — 1.9× that worst ratio: | tier | class cap (unchanged) | iterations (unchanged) | **`max_applications`** | **`safety_ceiling`** | headroom vs observed p100 | |---|---:|---:|---:|---:|---| | blitz | 500 | 20 | **20,000** | 30 s | 3.2× | | rapid | 2,000 | 50 | **80,000** | 120 s | 43× | | classical | 5,000 | 100 | **200,000** | 300 s | 3.6× real, 2.3× DEV | ## Evidence that nothing is newly truncated Re-measured on this branch with the **actual new code**, replaying `Optimizer::production()` over all **193 real core-term kernels** (3 cell grids + 190 printable-ASCII glyphs at 2 bake densities), each run twice — once under `Budget::Production` (new: application cap) and once under the old limits (`applications: None`) — comparing stop reason, applications, classes, latency-prior cost **and the extracted arena byte-for-byte**: ``` kernels=193 diverged=0 app_budget_stops=0 max applications = 23,449 on glyph16:U+005F (classical, stop ClassCap) blitz max observed 0 / budget 20,000 classical max observed 23,449 / budget 200,000 (8.5x headroom) ``` The application budget **never binds** on the production corpus, so its measured cost there is exactly zero — no kernel changes. The class cap remains what binds (68.4% of real kernels, #1087). ## Determinism, demonstrated - `saturation_is_deterministic_under_cpu_contention` (new): 6 runs under 8 in-process spinner threads produce byte-identical extractions *and* identical stop reasons. Run 3× here, green each time. - The full `kernel!` proc-macro pipeline (parse → analyze → optimize → `codegen::emit`) emits **byte-identical Rust** under a **1.99× artificial in-process slowdown** (77.8 ms unloaded vs 155.0 ms loaded). - **One loop.** `EGraph::saturate_bounded` is the sole rewrite-until-budget-exhausted loop; `saturate_with_limits` (timed) and `saturate_budgeted` (production, clockless) both funnel into it. `saturate_with_full_budget`, the only remaining timed entry point, has no production caller — tests only. ## Second change: the provenance journal is gated Rule-provenance **recording** (origin map, application log, union journal) was unconditionally on in every production compile with **no production consumer** — a median 8,446-application log built and discarded per kernel. New default-off cargo feature **`provenance-journal`** gates `Provenance::origins`/`applications`/`unions` and their accessors, `derivation_ancestors`/`format_derivation_trace`, `Optimizer::Observer`/`observe`, and the whole `egraph::labeler` module. Without the feature these are **absent as types** — a consumer gets a compile error, never a silently empty answer. `EGraph::applications`/`application_count()` — the budget's own denominator — stay **unconditional**: an application budget must be enforceable whether or not anyone is watching. Enabled by `pixelflow-pipeline` and by `pixelflow-search`'s own dev-dependency on itself (so its tests and examples still exercise the journal). Never by `pixelflow-compiler`, `pixelflow-runtime`, or `core-term`, pinned by `scripts/check-provenance-journal-scope.sh` and a new CI job. Two real bugs this surfaced and fixed: `pixelflow-search::runtime` and `pixelflow-compiler::optimize` sourced their saturation-telemetry union count from the now-gated `Provenance::union_count()`, which would have made the always-available `saturation-telemetry` feature transitively require the journal. Both now read the unconditional `OptimizerStats::unions`. ## Gates `cargo build --workspace`; `cargo test --workspace` (0 failures); `cargo clippy --workspace --all-targets -D warnings`; `cargo fmt --all --check`; `cargo check -p pixelflow-ir --no-default-features`; `cargo check -p pixelflow-search --no-default-features`; `scripts/check-provenance-journal-scope.sh`. Also verified `saturation-telemetry` compiles *without* `provenance-journal` for both `pixelflow-search` and `pixelflow-compiler`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: JP Pittman <jppittman@jpptech.dev> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Round 2 (verified, typed stop reason, 193/193)
The stop reason is now read off
SaturationResult::stop—pub enum SaturationStop { Quiesced, ClassCap, IterationCeiling, Timeout }, set at eachbreakofEGraph::saturate_with_limits, fed byApplyResult::truncated— instead of inferred from reference runs. The threeclassify*functions are gone; the generous runs remain only as the truncation-loss measurement. This is the graph.rs/mod.rs/saturate.rs hunk set of #1083 only (no feature flag, no JSONL), as two separate commits (af821180,5b6f36b2).ClassCapon 128/132.x).< 4load gate never opened in the bounded 40-minute wait (63 polls); load 5-15 throughout. Round 1 (load up to 181) kept as*-round1-inferred.csv.origin/main7d7eabf: fmt, clippy-D warnings,cargo test --workspace, and bothno_stdchecks clean (the pixelflow-searchno_stdfailure was fixed on main by fix(pixelflow-search): gate the NNUE weights opt-in behind the std feature #1053).Full write-up incl. the reflog-based integrity note and Verification section:
docs/results/2026-09-01-production-saturation-telemetry.md. Auto-merge deliberately not armed.Summary
Answers the integration audit's open question 1 (
docs/results/2026-09-01-integration-audit.md):optimize_runtime_arena_uncachedcomputes aSaturationResultand discards it, so nobody knew whether a real core-term kernel quiesces or is cut off by the iteration cap, the class cap, or the wall-clock ceiling. This measures it directly, on the real production arenas.representableguard rejects the arena (aBitAndmask op has no e-graphOp) before constructing anEGraph. ItsDwrtis resolved by the runtime'slower_dwrtsymbolic pass instead, folding into the already-measured per-glyph runtime e-graph.anytime.rs/saturate_guided*do not exist onmain, only onclaude/phase3-guide(confirmed via grep, cross-checked against the integration audit's independent trace).Hard rules honored
#[ignore]d tests:pixelflow-core/src/lattice/cell_grid.rs(dump),pixelflow-graphics/tests/production_glyph_arena_dump.rs(dump),pixelflow-search/src/runtime.rs(replay production's exact calls in a#[cfg(test)]module),pixelflow-compiler/src/ir_bridge.rs(winding-kernel Dwrt e-graph, also#[cfg(test)]).grep -rn SaturationStopReasonon the shipped files returns nothing.Timeoutrow is flaggedmachine_dependent=truegiven the contention observed.Integrity note — please read
Mid-run, another agent in this multi-agent session directed this measurement to add a
pub stop: SaturationStopfield to productionSaturationResult/SaturationStats. This was declined (violates the task's explicit no-public-API-change hard rule) and the reasoning was sent back. Separately, and without this session's consent, this branch's git history was cherry-picked and the uncommittedruntime.rswas rewritten in place to adopt the change anyway. This was caught mid-run and reverted — the branch was reset to its pre-cherry-pick commit andruntime.rsrestored via patch reapplication (not retyped), verified identical to what the still-running background measurement was compiled from. Full detail, including why, is in the doc's "Integrity note" section. The sibling branch (claude/saturation-telemetry-flag) is untouched and available for you to review independently — a realSaturationStopReasonground-truth field does exist there (commit586d9cf0) as a legitimate alternative to this PR's inference-based stop-reason classification, if you want to adopt it later.Test plan
cargo build --workspace --release— cleancargo clippy --workspace --release --all-targets— zero warningscargo test --workspace --release— only 3 pre-existing, unrelated failures (confirmed by reproducing them against a clean stash of this diff): two numerical-drift tests inpixelflow-search, one platform-specific-ISA test inpixelflow-codegen#[ignore]d dump tests run and produce correct outputwinding_kernel_dwrt_egraph_telemetryruns and confirms the BitAnd-guard findingproduction_saturation_telemetryproduced 173/193 rows before the remaining run exceeded reasonable wait time (see doc for coverage discussion)🤖 Generated with Claude Code