fix: promote immutable verified release bundles

This commit is contained in:
DongHyeonka
2026-08-02 06:08:06 +09:00
parent 100a3bb6ba
commit 7c5ed80407
17 changed files with 1203 additions and 263 deletions
+61 -1
View File
@@ -22,6 +22,10 @@ const document = parseCiContractDocument(
JSON.parse(await readFile("config/ci/gates.json", "utf8")),
);
const workflow = await readFile(document.providerAdapter, "utf8");
const packageDocument = JSON.parse(await readFile("package.json", "utf8")) as {
scripts?: Record<string, string>;
};
const packageScripts = packageDocument.scripts ?? {};
const nodeVersion = (await readFile(".nvmrc", "utf8")).trim();
const gateRunner = await readFile("scripts/run-ci-gate.ts", "utf8");
const drillRunner = await readFile("scripts/drill-runbook.ts", "utf8");
@@ -154,7 +158,7 @@ for (const pattern of forbiddenWorkflowPatterns) {
}
}
const jobTimeoutCount = workflow.match(/timeout-minutes:\s*45/g)?.length ?? 0;
if (jobTimeoutCount !== 5) {
if (jobTimeoutCount !== 9) {
failures.push("every CI gate job must declare timeout-minutes: 45");
}
if (/if-no-files-found:\s*warn/.test(workflow)) {
@@ -166,9 +170,16 @@ for (const requiredToken of [
"production_gate:",
"field_gate:",
"documentation_gate:",
"immutable_build:",
"vulnerability_provider:",
"provenance_provider:",
"promotion:",
"needs: merge_gate",
"needs: release_gate",
"needs: production_gate",
"needs: immutable_build",
"needs: [immutable_build, vulnerability_provider, provenance_provider]",
"actions/download-artifact@v4",
"actions/upload-artifact@v4",
"if: always()",
"permissions:",
@@ -178,11 +189,60 @@ for (const requiredToken of [
'VITE_COMMIT_SHA: "${{ gitea.sha }}"',
'RELEASE_ID: "${{ gitea.ref }}-${{ gitea.run_id }}-${{ gitea.run_attempt }}"',
'CI_RUNNER_IMAGE: "${{ vars.RUNNER_IMAGE_DIGEST }}"',
"VULNERABILITY_REPORT_PATH:",
"PROVENANCE_ATTESTATION_PATH:",
"VULNERABILITY_PUBLIC_KEY_PATH:",
"PROVENANCE_PUBLIC_KEY_PATH:",
"release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}",
"corepack pnpm verify:provider-evidence",
"corepack pnpm verify:promotion",
]) {
if (!workflow.includes(requiredToken)) {
failures.push(`workflow missing ${requiredToken}`);
}
}
for (const script of [
"build:release-candidate",
"verify:local-evidence",
"verify:provider-evidence",
"verify:promotion",
]) {
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");
}
const promotionWorkflow = workflow.match(
/\n {2}promotion:\n(?<body>[\s\S]*?)\n {2}production_gate:/u,
)?.groups?.body;
if (!promotionWorkflow) {
failures.push("workflow promotion job is missing or misplaced");
} else if (
/\b(?:build|build:[\w-]+|rebuild)\b/u.test(
promotionWorkflow.replaceAll("immutable_build", ""),
)
) {
failures.push("workflow promotion job must not build or rebuild candidate bytes");
}
const immutableGateSteps = document.gates["FE-GATE-015"]?.steps;
if (
JSON.stringify(immutableGateSteps) !==
JSON.stringify([
{ script: "build:release-candidate", expect: "pass" },
{ script: "verify:local-evidence", expect: "pass" },
])
) {
failures.push("FE-GATE-015 must build the candidate once and verify local evidence only");
}
for (const requiredToken of [
"ciCheckoutIdentityFailures",
"ciBuildEnvironmentFailures",