Skip to content

Post-#381 audit of all 18 required contexts: 11 have a path to green without measuring #386

Description

@avrabe

#381 was a required status check that spent four months reading a path that never existed, scoring every run as 0 survivors, and reporting green. The property that made it invisible was not that it was wrong — it was that its failure mode produced the flattering reading. 0 is the best possible survivor count. Nothing about a broken run looked different from a good one.

That is not a property of mutation testing. It is a property of any gate whose "nothing happened" renders identically to "it worked". So all 18 required contexts were audited against one question:

Can this check report success without having measured the thing its name claims?

Three clean-room agents, no shared framing, each given a slice. Every verdict below was reached by executing the gate's own logic against constructed inputs — mutating each guard to a no-op and checking whether CI would notice. Where something was reasoned rather than executed, it says so.

Result

11 of 18 have a path to green without measuring. Four are live today.

# Required context Verdict Mechanism
1 Bench compile smoke ✅ sound declared [[bench]] targets make a missing file exit 101
2 Cargo Deny ⚠️ narrow [bans] multiple-versions = "warn", wildcards = "allow"check bans cannot fail; advisories skipped (documented, covered by RustSec job)
3 Clippy ⚠️ narrow --workspace misses the 3 nested workspaces — see #383
4 Code Coverage vacuous cargo llvm-cov with no --fail-under-*; upload is fail_ci_if_error: false. Cannot fail for a coverage reason — it duplicates Test
5 Codegen compile oracle ✅ sound best-defended job here — real assert!(status.success()), per-field distinct sentinels, if n != 1 { bail! } on the source patch
6 Detect changed paths ⚠️ fails open #384
7 Detect changed paths (proofs) ⚠️ fails open #384 — byte-identical clone
8 Format live false-green #383 — green right now over unformatted tracked code
9 Fuzz smoke (60s/target) ✅ sound 3 targets hard-coded; nothing asserts they match cargo fuzz list
10 Lean proof typecheck vacuous (sorry sub-gate) #385 — 12 sorrys, 12 self-exempted, 0 enforced. lake build itself is sound
11 Miri ⚠️ misaimed guards spar-hir-def, the one crate with zero unsafe; the 6 crates that have it are never Miri-checked. Filter -- instance:: exits 0 if renamed
12 Mutation Testing vacuous #381 — fixed, pending merge
13 Proptest (extended) ✅ sound 10× cases is real; degrades silently to 100 on a bad env var. Incidentally the only job that runs doctests
14 Rivet validate (artifacts) ⚠️ narrow passes on an empty artifact set (881 → 0 is indistinguishable); duplicate IDs, missing title, typo'd field names are all non-fatal in the pinned v0.4.3
15 Security Audit (RustSec) ✅ sound engine mutation-proved; the rustsec/audit-check@v2 wrapper was reasoned, not executed
16 Supply Chain (cargo-vet) ✅ sound mechanically fail-closed (exit 255 proved); 207 exemptions vs 4 audits + empty imports.lock is a policy weakness, not a gate defect
17 Test vacuous for doctests cargo nextest has no doctest support at all; ~25 executable doctests never run under this name
18 Verification Gate vacuous on demand skip-as-pass + author-controlled Verify-Filter: — see #358

The four that are live

The recurring shapes

Four distinct mechanisms, one symptom:

  1. Scope narrower than the name. --all ≠ the repo (Format check is green on unformatted tracked code: cargo fmt --all covers 1 of the repo's 4 workspaces #383). --workspace ≠ the repo. -p one-crate ≠ Miri coverage. cargo test <substring> exits 0 on zero matches — 13 of the 20 verification artifacts rely on exactly that.
  2. The error path is the permissive path. || true + set -e being inert inside an if (Detect changed paths fails open two ways, skipping 11 of 18 required contexts (cloned into proofs.yml) #384). grep failing into a default. Mutation Testing gate reads a path cargo-mutants never writes — 4 months of vacuous green (210 survivors reported as 0) #381 itself.
  3. Absence renders as success. Zero survivors, zero sorrys, zero artifacts, zero matched tests, an empty report — every one is the ideal reading.
  4. A gate with no configured failure mode. multiple-versions = "warn". cargo llvm-cov with no threshold. A check that runs, costs money, and is structurally incapable of red.

The general fix

Every gate should print a positive count of what it examined, and fail when that count is implausible:

Surviving mutants: 210  (caught 603, unviable 793, timeout 2)   # not "0"
0 sorrys found, 0 allowlisted, 47 files scanned                 # not "gate green"
4 workspaces formatted                                          # not silence
881 artifacts validated                                         # not "PASS"

A count cannot be produced by a broken read. That is the whole trick, and it is what tools/check_mutants_report.py now does for #381 — including a cross-check of its own number against a second, independent derivation.

The second half is the tools/check_*.py convention already in use: put the logic in a script with a --self-test decision table, run the self-test as a step before the real check, and include the historical regression as a table row. Inline workflow shell is only testable by pushing, and a log cannot distinguish "0 findings" from "0 files read".

Remaining work (split into issues as they are picked up)

Honest scope

This audit covers the 18 required contexts on main. It does not cover nightly workflows, release workflows, or the release-tag path — #366 and #372 came from the same species of question and are already filed. Nothing here says the code is bad; it says the evidence is thinner than the green ticks imply. Several gates (Codegen compile oracle in particular) were actively hardened against exactly this and held up under attack.

Refs #381, #382, #358, #366, #372.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions