From 7c93cff84af6331c1eda9117fc47f3a569338b45 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 5 Aug 2026 04:14:30 +0800 Subject: [PATCH] fix(release): accept trusted extended-stable validation evidence (#119264) * fix(release): accept trusted extended-stable validation evidence * fix(release): document trusted candidate promotion * docs(release): clarify validation evidence recovery --- .../release-openclaw-maintainer/SKILL.md | 27 +++-- .github/workflows/openclaw-npm-release.yml | 107 ++++++++++-------- docs/reference/RELEASING.md | 26 +++++ ...nclaw-npm-extended-stable-workflow.test.ts | 72 ++++++++++-- .../package-acceptance-workflow.test.ts | 25 ++-- 5 files changed, 173 insertions(+), 84 deletions(-) diff --git a/.agents/skills/release-openclaw-maintainer/SKILL.md b/.agents/skills/release-openclaw-maintainer/SKILL.md index 5ab64c1c758b..574d8a8f3f1d 100644 --- a/.agents/skills/release-openclaw-maintainer/SKILL.md +++ b/.agents/skills/release-openclaw-maintainer/SKILL.md @@ -252,22 +252,29 @@ on pinned current `main` as the exact command and validation contract. and workflow validation. Run focused checks and freeze the untagged tip SHA. 2. From that branch, run npm preflight with the SHA as `tag`, `preflight_only=true`, and `npm_dist_tag=extended-stable`; save the run ID. -3. Run complete Full Release Validation from and against the canonical branch - with `release_profile=stable`; save its run ID and successful `run_attempt`. - Any branch change invalidates both gates. +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. 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, successful, and bound to - the canonical branch, tag SHA, and attempt. Reject `release-ci/*` and narrow - reruns. +5. Require the saved validation run to be complete and successful, bind its + manifest target SHA and attempt to the tag, and accept a direct run from the + 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 from the same branch with the tag, `npm_dist_tag=extended-stable`, - all three run IDs, and - `full_release_validation_run_attempt=`. Require the prepared - tarball and every run to match the branch and release SHA. +7. Publish core with the tag, `npm_dist_tag=extended-stable`, all three run IDs, + and `full_release_validation_run_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 `node --import tsx scripts/openclaw-npm-postpublish-verify.ts YYYY.M.P`. Verify signatures, provenance, inventories, exact versions, and selectors. diff --git a/.github/workflows/openclaw-npm-release.yml b/.github/workflows/openclaw-npm-release.yml index 49b7be2575bf..5c1eb071428d 100644 --- a/.github/workflows/openclaw-npm-release.yml +++ b/.github/workflows/openclaw-npm-release.yml @@ -32,6 +32,11 @@ on: description: Successful Plugin NPM Release run id for the exact extended-stable branch and release SHA required: false type: string + release_candidate_branch: + description: Canonical extended-stable branch when a trusted main workflow promotes its immutable tag + required: false + default: "" + type: string npm_dist_tag: description: npm dist-tag to publish to required: true @@ -709,6 +714,7 @@ jobs: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: + ref: ${{ inputs.release_candidate_branch != '' && format('refs/tags/{0}', inputs.tag) || github.sha }} fetch-depth: 0 filter: blob:none persist-credentials: false @@ -718,13 +724,14 @@ jobs: BYPASS_EXTENDED_STABLE_GUARD: ${{ inputs.bypass_extended_stable_guard }} RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} RELEASE_TAG: ${{ inputs.tag }} - NPM_WORKFLOW_REF: ${{ github.ref }} + NPM_WORKFLOW_REF: ${{ inputs.release_candidate_branch != '' && format('refs/heads/{0}', inputs.release_candidate_branch) || github.ref }} run: node scripts/openclaw-npm-extended-stable-release.mjs validate-request - name: Require trusted workflow ref for publish env: RELEASE_TAG: ${{ inputs.tag }} RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} + RELEASE_CANDIDATE_BRANCH: ${{ inputs.release_candidate_branch }} WORKFLOW_REF: ${{ github.ref }} WORKFLOW_SHA: ${{ github.workflow_sha }} run: | @@ -732,12 +739,29 @@ jobs: tideclaw_alpha_publish=false extended_stable_publish=false sha_pinned_release_publish=false + release_candidate_branch="${RELEASE_CANDIDATE_BRANCH:-}" if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" == "alpha" && "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then tideclaw_alpha_publish=true fi if [[ "${RELEASE_NPM_DIST_TAG}" == "extended-stable" && "${WORKFLOW_REF}" == refs/heads/extended-stable/* ]]; then extended_stable_publish=true fi + if [[ -n "${release_candidate_branch}" ]]; then + if [[ "${RELEASE_NPM_DIST_TAG}" != "extended-stable" || "${WORKFLOW_REF}" != "refs/heads/main" ]]; then + echo "release_candidate_branch is only valid for an extended-stable publish dispatched from main." >&2 + exit 1 + fi + if [[ ! "${RELEASE_TAG}" =~ ^v([0-9]{4})\.([1-9][0-9]*)\.[1-9][0-9]*$ ]]; then + echo "release_candidate_branch requires an exact final release tag." >&2 + exit 1 + fi + expected_candidate_branch="extended-stable/${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.33" + if [[ "${release_candidate_branch}" != "${expected_candidate_branch}" ]]; then + echo "release_candidate_branch must be ${expected_candidate_branch} for ${RELEASE_TAG}." >&2 + exit 1 + fi + extended_stable_publish=true + fi 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}" ]] || { @@ -857,6 +881,16 @@ jobs: # tarball, so real publishes do not need the repository's full history. fetch-depth: ${{ inputs.preflight_run_id != '' && 1 || 0 }} + - name: Checkout trusted validation verifier + if: ${{ inputs.full_release_validation_run_id != '' }} + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ github.workflow_sha }} + path: trusted-workflow + fetch-depth: 0 + filter: blob:none + persist-credentials: false + - name: Validate Tideclaw alpha publish target if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/') env: @@ -902,7 +936,7 @@ jobs: GH_TOKEN: ${{ github.token }} PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }} RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} - EXPECTED_EXTENDED_STABLE_BRANCH: ${{ github.ref_name }} + EXPECTED_EXTENDED_STABLE_BRANCH: ${{ inputs.release_candidate_branch || github.ref_name }} RUN_KIND: preflight run: | set -euo pipefail @@ -911,37 +945,32 @@ jobs: RUN_JSON="$(gh run view "$PREFLIGHT_RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,headBranch,headSha,event,conclusion,url)" printf '%s' "$RUN_JSON" | node scripts/openclaw-npm-extended-stable-release.mjs verify-run - - name: Verify full release validation run metadata - id: full_run + - name: Download full release validation manifest + if: ${{ inputs.full_release_validation_run_id != '' }} + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ inputs.full_release_validation_run_attempt }} + path: full-release-validation + repository: ${{ github.repository }} + run-id: ${{ inputs.full_release_validation_run_id }} + github-token: ${{ github.token }} + + - name: Verify full release validation evidence if: ${{ inputs.full_release_validation_run_id != '' }} env: GH_TOKEN: ${{ github.token }} FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }} FULL_RELEASE_VALIDATION_RUN_ATTEMPT: ${{ inputs.full_release_validation_run_attempt }} - RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} - EXPECTED_EXTENDED_STABLE_BRANCH: ${{ github.ref_name }} - RUN_KIND: validation + EXPECTED_WORKFLOW_BRANCH: ${{ inputs.release_candidate_branch || github.ref_name }} + STRICT_VALIDATOR_FILE: ${{ github.workspace }}/trusted-workflow/scripts/release-ci-summary.mjs run: | set -euo pipefail - EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)" - export EXPECTED_RELEASE_SHA - run_file="${RUNNER_TEMP}/full-release-validation-run.json" - gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" > "$run_file" - jq '{ - workflowName: .name, - headBranch: .head_branch, - headSha: .head_sha, - event, - status, - conclusion, - url: .html_url - }' "$run_file" | node scripts/openclaw-npm-extended-stable-release.mjs verify-run - run_attempt="$(jq -r '.run_attempt // ""' "$run_file")" - if [[ "$run_attempt" != "$FULL_RELEASE_VALIDATION_RUN_ATTEMPT" ]]; then - echo "Full Release Validation run ${FULL_RELEASE_VALIDATION_RUN_ID} attempt mismatch: expected ${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}, got ${run_attempt:-}." >&2 - exit 1 - fi - echo "attempt=$run_attempt" >> "$GITHUB_OUTPUT" + EXPECTED_SHA="$(git rev-parse HEAD)" + MANIFEST_FILE="full-release-validation/full-release-validation-manifest.json" + export EXPECTED_SHA MANIFEST_FILE + timeout --signal=TERM --kill-after=10s 120s git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main + gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}" | \ + node trusted-workflow/scripts/validate-full-release-validation-evidence.mjs - name: Verify plugin npm release run metadata if: ${{ inputs.npm_dist_tag == 'extended-stable' }} @@ -949,7 +978,7 @@ jobs: GH_TOKEN: ${{ github.token }} PLUGIN_NPM_RUN_ID: ${{ inputs.plugin_npm_run_id }} RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} - EXPECTED_EXTENDED_STABLE_BRANCH: ${{ github.ref_name }} + EXPECTED_EXTENDED_STABLE_BRANCH: ${{ inputs.release_candidate_branch || github.ref_name }} RUN_KIND: plugin run: | set -euo pipefail @@ -1011,16 +1040,6 @@ jobs: download_preflight_artifact - - name: Download full release validation manifest - if: ${{ inputs.full_release_validation_run_id != '' }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - name: full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ steps.full_run.outputs.attempt }} - path: full-release-validation - repository: ${{ github.repository }} - run-id: ${{ inputs.full_release_validation_run_id }} - github-token: ${{ github.token }} - - name: Validate release tag and package metadata if: ${{ inputs.preflight_run_id == '' }} env: @@ -1167,20 +1186,14 @@ jobs: env: RELEASE_TAG: ${{ inputs.tag }} RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} - EXPECTED_WORKFLOW_REF: ${{ github.ref_name }} - FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }} - FULL_RELEASE_VALIDATION_RUN_ATTEMPT: ${{ steps.full_run.outputs.attempt }} run: | set -euo pipefail - EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)" MANIFEST_FILE="full-release-validation/full-release-validation-manifest.json" if [[ ! -f "$MANIFEST_FILE" ]]; then echo "Full release validation manifest is missing." >&2 ls -la full-release-validation >&2 || true exit 1 fi - export EXPECTED_RELEASE_SHA MANIFEST_FILE - node scripts/openclaw-npm-extended-stable-release.mjs verify-manifest RERUN_GROUP="$(jq -r '.rerunGroup // ""' "$MANIFEST_FILE")" RUN_RELEASE_SOAK="$(jq -r '.runReleaseSoak // ""' "$MANIFEST_FILE")" PERFORMANCE_BLOCKING="$(jq -r '.controls.performanceBlocking // false' "$MANIFEST_FILE")" @@ -1202,7 +1215,7 @@ jobs: BYPASS_EXTENDED_STABLE_GUARD: ${{ inputs.bypass_extended_stable_guard }} RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} RELEASE_TAG: ${{ inputs.tag }} - NPM_WORKFLOW_REF: ${{ github.ref }} + NPM_WORKFLOW_REF: ${{ inputs.release_candidate_branch != '' && format('refs/heads/{0}', inputs.release_candidate_branch) || github.ref }} run: node scripts/openclaw-npm-extended-stable-release.mjs validate-request - name: Capture previous extended-stable selector @@ -1255,8 +1268,7 @@ jobs: env: BYPASS_EXTENDED_STABLE_GUARD: ${{ inputs.bypass_extended_stable_guard }} RELEASE_TAG: ${{ inputs.tag }} - RELEASE_SHA: ${{ github.sha }} - EXTENDED_STABLE_BRANCH: ${{ github.ref_name }} + EXTENDED_STABLE_BRANCH: ${{ inputs.release_candidate_branch || github.ref_name }} PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }} FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }} TARBALL_NAME: ${{ steps.preflight_provenance.outputs.tarball_name }} @@ -1270,13 +1282,14 @@ jobs: run: | set -euo pipefail release_version="${RELEASE_TAG#v}" + release_sha="$(git rev-parse HEAD)" { echo "## npm extended-stable publication" echo "- Package: openclaw@${release_version}" echo "- Extended-stable guard bypass: ${BYPASS_EXTENDED_STABLE_GUARD}" echo "- Extended-stable branch: ${EXTENDED_STABLE_BRANCH}" echo "- Release tag: ${RELEASE_TAG}" - echo "- Release SHA: ${RELEASE_SHA}" + echo "- Release SHA: ${release_sha}" echo "- npm preflight run: ${PREFLIGHT_RUN_ID}" echo "- Full Release Validation run: ${FULL_RELEASE_VALIDATION_RUN_ID}" echo "- Tarball: ${TARBALL_NAME:-unavailable}" diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index b62dc7aa98bf..13df3129889b 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -137,6 +137,32 @@ gh workflow run openclaw-npm-release.yml \ -f 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 \ + --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= \ + -f full_release_validation_run_id= \ + -f full_release_validation_run_attempt= \ + -f plugin_npm_run_id= +``` + +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. + 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, diff --git a/test/scripts/openclaw-npm-extended-stable-workflow.test.ts b/test/scripts/openclaw-npm-extended-stable-workflow.test.ts index 6c74a95f25c0..706ca45a6d50 100644 --- a/test/scripts/openclaw-npm-extended-stable-workflow.test.ts +++ b/test/scripts/openclaw-npm-extended-stable-workflow.test.ts @@ -21,6 +21,7 @@ type Workflow = { bypass_extended_stable_guard?: { default?: boolean; type?: string }; npm_dist_tag?: { options?: string[] }; plugin_npm_run_id?: { required?: boolean; type?: string }; + release_candidate_branch?: { default?: string; required?: boolean; type?: string }; }; }; }; @@ -43,7 +44,7 @@ describe("minimal npm extended-stable workflow", () => { it("bounds every git fetch operation", () => { const source = readFileSync(workflowPath, "utf8"); const gitFetchLines = source.split("\n").filter((line) => line.includes("git fetch")); - expect(gitFetchLines).toHaveLength(6); + expect(gitFetchLines).toHaveLength(7); expect( gitFetchLines.every((line) => line.includes("timeout --signal=TERM --kill-after=10s 120s")), ).toBe(true); @@ -128,6 +129,41 @@ describe("minimal npm extended-stable workflow", () => { expect(summary.run).toContain("Extended-stable guard bypass: ${BYPASS_EXTENDED_STABLE_GUARD}"); }); + it("lets main promote only the canonical immutable extended-stable candidate", () => { + const parsed = workflow(); + const releaseDocs = readFileSync("docs/reference/RELEASING.md", "utf8"); + const input = parsed.on?.workflow_dispatch?.inputs?.release_candidate_branch; + expect(input).toMatchObject({ default: "", required: false, type: "string" }); + + const validate = step(parsed.jobs?.validate_publish_request, "Validate npm release request"); + expect(validate.env?.NPM_WORKFLOW_REF).toBe( + "${{ inputs.release_candidate_branch != '' && format('refs/heads/{0}', inputs.release_candidate_branch) || github.ref }}", + ); + const checkout = step(parsed.jobs?.validate_publish_request, "Checkout"); + expect(checkout.with?.ref).toBe( + "${{ inputs.release_candidate_branch != '' && format('refs/tags/{0}', inputs.tag) || github.sha }}", + ); + + const trustedRef = step( + parsed.jobs?.validate_publish_request, + "Require trusted workflow ref for publish", + ); + expect(trustedRef.env?.RELEASE_CANDIDATE_BRANCH).toBe("${{ inputs.release_candidate_branch }}"); + expect(trustedRef.run).toContain('release_candidate_branch="${RELEASE_CANDIDATE_BRANCH:-}"'); + expect(trustedRef.run).toContain('"${WORKFLOW_REF}" != "refs/heads/main"'); + expect(trustedRef.run).toContain( + 'expected_candidate_branch="extended-stable/${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.33"', + ); + + 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"); + }); + it("accepts arbitrary SHA preflight targets and exercises every publishable plugin package", () => { const parsed = workflow(); const preflight = parsed.jobs?.preflight_openclaw_npm; @@ -192,26 +228,35 @@ describe("minimal npm extended-stable workflow", () => { expect(save.with?.key).toBe("${{ steps.dist_build_cache.outputs.cache-primary-key }}"); }); - it("authenticates exact extended-stable run and Full Validation identities", () => { + it("uses the trusted Full Validation evidence verifier", () => { const parsed = workflow(); const raw = readFileSync(workflowPath, "utf8"); expect(raw).toContain("--json workflowName,headBranch,headSha,event,conclusion,url"); - const fullValidationRun = step( + const verifier = step( parsed.jobs?.publish_openclaw_npm, - "Verify full release validation run metadata", + "Checkout trusted validation verifier", ); - expect(fullValidationRun.env?.FULL_RELEASE_VALIDATION_RUN_ATTEMPT).toBe( + expect(verifier.with?.ref).toBe("${{ github.workflow_sha }}"); + expect(verifier.with?.path).toBe("trusted-workflow"); + + const fullValidation = step( + parsed.jobs?.publish_openclaw_npm, + "Verify full release validation evidence", + ); + expect(fullValidation.env?.EXPECTED_WORKFLOW_BRANCH).toBe( + "${{ inputs.release_candidate_branch || github.ref_name }}", + ); + expect(fullValidation.env?.FULL_RELEASE_VALIDATION_RUN_ATTEMPT).toBe( "${{ inputs.full_release_validation_run_attempt }}", ); - expect(fullValidationRun.run).toContain( + expect(fullValidation.run).toContain( "actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}", ); - expect(fullValidationRun.run).toContain( - '"$run_attempt" != "$FULL_RELEASE_VALIDATION_RUN_ATTEMPT"', + expect(fullValidation.run).toContain( + "trusted-workflow/scripts/validate-full-release-validation-evidence.mjs", ); - expect(fullValidationRun.run).toContain('echo "attempt=$run_attempt" >> "$GITHUB_OUTPUT"'); - expect(raw.match(/openclaw-npm-extended-stable-release\.mjs verify-run/g)).toHaveLength(3); - expect(raw).toContain("openclaw-npm-extended-stable-release.mjs verify-manifest"); + expect(raw.match(/openclaw-npm-extended-stable-release\.mjs verify-run/g)).toHaveLength(2); + expect(raw).not.toContain("openclaw-npm-extended-stable-release.mjs verify-manifest"); }); it("requires and authenticates the plugin npm run before an extended-stable core publish", () => { @@ -250,6 +295,11 @@ describe("minimal npm extended-stable workflow", () => { expect(summary.if).toContain("always()"); expect(summary.run).toContain("openclaw-npm-extended-stable-release.mjs repair-command"); expect(summary.run).toContain('EXPECTED_VERSION="$RELEASE_TAG"'); + expect(summary.env?.EXTENDED_STABLE_BRANCH).toBe( + "${{ inputs.release_candidate_branch || github.ref_name }}", + ); + expect(summary.env?.RELEASE_SHA).toBeUndefined(); + expect(summary.run).toContain('release_sha="$(git rev-parse HEAD)"'); expect(publish?.environment).toBe("npm-release"); }); diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 7aae8f251c3c..e17c30932f61 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -4215,7 +4215,7 @@ describe("package artifact reuse", () => { "Download full release validation manifest", ); const npmPublishJob = workflowJob(OPENCLAW_NPM_RELEASE_WORKFLOW, "publish_openclaw_npm"); - const npmRun = workflowStep(npmPublishJob, "Verify full release validation run metadata"); + const npmRun = workflowStep(npmPublishJob, "Verify full release validation evidence"); const npmManifest = workflowStep(npmPublishJob, "Download full release validation manifest"); expect(releaseRun).toMatchObject({ @@ -4236,18 +4236,15 @@ describe("package artifact reuse", () => { "run-id": "${{ inputs.full_release_validation_run_id }}", }); - expect(npmRun).toMatchObject({ - id: "full_run", - env: { - FULL_RELEASE_VALIDATION_RUN_ID: "${{ inputs.full_release_validation_run_id }}", - FULL_RELEASE_VALIDATION_RUN_ATTEMPT: "${{ inputs.full_release_validation_run_attempt }}", - }, + expect(npmRun.env).toMatchObject({ + FULL_RELEASE_VALIDATION_RUN_ID: "${{ inputs.full_release_validation_run_id }}", + FULL_RELEASE_VALIDATION_RUN_ATTEMPT: "${{ inputs.full_release_validation_run_attempt }}", }); expect(npmRun.run).toContain( "actions/runs/${FULL_RELEASE_VALIDATION_RUN_ID}/attempts/${FULL_RELEASE_VALIDATION_RUN_ATTEMPT}", ); expect(npmManifest.with).toMatchObject({ - name: "full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ steps.full_run.outputs.attempt }}", + name: "full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ inputs.full_release_validation_run_attempt }}", "run-id": "${{ inputs.full_release_validation_run_id }}", }); }); @@ -4263,7 +4260,7 @@ describe("package artifact reuse", () => { const publishOrchestration = workflowStep(publishJob, "Dispatch publish workflows"); const npmPublishJob = workflowJob(OPENCLAW_NPM_RELEASE_WORKFLOW, "publish_openclaw_npm"); const npmCheckout = workflowStep(npmPublishJob, "Checkout"); - const npmFullRun = workflowStep(npmPublishJob, "Verify full release validation run metadata"); + const npmFullRun = workflowStep(npmPublishJob, "Verify full release validation evidence"); const npmDownload = workflowStep(npmPublishJob, "Download full release validation manifest"); const npmTarget = workflowStep(npmPublishJob, "Verify full release validation target"); @@ -4295,18 +4292,14 @@ describe("package artifact reuse", () => { "${{ needs.resolve_release_target.outputs.full_release_validation_run_attempt }}", ); expect(publishOrchestration.run).toContain('"${validation_target_sha}" != "${TARGET_SHA}"'); - expect(npmFullRun.id).toBe("full_run"); expect(npmFullRun.env?.FULL_RELEASE_VALIDATION_RUN_ATTEMPT).toBe( "${{ inputs.full_release_validation_run_attempt }}", ); expect(npmDownload.with?.name).toBe( - "full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ steps.full_run.outputs.attempt }}", + "full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ inputs.full_release_validation_run_attempt }}", ); - expect(npmTarget.env).toMatchObject({ - FULL_RELEASE_VALIDATION_RUN_ID: "${{ inputs.full_release_validation_run_id }}", - FULL_RELEASE_VALIDATION_RUN_ATTEMPT: "${{ steps.full_run.outputs.attempt }}", - }); - expect(npmTarget.run).toContain( + expect(npmTarget.env?.FULL_RELEASE_VALIDATION_RUN_ID).toBeUndefined(); + expect(npmTarget.run).not.toContain( "node scripts/openclaw-npm-extended-stable-release.mjs verify-manifest", ); expect(npmCheckout.with?.["fetch-depth"]).toBe(