import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import { evaluatePromotionReadiness, PROMOTION_FORMULA, type GateResult, } from "../src/application/policies/promotion-readiness.ts"; import { loadCiGateContract, indexCiGateContract, } from "./contracts/ci-gates.ts"; import { generateCiWorkflow, renderCiWorkflow } from "./generate-ci-workflow.ts"; import { ciContractReportSchema, } from "./lib/ci-contract-report.ts"; import { validateInstallScriptPolicy, validatePackageScriptGraph, } from "./lib/package-script-graph.ts"; import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts"; const removalFixtureMode = process.argv.includes("--reduced-removal-fixture"); const contract = await loadCiGateContract(process.cwd(), { mode: removalFixtureMode ? "removal-fixture" : "canonical", }); const index = indexCiGateContract(contract); const [packageDocument, nodeVersion] = await Promise.all([ readFile("package.json", "utf8").then((value) => JSON.parse(value) as { scripts?: Record }), readFile(".nvmrc", "utf8").then((value) => value.trim()), ]); const packageScripts = packageDocument.scripts ?? {}; const failures: string[] = []; if (!/^\d+\.\d+\.\d+$/u.test(nodeVersion)) { failures.push(".nvmrc must contain one exact Node.js semantic version"); } for (const script of [ "build:release-candidate", "verify:local-evidence", "verify:promotion", "generate:ci-workflow", "check:ci-workflow", "check:ci", ]) { if (!packageScripts[script]) failures.push(`package script missing ${script}`); } for (const removedScript of [ "build:release", "verify:supply-chain", "verify:supply-chain:promotion", ]) { if (packageScripts[removedScript]) failures.push(`legacy package script remains ${removedScript}`); } if (/\b(?:build|rebuild)(?::[\w-]+)?\b/u.test(packageScripts["verify:promotion"] ?? "")) { failures.push("verify:promotion must not build or rebuild candidate bytes"); } failures.push(...validatePackageScriptGraph(packageScripts, "check:ci")); failures.push( ...validateInstallScriptPolicy( packageScripts, [...new Set(contract.commands.map(({ script }) => script))], ), ); const immutable = index.gates.get("FE-GATE-015"); const immutableCommands = immutable?.commandIds.map((id) => index.commands.get(id)?.script); if (JSON.stringify(immutableCommands) !== JSON.stringify(["build:release-candidate", "verify:local-evidence"])) { failures.push("FE-GATE-015 must build candidate bytes once and verify local evidence only"); } const architecture = index.gates.get("FE-GATE-010"); if (!architecture?.commandIds.some((id) => index.commands.get(id)?.script === "check:ci")) { failures.push("a blocking gate must execute check:ci"); } if (!architecture?.evidenceArtifactIds.some((id) => index.artifacts.get(id)?.path === "artifacts/quality/ci-contract.json")) { failures.push("FE-GATE-010 must publish the typed CI contract report"); } const expectedGateIds = Array.from( { length: 26 }, (_, index) => `FE-GATE-${String(index + 1).padStart(3, "0")}`, ); const passingResults: Record = Object.fromEntries( expectedGateIds.map((gateId) => [gateId, "PASS"]), ); const allPass = evaluatePromotionReadiness(passingResults); const negativeFixtures: Array<{ readiness: keyof typeof PROMOTION_FORMULA; failedGate: string; passed: boolean; }> = []; for (const readiness of Object.keys(PROMOTION_FORMULA) as Array) { const failedGate = PROMOTION_FORMULA[readiness][0]; if (!failedGate) throw new TypeError(`${readiness} has no configured gates`); const evaluated = evaluatePromotionReadiness({ ...passingResults, [failedGate]: "FAIL" }); const passed = evaluated[readiness] === false; negativeFixtures.push({ readiness, failedGate, passed }); if (!passed) failures.push(`${readiness} did not fail closed`); } if (!Object.values(allPass).every(Boolean)) failures.push("all-PASS formula did not produce every readiness state"); const renderedWorkflow = renderCiWorkflow(contract); const workflowCheck = await generateCiWorkflow({ root: process.cwd(), contract, check: true, }); if (!workflowCheck.matches) { failures.push( `generated workflow drift at byte ${workflowCheck.firstDifferenceByte ?? "missing"}, line ${workflowCheck.firstDifferenceLine ?? "missing"}`, ); } let checkedWorkflowBytes = Buffer.from(renderedWorkflow, "utf8"); try { checkedWorkflowBytes = await readFile(workflowCheck.target); } catch (error) { failures.push(`generated workflow is unreadable: ${error instanceof Error ? error.message : String(error)}`); } const report = ciContractReportSchema.parse({ schemaVersion: 2, nodeVersion, gateCount: contract.gates.length, commandDefinitionCount: contract.commands.length, commandReferenceCount: contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0), artifactCount: contract.artifacts.length, jobCount: contract.jobs.length, workflowSha256: createHash("sha256").update(checkedWorkflowBytes).digest("hex"), durationStatus: contract.retention.durationStatus, negativeFixtures, failures, passed: failures.length === 0, }); await writeValidatedJsonArtifact({ path: "artifacts/quality/ci-contract.json", schema: ciContractReportSchema, value: report, }); if (failures.length > 0) { process.stderr.write(`CI contract failed:\n${failures.join("\n")}\n`); process.exit(1); } process.stdout.write("CI contract: 26 gates, strict v2 graph and generated workflow model PASS\n");