Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion .github/workflows/test-comprehensive.yml
Original file line number Diff line number Diff line change
Expand Up @@ -382,14 +382,72 @@ 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 — but vitest 4 cannot run on 18
# (engines ^20 || ^22 || >=24), so the old matrix leg tested the RUNNER's
# unsupported path and reported on neither. This runs what a Node 18 USER
# runs: the compiled dist, under Node 18, with no test framework in the way.
#
# Built on 20 and RUN on 18 on purpose — that is the user's situation, since
# they install a prebuilt package rather than compiling it.
#
# Gated on `run_core`, not `run` like cross-platform was: run_core is true on
# every PR. A skipped job satisfies a required check, so Node 18 coverage that
# only sometimes runs is Node 18 coverage that 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. Both directions are 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'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
node: ["18", "20", "22"]
# vitest 4 declares engines ^20 || ^22 || >=24 — it does not run on Node
# 18, so an 18 leg here exercised the RUNNER's unsupported path and said
# nothing about the shipped code. It failed intermittently for exactly
# that reason (1 test of 2194, only on 18). Node 18 remains supported by
# the PRODUCT — package.json engines >=18, measured 200/200 on the built
# dist — and node18-smoke below is what actually tests that claim.
node: ["20", "22"]
runs-on: ${{ matrix.os }}
defaults:
run:
Expand Down
27 changes: 27 additions & 0 deletions node/scripts/node18-smoke.mjs
Original file line number Diff line number Diff line change
@@ -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}`);