chore: address Copilot review round 20
All three findings are the same shape: a check that accepts a command which
exists but cannot gate.
- Splitting on `|` and `&` conflated `||`, so both failure-masking forms counted:
`npm run X || true` swallows a rejection and `true || npm run X` never runs
the validator. Both `runsCommand` and the chain walk now split on `\n`, `;`
and `&&` only, which refuses each without special-casing either.
- A CI step (or job) carrying `continue-on-error` asserts nothing, since a
rejected skill leaves the workflow green. Both are now skipped, treating a
string `"true"` and an unevaluable `${{ }}` expression as masking on the same
conservative principle as the `if:` checks; an explicit `false` still counts.
- `rootReachesScript` was substring-based, so a `validate` reading `echo npm run
verify:skills` satisfied the sibling vouch while the guard never executed —
the cycle protecting nothing. It now uses the exact chain walk, which is
therefore shared in `lib/npm-scripts.mjs` and fixes the same hole in all four
vouches, not just this PR's. `reachableScripts` keeps its substring behavior
for the harvesting callers, where over-inclusion is safe.
The chain walk also follows npm's implicit pre/post hooks, with a test — a guard
invoked from a `prevalidate` is genuinely run, and reporting it missing would be
a false failure introduced by the fix.
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
b91b746867
commit
a2ddece7a5
1 changed files with 160 additions and 46 deletions
|
|
@ -28,8 +28,8 @@ import {
|
|||
checkWiring,
|
||||
ciRunsUnconditionally,
|
||||
runsCommand,
|
||||
scriptChainRuns,
|
||||
} from "./verify-skills.mjs";
|
||||
import { scriptChainRuns } from "./lib/npm-scripts.mjs";
|
||||
|
||||
const SCRIPT = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
|
|
@ -483,3 +483,73 @@ test("checkWiring rejects a local:gate that only mentions the validator", () =>
|
|||
/local:gate` no longer runs `verify:skills:cli`/,
|
||||
);
|
||||
});
|
||||
|
||||
test("runsCommand rejects failure-masking and conditional shell forms", () => {
|
||||
// `npm run X || true` swallows a rejection so CI stays green; `true || npm
|
||||
// run X` never runs the validator. Both are `run:` steps containing the
|
||||
// command, so a separator-agnostic split counted them (Copilot).
|
||||
const C = "npm run verify:skills:cli";
|
||||
for (const script of [`${C} || true`, `true || ${C}`, `${C} | tee log`]) {
|
||||
assert.equal(runsCommand(script, C), false, script);
|
||||
}
|
||||
});
|
||||
|
||||
test("ciRunsUnconditionally rejects a step whose failure cannot fail the job", () => {
|
||||
const C = "npm run verify:skills:cli";
|
||||
const head = "on:\n push:\njobs:\n build:\n";
|
||||
// `continue-on-error` means a rejected skill leaves the workflow green, so
|
||||
// the step asserts nothing — on the step or on the whole job.
|
||||
assert.equal(
|
||||
ciRunsUnconditionally(
|
||||
`${head} steps:\n - continue-on-error: true\n run: ${C}\n`,
|
||||
C,
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
ciRunsUnconditionally(
|
||||
`${head} steps:\n - continue-on-error: "true"\n run: ${C}\n`,
|
||||
C,
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
ciRunsUnconditionally(
|
||||
`on:\n push:\njobs:\n build:\n continue-on-error: true\n steps:\n - run: ${C}\n`,
|
||||
C,
|
||||
),
|
||||
false,
|
||||
);
|
||||
// An explicit `false` is the normal case and must still count.
|
||||
assert.equal(
|
||||
ciRunsUnconditionally(
|
||||
`${head} steps:\n - continue-on-error: false\n run: ${C}\n`,
|
||||
C,
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("scriptChainRuns rejects failure-masking chains", () => {
|
||||
const T = "verify:skills:cli";
|
||||
assert.equal(
|
||||
scriptChainRuns({ "local:gate": `npm run ${T} || true` }, "local:gate", T),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
scriptChainRuns({ "local:gate": `true || npm run ${T}` }, "local:gate", T),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("scriptChainRuns follows npm's implicit pre/post hooks", () => {
|
||||
const T = "verify:skills:cli";
|
||||
assert.equal(
|
||||
scriptChainRuns(
|
||||
{ "local:gate": "npm run coverage", precoverage: `npm run ${T}` },
|
||||
"local:gate",
|
||||
T,
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue