The recurrence
Every Cargo.lock regeneration on the long-lived turnloop/integration branch re-resolves tempfile 3.27.0's getrandom from 0.4.2 down to 0.3.4. It has happened on three consecutive catch-up merges (2026-09-22), each time caught by hand and repaired with cargo update -p tempfile --precise 3.27.0.
It isn't a bad conflict resolution being repeated. tempfile requires getrandom >=0.3.0, <0.5, and main's lock already contains three majors — 0.2.17, 0.3.4 and 0.4.2 — because other crates pull each (ahash/bson/quinn-proto → 0.3.4, bcrypt/crypto-bigint/crypto-common → 0.4.2, jsonwebtoken/p12 → 0.2.17). With a satisfying version already in the graph, the resolver is free to reuse it, so regeneration flips tempfile to 0.3.4 whenever it feels like it.
Why CI cannot catch it
By design. #10982's gate is merge-time: the per-PR lint job runs only scripts/lock_no_downgrade.py --self-test, never the comparison. So on a branch that regenerates its lock repeatedly, the only thing standing between a silent downgrade and main is someone remembering to run the check by hand. That's the same shape as #10980 itself — a lock change nothing asserts on.
Options, roughly in order of durability
- Run the real comparison per PR, base-vs-head, not just the self-test. The objection was that a branch predating a bump would fail through no fault of its own, but comparing the PR's merge result against its base answers the question the gate actually cares about ("does merging this lower a pin?").
- Make the resolution deterministic for this dependency: a workspace-level
getrandom requirement that forces one major, so a fresh resolve can't pick another. Needs checking whether it actually constrains a transitive consumer, since three majors coexist legitimately today.
- Status quo: keep it as a documented manual step in the integration branch's merge procedure. It has worked three times because one session kept catching it; it fails the first time that session doesn't.
Worth recording either way
Three getrandom majors in one lock is itself worth a look. None is flagged by cargo-deny today, but it means "the lock has getrandom 0.4.2" says nothing about which version any given consumer actually resolves — which is exactly the false-negative that made the per-package-max form of the downgrade check useless (#10982).
The recurrence
Every
Cargo.lockregeneration on the long-livedturnloop/integrationbranch re-resolvestempfile 3.27.0'sgetrandomfrom 0.4.2 down to 0.3.4. It has happened on three consecutive catch-up merges (2026-09-22), each time caught by hand and repaired withcargo update -p tempfile --precise 3.27.0.It isn't a bad conflict resolution being repeated.
tempfilerequiresgetrandom >=0.3.0, <0.5, and main's lock already contains three majors — 0.2.17, 0.3.4 and 0.4.2 — because other crates pull each (ahash/bson/quinn-proto→ 0.3.4,bcrypt/crypto-bigint/crypto-common→ 0.4.2,jsonwebtoken/p12→ 0.2.17). With a satisfying version already in the graph, the resolver is free to reuse it, so regeneration flipstempfileto 0.3.4 whenever it feels like it.Why CI cannot catch it
By design. #10982's gate is merge-time: the per-PR
lintjob runs onlyscripts/lock_no_downgrade.py --self-test, never the comparison. So on a branch that regenerates its lock repeatedly, the only thing standing between a silent downgrade andmainis someone remembering to run the check by hand. That's the same shape as #10980 itself — a lock change nothing asserts on.Options, roughly in order of durability
getrandomrequirement that forces one major, so a fresh resolve can't pick another. Needs checking whether it actually constrains a transitive consumer, since three majors coexist legitimately today.Worth recording either way
Three getrandom majors in one lock is itself worth a look. None is flagged by
cargo-denytoday, but it means "the lock has getrandom 0.4.2" says nothing about which version any given consumer actually resolves — which is exactly the false-negative that made the per-package-max form of the downgrade check useless (#10982).