Skip to content

Implement CI: test, check lean, fuzz, bench - #100

Open
imlvts wants to merge 24 commits into
Adam-Vandervorst:masterfrom
imlvts:ci-self-hosted
Open

imlvts wants to merge 24 commits into
Adam-Vandervorst:masterfrom
imlvts:ci-self-hosted

Conversation

@imlvts

@imlvts imlvts commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Here's how it looks like:

Fuzzing result: imlvts#2 (comment)
Benches result: imlvts#1 (comment)

imlvts and others added 24 commits September 9, 2026 15:36
Workflow runs on the self-hosted runner: a test job (release build,
cargo test with default and arena_compact+random features, docs) and a
bench job that benchmarks the PR head against its base on the same
machine.  bench_ab.sh builds both sides once, runs them in alternating
order pinned to one core, averages the rounds with bench_avg_files.py
and posts the bench_cmp.py table to the step summary and the PR.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
…ning

bench_ab.sh failed on the runner with exit 101 because act_paths declares
required-features (arena_compact, serialization) and the script built
without them.  It now reads each side's Cargo.toml and passes the features
the requested benches need, and prints the cargo error into the step log
when a build fails instead of leaving it in a temp file.

The script also appends to progress.txt after every run and rewrites
compare.txt after every completed round.  The workflow runs it in the
background and, once a minute, posts progress plus the compare table of
the rounds finished so far to one PR comment via pr_comment.py (stdlib
only).  The comment is found by a marker so re-runs and later pushes
reuse it, and its id is cached per run so the periodic updates skip the
lookup.  This replaces the sticky-comment action.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
…th results

The per-minute progress updates duplicated the job log.  The comment now
carries a link to the specific job (looked up from the run's jobs as the
one in progress on this runner, falling back to the run link) and, when
the bench finishes, the compare table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
… run

The table for a bench is averaged over the rounds finished so far and
saved as cmp-<bench>.txt; compare.txt is the concatenation, rewritten at
the end of every round.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
The crate on master has 44 new divergences from the Lean model on the
seed-7 corpus, so "zero divergences" cannot be the bar.  fuzz_ab.sh runs
head and base on identical inputs (model vs crate, 20000; ACT read side,
5000) and fails only when head diverges on an input base did not.  Both
sides use head's differential/ and lean/, so only the crate under test
differs; if base cannot be built with head's harness its own is tried,
and with no baseline at all the job reports that and does not gate.

Two enabling fixes: the differential harness did not compile since the
ZipperValues/ZipperValuesAt split (ReadSource now requires
ZipperValuesAt<u64>), and differential.py accepts PATHMAP_TRACE /
PATHMAP_ACT_TRACE to find binaries built into another target dir.

The fuzz job runs after the tests, on PRs and manual runs.  Lean is
installed through elan for the runner user; lake's build dir is kept in
the runner's cache since checkout wipes ignored files.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
Same inputs, env vars and output files as the bash versions, which were
mostly embedded Python anyway.  bench_ab.py imports the divan helpers
from benches/ instead of shelling out to bench_avg_files.py and
bench_cmp.py.  Standard library only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
bench_ab.py now also writes summary.md and summary.json after every
round: one row per bench (cases, geometric mean, largest gain and loss,
counts beyond 5%) and a collapsed list of the cases that moved more than
5%.  The PR comment and the run summary use it; the full per-bench
tables stay in the job log and the bench-out artifact.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
The job stays green; the run shows the annotation and the summary names
the inputs.  FUZZ_STRICT=1 restores the hard failure (with an error
annotation).  A run that does not finish still fails the job.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
For up to FUZZ_REPROS (3) newly diverging inputs with distinct
first-differing operations, fuzz_ab.py runs lean/shrink.py on the saved
input and `pathmap_trace --repro` on the result.  The summary gets a
collapsed block per input with the differing trace lines and the Rust
program; the .min.bin and .rs files go to fuzz-out/repro/ in the
artifact.  shrink.py accepts PATHMAP_ORACLE / PATHMAP_TRACE /
PATHMAP_ACT_TRACE like differential.py.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
The toolchain step resolves lean/lean-toolchain through elan (which
downloads it when missing) and prints the versions; fuzz_ab.py reports
each lake and cargo build with its duration instead of only on failure.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
A clean runner has neither.  Every job's toolchain step now installs
rustup into $HOME if absent and puts it on the PATH; the fuzz job does
the same for elan and resolves lean/lean-toolchain there, which fetches
the pinned Lean.  Verified from an empty home with a bare PATH: rustup,
stable Rust, elan and Lean 4.33.1 all arrive in about 20 seconds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
Each bench's base and head runs still share one core back to back, but
different benches run at the same time on different cores from
BENCH_CPUS (default: one SMT thread per physical core, every other core,
at most 16, which spreads them over the L3 complexes).  A round's wall
time is now that of its slowest bench pair rather than the sum.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
An A/A run (same commit on both sides) reported 5-25% differences on
superdense_keys and binary_keys cases, consistent across rounds, with
the two sides pinned to the same core.  The sides were different
binaries: built at different paths into different target dirs, they got
different crate hashes and code layout.  Now one worktree is used for
both: check out base, build, copy the bench executables out; check out
head, build, copy out.  The historical bench run did the same and had a
round-to-round CV under 1% on those benches.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
`lake build` type-checks the theorems in Spec.lean and evaluates every
#guard in Check.lean, so a broken law or regression fixture fails the
job.  Lean only warns on sorry, so the job greps the build log and the
model sources for it (and for axioms) separately.  Lake's build dir is
kept in the runner's cache; a warm run takes seconds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
pr_comment.py takes --id / --title / --dir so each job owns one comment
per PR (bench keeps its marker and defaults).  The fuzz job posts its
summary after every run: verdict, per-mode tables, the inputs that
diverge on head but not on base, and the shrunk Rust reproducers in
collapsed blocks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
Its findings are warnings, and a PR with failing tests is one whose
divergences are worth seeing.  The bench job still needs both to pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
One row per first-differing operation (or final-state mismatch) with
the input count and a first example, instead of up to 50 inputs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
fuzz_ab.py writes a findings file when head has new divergences or a
run did not finish; pr_comment.py --create-only-if creates the comment
only then.  An existing comment is still updated by a clean run, so it
does not keep showing stale findings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
The comparison diffs only the unclassified divergences, so inputs that
moved between the known-bug bucket and agreement were invisible: a base
with 826 known hits and a head with 221 showed 44 new on both sides and
nothing fixed.  The summary now has a legend and a table of known
classes whose hit counts differ between base and head.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXEKcXZn55RYJY7BSJuYeS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant