fix(cache): re-scope mutation gate to a root task so releases stop busting it - #35
Merged
Merged
Conversation
…sting it The mutants task was package-scoped on the npm launcher while its command ran `cd ../..` into the repo root against the Rust crate. Turbo always hashes a package task's own manifest, so the launcher's version field -- rewritten by release automation on every release -- sat in the verdict's cache key. Measured via `turbo --dry=json`: the input set contained `package.json` beside the 20 Rust determinants, despite never being declared. Hit rate on release runs was therefore exactly 0. The transport layer made it permanent: the actions/cache key hashed only the Rust determinants, so it hit while the turbo hash missed, and exact-key restore suppresses the post-job save. The fresh verdict was discarded every run, so the miss never self-healed. Fix aligns scope, determinants and key: - turbo.json: `mutants` -> `//#mutants`, inputs = determinants only - root manifest: `mutants` is the raw cargo command turbo executes; `gate:mutants` is the cached entrypoint (distinct name avoids recursion) - launcher manifest: drops the script it never legitimately owned - ci.yml: transport key gains the root manifest so K = I = D by construction, and the job calls the cached entrypoint Verified: cold miss 117 mutants / 113 caught / 4 unviable / 0 survived in 2m4s; immediate re-run hit in 9ms; simulated release bump (version rewrite + changelog append) hit in 8ms -- previously a 4min re-run whose result was then thrown away. No changeset: CI-only, no consumer-observable change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The mutation gate re-ran its full 4-minute mutant loop on every release, and the fresh verdict was then discarded — so the miss never self-healed.
Root cause (measured, not inferred)
turbo run //#mutants --dry=jsonbefore the fix:The
mutantstask was package-scoped on the npm launcher, while its command rancd ../..into the repo root against the Rust crate. Turbo unconditionally hashes a package task's own manifest, so the launcher'sversion— rewritten byrelease-version.tson every release — sat in the verdict's cache key. It was never declared ininputs; a reading pass of the declared inputs cannot see it.Hit rate on release runs was therefore exactly 0.
Why it never self-healed
The transport key hashed only the Rust determinants, so it hit while the turbo hash missed. Exact-key restore suppresses the post-job save:
Two keys for one artifact, moving independently.
Fix — align scope, determinants and key
turbo.json:mutants→//#mutants(root task);inputs= determinants only.mutantsis the raw cargo command turbo executes;gate:mutantsis the cached entrypoint. A root task runs the identically-named root script, so a wrapper under the same name would recurse — hence the distinct name.ci.yml: transport key gains the root manifest, so key = engine surface = determinants by construction rather than as a maintained superset that silently drifts. Job now callspnpm gate:mutants.Verification
>>> FULL TURBO1019d456…→2117ac58…, missThe last row matters: caching that cannot still detect a real Rust change is worthless. The third row is the regression being fixed — that exact edit previously forced a full re-run whose result was then thrown away.
Full gate green:
cargo fmt --check,cargo clippy --all-targets -D warnings,cargo test --all-targets(13 passed),turbo lint typecheck build,deno fmt/lint/check-matrix,actionlint.Docs
The existing solution doc prescribed inputs that no longer exist (
flake.nix,flake.lock) and namedpackage.jsonas a required input — the very defect. Left in place it would have been a second correct-sounding source contradicting the code. Rewritten in place with the two-sided invariant:The prior revision audited only the first direction, which is how a cache with a hit rate of zero passed review. Renamed to match its corrected subject.
No changeset: CI-only, no consumer-observable change.