name: Plugin ClawHub New on: workflow_dispatch: inputs: plugins: description: Comma-separated plugin package names to bootstrap on ClawHub required: true type: string ref: description: Exact 40-character target commit SHA approved by the parent release workflow required: true type: string bootstrap_workflow_sha: description: Exact trusted-main workflow SHA approved by the parent release workflow required: false default: "" type: string release_tag: description: Exact release tag that must peel to ref; omit only for pre-tag validation required: false default: "" type: string release_publish_run_id: description: Approved OpenClaw Release Publish workflow run id required: false default: "" type: string release_publish_run_attempt: description: Exact approving OpenClaw Release Publish workflow run attempt required: false default: "" type: string release_publish_branch: description: Branch name of the approving OpenClaw Release Publish workflow run required: false default: "" type: string pretag_validation: description: Validate an exact main/release target before its release tag exists; requires dry_run=true required: false default: false type: boolean dry_run: description: Validate the token-gated ClawHub bootstrap handoff without publishing. required: false default: false type: boolean concurrency: group: plugin-clawhub-new-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }} cancel-in-progress: false env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" NODE_VERSION: "24.15.0" CLAWHUB_REGISTRY: "https://clawhub.ai" jobs: resolve_bootstrap_plan: runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: read outputs: ref_revision: ${{ steps.ref.outputs.sha }} has_bootstrap_candidates: ${{ steps.plan.outputs.has_bootstrap_candidates }} bootstrap_candidate_count: ${{ steps.plan.outputs.bootstrap_candidate_count }} matrix: ${{ steps.plan.outputs.matrix }} steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} fetch-depth: 0 - name: Require trusted main workflow source env: APPROVED_WORKFLOW_SHA: ${{ inputs.bootstrap_workflow_sha }} PRETAG_VALIDATION: ${{ inputs.pretag_validation }} WORKFLOW_REF: ${{ github.ref }} WORKFLOW_SHA: ${{ github.sha }} run: | set -euo pipefail [[ "${WORKFLOW_REF}" == "refs/heads/main" ]] || { echo "Plugin ClawHub New must be dispatched from trusted main." >&2 exit 1 } [[ "$(git rev-parse HEAD)" == "${WORKFLOW_SHA}" ]] || { echo "Trusted workflow checkout does not match github.sha." >&2 exit 1 } if [[ "${PRETAG_VALIDATION}" != "true" ]]; then [[ "${APPROVED_WORKFLOW_SHA}" =~ ^[a-f0-9]{40}$ ]] || { echo "Plugin ClawHub bootstrap requires an exact approved workflow SHA." >&2 exit 1 } [[ "${WORKFLOW_SHA}" == "${APPROVED_WORKFLOW_SHA}" ]] || { echo "Plugin ClawHub New workflow SHA does not match the parent-approved trusted-main SHA." >&2 exit 1 } elif [[ -n "${APPROVED_WORKFLOW_SHA}" && "${WORKFLOW_SHA}" != "${APPROVED_WORKFLOW_SHA}" ]]; then echo "Plugin ClawHub pre-tag validation workflow SHA does not match the requested trusted-main SHA." >&2 exit 1 fi - name: Resolve checked-out ref id: ref env: BOOTSTRAP_WORKFLOW_SHA: ${{ inputs.bootstrap_workflow_sha }} DRY_RUN: ${{ inputs.dry_run }} PRETAG_VALIDATION: ${{ inputs.pretag_validation }} RELEASE_TAG: ${{ inputs.release_tag }} RELEASE_PUBLISH_BRANCH: ${{ inputs.release_publish_branch }} RELEASE_PUBLISH_RUN_ATTEMPT: ${{ inputs.release_publish_run_attempt }} RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }} TARGET_REF: ${{ inputs.ref }} run: | set -euo pipefail [[ "${TARGET_REF}" =~ ^[a-f0-9]{40}$ ]] || { echo "Plugin ClawHub bootstrap ref must be a full lowercase commit SHA." >&2 exit 1 } if [[ "${PRETAG_VALIDATION}" == "true" ]]; then [[ "${DRY_RUN}" == "true" ]] || { echo "Plugin ClawHub pre-tag validation requires dry_run=true." >&2 exit 1 } if [[ -n "${RELEASE_TAG}${RELEASE_PUBLISH_RUN_ID}${RELEASE_PUBLISH_RUN_ATTEMPT}${RELEASE_PUBLISH_BRANCH}" ]]; then echo "Plugin ClawHub pre-tag validation must not include a release tag or parent approval tuple." >&2 exit 1 fi git fetch --force --no-tags origin \ +refs/heads/main:refs/remotes/origin/main \ '+refs/heads/release/*:refs/remotes/origin/release/*' git cat-file -e "${TARGET_REF}^{commit}" || { echo "Unable to resolve requested pre-tag validation ref: ${TARGET_REF}" >&2 exit 1 } trusted_target=false if git merge-base --is-ancestor "${TARGET_REF}" refs/remotes/origin/main; then trusted_target=true else while IFS= read -r release_ref; do if git merge-base --is-ancestor "${TARGET_REF}" "${release_ref}"; then trusted_target=true break fi done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release) fi [[ "${trusted_target}" == "true" ]] || { echo "Plugin ClawHub pre-tag validation target must be reachable from main or release/*." >&2 exit 1 } else [[ "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]] || { echo "Plugin ClawHub bootstrap release_tag is invalid." >&2 exit 1 } [[ "${RELEASE_PUBLISH_RUN_ID}" =~ ^[1-9][0-9]*$ && "${RELEASE_PUBLISH_RUN_ATTEMPT}" =~ ^[1-9][0-9]*$ && -n "${RELEASE_PUBLISH_BRANCH}" && "${BOOTSTRAP_WORKFLOW_SHA}" =~ ^[a-f0-9]{40}$ ]] || { echo "Plugin ClawHub bootstrap requires an exact parent approval tuple." >&2 exit 1 } git fetch --force --no-tags origin \ "+refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" git cat-file -e "${TARGET_REF}^{commit}" || { echo "Unable to resolve requested publish ref: ${TARGET_REF}" >&2 exit 1 } tag_sha="$(git rev-parse "${RELEASE_TAG}^{commit}")" [[ "${tag_sha}" == "${TARGET_REF}" ]] || { echo "Plugin ClawHub bootstrap target ${TARGET_REF} does not match ${RELEASE_TAG} (${tag_sha})." >&2 exit 1 } fi git checkout --detach "${TARGET_REF}" echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - name: Setup Node environment uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "false" - name: Validate publishable plugin metadata env: RELEASE_PLUGINS: ${{ inputs.plugins }} run: | set -euo pipefail if [[ -z "${RELEASE_PLUGINS// }" ]]; then echo "Plugin ClawHub bootstrap requires at least one package name in plugins." >&2 exit 1 fi pnpm release:plugins:clawhub:check -- --selection-mode selected --plugins "${RELEASE_PLUGINS}" - name: Resolve plugin bootstrap plan id: plan env: RELEASE_PLUGINS: ${{ inputs.plugins }} CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }} run: | set -euo pipefail mkdir -p .local node --import tsx scripts/plugin-clawhub-release-plan.ts \ --selection-mode selected \ --plugins "${RELEASE_PLUGINS}" > .local/plugin-clawhub-release-plan.json cat .local/plugin-clawhub-release-plan.json bootstrap_candidate_count="$(jq -r '(.bootstrapCandidates | length) + (.missingTrustedPublisher | length)' .local/plugin-clawhub-release-plan.json)" selected_count="$(jq -r '.all | length' .local/plugin-clawhub-release-plan.json)" matrix_json="$( jq -c ' [ .bootstrapCandidates[]? + { bootstrapMode: "publish", requiresManualOverride: false }, .missingTrustedPublisher[]? + { bootstrapMode: (if .alreadyPublished then "configure-only" else "publish" end), requiresManualOverride: true } ] ' .local/plugin-clawhub-release-plan.json )" has_bootstrap_candidates="false" if [[ "${bootstrap_candidate_count}" != "0" ]]; then has_bootstrap_candidates="true" fi invalid_scope="$( jq -r ' (.bootstrapCandidates[]?, .missingTrustedPublisher[]?) | select(.packageName | startswith("@openclaw/") | not) | "- \(.packageName)@\(.version)" ' .local/plugin-clawhub-release-plan.json )" if [[ -n "${invalid_scope}" ]]; then echo "Plugin ClawHub bootstrap only supports @openclaw/* packages." >&2 printf '%s\n' "${invalid_scope}" >&2 exit 1 fi not_bootstrap="$( jq -r ' (.bootstrapCandidates | map(.packageName)) as $bootstrapNames | (.missingTrustedPublisher | map(.packageName)) as $repairNames | .all[]? | select(.packageName as $name | ($bootstrapNames + $repairNames | index($name) | not)) | "- \(.packageName)@\(.version)" ' .local/plugin-clawhub-release-plan.json )" if [[ -n "${not_bootstrap}" ]]; then echo "Selected packages must all be first-publish bootstrap candidates or trusted-publisher repair candidates." >&2 printf '%s\n' "${not_bootstrap}" >&2 exit 1 fi if [[ "${selected_count}" == "0" || "${bootstrap_candidate_count}" == "0" ]]; then echo "No selected packages require ClawHub bootstrap." >&2 exit 1 fi { echo "bootstrap_candidate_count=${bootstrap_candidate_count}" echo "has_bootstrap_candidates=${has_bootstrap_candidates}" echo "matrix=${matrix_json}" } >> "$GITHUB_OUTPUT" echo "ClawHub bootstrap candidates:" jq -r ' .bootstrapCandidates[]? | "- \(.packageName)@\(.version) [\(.publishTag)] from \(.packageDir)" ' .local/plugin-clawhub-release-plan.json echo "ClawHub trusted-publisher repair candidates:" jq -r ' .missingTrustedPublisher[]? | "- \(.packageName)@\(.version) [\(.publishTag)] from \(.packageDir), alreadyPublished=\(.alreadyPublished)" ' .local/plugin-clawhub-release-plan.json - name: Validate Tideclaw alpha plugin channels env: TRUSTED_PUBLISH_BRANCH: ${{ inputs.release_publish_branch }} run: | set -euo pipefail if [[ ! "${TRUSTED_PUBLISH_BRANCH}" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then exit 0 fi invalid="$( jq -r ' (.bootstrapCandidates[]?, .missingTrustedPublisher[]?) | select(.publishTag != "alpha" or .channel != "alpha") | "- \(.packageName)@\(.version) [\(.publishTag)]" ' .local/plugin-clawhub-release-plan.json )" if [[ -n "${invalid}" ]]; then echo "Tideclaw alpha ClawHub bootstraps may only publish alpha plugin versions." >&2 printf '%s\n' "${invalid}" >&2 exit 1 fi validate_release_publish_approval: name: Validate release publish approval needs: resolve_bootstrap_plan if: github.event_name == 'workflow_dispatch' && inputs.pretag_validation != true && needs.resolve_bootstrap_plan.outputs.has_bootstrap_candidates == 'true' runs-on: ubuntu-latest timeout-minutes: 20 permissions: actions: read attestations: read contents: read steps: - name: Download parent ClawHub bootstrap approval uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: clawhub-bootstrap-approval-${{ inputs.release_publish_run_id }}-${{ inputs.release_publish_run_attempt }} path: ${{ runner.temp }}/clawhub-bootstrap-approval repository: ${{ github.repository }} run-id: ${{ inputs.release_publish_run_id }} github-token: ${{ github.token }} - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} - name: Validate release publish approval run env: APPROVAL_PATH: ${{ runner.temp }}/clawhub-bootstrap-approval/approval.json CHILD_WORKFLOW_SHA: ${{ github.sha }} GH_TOKEN: ${{ github.token }} RELEASE_APPROVAL_KIND: clawhub-bootstrap RELEASE_PACKAGES: ${{ inputs.plugins }} RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }} EXPECTED_RUN_ATTEMPT: ${{ inputs.release_publish_run_attempt }} EXPECTED_WORKFLOW_BRANCH: ${{ inputs.release_publish_branch }} RELEASE_TAG: ${{ inputs.release_tag }} RELEASE_TARGET_SHA: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} run: | set -euo pipefail direct_recovery=false if [[ "${GITHUB_ACTOR}" != "github-actions[bot]" ]]; then direct_recovery=true echo "Direct Plugin ClawHub New recovery with release_publish_run_id; relying on this workflow's clawhub-plugin-bootstrap environment approval." fi RUN_JSON="$( gh api \ "repos/${GITHUB_REPOSITORY}/actions/runs/${RELEASE_PUBLISH_RUN_ID}/attempts/${EXPECTED_RUN_ATTEMPT}" | jq '{ workflowName: .name, headBranch: .head_branch, headSha: .head_sha, event, status, conclusion, url: .html_url, runAttempt: .run_attempt }' )" EXPECTED_WORKFLOW_SHA="$( jq -er '.headSha | select(test("^[a-f0-9]{40}$"))' <<< "${RUN_JSON}" )" gh attestation verify "${APPROVAL_PATH}" \ --repo "${GITHUB_REPOSITORY}" \ --signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/openclaw-release-publish.yml" \ --source-ref "refs/heads/${EXPECTED_WORKFLOW_BRANCH}" \ --source-digest "${EXPECTED_WORKFLOW_SHA}" \ --deny-self-hosted-runners printf '%s' "$RUN_JSON" | DIRECT_RELEASE_RECOVERY="${direct_recovery}" node scripts/validate-release-publish-approval.mjs validate_bootstrap_trusted_publisher_cli: needs: [resolve_bootstrap_plan, validate_release_publish_approval] if: always() && github.event_name == 'workflow_dispatch' && needs.resolve_bootstrap_plan.outputs.has_bootstrap_candidates == 'true' && (inputs.pretag_validation == true || needs.validate_release_publish_approval.result == 'success') runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read steps: - name: Checkout trusted workflow tooling uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} - name: Setup trusted Node runtime uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: ${{ env.NODE_VERSION }} - name: Materialize locked ClawHub CLI id: clawhub_cli run: | set -euo pipefail bash scripts/materialize-clawhub-cli.sh \ .github/release/clawhub-cli \ "${RUNNER_TEMP}/clawhub-cli" \ "${GITHUB_OUTPUT}" - name: Validate pinned ClawHub trusted publisher CLI support env: CLAWHUB_CLI: ${{ steps.clawhub_cli.outputs.cli }} run: | set -euo pipefail help_output="$( "${CLAWHUB_CLI}" package trusted-publisher set --help 2>&1 || true )" printf '%s\n' "${help_output}" if ! grep -Fq "Usage: clawhub package trusted-publisher set" <<<"${help_output}"; then echo "::error::CLAW-277 03 - Split OpenClaw plugin ClawHub publishing into OIDC release and token bootstrap workflows requires locked clawhub@0.23.1 to expose 'package trusted-publisher set' before token bootstrap publish can run. The pinned CLI returned parent help or no set command, so this workflow is stopping before creating a ClawHub package row." exit 1 fi for required_flag in --repository --workflow-filename; do if ! grep -Fq -- "${required_flag}" <<<"${help_output}"; then echo "::error::CLAW-277 03 - Split OpenClaw plugin ClawHub publishing into OIDC release and token bootstrap workflows requires locked clawhub@0.23.1 trusted-publisher set help to include ${required_flag}." exit 1 fi done pack_bootstrap_plugins: name: Pack immutable ClawHub bootstrap artifacts needs: [resolve_bootstrap_plan, validate_release_publish_approval] if: always() && github.event_name == 'workflow_dispatch' && needs.resolve_bootstrap_plan.outputs.has_bootstrap_candidates == 'true' && (inputs.pretag_validation == true || needs.validate_release_publish_approval.result == 'success') runs-on: ubuntu-latest timeout-minutes: 60 permissions: actions: read contents: read outputs: artifact_digest: ${{ steps.upload.outputs.artifact-digest }} artifact_id: ${{ steps.upload.outputs.artifact-id }} artifact_name: ${{ steps.artifact.outputs.name }} artifact_run_attempt: ${{ github.run_attempt }} artifact_run_id: ${{ github.run_id }} artifact_size: ${{ steps.upload_binding.outputs.size }} clawhub_toolchain_sha256: ${{ steps.clawhub_cli.outputs.lock_sha256 }} steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} fetch-depth: 0 - name: Checkout target revision env: PRETAG_VALIDATION: ${{ inputs.pretag_validation }} RELEASE_TAG: ${{ inputs.release_tag }} TARGET_SHA: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} run: | set -euo pipefail if [[ "${PRETAG_VALIDATION}" == "true" ]]; then git fetch --force --no-tags origin \ +refs/heads/main:refs/remotes/origin/main \ '+refs/heads/release/*:refs/remotes/origin/release/*' git cat-file -e "${TARGET_SHA}^{commit}" else git fetch --force --no-tags origin \ "+refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" tag_sha="$(git rev-parse "${RELEASE_TAG}^{commit}")" [[ "${tag_sha}" == "${TARGET_SHA}" ]] || { echo "Plugin ClawHub bootstrap target ${TARGET_SHA} does not match ${RELEASE_TAG} (${tag_sha})." >&2 exit 1 } fi git checkout --detach "${TARGET_SHA}" - name: Checkout trusted workflow tooling uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} path: .release-harness - name: Setup Node environment uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" install-deps: "true" - name: Materialize locked ClawHub CLI id: clawhub_cli run: | set -euo pipefail bash .release-harness/scripts/materialize-clawhub-cli.sh \ .release-harness/.github/release/clawhub-cli \ "${RUNNER_TEMP}/clawhub-cli" \ "${GITHUB_OUTPUT}" - name: Pack immutable ClawHub bootstrap artifacts id: artifact env: BOOTSTRAP_MATRIX: ${{ needs.resolve_bootstrap_plan.outputs.matrix }} CLAWHUB_TOOLCHAIN_INTEGRITY: ${{ steps.clawhub_cli.outputs.integrity }} RELEASE_PLUGINS: ${{ inputs.plugins }} TARGET_SHA: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} WORKFLOW_SHA: ${{ github.sha }} CLAWHUB_TOOLCHAIN_SHA256: ${{ steps.clawhub_cli.outputs.lock_sha256 }} CLAWHUB_TOOLCHAIN_VERSION: ${{ steps.clawhub_cli.outputs.version }} OPENCLAW_CLAWHUB_CLI: ${{ steps.clawhub_cli.outputs.cli }} run: | set -euo pipefail artifact_root="${RUNNER_TEMP}/clawhub-bootstrap-artifact" matrix_path="${RUNNER_TEMP}/clawhub-bootstrap-matrix.json" artifact_name="clawhub-bootstrap-${TARGET_SHA:0:12}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" mkdir -p "${artifact_root}/packages" printf '%s\n' "${BOOTSTRAP_MATRIX}" > "${matrix_path}" while IFS= read -r plugin; do package_name="$(jq -r '.packageName' <<< "${plugin}")" package_dir="$(jq -r '.packageDir' <<< "${plugin}")" package_version="$(jq -r '.version' <<< "${plugin}")" package_tag="$(jq -r '.publishTag' <<< "${plugin}")" package_slug="${package_name#@openclaw/}" output_dir="${artifact_root}/packages/${package_slug}" mkdir -p "${output_dir}" node scripts/check-plugin-npm-runtime-builds.mjs --package "${package_dir}" OPENCLAW_CLAWHUB_PACK_OUTPUT_DIR="${output_dir}" \ PACKAGE_TAG="${package_tag}" \ bash .release-harness/scripts/plugin-clawhub-publish.sh --pack "${package_dir}" tgz="$(find "${output_dir}" -maxdepth 1 -type f -name '*.tgz' -print -quit)" artifact_sha256="$(sha256sum "${tgz}" | awk '{print $1}')" artifact_size="$(wc -c < "${tgz}" | tr -d '[:space:]')" PACKAGE_DIR="${package_dir}" \ PACKAGE_TAG="${package_tag}" \ EXPECTED_CLAWHUB_PACKAGE_NAME="${package_name}" \ EXPECTED_CLAWHUB_PACKAGE_VERSION="${package_version}" \ EXPECTED_CLAWHUB_ARTIFACT_SHA256="${artifact_sha256}" \ EXPECTED_CLAWHUB_ARTIFACT_SIZE="${artifact_size}" \ SOURCE_COMMIT="${TARGET_SHA}" \ SOURCE_REF="${TARGET_SHA}" \ SOURCE_REPO="${GITHUB_REPOSITORY}" \ bash .release-harness/scripts/plugin-clawhub-publish.sh \ --validate-packed "${tgz}" done < <(jq -c '.[]' "${matrix_path}") node "${GITHUB_WORKSPACE}/.release-harness/scripts/lib/clawhub-bootstrap-artifact.mjs" create \ --artifact-root "${artifact_root}" \ --artifact-name "${artifact_name}" \ --clawhub-toolchain-integrity "${CLAWHUB_TOOLCHAIN_INTEGRITY}" \ --clawhub-toolchain-sha256 "${CLAWHUB_TOOLCHAIN_SHA256}" \ --clawhub-toolchain-version "${CLAWHUB_TOOLCHAIN_VERSION}" \ --matrix "${matrix_path}" \ --output "${artifact_root}/manifest.json" \ --plugins "${RELEASE_PLUGINS}" \ --repository "${GITHUB_REPOSITORY}" \ --run-attempt "${GITHUB_RUN_ATTEMPT}" \ --run-id "${GITHUB_RUN_ID}" \ --target-sha "${TARGET_SHA}" \ --workflow-sha "${WORKFLOW_SHA}" echo "name=${artifact_name}" >> "${GITHUB_OUTPUT}" - name: Upload immutable ClawHub bootstrap artifact id: upload uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: archive: true name: ${{ steps.artifact.outputs.name }} path: ${{ runner.temp }}/clawhub-bootstrap-artifact if-no-files-found: error compression-level: 0 - name: Bind uploaded artifact size id: upload_binding env: ARTIFACT_DIGEST: ${{ steps.upload.outputs.artifact-digest }} ARTIFACT_ID: ${{ steps.upload.outputs.artifact-id }} ARTIFACT_NAME: ${{ steps.artifact.outputs.name }} GH_TOKEN: ${{ github.token }} run: | set -euo pipefail artifact_json="" for attempt in 1 2 3 4 5; do if artifact_json="$(gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}")"; then break fi [[ "${attempt}" -lt 5 ]] || exit 1 sleep 2 done size="$( jq -er \ --arg digest "sha256:${ARTIFACT_DIGEST}" \ --arg id "${ARTIFACT_ID}" \ --arg name "${ARTIFACT_NAME}" \ --arg run_id "${GITHUB_RUN_ID}" \ --arg workflow_sha "${GITHUB_SHA}" \ 'select( (.id | tostring) == $id and .name == $name and .expired == false and .digest == $digest and (.workflow_run.id | tostring) == $run_id and .workflow_run.head_sha == $workflow_sha ) | .size_in_bytes' \ <<< "${artifact_json}" )" [[ "${size}" =~ ^[1-9][0-9]*$ ]] || exit 1 echo "size=${size}" >> "${GITHUB_OUTPUT}" validate_bootstrap_artifact: name: Validate immutable bootstrap handoff needs: [ resolve_bootstrap_plan, validate_release_publish_approval, validate_bootstrap_trusted_publisher_cli, pack_bootstrap_plugins, ] if: always() && github.event_name == 'workflow_dispatch' && needs.resolve_bootstrap_plan.outputs.has_bootstrap_candidates == 'true' && (inputs.pretag_validation == true || needs.validate_release_publish_approval.result == 'success') && needs.validate_bootstrap_trusted_publisher_cli.result == 'success' && needs.pack_bootstrap_plugins.result == 'success' runs-on: ubuntu-latest timeout-minutes: 45 environment: clawhub-plugin-bootstrap permissions: actions: read contents: read steps: - name: Checkout trusted workflow tooling uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} path: .release-harness - name: Setup trusted Node runtime uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: ${{ env.NODE_VERSION }} - name: Materialize locked ClawHub CLI id: clawhub_cli run: | set -euo pipefail bash .release-harness/scripts/materialize-clawhub-cli.sh \ .release-harness/.github/release/clawhub-cli \ "${RUNNER_TEMP}/clawhub-cli" \ "${GITHUB_OUTPUT}" - name: Download and verify immutable ClawHub bootstrap artifact env: ARTIFACT_DIGEST: ${{ needs.pack_bootstrap_plugins.outputs.artifact_digest }} ARTIFACT_ID: ${{ needs.pack_bootstrap_plugins.outputs.artifact_id }} ARTIFACT_NAME: ${{ needs.pack_bootstrap_plugins.outputs.artifact_name }} ARTIFACT_RUN_ATTEMPT: ${{ needs.pack_bootstrap_plugins.outputs.artifact_run_attempt }} ARTIFACT_RUN_ID: ${{ needs.pack_bootstrap_plugins.outputs.artifact_run_id }} ARTIFACT_SIZE: ${{ needs.pack_bootstrap_plugins.outputs.artifact_size }} GH_TOKEN: ${{ github.token }} TARGET_SHA: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} WORKFLOW_SHA: ${{ github.sha }} CLAWHUB_TOOLCHAIN_INTEGRITY: ${{ steps.clawhub_cli.outputs.integrity }} CLAWHUB_TOOLCHAIN_SHA256: ${{ steps.clawhub_cli.outputs.lock_sha256 }} CLAWHUB_TOOLCHAIN_VERSION: ${{ steps.clawhub_cli.outputs.version }} run: | set -euo pipefail node .release-harness/scripts/lib/clawhub-bootstrap-artifact.mjs download \ --artifact-digest "${ARTIFACT_DIGEST}" \ --artifact-id "${ARTIFACT_ID}" \ --artifact-name "${ARTIFACT_NAME}" \ --artifact-size "${ARTIFACT_SIZE}" \ --clawhub-toolchain-integrity "${CLAWHUB_TOOLCHAIN_INTEGRITY}" \ --clawhub-toolchain-sha256 "${CLAWHUB_TOOLCHAIN_SHA256}" \ --clawhub-toolchain-version "${CLAWHUB_TOOLCHAIN_VERSION}" \ --consumer-run-attempt "${GITHUB_RUN_ATTEMPT}" \ --output-root "${RUNNER_TEMP}/clawhub-bootstrap-artifact" \ --producer-job-name "Pack immutable ClawHub bootstrap artifacts" \ --repository "${GITHUB_REPOSITORY}" \ --run-attempt "${ARTIFACT_RUN_ATTEMPT}" \ --run-id "${ARTIFACT_RUN_ID}" \ --target-sha "${TARGET_SHA}" \ --workflow-sha "${WORKFLOW_SHA}" - name: Rehash immutable ClawHub bootstrap artifacts env: ARTIFACT_NAME: ${{ needs.pack_bootstrap_plugins.outputs.artifact_name }} ARTIFACT_RUN_ATTEMPT: ${{ needs.pack_bootstrap_plugins.outputs.artifact_run_attempt }} ARTIFACT_RUN_ID: ${{ needs.pack_bootstrap_plugins.outputs.artifact_run_id }} RELEASE_PLUGINS: ${{ inputs.plugins }} TARGET_SHA: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} WORKFLOW_SHA: ${{ github.sha }} CLAWHUB_TOOLCHAIN_INTEGRITY: ${{ steps.clawhub_cli.outputs.integrity }} CLAWHUB_TOOLCHAIN_SHA256: ${{ steps.clawhub_cli.outputs.lock_sha256 }} CLAWHUB_TOOLCHAIN_VERSION: ${{ steps.clawhub_cli.outputs.version }} run: | set -euo pipefail node .release-harness/scripts/lib/clawhub-bootstrap-artifact.mjs verify \ --artifact-root "${RUNNER_TEMP}/clawhub-bootstrap-artifact" \ --artifact-name "${ARTIFACT_NAME}" \ --clawhub-toolchain-integrity "${CLAWHUB_TOOLCHAIN_INTEGRITY}" \ --clawhub-toolchain-sha256 "${CLAWHUB_TOOLCHAIN_SHA256}" \ --clawhub-toolchain-version "${CLAWHUB_TOOLCHAIN_VERSION}" \ --manifest "${RUNNER_TEMP}/clawhub-bootstrap-artifact/manifest.json" \ --output "${RUNNER_TEMP}/clawhub-bootstrap-validated.json" \ --plugins "${RELEASE_PLUGINS}" \ --repository "${GITHUB_REPOSITORY}" \ --run-attempt "${ARTIFACT_RUN_ATTEMPT}" \ --run-id "${ARTIFACT_RUN_ID}" \ --target-sha "${TARGET_SHA}" \ --workflow-sha "${WORKFLOW_SHA}" - name: Validate packed ClawHub package identities env: CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }} SOURCE_COMMIT: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} SOURCE_REF: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} SOURCE_REPO: ${{ github.repository }} OPENCLAW_CLAWHUB_CLI: ${{ steps.clawhub_cli.outputs.cli }} run: | set -euo pipefail while IFS= read -r plugin; do artifact_path="$(jq -r '.artifactPath' <<< "${plugin}")" PACKAGE_DIR="$(jq -r '.packageDir' <<< "${plugin}")" \ PACKAGE_TAG="$(jq -r '.publishTag' <<< "${plugin}")" \ EXPECTED_CLAWHUB_PACKAGE_NAME="$(jq -r '.packageName' <<< "${plugin}")" \ EXPECTED_CLAWHUB_PACKAGE_VERSION="$(jq -r '.version' <<< "${plugin}")" \ EXPECTED_CLAWHUB_ARTIFACT_SHA256="$(jq -r '.sha256' <<< "${plugin}")" \ EXPECTED_CLAWHUB_ARTIFACT_SIZE="$(jq -r '.size' <<< "${plugin}")" \ OPENCLAW_CLAWHUB_AUTH_LABEL="secretless immutable handoff validation" \ bash .release-harness/scripts/plugin-clawhub-publish.sh \ --validate-packed "${RUNNER_TEMP}/clawhub-bootstrap-artifact/${artifact_path}" done < <(jq -c '.entries[]' "${RUNNER_TEMP}/clawhub-bootstrap-validated.json") - name: Require configure-only registry bytes to match target env: ARTIFACT_DIGEST: ${{ needs.pack_bootstrap_plugins.outputs.artifact_digest }} ARTIFACT_ID: ${{ needs.pack_bootstrap_plugins.outputs.artifact_id }} CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }} CLAWHUB_TOOLCHAIN_INTEGRITY: ${{ steps.clawhub_cli.outputs.integrity }} CLAWHUB_TOOLCHAIN_SHA256: ${{ steps.clawhub_cli.outputs.lock_sha256 }} CLAWHUB_TOOLCHAIN_VERSION: ${{ steps.clawhub_cli.outputs.version }} run: | set -euo pipefail node .release-harness/scripts/verify-clawhub-published-artifact.mjs \ --artifact-digest "${ARTIFACT_DIGEST}" \ --artifact-id "${ARTIFACT_ID}" \ --clawhub-toolchain-integrity "${CLAWHUB_TOOLCHAIN_INTEGRITY}" \ --clawhub-toolchain-sha256 "${CLAWHUB_TOOLCHAIN_SHA256}" \ --clawhub-toolchain-version "${CLAWHUB_TOOLCHAIN_VERSION}" \ --manifest "${RUNNER_TEMP}/clawhub-bootstrap-validated.json" \ --mode configure-only-preflight \ --output "${RUNNER_TEMP}/clawhub-bootstrap-configure-only-preflight.json" \ --registry "${CLAWHUB_REGISTRY}" \ --terminal-run-attempt "${GITHUB_RUN_ATTEMPT}" - name: Upload immutable bootstrap validation evidence uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: clawhub-bootstrap-validation-${{ github.run_id }}-${{ github.run_attempt }} path: | ${{ runner.temp }}/clawhub-bootstrap-validated.json ${{ runner.temp }}/clawhub-bootstrap-configure-only-preflight.json if-no-files-found: error publish_bootstrap_plugins: needs: [ resolve_bootstrap_plan, validate_release_publish_approval, validate_bootstrap_trusted_publisher_cli, pack_bootstrap_plugins, validate_bootstrap_artifact, ] if: always() && github.event_name == 'workflow_dispatch' && inputs.pretag_validation != true && inputs.dry_run != true && needs.resolve_bootstrap_plan.outputs.has_bootstrap_candidates == 'true' && needs.validate_release_publish_approval.result == 'success' && needs.validate_bootstrap_trusted_publisher_cli.result == 'success' && needs.pack_bootstrap_plugins.result == 'success' && needs.validate_bootstrap_artifact.result == 'success' runs-on: ubuntu-latest timeout-minutes: 120 environment: clawhub-plugin-bootstrap permissions: actions: read contents: read steps: - name: Checkout trusted workflow tooling uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} path: .release-harness - name: Setup trusted Node runtime uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: ${{ env.NODE_VERSION }} - name: Materialize locked ClawHub CLI id: clawhub_cli run: | set -euo pipefail bash .release-harness/scripts/materialize-clawhub-cli.sh \ .release-harness/.github/release/clawhub-cli \ "${RUNNER_TEMP}/clawhub-cli" \ "${GITHUB_OUTPUT}" - name: Download and verify immutable ClawHub bootstrap artifact env: ARTIFACT_DIGEST: ${{ needs.pack_bootstrap_plugins.outputs.artifact_digest }} ARTIFACT_ID: ${{ needs.pack_bootstrap_plugins.outputs.artifact_id }} ARTIFACT_NAME: ${{ needs.pack_bootstrap_plugins.outputs.artifact_name }} ARTIFACT_RUN_ATTEMPT: ${{ needs.pack_bootstrap_plugins.outputs.artifact_run_attempt }} ARTIFACT_RUN_ID: ${{ needs.pack_bootstrap_plugins.outputs.artifact_run_id }} ARTIFACT_SIZE: ${{ needs.pack_bootstrap_plugins.outputs.artifact_size }} GH_TOKEN: ${{ github.token }} TARGET_SHA: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} WORKFLOW_SHA: ${{ github.sha }} CLAWHUB_TOOLCHAIN_INTEGRITY: ${{ steps.clawhub_cli.outputs.integrity }} CLAWHUB_TOOLCHAIN_SHA256: ${{ steps.clawhub_cli.outputs.lock_sha256 }} CLAWHUB_TOOLCHAIN_VERSION: ${{ steps.clawhub_cli.outputs.version }} run: | set -euo pipefail node .release-harness/scripts/lib/clawhub-bootstrap-artifact.mjs download \ --artifact-digest "${ARTIFACT_DIGEST}" \ --artifact-id "${ARTIFACT_ID}" \ --artifact-name "${ARTIFACT_NAME}" \ --artifact-size "${ARTIFACT_SIZE}" \ --clawhub-toolchain-integrity "${CLAWHUB_TOOLCHAIN_INTEGRITY}" \ --clawhub-toolchain-sha256 "${CLAWHUB_TOOLCHAIN_SHA256}" \ --clawhub-toolchain-version "${CLAWHUB_TOOLCHAIN_VERSION}" \ --consumer-run-attempt "${GITHUB_RUN_ATTEMPT}" \ --output-root "${RUNNER_TEMP}/clawhub-bootstrap-artifact" \ --producer-job-name "Pack immutable ClawHub bootstrap artifacts" \ --repository "${GITHUB_REPOSITORY}" \ --run-attempt "${ARTIFACT_RUN_ATTEMPT}" \ --run-id "${ARTIFACT_RUN_ID}" \ --target-sha "${TARGET_SHA}" \ --workflow-sha "${WORKFLOW_SHA}" - name: Rehash immutable ClawHub bootstrap artifacts env: ARTIFACT_NAME: ${{ needs.pack_bootstrap_plugins.outputs.artifact_name }} ARTIFACT_RUN_ATTEMPT: ${{ needs.pack_bootstrap_plugins.outputs.artifact_run_attempt }} ARTIFACT_RUN_ID: ${{ needs.pack_bootstrap_plugins.outputs.artifact_run_id }} RELEASE_PLUGINS: ${{ inputs.plugins }} TARGET_SHA: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} WORKFLOW_SHA: ${{ github.sha }} CLAWHUB_TOOLCHAIN_INTEGRITY: ${{ steps.clawhub_cli.outputs.integrity }} CLAWHUB_TOOLCHAIN_SHA256: ${{ steps.clawhub_cli.outputs.lock_sha256 }} CLAWHUB_TOOLCHAIN_VERSION: ${{ steps.clawhub_cli.outputs.version }} run: | set -euo pipefail node .release-harness/scripts/lib/clawhub-bootstrap-artifact.mjs verify \ --artifact-root "${RUNNER_TEMP}/clawhub-bootstrap-artifact" \ --artifact-name "${ARTIFACT_NAME}" \ --clawhub-toolchain-integrity "${CLAWHUB_TOOLCHAIN_INTEGRITY}" \ --clawhub-toolchain-sha256 "${CLAWHUB_TOOLCHAIN_SHA256}" \ --clawhub-toolchain-version "${CLAWHUB_TOOLCHAIN_VERSION}" \ --manifest "${RUNNER_TEMP}/clawhub-bootstrap-artifact/manifest.json" \ --output "${RUNNER_TEMP}/clawhub-bootstrap-validated.json" \ --plugins "${RELEASE_PLUGINS}" \ --repository "${GITHUB_REPOSITORY}" \ --run-attempt "${ARTIFACT_RUN_ATTEMPT}" \ --run-id "${ARTIFACT_RUN_ID}" \ --target-sha "${TARGET_SHA}" \ --workflow-sha "${WORKFLOW_SHA}" - name: Validate packed ClawHub package identities before credentials env: CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }} SOURCE_COMMIT: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} SOURCE_REF: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} SOURCE_REPO: ${{ github.repository }} OPENCLAW_CLAWHUB_CLI: ${{ steps.clawhub_cli.outputs.cli }} run: | set -euo pipefail while IFS= read -r plugin; do artifact_path="$(jq -r '.artifactPath' <<< "${plugin}")" PACKAGE_DIR="$(jq -r '.packageDir' <<< "${plugin}")" \ PACKAGE_TAG="$(jq -r '.publishTag' <<< "${plugin}")" \ EXPECTED_CLAWHUB_PACKAGE_NAME="$(jq -r '.packageName' <<< "${plugin}")" \ EXPECTED_CLAWHUB_PACKAGE_VERSION="$(jq -r '.version' <<< "${plugin}")" \ EXPECTED_CLAWHUB_ARTIFACT_SHA256="$(jq -r '.sha256' <<< "${plugin}")" \ EXPECTED_CLAWHUB_ARTIFACT_SIZE="$(jq -r '.size' <<< "${plugin}")" \ OPENCLAW_CLAWHUB_AUTH_LABEL="token-gated bootstrap (credential not loaded)" \ bash .release-harness/scripts/plugin-clawhub-publish.sh \ --validate-packed "${RUNNER_TEMP}/clawhub-bootstrap-artifact/${artifact_path}" done < <(jq -c '.entries[]' "${RUNNER_TEMP}/clawhub-bootstrap-validated.json") - name: Reconfirm configure-only registry bytes before credentials env: ARTIFACT_DIGEST: ${{ needs.pack_bootstrap_plugins.outputs.artifact_digest }} ARTIFACT_ID: ${{ needs.pack_bootstrap_plugins.outputs.artifact_id }} CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }} CLAWHUB_TOOLCHAIN_INTEGRITY: ${{ steps.clawhub_cli.outputs.integrity }} CLAWHUB_TOOLCHAIN_SHA256: ${{ steps.clawhub_cli.outputs.lock_sha256 }} CLAWHUB_TOOLCHAIN_VERSION: ${{ steps.clawhub_cli.outputs.version }} run: | set -euo pipefail node .release-harness/scripts/verify-clawhub-published-artifact.mjs \ --artifact-digest "${ARTIFACT_DIGEST}" \ --artifact-id "${ARTIFACT_ID}" \ --clawhub-toolchain-integrity "${CLAWHUB_TOOLCHAIN_INTEGRITY}" \ --clawhub-toolchain-sha256 "${CLAWHUB_TOOLCHAIN_SHA256}" \ --clawhub-toolchain-version "${CLAWHUB_TOOLCHAIN_VERSION}" \ --manifest "${RUNNER_TEMP}/clawhub-bootstrap-validated.json" \ --mode configure-only-preflight \ --registry "${CLAWHUB_REGISTRY}" \ --terminal-run-attempt "${GITHUB_RUN_ATTEMPT}" - name: Reconfirm release tag before credentials env: RELEASE_TAG: ${{ inputs.release_tag }} TARGET_SHA: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} run: | set -euo pipefail git -C .release-harness fetch --force --no-tags origin \ "+refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" tag_sha="$(git -C .release-harness rev-parse "${RELEASE_TAG}^{commit}")" [[ "${tag_sha}" == "${TARGET_SHA}" ]] || { echo "Plugin ClawHub bootstrap target ${TARGET_SHA} no longer matches ${RELEASE_TAG} (${tag_sha})." >&2 exit 1 } - name: Write ClawHub token config env: CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }} CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }} run: | set -euo pipefail umask 077 config_path="${RUNNER_TEMP}/clawhub-config.json" CONFIG_PATH="${config_path}" node --input-type=module <<'NODE' import { writeFileSync } from "node:fs"; const registry = process.env.CLAWHUB_REGISTRY?.trim(); const token = process.env.CLAWHUB_TOKEN?.trim(); const configPath = process.env.CONFIG_PATH; if (!registry) { throw new Error("CLAWHUB_REGISTRY is required for token-gated ClawHub bootstrap."); } if (!token) { throw new Error("CLAWHUB_TOKEN is required for token-gated ClawHub bootstrap."); } if (!configPath) { throw new Error("CONFIG_PATH is required."); } writeFileSync(configPath, `${JSON.stringify({ registry, token }, null, 2)}\n`, { encoding: "utf8", mode: 0o600, }); NODE echo "CLAWHUB_CONFIG_PATH=${config_path}" >> "${GITHUB_ENV}" - name: Publish exact ClawHub bootstrap artifacts env: CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }} SOURCE_COMMIT: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} SOURCE_REF: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} SOURCE_REPO: ${{ github.repository }} OPENCLAW_CLAWHUB_CLI: ${{ steps.clawhub_cli.outputs.cli }} RELEASE_TAG: ${{ inputs.release_tag }} TARGET_SHA: ${{ needs.resolve_bootstrap_plan.outputs.ref_revision }} run: | set -euo pipefail verify_release_tag_target() { git -C .release-harness fetch --force --no-tags origin \ "+refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" local tag_sha tag_sha="$(git -C .release-harness rev-parse "${RELEASE_TAG}^{commit}")" [[ "${tag_sha}" == "${TARGET_SHA}" ]] || { echo "Plugin ClawHub bootstrap target ${TARGET_SHA} no longer matches ${RELEASE_TAG} (${tag_sha})." >&2 exit 1 } } while IFS= read -r plugin; do verify_release_tag_target mode="$(jq -r '.bootstrapMode' <<< "${plugin}")" package_name="$(jq -r '.packageName' <<< "${plugin}")" if [[ "${mode}" == "publish" ]]; then artifact_path="$(jq -r '.artifactPath' <<< "${plugin}")" # The prefix lets ClawHub reuse only an existing same-integrity # release after an ambiguous token-publish timeout. manual_override="GitHub Actions immutable bootstrap retry" if [[ "$(jq -r '.requiresManualOverride' <<< "${plugin}")" == "true" ]]; then manual_override="GitHub Actions trusted publisher repair before OIDC migration" fi PACKAGE_DIR="$(jq -r '.packageDir' <<< "${plugin}")" \ PACKAGE_TAG="$(jq -r '.publishTag' <<< "${plugin}")" \ EXPECTED_CLAWHUB_PACKAGE_NAME="${package_name}" \ EXPECTED_CLAWHUB_PACKAGE_VERSION="$(jq -r '.version' <<< "${plugin}")" \ EXPECTED_CLAWHUB_ARTIFACT_SHA256="$(jq -r '.sha256' <<< "${plugin}")" \ EXPECTED_CLAWHUB_ARTIFACT_SIZE="$(jq -r '.size' <<< "${plugin}")" \ OPENCLAW_CLAWHUB_AUTH_LABEL="token-gated bootstrap" \ OPENCLAW_CLAWHUB_MANUAL_OVERRIDE_REASON="${manual_override}" \ OPENCLAW_CLAWHUB_RELEASE_GIT_DIR="${GITHUB_WORKSPACE}/.release-harness" \ OPENCLAW_CLAWHUB_RELEASE_TAG="${RELEASE_TAG}" \ OPENCLAW_CLAWHUB_TARGET_SHA="${TARGET_SHA}" \ bash .release-harness/scripts/plugin-clawhub-publish.sh \ --publish-packed "${RUNNER_TEMP}/clawhub-bootstrap-artifact/${artifact_path}" else echo "Skipping bootstrap publish because the exact version is already present; configuring trusted publisher only." fi verify_release_tag_target timeout --signal=TERM --kill-after=10s 300s \ "${OPENCLAW_CLAWHUB_CLI}" package trusted-publisher set "${package_name}" \ --repository openclaw/openclaw \ --workflow-filename plugin-clawhub-release.yml done < <(jq -c '.entries[]' "${RUNNER_TEMP}/clawhub-bootstrap-validated.json") - name: Verify exact ClawHub registry artifact bytes env: ARTIFACT_DIGEST: ${{ needs.pack_bootstrap_plugins.outputs.artifact_digest }} ARTIFACT_ID: ${{ needs.pack_bootstrap_plugins.outputs.artifact_id }} CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }} CLAWHUB_TOOLCHAIN_INTEGRITY: ${{ steps.clawhub_cli.outputs.integrity }} CLAWHUB_TOOLCHAIN_SHA256: ${{ steps.clawhub_cli.outputs.lock_sha256 }} CLAWHUB_TOOLCHAIN_VERSION: ${{ steps.clawhub_cli.outputs.version }} run: | set -euo pipefail node .release-harness/scripts/verify-clawhub-published-artifact.mjs \ --artifact-digest "${ARTIFACT_DIGEST}" \ --artifact-id "${ARTIFACT_ID}" \ --clawhub-toolchain-integrity "${CLAWHUB_TOOLCHAIN_INTEGRITY}" \ --clawhub-toolchain-sha256 "${CLAWHUB_TOOLCHAIN_SHA256}" \ --clawhub-toolchain-version "${CLAWHUB_TOOLCHAIN_VERSION}" \ --manifest "${RUNNER_TEMP}/clawhub-bootstrap-validated.json" \ --output "${RUNNER_TEMP}/clawhub-bootstrap-readback.json" \ --registry "${CLAWHUB_REGISTRY}" \ --terminal-run-attempt "${GITHUB_RUN_ATTEMPT}" - name: Upload ClawHub bootstrap readback evidence uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: clawhub-bootstrap-readback-${{ github.run_id }}-${{ github.run_attempt }} path: ${{ runner.temp }}/clawhub-bootstrap-readback.json if-no-files-found: error