From d90e15ef1ceff2e7bfa745484c5aaf68f6dad12e Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Fri, 18 Sep 2026 00:31:04 -0400 Subject: [PATCH] fix(cosim): the excluded crate's lockfile named neither of the last two releases `crates/rustynes-cosim/Cargo.lock` pinned the first-party crates at 2.6.17 while the workspace was 2.6.19. The drift started one release earlier: v2.6.15 workspace 2.6.15 cosim lock 2.6.15 ok v2.6.16 workspace 2.6.16 cosim lock 2.6.16 ok v2.6.17 workspace 2.6.17 cosim lock 2.6.17 ok v2.6.18 workspace 2.6.18 cosim lock 2.6.17 DRIFT v2.6.19 workspace 2.6.19 cosim lock 2.6.17 DRIFT The mechanism is the crate's exclusion, which is the whole reason it needs a committed lockfile in the first place. No workspace command reaches an excluded package, so its `Cargo.toml` version is bumped BY HAND at each release -- and the hand step does not touch the lockfile beside it, which moves only when somebody runs cargo in that directory. `the_excluded_crates_lockfile_is_tracked` asserts the file is committed, because without one CI re-resolves. That is necessary and not sufficient: a tracked file can be stale, and cargo rewrites a stale one on the next build, so the committed resolve is not the resolve anything actually uses. The tracked-ness test was guarding the door while the contents drifted. HOW IT SURFACED, which is the part worth keeping. It was not found by reading the lockfile. The pinned oracle worktree at `~/.cache/rustynes-cosim/` -- checked out detached at the v2.6.18 commit, and the source the co-simulation goldens are exported from -- came up DIRTY during a branch cleanup, carrying an unstaged `Cargo.lock` change. That looked like build residue to discard. It was cargo correcting a stale file, at a commit where the committed one was already wrong. Refreshed with `cargo metadata --manifest-path crates/rustynes-cosim/Cargo.toml`, which is a minimal resolve: the diff is exactly six version lines, 2.6.17 -> 2.6.19, with no third-party churn. The gate is `the_excluded_crates_lockfile_names_the_workspace_version`. It walks `[[package]]` blocks rather than grepping `version = `, so a third-party crate that happens to sit at the same number cannot stand in for a first-party one that does not, and it FAILS CLOSED on fewer than five first-party entries -- a parse finding nothing must not read as agreement, which is this project's most-repeated defect in its smallest form. Demonstrated by three mutations, all CAUGHT: * the actual file this release shipped (all six at 2.6.17) * one entry wrong and the other five correct * the lockfile emptied of `[[package]]` blocks -- caught by the fail-closed clause rather than by the comparison, reporting "only 0 first-party packages found" Baseline green, `cargo fmt --all --check` clean, clippy clean on the harness. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- crates/rustynes-cosim/Cargo.lock | 12 ++-- .../tests/cosim_manifest_audit.rs | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/crates/rustynes-cosim/Cargo.lock b/crates/rustynes-cosim/Cargo.lock index 5f2a529c..a77eb10a 100644 --- a/crates/rustynes-cosim/Cargo.lock +++ b/crates/rustynes-cosim/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "rustynes-apu" -version = "2.6.17" +version = "2.6.19" dependencies = [ "bitflags", "libm", @@ -107,7 +107,7 @@ dependencies = [ [[package]] name = "rustynes-core" -version = "2.6.17" +version = "2.6.19" dependencies = [ "bitflags", "lz4_flex", @@ -121,7 +121,7 @@ dependencies = [ [[package]] name = "rustynes-cosim" -version = "2.6.17" +version = "2.6.19" dependencies = [ "rustynes-core", "sha2", @@ -129,7 +129,7 @@ dependencies = [ [[package]] name = "rustynes-cpu" -version = "2.6.17" +version = "2.6.19" dependencies = [ "bitflags", "thiserror", @@ -137,7 +137,7 @@ dependencies = [ [[package]] name = "rustynes-mappers" -version = "2.6.17" +version = "2.6.19" dependencies = [ "bitflags", "rustynes-apu", @@ -146,7 +146,7 @@ dependencies = [ [[package]] name = "rustynes-ppu" -version = "2.6.17" +version = "2.6.19" dependencies = [ "bitflags", "libm", diff --git a/crates/rustynes-test-harness/tests/cosim_manifest_audit.rs b/crates/rustynes-test-harness/tests/cosim_manifest_audit.rs index ff12997b..a25b00a8 100644 --- a/crates/rustynes-test-harness/tests/cosim_manifest_audit.rs +++ b/crates/rustynes-test-harness/tests/cosim_manifest_audit.rs @@ -98,6 +98,70 @@ fn the_excluded_crate_still_matches_the_workspace_package_fields() { } } +/// The lockfile must also AGREE with the workspace, not merely exist. +/// +/// `the_excluded_crates_lockfile_is_tracked` below asserts the file is +/// committed, because an excluded package re-resolves in CI without one. That +/// is necessary and it is not sufficient: the tracked file can be STALE, and +/// it was. The workspace bump is applied to `crates/rustynes-cosim/Cargo.toml` +/// by hand (the crate is excluded, so nothing inherits it) and that hand step +/// does not touch the lockfile beside it, which only moves when somebody runs +/// cargo in that directory. +/// +/// Measured across releases when this test was written: in sync at v2.6.15, +/// v2.6.16 and v2.6.17, then `2.6.17` against a `2.6.18` workspace, and still +/// `2.6.17` against `2.6.19` -- two releases shipped with a lockfile naming +/// neither of them. The symptom that exposed it is the mechanism: cargo +/// rewrites the file on the next build in that crate, so a pinned worktree +/// checked out at a release commit came up dirty the moment anything was built +/// there. A lockfile cargo silently rewrites is not pinning anything, which is +/// exactly what the tracked-ness test exists to prevent one layer down. +#[test] +fn the_excluded_crates_lockfile_names_the_workspace_version() { + let root = read("Cargo.toml"); + let want = field_in_table(&root, "[workspace.package]", "version") + .expect("workspace.package has no `version` -- did the table move?"); + let lock = read("crates/rustynes-cosim/Cargo.lock"); + + // Walk `[[package]]` blocks and check every first-party entry. Scanning the + // blocks rather than grepping `version = ` is what keeps a third-party + // dependency that happens to be at the same number from standing in for a + // rustynes crate that is not. + let mut checked = 0usize; + let mut name: Option<&str> = None; + for line in lock.lines() { + let line = line.trim(); + if line == "[[package]]" { + name = None; + } else if let Some(v) = line.strip_prefix("name = ") { + let v = v.trim().trim_matches('"'); + name = v.starts_with("rustynes-").then_some(v); + } else if let Some(v) = line.strip_prefix("version = ") + && let Some(pkg) = name.take() + { + let got = v.trim().trim_matches('"'); + assert_eq!( + got, want, + "crates/rustynes-cosim/Cargo.lock pins {pkg} at {got:?} while the \ + workspace is {want:?}. Refresh it -- `cargo metadata \ + --manifest-path crates/rustynes-cosim/Cargo.toml` is enough, and \ + commit the result; the crate is excluded, so no workspace \ + command reaches it." + ); + checked += 1; + } + } + + // Fail closed. A parse that finds nothing must not read as agreement -- the + // same shape as a gate reporting a pass over zero rows. + assert!( + checked >= 5, + "only {checked} first-party packages found in the cosim lockfile; the \ + file is empty, renamed, or no longer in `[[package]]` form, so this \ + test verified almost nothing" + ); +} + /// The crate must not quietly hold itself to weaker lints than the project. #[test] fn the_excluded_crate_carries_the_same_lints() {