Repository navigation
EN Contribute Releasing
Français · Type: how-to · Audience: contributor · Context: engine repository · Status: ready · Applies from: 4.2.0
How a version of quest'n'click is made, for the maintainer: a candidate run on main, a signed tag that names it, a
release that publishes that run's files and nothing rebuilt, then a read-back. The commands are
npm run ship -- … (tools/release/ship.mjs); every one writes its PID to
.cache/pids/ship-<command>.pid (SHIP_PIDS chooses the folder): to stop one, stop that PID, not a pattern.
A published, immutable GitHub release v<version> built from the commit CI and the candidate tested, carrying its
archives, manifests, SBOMs, sums and provenance attestations, read back by ship verify; from 4.5.0, its
documentation published after that read-back.
In the engine repository, by the maintainer (it needs write access, the signing key and the repository settings). The chain is the one of 4.1.20 "Last Mile" (plan §11); tags are signed from 4.2.0. This page holds from 4.2.0.
- The GitHub CLI
gh, signed in as the maintainer. - The repository settings turned on once (below): immutable releases and the tag ruleset.
- The signing key set up (below).
- The CHANGELOG section of the version assembled from the
changes/fragments (the release branch's job, Conventions), and the people's passes sheet started (Field passes).
In the engine repository, on the maintainer's machine, on main up to date. The release itself runs on GitHub
Actions (.github/workflows/release.yml), started by the tag's ci run.
-
A candidate on main. Start it by hand on main for the full SHA:
# in the engine repository gh workflow run candidate --ref main -f sha=<full sha>
It runs every heavy family on that SHA, the full qualification profile on its own archives and image, the three loads and the signing rehearsal. Its gate (
candidate-gate) writes and attestscandidate-manifest.json, with every file the release will publish (tools/release/artifacts.mjs). Read the gate and its conclusion. Only a run started by hand (workflow_dispatch) on main may be named by a tag; a pull request labelledfull-ciruns it too, as information only.
-
A tag that names it.
# in the engine repository npm run ship -- tag <version> <sha> --candidate=<run> --sign --now
The previous stable tag must be an ancestor of the SHA.
--candidate=<run id>is required from 4.1.17: the run must be green and its manifest must name this SHA and this run; the tag's annotation names it, and release.yml publishes that run's files.--nowdoes not wait for main's owncirun (the tag'scirun, the same suite on the same commit, is what release.yml checks).--signsigns the tag with the SSH key (below).ship tagrefuses to tag while immutable releases are off. It then waits asship watchdoes.
-
The release, from that run. The tag's
cirun, once green, startsrelease.yml. It checks that the tag is the commit ci tested, that the previous stable tag is an ancestor, and that the candidate named is a green run ofcandidatestarted by hand on main. It downloads the run's files and checks every SHA-256 against the manifest. It re-runs the release checks (npm run release-check:ci), proves the Remix catalogues, and copies what the candidate made: it builds nothing the candidate made (archives, image, SBOMs, the site, the surfaces). It then makes the release a draft, uploads every file, publishes it and reads it back (ship verify). From 4.1.20 it also needs the run's signing rehearsal, so a candidate made before that release's PR 3b cannot be released: start a new one. One release run per tag at a time: a second one waits, then finds the first's release.
-
Read back.
# in the engine repository npm run ship -- verify <version>
It downloads the release and checks the sums, every provenance attestation and the list of files against the manifest; from 4.1.20 it also runs
gh release verifyandgh release verify-assetfor each file. release.yml runs it as its last check.
Limit ·
published-never-changedA published release is never changed. A run on a release already published stops before it makes anything. A draft left by a run that failed before publishing was never public: it is deleted (its tag stays) and made again from the same candidate. A failed RC is not fixed: it becomes anrc.N+1.
Each release is built from the commit CI tested, carries its licences inside the archive, and attaches: the built
game (dist), its assets manifest, the five npm package archives (quest-n-click, create-quest-n-click,
quest-n-click-bridge, quest-n-click-connectors, quest-n-click-studio-server; npm packages),
one CycloneDX SBOM per package and one for the Bridge's image (five SBOMs in 4.1.19, six since the Studio Server's
package in 4.2.1), the Bridge's image, the Remix catalogues' proofs, a complete speedrun of the reference chapter
verified by speedrun:verify, the candidate's runs, baseline, site files, surfaces and signing rehearsal, the
candidate's manifest, SHA-256 sums, and a build provenance attestation
(gh attestation verify <file> -R wanoo/quest-n-click); from 4.5.0, the documentation archive. It is never replaced
once published. What only people and real devices can check is reported in its notes (Field passes).
A pre-release version (4.1.8-rc.1) is tagged the same way; release.yml marks it a pre-release on GitHub.
Applies from 4.5.0
From 4.5.0, release.yml's docs-publish job needs the release job, so it starts only after ship verify passed. It
calls .github/workflows/docs-publish.yml with the tag, the SHA, the candidate run and the sha256 of the documentation
archive, promotes that archive (the candidate's, checked again), records it in publication.json and pushes the wiki;
an rc publishes /docs/<v-rc>/ only. It is never started by release: published, which a release published with
GITHUB_TOKEN does not fire. Only the wiki's deploy key WIKI_DEPLOY_KEY is passed to it. See
Writing documentation.
- Immutable releases: Settings → General → Releases. A published release then keeps its files and its tag.
-
A ruleset on the release tags: target
tag,refs/tags/v*, active. It restricts creation, refuses deletion and forbids force-push. -
npm run ship -- preflight-stable --candidate=<run>must then see both. Before the first 4.1.20 tag,ship tagrefuses to tag while immutable releases are off: release.yml's token cannot read that setting (it needs admin read), soship tagchecks it with the maintainer's own access.
4.1.x tags stay annotated (D30). From 4.2 they are signed with the maintainer's SSH key:
# in the engine repository
git config gpg.format ssh
git config user.signingkey ~/.ssh/id_ed25519.pub
npm run ship -- tag 4.2.0-rc.2 <sha> --candidate=<run> --sign --now- The key must also be registered on GitHub as a signing key (Settings → SSH and GPG keys).
- It must be in
.github/allowed_signers, added in a commit signed by that key (8cebea2, made by Claude at the maintainer's request: D37). That file is also the trust root of carried passes (ADR 0024). -
--key=<file>gives another key file thanuser.signingkey. - Each candidate rehearses the path with an ephemeral key (
signing-rehearsal): it signs a tag of the commit that is never pushed, checks it withgit verify-tag, then destroys the key.
Limit ·
signature-not-approvalWithid_ed25519, the signature proves the key was used, not that the maintainer approved: an approval is recorded explicitly (D37).
# in the engine repository
npm run ship -- preflight-stable --candidate=<run> [--version=4.2.0]It signs nothing, publishes nothing and changes no setting (tools/release/preflight.mjs).
It says, one check at a time, ok, missing (a step still to take) or blocked (something wrong):
- the signing key, local and registered;
-
allowed_signers; - immutability and the tag ruleset;
- the candidate's provenance;
- for each npm package: the archive (
npm publish --dry-run), the name, the bootstrap, the trusted publisher (stage-only), an OIDC publication proven, and the version not already published; - the publishing step's exact Node and npm;
- GO / NO-GO against the people's reports ("GO for 4.2 on a reduced scope" is read with D38 written).
It exits 1 only on a blocked.
| Command | What it does |
|---|---|
ship checks <pr> |
Waits for the pull request's checks; re-runs a failed job once; exit 1 if it fails again |
ship merge <pr> |
checks, then merges (a merge commit, the only method the ruleset allows); prints the SHA |
ship main <sha> |
Waits for main's ci run on that SHA to be green (re-run once) |
ship tag <version> <sha> --candidate=<run> [--now] [--sign [--key=<file>]] |
Tags (above), pushes, then watch
|
ship watch <version> |
Waits for the tag's ci run, then the release run; exits by its conclusion |
ship verify <version> |
The read-back (above) |
ship preflight-stable --candidate=<run> [--version=<x.y.z>] |
The stable preflight (above) |
ship chain <pr> <version> |
merge → tag → watch → verify; refused from 4.1.17, whose tags need a candidate run of the merge commit (merge, candidate, then tag) |
Each command is idempotent and says what it waits for.
ship watch exits 0 with the release run's success; ship verify <version> prints every file checked and exits 0;
the release page shows the files and "Immutable"; gh attestation verify <file> -R wanoo/quest-n-click passes for any
of its files.
| Symptom | Cause | Fix |
|---|---|---|
| release.yml: "names no candidate run" | The tag was made without --candidate
|
Tag the next version (or rc.N+1) with --candidate: the tag ruleset refuses deleting a tag |
| release.yml: "run … is not a green candidate started on main" | The run is a pull request's, another branch's, or red | Start candidate by hand on main for that SHA |
release.yml: "has no candidate-site" (or -surfaces, -signing, -pack, -sbom, -image) |
A candidate made before the step existed | Start a new candidate |
| "is published: a published release is never changed" | A rerun on a published tag | Nothing to do; a new version is a new tag |
ship tag refuses: immutable releases off |
The setting is not on | Turn it on (above), then tag |
| A failed RC | — | Tag rc.N+1 from a new candidate |
A tag v<version> (signed), a GitHub release with its files and attestations, .cache/pids/ship-*.pid locally;
from 4.5.0, the wiki and publication.json (through docs-publish). The release branch before it edits CHANGELOG.md,
docs/dev/LOG.md and docs/dev/passes/<version>.md.
A published release cannot be changed or removed: a defect is fixed in a new version. A draft is deleted by the next release run. A tag not yet released can be left alone and a new candidate tagged under the next pre-release number.
-
tools/release/ship.mjs,tools/release/candidate.mjs,tools/release/artifacts.mjs,tools/release/preflight.mjs,tools/release/signing.mjs. -
.github/workflows/release.yml,.github/workflows/candidate.yml. - D30, D37, D38; ADR 0024. Field passes, npm packages, Stability and support.
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.4.0 r1, documentation revision · révision de la documentation 8dc717c69acb62c4742b69c54a7e7dfb620f75d8.
Earlier versions and permalinks · versions précédentes et liens permanents: https://wanoo.github.io/quest-n-click/docs/
An edit made in this wiki is overwritten at the next publication · une modification faite dans ce wiki est écrasée à la publication suivante.
Edit the source · Modifier la source · Report an error · Signaler une erreur
Index (6)
Start (6)
Play (9)
Install (17)
- Requirements
- Install from the release archive
- Install from npm, once published
- Install from the repository to work on the engine
- doctor: checking your machine
- Image toolchain
- Audio toolchain: ffmpeg, fluidsynth, Furnace, uv
- Upgrade
- Upgrade notes: 4.4
- Upgrade notes: 4.3
- Upgrade notes: 4.2
- Upgrade notes: 4.1
- Upgrade notes: v3
- Upgrade notes: v2 to v3
- Coming from web-scumm
- Package reference
- Install troubleshooting
Create a game (55)
- Your first game
- Layout of a created project and of the engine repository
- The working loop
- Content principles and the identity contract
- Rooms
- Geometry: placement, walk zones, masks, layers, camera
- Something is visible but unreachable
- Reactions
- Game commands
- Conditions
- Flags, variables and events: which to use
- Custom code
- Characters
- Items and inventory
- World map
- Dialogues
- Scripts and events
- Moving characters: patrols and walks
- Exits
- Chapters and objectives
- State and saves: persistent or temporary, renaming an id, migrations
- Staging: wide rooms and animations
- The stage: lights, particles, floors
- Skin and fonts
- Title and credits
- Heroes: several characters and hero choice
- Built-in minigames
- A custom minigame
- Translations
- Music in stems
- Sound effects
- Voices: per language and per hero
- Images: prompts, cutting sheets, transparency, scale
- Asset provenance
- The storyboard, source of truth
- Puzzle design
- Classic mechanics
- Review pages
- DSL reference
- Checking your game
- What the solver proves
- Solver options
- Playtests
- Remix
- Speedrun for authors
- The sealed ending
- Production template
- Example games
- Working with an AI
- Build and release a game
- Publish on GitHub Pages
- The static game on Clever Cloud
- Other static hosts
- Sub-path, cache and service worker, incomplete deploys, going back
- Testing on a phone over the LAN
Local Studio (24)
- The local Studio: overview and launch
- Edit a room
- Placement and stage
- Preview, apply, undo
- Storyboard
- Assets and cutting
- Check: validator, solver, critical path, heat, health, coverage
- Before and after captures
- Semantic diff
- Play and replay a session
- Rule explainer
- Language and objectives
- Voices and the heroes × languages matrix
- Map
- Notes
- Remix in the Studio
- Music
- The Assistant: bring your own key
- Demo mode and studio-apply
- Local and LAN security
- Studio API
- Local MCP: setting up each client
- MCP tools
- Studio troubleshooting
Team Studio (21)
- What runs where
- Concepts: revision, transaction, span, lease, preview, build
- Starting as an invited dev
- Working together day to day
- Roles and actions: the full matrix
- Invitations
- Managing members
- Write refused on the hosted Studio: stale base revision (409)
- Write refused on the hosted Studio: span locked (423)
- Recovering after a lost lease, a disconnection or a stale preview
- Comments
- Map and semantic diff
- Publishing builds
- Remote MCP
- The game in Git
- Upgrading the engine of a hosted game
- Hosted assets
- The server's command line
- Health and metrics routes
- Trying it locally with compose.yml
- Known limits
Clever Cloud (23)
- Clever Cloud: overview, cost and prerequisites
- Apps and add-ons
- Domains and TLS
- Keycloak
- Generic OIDC and GitLab
- Configuration
- First deployment
- Backups
- RPO and RTO
- Restore the Studio on a fresh installation
- Redeploys
- Upgrading the server and checking state after a migration
- Limits of going back
- Monitoring
- Security
- Rotating keys
- Recovering admin access
- Changing domain or OIDC provider
- Worker, PostgreSQL or Cellar down
- Exporting the game and leaving the installation
- Retention
- Clever Cloud troubleshooting
- Costs and teardown
Reality Bridge and connectors (16)
- The Reality Bridge: what it is
- Quick start
- Guarantees
- Install and run the Bridge
- Backup and restore
- Several tenants
- Signals and the manifest
- The Bridge's command line
- Connectors overview
- Email connector
- Telnet connector
- SSH connector
- Open Badges connector
- The connector SDK
- Security: Biscuit and signatures
- Bridge and connector troubleshooting
Contribute (25)
- Welcome
- Developer setup
- The dev loop
- Before a pull request
- Conventions
- Writing documentation
- Architecture
- Code tour
- Import boundaries
- Which tests run when
- Mutation testing
- The coverage ratchet
- The quality baseline
- CI explained
- Field passes
- Benchmarks
- Releasing
- npm packages
- Decisions and ADRs
- Governance
- Bot pull requests
- Contributing with AI agents
- Security policy
- Code of conduct
- Licences and credits