feat: Make Cargo task hashing sound across toolchains, lockfile changes, and profiles
Three soundness gaps in the Cargo experiment, plus a latent hashing bug
the verification loop exposed:
- The compiler version now participates in every Cargo package's hash via
the same external-dependency mechanism JS packages use: `rustc
--version` (resolved from the repo root so rust-toolchain overrides
apply) becomes a synthetic entry in `transitive_dependencies`. A
different toolchain never restores another toolchain's artifacts, with
or without a committed toolchain file.
- Cargo.lock is no longer a global invalidator. A new parser in
turborepo-lockfiles computes each workspace member's transitive
external closure from the lockfile (identity = version + source +
checksum, so git rev bumps count), feeding per-crate external hashes.
Verified live: mutating a dependency checksum flips only the tasks of
crates whose closures contain it. A missing lockfile contributes
nothing; an unparsable one is a hard error.
- Deliverable output globs use a wildcard profile segment (target/*/...),
so --release and custom profiles cache without configuration;
pass-through args already give each profile its own hash.
- Fix nondeterministic global hashing: the synthetic cargo workspace
package shares the repo-root directory with the root JS package, and
both fed the JS lockfile's directory-keyed external-dependency map —
HashMap iteration order decided which entry survived, flipping the
root's external hash (and the global hash) roughly 50/50 per process
and silently halving cache hit rates. Cargo packages are now excluded
from JS lockfile closure computation on both the read and write sides,
with a probabilistic regression test.