Skip to content

changelog: the per-crate publish-dry-run entry #21

changelog: the per-crate publish-dry-run entry

changelog: the per-crate publish-dry-run entry #21

Workflow file for this run

name: Release
# The reference pipeline from docs/release-flow.md, dogfooded with local
# action paths — in publish-go-live mode: no per-release signing. A candidate
# run builds the draft and its rcN marker; the release manager merges the
# merge-back PR and then publishes the draft, the one human gate. The draft's
# target is pinned to the merged commit (the guidance summary prints the
# command), so publishing creates vX.Y.Z on exactly the content the marker
# sealed, and the tag push runs the final path: version coherence, the tree
# seal, the (idempotent) flip, and the moving major. An optional signed
# companion (vX.Y.Z-sig, the only tags the ruleset requires signatures on)
# attests the release commit out of band.
on:
push:
branches: ["release/v**"]
# Exclude the signed provenance companion (vX.Y.Z-sig): it attests the
# release commit out of band and carries no changelog section or marker, so
# it must not drive the release pipeline — it routes to the attest workflow.
tags: ["v*", "!v*-sig"]
workflow_dispatch: # cut-release dispatches the first run explicitly
permissions:
contents: write
jobs:
gate:
name: release readiness gate
runs-on: ubuntu-latest
# The moving major (v1) names no version to check. The pipeline pushes it
# with the workflow token, which fires no events, but a maintainer moving
# it by hand does — and without this the gate would derive "1" from the ref
# and refuse the repository's own major tag.
if: ${{ github.ref_type != 'tag' || contains(github.ref_name, '.') }}
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"
# 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 }}
# publish-draft: the admin publishing the draft is the go-live. The step
# refuses at candidate time if a signature rule still covers the version
# itself, so the publish-created tag is never one the repository rejects.
- uses: ./.github/actions/release-guidance
with:
version: ${{ env.VERSION }}
marker-tag: ${{ steps.draft.outputs.marker }}
commit: ${{ github.sha }}
draft-url: ${{ steps.draft.outputs.url }}
go-live: publish-draft
publish:
name: publish the release (final path)
needs: gate
# The vX.Y.Z tag normally arrives from the admin publishing the draft
# (markers are workflow-token pushes and trigger nothing); a hand-pushed
# tag runs the same seal.
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"