#!/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 { loadFlakeClassifications } from "./full-release-flake-classification.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, releaseAdvisoryJobs, releaseChildClassificationEvidence, terminalPolicyPass, validateReleaseManifestAdvisoryJobs, 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 {(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), ); } } } 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(runReleaseCiGh(args)); } 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))); } 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 downloadReleaseJsonArtifact( runId, repository, { artifactName, entryName, directoryPrefix, label, retryTransient = false }, runGh = runReleaseCiGh, ) { const downloadDir = mkdtempSync(join(tmpdir(), directoryPrefix)); try { try { runGh( [ "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 (retryTransient && classifyReleaseGhTransportError(error) === "transient") { console.warn(`${label} artifact unavailable this poll; retrying: ${message}`); return undefined; } throw new Error(`${label} artifact read failed: ${message}`, { cause: error }); } const path = join(downloadDir, entryName); if (!statSync(path, { throwIfNoEntry: false })) { throw new Error(`${label} artifact ${artifactName} omitted its manifest`); } if (statSync(path).size > MAX_RELEASE_ARTIFACT_BYTES) { throw new Error(`${label} artifact ${artifactName} exceeds the size limit`); } return JSON.parse(readFileSync(path, "utf8")); } finally { rmSync(downloadDir, { force: true, recursive: true }); } } function tryDownloadExecutionPlan(runId, repository = DEFAULT_REPO) { return downloadReleaseJsonArtifact(runId, repository, { artifactName: `full-release-execution-plan-${runId}`, entryName: "full-release-execution-plan.json", directoryPrefix: "openclaw-release-execution-plan-", label: "release execution plan", }); } 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, ...releaseChildClassificationEvidence(child), 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) { throw new Error( "Waived release evidence is no longer accepted; rerun Full Release Validation without waivers.", ); } const advisoryJobs = validateReleaseManifestAdvisoryJobs(value); 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> }} manifest * @param {Child[]} children * @param {Set} 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 { loadFlakeClassifications(request) { return loadFlakeClassifications({ ...request, repo: normalizedRepository }); }, 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, workflowSha: parentEvidence.manifest.workflowSha, }) : 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 }, }, }); if ( JSON.stringify(sortReleaseJsonValueKeys(childEvidence)) !== JSON.stringify( sortReleaseJsonValueKeys({ ...evidence, ...releaseChildClassificationEvidence(childEvidence), }), ) ) { 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); } const policyChild = { conclusion: run.conclusion, jobs, key: child.manifestKey, runId, status: run.status, }; const classifications = child.manifestKey === "normalCi" && run.conclusion !== "success" ? await client.loadFlakeClassifications({ child: policyChild, parentRunId: parentEvidence.manifest.runId, parentRunAttempt: originAttempt, targetSha: parentEvidence.manifest.targetSha, }) : {}; Object.assign(policyChild, classifications); if ( childEvidence && JSON.stringify(sortReleaseJsonValueKeys(releaseChildClassificationEvidence(childEvidence))) !== JSON.stringify(sortReleaseJsonValueKeys(releaseChildClassificationEvidence(policyChild))) ) { throw new Error(`manifest child classification evidence mismatch: ${child.name}`); } if ( run.repository?.full_name !== repository || run.head_sha !== (plannedChild?.workflowSha ?? parentEvidence.manifest.workflowSha) || !terminalPolicyPass(policyChild) ) { 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: releaseAdvisoryJobs([policyChild]), 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 }, * runId: string, * expectedChangedPaths?: string[], * expectedEvidencePolicy?: string, * expectedEvidenceSha?: string, * expectedRootRunId?: string, * expectedRunAttempts?: Record, * 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 ", " release-ci-summary.mjs --watch [--interval seconds]", " release-ci-summary.mjs --validate-run [--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 decision = downloadReleaseJsonArtifact( runId, repository, { artifactName: `full-release-decision-${runId}-${parent.attempt}`, entryName: "full-release-decision.json", directoryPrefix: "openclaw-release-decision-watch-", label: "release decision", retryTransient: true, }, runReleaseCiGhImpl, ); if (decision === undefined) { return undefined; } return validateReleaseStateArtifact( decision, { parentRunAttempt: parent.attempt, parentRunId: String(runId), workflowSha: parent.headSha, }, "decision", ); } 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 sleep(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({ ...options, verifierSourceContent: options.verifierSourceFile ? readFileSync(options.verifierSourceFile) : undefined, }); 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); }, ); }