mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 19:08:22 -06:00
ci: simplify extended-stable release finalization
This commit is contained in:
@@ -319,8 +319,9 @@ node scripts/full-release-validation-at-sha.mjs \
|
||||
|
||||
Extended-stable may use the helper's trusted main-pinned `release-ci/*` harness
|
||||
or a direct run from `extended-stable/YYYY.M.33` with
|
||||
`release_profile=stable`. In either case, require a complete v3 manifest that
|
||||
binds the exact canonical branch tip, workflow SHA, and run attempt. Use
|
||||
`release_profile=stable`. In either case, require a complete Full Release
|
||||
Validation evidence manifest using schema version 3. It must bind the exact
|
||||
canonical branch tip, workflow SHA, and run attempt. Use
|
||||
`$release-openclaw-ci` for failure classification and identity rules.
|
||||
|
||||
The helper verifies and pins the recorded Tooling SHA on trusted `main`, passes
|
||||
|
||||
@@ -304,13 +304,13 @@ on pinned current `main` as the exact command and validation contract.
|
||||
approval-gated `docker-channel-promote.yml` from current `main` with the exact
|
||||
tag; never rebuild or move the release tag.
|
||||
10. Dispatch the extended-stable `OpenClaw Release Publish` closeout. It must
|
||||
reverify the exact npm selector and tarball, render the canonical release
|
||||
notes, publish Docker, and only then create the public non-prerelease GitHub
|
||||
Release with `latest=false`. Re-run failed jobs to retry a failed finalizer
|
||||
without repeating successful Docker work; a fresh dispatch safely repeats
|
||||
same-version Docker verification and promotion. Do not publish macOS,
|
||||
Windows, mobile, website, ClawHub, npm `latest`, or private dist-tag
|
||||
artifacts from this path.
|
||||
reverify the exact npm selector and tarball, then publish Docker. Only after
|
||||
Docker succeeds does the finalizer render the canonical release notes and
|
||||
create the public non-prerelease GitHub Release with `latest=false`. Re-run
|
||||
failed jobs to retry a failed finalizer without repeating successful Docker
|
||||
work; a fresh dispatch safely repeats same-version Docker verification and
|
||||
promotion. Do not publish macOS, Windows, mobile, website, ClawHub, npm
|
||||
`latest`, or private dist-tag artifacts from this path.
|
||||
|
||||
## Keep release channel naming aligned
|
||||
|
||||
|
||||
@@ -2492,8 +2492,8 @@ jobs:
|
||||
path: ${{ runner.temp }}/openclaw-release-postpublish-evidence
|
||||
if-no-files-found: error
|
||||
|
||||
prepare_extended_stable_release:
|
||||
name: Verify npm and prepare extended-stable release notes
|
||||
verify_core_npm_registry:
|
||||
name: Verify already-published core npm package
|
||||
needs: [resolve_release_target]
|
||||
if: ${{ inputs.publish_docker_only }}
|
||||
runs-on: ubuntu-latest
|
||||
@@ -2524,60 +2524,10 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout trusted release tooling
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup trusted release tooling
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Render canonical extended-stable release notes
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
TARGET_SHA: ${{ needs.resolve_release_target.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
release_version="${RELEASE_TAG#v}"
|
||||
notes_dir="${RUNNER_TEMP}/extended-stable-release-notes"
|
||||
changelog_file="${notes_dir}/CHANGELOG.md"
|
||||
notes_file="${notes_dir}/release-notes.md"
|
||||
verification_file="${notes_dir}/verification.md"
|
||||
mkdir -p "${notes_dir}"
|
||||
|
||||
git fetch --no-tags --depth=1 origin "${TARGET_SHA}"
|
||||
git show "${TARGET_SHA}:CHANGELOG.md" > "${changelog_file}"
|
||||
cat > "${verification_file}" <<EOF
|
||||
### Release verification
|
||||
|
||||
- Scope: `openclaw@${release_version}`, official npm plugins, and Gateway container images only.
|
||||
- Channel: npm and container `extended-stable`; regular `latest` is unchanged.
|
||||
- Containers: published and verified before this release page became public.
|
||||
- Excluded: ClawHub, macOS, Windows, mobile, website, and private dist-tags.
|
||||
EOF
|
||||
node --import tsx scripts/render-github-release-notes.mts \
|
||||
--changelog "${changelog_file}" \
|
||||
--tag "${RELEASE_TAG}" \
|
||||
--repository "${GITHUB_REPOSITORY}" \
|
||||
--verification-file "${verification_file}" \
|
||||
--output "${notes_file}"
|
||||
|
||||
- name: Upload canonical extended-stable release notes
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: extended-stable-release-notes-${{ inputs.tag }}
|
||||
path: ${{ runner.temp }}/extended-stable-release-notes/release-notes.md
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
publish_docker:
|
||||
name: Publish Docker images
|
||||
needs: [resolve_release_target, publish, prepare_extended_stable_release]
|
||||
if: ${{ always() && ((inputs.publish_openclaw_npm && needs.publish.result == 'success') || (inputs.publish_docker_only && needs.prepare_extended_stable_release.result == 'success')) }}
|
||||
needs: [resolve_release_target, publish, verify_core_npm_registry]
|
||||
if: ${{ always() && ((inputs.publish_openclaw_npm && needs.publish.result == 'success') || (inputs.publish_docker_only && needs.verify_core_npm_registry.result == 'success')) }}
|
||||
uses: ./.github/workflows/docker-release.yml
|
||||
with:
|
||||
tag: ${{ inputs.tag }}
|
||||
@@ -2632,20 +2582,26 @@ jobs:
|
||||
|
||||
finalize_extended_stable_github_release:
|
||||
name: Publish extended-stable GitHub release
|
||||
needs: [resolve_release_target, prepare_extended_stable_release, publish_docker]
|
||||
if: ${{ always() && inputs.publish_docker_only && needs.prepare_extended_stable_release.result == 'success' && needs.publish_docker.result == 'success' }}
|
||||
needs: [resolve_release_target, publish_docker]
|
||||
if: ${{ always() && inputs.publish_docker_only && needs.publish_docker.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download canonical extended-stable release notes
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v8.0.1
|
||||
- name: Checkout trusted release tooling
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
name: extended-stable-release-notes-${{ inputs.tag }}
|
||||
path: ${{ runner.temp }}/extended-stable-release-notes
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Publish canonical extended-stable release
|
||||
- name: Setup trusted release tooling
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Render and publish canonical extended-stable release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
@@ -2654,13 +2610,29 @@ jobs:
|
||||
set -euo pipefail
|
||||
release_version="${RELEASE_TAG#v}"
|
||||
release_title="openclaw ${release_version}"
|
||||
notes_file="${RUNNER_TEMP}/extended-stable-release-notes/release-notes.md"
|
||||
notes_dir="${RUNNER_TEMP}/extended-stable-release-notes"
|
||||
changelog_file="${notes_dir}/CHANGELOG.md"
|
||||
notes_file="${notes_dir}/release-notes.md"
|
||||
verification_file="${notes_dir}/verification.md"
|
||||
resource_file="${RUNNER_TEMP}/extended-stable-release.json"
|
||||
mkdir -p "${notes_dir}"
|
||||
|
||||
if [[ ! -s "${notes_file}" ]]; then
|
||||
echo "Canonical extended-stable release notes artifact is missing or empty." >&2
|
||||
exit 1
|
||||
fi
|
||||
git fetch --no-tags --depth=1 origin "${TARGET_SHA}"
|
||||
git show "${TARGET_SHA}:CHANGELOG.md" > "${changelog_file}"
|
||||
cat > "${verification_file}" <<EOF
|
||||
### Release verification
|
||||
|
||||
- Scope: `openclaw@${release_version}`, official npm plugins, and Gateway container images only.
|
||||
- Channel: npm and container `extended-stable`; regular `latest` is unchanged.
|
||||
- Containers: published and verified before this release page became public.
|
||||
- Excluded: ClawHub, macOS, Windows, mobile, website, and private dist-tags.
|
||||
EOF
|
||||
node --import tsx scripts/render-github-release-notes.mts \
|
||||
--changelog "${changelog_file}" \
|
||||
--tag "${RELEASE_TAG}" \
|
||||
--repository "${GITHUB_REPOSITORY}" \
|
||||
--verification-file "${verification_file}" \
|
||||
--output "${notes_file}"
|
||||
|
||||
verify_release_tag_target() {
|
||||
local direct_sha peeled_sha remote_refs remote_sha
|
||||
|
||||
+3
-3
@@ -383,9 +383,9 @@ preflight and Full Release Validation evidence as a core publish.
|
||||
Extended-stable uses this workflow only after npm publication, with
|
||||
`publish_openclaw_npm=false` and `publish_docker_only=true`. That closeout
|
||||
rechecks immutable npm evidence and the exact canonical validation branch,
|
||||
renders canonical release notes, publishes Docker, and creates the public
|
||||
notes-only non-Latest GitHub Release without entering plugin, ClawHub, or
|
||||
native-app publication. Re-running failed jobs retries the finalizer without
|
||||
publishes Docker, and only then renders canonical release notes and creates the
|
||||
public notes-only non-Latest GitHub Release without entering plugin, ClawHub,
|
||||
or native-app publication. Re-running failed jobs retries the finalizer without
|
||||
repeating a successful Docker job; a fresh dispatch repeats the idempotent
|
||||
same-version Docker verification and promotion.
|
||||
|
||||
|
||||
@@ -203,11 +203,11 @@ After that core registry readback succeeds, start Docker publication only throug
|
||||
`OpenClaw Release Publish`. Its extended-stable closeout path rechecks the
|
||||
saved npm preflight artifact, exact `Full Release Validation` evidence, exact npm
|
||||
version and `extended-stable` selector, and published tarball digest. It then
|
||||
renders and validates the canonical release notes, calls the reusable
|
||||
`Docker Release` workflow, and creates the public non-prerelease GitHub Release
|
||||
with `latest=false` only after Docker succeeds. Re-run failed jobs to retry a
|
||||
failed release-page finalizer without repeating successful Docker work; a fresh
|
||||
workflow dispatch safely repeats same-version Docker verification and
|
||||
calls the reusable `Docker Release` workflow. Only after Docker succeeds does
|
||||
the finalizer render and validate the canonical release notes and create the
|
||||
public non-prerelease GitHub Release with `latest=false`. Re-run failed jobs to
|
||||
retry a failed release-page finalizer without repeating successful Docker work;
|
||||
a fresh workflow dispatch safely repeats same-version Docker verification and
|
||||
promotion. A tag push never publishes Docker images or a release page by
|
||||
itself:
|
||||
|
||||
@@ -732,7 +732,7 @@ readback confirms that every exact package and `extended-stable` tag converged.
|
||||
- `windows_node_installer_digests`: candidate-approved compact JSON map of the current Windows installer names to their pinned `sha256:` digests; required for stable OpenClaw publish
|
||||
- `npm_telegram_run_id`: optional successful `NPM Telegram Beta E2E` run id to include in final release evidence
|
||||
- `npm_dist_tag`: npm target tag for the OpenClaw package, one of `alpha`, `beta`, `latest`, or `extended-stable`
|
||||
- `publish_docker_only`: legacy input name for the extended-stable recovery/closeout path. It requires `publish_openclaw_npm=false`, complete preflight and Full Release Validation evidence, then verifies the exact npm package, selector, and tarball digest, renders canonical notes, publishes Docker, and creates the public notes-only GitHub Release with `latest=false`.
|
||||
- `publish_docker_only`: legacy input name for the extended-stable recovery/closeout path. It requires `publish_openclaw_npm=false`, complete preflight and Full Release Validation evidence, then verifies the exact npm package, selector, and tarball digest, publishes Docker, and finally renders canonical notes and creates the public notes-only GitHub Release with `latest=false`.
|
||||
- `plugin_publish_scope`: defaults to `all-publishable`; use `selected` only for focused plugin-only repair work with `publish_openclaw_npm=false`
|
||||
- `plugins`: comma-separated `@openclaw/*` package names when `plugin_publish_scope=selected`
|
||||
- `publish_openclaw_npm`: defaults to `true`; set `false` only when using the workflow as a plugin-only repair orchestrator
|
||||
|
||||
@@ -1072,10 +1072,10 @@ describe("release validation no-push transport", () => {
|
||||
expect(dockerCall.needs).toEqual([
|
||||
"resolve_release_target",
|
||||
"publish",
|
||||
"prepare_extended_stable_release",
|
||||
"verify_core_npm_registry",
|
||||
]);
|
||||
expect(dockerCall.if).toContain("needs.publish.result == 'success'");
|
||||
expect(dockerCall.if).toContain("needs.prepare_extended_stable_release.result == 'success'");
|
||||
expect(dockerCall.if).toContain("needs.verify_core_npm_registry.result == 'success'");
|
||||
expect(dockerCall.with).toEqual({
|
||||
tag: "${{ inputs.tag }}",
|
||||
release_sha: "${{ needs.resolve_release_target.outputs.sha }}",
|
||||
@@ -1104,66 +1104,59 @@ describe("release validation no-push transport", () => {
|
||||
"publish_docker",
|
||||
]);
|
||||
|
||||
const prepareRelease = job(releasePublish, "prepare_extended_stable_release");
|
||||
const prepareSteps = prepareRelease.steps ?? [];
|
||||
const prepareStepNames = prepareSteps.map((workflowStep) => workflowStep.name);
|
||||
const verifyRegistry = job(releasePublish, "verify_core_npm_registry");
|
||||
const verifyNpm = step(
|
||||
prepareRelease,
|
||||
verifyRegistry,
|
||||
"Verify exact npm and selector readback matches preflight bytes",
|
||||
);
|
||||
const renderNotes = step(prepareRelease, "Render canonical extended-stable release notes");
|
||||
const checkoutIndex = prepareStepNames.indexOf("Checkout trusted release tooling");
|
||||
const setupIndex = prepareStepNames.indexOf("Setup trusted release tooling");
|
||||
const renderIndex = prepareStepNames.indexOf("Render canonical extended-stable release notes");
|
||||
const uploadIndex = prepareStepNames.indexOf("Upload canonical extended-stable release notes");
|
||||
|
||||
expect(prepareRelease.needs).toEqual(["resolve_release_target"]);
|
||||
expect(prepareRelease.if).toBe("${{ inputs.publish_docker_only }}");
|
||||
expect(prepareRelease.environment).toBeUndefined();
|
||||
expect(prepareRelease.permissions).toEqual({ contents: "read" });
|
||||
expect(setupIndex).toBeGreaterThan(checkoutIndex);
|
||||
expect(renderIndex).toBeGreaterThan(setupIndex);
|
||||
expect(uploadIndex).toBeGreaterThan(renderIndex);
|
||||
expect(verifyRegistry.needs).toEqual(["resolve_release_target"]);
|
||||
expect(verifyRegistry.if).toBe("${{ inputs.publish_docker_only }}");
|
||||
expect(verifyRegistry.environment).toBeUndefined();
|
||||
expect(verifyRegistry.permissions).toEqual({ contents: "read" });
|
||||
expect(verifyRegistry.steps).toHaveLength(1);
|
||||
expect(verifyNpm.run).toContain('npm view "openclaw@${version}" version');
|
||||
expect(verifyNpm.run).toContain("Published npm tarball does not match");
|
||||
expect(step(prepareRelease, "Checkout trusted release tooling").with).toMatchObject({
|
||||
ref: "${{ github.sha }}",
|
||||
"persist-credentials": false,
|
||||
});
|
||||
expect(step(prepareRelease, "Setup trusted release tooling")).toMatchObject({
|
||||
uses: "./.github/actions/setup-node-env",
|
||||
with: { "install-bun": "false" },
|
||||
});
|
||||
expect(renderNotes.run).toContain('git fetch --no-tags --depth=1 origin "${TARGET_SHA}"');
|
||||
expect(renderNotes.run).toContain('git show "${TARGET_SHA}:CHANGELOG.md"');
|
||||
expect(renderNotes.run).toContain("node --import tsx scripts/render-github-release-notes.mts");
|
||||
expect(renderNotes.run).not.toContain("render-github-release-notes.mjs");
|
||||
const uploadNotes = step(prepareRelease, "Upload canonical extended-stable release notes");
|
||||
expect(uploadNotes.with).toMatchObject({
|
||||
name: "extended-stable-release-notes-${{ inputs.tag }}",
|
||||
"if-no-files-found": "error",
|
||||
});
|
||||
|
||||
const finalizeRelease = job(releasePublish, "finalize_extended_stable_github_release");
|
||||
const downloadNotes = step(finalizeRelease, "Download canonical extended-stable release notes");
|
||||
const publishRelease = step(finalizeRelease, "Publish canonical extended-stable release");
|
||||
const finalizeSteps = finalizeRelease.steps ?? [];
|
||||
const finalizeStepNames = finalizeSteps.map((workflowStep) => workflowStep.name);
|
||||
const checkoutIndex = finalizeStepNames.indexOf("Checkout trusted release tooling");
|
||||
const setupIndex = finalizeStepNames.indexOf("Setup trusted release tooling");
|
||||
const publishIndex = finalizeStepNames.indexOf(
|
||||
"Render and publish canonical extended-stable release",
|
||||
);
|
||||
const publishRelease = step(
|
||||
finalizeRelease,
|
||||
"Render and publish canonical extended-stable release",
|
||||
);
|
||||
const publishReleaseRun = publishRelease.run ?? "";
|
||||
|
||||
expect(finalizeRelease.needs).toEqual([
|
||||
"resolve_release_target",
|
||||
"prepare_extended_stable_release",
|
||||
"publish_docker",
|
||||
]);
|
||||
expect(finalizeRelease.needs).toEqual(["resolve_release_target", "publish_docker"]);
|
||||
expect(finalizeRelease.if).toContain("inputs.publish_docker_only");
|
||||
expect(finalizeRelease.if).toContain(
|
||||
"needs.prepare_extended_stable_release.result == 'success'",
|
||||
);
|
||||
expect(finalizeRelease.if).toContain("needs.publish_docker.result == 'success'");
|
||||
expect(finalizeRelease.environment).toBe("npm-release");
|
||||
expect(finalizeRelease.permissions).toEqual({ contents: "write" });
|
||||
expect(downloadNotes.with).toMatchObject({
|
||||
name: "extended-stable-release-notes-${{ inputs.tag }}",
|
||||
expect(checkoutIndex).toBeGreaterThan(-1);
|
||||
expect(setupIndex).toBeGreaterThan(checkoutIndex);
|
||||
expect(publishIndex).toBeGreaterThan(setupIndex);
|
||||
expect(step(finalizeRelease, "Checkout trusted release tooling").with).toMatchObject({
|
||||
ref: "${{ github.sha }}",
|
||||
"persist-credentials": false,
|
||||
});
|
||||
expect(step(finalizeRelease, "Setup trusted release tooling")).toMatchObject({
|
||||
uses: "./.github/actions/setup-node-env",
|
||||
with: { "install-bun": "false" },
|
||||
});
|
||||
expect(publishReleaseRun).toContain('git fetch --no-tags --depth=1 origin "${TARGET_SHA}"');
|
||||
expect(publishReleaseRun).toContain('git show "${TARGET_SHA}:CHANGELOG.md"');
|
||||
expect(publishReleaseRun).toContain(
|
||||
"node --import tsx scripts/render-github-release-notes.mts",
|
||||
);
|
||||
expect(publishReleaseRun).not.toContain("render-github-release-notes.mjs");
|
||||
expect(publishReleaseRun.indexOf("render-github-release-notes.mts")).toBeLessThan(
|
||||
publishReleaseRun.indexOf('gh release create "${RELEASE_TAG}"'),
|
||||
);
|
||||
expect(publishReleaseRun).toContain("verify_release_tag_target");
|
||||
expect(publishReleaseRun).toContain('gh release create "${RELEASE_TAG}"');
|
||||
expect(publishReleaseRun).toContain("--verify-tag");
|
||||
@@ -1181,6 +1174,8 @@ describe("release validation no-push transport", () => {
|
||||
expect(publishReleaseRun).not.toContain("/statuses");
|
||||
|
||||
const releasePublishText = readFileSync(releasePublishPath, "utf8");
|
||||
expect(releasePublishText).not.toContain("prepare_extended_stable_release");
|
||||
expect(releasePublishText).not.toContain("extended-stable-release-notes-${{ inputs.tag }}");
|
||||
expect(releasePublishText).not.toContain("verify_extended_stable_docker_completion");
|
||||
expect(releasePublishText).not.toContain("Docker completion status");
|
||||
expect(JSON.stringify(dockerRelease)).not.toContain("statuses");
|
||||
@@ -1199,7 +1194,7 @@ describe("release validation no-push transport", () => {
|
||||
const releasePublish = readWorkflow(".github/workflows/openclaw-release-publish.yml");
|
||||
const publishDraft = step(
|
||||
job(releasePublish, "finalize_extended_stable_github_release"),
|
||||
"Publish canonical extended-stable release",
|
||||
"Render and publish canonical extended-stable release",
|
||||
);
|
||||
const verifyTag = shellFunctionSource(publishDraft.run ?? "", "verify_release_tag_target");
|
||||
const targetSha = "a".repeat(40);
|
||||
|
||||
Reference in New Issue
Block a user