ADR-069: One versions table pins everything, and every release proves the upgrade from the previous one
Status: Accepted (2026-10-28) Builds on: ADR-058: Images and operators are pinned exactly per release; the platform installs the operators a cluster needs as Argo CD applications (the versions table in the renderer), ADR-060: A cluster repo owns what a cluster shares and lists the systems it runs (cluster repos share the table through a copy), spec 121; the rollout side is the companion ADR ADR-068: Resource and operator upgrades roll through the environments in verified steps
Context
Section titled “Context”ADR-058 put the database image and the operator charts into a table in render.py. Other references stayed outside it: the local cluster
script installed Argo CD from stable and took operator versions from environment variables, the images carried tags without digests,
and CloudNativePG stopped publishing the plain <major>.<minor> tags the table used. Nothing showed that a cluster set up by one release
still works after the next, and the CHANGELOG did not say which pins moved.
Decision
Section titled “Decision”- Every image and chart the platform renders or installs comes from the table in
templates/gitops-app/scripts/render.py: engine images per major (<repository>:<exact tag>@sha256:<digest>), the NATS chart, the collector, LGTM, the Velero plugin, Argo CD, k3s, the Gateway API manifest, and each operator chart with its app version and the CRD versions it serves.versions_table()is the one reader;cplat platform versions, doctor, the local cluster script (render.py --versions-env) and the checks use it. - A pin check in CI fails on any reference outside the table, an image without a digest, a floating tag and a chart without an exact version, naming the file and the reference.
- Releases list and classify their pin changes. The CHANGELOG section of a release lists every changed row with old and new value and marks major moves; a major operator move, a dropped CRD version or a dropped resource major is refused unless the release is a new major platform version.
- Every release candidate passes an upgrade end-to-end test from the previous release (the first time from the latest 5.0.x, with its floating references resolved and recorded): data written before must read back after the platform update and a database major upgrade through dev, staging and prod, with a refused upgrade without a recent backup and a forced failed verification on the way. It runs in its own workflow; the release procedure tags only a commit with a green run, and a tag-push check turns red otherwise. No automation tags, publishes or deletes.
- Environment-property operators are platform-installed on request. External Secrets, Kyverno and LGTM are rendered under
operators/only for repos that answerproperty_operators: install(new systems), because clusters set up before have them installed by hand.
Consequences
Section titled “Consequences”- A new pin is one edit of the table, visible in the CHANGELOG and proven by the upgrade test before the tag.
- Engine image references in every rendered tree changed once (exact tags and digests); nothing else changed for existing systems.
- Local clusters warn when an environment variable overrides a pinned version.
- The upgrade test needs the previous release’s tag and about 40 minutes of a runner per release candidate.
Not included
Section titled “Not included”- Pinning component images (they stay in the overlays) or the base images of component Dockerfiles.
- A per-system choice of minor versions: the release decides them.