Releasing
Updated
What the release script is for
Section titled “What the release script is for”tools/release.sh <tag> --stage # build the candidate and bake its imagetools/release.sh <tag> --promote [--no-receipt] # copy the accepted image to stableThe tag is v plus the version in Cargo.toml.
Nothing is built on the workstation. --stage pushes HEAD to the build machine, which
builds the artifacts inside containers, bakes the bootc host image from them and pushes it
to the release-candidate channel; the script then reads the digest back from the registry.
--promote copies that same image, by digest, into the stable channel. The script exists so
that everything which has to be true before that copy can be repeated identically instead
of reconstructed from memory.
--stage refuses to run unless the working tree is clean, Cargo.toml already carries the
version the tag names, and CHANGELOG.md carries that tag’s <!-- release: <tag> -->
boundary. It then checks that the build machine’s tree is at the same commit, so the
artifacts can only come from the commit being named.
| Flag | Effect |
|---|---|
--stage |
Push the tree, then build and package it on the build machine, bake the host image and push it to the release-candidate channel. This is the build that acceptance is then run against, and the registry digest it reports is the one the receipt must name |
--promote |
Require an acceptance receipt for the tag whose image.digest equals the digest the release-candidate channel holds, then copy that image, by digest, to the stable channel. Nothing is built |
--no-receipt |
Skip the receipt check. The copy still happens, by digest, from the release-candidate channel; only the owner decides when skipping acceptance is right |
What has to be true first
Section titled “What has to be true first”- Preflight passes.
tools/preflight.shis the local gate for source, licences, advisories, contracts and the whole Rust test suite. - The candidate is staged. The build machine produces the tarball, the packaged ELF, the independent initramfs and the SBOM, bakes the host image from them, and pushes it to the release-candidate channel. Those four artifacts are the set the receipt binds by SHA-256, and the registry digest read back afterwards is what everything downstream is anchored to.
- The SBOM describes the shipped bytes. It is generated from the final binary, which carries an auditable dependency record compiled in, rather than from the source tree that was intended to produce it.
- The image is baked and accepted. See the bootc host image and the next section. Acceptance runs against the image, so the image has to exist before there is anything to accept.
- The receipt matches the tree.
docs/releases/acceptance/<tag>.jsonmust record a pass for every required result, and its source digest must still match the tree being released. Itsimage.digestmust also be the digest the release-candidate channel holds for that tag, which is what--promotechecks before it copies anything.
Acceptance on real hardware
Section titled “Acceptance on real hardware”docs/RELEASE-ACCEPTANCE.md is the procedure. A candidate is baked to a release-candidate
repository, booted on a real Fedora host, and driven through a matrix of destructive and
recovery scenarios: lifecycle across an agent restart, snapshot and restore, backup and
restore, network realization, AppVM admission and update, escape and isolation checks.
Each result is captured as evidence, and the evidence is checked rather than trusted.
tools/check-release-artifact-set.sh re-hashes the tarball, the ELF inside it, the
initramfs and the SBOM against the receipt, and
tools/check-release-evidence-bundle.sh opens the evidence archive and requires every file
the receipt references to be present and to hash as recorded.
Only then is the same image promoted, by digest, to the stable repository. Promotion is a copy, not a second build. Re-baking from the same source tree is not allowed, and since bakes are not byte-reproducible, a re-bake would be visibly a different image.
There is no signature, deliberately
Section titled “There is no signature, deliberately”Earlier versions of this project specified keyless signing bound to a hosted build service’s identity. That was withdrawn: a release is not built on that service, so the identity the signature would attest belongs to something other than the build, and a signature from a different identity is worse than none. A verifier that checks it per the documentation fails, and that failure looks exactly like tampering.
What stands in its place is a chain of five links:
| Link | What it proves |
|---|---|
| Tag | Which source tree |
| Receipt | That tree passed acceptance, and on which bytes |
SHA256SUMS |
That the release directory did not corrupt before it was baked |
host-image-provenance.json |
Which base digest, kernel and component versions went into the image |
| Registry digest | The exact image a host booted |
The last one is the anchor. A tag can be re-pointed; a digest cannot, and it is what
bootc status reports on the machine.
Version numbers and tags
Section titled “Version numbers and tags”A release tag is SemVer, and the version is read out of the artifact filename by the bake.
Two rules are enforced rather than remembered: a version containing + cannot become an
image tag at all, and a prerelease such as 1.0.1-rc.2 must survive tag derivation
untouched, because collapsing every release candidate onto one tag would make the
candidate channel useless.