import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; const root = process.cwd(); const expectedTriggers = `when: # PR + manual CI run on any branch — the pull_request pipeline is the merge gate. # push CI is restricted to protected branches (main) so a feature-branch push no # longer fires a redundant SECOND pipeline alongside its PR pipeline. This ~halves # CI load on the storage-constrained runner with zero loss of gating (branch # protection requires no push/ci status context; main still gets full push CI). - event: [pull_request, manual] - event: push branch: main`; const expectedGateStep = ` image: *node_image # Woodpecker's shallow marker makes merge-base reject even present parents; # full history is required for activation ancestry and manifest provenance. commands: - *enable_pnpm - apk add --no-cache bubblewrap - if [ -f .git/shallow ]; then git fetch --unshallow --no-tags origin; fi - pnpm gate:verify depends_on: - install - sanitization - upgrade-guard`; export function assertUnprivilegedGateStep(pipeline) { assert.doesNotMatch( pipeline, /privileged/i, 'no pull-request pipeline step may declare privilege', ); for (const line of pipeline.split('\n')) { const candidate = line.trimStart().replace(/^-\s+/, ''); if (/^(?:["'!<].*|[A-Za-z_][A-Za-z0-9_-]*\s+):(?:\s|$)/.test(candidate)) { assert.fail(`non-canonical or merged YAML key is forbidden: ${candidate}`); } } const triggerMatches = [...pipeline.matchAll(/^when:\n([\s\S]*?)(?=\n\n)/gm)]; assert.equal(triggerMatches.length, 1, 'exactly one top-level trigger is required'); assert.equal( `when:\n${triggerMatches[0][1].trimEnd()}`, expectedTriggers, 'top-level triggers must match closed PR/main construction', ); const matches = [ ...pipeline.matchAll(/\n gate-verify:\n([\s\S]*?)(?=\n [a-z][a-z0-9-]+:|\nservices:|$)/g), ]; assert.equal(matches.length, 1, 'exactly one gate-verify step is required'); // Closed textual construction by design: accepting arbitrary YAML syntax here // would require a duplicate-key-preserving parser. Exact equality rejects all // extra keys, quoted/escaped key spellings, aliases, and mapping merges. assert.equal(matches[0][1].trimEnd(), expectedGateStep, 'gate-verify step must match closed unprivileged construction'); } test('package.json exposes the canonical gate:verify command', async () => { const packageJson = JSON.parse(await readFile(`${root}/package.json`, 'utf8')); assert.equal(packageJson.scripts['gate:verify'], 'node scripts/gate-verify.mjs'); }); test('Woodpecker runs the closed unprivileged gate construction on every pipeline', async () => { const pipeline = await readFile(`${root}/.woodpecker/ci.yml`, 'utf8'); assertUnprivilegedGateStep(pipeline); }); test('gate wiring rejects privilege syntax, merges, duplicate keys, and trigger narrowing', async () => { const pipeline = await readFile(`${root}/.woodpecker/ci.yml`, 'utf8'); const additions = [ ' privileged: *enabled\n', ' "privileged": true\n', " 'privileged': true\n", ' privileged : true\n', ' "priv\\u0069leged": true\n', ' <<: *privileged-step\n', ' "<<": *privileged-step\n', ]; for (const addition of additions) { const changed = pipeline.replace(' gate-verify:\n image:', ` gate-verify:\n${addition} image:`); assert.throws(() => assertUnprivilegedGateStep(changed)); } const privilegedInstall = pipeline.replace( ' install:\n image:', ' install:\n privileged: true\n image:', ); const duplicate = `${pipeline}\n gate-verify:\n image: *node_image\n`; const noPullRequest = pipeline.replace( ' - event: [pull_request, manual]', ' - event: manual', ); const filteredPullRequest = pipeline.replace( ' - event: [pull_request, manual]', ' - event: [pull_request, manual]\n path: [scripts/**]', ); assert.throws(() => assertUnprivilegedGateStep(privilegedInstall), /privilege/i); assert.throws(() => assertUnprivilegedGateStep(duplicate), /exactly one gate-verify/); assert.throws(() => assertUnprivilegedGateStep(noPullRequest), /closed PR\/main construction/); assert.throws( () => assertUnprivilegedGateStep(filteredPullRequest), /closed PR\/main construction/, ); });