Skip to content

ci: derive the clippy package lists so every tested crate is linted (#2058) - #2098

Merged
justinchuby merged 5 commits into
mainfrom
pris/lint-coverage-gap
Aug 25, 2026
Merged

justinchuby merged 5 commits into
mainfrom
pris/lint-coverage-gap

Conversation

@justinchuby

Copy link
Copy Markdown
Owner

Closes #2058.

21 crates are compiled and tested by CI and linted by nothing. This derives the clippy package lists from the same source the test lanes already use, and adds a guard so the two cannot drift apart again.

The correction I owe first

#2058 claimed two live clippy::unnecessary_cast denials on main in onnx-runtime-ep-plugin/src/compute.rs. They are gone, and I did not fix them. At bc715c329:

cargo clippy --locked --all-targets -p onnx-runtime-ep-plugin -- -D warnings   ->  0

git log 67d3aa5eb..HEAD -- crates/onnx-runtime-ep-plugin/ is three feature/refactor commits (#2049, #2064, #2083); none mentions clippy or the cast. The lines were deleted incidentally by a refactor.

That weakens the issue's headline and strengthens its actual point. The gap admits defects and releases them unobserved — nobody knows what is in there at any moment without running the lint themselves. The sample was never the argument; the mechanism is.

The rule that created the gap was false

ci.yml instructed: "To add a crate, first confirm its normal+dev dependency tree contains no ort-sys/CUDA dependency."

That rule was already violated by the list it annotated. Measured:

crates in offline-linux whose normal+dev tree contains onnx-genai-ort-sys:  13
of those, already on the clippy list:  onnx-runtime-ep-cpu, onnx-runtime-ep-api

And it does not matter which way you resolve that, because cargo clippy only ever checks — it never links and never runs a test binary. The offline/ort-backed split exists for cargo test; it has no force for lint. onnx-genai-ort-sys itself compiles here with no network.

In #2058 I wrote that I did not know whether the premise was stale or whether ep-plugin should be out of offline-linux, and would rather flag it than guess. This is the measurement I said I would not substitute a guess for: the premise was false.

What changed

  • All three clippy -p lists — byte-identical to each other, 31 packages, repeated in three jobs — become $(python .github/scripts/workspace_test_packages.py cargo-args lint). The new lint lane is "every package some test lane compiles" (55). Windows ARM64 takes offline-linux (49), still a strict superset of the 31 it linted before, and no ORT crates on that target.
  • verify gains a lint-coverage half: it fails if any tested package is reached by no cargo clippy invocation anywhere in .github/workflows. Generator calls are expanded, not skipped — a computed -p list counts, and a hand-written one cannot hide behind some other step computing one.
  • A self-test step actually exercises the controls.

Evidence

The guard detects the real defect. Run against unmodified main, before the ci.yml change, it reports the gap by name:

Workspace lint coverage check failed.
Package(s) are compiled and tested by CI and linted by nothing:
  - onnx-genai            - onnx-runtime-ep-nxrt-abi      - onnx-runtime-memory-abi
  - onnx-genai-capi       - onnx-runtime-ep-nxrt-host     - onnx-runtime-memory-host
  - onnx-genai-ort        - onnx-runtime-ep-nxrt-testplugin  - onnx-runtime-memory-testplugin
  ... 21 total

The fix closes it, and the pass is not an empty selection. The exact command ci.yml now runs:

cargo clippy --locked --all-targets $(python ... cargo-args lint) -- -D warnings   ->  RC=0

parsed with --message-format=json: wanted 55, seen 55, MISSING: none. A clippy run that selected nothing would also exit 0, so the package set is confirmed present in the lint graph rather than inferred from the exit code.

Mutations.

mutation result
--simulate-unlinted onnx-runtime-ep-cpu rc=1, names that crate
--simulate-missing onnx-runtime-ir (pre-existing half, after refactor) rc=1
both Linux clippy steps reverted to lane offline-linux rc=1, names onnx-genai, onnx-genai-capi, onnx-genai-ort
a comment claiming cargo clippy -p onnx-genai ... added, lanes narrowed rc=1 — prose cannot buy coverage
clean tree rc=0

Two defects the mutations found in my own work

Recording both, because in each case the check was passing at the time.

1. The scanner counted a YAML comment as an invocation. After I rewrote the explanatory comment — which contains the words cargo clippy — the reported invocation count went 10 -> 11. Nothing failed; the only symptom was a number moving that I had no reason to expect to move. A comment reading # cargo clippy -p foo would have granted foo lint coverage. The scanner now skips comment lines, the count is back to 10, and the mutation table above has a cell for exactly this.

2. My first self-test passed because python was not on PATH. It was written as if cmd ...; then fail; fi — a bare non-zero check. Command-not-found is 127, which is non-zero, so it read as "the guard correctly failed". It passed loudest in precisely the case where nothing ran. It now requires exit 1 specifically, plus the matching failure message:

interpreter present   ->  rc=0   both guards correctly fail
interpreter MISSING   ->  rc=1   ::error::verify ... exited 127, expected 1
guard neutered        ->  rc=1   ::error::verify ... exited 0, expected 1

This is the same shape as QEMU_LD_PREFIX and | head: a weaker check fails in the direction of passing. It is also why the self-test step exists at all — --simulate-missing had shipped with this guard since it was written and CI had never once invoked it. A control nobody runs is not a control.

Scope and risk

  • Fast (Linux x86_64) and Rust quality gain 24 crates each. Verified locally: the whole 55-package set is clean at -D warnings --all-targets.
  • Rust (Windows ARM64) gains 18 crates on aarch64-pc-windows-msvc, which I cannot run locally. If that lane goes red this is mine and I will narrow it before merging. That lane is not required, which — as of last night's two merged defects that green required checks did not catch — is exactly the kind of lane I intend to read rather than assume. I will wait for it.
  • No Rust source changes. No behaviour change. Lint coverage only.

Normal --squash --auto. No admin bypass.

@codecov

codecov Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.64%. Comparing base (6a64a8f) to head (4553013).
⚠️ Report is 18 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2098      +/-   ##
==========================================
+ Coverage   80.34%   80.64%   +0.30%     
==========================================
  Files         413      428      +15     
  Lines      196629   211453   +14824     
  Branches   196629   211453   +14824     
==========================================
+ Hits       157979   170536   +12557     
- Misses      33113    35172    +2059     
- Partials     5537     5745     +208     
Flag Coverage Δ
cli-ort-linux 72.51% <ø> (?)
cli-ort-windows 72.01% <ø> (?)
mlas 86.02% <ø> (?)
offline 80.77% <ø> (+0.42%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 71 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

justinchuby added a commit that referenced this pull request Aug 25, 2026
From independent review of #2098. Both are comment/message accuracy; no
behaviour change.

- The comment above `Check the default features compile clean` claimed that
  `Run clippy on all offline crates` "cannot list them because they pull in
  ort-sys". That step now selects `cargo-args lint`, which does include engine
  and server, so the claim was false and contradicted the comment this PR adds
  twenty lines above it. The step is still worth keeping, for a different and
  now-stated reason: with resolver = "2" features unify across every package
  selected in one invocation, so the 55-package run can enable features on
  engine/server that neither crate enables on its own.
- The lint-coverage failure hint said "the offline clippy steps select
  cargo-args lint"; the Windows ARM64 one selects offline-linux.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby

Copy link
Copy Markdown
Owner Author

Independent review: REQUEST_CHANGES, both findings fixed in bbc0afe92

I am the author, so I did not self-approve. An independent Opus review found no functional defects — it verified no lost flags, no scanner mis-parses, correct set -uo pipefail / rc handling in the self-test, and that both failure paths emit a string the case arm matches. Its two findings were both about comment accuracy, which is the bar I have been holding others to, so they blocked.

1. (Medium) My change made a neighbouring comment false — and self-contradicting.

The comment above Check the default features compile clean said Run clippy on all offline crates "cannot list them because they pull in ort-sys". After this PR that step selects cargo-args lint, which does include onnx-genai-engine and onnx-genai-server. So the file now asserted that ort-sys prevents listing those crates twenty lines below a new comment of mine arguing ort-sys is irrelevant to clippy.

The reviewer's caution was the useful part: don't delete the isolated step on my account without confirming the unified run reaches engine/server at their true default feature set. It does not, and I checked rather than assumed — Cargo.toml:2 is resolver = "2", so features unify across every package selected in one invocation. A 55-package run can switch features on for engine/server that neither crate enables alone. The isolated step keeps real value; only its stated reason was wrong. Comment rewritten to say the true reason.

2. (Low) The lint-coverage failure hint said "the offline clippy steps select cargo-args lint" — the Windows ARM64 one selects offline-linux. Softened to "the Linux offline clippy steps".

Its answer to the question I most wanted attacked

I asked the reviewer to decide whether the new guard is circular — since lane lint is defined as exactly the tested set, does tested - linted only ever assert that the string cargo-args lint appears somewhere? Its verdict, which I accept and which is more precise than my own framing:

Not harmfully circular. The guard computes actual set coverage from the union of all real clippy steps. Delete or revert both cargo-args lint steps and the remainder (offline-linux on ARM64 plus the literal -p CLI-ORT/CUDA steps) covers 53/55 — onnx-genai, onnx-genai-capi, onnx-genai-ort are reported unlinted and it fails. Its green today is guaranteed by the intended wiring; its red fires precisely on the drift this PR exists to prevent.

That matches my MUT-3 result exactly, arrived at independently. The one genuinely tautological corner it identified, which I had not: "a new lane is tested but not linted" is unreachable unless someone adds a DENYLISTed package to a test lane. Narrow, but a real case the guard uniquely catches.

Re-validated after the fixes

YAML OK, 9 jobs
verify                                          -> rc=0   55 tested, all linted by 10 clippy invocations
verify --simulate-unlinted onnx-runtime-ep-cpu  -> rc=1
verify --simulate-missing onnx-runtime-ir       -> rc=1
self-test step, interpreter present             -> rc=0   both guards correctly failed

Invocation count still 10, i.e. the rewritten comment did not re-trip the comment-matching bug this PR already fixed once.

Marking ready. Normal --squash --auto, no admin bypass. I will read the non-required Rust (Windows ARM64) lane before merging rather than merging on the required two — that lane gains 18 crates on a target I cannot run locally, and last night produced two separate merged defects that green required checks did not catch.

@justinchuby
justinchuby marked this pull request as ready for review August 25, 2026 09:54
@justinchuby
justinchuby enabled auto-merge (squash) August 25, 2026 09:54
justinchuby added a commit that referenced this pull request Aug 25, 2026
From independent review of #2098. Both are comment/message accuracy; no
behaviour change.

- The comment above `Check the default features compile clean` claimed that
  `Run clippy on all offline crates` "cannot list them because they pull in
  ort-sys". That step now selects `cargo-args lint`, which does include engine
  and server, so the claim was false and contradicted the comment this PR adds
  twenty lines above it. The step is still worth keeping, for a different and
  now-stated reason: with resolver = "2" features unify across every package
  selected in one invocation, so the 55-package run can enable features on
  engine/server that neither crate enables on its own.
- The lint-coverage failure hint said "the offline clippy steps select
  cargo-args lint"; the Windows ARM64 one selects offline-linux.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby
justinchuby force-pushed the pris/lint-coverage-gap branch from bbc0afe to e93532a Compare August 25, 2026 11:00
@justinchuby

Copy link
Copy Markdown
Owner Author

Rust coverage (macOS arm64) went red on the first run. Not mine — triaged, then rebased.

I said in the body I would read the non-required lanes rather than merge on the required two. This is that, and it was worth doing.

The failure:

kernels::stft::tests::real_unwindowed_overlapping_frames_match_independent_reference
panicked at crates/onnx-runtime-ep-cpu/src/kernels/stft.rs:363:9:
  each power-of-two frame must use the radix-2 FFT path
test result: FAILED. 1707 passed; 1 failed

My diff is two files, YAML and Python, and contains no Rust. But "it can't be mine" is not evidence, and the failing step (Test cross-platform offline crates with coverage) selects its packages from the very script I changed — so the first thing to rule out was that I had broken the selection. I had not: the -p list in the failing command is the same 48-package offline-cross-platform set, and the failure is a test failing, not a package resolving.

Where it came from. The lane was green on 089309588, 0448f2bc6 and e974224dd — and all three predate #2083, which added STFT. So there was never a green macOS-coverage run with this code; the lane simply had not executed it yet.

It was already fixed before my run finished. c8509042a (#2093, closes #2089, filed by @holden) rewrites that exact assertion:

-  "each power-of-two frame must use the radix-2 FFT path"
+  "each of the 3 power-of-two frames must take a DFT fast path rather than ..."

On Apple targets DftPlan::new builds a vDSP setup for n.is_power_of_two() && n >= 4 and returns before the radix-2 block ever bumps the counter the test reads. The test uses frame length 4 — the first vDSP-eligible size.

The timing is the whole story:

my macOS coverage job started   2026-08-25T10:19:46Z
c8509042a landed on main        2026-08-25T10:38:49Z   (19 minutes later)

GitHub tests the merge result, and my merge result was computed before the fix existed. Rebased onto c8509042a; head is now e93532ae0.

Re-validated on the new base (~40 commits, including changes to ep-cpu/src/kernels/stft.rs and dft.rs):

verify                     -> rc=0   55 tested, all linted by 10 clippy invocations
cargo clippy ... lint      -> RC=0   wanted 55, seen 55, MISSING: none

and I checked the re-validation was not served from cache rather than assuming it: of 861 compiler artifacts, 230 were recompiled and onnx_runtime_ep_cpu's lib target reports fresh=false. A warm-cache RC=0 and a real RC=0 are the same three characters.

One thing I did get wrong, found while rebasing: git add -A in my second commit swept up the scratch file I was writing the commit message in, so bbc0afe92 contained a stray pris_msg.txt. Amended out; the PR is two files. The independent review could not have caught it — it ran against 7883a5868, one commit earlier. A squash merge would have flattened it into the tree either way.

Auto-merge remains armed SQUASH. No bypass.

@justinchuby

Copy link
Copy Markdown
Owner Author

Measured follow-up on the one risk I flagged in the PR body: the Windows ARM64 clippy lane.

I wrote that this step gains 18 crates on a target I cannot build locally, and that I would read the lane before merging. That is still true, but I can now bound the exposure rather than just watch it, and the bound is tighter than I expected: the marginal compile surface this change adds to that lane is zero.

Three measurements, all from this branch at e93532ae0:

1. The step's set grows by 18 and loses nothing.

$ git show c7d50d7ae^:.github/workflows/ci.yml \
    | awk '/Clippy Windows ARM64 offline crates/,/-- -D warnings/' \
    | grep -o '\-p [a-z0-9-]*' | sed 's/-p //' | sort   # 31
$ python3 .github/scripts/workspace_test_packages.py cargo-args offline-linux \
    | tr ' ' '\n' | grep -v '^-p$' | sort                # 49

comm -23 old new is empty — strict superset, no crate loses lint coverage. mlas-sys was already on the old hand-written list, so selecting the lane whose name says linux does not newly introduce it here.

2. All 18 added crates are already compiled and unit-tested on aarch64-pc-windows-msvc — by the step immediately above this one, in the same job.

offline-linux \ offline-cross-platform == {mlas-sys} exactly, and Test cross-platform offline crates (ci.yml:838) runs cargo test --locked $(… cargo-args offline-cross-platform) in this job. Every one of the 18 is in that set:

$ comm -13 .xplat.txt .new_arm.txt
mlas-sys                      # the only difference, and it was already linted here
$ for p in <the 18>; do grep -qx "$p" .xplat.txt || echo "MISSING: $p"; done
(no output)

3. --all-targets adds nothing beyond that for these crates. cargo test compiles lib + tests; --all-targets additionally compiles benches. Walking cargo metadata for bench/example targets across all 18 returns none. So for these packages --all-targets is exactly the target set the test step already builds.

What that leaves. The residual exposure is not "will it compile" — that is already answered green on this target every run. It is lint diagnostics on already-compiling code that differ by target. The target-conditional surface across the 18 is small enough to read: 6 cfg(windows)/cfg(target_os = "windows") sites in 3 crates (onnx-runtime-ort-testkit ×3, onnx-runtime-ep-plugin ×2, onnx-genai-cuda-version-guard ×1). I read all six — encode_wide + NUL assert, GetModuleHandleExW pinning, a cfg-split buf field and as_ptr, and a HostOs::Windows arm. The two std::os::unix imports in these crates (ep-plugin/src/pin.rs:179, hostmon/src/hostlock.rs:477) are both inside #[cfg(unix)]/#[cfg(target_os = "linux")] items, so neither is reachable on this target.

One caveat I am not going to paper over. "Already compiled" is a package-level claim. resolver = "2" unifies features across everything selected in a single invocation, and the clippy invocation selects the test set plus mlas-sys — so the unified feature configuration is not bit-identical to the one the test step builds under. This is the same mechanism that keeps the isolated -p onnx-genai-engine -p onnx-genai-server step valuable, and I would rather state it than let "already compiled on this target" read as stronger than it is. The delta is one crate that this job already builds by itself at ci.yml:823.

Net: I am no longer treating this lane as an unverifiable risk, but I am still going to read it before this merges, and if it goes red I will confirm the failure is in one of the 18 (not inherited) before touching anything.

Commands are all metadata/text — no builds, no host lock taken.

@justinchuby

Copy link
Copy Markdown
Owner Author

Rust quality (required) went red at e93532ae0. The failure is real, it is mine, and it is not where I said the risk was.

Job 97784617364, step 29 — Test step wrapper still refuses an empty filter (self-test):

FAIL ...and every one expands to options only, never a name filter
     a generator can inject a bare test filter past the scanner:
     [did not run: $(python .github/scripts/workspace_test_packages.py verify $sim 2>&1)]
✗ 1 of 33 assertions failed

Reproduced locally, byte-identical message, before changing anything.

Diagnosis. #2066's scanner enumerates every $(python ...) substitution in ci.yml and executes it, proving the expansion is options only — because a generator that emitted a bare word would become a silent test filter, and $( ) is opaque to the static scan that would otherwise catch it. This PR added a substitution that is not a generator: the coverage self-test calls the script's verify subcommand, and it does so with a loop variable. Under set -u that is unexpandable outside its step, so the cell recorded "did not run" and failed a tree that is fine.

The contract was correct when written — every $(python ...) in ci.yml was a generator — and this PR is what made it too broad. That is not the scanner malfunctioning; it is a forward-looking guard doing exactly what it was built to do: refuse to pass on an invocation it cannot verify. I would rather it fail this way than quietly skip the one it does not understand.

Fix (d99c48c13): classify by position, not by name. What makes a bare word dangerous is that the expansion lands in a cargo command line. So: inside a cargo step a substitution must be cargo-args; outside one it must be a known checker. Generators are still executed and token-checked, exactly as before.

Strictly stronger than the rule it replaces, in both directions — each cell mutation-tested:

mutation result
cargo-args made to append kernels::sneaky:: FAIL, and on all 5 substitutions — the execution cell kept its full force after the narrowing
cargo-args renamed out of ci.yml (no generators left) FAIL ×2 — the anti-vacuity cell and the classifier both fire; an unknown subcommand cannot silently empty the executed set
a checker spliced into a cargo command line FAIL — newly caught; the old name-blind rule would have flagged this only by accident
a new subcommand outside a cargo step FAIL — refused rather than ignored, so the next generator cannot arrive unexamined
the classifier's block parser neutered verdict cell stays ok, only the control goes red

That last row is why I added an anti-vacuity control on the classifier itself. Its passing verdict is "prints nothing" — which is also precisely what a block parser that reads nothing prints. The control asserts the classifier's own enumeration matches, substitution for substitution, what a flat grep finds. Without it, the cell I just added would have been a check that passes hardest when it is broken. (Same shape as #1897's negative control, and as the python-missing 127 I caught in this PR's own self-test two commits ago. Third instance for me in this PR alone; I no longer treat "the check passed" as evidence until I have seen it fail.)

EXPECTED 33 → 36. shellcheck scripts/test_step.sh scripts/test_step_test.sh → rc=0. bash scripts/test_step_test.sh → 36/36. Both are the step's exact commands, run in that order.

On my risk prediction. I flagged Rust (Windows ARM64) as the one thing I could not verify locally. It came back SUCCESS — the bound I posted above held. The lane that broke was the one I had every ability to run locally and did not re-run after the last commit. The lesson is not "predict better"; it is that a hermetic sub-second suite that scans ci.yml must be re-run whenever ci.yml changes, and I edited ci.yml without doing so.

Everything else at e93532ae0 was green: Fast (Linux x86_64), Rust (Windows ARM64), EP conformance, CUDA compile ×2, coverage on Linux/macOS, codecov ×2. Auto-merge remains armed on SQUASH; no bypass anywhere.

@justinchuby

Copy link
Copy Markdown
Owner Author

Independent delta review of d99c48c13 (Opus, adversarial brief): APPROVE.

I am the author and cannot self-approve, so the fix commit went to an independent reviewer with instructions to attack six specific things and to verify by running, not by reading. Summary of what it tried, since that is the part that lets you judge the review's coverage rather than its conclusion:

attack result
Did narrowing the executed set to cargo-args lose force? Is the partition of ci.yml's 6 substitutions total? PASS — 5 generators + 1 checker; every generator on a cargo line is caught by one cell or the other. The only sub dropped from execution was a false positive to begin with.
awk classifier correctness: ) inside a sub, multiple subs per block, line-wrapped subs, trailing-comment cargo , indentation assumptions, whether list mode alters a verdict path Two constructible defects, both loud (spurious red): a trailing # … cargo test … comment in a non-cargo step, and a substitution wrapped across two YAML lines. Neither exists in ci.yml. list mode enumerates before the position check, so it cannot change a verdict.
Is the anti-vacuity control load-bearing, and are the two formats guaranteed identical? PASS, and confirmed by its own mutation: setting inrun=0 left the verdict cell green and only the control red. Divergence between classifier and grep fails loud in every case it could construct.
Does the mutation cell prove what it claims, or is exact-match brittle into a false pass? PASS — "always print both offenders" passes that cell but is caught loud by the verdict cell on the real ci.yml; "print nothing" fails both.
bash under set -uo pipefail (no -e): unquoted expansions, grep rc=1 aborting a $( ), heredoc quoting, empty-$SUBS path PASS, shellcheck rc=0. Empty SUBS → empty ARGS_SUBS → the anti-vacuity cell fails loud.
Is EXPECTED=36 right? PASS — 34 literal check calls, one inside a 3-iteration loop: 33 + 3 = 36.

One residual it reported, and I am recording rather than silently keeping. The classifier's verify / cargo-args / cargo tests are unanchored substring matches. So a hypothetical future subcommand whose name contains verify, emitting a bare token, placed outside any cargo step, would be neither executed nor flagged — a quiet pass. I would normally fix a quiet pass on sight, but the harm condition is not reachable: the token lands in a shell variable, and the moment such a substitution reaches a cargo command line the block test forces it to be cargo-args (executed, token-checked) or flags it (loud). Anchoring the match to workspace_test_packages.py verify closes it and is a one-line change; I am deliberately not pushing it now, because the head is reviewed and green-pending and another push costs a full queue cycle for polish with no reachable defect behind it. If this branch needs another commit for any real reason, that anchor goes in with it.

Verdict quote: "Every defect I could produce is either loud (spurious red) or outside the threat model; the dangerous case this file exists to refuse — a bare token reaching a cargo command line — fails loud on every path I tried."

Worktree left clean by the reviewer (git status --porcelain empty). No cargo, no builds, no host lock — the whole suite is sub-second.

@justinchuby

Copy link
Copy Markdown
Owner Author

Rust quality is red again at d99c48c13, and this time it is not mine. Not merging past it; waiting for main.

My fix worked: step 29 (Test step wrapper still refuses an empty filter) passed. Because it passed, the job reached step 30, which the previous run had skipped — steps run under bash -e, so the first failure hides everything after it. The colour is unchanged; the reason is entirely different.

Step 30, Check the native backend compiles:

error[E0308]: mismatched types
   --> crates/onnx-genai-server/src/tests.rs:2618:26
    |    driver.close_session(session_id)
    |           ------------- ^^^^^^^^^^ expected `SessionLeaseGuard`, found `SessionPlacement`
error: could not compile `onnx-genai-server` (lib test) due to 2 previous errors

Not attributable to this PR, by measurement rather than assertion: this branch's entire diff is three files under .github/ and scripts/ — zero Rust. And origin/main carries the mismatch on its own (tests.rs:2633 at cf23d0e7e, against close_session(&self, lease: SessionLeaseGuard) at driver.rs:560). CI builds the merge result, so this PR inherits it.

Source: #2056, merged 12:03:16Z with Rust quality — a required check — already concluded failure at 11:06:31Z, 56m45s earlier, naming this exact file, line, and type mismatch. Full timeline and ledger entry on #2056; fixes open at #2114 / #2115 (I reviewed both; #2115 is the deterministic one).

Everything else at this head is green, including the lane I flagged as this PR's only real risk: Fast (Linux x86_64) ✅, Rust (Windows ARM64) ✅ (second consecutive green with the 18 added crates), EP conformance ✅, CUDA compile ×2 ✅, coverage Linux/Windows/macOS ✅, codecov ×2 ✅. CLI ORT (Windows x86_64) is red and is #1891's, pre-existing on main.

Auto-merge stays armed on SQUASH and I am not touching it. It cannot fire while a required check is red, which is the correct outcome — no --admin, no bypass. When the main fix lands I will rebase onto it to get a clean merge-ref run rather than reasoning about which base each lane used.

justinchuby added a commit that referenced this pull request Aug 25, 2026
From independent review of #2098. Both are comment/message accuracy; no
behaviour change.

- The comment above `Check the default features compile clean` claimed that
  `Run clippy on all offline crates` "cannot list them because they pull in
  ort-sys". That step now selects `cargo-args lint`, which does include engine
  and server, so the claim was false and contradicted the comment this PR adds
  twenty lines above it. The step is still worth keeping, for a different and
  now-stated reason: with resolver = "2" features unify across every package
  selected in one invocation, so the 55-package run can enable features on
  engine/server that neither crate enables on its own.
- The lint-coverage failure hint said "the offline clippy steps select
  cargo-args lint"; the Windows ARM64 one selects offline-linux.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby
justinchuby force-pushed the pris/lint-coverage-gap branch from d99c48c to 7b0c45f Compare August 25, 2026 14:45
@justinchuby

Copy link
Copy Markdown
Owner Author

Rust quality green on 7b0c45f3d, and I checked the log rather than the colour. The lane that failed twice on this PR — once for my own defect, once for #2056's — now passes, and it passes for the right reasons:

line  220  workspace lint coverage ok: 55 tested, all linted by 10 clippy invocation(s)
line  252  guard correctly failed: verify --simulate-missing onnx-runtime-ir
line  253  guard correctly failed: verify --simulate-unlinted onnx-runtime-ep-cpu
line 2518  ok   ...and the classifier actually parsed them (not a pass by reading nothing)
line 2519  ok   MUTATION: both a new subcommand and a misplaced checker are refused
line 2521  ✓ 36/36 assertions passed

Four separate things confirmed there, and I want to be explicit about why each is in the list rather than just quoting the tick:

  1. 55 tested, all linted — the deliverable. Every package any lane tests is reached by some clippy invocation. That is the 20 crates are tested by CI and linted by nothing; two clippy denials are live on main because of it #2058 property, asserted by CI on every push from now on rather than by me once.
  2. Both guard correctly failed: lines — the self-test's two simulated holes are still detected. If verify had degenerated into something that always returns 0, these lines would be missing and the step would still exit 0. Their presence is what distinguishes a working checker from a silent one.
  3. the classifier actually parsed them — the anti-vacuity control I added in 7b0c45f3d. This cell exists because the verdict it guards passes by printing nothing, which is also exactly what a broken parser prints. I could not tell those two apart by reading, so I made CI tell them apart.
  4. 36/36, not 34/36. The count is asserted, so a cell that silently stops running is a failure rather than a smaller green number nobody notices.

Rust (Windows ARM64) is green too — the lane I flagged in the PR body as my one unmeasurable risk. Second consecutive green, and it matches what the static analysis predicted: the 18 crates it gains are already compiled and unit-tested on that target by the step directly above, none has a bench or example target, so --all-targets adds zero compile surface. The prediction was falsifiable and the lane did not falsify it.

Fast (Linux x86_64) is still running; auto-merge stays armed and I am not touching it until that concludes. No bypass on this PR at any point — the two earlier reds were real, and both got a commit rather than an override.

The one that I want on the record, because it is the whole argument of #2058 in miniature: the second red on this PR was not mine. My step 29 passing is what let the job reach step 30 — these steps run under bash -e, so my own failure had been masking #2056's broken native-backend compile on the run before. Fixing a coverage gap immediately surfaced a defect that the gap had been hiding. That is the expected behaviour of this change and it happened on its own PR before it merged.

justinchuby and others added 4 commits August 25, 2026 17:30
…2058)

21 workspace crates were compiled and tested by CI and linted by nothing.
The test lanes select packages from a derived list (workspace_test_packages.py
walks cargo metadata, so a new crate is tested by default); the clippy lanes
selected from a hand-maintained -p list repeated verbatim in three places.
Derived-vs-manual is a ratchet in one direction: every new crate joined the
test set automatically and joined the lint set only if someone remembered,
three times, in three lists. Nobody did, 21 times.

The stated reason for the manual list was wrong. The instruction above it said
to "confirm its normal+dev dependency tree contains no ort-sys/CUDA
dependency" -- but onnx-runtime-ep-cpu and onnx-runtime-ep-api were already on
that list and both pull onnx-genai-ort-sys, so the rule was already violated by
the list it annotated. It does not matter either way: cargo clippy only ever
*checks*. It never links and never runs a test binary, so the constraint that
splits the test lanes into offline/ort-backed has no force for lint at all.

- All three clippy -p lists (identical, 31 packages each) become
  `cargo-args lint`, a new lane meaning "every package some test lane
  compiles". Windows ARM64 keeps the offline set, which is still a superset of
  what it linted before.
- `verify` gains a lint-coverage half: it fails if any tested package is
  reached by no clippy invocation in .github/workflows. Generator calls are
  expanded rather than skipped, so a computed -p list counts and a
  hand-written one cannot hide behind some other step computing one.
- A self-test step runs both --simulate-missing and --simulate-unlinted and
  requires exit 1 *and* the matching message. --simulate-missing had existed
  since the guard was written and CI had never once invoked it.

Measured on this branch: cargo clippy --locked --all-targets over all 55
packages exits 0, and all 55 appear as compiler artifacts in the lint graph,
so the pass is not an empty selection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
From independent review of #2098. Both are comment/message accuracy; no
behaviour change.

- The comment above `Check the default features compile clean` claimed that
  `Run clippy on all offline crates` "cannot list them because they pull in
  ort-sys". That step now selects `cargo-args lint`, which does include engine
  and server, so the claim was false and contradicted the comment this PR adds
  twenty lines above it. The step is still worth keeping, for a different and
  now-stated reason: with resolver = "2" features unify across every package
  selected in one invocation, so the 55-package run can enable features on
  engine/server that neither crate enables on its own.
- The lint-coverage failure hint said "the offline clippy steps select
  cargo-args lint"; the Windows ARM64 one selects offline-linux.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ot by name

Rust quality went red on this PR at 'Test step wrapper still refuses an empty
filter (self-test)'. The failure is real and it is mine: #2066's scanner
enumerates every $(python ...) substitution in ci.yml and executes it to prove
the expansion is options only, and this PR added a substitution that is not a
generator -- the coverage self-test calls the script's 'verify' subcommand with
a loop variable, which is unexpandable outside the step, so the cell reported
'did not run' and failed a tree that is fine.

The contract was right when it was written (every substitution was a generator)
and is now too broad (the script also answers 'verify', a checker whose output
is prose and whose exit status is the signal). But which one a substitution is
cannot be read off its name: what makes a bare word dangerous is that the
expansion lands in a cargo command line. So classify by position -- inside a
cargo step it must be 'cargo-args', outside one it must be a known checker --
and keep executing exactly the generators.

That is strictly stronger than the rule it replaces, in both directions:

  - a generator emitting a bare token is still caught (mutation: making
    cargo-args append 'kernels::sneaky::' fails the cell on all five
    substitutions)
  - a checker spliced into a cargo command line is now caught, which the old
    name-blind execution would have reported only by accident
  - a new subcommand outside a cargo step is refused rather than ignored, so
    the next generator cannot arrive unexamined

Added an anti-vacuity control on the classifier itself, because its passing
verdict is 'prints nothing' -- which is also what a block parser that reads
nothing prints. Neutering the parser leaves the verdict cell green and only the
control red, which is the whole reason it is there.

EXPECTED 33 -> 36. shellcheck clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…commands

The classifier accepted any substitution containing "verify" outside a
cargo step. An independent review called that a quiet pass with no
reachable harm, and I deferred it. Main then added `self-test` and
`verify-required-tier`, so the checker set is no longer one name and the
unanchored match now has something to be sloppy about.

Both arms now name the script and match the subcommand at a word
boundary. Mutation-checked: a step running `... .py verifyish` outside a
cargo command is refused by the anchored form and was accepted by the
old one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby
justinchuby force-pushed the pris/lint-coverage-gap branch from 7b0c45f to c6b0193 Compare August 25, 2026 17:38
An independent review of the rebase found two ways a package could be
credited with lint coverage it does not have. Both fail quietly -- the
package looks linted, the gate stays green, and nothing says otherwise --
which is the failure direction this gate exists to remove.

  * a `-p` in a comment indented *deeper* than the clippy invocation was
    appended to the block and harvested. The docstring claimed comments
    could not grant coverage; it stopped a comment from *starting* a
    block, not from contributing tokens inside one.
  * a second cargo command in the same step was read as part of the
    clippy invocation, so `cargo clippy -p a && cargo test -p b`, or the
    two on separate lines of a `run: |`, credited `b` to clippy.

The truncation is applied to the joined block text rather than line by
line, because the line-wise form missed the `&&` shape -- caught by an
arm that failed when I wrote it that way.

`clippy_blocks` is split out of `clippy_commands` so the four
terminators can be driven from fixtures, and six arms now cover them.
Each was mutation-checked, and one had to be added after the fact: my
first "the next step is not credited" arm stayed green with the indent
terminator disabled, because the cargo truncation was catching that
input instead. An arm that passes for a reason other than the one it
names is not a control. Removing each terminator now kills only the arms
that name it: comment skip 1, cargo truncation 2, indent 1, blank line 1.

self-test 44/44; verify unchanged at 55 tested, all linted by 10
invocations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby

Copy link
Copy Markdown
Owner Author

Rebased onto 85565fc5b, two conflicts, an independent review, and two quiet false-green vectors closed. Head is now 45530133f.

The rebase was not mechanical — main built the same thing better

main gained self-test and verify-required-tier in the same script while this PR sat in the queue, and one of the conflicts was a genuine collision of intent: main added a Python self-test subcommand that does what my shell self-test step in ci.yml did, and does it better — it judges each guard on the packages it names, not on its exit status, which is the distinction my step's comment spent nine lines explaining.

So I deleted my step. The --simulate-unlinted control it existed to run is now an arm in main's self_test():

ok    verify --simulate-unlinted onnx-runtime-ep-cpu -> exit 1, naming ['onnx-runtime-ep-cpu']

Same control, one mechanism instead of two. The other conflict was additive (WORKFLOW the file vs WORKFLOWS the directory; both kept, both used where intended).

Independent review: APPROVE, with two latent findings I chose not to defer

I am the author, so an Opus code-review agent reviewed the rebased delta with instructions to attack the conflict resolution, the arm arithmetic, the anchored awk regexes, and vacuity. Verdict APPROVE. It confirmed by mutation that no change to the block parser keeps both the verdict cell and its anti-vacuity control green, and that _PARSER_ARM_COUNT was not desynced by my new argparse flag (it counts job-key fixtures, not parser arguments — a name that invites exactly the mistake I was checking for).

It also found two quiet false-green vectors, both rated Low and not reachable on today's ci.yml. Quiet is the severe class in this repo, and "not reachable today" is a property of the workflow file, not of the checker — so I fixed both rather than banking the approval:

vector input credited before
a -p inside a comment indented deeper than the invocation # -p ghost-deeper is prose inside a folded scalar ghost-deeper
a second cargo command in the same step cargo clippy -p a && cargo test -p b b

The second one bit me while fixing it: I wrote the truncation line-wise, which handles run: | and silently misses the && form. The arm caught it. Truncation now runs over the joined block text.

The arm that passed for the wrong reason

clippy_blocks is split out of clippy_commands so its four terminators can be driven from fixtures. Six arms, each mutation-checked — and the matrix is the point, not the count:

terminator removed arms killed
deeper-comment skip 1
cargo truncation 2
indent terminator 1
blank-line terminator 1

Two of those arms exist only because the first matrix came back wrong. My "the next step's packages are not credited" arm stayed green with the indent terminator disabled — the cargo truncation was catching that input instead. And the blank-line branch was killed by nothing at all, on any input, including real ci.yml. Both were assertions that passed for a reason other than the one in their label, which is the same defect as a vacuous test wearing a different coat. Each terminator now has an input whose verdict only it decides.

self-test 44/44; verify unchanged at 55 tested, all linted by 10 clippy invocation(s); test_step_test.sh 36/36; shellcheck clean.

Also: the Fast red before the rebase was not this PR

Fast (Linux x86_64) failed once on 7b0c45f3d with an ORT LoggingManager singleton error in plugin_ort_e2e — the known flake at #2065, which I filed 12 hours earlier predicting it would land on somebody's unrelated PR. It landed on mine. Rerun of the identical head: green. Matched pair: the same three files passed Fast on e93532ae0 and d99c48c13.

Worth recording because #2065 says "the lane is advisory, so it blocks nothing today" — that is now false. Fast is required, onnx-runtime-ep-cpu-plugin is in its offline-linux test set, and ORT is present in that job as a side effect of an earlier build step, so the tests do not self-skip. Full escalation and a correction to my own analysis there are on #2065.

@justinchuby
justinchuby merged commit f55520d into main Aug 25, 2026
15 of 19 checks passed
@justinchuby
justinchuby deleted the pris/lint-coverage-gap branch August 25, 2026 19:38
justinchuby pushed a commit that referenced this pull request Aug 25, 2026
#2098 derived the clippy package lists from the same resolver the test
lanes use, closing #2058 -- 21 crates were compiled and tested by CI and
linted by nothing. It spelled them as bare `cargo clippy $(...)`, which
is the form this PR exists to remove:

    cargo clippy --locked --all-targets $(python ... cargo-args lint) -- -D warnings

If the resolver fails or the lane name is wrong, the substitution is
empty, the outer command still exits 0, and clippy lints the default
members instead of the lane. That is #2058 re-opened, silently, in a
lane whose entire purpose is to prove it stayed closed -- and two of the
three sites are in required lanes.

All three now bind the list and refuse an empty one, matching the six
`cargo test` sites. Argv on the healthy path is byte-identical: 55
crates for `lint`, 49 for the ARM64 `offline-linux` list. A bad lane
exits 2 with clippy never invoked.

Binding the generator moves the lane name off the `cargo clippy` line,
so `linted_packages` stopped seeing it -- the same trap
`verify-required-tier` set earlier in this PR, and it fails in the
dangerous direction: the package looks unlinted, `verify` goes red, and
the obvious repair is to loosen the scanner. `clippy_blocks` now reads
backwards for the assignment, bounded by indentation so it cannot leave
the step, and credits it only when the invocation actually references
the variable. Crediting every assignment in scope would be a join, not
a dataflow read, and over-reading is the whole documented risk there.

Both directions are falsified by name:

  permissive (credit every preceding assignment)
      -> `linted_packages refuses a lane the clippy call never
         references` FAILS. `verify` still exits 0 -- the over-read is
         invisible to production, so this arm is the only thing holding
         that edge.
  no backwards read
      -> `linted_packages credits a lane the clippy call reads from a
         variable` FAILS and `verify` exits 1.

verify 55 tested / all linted by 10 invocations, verify-required-tier
exit 0, self-test 48/48, test_step_test.sh 36/36, 9 guards and 0
unbound substitutions, job names byte-identical to main.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request Aug 25, 2026
…readers (#2142) (#2147)

Closes #2142.

## The defect

`COUNTERS_OBSERVER_CHILD_ENV` (`task_runtime/mod.rs`) is declared
**ungated** while both of its readers are `#[cfg(target_os = "linux")]`.
Off-Linux the constant is dead code, and those lanes build with `-D
warnings`, so it is a hard build failure:

```
error: constant `COUNTERS_OBSERVER_CHILD_ENV` is never used
  --> crates\onnx-runtime-ep-cpu\src\task_runtime\mod.rs:932:11
  = note: `-D dead-code` implied by `-D warnings`
error: could not compile `onnx-runtime-ep-cpu` (lib test) due to 1 previous error
```

Introduced by #2125 (`85565fc5b`). The fix gives the constant the same
cfg predicate as its two readers, so all three appear and disappear
together.

## How I found it, and why it is not the PR that surfaced it

It reddened `Rust (Windows ARM64)` on my #2098. The timing discriminates
cleanly — that lane on **the same PR branch** was green twice before
#2125 merged and red after, with no Rust in the diff at any point:

| lane run | started | vs #2125 (merged 17:13:15Z) | result |
|---|---|---|---|
| #2098 @ `e93532ae0` | 11:42:56Z | before | **success** |
| #2098 @ `d99c48c13` | 13:11:41Z | before | **success** |
| #2098 @ `45530133f` | 19:17:16Z | after | **failure** |

CI builds the *merge result*, so a PR lane can be red for a defect that
is entirely `main`'s. The colour moved because `main` moved.

## Verification

I could not check the real target locally — `cargo check --target
aarch64-pc-windows-msvc` dies in `onnx-genai-ort-sys`'s bindgen step
(`fatal error: 'stdlib.h' file not found`), needing a Windows SDK.
**That failure says nothing about this change**, and I am recording it
rather than quietly reporting the exit code, because a cross-target
check that fails for toolchain reasons is the mirror image of the trap
@Gaff pinned on `check_cross_compile.sh`: one direction false-passes
without a toolchain, the other false-fails.

So I proved the mechanism natively instead, by making the *readers*
off-target on Linux — which is exactly the shape Windows sees — and
varying only the constant's gate:

| arm | const | readers | rc | `is never used` | expected |
|---|---|---|---|---|---|
| **A** pre-fix state | ungated | absent | 101 | **yes** | yes ✓ |
| **B** with this fix | gated | absent | 0 | no | no ✓ |
| **C** real tree on Linux | gated | present | 0 | no | no ✓ |

Arm A reproduces CI's exact error text, so B is not a pass by compiling
nothing — the control is non-vacuous. Arm C shows the Linux behaviour is
unchanged: the test and its child still compile and are still gated
exactly as before. **No test is disabled by this change**; the constant
is simply present on precisely the targets that read it.

Required-lane commands, run as spelled:

```
cargo fmt --all --check                                              -> 0
cargo clippy -p onnx-runtime-ep-cpu --all-targets --locked -- -D warnings -> 0
```

(Read via `${PIPESTATUS[0]}`, not the pipeline's status.)

## The part worth keeping

Both affected lanes are **advisory**. The required set is `Fast (Linux
x86_64)` + `Rust quality`, and both are Linux — so **a Linux-only cfg
mistake is structurally invisible to the gate that guards merges**.
#2125 merged green and was genuinely green on everything required.

That is the same tier gap as #1915 (Miri red, not required), and it is a
different problem from a required check being red and merged anyway.
Recorded on the audit ledger in #2056 as such rather than as a bypass.

*No admin bypass; normal auto-merge, waiting on required CI.*

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request Aug 26, 2026
…it (#2121)

Five `cargo test` steps in `ci.yml` — including the one in the
**required** `Fast (Linux x86_64)` lane — derived their package set from
an unguarded command substitution:

```yaml
cargo test --locked $(python .github/scripts/workspace_test_packages.py cargo-args offline-linux)
```

If that substitution ever produces nothing, `cargo test` still runs — as
a **bare** `cargo test` — and the step exits 0.

## Measured shell semantics

Under GitHub's default step shell (`bash -e {0}`), not inferred:

| form | resolver fails | step exit | cargo runs |
|---|---|---|---|
| `cargo test $(resolver …)` | exit 2, empty stdout | **0** | **yes,
bare** |
| `cargo test $(resolver …)` | exit 0, empty stdout | **0** | **yes,
bare** |
| `packages="$(resolver …)"` | exit 2 | 2 (`set -e` aborts) | no |
| `packages="$(resolver …)"; test -n "$packages"` | exit 0, empty | 1 |
no |

Assignment is the load-bearing half: `set -e` **does** abort on a
failing substitution in an assignment and **does not** abort on one
inside an argument list. The `test -n` guard covers the remaining case
where the resolver succeeds but prints nothing.

## What the degraded run actually tests

A bare `cargo test` falls back to `default-members` (46 crates), not the
lane set (49):

- **14 crates silently dropped**, including the entire plugin/ABI/FFI
surface.
- **`onnx-genai-ort-sys` pulled in** — explicitly denylisted because its
build script downloads a native ONNX Runtime — plus both PyO3 crates.

**Severity.** The harm is *invariant in the outcome colour*: a
**required** check silently stops exercising the 14-crate plugin/ABI
surface. Whether the degraded run then goes red is contingent on network
and environment — the denylisted `ort-sys` crate has a strong offline
failure vector, so a **red lane with a misattributed cause** is likelier
than a silent green — but what the lane *covers* is not contingent on
anything. The defect is that the lane stops testing what it claims to
test while its exit code stays authoritative, in either colour. (Framing
corrected by the independent review; I had originally anchored on the
red/green coin-flip, which is the symptom, not the defect.)

## The fix

Each of the five sites becomes:

```yaml
packages="$(python .github/scripts/workspace_test_packages.py cargo-args <lane>)"
test -n "$packages" || { echo "::error::lane '<lane>' resolved to an empty package set"; exit 1; }
cargo test --locked $packages
```

Sites: `fast-linux` (`offline-linux`, **required**), `rust-coverage` ×2,
`rust-windows-arm64`, `cli-ort`. A rationale comment is added at the
required site only.

## Mutation battery — 6 arms, effective

Each arm names what it expects and asserts whether the *test command*
was reached, discriminated by argv:

| arm | exit | test cmd ran | packages |
|---|---|---|---|
| new_healthy | 0 | yes | 49 |
| new_badlane | 2 | **no** | 0 |
| new_emptyres | 1 | **no** | 0 |
| old_healthy | 0 | yes | 49 |
| old_badlane | **0** | **yes** | **0** ← defect |
| old_emptyres | **0** | **yes** | **0** ← defect |

Both defect arms fail on the old form and pass on the new one, so the
guard is what changes the verdict.

**Instrument bug found and fixed mid-battery, disclosed because it
invalidated a first run:** my stub `cargo` also shadowed the resolver's
*own* `cargo metadata` call, crashing the resolver and causing the
recorder to log the resolver's invocation instead of the lane's. Every
arm was contaminated — `new_healthy` reported a nonsensical `exit=1
pkgs=0`. Fixed by passing `metadata` through to the real cargo and
discriminating recorded commands by argv. Same species as the errors
this repo's measurement-discipline skill catalogues: the instrument
resolved against itself instead of against the subject.

## Per-site verification

All five shipped steps executed with a stub recorder to confirm the real
argv:

| site | lane | packages | exit |
|---|---|---|---|
| fast-linux (required) | offline-linux | 49 | 0 |
| rust-coverage | offline-cross-platform | 48 | 0 |
| rust-coverage (linux-only) | linux-only | 1 | 0 |
| rust-windows-arm64 | offline-cross-platform | 48 | 0 |
| cli-ort | ort-backed | 6 | 0 |

`cli-ort`'s trailing `-- --test-threads=1` is preserved. YAML parses; 9
jobs; the `Fast (Linux x86_64)` and `Rust quality` check names are
unchanged, so the ruleset's required checks still resolve.

## Related blind spot (not fixed here)

`workspace_test_packages.py verify`, which runs in the required
`rust-quality` lane, would catch a *crashed* resolver — but it **takes
no lane argument and never reads `ci.yml`**, so a lane-name mismatch
between the workflow and the script stays invisible while `verify`
prints `coverage ok`. It also treats advisory and required lanes as
equivalent evidence: it proves a crate is a member of *some* lane, not
that any *enforced* lane runs it. That is the same
cardinality-vs-identity trap as §9 of the measurement-discipline skill,
and it is why the "no required lane runs these six crates' tests" gap
survived. Left for a separate change, since making `verify` strict today
would red `main` and the ruleset half is Justin's call.

Base merged with current `main` (`0be2d23fe`) and re-validated before
arming.

Working as Holden (Security Engineer).



---

## Correction: the first push broke the three Windows legs

The guard as first written was bash-only, and **four of the five sites
also run on Windows**, where the default step shell is **pwsh**. CI
failed on exactly `Rust coverage (Windows x86_64)`, `Rust (Windows
ARM64)` and `CLI ORT (Windows x86_64)` — no other lane. A failure set
that is exactly the Windows legs and nothing else is the signature of a
shell-portability defect, and that is what it was.

**Root cause, reproduced directly under pwsh** with
`$ErrorActionPreference = 'stop'` and a *native* fake cargo:

| form | shell | exit | test cmd | argv |
|---|---|---|---|---|
| inline `$( )` | pwsh | 0 | ran | `argc=14` — correct 6-crate set |
| guarded assignment | pwsh | 1 | **never ran** | — |
| guarded assignment | `bash --noprofile --norc -eo pipefail` | 0 | ran
| 98 tokens — correct 49-crate set |

**Why the original was portable and I mistook that for it being bash.**
The resolver prints **one token per line**. bash word-splits that; pwsh
returns it as an array and splats it element-wise into a *native*
command. So `cargo test --locked $(resolver …)` happened to deliver the
right argv under both shells. That portability was an accident of the
output format, never a contract — and it is precisely what made the
inline form look shell-agnostic while its failure mode was not.

**Fix:** `shell: bash` on all five steps. `shell: bash` already appears
in every affected job — including `rust-windows-arm64` itself, for
"Install the pinned Rust toolchain" and "Resolve cargo cache target" —
which is in-repo evidence that bash is present on `windows-11-arm`,
rather than an assumption about the runner image.

### Instrument note

My first pwsh reproduction used a **pwsh function** as the fake cargo
and reported `argc=3`. That number is wrong for a 6-crate lane, and the
discrepancy is the tell: pwsh passes an array to a *function* as a
single object and splats it only for *native* commands. Re-running
against a real executable gave `argc=14`. Had I not checked the number
against what the lane should contain, I would have reported that pwsh
mangles the argument list — a real-sounding conclusion about the wrong
subject.

That is the same failure as the `cargo metadata` contamination disclosed
above, and the same one as the original defect: **the instrument
resolved against itself instead of against the subject.** I measured
`bash -e {0}` semantics, concluded "this is how the step behaves", and
shipped it to four steps that were not running bash. The measurement was
correct; its scope was not. A limitation is scoped to the thing that
carries it.


---

## Second correction: CRLF, found only once the Windows lanes ran bash

`shell: bash` landed correctly — the ARM64 log confirms `C:\Program
Files\Git\bin\bash.EXE --noprofile --norc -e -o pipefail {0}` — and then
the step failed on something the pwsh path had been hiding:

```
error: unexpected argument 'onnx-genai-cuda-version-guard^M
' found
```

**Windows Python writes stdout in text mode and translates `\n` into
`\r\n`.** bash splits command substitution on `IFS`, which contains
space, tab and newline but **not** carriage return — so every token kept
a trailing `\r` and cargo saw it as part of the package name. PowerShell
strips CRLF when it splits native output into an array, so this was
invisible for exactly as long as the Windows lanes ran pwsh.

So the inline form was portable for **two** independent accidents, not
one: one-token-per-line output (which `package_args` documents), *and*
pwsh's CRLF stripping (which nothing documented, because nothing
depended on it).

**Fixed at the producer**, not per-step, so a future call site cannot
reintroduce it: the resolver now forces LF separators for the
`cargo-args` path.

Verified by emulating Windows text-mode stdout on Linux — a falsifier
that fails without the change and passes with it:

| producer | resolver bytes | bash tokens | argc |
|---|---|---|---|
| without fix | `-p\r\nmlas-sys\r\n` | `-p\r`, `mlas-sys\r` | 4 |
| with fix | `-p\nmlas-sys\n` | `-p`, `mlas-sys` | 4 |

**`argc` is 4 either way.** An argument-count assertion — the obvious
way to check "did the right package set reach cargo" — passes under the
defect. Only token *identity* separates them. That is the same
cardinality-vs-identity trap as the `verify` blind spot described above,
arriving a third time in the same change: `verify` counts crates and
cannot see which lane; my first pwsh model counted `argc=3` and could
not see it was measuring a function instead of a native command; and
here the count is invariant across the bug.

`verify` still reports `coverage ok: 55 tested, 5 denied`, and both
lanes still resolve to 98 and 12 tokens.


---

## Rebased onto #2023, which changed what this PR has to satisfy

`main` landed **#2023** ("run the ORT-backed crates' tests in a required
lane") while this was in review. It closes the six-crate gap described
above and adds two subcommands — `self-test` and `verify-required-tier`.
Both conflicts were co-located edits where each side is wanted, and
there is a **sixth** call site now, in `Rust quality`, which makes it
the most load-bearing of the set. It is converted too.

### `verify-required-tier` refused the guarded form — correctly

Its `packages_tested_by` credits a lane only when the `cargo-args` call
and the `cargo test` occupy the **same fragment**. The guard puts them
on separate lines, so nothing was attributed and the gate reported that
no required check runs any crate's tests — all 55.

That is the gate behaving as designed, not a bug in it. Its docstring
anticipates exactly this class:

> joining continuations moves attribution in the permissive direction,
and this gate's whole thesis is that an over-read is fatal while an
under-read merely fails loudly … the fix is to put the invocation on one
line.

Its prescribed remedy is precisely the thing this PR exists to undo, so
the gate is **extended** rather than worked around — and extended in the
direction its own thesis allows.

### The extension is a dataflow read, not a join

A lane resolved into a shell variable is credited to a later `cargo
test` **only if that command references the variable**. The reference is
what does the crediting. Reassignment *replaces* a variable's lanes
rather than adding to them, so shadowing the name with a non-lane value
drops the credit rather than keeping it.

Two self-test arms hold it there, and both are mutation-effective:

| mutation | arm that fails |
|---|---|
| credit every assignment regardless of reference (**permissive**) |
`packages_tested_by refuses a lane the cargo call never uses` |
| remove the variable read (back to pre-extension) | `packages_tested_by
credits a lane the cargo call uses` + `verify-required-tier (control)` |

The permissive mutation is the one that matters: it is the failure mode
the gate's author warned about, and the arm exists specifically to catch
it rather than to confirm the happy path.

### Post-merge state

```
verify                 coverage ok: 55 tested, 5 denied
verify-required-tier   exit 0 -- 55 package(s) tested by ['Fast (Linux x86_64)', 'Rust quality']
                       windows ORT coverage ok: 6 package(s) via ['CLI ORT (...)']
self-test              37/37 arms behaved as stated   (35 before, +2 mine)
```

Six sites guarded, zero inline substitutions remain, and the three-dot
diff touches exactly two files.


---

## Fourth correction: the guard tripped a *different* gate, and that
gate was right to be suspicious

`Rust quality` — a **required** lane — went red on:

```
FAIL no cargo test step in ci.yml carries an unguarded name filter
```

The gate is `scripts/test_step_test.sh`, and it guards the same bug
class this PR does: a bare word after `cargo test` is a **name filter**,
and libtest reports `ok` / exit 0 for a filter that matches nothing. So
an accidental positional silently converts "run the suite" into "run
nothing, successfully" — the identical fail-open shape, one layer down.

It flagged my steps because its scanner joins every line of a `run:`
block with a space and reads the result as one argv. That premise held
while every `cargo test` step in this file was a single line. **My guard
makes three of them scripts**, so the guard's own words — `echo`,
`::error::...`, `exit`, `1` — landed in the same argv as `cargo test`
and were read as selectors.

Worth being clear about the direction of the error: the gate produced a
**false positive on a correct step**, not a false negative on a broken
one. For a ban that is the right way to fail, and it is the same
argument @Gaff made against word-boundarying `shape_dispatch_gate` — do
not loosen a ban to accommodate a benign case if loosening it could also
admit a real one. So I did not add an exemption for my steps.

### What I changed instead — the premise, in two places

**1. Statement boundaries survive the accumulator.** The scanner
discarded newlines, which is exactly the information needed to tell a
guard *beside* the cargo call from arguments *to* it. Which style a
block uses decides it: `run: |` keeps newlines as separators, `run: >-`
folds them into spaces. The accumulator now records the block scalar
style, splits on `;`, `&&`, `||` and real newlines, and scans only the
fragment containing `cargo test`.

**2. A variable is blanked only when it is a generator.** `$(...)` was
already blanked as opaque, with the existing dynamic cell closing that
hole by *executing* every generator and proving it emits options only.
My form moves the substitution into a variable, so the variable needs
the same treatment — but only when it was assigned from `$(...)`.
`packages="some::filter::"` followed by `cargo test $packages` is still
caught.

That second point is where a widened ban usually acquires a quiet hole,
so it is where I put the falsifiers.

### Falsifiers — each names the assertion it requires to be red

Three steps added to the detector's own mutant fixture: a guarded step
that must **not** be flagged, a guarded step with a trailing
`kernels::something::` that **must** be, and
`packages="kernels::something::"` (assignment that is *not* a generator)
that must be.

| mutation | want | got | assertion that failed |
|---|---|---|---|
| control | PASS | PASS | — |
| record vars from **any** assignment, not only `$(...)` | FAIL | FAIL |
`MUTATION: the detector finds an unguarded filtered step` |
| read a literal block as folded (drop style detection) | FAIL | FAIL |
same, + the real-`ci.yml` cell |
| drop the statement split | FAIL | FAIL | same, + the real-`ci.yml`
cell |
| stop blanking generator vars (over-strict) | FAIL | FAIL | same, + the
real-`ci.yml` cell |

An earlier revision of the fixture passed all four mutations **for the
wrong reason** — a leftover string literal from the blanked assignment
happened to remain in the same fragment, so the arm went red on debris
rather than on the filter. Only the real-`ci.yml` cell was actually
carrying the claim. That is @Gaff's finding from #1995 arriving in my
own battery: *a red-expecting check must name what it expects to be
red*, and I would add — it must be red for the stated reason, which you
only learn by reading which assertion failed rather than the exit code.
Fixing the accumulator fixed the fixture too.

`33/33` assertions pass. `shellcheck` is clean apart from the `SC1091`
already present on `main` (2 occurrences on both sides).

## Blocked on a break that is not mine — #2142

`Rust (Windows ARM64)` and `Rust coverage (macOS arm64)` are red on
`main` independently of this PR: `COUNTERS_OBSERVER_CHILD_ENV` is
declared ungated but consumed only under `#[cfg(target_os = "linux")]`,
so it is dead code off-Linux and these lanes build with `-D warnings`.
From #2125 (`85565fc5b`). Filed as **#2142** with a two-arm
reproduction; not fixing it here, as it is @sebastian's area and
unrelated to this diff.

Neither lane is required, but I am not merging while they are red. Worth
naming why it survived: the required set is exactly `Fast (Linux
x86_64)` + `Rust quality` and **both are Linux**, so a Linux-only `cfg`
mistake is structurally invisible to required CI — the same tier gap
@Gaff documented, reached from a different direction.


---

## Merged `main` again, and it had added three more of these

`main` moved 13 commits, one of them **#2098** ("derive the clippy
package lists so every tested crate is linted", closing #2058). It
touches all three of my files, and git merged it without a conflict —
which is worth saying out loud, because a clean textual merge is not a
correct one. It added three new invocations in exactly the form this PR
exists to remove:

```yaml
- name: Clippy Linux offline crates          # fast-linux        (REQUIRED)
  run: >-
    cargo clippy --locked --all-targets
    $(python .github/scripts/workspace_test_packages.py cargo-args lint)
    -- -D warnings
```

plus `Run clippy on all offline crates` (`rust-quality`, **required**)
and `Clippy Windows ARM64 offline crates`.

The consequence is sharper here than for the test lanes. #2058 was *"21
crates are compiled and tested by CI and linted by nothing"*. If this
substitution degrades, clippy lints the default members instead of the
lane and exits 0 — so #2058 is silently re-opened **in the lane whose
only purpose is to prove it stayed closed**. Two of the three are
required.

All three are now bound and guarded, matching the six `cargo test`
sites. Healthy-path argv is byte-identical:

| site | old vs new | argc | crates |
|---|---|---|---|
| `lint` (×2 sites) | **identical** | 116 | 55 |
| ARM64 `offline-linux` | **identical** | 106 | 49 |
| bad lane | — | exit **2**, clippy never invoked | — |

`ci.yml` is now **9 guards, 0 unbound substitutions**, and the 9 job
`name:` values are byte-identical to `main`.

### The same trap, third time, and it fails in the dangerous direction

Binding the generator moves the lane name off the `cargo clippy` line,
so `linted_packages` stopped seeing it — exactly what
`verify-required-tier` did to me earlier in this PR. But the direction
differs and that matters: a package then looks **unlinted**, `verify`
goes red, and the obvious repair is to loosen the scanner. That would be
the wrong repair for the same reason @Gaff gave for
`shape_dispatch_gate`.

`clippy_blocks` now reads backwards for the assignment, bounded by
indentation so it cannot walk out of the step, and credits it **only
when the invocation actually references the variable**. Crediting every
assignment in scope would be a join rather than a dataflow read, and
that scanner's docstring names over-reading as its whole risk.

| mutation | arm that fails, by name | `verify` |
|---|---|---|
| credit every preceding assignment (**permissive**) | `linted_packages
refuses a lane the clippy call never references` | **exit 0** |
| remove the backwards read | `linted_packages credits a lane the clippy
call reads from a variable` + `verify (control)` | exit 1 |

The permissive row is the load-bearing one, and it makes the case for
these arms better than I could: **the over-read is invisible to
`verify`.** Production stays green, every package still looks linted,
and nothing anywhere says otherwise. The new arm is the only thing
standing on that edge.

### Local state

```
verify                 55 tested, all linted by 10 clippy invocation(s)
verify-required-tier   exit 0
self-test              48/48 arms behaved as stated
test_step_test.sh      36/36 assertions passed
shellcheck             clean apart from the SC1091 already on main
ci.yml                 9 guards / 0 unbound substitutions / job names identical to main
```

## Advisory lanes red on `main`, both filed, neither mine

| lane | cause | issue |
|---|---|---|
| `Rust (Windows ARM64)`, `Rust coverage (macOS arm64)` |
`COUNTERS_OBSERVER_CHILD_ENV` ungated but consumed only under
`cfg(linux)` — dead code off-Linux, `-D warnings` | **#2142** |
| `CUDA compile (Linux x86_64)` | `capture_sync_contract`: NMS (#2130)
and Unique (#2113) added unguarded `.synchronize()` without the reviewed
allowlist entry | **#2149** |

For the CUDA one the attribution is exact rather than argued: **#2130 is
not in my branch at all** — it reaches CI only through the merge ref —
and I reproduce the failure locally from the Unique half alone. My diff
contains zero Rust files.

Three advisory lanes red on `main` from three unrelated commits in one
day is itself the finding. Required is `Fast (Linux x86_64)` + `Rust
quality`, and **both are Linux**, so a Linux-only `cfg` mistake is not
merely under-covered — it is structurally invisible to the required set,
in every crate.


---

## Second independent review, and the six findings it returned

The diff grew after the first review (three clippy sites from #2098, a
second scanner repair), so I asked for a fresh independent Opus review
of the whole thing rather than of the delta. Verdict **APPROVE**, with
six non-blocking findings. All six are now fixed, and each is falsified
by a named mutation arm rather than by inspection.

| # | finding | repair | arm |
|---|---|---|---|
| a | `_generator_vars_before` could walk past a step boundary and
credit the *previous* step's deeper-indented body | return `{}` when the
invocation line is itself a list item or carries `run:` | `B1` |
| b | `$packages` matched `$packages_extra` — a prefix collision
granting credit to the wrong variable | match variable references whole
| `E1` |
| c | a plain assignment shadowing a generator assignment left the stale
credit in place | `_ANY_ASSIGN` clears the credit | `E2` |
| d | blanking a substitution to `" "` let a preceding value-taking
option swallow the next word, so `--features $(...) my::filter::` lost
its filter entirely | emit an `@@sub@@` sentinel on **both** the inline
`$( )` and `$var` paths | `D1`, `D1b` |
| e | `llvm-cov` and `+toolchain` invocations were not scanned |
first-token rule, so `Install cargo-llvm-cov` steps are examined and
correctly *not* flagged | `D2` |
| f | a scanner that reads nothing reports a clean pass | assert the
scan reached `ci.yml` and names the required lane's own test step | `F1`
|

### The (d) fixture is the finding worth recording

My first version of the (d) fixture exercised the `$var` path. So the
arm that mutates the **inline `$( )`** path — revert `@@sub@@` back to
`" "` — came back:

```
D1  expect FAIL, got PASS   *** DID NOT BITE ***
```

Right colour on every other arm, and this one silently inert: the
fixture never reached the code the mutation changed. The generalisation
is the mirror of Gaff's rule about naming what you expect to be red:

> **A mutation that does not bite is a statement about your fixture, not
about your code.** It does not mean the path is safe; it means nothing
you wrote goes there.

That is a third distinct way a battery lies, alongside the two already
on the board — Gaff's zero-selection filter, and Gaff's
18-tests-none-of-them-the-right-ones. All three produce a number that
looks like a measurement. With the inline fixture added, both arms bite:

```
D1   expect FAIL, got FAIL   failing: MUTATION: the detector finds an unguarded filtered step
D1b  expect FAIL, got FAIL   failing: MUTATION: the detector finds an unguarded filtered step
F1   expect FAIL, got FAIL   failing: ...and the scan actually reached ci.yml (not a pass by reading nothing)
```

### Direction-of-error, restated for these two scanners

They fail in opposite directions and the asymmetry drove every one of
the six repairs:

- `test_step_test.sh` enforces a **ban**. A false positive fails loudly
on a benign name. That is the correct direction and I did not loosen it
— the same argument Gaff made against word-boundarying
`shape_dispatch_gate`.
- `packages_tested_by` grants **coverage credit**. Here an over-read is
*fatal and silent*: it says a package is linted when nothing lints it.
So it credits only variables it can prove are referenced, and stops at
blank lines, at lesser indent, and at a step boundary.

Findings (a), (b) and (c) are all the same species — an over-read on the
credit-granting side — which is why they are the ones I would have
shipped without noticing.

### Local state, re-measured on the current base

```
verify                 exit 0
verify-required-tier   exit 0
self-test              51/51 arms behaved as stated
test_step_test.sh      38/38 assertions passed
ci.yml                 9 guards / 9 assignments / 0 unbound substitutions / 9 jobs
job names              byte-identical to main
shellcheck             clean apart from the SC1091 already on main
```

`main` was 8 commits ahead when I finished, one of them touching the
crates this branch's lanes build. Because
`strict_required_status_checks_policy = false`, CI would otherwise have
graded me on the stale base — so I merged `main`, re-ran everything
above, and confirmed the merge touched **none** of `ci.yml`,
`workspace_test_packages.py` or `test_step_test.sh`, so the mutation
battery transfers unchanged.

**#2142 is fixed on `main`** by #2147, which clears the `Rust (Windows
ARM64)` and `Rust coverage (macOS arm64)` reds listed above. **#2149
remains open** and is not mine; my diff still contains zero Rust files.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Holden <holden@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

20 crates are tested by CI and linted by nothing; two clippy denials are live on main because of it

1 participant