From be36795f59de53257b8f36a65bdd09da2130ef4c Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:55:09 -0700 Subject: [PATCH] fix(release): restore the SignPath composite action when cutting from an older ref Co-authored-by: Orca --- .github/workflows/release-cut.yml | 21 +++++++++++ ...windows-signing-workflow-contract.test.mjs | 35 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/.github/workflows/release-cut.yml b/.github/workflows/release-cut.yml index 1840514e5fd..6dba5ac1ff6 100644 --- a/.github/workflows/release-cut.yml +++ b/.github/workflows/release-cut.yml @@ -1015,6 +1015,27 @@ jobs: with: ref: refs/tags/${{ needs.cut.outputs.tag }} + # Why: `uses: ./…` resolves from the checked-out tag, not from the workflow + # ref, so cutting from an older/off-main ref whose tree predates a composite + # action would fail the step with "Can't find 'action.yml'". Restore the + # actions directory from the commit this workflow file itself came from. + - name: Restore composite actions from the workflow ref + if: matrix.platform == 'win' + shell: bash + env: + WORKFLOW_SHA: ${{ github.workflow_sha }} + run: | + set -euo pipefail + action_path=".github/actions/install-signpath-module/action.yml" + if [ -f "$action_path" ]; then + echo "Composite actions already present at the cut ref." + exit 0 + fi + echo "Cut ref predates $action_path; restoring it from $WORKFLOW_SHA." + git fetch --no-tags --depth=1 origin "$WORKFLOW_SHA" + git checkout "$WORKFLOW_SHA" -- .github/actions + test -f "$action_path" + # pnpm must be on PATH before setup-node so setup-node can locate the store for caching. - name: Setup pnpm uses: pnpm/action-setup@v6 diff --git a/config/scripts/windows-signing-workflow-contract.test.mjs b/config/scripts/windows-signing-workflow-contract.test.mjs index fc2087379f8..b5a8f41d01f 100644 --- a/config/scripts/windows-signing-workflow-contract.test.mjs +++ b/config/scripts/windows-signing-workflow-contract.test.mjs @@ -104,6 +104,41 @@ describe('Windows signing workflow contract', () => { expect(installRun).toContain('throw "Unable to install the SignPath PowerShell module') }) + it('still installs SignPath when the cut ref predates the composite action', () => { + const parsedWorkflow = readWorkflow('.github/workflows/release-cut.yml') + const steps = parsedWorkflow.jobs.build.steps + const stepNames = steps.map((step) => step.name) + const checkoutIndex = stepNames.indexOf('Checkout') + const restoreIndex = stepNames.indexOf('Restore composite actions from the workflow ref') + const installIndex = stepNames.indexOf('Install SignPath PowerShell module') + + // Why: the build job checks out the cut tag, which for a hotfix cut from an + // older ref can predate `.github/actions/install-signpath-module`; without + // this restore the `uses: ./…` step dies on a missing action.yml. + expect(restoreIndex).toBeGreaterThan(checkoutIndex) + expect(restoreIndex).toBeLessThan(installIndex) + + const restoreStep = steps[restoreIndex] + const restoreRun = restoreStep.run + + expect(restoreStep.env.WORKFLOW_SHA).toBe('${{ github.workflow_sha }}') + expect(restoreRun).toContain('.github/actions/install-signpath-module/action.yml') + expect(restoreRun).toContain('git fetch --no-tags --depth=1 origin "$WORKFLOW_SHA"') + expect(restoreRun).toContain('git checkout "$WORKFLOW_SHA" -- .github/actions') + + // Why: restoring the action must not turn signing into a soft dependency — + // a missing module still has to fail the Windows job, and the CDN fallback + // still has to reject an unexpected payload. + expect(steps[installIndex]['continue-on-error']).toBeUndefined() + expect(restoreStep['continue-on-error']).toBeUndefined() + + const installRun = readWorkflow('.github/actions/install-signpath-module/action.yml').runs + .steps[0].run + + expect(installRun).toContain('$actualHash -ne $expectedHash.ToUpperInvariant()') + expect(installRun).toContain('throw "SHA-256 mismatch for $source') + }) + it('shares one SignPath module install path between release and rehearsal', () => { const rehearsalWorkflow = readWorkflow('.github/workflows/windows-signing-rehearsal.yml') const stepNames = rehearsalWorkflow.jobs.rehearse.steps.map((step) => step.name)