Release pipeline
Releases are cut by pushing a version tag. Everything after that runs on GitHub Actions: no artifact is built or published from a workstation. This page describes the mechanics.
What a release contains
| Channel | What ships | Produced by |
|---|---|---|
| GitHub release | pgrdf-<ver>-pg18-glibc-amd64.tar.gz, pgrdf-<ver>-pg18-glibc-arm64.tar.gz, the PGXN source archive pgrdf-<ver>.zip, and an aggregate SHA256SUMS | release.yml |
| OCI bundle on GHCR | ghcr.io/styk-tv/pgrdf-bundle:<ver>-pg18-<arch> for each architecture, and the multi-architecture index :<ver> (also tagged :v<ver>), each with an SLSA Build Provenance v1 attestation | oci-publish.yml |
LATEST.md | the current attested version | update-latest-md.yml |
| PGXN | the same source archive; PGXN renders README.pgxn.md as the package page | uploaded by hand |
Each tarball's layout and MANIFEST.json are described on Packaging.
Before the tag
A release is prepared in a pull request, so the version bump and the changelog are reviewed before anything is built.
- One version everywhere. The tag (without its
v) must equal theversioninCargo.toml,default_versioninpgrdf.control, and both version fields inMETA.json. The compose mount line and the smoke test's expected output follow the same version, and CI checks them on every push. - Changelog. The
[Unreleased]section ofCHANGELOG.mdbecomes the new version's section in the same pull request. - Annotated tag. The tag annotation becomes the GitHub release body. If the annotation is empty, the
[Unreleased]section of the changelog is used instead, and the body must mention the tag. - One version, one commit, forever. A tag is never deleted, re-pushed or reused. If a release fails after its tag is pushed, the version is abandoned and the next number is used. Version gaps are normal.
- One at a time. The next tag is pushed only once the previous release appears in
LATEST.md.
release.yml: build
Runs on a push of a v* tag.
- Pre-build check. Compares the tag with
Cargo.toml,pgrdf.controlandMETA.json; any mismatch stops the build before anything is produced. - Build. One job per architecture, each on its native runner (amd64 on
ubuntu-22.04, arm64 onubuntu-24.04-arm), never cross-compiled. The.sois built inside the PostgreSQL 18 / Debian trixie builder image (compose/builder.Containerfile), so the container's userland, not the runner's, fixes the glibc floor, and both architectures floor the same way. Release builds use no build cache.build_id()is set to the tag. - Repack. The output is repacked into the tarball layout, with every upgrade script from
sql/,LICENSE, aMANIFEST.jsongenerated from the repacked files (including the glibc floor measured from the.so), and aSHA256SUMSover the contents. - Post-build check. Confirms the layout carries the tag's install script and control version, and that
MANIFEST.jsonreportsversion == extversion == runtime == tag. - Release job. Builds the PGXN archive (
make dist), writes an aggregateSHA256SUMSover the tarballs and the archive, renders the release body, and creates the GitHub release as a draft. Its last step triggersoci-publish.ymlwith arepository_dispatchevent carrying the tag.
oci-publish.yml: gate, publish, attest
- Gate (both architectures, each on its native runner). Boots a clean
postgres:18-trixie, installs the exact tarball bytes about to be published, runsCREATE EXTENSION pgrdf VERSION '<ver>', and checks thatextversionandpgrdf.version()equal the version, thatpgrdf.build_id()equals the tag, and thatMANIFEST.jsonagrees with the running library. If either architecture fails, nothing is pushed or attested. - Leaf (needs the gate). Pushes each tarball with
orasasghcr.io/styk-tv/pgrdf-bundle:<ver>-pg18-<arch>, annotated with the base image, commit and architecture, and attests it withactions/attest-build-provenance. - Index. Creates the multi-architecture index under
:<ver>and:v<ver>, attests it, then runs a consumer-style smoke test: pull the published artifact, install it into a cleanpostgres:18-trixie, and check the identity triple again. It then triggersupdate-latest-md.yml.
The workflows are chained with repository_dispatch because events caused by the default GITHUB_TOKEN (a release it created, a run it started) do not trigger release: published or workflow_run downstream.
update-latest-md.yml: advertise
- Runs
gh attestation verifyon the digests it is about to advertise. - Only if every digest verifies, renders
LATEST.mdand commits it tomainasdocs(auto): refresh LATEST.md to v<ver>. - Flips the GitHub release from draft to published and marks it Latest.
A publicly visible GitHub release therefore always has a verified, advertised OCI bundle behind it. LATEST.md is written only by this workflow and is never edited by hand. A release counts as done when LATEST.md advertises it.
Re-running
There is no manual publish path. If one job fails, use Re-run failed jobs on the existing run; never delete and re-push the tag. oci-publish.yml and update-latest-md.yml also accept a manual workflow_dispatch with the tag or version, to re-run a single hop of the chain.
Verifying a release
VER=0.6.34
ARCH=amd64
curl -fsSLO https://github.com/styk-tv/pgRDF/releases/download/v$VER/pgrdf-$VER-pg18-glibc-$ARCH.tar.gz
curl -fsSLO https://github.com/styk-tv/pgRDF/releases/download/v$VER/SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing # the tarball matches the release
tar -xzf pgrdf-$VER-pg18-glibc-$ARCH.tar.gz
(cd pgrdf-$VER-pg18-glibc-$ARCH && sha256sum -c SHA256SUMS) # each file inside it
gh attestation verify oci://ghcr.io/styk-tv/pgrdf-bundle:$VER --repo styk-tv/pgRDFAfter installing, the identity triple confirms which build is running:
SELECT pgrdf.version(), pgrdf.build_id(),
(SELECT extversion FROM pg_extension WHERE extname = 'pgrdf');
-- 0.6.34 | v0.6.34 | 0.6.34