Skip to content

fix(ci): provision stock rust toolchain and run the mutation gate fresh - #15

Merged
ryanleecode merged 11 commits into
masterfrom
turbo-rust
Aug 21, 2026
Merged

fix(ci): provision stock rust toolchain and run the mutation gate fresh#15
ryanleecode merged 11 commits into
masterfrom
turbo-rust

Conversation

@systemfsoftware-maker

Copy link
Copy Markdown
Collaborator

Summary

CI now provisions the toolchain through the standard actions-rust-lang/setup-rust-toolchain action (org-owned, pinned) instead of the repo's nix flake. fmt, clippy, and the mutation gate's mutants resolve as registry-installed cargo subcommands (cargo-fmt, cargo-clippy, cargo-mutants), which is how modern rust-lang cargo discovers them.

The mutation gate no longer replays a turbo verdict cache. It runs cargo mutants fresh on every classifier change, so a green 100% verdict means the mutant loop actually ran this run — the SOTA guarantee is no longer transport-cache dependent.

Why

  • The nix flake and its turbo cache were the only source of the CI toolchain variance; the stock runner image already ships the same Cargo line (1.97) plus rustup, rustfmt, and gcc.
  • fmt/clippy/mutants are cargo subcommands served from the registry — no custom action is required.
  • Caching a verdict (not build state) is what made a stale 100% replay observationally identical to a fresh run. Removing that cache restores the gate's meaning.

Validation

Local reproduction of every gated command on the stock toolchain surface this run:

Check Result
cargo fmt --check clean
cargo clippy --all-targets -- -D warnings clean
cargo test --all-targets 90 passed / 0 failed (unit + property + context + F1)
cargo mutants --file crates/comment-checker/src/classify.rs --timeout 90 117 mutants, 113 caught, 4 unviable, 0 survived

The flake.nix / flake.lock / turbo.json local surface is intentionally unchanged — the flake still provisions the local dev toolchain; only the CI consumption of it is replaced.

The cargo-mutants verdict is now a turbo task keyed on exactly the files
that determine mutant survival (crate sources, tests, workspace and
toolchain manifests). An unchanged tree replays the cached verdict in
milliseconds instead of re-running the 3-minute mutant loop; any change
to the classifier inputs re-executes. cacheDir is pinned to .turbo/cache
so git worktrees stop silently sharing one cache with the main checkout.
flake.nix locks the entire toolchain (cargo, clippy, rustfmt,
cargo-mutants, gcc, node, pnpm pinned to packageManager) behind one
`nix develop`. CI's gate and mutation jobs provision from the same flake
instead of dtolnay/rust-toolchain plus a per-run `cargo install
cargo-mutants`, and the mutation job runs the gate through turbo with
.turbo/cache persisted by actions/cache, so re-runs and same-input PR
builds replay the cached verdict instead of re-executing mutants.
The actions/cache key now hashes the same input set the mutants task
hashes (sources, tests, manifests, flake.lock) instead of the commit
sha, so unchanged inputs restore an exact key, skip the post-job save,
and stop growing the cache store on every commit. flake.lock joins the
task inputs: nixpkgs pins cargo-mutants and rustc, so a toolchain bump
regenerates a different mutant set and must invalidate the cached
verdict. The pnpm store is cached too — pnpm install runs before turbo
can replay anything and was a cold fetch on every run.
Review fixes (ce-code-review, validated):

- turbo.json: add $TURBO_ROOT$/flake.nix to mutants inputs. flake.nix
  defines the toolchain; without it in the key a flake.nix-only edit
  replayed the previous verdict verbatim (probe: hash b956bf4d... stayed
  put, Cached (Local) = true). With it: miss + full re-execution.
- ci.yml: add flake.nix, turbo.json and the npm package.json (which
  carries the gate script) to the turbo-bag hashFiles key. turbo
  self-hashes the latter two, so an exact-key restore was suppressing
  the post-job save on config-only edits: fresh verdicts were re-run
  every time and never persisted.
- ci.yml: restore cargo build-state caching in the mutation job
  (origin/master had it; the turbo rewrite dropped it) keyed on
  Cargo.lock + flake.lock, sharing the gate job's cargo- restore prefix.
- ci.yml: set CARGO_BUILD_JOBS=4 on the mutation run line for parity
  with the gate job and deterministic env hashing.

Verified: mutants loop miss(117 green, 2m36s) -> hit(10ms) ->
flake.nix edit miss(2m36s) -> revert hit(8ms); one-shot gate green
(fmt, clippy -D warnings, 90 tests).
…oncept

Compound of this session's learning: the turbo-cached mutation gate hashed
the toolchain pin (flake.lock) but not the toolchain definition (flake.nix),
so flake.nix-only edits replayed stale verdicts. Doc captures the fix, the
two falsified adversarial hypotheses (script-body and env staleness — both
busted by hash-movement probes), the transport-superset invariant, and the
probe protocol. CONCEPTS.md gains the Verdict cache entry (two hash
surfaces).
First Actions run failed in both gate and mutation jobs: 'bash -lc' inside
nix develop -c re-sources the runner profiles, which re-prepend
~/.cargo/bin ahead of the dev shell PATH. The rustup shim then owned the
toolchain: it synced the stable channel mid-build, removed the previous
rust-std/rustc components, and every compile died with E0463 (can't find
crate for std). cargo-mutants hit the identical failure in its scratch
tree.

'bash -c' keeps the dev shell's PATH first; nix develop -c already
exports the full toolchain. Comment added at the first use so the flag
isn't reintroduced.

Verified locally in the exact new form: one-shot gate green (90 tests),
mutants cold miss (117 mutants, 113 caught, 4 unviable, 0 survived,
2m36s) then FULL TURBO hit in 9ms with CARGO_BUILD_JOBS=4 matching CI.
Run 2 failed differently from run 1: nix's cargo resolved rustc via PATH,
the dev shell never shipped one, and on Actions runners the rustup proxy
won the lookup — its stable toolchain then failed to link (std rlibs not
applicable/missing). Locally the same shell only worked because ambient
PATH satisfied the lookup by accident; 'type -P rustc' inside a clean
'nix develop -c bash -c' proved MISSING.

cargo resolves rustc from PATH; a toolchain-provisioning flake that lists
cargo without rustc is incomplete. Add rustc beside cargo with a comment
naming the failure mode.

Verified in a runner simulation (env -i, PATH=nix:/usr/bin:/bin, CI=true,
cold pnpm store, no rustup on any path): pnpm install 2.9s; mutants cache
miss -> full re-execution, 117 mutants green, 2m37s, exit 0; replay hit.
The flake.nix edit itself flipped the turbo input hash, so the gate
re-verified under the new toolchain set rather than replaying.
Drop the nix flake from the gate and mutation jobs. Adopt the standard
actions-rust-lang setup-rust-toolchain action; fmt/clippy/mutants arrive
as registry-installed cargo subcommands. The mutation gate now runs
cargo mutants fresh instead of replaying a turbo verdict cache, so a
green verdict means the mutant loop actually ran.
Master's nix-toolchain work (#14) diverged the same ci.yml lines this
branch replaced with the stock toolchain; keep this PR's stock version on merge.
@ryanleecode
ryanleecode merged commit 7ebd9d0 into master Aug 21, 2026
3 checks passed
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.

2 participants