Files
openclaw/scripts/verify-pr-hosted-gates.mjs
Vincent Koc abb6f04e0c ci(release): harden release controls
One-time maintainer-authorized bootstrap merge for the release-gate verifier policy. Exact hosted CI and all supporting workflow gates passed on 66133de419.
2026-06-18 03:11:20 +08:00

245 lines
7.4 KiB
JavaScript

#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { isDirectRunUrl } from "./lib/direct-run.mjs";
export const SCHEDULED_HOSTED_WORKFLOWS = [
"Blacksmith Testbox",
"Blacksmith ARM Testbox",
"Blacksmith Build Artifacts Testbox",
"Workflow Sanity",
];
const CI_WORKFLOW_PATH = ".github/workflows/ci.yml";
const BUILD_ARTIFACTS_WORKFLOW = "Blacksmith Build Artifacts Testbox";
const ARTIFACT_FALLBACK_REQUIRED_WORKFLOWS = [
"Blacksmith Testbox",
"Blacksmith ARM Testbox",
"Workflow Sanity",
];
function readOptionValue(argv, index, optionName) {
const value = argv[index + 1];
if (!value || value.startsWith("--")) {
throw new Error(`Expected ${optionName} <value>.`);
}
return value;
}
export function parseArgs(argv) {
const args = { repo: "", sha: "", output: "", changelogOnly: false };
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
switch (arg) {
case "--repo":
args.repo = readOptionValue(argv, index, arg);
index += 1;
break;
case "--sha":
args.sha = readOptionValue(argv, index, arg);
index += 1;
break;
case "--output":
args.output = readOptionValue(argv, index, arg);
index += 1;
break;
case "--changelog-only":
args.changelogOnly = true;
break;
default:
throw new Error(`Unknown option: ${arg}`);
}
}
if (!args.repo || !args.sha || !args.output) {
throw new Error(
"Usage: node scripts/verify-pr-hosted-gates.mjs --repo <owner/repo> --sha <sha> --output <path>",
);
}
return args;
}
function formatObservedRuns(runs) {
if (runs.length === 0) {
return "none";
}
return runs
.map(
(run) => `${run.id ?? "unknown"}:${run.status ?? "unknown"}/${run.conclusion ?? "unknown"}`,
)
.join(", ");
}
function isReleaseGateCiRun(run, sha) {
return (
run?.event === "workflow_dispatch" &&
run?.head_sha === sha &&
String(run?.path ?? "").split("@", 1)[0] === CI_WORKFLOW_PATH &&
run?.display_title === `CI release gate ${sha}`
);
}
function matchingAuthoritativeRuns(runs, workflowName, sha) {
return runs.filter((run) => {
if (run?.head_sha !== sha) {
return false;
}
if (run?.event === "pull_request") {
return run.name === workflowName;
}
return workflowName === "CI" && isReleaseGateCiRun(run, sha);
});
}
function latestRun(runs) {
return runs.toSorted((left, right) =>
String(right.updated_at ?? "").localeCompare(String(left.updated_at ?? "")),
)[0];
}
function preferredCiRun(runs) {
const scheduledRuns = runs.filter((run) => run.event === "pull_request");
const failedScheduledRun = scheduledRuns.find(
(run) => run.status === "completed" && run.conclusion !== "success",
);
if (failedScheduledRun) {
return failedScheduledRun;
}
const latestScheduledRun = latestRun(scheduledRuns);
if (latestScheduledRun?.status === "completed") {
return latestScheduledRun;
}
return latestRun(runs.filter((run) => run.event === "workflow_dispatch")) ?? latestScheduledRun;
}
function successfulRunOrThrow(runs, workflowName, sha) {
const matchingRuns = matchingAuthoritativeRuns(runs, workflowName, sha);
const run = workflowName === "CI" ? preferredCiRun(matchingRuns) : latestRun(matchingRuns);
if (!run || run.status !== "completed" || run.conclusion !== "success") {
throw new Error(
`Missing successful exact-head ${workflowName} workflow for ${sha}. Observed: ${formatObservedRuns(matchingRuns)}`,
);
}
return run;
}
function successfulReleaseGateFallback(workflowRuns, sha) {
const fallback = latestRun(workflowRuns.filter((run) => isReleaseGateCiRun(run, sha)));
if (fallback?.status !== "completed" || fallback.conclusion !== "success") {
return null;
}
return fallback;
}
function canCoverQueuedBuildArtifacts(workflowRuns, sha) {
if (!successfulReleaseGateFallback(workflowRuns, sha)) {
return false;
}
const supportingGatesPassed = ARTIFACT_FALLBACK_REQUIRED_WORKFLOWS.every((workflowName) => {
const run = latestRun(matchingAuthoritativeRuns(workflowRuns, workflowName, sha));
return run?.status === "completed" && run.conclusion === "success";
});
if (!supportingGatesPassed) {
return false;
}
const buildArtifactRuns = matchingAuthoritativeRuns(workflowRuns, BUILD_ARTIFACTS_WORKFLOW, sha);
const latestBuildArtifactRun = latestRun(buildArtifactRuns);
return (
latestBuildArtifactRun?.status === "queued" &&
buildArtifactRuns.every(
(run) =>
run.status === "queued" || (run.status === "completed" && run.conclusion === "success"),
)
);
}
function stripAnsi(raw) {
const escape = String.fromCharCode(27);
return raw.replace(new RegExp(`${escape}\\[[0-?]*[ -/]*[@-~]`, "gu"), "");
}
export function parseWorkflowRunPages(raw) {
return JSON.parse(stripAnsi(raw)).flatMap((page) => page.workflow_runs ?? []);
}
export function collectHostedGateEvidence({ sha, workflowRuns, changelogOnly = false }) {
if (!Array.isArray(workflowRuns)) {
throw new Error("workflowRuns must be an array.");
}
const workflows = [];
const fallbackCoveredWorkflows = [];
let ciRun;
if (!changelogOnly) {
ciRun = successfulRunOrThrow(workflowRuns, "CI", sha);
workflows.push(ciRun);
}
for (const workflowName of SCHEDULED_HOSTED_WORKFLOWS) {
const matchingRuns = matchingAuthoritativeRuns(workflowRuns, workflowName, sha);
if (matchingRuns.length > 0) {
if (
workflowName === BUILD_ARTIFACTS_WORKFLOW &&
canCoverQueuedBuildArtifacts(workflowRuns, sha)
) {
fallbackCoveredWorkflows.push({
name: workflowName,
coveredBy: "CI release gate",
reason: "scheduled workflow is queued",
});
continue;
}
workflows.push(successfulRunOrThrow(workflowRuns, workflowName, sha));
}
}
const evidence = {
headSha: sha,
workflows: workflows.map((run) => ({
id: run.id,
name: run.name,
event: run.event,
status: run.status,
conclusion: run.conclusion,
createdAt: run.created_at,
updatedAt: run.updated_at,
url: run.html_url,
})),
};
if (fallbackCoveredWorkflows.length > 0) {
evidence.fallbackCoveredWorkflows = fallbackCoveredWorkflows;
}
return evidence;
}
function loadWorkflowRuns(repo, sha) {
const raw = execFileSync(
"gh",
["api", `repos/${repo}/actions/runs?head_sha=${sha}&per_page=100`, "--paginate", "--slurp"],
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
);
return parseWorkflowRunPages(raw);
}
export function main(argv = process.argv.slice(2)) {
const args = parseArgs(argv);
const evidence = collectHostedGateEvidence({
sha: args.sha,
workflowRuns: loadWorkflowRuns(args.repo, args.sha),
changelogOnly: args.changelogOnly,
});
const manifest = {
schemaVersion: 1,
generatedAt: new Date().toISOString(),
repo: args.repo,
...evidence,
};
mkdirSync(path.dirname(args.output), { recursive: true });
writeFileSync(args.output, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(
`Exact-head hosted gates passed for ${args.sha}: ${manifest.workflows
.map((workflow) => `${workflow.name}#${workflow.id}`)
.join(", ")}`,
);
}
if (isDirectRunUrl(process.argv[1], import.meta.url)) {
main();
}