Releasing
How a release reaches its users. The owner decides that a release happens; the steps below are what follows. Nothing here publishes by itself: the release workflow builds and attaches, and never creates a release.
The procedure
- Update dependencies, once per release cycle. At the start of the cycle
(RFC 020, D-14 as amended by D-23), and not on a timer:
cargo update; the pinned actions (the latest release of each, by commit SHA with its version as a comment); the base image digest in theDockerfile; the fuzz workspace’s own lock file,fuzz/Cargo.lock(cargo update --manifest-path fuzz/Cargo.toml, then--precisefor any crate whose version differs fromCargo.lock:tools/check-fuzz-lockfails in CI when a crate both files hold is at another version, so that the fuzz targets test the parsers that ship); and the release compiler, thetoolchain:line of.github/actions/build-release/action.yml, the one place that names it (set it to the current stable:rustup update stable,rustc --version). One commit, through CI. The compiler is a deliberate change of the cycle, made once, not a surprise of the day and not a change after the checks. CI keepsstableon purpose, to see new lints early; its 1.88 job covers the floor. Between cycles nothing updates by itself. - The release commit. The version bump and the changelog, through CI;
the gates are green on it. When a tool definition changed since the last
release, the section has a
### Tool definitionsheading, first under the version, with one line per change: the tool, the field (name, description, input schema, output schema, annotations, or the server instructions) and what changed (RFC 021, D3). The release checks compare the pinned definitions with the last release tag and fail when they differ and the heading is missing. - Run the release checks on the release commit. Dispatch
release-checks.ymlfrommainat that commit (Actions, Release checks, Run workflow). Every job must be green or explained as When checks run says. - Go and authorization. The reviewer’s readiness report quotes the id of
the release-checks run and its
head_shaand records the Go; the owner authorizes the release. - Tag the commit the run checked (its
head_sha); a later commit runs the checks again. Tag itX.Y.Z(novprefix) and push the tag. The tag must equal the workspaceversionofCargo.toml; the attach job fails otherwise. - Publish. The owner publishes the release on GitHub from the tag, or publishes first: creating the release creates the tag, which starts the workflow, whose attach job then finds the release. When publishing first, choose the release commit as the target of the new tag, not the branch head: the tag goes on the commit chosen, and the attach job’s version check catches a wrong version but not a wrong commit with the same version.
- Read the run. The
releaseworkflow runs four jobs in order:build(the four binaries),image(the two container images, made from the Linux binaries),checksums(SHA256SUMSover the four archives and the two images, and their provenance attestations) andattach. If the release did not exist when its attach job ran, the job wrote a notice in the run summary and attached nothing. In that case run the workflow again from the tag (Actions, release, Run workflow, “Use workflow from: Tag X.Y.Z”, orgh workflow run release.yml --ref X.Y.Z): the attach job uploads to the release that now exists. - Check the release page. It lists seven files: four archives (Linux
x86_64 and aarch64, both static musl; macOS arm64; Windows x86_64), two
images (
-image-amd64.tar.gz,-image-arm64.tar.gz) andSHA256SUMS. The release notes name the compiler: paste the lineBuilt with rustc …that the attach job’s run summary printed. Verify one file as a user does (gh attestation verify <file> --repo nabbisen/rypsok). - Nothing is overwritten. The attach job refuses to replace an asset that is already on the release. To replace one, delete it on the release page by hand and say so in the release notes, then run the workflow again from the tag.
The book
The book is published from the release tag (RFC 020, D-17), so that a reader
of a release reads what that release does. The docs workflow runs on a tag
X.Y.Z and on a manual run, and never on a push to main; CI’s rules job
builds the book on every push, so a broken book is red at once. Before it
builds, the workflow puts on the first page the line “This book describes
rypsok X.Y.Z.” (or, for a manual run from a branch, the commit and the words
“not a release”) and fails if the markers around that line are missing.
Pages publishes only from the branches and tags that the github-pages
environment allows. The owner sets one rule once: Settings, Environments,
github-pages, Deployment branches and tags, add the tag rule [0-9]*. Until
it is set, the publish job of a tag fails red, which is visible, and nothing
is published. A failed publish is never ignored.
A fix to the documents alone is a patch release (RFC 020, D-17): the book is
published from a tag, so a corrected page reaches readers with the next tag. A
manual run from main (Actions, Book, Run workflow, “Use workflow from:
Branch main”) publishes the development version, and its first page says so;
it needs the same environment rule for main.
If the attach job fails
A failed attach job leaves the release as the owner published it: it
creates nothing and replaces nothing. The run’s workflow artifacts
(archive-*, image-* and checksums) are the files the workflow built
from the tag. To attach them by hand:
-
Download the artifacts
archive-*,image-*andchecksumsof the tag’s run, and not the smallsha-*artifacts.ghputs each artifact in a directory of its own (the run page’s “Artifacts” gives one zip each), so move the files into one directory afterwards, in an empty directory:gh run download <run id> --repo nabbisen/rypsok --pattern 'archive-*' --pattern 'image-*' --pattern checksums --dir artifacts mv artifacts/*/* . rmdir artifacts/* artifactsThe four archives, the two images and
SHA256SUMSnow lie side by side. -
Check them:
sha256sum --check SHA256SUMSmust printOKfor all six files and exit 0. It fails when a file is missing; do not add--ignore-missing, which passes with a file absent. All six files must be there before the upload, and nothing that does not match is uploaded. -
Upload the six files and
SHA256SUMSto the release (the release page, “Edit”, orgh release upload X.Y.Z <files>). -
Say in the release notes that the files were attached by hand from the artifacts of the run, naming the run. The attestations belong to the files of that run, so they still verify.
A run again from the tag does not help while the defect is in the workflow: it uses the workflow as the tag holds it. The defect is fixed in the next release. Never rebuild the binaries locally for a release: the checksums and the notes would then describe files the workflow did not build.
The reproducibility check
Builds of one commit are meant to be reproducible: the same compiler, the
locked dependencies and, on Windows, the linker flag /Brepro (without it the
linker writes a timestamp into the executable). The release checks prove it on
every release: the jobs reproducible and reproducible-compare build every
release target twice, through the same local action as the release itself,
and compare the SHA-256 of the unpacked rypsok binaries (the archives differ
in their own timestamps, so the binaries are compared, not the archives). A
difference fails the run.
A run of release.yml from a branch (Actions, release, Run workflow, “Use
workflow from: Branch main”) still builds the archives and the checksums and
uploads them as workflow artifacts. It builds the two images and
SHA256SUMS too, so the dry run proves that the image builds, but it attests
nothing and attaches nothing: those two steps run on a tag ref only. What no
check shows is the attestation and the attach, which first run for a real
tag.
What the checksums prove
SHA256SUMS shows that a download is intact. It does not show where it
came from: anyone who can replace the archive can replace the file beside
it.
The provenance attestation (RFC 020, D-16) shows the origin. On a tag, the
checksums job asks GitHub to sign, for each of the six files, a statement
that this repository’s release workflow built it from the tagged commit
(actions/attest-build-provenance). gh attestation verify <file> --repo nabbisen/rypsok checks the signature. That job is the only one that may sign
(id-token: write, attestations: write) and it runs no build code: it has
no checkout and no cargo, because the build jobs run the build.rs of
third-party crates and must never hold a token that can sign. The attestation
shows the file came out of the workflow; it does not show that the source is
free of defects, and the workflow is trusted as the owner’s.
The checksums job verifies what it signs: it checks the four archives
against the SHA-256 each build job wrote, before it writes SHA256SUMS and
attests the files.
The images are built from the release binaries (RFC 020, D-12): the image
job checks the Linux archives it downloads against the SHA-256 that each
build job computed, unpacks them, and copies each binary onto the pinned
base image. Nothing is compiled in the image, so the binary in it is the one
the release checks examined. There is no registry (a later choice of the
owner).