Prevent resolved dependency versions in Cargo.lock from dropping below a
committed baseline. Rebases, lockfile regeneration, and targeted
cargo update invocations can silently downgrade resolved versions;
cargo build --locked detects drift from the committed lockfile but does
not enforce a version floor.
cargo install cargo-ratchet
cargo ratchet
The first run creates .Cargo.lock.ratchet at the workspace root; commit it
alongside Cargo.lock. After changing dependencies, run cargo ratchet
again and commit the updated baseline.
In CI, run:
cargo ratchet --fail-on-change
This never writes and passes only when the baseline is byte-identical to
Cargo.lock. The fix for any failure is the same: run cargo ratchet
locally and commit the result. Exit code is 0 on pass, nonzero otherwise.
--fail-on-change— never write; fail unless the baseline exactly matches the lockfile.--json— report violations as JSON on stdout instead of text on stderr.--manifest-path <PATH>— select the workspace (the lockfile lives at its root).--lockfile <PATH>— check a specific lockfile without workspace discovery.--ratchet-file <PATH>— override the baseline path [default:.<lockfile name>.ratchetnext to the lockfile].
For each package name in the baseline, the highest resolved version in each
lockfile is compared: removing a package is allowed, lowering its highest
version is not. A name may legitimately appear at multiple versions (e.g.
http 0.2 and http 1 during ecosystem transitions); only the highest per
name participates.
Resolved crates.io dependency edges from those highest-version packages are compared the same way, which catches a package's resolved dependency being downgraded even when the workspace still contains the newer version elsewhere. Git, path, and alternate-registry edges are skipped because version ordering alone does not establish immutable identity there.
Every passing check copies Cargo.lock over the baseline — that is what
produces the ratchet effect; a separate advance command would be forgotten
and the floor would lag. And the baseline can never advance without a
passing check, so baseline updates can't slip in unchecked.
To permit a deliberate downgrade (e.g. backing off a broken upgrade), edit
.Cargo.lock.ratchet to lower the affected floor — with justification in
review — then re-run cargo ratchet.
compute_violations(&baseline, ¤t) is a pure function from two parsed
cargo_lock::Lockfiles to a list of
violations; check_and_advance(&paths) and check_fail_on_change(&paths)
add the file orchestration described above.
- Other ecosystems' lockfiles (package-lock.json, uv.lock, flake.lock, …). The semantics here — semver ordering, crates.io release immutability, resolved-edge structure — are Cargo-specific.
- Ratcheting git/path/alternate-registry dependency identity (commit pinning policy). Possible future expansion, separate policy.
- Duplicate-version bans (cargo-deny covers this) or vulnerability checks (cargo-audit covers this).