The mechanics of cutting a release. This is the how; the clinical release gate is TF-09 §6, and the change-control rules (patch / minor / major, and what evidence each level needs) are TF-18 §4. Follow those first — this page assumes the decision to release has already been made and recorded.
Beta releases. TF-09 §6 is headed per clinical release. A pre-release on synthetic data is not one, so its clinical items do not gate a beta. Run this procedure anyway: the beta is the rehearsal, and the release record it produces is the first evidence that the process works.
- The change level (patch / minor / major) is decided and its TF-18 evidence exists.
-
mainis green and you are releasing the commit you think you are. - You can reach the GCP project, or you accept that
deploywill skip (see Known limitations).
Decide the version. The convention (TF-18 §2) is that VERSION is the single source of
truth and the tag mirrors it as v<VERSION>. Pre-releases take a SemVer suffix:
0.1.0-beta.1, 0.1.0-rc.2.
# 1a. Bump VERSION
printf '0.1.0-beta.1\n' > VERSION
# 1b. Move the CHANGELOG's [Unreleased] heading to the new version, dated.
# From the first release onward each entry also carries its TF-18 level —
# see the format note at the top of CHANGELOG.md.
# 1c. Check it before you push anything
./scripts/check-release-version.sh v0.1.0-beta.1That last command is the same guard CI runs. It fails if the tag and VERSION disagree, or
if VERSION is not valid SemVer — run it locally so you find out now, not after pushing a
tag you then have to delete.
Open a PR with the bump, let the ten required checks pass, and merge it.
Publishing the GitHub Release is the only thing that triggers a release build. Pushing a tag on its own does nothing (deliberately — see Known limitations).
git checkout main && git pull
git tag -a v0.1.0-beta.1 -m "CoGA 0.1.0-beta.1"
git push origin v0.1.0-beta.1
gh release create v0.1.0-beta.1 \
--title "CoGA 0.1.0-beta.1" \
--notes-file <(sed -n '/## \[0.1.0-beta.1\]/,/^## /p' CHANGELOG.md) \
--prerelease # omit for a clinical releasebuild.yml runs once, in this order:
| Job | What it does | If it fails |
|---|---|---|
prepare |
Asserts the tag matches VERSION, then computes build metadata |
Nothing was built. Fix VERSION, delete the tag and release, start again at step 1 |
build-backend / build-frontend |
Cloud Build → Artifact Registry, stamped with APP_VERSION + GIT_SHA |
Images may be partially pushed; safe to re-run |
deploy |
terraform plan then apply against the gcp-deploy environment |
Infrastructure may be partly applied — read the plan output before re-running |
Do this before the artifacts expire — the SBOM is deleted after 90 days.
RUN=$(gh run list --workflow build.yml --limit 1 --json databaseId --jq '.[0].databaseId')
# SBOMs (CycloneDX 1.6, backend + frontend)
gh run download "$RUN" --name sbom-cyclonedx --dir release-evidence/
shasum -a 256 release-evidence/*.cdx.json
# Image digests — NOT captured automatically; resolve the tag to its digest
REG=europe-west1-docker.pkg.dev/<registry-project>/gen-ghreg-shared-gbl
for c in backend frontend; do
gcloud artifacts docker images describe "$REG/coga-$c:v0.1.0-beta.1" \
--format='value(image_summary.digest)'
doneThen verify the deployed build really is the one you released:
curl -s https://<host>/api/version
# -> {"version":"0.1.0-beta.1","git_sha":"<12-char sha>"}
# `version` must equal VERSION, `git_sha` the tagged commit. A response of
# {"version":"0.0.0+unknown","git_sha":"unknown"} means the image was built without
# APP_VERSION/GIT_SHA build args — the deploy did not ship a stamped build.Copy docs/release-record-template.md, fill it, and file
it per the CMGG QMS (TF-18 §7).
It is the artefact that ties a signed clinical report back to a specific build, so the image
digests and SBOM hashes matter more than they look.
There is no automated rollback. To go back to a known-good release, re-point the deployment at its images and re-apply:
gcloud artifacts docker images describe "$REG/coga-backend:<previous-tag>" \
--format='value(image_summary.digest)' # confirm the target exists firstThen re-run deploy from the previous release's tag, or apply Terraform locally with
backend_image / frontend_image set to the previous tag. Record the rollback as a change
under TF-18 — a rollback is a release.
Rolling back the application does not roll back a database migration. The schema loader applies every file on boot; check whether the previous image can run against the current schema before rolling back.
Stated here so nobody discovers them mid-release:
- Deploy pins a tag, not a digest. Terraform receives
…coga-backend:<tag>. For a release that is effectively immutable (you do not re-tag), but onmainthe tag is literallymain, so Cloud Run may not roll a new revision at all. Verify with/api/versionrather than assuming a green deploy shipped your code. - No digest is captured automatically — step 4 resolves them by hand.
- The SBOM artifact expires after 90 days and CI does not run on
release: publishedfor the SBOM job, so archiving it into the technical file is a manual step. Miss it and the dependency evidence for that build is gone. deployskips entirely when GCP is not configured (noGCP_WIF_PROVIDERsecret). The run goes green having deployed nothing. Check the job actually ran.- The
gcp-deployenvironment has no required reviewer until one is configured, soterraform apply -auto-approveruns unattended. - A tag is not verified to be on
main. Nothing stops tagging an arbitrary commit.