# Verified platform releases [Decision 0056](decisions/0056-verified-platform-releases.md) separates official release publication from personal development and production activation. ## Shared sources `gitops/manifests/images.yaml` defines every image's Dockerfile, context, target, arguments and fingerprint inputs. Both in-cluster builds and the reusable CI workflow read it. Add an image there, with its owner manifest; no handwritten CI matrix is required. Existing optional development publication retains its image lock artefacts and adds complete evidence for official packaging. Shared release code lives in `devops/platform/scripts/platform_release`: `plan.py` reads that catalogue, `package.py` produces deterministic OCI payloads, `promotion.py` selects a package through Git and `health.py` waits for Argo's exact-release notifications. Workstation takeover and browser tooling stay in DX. ## Commands ### Changelog preparation Before tagging a platform release, create a release-preparation PR from current `main`. Pending notes live in [changelog fragments](../changelog.d/README.md). Preview them with the intended tag and release date: ```sh pnpm changelog:preview --version platform-v1.2.3 --date 2026-11-04 ``` Preview leaves files and the Git index unchanged. Commit any edits to the fragments, changelog or Towncrier configuration before assembling: ```sh pnpm changelog:build --version platform-v1.2.3 --date 2026-11-04 ``` The pinned Towncrier dependency inserts the new release above earlier history, stages `CHANGELOG.md` and removes and stages the consumed fragments. Review the staged diff, commit it and merge the release-preparation PR. Tag its merge commit once that commit's main-push CI succeeds. An existing version is rejected even if a different date is supplied. Assemble once per release. Feature PRs continue to add unique fragments while release preparation is reviewed; notes merged after preparation remain pending for the next release. The tag workflow retains its existing generated GitHub release notes and performs no changelog commits. ### Release and deployment commands These commands run from a clean checkout of the selected source: ```sh make release-plan VERSION=platform-v1.2.3 SOURCE=<40-character-commit> make release VERSION=platform-v1.2.3 SOURCE=<40-character-commit> make release-status VERSION=platform-v1.2.3 ``` The first command is read-only. `release` checks that HEAD matches SOURCE and is on main's history, refuses an existing remote tag, then creates and pushes an annotated tag. It applies the same forward-only check before creating the tag. Configure protected release tags and the workflow's package permissions before using it. Release tags are forward-only and must be greater than every earlier tag reachable from `main`. Never move a published tag. The workflow repeats the policy check and waits for successful main-push CI for the exact source before building and publishing. Every catalogue image participates, including administration and identity images. The catalogue at the audited `main` commit `32cea6102` contained fourteen images; the workflow always reads `gitops/manifests/images.yaml`, so no documentation or CI matrix carries a second count. Their credentials remain runtime private inputs, never build arguments. The workflow verifies signatures and complete source-matched evidence before publishing `ghcr.io/pharos-ai-factory/pharos-platform/release:`. Consumers resolve that name once and use its digest. One built archive supplies both publication channels. The workflow pushes the exact `release.tar.gz` to OCI and uploads the same bytes to the GitHub Release, together with the extracted `release.json`, `SHA256SUMS` and a Sigstore blob bundle. It never rebuilds the bytes between channels. The archive contains only committed GitOps files plus `release.json`; it excludes workstation files and private runner state. Archive reading is bounded and never extracts untrusted paths. The repository tag ruleset must protect `refs/tags/platform-v*`, `refs/tags/hpc-site-v*` and `refs/tags/dx-v*`, as well as the legacy `v*` rule. Ruleset administration is an external repository-owner action; a workflow cannot enforce it. The HPC Site archive follows the same reviewed tag path: ```sh make hpc-release-plan VERSION=hpc-site-v1.2.3 make hpc-release VERSION=hpc-site-v1.2.3 SOURCE=<40-character-commit> ``` `hpc-release` refuses a source outside `main`, an existing remote tag or a version not greater than every earlier `hpc-site-v*` tag reachable from `main`. The tag workflow then builds, verifies and signs `pharos-hpc-site-v1.2.3.tar.gz` and publishes it with its checksum and Sigstore bundle. A tag can be created only after the reviewed installer source is on `main`; this release-management PR itself does not publish or activate an HPC Site release. After activation, the ordinary interface selects an existing release: ```sh make deploy ENV=production RELEASE=platform-v1.2.3 make rollback ENV=production RELEASE=platform-v1.2.2 ``` These commands require `oras`, `cosign`, authenticated GHCR access and `GH_TOKEN` with the intended selection-branch permission. Set `PHAROS_RELEASE_STATUS_CREATOR_ID` to the dedicated notification App bot's numeric user ID. CI uses the corresponding `RELEASE_STATUS_CREATOR_ID` repository variable. Selection validates these inputs before changing Git, verifies package/image signatures and waits for all selected Applications to report exact-release health from that App. Another CI status or an older deployment attempt cannot satisfy it. No cluster credential is required. Rollback requires unchanged contract/migration fingerprints. One conservative fingerprint covers all packaged compositions: committed contracts, every PostgreSQL context's manifest and SQL from the migration catalogue, service delivery migrations and retained Themelio migrations. Catalogue owners and checksums are validated against the selected Git commit; untracked files cannot contribute. SQLite and storage-free contexts need no PostgreSQL manifest. Rollback does not undo database migrations or restore data. A removed Application or unknown selection branch file requires an explicit reviewed transition rather than ordinary promotion. ## Production remains disabled `devops/platform/environments/production.yml` declares `release.enabled: false`. Tags can publish artefacts; the production selection job remains skipped. No production overlay or external-identity integration is fabricated by this change. Before enabling selection, the deferred identity/application composition must add the reviewed `production` Application overlay and pass real environment qualification. Bootstrap must configure Argo OCI repository access and permit that exact source in each existing owner AppProject. A root Application follows the engineering repository's `deploy/production` branch at path `apps`; child Applications use the verified OCI package. Fence earlier writers before adopting existing services. Install the dedicated notification App and triggers matching the selected child's `pharos.platform/deployment-context` and `pharos.platform/source` annotations. Success requires Synced, Healthy, a successful operation and both observed sync revisions equal to `spec.source.targetRevision`. Failure must identify that same revision. Bootstrap credentials and public origin/AAI client bindings stay outside the release package. Package publication alone does not establish a working site. ## Verification `scripts/test_platform_release.py` exercises real Git fixtures, Kustomize rendering and archive bytes, complete image evidence, tamper/path rejection, disabled targets, non-forced selection and notification identity/attempt fencing. `pnpm verify` includes these checks. Live GHCR publication, signature identity, Argo OCI rendering, notification delivery, browser/UAT and production rollout require separate evidence.