This page describes how a maintainer cuts a release. Releases are
cut from main only.
- Semantic Versioning 2.0.0.
- Pre-1.0 (current phase): minor bumps may include breaking changes. Patch bumps remain backward compatible.
- Tag format:
vX.Y.Z(e.g.v0.1.0). Thechart-releaseraction also produces anomega-X.Y.ZHelm chart tag automatically; do not create that one by hand.
Pre-1.0: release as features are ready, with at most one minor per month so downstream consumers can plan upgrades.
After 1.0: minor releases on a 6-week cadence, patch releases as needed.
- CI on
mainis green for the release commit (test, cross, demo, examples, helm, kind-operator, govulncheck, gosec, markdownlint). CHANGELOG.md[Unreleased]section is populated. In the release commit, rename it to the new version heading and add a fresh empty[Unreleased]block.make demoandmake docker-demopass on the release commit locally.charts/omega/Chart.yamlversionandappVersionare bumped to the new version (chart-releaser usesversionas the source of truth).- README quickstart still works against the new image tag.
git switch main && git pull --ff-only
git tag -a vX.Y.Z -m "Omega vX.Y.Z"
git push origin vX.Y.Z
The vX.Y.Z tag triggers two jobs in
.github/workflows/ci.yml:
image: builds linux/amd64 + linux/arm64, pushes toghcr.io/kanywst/omega:X.Y.Zand the:X.Yand:X.Y.Z-<sha>floating tags.chart-release: packagescharts/omega, appends togh-pages/index.yaml, and publishes the chart athttps://kanywst.github.io/omega/.
Do not hand-edit the gh-pages branch (see
CLAUDE.md note in the repo). The only manual touch was
the bootstrap orphan commit.
-
helm repo update && helm search repo omegashows the new version. -
The Helm chart
.tgzwas cosign-signed and its sigstore bundle is attached to theomega-X.Y.ZGitHub Release. Verify with:helm pull omega/omega --version X.Y.Z gh release download omega-X.Y.Z \ --repo kanywst/omega \ --pattern 'omega-X.Y.Z.tgz.sigstore.json' cosign verify-blob \ --bundle omega-X.Y.Z.tgz.sigstore.json \ --certificate-identity-regexp '^https://github\.com/kanywst/omega/\.github/workflows/ci\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ omega-X.Y.Z.tgz -
Both architectures pull cleanly.
docker pulldefaults to the host's platform, so verify each explicitly:docker pull --platform linux/amd64 ghcr.io/kanywst/omega:X.Y.Z docker pull --platform linux/arm64 ghcr.io/kanywst/omega:X.Y.Z -
The image's keyless cosign signature verifies against the workflow that produced it:
cosign verify ghcr.io/kanywst/omega:X.Y.Z \ --certificate-identity-regexp '^https://github\.com/kanywst/omega/\.github/workflows/ci\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com -
A per-platform SPDX SBOM is attached to each architecture's manifest as a cosign attestation. The SBOM hangs off the per-arch manifest, not the index, so resolve that digest first and verify it directly —
cosign verify-attestationhas no--platformflag:There is one SBOM per architecture, so this runs twice — once per digest. Verifying only one arch leaves the other unchecked:
AMD=$(crane digest --platform linux/amd64 ghcr.io/kanywst/omega:X.Y.Z) ARM=$(crane digest --platform linux/arm64 ghcr.io/kanywst/omega:X.Y.Z) for DIGEST in "$AMD" "$ARM"; do cosign verify-attestation \ --type spdxjson \ --certificate-identity-regexp '^https://github\.com/kanywst/omega/\.github/workflows/ci\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ "ghcr.io/kanywst/omega@$DIGEST" doneWithout
crane, read the digests straight out of the index:TOKEN=$(curl -s 'https://ghcr.io/token?scope=repository:kanywst/omega:pull&service=ghcr.io' \ | python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])') curl -s https://ghcr.io/v2/kanywst/omega/manifests/X.Y.Z \ -H "Authorization: Bearer $TOKEN" \ -H 'Accept: application/vnd.oci.image.index.v1+json' \ | python3 -c 'import sys,json for m in json.load(sys.stdin)["manifests"]: p = m.get("platform", {}) if p.get("os") == "linux": print(p["architecture"], m["digest"])'The amd64 SBOM is also published as a build artefact named
omega-sbom-linux-amd64.spdx.json; arm64 likewise. -
The SLSA Build Level 3 provenance attestation is reachable through
gh. The flag is--signer-workflowand it takes the fullowner/repo/pathform; a bare--workflow ci.ymlis not agh attestation verifyflag:Verify the index digest, not the tag. The attestation subject is
steps.build.outputs.digest, which at tag time is the multi-arch index; a tag is a mutable ref, so pinning the digest is what actually ties the provenance to the artefact you shipped:INDEX=$(crane digest ghcr.io/kanywst/omega:X.Y.Z) # index, not per-arch gh auth status # preflight gh attestation verify "oci://ghcr.io/kanywst/omega@$INDEX" \ --repo kanywst/omega \ --signer-workflow kanywst/omega/.github/workflows/ci.ymlWithout
crane, the index digest is theDocker-Content-Digestheader on a HEAD of the manifest, requested with the index media type. TheAcceptheader is required, not decorative: without it GHCR answers404 MANIFEST_UNKNOWN— "OCI index found, but Accept header does not support OCI indexes" — which looks like a missing image rather than a malformed request:curl -sI https://ghcr.io/v2/kanywst/omega/manifests/X.Y.Z \ -H "Authorization: Bearer $TOKEN" \ -H 'Accept: application/vnd.oci.image.index.v1+json' \ | grep -i docker-content-digestTwo access notes. This command reads GitHub's attestation store, which is not the registry: the token
ghuses is not the one that authenticated thedocker/cosignpulls above, so registry access alone is not enough. It needs a classic PAT withread:packages, or a fine-grained token withattestations:read. Without it the command fails with "the provided token was denied access", which reads like a missing attestation rather than a local credential gap. Separately, GHCR's referrers API does not enumerate these attestations, so an empty referrers list is not evidence that provenance is missing either. -
A draft GitHub Release with the relevant
CHANGELOG.mdexcerpt is prepared and reviewed by another maintainer before publishing.
For a security or critical-bug fix on the latest minor:
- Branch
release/X.Yfrom the prior tag if it does not already exist. - Cherry-pick the fix and bump to
vX.Y.(Z+1). - Tag and push as in "Cutting the release". Backport to
mainseparately.
Releases are not deleted from GHCR or the Helm index. To yank a broken
release, publish a vX.Y.(Z+1) immediately and update the affected
versions list in SECURITY.md, plus a "yanked" note
in CHANGELOG.md.