Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 61 additions & 228 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

25 changes: 16 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

The first release published by CI: every engine image and the cli image on GHCR,
for amd64 and arm64, each with an SBOM and build provenance. It also renames the
project to dbpantry and fixes the findings that failed the Trivy gate on every
image, replacing Cassandra's vulnerable vendored JARs rather than waiving them.
The first release published by CI: the GitHub release, with the source tarball
and its provenance, once every engine image and the cli image has built and passed
the Trivy gate on amd64 and arm64. Container images are no longer published. It
also renames the project to dbpantry and fixes the findings that failed the Trivy
gate on every image, replacing Cassandra's vulnerable vendored JARs rather than
waiving them.

### Upgrade

Expand All @@ -24,11 +26,16 @@ image, replacing Cassandra's vulnerable vendored JARs rather than waiving them.
`MDB_EXTRA_SANS`, then restart `pg-pooler`.

### Changed
- **Pushing a `vX.Y.Z` tag no longer publishes anything.** `release.yml` runs only from a
`workflow_dispatch` on a version tag already on GitHub, with `-f confirm=release-go`, and its first
step refuses anything else before a checkout or build:
`gh workflow run release.yml --repo simtabi/dbpantry --ref vX.Y.Z -f confirm=release-go`.
What a release publishes is unchanged (docs/guides/release.md, design.md D-76).
- **Container images are no longer published.** A release publishes the GitHub release only, with
the notes and the source tarball (owner, 2026-11-13). The `ghcr.io/simtabi/dbpantry-*` images are
frozen: no new tag is pushed and nothing is deleted from ghcr.io. Build the images with
`make build`. The Kubernetes backend and the Helm chart still name `ghcr.io/simtabi` as their
registry, so push your own build there or point `global.imageRegistry` at your own registry
(design.md D-77).
- **A release runs on a pushed `vX.Y.Z` tag, or on request for a tag already on GitHub:**
`gh workflow run release.yml --repo simtabi/dbpantry -f tag=vX.Y.Z`. This replaces the dispatch
gated on `-f confirm=release-go` (D-76). Every job checks out the tag, and the first step refuses
anything but a version tag that exists on GitHub (docs/guides/release.md, design.md D-77).
- **`make dbpantry` writes `build/<os>/dbpantry`** (`build/macos/dbpantry`, `build/linux/dbpantry`),
the org's build layout: one folder per OS and nothing else in `build/`. It wrote `build/dbpantry`
before; delete that file if you built one. Check 67 fails on anything else in `build/`, and the
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ The descriptors are also published as a versioned JSON manifest by the `dbpantry
## <a name="documentation"></a>Documentation

The documentation lives in [`docs/`](docs/), indexed below. Engine images are not
published yet: the release workflow publishes them only when dispatched by hand
for an approved version (a tag push publishes nothing), and until then
`make build` builds them locally (see [Release](docs/guides/release.md)).
published: `make build` builds them locally, and the `ghcr.io/simtabi/dbpantry-*`
images are frozen. A release is the GitHub release, made by pushing a `vX.Y.Z`
tag (see [Release](docs/guides/release.md)).

### Guides — `docs/guides/`

Expand Down
7 changes: 4 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ minor release, announced in `CHANGELOG.md`.

## Verifying what you run

Every published image carries a build-provenance attestation and an SBOM
generated in CI.
Container images are no longer published; `make build` builds them from
digest-pinned bases, and CI scans every one before a release. Each release's
source tarball carries a build-provenance attestation:

```bash
gh attestation verify oci://ghcr.io/simtabi/dbpantry-pg:17 --owner simtabi
gh attestation verify dbpantry-X.Y.Z.tar.gz --repo simtabi/dbpantry
```

Images are pinned by digest throughout this repository. If a digest in
Expand Down
2 changes: 2 additions & 0 deletions design.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ Relied on: pgBouncer configuration reference (<https://www.pgbouncer.org/config.

**D-76: a release publishes only from a confirmed dispatch; a tag push publishes nothing.** Supersedes D-56's trigger, and nothing else in it. D-56 made a `vX.Y.Z` tag push the release decision. The Simtabi rule since 2026-11-02 is that no Go or Docker release fires on a tag, after a tag push in another repository started a release nobody wanted yet. dbpantry's product is its container images, so the owner chose on 2026-11-07 to keep publishing them and change only the trigger and the gate. `release.yml` now has `workflow_dispatch` as its only trigger, with a required `confirm` input. The first step of its first job refuses unless `confirm` is `release-go` and `GITHUB_REF` is a `refs/tags/v[0-9]*` tag, before any checkout or build, and every other job needs that job, so a refusal publishes nothing. Dispatched with `--ref vX.Y.Z`, `github.ref`, `github.ref_name` and `GITHUB_SHA` mean what they meant under a tag push, so every later check (vX.Y.Z, tag on `main`, CHANGELOG section, no MongoDB) and everything published is unchanged. The command is `gh workflow run release.yml --repo simtabi/dbpantry --ref vX.Y.Z -f confirm=release-go`. `GOPRIVATE`/`GONOSUMDB` are set for `github.com/simtabi/*`, as in `lint.yml`, since `make verify` builds the dbpantry binary. The guard is `src/internal/release`'s `TestReleaseRunsOnlyOnAConfirmedDispatch`, which parses the workflow and fails on a `push`, `schedule`, `pull_request` or `release` trigger, a missing or optional `confirm`, a first step that is not the refusal, or a job that does not need the gating job; its companion test proves each of those can fail. It runs in `make go-ci`, in `lint.yml`'s go job on every pull request.

**D-77: a release publishes the GitHub release only, on a pushed version tag or on request; images stop.** Supersedes D-76's trigger and D-56's image publishing, and nothing else in them. The owner decided on 2026-11-13 that every Go repository ships on its GitHub release only, with no container registry among the channels, and answered "Yes, stop images" for dbpantry. `release.yml` loses its publish and manifest jobs: the registry login, the per-architecture push, the SBOM and provenance attested to the registry, the multi-architecture manifest and its provenance, and the `packages: write` they needed. Nothing is deleted from ghcr.io, and measured that day no dbpantry package existed there, so no consumer could have pulled one. The scan and verify gates stay: the images are still the product, every user builds them with `make build`, and a release still refuses a tag whose images do not build or carry a fixable finding. The GitHub release now needs scan and verify instead of the manifest job. The triggers become the org's pair: a pushed `v[0-9]+.[0-9]+.[0-9]+` tag, and `workflow_dispatch` with a required `tag` input (`gh workflow run release.yml --repo simtabi/dbpantry -f tag=vX.Y.Z`), replacing `confirm=release-go`. The tag is `inputs.tag || github.ref_name`; the first step refuses anything but a `vX.Y.Z[-pre]` tag that exists on GitHub, the next step still refuses a pre-release, and every job checks out `refs/tags/<tag>`, because a dispatch's `GITHUB_SHA` is its branch's head, not the tag's commit: the tag-on-`main` check now tests `HEAD` after that checkout. No pre-release tag filter is declared, since dbpantry refuses pre-releases. dbpantry builds no release binaries, so it keeps its own jobs rather than calling `simtabi/.github`'s shared `go-release.yml`, which runs GoReleaser. The Kubernetes backend (`scripts/backends/kubernetes.sh`) and the Helm chart (`global.imageRegistry: ghcr.io/simtabi`) still name the frozen registry; changing them is a separate decision. The guard is `src/internal/release`'s `TestReleaseRunsOnAVersionTagOrOnRequest`, which fails on any other trigger, a branch or non-version tag filter, a missing or optional `tag` input, a first step that is not the refusal, a checkout of anything but the tag, a job that does not need the gating job, or any image publish (registry login or push, `buildx --push`, `imagetools create`, `push-to-registry: true`, a `docker/` publish action, `packages: write`); `TestReleasePolicyRejectsAWrongRelease` proves each can fail, and that a local build and scan passes.

## 8. arm64 deliverability

**Nothing in the spec is undeliverable on arm64.** The named unknown anticipated gaps that do not exist:
Expand Down
129 changes: 65 additions & 64 deletions docs/guides/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,42 @@
How a version of dbpantry ships, and what a consumer gets.

> **Status:** [`release.yml`](../../.github/workflows/release.yml) implements this
> contract. It has not run yet: v0.1.0 was released by hand, with no images, so
> the first owner-approved dispatch is the first that publishes anything. Until
> then the images exist only where `make build` built them.
> contract. v0.1.0 was tagged by hand, and the GitHub release has not been
> published for any version yet.
>
> **Container images are no longer published** (owner, 2026-11-13: a release ships
> on the GitHub release only). The `ghcr.io/simtabi/dbpantry-*` images are frozen:
> no new tag is pushed, and nothing is deleted from ghcr.io. Measured 2026-11-13,
> no dbpantry image had been published there. Build the images with `make build`.

## What ships

A release is one git tag, `vX.Y.Z`, plus one confirmed dispatch of the release
workflow on that tag. Together they produce:
A release is one git tag, `vX.Y.Z`, pushed to GitHub (see [Publishing](#publishing)).
It produces the GitHub release, and nothing else:

| Artefact | Where |
|---|---|
| Engine images, one per engine per supported major | `ghcr.io/simtabi/dbpantry-<engine>` |
| The `cli` image | `ghcr.io/simtabi/dbpantry-cli` |
| Source tarball | GitHub release |
| SBOM per image | attached to the image |
| Build provenance attestation | attached to the image |

Every image is built for **amd64 and arm64**, from the same digest-pinned bases,
and both architectures are proven by CI before anything is pushed. There is no
emulated build: an engine with no arm64 upstream image is not published for
arm64 rather than shipped as a QEMU image that corrupts data under load.

The derived images, `dbpantry-pgbouncer` and `dbpantry-patroni`, are not
published: `make build` builds them locally from the engine images it has just
built.
| Release notes, from the version's `CHANGELOG.md` section | GitHub release |
| Source tarball, `dbpantry-X.Y.Z.tar.gz`, and its `.sha256` | GitHub release |
| Build provenance attestation for the tarball | GitHub attestations |

The images are the toolkit's product, but they are built where they run:
`make build` builds every engine image, the `cli` image and the derived
`dbpantry-pgbouncer` and `dbpantry-patroni` images from the digest-pinned bases.
Before a release is published, CI still builds every engine image and the `cli`
image on **amd64 and arm64**, on native runners, and scans each one. There is no
emulated build: an engine with no arm64 upstream image fails the release rather
than shipping as a QEMU image that corrupts data under load.

**MongoDB is not published.** It is SSPL-licensed, so the toolkit references the
upstream image rather than deriving one. See [Licensing](licensing.md). Check 31
fails the build if that ever changes silently.

## Image tags
## Image tags (frozen)

No image is published any more. This is the tag scheme `release.yml` used for
`ghcr.io/simtabi/dbpantry-*` until 2026-11-13, kept so that a tag already pulled
can be read. `make build` tags local images `<major>` (engines) and `dev` (cli).

| Tag | Example | Moves |
|---|---|---|
Expand All @@ -42,9 +47,8 @@ fails the build if that ever changes silently.
| `<release>`, cli only | `dbpantry-cli:0.1.1` | never |
| `<tag>-amd64`, `<tag>-arm64` | `dbpantry-pg:17-0.1.1-arm64` | never |

Pin `<major>-<release>`, or better its digest, for anything that matters. The
per-architecture tags are the two halves of each multi-architecture manifest;
pull the manifest, not them. `latest` is never published.
The per-architecture tags were the two halves of each multi-architecture
manifest. `latest` was never published.

## Versioning

Expand All @@ -70,58 +74,60 @@ git tag -a v1.4.0 -m "..."
git push origin v1.4.0
```

**Pushing a tag publishes nothing.** It names the version and fixes the commit;
no workflow starts. Nothing is published from a branch, a pull request or a tag
push.
**Pushing a `vX.Y.Z` tag releases it.** Push one only for final code that the
owner has approved: tags are final, and the push starts the release workflow.
Nothing is published from a branch or a pull request.

## Publishing

A release runs only when dispatched by hand, with this exact command, from any
directory:
A release runs in two ways, from any directory:

```bash
gh workflow run release.yml --repo simtabi/dbpantry --ref vX.Y.Z -f confirm=release-go
git -C ~/Artisan/projects/opensource/simtabi/packages/dbpantry push origin vX.Y.Z # releases vX.Y.Z
gh workflow run release.yml --repo simtabi/dbpantry -f tag=vX.Y.Z # the same, for a tag already pushed
```

- `vX.Y.Z` must be a tag **already on GitHub** (see [Tagging](#tagging)). The
run is dispatched on that tag, so the workflow reads the version and the
commit from it.
- The command **publishes for real**: every image to `ghcr.io/simtabi` and the
GitHub release. Run it only for a version the owner has approved.
- Without `-f confirm=release-go`, or dispatched on anything but a `vX.Y.Z` tag,
the workflow's first step refuses before anything is checked out or built.

`src/internal/release`'s `TestReleaseRunsOnlyOnAConfirmedDispatch` fails if
`release.yml` gains a `push`, `schedule`, `pull_request` or `release` trigger,
loses the required `confirm` input, or its first step stops refusing.
- The tag push is the normal path. The dispatch is for a tag that is already
on GitHub: a run that failed, or a tag pushed before this workflow existed.
- `vX.Y.Z` must be a tag **already on GitHub** (see [Tagging](#tagging)). Every
job checks out `refs/tags/vX.Y.Z`, so the version and the commit come from the
tag, never from the branch a dispatch was started from.
- Anything but a `vX.Y.Z` tag that exists on GitHub is refused by the
workflow's first step, before anything is checked out or built.

`src/internal/release`'s `TestReleaseRunsOnAVersionTagOrOnRequest` fails if
`release.yml` gains a branch push, `schedule`, `pull_request` or `release`
trigger, a tag filter that is not a version, loses the required `tag` input,
stops refusing first, checks out anything but the tag, or publishes a container
image (a registry login or push, a manifest, an attestation pushed to a
registry, a `docker/` publish action, or `packages: write`).

## The workflow

`release.yml` runs only on a confirmed `workflow_dispatch` (see
`release.yml` runs on a pushed version tag or a dispatch naming one (see
[Publishing](#publishing)). Its jobs run in this order, and each refuses as
early as it can:

| Job | What it does | Refuses when |
|---|---|---|
| resolve release | refuses first unless `confirm` is `release-go` on a `vX.Y.Z` tag, then reads the version from the tag and the images from the engine descriptors | the dispatch is unconfirmed or not on a version tag, the tag is not on `main`, `CHANGELOG.md` has no section for it, or MongoDB is in the matrix |
| resolve release | refuses first unless the tag is a `vX.Y.Z` tag on GitHub, then checks it out, reads the version from it and the images to scan from the engine descriptors | the tag is not a version tag on GitHub, the tag is not on `main`, `CHANGELOG.md` has no section for it, or MongoDB is in the matrix |
| scan | builds every image on both architectures and runs the Trivy gate | any image fails to build or has a fixable HIGH or CRITICAL finding |
| verify | runs `make verify` on amd64 and arm64 | any check fails |
| publish | builds again, scans again, pushes each architecture, and attaches its SBOM and build provenance | the second scan fails |
| manifest | joins the two architectures under the tags above, with provenance | — |
| GitHub release | creates the release with the notes and the source tarball, its checksum and provenance | the release already exists |
| GitHub release | once scan and verify have passed, creates the release with the notes and the source tarball, its checksum and provenance | the release already exists |

Nothing is published until scan and verify have passed for every image. The
images are built on native runners for each architecture, never under QEMU.
The workflow uses no third-party action: trivy is a binary checked against its
published checksums, and GitHub's own `actions/checkout` and `actions/attest`
float on their major tag, per the Simtabi action rule.

Nothing is pushed until scan and verify have passed for every image, so a
finding in one engine cannot leave the others half-published under the version.
The images are built on native runners for each architecture, never under QEMU.
The workflow uses no third-party action: docker login and `docker buildx
imagetools` come from the runner, and trivy and syft are binaries checked
against their published checksums. GitHub's own `actions/checkout` and
`actions/attest` float on their major tag, per the Simtabi action rule.
dbpantry builds no release binaries, so it keeps its own jobs rather than
calling `simtabi/.github`'s shared `go-release.yml`, which runs GoReleaser.

Verify an image's provenance with:
Verify the source tarball's provenance with:

```bash
gh attestation verify oci://ghcr.io/simtabi/dbpantry-pg:17-0.1.1 --owner simtabi
gh attestation verify dbpantry-X.Y.Z.tar.gz --repo simtabi/dbpantry
```

## Release notes
Expand Down Expand Up @@ -169,14 +175,9 @@ Then confirm, by hand:
bottom of `CHANGELOG.md`. `scripts/release-notes X.Y.Z` prints the body the
release will carry.
2. Merge it, once CI is green.
3. Tag the merge commit on `main` and push the tag (see [Tagging](#tagging)).
This publishes nothing.
4. Once the owner approves the version, dispatch the release with the command in
[Publishing](#publishing).

The first publish creates each `dbpantry-*` package on GHCR, and GHCR creates a
package private. The owner sets each one public once, in the package settings;
until then a pull without credentials fails.
3. Once the owner approves the version, tag the merge commit on `main` and push
the tag (see [Tagging](#tagging) and [Publishing](#publishing)). The push
starts the release.

## Digest pinning

Expand All @@ -196,8 +197,8 @@ make init
make up
```

Or pull images directly without the repository, pinned by digest for anything
that matters.
`make build` builds the images. They are no longer published to ghcr.io, so
there is no image to pull without the repository.

`make self-update` updates the toolkit itself and never touches project data or
`secrets/`.
Expand Down
5 changes: 4 additions & 1 deletion scripts/verify/48-runtime-detection.sh
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ for d in /usr/bin /bin; do
for f in "$d"/*; do
n="${f##*/}"
case "$n" in docker*|podman*|nerdctl*|container|socktainer|incus*|kubectl|lima*|colima) continue ;; esac
[[ -e "$tools/$n" ]] || ln -s "$f" "$tools/$n"
# -L as well as -e: a dangling symlink in /usr/bin (grub-ntldr-img on the
# 2026-11 ubuntu-latest image) fails -e, so /bin, which is /usr/bin there,
# would link the same name twice and ln would stop the check.
[[ -e "$tools/$n" || -L "$tools/$n" ]] || ln -s "$f" "$tools/$n"
done
done

Expand Down
Loading
Loading