import { readFile } from "node:fs/promises"; import { constants } from "node:fs"; import { spawnSync } from "node:child_process"; import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { CI_ACTION_REGISTRY, indexCiGateContract, loadCiGateContract, parseCiActionRegistry, parseCiGateContract, resolveCiActionUses, withCiGatePreflight, } from "../../scripts/contracts/ci-gates.ts"; import { createCiWorkflowGenerator, generateCiWorkflow, renderCiWorkflow, type CiWorkflowFileSystem, } from "../../scripts/generate-ci-workflow.ts"; import { validateInstallScriptPolicy, validatePackageScriptGraph, } from "../../scripts/lib/package-script-graph.ts"; const temporaryRoots: string[] = []; const npmPostScriptForeignScopeCommands = [ "npm run nested --workspace fixture", "npm run nested --workspace=fixture", "npm run nested -w fixture", "npm run nested --prefix fixture", "npm run nested --prefix=fixture", "npm test --workspace fixture", "npm start --prefix fixture", ] as const; const npmLifecycleHookDispatches = [ ["npm run nested", "nested"], ["npm run-script nested", "nested"], ["npm start", "start"], ["npm stop", "stop"], ["npm test", "test"], ["npm restart", "restart"], ] as const; const npmDispatchExpansionCommands = [ "SCOPE=--workspace; npm run nested $SCOPE fixture", "SCOPE=--workspace; npm run nested ${SCOPE} fixture", "npm run nested $SCOPE fixture", "npm test ${SCOPE}", "npm run nested --if-present=${FLAG}", ] as const; const npmDispatchScopeEnvironmentCommands = [ "npm_config_workspace=fixture npm run nested", "NPM_CONFIG_WORKSPACES=true npm test", "env npm_config_prefix=fixture npm run nested", "/usr/bin/env npm_config_workspace=fixture npm run nested", "export NpM_CoNfIg_PrEfIx=fixture && npm run nested", "/bin/env npm_config_prefix=fixture npm run nested", "command env npm_config_workspace=fixture npm run nested", "command /opt/reviewer/bin/env NPM_CONFIG_WORKSPACES=true npm test", "npm_config_prefix=fixture; export npm_config_prefix; npm run nested", "npm_config_workspace=fixture && export npm_config_workspace && npm run nested", "export npm_config_workspaces; npm_config_workspaces=true; npm test", "set -a; npm_config_prefix=fixture; npm run nested", "DYNAMIC_SCOPE=workspace; env npm_config_${DYNAMIC_SCOPE}=fixture npm run nested", "DYNAMIC_SCOPE=prefix; npm_config_${DYNAMIC_SCOPE}=fixture npm run nested", "DYNAMIC_SCOPE=workspaces; export npm_config_${DYNAMIC_SCOPE}=true; npm test", "ENV_WRAPPER=env; $ENV_WRAPPER npm_config_prefix=fixture npm run nested", "set +a; npm run nested", "unset npm_config_prefix; npm run nested", "export -n npm_config_workspace; npm run nested", "eval 'export npm_config_prefix=fixture'; npm run nested", ". ./scope-env.sh; npm run nested", "source ./scope-env.sh; npm run nested", ] as const; const npmShellPrefixScopeEnvironmentCommands = [ "exec env npm_config_workspace=fixture npm run nested", "exec /bin/env npm_config_prefix=fixture npm run nested", "command exec env npm_config_workspaces=true npm test", "command -- env npm_config_prefix=fixture npm run nested", "command -p env npm_config_workspace=fixture npm run nested", "SAFE=1 export npm_config_prefix=fixture; npm run nested", "SAFE=1 set -a; npm_config_prefix=fixture; npm run nested", "command --unknown env npm_config_prefix=fixture npm run nested", "exec -a reviewer env npm_config_workspace=fixture npm run nested", ] as const; const npmIndirectConfigAuthorityCommands = [ "npm --userconfig fixture run nested", "npm --globalconfig=fixture test", "npm --userconfig fixture ci --ignore-scripts", "npm --globalconfig fixture audit", "NPM_CONFIG_USERCONFIG=fixture npm run nested", "npm_config_globalconfig=fixture npm test", "env NPM_CONFIG_USERCONFIG=fixture npm ci --ignore-scripts", "/usr/bin/env npm_config_globalconfig=fixture npm audit", ] as const; const npmPreDelimiterGlobCommands = [ "npm run nested *", "npm test ?", "npm run nested [a-z]*", ] as const; const unmodeledManagerPrefixCommands = [ "nice env npm_config_workspace=fixture npm run nested", "/usr/bin/nice env npm_config_prefix=fixture npm run nested", "nohup env npm_config_workspaces=true npm test", "SAFE=1 nice env npm_config_workspace=fixture npm run nested", "nice npm run nested", "time pnpm nested", ] as const; afterEach(async () => { await Promise.all( temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })), ); }); async function createUnsafeCiGateFixture( command: string, additionalScripts: Readonly> = {}, ): Promise { const root = await mkdtemp(path.join(tmpdir(), "ci-contract-unsafe-command-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const contract = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; const packageDocument = JSON.parse(await readFile("package.json", "utf8")) as { scripts: Record; }; contract.commands[0].script = "unsafe:preflight"; packageDocument.scripts["unsafe:preflight"] = command; Object.assign(packageDocument.scripts, additionalScripts); await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`); return root; } describe("CI gate contract", () => { it.each([ ["unknown-field.json", /unrecognized|unknown/i], ["duplicate-gate-id.json", /duplicate gate id/i], ["missing-artifact-schema.json", /unknown artifact schema/i], ])("rejects %s before projection", async (fixture, diagnostic) => { const candidate = JSON.parse( await readFile(`tests/fixtures/ci-contract/${fixture}`, "utf8"), ); expect(() => parseCiGateContract(candidate)).toThrow(diagnostic); }); it.each([ ["unknown-job-dependency.json", /unknown job dependency/i], ["job-cycle.json", /job dependency cycle/i], ["unowned-gate.json", /unowned gate: FE-GATE-001/i], ["multiply-owned-gate.json", /multiply owned gate: FE-GATE-001/i], ])("applies and rejects physical semantic fixture %s", async (fixture, diagnostic) => { const mutation = JSON.parse( await readFile(`tests/fixtures/ci-contract/${fixture}`, "utf8"), ) as Readonly<{ jobId: string; needs?: string[]; removeGateId?: string; addGateId?: string; }>; const candidate = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; const job = candidate.jobs.find( (entry: Record) => entry.id === mutation.jobId, ); if (!job) throw new Error(`fixture job does not exist: ${mutation.jobId}`); if (mutation.needs) job.needs = mutation.needs; if (mutation.removeGateId) { job.gateIds = job.gateIds.filter((id: string) => id !== mutation.removeGateId); } if (mutation.addGateId) job.gateIds.push(mutation.addGateId); expect(() => parseCiGateContract(candidate)).toThrow(diagnostic); }); it("loads the strict v2 registry and preserves the reviewed baseline", async () => { const contract = await loadCiGateContract(process.cwd()); const index = indexCiGateContract(contract); expect(contract.schemaVersion).toBe(2); expect(contract.gates.map(({ id }) => id)).toEqual( Array.from({ length: 27 }, (_, index) => `FE-GATE-${String(index + 1).padStart(3, "0")}`, ), ); expect(contract.jobs).toHaveLength(9); expect(contract.commands).toHaveLength(82); expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(94); expect(contract.commands.filter(({ expect }) => expect === "fail")).toHaveLength(23); expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(86); expect(contract.artifacts).toHaveLength(107); expect(contract.stages).toHaveLength(5); expect(contract.retention.classes).toHaveLength(5); expect(index.gates.get("FE-GATE-015")?.commandIds).toHaveLength(2); expect(index.gates.get("FE-GATE-020")?.name).toBe("removability"); }); it("keeps the action registry recursively immutable and resolves only known actions", () => { expect(Object.isFrozen(CI_ACTION_REGISTRY)).toBe(true); expect(Object.values(CI_ACTION_REGISTRY).every((action) => Object.isFrozen(action))).toBe(true); expect(resolveCiActionUses("checkout")).toBe( "https://github.com/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5", ); expect(() => resolveCiActionUses("actions/checkout@v4" as never)).toThrow(/unknown CI action/i); }); it.each([ ["unknown action", (registry: Record) => { registry.unknown = registry.checkout; }, /unrecognized|unknown/i], ["movable branch", (registry: Record) => { registry.checkout.revision = "v4"; }, /full 40-hex commit SHA/i], ["short SHA", (registry: Record) => { registry.checkout.revision = "34e114876b0b"; }, /full 40-hex commit SHA/i], ["relative repository", (registry: Record) => { registry.checkout.repository = "actions/checkout"; }, /absolute upstream GitHub URL/i], ])("rejects an unsafe CI action registry mutation: %s", (_name, mutate, diagnostic) => { const candidate = JSON.parse(JSON.stringify(CI_ACTION_REGISTRY)) as Record; mutate(candidate); expect(() => parseCiActionRegistry(candidate)).toThrow(diagnostic); }); it.each([ ["unknown nested field", (value: Record) => (value.commands[0].unknown = true), /unrecognized|unknown/i], ["duplicate command id", (value: Record) => value.commands.push({ ...value.commands[0] }), /duplicate command id/i], ["duplicate command tuple", (value: Record) => value.commands.push({ ...value.commands[0], id: "duplicate-tuple" }), /duplicate command tuple/i], ["duplicate command reference", (value: Record) => value.gates[0].commandIds.push(value.gates[0].commandIds[0]), /duplicate command reference within gate/i], ["duplicate artifact id", (value: Record) => value.artifacts.push({ ...value.artifacts[0] }), /duplicate artifact id/i], ["duplicate artifact schema id", (value: Record) => value.artifactSchemas.push({ ...value.artifactSchemas[0] }), /duplicate artifact schema id/i], ["duplicate stage id", (value: Record) => value.stages.push({ ...value.stages[0] }), /duplicate stage id/i], ["duplicate job id", (value: Record) => value.jobs.push({ ...value.jobs[0] }), /duplicate job id/i], ["duplicate artifact path", (value: Record) => value.artifacts.push({ ...value.artifacts[0], id: "duplicate-path" }), /duplicate artifact path/i], ["missing artifact production classification", (value: Record) => delete value.artifacts[0].production, /production/i], ["unknown command reference", (value: Record) => value.gates[0].commandIds.push("missing-command"), /unknown command missing-command/i], ["unknown artifact reference", (value: Record) => (value.gates[0].logArtifactId = "missing-artifact"), /unknown artifact missing-artifact/i], ["unknown schema reference", (value: Record) => (value.artifacts[0].schemaId = "missing-schema"), /unknown artifact schema missing-schema/i], ["unknown retention reference", (value: Record) => (value.gates[0].retentionClassId = "missing-retention"), /unknown retention class missing-retention/i], ["unsafe artifact path", (value: Record) => (value.artifacts[0].path = "../escape"), /unsafe repository path/i], ["artifact path controls", (value: Record) => (value.artifacts[0].path = "artifacts/bad\n.json"), /control and Unicode line-break characters/i], ["empty commands", (value: Record) => (value.commands = []), /too small|at least 1/i], ["empty artifacts", (value: Record) => (value.artifacts = []), /too small|at least 1/i], ["empty gates", (value: Record) => (value.gates = []), /too small|at least 1/i], ["empty gate command refs", (value: Record) => (value.gates[0].commandIds = []), /too small|at least 1/i], ["empty gate evidence refs", (value: Record) => (value.gates[0].evidenceArtifactIds = []), /too small|at least 1/i], ["unowned gate", (value: Record) => (value.jobs[0].gateIds = value.jobs[0].gateIds.filter((id: string) => id !== "FE-GATE-001")), /unowned gate: FE-GATE-001/i], ["multiply owned gate", (value: Record) => value.jobs[1].gateIds.push("FE-GATE-001"), /multiply owned gate: FE-GATE-001/i], ["duplicated immutable gate", (value: Record) => value.jobs[1].gateIds.push("FE-GATE-015"), /release matrix duplicates FE-GATE-015/i], ["unreachable producer", (value: Record) => (value.jobs.find((job: any) => job.id === "promotion").needs = ["immutable_build", "provenance_provider"]), /download producer vulnerability_provider is unreachable/i], ["missing producer", (value: Record) => { const producer = value.jobs.find((job: any) => job.id === "immutable_build"); producer.steps = producer.steps.filter((step: any) => step.kind !== "upload"); }, /unknown download transfer release-candidate|job step sequence drift/i], ["promotion rebuild authority", (value: Record) => value.jobs.find((job: any) => job.id === "promotion").steps.splice(3, 0, { kind: "archive-candidate", stepId: "bad", archivePath: ".release/bad.tar.gz", members: ["dist"], archiveOutputName: "bad", distOutputName: "bad-dist" }), /promotion job must not build|step kind archive-candidate is forbidden/i], ["browser gate drift", (value: Record) => (value.jobs[0].browserGateIds = ["FE-GATE-008"]), /browser gate set drift/i], ["missing gate execution step", (value: Record) => (value.jobs[0].steps = value.jobs[0].steps.filter((step: any) => step.kind !== "run-gate")), /canonical job step sequence drift/i], ["browser install drift", (value: Record) => (value.jobs[0].steps = value.jobs[0].steps.filter((step: any) => step.kind !== "browser-install")), /browser install step drift|job step sequence drift/i], ["job environment drift", (value: Record) => value.jobs[0].environment.push({ name: "UNOWNED", value: "x" }), /job environment binding drift/i], ["job kind drift", (value: Record) => (value.jobs[0].kind = "gate-single"), /job graph drift/i], ["job needs drift", (value: Record) => (value.jobs[1].needs = []), /job graph drift/i], ["job condition drift", (value: Record) => (value.jobs[1].condition = "always"), /job graph drift/i], ["promotion success condition drift", (value: Record) => (value.jobs.find((job: any) => job.id === "promotion").condition = "always"), /job graph drift|promotion.*condition/i], ["candidate output identity drift", (value: Record) => { const job = value.jobs.find((candidate: any) => candidate.id === "immutable_build"); job.steps.find((step: any) => step.kind === "archive-candidate").archiveOutputName = "renamed"; }, /candidate output identity drift/i], ["stage cycle", (value: Record) => (value.stages[0].needs = ["release"]), /stage dependency cycle/i], ["provider adapter target drift", (value: Record) => (value.providerAdapter = "package.json"), /canonical generated workflow/i], ["promotion standalone extraction", (value: Record) => value.jobs.find((candidate: any) => candidate.id === "promotion").steps.splice(6, 0, { kind: "extract", archivePath: ".release/candidate/candidate.tar.gz", targetRoot: ".release/verified-candidate" }), /step kind extract is forbidden|job step sequence drift/i], ["normalized upload root", (value: Record) => (value.jobs[0].steps.find((step: any) => step.kind === "upload").paths = ["foo/.."]), /unsafe workflow path/i], ["immutable archive field drift", (value: Record) => (value.jobs.find((candidate: any) => candidate.id === "immutable_build").steps.find((step: any) => step.kind === "archive-candidate").archivePath = ".release/other.tar.gz"), /candidate output identity drift|archive and upload fields must remain linked/i], ["provider role drift", (value: Record) => (value.jobs.find((candidate: any) => candidate.id === "vulnerability_provider").steps.find((step: any) => step.kind === "run-provider").provider = "provenance"), /provider archive, extraction, evidence, and upload fields must remain linked/i], ["provider archive SHA environment drift", (value: Record) => (value.jobs.find((candidate: any) => candidate.id === "vulnerability_provider").environment.find((entry: any) => entry.name === "CANDIDATE_ARCHIVE_SHA256").value = "0".repeat(64)), /job environment binding drift/i], ["promotion transfer swap", (value: Record) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.find((step: any) => step.kind === "download").transferId = "vulnerability-provider-evidence"), /promotion download fields.*remain linked|duplicate.*download/i], ["raw provider upload", (value: Record) => (value.jobs.find((candidate: any) => candidate.id === "vulnerability_provider").steps.find((step: any) => step.kind === "upload").paths = ["provider-evidence/untrusted/vulnerability-report.json"]), /provider archive, extraction, evidence, and upload fields must remain linked/i], ["intervening promotion step", (value: Record) => { const steps = value.jobs.find((candidate: any) => candidate.id === "promotion").steps; steps.splice(steps.findIndex((step: any) => step.kind === "upload"), 0, { kind: "frozen-install" }); }, /promotion verification and upload must be immediately adjacent/i], ["promotion upload path drift", (value: Record) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.find((step: any) => step.kind === "upload").paths[0] = ".release/promoted-staging/replaced.tar.gz"), /exact five typed paths/i], ["always promotion upload", (value: Record) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.find((step: any) => step.kind === "upload").always = true), /promotion upload must not use always/i], ["provider nonce output step drift", (value: Record) => (value.jobs.find((candidate: any) => candidate.id === "vulnerability_provider").steps.find((step: any) => step.kind === "run-provider").stepId = "renamed"), /provider.*linked|step identity/i], ["promotion nonce binding drift", (value: Record) => (value.jobs.find((candidate: any) => candidate.id === "promotion").environment.find((entry: any) => entry.name === "VULNERABILITY_INVOCATION_NONCE").value = "5".repeat(64)), /job environment binding drift/i], ["missing promotion cleanup", (value: Record) => { const job = value.jobs.find((candidate: any) => candidate.id === "promotion"); job.steps = job.steps.filter((step: any) => step.kind !== "cleanup-promotion"); }, /job step sequence drift|cleanup/i], ])("rejects semantic mutation: %s", async (_name, mutate, diagnostic) => { const contract = await loadCiGateContract(process.cwd()); const candidate = JSON.parse(JSON.stringify(contract)) as Record; mutate(candidate); expect(() => parseCiGateContract(candidate)).toThrow(diagnostic); }); it("rejects package scripts missing from the shared command registry", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-contract-package-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const contract = await loadCiGateContract(process.cwd()); await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), '{"scripts":{}}\n'); await expect(loadCiGateContract(root)).rejects.toThrow(/missing package scripts/i); }); it("rejects swapped canonical gate command ownership", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-contract-gate-shape-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const contract = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; const security = contract.gates.find((gate: Record) => gate.id === "FE-GATE-013"); const documentation = contract.gates.find((gate: Record) => gate.id === "FE-GATE-017"); [security.commandIds, documentation.commandIds] = [ documentation.commandIds, security.commandIds, ]; await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), await readFile("package.json")); await expect(loadCiGateContract(root)).rejects.toThrow( /lacks a bound producer command/i, ); }); it("rejects canonical gate name drift through the semantic-shape digest", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-contract-gate-name-shape-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const contract = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; const gate = contract.gates.find( (candidate: Record) => candidate.id === "FE-GATE-013", ); gate.name = `${gate.name}-renamed`; await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), await readFile("package.json")); await expect(loadCiGateContract(root)).rejects.toThrow( /canonical gate semantic shape/i, ); }); it.each(["check:artifact-schemas", "check:ci-workflow"])( "rejects a missing nested check:ci dependency: %s", async (removedScript) => { const root = await mkdtemp(path.join(tmpdir(), "ci-contract-script-graph-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const contract = await loadCiGateContract(process.cwd()); const packageDocument = JSON.parse(await readFile("package.json", "utf8")) as { scripts: Record; }; delete packageDocument.scripts[removedScript]; await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`); await expect(loadCiGateContract(root)).rejects.toThrow( /missing package scripts|package script graph invalid|canonical check:ci dependency/i, ); }, ); it.each(["check:artifact-schemas", "check:ci-workflow"])( "rejects a no-op nested check:ci dependency: %s", async (bypassedScript) => { const root = await mkdtemp(path.join(tmpdir(), "ci-contract-script-meaning-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const contract = await loadCiGateContract(process.cwd()); const packageDocument = JSON.parse(await readFile("package.json", "utf8")) as { scripts: Record; }; packageDocument.scripts[bypassedScript] = "true"; await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`); await expect(loadCiGateContract(root)).rejects.toThrow(/canonical check:ci dependency/i); }, ); it.each([ ["true bypass", "true"], ["direct self recursion", "corepack pnpm check:ci"], ["alias cycle", "corepack pnpm check:ci-alias"], ["option-form gate alias", "corepack pnpm --silent ci:gate"], ])("rejects non-canonical check:ci orchestration: %s", async (_name, command) => { const root = await mkdtemp(path.join(tmpdir(), "ci-contract-closed-script-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const contract = await loadCiGateContract(process.cwd()); const packageDocument = JSON.parse(await readFile("package.json", "utf8")) as { scripts: Record; }; packageDocument.scripts["check:ci"] = command; packageDocument.scripts["check:ci-alias"] = "corepack pnpm check:ci"; await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`); await expect(loadCiGateContract(root)).rejects.toThrow(/exact canonical non-recursive orchestration/i); }); it.each([ "npm ci --ignore-scripts --no-ignore-scripts", "pnpm install --ignore-scripts --config.ignore-scripts=false", "pnpm ln fixture", ])("rejects an unsafe lifecycle in every contract command entry: %s", async (command) => { const root = await createUnsafeCiGateFixture(command); await expect(loadCiGateContract(root)).rejects.toThrow( /install policy|install-bearing|ignore-scripts/i, ); }); it("does not enter the production runner callback when contract preflight fails", async () => { const root = await createUnsafeCiGateFixture( "npm ci --ignore-scripts --no-ignore-scripts", ); const contracts = await import("../../scripts/contracts/ci-gates.ts"); const preflight = (contracts as typeof contracts & { withCiGatePreflight?: ( root: string, gateId: string | undefined, execute: (context: unknown) => Promise, ) => Promise; }).withCiGatePreflight; expect(preflight).toBeTypeOf("function"); if (!preflight) return; let executed = false; await expect( preflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|ignore-scripts/i); expect(executed).toBe(false); }); it.each(npmPostScriptForeignScopeCommands)( "rejects post-script foreign npm scope before entering the runner callback: %s", async (command) => { const root = await createUnsafeCiGateFixture(command, { nested: "echo root-only safe", start: "echo root-only safe", test: "echo root-only safe", }); await expect(loadCiGateContract(root)).rejects.toThrow( /install policy|install-bearing|not safely parseable/i, ); let executed = false; await expect( withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|not safely parseable/i); expect(executed).toBe(false); }, ); it.each([ ...npmDispatchExpansionCommands, ...npmDispatchScopeEnvironmentCommands, ])( "rejects dynamic npm dispatch scope before entering the runner callback: %s", async (command) => { const root = await createUnsafeCiGateFixture(command, { nested: "echo root-only safe", test: "echo root-only safe", }); await expect(loadCiGateContract(root)).rejects.toThrow( /install policy|install-bearing|not safely parseable/i, ); let executed = false; await expect( withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|not safely parseable/i); expect(executed).toBe(false); }, ); it.each(npmShellPrefixScopeEnvironmentCommands)( "rejects unsafe npm shell prefix before entering the runner callback: %s", async (command) => { const root = await createUnsafeCiGateFixture(command, { nested: "echo root-only safe", test: "echo root-only safe", }); await expect(loadCiGateContract(root)).rejects.toThrow( /install policy|install-bearing|not safely parseable/i, ); let executed = false; await expect( withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|not safely parseable/i); expect(executed).toBe(false); }, ); it.each(unmodeledManagerPrefixCommands)( "rejects an unmodeled manager prefix before entering the runner callback: %s", async (command) => { const root = await createUnsafeCiGateFixture(command, { nested: "echo root-only safe", test: "echo root-only safe", }); await expect(loadCiGateContract(root)).rejects.toThrow( /install policy|install-bearing|not safely parseable/i, ); let executed = false; await expect( withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|not safely parseable/i); expect(executed).toBe(false); }, ); it.each(npmIndirectConfigAuthorityCommands)( "rejects indirect npm config authority before entering the runner callback: %s", async (command) => { const root = await createUnsafeCiGateFixture(command, { nested: "echo root-only safe", test: "echo root-only safe", }); await expect(loadCiGateContract(root)).rejects.toThrow( /install policy|install-bearing|not safely parseable/i, ); let executed = false; await expect( withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|not safely parseable/i); expect(executed).toBe(false); }, ); it.each(npmPreDelimiterGlobCommands)( "rejects pre-delimiter npm glob expansion before entering the runner callback: %s", async (command) => { const root = await createUnsafeCiGateFixture(command, { nested: "echo root-only safe", test: "echo root-only safe", }); await expect(loadCiGateContract(root)).rejects.toThrow( /install policy|install-bearing|not safely parseable/i, ); let executed = false; await expect( withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|not safely parseable/i); expect(executed).toBe(false); }, ); it("rejects scoped npm lifecycle before entering the runner callback", async () => { const root = await createUnsafeCiGateFixture( "npm_config_prefix=fixture npm ci --ignore-scripts", ); let executed = false; await expect( withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|not safely parseable/i); expect(executed).toBe(false); }); it.each(npmLifecycleHookDispatches)( "rejects unsafe npm hooks before entering the runner callback: %s", async (command, scriptName) => { const root = await createUnsafeCiGateFixture(command, { [`pre${scriptName}`]: "npm ci", [scriptName]: "echo root main safe", [`post${scriptName}`]: "npm ci", }); await expect(loadCiGateContract(root)).rejects.toThrow( /install policy|install-bearing|ignore-scripts/i, ); let executed = false; await expect( withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|ignore-scripts/i); expect(executed).toBe(false); }, ); it("rejects inherited npm scope configuration before entering the runner callback", async () => { const variable = "NpM_CoNfIg_WoRkSpAcE"; const previous = process.env[variable]; process.env[variable] = "fixture"; let executed = false; try { await expect( withCiGatePreflight(process.cwd(), "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/npm.*scope.*environment|npm_config_workspace/i); expect(executed).toBe(false); } finally { if (previous === undefined) delete process.env[variable]; else process.env[variable] = previous; } }); it.each(["NPM_CONFIG_USERCONFIG", "npm_config_globalconfig"])( "rejects inherited indirect npm config authority before callback: %s", async (variable) => { const previous = process.env[variable]; process.env[variable] = "fixture"; let executed = false; try { await expect( withCiGatePreflight(process.cwd(), "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/npm.*scope.*environment|npm_config/i); expect(executed).toBe(false); } finally { if (previous === undefined) delete process.env[variable]; else process.env[variable] = previous; } }, ); it.each([ ["npm run nested --ignore-scripts=false", "nested"], ["npm test -- --ignore-scripts=true", "test"], ])( "keeps unsafe npm hooks inside contract preflight without effective suppression: %s", async (command, scriptName) => { const root = await createUnsafeCiGateFixture(command, { [`pre${scriptName}`]: "npm ci", [scriptName]: "echo root main safe", [`post${scriptName}`]: "npm ci", }); let executed = false; await expect( withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }), ).rejects.toThrow(/install policy|install-bearing|ignore-scripts/i); expect(executed).toBe(false); }, ); it.each([ ["npm --ignore-scripts=true run nested", "nested"], ["npm test --ignore-scripts=true", "test"], ["npm restart --ignore-scripts true", "restart"], ["npm --ignore-scripts run nested", "nested"], ["npm run nested --ignore-scripts", "nested"], ["npm run-script nested --ignore-scripts", "nested"], ["npm start --ignore-scripts", "start"], ["npm stop --ignore-scripts", "stop"], ["npm restart --ignore-scripts", "restart"], ])( "allows contract execution when effective true suppresses npm hooks: %s", async (command, scriptName) => { const root = await createUnsafeCiGateFixture(command, { [`pre${scriptName}`]: "npm ci", [scriptName]: "echo root main safe", [`post${scriptName}`]: "npm ci", }); let executed = false; await withCiGatePreflight(root, "FE-GATE-001", async () => { executed = true; }); expect(executed).toBe(true); }, ); it("detects reachable alias cycles and option-form ci:gate invocations", () => { expect(validatePackageScriptGraph({ "check:ci": "pnpm alias", alias: "pnpm check:ci" }, "check:ci")) .toEqual(expect.arrayContaining([expect.stringMatching(/cycle/i)])); expect(validatePackageScriptGraph({ "check:ci": "pnpm alias", alias: "pnpm --silent ci:gate", "ci:gate": "node scripts/run-ci-gate.ts" }, "check:ci")) .toEqual(expect.arrayContaining([expect.stringMatching(/must not invoke ci:gate/i)])); }); it.each(npmLifecycleHookDispatches)( "traverses existing npm pre/main/post scripts in lifecycle order: %s", (gate, scriptName) => { const preScript = `pre${scriptName}`; const postScript = `post${scriptName}`; const scripts = { gate, [preScript]: "npm run missing:pre", [scriptName]: "npm run missing:main", [postScript]: "npm run missing:post", }; expect(validatePackageScriptGraph(scripts, "gate")).toEqual([ `package script missing: ${preScript} -> missing:pre`, `package script missing: ${scriptName} -> missing:main`, `package script missing: ${postScript} -> missing:post`, ]); }, ); it.each(npmLifecycleHookDispatches)( "applies install policy to existing npm pre/main/post scripts: %s", (gate, scriptName) => { const preScript = `pre${scriptName}`; const postScript = `post${scriptName}`; const scripts = { gate, [preScript]: "npm ci", [scriptName]: "npm ci", [postScript]: "npm ci", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toEqual([ `install-bearing package script must use --ignore-scripts: ${preScript}`, `install-bearing package script must use --ignore-scripts: ${scriptName}`, `install-bearing package script must use --ignore-scripts: ${postScript}`, ]); }, ); it.each([ ["npm --ignore-scripts=true run nested", "nested"], ["npm --ignore-scripts true run nested", "nested"], ["npm run nested --ignore-scripts=true", "nested"], ["npm test --ignore-scripts=true", "test"], ["npm restart --ignore-scripts true", "restart"], ["npm --ignore-scripts run nested", "nested"], ["npm run nested --ignore-scripts", "nested"], ["npm run-script nested --ignore-scripts", "nested"], ["npm start --ignore-scripts", "start"], ["npm stop --ignore-scripts", "stop"], ["npm restart --ignore-scripts", "restart"], ])("omits npm hooks for effective suppression: %s", (gate, scriptName) => { const scripts = { gate, [`pre${scriptName}`]: "npm ci", [scriptName]: "echo root main safe", [`post${scriptName}`]: "npm run missing:post", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toEqual([]); expect(validatePackageScriptGraph(scripts, "gate")).toEqual([]); }); it.each([ ["npm --ignore-scripts=false run nested", "nested"], ["npm test --ignore-scripts=false", "test"], ["npm --ignore-scripts=true run nested --no-ignore-scripts", "nested"], ["npm restart --ignore-scripts=true --ignore-scripts=false", "restart"], ["npm run nested -- --ignore-scripts=true", "nested"], ["npm test -- --ignore-scripts=true", "test"], ])("keeps npm hooks for non-effective or post-delimiter suppression: %s", (gate, scriptName) => { const preScript = `pre${scriptName}`; const postScript = `post${scriptName}`; const scripts = { gate, [preScript]: "npm ci", [scriptName]: "echo root main safe", [postScript]: "npm run missing:post", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( `install-bearing package script must use --ignore-scripts: ${preScript}`, ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( `package script missing: ${postScript} -> missing:post`, ); }); it("rejects reachable nested package installs without lifecycle suppression", () => { const unsafe = { gate: "corepack pnpm nested", nested: "corepack pnpm install --frozen-lockfile", }; const safe = { ...unsafe, nested: "corepack pnpm install --frozen-lockfile --ignore-scripts", }; expect(validateInstallScriptPolicy(unsafe, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: nested", ); expect(validateInstallScriptPolicy(safe, ["gate"])).toEqual([]); }); it.each([ "corepack pnpm install --frozen-lockfile && echo --ignore-scripts", "corepack pnpm install && corepack pnpm install --ignore-scripts", ])("rejects an unsafe install invocation masked by another token: %s", (nested) => { const scripts = { gate: "corepack pnpm nested", nested, }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: nested", ); }); it.each([ "corepack pnpm --dir=fixture install", "corepack pnpm --dir fixture install", "pnpm -C fixture install", "pnpm --dir . install", "pnpm i", "npm --prefix fixture install", "npm ci", "npm i", "yarn --cwd fixture install", ])("rejects install aliases and manager-global-option forms: %s", (nested) => { expect(validateInstallScriptPolicy({ gate: "pnpm nested", nested }, ["gate"])) .toContain("install-bearing package script must use --ignore-scripts: nested"); }); it.each([ "corepack pnpm --dir", "pnpm --unknown-manager-option install --ignore-scripts", ])("fails closed for an unparsed reachable manager invocation: %s", (nested) => { expect(validateInstallScriptPolicy({ gate: "pnpm nested", nested }, ["gate"])) .toContain("install-bearing package script must use --ignore-scripts: nested"); }); it("treats a single ampersand as a command boundary for install policy", () => { const scripts = { gate: "pnpm install & pnpm install --ignore-scripts", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); }); it.each([ "pnpm install --ignore-scripts=false", "pnpm install --ignore-scripts --ignore-scripts=false", "npm ci --ignore-scripts=false", "npm install --ignore-scripts=true --ignore-scripts=false", "yarn install --ignore-scripts=false", "yarn install --ignore-scripts --ignore-scripts=false", "pnpm install --ignore-scripts=", "npm ci --ignore-scripts=maybe", "yarn install --ignore-scripts false", "pnpm install --ignore-scripts=false --ignore-scripts=true", ])("rejects ineffective or contradictory lifecycle suppression: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); }); it.each([ "pnpm install --ignore-scripts=true", "npm ci --ignore-scripts", "yarn install --ignore-scripts=true --ignore-scripts=true", ])("accepts an unambiguous effective ignore-scripts true: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toEqual([]); }); it.each([ "npm --silent run nested", "yarn nested", "yarn run nested", "corepack yarn nested", ])("follows manager-option and yarn script invocations into unsafe installs: %s", (gate) => { const scripts = { gate, nested: "npm ci" }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: nested", ); }); it.each([ "npm --silent run missing", "corepack yarn run missing", ])("uses the manager parser for package graph dependencies: %s", (gate) => { expect(validatePackageScriptGraph({ gate }, "gate")).toContain( "package script missing: gate -> missing", ); }); it.each([ "yarn workspace fixture run nested", "yarn workspace fixture nested", "yarn workspaces foreach -A run nested", ])("fails closed for an unmodeled Yarn workspace dispatcher: %s", (gate) => { const scripts = { gate, nested: "npm ci" }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it.each([ "pnpm install # --ignore-scripts", "npm ci # --ignore-scripts=true", ])("fails closed when an unquoted shell comment masks lifecycle suppression: %s", (gate) => { const scripts = { gate }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it("keeps a quoted hash as ordinary lifecycle argument content", () => { const scripts = { gate: "pnpm install '#' --ignore-scripts" }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toEqual([]); expect(validatePackageScriptGraph(scripts, "gate")).toEqual([]); }); it.each([ "npm ci --ignore-scripts --no-ignore-scripts", "pnpm install --ignore-scripts --config.ignore-scripts=false", "pnpm install --ignore-scripts --unknown-lifecycle-setting", "pnpm install --config.ignore-scripts=maybe --ignore-scripts", ])("rejects contradictory, malformed, or unknown lifecycle settings: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); }); it.each([ "npm ci --ignore-scripts true", "pnpm install --config.ignore-scripts", "pnpm install --config.ignore-scripts=true", "pnpm install --config.ignore-scripts true --frozen-lockfile", ])("accepts a supported canonical lifecycle suppression form: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toEqual([]); }); it("canonicalizes a lifecycle builtin alias before package-script lookup", () => { const scripts = { gate: "pnpm ln fixture", ln: "npm ci", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toEqual([ "install-bearing package script must use --ignore-scripts: gate", ]); expect(validatePackageScriptGraph(scripts, "gate")).toEqual([]); }); it.each([ "pnpm frobnicate", "yarn frobnicate", ])("fails closed for an unknown manager subcommand absent from root scripts: %s", (gate) => { const scripts = { gate }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it.each([ "pnpm --filter fixture run nested", "pnpm --dir fixture run nested", "pnpm -C fixture nested", "npm --workspace fixture run nested", "npm --prefix fixture run nested", "yarn --cwd fixture run nested", ])("fails closed when package-script dispatch changes authoritative manifest scope: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe" }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it.each(npmPostScriptForeignScopeCommands)( "fails closed when post-script npm options change package-script scope: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", start: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }, ); it.each([ ...npmDispatchExpansionCommands, ...npmDispatchScopeEnvironmentCommands, ])("fails closed for dynamic npm dispatch scope: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it.each(npmShellPrefixScopeEnvironmentCommands)( "fails closed for unsafe npm shell prefix: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }, ); it.each(unmodeledManagerPrefixCommands)( "fails closed for an unmodeled manager prefix: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }, ); it.each(npmIndirectConfigAuthorityCommands)( "fails closed for indirect npm config authority: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }, ); it.each(npmPreDelimiterGlobCommands)( "fails closed for unquoted glob expansion before npm's literal delimiter: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }, ); it.each([ "npm_config_prefix=fixture npm ci --ignore-scripts", "env npm_config_workspace=fixture npm audit", ])("rejects npm scope environment for every npm command class: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph({ gate }, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it.each([ "npm run nested --workspaces", "npm run nested -wfixture", "npm run nested -w=fixture", "npm run nested --unknown-manager-option", ])("fails closed for additional or unknown pre-delimiter npm options: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe" }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it.each([ "pnpm run nested --filter foreign", "pnpm nested --dir foreign", "yarn run nested --cwd foreign", "yarn nested --cwd foreign", ])("treats other-manager tokens after a script name as script arguments: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toEqual([]); expect(validatePackageScriptGraph(scripts, "gate")).toEqual([]); }); it.each([ "npm run nested -- --workspace foreign", "npm run-script nested -- --prefix foreign", "npm test -- --workspace foreign", "npm start -- --unknown-manager-option", "SCOPE=--workspace; npm run nested -- $SCOPE fixture", "SCOPE=--prefix; npm test -- ${SCOPE} fixture", ])("treats npm tokens after the script-argument delimiter as script arguments: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", start: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toEqual([]); expect(validatePackageScriptGraph(scripts, "gate")).toEqual([]); }); it.each([ "SAFE=value npm run nested", "npm_config_loglevel=warn npm run nested", "/bin/env npm_config_loglevel=warn npm run nested", "command env SAFE=value npm run nested", "command -- env SAFE=value npm run nested", "command -p env SAFE=value npm run nested", "command /opt/reviewer/bin/env SAFE=value npm run nested", "exec env SAFE=value npm run nested", "exec /bin/env SAFE=value npm run nested", "command exec env SAFE=value npm run nested", "SAFE=1 command -- npm run nested", "command exec env SAFE=value corepack npm run nested", "SAFE=1 corepack pnpm nested", "exec corepack pnpm nested", "MESSAGE='npm_config_workspace=fixture' npm run nested", "MESSAGE=\"npm_config_${SAFE_NAME}=fixture\" npm run nested", "echo 'npm_config_workspace=fixture' && npm run nested", "echo 'set -a; npm_config_prefix=fixture' && npm run nested", "export npm_config_loglevel=warn && npm run nested", "SAFE=value; export SAFE; npm run nested", "SAFE=1 export npm_config_loglevel=warn; npm run nested", "SAFE=1 set -a; SAFE_TWO=2; npm run nested", "command npm run nested", "exec npm run nested", "env -i SAFE=value npm run nested", "/usr/bin/env --ignore-environment npm test", "env -u npm_config_workspace npm run nested", "env --unset=NPM_CONFIG_PREFIX npm test", "env -- npm run nested", "env '$NAME=fixture' npm run nested", "SCOPE=--workspace; pnpm run nested $SCOPE fixture", "SCOPE=--workspace; yarn run nested ${SCOPE} fixture", ])("preserves unrelated environment and other-manager script arguments: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toEqual([]); expect(validatePackageScriptGraph(scripts, "gate")).toEqual([]); }); it.each([ "npm run nested '*'", 'npm test "?"', "npm run nested \\*", "npm run nested -- *", "npm test -- [a-z]*", ])("preserves quoted, escaped, or post-delimiter glob arguments: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", test: "echo root-only safe" }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toEqual([]); expect(validatePackageScriptGraph(scripts, "gate")).toEqual([]); }); it.each([ "npm run nested", "npm run nested --if-present", "npm run nested --ignore-scripts=true", "npm test --foreground-scripts", "npm start", ])("keeps current-tree npm dispatch and exact harmless options supported: %s", (gate) => { const scripts = { gate, nested: "echo root-only safe", start: "echo root-only safe", test: "echo root-only safe", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toEqual([]); expect(validatePackageScriptGraph(scripts, "gate")).toEqual([]); }); it.each([ "pnpm --dir fixture install --ignore-scripts", "npm --prefix fixture ci --ignore-scripts", "yarn --cwd fixture install --ignore-scripts", ])("still classifies externally scoped lifecycle commands by suppression: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toEqual([]); expect(validatePackageScriptGraph({ gate }, "gate")).toEqual([]); }); it.each([ "npm init unreviewed-tool", "npm explore fixture -- npm ci", "npm audit fix", "yarn npm publish", ])("fails closed for an argument-sensitive or mutating builtin dispatcher: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph({ gate }, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it.each(["npm audit", "pnpm audit"])( "accepts only an exact bare read-only audit builtin: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toEqual([]); expect(validatePackageScriptGraph({ gate }, "gate")).toEqual([]); }, ); it.each([ "npm --silent audit", "npm --prefix fixture audit", "npm -- audit", "npm audit --silent", "npm audit --", "pnpm --silent audit", "pnpm --dir fixture audit", "pnpm -- audit", "pnpm audit --silent", "pnpm audit --", ])("rejects a read-only builtin with manager syntax or arguments: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph({ gate }, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it.each([ "pnpm exec npm ci", "corepack pnpm exec npm install", "pnpm dlx npm ci", "npm exec pnpm install", "pnpm exec unreviewed-tool", ])("fails closed for nested manager and unknown executable dispatchers: %s", (gate) => { expect(validateInstallScriptPolicy({ gate }, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph({ gate }, "gate")).toContain( "package script manager invocation is not safely parseable: gate", ); }); it.each([ "pnpm add fixture", "npm update fixture", "yarn rebuild fixture", "corepack pnpm rebuild fixture", ])("treats lifecycle mutation builtins as unsafe even when a script has the same name: %s", (gate) => { const scripts = { gate, add: "echo script", update: "echo script", rebuild: "echo script", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: gate", ); expect(validatePackageScriptGraph(scripts, "gate")).not.toEqual( expect.arrayContaining([expect.stringMatching(/package script missing|cycle/u)]), ); }); it("allows an explicit run of a script whose name collides with a manager builtin", () => { const scripts = { gate: "pnpm run add", add: "npm ci", }; expect(validateInstallScriptPolicy(scripts, ["gate"])).toContain( "install-bearing package script must use --ignore-scripts: add", ); }); it("rejects an invalid package graph before the gate runner can spawn it", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-contract-runner-preflight-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const marker = path.join(root, "spawned"); const contract = await loadCiGateContract(process.cwd()); const packageDocument = JSON.parse(await readFile("package.json", "utf8")) as { scripts: Record }; packageDocument.scripts["check:ci"] = `node -e 'require("node:fs").writeFileSync(${JSON.stringify(marker)}, "spawned")'`; await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`); const result = spawnSync(process.execPath, [path.resolve("scripts/run-ci-gate.ts"), "FE-GATE-010"], { cwd: root, encoding: "utf8" }); expect(result.status).not.toBe(0); await expect(readFile(marker)).rejects.toMatchObject({ code: "ENOENT" }); }); it("records checked-in workflow drift as a failing typed report", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-contract-workflow-drift-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); await mkdir(path.join(root, ".gitea/workflows"), { recursive: true }); await mkdir(path.join(root, "artifacts/quality"), { recursive: true }); const contract = await loadCiGateContract(process.cwd()); await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), await readFile("package.json")); await writeFile(path.join(root, ".nvmrc"), await readFile(".nvmrc")); const drift = Buffer.from("drifted workflow\n", "utf8"); await writeFile(path.join(root, ".gitea/workflows/quality-gates.yml"), drift); const result = spawnSync(process.execPath, [path.resolve("scripts/check-ci-contract.ts")], { cwd: root, encoding: "utf8", }); expect(result.status).toBe(1); const report = JSON.parse( await readFile(path.join(root, "artifacts/quality/ci-contract.json"), "utf8"), ) as { passed: boolean; failures: string[]; workflowSha256: string }; expect(report.passed).toBe(false); expect(report.failures).toEqual(expect.arrayContaining([expect.stringMatching(/workflow drift/i)])); expect(report.workflowSha256).toBe( await import("node:crypto").then(({ createHash }) => createHash("sha256").update(drift).digest("hex")), ); }); it("caps aggregate gate output at the log schema before later commands can accumulate", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-gate-output-budget-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); const contract = JSON.parse(JSON.stringify(await loadCiGateContract(process.cwd()))) as Record; const gate = contract.gates.find((entry: any) => entry.id === "FE-GATE-001"); const command = contract.commands.find((entry: any) => entry.id === gate.commandIds[0]); command.script = "test:huge-output"; const logArtifact = contract.artifacts.find((entry: any) => entry.id === gate.logArtifactId); const logSchema = contract.artifactSchemas.find((entry: any) => entry.id === logArtifact.schemaId); logSchema.maxBytes = 8_192; const packageDocument = JSON.parse(await readFile("package.json", "utf8")) as { scripts: Record }; packageDocument.scripts["test:huge-output"] = "node -e \"process.stdout.write('x'.repeat(20000))\""; await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`); const environment = { ...process.env, CI: "false" }; const result = spawnSync(process.execPath, [path.resolve("scripts/run-ci-gate.ts"), "FE-GATE-001"], { cwd: root, encoding: "utf8", env: environment, timeout: 15_000, }); expect(result.status).toBe(1); const log = await readFile(path.join(root, logArtifact.path)); expect(log.byteLength).toBeLessThanOrEqual(8_192); expect(log.toString("utf8")).toMatch(/aggregate output|INFRASTRUCTURE_FAILURE/i); }, 20_000); it("rejects stale command-generated evidence from a successful no-op producer", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-gate-stale-evidence-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); await mkdir(path.join(root, "artifacts/tests"), { recursive: true }); const contract = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; const command = contract.commands.find( (entry: Record) => entry.id === "test-runtime-schema", ); command.script = "test:stale-evidence-noop"; const evidence = contract.artifacts.find( (entry: Record) => entry.path === "artifacts/tests/runtime-schema.xml", ); const packageDocument = JSON.parse(await readFile("package.json", "utf8")) as { scripts: Record; }; packageDocument.scripts[command.script] = "true"; await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`); await writeFile( path.join(root, evidence.path), '\n', ); const result = spawnSync( process.execPath, [path.resolve("scripts/run-ci-gate.ts"), "FE-GATE-004"], { cwd: root, encoding: "utf8", env: { ...process.env, CI: "false" } }, ); expect(result.status).toBe(1); expect( await readFile(path.join(root, "artifacts/quality/gates/FE-GATE-004.txt"), "utf8"), ).toMatch(/not freshly produced/i); }); it("accepts a fresh deterministic rewrite with identical evidence bytes", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-gate-identical-rewrite-")); temporaryRoots.push(root); await mkdir(path.join(root, "config/ci"), { recursive: true }); await mkdir(path.join(root, "artifacts/tests"), { recursive: true }); const contract = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; const command = contract.commands.find( (entry: Record) => entry.id === "test-runtime-schema", ); command.script = "test:identical-evidence-rewrite"; const evidence = contract.artifacts.find( (entry: Record) => entry.path === "artifacts/tests/runtime-schema.xml", ); const evidenceBytes = '\n'; const packageDocument = JSON.parse(await readFile("package.json", "utf8")) as { scripts: Record; }; packageDocument.scripts[command.script] = `node -e 'require("node:fs").writeFileSync("${evidence.path}", Buffer.from("${Buffer.from(evidenceBytes).toString("base64")}", "base64"))'`; await writeFile(path.join(root, "config/ci/gates.json"), `${JSON.stringify(contract)}\n`); await writeFile(path.join(root, "package.json"), `${JSON.stringify(packageDocument)}\n`); await writeFile(path.join(root, evidence.path), evidenceBytes); const result = spawnSync( process.execPath, [path.resolve("scripts/run-ci-gate.ts"), "FE-GATE-004"], { cwd: root, encoding: "utf8", env: { ...process.env, CI: "false" } }, ); expect(result.status).toBe(0); expect(result.stdout).toMatch(/FE-GATE-004 runtime-schema: PASS/); }); }); describe("CI workflow generation", () => { it("renders the complete workflow deterministically with one final LF", async () => { const contract = await loadCiGateContract(process.cwd()); const first = renderCiWorkflow(contract); const second = renderCiWorkflow(contract); expect(second).toBe(first); expect(first).toMatchSnapshot(); expect(first).toMatch(/^# GENERATED FILE/u); expect(first.endsWith("\n")).toBe(true); expect(first.endsWith("\n\n")).toBe(false); expect(first).not.toContain("\r"); expect(first).not.toMatch(/\\\$\{\{/u); expect(first).toContain( 'node scripts/verify-ci-candidate-archive.ts --archive ".release/release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tar.gz" --github-output "$GITHUB_OUTPUT"', ); expect(first).not.toContain("process_dist_sha256"); expect(first).toContain("persist-credentials: false"); expect( first.match(/corepack pnpm install --frozen-lockfile --ignore-scripts/gu), ).toHaveLength(9); expect(first).not.toMatch(/corepack pnpm install --frozen-lockfile$/mu); expect(first).toContain("verify-ci-candidate-archive.ts --archive"); expect(first).not.toContain("--extract-to"); expect(first).not.toMatch(/\btar\s+[^\n]*--extract/u); expect(first).toContain("node scripts/stage-verified-promotion.ts"); expect(first).toContain("outputs:\n invocation_nonce: ${{ steps.supervise_vulnerability.outputs.invocation_nonce }}"); expect(first).toContain("outputs:\n invocation_nonce: ${{ steps.supervise_provenance.outputs.invocation_nonce }}"); expect(first).toContain('VULNERABILITY_INVOCATION_NONCE: "${{ needs.vulnerability_provider.outputs.invocation_nonce }}"'); expect(first).toContain('PROVENANCE_INVOCATION_NONCE: "${{ needs.provenance_provider.outputs.invocation_nonce }}"'); expect(first).toContain("${{ steps.finalize.outputs.staging_root }}/release-candidate.tar.gz"); expect(first).not.toContain(".release/promoted-staging"); const promotionJobStart = first.indexOf(" promotion:\n"); const productionJobStart = first.indexOf(" production_gate:\n"); expect(promotionJobStart).toBeGreaterThan(0); expect(productionJobStart).toBeGreaterThan(promotionJobStart); const promotionJob = first.slice(promotionJobStart, productionJobStart); expect(promotionJob.slice(0, promotionJob.indexOf(" steps:\n"))).not.toMatch(/^\s+if:/mu); expect(promotionJob).not.toContain("cancelled()"); const finalizerIndex = first.indexOf("node scripts/stage-verified-promotion.ts"); const promotedUploadIndex = first.indexOf("Upload promoted release"); const cleanupStepIndex = first.indexOf("- name: Always remove private promotion staging"); const cleanupIndex = first.indexOf("node scripts/cleanup-verified-promotion.ts"); expect(finalizerIndex).toBeGreaterThan(0); expect(promotedUploadIndex).toBeGreaterThan(finalizerIndex); expect(cleanupStepIndex).toBeGreaterThan(promotedUploadIndex); expect(cleanupIndex).toBeGreaterThan(cleanupStepIndex); expect(first.slice(promotedUploadIndex, cleanupStepIndex)).not.toContain("if: always()"); expect(first.slice(cleanupStepIndex, cleanupIndex)).toContain("if: always()"); expect(first.slice(cleanupStepIndex, cleanupIndex)).toContain( 'if [ -n "$PROMOTION_STAGING_ROOT" ] && [ -n "$PROMOTION_CLEANUP_TOKEN" ] && [ -n "$PROMOTION_RUNNER_TEMP_DEV" ] && [ -n "$PROMOTION_RUNNER_TEMP_INO" ] && [ -n "$PROMOTION_STAGING_DEV" ] && [ -n "$PROMOTION_STAGING_INO" ]; then', ); const actionUses = [...first.matchAll(/^\s+-?\s*uses: (.+)$/gmu)].map((match) => match[1]); expect(actionUses).toHaveLength(32); expect(new Set(actionUses)).toEqual( new Set([ "https://github.com/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5", "https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020", "https://github.com/ChristopherHX/gitea-upload-artifact@81f940d004763f986ba3582c007fd842dd5cb0d7", "https://github.com/ChristopherHX/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7", ]), ); expect(actionUses.every((uses) => /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+@[0-9a-f]{40}$/u.test(uses!))).toBe(true); }); it("derives download artifact names from the typed upload producer", async () => { const contract = await loadCiGateContract(process.cwd()); const candidate = JSON.parse(JSON.stringify(contract)) as Record; const producer = candidate.jobs.find( (job: Record) => job.id === "immutable_build", ); const upload = producer.steps.find( (step: Record) => step.kind === "upload", ); upload.name = "renamed-candidate-${{ gitea.run_id }}"; const rendered = renderCiWorkflow( candidate as unknown as Awaited>, ); expect(rendered.match(/name: "renamed-candidate-\$\{\{ gitea\.run_id \}\}"/gu)).toHaveLength(4); }); it("check mode reports missing and byte-level drift without writing", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-workflow-check-")); temporaryRoots.push(root); await writeFile(path.join(root, "package.json"), '{"scripts":{}}\n'); const contract = await loadCiGateContract(process.cwd()); const missing = await generateCiWorkflow({ root, contract, check: true }); expect(missing).toEqual( expect.objectContaining({ written: false, matches: false, firstDifferenceLine: 1 }), ); const target = path.join(root, ".gitea/workflows/quality-gates.yml"); await mkdir(path.dirname(target), { recursive: true }); await writeFile(target, renderCiWorkflow(contract).replace("permissions:", "permissions: ")); const drift = await generateCiWorkflow({ root, contract, check: true }); expect(drift.written).toBe(false); expect(drift.matches).toBe(false); expect(drift.firstDifferenceByte).toBeGreaterThan(0); expect(await readFile(target, "utf8")).toContain("permissions: "); }); it("check mode rejects CRLF and extra final newlines as byte drift", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-workflow-newline-")); temporaryRoots.push(root); const contract = await loadCiGateContract(process.cwd()); const target = path.join(root, ".gitea/workflows/quality-gates.yml"); await mkdir(path.dirname(target), { recursive: true }); await writeFile(target, `${renderCiWorkflow(contract).replaceAll("\n", "\r\n")}\r\n`); const result = await generateCiWorkflow({ root, contract, check: true }); expect(result.matches).toBe(false); expect(result.firstDifferenceLine).toBeGreaterThan(0); }); it("writes a missing workflow and then passes byte-for-byte check mode", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-workflow-write-")); temporaryRoots.push(root); const contract = await loadCiGateContract(process.cwd()); const written = await generateCiWorkflow({ root, contract, check: false }); expect(written).toEqual(expect.objectContaining({ written: true, matches: true })); const checked = await generateCiWorkflow({ root, contract, check: true }); expect(checked).toEqual(expect.objectContaining({ written: false, matches: true })); }); it.each(["ancestor", "leaf"])("rejects a %s symlink in check and write modes without touching its canary", async (kind) => { const root = await mkdtemp(path.join(tmpdir(), "ci-workflow-symlink-root-")); const outside = await mkdtemp(path.join(tmpdir(), "ci-workflow-symlink-outside-")); temporaryRoots.push(root, outside); const contract = await loadCiGateContract(process.cwd()); const canary = path.join(outside, "canary"); await writeFile(canary, "unchanged\n"); if (kind === "ancestor") { await mkdir(path.join(outside, "workflows"), { recursive: true }); await writeFile(path.join(outside, "workflows/quality-gates.yml"), renderCiWorkflow(contract)); await symlink(outside, path.join(root, ".gitea")); } else { await mkdir(path.join(root, ".gitea/workflows"), { recursive: true }); await writeFile(path.join(outside, "quality-gates.yml"), renderCiWorkflow(contract)); await symlink(path.join(outside, "quality-gates.yml"), path.join(root, ".gitea/workflows/quality-gates.yml")); } await expect(generateCiWorkflow({ root, contract, check: true })).rejects.toThrow(/unsafe/i); await expect(generateCiWorkflow({ root, contract, check: false })).rejects.toThrow(/unsafe/i); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); }); it("preserves the destination and cleans only its owned temp when atomic write fails", async () => { const contract = await loadCiGateContract(process.cwd()); const removed: string[] = []; const flags: Array<{ flags: number; mode: number }> = []; let renamed = false; const fileSystem: CiWorkflowFileSystem = { mkdir: async () => undefined, readFile: async () => Buffer.from("existing workflow\n"), open: async (_target, openFlags, mode) => { flags.push({ flags: openFlags, mode }); return { writeFile: async () => { throw new Error("injected write failure"); }, chmod: async () => undefined, sync: async () => undefined, close: async () => undefined, }; }, openDirectory: async () => ({ sync: async () => undefined, close: async () => undefined, }), rename: async () => { renamed = true; }, rm: async (target) => { removed.push(target); }, }; const generate = createCiWorkflowGenerator({ fileSystem, createNonce: () => "owned", }); await expect( generate({ root: "/tmp/ci-workflow-atomic", contract, check: false }), ).rejects.toThrow(/injected write failure/u); expect(renamed).toBe(false); expect(removed).toEqual([ "/tmp/ci-workflow-atomic/.gitea/workflows/.quality-gates.yml.owned.tmp", ]); expect(flags).toEqual([ { flags: constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, mode: 0o644, }, ]); }); });