diff --git a/.agents/skills/wave/agent-rules.md b/.agents/skills/wave/agent-rules.md index 7358f704..9d6554a7 100644 --- a/.agents/skills/wave/agent-rules.md +++ b/.agents/skills/wave/agent-rules.md @@ -56,6 +56,7 @@ deleted worktree. Tidying history before the PR is fine; losing it is not. ``` direnv exec . ./scripts/fmt-workspaces.sh # NOT cargo fmt --all; exit 2 = the gate did not run +direnv exec . ./scripts/check-harness-lock.sh # offline; a Cargo.toml change can stale harness/opfs-contract's lock direnv exec . cargo clippy --workspace --all-targets -- -D warnings direnv exec . cargo test --workspace --no-fail-fast direnv exec . python3 scripts/check-comment-runs.py --self-test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4646ac2d..9b026c5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,6 +144,10 @@ jobs: # roots. Exit 2 here is a usage error, never a verdict (improvise-b4ks). - name: Every workspace root is formatted run: ./scripts/fmt-workspaces.sh + # A manifest-only change to a crate the harness reaches by path stales + # its own lock, which only the slow `build` job saw (improvise-zs2r). + - name: The harness lock matches the manifests it resolves against + run: ./scripts/check-harness-lock.sh --allow-network - run: cargo clippy --workspace --all-targets -- -D warnings - run: cargo test --workspace # The comment cap and the test-evidence keys (improvise-9c8c); without @@ -281,9 +285,10 @@ jobs: # fmt-workspaces is here because its own failure mode is the bug it # exists for: a usage error wearing a formatting failure's exit code. # bd-audit-ids is the dead-ID audit, whose recipe once read a bd that - # never ran as a clean audit (improvise-52eq). + # never ran as a clean audit (improvise-52eq). check-harness-lock must + # never read "could not resolve" as "stale" (improvise-zs2r). - name: The remaining checkers refuse what they claim to - run: ./scripts/checkers.sh pr-watch release-preflight pr-reviews fmt-workspaces bd-audit-ids + run: ./scripts/checkers.sh pr-watch release-preflight pr-reviews fmt-workspaces bd-audit-ids check-harness-lock # --------------------------------------------------------------------------- # Lints the one crate the `test` job cannot reach. diff --git a/context/code/tooling.md b/context/code/tooling.md index 6b09fc0f..81e94475 100644 --- a/context/code/tooling.md +++ b/context/code/tooling.md @@ -706,3 +706,51 @@ configuration.** A script may declare `#!/usr/bin/env bash`, pass the gate, and still be handed to dash by whatever invokes it. The only defence there is reading, which is how this one was found — by grepping for the *call* after fixing the *declaration*, rather than stopping at the gate going green. + +## 6. The harness lock gate (improvise-zs2r) + +`harness/opfs-contract` declares its own `[workspace]`, so it has its own +`Cargo.lock`. It reaches `improvise-io`, and through it `improvise-core` and +`improvise-formula`, by path. So a change to one of those crates' manifests, +such as removing a dependency or a feature, can stale that lock while the root +lock is updated and every host gate stays green. Before this gate, the only +thing that noticed was `harness/build.sh`'s `cargo metadata --locked`, in the +slow `build` job, and #214 and #224 both found out that way. + +`scripts/check-harness-lock.sh` runs that same `cargo metadata --locked` +against the harness manifest. It runs in the `test` job (with +`--allow-network`) and locally, where it is **offline** by default. + +**Why offline is sound, measured rather than assumed.** `--locked --offline` +resolves from the local registry cache and fails on a real mismatch with its +own message: "cannot update the lock file … because --locked was passed". A +dependency missing from the cache fails differently, with a resolution error +naming the index ("location searched: crates.io index"). Both were measured: +a manifest-only change to `improvise-formula` produced the first, and an empty +`CARGO_HOME` produced the second. So the script can tell the two apart, and it +does: + +- exit 0: the lock is current; +- exit 1: stale, and the fix is named; +- exit 2: the gate did not run. + +That follows `fmt-workspaces.sh`'s rule that "did not run" is never collapsed +into a verdict. Offline, then, it never needs the network and never lies: +- a developer whose cache is warm, which is anyone who has built the root + workspace, gets a real answer; +- one whose cache is cold is told the gate did not run, not that the lock is + stale. + +**The one residual, stated.** A manifest change that adds a crate never +downloaded is, offline, a resolution failure. So it reads as exit 2, not +exit 1. The developer is still stopped, but told the gate did not run rather +than that the lock is stale. CI runs with `--allow-network` and answers it +properly. + +`scripts/check-harness-lock-test.sh` pins the three answers against path-only +fixture workspaces, which need no registry at all. The fixtures have the same +shape as the harness: a root that reaches a crate by path, whose manifest then +changes. Two rivals were run: +- reading every failure as stale fails the offline case (exit 1, wanted 2); +- dropping `--locked` fails the stale case (exit 0, wanted 1). Without + `--locked`, cargo quietly rewrites the lock and reports success. diff --git a/scripts/check-harness-lock-test.sh b/scripts/check-harness-lock-test.sh new file mode 100755 index 00000000..dbed9edf --- /dev/null +++ b/scripts/check-harness-lock-test.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# check_the_checker for scripts/check-harness-lock.sh (improvise-zs2r). The +# fixtures are path-only crates, so every case runs offline with no cache. +set -uo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUN="$here/check-harness-lock.sh" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +pass=0; fail=0 +ok() { printf ' ok %s\n' "$1"; pass=$((pass+1)); } +bad() { printf ' FAIL %s (exit %s, wanted %s)\n' "$1" "$2" "$3"; fail=$((fail+1)); } + +# $1 name, $2 expected exit, $3 expected stderr substring ("" for none), rest: args +run_case() { + local name="$1" want="$2" says="$3"; shift 3 + local err + err="$("$RUN" "$@" 2>&1 >/dev/null)" + local got=$? + if [ "$got" != "$want" ]; then + bad "$name" "$got" "$want" + printf '%s\n' "$err" | sed 's/^/ /' + elif [ -n "$says" ] && [[ "$err" != *"$says"* ]]; then + bad "$name: stderr did not say $says" "$got" "$want" + else + ok "$name" + fi +} + +# $1 dir, $2 crate name, $3 extra [dependencies] lines, $4 "workspace" or "" +crate() { + mkdir -p "$1/src" + : > "$1/src/lib.rs" + { + printf '[package]\nname = "%s"\nversion = "0.0.0"\nedition = "2021"\n' "$2" + [ "${4:-}" = workspace ] && printf '\n[workspace]\n' + printf '\n[dependencies]\n%s' "$3" + } > "$1/Cargo.toml" +} + +# A two-crate workspace shaped like the harness: `app` is its own root and +# reaches `lib` by path, so `lib`'s manifest is one the lock resolves against. +fixture() { + local root="$1" + crate "$root/lib" lib "" + crate "$root/extra" extra "" + crate "$root/app" app 'lib = { path = "../lib" } +' workspace + cargo generate-lockfile --offline --manifest-path "$root/app/Cargo.toml" 2>/dev/null +} + +fixture "$work/current" +run_case "a current lock passes" 0 "" "$work/current/app/Cargo.toml" + +# The shape improvise-zs2r names: only a PATH DEPENDENCY's manifest changed. +fixture "$work/stale" +crate "$work/stale/lib" lib 'extra = { path = "../extra" } +' +run_case "a manifest-only change to a path dependency is stale" 1 "STALE" \ + "$work/stale/app/Cargo.toml" + +# Offline and unresolvable is NOT a stale lock: the gate did not run. +fixture "$work/cold" +crate "$work/cold/lib" lib 'zs2r-no-such-crate-anywhere = "1" +' +run_case "an offline resolution failure is not a verdict" 2 "did not run" \ + "$work/cold/app/Cargo.toml" + +run_case "a missing manifest is a usage error" 2 "no manifest" \ + "$work/nowhere/Cargo.toml" +run_case "two manifests is a usage error" 2 "" \ + "$work/current/app/Cargo.toml" "$work/stale/app/Cargo.toml" +run_case "an unknown option is a usage error" 2 "unknown option" --bogus + +printf '\ncheck-harness-lock: %d passed, %d failed\n' "$pass" "$fail" +[ "$fail" = 0 ] diff --git a/scripts/check-harness-lock.sh b/scripts/check-harness-lock.sh new file mode 100755 index 00000000..8acf87dc --- /dev/null +++ b/scripts/check-harness-lock.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Fails in seconds when harness/opfs-contract/Cargo.lock no longer matches the +# manifests it resolves against. Reasoning: context/code/tooling.md, "The +# harness lock gate" (improvise-zs2r). +set -uo pipefail + +usage() { + cat >&2 <<'EOF' +usage: check-harness-lock.sh [--allow-network] [manifest] + + manifest the Cargo.toml whose lock is checked; defaults to + harness/opfs-contract/Cargo.toml beside this script + --allow-network resolve without --offline (CI); the default never + touches the network + +exit 0 the lock is current +exit 1 the lock is stale: a manifest it resolves against changed +exit 2 the gate did not run: usage error, no cargo, or cargo could not + resolve at all (offline, with a dependency missing from the local + cache). NO verdict was produced. Never collapse 2 into 1. +EOF +} + +main() { + local network=0 manifest="" + while [ $# -gt 0 ]; do + case "$1" in + --allow-network) network=1 ;; + -h|--help) usage; return 2 ;; + -*) printf 'check-harness-lock: unknown option %s\n' "$1" >&2; usage; return 2 ;; + *) + if [ -n "$manifest" ]; then usage; return 2; fi + manifest="$1" ;; + esac + shift + done + if [ -z "$manifest" ]; then + manifest="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/harness/opfs-contract/Cargo.toml" + fi + if [ ! -f "$manifest" ]; then + printf 'check-harness-lock: no manifest at %s\n' "$manifest" >&2 + return 2 + fi + if ! command -v cargo >/dev/null 2>&1; then + echo 'check-harness-lock: no cargo on PATH' >&2 + return 2 + fi + + local -a args=(metadata --locked --format-version 1 --manifest-path "$manifest") + if [ "$network" = 0 ]; then args+=(--offline); fi + + local err rc + err="$(cargo "${args[@]}" 2>&1 >/dev/null)" + rc=$? + if [ "$rc" = 0 ]; then + printf 'check-harness-lock: %s is current\n' "${manifest%Cargo.toml}Cargo.lock" + return 0 + fi + # The one message that means "resolution succeeded and disagrees with the lock". + if printf '%s' "$err" | grep -q 'cannot update the lock file .* because --locked was passed'; then + printf 'check-harness-lock: STALE: %s\n' "${manifest%Cargo.toml}Cargo.lock" >&2 + printf ' Run: cargo update -w --manifest-path %s, and commit the lock.\n' "$manifest" >&2 + return 1 + fi + printf 'check-harness-lock: the gate did not run; cargo could not resolve:\n%s\n' "$err" >&2 + if [ "$network" = 0 ]; then + echo ' Offline, this is usually a dependency missing from the local cache.' >&2 + echo ' Retry with --allow-network, or fetch once and rerun.' >&2 + fi + return 2 +} + +main "$@"