fix(release): bind npm publishers to live tooling

This commit is contained in:
Vincent Koc
2026-08-20 20:17:17 -07:00
parent c89a3dde28
commit 20a01f9308
11 changed files with 695 additions and 54 deletions
@@ -1407,11 +1407,25 @@ jobs:
id: publish
env:
BYPASS_EXTENDED_STABLE_GUARD: ${{ inputs.bypass_extended_stable_guard }}
GH_TOKEN: ${{ github.token }}
OPENCLAW_PREPACK_PREPARED: "1"
OPENCLAW_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag }}
PUBLISH_TARBALL_PATH: ${{ steps.preflight_provenance.outputs.tarball_path }}
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
WORKFLOW_FULL_REF: ${{ github.ref }}
WORKFLOW_REF: ${{ github.ref_name }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: |
set -euo pipefail
verify_release_tooling_identity() {
node trusted-workflow/scripts/release-tooling-identity.mjs verify \
--repository "$GITHUB_REPOSITORY" \
--workflow-ref "$WORKFLOW_REF" \
--workflow-full-ref "$WORKFLOW_FULL_REF" \
--workflow-sha "$WORKFLOW_SHA" \
--release-publish-run-id "$RELEASE_PUBLISH_RUN_ID" \
--allow-prevalidated-ref
}
publish_target="${PUBLISH_TARBALL_PATH}"
if [[ -n "${publish_target}" ]]; then
publish_target="./${publish_target}"
@@ -1429,11 +1443,13 @@ jobs:
echo "${package_name}@${package_version} is already published; reusing it."
return 0
fi
verify_release_tooling_identity
bash scripts/openclaw-npm-publish.sh --publish "./${tarball_path}"
}
while IFS=$'\t' read -r package_name tarball_name; do
publish_if_missing "$package_name" "preflight-tarball/$tarball_name"
done < <(jq -r '(.corePackageTarballs // [])[] | [.packageName, .tarballName] | @tsv' preflight-tarball/preflight-manifest.json)
verify_release_tooling_identity
bash scripts/openclaw-npm-publish.sh --publish "${publish_target}"
- name: Verify extended-stable registry readback
@@ -962,6 +962,7 @@ jobs:
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
PARENT_WORKFLOW_SHA: ${{ github.sha }}
PARENT_WORKFLOW_BRANCH: ${{ github.ref_name }}
PARENT_WORKFLOW_FULL_REF: ${{ github.ref }}
RELEASE_TAG: ${{ inputs.tag }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
@@ -2283,6 +2284,13 @@ jobs:
if [[ "$(jq -r '.normal.shouldDispatch' "${clawhub_plan_path}")" == "true" ]]; then
clawhub_dispatch_args=()
append_clawhub_dispatch_args normal
if [[ "${PARENT_WORKFLOW_FULL_REF}" == "refs/heads/main" ||
"${PARENT_WORKFLOW_FULL_REF}" =~ ^refs/tags/release-publish/[a-f0-9]{12}-[1-9][0-9]*$ ]]; then
clawhub_dispatch_args+=(
-f release_publish_full_ref="${PARENT_WORKFLOW_FULL_REF}"
-f release_publish_workflow_sha="${PARENT_WORKFLOW_SHA}"
)
fi
plugin_clawhub_run_id="$(dispatch_workflow_at_ref \
"$(jq -r '.normal.ref' "${clawhub_plan_path}")" \
"${TARGET_SHA}" \
+62 -1
View File
@@ -28,6 +28,16 @@ on:
description: Branch name of the approving OpenClaw Release Publish workflow run
required: false
type: string
release_publish_full_ref:
description: Exact full ref of the approving OpenClaw Release Publish workflow tooling
required: false
default: ""
type: string
release_publish_workflow_sha:
description: Exact workflow SHA of the approving OpenClaw Release Publish tooling
required: false
default: ""
type: string
dry_run:
description: Validate the full ClawHub artifact handoff without publishing.
required: false
@@ -61,6 +71,7 @@ jobs:
matrix: ${{ steps.plan.outputs.matrix }}
bootstrap_matrix: ${{ steps.plan.outputs.bootstrap_matrix }}
missing_trusted_publisher_matrix: ${{ steps.plan.outputs.missing_trusted_publisher_matrix }}
trusted_tooling_identity_json: ${{ steps.tooling_identity.outputs.json }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
@@ -92,6 +103,55 @@ jobs:
fi
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Capture trusted tooling identity
id: tooling_identity
env:
CALLER_FULL_REF: ${{ github.ref }}
CALLER_REF: ${{ github.ref_name }}
CALLER_RUN_ATTEMPT: ${{ github.run_attempt }}
CALLER_RUN_ID: ${{ github.run_id }}
CALLER_SHA: ${{ github.sha }}
TOOLING_FULL_REF: ${{ inputs.release_publish_full_ref }}
TOOLING_REF: ${{ inputs.release_publish_branch }}
TOOLING_SHA: ${{ inputs.release_publish_workflow_sha }}
run: |
set -euo pipefail
if [[ -z "${TOOLING_FULL_REF}${TOOLING_SHA}" ]]; then
echo "json=" >> "$GITHUB_OUTPUT"
exit 0
fi
[[ -n "$TOOLING_FULL_REF" && -n "$TOOLING_REF" && -n "$TOOLING_SHA" ]] || {
echo "Trusted release tooling ref, full ref, and SHA must be provided together." >&2
exit 1
}
identity="$(
jq -cn \
--arg repository "$GITHUB_REPOSITORY" \
--arg workflow ".github/workflows/plugin-clawhub-release.yml" \
--arg runId "$CALLER_RUN_ID" \
--arg runAttempt "$CALLER_RUN_ATTEMPT" \
--arg ref "$CALLER_REF" \
--arg fullRef "$CALLER_FULL_REF" \
--arg sha "$CALLER_SHA" \
--arg toolingRef "$TOOLING_REF" \
--arg toolingFullRef "$TOOLING_FULL_REF" \
--arg toolingSha "$TOOLING_SHA" \
'{
version: 1,
repository: $repository,
workflow: $workflow,
runId: $runId,
runAttempt: $runAttempt,
ref: $ref,
fullRef: $fullRef,
sha: $sha,
toolingRef: $toolingRef,
toolingFullRef: $toolingFullRef,
toolingSha: $toolingSha
}'
)"
echo "json=${identity}" >> "$GITHUB_OUTPUT"
- name: Validate OIDC source matches workflow ref
env:
TARGET_SHA: ${{ steps.ref.outputs.sha }}
@@ -388,7 +448,7 @@ jobs:
needs:
[preview_plugins_clawhub, pack_plugins_clawhub_artifacts, approve_plugins_clawhub_release]
if: always() && github.event_name == 'workflow_dispatch' && needs.preview_plugins_clawhub.outputs.has_candidates == 'true' && needs.pack_plugins_clawhub_artifacts.result == 'success' && (inputs.dry_run == true || needs.approve_plugins_clawhub_release.result == 'success')
uses: openclaw/clawhub/.github/workflows/package-publish.yml@d8096dfc039e86ab942ddf9ef117d04849fd84c1
uses: openclaw/clawhub/.github/workflows/package-publish.yml@6dc1e2bd67a90b5e5c54b3a026dbdfe3691f1202
permissions:
actions: read
contents: read
@@ -411,6 +471,7 @@ jobs:
source_path: ${{ matrix.plugin.packageDir }}
inspector_artifact_name: ${{ matrix.plugin.artifactName }}-inspector
publish_json_artifact_name: ${{ matrix.plugin.artifactName }}-publish-json
trusted_tooling_identity_json: ${{ needs.preview_plugins_clawhub.outputs.trusted_tooling_identity_json }}
verify_published_clawhub_package:
needs: [preview_plugins_clawhub, publish_plugins_clawhub]
+28 -29
View File
@@ -22,6 +22,8 @@ on:
- "scripts/lib/actions-artifact-archive.mjs"
- "scripts/plugin-npm-publish.sh"
- "scripts/plugin-publication-artifact.mjs"
- "scripts/release-tooling-identity.d.mts"
- "scripts/release-tooling-identity.mjs"
- "scripts/plugin-npm-release-check.ts"
- "scripts/plugin-npm-release-plan.ts"
- "scripts/verify-plugin-npm-published-runtime.mts"
@@ -1103,6 +1105,7 @@ jobs:
PACKAGE_NAME: ${{ matrix.plugin.packageName }}
PACKAGE_VERSION: ${{ matrix.plugin.version }}
PUBLISH_TAG: ${{ matrix.plugin.publishTag }}
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
TARGET_SHA: ${{ needs.preview_plugins_npm.outputs.ref_revision }}
WORKFLOW_HEAD_BRANCH: ${{ github.ref_name }}
WORKFLOW_REF: ${{ github.ref }}
@@ -1161,35 +1164,12 @@ jobs:
exit 1
}
if [[ "$publish_route" == "npm-token-bootstrap" ]]; then
sha_pinned_release_publish=false
if [[ "$WORKFLOW_REF" =~ ^refs/tags/release-publish/([a-f0-9]{12})-[1-9][0-9]*$ ]]; then
workflow_sha_prefix="${BASH_REMATCH[1]}"
[[ "$WORKFLOW_SHA" =~ ^[a-f0-9]{40}$ && "${WORKFLOW_SHA:0:12}" == "$workflow_sha_prefix" ]] || {
echo "npm token bootstrap release-publish tag does not match the workflow SHA." >&2
exit 1
}
workflow_tag="${WORKFLOW_REF#refs/tags/}"
remote_workflow_sha="$(
gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${workflow_tag}" \
--jq '.object | select(.type == "commit") | .sha | select(test("^[a-f0-9]{40}$"))'
)"
[[ "$remote_workflow_sha" == "$WORKFLOW_SHA" ]] || {
echo "npm token bootstrap release-publish tag does not resolve to the workflow SHA." >&2
exit 1
}
sha_pinned_release_publish=true
fi
[[ "$WORKFLOW_REF" == "refs/heads/main" || "$sha_pinned_release_publish" == "true" ]] || {
echo "npm token bootstrap requires trusted main tooling or a protected SHA-pinned release-publish tag." >&2
exit 1
}
if [[ "$WORKFLOW_REF" == "refs/heads/main" ]]; then
timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
git merge-base --is-ancestor "$WORKFLOW_SHA" origin/main || {
echo "npm token bootstrap workflow revision is not reachable from current main." >&2
exit 1
}
fi
node scripts/release-tooling-identity.mjs verify \
--repository "$GITHUB_REPOSITORY" \
--workflow-ref "$WORKFLOW_HEAD_BRANCH" \
--workflow-full-ref "$WORKFLOW_REF" \
--workflow-sha "$WORKFLOW_SHA" \
--release-publish-run-id "$RELEASE_PUBLISH_RUN_ID"
fi
artifact_id="$(jq -er '.id' "$artifact_metadata")"
artifact_digest="$(jq -er '.digest' "$artifact_metadata")"
@@ -1318,8 +1298,16 @@ jobs:
- name: Publish with trusted publisher
if: steps.publication_evidence.outputs.publish_route == 'npm-oidc' && steps.npm_package_version.outputs.already_published != 'true'
env:
GH_TOKEN: ${{ github.token }}
OPENCLAW_NPM_PUBLISH_AUTH_MODE: trusted-publisher
OPENCLAW_PLUGIN_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag == 'extended-stable' && inputs.npm_dist_tag || '' }}
OPENCLAW_RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
OPENCLAW_RELEASE_TOOLING_ALLOW_PREVALIDATED_REF: "true"
OPENCLAW_RELEASE_TOOLING_FULL_REF: ${{ github.ref }}
OPENCLAW_RELEASE_TOOLING_IDENTITY_REQUIRED: "true"
OPENCLAW_RELEASE_TOOLING_REF: ${{ github.ref_name }}
OPENCLAW_RELEASE_TOOLING_REPOSITORY: ${{ github.repository }}
OPENCLAW_RELEASE_TOOLING_SHA: ${{ github.workflow_sha }}
run: bash scripts/plugin-npm-publish.sh --repo-root .publication-target --publish "${{ matrix.plugin.packageDir }}"
- name: Verify OIDC published runtime
@@ -1379,12 +1367,17 @@ jobs:
- name: Publish approved bootstrap tarball
if: steps.publication_evidence.outputs.publish_route == 'npm-token-bootstrap' && steps.bootstrap_npm_package_version.outputs.already_published != 'true'
env:
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
PACKAGE_DIR: ${{ matrix.plugin.packageDir }}
PACKAGE_NAME: ${{ steps.publication_evidence.outputs.package_name }}
PACKAGE_VERSION: ${{ steps.publication_evidence.outputs.package_version }}
PUBLISH_TAG: ${{ steps.publication_evidence.outputs.publish_tag }}
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
TARBALL_PATH: ${{ steps.publication_evidence.outputs.tarball_path }}
WORKFLOW_FULL_REF: ${{ github.ref }}
WORKFLOW_REF: ${{ github.ref_name }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: |
set -euo pipefail
[[ "$PACKAGE_VERSION" == *"-beta."* && "$PUBLISH_TAG" == "beta" ]] || {
@@ -1410,6 +1403,12 @@ jobs:
unset NODE_AUTH_TOKEN NPM_TOKEN NODE_OPTIONS
# A timeout can race a committed publish. On rerun, the preceding check
# accepts only this tarball's exact integrity and shasum before skipping.
node scripts/release-tooling-identity.mjs verify \
--repository "$GITHUB_REPOSITORY" \
--workflow-ref "$WORKFLOW_REF" \
--workflow-full-ref "$WORKFLOW_FULL_REF" \
--workflow-sha "$WORKFLOW_SHA" \
--release-publish-run-id "$RELEASE_PUBLISH_RUN_ID"
HOME="$publish_home" \
NPM_CONFIG_GLOBALCONFIG=/dev/null \
NPM_CONFIG_IGNORE_SCRIPTS=true \