Skip to content

Audit umbrella: gates that pass vacuously (traceability, cargo-vet, conformance suites, Playwright) #957

Description

@avrabe

Umbrella from the inconsistency audit, weak-green lens. Headline instances filed separately: #953 (compliance discards validate's exit code), #954 (check verification-evidence ok-on-empty).

Ground truth: gh api repos/pulseengine/rivet/branches/main/protection → required_status_checks.contexts == ["CI Gate"], enforce_admins: true. Severity below is measured against that.

Verification status: agent-reported, several with constructed negative controls. I verified #953 and #954 myself; the rest need confirming before fixing.

1. The traceability gate cannot fail on a traceability regression — negative control constructed

ci.yml:304 runs rivet validate. On the real corpus: Result: PASS (931 warnings), exit 0. Every traceability finding is Warning or Info — 286 prose-mention-without-typed-link, 240/87 requirement-verification, 214/73 requirement-coverage, 119 orphan-artifact.

Control: appending one implemented requirement with no links, no design, no test still gives PASS, exit 0 (935 warnings). A PR merging an untraced implemented requirement passes the gate whose section header reads "The project that BUILDS the traceability tool must gate its own PRs on traceability."

The step comment is honest about it. But rivet-cli/src/docs.rs:1468,1498 publish to users the recipe rivet validate --strict-cited-sources --fail-on warning. rivet ships a stricter gate to its customers than it runs on itself. The ready-made ratchet — --new-since <ref> --fail-on … at main.rs:5445 — is wired into no workflow.

2. Supply Chain (cargo-vet) audits zero dependencies

ci.yml:1281-1313:

if [ ! -d supply-chain ]; then cargo vet init; ... fi
cargo vet --locked

ls supply-chain/ → No such file or directory. git log -- supply-chain → empty; it has never existed and is not gitignored. So init runs every time, and cargo vet init records every current dependency as an exemption precisely so the initial state passes. Observed green in run 35052742650. Also outside CI Gate's needs.

3. Miri and Proptest are outside CI Gate and outside its documented exclusion list

ci.yml:1520-1524 enumerates exclusions as "Kani, Mutation Testing, Coverage, Playwright" — presenting itself as complete. Also absent from needs and from that list: Miri (safety surface), Proptest (extended), Security Audit, Supply Chain, VS Code Extension, Zola export smoke, Traceability (hosted fallback).

Miri is the only gate over the std::mem::transmute of raw SyntaxKind in sexpr.rs/yaml_cst.rs — the sole unsafe in rivet-core. A Miri red cannot block a merge. (Whether that's intended is the decision; the list being wrong is the bug.)

4. yaml_test_suite.rs — 68 conformance cases blind to total loss of parse structure

Module doc promises three properties; the third ("graceful Error recovery") is never asserted — fn parse_has_errors at :93 has zero call sites. No test inspects tree shape. If yaml_cst::parse degenerated so every document lexed into one flat Scalar, root.text() == input would still hold (rowan is lossless by construction) and all 68 cases stay green against a CST with no mappings or sequences.

5. sexpr_fuzz::roundtrip_equivalence — the failure mode is prop_assume!d away

sexpr_fuzz.rs:375: prop_assume!(reparsed.is_ok(), "pretty-print must re-parse"). prop_assume! rejects the sample rather than failing. The one outcome the property exists to detect is routed to "skip". Partial breakage never surfaces.

6. unknown-field is Severity::Info

validate.rs:1187. Control: renaming priority→prioriti, category→categorie, adding relase: on REQ-001 gives PASS, exit 0 with three INFO lines. A rename silently detaches schema-declared fields; release-scoping queries go empty; the gate stays green.

7. Runner Liveness — the liveness branch is unreachable (opposite failure from REQ-354)

runner-liveness.yml:114-121 queries repo-scope runners. The workflow's own issue body at :264 says "runners are registered at the ORG. Repo scope returns 0 on a perfectly healthy pool and can never say otherwise." So total is always 0, the -gt 0 guard fails, and the "All N offline" branch is unreachable. Scenario: pool dies on a quiet Sunday with nothing queued → probe sees total=0 and oldest_age=0 → posts "✅ Runner pool healthy again" and closes the alert.

8. || true on the release path

  • scripts/build-wasm.sh:27-28 — success is echoed before the check, and the ls verifying both wasm filenames has stderr and exit code discarded. The only artifact assertion on the release path. (See Two spar revisions ship in one binary: native pinned to v0.10.0, wasm cloned from HEAD, and the drift guard is dead code #951.)
  • ci.yml:652 — cargo install cargo-deny --version 0.20.2 || true silently falls back to a preinstalled 0.16.4 whose unmaintained default differs. This one is in CI Gate's needs, so it ranks highest of this group. REQ-347's own text calls that "exactly how a gate passes vacuously".
  • ci.yml:1499 — cp target/nextest/ci/junit.xml … || true; the release tarball ships without test results, green.

9. Playwright specs that assert nothing

  • documents.spec.ts:32,53,84 select a[hx-get^='/documents/'], but render/documents.rs:53,81 emits href-only links. All three loops iterate an empty list; :84 then logs "No .doc-ref links found" while the corpus holds 234 [[…]] refs.
  • rendering-invariants.spec.ts:239 — comment says "Strict inequality: scoping must yield fewer nodes", assertion is toBeGreaterThanOrEqual. graph_view could ignore variant entirely and pass. The sibling at :243 gets it right.
  • audit-regression.spec.ts:104 — XSS regression test whose only assertion is body?.length > 0, inside an unguarded if (isVisible()) with no else/test.skip().
  • coverage-view.spec.ts:58, source-view.spec.ts:45,81 — assert row counts / body.length > 100; the nav shell alone clears them.

10. Smaller, verified

  • verify_archive_size.sh — cap with no floor; an empty report tars to ~45 bytes and passes (see Release path: the compliance action discards rivet validate's exit code, so a release can ship on a failing validate #953).
  • action.yml:308 — grep 'name:' rivet.yaml | head -1 | awk … in bash -e without pipefail; a missing file gives PROJECT="" and a misnamed archive that looks intentional.
  • zola-export-smoke.sh:93 computes PAGES after the verdict; an empty content dir prints OK: … no absolute artifact-link leaks. Its leak regex requires exactly one path segment, so href="/artifacts/req-001/" evades it. (Advisory — continue-on-error: true.)
  • check-schema-version-bump.sh:30-33 — git diff | grep -v … || true swallows a failing git diff; STRICT defaults to 0 and ci.yml:257 never sets it.
  • install-hooks.sh:68 — … || echo "0"; a JSON-shape change or a validate panic yields errors=0 and the hook commits.
  • rivet-core/tests/integration.rs:1269 — strictdoc_reqif_import gated on /tmp/zephyr-reqif/… which no script creates. Dead on every machine.

The inverse, and the cheapest thing here

rivet check bidirectional exits 1 on the live corpus and is run by no workflow (grep -rn 'rivet check' .github/ matches only verification-evidence). A working oracle wired into nothing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions