Skip to content

ci: fail when a test or crate is reached by no runner (#194) - #195

Merged
ruvnet merged 1 commit into
ruvnet:mainfrom
vidaunited:fix/194-runner-coverage-guard
Sep 1, 2026
Merged

ci: fail when a test or crate is reached by no runner (#194)#195
ruvnet merged 1 commit into
ruvnet:mainfrom
vidaunited:fix/194-runner-coverage-guard

Conversation

@vidaunited

Copy link
Copy Markdown

ci: fail when a test or crate is reached by no runner (#194)

npm test is npm run -ws --if-present test and cargo test is --workspace, so
two things are currently indistinguishable from a pass: a workspace with no test
script, and a crate outside the members list. Both runners then report green and
complete. draco.yml already carries a note from the last time this bit --
"An explicit list silently drifts: arm tests existed but CI never ran them" --
and this generalises that fix instead of repeating it per-workflow.

scripts/check-runner-coverage.mjs walks every test file, package.json and
Cargo.toml and asserts each is either reached by a runner or recorded as a
deliberate exclusion with a reason. Zero dependencies, static, <1s, no network.

It counts three invocation forms as "reached", all of which are in use here:

  • a workspace matched by the root workspaces globs that has a test script
  • a file named explicitly in a workflow, as vitest run <path> OR node <path>
  • a workflow job with working-directory: <dir> plus its own npm test

The second and third were added after the first draft produced FALSE POSITIVES
on this repo: ci.yml's "SOTA integrity-attestation gate" runs its suites under
plain node, and pages.yml tests apps/web-ui via working-directory. Those 11
findings were wrong, and a checker that cries wolf gets deleted. The
working-directory match is deliberately coarse (workflow-level, not job-level):
a missed gap costs less than a false alarm that blocks CI on correct code.

The allowlist is a RATCHET, not a suppression list -- seeded with what is
already unreached today so this passes on main unchanged, and anything NEW
fails. Entries require a non-empty reason; an allowlist without reasons is a
mute button and the next reader cannot tell a decision from an oversight. A
stale entry also fails, so it cannot rot into blanket permission. Deleting
entries is the point.

Seeded state (measured on 1f3b870, all cross-checked against the workflows):
56 test files -- 45 root tests/ (npm run -ws excludes the root package),
9 services/apicompletions (outside packages/),
2 kimi-k3-harness (outside packages/
, has its own
"test": "vitest run" that nothing invokes)
4 crates -- oo-agents 20, horizon 14, k3rs 9, k3-kernel-bench 1
(#[test] fns; each declares its own [workspace], so cargo is
behaving correctly -- nothing else runs them either)

I am not asserting these should run: kimi-k3-harness reads like a self-contained
experiment and nested [workspace] crates are a normal way to isolate a
dependency graph. The claim is only that nothing currently distinguishes
"deliberately not run" from "accidentally not run".

Proved able to fail, four ways, each verified to die for the right reason:
new unreached test file -> red, names the file and why
allowlist entry with no reason -> red
stale allowlist entry -> red
drop the node <path> form -> the 2 SOTA suites reappear as findings
(no syntax error; the fix is load-bearing)

actionlint clean. The added CI step uses no ${{ }} expressions.

npm test is `npm run -ws --if-present test` and cargo test is `--workspace`, so
two things are currently indistinguishable from a pass: a workspace with no test
script, and a crate outside the members list. Both runners then report green and
complete. draco.yml already carries a note from the last time this bit --
"An explicit list silently drifts: arm tests existed but CI never ran them" --
and this generalises that fix instead of repeating it per-workflow.

scripts/check-runner-coverage.mjs walks every test file, package.json and
Cargo.toml and asserts each is either reached by a runner or recorded as a
deliberate exclusion with a reason. Zero dependencies, static, <1s, no network.

It counts three invocation forms as "reached", all of which are in use here:
  - a workspace matched by the root `workspaces` globs that has a test script
  - a file named explicitly in a workflow, as `vitest run <path>` OR `node <path>`
  - a workflow job with `working-directory: <dir>` plus its own `npm test`

The second and third were added after the first draft produced FALSE POSITIVES
on this repo: ci.yml's "SOTA integrity-attestation gate" runs its suites under
plain `node`, and pages.yml tests apps/web-ui via working-directory. Those 11
findings were wrong, and a checker that cries wolf gets deleted. The
working-directory match is deliberately coarse (workflow-level, not job-level):
a missed gap costs less than a false alarm that blocks CI on correct code.

The allowlist is a RATCHET, not a suppression list -- seeded with what is
already unreached today so this passes on main unchanged, and anything NEW
fails. Entries require a non-empty reason; an allowlist without reasons is a
mute button and the next reader cannot tell a decision from an oversight. A
stale entry also fails, so it cannot rot into blanket permission. Deleting
entries is the point.

Seeded state (measured on 1f3b870, all cross-checked against the workflows):
  56 test files -- 45 root __tests__/ (npm run -ws excludes the root package),
                    9 services/apicompletions (outside packages/*),
                    2 kimi-k3-harness (outside packages/*, has its own
                      "test": "vitest run" that nothing invokes)
   4 crates     -- oo-agents 20, horizon 14, k3rs 9, k3-kernel-bench 1
                    (#[test] fns; each declares its own [workspace], so cargo is
                     behaving correctly -- nothing else runs them either)

I am not asserting these should run: kimi-k3-harness reads like a self-contained
experiment and nested [workspace] crates are a normal way to isolate a
dependency graph. The claim is only that nothing currently distinguishes
"deliberately not run" from "accidentally not run".

Proved able to fail, four ways, each verified to die for the right reason:
  new unreached test file      -> red, names the file and why
  allowlist entry with no reason -> red
  stale allowlist entry        -> red
  drop the `node <path>` form  -> the 2 SOTA suites reappear as findings
                                  (no syntax error; the fix is load-bearing)

actionlint clean. The added CI step uses no ${{ }} expressions.
@vidaunited

Copy link
Copy Markdown
Author

@ruvnet — this one needs an Approve and run workflows click when you get a chance; all three runs (CI / Security / Real Tools) are sitting at action_required as a first-time-contributor gate, so nothing has executed yet.

Low-risk to approve, for what it's worth: the check is a static, zero-dependency Node script — no network, no install, <1s — and the allowlist is seeded with the current state so it passes on main unchanged. It can only fail on something newly unreached.

If you'd rather sanity-check it locally before spending CI minutes:

git fetch origin pull/195/head:pr-195 && git checkout pr-195
node scripts/check-runner-coverage.mjs   # exits 0 today

And to see it bite, add any test file nothing runs — e.g. __tests__/x.test.ts — and it exits 1 naming the file and why.

No rush, and happy to adjust the approach if you'd prefer this as a warning rather than a gate, or scoped to fewer directories to start.

@vidaunited

Copy link
Copy Markdown
Author

@ruvnet — thanks for #193, that closes the scope gap cleanly.

I checked it against the original defect rather than assuming: the new pattern matches the real pre-fix root build:wasm string at b17c50b (wasm-pack build crates/kernel-wasm --target bundler --out-dir ../../packages/kernel-js/pkg --release) and rejects the three near-misses worth worrying about — the current fixed command, a correct --target nodejs build, and a legitimate bundler build that doesn't aim at kernel-js/pkg. It also runs rather than skipping alongside the runtime half: CI reports loader.test.ts (16 tests | 1 skipped), and the file carries 15 unconditional it( blocks plus the single skipIf, so the skip is the wasm-artifact guard and the new check is among the 15 that executed.

Nudging #195 one last time: it's still sitting at action_required and needs an Approve and run workflows click before anything runs. It's the runner-coverage ratchet from #194, independent of #193.

Genuinely fine to close it if it's not wanted — I'd rather that than have it linger, and I won't keep bumping either way.

ruvnet added a commit that referenced this pull request Sep 1, 2026
…on fix (#212)

Ships #212's shellDq()/yamlCommentSafe() + the move from a plain `run: echo`
scalar to a `|` block literal, across both published copies.

metaharness needs its own bump because 0.4.9 was published BEFORE #212 merged:
its dist/host-config.js has no block literal, so the fix was absent from the
shipped CLI. Verified by unpacking the published 0.4.9 tarball.

Note: the third byte-parity copy, apps/web-ui/src/generator/scaffold.ts, is
private:true and unpublished — but it is also outside CI (root workspaces is
["packages/*"] and ci.yml never references web-ui), so its 71 tests guarding
this fix run on no runner. See #195/#194.

Lockfile resynced in the same commit.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01G2iAKhp4FGq2Ty5CFnj8mT
@ruvnet

ruvnet commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Reviewed and independently verified. This is a well-built guard and the PR description is unusually honest about its own limits — the false-positive story (node <path> and working-directory forms added after the first draft produced 11 wrong findings) is exactly the right instinct: a checker that cries wolf gets deleted.

Verified all four failure modes myself, checking real exit codes (a pipe to tail masks them — I caught myself doing that mid-review):

scenario exit
clean tree, merged with current main 0
new unreached test file (tools/orphan/__tests__/…) 1 — names the file and why
allowlist entry with an empty reason 1allowlist entry with an empty reason: tests["…"]
stale allowlist entry (path gone/now reachable) 1is now reachable (or gone) — drop it

It also still passes against main after everything merged today (@metaharness/avo, numeric-* in darwin, host-adapter changes): workspaces npm test visits: 42 · cargo workspace members: 5 · test files not reached: 56 (56 allowlisted) · crates not reached: 4. The ratchet held across a busy day without anyone touching the allowlist, which is the property that matters.

It corrected me. While reviewing #212 I claimed apps/web-ui's 71 tests "run on no CI runner." That was wrong — pages.yml runs them via working-directory: apps/web-ui + npm test. This checker classifies web-ui as reached and correctly keeps it out of the allowlist, i.e. it got right what I got wrong by hand, precisely because of the working-directory form you added after the false positives. I've posted a correction on #212.

The residual gap there is narrower than I first said and this PR doesn't claim to cover it: pages.yml triggers only on push to main, never pull_request, so web-ui tests give no pre-merge signal. "Reached by a runner" and "reached before merge" are different properties; this checker measures the first, which is what it says on the tin.

node scripts/check-runner-coverage.mjs, zero deps, <1s. Merging.

@ruvnet
ruvnet merged commit d9f9a84 into ruvnet:main Sep 1, 2026
ruvnet added a commit that referenced this pull request Sep 1, 2026
Two defects, both caught by __tests__/adr-index.test.ts -- which never runs in
CI (root suite, allowlisted as unreached by #195/#194), so neither would have
surfaced on merge.

1. ADR NUMBER COLLISION. ADR-251 is already taken on main by
   ADR-251-governed-autonomous-variation-runtime.md (@metaharness/avo, Accepted
   and shipped). This PR added a SECOND ADR-251, and INDEX.md carried two rows
   with the same number. ADR-252 (LatentMesh) already cites "ADR-251 capability
   policy", meaning the AVO one -- so the collision made an existing
   cross-reference ambiguous. Renumbered to 273 (272 is the current high-water
   mark); the AVO reference is left pointing where it always did.

2. NON-CONFORMING HEADINGS. The ADR used numbered sections ("## 1. Context").
   235 of the repo's 236 ADRs use the unnumbered form and the test asserts it;
   this file was the sole exception. Stripped the numeric prefixes.

Also corrected the INDEX status, which claimed "routine created; first live run
pending" while LEDGER.md records 10 completed runs through 2026-08-29.

Not fixed here (pre-existing on main, unrelated to this PR): ADR-253 has no
Consequences section and fails the same test. It has no section that could be
renamed into one, so writing it is the author's call, not a mechanical fix.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01G2iAKhp4FGq2Ty5CFnj8mT
ruvnet added a commit that referenced this pull request Sep 1, 2026
…d evolution routine) (#179)

* docs: add ADR-251 nightly dream machine (cloud-scheduled dream cycle)

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs: ADR-251 — record created routine id trig_01T9FVWfahfGrkK9E9eCsvyw

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs(dream-cycle): prompt v2 — port Ruflo Dream Cycle v3.1 research-depth protections

Operator feedback on the first Ruflo v3 nights: research had visibly
thinned (all-None competitor rows, thin justification) because the budget
language treated research as the compressible phase. v2 flips that:

- STEP 0.6: research phase is protected; budget pressure cuts from the END
  of the pipeline (Darwin generations, corpus size, adversarial depth), and
  a shortened research pass must be disclosed, never silent
- STEP 1: sparse-ledger caution — verify dream/* branches + PRs before
  reporting a prior night as having skipped a step
- STEP 3: minimum research depth — all-None rows need a WHY, fixed lists
  are a floor not a ceiling, prefer >=1 source <12 months old, no
  same-shaped tables night after night
- STEP 7: note that a fresh corpus is graded by its own author-session,
  pointing at STEP 10's fairness check
- GLOBAL INVARIANTS: leads with trustworthy-improvement-over-activity
- ADR-251: Updated stamp + status reflects two completed runs (#181, #188)

Scheduler copy updated in the same piece of work per the mirror rule.

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs: renumber the Dream Machine ADR 251 -> 273 and conform its headings

Two defects, both caught by __tests__/adr-index.test.ts -- which never runs in
CI (root suite, allowlisted as unreached by #195/#194), so neither would have
surfaced on merge.

1. ADR NUMBER COLLISION. ADR-251 is already taken on main by
   ADR-251-governed-autonomous-variation-runtime.md (@metaharness/avo, Accepted
   and shipped). This PR added a SECOND ADR-251, and INDEX.md carried two rows
   with the same number. ADR-252 (LatentMesh) already cites "ADR-251 capability
   policy", meaning the AVO one -- so the collision made an existing
   cross-reference ambiguous. Renumbered to 273 (272 is the current high-water
   mark); the AVO reference is left pointing where it always did.

2. NON-CONFORMING HEADINGS. The ADR used numbered sections ("## 1. Context").
   235 of the repo's 236 ADRs use the unnumbered form and the test asserts it;
   this file was the sole exception. Stripped the numeric prefixes.

Also corrected the INDEX status, which claimed "routine created; first live run
pending" while LEDGER.md records 10 completed runs through 2026-08-29.

Not fixed here (pre-existing on main, unrelated to this PR): ADR-253 has no
Consequences section and fails the same test. It has no section that could be
renamed into one, so writing it is the author's call, not a mechanical fix.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01G2iAKhp4FGq2Ty5CFnj8mT

---------

Co-authored-by: ruv <ruvnet@gmail.com>
ruvnet added a commit that referenced this pull request Sep 1, 2026
…red on Windows

main has been failing on Node 20/windows and Node 22/windows since #178 merged:

  FAIL reads the pinned Ed25519 key out of the real meta-proxy.ts:
    The input did not match the regular expression /^-----BEGIN PUBLIC KEY-----\n/

meta-proxy.ts is not pinned to `eol=lf` in .gitattributes (which covers only the
draco corpus and a few golden fixtures), so a Windows checkout produces a CRLF
PEM and the LF-anchored assertion fails.

Production is NOT affected, verified rather than assumed: the PEM only ever
reaches crypto.createPublicKey(), is never string-compared or hashed, and
createPublicKey() accepts CRLF and LF alike (checked both). So the fix belongs
in the test's platform assumption, not in readPinnedPublicKey().

  simulated CRLF PEM:  OLD -> false, NEW -> true
  LF PEM:              NEW -> true (unchanged)

Root cause of it reaching main: #178 came from a fork, and fork PRs report no
checks on this repo, so its CI never ran. I merged it on local (Linux) verification
alone. The same blind spot applies to #195. Worth a follow-up on fork-PR CI.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01G2iAKhp4FGq2Ty5CFnj8mT
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.

2 participants