Release process#
How a Whoobe release is made. Decisions taken on 2026-10-05: one aligned product version, first release 0.1.0, container images published on ghcr.io, security fixes for the latest two minor versions.
Status of the automation: the gates and the preparation step (scripts/release.sh) exist and are described below.
Publishing the container images and the draft GitHub Release from a tag is written (.github/workflows/release.yml) and
ran for v0.1.1 (2026-10-18). If a package already exists on ghcr.io (made by hand or by another account), the repository must be added under the package's Package settings > Manage Actions access with the role Write, or the push fails with permission_denied: write_package; that is what happened at the first attempt. Steps 5 to 7 by hand remain the fallback.
1. Versions#
- One version for the whole product, in the form
MAJOR.MINOR.PATCH. The rootpackage.jsonand every package underapps/,packages/andpackages/sdk/carry the same number (scripts/securityis a test package and is not part of it). The SDK packages (@whoobe-cms/*) are versioned with the product, not on their own. - While the version starts with
0.: a minor release may change behaviour or break compatibility, a patch release contains only fixes (including security fixes and base-image updates). - A release is the git tag
vMAJOR.MINOR.PATCHon a commit ofmaster, or of arelease/MAJOR.MINORbranch for a patch of an older line. The tag is annotated and never moved.
2. Support#
Security fixes are made for the latest two minor versions (for example, with 0.3.x current: 0.3.x and 0.2.x).
Anything older gets no fix: upgrade. A fix lands on master first, then is cherry-picked to the release/ branch of
the other supported line and released as a patch of both.
3. What a release contains#
| Artifact | Where |
|---|---|
| Source | the tag, and the GitHub Release page |
| Container images, one per service, referenced by digest | ghcr.io (see step 5) |
| Documentation | docs/release/ at the tag; docs/release/README.md states the release it is aligned to |
| API description | docs/release/api/openapi.yaml, attached to the GitHub Release |
| Security evidence | reports of the CI run on the release commit, attached to the GitHub Release |
| Release notes | draft from CHANGES.md, edited by hand |
4. The gates#
./scripts/release.sh check runs them on the current commit and changes nothing.
| Gate | Automatic | What it checks |
|---|---|---|
| Git | yes | clean working tree; on master or release/*; HEAD is pushed, so the CI result belongs to it |
| CI | yes | the security workflow succeeded on exactly this commit, and its DAST job (OWASP ZAP) succeeded on it. DAST does not run on push: start the workflow by hand with Also run the ZAP DAST scan ticked, on the release commit |
| License | yes | LICENSE.md (MIT) exists and every product package.json declares "license": "MIT" |
| Documentation | yes | every core-api route is in the API route table; api-reference.md and openapi.yaml equal what the generator produces; no broken link (links written as examples inside backticks do not count), no docs/... path or quoted repository path that does not exist; every environment variable read by the services or listed in .env.example is in operations/configuration-reference.md; the alignment line is present |
| Upgrade test | no | install the previous release, load data, upgrade to this commit, check the migrations and a login. Not automated yet - the most valuable test still missing |
| Fresh install | no | follow docs/release/operations/creating-workspaces.md on an empty host |
| Behaviour changes | no | operators are told about new or changed environment variables and defaults, in the release notes |
| Results published | no | the record of the version (a file named after the version, in the security/results folder) is written and committed: the run on the release commit (id, commit, every job and its result), the runs that failed before the green one, what differs between the tested commit and the tag, and what the run did not cover (policy) |
| Security summary | no | docs/release/security/status-summary.md is current and its open items are acknowledged; new findings of the documentation review (security/findings.md) are fixed or accepted in the release notes |
| Documentation review | no | the table "What a documentation release still needs" in docs/release/README.md has no unowned row; the technical set (architecture/, operations/, security/) was read by someone other than its author since the last change of behaviour |
The non-automatic gates are printed as a reminder every time; they are the release manager's responsibility.
5. Procedure#
Freeze. Merge what goes in; wait for the
securityworkflow on the last commit; start the DAST run on it.Gates.
./scripts/release.sh check- fix what fails, do the manual gates.Prepare.
./scripts/release.sh prepare 0.1.0. After the gates pass, it- sets the version in the root and in every product package (a version must be higher than the latest
v*tag); - adds a
# Release vX.Y.Z - datemarker and a release entry at the top ofCHANGES.md. Everything under a marker, down to the next one, belongs to that release; entries above the newest marker are unreleased; - writes the alignment line of
docs/release/README.md; - drafts
.release/notes-vX.Y.Z.md(git-ignored) from the entry titles. Edit it: highlights, behaviour changes.
--dry-runshows the plan without writing. The script does not commit, tag or push.- sets the version in the root and in every product package (a version must be higher than the latest
Commit and tag, by hand, as the script prints:
git commit -m "Release vX.Y.Z",git tag -s vX.Y.Z -m "Whoobe vX.Y.Z"(signed: a GPG or SSH key registered on GitHub as a signing key;git config gpg.format sshanduser.signingkeyfor an SSH key).release.ymlrefuses a lightweight or unverified tag before building anything,git push origin <branch> --follow-tags.SBOM:
scripts/release/sbom.sh --images(CycloneDX for the source tree and the service images, written to.release/sbom/); attach to the GitHub Release.Publish images (
release.yml, on the tag):core-api,asset-processor,gateway,publisher,admin-dashboardandwhoobe-one-spikeare built for amd64 and arm64, pushed toghcr.io/<owner>/whoobe-<service>:X.Y.Z(plusX.Y), signed by digest with cosign (keyless), given a SLSA build provenance attestation (stored with the image; check with (GitHub CLI 2.49 or later)gh attestation verify oci://ghcr.io/<owner>/whoobe-<service>@<digest> -R <owner>/<repo>), and the digests are written into the release notes. Verify a signature withcosign verify --certificate-identity-regexp '^https://github.com/<owner>/<repo>/\.github/workflows/release\.yml@refs/tags/vX\.Y\.Z$' --certificate-oidc-issuer https://token.actions.githubusercontent.com ghcr.io/<owner>/whoobe-<service>@<digest>(tried on 2026-10-23 with a trial tag, all six images).admin-dashboardandwhoobe-one-spikeread their addresses, fonts and placeholder mode from the container's environment at start (/config.js), so one image serves every workspace. The job stops if the tag differs frompackage.json.Publish the release:
release.ymlcreates a draft GitHub Release with the release notes file of the version (release-notes-<version>.mdindocs/release/, or a one-line note if the file is missing), the image digests,openapi.yamland the source SBOM. Add the image SBOMs (step 5) and the CI security reports by hand, then publish the draft after review. Read the reports first: one that describes a High or Critical finding that is not fixed yet is not attached (disclosure policy).
6. Patches and security updates#
- A patch release follows the same steps from the relevant branch. Typical triggers: a CVE in a dependency or in the
pinned base image (as with
libpcre2andperl-basein the Debian runtime image on 2026-10-05), a vulnerability found by the security suites, a regression. - For a security fix, the release notes say what was fixed and which versions are affected; supported versions get the patch at the same time.
- When a finding becomes public follows the disclosure policy: a High or Critical finding found by the project is entered in the register, and its test joins the public suite, with the release that fixes it; the release notes get a Security section.
7. The documentation as a release artifact#
docs/release/ at the tag is the documentation of that release; docs/release/README.md states the release it is aligned
to (written by release.sh prepare) and carries the table of what is still missing. To change the documentation after a
release, a patch release is not needed for typos, but any change of behaviour, configuration or security posture must update the
configuration reference, the release notes and the security status summary in the same commit as the code (the gate catches the
variables and the routes, not the prose). A rendered copy for readers can be produced with the product's own documentation-site
feature (point it at docs/release); not automated.
8. Not done yet#
- Image SBOMs and the CI security reports are still attached to the draft release by hand; the cosign signature has not been verified by a consumer yet (the signing step succeeded).
- Pinning the base images' freshness: the pinned digests lag behind Debian's security updates (see
CHANGES.md, 2026-10-05), which today costs a patch release or a manualapt-getupgrade line in theDockerfile.
Last updated