mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(release): pin publish workflows to protected tags
This commit is contained in:
@@ -174,9 +174,9 @@ jobs:
|
||||
tideclaw_alpha_publish=true
|
||||
fi
|
||||
sha_pinned_release_publish=false
|
||||
if [[ "${WORKFLOW_REF}" =~ ^refs/heads/release-publish/([a-f0-9]{12})-[1-9][0-9]*$ ]]; then
|
||||
if [[ "${WORKFLOW_REF}" =~ ^refs/tags/release-publish/([a-f0-9]{12})-[1-9][0-9]*$ ]]; then
|
||||
if [[ "${WORKFLOW_SHA:0:12}" != "${BASH_REMATCH[1]}" ]]; then
|
||||
echo "SHA-pinned release publish branch does not match workflow SHA ${WORKFLOW_SHA}." >&2
|
||||
echo "SHA-pinned release publish tag does not match workflow SHA ${WORKFLOW_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
merge_base_sha="$(
|
||||
@@ -184,13 +184,13 @@ jobs:
|
||||
--jq '.merge_base_commit.sha | select(test("^[a-f0-9]{40}$"))'
|
||||
)"
|
||||
if [[ "${merge_base_sha}" != "${WORKFLOW_SHA}" ]]; then
|
||||
echo "SHA-pinned release publish workflow revision is not reachable from current main." >&2
|
||||
echo "SHA-pinned release publish tag revision is not reachable from current main." >&2
|
||||
exit 1
|
||||
fi
|
||||
sha_pinned_release_publish=true
|
||||
fi
|
||||
if [[ "${WORKFLOW_REF}" != "refs/heads/main" && "${tideclaw_alpha_publish}" != "true" && "${sha_pinned_release_publish}" != "true" ]]; then
|
||||
echo "OpenClaw Release Publish must use trusted main workflow tooling, a SHA-pinned release-publish branch, or a matching Tideclaw alpha branch." >&2
|
||||
echo "OpenClaw Release Publish must use trusted main workflow tooling, a protected SHA-pinned release-publish tag, or a matching Tideclaw alpha branch." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && "${PLUGIN_PUBLISH_SCOPE}" != "all-publishable" ]]; then
|
||||
@@ -594,23 +594,31 @@ jobs:
|
||||
id: clawhub_plan
|
||||
env:
|
||||
TARGET_SHA: ${{ needs.resolve_release_target.outputs.sha }}
|
||||
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
||||
BOOTSTRAP_WORKFLOW_REF: ${{ startsWith(github.ref, 'refs/tags/release-publish/') && github.ref_name || 'main' }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PARENT_WORKFLOW_BRANCH: ${{ github.ref_name }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
|
||||
PLUGINS: ${{ inputs.plugins }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
plan_path="${RUNNER_TEMP}/openclaw-release-clawhub-plan.json"
|
||||
bootstrap_workflow_sha="$(
|
||||
gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/main" \
|
||||
--jq '.object.sha | select(test("^[a-f0-9]{40}$"))'
|
||||
)"
|
||||
if [[ "${BOOTSTRAP_WORKFLOW_REF}" == "main" ]]; then
|
||||
bootstrap_workflow_sha="$(
|
||||
gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/main" \
|
||||
--jq '.object.sha | select(test("^[a-f0-9]{40}$"))'
|
||||
)"
|
||||
else
|
||||
# Protected release-publish tags bind every child to this exact
|
||||
# parent revision, so unrelated main movement cannot change tooling.
|
||||
bootstrap_workflow_sha="${GITHUB_SHA}"
|
||||
fi
|
||||
plan_args=(
|
||||
--bootstrap-workflow-ref "${BOOTSTRAP_WORKFLOW_REF}"
|
||||
--bootstrap-workflow-sha "${bootstrap_workflow_sha}"
|
||||
--release-tag "${RELEASE_TAG}"
|
||||
--release-sha "${TARGET_SHA}"
|
||||
--release-publish-branch "${CHILD_WORKFLOW_REF}"
|
||||
--release-publish-branch "${PARENT_WORKFLOW_BRANCH}"
|
||||
--release-publish-run-attempt "${GITHUB_RUN_ATTEMPT}"
|
||||
--release-publish-run-id "${GITHUB_RUN_ID}"
|
||||
--plugin-publish-scope "${PLUGIN_PUBLISH_SCOPE}"
|
||||
@@ -634,14 +642,15 @@ jobs:
|
||||
fi
|
||||
|
||||
jq -e \
|
||||
--arg branch "${CHILD_WORKFLOW_REF}" \
|
||||
--arg bootstrap_ref "${BOOTSTRAP_WORKFLOW_REF}" \
|
||||
--arg branch "${PARENT_WORKFLOW_BRANCH}" \
|
||||
--arg bootstrap_workflow_sha "${bootstrap_workflow_sha}" \
|
||||
--arg attempt "${GITHUB_RUN_ATTEMPT}" \
|
||||
--arg run_id "${GITHUB_RUN_ID}" \
|
||||
--arg tag "${RELEASE_TAG}" \
|
||||
--arg target "${TARGET_SHA}" \
|
||||
'.bootstrapWorkflowSha == $bootstrap_workflow_sha and
|
||||
.bootstrap.ref == "main" and
|
||||
.bootstrap.ref == $bootstrap_ref and
|
||||
.bootstrap.inputs.ref == $target and
|
||||
.bootstrap.inputs.bootstrap_workflow_sha == $bootstrap_workflow_sha and
|
||||
.bootstrap.inputs.release_tag == $tag and
|
||||
@@ -667,7 +676,7 @@ jobs:
|
||||
workflow: "OpenClaw Release Publish",
|
||||
parentRunId: process.env.GITHUB_RUN_ID,
|
||||
parentRunAttempt: Number(process.env.GITHUB_RUN_ATTEMPT),
|
||||
workflowBranch: process.env.CHILD_WORKFLOW_REF,
|
||||
workflowBranch: process.env.PARENT_WORKFLOW_BRANCH,
|
||||
parentWorkflowSha: process.env.GITHUB_SHA,
|
||||
bootstrapWorkflowSha: plan.bootstrapWorkflowSha,
|
||||
releaseTag: process.env.RELEASE_TAG,
|
||||
@@ -761,6 +770,7 @@ jobs:
|
||||
TARGET_SHA: ${{ needs.resolve_release_target.outputs.sha }}
|
||||
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
||||
PARENT_WORKFLOW_SHA: ${{ github.sha }}
|
||||
PARENT_WORKFLOW_BRANCH: ${{ github.ref_name }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
|
||||
@@ -883,16 +893,28 @@ jobs:
|
||||
}
|
||||
|
||||
verify_bootstrap_workflow_sha() {
|
||||
local approved_sha current_sha
|
||||
local approved_ref approved_sha current_main_sha
|
||||
approved_ref="$(jq -er '.bootstrap.ref | select(type == "string" and length > 0)' "${CLAWHUB_PLAN_PATH}")"
|
||||
approved_sha="$(jq -er '.bootstrapWorkflowSha | select(test("^[a-f0-9]{40}$"))' "${CLAWHUB_PLAN_PATH}")"
|
||||
current_sha="$(
|
||||
gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/main" \
|
||||
--jq '.object.sha | select(test("^[a-f0-9]{40}$"))'
|
||||
)"
|
||||
[[ "${current_sha}" == "${approved_sha}" ]] || {
|
||||
echo "Trusted main moved from approved ClawHub bootstrap workflow SHA ${approved_sha} to ${current_sha}; rerun release approval." >&2
|
||||
exit 1
|
||||
}
|
||||
if [[ "${approved_ref}" == "main" ]]; then
|
||||
current_main_sha="$(
|
||||
gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/main" \
|
||||
--jq '.object.sha | select(test("^[a-f0-9]{40}$"))'
|
||||
)"
|
||||
[[ "${approved_sha}" == "${current_main_sha}" ]] || {
|
||||
echo "Trusted main moved from approved ClawHub bootstrap workflow SHA ${approved_sha} to ${current_main_sha}; rerun release approval." >&2
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
[[ "${approved_ref}" == "${CHILD_WORKFLOW_REF}" ]] || {
|
||||
echo "Approved ClawHub bootstrap workflow ref ${approved_ref} does not match protected child workflow ref ${CHILD_WORKFLOW_REF}." >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "${approved_sha}" == "${PARENT_WORKFLOW_SHA}" ]] || {
|
||||
echo "Approved ClawHub bootstrap workflow SHA ${approved_sha} does not match parent workflow SHA ${PARENT_WORKFLOW_SHA}." >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
printf '%s\n' "${approved_sha}"
|
||||
}
|
||||
|
||||
@@ -1657,7 +1679,7 @@ jobs:
|
||||
android_release_run_id="$(dispatch_workflow_at_ref "${RELEASE_TAG}" "${TARGET_SHA}" android-release.yml \
|
||||
-f tag="${RELEASE_TAG}" \
|
||||
-f release_publish_run_id="${GITHUB_RUN_ID}" \
|
||||
-f release_publish_branch="${CHILD_WORKFLOW_REF}" \
|
||||
-f release_publish_branch="${PARENT_WORKFLOW_BRANCH}" \
|
||||
-f release_target_sha="${TARGET_SHA}" \
|
||||
-f direct_release_recovery=false)"
|
||||
# Promotion runs in a background subshell; hand the run id to the
|
||||
@@ -1952,12 +1974,14 @@ jobs:
|
||||
fi
|
||||
}
|
||||
|
||||
bootstrap_summary_ref="$(jq -er '.bootstrap.ref | select(type == "string" and length > 0)' "${CLAWHUB_PLAN_PATH}")"
|
||||
bootstrap_summary_sha="$(jq -er '.bootstrapWorkflowSha | select(test("^[a-f0-9]{40}$"))' "${CLAWHUB_PLAN_PATH}")"
|
||||
{
|
||||
echo "### Publish sequence"
|
||||
echo
|
||||
echo "- Workflow ref: \`${CHILD_WORKFLOW_REF}\`"
|
||||
echo "- Normal ClawHub workflow ref: release tag \`${RELEASE_TAG}\`"
|
||||
echo "- ClawHub bootstrap workflow ref: trusted \`main\`, target \`${TARGET_SHA}\`"
|
||||
echo "- ClawHub bootstrap workflow ref: \`${bootstrap_summary_ref}\` at \`${bootstrap_summary_sha}\`"
|
||||
echo "- Release tag: \`${RELEASE_TAG}\`"
|
||||
echo "- Release SHA: \`${TARGET_SHA}\`"
|
||||
echo "- Release approval: this workflow job"
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Require trusted main workflow source
|
||||
- name: Require trusted workflow source
|
||||
env:
|
||||
APPROVED_WORKFLOW_SHA: ${{ inputs.bootstrap_workflow_sha }}
|
||||
PRETAG_VALIDATION: ${{ inputs.pretag_validation }}
|
||||
@@ -83,26 +83,42 @@ jobs:
|
||||
WORKFLOW_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ "${WORKFLOW_REF}" == "refs/heads/main" ]] || {
|
||||
echo "Plugin ClawHub New must be dispatched from trusted main." >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "$(git rev-parse HEAD)" == "${WORKFLOW_SHA}" ]] || {
|
||||
echo "Trusted workflow checkout does not match github.sha." >&2
|
||||
exit 1
|
||||
}
|
||||
if [[ "${PRETAG_VALIDATION}" != "true" ]]; then
|
||||
# Repository release-tag rules make this exact-SHA ref immutable.
|
||||
# Parent attestation is checked again before environment approval.
|
||||
[[ "${GITHUB_ACTOR}" == "github-actions[bot]" ]] || {
|
||||
echo "Plugin ClawHub bootstrap release must be dispatched by the approved parent workflow." >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "${APPROVED_WORKFLOW_SHA}" =~ ^[a-f0-9]{40}$ ]] || {
|
||||
echo "Plugin ClawHub bootstrap requires an exact approved workflow SHA." >&2
|
||||
exit 1
|
||||
}
|
||||
if [[ "${WORKFLOW_REF}" == "refs/heads/main" ]]; then
|
||||
:
|
||||
elif [[ "${WORKFLOW_REF}" =~ ^refs/tags/release-publish/([a-f0-9]{12})-[1-9][0-9]*$ && "${APPROVED_WORKFLOW_SHA:0:12}" == "${BASH_REMATCH[1]}" ]]; then
|
||||
:
|
||||
else
|
||||
echo "Plugin ClawHub bootstrap requires trusted main or the protected SHA-pinned release-publish tag." >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ "${WORKFLOW_SHA}" == "${APPROVED_WORKFLOW_SHA}" ]] || {
|
||||
echo "Plugin ClawHub New workflow SHA does not match the parent-approved trusted-main SHA." >&2
|
||||
exit 1
|
||||
}
|
||||
elif [[ -n "${APPROVED_WORKFLOW_SHA}" && "${WORKFLOW_SHA}" != "${APPROVED_WORKFLOW_SHA}" ]]; then
|
||||
echo "Plugin ClawHub pre-tag validation workflow SHA does not match the requested trusted-main SHA." >&2
|
||||
exit 1
|
||||
else
|
||||
[[ "${WORKFLOW_REF}" == "refs/heads/main" ]] || {
|
||||
echo "Plugin ClawHub pre-tag validation must be dispatched from trusted main." >&2
|
||||
exit 1
|
||||
}
|
||||
if [[ -n "${APPROVED_WORKFLOW_SHA}" && "${WORKFLOW_SHA}" != "${APPROVED_WORKFLOW_SHA}" ]]; then
|
||||
echo "Plugin ClawHub pre-tag validation workflow SHA does not match the requested trusted-main SHA." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Resolve checked-out ref
|
||||
@@ -362,10 +378,14 @@ jobs:
|
||||
EXPECTED_WORKFLOW_SHA="$(
|
||||
jq -er '.headSha | select(test("^[a-f0-9]{40}$"))' <<< "${RUN_JSON}"
|
||||
)"
|
||||
EXPECTED_WORKFLOW_REF="refs/heads/${EXPECTED_WORKFLOW_BRANCH}"
|
||||
if [[ "${EXPECTED_WORKFLOW_BRANCH}" =~ ^release-publish/[a-f0-9]{12}-[1-9][0-9]*$ ]]; then
|
||||
EXPECTED_WORKFLOW_REF="refs/tags/${EXPECTED_WORKFLOW_BRANCH}"
|
||||
fi
|
||||
gh attestation verify "${APPROVAL_PATH}" \
|
||||
--repo "${GITHUB_REPOSITORY}" \
|
||||
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/openclaw-release-publish.yml" \
|
||||
--source-ref "refs/heads/${EXPECTED_WORKFLOW_BRANCH}" \
|
||||
--source-ref "${EXPECTED_WORKFLOW_REF}" \
|
||||
--source-digest "${EXPECTED_WORKFLOW_SHA}" \
|
||||
--deny-self-hosted-runners
|
||||
printf '%s' "$RUN_JSON" | DIRECT_RELEASE_RECOVERY="${direct_recovery}" node scripts/validate-release-publish-approval.mjs
|
||||
|
||||
@@ -23,6 +23,7 @@ type ClawHubDispatchTarget = {
|
||||
};
|
||||
|
||||
type OpenClawReleaseClawHubPlanArgs = {
|
||||
bootstrapWorkflowRef: string;
|
||||
bootstrapWorkflowSha: string;
|
||||
releaseTag: string;
|
||||
releaseSha: string;
|
||||
@@ -98,6 +99,14 @@ function requireCommitSha(value: string | undefined, label: string): string {
|
||||
return sha;
|
||||
}
|
||||
|
||||
function requireBootstrapWorkflowRef(value: string | undefined): string {
|
||||
const ref = requireArg(value, "--bootstrap-workflow-ref");
|
||||
if (ref !== "main" && !/^release-publish\/[a-f0-9]{12}-[1-9][0-9]*$/u.test(ref)) {
|
||||
throw new Error("--bootstrap-workflow-ref must be main or a SHA-pinned release-publish tag.");
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
function requirePositiveInteger(value: string | undefined, label: string): string {
|
||||
const result = requireArg(value, label);
|
||||
if (!/^[1-9][0-9]*$/u.test(result)) {
|
||||
@@ -227,6 +236,7 @@ export function parseOpenClawReleaseClawHubPlanArgs(
|
||||
|
||||
let releaseTag: string | undefined;
|
||||
let releaseSha: string | undefined;
|
||||
let bootstrapWorkflowRef: string | undefined;
|
||||
let bootstrapWorkflowSha: string | undefined;
|
||||
let releasePublishBranch: string | undefined;
|
||||
let releasePublishRunAttempt: string | undefined;
|
||||
@@ -247,6 +257,9 @@ export function parseOpenClawReleaseClawHubPlanArgs(
|
||||
};
|
||||
|
||||
switch (arg) {
|
||||
case "--bootstrap-workflow-ref":
|
||||
bootstrapWorkflowRef = next();
|
||||
break;
|
||||
case "--bootstrap-workflow-sha":
|
||||
bootstrapWorkflowSha = next();
|
||||
break;
|
||||
@@ -289,6 +302,7 @@ export function parseOpenClawReleaseClawHubPlanArgs(
|
||||
}
|
||||
|
||||
return {
|
||||
bootstrapWorkflowRef: requireBootstrapWorkflowRef(bootstrapWorkflowRef),
|
||||
bootstrapWorkflowSha: requireCommitSha(bootstrapWorkflowSha, "--bootstrap-workflow-sha"),
|
||||
releaseTag: requireArg(releaseTag, "--release-tag"),
|
||||
releaseSha: requireCommitSha(releaseSha, "--release-sha"),
|
||||
@@ -311,6 +325,7 @@ export async function buildOpenClawReleaseClawHubPlan(
|
||||
registryBaseUrl?: string;
|
||||
} = {},
|
||||
): Promise<OpenClawReleaseClawHubPlan> {
|
||||
const bootstrapWorkflowRef = requireBootstrapWorkflowRef(args.bootstrapWorkflowRef);
|
||||
const bootstrapWorkflowSha = requireCommitSha(args.bootstrapWorkflowSha, "bootstrapWorkflowSha");
|
||||
const releaseTag = requireArg(args.releaseTag, "releaseTag");
|
||||
const releaseSha = requireCommitSha(args.releaseSha, "releaseSha");
|
||||
@@ -350,7 +365,7 @@ export async function buildOpenClawReleaseClawHubPlan(
|
||||
}),
|
||||
bootstrap: createDispatchTarget({
|
||||
workflow: "plugin-clawhub-new.yml",
|
||||
ref: "main",
|
||||
ref: bootstrapWorkflowRef,
|
||||
packages: bootstrapPackages,
|
||||
releasePublishRunId,
|
||||
releasePublishBranch,
|
||||
|
||||
@@ -1202,6 +1202,7 @@ describe("buildOpenClawReleaseClawHubPlan", () => {
|
||||
|
||||
const plan = await buildOpenClawReleaseClawHubPlan(
|
||||
{
|
||||
bootstrapWorkflowRef: `release-publish/${"d".repeat(12)}-12345`,
|
||||
bootstrapWorkflowSha: "d".repeat(40),
|
||||
releaseTag: "v2026.4.1-beta.1",
|
||||
releaseSha: "a".repeat(40),
|
||||
@@ -1235,7 +1236,7 @@ describe("buildOpenClawReleaseClawHubPlan", () => {
|
||||
});
|
||||
expect(plan.bootstrap).toEqual({
|
||||
workflow: "plugin-clawhub-new.yml",
|
||||
ref: "main",
|
||||
ref: `release-publish/${"d".repeat(12)}-12345`,
|
||||
shouldDispatch: true,
|
||||
packages: ["@openclaw/demo-two", "@openclaw/demo-three"],
|
||||
inputs: {
|
||||
@@ -1289,6 +1290,7 @@ describe("buildOpenClawReleaseClawHubPlan", () => {
|
||||
|
||||
const plan = await buildOpenClawReleaseClawHubPlan(
|
||||
{
|
||||
bootstrapWorkflowRef: `release-publish/${"d".repeat(12)}-12345`,
|
||||
bootstrapWorkflowSha: "d".repeat(40),
|
||||
releaseTag: "v2026.4.1-beta.1",
|
||||
releaseSha: "b".repeat(40),
|
||||
@@ -1308,7 +1310,7 @@ describe("buildOpenClawReleaseClawHubPlan", () => {
|
||||
expect(plan.normal.shouldDispatch).toBe(false);
|
||||
expect(plan.bootstrap).toMatchObject({
|
||||
workflow: "plugin-clawhub-new.yml",
|
||||
ref: "main",
|
||||
ref: `release-publish/${"d".repeat(12)}-12345`,
|
||||
shouldDispatch: true,
|
||||
packages: ["@openclaw/demo-plugin"],
|
||||
inputs: {
|
||||
@@ -1333,6 +1335,8 @@ describe("buildOpenClawReleaseClawHubPlan", () => {
|
||||
it("rejects incompatible all-publishable plugin selection args", () => {
|
||||
expect(() =>
|
||||
parseOpenClawReleaseClawHubPlanArgs([
|
||||
"--bootstrap-workflow-ref",
|
||||
`release-publish/${"d".repeat(12)}-12345`,
|
||||
"--bootstrap-workflow-sha",
|
||||
"d".repeat(40),
|
||||
"--release-tag",
|
||||
@@ -1355,6 +1359,8 @@ describe("buildOpenClawReleaseClawHubPlan", () => {
|
||||
|
||||
it("requires an exact lowercase release SHA for bootstrap targeting", () => {
|
||||
const baseArgs = [
|
||||
"--bootstrap-workflow-ref",
|
||||
`release-publish/${"d".repeat(12)}-12345`,
|
||||
"--bootstrap-workflow-sha",
|
||||
"d".repeat(40),
|
||||
"--release-tag",
|
||||
@@ -1376,6 +1382,8 @@ describe("buildOpenClawReleaseClawHubPlan", () => {
|
||||
|
||||
it("requires an exact parent release run attempt for bootstrap approval binding", () => {
|
||||
const args = [
|
||||
"--bootstrap-workflow-ref",
|
||||
`release-publish/${"d".repeat(12)}-12345`,
|
||||
"--bootstrap-workflow-sha",
|
||||
"d".repeat(40),
|
||||
"--release-tag",
|
||||
|
||||
@@ -343,7 +343,7 @@ describe("package acceptance workflow", () => {
|
||||
expect(runReleasePublishInputValidation({ PUBLISH_OPENCLAW_NPM: "false" }).status).toBe(0);
|
||||
});
|
||||
|
||||
it("accepts only main-reachable SHA-pinned release publish branches", () => {
|
||||
it("accepts only main-reachable protected SHA-pinned release publish tags", () => {
|
||||
const workflowSha = "a".repeat(40);
|
||||
const binDir = tempDirs.make("release-publish-gh-");
|
||||
const ghPath = `${binDir}/gh`;
|
||||
@@ -352,7 +352,7 @@ describe("package acceptance workflow", () => {
|
||||
const pinnedEnv = {
|
||||
GITHUB_REPOSITORY: "openclaw/openclaw",
|
||||
PATH: `${binDir}:${process.env.PATH}`,
|
||||
WORKFLOW_REF: `refs/heads/release-publish/${workflowSha.slice(0, 12)}-123`,
|
||||
WORKFLOW_REF: `refs/tags/release-publish/${workflowSha.slice(0, 12)}-123`,
|
||||
WORKFLOW_SHA: workflowSha,
|
||||
};
|
||||
|
||||
@@ -364,11 +364,11 @@ describe("package acceptance workflow", () => {
|
||||
|
||||
const mismatchedName = runReleasePublishInputValidation({
|
||||
...pinnedEnv,
|
||||
WORKFLOW_REF: `refs/heads/release-publish/${"b".repeat(12)}-123`,
|
||||
WORKFLOW_REF: `refs/tags/release-publish/${"b".repeat(12)}-123`,
|
||||
});
|
||||
expect(mismatchedName.status).toBe(1);
|
||||
expect(mismatchedName.stderr).toContain(
|
||||
"SHA-pinned release publish branch does not match workflow SHA",
|
||||
"SHA-pinned release publish tag does not match workflow SHA",
|
||||
);
|
||||
|
||||
const unreachable = runReleasePublishInputValidation({
|
||||
@@ -377,7 +377,7 @@ describe("package acceptance workflow", () => {
|
||||
});
|
||||
expect(unreachable.status).toBe(1);
|
||||
expect(unreachable.stderr).toContain(
|
||||
"SHA-pinned release publish workflow revision is not reachable from current main",
|
||||
"SHA-pinned release publish tag revision is not reachable from current main",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -407,6 +407,7 @@ describe("package acceptance workflow", () => {
|
||||
}
|
||||
|
||||
expect(publishOrchestration.env?.PARENT_WORKFLOW_SHA).toBe("${{ github.sha }}");
|
||||
expect(publishOrchestration.env?.CHILD_WORKFLOW_REF).toBe("${{ github.ref_name }}");
|
||||
expectTextToIncludeAll(publishOrchestration.run, [
|
||||
'gh api "repos/${GITHUB_REPOSITORY}/commits/${encoded_workflow_ref}"',
|
||||
'if [[ "$resolved_workflow_sha" != "$expected_sha" ]]',
|
||||
@@ -3646,9 +3647,15 @@ describe("package artifact reuse", () => {
|
||||
expect(trustedClawHubPlan.run).toContain(
|
||||
'--release-publish-run-attempt "${GITHUB_RUN_ATTEMPT}"',
|
||||
);
|
||||
expect(trustedClawHubPlan.run).toContain(
|
||||
'--bootstrap-workflow-ref "${BOOTSTRAP_WORKFLOW_REF}"',
|
||||
);
|
||||
expect(trustedClawHubPlan.run).toContain(
|
||||
'--bootstrap-workflow-sha "${bootstrap_workflow_sha}"',
|
||||
);
|
||||
expect(trustedClawHubPlan.run).toContain('if [[ "${BOOTSTRAP_WORKFLOW_REF}" == "main" ]]');
|
||||
expect(trustedClawHubPlan.run).toContain('bootstrap_workflow_sha="${GITHUB_SHA}"');
|
||||
expect(trustedClawHubPlan.run).toContain(".bootstrap.ref == $bootstrap_ref");
|
||||
expect(trustedClawHubPlan.run).toContain(
|
||||
'gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/main"',
|
||||
);
|
||||
@@ -3682,6 +3689,9 @@ describe("package artifact reuse", () => {
|
||||
expect(releaseWorkflow).toContain(".proofLines.bootstrap");
|
||||
expect(releaseWorkflow).toContain("Bootstrap/repair candidates:");
|
||||
expect(releaseWorkflow).toContain("Trusted-publisher repair plugins:");
|
||||
expect(releaseWorkflow).toContain(
|
||||
'echo "- ClawHub bootstrap workflow ref: \\`${bootstrap_summary_ref}\\` at \\`${bootstrap_summary_sha}\\`"',
|
||||
);
|
||||
expect(releaseWorkflow).toContain(
|
||||
"Waiting for plugin-clawhub-new.yml bootstrap to finish before continuing release publish.",
|
||||
);
|
||||
@@ -3691,6 +3701,12 @@ describe("package artifact reuse", () => {
|
||||
const verifyBootstrapWorkflowIndex = releaseWorkflow.indexOf(
|
||||
'bootstrap_workflow_sha="$(verify_bootstrap_workflow_sha)"',
|
||||
);
|
||||
expect(releaseWorkflow).toContain('if [[ "${approved_ref}" == "main" ]]');
|
||||
expect(releaseWorkflow).toContain('[[ "${approved_ref}" == "${CHILD_WORKFLOW_REF}" ]]');
|
||||
expect(releaseWorkflow).toContain('[[ "${approved_sha}" == "${PARENT_WORKFLOW_SHA}" ]]');
|
||||
expect(releaseWorkflow).toContain(
|
||||
"Trusted main moved from approved ClawHub bootstrap workflow SHA",
|
||||
);
|
||||
const dispatchPluginNpmIndex = releaseWorkflow.indexOf(
|
||||
'plugin_npm_run_id="$(dispatch_workflow plugin-npm-release.yml',
|
||||
);
|
||||
@@ -3762,7 +3778,7 @@ describe("package artifact reuse", () => {
|
||||
expect(releaseInputGuard).toContain(
|
||||
'[[ "${WORKFLOW_REF}" != "refs/heads/main" && "${tideclaw_alpha_publish}" != "true" && "${sha_pinned_release_publish}" != "true" ]]',
|
||||
);
|
||||
expect(releaseInputGuard).toContain("refs/heads/release-publish/");
|
||||
expect(releaseInputGuard).toContain("refs/tags/release-publish/");
|
||||
expect(releaseInputGuard).not.toContain("refs/heads/release/");
|
||||
expect(releaseInputGuard).toContain(
|
||||
'"${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" == "alpha"',
|
||||
@@ -3894,7 +3910,7 @@ describe("package artifact reuse", () => {
|
||||
expect(clawHubNewWorkflow).toContain("verify-clawhub-published-artifact.mjs");
|
||||
expect(openclawNpmWorkflow).toContain("environment: npm-release");
|
||||
expect(releaseWorkflow).toContain("default: from-validation");
|
||||
expect(releaseWorkflow).toContain('--release-publish-branch "${CHILD_WORKFLOW_REF}"');
|
||||
expect(releaseWorkflow).toContain('--release-publish-branch "${PARENT_WORKFLOW_BRANCH}"');
|
||||
expect(releaseWorkflow).toContain('--release-publish-run-attempt "${GITHUB_RUN_ATTEMPT}"');
|
||||
expect(releaseWorkflow).toContain('--release-publish-run-id "${GITHUB_RUN_ID}"');
|
||||
expect(releaseWorkflow).toContain('--release-sha "${TARGET_SHA}"');
|
||||
|
||||
@@ -71,8 +71,10 @@ describe("Plugin ClawHub New workflow", () => {
|
||||
const resolve = job("resolve_bootstrap_plan");
|
||||
const checkout = step(resolve, "Checkout");
|
||||
expect(checkout.with?.ref).toBe("${{ github.sha }}");
|
||||
const guard = step(resolve, "Require trusted main workflow source").run ?? "";
|
||||
const guard = step(resolve, "Require trusted workflow source").run ?? "";
|
||||
expect(guard).toContain('WORKFLOW_REF}" == "refs/heads/main"');
|
||||
expect(guard).toContain('GITHUB_ACTOR}" == "github-actions[bot]"');
|
||||
expect(guard).toContain("refs/tags/release-publish/");
|
||||
expect(guard).toContain(
|
||||
"Plugin ClawHub New workflow SHA does not match the parent-approved trusted-main SHA.",
|
||||
);
|
||||
@@ -142,6 +144,10 @@ describe("Plugin ClawHub New workflow", () => {
|
||||
expect(validation.run).toContain(
|
||||
"actions/runs/${RELEASE_PUBLISH_RUN_ID}/attempts/${EXPECTED_RUN_ATTEMPT}",
|
||||
);
|
||||
expect(validation.run).toContain(
|
||||
'EXPECTED_WORKFLOW_REF="refs/tags/${EXPECTED_WORKFLOW_BRANCH}"',
|
||||
);
|
||||
expect(validation.run).toContain('--source-ref "${EXPECTED_WORKFLOW_REF}"');
|
||||
expect(validation.run).toContain('--source-digest "${EXPECTED_WORKFLOW_SHA}"');
|
||||
});
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ describe("release wrapper scripts", () => {
|
||||
|
||||
it("loads the OpenClaw ClawHub plan CLI and validates required arguments before planning", () => {
|
||||
const result = runTsxScript("scripts/openclaw-release-clawhub-plan.ts", [
|
||||
"--bootstrap-workflow-ref",
|
||||
"main",
|
||||
"--bootstrap-workflow-sha",
|
||||
"b".repeat(40),
|
||||
"--release-tag",
|
||||
@@ -83,9 +85,12 @@ describe("release wrapper scripts", () => {
|
||||
const oldTarget = createOldReleaseTarget();
|
||||
const repositoryRoot = process.cwd();
|
||||
const releaseSha = "a".repeat(40);
|
||||
const bootstrapWorkflowRef = `release-publish/${"b".repeat(12)}-123`;
|
||||
const plan = runTsxScript(
|
||||
join(repositoryRoot, "scripts/openclaw-release-clawhub-plan.ts"),
|
||||
[
|
||||
"--bootstrap-workflow-ref",
|
||||
bootstrapWorkflowRef,
|
||||
"--bootstrap-workflow-sha",
|
||||
"b".repeat(40),
|
||||
"--release-tag",
|
||||
@@ -106,7 +111,7 @@ describe("release wrapper scripts", () => {
|
||||
expect(plan.status, plan.stderr).toBe(0);
|
||||
expect(JSON.parse(plan.stdout)).toMatchObject({
|
||||
bootstrapWorkflowSha: "b".repeat(40),
|
||||
bootstrap: { ref: "main", shouldDispatch: false },
|
||||
bootstrap: { ref: bootstrapWorkflowRef, shouldDispatch: false },
|
||||
normal: { ref: "v2026.7.1-beta.3", shouldDispatch: false },
|
||||
});
|
||||
expect(plan.stderr).not.toContain("old target planner invoked");
|
||||
|
||||
Reference in New Issue
Block a user