Release runbook
August 27, 2026 · View on GitHub
The release workflow is the only supported way to publish @qualitymax/qmax-mcp.
A publish that does not come from it has no provenance attestation and no
gitHead, which breaks the chain the launch gate depends on — see
the 0.2.1 release-integrity incident.
Enforced gates
Two steps of this runbook are now checked by machine rather than trusted to a human reading it:
npm run checkrunsnpm run version:sync -- --checkfirst, so any drift betweenpackage.json,package-lock.json,server.json,smithery.yamlandsrc/metadata.tsfails both the Quality workflow and the release workflow'sverify-packageandpublishjobs.prepublishOnlyrunsscripts/guard-publish.cjs, which refuses to publish unless the process is a GitHub Actions run that requested--provenance.- The
versionnpm lifecycle script re-synchronizes and stages every metadata surface, sonpm version <patch|minor|major>cannot leave a half-bumped tree.
The publish guard is a speed bump, not a lock: npm publish --ignore-scripts
skips it. The lock is npm's per-package publishing setting — require a trusted
publisher, so the registry itself rejects workstation credentials. Configure it
at npmjs.com → the package → Settings → Publishing access.
Steps
-
Start from a clean, reviewed candidate commit and record its SHA in Linear.
-
Synchronize public metadata atomically:
npm run version:sync -- <semver> npm run version:sync -- --check npm run check npm run registry:preview -
Build the package, run
npm pack --json --ignore-scripts, and review the resulting file allowlist. Install that tarball in clean Node 22.13 and 24 environments;qmax-mcp --helpmust start successfully in each. -
Review dependency changes, the SPDX license field and
LICENSE,SECURITY.md,server.json, Smithery metadata, and the registry preview. -
Create a protected, immutable annotated tag named
v<package-version>for the recorded candidate SHA. Dispatch the release workflow from that tag, withrelease_tagset to the same tag. Thenpm-publishenvironment only acceptsv*tag runs. The workflow rejects a ref/input mismatch and tags whose commit is not reachable frommain; it then resolves the tag once, verifies the package version, and uses the resulting SHA in every job. It uses npm trusted publishing with provenance; it does not use a long-lived npm token. -
Publish the MCP Registry entry, from the same tag, after npm has the version: dispatch Publish to MCP Registry with
release_tagset to that tag. It repeats the immutable-tag gate, re-checks metadata synchronization and theserver.jsonschema, refuses to run untilnpm viewreports the exact version and amcpNamematchingserver.json, and confirms the live entry afterwards. It authenticates with GitHub OIDC, so there is no registry token in this repository. -
Publish the GitHub Release for the same tag, last, once npm and the registry both serve the version:
gh release create v<package-version> --verify-tag \ --title "qmax-mcp <package-version>" --notes-file <notes>Notes follow the published convention: highlights, an
npx -y @qualitymax/qmax-mcp@<version>install line, a link to the CHANGELOG anchor at that tag, and the merged pull requests. Say plainly when a change makes a result incomparable to the previous version, so nobody reads a scoring correction as an improvement in their own application. -
Record the package version, SHA, provenance link, registry metadata, test results, rollback owner, and all evidence URLs in the Linear ticket.
The registry entry is a pointer, not a copy: it advertises an npm version and carries no artifact of its own. Publishing it before npm has the version advertises something nobody can install, which is why it is a separate dispatch rather than a step inside the release workflow. The GitHub Release comes last for the same reason: its notes advertise an installable version, so publishing them first points readers at something they cannot yet install.
--verify-tag is not optional. Without it gh release create creates a missing
tag itself, which would attach the release to whatever main points at instead
of to the reviewed, published commit — and a lightweight tag created that way
would not satisfy the annotated-tag gate either workflow enforces. This step is
the one publication surface no workflow checks, so the flag is the check.
Rollback
If a published version needs to be withdrawn, stop further publication, record the affected version and reason in Linear, deprecate only that npm version with an upgrade message, and publish a tested patched version after a new security review. Never overwrite an existing npm version. If registry metadata is wrong, correct it through the registry workflow and preserve the incident evidence.
npm does not allow a version number to be reused once published, even after an unpublish, so a withdrawn version is spent: recover by releasing the next patch, not by reissuing the same number.