Skip to content

Release

Release #5

Workflow file for this run

name: Release
# The reference pipeline from docs/release-flow.md, dogfooded with local
# action paths: a tag releases itself with the actions it carries.
on:
push:
branches: ["release/v**"]
tags: ["v*"]
workflow_dispatch: # cut-release dispatches the first run explicitly
permissions:
contents: write
jobs:
gate:
name: release readiness gate
runs-on: ubuntu-latest
outputs:
version: ${{ steps.expect.outputs.version }}
steps:
- uses: actions/checkout@v7
- name: Derive the expected version from the ref
id: expect
run: |
set -euo pipefail
# Only human-pushed final tags reach the pipeline; workflow-token
# marker pushes trigger nothing.
case "${GITHUB_REF_TYPE}:${GITHUB_REF_NAME}" in
branch:release/v*) version="${GITHUB_REF_NAME#release/v}" ;;
tag:v*) version="${GITHUB_REF_NAME#v}" ;;
*) version="" ;;
esac
echo "version=${version}" >> "$GITHUB_OUTPUT"
- name: Require a verified signed tag (final path only)
if: github.ref_type == 'tag'
uses: ./.github/actions/require-signed-tag
# No crate here: the declared version is the changelog's newest released
# section, checked against the ref-derived one.
- uses: ./.github/actions/check-release-readiness
with:
expected-version: ${{ steps.expect.outputs.version }}
# The seal is four API calls and no build, so it belongs here: a tag that
# does not carry the reviewed content fails in seconds, not after the
# artifact jobs.
- name: The tag must seal the newest candidate marker (early gate)
if: github.ref_type == 'tag'
uses: ./.github/actions/publish-draft-release
with:
version: ${{ steps.expect.outputs.version }}
seal-only: "true"
draft:
name: build the draft pre-release (candidate path)
needs: gate
# Only release branches build candidates: a workflow_dispatch from any other
# branch derives no version and must not create a draft.
if: github.ref_type == 'branch' && startsWith(github.ref_name, 'release/v')
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.gate.outputs.version }}
steps:
- uses: actions/checkout@v7
# Notes render, draft create/refresh, marker push — one step.
- uses: ./.github/actions/draft-release
id: draft
with:
version: ${{ env.VERSION }}
- uses: ./.github/actions/release-guidance
with:
version: ${{ env.VERSION }}
marker-tag: ${{ steps.draft.outputs.marker }}
commit: ${{ github.sha }}
draft-url: ${{ steps.draft.outputs.url }}
# The tags-signed ruleset resolves this to manual — the release manager
# signs — and every candidate run dogfoods the detection.
- uses: ./.github/actions/promote-release
with:
version: ${{ env.VERSION }}
marker-tag: ${{ steps.draft.outputs.marker }}
moving-major: "true"
publish:
name: publish the release (final path)
needs: gate
# Any tag reaching the pipeline is a human-pushed final tag: marker tags are
# pushed with the workflow token and trigger nothing, and require-signed-tag
# rejects unsigned tags.
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# Tree seal against the newest marker, the draft flip, and the moving
# major — one step.
- uses: ./.github/actions/publish-draft-release
with:
version: ${{ needs.gate.outputs.version }}
moving-major: "true"