mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
ci: route extended-stable through shared release publish
This commit is contained in:
@@ -273,7 +273,8 @@ Extended-stable has one release commit and one canonical GitHub Release body.
|
||||
After version prep and approved backports, regenerate `## YYYY.M.P` with the
|
||||
regular manifest and original-main-PR provenance rules. Land it by PR, then
|
||||
validate the final branch tip before tagging. The release closeout renders that
|
||||
tag-owned section into a non-Latest release page. Re-audit after a product
|
||||
tag-owned section into the shared draft before the parent pipeline publishes
|
||||
the non-Latest release page. Re-audit after a product
|
||||
backport; a tooling-only repair needs no changelog entry. Never rewrite a
|
||||
published tag or changelog.
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ description: "Run or recover OpenClaw macOS release signing, notarization, appca
|
||||
Use with `$release-openclaw-maintainer`, `$release-openclaw-ci`, `$one-password`, and `$release-private` if it exists when stable macOS assets, release-ops mac preflight, notarization, appcast promotion, or mac release recovery is involved.
|
||||
|
||||
This is a regular stable-release skill. Do not invoke it for extended-stable;
|
||||
that track's notes-only GitHub Release does not inherit macOS assets or appcast
|
||||
promotion.
|
||||
that track's GitHub Release carries shared validation evidence but does not
|
||||
inherit macOS assets or appcast promotion.
|
||||
|
||||
## Credentials
|
||||
|
||||
|
||||
@@ -225,8 +225,9 @@ or publication work. Treat backport discovery and preparation as an ability of
|
||||
this release skill, not as a separate release workflow.
|
||||
|
||||
The backport flow covers mainline inventory, private-security reconciliation,
|
||||
approval, the staging PR, and proof handoff. After it lands, use the sequence
|
||||
below. Never route `.33+` through regular beta/stable release steps.
|
||||
approval, the staging PR, and proof handoff. After it lands, use the shared
|
||||
release pipeline with the extended-stable track inputs below, never regular
|
||||
beta/stable track inputs.
|
||||
|
||||
Extended-stable requires a visible **SDK/config backport warning** whenever a
|
||||
candidate changes the public plugin SDK or a config/default/schema/migration
|
||||
@@ -257,7 +258,9 @@ on pinned current `main` as the exact command and validation contract.
|
||||
3. Run complete Full Release Validation against the canonical branch with
|
||||
`release_profile=stable`; save its run ID and successful `run_attempt`.
|
||||
Prefer the trusted main-pinned harness, which attests the immutable target
|
||||
SHA in its v3 manifest. Any candidate branch change invalidates both gates.
|
||||
SHA in its Full Release Validation evidence manifest with schema
|
||||
`openclaw.release-validation-evidence/v3`. Any candidate branch change
|
||||
invalidates both gates.
|
||||
4. Require the tip still equals the frozen SHA, then create signed `vYYYY.M.P`.
|
||||
Never move or delete a final tag; later source changes need a new patch.
|
||||
5. Require the saved validation run to be complete and successful, bind its
|
||||
@@ -265,38 +268,28 @@ on pinned current `main` as the exact command and validation contract.
|
||||
canonical branch, a direct current-`main` run whose workflow SHA is still
|
||||
reachable from main, or a trusted main-pinned `release-ci/*` harness. Reject
|
||||
narrow reruns.
|
||||
6. Dispatch `plugin-npm-release.yml` from the same branch with
|
||||
`publish_scope=all-publishable`, the full release SHA as `ref`, and
|
||||
`npm_dist_tag=extended-stable`. Require complete exact-version and selector
|
||||
readback, then save the successful plugin run ID.
|
||||
7. Publish core with the tag, `npm_dist_tag=extended-stable`, all three run IDs,
|
||||
and `full_release_validation_run_attempt=<saved-attempt>`. Normally dispatch
|
||||
from the canonical branch. For a workflow-only recovery after the candidate
|
||||
is immutable, dispatch trusted current `main` with
|
||||
`release_candidate_branch=extended-stable/YYYY.M.33`; it still publishes the
|
||||
tag checkout and accepts canonical-branch, current-main, or trusted-pinned
|
||||
validation evidence; the prepared tarball and every evidence identity must
|
||||
still match the candidate SHA.
|
||||
8. From a clean current-`main` checkout, run
|
||||
6. Dispatch trusted current-main `OpenClaw Release Publish` with the tag,
|
||||
`npm_dist_tag=extended-stable`, `publish_openclaw_npm=true`, the saved
|
||||
preflight and Full Release Validation run IDs, and the saved validation run
|
||||
attempt. The parent derives `release_candidate_branch`, creates the draft,
|
||||
publishes every official npm plugin and core under `extended-stable`,
|
||||
attaches release evidence, skips ClawHub/native publication, publishes
|
||||
Docker, and finalizes the release with `latest=false`.
|
||||
7. From a clean current-`main` checkout, run
|
||||
`node --import tsx scripts/openclaw-npm-postpublish-verify.ts YYYY.M.P`.
|
||||
Verify signatures, provenance, inventories, exact versions, and selectors.
|
||||
Use the generated repair only for the root selector; repair other selectors
|
||||
with approved credential-isolated tooling. Never republish a version.
|
||||
9. Require `Docker Release` to verify default, slim, browser, and architecture
|
||||
8. Require `Docker Release` to verify default, slim, browser, and architecture
|
||||
images in GHCR and Docker Hub, including attestations and platform versions.
|
||||
It must advance only
|
||||
`extended-stable`, `extended-stable-slim`, and `extended-stable-browser` by
|
||||
digest and refuse automatic rollback. For alias repair, dispatch the
|
||||
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, 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.
|
||||
It must advance only the `extended-stable*` aliases by digest and refuse
|
||||
automatic rollback. For alias repair, dispatch the approval-gated
|
||||
`docker-channel-promote.yml` from current `main` with the exact tag; never
|
||||
rebuild or move the release tag. Re-run failed jobs for finalizer recovery.
|
||||
Use `publish_docker_only=true` only when npm was already published outside
|
||||
the parent pipeline. This track selects npm `extended-stable`, leaving npm
|
||||
`latest` unchanged, and does not publish macOS, Windows, mobile, website,
|
||||
ClawHub, or private dist-tag artifacts.
|
||||
|
||||
## Keep release channel naming aligned
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ extended-stable package and publication constraints.
|
||||
- Carry the complete current-main Docker release-channel unit in the tagged
|
||||
tree: workflow, promoter, policy, shared release-version classifier, tests,
|
||||
and workflow validation. GitHub evaluates tag-push workflows from that tree.
|
||||
- Exclude ClawHub publication, GitHub Release assets, the macOS app, Windows
|
||||
Hub, mobile apps, website downloads, npm `latest`, and private-repository
|
||||
dist-tags. The release closeout creates a notes-only GitHub Release page.
|
||||
- Exclude ClawHub publication, native-app artifacts, website downloads, npm
|
||||
`latest`, and private-repository dist-tags. The shared release pipeline
|
||||
attaches dependency and validation evidence to the non-Latest GitHub Release.
|
||||
- Review the complete mainline delta using the shared evidence-driven audit.
|
||||
Do not stop after the first obvious fixes or consider public PRs, titles, or
|
||||
dependency bumps the complete source set.
|
||||
@@ -155,7 +155,7 @@ fi
|
||||
|
||||
Do not use GitHub's latest nonprerelease Release as the source of truth. The
|
||||
npm `extended-stable` selector remains authoritative for the active line; its
|
||||
notes-only GitHub Release is always created with `latest=false`. In bootstrap
|
||||
evidence-bearing GitHub Release is always created with `latest=false`. In bootstrap
|
||||
mode, record the approving maintainer and approved base commit. Stop before
|
||||
discovery or mutation if npm, the canonical branch, tags, package versions,
|
||||
approved base, or protected `main` disagree.
|
||||
@@ -365,9 +365,8 @@ Report:
|
||||
harness compatibility repair, and superseded validation runs;
|
||||
- remaining security, release, or maintainer approvals;
|
||||
- the coordinated PR URL or why no PR was opened;
|
||||
- exact intended Docker images and aliases, plus explicit confirmation that
|
||||
the notes-only, non-Latest GitHub Release is the only other publication
|
||||
planned.
|
||||
- exact intended Docker images and aliases, GitHub Release evidence assets, and
|
||||
explicit confirmation that no native or ClawHub artifacts are planned.
|
||||
|
||||
Then follow the parent skill's publish and recovery sequence. Keep exact
|
||||
branch/tag/package/run identity, never republish for selector repair, and move
|
||||
|
||||
@@ -13,10 +13,10 @@ publish skill; use `$release-openclaw-maintainer` before changing release state.
|
||||
|
||||
- Resolve short suffixes like `.27` to the concrete CalVer version from the
|
||||
current date/context, then say the resolved version.
|
||||
- Resolve the track first. Regular beta/stable uses a GitHub Release and the
|
||||
platform graph; extended-stable uses a notes-only GitHub Release, its
|
||||
canonical branch, npm selector, and Gateway surfaces. Do not require one
|
||||
track's artifacts from the other.
|
||||
- Resolve the track first. Both tracks use the shared GitHub Release evidence
|
||||
ledger. Regular beta/stable also uses the platform graph; extended-stable
|
||||
uses its canonical branch, npm selector, and Gateway surfaces. Do not require
|
||||
one track's native or ClawHub artifacts from the other.
|
||||
- Verify live state. Do not trust local checkout state, release notes, or old
|
||||
memory as current truth.
|
||||
- If the checkout is dirty or divergent, use it only for scripts/reference.
|
||||
@@ -77,17 +77,20 @@ Use these checks only for the regular orchestrated release track.
|
||||
|
||||
## Extended-stable checks
|
||||
|
||||
Extended-stable has a notes-only GitHub Release ledger. Verify it alongside
|
||||
Extended-stable has a GitHub Release with shared release evidence but no native
|
||||
or ClawHub artifacts. Verify it alongside
|
||||
the live tag, workflow, registry, provenance, and image state.
|
||||
|
||||
1. **Identity:** require final `v<VERSION>` at patch `33+`, with no suffix,
|
||||
contained in `extended-stable/YYYY.M.33`. Only an active candidate must equal
|
||||
the tip. Root and every publishable official plugin must declare `<VERSION>`.
|
||||
Require the Git tag and a public, non-prerelease GitHub Release whose title
|
||||
and canonical body match the tag. Require `isLatest=false` and no native or
|
||||
platform assets.
|
||||
2. **Workflow chain:** find successful preflight, complete validation, plugin
|
||||
npm, and core publish runs on the canonical branch and SHA. Validation must
|
||||
and canonical body match the tag. Require `isLatest=false`, the dependency
|
||||
evidence, immutable Full Release Validation manifest, postpublish evidence,
|
||||
and their checksums. Require no native or ClawHub assets.
|
||||
2. **Workflow chain:** find the successful parent release run plus its
|
||||
preflight, complete validation, plugin npm, and core publish children.
|
||||
Validation must
|
||||
use `rerun_group=all`, `release_profile=stable`, blocking soak/performance,
|
||||
and the saved attempt. Core publish must reference all three run IDs and bind
|
||||
its manifest, workflow ref, and tarball digest to the release SHA.
|
||||
@@ -102,14 +105,14 @@ the live tag, workflow, registry, provenance, and image state.
|
||||
5. **Docker:** verify exact default, slim, browser, and architecture images and
|
||||
attestations in both registries. Only the three `extended-stable*` aliases may
|
||||
resolve to those digests. Require the successful `OpenClaw Release Publish`
|
||||
closeout run whose Docker job precedes GitHub Release publication. Repair
|
||||
parent run whose Docker job precedes GitHub Release publication. Repair
|
||||
aliases through current-main `Docker Channel Promotion` for the exact tag,
|
||||
without rebuilding.
|
||||
6. **Recovery:** never republish. Use the generated command only for the root
|
||||
selector and approved credential-isolated tooling for others, then repeat
|
||||
complete readback. Do not require ClawHub, native/mobile apps, website,
|
||||
private dist-tags, or regular `latest`. Require the notes-only GitHub
|
||||
Release, but do not require regular-release assets or platform evidence.
|
||||
private dist-tags, or regular `latest`. Require shared release evidence, but
|
||||
do not require regular native or ClawHub assets.
|
||||
|
||||
## Shared live smoke
|
||||
|
||||
|
||||
@@ -979,6 +979,8 @@ jobs:
|
||||
PLUGIN_NPM_RUN_ID: ${{ inputs.plugin_npm_run_id }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
EXPECTED_EXTENDED_STABLE_BRANCH: ${{ inputs.release_candidate_branch || github.ref_name }}
|
||||
EXPECTED_ORCHESTRATOR_BRANCH: ${{ inputs.release_candidate_branch != '' && github.ref_name || '' }}
|
||||
EXPECTED_ORCHESTRATOR_SHA: ${{ inputs.release_candidate_branch != '' && github.workflow_sha || '' }}
|
||||
RUN_KIND: plugin
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -20,7 +20,7 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
windows_node_tag:
|
||||
description: Exact openclaw-windows-node release tag, required for stable OpenClaw publish
|
||||
description: Exact openclaw-windows-node release tag, required for regular stable OpenClaw publish
|
||||
required: false
|
||||
type: string
|
||||
windows_node_installer_digests:
|
||||
@@ -36,7 +36,7 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
npm_dist_tag:
|
||||
description: npm dist-tag for the OpenClaw package
|
||||
description: npm dist-tag passed to the plugin and core publishers; extended-stable leaves npm latest unchanged
|
||||
required: true
|
||||
default: beta
|
||||
type: choice
|
||||
@@ -58,12 +58,12 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
publish_openclaw_npm:
|
||||
description: Publish the OpenClaw npm package after plugin npm succeeds; ClawHub may still run
|
||||
description: Publish the OpenClaw npm package under npm_dist_tag after plugin npm succeeds; ClawHub may still run
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
publish_docker_only:
|
||||
description: Complete an already-published extended-stable release by verifying npm, publishing Docker, then publishing its GitHub Release
|
||||
description: Recover an already-published extended-stable release by verifying npm, publishing Docker, then publishing its missing GitHub Release
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
@@ -186,24 +186,24 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" && "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
|
||||
echo "Extended-stable core npm publication stays on the canonical extended-stable release flow; use publish_docker_only=true only after its registry readback." >&2
|
||||
exit 1
|
||||
fi
|
||||
stable_release=true
|
||||
if [[ "${RELEASE_TAG}" == *"-alpha."* || "${RELEASE_TAG}" == *"-beta."* ]]; then
|
||||
stable_release=false
|
||||
fi
|
||||
regular_stable_release="${stable_release}"
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" ]]; then
|
||||
regular_stable_release=false
|
||||
fi
|
||||
if [[ -n "${WINDOWS_NODE_TAG}" && ! "${WINDOWS_NODE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$ ]]; then
|
||||
echo "windows_node_tag must be an explicit openclaw-windows-node release tag, not latest: ${WINDOWS_NODE_TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && "${stable_release}" == "true" && -z "${WINDOWS_NODE_TAG}" ]]; then
|
||||
echo "Stable OpenClaw publish requires an explicit windows_node_tag." >&2
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && "${regular_stable_release}" == "true" && -z "${WINDOWS_NODE_TAG}" ]]; then
|
||||
echo "Regular stable OpenClaw publish requires an explicit windows_node_tag." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && "${stable_release}" == "true" && -z "${WINDOWS_NODE_INSTALLER_DIGESTS}" ]]; then
|
||||
echo "Stable OpenClaw publish requires candidate-approved windows_node_installer_digests." >&2
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && "${regular_stable_release}" == "true" && -z "${WINDOWS_NODE_INSTALLER_DIGESTS}" ]]; then
|
||||
echo "Regular stable OpenClaw publish requires candidate-approved windows_node_installer_digests." >&2
|
||||
exit 1
|
||||
fi
|
||||
tideclaw_alpha_publish=false
|
||||
@@ -250,10 +250,10 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
expected_validation_branch="${WORKFLOW_REF#refs/heads/}"
|
||||
if [[ "${PUBLISH_DOCKER_ONLY}" == "true" ]]; then
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" ]]; then
|
||||
if [[ ! "${RELEASE_TAG}" =~ ^v([0-9]{4})\.([1-9][0-9]*)\.([1-9][0-9]*)$ ]] || \
|
||||
(( 10#${BASH_REMATCH[3]:-0} < 33 )); then
|
||||
echo "Extended-stable closeout requires a final .33+ release tag." >&2
|
||||
echo "Extended-stable publication requires a final .33+ release tag." >&2
|
||||
exit 1
|
||||
fi
|
||||
expected_validation_branch="extended-stable/${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.33"
|
||||
@@ -262,7 +262,7 @@ jobs:
|
||||
|
||||
- name: Validate stable Windows source release
|
||||
id: windows_source
|
||||
if: ${{ inputs.publish_openclaw_npm }}
|
||||
if: ${{ inputs.publish_openclaw_npm && inputs.npm_dist_tag != 'extended-stable' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
@@ -282,11 +282,11 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(printf '%s' "${source_json}" | jq -r '.isDraft')" == "true" ]]; then
|
||||
echo "Stable OpenClaw publish requires a published Windows source release." >&2
|
||||
echo "Regular stable OpenClaw publish requires a published Windows source release." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(printf '%s' "${source_json}" | jq -r '.isPrerelease')" == "true" ]]; then
|
||||
echo "Stable OpenClaw publish requires a non-prerelease Windows source release." >&2
|
||||
echo "Regular stable OpenClaw publish requires a non-prerelease Windows source release." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -550,7 +550,7 @@ jobs:
|
||||
- name: Validate release tag is reachable from a trusted release branch
|
||||
env:
|
||||
EXPECTED_VALIDATION_BRANCH: ${{ steps.inputs.outputs.expected_validation_branch }}
|
||||
PUBLISH_DOCKER_ONLY: ${{ inputs.publish_docker_only && 'true' || 'false' }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
WORKFLOW_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
@@ -559,7 +559,7 @@ jobs:
|
||||
+refs/heads/main:refs/remotes/origin/main \
|
||||
'+refs/heads/release/*:refs/remotes/origin/release/*' \
|
||||
'+refs/heads/extended-stable/*:refs/remotes/origin/extended-stable/*'
|
||||
if [[ "${PUBLISH_DOCKER_ONLY}" == "true" ]]; then
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" ]]; then
|
||||
expected_ref="refs/remotes/origin/${EXPECTED_VALIDATION_BRANCH}"
|
||||
if git show-ref --verify --quiet "${expected_ref}" && \
|
||||
git merge-base --is-ancestor HEAD "${expected_ref}"; then
|
||||
@@ -687,6 +687,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PARENT_WORKFLOW_BRANCH: ${{ github.ref_name }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
|
||||
PLUGINS: ${{ inputs.plugins }}
|
||||
run: |
|
||||
@@ -715,6 +716,9 @@ jobs:
|
||||
if [[ -n "${PLUGINS// }" ]]; then
|
||||
plan_args+=(--plugins "${PLUGINS}")
|
||||
fi
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" ]]; then
|
||||
plan_args+=(--skip-clawhub)
|
||||
fi
|
||||
|
||||
CLAWHUB_REGISTRY="${CLAWHUB_REGISTRY:-https://clawhub.ai}" \
|
||||
node --import tsx \
|
||||
@@ -809,7 +813,7 @@ jobs:
|
||||
--output "${notes_file}"
|
||||
|
||||
- name: Write Android release approval
|
||||
if: ${{ inputs.publish_openclaw_npm && !contains(inputs.tag, '-alpha.') && !contains(inputs.tag, '-beta.') }}
|
||||
if: ${{ inputs.publish_openclaw_npm && inputs.npm_dist_tag != 'extended-stable' && !contains(inputs.tag, '-alpha.') && !contains(inputs.tag, '-beta.') }}
|
||||
env:
|
||||
RELEASE_PUBLISH_BRANCH: ${{ github.ref_name }}
|
||||
RELEASE_PUBLISH_RUN_ID: ${{ github.run_id }}
|
||||
@@ -839,13 +843,13 @@ jobs:
|
||||
NODE
|
||||
|
||||
- name: Attest Android release approval
|
||||
if: ${{ inputs.publish_openclaw_npm && !contains(inputs.tag, '-alpha.') && !contains(inputs.tag, '-beta.') }}
|
||||
if: ${{ inputs.publish_openclaw_npm && inputs.npm_dist_tag != 'extended-stable' && !contains(inputs.tag, '-alpha.') && !contains(inputs.tag, '-beta.') }}
|
||||
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
|
||||
with:
|
||||
subject-path: ${{ runner.temp }}/android-release-approval/approval.json
|
||||
|
||||
- name: Upload Android release approval
|
||||
if: ${{ inputs.publish_openclaw_npm && !contains(inputs.tag, '-alpha.') && !contains(inputs.tag, '-beta.') }}
|
||||
if: ${{ inputs.publish_openclaw_npm && inputs.npm_dist_tag != 'extended-stable' && !contains(inputs.tag, '-alpha.') && !contains(inputs.tag, '-beta.') }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: android-release-approval-${{ github.run_id }}
|
||||
@@ -884,11 +888,11 @@ jobs:
|
||||
openclaw_npm_expected_workflow_sha="${PARENT_WORKFLOW_SHA}"
|
||||
|
||||
is_stable_release() {
|
||||
[[ "${RELEASE_TAG}" != *"-alpha."* && "${RELEASE_TAG}" != *"-beta."* ]]
|
||||
[[ "${RELEASE_NPM_DIST_TAG}" != "extended-stable" && "${RELEASE_TAG}" != *"-alpha."* && "${RELEASE_TAG}" != *"-beta."* ]]
|
||||
}
|
||||
|
||||
is_android_release() {
|
||||
[[ "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*)?$ ]]
|
||||
[[ "${RELEASE_NPM_DIST_TAG}" != "extended-stable" && "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*)?$ ]]
|
||||
}
|
||||
|
||||
verify_child_run_sha() {
|
||||
@@ -1932,7 +1936,7 @@ jobs:
|
||||
local validation_target_sha validation_url validation_workflow_ref
|
||||
local -a verify_args
|
||||
|
||||
skip_clawhub="${1:-false}"
|
||||
skip_clawhub="${1:-${force_skip_clawhub}}"
|
||||
|
||||
release_version="${RELEASE_TAG#v}"
|
||||
evidence_path="${POSTPUBLISH_EVIDENCE_DIR}/release-postpublish-evidence.json"
|
||||
@@ -2044,7 +2048,7 @@ jobs:
|
||||
telegram_line="- npm Telegram beta E2E: not supplied"
|
||||
fi
|
||||
clawhub_runtime_state_path="${RUNNER_TEMP}/openclaw-release-clawhub-runtime-state-proof.json"
|
||||
write_clawhub_runtime_state false "${clawhub_runtime_state_path}"
|
||||
write_clawhub_runtime_state "${force_skip_clawhub}" "${clawhub_runtime_state_path}"
|
||||
clawhub_line="$(jq -r '.proofLines.normal' "${clawhub_runtime_state_path}")"
|
||||
clawhub_bootstrap_line="$(jq -r '.proofLines.bootstrap' "${clawhub_runtime_state_path}")"
|
||||
windows_line=""
|
||||
@@ -2122,16 +2126,28 @@ jobs:
|
||||
|
||||
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}")"
|
||||
force_skip_clawhub=false
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" ]]; then
|
||||
force_skip_clawhub=true
|
||||
fi
|
||||
{
|
||||
echo "### Publish sequence"
|
||||
echo
|
||||
echo "- Workflow ref: \`${CHILD_WORKFLOW_REF}\`"
|
||||
echo "- Normal ClawHub workflow ref: release tag \`${RELEASE_TAG}\`"
|
||||
echo "- ClawHub bootstrap workflow ref: \`${bootstrap_summary_ref}\` at \`${bootstrap_summary_sha}\`"
|
||||
if [[ "${force_skip_clawhub}" == "true" ]]; then
|
||||
echo "- ClawHub: skipped by release track"
|
||||
else
|
||||
echo "- Normal ClawHub workflow ref: release tag \`${RELEASE_TAG}\`"
|
||||
echo "- ClawHub bootstrap workflow ref: \`${bootstrap_summary_ref}\` at \`${bootstrap_summary_sha}\`"
|
||||
fi
|
||||
echo "- Release tag: \`${RELEASE_TAG}\`"
|
||||
echo "- Release SHA: \`${TARGET_SHA}\`"
|
||||
echo "- Release approval: this workflow job"
|
||||
echo "- Plugin npm and ClawHub publish: dispatched in parallel"
|
||||
if [[ "${force_skip_clawhub}" == "true" ]]; then
|
||||
echo "- Plugin npm publish: dispatched before core npm"
|
||||
else
|
||||
echo "- Plugin npm and ClawHub publish: dispatched in parallel"
|
||||
fi
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
|
||||
echo "- OpenClaw npm publish: starts after plugin npm succeeds"
|
||||
else
|
||||
@@ -2143,7 +2159,9 @@ jobs:
|
||||
if is_stable_release && [[ "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
|
||||
echo "- Windows Hub promotion: promoted concurrently with the OpenClaw npm publish; required before the GitHub release can be published"
|
||||
fi
|
||||
if [[ "${WAIT_FOR_CLAWHUB}" == "true" ]]; then
|
||||
if [[ "${force_skip_clawhub}" == "true" ]]; then
|
||||
:
|
||||
elif [[ "${WAIT_FOR_CLAWHUB}" == "true" ]]; then
|
||||
echo "- Workflow completion waits for ClawHub"
|
||||
else
|
||||
echo "- Workflow completion does not wait for ClawHub; monitor the dispatched ClawHub run separately"
|
||||
@@ -2172,6 +2190,12 @@ jobs:
|
||||
if [[ -n "${PLUGINS}" ]]; then
|
||||
npm_args+=(-f plugins="${PLUGINS}")
|
||||
fi
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" ]]; then
|
||||
npm_args+=(
|
||||
-f npm_dist_tag=extended-stable
|
||||
-f release_candidate_branch="${{ needs.resolve_release_target.outputs.expected_validation_branch }}"
|
||||
)
|
||||
fi
|
||||
|
||||
plugin_npm_run_id="$(dispatch_workflow plugin-npm-release.yml "${npm_args[@]}")"
|
||||
plugin_clawhub_run_id=""
|
||||
@@ -2237,14 +2261,22 @@ jobs:
|
||||
if [[ "${openclaw_npm_already_published}" == "true" ]]; then
|
||||
echo "- OpenClaw npm publish: already on npm; resuming postpublish stages" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
openclaw_npm_run_id="$(dispatch_workflow openclaw-npm-release.yml \
|
||||
-f tag="${RELEASE_TAG}" \
|
||||
-f preflight_only=false \
|
||||
-f preflight_run_id="${PREFLIGHT_RUN_ID}" \
|
||||
-f full_release_validation_run_id="${FULL_RELEASE_VALIDATION_RUN_ID}" \
|
||||
-f full_release_validation_run_attempt="${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" \
|
||||
-f release_publish_run_id="${GITHUB_RUN_ID}" \
|
||||
-f npm_dist_tag="${RELEASE_NPM_DIST_TAG}")"
|
||||
openclaw_npm_args=(
|
||||
-f tag="${RELEASE_TAG}"
|
||||
-f preflight_only=false
|
||||
-f preflight_run_id="${PREFLIGHT_RUN_ID}"
|
||||
-f full_release_validation_run_id="${FULL_RELEASE_VALIDATION_RUN_ID}"
|
||||
-f full_release_validation_run_attempt="${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}"
|
||||
-f release_publish_run_id="${GITHUB_RUN_ID}"
|
||||
-f npm_dist_tag="${RELEASE_NPM_DIST_TAG}"
|
||||
)
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" ]]; then
|
||||
openclaw_npm_args+=(
|
||||
-f release_candidate_branch="${{ needs.resolve_release_target.outputs.expected_validation_branch }}"
|
||||
-f plugin_npm_run_id="${plugin_npm_run_id}"
|
||||
)
|
||||
fi
|
||||
openclaw_npm_run_id="$(dispatch_workflow openclaw-npm-release.yml "${openclaw_npm_args[@]}")"
|
||||
echo "- OpenClaw npm run ID: \`${openclaw_npm_run_id}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
else
|
||||
|
||||
@@ -46,6 +46,10 @@ on:
|
||||
description: Approved OpenClaw Release Publish workflow run id
|
||||
required: false
|
||||
type: string
|
||||
release_candidate_branch:
|
||||
description: Canonical extended-stable branch when trusted main release tooling publishes its immutable target
|
||||
required: false
|
||||
type: string
|
||||
preflight_only:
|
||||
description: Prepare and verify immutable plugin npm artifacts without publishing
|
||||
required: true
|
||||
@@ -101,6 +105,7 @@ jobs:
|
||||
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
|
||||
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
|
||||
RELEASE_PUBLISH_RUN_ID: ${{ github.event_name == 'workflow_dispatch' && inputs.release_publish_run_id || '' }}
|
||||
RELEASE_CANDIDATE_BRANCH: ${{ github.event_name == 'workflow_dispatch' && inputs.release_candidate_branch || '' }}
|
||||
SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
WORKFLOW_REF: ${{ github.ref }}
|
||||
WORKFLOW_SHA: ${{ github.workflow_sha }}
|
||||
@@ -119,6 +124,10 @@ jobs:
|
||||
echo "Plugin npm preflight must not include release_publish_run_id." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${RELEASE_CANDIDATE_BRANCH// }" ]]; then
|
||||
echo "Plugin npm preflight must not include release_candidate_branch." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [[ "${NPM_DIST_TAG}" == "extended-stable" ]]; then
|
||||
if [[ "${PUBLISH_SCOPE}" != "all-publishable" || -n "${RELEASE_PLUGINS// }" ]]; then
|
||||
@@ -138,12 +147,32 @@ jobs:
|
||||
release_month="${BASH_REMATCH[2]}"
|
||||
extended_stable_branch="extended-stable/${release_year}.${release_month}.33"
|
||||
timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags origin "+refs/heads/${extended_stable_branch}:refs/remotes/origin/${extended_stable_branch}"
|
||||
if [[ -n "${RELEASE_CANDIDATE_BRANCH// }" ]]; then
|
||||
if [[ "${RELEASE_CANDIDATE_BRANCH}" != "${extended_stable_branch}" || "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
|
||||
echo "release_candidate_branch must be ${extended_stable_branch} and is only valid with trusted main workflow tooling." >&2
|
||||
exit 1
|
||||
fi
|
||||
timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
if ! git merge-base --is-ancestor "${WORKFLOW_SHA}" origin/main; then
|
||||
echo "Extended-stable plugin workflow revision is not reachable from current main." >&2
|
||||
exit 1
|
||||
fi
|
||||
if git merge-base --is-ancestor HEAD "refs/remotes/origin/${extended_stable_branch}"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "Extended-stable plugin target must be reachable from ${extended_stable_branch}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${WORKFLOW_REF}" == "refs/heads/${extended_stable_branch}" ]] && [[ "$(git rev-parse HEAD)" == "$(git rev-parse "refs/remotes/origin/${extended_stable_branch}")" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
echo "Extended-stable plugin npm publishes must run from ${extended_stable_branch} at its exact branch tip." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${RELEASE_CANDIDATE_BRANCH// }" ]]; then
|
||||
echo "release_candidate_branch is only valid for extended-stable publication." >&2
|
||||
exit 1
|
||||
fi
|
||||
timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags origin \
|
||||
+refs/heads/main:refs/remotes/origin/main \
|
||||
'+refs/heads/release/*:refs/remotes/origin/release/*'
|
||||
@@ -1274,9 +1303,6 @@ jobs:
|
||||
cp \
|
||||
scripts/lib/tsx-cli-shim.mjs \
|
||||
.publication-target/scripts/lib/tsx-cli-shim.mjs
|
||||
cp \
|
||||
scripts/lib/tsx-cli-shim.mjs \
|
||||
.publication-target/scripts/lib/tsx-cli-shim.mjs
|
||||
|
||||
- name: Setup OIDC publication target
|
||||
if: steps.publication_evidence.outputs.publish_route == 'npm-oidc'
|
||||
|
||||
+12
-13
@@ -171,11 +171,12 @@ Gateway extended-stable runs npm preflight, Full Release Validation, and plugin
|
||||
npm release from `extended-stable/YYYY.M.33`; core publish consumes those three
|
||||
run IDs plus the validation attempt. Complete evidence may come from the
|
||||
canonical branch, reachable current-main tooling, or the trusted main-pinned
|
||||
`release-ci/*` harness when its v3 manifest binds the exact target and attempt.
|
||||
The post-npm `OpenClaw Release Publish` closeout publishes Gateway images and
|
||||
only the `extended-stable*` aliases, then publishes the notes-only GitHub
|
||||
Release with `latest=false`; it skips ClawHub, native-app, website, and private
|
||||
dist-tag surfaces. See [Monthly Gateway extended-stable
|
||||
`release-ci/*` harness when its Full Release Validation evidence manifest uses
|
||||
schema `openclaw.release-validation-evidence/v3` and binds the exact target and
|
||||
attempt. `OpenClaw Release Publish` then runs the shared draft, plugin npm, core
|
||||
npm, evidence, Docker, and finalization stages with
|
||||
`npm_dist_tag=extended-stable`. That selection leaves npm `latest` unchanged;
|
||||
the track skips ClawHub, native-app, website, and private dist-tag surfaces. See [Monthly Gateway extended-stable
|
||||
publication](/reference/RELEASING#monthly-gateway-extended-stable-publication)
|
||||
for commands and recovery.
|
||||
|
||||
@@ -315,14 +316,12 @@ Focused plugin-only repairs use `plugin_publish_scope=selected` with a nonempty
|
||||
package list. Plugin-only `all-publishable` runs require the same immutable npm
|
||||
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,
|
||||
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.
|
||||
Extended-stable uses the same workflow with `publish_openclaw_npm=true` and
|
||||
`npm_dist_tag=extended-stable`. The parent publishes all official npm plugins
|
||||
and core under that selector, creates the draft and attaches immutable release
|
||||
evidence, skips ClawHub/native publication, publishes Docker, and finalizes the
|
||||
non-Latest GitHub Release. `publish_docker_only=true` is reserved for recovery
|
||||
when npm was already published outside the parent pipeline.
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-release-publish.yml \
|
||||
|
||||
+46
-94
@@ -18,7 +18,8 @@ OpenClaw exposes four user-facing update channels:
|
||||
|
||||
Extended-stable ships the trailing month's Gateway, official npm plugins, and
|
||||
Docker images without moving regular `latest` or `main` selectors. Each release
|
||||
also has a notes-only GitHub Release page that is never marked Latest.
|
||||
also has a GitHub Release with shared validation evidence that is never marked
|
||||
Latest.
|
||||
|
||||
Tideclaw alpha builds are a separate internal prerelease track (npm dist-tag `alpha`), covered under [NPM workflow inputs](#npm-workflow-inputs) and [Release test boxes](#release-test-boxes).
|
||||
|
||||
@@ -109,67 +110,35 @@ equals `RELEASE_SHA`, then push signed `vYYYY.M.P`. Later changes need the next
|
||||
patch; never move or delete the tag. Tagging fixes the immutable release
|
||||
identity; it does not publish Docker images.
|
||||
|
||||
### Publish the npm packages
|
||||
### Publish the release
|
||||
|
||||
Publish every npm-publishable official plugin from the same SHA and save the
|
||||
successful run ID:
|
||||
Run the normal release orchestrator from trusted current `main`, selecting the
|
||||
extended-stable npm track:
|
||||
|
||||
```bash
|
||||
RELEASE_SHA="$(git rev-parse HEAD)"
|
||||
gh workflow run plugin-npm-release.yml \
|
||||
--ref extended-stable/YYYY.M.33 \
|
||||
-f publish_scope=all-publishable \
|
||||
-f ref="$RELEASE_SHA" \
|
||||
-f npm_dist_tag=extended-stable
|
||||
```
|
||||
|
||||
The workflow covers all `all-publishable` packages, including unchanged ones,
|
||||
and verifies every exact version and selector. Reruns reuse published versions.
|
||||
|
||||
Then publish the prepared core tarball with all three saved run identities:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-npm-release.yml \
|
||||
--ref extended-stable/YYYY.M.33 \
|
||||
-f tag=vYYYY.M.P \
|
||||
-f preflight_only=false \
|
||||
-f npm_dist_tag=extended-stable \
|
||||
-f preflight_run_id=<npm-preflight-run-id> \
|
||||
-f full_release_validation_run_id=<full-validation-run-id> \
|
||||
-f full_release_validation_run_attempt=<full-validation-run-attempt> \
|
||||
-f plugin_npm_run_id=<plugin-npm-run-id>
|
||||
```
|
||||
|
||||
If the immutable candidate has already passed its saved preflight and Full
|
||||
Release Validation but core publication needs a workflow-only recovery, dispatch
|
||||
the trusted current-`main` workflow instead. Keep the same tag and evidence
|
||||
identities; do not move the tag or republish plugins:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-npm-release.yml \
|
||||
gh workflow run openclaw-release-publish.yml \
|
||||
--ref main \
|
||||
-f tag=vYYYY.M.P \
|
||||
-f preflight_only=false \
|
||||
-f npm_dist_tag=extended-stable \
|
||||
-f release_candidate_branch=extended-stable/YYYY.M.33 \
|
||||
-f preflight_run_id=<npm-preflight-run-id> \
|
||||
-f full_release_validation_run_id=<full-validation-run-id> \
|
||||
-f full_release_validation_run_attempt=<full-validation-run-attempt> \
|
||||
-f plugin_npm_run_id=<plugin-npm-run-id>
|
||||
-f npm_dist_tag=extended-stable \
|
||||
-f plugin_publish_scope=all-publishable \
|
||||
-f publish_openclaw_npm=true
|
||||
```
|
||||
|
||||
This recovery path checks out and publishes the immutable tag and requires the
|
||||
canonical branch implied by that tag. It accepts Full Release Validation
|
||||
evidence from the canonical candidate branch directly, from current `main`
|
||||
directly when its workflow SHA is reachable from current `main`, or from the
|
||||
trusted main-pinned harness. Every accepted form must attest the immutable
|
||||
tag's SHA. Use it only when the candidate source and recorded evidence are
|
||||
unchanged.
|
||||
The parent derives the canonical `extended-stable/YYYY.M.33` branch from the
|
||||
tag and passes it to both npm children. It creates the draft GitHub Release,
|
||||
publishes every `all-publishable` official plugin and core under the
|
||||
`extended-stable` selector, verifies registry bytes, attaches dependency and
|
||||
validation evidence, publishes Docker, then finalizes the release with
|
||||
`latest=false`. ClawHub and native-app stages are disabled by the selected
|
||||
track. Use the lower-level plugin/core workflows only for an approved recovery;
|
||||
never republish an immutable version.
|
||||
|
||||
For non-production rehearsal only, add
|
||||
`-f bypass_extended_stable_guard=true` to preflight and publish. It bypasses the
|
||||
month guard only, never canonical-ref, SHA/tag/version equality, provenance,
|
||||
approval, or readback checks. Never use it for production.
|
||||
For non-production child-workflow rehearsal only, the lower-level npm workflow
|
||||
has `bypass_extended_stable_guard=true`. The normal parent publish does not
|
||||
expose that bypass. Never use it for production.
|
||||
|
||||
### Verify and recover
|
||||
|
||||
@@ -194,43 +163,23 @@ them. Never republish an immutable version.
|
||||
|
||||
Require `Docker Release` to verify exact default, slim, browser, and architecture
|
||||
images in GHCR and Docker Hub, including attestations and platform versions. It
|
||||
must advance only
|
||||
`extended-stable`, `extended-stable-slim`, and `extended-stable-browser` by
|
||||
digest; regular aliases remain unchanged and automatic rollback is rejected.
|
||||
|
||||
After that core registry readback succeeds, start Docker publication only through
|
||||
`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
|
||||
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:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-release-publish.yml \
|
||||
--ref main \
|
||||
-f tag=vYYYY.M.P \
|
||||
-f preflight_run_id=<npm-preflight-run-id> \
|
||||
-f full_release_validation_run_id=<full-validation-run-id> \
|
||||
-f full_release_validation_run_attempt=<full-validation-run-attempt> \
|
||||
-f npm_dist_tag=extended-stable \
|
||||
-f publish_openclaw_npm=false \
|
||||
-f publish_docker_only=true
|
||||
```
|
||||
must advance only `extended-stable`, `extended-stable-slim`, and
|
||||
`extended-stable-browser` by digest; regular aliases remain unchanged and
|
||||
automatic rollback is rejected. Confirm the GitHub Release contains the shared
|
||||
dependency, Full Release Validation, and postpublish evidence assets but no
|
||||
native-app assets.
|
||||
|
||||
For alias repair, run approval-gated `Docker Channel Promotion` from current
|
||||
`main` with the tag. It repeats digest, attestation, and platform checks, allows
|
||||
an explicit rollback, and never rebuilds images.
|
||||
|
||||
Slack, Discord, and Codex are the initial documented support surfaces, not a
|
||||
release allowlist: every npm-publishable official plugin ships. The regular
|
||||
checklist alone owns beta/`latest`, GitHub Release assets, ClawHub, native apps,
|
||||
mobile, website, and private dist-tags; do not run those steps for this Gateway
|
||||
path. Extended-stable owns only its notes-only, non-Latest GitHub Release page.
|
||||
release allowlist: every npm-publishable official plugin ships. The shared
|
||||
pipeline attaches dependency, Full Release Validation, and postpublish evidence
|
||||
to the extended-stable GitHub Release. The selected npm tag is
|
||||
`extended-stable`, so npm `latest` remains unchanged. Do not publish ClawHub
|
||||
packages, native apps, website artifacts, or private dist-tags from this Gateway
|
||||
track.
|
||||
|
||||
## Regular release operator checklist
|
||||
|
||||
@@ -541,19 +490,18 @@ For package-candidate Telegram proof, enable `telegram_mode=mock-openai` or `tel
|
||||
|
||||
## Regular release publish automation
|
||||
|
||||
For beta, `latest`, plugin, GitHub Release, and platform publication,
|
||||
`OpenClaw Release Publish` is the normal mutating entrypoint. The monthly
|
||||
`.33+` Gateway extended-stable path also uses its closeout after npm
|
||||
publication. The regular path orchestrates the trusted-publisher workflows in
|
||||
the order the release needs:
|
||||
For beta, `latest`, extended-stable, plugin, GitHub Release, and platform
|
||||
publication, `OpenClaw Release Publish` is the normal mutating entrypoint. It
|
||||
orchestrates the trusted-publisher workflows in the order the selected release
|
||||
track needs:
|
||||
|
||||
1. Check out the release tag and resolve its commit SHA.
|
||||
2. Verify the tag is reachable from `main` or `release/*` (or a Tideclaw alpha branch for alpha prereleases).
|
||||
3. Run `pnpm plugins:sync:check`.
|
||||
4. Dispatch `Plugin NPM Release` with `publish_scope=all-publishable` and `ref=<release-sha>`.
|
||||
5. Dispatch `Plugin ClawHub Release` with the same scope and SHA.
|
||||
5. Dispatch `Plugin ClawHub Release` with the same scope and SHA, except for the extended-stable track.
|
||||
6. Dispatch `OpenClaw NPM Release` with the release tag, npm dist-tag, and saved `preflight_run_id` after verifying the saved `full_release_validation_run_id` and exact run attempt.
|
||||
7. Verify the published npm package and selector readback, then call reusable `Docker Release` with the immutable tag and SHA. For stable releases, create or update the GitHub release as a draft, dispatch `Windows Node Release` with the explicit `windows_node_tag` and candidate-approved `windows_node_installer_digests`, and verify the canonical Windows installer/checksum assets. Also dispatch `Android Release` to build the exact-tag signed APK plus checksum and provenance. Finalize the GitHub release only after Docker and both native asset contracts succeed.
|
||||
7. Verify the published npm package and selector readback, attach release evidence, then call reusable `Docker Release` with the immutable tag and SHA. Regular stable releases also dispatch Windows and Android promotion against the draft and require those asset contracts. Extended-stable skips those native stages. Finalize the same draft only after every enabled stage succeeds.
|
||||
|
||||
Beta publish example:
|
||||
|
||||
@@ -664,7 +612,9 @@ SHA-256, and npm integrity. A mismatch requires a new package version.
|
||||
behavior or `npm_dist_tag=extended-stable` for the guarded monthly path. The
|
||||
extended-stable option requires `publish_scope=all-publishable`, an empty
|
||||
`plugins` input, a final patch at or above `33`, and the canonical
|
||||
`extended-stable/YYYY.M.33` branch at its exact tip. It never moves plugin
|
||||
`extended-stable/YYYY.M.33` branch at its exact tip, or the same immutable
|
||||
target dispatched by trusted current-main `OpenClaw Release Publish` with that
|
||||
canonical branch named in `release_candidate_branch`. It never moves plugin
|
||||
`latest` or `beta`. New package versions receive `extended-stable` atomically
|
||||
through OIDC trusted publication (`npm publish --tag extended-stable`); this
|
||||
source workflow does not use token-authenticated `npm dist-tag add`. Retries
|
||||
@@ -677,11 +627,11 @@ readback confirms that every exact package and `extended-stable` tag converged.
|
||||
- `preflight_run_id`: successful `OpenClaw NPM Release` preflight run id; required when `publish_openclaw_npm=true` or `plugin_publish_scope=all-publishable`
|
||||
- `full_release_validation_run_id`: successful `Full Release Validation` run id; required when `publish_openclaw_npm=true` or `plugin_publish_scope=all-publishable`
|
||||
- `full_release_validation_run_attempt`: exact positive attempt paired with `full_release_validation_run_id`; required whenever the run id is provided
|
||||
- `windows_node_tag`: exact non-prerelease `openclaw/openclaw-windows-node` release tag; required for stable OpenClaw publish
|
||||
- `windows_node_tag`: exact non-prerelease `openclaw/openclaw-windows-node` release tag; required for regular stable OpenClaw publish
|
||||
- `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, publishes Docker, and finally renders canonical notes and creates the public notes-only GitHub Release with `latest=false`.
|
||||
- `npm_dist_tag`: npm target tag passed to the plugin and core publishers, one of `alpha`, `beta`, `latest`, or `extended-stable`; only `latest` moves npm `latest`
|
||||
- `publish_docker_only`: recovery-only path for an extended-stable version whose npm publication already completed outside the parent pipeline. 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 creates a missing public GitHub Release with `latest=false`. Do not use it for a normal extended-stable release.
|
||||
- `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
|
||||
@@ -703,7 +653,9 @@ Rules:
|
||||
|
||||
## Regular beta/latest stable release sequence
|
||||
|
||||
This legacy sequence is for the regular orchestrated release that also owns plugins, GitHub Release, Windows, and other platform work. It is not the monthly `.33+` Gateway extended-stable path documented at the top of this page.
|
||||
This sequence uses the same orchestrator as extended-stable. Its `beta` or
|
||||
`latest` track additionally enables ClawHub and the applicable native/platform
|
||||
stages.
|
||||
|
||||
When cutting a regular orchestrated stable release:
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ passes that branch to the validation-evidence verifier. It does not substitute
|
||||
the closeout workflow's `main` ref for the candidate branch.
|
||||
|
||||
The helper's temporary `release-ci/*` ref is valid only when every child uses
|
||||
the pinned trusted workflow SHA and the v3 manifest binds the canonical branch,
|
||||
the pinned trusted workflow SHA and the Full Release Validation evidence
|
||||
manifest uses schema `openclaw.release-validation-evidence/v3` to bind the canonical branch,
|
||||
exact release commit, run ID, and attempt. Reject narrow runs, stale attempts,
|
||||
untrusted workflow SHAs, and mismatched targets.
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ type OpenClawReleaseClawHubPlanArgs = {
|
||||
releasePublishRunId: string;
|
||||
pluginPublishScope: PluginReleaseSelectionMode;
|
||||
plugins: string[];
|
||||
skipClawHub?: boolean;
|
||||
};
|
||||
|
||||
type OpenClawReleaseClawHubPlan = {
|
||||
@@ -244,6 +245,7 @@ export function parseOpenClawReleaseClawHubPlanArgs(
|
||||
let pluginPublishScope: PluginReleaseSelectionMode | undefined;
|
||||
let plugins: string[] = [];
|
||||
let pluginsFlagProvided = false;
|
||||
let skipClawHub = false;
|
||||
|
||||
for (let index = 0; index < values.length; index += 1) {
|
||||
const arg = values[index];
|
||||
@@ -285,6 +287,9 @@ export function parseOpenClawReleaseClawHubPlanArgs(
|
||||
plugins = parsePluginReleaseSelection(next());
|
||||
pluginsFlagProvided = true;
|
||||
break;
|
||||
case "--skip-clawhub":
|
||||
skipClawHub = true;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
@@ -314,6 +319,7 @@ export function parseOpenClawReleaseClawHubPlanArgs(
|
||||
releasePublishRunId: requireArg(releasePublishRunId, "--release-publish-run-id"),
|
||||
pluginPublishScope: resolvedPluginPublishScope,
|
||||
plugins,
|
||||
skipClawHub,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -335,6 +341,38 @@ export async function buildOpenClawReleaseClawHubPlan(
|
||||
"releasePublishRunAttempt",
|
||||
);
|
||||
const releasePublishRunId = requireArg(args.releasePublishRunId, "releasePublishRunId");
|
||||
if (args.skipClawHub) {
|
||||
return {
|
||||
bootstrapWorkflowSha,
|
||||
clawHubWorkflowRef: releaseTag,
|
||||
releasePublishBranch,
|
||||
normal: createDispatchTarget({
|
||||
workflow: "plugin-clawhub-release.yml",
|
||||
ref: releaseTag,
|
||||
packages: [],
|
||||
releasePublishRunId,
|
||||
releasePublishBranch,
|
||||
includePublishScope: true,
|
||||
}),
|
||||
bootstrap: createDispatchTarget({
|
||||
workflow: "plugin-clawhub-new.yml",
|
||||
ref: bootstrapWorkflowRef,
|
||||
packages: [],
|
||||
releasePublishRunId,
|
||||
releasePublishBranch,
|
||||
includePublishScope: false,
|
||||
}),
|
||||
summary: {
|
||||
normalCount: 0,
|
||||
bootstrapCount: 0,
|
||||
missingTrustedPublisherCount: 0,
|
||||
normalPlugins: "",
|
||||
bootstrapPlugins: "",
|
||||
missingTrustedPlugins: "",
|
||||
},
|
||||
verifier: { clawHubWorkflowRef: releaseTag },
|
||||
};
|
||||
}
|
||||
const plan = await collectPluginClawHubReleasePlan({
|
||||
rootDir: options.rootDir ?? resolve("."),
|
||||
selection: args.plugins,
|
||||
|
||||
@@ -175,6 +175,8 @@ export function validateExtendedStableRunIdentity({
|
||||
npmDistTag,
|
||||
expectedBranch,
|
||||
expectedSha,
|
||||
expectedOrchestratorBranch,
|
||||
expectedOrchestratorSha,
|
||||
}) {
|
||||
const expectedWorkflowName =
|
||||
kind === "preflight"
|
||||
@@ -198,10 +200,16 @@ export function validateExtendedStableRunIdentity({
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
npmDistTag === "extended-stable" &&
|
||||
(run.headBranch !== expectedBranch || run.headSha !== expectedSha)
|
||||
) {
|
||||
const directTargetIdentity = run.headBranch === expectedBranch && run.headSha === expectedSha;
|
||||
const orchestratedPluginIdentity =
|
||||
kind === "plugin" &&
|
||||
typeof expectedOrchestratorBranch === "string" &&
|
||||
expectedOrchestratorBranch.length > 0 &&
|
||||
typeof expectedOrchestratorSha === "string" &&
|
||||
expectedOrchestratorSha.length > 0 &&
|
||||
run.headBranch === expectedOrchestratorBranch &&
|
||||
run.headSha === expectedOrchestratorSha;
|
||||
if (npmDistTag === "extended-stable" && !directTargetIdentity && !orchestratedPluginIdentity) {
|
||||
throw new Error(
|
||||
`Referenced extended-stable ${kind} run must have headBranch=${expectedBranch} and headSha=${expectedSha}; got ${run.headBranch ?? "<missing>"} and ${run.headSha ?? "<missing>"}.`,
|
||||
);
|
||||
@@ -490,6 +498,8 @@ async function main() {
|
||||
npmDistTag: process.env.RELEASE_NPM_DIST_TAG,
|
||||
expectedBranch: process.env.EXPECTED_EXTENDED_STABLE_BRANCH,
|
||||
expectedSha: process.env.EXPECTED_RELEASE_SHA,
|
||||
expectedOrchestratorBranch: process.env.EXPECTED_ORCHESTRATOR_BRANCH,
|
||||
expectedOrchestratorSha: process.env.EXPECTED_ORCHESTRATOR_SHA,
|
||||
});
|
||||
console.log(`Verified referenced ${process.env.RUN_KIND} run.`);
|
||||
return;
|
||||
|
||||
@@ -1122,7 +1122,9 @@ export function validateTrustedProducerIdentity(evidence, client, verifier, trus
|
||||
}
|
||||
if (shaPinned) {
|
||||
if (manifest.version !== 3) {
|
||||
throw new Error("SHA-pinned release evidence requires a v3 manifest");
|
||||
throw new Error(
|
||||
"SHA-pinned release evidence requires a Full Release Validation manifest with schema openclaw.release-validation-evidence/v3",
|
||||
);
|
||||
}
|
||||
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");
|
||||
|
||||
@@ -1426,6 +1426,58 @@ describe("buildOpenClawReleaseClawHubPlan", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a zero-dispatch plan without reading ClawHub when the release track excludes it", async () => {
|
||||
const plan = await buildOpenClawReleaseClawHubPlan(
|
||||
{
|
||||
bootstrapWorkflowRef: "main",
|
||||
bootstrapWorkflowSha: "d".repeat(40),
|
||||
releaseTag: "v2026.6.35",
|
||||
releaseSha: "a".repeat(40),
|
||||
releasePublishBranch: "main",
|
||||
releasePublishRunAttempt: "1",
|
||||
releasePublishRunId: "12345",
|
||||
pluginPublishScope: "all-publishable",
|
||||
plugins: [],
|
||||
skipClawHub: true,
|
||||
},
|
||||
{
|
||||
fetchImpl: () => {
|
||||
throw new Error("ClawHub must not be queried for an excluded release track.");
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(plan.normal).toMatchObject({ shouldDispatch: false, packages: [] });
|
||||
expect(plan.bootstrap).toMatchObject({ shouldDispatch: false, packages: [] });
|
||||
expect(plan.summary).toEqual({
|
||||
normalCount: 0,
|
||||
bootstrapCount: 0,
|
||||
missingTrustedPublisherCount: 0,
|
||||
normalPlugins: "",
|
||||
bootstrapPlugins: "",
|
||||
missingTrustedPlugins: "",
|
||||
});
|
||||
expect(
|
||||
parseOpenClawReleaseClawHubPlanArgs([
|
||||
"--bootstrap-workflow-ref",
|
||||
"main",
|
||||
"--bootstrap-workflow-sha",
|
||||
"d".repeat(40),
|
||||
"--release-tag",
|
||||
"v2026.6.35",
|
||||
"--release-sha",
|
||||
"a".repeat(40),
|
||||
"--release-publish-branch",
|
||||
"main",
|
||||
"--release-publish-run-attempt",
|
||||
"1",
|
||||
"--release-publish-run-id",
|
||||
"12345",
|
||||
"--skip-clawhub",
|
||||
]).skipClawHub,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects incompatible all-publishable plugin selection args", () => {
|
||||
expect(() =>
|
||||
parseOpenClawReleaseClawHubPlanArgs([
|
||||
|
||||
@@ -367,6 +367,47 @@ describe("extended-stable npm run identity", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts a plugin run dispatched by the trusted main orchestrator for the exact target", () => {
|
||||
const workflowSha = "c".repeat(40);
|
||||
const pluginRun = {
|
||||
workflowName: "Plugin NPM Release",
|
||||
displayTitle: `Plugin NPM Release [extended-stable] ${sha}`,
|
||||
event: "workflow_dispatch",
|
||||
status: "completed",
|
||||
conclusion: "success",
|
||||
headBranch: "main",
|
||||
headSha: workflowSha,
|
||||
};
|
||||
expect(() =>
|
||||
validateExtendedStableRunIdentity({
|
||||
run: pluginRun,
|
||||
kind: "plugin",
|
||||
npmDistTag: "extended-stable",
|
||||
expectedBranch: branch,
|
||||
expectedSha: sha,
|
||||
expectedOrchestratorBranch: "main",
|
||||
expectedOrchestratorSha: workflowSha,
|
||||
}),
|
||||
).not.toThrow();
|
||||
for (const changes of [
|
||||
{ headBranch: "release/2026.6.35" },
|
||||
{ headSha: "not-a-sha" },
|
||||
{ displayTitle: `Plugin NPM Release [extended-stable] ${"b".repeat(40)}` },
|
||||
]) {
|
||||
expect(() =>
|
||||
validateExtendedStableRunIdentity({
|
||||
run: { ...pluginRun, ...changes },
|
||||
kind: "plugin",
|
||||
npmDistTag: "extended-stable",
|
||||
expectedBranch: branch,
|
||||
expectedSha: sha,
|
||||
expectedOrchestratorBranch: "main",
|
||||
expectedOrchestratorSha: workflowSha,
|
||||
}),
|
||||
).toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
["wrong branch", { headBranch: "main" }],
|
||||
["missing branch", { headBranch: undefined }],
|
||||
|
||||
@@ -158,10 +158,11 @@ describe("minimal npm extended-stable workflow", () => {
|
||||
const recheck = step(parsed.jobs?.publish_openclaw_npm, "Recheck npm release request");
|
||||
expect(recheck.env?.NPM_WORKFLOW_REF).toBe(validate.env?.NPM_WORKFLOW_REF);
|
||||
expect(releaseDocs).toContain("--ref main");
|
||||
expect(releaseDocs).toContain("-f release_candidate_branch=extended-stable/YYYY.M.33");
|
||||
expect(releaseDocs).toContain("canonical candidate branch directly");
|
||||
expect(releaseDocs).toContain("workflow SHA is reachable from current `main`");
|
||||
expect(releaseDocs).toContain("trusted main-pinned harness");
|
||||
expect(releaseDocs).toContain(
|
||||
"The parent derives the canonical `extended-stable/YYYY.M.33` branch",
|
||||
);
|
||||
expect(releaseDocs).toContain("workflow SHA remains reachable from `main`");
|
||||
expect(releaseDocs).toContain("trusted main-pinned `release-ci/*` harness");
|
||||
});
|
||||
|
||||
it("accepts arbitrary SHA preflight targets and exercises every publishable plugin package", () => {
|
||||
@@ -278,6 +279,12 @@ describe("minimal npm extended-stable workflow", () => {
|
||||
"Verify plugin npm release run metadata",
|
||||
);
|
||||
expect(verify.env?.RUN_KIND).toBe("plugin");
|
||||
expect(verify.env?.EXPECTED_ORCHESTRATOR_BRANCH).toBe(
|
||||
"${{ inputs.release_candidate_branch != '' && github.ref_name || '' }}",
|
||||
);
|
||||
expect(verify.env?.EXPECTED_ORCHESTRATOR_SHA).toBe(
|
||||
"${{ inputs.release_candidate_branch != '' && github.workflow_sha || '' }}",
|
||||
);
|
||||
expect(verify.run).toContain(
|
||||
"--json workflowName,displayTitle,headBranch,headSha,event,status,conclusion,url",
|
||||
);
|
||||
|
||||
@@ -5370,10 +5370,10 @@ describe("package artifact reuse", () => {
|
||||
const releaseSkill = readFileSync(RELEASE_MAINTAINER_SKILL, "utf8");
|
||||
|
||||
expect(releaseWorkflow).toContain(
|
||||
"Stable OpenClaw publish requires an explicit windows_node_tag.",
|
||||
"Regular stable OpenClaw publish requires an explicit windows_node_tag.",
|
||||
);
|
||||
expect(releaseWorkflow).toContain(
|
||||
"Stable OpenClaw publish requires candidate-approved windows_node_installer_digests.",
|
||||
"Regular stable OpenClaw publish requires candidate-approved windows_node_installer_digests.",
|
||||
);
|
||||
expect(releaseWorkflow).toContain("promote_windows_release_assets()");
|
||||
expect(releaseWorkflow).toContain("dispatch_workflow windows-node-release.yml");
|
||||
@@ -5459,7 +5459,7 @@ describe("package artifact reuse", () => {
|
||||
"the selected `windows_node_tag`, its saved `windows_node_installer_digests`,",
|
||||
);
|
||||
expect(releaseDocs).toContain(
|
||||
"candidate-approved `windows_node_installer_digests`, and verify the canonical",
|
||||
"candidate-approved `windows_node_installer_digests` map. Before dispatching",
|
||||
);
|
||||
expect(releaseSkill).toContain(
|
||||
"candidate-approved installer digest map as `windows_node_installer_digests`.",
|
||||
|
||||
@@ -79,6 +79,12 @@ describe("plugin npm extended-stable workflow", () => {
|
||||
expect(inputs?.ref?.description).toBe(
|
||||
"Exact commit SHA; preflight accepts main/release ancestry, while publish mode also supports canonical extended-stable or matching Tideclaw alpha branches",
|
||||
);
|
||||
expect(inputs?.release_candidate_branch).toEqual({
|
||||
description:
|
||||
"Canonical extended-stable branch when trusted main release tooling publishes its immutable target",
|
||||
required: false,
|
||||
type: "string",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses one override for check, plan, preview, pack, and publish", () => {
|
||||
@@ -151,7 +157,7 @@ describe("plugin npm extended-stable workflow", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("trusts only the canonical monthly branch at the exact checked-out SHA", () => {
|
||||
it("accepts canonical monthly targets from the branch or trusted main orchestrator", () => {
|
||||
const trusted = step(
|
||||
workflow().jobs?.preview_plugins_npm,
|
||||
"Validate ref is on a trusted publish branch",
|
||||
@@ -164,6 +170,16 @@ describe("plugin npm extended-stable workflow", () => {
|
||||
expect(trusted.run).toContain(
|
||||
'[[ "$(git rev-parse HEAD)" == "$(git rev-parse "refs/remotes/origin/${extended_stable_branch}")" ]]',
|
||||
);
|
||||
expect(trusted.env?.RELEASE_CANDIDATE_BRANCH).toBe(
|
||||
"${{ github.event_name == 'workflow_dispatch' && inputs.release_candidate_branch || '' }}",
|
||||
);
|
||||
expect(trusted.run).toContain(
|
||||
'[[ "${RELEASE_CANDIDATE_BRANCH}" != "${extended_stable_branch}" || "${WORKFLOW_REF}" != "refs/heads/main" ]]',
|
||||
);
|
||||
expect(trusted.run).toContain(
|
||||
'git merge-base --is-ancestor HEAD "refs/remotes/origin/${extended_stable_branch}"',
|
||||
);
|
||||
expect(trusted.run).toContain('git merge-base --is-ancestor "${WORKFLOW_SHA}" origin/main');
|
||||
});
|
||||
|
||||
it("binds preflight to an exact source SHA without release-publish approval", () => {
|
||||
@@ -199,6 +215,7 @@ describe("plugin npm extended-stable workflow", () => {
|
||||
'[[ "$(git rev-parse HEAD)" != "$(git rev-parse "${SOURCE_REF}^{commit}")" ]]',
|
||||
);
|
||||
expect(trusted.run).toContain("preflight must not include release_publish_run_id");
|
||||
expect(trusted.run).toContain("preflight must not include release_candidate_branch");
|
||||
const preflightBranchRejection = trusted.run?.indexOf(
|
||||
"Plugin npm preflight target must be reachable from main or release/*.",
|
||||
);
|
||||
@@ -378,7 +395,7 @@ describe("plugin npm extended-stable workflow", () => {
|
||||
.split("\n")
|
||||
.filter((line) => line.includes('npm publish "$TARBALL_PATH"'));
|
||||
|
||||
expect(gitFetchLines).toHaveLength(6);
|
||||
expect(gitFetchLines).toHaveLength(7);
|
||||
expect(
|
||||
gitFetchLines.every((line) => line.includes("timeout --signal=TERM --kill-after=10s 120s")),
|
||||
).toBe(true);
|
||||
|
||||
@@ -1021,6 +1021,10 @@ describe("release validation no-push transport", () => {
|
||||
resolveTarget,
|
||||
"Validate release tag is reachable from a trusted release branch",
|
||||
);
|
||||
const publishJob = job(releasePublish, "publish");
|
||||
const resolveClawHubPlan = step(publishJob, "Resolve ClawHub release plan");
|
||||
const dispatchPublish = step(publishJob, "Dispatch publish workflows");
|
||||
const dispatchRun = dispatchPublish.run ?? "";
|
||||
|
||||
expect(dockerRelease.on?.push).toBeUndefined();
|
||||
expect(dockerRelease.on?.workflow_dispatch).toBeUndefined();
|
||||
@@ -1047,6 +1051,13 @@ describe("release validation no-push transport", () => {
|
||||
expect(validateInputs.run).toContain(
|
||||
'expected_validation_branch="extended-stable/${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.33"',
|
||||
);
|
||||
expect(validateInputs.run).not.toContain(
|
||||
"Extended-stable core npm publication stays on the canonical extended-stable release flow",
|
||||
);
|
||||
expect(validateInputs.run).toContain(
|
||||
'if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" ]]; then',
|
||||
);
|
||||
expect(publishJob.if).toBe("${{ !inputs.publish_docker_only }}");
|
||||
expect(validateEvidence.env?.EXPECTED_WORKFLOW_BRANCH).toBe(
|
||||
"${{ steps.inputs.outputs.expected_validation_branch }}",
|
||||
);
|
||||
@@ -1087,6 +1098,26 @@ describe("release validation no-push transport", () => {
|
||||
expect(readFileSync(releasePublishPath, "utf8")).toContain(
|
||||
"kept draft until Docker publication succeeds",
|
||||
);
|
||||
expect(resolveClawHubPlan.run).toContain("plan_args+=(--skip-clawhub)");
|
||||
expect(dispatchRun).toContain("force_skip_clawhub=true");
|
||||
expect(dispatchRun).toContain('elif [[ "${RELEASE_NPM_DIST_TAG}" == "latest" ]]; then');
|
||||
expect(dispatchRun).toContain('latest_arg="--latest=false"');
|
||||
expect(dispatchRun).toContain("-f npm_dist_tag=extended-stable");
|
||||
expect(dispatchRun).toContain(
|
||||
'-f release_candidate_branch="${{ needs.resolve_release_target.outputs.expected_validation_branch }}"',
|
||||
);
|
||||
expect(dispatchRun).toContain('-f plugin_npm_run_id="${plugin_npm_run_id}"');
|
||||
expect(dispatchRun).toContain("upload_dependency_evidence_release_asset");
|
||||
expect(dispatchRun).toContain("upload_release_evidence_assets");
|
||||
expect(dispatchRun).toContain(
|
||||
'[[ "${RELEASE_NPM_DIST_TAG}" != "extended-stable" && "${RELEASE_TAG}" != *"-alpha."*',
|
||||
);
|
||||
expect(step(resolveTarget, "Validate stable Windows source release").if).toContain(
|
||||
"inputs.npm_dist_tag != 'extended-stable'",
|
||||
);
|
||||
expect(step(publishJob, "Attest Android release approval").if).toContain(
|
||||
"inputs.npm_dist_tag != 'extended-stable'",
|
||||
);
|
||||
expect(job(releasePublish, "finalize_github_release").needs).toEqual([
|
||||
"resolve_release_target",
|
||||
"publish",
|
||||
|
||||
Reference in New Issue
Block a user