One repository, four workflows, every step an action from this repository. The per-action READMEs carry the inputs; this page shows how the pieces line up.
Native runners: install-toolchain, rust-cache, lint-and-test, changelog in check mode, rust-cache-save with save: "false" so pull requests only read what the default branch maintains.
Sealed runners: the reusable rust.yml workflow, which builds the toolchain image, warms the cache with cargo-fetch and runs lint-and-test-docker under --network=none, plus msrv for the support floor.
on: [pull_request]
jobs:
qa:
uses: gronke/ci/.github/workflows/rust.yml@v3
changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: gronke/ci/.github/actions/changelog@v3
with:
mode: checkThe same QA job on pushes to the default branch, with rust-cache-save saving, so every pull request restores a current target entry.
With a self-hosted tier, sccache and crates-mirror pick up the host's backend from the job environment and need no workflow change (self-hosted.md).
cut-release turns [Unreleased] into the released section, pushes the release branch, opens the merge-back pull request and dispatches the pipeline.
The pipeline gates with check-release-readiness, builds, then draft-release creates the draft pre-release and pushes an unsigned candidate marker, and release-guidance writes the sign-and-push commands into the step summary.
A human signs the marker's commit as the final tag and pushes it by name; require-signed-tag gates the tag run, publish-draft-release seals the tag against the newest marker, publishes the draft and advances the moving major.
release-flow.md is the runbook, and this repository's own release.yml and cut.yml are the reference pipeline.
require-signed-tag stops the tag run unless the tag is an annotated object whose signature GitHub verifies.
cargo-publish runs behind it and uploads only versions matching its tag-pattern, with the Trusted Publishing token from rust-lang/crates-io-auth-action passed as registry-token; with publish left at its default it rehearses with cargo publish --dry-run.
permissions: { id-token: write, contents: write }
steps:
- uses: actions/checkout@v7
- uses: gronke/ci/.github/actions/check-release-readiness@v3
- uses: gronke/ci/.github/actions/require-signed-tag@v3
- id: auth
uses: rust-lang/crates-io-auth-action@v1
- uses: gronke/ci/.github/actions/rust/cargo-publish@v3
with:
publish: "true"
registry-token: ${{ steps.auth.outputs.token }}- Signed tags: import
.github/rulesets/tags-maintainer-only.jsonso only admins createv*tags, and.github/rulesets/release-branches.jsonso only admins and the cut's token touchrelease/v*; a ruleset cannot verify a tag's signature,require-signed-tagdoes. - Sealed verify-build:
publish-dry-runruns the publish checks without a build, thencargo packageunder--network=none, so dependency code executes without network at release time. - No stored registry secret: Trusted Publishing mints a 30-minute token from the job's OIDC identity.
- Least privilege: each job declares only the permissions its steps need; the sealed jobs need
contents: read. - Pinned actions: pin third-party actions by commit SHA and let Dependabot propose bumps.
actions/attest-build-provenancefor signed provenance on release assets.cargo-denyorcargo-audit, installed withcargo-installand run sealed withcargo-use.- Dependabot for the action pins in
.github/workflows.