Releasing GuestCraft
Questi contenuti non sono ancora disponibili nella tua lingua.
This guide covers how GuestCraft is versioned and released, how to verify a published image and how to recover from a bad upgrade. The reasoning is in ADR 0039.
Status (28 October 2026): the automation is in place, but no version has been released yet. The first release needs the one-time setup below and the owner’s approval of the first release pull request.
Versions
Section titled “Versions”GuestCraft is pre-alpha and uses 0.MINOR.PATCH semantic versions, computed from the Conventional Commits on main:
| Change since the last release | Next version | Meaning for an operator |
|---|---|---|
Only fix or perf commits |
patch, 0.1.0 → 0.1.1 |
Fixes. No new required setting and no removal from the API. |
A feat commit |
minor, 0.1.1 → 0.2.0 |
New capabilities. Read the notes before upgrading. |
A breaking change (feat!, fix! or a BREAKING CHANGE: footer) |
minor, 0.2.0 → 0.3.0 |
Something you use may change. The changelog lists it first. |
- Mark a commit as breaking when it removes or changes an API operation or field, adds a required setting, renames an environment variable or changes behaviour that an operator relies on.
- Any version can include database migrations, including a patch. The release notes list them, because migrations decide whether you can go back (see rollback).
1.0.0is a decision of the owner, not of the tooling. It needs a supported upgrade path between consecutive releases. From 1.0, a breaking change bumps the major version.docs,test,ci,build,refactorandchorecommits do not trigger a release and are hidden from the changelog.
What carries the version
Section titled “What carries the version”One product version covers the whole repository. The release updates version in the root package.json, apps/web/package.json and packages/sdk/package.json, and the image tags in docker-compose.yml. The other workspace packages are internal and stay at 0.0.0.
The MIT SDK (@guestcraft/sdk) carries the product version while it is not published on npm. SDK 0.4.2 is generated from the API of GuestCraft 0.4.2. When the SDK is first published, it may get its own version line, with npm trusted publishing (see ADR 0039).
The API revision is separate
Section titled “The API revision is separate”The HTTP API has its own contract revision: info.version of openapi/v1.json, API_REVISION in packages/api, following ADR 0016. It changes only when the contract changes. A product release can leave it unchanged. Every release note states the revision it serves, for example “/api/v1, contract revision 2.2.0”. Integrations should check the revision, not the product version.
How a release happens
Section titled “How a release happens”- Every push to
mainruns the release workflow. release-please opens or updates one pull request,chore(main): release X.Y.Z. That pull request bumps the versions and adds the new section toCHANGELOG.md. - The release pull request runs the same required checks as any other:
qualityandsmoke. - The owner merges it, following the checklist below.
- On that merge, release-please tags
vX.Y.Zand creates a draft GitHub release, marked as a pre-release while the version is below 1.0. The draft holds the changelog section. - The workflow builds the
web,workerandmigratetargets of theDockerfilenatively onlinux/amd64andlinux/arm64runners, from the tagged commit, without a build cache. It pushes the images by digest to GHCR. - For every platform image, the workflow creates a SLSA build provenance attestation and an SPDX SBOM attestation with
actions/attest, signed through Sigstore and stored in GitHub and in GHCR. It then joins the platforms into one index per image, tags itX.Y.ZandX.Y, and attests the provenance of the index. - Last, the release-notes script appends the image digests, the verification command, the API revision and the new migrations. The workflow attaches the SPDX and CycloneDX SBOMs and publishes the release.
If any step fails, the release stays a draft. Nobody can mistake a release without its images for a published one.
Every push to main, and every pull request that changes the build inputs, runs the image workflow. It is a dry run of steps 5 and 6. It uses the same build action, loads the images locally instead of pushing them, checks their architecture and OCI labels, starts Node.js in each, and writes their SBOMs. Nothing is pushed or attested.
Release checklist
Section titled “Release checklist”The owner (the code owner of /.github/ in CODEOWNERS) approves and merges every release pull request. With one maintainer, nobody else can release (GOVERNANCE).
Before merging the release pull request:
-
qualityandsmokeare green on the release pull request itself. - On the
maincommit it releases,image,browseranddependenciesare green. Check the latestnightlyrun too. A red run either blocks the release or appears in the notes as a known issue. - There is no open security advisory or vulnerable-dependency issue that this version should fix.
- The notes name every publicly known vulnerability that this version fixes, with its GitHub advisory and CVE identifiers.
- The changelog section is correct and breaking changes are marked. An unmarked one needs a follow-up commit marked with
!that describes it, before the merge. - If the contract changed,
openapi/v1.json,API_REVISIONand the SDK are regenerated and committed. - Each new migration was tested on a copy of real-shaped data, including the upgrade from the previous release.
- Product status and the guides match what the version does. Partial capabilities are described as partial.
After merging:
- The release workflow is green and the release is published, no longer a draft.
- The images and attestations verify (below).
- A clean installation works from the published images:
docker compose upfrom the tag, then/api/v1/healthand/setup. - An upgrade works: the previous release’s Compose installation, upgraded to the new tag.
- First release only: both packages are public (see the one-time setup).
Vercel deployments are separate: a one-click installation deploys the branch it was created from, not a release (one-click guide).
Verify an image
Section titled “Verify an image”Use a current GitHub CLI (the flags below exist in 2.103), signed in with gh auth login or a GH_TOKEN; any account can verify a public repository’s attestations.
The provenance of the multi-platform index, built by this repository’s release workflow from main on GitHub-hosted runners:
gh attestation verify oci://ghcr.io/spinny03/guestcraft:0.1.0 \ --repo Spinny03/guestcraft \ --signer-workflow Spinny03/guestcraft/.github/workflows/release.yml \ --source-ref refs/heads/main --deny-self-hosted-runnersRepeat with ghcr.io/spinny03/guestcraft-worker:0.1.0 and ghcr.io/spinny03/guestcraft-migrate:0.1.0. To bind an image to the exact tagged commit as well, add --source-digest "$(git rev-parse 'v0.1.0^{commit}')" in a clone.
Each platform image has its own provenance and SBOM. List the platform digests, then verify the SBOM attestation of one of them:
docker buildx imagetools inspect ghcr.io/spinny03/guestcraft:0.1.0gh attestation verify oci://ghcr.io/spinny03/guestcraft@sha256:<platform digest> \ --repo Spinny03/guestcraft \ --signer-workflow Spinny03/guestcraft/.github/workflows/release.yml \ --predicate-type https://spdx.dev/Document/v2.3Add --bundle-from-oci to read the attestation from GHCR instead of GitHub’s API. Add --format json to inspect the SBOM or provenance after verification.
Deploy by digest. The release notes list the digest of each index; image: ghcr.io/spinny03/guestcraft@sha256:… cannot change under you, unlike a tag.
The OCI labels (org.opencontainers.image.source, .revision, .version, .licenses) are informative. They are not proof: the attestation is.
Roll back or roll forward
Section titled “Roll back or roll forward”- No new migration since the version you are leaving: go back by setting the previous version (
GUESTCRAFT_VERSIONor the tag indocker-compose.yml) and runningdocker compose upagain. The release notes say whether a version adds migrations. - The version applied only
expandmigrations: a previous runtime with the compatibility check can cold-start against the newer schema after its own checksum prefix matches. Do not run the older migration command; it still rejects a newer journal before grants or job registration. For Compose, useGUESTCRAFT_VERSION=<previous> docker compose up -d --no-deps web worker; a plaindocker compose upstops at the oldermigrateservice by design and also stops the running web and worker, leaving them inCreatedstate. The--no-depscommand is the recovery. Verify old code can read and write against the expanded schema and grants before switching the image or Vercel alias. Code built before the compatibility check still has a strict revision rule and needs roll-forward after any newer migration. - The version applied a
contractmigration: roll forward. The older runtime refuses a newer contract entry. Fix the problem in a new release, with a new migration if data needs one. Never edit an applied migration or delete journal rows. - Roll forward is not possible in time: restore the database backup taken before the upgrade, then run the previous version. Data written since the backup is lost. Restoring is an operator decision, not part of the release.
- A bad release: do not delete or move its tag, and do not reuse its version. Publish a fixed patch release and say in the bad release’s notes which version replaces it. A tag that was public may already be pinned or verified by someone.
For a breaking schema change, ship additive expand SQL first, switch code in a later release while retaining the old objects, and ship contract cleanup after old processes drain. A failed batch leaves earlier committed migrations in the journal, so inspect each extra kind before choosing rollback or roll-forward.
The release-please pull request regenerates packages/db/migrations/grant-floor.json from the grant inventory using pnpm --filter @guestcraft/db migrations:grant-floor. Review that change before merging the release. No release has shipped yet, so the first release pull request establishes the first floor.
One-time setup (owner)
Section titled “One-time setup (owner)”- GitHub App for release-please. A pull request opened with
GITHUB_TOKENdoes not trigger the required checks. This repository also does not let Actions open pull requests. Create a GitHub App: Settings → Developer settings → GitHub Apps → New GitHub App. Use any name (for exampleguestcraft-release) and the repository URL as homepage, and turn the webhook off. Repository permissions: Contents, Pull requests and Issues read and write; Metadata read-only. Install it onSpinny03/guestcraftonly. It costs nothing. - In the repository, under Settings → Secrets and variables → Actions, add the App’s Client ID as the variable
RELEASE_APP_CLIENT_ID. Generate a private key and add it as the secretRELEASE_APP_PRIVATE_KEY. The release workflow does nothing until the variable exists. - After the first release, open each package (
guestcraft,guestcraft-worker,guestcraft-migrate) under the owner’s Packages. Check that it is linked to the repository, and set its visibility to public if GitHub created it private. GitHub offers no API for this step. - Recommended: enable immutable releases (Settings → General → Releases), so a published release’s tag and assets cannot change. The draft-then-publish flow already uploads every asset before publication. Also recommended: a tag ruleset that blocks updating and deleting
v*tags. Optionally, move the private key into an environment limited tomainand give therelease-pleasejob thatenvironment. The repository secret is acceptable meanwhile, because the App can do nothing that a collaborator with write access cannot already do.
When the release workflow fails
Section titled “When the release workflow fails”release-pleasefails: usually the App token or its permissions. Fix them, then re-run the job. Nothing is tagged until a release pull request is merged.- An image, manifest or publish job fails: the tag and the draft exist. Re-run the failed jobs from the run’s page. They rebuild from the same tagged commit. Images pushed by digest without a tag are harmless. Never publish the draft by hand without its images and notes.
- The tagged commit itself is wrong: leave the draft unpublished. Delete the draft and the tag only if they were never published. Then fix forward with a new commit, which becomes the next release.