From e9691fba88550f1eb39e12df7d62e7075f5550ec Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:45:04 -0700 Subject: [PATCH] fix(release): use validated site release identity --- .github/workflows/deploy-site.yml | 1 - scripts/check-release-consistency.mjs | 16 ++++++++++++++++ scripts/check-release-consistency.test.mjs | 13 +++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index db818d6b..83695ad4 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -207,7 +207,6 @@ jobs: echo "release_commit must be an immutable commit SHA" >&2 exit 1 fi - release_tag="$REQUESTED_RELEASE_TAG" fi release_flags=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${release_tag}" --jq '[.draft, .prerelease] | @tsv') if [[ "$release_flags" != $'false\tfalse' ]]; then diff --git a/scripts/check-release-consistency.mjs b/scripts/check-release-consistency.mjs index 8669d0da..8ac546e3 100644 --- a/scripts/check-release-consistency.mjs +++ b/scripts/check-release-consistency.mjs @@ -1434,6 +1434,22 @@ export function collectReleaseConsistencyErrors(files, releaseTag) { ".github/workflows/deploy-site.yml", errors, ); + if (deploySiteWorkflow.includes('release_tag="$RELEASE_TAG"')) { + const resolveStart = deploySiteWorkflow.indexOf(" - name: Resolve immutable deployment source"); + const checkoutStart = deploySiteWorkflow.indexOf( + " - name: Check out immutable deployment source", + resolveStart, + ); + const resolveStep = + resolveStart >= 0 && checkoutStart > resolveStart + ? deploySiteWorkflow.slice(resolveStart, checkoutStart) + : ""; + if (resolveStep.includes('release_tag="$REQUESTED_RELEASE_TAG"')) { + errors.push( + ".github/workflows/deploy-site.yml must use the validated release identity during immutable source resolution", + ); + } + } if ((files.get(".github/workflows/deploy-site.yml") ?? "").includes('source_sha="$MAIN_SHA"')) { errors.push( ".github/workflows/deploy-site.yml must deploy the published release tag, not a same-version main SHA", diff --git a/scripts/check-release-consistency.test.mjs b/scripts/check-release-consistency.test.mjs index 1a7d8c1d..543bc7d4 100644 --- a/scripts/check-release-consistency.test.mjs +++ b/scripts/check-release-consistency.test.mjs @@ -378,6 +378,14 @@ test("rejects updater channel, stable manifest, and publication-contract drift", (text) => text.replace("github.ref == 'refs/heads/main'", "github.ref == 'refs/tags/untrusted'"), ], + [ + ".github/workflows/deploy-site.yml", + (text) => + text.replace( + 'release_tag="$RELEASE_TAG"', + 'release_tag="$RELEASE_TAG"\n release_tag="$REQUESTED_RELEASE_TAG"', + ), + ], ]; for (const [path, replace] of cases) { assert.ok( @@ -837,6 +845,11 @@ test("deploys release site source only after artifact publication", () => { assert.ok(deployWorkflow.includes("ref: ${{ steps.immutable_source.outputs.source_sha }}")); assert.ok(deployWorkflow.includes("path: .release-source")); assert.ok(deployWorkflow.includes("working-directory: .release-source")); + const immutableResolution = deployWorkflow.slice( + deployWorkflow.indexOf(" - name: Resolve immutable deployment source"), + deployWorkflow.indexOf(" - name: Check out immutable deployment source"), + ); + assert.ok(!immutableResolution.includes('release_tag="$REQUESTED_RELEASE_TAG"')); assert.ok(!deployWorkflow.includes('source_sha="$MAIN_SHA"')); assert.ok(!deployWorkflow.includes("cache: pnpm")); assert.ok(