fix(release): retain npm provenance evidence

This commit is contained in:
Peter Steinberger
2026-07-15 20:02:53 +01:00
parent ff4d854167
commit 23d2f664fb
4 changed files with 66 additions and 18 deletions
@@ -1918,17 +1918,17 @@ jobs:
fi
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
OPENCLAW_NPM_EXPECTED_WORKFLOW_REF="${openclaw_npm_expected_workflow_ref}" \
OPENCLAW_NPM_EXPECTED_WORKFLOW_SHA="${openclaw_npm_expected_workflow_sha}" \
node --import tsx \
"${GITHUB_WORKSPACE}/.release-harness/scripts/openclaw-npm-postpublish-verify.ts" \
"${release_version}"
verify_args+=(--skip-postpublish)
verify_args+=(
--postpublish-verifier
"${GITHUB_WORKSPACE}/.release-harness/scripts/openclaw-npm-postpublish-verify.ts"
)
fi
node --import tsx \
"${GITHUB_WORKSPACE}/.release-harness/scripts/release-verify-beta.ts" \
"${verify_args[@]}"
OPENCLAW_NPM_EXPECTED_WORKFLOW_REF="${openclaw_npm_expected_workflow_ref}" \
OPENCLAW_NPM_EXPECTED_WORKFLOW_SHA="${openclaw_npm_expected_workflow_sha}" \
node --import tsx \
"${GITHUB_WORKSPACE}/.release-harness/scripts/release-verify-beta.ts" \
"${verify_args[@]}"
# Resolve already validated this exact v3 run before mutation. Append
# its immutable tuple here so frozen targets need no new CLI option.
+25 -6
View File
@@ -26,6 +26,7 @@ type ReleaseVerifyBetaArgs = {
pluginSelection: string[];
clawHubBootstrapPlugins: string[];
evidenceOut?: string;
postpublishVerifier?: string;
skipPostpublish: boolean;
skipGitHubRelease: boolean;
skipClawHub: boolean;
@@ -242,6 +243,7 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
pluginSelection: [],
clawHubBootstrapPlugins: [],
evidenceOut: undefined,
postpublishVerifier: undefined,
skipPostpublish: false,
skipGitHubRelease: false,
skipClawHub: false,
@@ -300,6 +302,9 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
case "--evidence-out":
parsed.evidenceOut = next();
break;
case "--postpublish-verifier":
parsed.postpublishVerifier = next();
break;
case "--full-release-validation-run":
parsed.workflowRuns.fullReleaseValidation = next();
break;
@@ -335,6 +340,10 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
}
}
if (parsed.skipPostpublish && parsed.postpublishVerifier !== undefined) {
throw new Error("--postpublish-verifier cannot be combined with --skip-postpublish.");
}
if (parsed.workflowRuns.pluginClawHubBootstrap !== undefined) {
if (parsed.releaseSha === undefined) {
throw new Error("--plugin-clawhub-bootstrap-run requires --release-sha.");
@@ -349,6 +358,17 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
return parsed;
}
export function resolveOpenClawNpmPostpublishVerifier(rootDir: string, override?: string): string {
if (override === undefined) {
return resolve(rootDir, "scripts/openclaw-npm-postpublish-verify.ts");
}
const verifier = resolve(override);
if (verifier !== resolve(TRUSTED_TOOLING_ROOT, "scripts/openclaw-npm-postpublish-verify.ts")) {
throw new Error("--postpublish-verifier must select the trusted tooling verifier.");
}
return verifier;
}
async function fetchWithRetry(
url: string,
options: RequestInit,
@@ -1274,12 +1294,11 @@ export async function verifyBetaRelease(
lines.push(`openclaw npm OK: ${args.version} (${args.distTag})`);
if (!args.skipPostpublish) {
runCommandInherited("node", [
"--import",
"tsx",
"scripts/openclaw-npm-postpublish-verify.ts",
args.version,
]);
const postpublishVerifier = resolveOpenClawNpmPostpublishVerifier(
rootDir,
args.postpublishVerifier,
);
runCommandInherited("node", ["--import", "tsx", postpublishVerifier, args.version]);
lines.push("openclaw postpublish verifier OK");
}
@@ -4028,7 +4028,10 @@ describe("package artifact reuse", () => {
expect(releaseWorkflow).toContain(
'"${GITHUB_WORKSPACE}/.release-harness/scripts/openclaw-npm-postpublish-verify.ts"',
);
expect(releaseWorkflow).toContain("verify_args+=(--skip-postpublish)");
expect(releaseWorkflow).toContain(
'"${GITHUB_WORKSPACE}/.release-harness/scripts/openclaw-npm-postpublish-verify.ts"',
);
expect(releaseWorkflow).toContain("--postpublish-verifier");
expect(releaseWorkflow).toContain('"${verify_args[@]}"');
expect(releaseWorkflow).toContain(
"OpenClaw Release Publish must use trusted main workflow tooling",
+28 -2
View File
@@ -1,6 +1,7 @@
// Release Beta Verifier tests cover release beta verifier script behavior.
/* oxlint-disable typescript/no-base-to-string -- fetch mock normalizes standard RequestInfo inputs for URL assertions. */
import { createHash } from "node:crypto";
import { resolve } from "node:path";
import { expectDefined } from "@openclaw/normalization-core";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
@@ -10,6 +11,7 @@ import {
parseNpmViewFields,
parseReleaseVerifyBetaArgs,
readBoundedJsonResponse,
resolveOpenClawNpmPostpublishVerifier,
runNpmViewWithRetry,
validateClawHubBootstrapEvidence,
} from "../../scripts/lib/release-beta-verifier.ts";
@@ -91,6 +93,7 @@ describe("parseReleaseVerifyBetaArgs", () => {
pluginSelection: [],
clawHubBootstrapPlugins: [],
evidenceOut: undefined,
postpublishVerifier: undefined,
skipPostpublish: false,
skipGitHubRelease: false,
skipClawHub: false,
@@ -128,7 +131,8 @@ describe("parseReleaseVerifyBetaArgs", () => {
"44",
"--evidence-out",
".artifacts/release-evidence.json",
"--skip-postpublish",
"--postpublish-verifier",
"/tmp/trusted-postpublish.ts",
"--skip-github-release",
"--skip-clawhub",
"--rerun-failed-clawhub",
@@ -145,7 +149,8 @@ describe("parseReleaseVerifyBetaArgs", () => {
pluginSelection: ["@openclaw/plugin-a", "@openclaw/plugin-b"],
clawHubBootstrapPlugins: ["@openclaw/plugin-b"],
evidenceOut: ".artifacts/release-evidence.json",
skipPostpublish: true,
postpublishVerifier: "/tmp/trusted-postpublish.ts",
skipPostpublish: false,
skipGitHubRelease: true,
skipClawHub: true,
rerunFailedClawHub: true,
@@ -160,6 +165,27 @@ describe("parseReleaseVerifyBetaArgs", () => {
});
});
it("only accepts the trusted tooling postpublish verifier override", () => {
expect(resolveOpenClawNpmPostpublishVerifier("/tmp/release")).toBe(
"/tmp/release/scripts/openclaw-npm-postpublish-verify.ts",
);
const trustedVerifier = resolve("scripts/openclaw-npm-postpublish-verify.ts");
expect(resolveOpenClawNpmPostpublishVerifier("/tmp/release", trustedVerifier)).toBe(
trustedVerifier,
);
expect(() =>
resolveOpenClawNpmPostpublishVerifier("/tmp/release", "/tmp/untrusted-verifier.ts"),
).toThrow("must select the trusted tooling verifier");
expect(() =>
parseReleaseVerifyBetaArgs([
"2026.5.10-beta.3",
"--postpublish-verifier",
trustedVerifier,
"--skip-postpublish",
]),
).toThrow("cannot be combined");
});
it("requires exact target and package inputs for bootstrap run verification", () => {
expect(() =>
parseReleaseVerifyBetaArgs(["2026.5.10-beta.3", "--plugin-clawhub-bootstrap-run", "34"]),