Skip to content

ci: 3-stage release workflow (prepare / review / publish)#237

Merged
hedgar2017 merged 12 commits intomainfrom
release-v2-workflow
Feb 26, 2026
Merged

ci: 3-stage release workflow (prepare / review / publish)#237
hedgar2017 merged 12 commits intomainfrom
release-v2-workflow

Conversation

@nebasuke
Copy link
Member

@nebasuke nebasuke commented Feb 22, 2026

Summary

  • Restructures release.yaml into a 3-stage pipeline: buildprepare & reviewpublish
  • Adds a review gate that validates the release bundle (binary count, SHA256 checksum verification, size audit) before publish
  • Adds per-job least-privilege permissions with a permissions: {} baseline
  • Adds SHA256 checksums for all release binaries
  • Bundles all artifacts into a single tarball between stages for atomic handoff
  • Pins publish-unit-test-result-action to v2.23.0 to fix transitive pinning (the previous commit hash resolved to an unpinned version)

Release workflow stages

  1. Stage 0 — Build: label-checkprepare-matrixbuild (5-platform matrix) + get-previous-release
  2. Stage 1 — Prepare: downloads all build artifacts, creates universal macOS binary, generates SHA256 checksums, bundles into tarball
  3. Stage 2 — Review: extracts bundle, lists contents, validates binary count, verifies checksums, displays sizes
  4. Stage 3 — Publish: changelog, attestation (tag-only), GitHub Release (non-PR), dry-run summary (PR-only), then deploy-docs + check-install-script (tag-only)

Safety on PR

Requires ci:release label. Runs the full pipeline as a dry-run:

  • Attestation: skipped (github.ref_type != 'tag')
  • softprops publish: skipped (github.event_name == 'pull_request')
  • deploy-docs / check-install-script: skipped (github.ref_type != 'tag')
  • Environment gate: empty on PR (no manual approval needed)

Test plan

@nebasuke nebasuke added the ci:release Trigger dry-run release workflow on PR label Feb 23, 2026
@nebasuke nebasuke force-pushed the release-v2-workflow branch 2 times, most recently from aac4106 to fb69bed Compare February 23, 2026 16:39
@nebasuke nebasuke changed the title ci: add 3-stage release workflow (prepare / review / publish) ci: 3-stage release workflow (prepare / review / publish) Feb 23, 2026
@nebasuke nebasuke requested a review from Copilot February 23, 2026 23:09
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restructures the release workflow into a 3-stage pipeline (build → prepare/review → publish) with enhanced security, validation, and dry-run capabilities for pull requests. The changes introduce per-job least-privilege permissions, SHA256 checksum generation and verification, artifact bundling between stages, and a review gate before publishing. It also enables PR dry-runs with the ci:release label to test the full release pipeline without creating actual releases.

Changes:

  • Restructured workflow into 3 stages: build (multi-platform matrix), prepare (bundle artifacts + checksums), review (validate bundle), and publish (create release)
  • Added permissions: {} baseline with per-job least-privilege permissions
  • Enabled pull request dry-runs with ci:release label requirement
  • Updated publish-unit-test-result-action to v2.23.0 (pinned commit)

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 11 comments.

File Description
.github/workflows/release.yaml Complete workflow restructure with 3-stage pipeline, label-gated PR support, SHA256 checksums, bundle validation, and environment-based gates
.github/actions/rust-unit-tests/action.yml Updated publish-unit-test-result-action pins to v2.23.0 for all platforms (Linux, macOS, Windows)
Comments suppressed due to low confidence (1)

.github/workflows/release.yaml:96

  • The conditional logic on lines 90-96 attempts to access github.event.inputs.* values even when the event is not workflow_dispatch. For pull_request events, these inputs will be undefined/null, which could cause the bash test expressions [ ${{ github.event.inputs.release_windows_amd64 }} != true ] to fail with syntax errors due to empty expansion.

Consider restructuring to only access inputs when the event is workflow_dispatch, or use github.event.inputs.release_windows_amd64 || true to provide a default value.

          if [ '${{ github.event_name }}' = 'workflow_dispatch' ] && [ ${GITHUB_REF_TYPE} != tag ]; then
            [ ${{ github.event.inputs.release_windows_amd64 }} != true ] && WINDOWS=
            [ ${{ github.event.inputs.release_macos_amd64 }} != true ] && MACOS_AMD64=
            [ ${{ github.event.inputs.release_macos_arm64 }} != true ] && MACOS_ARM64=
            [ ${{ github.event.inputs.release_linux_amd64_gnu }} != true ] && LINUX_AMD64_GNU=
            [ ${{ github.event.inputs.release_linux_arm64_gnu }} != true ] && LINUX_ARM64_GNU=
          fi

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

