openclaw/scripts/release-ci-summary.mjs
Dallin Romney 7e8732d9d7
fix: restore blocking release validation and remove publication waivers (#157864)
* fix: restore blocking release validation and remove publication waivers

* fix(release): align recovery proof with strict validation

* docs(release): preserve qualified beta promotion

* fix(release): require strict stable orchestration and retire waived replay

* fix: read waiver-free saved release state after retry removal

* fix: discard retired capability metadata from strict release state
2026-09-25 16:27:23 -07:00

3629 lines
128 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* Release CI summary helper that prints parent and child workflow status for a
* full release run.
*/
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import { closeSync, mkdtempSync, openSync, readFileSync, rmSync, statSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import process from "node:process";
import { setTimeout as sleep } from "node:timers/promises";
import { fileURLToPath } from "node:url";
import { validateFullReleaseCandidateBinding } from "./full-release-candidate-contract.mjs";
import {
publicationAdmissionContract,
publicationObservationJson,
publicationSourceContract,
publicationSourceJson,
publicationSourceReuseIdentity,
validatePublicationAdmissionBinding,
validatePublicationSourceBinding,
} from "./full-release-publication-contract.mjs";
import {
classifyReleaseGhTransportError,
compareReleaseJobsByName,
composeReleaseChildAttemptEvidence,
formatReleaseStateOutcome,
isReleaseGhArtifactMissingError,
isSplitChangelogEvidenceDelta,
classifyReleaseChangelogEvidenceComparison,
SPLIT_CHANGELOG_EVIDENCE_REUSE_POLICY,
MAX_RELEASE_ARTIFACT_BYTES,
normalizeReleaseCoveragePolicy,
normalizeReleaseTelegramWaiver,
releaseCompositeJobsSha256,
terminalPolicyPass,
validateReleaseChildDispatchBinding,
validateReleaseCoveragePolicyBinding,
validateReleaseExecutionPlanArtifact,
validateRetiredReleaseRetryFields,
validateReleaseChildRunProvenance,
validateReleaseStateArtifact,
validateReleaseTelegramWaiverBinding,
} from "./full-release-validation-policy.mjs";
import { inspectActionsArtifactZip } from "./lib/actions-artifact-archive.mjs";
import { sortJsonValueKeys } from "./lib/canonical-json.mjs";
import { releaseChildReuseSha256 } from "./lib/full-release-child-request.mjs";
import { validateReusableReleaseChild } from "./lib/full-release-child-reuse.mjs";
import {
execGhRead,
execGhReadAsync,
plainGhAuthenticatedEnv,
resolvePlainGhBin,
} from "./lib/plain-gh.mjs";
import { resolveReleaseContextIdentity } from "./lib/release-context.mjs";
import { resolveReleasePublishInputs } from "./lib/release-publish-inputs.mjs";
const sortReleaseJsonValueKeys = /** @type {<T>(value: T) => T} */ (sortJsonValueKeys); // Validated release JSON preserves its structural type.
const DEFAULT_REPO = process.env.OPENCLAW_RELEASE_REPO || "openclaw/openclaw";
const RELEASE_EVIDENCE_SCHEMA = "openclaw.release-validation-evidence/v3";
const PHASED_RELEASE_EVIDENCE_SCHEMA = "openclaw.release-validation-evidence/v4";
const SHA_PINNED_BRANCH_PATTERN = /^release-ci\/[a-f0-9]{12}-[1-9][0-9]*$/u;
const TRUSTED_RELEASE_PUBLISH_TAG_PATTERN =
/^refs\/tags\/release-publish\/([a-f0-9]{12})-[1-9][0-9]*$/u;
const RELEASE_EVIDENCE_SCRIPT = "scripts/release-ci-summary.mjs";
const RELEASE_EVIDENCE_FILE = fileURLToPath(import.meta.url);
const RELEASE_EVIDENCE_REPO_ROOT = resolve(dirname(RELEASE_EVIDENCE_FILE), "..");
const MANIFEST_ARTIFACT_ENTRY = "full-release-validation-manifest.json";
const MAX_MANIFEST_ENTRY_LIST_BYTES = 8 * 1024;
const MAX_MANIFEST_ARTIFACT_ZIP_BYTES = MAX_RELEASE_ARTIFACT_BYTES + MAX_MANIFEST_ENTRY_LIST_BYTES;
// Release evidence lookups run during full release validation, so keep enough
// headroom for GitHub latency while preventing one stalled read from consuming
// the workflow budget.
const GH_COMMAND_TIMEOUT_MS = 60_000;
const ARTIFACT_DOWNLOAD_MIN_BYTES_PER_SECOND = 256 * 1024;
const ARTIFACT_DOWNLOAD_OVERHEAD_MS = 60_000;
const ARTIFACT_DOWNLOAD_MAX_TIMEOUT_MS = 30 * 60_000;
const ARTIFACT_DOWNLOAD_ATTEMPTS = 2;
const SUCCESSFUL_PARENT_JOB_CONCLUSIONS = new Set(["neutral", "skipped", "success"]);
const LEGACY_CHILD_DISPATCHES = [
{
manifestKey: "normalCi",
name: "CI",
parentJobName: "Run normal full CI",
suffix: "-ci",
trustedRef: "parent",
workflow: "ci.yml",
},
{
manifestKey: "releaseChecks",
name: "OpenClaw Release Checks",
parentJobName: "Run release/live/Docker/QA validation",
suffix: "-release-checks",
trustedRef: "parent",
workflow: "openclaw-release-checks.yml",
},
{
manifestKey: "pluginPrerelease",
name: "Plugin Prerelease",
parentJobName: "Run plugin prerelease validation",
suffix: "-plugin-prerelease",
trustedRef: "parent",
workflow: "plugin-prerelease.yml",
},
{
manifestKey: "npmTelegram",
name: "NPM Telegram Beta E2E",
parentJobName: "Run package Telegram E2E",
suffix: "-npm-telegram",
trustedRef: "parent",
workflow: "npm-telegram-beta-e2e.yml",
},
{
manifestKey: "productPerformance",
name: "OpenClaw Performance",
parentJobName: "Run product performance evidence",
suffix: "",
trustedRef: "parent",
workflow: "openclaw-performance.yml",
},
];
const PHASED_CHILD_DISPATCHES = [
LEGACY_CHILD_DISPATCHES.find((child) => child.manifestKey === "normalCi"),
{
manifestKey: "pluginPrereleaseIndependent",
name: "Plugin Prerelease",
parentJobName: "Run plugin prerelease independent validation",
suffix: "-plugin-prerelease-independent",
trustedRef: "parent",
workflow: "plugin-prerelease.yml",
},
{
manifestKey: "pluginPrereleaseCandidate",
name: "Plugin Prerelease",
parentJobName: "Run plugin prerelease candidate validation",
suffix: "-plugin-prerelease-candidate",
trustedRef: "parent",
workflow: "plugin-prerelease.yml",
},
{
manifestKey: "releaseChecksIndependent",
name: "OpenClaw Release Checks",
parentJobName: "Run release checks independent validation",
suffix: "-release-checks-independent",
trustedRef: "parent",
workflow: "openclaw-release-checks.yml",
},
{
manifestKey: "releaseChecksCandidate",
name: "OpenClaw Release Checks",
parentJobName: "Run release checks candidate validation",
suffix: "-release-checks-candidate",
trustedRef: "parent",
workflow: "openclaw-release-checks.yml",
},
LEGACY_CHILD_DISPATCHES.find((child) => child.manifestKey === "npmTelegram"),
LEGACY_CHILD_DISPATCHES.find((child) => child.manifestKey === "productPerformance"),
];
// One phased child set plus current and reused parents.
const MAX_EXPECTED_RUN_ATTEMPTS = PHASED_CHILD_DISPATCHES.length + 2;
const MAX_EXPECTED_RUN_ATTEMPTS_JSON_BYTES = 4 * 1024;
class ReleaseEvidenceRefreshRequiredError extends Error {
constructor(message) {
super(message);
this.name = "ReleaseEvidenceRefreshRequiredError";
this.refreshable = true;
}
}
const EXACT_TARGET_EVIDENCE_REUSE_POLICY = "exact-target-full-validation-v1";
const CHANGELOG_ONLY_EVIDENCE_REUSE_POLICY = "changelog-only-release-v1";
const EVIDENCE_REUSE_POLICIES = new Set([
EXACT_TARGET_EVIDENCE_REUSE_POLICY,
CHANGELOG_ONLY_EVIDENCE_REUSE_POLICY,
SPLIT_CHANGELOG_EVIDENCE_REUSE_POLICY,
]);
const RERUN_GROUP_CHILD_KEYS = new Map([
["all", ["normalCi", "releaseChecks", "pluginPrerelease", "productPerformance"]],
["ci", ["normalCi"]],
["plugin-prerelease", ["pluginPrerelease"]],
["install-smoke", ["releaseChecks"]],
["cross-os", ["releaseChecks"]],
["live-e2e", ["releaseChecks"]],
["package", ["releaseChecks"]],
["qa-parity", ["releaseChecks"]],
["qa-live", ["releaseChecks"]],
["npm-telegram", ["npmTelegram"]],
["performance", ["productPerformance"]],
]);
const PHASED_RERUN_GROUP_CHILD_KEYS = new Map([
[
"all",
[
"normalCi",
"pluginPrereleaseIndependent",
"pluginPrereleaseCandidate",
"releaseChecksIndependent",
"releaseChecksCandidate",
"productPerformance",
],
],
["ci", ["normalCi"]],
["plugin-prerelease", ["pluginPrereleaseIndependent", "pluginPrereleaseCandidate"]],
["install-smoke", ["releaseChecksIndependent"]],
["cross-os", ["releaseChecksCandidate"]],
["live-e2e", ["releaseChecksIndependent", "releaseChecksCandidate"]],
["package", ["releaseChecksCandidate"]],
["qa-parity", ["releaseChecksIndependent"]],
["qa-live", ["releaseChecksIndependent"]],
["npm-telegram", ["npmTelegram"]],
["performance", ["productPerformance"]],
]);
const HISTORICAL_MANIFEST_RERUN_GROUP_CHILD_KEYS = new Map([
["release-checks", ["releaseChecks"]],
["qa", ["releaseChecks"]],
]);
function evidenceReadRetryDelay(args, error, attempt) {
// Only these argument shapes are GETs; fields, input, methods, and GraphQL
// can mutate state and must never acquire retries through this read wrapper.
const isGet =
args[0] === "api" &&
/^(?:repos\/|rate_limit$)/u.test(args[1] ?? "") &&
(args.length === 2 || (args.length === 3 && args[2] === "--allow-escape-sequences"));
const diagnostic = `${error?.message ?? ""}\n${error?.stderr ?? ""}`;
if (
!isGet ||
attempt >= 3 ||
/HTTP [1-4][0-9]{2}\b/u.test(diagnostic) ||
classifyReleaseGhTransportError(error) !== "transient"
) {
throw error;
}
return 2_000 * 2 ** attempt;
}
export function runReleaseCiGh(args, params = {}) {
const execFileSyncImpl = params.execFileSyncImpl ?? execFileSync;
const timeoutMs = params.timeoutMs ?? GH_COMMAND_TIMEOUT_MS;
const stdio = params.stdio ?? ["ignore", "pipe", "pipe"];
for (let attempt = 0; ; attempt += 1) {
try {
return execGhRead(
args,
{
encoding: "utf8",
killSignal: "SIGKILL",
maxBuffer: 64 * 1024 * 1024,
stdio,
timeout: timeoutMs,
},
{ execFileSyncImpl },
);
} catch (error) {
Atomics.wait(
new Int32Array(new SharedArrayBuffer(4)),
0,
0,
evidenceReadRetryDelay(args, error, attempt),
);
}
}
}
function gh(args) {
return runReleaseCiGh(args);
}
async function ghAsync(args) {
for (let attempt = 0; ; attempt += 1) {
try {
return await execGhReadAsync(args, {
killSignal: "SIGKILL",
maxBuffer: 64 * 1024 * 1024,
timeout: GH_COMMAND_TIMEOUT_MS,
});
} catch (error) {
await sleep(evidenceReadRetryDelay(args, error, attempt));
}
}
}
function jsonGh(args) {
return JSON.parse(gh(args));
}
export function githubRestArgs(pathSuffix, repository = DEFAULT_REPO) {
return ["api", `repos/${repository}/${pathSuffix}`];
}
function githubRestJson(pathSuffix, repository = DEFAULT_REPO) {
return jsonGh(githubRestArgs(pathSuffix, repository));
}
async function githubRestJsonAsync(pathSuffix, repository = DEFAULT_REPO) {
return JSON.parse(await ghAsync(githubRestArgs(pathSuffix, repository)));
}
export function artifactDownloadArgs(artifactId, repository = DEFAULT_REPO) {
return ["api", `repos/${repository}/actions/artifacts/${artifactId}/zip`];
}
export function artifactDownloadTimeoutMs(sizeInBytes) {
const size = Number(sizeInBytes);
if (!Number.isSafeInteger(size) || size < 1) {
throw new Error("artifact download size is invalid");
}
return Math.min(
ARTIFACT_DOWNLOAD_MAX_TIMEOUT_MS,
Math.max(
GH_COMMAND_TIMEOUT_MS,
Math.ceil((size / ARTIFACT_DOWNLOAD_MIN_BYTES_PER_SECOND) * 1000) +
ARTIFACT_DOWNLOAD_OVERHEAD_MS,
),
);
}
function downloadArtifactZip(artifactId, destination, sizeInBytes, repository = DEFAULT_REPO) {
const timeout = sizeInBytes ? artifactDownloadTimeoutMs(sizeInBytes) : GH_COMMAND_TIMEOUT_MS;
for (let attempt = 1; attempt <= ARTIFACT_DOWNLOAD_ATTEMPTS; attempt += 1) {
const output = openSync(destination, "w");
try {
execFileSync(resolvePlainGhBin(), artifactDownloadArgs(artifactId, repository), {
env: plainGhAuthenticatedEnv(),
killSignal: "SIGKILL",
maxBuffer: 64 * 1024 * 1024,
stdio: ["ignore", output, "pipe"],
timeout,
});
return;
} catch (error) {
if (
attempt === ARTIFACT_DOWNLOAD_ATTEMPTS ||
classifyReleaseGhTransportError(error) !== "transient"
) {
throw error;
}
} finally {
closeSync(output);
}
}
}
function tryDownloadExecutionPlan(runId, repository = DEFAULT_REPO) {
const artifactName = `full-release-execution-plan-${runId}`;
const downloadDir = mkdtempSync(join(tmpdir(), "openclaw-release-execution-plan-"));
try {
try {
runReleaseCiGh(
[
"run",
"download",
String(runId),
"--repo",
repository,
"--name",
artifactName,
"--dir",
downloadDir,
],
{ stdio: ["ignore", "ignore", "pipe"] },
);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (isReleaseGhArtifactMissingError(error)) {
return undefined;
}
throw new Error(`release execution plan artifact read failed: ${message}`, {
cause: error,
});
}
const path = join(downloadDir, "full-release-execution-plan.json");
if (!statSync(path, { throwIfNoEntry: false })) {
throw new Error(`release execution plan artifact ${artifactName} omitted its manifest`);
}
if (statSync(path).size > MAX_RELEASE_ARTIFACT_BYTES) {
throw new Error(`release execution plan artifact ${artifactName} exceeds the size limit`);
}
return JSON.parse(readFileSync(path, "utf8"));
} finally {
rmSync(downloadDir, { force: true, recursive: true });
}
}
function readExecutionPlanEvidence(runId, repository) {
const name = `full-release-execution-plan-${runId}`;
const artifacts = [];
let total;
for (let page = 1; page <= 10; page += 1) {
const response = githubRestJson(
`actions/runs/${runId}/artifacts?per_page=100&page=${page}`,
repository,
);
if (
!Number.isSafeInteger(response?.total_count) ||
response.total_count < 0 ||
!Array.isArray(response.artifacts) ||
response.artifacts.length > 100 ||
(total !== undefined && total !== response.total_count)
) {
throw new Error("publication execution plan artifact enumeration is invalid");
}
total = response.total_count;
artifacts.push(...response.artifacts);
if (artifacts.length === total) {
break;
}
if (artifacts.length > total || response.artifacts.length < 100) {
throw new Error("publication execution plan artifact enumeration is incomplete");
}
}
if (artifacts.length !== total || new Set(artifacts.map((entry) => entry.id)).size !== total) {
throw new Error("publication execution plan artifact enumeration is incomplete or duplicated");
}
const matches = artifacts.filter((artifact) => artifact.name === name);
if (matches.length !== 1) {
throw new Error(
"publication original execution plan is missing or ambiguous; use a fresh parent",
);
}
const listed = matches[0];
const artifact = githubRestJson(`actions/artifacts/${listed.id}`, repository);
if (
String(artifact.id) !== String(listed.id) ||
artifact.name !== name ||
artifact.digest !== listed.digest ||
artifact.size_in_bytes !== listed.size_in_bytes ||
artifact.expired !== false ||
String(artifact.workflow_run?.id) !== String(runId) ||
!/^sha256:[a-f0-9]{64}$/u.test(artifact.digest) ||
!Number.isSafeInteger(artifact.size_in_bytes) ||
artifact.size_in_bytes < 1 ||
artifact.size_in_bytes > MAX_MANIFEST_ARTIFACT_ZIP_BYTES
) {
throw new Error("publication execution plan artifact identity mismatch");
}
const directory = mkdtempSync(join(tmpdir(), "openclaw-publication-plan-"));
try {
const path = join(directory, "plan.zip");
downloadArtifactZip(String(artifact.id), path, artifact.size_in_bytes, repository);
const archive = readFileSync(path);
if (
archive.length !== artifact.size_in_bytes ||
`sha256:${createHash("sha256").update(archive).digest("hex")}` !== artifact.digest
) {
throw new Error("publication execution plan artifact bytes differ");
}
const entry = "full-release-execution-plan.json";
const files = inspectActionsArtifactZip(archive, [entry], {
maxArchiveBytes: MAX_MANIFEST_ARTIFACT_ZIP_BYTES,
maxCompressedEntryBytes: MAX_MANIFEST_ARTIFACT_ZIP_BYTES,
maxEntryBytes: MAX_RELEASE_ARTIFACT_BYTES,
maxExpandedBytes: MAX_RELEASE_ARTIFACT_BYTES,
});
return {
artifact,
plan: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(files.get(entry))),
};
} finally {
rmSync(directory, { force: true, recursive: true });
}
}
export function validatePublicationObservationArtifactIdentity(artifact, source, uploaded) {
if (
source.repository !== DEFAULT_REPO ||
String(artifact?.id) !== String(uploaded.id) ||
artifact.digest !== uploaded.digest ||
artifact.name !==
`full-release-publication-observations-${source.runId}-${source.runAttempt}` ||
artifact.expired !== false ||
String(artifact.workflow_run?.id) !== source.runId ||
artifact.workflow_run?.head_sha !== source.workflow.sha ||
artifact.workflow_run?.head_branch !==
source.workflow.ref.replace(/^refs\/(?:heads|tags)\//u, "") ||
!Number.isSafeInteger(artifact.size_in_bytes) ||
artifact.size_in_bytes < 1 ||
artifact.size_in_bytes > MAX_MANIFEST_ARTIFACT_ZIP_BYTES
) {
throw new Error("publication observation upload identity mismatch");
}
return {
id: String(artifact.id),
name: artifact.name,
digest: artifact.digest,
sizeInBytes: artifact.size_in_bytes,
};
}
export function releaseExecutionPlanRestoreContract(workflow) {
const marker = "FULL_RELEASE_EXECUTION_PLAN_RESTORE_CONTRACT";
const contracts = [
...workflow.matchAll(/^ {2}FULL_RELEASE_EXECUTION_PLAN_RESTORE_CONTRACT: *([^\r\n]+)$/gmu),
];
if (!contracts.length && !workflow.includes(marker)) {
return undefined;
}
if (contracts.length !== 1 || !/^(?:"1"|'1'|1)$/u.test(contracts[0][1])) {
throw new Error("unsupported execution plan restore contract");
}
return "1";
}
async function originalExecutionPlanDigest(workflow, sealer, upload, client) {
if (!releaseExecutionPlanRestoreContract(workflow)) {
return undefined;
}
const witnesses = sealer.steps.filter(
(step) => step.name === "Record immutable release execution plan digest",
);
const witness = witnesses[0];
const start = Date.parse(witness?.started_at);
const end = Date.parse(witness?.completed_at);
if (
witnesses.length !== 1 ||
witness.status !== "completed" ||
witness.conclusion !== "success" ||
!Number.isSafeInteger(witness.number) ||
witness.number <= upload.number ||
!Number.isFinite(start) ||
!Number.isFinite(end) ||
start < Date.parse(upload.completed_at) ||
end < start
) {
throw new Error("publication original plan digest witness did not succeed");
}
const log = await client.getJobLog(sealer.id);
if (typeof log !== "string" || Buffer.byteLength(log) > 8 * 1024 * 1024) {
throw new Error("publication original plan digest log is missing or oversized");
}
const matches = [
...log.matchAll(
/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z) FRV_EXECUTION_PLAN_SHA256=([a-f0-9]{64})\r?$/gmu,
),
];
// Job step times have second precision; log timestamps retain fractional seconds.
// Only the post-upload witness authenticates cache bytes, never their own digest.
const time = Date.parse(matches[0]?.[1]);
if (matches.length !== 1 || !Number.isFinite(time) || time < start || time >= end + 1000) {
throw new Error("publication original plan digest witness is missing or ambiguous");
}
return matches[0][2];
}
export async function restoreOriginalPublicationAdmission({ request, client, cachedPlan }) {
const evidenceClient = client ?? createReleaseEvidenceClient(request.repository);
const original = await evidenceClient.getRunAttempt(request.runId, 1);
const branch = request.workflow.ref.replace(/^refs\/(?:heads|tags)\//u, "");
if (
request.repository !== "openclaw/openclaw" ||
original.repository?.full_name !== request.repository ||
original.head_repository?.full_name !== request.repository ||
String(original.id) !== request.runId ||
original.run_attempt !== 1 ||
original.event !== "workflow_dispatch" ||
original.path !== ".github/workflows/full-release-validation.yml" ||
original.head_sha !== request.workflow.sha ||
original.head_branch !== branch
) {
throw new Error("publication original parent identity mismatch");
}
const workflow = evidenceClient.getWorkflowSource(request.workflow.sha);
if (
publicationAdmissionContract(workflow) !== "1" ||
publicationSourceContract(workflow) !== "1"
) {
throw new Error("publication original parent lacks the required source-qualified contract");
}
const jobs = await evidenceClient.getRunAttemptJobs(request.runId, 1, { requireComplete: true });
const resolutions = jobs.filter((job) => job.name === "Resolve target ref");
if (
resolutions.length !== 1 ||
resolutions[0].run_attempt !== 1 ||
resolutions[0].status !== "completed" ||
resolutions[0].conclusion !== "success" ||
resolutions[0].steps?.filter(
(step) => step.name === "Finalize publication admission" && step.conclusion === "success",
).length !== 1
) {
throw new Error("publication original admission did not succeed; use a fresh parent");
}
const sealers = jobs.filter((job) => job.name === "Seal release execution plan");
const sealer = sealers[0];
const seals =
sealer?.steps?.filter((step) => step.name === "Seal immutable release execution plan") ?? [];
const uploads =
sealer?.steps?.filter((step) => step.name === "Upload immutable release execution plan") ?? [];
const seal = seals[0];
const upload = uploads[0];
const sealStart = Date.parse(seal?.started_at);
const sealEnd = Date.parse(seal?.completed_at);
const uploadStart = Date.parse(upload?.started_at);
const uploadEnd = Date.parse(upload?.completed_at);
// A failed/cancelled sealer may have written its complete interruption
// checkpoint. The guarded successful upload, not overall job success, seals it.
if (
sealers.length !== 1 ||
sealer.run_attempt !== 1 ||
sealer.status !== "completed" ||
seals.length !== 1 ||
seal.status !== "completed" ||
!["success", "failure", "cancelled"].includes(seal.conclusion) ||
uploads.length !== 1 ||
upload.status !== "completed" ||
upload.conclusion !== "success" ||
!Number.isSafeInteger(seal.number) ||
!Number.isSafeInteger(upload.number) ||
upload.number <= seal.number ||
![sealStart, sealEnd, uploadStart, uploadEnd].every(Number.isFinite) ||
sealStart > sealEnd ||
sealEnd > uploadStart ||
uploadStart > uploadEnd
) {
throw new Error("publication original execution plan sealer/upload did not succeed");
}
const originalDigest = await originalExecutionPlanDigest(
workflow,
sealer,
upload,
evidenceClient,
);
// Frozen historical workflows have no durable digest witness. Their original
// artifact remains mandatory; a self-consistent cache cannot replace provenance.
const retained =
originalDigest && cachedPlan !== undefined
? { plan: cachedPlan }
: evidenceClient.loadExecutionPlanEvidence(request.runId);
if (
!retained ||
(retained.artifact &&
(retained.artifact.workflow_run?.head_sha !== original.head_sha ||
retained.artifact.workflow_run?.head_branch !== original.head_branch))
) {
throw new Error("publication original plan artifact producer mismatch");
}
if (!originalDigest) {
const created = Date.parse(retained?.artifact.created_at);
if (!Number.isFinite(created) || created < uploadStart || created > uploadEnd) {
throw new Error("publication original plan artifact producer mismatch");
}
}
const plan = validateReleaseExecutionPlanArtifact(retained.plan, {
publicationAdmissionContract: "1",
sourceAdmissionContract: "1",
repository: request.repository,
parentRunId: request.runId,
sourceParentRunAttempt: 1,
targetSha: request.candidateSha,
targetContextRef: request.targetContextRef,
trustedWorkflowFullRef: request.tooling.ref,
trustedWorkflowSha: request.tooling.sha,
workflowRef: branch,
workflowSha: request.workflow.sha,
releaseProfile: request.coverage.release_profile,
rerunGroup: request.coverage.rerun_group,
});
if (
(originalDigest && plan.sha256 !== originalDigest) ||
(cachedPlan !== undefined &&
JSON.stringify(sortJsonValueKeys(cachedPlan)) !== JSON.stringify(sortJsonValueKeys(plan)))
) {
throw new Error("cached publication plan differs from its authenticated original");
}
const source = validatePublicationSourceBinding(plan);
if (
publicationSourceJson(source.coverage) !== publicationSourceJson(request.coverage) ||
publicationSourceJson(source.publicationSelection) !==
publicationSourceJson(request.publicationSelection) ||
source.validationPurpose !== request.validationPurpose
) {
throw new Error("publication original admission operands differ from this attempt");
}
const admission = validatePublicationAdmissionBinding(plan, {
publicationAdmissionContract: "1",
});
return { source, admission, plan };
}
function rate() {
try {
return jsonGh(["api", "rate_limit"]).resources.core;
} catch {
return undefined;
}
}
export function validateParentRunBinding(parentView, parentRest, expectedRunId) {
const boundWorkflowPath = String(parentRest.path ?? "").split("@", 1)[0];
if (
String(parentRest.id) !== String(expectedRunId) ||
parentRest.event !== "workflow_dispatch" ||
boundWorkflowPath !== ".github/workflows/full-release-validation.yml" ||
Number(parentRest.run_attempt) !== Number(parentView.attempt) ||
parentRest.head_branch !== parentView.headBranch ||
parentRest.head_sha !== parentView.headSha
) {
throw new Error(`full release parent run binding mismatch: ${expectedRunId}`);
}
return parentRest;
}
function childDispatchesForPhaseVersion(childPhaseVersion) {
return childPhaseVersion === 3 ? PHASED_CHILD_DISPATCHES : LEGACY_CHILD_DISPATCHES;
}
export function expectedChildDispatches(
parentRunId,
parentRunAttempt,
parentWorkflowRef,
childPhaseVersion = 2,
) {
if (!/^[1-9][0-9]*$/u.test(String(parentRunId))) {
throw new Error("parent run ID must be a positive decimal");
}
if (!Number.isSafeInteger(parentRunAttempt) || parentRunAttempt < 1) {
throw new Error("parent run attempt must be a positive integer");
}
if (typeof parentWorkflowRef !== "string" || parentWorkflowRef.length === 0) {
throw new Error("parent workflow ref is required");
}
const dispatchPrefix = `full-release-validation-${parentRunId}-${parentRunAttempt}`;
return childDispatchesForPhaseVersion(childPhaseVersion).map((child) =>
Object.assign({}, child, {
displayTitle: `${child.name} ${dispatchPrefix}${child.suffix}`,
headBranch: child.trustedRef === "main" ? "main" : parentWorkflowRef,
}),
);
}
export function requiredChildKeysForRerunGroup(
rerunGroup,
validationInputs = {},
childPhaseVersion = 2,
) {
const childKeys = (
childPhaseVersion === 3 ? PHASED_RERUN_GROUP_CHILD_KEYS : RERUN_GROUP_CHILD_KEYS
).get(rerunGroup);
if (!childKeys) {
throw new Error(`release validation manifest rerun group is invalid: ${rerunGroup}`);
}
const selectedKeys = new Set(childKeys);
if (
childPhaseVersion === 3 &&
rerunGroup === "live-e2e" &&
typeof validationInputs.liveSuiteFilter === "string" &&
validationInputs.liveSuiteFilter.trim().length > 0
) {
selectedKeys.delete("releaseChecksCandidate");
}
if (
rerunGroup === "all" &&
!validationInputs.telegramWaiver &&
((typeof validationInputs.npmTelegramPackageSpec === "string" &&
validationInputs.npmTelegramPackageSpec.length > 0) ||
(typeof validationInputs.releasePackageSpec === "string" &&
validationInputs.releasePackageSpec.length > 0))
) {
selectedKeys.add("npmTelegram");
}
return selectedKeys;
}
function requiredChildKeysForManifest(manifest) {
if (
[2, 3].includes(manifest.version) &&
HISTORICAL_MANIFEST_RERUN_GROUP_CHILD_KEYS.has(manifest.rerunGroup)
) {
return new Set(HISTORICAL_MANIFEST_RERUN_GROUP_CHILD_KEYS.get(manifest.rerunGroup));
}
const selectedKeys = requiredChildKeysForRerunGroup(
manifest.rerunGroup,
manifest.validationInputs,
manifest.version === 4 ? 3 : 2,
);
// validateParentManifest authenticates the explicit policy before selection;
// an older beta receipt without this marker still requires its full child set.
if (manifest.validationInputs?.coveragePolicy === "npm-beta-v1") {
selectedKeys.delete("productPerformance");
selectedKeys.delete("npmTelegram");
}
return selectedKeys;
}
export function expectedSelectedChildDispatches(
parentRunId,
parentRunAttempt,
parentWorkflowRef,
selectedKeys,
childPhaseVersion = 2,
) {
return expectedChildDispatches(
parentRunId,
parentRunAttempt,
parentWorkflowRef,
childPhaseVersion,
).filter((child) => selectedKeys.has(child.manifestKey));
}
export function selectExactChildRun(runs, expectedDisplayTitle, expectedHeadBranch) {
const matches = runs.filter(
(run) =>
run.event === "workflow_dispatch" &&
run.display_title === expectedDisplayTitle &&
run.head_branch === expectedHeadBranch,
);
if (matches.length > 1) {
throw new Error(
`multiple child runs have exact dispatch title and branch: ${expectedDisplayTitle} (${expectedHeadBranch})`,
);
}
return matches[0];
}
export function selectExactChildRunFromPages(runPages, expectedDisplayTitle, expectedHeadBranch) {
let exactMatch;
for (const runs of runPages) {
const match = selectExactChildRun(runs, expectedDisplayTitle, expectedHeadBranch);
if (match) {
if (exactMatch) {
throw new Error(
`multiple child runs have exact dispatch title and branch: ${expectedDisplayTitle} (${expectedHeadBranch})`,
);
}
exactMatch = match;
}
if (runs.length < 100) {
break;
}
}
return exactMatch;
}
function findExactChildRun(child, repository = DEFAULT_REPO) {
const runPages = [];
for (let page = 1; page <= 10; page += 1) {
const query = new URLSearchParams({
event: "workflow_dispatch",
branch: child.headBranch,
page: String(page),
per_page: "100",
});
const runs =
githubRestJson(`actions/workflows/${child.workflow}/runs?${query.toString()}`, repository)
.workflow_runs ?? [];
runPages.push(runs);
if (runs.length < 100) {
break;
}
}
return selectExactChildRunFromPages(runPages, child.displayTitle, child.headBranch);
}
async function findParentJobsAll(parentRunId, repository = DEFAULT_REPO) {
const jobs = [];
for (let page = 1; page <= 10; page += 1) {
const query = new URLSearchParams({
filter: "all",
page: String(page),
per_page: "100",
});
const pageJobs =
(
await githubRestJsonAsync(
`actions/runs/${parentRunId}/jobs?${query.toString()}`,
repository,
)
).jobs ?? [];
jobs.push(...pageJobs);
if (pageJobs.length < 100) {
break;
}
}
return jobs;
}
async function findRunAttemptJobsAll(
runId,
runAttempt,
repository = DEFAULT_REPO,
requireComplete = false,
) {
const jobs = [];
let total;
for (let page = 1; page <= 10; page += 1) {
const query = new URLSearchParams({
page: String(page),
per_page: "100",
});
const response = await githubRestJsonAsync(
`actions/runs/${runId}/attempts/${runAttempt}/jobs?${query.toString()}`,
repository,
);
const pageJobs = response.jobs ?? [];
if (
requireComplete &&
(!Array.isArray(response.jobs) ||
!Number.isSafeInteger(response.total_count) ||
response.total_count < 0 ||
(total !== undefined && total !== response.total_count))
) {
throw new Error("publication original jobs enumeration is invalid");
}
total = response.total_count;
jobs.push(...pageJobs);
if (pageJobs.length < 100) {
break;
}
}
if (
requireComplete &&
(jobs.length !== total || new Set(jobs.map((job) => job.id)).size !== total)
) {
throw new Error("publication original jobs enumeration is incomplete or duplicated");
}
return jobs;
}
function parentJobLogArgs(jobId, repository = DEFAULT_REPO, allowEscapeSequences = true) {
const args = ["api", `repos/${repository}/actions/jobs/${jobId}/logs`];
if (allowEscapeSequences) {
args.push("--allow-escape-sequences");
}
return args;
}
function isUnknownAllowEscapeSequencesFlag(error) {
if (typeof error !== "object" || error === null || !("stderr" in error)) {
return false;
}
const stderr = error.stderr;
return (
typeof stderr === "string" &&
stderr.replace(/\r\n?/gu, "\n").split("\n").includes("unknown flag: --allow-escape-sequences")
);
}
async function parentJobLog(jobId, repository = DEFAULT_REPO) {
try {
return await ghAsync(parentJobLogArgs(jobId, repository));
} catch (error) {
if (!isUnknownAllowEscapeSequencesFlag(error)) {
throw error;
}
return ghAsync(parentJobLogArgs(jobId, repository, false));
}
}
function normalizeOptionalRunId(value, label) {
if (value === "") {
return "";
}
if (!/^[1-9][0-9]*$/u.test(String(value))) {
throw new Error(`${label} must be empty or a positive decimal run ID`);
}
return String(value);
}
function normalizeRequiredRunId(value, label) {
const runId = normalizeOptionalRunId(value, label);
if (!runId) {
throw new Error(`${label} is required`);
}
return runId;
}
function normalizeRepository(value) {
const repository = String(value ?? "");
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(repository)) {
throw new Error("repository must use the owner/name form");
}
return repository;
}
function normalizeWorkflowRef(value, label) {
const workflowRef = String(value ?? "");
const hasForbiddenCharacter = Array.from(workflowRef).some((character) => {
const codePoint = character.codePointAt(0) ?? 0;
return (
codePoint <= 0x1f ||
codePoint === 0x7f ||
character.trim() === "" ||
"~^:?*[\\".includes(character)
);
});
if (workflowRef.length === 0 || workflowRef.length > 255 || hasForbiddenCharacter) {
throw new Error(`${label} is invalid`);
}
return workflowRef;
}
function normalizeSha(value, label) {
const sha = String(value ?? "");
if (!/^[a-f0-9]{40}$/u.test(sha)) {
throw new Error(`${label} is invalid`);
}
return sha;
}
function normalizePositiveInteger(value, label) {
const number = Number(value);
if (!Number.isSafeInteger(number) || number < 1) {
throw new Error(`${label} must be a positive integer`);
}
return number;
}
function normalizeJsonObject(value, label) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`${label} is invalid`);
}
return value;
}
function normalizeExpectedRunAttempts(value) {
if (value === undefined) {
return undefined;
}
const entries = Object.entries(normalizeJsonObject(value, "expected run attempts"));
if (entries.length === 0 || entries.length > MAX_EXPECTED_RUN_ATTEMPTS) {
throw new Error(`expected run attempts must contain 1-${MAX_EXPECTED_RUN_ATTEMPTS} run IDs`);
}
return new Map(
entries.map(([runId, runAttempt]) => {
if (typeof runAttempt !== "number") {
throw new Error(`expected run ${runId} attempt must be a positive integer`);
}
return [
normalizeRequiredRunId(runId, "expected run ID"),
normalizePositiveInteger(runAttempt, `expected run ${runId} attempt`),
];
}),
);
}
function consumeExpectedRunAttempt(expectedRunAttempts, runId, runAttempt, label) {
if (expectedRunAttempts === undefined) {
return;
}
const expected = expectedRunAttempts.get(runId);
if (expected === undefined) {
throw new Error(`expected run attempts omitted ${label} run ID: ${runId}`);
}
expectedRunAttempts.delete(runId);
if (runAttempt !== expected) {
throw new Error(
`${label} run attempt changed: ${runId} expected ${expected}, observed ${runAttempt}`,
);
}
}
function normalizeManifestChildEvidence(value) {
if (value === undefined) {
return undefined;
}
const evidence = normalizeJsonObject(value, "release validation manifest child evidence");
return Object.fromEntries(
Object.entries(evidence)
.toSorted(([left], [right]) => left.localeCompare(right))
.map(([key, raw]) => {
const child = normalizeJsonObject(raw, `release validation child evidence ${key}`);
const plannedRunAttempt = normalizePositiveInteger(
child.plannedRunAttempt,
`${key} planned run attempt`,
);
const effectiveRunAttempt = normalizePositiveInteger(
child.effectiveRunAttempt,
`${key} effective run attempt`,
);
if (effectiveRunAttempt < plannedRunAttempt) {
throw new Error(`release validation child attempt regressed: ${key}`);
}
const observedRunAttempts = Array.isArray(child.observedRunAttempts)
? child.observedRunAttempts.map((attempt) =>
normalizePositiveInteger(attempt, `${key} observed run attempt`),
)
: [];
const expectedAttempts = Array.from(
{ length: effectiveRunAttempt - plannedRunAttempt + 1 },
(_, index) => plannedRunAttempt + index,
);
if (JSON.stringify(observedRunAttempts) !== JSON.stringify(expectedAttempts)) {
throw new Error(`release validation child attempt evidence is gapped: ${key}`);
}
if (!Array.isArray(child.jobs) || child.jobs.length === 0) {
throw new Error(`release validation child jobs are missing: ${key}`);
}
const jobs = child.jobs.map((rawJob) => {
const job = normalizeJsonObject(rawJob, `release validation child job ${key}`);
const acceptedRunAttempt = normalizePositiveInteger(
job.acceptedRunAttempt,
`${key} accepted run attempt`,
);
if (acceptedRunAttempt < plannedRunAttempt || acceptedRunAttempt > effectiveRunAttempt) {
throw new Error(`release validation child job attempt is invalid: ${key}`);
}
const name = String(job.name ?? "");
if (!name) {
throw new Error(`release validation child job identity is invalid: ${key}`);
}
return {
acceptedRunAttempt,
completedAt: String(job.completedAt ?? ""),
conclusion: String(job.conclusion ?? ""),
name,
startedAt: String(job.startedAt ?? ""),
status: String(job.status ?? ""),
url: String(job.url ?? ""),
};
});
if (
new Set(jobs.map((job) => job.name)).size !== jobs.length ||
jobs.some(
(job, index) => index > 0 && compareReleaseJobsByName(jobs[index - 1], job) >= 0,
)
) {
throw new Error(`release validation child job identity is duplicated: ${key}`);
}
const composite = { effectiveRunAttempt, jobs, plannedRunAttempt };
const compositeJobsSha256 = String(child.compositeJobsSha256 ?? "");
if (
!/^[a-f0-9]{64}$/u.test(compositeJobsSha256) ||
releaseCompositeJobsSha256(composite) !== compositeJobsSha256
) {
throw new Error(`release validation child composite digest is invalid: ${key}`);
}
const dispatchActor = String(child.dispatchActor ?? "");
const triggeringActor = String(child.triggeringActor ?? "");
const repository = String(child.repository ?? "");
if (
dispatchActor !== "github-actions[bot]" ||
!triggeringActor ||
!/^[^/]+\/[^/]+$/u.test(repository) ||
(effectiveRunAttempt === plannedRunAttempt && triggeringActor !== "github-actions[bot]")
) {
throw new Error(`release validation child rerun provenance is invalid: ${key}`);
}
return [
key,
{
...composite,
compositeJobsSha256,
dispatchActor,
observedRunAttempts,
repository,
runId: normalizeRequiredRunId(child.runId, `${key} run ID`),
triggeringActor,
},
];
}),
);
}
function manifestEvidenceIdentity(manifest) {
return sortReleaseJsonValueKeys({
sourceAdmission: publicationSourceReuseIdentity(manifest.sourceAdmission) ?? null,
childRunIds: manifest.childRunIds,
controls: manifest.controls,
releaseProfile: manifest.releaseProfile,
rerunGroup: manifest.rerunGroup,
runReleaseSoak: manifest.runReleaseSoak,
validationInputs: manifest.validationInputs,
});
}
export function validateParentManifest(value, expected) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error("release validation manifest must be an object");
}
validateRetiredReleaseRetryFields(value);
if (![2, 3, 4].includes(value.version) || value.workflowName !== "Full Release Validation") {
throw new Error("release validation manifest schema is unsupported");
}
if (String(value.runId) !== String(expected.runId)) {
throw new Error("release validation manifest run ID mismatch");
}
if (
!/^[1-9][0-9]*$/u.test(String(value.runAttempt)) ||
(expected.runAttempt !== undefined && Number(value.runAttempt) !== Number(expected.runAttempt))
) {
throw new Error("release validation manifest run attempt mismatch");
}
const targetSha = normalizeSha(value.targetSha, "release validation manifest target SHA");
if (typeof value.workflowRef !== "string" || value.workflowRef.length === 0) {
throw new Error("release validation manifest workflow ref is invalid");
}
if (expected.workflowRef !== undefined && value.workflowRef !== expected.workflowRef) {
throw new Error("release validation manifest workflow ref mismatch");
}
let workflowSha;
let workflowFullRef;
let workflowRefType;
if (value.version >= 3) {
workflowSha = normalizeSha(value.workflowSha, "release validation manifest workflow SHA");
if (expected.workflowSha !== undefined && workflowSha !== expected.workflowSha) {
throw new Error("release validation manifest workflow SHA mismatch");
}
workflowFullRef = String(value.workflowFullRef ?? "");
workflowRefType = String(value.workflowRefType ?? "");
if (
!["branch", "tag"].includes(workflowRefType) ||
workflowFullRef !==
`refs/${workflowRefType === "branch" ? "heads" : "tags"}/${value.workflowRef}`
) {
throw new Error("release validation manifest workflow full ref is invalid");
}
} else if (expected.workflowSha !== undefined) {
workflowSha = normalizeSha(expected.workflowSha, "release validation workflow SHA");
}
const rerunGroup = String(value.rerunGroup ?? "");
requiredChildKeysForManifest({ rerunGroup, version: value.version });
const releaseProfile = String(value.releaseProfile ?? "");
if (!["beta", "stable", "full"].includes(releaseProfile)) {
throw new Error("release validation manifest release profile is invalid");
}
const candidateBinding =
value.candidateBinding === undefined || value.candidateBinding === null
? null
: validateFullReleaseCandidateBinding(value.candidateBinding);
if (
candidateBinding !== null &&
(candidateBinding.request.targetSha !== targetSha ||
candidateBinding.request.toolingSha !== workflowSha ||
candidateBinding.request.releaseProfile !== releaseProfile ||
(expected.repository !== undefined &&
candidateBinding.request.repository !== expected.repository))
) {
throw new Error("release validation manifest candidate binding is invalid");
}
if (
Object.hasOwn(expected, "candidateBinding") &&
JSON.stringify(sortReleaseJsonValueKeys(candidateBinding)) !==
JSON.stringify(
sortReleaseJsonValueKeys(
expected.candidateBinding === null
? null
: validateFullReleaseCandidateBinding(expected.candidateBinding),
),
)
) {
throw new Error("release validation manifest candidate differs from the immutable plan");
}
const runReleaseSoak = String(value.runReleaseSoak ?? "");
if (!["true", "false"].includes(runReleaseSoak)) {
throw new Error("release validation manifest release soak value is invalid");
}
const controls = normalizeJsonObject(value.controls, "release validation manifest controls");
if (value.version >= 3 && controls.performanceReportPublication !== "artifact-only") {
throw new Error("release validation manifest performance report publication mode is invalid");
}
const validationInputs =
value.validationInputs === undefined
? undefined
: normalizeJsonObject(
value.validationInputs,
"release validation manifest validation inputs",
);
if (
Object.hasOwn(validationInputs ?? {}, "knownFlakyJobsJson") &&
validationInputs.knownFlakyJobsJson !== "[]"
) {
throw new Error("release validation manifest knownFlakyJobsJson must be empty");
}
const sourceAdmission = validatePublicationSourceBinding(value, expected);
const publicationAdmission = validatePublicationAdmissionBinding(value, expected);
const publishInputs =
value.publishInputs === undefined ? undefined : resolveReleasePublishInputs(value);
normalizeReleaseTelegramWaiver({
...validationInputs,
candidateVersion: candidateBinding?.package.version,
releaseProfile,
rerunGroup: value.rerunGroup,
});
if (validationInputs?.coveragePolicy !== undefined && value.version !== 4) {
throw new Error("release coverage policy requires a version 4 manifest");
}
const coveragePolicy = normalizeReleaseCoveragePolicy({
...validationInputs,
candidateVersion: candidateBinding?.package.version,
releaseProfile,
rerunGroup,
runReleaseSoak,
});
if (
coveragePolicy === "npm-stable-v1" &&
(!resolveReleaseContextIdentity(
validationInputs.targetContextRef || String(value.targetRef ?? ""),
validationInputs.targetVersion,
) ||
controls.performanceBlocking !== true ||
controls.stableSoakRequired !== true)
) {
throw new Error(
"npm stable coverage policy requires release context, blocking performance, and stable soak",
);
}
const childEvidence = normalizeManifestChildEvidence(value.childEvidence);
if (
validationInputs?.laneWaiver ||
value.publishInputs?.stableSoakWaiver ||
(value.advisoryJobs !== undefined &&
(!Array.isArray(value.advisoryJobs) || value.advisoryJobs.length > 0))
) {
throw new Error(
"Waived or advisory release evidence is no longer accepted; rerun Full Release Validation without waivers.",
);
}
const advisoryJobs = [];
const childRuns = value.childRuns;
if (!childRuns || typeof childRuns !== "object" || Array.isArray(childRuns)) {
throw new Error("release validation manifest childRuns is invalid");
}
const childRunIds =
value.version === 4
? {
normalCi: normalizeOptionalRunId(childRuns.normalCi, "normal CI run ID"),
npmTelegram: normalizeOptionalRunId(childRuns.npmTelegram, "npm Telegram run ID"),
pluginPrereleaseIndependent: normalizeOptionalRunId(
childRuns.pluginPrereleaseIndependent,
"plugin prerelease independent run ID",
),
pluginPrereleaseCandidate: normalizeOptionalRunId(
childRuns.pluginPrereleaseCandidate,
"plugin prerelease candidate run ID",
),
productPerformance: normalizeOptionalRunId(
childRuns.productPerformance?.runId ?? "",
"performance run ID",
),
releaseChecksIndependent: normalizeOptionalRunId(
childRuns.releaseChecksIndependent,
"release checks independent run ID",
),
releaseChecksCandidate: normalizeOptionalRunId(
childRuns.releaseChecksCandidate,
"release checks candidate run ID",
),
}
: {
normalCi: normalizeOptionalRunId(childRuns.normalCi, "normal CI run ID"),
npmTelegram: normalizeOptionalRunId(childRuns.npmTelegram, "npm Telegram run ID"),
pluginPrerelease: normalizeOptionalRunId(
childRuns.pluginPrerelease,
"plugin prerelease run ID",
),
productPerformance: normalizeOptionalRunId(
childRuns.productPerformance?.runId ?? "",
"performance run ID",
),
releaseChecks: normalizeOptionalRunId(childRuns.releaseChecks, "release checks run ID"),
};
if (
coveragePolicy === "npm-beta-v1" &&
(childRunIds.productPerformance ||
childRunIds.npmTelegram ||
controls.performanceBlocking !== false ||
validationInputs.skipPackageTelegramE2e !== "true")
) {
throw new Error("npm beta coverage policy requires deferred confidence children");
}
let evidenceReuse;
if (value.evidenceReuse !== undefined) {
const reuse = normalizeJsonObject(
value.evidenceReuse,
"release validation manifest evidence reuse",
);
if (!EVIDENCE_REUSE_POLICIES.has(reuse.policy)) {
throw new Error("release validation manifest evidence reuse policy is invalid");
}
if (!/^[a-f0-9]{40}$/u.test(String(reuse.evidenceSha))) {
throw new Error("release validation manifest evidence SHA is invalid");
}
if (
!Array.isArray(reuse.changedPaths) ||
reuse.changedPaths.some(
(changedPath) => typeof changedPath !== "string" || changedPath.length === 0,
) ||
new Set(reuse.changedPaths).size !== reuse.changedPaths.length
) {
throw new Error("release validation manifest evidence changed paths are invalid");
}
evidenceReuse = {
changedPaths: reuse.changedPaths,
evidenceSha: String(reuse.evidenceSha),
policy: reuse.policy,
runId: normalizeRequiredRunId(reuse.runId, "evidence reuse root run ID"),
selectedRunId: normalizeRequiredRunId(reuse.selectedRunId, "evidence reuse selected run ID"),
};
if (reuse.publication !== undefined) {
const publication = normalizeJsonObject(reuse.publication, "retained root publication");
if (
Object.keys(publication).toSorted().join(",") !==
"publicationAdmission,publicationAdmissionContract,sourceAdmission,sourceAdmissionContract"
) {
throw new Error("retained root publication fields are invalid");
}
validatePublicationAdmissionBinding(publication, { publicationAdmissionContract: "1" });
if (
publication.sourceAdmission.runId !== evidenceReuse.runId ||
publication.sourceAdmission.candidateSha !== evidenceReuse.evidenceSha
) {
throw new Error("retained root publication identity mismatch");
}
evidenceReuse.publication = publication;
}
}
return {
advisoryJobs,
...(publishInputs ? { publishInputs } : {}),
...(Object.hasOwn(value, "knownFlakyJobs") ? { knownFlakyJobs: value.knownFlakyJobs } : {}),
...(Object.hasOwn(value, "automaticRetries")
? { automaticRetries: value.automaticRetries }
: {}),
...(value.publicationAdmissionContract !== undefined
? { publicationAdmissionContract: value.publicationAdmissionContract, publicationAdmission }
: {}),
...(sourceAdmission
? {
sourceAdmissionContract: value.sourceAdmissionContract,
sourceAdmission,
trustedWorkflow: value.trustedWorkflow,
sourceParentRunAttempt: value.sourceParentRunAttempt,
}
: {}),
candidateBinding,
childEvidence,
childRunIds,
controls,
evidenceReuse,
releaseProfile,
rerunGroup,
runAttempt: Number(value.runAttempt),
runId: String(value.runId),
runReleaseSoak,
targetRef: String(value.targetRef ?? ""),
targetSha,
validationInputs,
version: value.version,
workflowFullRef,
workflowSha,
workflowRef: value.workflowRef,
workflowRefType,
};
}
export function validateEvidenceReuseChain(
currentManifest,
selectedManifest,
rootManifest,
compareCommits,
) {
const reuse = currentManifest.evidenceReuse;
if (!reuse) {
throw new Error("release validation manifest does not authorize evidence reuse");
}
if (rootManifest.evidenceReuse || selectedManifest.evidenceReuse) {
throw new Error("evidence reuse must select a root execution manifest");
}
if (
!currentManifest.validationInputs ||
!selectedManifest.validationInputs ||
!rootManifest.validationInputs
) {
throw new Error("evidence reuse manifests must record validation inputs");
}
if (rootManifest.runId !== reuse.runId) {
throw new Error("evidence reuse root manifest run ID mismatch");
}
if (selectedManifest.runId !== reuse.selectedRunId) {
throw new Error("evidence reuse selected manifest run ID mismatch");
}
if (selectedManifest.targetSha !== reuse.evidenceSha) {
throw new Error("evidence reuse selected manifest SHA mismatch");
}
if (rootManifest.targetSha !== reuse.evidenceSha) {
throw new Error("full release evidence reuse root SHA mismatch");
}
if (selectedManifest.runId !== rootManifest.runId) {
throw new Error("evidence reuse selected manifest is not the chain root");
}
const rootPublication =
rootManifest.publicationAdmissionContract === "1"
? {
sourceAdmissionContract: rootManifest.sourceAdmissionContract,
sourceAdmission: rootManifest.sourceAdmission,
publicationAdmissionContract: rootManifest.publicationAdmissionContract,
publicationAdmission: rootManifest.publicationAdmission,
}
: null;
if (
publicationObservationJson(reuse.publication ?? null) !==
publicationObservationJson(rootPublication)
) {
throw new Error("retained root publication differs from authenticated root evidence");
}
if (reuse.policy === EXACT_TARGET_EVIDENCE_REUSE_POLICY) {
if (reuse.changedPaths.length !== 0 || currentManifest.targetSha !== reuse.evidenceSha) {
throw new Error("exact-target release evidence reuse requires no changed paths");
}
} else if (
reuse.policy === CHANGELOG_ONLY_EVIDENCE_REUSE_POLICY ||
reuse.policy === SPLIT_CHANGELOG_EVIDENCE_REUSE_POLICY
) {
const split = reuse.policy === SPLIT_CHANGELOG_EVIDENCE_REUSE_POLICY;
const version =
currentManifest.candidateBinding?.package?.version ??
currentManifest.validationInputs?.targetVersion;
if (
(split
? !isSplitChangelogEvidenceDelta(reuse.changedPaths, version)
: reuse.changedPaths.length !== 1 || reuse.changedPaths[0] !== "CHANGELOG.md") ||
currentManifest.targetSha === reuse.evidenceSha
) {
throw new Error("changelog-only release evidence reuse has an invalid target delta");
}
if (typeof compareCommits !== "function") {
throw new Error("changelog-only release evidence reuse requires commit comparison");
}
const comparison = compareCommits(reuse.evidenceSha, currentManifest.targetSha);
const verified = classifyReleaseChangelogEvidenceComparison(comparison, {
baseSha: reuse.evidenceSha,
version,
});
if (
verified.policy !== reuse.policy ||
verified.changedPaths.length !== reuse.changedPaths.length ||
verified.changedPaths.some((name) => !reuse.changedPaths.includes(name))
) {
throw new Error("changelog-only release evidence reuse failed commit comparison");
}
} else {
throw new Error("release validation manifest evidence reuse policy is invalid");
}
const rootIdentity = JSON.stringify(manifestEvidenceIdentity(rootManifest));
for (const [label, manifest] of [
["selected", selectedManifest],
["current", currentManifest],
]) {
if (JSON.stringify(manifestEvidenceIdentity(manifest)) !== rootIdentity) {
throw new Error(`evidence reuse ${label} manifest policy differs from the chain root`);
}
}
return rootManifest.targetSha;
}
export function validateRequestedEvidenceReuse(
currentManifest,
selectedManifest,
rootManifest,
{
expectedChangedPaths,
expectedEvidencePolicy,
expectedEvidenceSha,
expectedRootRunId,
expectedSelectedRunId,
expectedTargetSha,
},
compareCommits,
) {
if (
!Array.isArray(expectedChangedPaths) ||
expectedChangedPaths.some(
(changedPath) => typeof changedPath !== "string" || changedPath.length === 0,
) ||
new Set(expectedChangedPaths).size !== expectedChangedPaths.length
) {
throw new Error("expected evidence changed paths are invalid");
}
const requested = {
changedPaths: expectedChangedPaths,
evidenceSha: normalizeSha(expectedEvidenceSha, "expected evidence SHA"),
policy: String(expectedEvidencePolicy ?? ""),
runId: normalizeRequiredRunId(expectedRootRunId, "expected evidence root run ID"),
selectedRunId: normalizeRequiredRunId(
expectedSelectedRunId,
"expected evidence selected run ID",
),
};
const expectedTarget = normalizeSha(expectedTargetSha, "expected target SHA");
const reuse = currentManifest.evidenceReuse;
if (!reuse) {
if (
currentManifest.runId !== requested.selectedRunId ||
selectedManifest.runId !== requested.selectedRunId ||
rootManifest.runId !== requested.runId
) {
throw new Error("reused release evidence no longer matches the requested validation");
}
validateEvidenceReuseChain(
{ ...currentManifest, evidenceReuse: requested, targetSha: expectedTarget },
selectedManifest,
rootManifest,
compareCommits,
);
return;
}
if (
currentManifest.targetSha !== expectedTarget ||
selectedManifest.runId !== requested.selectedRunId ||
rootManifest.runId !== requested.runId ||
rootManifest.targetSha !== requested.evidenceSha ||
reuse.evidenceSha !== requested.evidenceSha ||
reuse.policy !== requested.policy ||
reuse.runId !== requested.runId ||
reuse.selectedRunId !== requested.selectedRunId ||
JSON.stringify(reuse.changedPaths) !== JSON.stringify(requested.changedPaths)
) {
throw new Error("reused release evidence no longer matches the requested validation");
}
}
function hasRequestedEvidenceReuse(options) {
return [
options.expectedTargetSha,
options.expectedEvidencePolicy,
options.expectedEvidenceSha,
options.expectedChangedPaths,
options.expectedRootRunId,
options.expectedSelectedRunId,
].some((value) => value !== undefined);
}
export function selectedChildKeys(parentJobs) {
return new Set(
[...LEGACY_CHILD_DISPATCHES, ...PHASED_CHILD_DISPATCHES]
.filter((child) => {
const parentJob = parentJobs.find((job) => job.name === child.parentJobName);
return parentJob && parentJob.conclusion !== "skipped";
})
.map((child) => child.manifestKey),
);
}
/**
* @template {{ manifestKey: string, name: string }} Child
* @param {{ childRunIds: Partial<Record<string, string>> }} manifest
* @param {Child[]} children
* @param {Set<string>} selectedKeys
* @returns {Array<{ child: Child, runId: string }>}
*/
export function manifestChildEntries(manifest, children, selectedKeys) {
return children.flatMap((child) => {
const runId = manifest.childRunIds[child.manifestKey];
if (!runId) {
if (selectedKeys.has(child.manifestKey)) {
throw new Error(`selected child is missing from manifest: ${child.name}`);
}
return [];
}
return [{ child, runId }];
});
}
function childDispatchAttempt(displayTitle, child, parentRunId, parentRunAttempt) {
const prefix = `${child.name} full-release-validation-${parentRunId}-`;
if (!displayTitle.startsWith(prefix) || !displayTitle.endsWith(child.suffix)) {
return undefined;
}
const attemptEnd = child.suffix ? -child.suffix.length : undefined;
const attemptText = displayTitle.slice(prefix.length, attemptEnd);
if (!/^[1-9][0-9]*$/u.test(attemptText)) {
return undefined;
}
const attempt = Number(attemptText);
if (!Number.isSafeInteger(attempt) || attempt > parentRunAttempt) {
return undefined;
}
return attempt;
}
function parentJobExecutionFingerprint(job) {
return sortReleaseJsonValueKeys({
completedAt: job.completed_at,
conclusion: job.conclusion,
name: job.name,
startedAt: job.started_at,
status: job.status,
steps: (job.steps ?? []).map((step) => ({
completedAt: step.completed_at,
conclusion: step.conclusion,
name: step.name,
number: step.number,
startedAt: step.started_at,
status: step.status,
})),
});
}
function selectedAttemptParentJob(parentJobs, child, parentManifest) {
const slotJobs = parentJobs.filter((job) => job.name === child.parentJobName);
if (slotJobs.length === 0) {
throw new Error(`manifest parent job is missing: ${child.name}`);
}
const latestAttempt = Math.max(...slotJobs.map((job) => Number(job.run_attempt)));
if (latestAttempt !== parentManifest.runAttempt) {
throw new Error(`manifest parent job latest attempt mismatch: ${child.name}`);
}
const currentJobs = slotJobs.filter(
(job) => Number(job.run_attempt) === parentManifest.runAttempt,
);
if (currentJobs.length !== 1) {
throw new Error(`manifest parent job is not unique at the selected attempt: ${child.name}`);
}
return { currentJob: currentJobs[0], slotJobs };
}
export function resolveManifestChildOriginAttempt(run, child, parentManifest, parentJobs) {
const correlatedAttempt = childDispatchAttempt(
String(run.display_title ?? ""),
child,
parentManifest.runId,
parentManifest.runAttempt,
);
if (correlatedAttempt !== undefined) {
return correlatedAttempt;
}
if (run.display_title !== child.name) {
return undefined;
}
const { currentJob, slotJobs } = selectedAttemptParentJob(parentJobs, child, parentManifest);
if (currentJob.status !== "completed" || currentJob.conclusion !== "success") {
throw new Error(`manifest parent job is not completed/success: ${child.name}`);
}
const currentFingerprint = JSON.stringify(parentJobExecutionFingerprint(currentJob));
const carriedOriginAttempts = slotJobs
.filter(
(job) =>
Number(job.run_attempt) < parentManifest.runAttempt &&
job.status === "completed" &&
job.conclusion === "success" &&
JSON.stringify(parentJobExecutionFingerprint(job)) === currentFingerprint,
)
.map((job) => Number(job.run_attempt));
return carriedOriginAttempts.length > 0
? Math.min(...carriedOriginAttempts)
: parentManifest.runAttempt;
}
export function selectManifestParentJob(
parentJobs,
child,
parentManifest,
originAttempt,
options = {},
) {
const { currentJob, slotJobs } = selectedAttemptParentJob(parentJobs, child, parentManifest);
const originJobs = slotJobs.filter((job) => Number(job.run_attempt) === originAttempt);
if (originJobs.length !== 1) {
throw new Error(`manifest parent job origin is not unique: ${child.name}`);
}
const originJob = originJobs[0];
if (originJob.status !== "completed" || originJob.conclusion !== "success") {
throw new Error(`manifest parent job origin is not completed/success: ${child.name}`);
}
if (originAttempt === parentManifest.runAttempt) {
return originJob;
}
if (originAttempt > parentManifest.runAttempt) {
throw new Error(`manifest parent job origin attempt is invalid: ${child.name}`);
}
if (options.requireSkippedCarryForward === true) {
for (let attempt = originAttempt + 1; attempt <= parentManifest.runAttempt; attempt += 1) {
const carriedJobs = slotJobs.filter((job) => Number(job.run_attempt) === attempt);
if (carriedJobs.length !== 1) {
throw new Error(`manifest parent job carry-forward is not unique: ${child.name}`);
}
const carriedJob = carriedJobs[0];
if (carriedJob.status !== "completed" || carriedJob.conclusion !== "skipped") {
throw new Error(`manifest parent job was redispatched during recovery: ${child.name}`);
}
}
return originJob;
}
if (currentJob.status !== "completed" || currentJob.conclusion !== "success") {
throw new Error(`manifest parent job is not completed/success: ${child.name}`);
}
if (
JSON.stringify(parentJobExecutionFingerprint(currentJob)) !==
JSON.stringify(parentJobExecutionFingerprint(originJob))
) {
throw new Error(`manifest parent job carry-forward fingerprint mismatch: ${child.name}`);
}
return currentJob;
}
export function validateManifestChildRun(
run,
child,
runId,
parentManifest,
parentJobs,
selectedParentJobLog,
repository,
plannedRunAttempt,
requireSkippedCarryForward = false,
) {
const targetRepository = repository ?? DEFAULT_REPO;
if (String(run.id) !== String(runId)) {
throw new Error(`manifest child run ID mismatch: ${child.name}`);
}
const originAttempt = resolveManifestChildOriginAttempt(run, child, parentManifest, parentJobs);
if (plannedRunAttempt !== undefined) {
validateReleaseChildRunProvenance(run, {
displayTitle: child.displayTitle,
key: child.manifestKey,
plannedRunAttempt,
repository: targetRepository,
runId,
workflow: child.workflow,
workflowRef: child.headBranch,
workflowSha: parentManifest.workflowSha,
});
} else if (
run.event !== "workflow_dispatch" ||
run.head_branch !== child.headBranch ||
(child.trustedRef === "parent" && run.head_sha !== parentManifest.workflowSha) ||
!/^[a-f0-9]{40}$/u.test(String(run.head_sha)) ||
run.actor?.login !== "github-actions[bot]" ||
run.triggering_actor?.login !== "github-actions[bot]" ||
!Number.isSafeInteger(Number(run.run_attempt)) ||
Number(run.run_attempt) < 1
) {
throw new Error(`manifest child dispatch tuple mismatch: ${child.name}`);
}
if (originAttempt === undefined) {
throw new Error(`manifest child dispatch tuple mismatch: ${child.name}`);
}
const childWorkflowPath = String(run.path ?? "").split("@", 1)[0];
if (childWorkflowPath !== `.github/workflows/${child.workflow}`) {
throw new Error(`manifest child workflow mismatch: ${child.name}`);
}
selectManifestParentJob(parentJobs, child, parentManifest, originAttempt, {
requireSkippedCarryForward,
});
validateReleaseChildDispatchBinding({
child: {
key: child.manifestKey,
runId,
},
log: selectedParentJobLog,
coveragePolicy: parentManifest.validationInputs?.coveragePolicy,
plannedRunAttempt: plannedRunAttempt ?? run.run_attempt,
repository: targetRepository,
targetSha: parentManifest.targetSha,
});
return run;
}
export function validatePerformanceArtifactOnlyJobs(jobs) {
const guards = jobs.filter((job) => job.name === "Verify artifact-only report mode");
if (
guards.length !== 1 ||
guards[0].status !== "completed" ||
guards[0].conclusion !== "success"
) {
throw new Error("performance artifact-only guard is missing or unsuccessful");
}
const unsafePublisher = jobs.find(
(job) =>
String(job.name ?? "").startsWith("Publish ") &&
String(job.name ?? "").endsWith(" report") &&
job.conclusion !== "skipped",
);
if (unsafePublisher) {
throw new Error(`performance report publisher was not skipped: ${unsafePublisher.name}`);
}
return guards[0];
}
function manifestArtifactName(runId, runAttempt) {
const normalizedRunId = normalizeRequiredRunId(runId, "full release run ID");
const normalizedRunAttempt = normalizePositiveInteger(runAttempt, "full release run attempt");
return `full-release-validation-${normalizedRunId}-${normalizedRunAttempt}`;
}
function legacyManifestArtifactName(runId) {
return `full-release-validation-${normalizeRequiredRunId(runId, "full release run ID")}`;
}
export function validateManifestArtifactIdentity(
artifact,
{ artifactDigest, artifactId, runAttempt, runId },
) {
const normalizedArtifactId = normalizeRequiredRunId(artifactId, "manifest artifact ID");
const normalizedRunId = normalizeRequiredRunId(runId, "full release run ID");
const normalizedRunAttempt = normalizePositiveInteger(runAttempt, "full release run attempt");
const normalizedDigest = String(artifactDigest ?? "");
if (!/^sha256:[a-f0-9]{64}$/u.test(normalizedDigest)) {
throw new Error(`release validation manifest artifact digest is invalid: ${normalizedRunId}`);
}
const canonicalName = manifestArtifactName(normalizedRunId, normalizedRunAttempt);
const legacyName = legacyManifestArtifactName(normalizedRunId);
const validName =
artifact.name === canonicalName || (normalizedRunAttempt === 1 && artifact.name === legacyName);
if (
String(artifact.id) !== normalizedArtifactId ||
!validName ||
artifact.digest !== normalizedDigest ||
artifact.expired !== false ||
String(artifact.workflow_run?.id) !== normalizedRunId ||
!Number.isSafeInteger(Number(artifact.size_in_bytes)) ||
Number(artifact.size_in_bytes) < 1
) {
throw new Error(`release validation manifest artifact identity mismatch: ${normalizedRunId}`);
}
return artifact;
}
export function selectManifestArtifact(artifacts, runId, runAttempt) {
const expectedName = manifestArtifactName(runId, runAttempt);
const canonicalMatches = artifacts.filter(
(artifact) =>
artifact.name === expectedName &&
artifact.expired === false &&
String(artifact.workflow_run?.id) === String(runId),
);
if (canonicalMatches.length > 1) {
throw new Error(`multiple release validation manifest artifacts found: ${runId}`);
}
const canonicalArtifact = canonicalMatches[0];
if (canonicalArtifact) {
return validateManifestArtifactIdentity(canonicalArtifact, {
artifactDigest: canonicalArtifact.digest,
artifactId: canonicalArtifact.id,
runAttempt,
runId,
});
}
const legacyName = legacyManifestArtifactName(runId);
const legacyMatches = artifacts.filter(
(artifact) =>
artifact.name === legacyName &&
artifact.expired === false &&
String(artifact.workflow_run?.id) === String(runId),
);
if (legacyMatches.length > 1) {
throw new Error(`multiple legacy release validation manifest artifacts found: ${runId}`);
}
const legacyArtifact = legacyMatches[0];
if (!legacyArtifact) {
return undefined;
}
if (Number(runAttempt) !== 1) {
throw new Error(`legacy release validation manifest requires run attempt 1: ${runId}`);
}
return validateManifestArtifactIdentity(legacyArtifact, {
artifactDigest: legacyArtifact.digest,
artifactId: legacyArtifact.id,
runAttempt,
runId,
});
}
export function validateManifestArtifactCompatibility(artifact, manifest, runId, runAttempt) {
if (artifact.name === manifestArtifactName(runId, runAttempt)) {
return artifact;
}
if (
Number(runAttempt) === 1 &&
artifact.name === legacyManifestArtifactName(runId) &&
manifest?.version === 2
) {
return artifact;
}
throw new Error(`legacy release validation manifest artifact is not compatible: ${runId}`);
}
export function readManifestArtifactArchive(archivePath, expectedDigest) {
const archiveSize = statSync(archivePath).size;
if (
!Number.isSafeInteger(archiveSize) ||
archiveSize < 1 ||
archiveSize > MAX_MANIFEST_ARTIFACT_ZIP_BYTES
) {
throw new Error("release validation manifest artifact compressed size is invalid");
}
const archiveBytes = readFileSync(archivePath);
if (archiveBytes.byteLength !== archiveSize) {
throw new Error("release validation manifest artifact changed while being verified");
}
const actualDigest = `sha256:${createHash("sha256").update(archiveBytes).digest("hex")}`;
if (actualDigest !== expectedDigest) {
throw new Error("release validation manifest artifact digest mismatch");
}
let entryList;
try {
entryList = execFileSync("unzip", ["-Z", "-1", archivePath], {
encoding: "utf8",
maxBuffer: MAX_MANIFEST_ENTRY_LIST_BYTES,
stdio: ["ignore", "pipe", "pipe"],
});
} catch {
throw new Error("release validation manifest artifact entry list is invalid");
}
const entries = entryList.split(/\r?\n/u).filter((entry) => entry.length > 0);
if (entries.length !== 1 || entries[0] !== MANIFEST_ARTIFACT_ENTRY) {
throw new Error(
`release validation manifest artifact must contain only ${MANIFEST_ARTIFACT_ENTRY}`,
);
}
let manifestBytes;
try {
manifestBytes = execFileSync("unzip", ["-p", archivePath, MANIFEST_ARTIFACT_ENTRY], {
maxBuffer: MAX_RELEASE_ARTIFACT_BYTES + 1,
stdio: ["ignore", "pipe", "pipe"],
});
} catch {
throw new Error("release validation manifest artifact entry could not be read safely");
}
if (manifestBytes.byteLength < 1 || manifestBytes.byteLength > MAX_RELEASE_ARTIFACT_BYTES) {
throw new Error("release validation manifest artifact entry size is invalid");
}
return JSON.parse(manifestBytes.toString("utf8"));
}
function downloadParentManifestEvidence(runId, runAttempt, repository, manifestPath) {
const targetRepository = repository ?? DEFAULT_REPO;
const artifacts = [];
for (let page = 1; page <= 10; page += 1) {
const pageArtifacts =
githubRestJson(`actions/runs/${runId}/artifacts?per_page=100&page=${page}`, targetRepository)
.artifacts ?? [];
artifacts.push(...pageArtifacts);
if (pageArtifacts.length < 100) {
break;
}
}
const listedArtifact = selectManifestArtifact(artifacts, runId, runAttempt);
if (!listedArtifact) {
return undefined;
}
const artifact = validateManifestArtifactIdentity(
githubRestJson(`actions/artifacts/${listedArtifact.id}`, targetRepository),
{
artifactDigest: listedArtifact.digest,
artifactId: listedArtifact.id,
runAttempt,
runId,
},
);
const downloadDir = mkdtempSync(join(tmpdir(), "openclaw-release-ci-summary-"));
try {
const archivePath = join(downloadDir, "manifest.zip");
downloadArtifactZip(String(artifact.id), archivePath, artifact.size_in_bytes, targetRepository);
const manifest = readManifestArtifactArchive(archivePath, artifact.digest);
validateManifestArtifactCompatibility(artifact, manifest, runId, runAttempt);
if (manifestPath) {
const providedManifest = JSON.parse(readFileSync(resolve(manifestPath), "utf8"));
if (
JSON.stringify(sortReleaseJsonValueKeys(providedManifest)) !==
JSON.stringify(sortReleaseJsonValueKeys(manifest))
) {
throw new Error("provided release validation manifest differs from the run artifact");
}
}
return { artifact, manifest };
} finally {
rmSync(downloadDir, { force: true, recursive: true });
}
}
function tryDownloadParentManifest(runId, runAttempt, repository = DEFAULT_REPO) {
return downloadParentManifestEvidence(runId, runAttempt, repository)?.manifest;
}
function workflowPath(run) {
return String(run.path ?? "").split("@", 1)[0];
}
function normalizedManifestArtifact(artifact, runAttempt) {
return {
digest: artifact.digest,
id: String(artifact.id),
name: artifact.name,
runAttempt,
sizeInBytes: Number(artifact.size_in_bytes),
};
}
function validateManifestArtifactBinding(artifact, manifest, parentRun, runId) {
validateManifestArtifactCompatibility(artifact, manifest, runId, parentRun.run_attempt);
if (
String(artifact.workflow_run?.id) !== String(runId) ||
artifact.workflow_run?.head_branch !== parentRun.head_branch ||
artifact.workflow_run?.head_sha !== parentRun.head_sha
) {
throw new Error(`release validation manifest artifact binding mismatch: ${runId}`);
}
}
function validateCompletedParentRun(parentView, parentRest, repository, runId) {
validateParentRunBinding(parentView, parentRest, runId);
if (
parentView.status !== "completed" ||
parentView.conclusion !== "success" ||
parentRest.status !== "completed" ||
parentRest.conclusion !== "success" ||
parentRest.repository?.full_name !== repository
) {
throw new Error(`full release parent run is not completed/success: ${runId}`);
}
}
export function createReleaseEvidenceClient(repository = DEFAULT_REPO) {
const normalizedRepository = normalizeRepository(repository);
return {
validateChildReuse(selection, request) {
return validateReusableReleaseChild(selection, request);
},
getWorkflowSource(sha) {
const exactSha = normalizeSha(sha, "source admission workflow SHA");
const payload = githubRestJson(
`contents/.github/workflows/full-release-validation.yml?ref=${exactSha}`,
normalizedRepository,
);
if (
payload?.type !== "file" ||
payload.encoding !== "base64" ||
payload.path !== ".github/workflows/full-release-validation.yml" ||
!Number.isSafeInteger(payload.size) ||
payload.size < 1 ||
payload.size > 1024 * 1024 ||
typeof payload.content !== "string" ||
payload.content.length > 2 * 1024 * 1024
) {
throw new Error("invalid immutable source-admission workflow response");
}
const bytes = Buffer.from(payload.content, "base64");
const blob = createHash("sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
if (bytes.length !== payload.size || blob !== payload.sha) {
throw new Error("source-admission workflow blob mismatch");
}
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
},
compareCommitLineage(base, head) {
return githubRestJson(`compare/${base}...${head}?per_page=1&page=2`, normalizedRepository);
},
compareCommits(base, head) {
return githubRestJson(`compare/${base}...${head}`, normalizedRepository);
},
getJobLog(jobId) {
return parentJobLog(jobId, normalizedRepository);
},
getParentJobs(runId) {
return findParentJobsAll(runId, normalizedRepository);
},
getRunAttemptJobs(runId, runAttempt, { requireComplete = false } = {}) {
return findRunAttemptJobsAll(runId, runAttempt, normalizedRepository, requireComplete);
},
getRunAttempt(runId, runAttempt) {
return githubRestJson(`actions/runs/${runId}/attempts/${runAttempt}`, normalizedRepository);
},
getArtifact(artifactId) {
if (!/^[1-9][0-9]{0,19}$/u.test(String(artifactId))) {
throw new Error("invalid publication artifact ID");
}
return githubRestJson(`actions/artifacts/${artifactId}`, normalizedRepository);
},
getRef(fullRef) {
const refPath = String(fullRef)
.replace(/^refs\//u, "")
.split("/")
.map(encodeURIComponent)
.join("/");
return githubRestJson(`git/ref/${refPath}`, normalizedRepository);
},
getRun(runId) {
return githubRestJsonAsync(`actions/runs/${runId}`, normalizedRepository);
},
getRunView(runId) {
return jsonGh([
"run",
"view",
String(runId),
"--repo",
normalizedRepository,
"--json",
"status,conclusion,attempt,headBranch,headSha,url",
]);
},
loadManifest(runId, runAttempt, manifestPath) {
return downloadParentManifestEvidence(runId, runAttempt, normalizedRepository, manifestPath);
},
loadExecutionPlan(runId) {
return tryDownloadExecutionPlan(runId, normalizedRepository);
},
loadExecutionPlanEvidence(runId) {
return readExecutionPlanEvidence(
normalizeRequiredRunId(runId, "publication parent run ID"),
normalizedRepository,
);
},
};
}
async function loadValidatedParentEvidence({
client,
expectedRunAttempts,
manifestPath,
repository,
runId,
}) {
const parentView = client.getRunView(runId);
const parentRun = await client.getRun(runId);
const parentRunAttempt = normalizePositiveInteger(
parentRun.run_attempt,
`full release parent ${runId} run attempt`,
);
consumeExpectedRunAttempt(expectedRunAttempts, runId, parentRunAttempt, "parent");
validateCompletedParentRun(parentView, parentRun, repository, runId);
const manifestEvidence = client.loadManifest(runId, parentRunAttempt, manifestPath);
if (!manifestEvidence) {
throw new ReleaseEvidenceRefreshRequiredError(
`successful parent run is missing its release validation manifest: ${runId}`,
);
}
const manifest = validateParentManifest(manifestEvidence.manifest, {
runAttempt: parentRun.run_attempt,
runId,
workflowRef: parentRun.head_branch,
workflowSha: parentRun.head_sha,
});
validateManifestArtifactBinding(manifestEvidence.artifact, manifest, parentRun, runId);
return {
artifact: manifestEvidence.artifact,
manifest,
manifestJson: sortReleaseJsonValueKeys(manifestEvidence.manifest),
parentRun,
parentView,
};
}
function resolveTrustedWorkflowIdentity(workflowRef, workflowFullRef, workflowSha) {
const fullRef = workflowFullRef ?? `refs/heads/${workflowRef}`;
const protectedTag = TRUSTED_RELEASE_PUBLISH_TAG_PATTERN.exec(fullRef);
if (protectedTag) {
if (workflowRef !== fullRef.slice("refs/tags/".length)) {
throw new Error("trusted workflow tag name does not match its full ref");
}
const sha = normalizeSha(workflowSha, "trusted workflow SHA");
if (sha.slice(0, 12) !== protectedTag[1]) {
throw new Error("trusted workflow tag does not match its workflow SHA");
}
return { fullRef, ref: workflowRef, sha, type: "tag" };
}
if (fullRef !== `refs/heads/${workflowRef}`) {
throw new Error("trusted workflow full ref does not match its ref");
}
if (workflowRef.startsWith("release-publish/")) {
throw new Error("trusted release-publish workflow ref must be a protected tag");
}
return { fullRef, ref: workflowRef, sha: undefined, type: "branch" };
}
function normalizeWorkflowPathRef(ref) {
if (!ref || ref.startsWith("refs/")) {
return ref;
}
return `refs/heads/${ref}`;
}
export function validateTrustedProducerIdentity(
evidence,
client,
verifier,
trustedWorkflowRef,
trustedWorkflowFullRef,
trustedWorkflowSha,
) {
const { manifest, parentRun } = evidence;
const trustedIdentity = resolveTrustedWorkflowIdentity(
trustedWorkflowRef,
trustedWorkflowFullRef,
trustedWorkflowSha,
);
const shaPinned = SHA_PINNED_BRANCH_PATTERN.test(manifest.workflowRef ?? "");
const protectedTagRoute = trustedIdentity.type === "tag";
let protectedTagWorkflowRefProof = "manifest-v3-protected-tag-exact-sha";
if (protectedTagRoute) {
let liveTag;
try {
liveTag = client.getRef(trustedIdentity.fullRef);
} catch (error) {
throw new Error(
`protected tooling tag is unavailable: ${
error instanceof Error ? error.message : String(error)
}`,
{ cause: error },
);
}
if (liveTag?.object?.sha !== trustedIdentity.sha) {
throw new Error("protected tooling tag moved after release validation was sealed");
}
if (!shaPinned) {
throw new Error("protected-tag release evidence must use a canonical release-ci branch");
}
if (manifest.workflowSha !== trustedIdentity.sha) {
const comparison = client.compareCommitLineage(manifest.workflowSha, trustedIdentity.sha);
if (
!["ahead", "identical"].includes(String(comparison.status)) ||
comparison.merge_base_commit?.sha !== manifest.workflowSha
) {
throw new Error(
"protected-tag release evidence producer is not on the trusted tooling lineage",
);
}
protectedTagWorkflowRefProof = "manifest-v3-protected-tag-tooling-lineage";
}
} else if (manifest.workflowRef !== trustedWorkflowRef && !shaPinned) {
throw new Error(
`release evidence producer must run from trusted workflow ref: ${trustedWorkflowRef}`,
);
}
if (shaPinned) {
if (manifest.version < 3) {
throw new Error("SHA-pinned release evidence requires a v3+ manifest");
}
if (!manifest.workflowRef.startsWith(`release-ci/${manifest.workflowSha.slice(0, 12)}-`)) {
throw new Error("SHA-pinned release evidence branch does not match its workflow SHA");
}
if (manifest.targetRef !== manifest.targetSha) {
throw new Error("SHA-pinned release evidence target ref must equal its target SHA");
}
}
const expectedFullRef = `refs/heads/${manifest.workflowRef}`;
const runPath = String(parentRun.path ?? "");
const [runWorkflowPath, runWorkflowFullRef] = runPath.split("@", 2);
if (runWorkflowPath !== ".github/workflows/full-release-validation.yml") {
throw new Error("release evidence producer workflow path is not trusted");
}
if (runWorkflowFullRef && normalizeWorkflowPathRef(runWorkflowFullRef) !== expectedFullRef) {
throw new Error("release evidence producer workflow full ref is not trusted");
}
let workflowRefProof = "legacy-v2-main-ancestry";
if (manifest.version >= 3) {
if (manifest.workflowRefType !== "branch" || manifest.workflowFullRef !== expectedFullRef) {
throw new Error("release evidence producer workflow full ref is not trusted");
}
workflowRefProof = protectedTagRoute
? protectedTagWorkflowRefProof
: shaPinned
? "manifest-v3-sha-pinned-main-ancestry"
: "manifest-v3-branch";
}
if (!protectedTagRoute) {
const comparison = client.compareCommitLineage(manifest.workflowSha, verifier.sourceSha);
if (
!["ahead", "identical"].includes(String(comparison.status)) ||
comparison.merge_base_commit?.sha !== manifest.workflowSha
) {
throw new Error("release evidence producer is not on the trusted main verifier lineage");
}
}
return {
producerOnTrustedMainLineage: !protectedTagRoute,
workflowFullRef: expectedFullRef,
workflowQualifiedPath: `${runWorkflowPath}@${expectedFullRef}`,
workflowRefProof,
workflowRefType: "branch",
workflowRunPath: runPath,
};
}
function normalizedParentTuple(evidence, identity) {
const { manifest, parentRun } = evidence;
return {
artifact: normalizedManifestArtifact(evidence.artifact, manifest.runAttempt),
conclusion: parentRun.conclusion,
manifest: evidence.manifestJson,
manifestVersion: manifest.version,
runAttempt: manifest.runAttempt,
runId: manifest.runId,
status: parentRun.status,
targetSha: manifest.targetSha,
url: parentRun.html_url ?? evidence.parentView.url,
...identity,
workflowPath: workflowPath(parentRun),
workflowRef: manifest.workflowRef,
workflowSha: manifest.workflowSha,
};
}
export function resolveVerifierIdentity(
sourceSha,
verifierSourceContent,
repositoryRoot = RELEASE_EVIDENCE_REPO_ROOT,
) {
let normalizedSourceSha = sourceSha ?? process.env.GITHUB_SHA;
if (!/^[a-f0-9]{40}$/u.test(String(normalizedSourceSha ?? ""))) {
try {
normalizedSourceSha = execFileSync("git", ["-C", repositoryRoot, "rev-parse", "HEAD"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
}).trim();
} catch {
normalizedSourceSha = null;
}
}
if (!/^[a-f0-9]{40}$/u.test(String(normalizedSourceSha ?? ""))) {
throw new Error("release evidence verifier source SHA is unavailable");
}
const script = readFileSync(RELEASE_EVIDENCE_FILE);
const scriptSha256 = createHash("sha256").update(script).digest("hex");
let sourceScript;
if (verifierSourceContent !== undefined) {
sourceScript = Buffer.from(verifierSourceContent);
} else {
try {
sourceScript = execFileSync(
"git",
["-C", repositoryRoot, "show", `${normalizedSourceSha}:${RELEASE_EVIDENCE_SCRIPT}`],
{
// Evidence verification must stay local-deterministic: in a partial
// clone a missing blob would otherwise trigger a promisor network
// fetch (hang/minutes) inside this security check.
env: { ...process.env, GIT_NO_LAZY_FETCH: "1" },
maxBuffer: 16 * 1024 * 1024,
stdio: ["ignore", "pipe", "pipe"],
},
);
} catch {
throw new Error("release evidence verifier source blob is unavailable");
}
}
const sourceScriptSha256 = createHash("sha256").update(sourceScript).digest("hex");
if (scriptSha256 !== sourceScriptSha256) {
throw new Error("release evidence verifier script differs from its source SHA");
}
return {
schemaVersion: 3,
script: RELEASE_EVIDENCE_SCRIPT,
scriptSha256,
sourceSha: normalizedSourceSha,
};
}
async function validateStrictChildRun({
child,
childEvidence,
childReuse,
client,
executionPlan,
parentEvidence,
parentJobs,
plannedChild,
repository,
runId,
expectedRunAttempts,
}) {
const reused = childReuse
? await client.validateChildReuse(childReuse, {
inputs: childReuse.inputs,
repository,
role: child.manifestKey,
targetSha: parentEvidence.manifest.targetSha,
})
: undefined;
const run = reused?.run ?? (await client.getRun(runId));
const effectiveRunAttempt = normalizePositiveInteger(
run.run_attempt,
`${child.name} run attempt`,
);
consumeExpectedRunAttempt(expectedRunAttempts, runId, effectiveRunAttempt, "child");
if (plannedChild) {
try {
validateReleaseChildRunProvenance(run, {
...plannedChild,
plannedRunAttempt: plannedChild.runAttempt,
repository,
});
} catch {
throw new Error(`execution plan child dispatch tuple mismatch: ${child.name}`);
}
}
const originAttempt = reused
? childReuse.sourceParentAttempt
: resolveManifestChildOriginAttempt(run, child, parentEvidence.manifest, parentJobs);
if (originAttempt === undefined) {
throw new Error(`manifest child dispatch tuple mismatch: ${child.name}`);
}
const parentJob = selectManifestParentJob(
parentJobs,
child,
parentEvidence.manifest,
reused ? executionPlan.parentRunAttempt : originAttempt,
{ requireSkippedCarryForward: plannedChild !== undefined },
);
const parentLog = await client.getJobLog(parentJob.id);
if (reused) {
const selectionSha256 = releaseChildReuseSha256(childReuse);
const witnesses = [
...String(parentLog).matchAll(/\bFRV_CHILD_REUSE_SHA256=([a-f0-9]{64})\b/gu),
];
if (witnesses.length !== 1 || witnesses[0][1] !== selectionSha256) {
throw new Error(`release child reuse adoption witness mismatch: ${child.name}`);
}
validateReleaseChildDispatchBinding({
child: plannedChild,
coveragePolicy: parentEvidence.manifest.validationInputs?.coveragePolicy,
log: parentLog,
plannedRunAttempt: plannedChild.runAttempt,
repository,
targetSha: parentEvidence.manifest.targetSha,
});
} else {
validateManifestChildRun(
run,
child,
runId,
parentEvidence.manifest,
parentJobs,
parentLog,
repository,
plannedChild?.runAttempt,
plannedChild !== undefined,
);
}
let jobs;
let composite;
if (plannedChild && childEvidence) {
if (childEvidence.effectiveRunAttempt > effectiveRunAttempt) {
throw new Error(`manifest child composite evidence mismatch: ${child.name}`);
}
const attempts = [];
for (
let runAttempt = plannedChild.runAttempt;
runAttempt <= childEvidence.effectiveRunAttempt;
runAttempt += 1
) {
const currentAttemptJobs = await client.getRunAttemptJobs(runId, runAttempt);
attempts.push({ jobs: currentAttemptJobs, runAttempt });
}
const evidence = composeReleaseChildAttemptEvidence({
attempts,
expected: {
...plannedChild,
plannedRunAttempt: plannedChild.runAttempt,
repository,
},
run:
childEvidence.effectiveRunAttempt === effectiveRunAttempt
? run
: {
...run,
run_attempt: childEvidence.effectiveRunAttempt,
triggering_actor: { login: childEvidence.triggeringActor },
},
});
const expectedEvidence = {
...evidence,
};
if (
JSON.stringify(sortReleaseJsonValueKeys(childEvidence)) !==
JSON.stringify(sortReleaseJsonValueKeys(expectedEvidence))
) {
throw new Error(`manifest child composite evidence mismatch: ${child.name}`);
}
if (childEvidence.effectiveRunAttempt < effectiveRunAttempt) {
throw new ReleaseEvidenceRefreshRequiredError(
`successful parent manifest predates ${child.name} attempt ${effectiveRunAttempt}`,
);
}
composite = {
effectiveRunAttempt: evidence.effectiveRunAttempt,
jobs: evidence.jobs,
plannedRunAttempt: evidence.plannedRunAttempt,
sha256: evidence.compositeJobsSha256,
};
jobs = evidence.jobs;
} else {
jobs =
run.conclusion === "success" && child.manifestKey !== "productPerformance"
? []
: await client.getParentJobs(runId);
}
if (
run.repository?.full_name !== repository ||
run.head_sha !== (plannedChild?.workflowSha ?? parentEvidence.manifest.workflowSha) ||
!terminalPolicyPass({
conclusion: run.conclusion,
jobs,
key: child.manifestKey,
status: run.status,
})
) {
throw new Error(`manifest child run does not pass release policy: ${child.name}`);
}
if (child.manifestKey === "productPerformance") {
// The authenticated composite selects the newest executed attempt per job,
// including a carried guard or a newer failure that supersedes its success.
validatePerformanceArtifactOnlyJobs(
composite ? jobs : jobs.filter((job) => Number(job.run_attempt) === effectiveRunAttempt),
);
}
return {
advisoryJobs: [],
conclusion: run.conclusion,
dispatchNonce: `full-release-validation-${reused ? childReuse.sourceParentRunId : parentEvidence.manifest.runId}-${originAttempt}${child.suffix}`,
displayTitle: run.display_title,
event: run.event,
headBranch: run.head_branch,
parentJobId: String(parentJob.id),
path: workflowPath(run),
policyPassed: true,
role: child.manifestKey,
...(composite
? {
compositeJobsSha256: composite.sha256,
dispatchActor: run.actor.login,
plannedRunAttempt: plannedChild.runAttempt,
triggeringActor: run.triggering_actor.login,
}
: {}),
runAttempt: effectiveRunAttempt,
runId: String(run.id),
sourceParentAttempt: originAttempt,
sourceParentRunId: reused ? childReuse.sourceParentRunId : parentEvidence.manifest.runId,
status: run.status,
url: run.html_url,
workflowSha: run.head_sha,
...(child.manifestKey === "productPerformance" ? { reportPublication: "artifact-only" } : {}),
};
}
/**
* @param {{
* manifestPath?: string,
* repository?: string,
* reuseRequest?: { releaseProfile: string, runReleaseSoak: string, targetSha: string, validationInputs: Record<string, unknown> },
* runId: string,
* expectedChangedPaths?: string[],
* expectedEvidencePolicy?: string,
* expectedEvidenceSha?: string,
* expectedRootRunId?: string,
* expectedRunAttempts?: Record<string, number>,
* expectedSelectedRunId?: string,
* expectedTargetSha?: string,
* trustedWorkflowFullRef?: string,
* trustedWorkflowRef?: string,
* trustedWorkflowSha?: string,
* verifierSourceContent?: string | Uint8Array,
* verifierSourceSha: string,
* }} options
*/
export async function validateReleaseRunEvidence(
{
manifestPath,
repository = DEFAULT_REPO,
reuseRequest,
runId,
expectedChangedPaths,
expectedEvidencePolicy,
expectedEvidenceSha,
expectedRootRunId,
expectedRunAttempts,
expectedSelectedRunId,
expectedTargetSha,
trustedWorkflowFullRef,
trustedWorkflowRef = "main",
trustedWorkflowSha,
verifierSourceContent,
verifierSourceSha,
},
client,
) {
const normalizedRepository = normalizeRepository(repository);
const normalizedRunId = normalizeRequiredRunId(runId, "full release run ID");
const remainingExpectedRunAttempts = normalizeExpectedRunAttempts(expectedRunAttempts);
const normalizedTrustedWorkflowRef = normalizeWorkflowRef(
trustedWorkflowRef,
"trusted workflow ref",
);
const trustedIdentity = resolveTrustedWorkflowIdentity(
normalizedTrustedWorkflowRef,
trustedWorkflowFullRef,
trustedWorkflowSha,
);
const evidenceClient = client ?? createReleaseEvidenceClient(normalizedRepository);
const verifier = resolveVerifierIdentity(verifierSourceSha, verifierSourceContent);
const currentEvidence = await loadValidatedParentEvidence({
client: evidenceClient,
expectedRunAttempts: remainingExpectedRunAttempts,
manifestPath,
repository: normalizedRepository,
runId: normalizedRunId,
});
const requestedReuse = {
expectedChangedPaths,
expectedEvidencePolicy,
expectedEvidenceSha,
expectedRootRunId,
expectedSelectedRunId,
expectedTargetSha,
};
if (reuseRequest !== undefined) {
// Reject mismatched searches before fetching root/child evidence. Matching
// metadata only admits a candidate to the full verifier below; it never passes it.
const manifest = currentEvidence.manifest;
if (
manifest.version !== 4 ||
manifest.evidenceReuse ||
manifest.rerunGroup !== "all" ||
manifest.releaseProfile !== reuseRequest.releaseProfile ||
manifest.runReleaseSoak !== reuseRequest.runReleaseSoak ||
JSON.stringify(sortReleaseJsonValueKeys(manifest.validationInputs)) !==
JSON.stringify(sortReleaseJsonValueKeys(reuseRequest.validationInputs))
) {
throw new Error(
"ineligible reuse candidate: requires a direct full run with matching profile, soak, and inputs",
);
}
const exactTarget = manifest.targetSha === reuseRequest.targetSha;
const comparison = exactTarget
? null
: evidenceClient.compareCommits(manifest.targetSha, reuseRequest.targetSha);
const delta = exactTarget
? { changedPaths: [], policy: EXACT_TARGET_EVIDENCE_REUSE_POLICY }
: classifyReleaseChangelogEvidenceComparison(comparison, {
baseSha: manifest.targetSha,
version:
manifest.candidateBinding?.package?.version ?? manifest.validationInputs?.targetVersion,
});
validateRequestedEvidenceReuse(
manifest,
manifest,
manifest,
{
expectedChangedPaths: delta.changedPaths,
expectedEvidencePolicy: delta.policy,
expectedEvidenceSha: manifest.targetSha,
expectedRootRunId: manifest.runId,
expectedSelectedRunId: manifest.runId,
expectedTargetSha: reuseRequest.targetSha,
},
() => comparison,
);
}
const producerIdentities = new Map([
[
currentEvidence.manifest.runId,
validateTrustedProducerIdentity(
currentEvidence,
evidenceClient,
verifier,
normalizedTrustedWorkflowRef,
trustedIdentity.fullRef,
trustedIdentity.sha,
),
],
]);
let rootEvidence = currentEvidence;
let selectedEvidence = currentEvidence;
const reuse = currentEvidence.manifest.evidenceReuse;
if (reuse) {
rootEvidence = await loadValidatedParentEvidence({
client: evidenceClient,
expectedRunAttempts: remainingExpectedRunAttempts,
repository: normalizedRepository,
runId: reuse.runId,
});
selectedEvidence =
reuse.selectedRunId === reuse.runId
? rootEvidence
: await loadValidatedParentEvidence({
client: evidenceClient,
expectedRunAttempts: remainingExpectedRunAttempts,
repository: normalizedRepository,
runId: reuse.selectedRunId,
});
validateEvidenceReuseChain(
currentEvidence.manifest,
selectedEvidence.manifest,
rootEvidence.manifest,
(base, head) => evidenceClient.compareCommits(base, head),
);
}
if (hasRequestedEvidenceReuse(requestedReuse)) {
validateRequestedEvidenceReuse(
currentEvidence.manifest,
selectedEvidence.manifest,
rootEvidence.manifest,
requestedReuse,
(base, head) => evidenceClient.compareCommits(base, head),
);
}
const sourcePlans = new Map();
const sourceContracts = new Map();
for (const evidence of [currentEvidence, selectedEvidence, rootEvidence]) {
if (!producerIdentities.has(evidence.manifest.runId)) {
producerIdentities.set(
evidence.manifest.runId,
validateTrustedProducerIdentity(
evidence,
evidenceClient,
verifier,
normalizedTrustedWorkflowRef,
trustedIdentity.fullRef,
trustedIdentity.sha,
),
);
}
const manifest = evidence.manifest;
if (!sourceContracts.has(manifest.workflowSha)) {
const workflow = evidenceClient.getWorkflowSource(manifest.workflowSha);
sourceContracts.set(manifest.workflowSha, {
workflow,
source: publicationSourceContract(workflow),
publication: publicationAdmissionContract(workflow),
});
}
const { source: contract, publication: registryContract } = sourceContracts.get(
manifest.workflowSha,
);
if (manifest.sourceAdmissionContract !== contract) {
throw new Error("source admission differs from the exact trusted workflow contract");
}
if (manifest.publicationAdmissionContract !== registryContract) {
throw new Error("publication admission differs from the exact trusted workflow contract");
}
validatePublicationSourceBinding(manifest, { sourceAdmissionContract: contract });
validatePublicationAdmissionBinding(manifest, {
publicationAdmissionContract: registryContract,
});
if (contract && !sourcePlans.has(manifest.runId)) {
const original =
registryContract && manifest.sourceAdmission.validationPurpose === "publish"
? await restoreOriginalPublicationAdmission({
request: manifest.sourceAdmission,
client: {
...evidenceClient,
getWorkflowSource: (sha) =>
sourceContracts.get(sha)?.workflow ?? evidenceClient.getWorkflowSource(sha),
},
})
: undefined;
const plan = validateReleaseExecutionPlanArtifact(
original?.plan ?? evidenceClient.loadExecutionPlan(manifest.runId),
{
sourceAdmissionContract: contract,
publicationAdmissionContract: registryContract,
parentRunId: manifest.runId,
repository: normalizedRepository,
targetSha: manifest.targetSha,
workflowRef: manifest.workflowRef,
workflowSha: manifest.workflowSha,
releaseProfile: manifest.releaseProfile,
rerunGroup: manifest.rerunGroup,
},
);
if (
publicationSourceJson(plan.sourceAdmission) !==
publicationSourceJson(manifest.sourceAdmission) ||
evidence.manifestJson.executionPlanSha256 !== plan.sha256 ||
Number(evidence.manifestJson.sourceParentRunAttempt) !== plan.parentRunAttempt
) {
throw new Error("source admission manifest differs from its immutable execution plan");
}
if (
registryContract &&
publicationObservationJson(plan.publicationAdmission) !==
publicationObservationJson(manifest.publicationAdmission)
) {
throw new Error("publication admission manifest differs from its original execution plan");
}
sourcePlans.set(manifest.runId, plan);
}
}
const selectedKeys = requiredChildKeysForManifest(rootEvidence.manifest);
const executionPlanPayload =
sourcePlans.get(rootEvidence.manifest.runId) ??
evidenceClient.loadExecutionPlan?.(rootEvidence.manifest.runId);
const executionPlan = executionPlanPayload
? validateReleaseExecutionPlanArtifact(executionPlanPayload, {
parentRunId: rootEvidence.manifest.runId,
repository: normalizedRepository,
releaseProfile: rootEvidence.manifest.releaseProfile,
rerunGroup: rootEvidence.manifest.rerunGroup,
targetSha: rootEvidence.manifest.targetSha,
workflowRef: rootEvidence.manifest.workflowRef,
workflowSha: rootEvidence.manifest.workflowSha,
})
: undefined;
validateReleaseTelegramWaiverBinding(executionPlan, rootEvidence.manifest.validationInputs);
validateReleaseCoveragePolicyBinding(executionPlan, rootEvidence.manifest.validationInputs);
const plannedByKey = new Map(
(executionPlan?.children ?? []).map((plannedChild) => [plannedChild.key, plannedChild]),
);
if (executionPlan?.attemptEvidenceVersion !== undefined) {
if (
rootEvidence.manifestJson.executionPlanSha256 !== executionPlan.sha256 ||
Number(rootEvidence.manifestJson.sourceParentRunAttempt) !== executionPlan.parentRunAttempt ||
JSON.stringify(sortReleaseJsonValueKeys(rootEvidence.manifest.candidateBinding)) !==
JSON.stringify(sortReleaseJsonValueKeys(executionPlan.candidate))
) {
throw new Error("release validation manifest differs from its immutable execution plan");
}
if (!rootEvidence.manifest.childEvidence) {
throw new Error("release validation manifest omitted composite child evidence");
}
if (
executionPlan.coveragePolicy &&
JSON.stringify(
executionPlan.children
.filter((child) => child.selected)
.map((child) => child.key)
.toSorted(),
) !== JSON.stringify([...selectedKeys].toSorted())
) {
throw new Error(
"release validation selected child set differs from its immutable execution plan",
);
}
}
const expectedChildren = executionPlan
? childDispatchesForPhaseVersion(executionPlan.attemptEvidenceVersion === 3 ? 3 : 2)
.filter((child) => selectedKeys.has(child.manifestKey))
.map((child) => {
const plannedChild = plannedByKey.get(child.manifestKey);
if (
!plannedChild?.selected ||
!plannedChild.required ||
!plannedChild.runId ||
!plannedChild.runAttempt
) {
throw new Error(`execution plan omits required child: ${child.name}`);
}
return Object.assign({}, child, {
displayTitle: plannedChild.displayTitle,
headBranch: plannedChild.workflowRef,
plannedChild,
});
})
: expectedSelectedChildDispatches(
rootEvidence.manifest.runId,
rootEvidence.manifest.runAttempt,
rootEvidence.manifest.workflowRef,
selectedKeys,
rootEvidence.manifest.version === 4 ? 3 : 2,
);
if (
executionPlan?.attemptEvidenceVersion !== undefined &&
JSON.stringify(Object.keys(rootEvidence.manifest.childEvidence).toSorted()) !==
JSON.stringify([...selectedKeys].toSorted())
) {
throw new Error("release validation manifest composite child set is invalid");
}
const dispatchEvidence = rootEvidence;
const parentJobs = await evidenceClient.getParentJobs(dispatchEvidence.manifest.runId);
const childEntries = executionPlan
? expectedChildren.map((child) => {
const manifestRunId = rootEvidence.manifest.childRunIds[child.manifestKey];
if (manifestRunId !== child.plannedChild.runId) {
throw new Error(`execution plan and manifest child identity differ: ${child.name}`);
}
return { child, runId: child.plannedChild.runId };
})
: manifestChildEntries(rootEvidence.manifest, expectedChildren, selectedKeys);
// The fixed child set bounds concurrent reads (at most seven). Each child
// walks its attempts/pages serially; drain all reads before returning or failing.
const childResults = await Promise.allSettled(
childEntries.map(({ child, runId: childRunId }) =>
validateStrictChildRun({
child,
childEvidence: rootEvidence.manifest.childEvidence?.[child.manifestKey],
childReuse: executionPlan?.childReuse?.[child.manifestKey],
client: evidenceClient,
executionPlan,
parentEvidence: dispatchEvidence,
parentJobs,
plannedChild: child.plannedChild,
releaseProfile: rootEvidence.manifest.releaseProfile,
repository: normalizedRepository,
runId: childRunId,
expectedRunAttempts: remainingExpectedRunAttempts,
}),
),
);
const children = childResults.map((result) => {
if (result.status === "rejected") {
throw result.reason;
}
return result.value;
});
if (remainingExpectedRunAttempts?.size) {
throw new Error(
`expected run attempts contain unvalidated run IDs: ${[...remainingExpectedRunAttempts.keys()].join(", ")}`,
);
}
const current = normalizedParentTuple(
currentEvidence,
producerIdentities.get(currentEvidence.manifest.runId),
);
const root = normalizedParentTuple(
rootEvidence,
producerIdentities.get(rootEvidence.manifest.runId),
);
const childConclusions = Object.fromEntries(
children.map((child) => [child.role, child.conclusion]),
);
return sortReleaseJsonValueKeys({
children,
conclusions: {
allRequiredSucceeded: children.every((child) => child.policyPassed),
children: childConclusions,
current: current.conclusion,
root: root.conclusion,
},
controls: rootEvidence.manifest.controls,
current,
directRoot: !reuse,
evidenceReuse: reuse
? {
changedPaths: reuse.changedPaths,
evidenceSha: reuse.evidenceSha,
policy: reuse.policy,
rootRunId: reuse.runId,
selectedRunId: reuse.selectedRunId,
}
: null,
executionPlan: executionPlan
? {
parentRunAttempt: executionPlan.parentRunAttempt,
sha256: executionPlan.sha256,
}
: null,
manifest: rootEvidence.manifestJson,
releaseProfile: rootEvidence.manifest.releaseProfile,
repository: normalizedRepository,
rerunGroup: rootEvidence.manifest.rerunGroup,
root,
runReleaseSoak: rootEvidence.manifest.runReleaseSoak === "true",
schema:
rootEvidence.manifest.version === 4
? PHASED_RELEASE_EVIDENCE_SCHEMA
: RELEASE_EVIDENCE_SCHEMA,
producerOnTrustedMainLineage: trustedIdentity.type === "branch",
trustedWorkflowFullRef: trustedIdentity.fullRef,
trustedWorkflowRef: normalizedTrustedWorkflowRef,
valid: true,
validationInputs: rootEvidence.manifest.validationInputs ?? null,
verifier,
});
}
function parseReleaseCiSummaryArgs(argv) {
const options = {
intervalMs: 30_000,
expectedChangedPaths: undefined,
expectedEvidencePolicy: undefined,
expectedEvidenceSha: undefined,
expectedRootRunId: undefined,
expectedRunAttempts: undefined,
expectedSelectedRunId: undefined,
expectedTargetSha: undefined,
json: false,
manifestPath: undefined,
repository: DEFAULT_REPO,
reuseRequest: undefined,
runId: undefined,
trustedWorkflowFullRef: undefined,
trustedWorkflowRef: "main",
trustedWorkflowSha: undefined,
validate: false,
verifierSourceFile: undefined,
verifierSourceSha: undefined,
watch: false,
};
for (let index = 0; index < argv.length; index += 1) {
const argument = argv[index];
if (argument === "--validate-run") {
options.validate = true;
options.runId = argv[++index];
} else if (argument === "--repo") {
options.repository = argv[++index];
} else if (argument === "--manifest") {
options.manifestPath = argv[++index];
} else if (argument === "--reuse-request-json") {
options.reuseRequest = normalizeJsonObject(JSON.parse(argv[++index]), "reuse request");
} else if (argument === "--trusted-workflow-ref") {
options.trustedWorkflowRef = argv[++index];
} else if (argument === "--trusted-workflow-full-ref") {
options.trustedWorkflowFullRef = argv[++index];
} else if (argument === "--trusted-workflow-sha") {
options.trustedWorkflowSha = argv[++index];
} else if (argument === "--verifier-source-sha") {
options.verifierSourceSha = argv[++index];
} else if (argument === "--verifier-source-file") {
options.verifierSourceFile = argv[++index];
} else if (argument === "--expected-target-sha") {
options.expectedTargetSha = argv[++index];
} else if (argument === "--expected-evidence-policy") {
options.expectedEvidencePolicy = argv[++index];
} else if (argument === "--expected-evidence-sha") {
options.expectedEvidenceSha = argv[++index];
} else if (argument === "--expected-root-run-id") {
options.expectedRootRunId = argv[++index];
} else if (argument === "--expected-run-attempts-json") {
const value = argv[++index];
if (!value || Buffer.byteLength(value, "utf8") > MAX_EXPECTED_RUN_ATTEMPTS_JSON_BYTES) {
throw new Error("--expected-run-attempts-json requires a bounded JSON object");
}
try {
options.expectedRunAttempts = JSON.parse(value);
} catch {
throw new Error("--expected-run-attempts-json requires a JSON object");
}
if (!options.expectedRunAttempts || Array.isArray(options.expectedRunAttempts)) {
throw new Error("--expected-run-attempts-json requires a JSON object");
}
normalizeExpectedRunAttempts(options.expectedRunAttempts);
} else if (argument === "--expected-selected-run-id") {
options.expectedSelectedRunId = argv[++index];
} else if (argument === "--expected-changed-paths-json") {
const value = argv[++index];
try {
options.expectedChangedPaths = JSON.parse(value);
} catch {
throw new Error("--expected-changed-paths-json requires a JSON array");
}
if (
!Array.isArray(options.expectedChangedPaths) ||
options.expectedChangedPaths.some((entry) => typeof entry !== "string")
) {
throw new Error("--expected-changed-paths-json requires a JSON array");
}
} else if (argument === "--json") {
options.json = true;
} else if (argument === "--watch") {
options.watch = true;
} else if (argument === "--interval") {
const seconds = argv[++index];
if (!/^[1-9][0-9]*$/u.test(seconds ?? "")) {
throw new Error("--interval requires a positive number of seconds");
}
options.intervalMs = Number(seconds) * 1000;
} else if (!argument.startsWith("-") && !options.runId && !options.validate) {
options.runId = argument;
} else {
throw new Error(`unknown or incomplete argument: ${argument}`);
}
}
if (!options.validate && options.manifestPath) {
throw new Error("--manifest requires --validate-run");
}
if (!options.validate && options.reuseRequest !== undefined) {
throw new Error("--reuse-request-json requires --validate-run");
}
if (!options.validate && options.expectedRunAttempts !== undefined) {
throw new Error("--expected-run-attempts-json requires --validate-run");
}
if (options.validate && options.watch) {
throw new Error("--watch cannot be combined with --validate-run");
}
if (options.verifierSourceFile && !options.verifierSourceSha) {
throw new Error("--verifier-source-file requires --verifier-source-sha");
}
if (!options.runId) {
throw new Error("full release run ID is required");
}
return options;
}
function printUsage() {
console.error(
[
"usage: release-ci-summary.mjs <full-release-run-id>",
" release-ci-summary.mjs <full-release-run-id> --watch [--interval seconds]",
" release-ci-summary.mjs --validate-run <id> [--repo owner/name] [--trusted-workflow-ref main --trusted-workflow-full-ref refs/heads/main] [--trusted-workflow-sha sha] [--manifest path] [--verifier-source-sha sha --verifier-source-file path] [--expected-target-sha sha --expected-evidence-sha sha --expected-evidence-policy policy --expected-root-run-id id --expected-selected-run-id id --expected-changed-paths-json json] [--expected-run-attempts-json json] [--reuse-request-json json] --json",
].join("\n"),
);
}
function releaseCiWatchFingerprint(parent) {
return JSON.stringify({
attempt: parent.attempt,
conclusion: parent.conclusion ?? "",
jobs: (parent.jobs ?? [])
.map((job) => ({
conclusion: job.conclusion ?? "",
name: job.name,
status: job.status,
}))
.toSorted((left, right) => left.name.localeCompare(right.name)),
status: parent.status,
});
}
function terminalParentJobFailures(parent) {
return (parent.jobs ?? [])
.filter(
(job) =>
job.status === "completed" &&
!SUCCESSFUL_PARENT_JOB_CONCLUSIONS.has(String(job.conclusion ?? "")),
)
.map((job) => String(job.name || "unnamed parent job"));
}
export function tryReadReleaseDecisionArtifact(
parent,
runId,
repository,
runReleaseCiGhImpl = runReleaseCiGh,
) {
const artifactName = `full-release-decision-${runId}-${parent.attempt}`;
const downloadDir = mkdtempSync(join(tmpdir(), "openclaw-release-decision-watch-"));
try {
try {
runReleaseCiGhImpl(
[
"run",
"download",
String(runId),
"--repo",
repository,
"--name",
artifactName,
"--dir",
downloadDir,
],
{ stdio: ["ignore", "ignore", "pipe"] },
);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (isReleaseGhArtifactMissingError(error)) {
return undefined;
}
if (classifyReleaseGhTransportError(error) === "transient") {
console.warn(`release decision artifact unavailable this poll; retrying: ${message}`);
return undefined;
}
throw new Error(`release decision artifact read failed: ${message}`, { cause: error });
}
const path = join(downloadDir, "full-release-decision.json");
if (!statSync(path, { throwIfNoEntry: false })) {
throw new Error(`release decision artifact ${artifactName} omitted its manifest`);
}
if (statSync(path).size > MAX_RELEASE_ARTIFACT_BYTES) {
throw new Error(`release decision artifact ${artifactName} exceeds the size limit`);
}
return validateReleaseStateArtifact(
JSON.parse(readFileSync(path, "utf8")),
{
parentRunAttempt: parent.attempt,
parentRunId: String(runId),
workflowSha: parent.headSha,
},
"decision",
);
} finally {
rmSync(downloadDir, { force: true, recursive: true });
}
}
function releaseDecisionBlockedDuringDrain(parent, runId, repository) {
const jobs = parent.jobs ?? [];
const decision = jobs.find((job) => job.name === "Release Decision");
const drain = jobs.find((job) => job.name === "Diagnostic Drain");
if (
decision?.status !== "completed" ||
SUCCESSFUL_PARENT_JOB_CONCLUSIONS.has(String(decision.conclusion ?? "")) ||
!drain ||
drain.status === "completed"
) {
return undefined;
}
return tryReadReleaseDecisionArtifact(parent, runId, repository);
}
function summarizeReleaseCiRun(options) {
execFileSync(
process.execPath,
[
RELEASE_EVIDENCE_FILE,
options.runId,
"--repo",
options.repository,
"--trusted-workflow-ref",
options.trustedWorkflowRef,
],
{ stdio: "inherit" },
);
}
async function watchReleaseCiRun(options) {
let previousFingerprint;
while (true) {
const parent = jsonGh([
"run",
"view",
options.runId,
"--repo",
options.repository,
"--json",
"status,conclusion,attempt,headSha,jobs",
]);
const fingerprint = releaseCiWatchFingerprint(parent);
if (fingerprint !== previousFingerprint) {
summarizeReleaseCiRun(options);
previousFingerprint = fingerprint;
}
const blockedDuringDrain = releaseDecisionBlockedDuringDrain(
parent,
options.runId,
options.repository,
);
if (blockedDuringDrain) {
throw new Error(
`full release run ${options.runId} stopped at Release Decision:\n${formatReleaseStateOutcome(blockedDuringDrain)}`,
);
}
const failedJobs = terminalParentJobFailures(parent);
if (failedJobs.length > 0) {
throw new Error(
`full release run ${options.runId} has terminal parent job failure(s): ${failedJobs.join(", ")}`,
);
}
if (parent.status === "completed") {
if (parent.conclusion !== "success") {
throw new Error(
`full release run ${options.runId} completed with ${parent.conclusion || "no conclusion"}`,
);
}
return;
}
await new Promise((complete) => {
setTimeout(complete, options.intervalMs);
});
}
}
async function main() {
let options;
try {
options = parseReleaseCiSummaryArgs(process.argv.slice(2));
} catch (error) {
printUsage();
console.error(error instanceof Error ? error.message : String(error));
process.exit(2);
}
const { repository, runId } = options;
if (options.validate) {
try {
const evidence = await validateReleaseRunEvidence({
expectedChangedPaths: options.expectedChangedPaths,
expectedEvidencePolicy: options.expectedEvidencePolicy,
expectedEvidenceSha: options.expectedEvidenceSha,
expectedRootRunId: options.expectedRootRunId,
expectedRunAttempts: options.expectedRunAttempts,
expectedSelectedRunId: options.expectedSelectedRunId,
expectedTargetSha: options.expectedTargetSha,
manifestPath: options.manifestPath,
repository,
reuseRequest: options.reuseRequest,
runId,
trustedWorkflowFullRef: options.trustedWorkflowFullRef,
trustedWorkflowRef: options.trustedWorkflowRef,
trustedWorkflowSha: options.trustedWorkflowSha,
verifierSourceContent: options.verifierSourceFile
? readFileSync(options.verifierSourceFile)
: undefined,
verifierSourceSha: options.verifierSourceSha,
});
console.log(JSON.stringify(evidence, null, options.json ? 2 : 0));
} catch (error) {
const failure = {
error: error instanceof Error ? error.message : String(error),
...(error instanceof ReleaseEvidenceRefreshRequiredError ? { refreshable: true } : {}),
schema: RELEASE_EVIDENCE_SCHEMA,
valid: false,
};
if (options.json) {
console.log(JSON.stringify(failure, null, 2));
} else {
console.error(failure.error);
}
process.exit(1);
}
return;
}
if (options.watch) {
await watchReleaseCiRun(options);
return;
}
const core = rate();
if (core) {
const reset = new Date(core.reset * 1000).toISOString();
console.log(`rate: remaining=${core.remaining}/${core.limit} reset=${reset}`);
if (core.remaining < 20) {
console.error("rate too low for CI summary; wait for reset before polling");
process.exit(3);
}
}
const parent = jsonGh([
"run",
"view",
runId,
"--repo",
repository,
"--json",
"status,conclusion,attempt,headBranch,headSha,url,jobs",
]);
validateParentRunBinding(parent, githubRestJson(`actions/runs/${runId}`, repository), runId);
console.log(`parent: ${runId} ${parent.status}/${parent.conclusion || "none"}`);
console.log(`workflow-ref: ${parent.headBranch}`);
console.log(`workflow-sha: ${parent.headSha}`);
console.log(`url: ${parent.url}`);
for (const job of parent.jobs ?? []) {
const marker = job.conclusion || job.status;
console.log(`parent-job: ${marker} ${job.name}`);
}
const currentManifestRaw = tryDownloadParentManifest(runId, parent.attempt, repository);
let children;
if (currentManifestRaw) {
const currentManifest = validateParentManifest(currentManifestRaw, {
runAttempt: parent.attempt,
runId,
workflowRef: parent.headBranch,
workflowSha: parent.headSha,
});
console.log(`candidate-sha: ${currentManifest.targetSha}`);
console.log(`manifest-run: ${currentManifest.runId}/${currentManifest.runAttempt}`);
let sourceManifest = currentManifest;
let sourceParent = parent;
if (currentManifest.evidenceReuse) {
const rootRunId = currentManifest.evidenceReuse.runId;
const rootParent = jsonGh([
"run",
"view",
rootRunId,
"--repo",
repository,
"--json",
"status,conclusion,attempt,headBranch,headSha,url,jobs",
]);
validateParentRunBinding(
rootParent,
githubRestJson(`actions/runs/${rootRunId}`, repository),
rootRunId,
);
if (rootParent.status !== "completed" || rootParent.conclusion !== "success") {
throw new Error(`evidence root run is not completed/success: ${rootRunId}`);
}
const rootManifestRaw = tryDownloadParentManifest(rootRunId, rootParent.attempt, repository);
if (!rootManifestRaw) {
throw new Error(`evidence root manifest is unavailable: ${rootRunId}`);
}
const rootManifest = validateParentManifest(rootManifestRaw, {
runAttempt: rootParent.attempt,
runId: rootRunId,
workflowRef: rootParent.headBranch,
workflowSha: rootParent.headSha,
});
const selectedRunId = currentManifest.evidenceReuse.selectedRunId;
let selectedManifest = rootManifest;
if (selectedRunId !== rootRunId) {
const selectedParent = jsonGh([
"run",
"view",
selectedRunId,
"--repo",
repository,
"--json",
"status,conclusion,attempt,headBranch,headSha,url,jobs",
]);
validateParentRunBinding(
selectedParent,
githubRestJson(`actions/runs/${selectedRunId}`, repository),
selectedRunId,
);
if (selectedParent.status !== "completed" || selectedParent.conclusion !== "success") {
throw new Error(`selected evidence run is not completed/success: ${selectedRunId}`);
}
const selectedManifestRaw = tryDownloadParentManifest(
selectedRunId,
selectedParent.attempt,
repository,
);
if (!selectedManifestRaw) {
throw new Error(`selected evidence manifest is unavailable: ${selectedRunId}`);
}
selectedManifest = validateParentManifest(selectedManifestRaw, {
runAttempt: selectedParent.attempt,
runId: selectedRunId,
workflowRef: selectedParent.headBranch,
workflowSha: selectedParent.headSha,
});
}
const evidenceSha = validateEvidenceReuseChain(
currentManifest,
selectedManifest,
rootManifest,
(base, head) => githubRestJson(`compare/${base}...${head}`, repository),
);
sourceManifest = rootManifest;
sourceParent = rootParent;
console.log(`evidence-selected-run: ${selectedRunId}`);
console.log(`evidence-root-run: ${rootRunId}`);
console.log(`evidence-sha: ${evidenceSha}`);
console.log(`evidence-policy: ${currentManifest.evidenceReuse.policy}`);
console.log(
`evidence-changed-paths: ${JSON.stringify(currentManifest.evidenceReuse.changedPaths)}`,
);
}
const selectedKeys = requiredChildKeysForManifest(sourceManifest);
const expectedChildren = expectedSelectedChildDispatches(
sourceManifest.runId,
sourceManifest.runAttempt,
sourceManifest.workflowRef,
selectedKeys,
sourceManifest.version === 4 ? 3 : 2,
);
const sourceParentJobs = await findParentJobsAll(sourceManifest.runId, repository);
children = [];
for (const { child, runId: childRunId } of manifestChildEntries(
sourceManifest,
expectedChildren,
selectedKeys,
)) {
const run = githubRestJson(`actions/runs/${childRunId}`, repository);
const originAttempt = resolveManifestChildOriginAttempt(
run,
child,
sourceManifest,
sourceParentJobs,
);
if (originAttempt === undefined) {
throw new Error(`manifest child dispatch tuple mismatch: ${child.name}`);
}
const parentJob = selectManifestParentJob(
sourceParentJobs,
child,
sourceManifest,
originAttempt,
);
const validatedRun = validateManifestChildRun(
run,
child,
childRunId,
{ ...sourceManifest, workflowSha: sourceParent.headSha },
sourceParentJobs,
await parentJobLog(parentJob.id, repository),
repository,
);
if (child.manifestKey === "productPerformance") {
validatePerformanceArtifactOnlyJobs(
(await findParentJobsAll(childRunId, repository)).filter(
(job) => Number(job.run_attempt) === Number(run.run_attempt),
),
);
}
children.push({ child, run: validatedRun });
}
} else {
console.log("candidate-sha: unavailable (release validation manifest not uploaded)");
if (parent.status === "completed" && parent.conclusion === "success") {
throw new Error("successful parent run is missing its release validation manifest");
}
const selectedKeys = selectedChildKeys(parent.jobs ?? []);
children = expectedSelectedChildDispatches(
runId,
parent.attempt,
parent.headBranch,
selectedKeys,
selectedKeys.has("releaseChecksCandidate") ? 3 : 2,
)
.map((child) => {
const run = findExactChildRun(child, repository);
if (!run) {
console.log(
`child-missing: ${child.name} title=${child.displayTitle} branch=${child.headBranch}`,
);
}
return { child, run };
})
.filter((entry) => entry.run);
}
if (children.length === 0) {
console.log("children: none found yet");
return;
}
console.log("children:");
for (const { child, run } of children) {
console.log(
`child: ${run.id} ${child.name} ${run.status}/${run.conclusion || "none"} branch=${run.head_branch} workflow_sha=${run.head_sha}`,
);
console.log(`child-url: ${run.html_url}`);
}
}
if (process.argv[1]?.endsWith("release-ci-summary.mjs")) {
await main().catch(
/** @param {unknown} error */ (error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
},
);
}