From f33b3a05f2301ed34457f441c241bf02b9eb356c Mon Sep 17 00:00:00 2001 From: Rome-1 Date: Tue, 8 Sep 2026 18:48:02 -0700 Subject: [PATCH] ci: add the Node 18 smoke test that 5409a84 left missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5409a84 dropped Node 18 from the vitest matrix, which was right — vitest 4 declares engines ^20 || ^22 || >=24 and cannot run there, so that leg was exercising the RUNNER's unsupported path and reporting on neither it nor the product. But it left NO replacement, so main currently ships engines >=18 with zero Node 18 signal. A support claim nothing checks is a claim, not a guarantee. This is the replacement, and it is all that remains of #239 after the rebase — the matrix edit is already on main and is dropped here rather than re-applied. It runs what a Node 18 USER runs: built on Node 20, RUN on Node 18, no test framework in the way. That is their situation exactly, since they install a prebuilt package rather than compiling one. It asserts the rf-fuwy liveness probe BOTH ways — a live gate yields its decision, an inert one yields none with a non-zero status. The second assertion IS rf-fuwy: the whole defect was `agent verify` reporting a dead gate as healthy. Measured before the job was written, not after: shipped dist, exported runConfiguredHook, 200 iterations v18.20.8 200/200 read "deny" v24.14.0 200/200 raw spawnSync, isolated from vitest and TS, 350 each 0 failures on either version, byte-identical results Mutation-checked in both directions, in-tree so the import resolves: blanking the live fixture gives `FAIL: probe read null, expected "deny"`; making the inert fixture work gives `FAIL: an inert gate was not reported inert`. My first attempt at this mutation was itself vacuous — the mutants exited 1 on a module-not-found rather than on the assertion, and I nearly recorded that as a pass. Gated on run_core rather than the `run` that gates cross-platform: run_core is true on every PR, and a skipped job satisfies a required check, so coverage that only sometimes runs can be absent exactly when it matters. --- .github/workflows/test-comprehensive.yml | 54 ++++++++++++++++++++++++ node/scripts/node18-smoke.mjs | 27 ++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 node/scripts/node18-smoke.mjs diff --git a/.github/workflows/test-comprehensive.yml b/.github/workflows/test-comprehensive.yml index c1b8828..24ff5b0 100644 --- a/.github/workflows/test-comprehensive.yml +++ b/.github/workflows/test-comprehensive.yml @@ -382,6 +382,60 @@ jobs: echo "Hook installation verified" # ── Cross-platform smoke test ────────────────────────────────────── + # Node 18 coverage that means something. package.json declares engines >=18 + # and the shipped code genuinely works there — measured 200/200 on the built + # dist — but vitest 4 cannot run on Node 18 (engines ^20 || ^22 || >=24), so + # the matrix leg that used to sit here tested the RUNNER's unsupported path + # and reported on neither. 5409a84 removed it; without this job the release + # would ship with NO Node 18 signal at all. + # + # This runs what a Node 18 USER runs: built on 20, RUN on 18, no test + # framework in the way — their situation exactly, since they install a + # prebuilt package rather than compiling one. + # + # Gated on `run_core`, not the `run` that gates cross-platform: run_core is + # true on every PR, and a skipped job satisfies a required check, so coverage + # that only sometimes runs can be absent exactly when it matters. + node18-smoke: + needs: gate + if: needs.gate.outputs.run_core == 'true' + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./node + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Enable pnpm + run: corepack enable && corepack prepare pnpm@10 --activate + + - name: Install and build on a supported Node + run: | + pnpm install --frozen-lockfile + pnpm run build + + # Switch the runtime only — the artifact under test is the one built above. + - uses: actions/setup-node@v4 + with: + node-version: "18" + + - name: The CLI runs on Node 18 + run: | + node --version + node ./dist/index.js --version + + # Asserts the rf-fuwy liveness probe BOTH ways: a live gate yields its + # decision, an inert one yields none with a non-zero status. The second + # assertion IS rf-fuwy. Both directions mutation-checked — blanking the + # live fixture or making the inert one work fails the script with its own + # message, not an import error. + - name: The rf-fuwy liveness probe works on Node 18 + run: node scripts/node18-smoke.mjs + cross-platform: needs: gate if: needs.gate.outputs.run == 'true' diff --git a/node/scripts/node18-smoke.mjs b/node/scripts/node18-smoke.mjs new file mode 100644 index 0000000..cf9d45a --- /dev/null +++ b/node/scripts/node18-smoke.mjs @@ -0,0 +1,27 @@ +// Node 18 smoke test of the BUILT artifact — deliberately not a vitest test. +// +// vitest 4 declares engines ^20 || ^22 || >=24, so it cannot run on Node 18 at +// all; the old cross-platform matrix leg was therefore exercising the runner's +// unsupported path rather than the product, and said nothing about either. But +// package.json still declares engines >=18, and a support claim nothing checks +// is a claim, not a guarantee. +// +// So this runs what a Node 18 USER runs: the compiled dist, under Node 18, with +// no test framework in the way. It asserts the rf-fuwy liveness probe both ways +// — a live gate yields its decision, an inert one yields none — because that is +// the property the release exists to establish, and the one place Node 18 +// coverage would actually matter. +import { runConfiguredHook } from "../dist/commands/agent/verify.js"; +const cmd = `printf '%s' '{"hookSpecificOutput":{"permissionDecision":"deny"}}'`; +const r = runConfiguredHook(cmd, "rm -rf / --no-preserve-root"); +if (r.decision !== "deny") { + console.error(`FAIL: probe read ${JSON.stringify(r.decision)}, expected "deny"`); + console.error(JSON.stringify(r)); + process.exit(1); +} +const dead = runConfiguredHook("rafter-does-not-exist-9c1f hook pretool", "rm -rf /"); +if (dead.decision !== null || dead.status === 0) { + console.error(`FAIL: an inert gate was not reported inert: ${JSON.stringify(dead)}`); + process.exit(1); +} +console.log(`OK on ${process.version}: live gate -> "deny", inert gate -> no decision, status ${dead.status}`);