nebasuke added a commit that referenced this pull request Feb 24, 2026
- Remove dead code: unused `id: bundle` and `tarball_name` step output
- Add explicit `if` on publish job to tolerate get-previous-release failure
- Fix double ${{ }} evaluation in review job condition
- Increase artifact retention from 2 to 5 days for manual approval window
- Add `opened` to PR event types for pre-labeled PRs
- Enforce exact binary count (4) for tag releases to catch partial builds
@nebasuke nebasuke force-pushed the release-v2-workflow branch from cc1ee3d to 60c83f6 Compare February 24, 2026 09:24
@nebasuke nebasuke requested a review from Copilot February 24, 2026 09:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@nebasuke nebasuke marked this pull request as ready for review February 24, 2026 12:35
@nebasuke nebasuke requested a review from a team February 24, 2026 12:35
@nebasuke
Copy link
Member Author

@hedgar2017 hedgar2017 enabled auto-merge (squash) February 26, 2026 17:16
Split the monolithic prepare-release job into three environment-gated
stages so elevated permissions are only granted to the final publish
job, which requires manual approval. This v2 workflow runs in dry-run
mode for validation before replacing the original release.yaml.
…release-v2

- Guard platform-toggle block with event_name check so inputs are only
  read on workflow_dispatch, preventing an empty build matrix on
  pull_request events.
- Replace find|while subshell loop with sha256sum --check so a failed
  checksum actually fails the step.
PR ref names (e.g. `237/merge`) contain a slash which breaks the
binary mv in build-rust. Use `pr<number>` instead for pull_request events.
Only use github.ref_name when ref_type is 'tag'; otherwise fall back
to inputs.prerelease_suffix or 'notag'. This avoids slashes in paths
from any non-tag ref (PRs, branches) without event-specific guards.
The review job was skipped on PR builds because get-previous-release
(a transitive dependency via prepare) was skipped. GitHub Actions
propagates skipped status through the needs chain unless overridden.
Add !cancelled() + result check so review runs whenever prepare succeeds.
Replace the temporary unconditional pull_request trigger with a proper
label-gated mechanism matching the pattern used by integration-tests
and coverage workflows:

- Add label-check job gating on `ci:release` label (blocks forks)
- Run get-previous-release and changelog builder for PRs so dry-run
  output is realistic
- Handle pull_request event in release name identification to avoid
  stripping `refs/pull/*/merge` as a tag
- Skip the `release` environment approval gate for PR dry-runs
- Remove obsolete TODO comment about temporary trigger
The previous pin (v2, SHA 27d65e1) internally used
actions/cache/restore@v4 and actions/cache/save@v4 (unpinned tags),
which violates the org's SHA-pinning policy. v2.23.0 pins its cache
dependencies to full SHAs.
Delete release-v2.yaml — its 3-stage architecture (prepare/review/publish),
per-job permissions, SHA256 checksums, and bundle/review gate are now in
release.yaml alongside the production publish steps (attestation, softprops,
deploy-docs, check-install-script).

Remove the `always()` + `skipped` conditionals by making get-previous-release
always run (~10s read-only gh call) and dropping it from prepare's needs
(only publish consumes its output).
- Remove unused `tarball_name` output from prepare job
- Make `get-previous-release` resilient to fresh repos with no releases
- Add `permissions: contents: read` to `check-install-script` job
- Add checksum file count validation before `sha256sum --check`
- Clarify macOS universal binary condition with explicit trigger paths
- Add `|| github.sha` fallback to `target_commitish` for tag releases
- Trim `deploy-docs` permissions to only `contents: write`
- Remove dead code: unused `id: bundle` and `tarball_name` step output
- Add explicit `if` on publish job to tolerate get-previous-release failure
- Fix double ${{ }} evaluation in review job condition
- Increase artifact retention from 2 to 5 days for manual approval window
- Add `opened` to PR event types for pre-labeled PRs
- Enforce exact binary count (4) for tag releases to catch partial builds
@hedgar2017 hedgar2017 merged commit db941aa into main Feb 26, 2026
33 checks passed
@hedgar2017 hedgar2017 deleted the release-v2-workflow branch February 26, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:release Trigger dry-run release workflow on PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants