chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
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<string, string> }),
|
||||
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<string, GateResult> = 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<keyof typeof PROMOTION_FORMULA>) {
|
||||
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");
|
||||
Reference in New Issue
Block a user