Releases
How a release of PerspectiveGraph is made, what it publishes, and what to do when one goes wrong.
The rules a release follows are written elsewhere, and this file does not repeat them: which versions receive fixes is in SUPPORT.md, what a version number promises in API stability, why there is no fixed cadence in GOVERNANCE.md, and how a fixed vulnerability is disclosed in SECURITY.md. This is the procedure that carries them out.
From commits to a version
Section titled “From commits to a version”release-please reads the
Conventional Commits merged into main since the last
release, and their subjects decide the version:
| Merged since the last release | Release |
|---|---|
a breaking change: feat!:, or a BREAKING CHANGE: footer |
major |
a feat: |
minor |
a fix: |
patch |
only chore:, docs:, test:, ci:, refactor:, build: or style: |
none - they ship with the next release, and the CHANGELOG leaves them out |
Dependabot titles its updates chore(deps) and chore(ci), so an update never cuts a
release by itself. When one fixes something in what a release ships - a Go toolchain with a
standard-library fix, say - merge it as a fix: that names what it fixes, as 1.38.3 did for
Go 1.27.3.
Two footers ask for more than a version. BREAKING CHANGE: is the Conventional Commits one;
BEHAVIOUR CHANGE: is this project’s, for a change that alters what a deployment does
without breaking the stable interface - a control that starts refusing something, a default
that flips. Either way an operator has to be told what to do, and that is the job of
docs/UPGRADING.md, not of the CHANGELOG: a pull request whose commits
carry either footer fails the Behaviour changes carry a note check until it adds a section
there.
What release-please reads is the commit that lands on main. A squashed pull request of one
commit keeps that commit’s subject; one of several takes the pull request’s title, which the
Conventional PR title check holds to the format. It reads the last paragraph of the
message too, and takes one that starts with a type for one more change: a documentation
commit that ended in a fix(site): … paragraph cut 1.27.1 on its own, with a CHANGELOG line
that stops mid-sentence. Say a second change in prose - “Also fixes …” - and never as the
last paragraph.
How a release is cut
Section titled “How a release is cut”- release-please keeps a release pull request open. On every push to
main, its workflow updates one pull request,chore(main): release X.Y.Z. It moves the version in.release-please-manifest.json, adds the release’s section to the CHANGELOG, and rewrites every version string that carries release-please’s marker - the comment that ends theversion:line of the chart - in the filesrelease-please-config.jsonlists: the chart, the compose files, the Trivy plugin manifest, and the commands in the README, SECURITY.md, the operations runbook and the manual. A new file that names the version needs both, the marker on the line and an entry inextra-files. A test fails on a file that carries the marker without the entry, but nothing finds a version written without the marker: that one keeps naming the version it was written with. - Merging it is the release. release-please tags the merge commit
vX.Y.Zand creates the GitHub release with the CHANGELOG section as its notes, and the same workflow then calls Publish images with the tag. The release pull request goes through the same required checks as any other. - Publish images builds from the tag, not from
main, and publishes what the next section lists. Its chart job refuses to publish whenChart.yamlnames a version other than the tag.
Merging into main takes a pull request that passes the required checks, with signed
commits and a linear history, and the ruleset that enforces this lets nobody bypass it, the
maintainer included. So whoever can merge the release pull request cuts the release: today,
the maintainer.
What a release publishes
Section titled “What a release publishes”| What | Where | Verified by |
|---|---|---|
The images: perspectivegraph (the engine, which is also the MCP server), perspectivegraph-dashboard and perspectivegraph-postgres, each for linux/amd64 and linux/arm64 |
ghcr.io/luiacuaniello/, tagged vX.Y.Z |
a cosign keyless signature, an SPDX SBOM attested to the image and attached to the release, and SLSA build provenance |
| The binaries: Linux and macOS on amd64 and arm64, Windows on amd64 | the GitHub release | SHA256SUMS, signed in SHA256SUMS.sigstore.json (the GitHub Action reads the same file as SHA256SUMS.bundle), and perspectivegraph.intoto.jsonl, the build provenance of every archive |
| The Helm chart | oci://ghcr.io/luiacuaniello/charts/perspectivegraph, and the .tgz on the GitHub release |
a cosign keyless signature |
The commands that verify them are in SECURITY.md for the images and the binaries, and in Deploy to Kubernetes for the chart.
Around those:
latestmoves only when the tag being built is the newest release, so publishing an older tag again never drags it backwards.- The archives carry no version in their names, so
releases/latest/download/<name>is a URL that does not go stale. - The floating tag
v1, whichuses: luiacuaniello/perspectivegraph@v1resolves, moves to the release only once its images and binaries exist: the action downloads one of those binaries. It follows stablevX.Y.Ztags only, and so does the MCP Registry entryio.github.luiacuaniello/perspectivegraph, which the same run updates. - Artifact Hub reads the chart from the registry, and the Trivy plugin manifest on
mainpoints at the new archives once the release pull request merges. Neither needs a step of its own. - The documentation site is not part of a release: it is
published from
mainon every merge that touches the docs. Between a merge and the release that carries it, it can describe something the newest release does not do yet.
Before merging the release pull request
Section titled “Before merging the release pull request”-
The upgrade notes name this version. A section in UPGRADING.md is written before anyone knows which release it lands in, so if a
feat:merged after it turned the expected patch into a minor, its heading names the wrong version. Correct it in a pull request first. -
The chart’s security annotations match the release. A release that fixes a vulnerability carries, in
deploy/helm/perspectivegraph/Chart.yaml, the two annotations that make Artifact Hub mark the version as a security update:annotations:artifacthub.io/containsSecurityUpdates: "true"artifacthub.io/changes: |- kind: securitydescription: <what the fix does, in one line>links:- name: GHSA-xxxx-xxxx-xxxxurl: https://github.com/luiacuaniello/perspectivegraph/security/advisories/GHSA-xxxx-xxxx-xxxxA release that fixes none carries neither. Add or remove them in an ordinary pull request, not in a commit on the release branch, which release-please rewrites on every push to
main. A chart version is pushed once and never replaced, so a security release that ships without them stays unmarked, as 1.39.2 did. -
The advisory names this version as the patched one. It is still a private draft until the release exists. Request its CVE while the fix is in review: GitHub reviews each request by hand.
After the release
Section titled “After the release”- Every job of the run is green: the three image legs, the binaries, the chart, the floating tag and the MCP Registry. A failed one is handled as in the next section, not by re-running it straight away.
- Verify from your own machine, with the commands a user is given. The workflow checks the signatures of the binaries and the chart on the runner that made them, and those of the images not at all; this checks every one of them on the path a user takes, from the registry and the release page.
- After a security release, publish the advisory, then add its link to the release notes, next to the fix: the notes are generated from commit subjects, so the link goes in by hand, on the release page. In the next pull request, take the chart’s security annotations out again.
When a release fails halfway
Section titled “When a release fails halfway”It has happened twice, both times a tool download from GitHub’s release CDN failing after
something was already published: the chart on 1.13.0, the dashboard image on 1.15.0. What
makes it delicate is that not every job can safely run twice. So first establish what was
published - the tags in GHCR, the assets on the release, where v1 points - and then finish
only what is missing.
| Job | Run it again? |
|---|---|
publish, one leg per image |
Only for a leg that pushed nothing, or whose image is incomplete: no signature, SBOM or provenance. A build is not reproducible, so a second one is a new digest under the same tag. |
binaries |
Yes, if nothing is on the release yet. Otherwise it replaces the archives, and the checksums and their signature with them, under files people may already have downloaded. |
chart |
Yes. It asks GHCR first, and a version already there is reused, never pushed again. |
major-tag |
Yes. It points v1 at the same tag. |
mcp-registry |
Yes. It asks the registry first, and skips a version it already has. |
Re-run failed jobs does not always pick up the jobs that depend on the failed one: read the list in GitHub’s confirmation dialog before you confirm. Running Publish images by hand on an existing tag rebuilds every image, so it is not a lighter retry. And when the build itself is broken, cut a patch release rather than change what an existing tag builds: a version has to mean one thing.