Release runbook
GuildControl MCP uses deliberately separate release operations for a credential-free first-publication candidate, normal staged npm publication, immutable OCI publication, an immutable GitHub Release, and MCP Registry registration. No operation contacts Discord or needs a Discord bot token.
Public-source preflight
Section titled “Public-source preflight”Changing the repository from private to public exposes its reachable Git history and Actions history, permits public forks, publishes repository activity, and disables existing push rulesets. Treat the change as an irreversible disclosure even though GitHub permits a later visibility change.
Before changing visibility:
- Create a mirror clone of the exact private remote outside the working repository. Enumerate every advertised ref, run strict Git object verification, and retain the mirror until the public transition and protections are verified.
- Run a reviewed credential scanner in full-redaction mode across every reachable commit, the selected current tree, every retained Actions log, and every retained artifact including nested archives. Classify every candidate from rule, path, and redacted context without printing the matched value. Rotate any real credential before proceeding.
- Inspect commit author metadata, historical filenames, repository issues, pull requests, releases, deployments, Actions variables and secret names, environments, and public-facing repository metadata. Confirm that no private identity, path, discussion, artifact, or log should remain hidden.
- Scan the current tree and every retained package for machine-local paths and model-, vendor-, client-, or harness-specific branding. Decide explicitly whether transparent historical references are acceptable. A zero-history-reference policy requires a separately authorized history rewrite with an external mirror backup and credential rotation where applicable.
- Run the complete release metadata, test, coverage, build, package, dependency, and container gates on the exact commit intended for public exposure. Require the remote CI gate to pass as well.
- Confirm that
CONTRIBUTING.md,CODE_OF_CONDUCT.md,SUPPORT.md, privacy-safe issue forms including operator questions and verified outcomes, the pull-request template,SECURITY.md, CODEOWNERS, and the release runbook are present and protected.
Repository prerequisites
Section titled “Repository prerequisites”Before any publication:
- Make
j-256/guildcontrolpublic. npm provenance and public GitHub attestations fail for a private source repository, and the workflow enforces this boundary. - Set the repository description exactly to
Safety-first MCP server for Discord with privacy-safe reads, audits, and reviewed administration. Replace its topics with the exact model- and harness-neutral topic setai-agents,automation,community-management,discord,discord-api,discord-bot,guildcontrol,least-privilege,mcp,mcp-server,model-context-protocol,moderation,security, andtypescript. Keep Issues enabled. Leave Discussions, Projects, and the wiki disabled until each has an owned maintenance purpose. Topic names are public even for a private repository, so apply this profile only after the visibility decision. - Create or re-enable protection for
mainafter the visibility change and require theCI gateand CodeQL checks. Require CODEOWNERS review for workflows, package metadata, registry metadata, release scripts, security policy, and community files. - Require the canonical documentation portal at
https://guildcontrol.lasers.appto match the release commit undernode scripts/check-public-documentation.mjs, and require itsPublish documentation portaldeployment to be green. Keep one-time hosting provisioning, credential rotation, domain cutover, rollback, and retirement procedures in the separate documentation portal operations guide. - Enable private vulnerability reporting and its maintainer notifications. Enable and verify Dependabot alerts, secret scanning, push protection, and code scanning; a skipped private-repository CodeQL run is not public-release evidence.
- Create a repository ruleset protecting
v*tags from deletion, update, or unreviewed creation. Enable repository-level immutable Releases before publishing any GitHub Release. Create a GitHub Actions environment namedrelease, require a human reviewer, prevent self-review when the repository plan supports it, allow deployments only from protected tags, and do not allow administrators to bypass the review gate. - Enable two-factor authentication on the npm maintainer account.
- Confirm that the unscoped npm name
guildcontrolis either available for the first publication or already owned by thej-256maintainer account for later releases. - Install npm 11.15 or newer for human
npm stagereview commands. The workflow uses a fixed Node.js release whose bundled npm satisfies this floor. - Confirm that the repository owner can administer the
guildcontrolcontainer package underj-256. The first image version is created by the protected workflow and requires one explicit visibility review before it can be made public.
The workflow must be dispatched at the same tag supplied as its input. This makes GitHub and npm provenance identify the commit that produced the package rather than the default branch's dispatch commit. The workflow accepts only an existing stable vMAJOR.MINOR.PATCH tag that points at the checked-out commit and is an ancestor of origin/main. Package metadata, the lockfile, source constants, server.json, and the immutable icon URL must all contain the same version. Every release operation also requires the canonical public documentation manifest to contain that version and exact hashes for the release's documentation source frontier.
First npm publication
Section titled “First npm publication”npm requires a package to exist before staged or trusted publishing can be configured. Do not bridge that boundary with a token that bypasses two-factor authentication. The first version uses a credential-free protected workflow to produce an attested candidate, followed by an interactive maintainer publication:
- Create and push the exact release tag after its commit has passed CI on
main. - Dispatch
release.ymlat that tag with operationcandidateand the same exact tag as input:
gh workflow run release.yml --ref vMAJOR.MINOR.PATCH -f operation=candidate -f tag=vMAJOR.MINOR.PATCH- Approve the protected
releaseenvironment only after confirming the exact tag, source commit, and absent npm, OCI, MCP Registry, and GitHub Release versions. The workflow receives no npm credential, verifies source and supply-chain state, reconstructs the npm archive and MCPB twice, verifies their installed behavior, signs both artifacts, their SPDX SBOMs, and catalog evidence with GitHub artifact attestations, and retains the exact files. - Download the completed workflow artifact, inspect its npm archive, MCPB, catalog evidence, dependency notices, privacy policy, and SBOMs, and verify the artifact attestations before publication:
gh run download RUN_ID --name release-evidence-candidate-vMAJOR.MINOR.PATCHgh attestation verify guildcontrol-MAJOR.MINOR.PATCH.tgz \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runnersgh attestation verify guildcontrol-MAJOR.MINOR.PATCH.tgz \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runners \ --predicate-type https://spdx.dev/Document/v2.3gh attestation verify guildcontrol-MAJOR.MINOR.PATCH.mcpb \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runnersgh attestation verify guildcontrol-MAJOR.MINOR.PATCH.mcpb \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runners \ --predicate-type https://spdx.dev/Document/v2.3- From an authenticated maintainer workstation, publish that exact tarball and complete npm's interactive passkey challenge. Package metadata enables automatic provenance for normal OIDC releases, so this exceptional local first publication must explicitly disable it. The protected GitHub artifact attestations verified above remain the first version's public provenance:
npm login --auth-type=webnpm whoaminpm publish ./guildcontrol-MAJOR.MINOR.PATCH.tgz --provenance=false- After npm makes the version available, require its published SHA-512 integrity to match the candidate before any OCI or MCP Registry operation:
node scripts/check-published-artifacts.mjs \ --tarball ./guildcontrol-MAJOR.MINOR.PATCH.tgz \ --expect-package matching \ --expect-npm matching \ --expect-oci missing \ --expect-registry missingThe first version's public provenance is the GitHub artifact attestation bound to the protected workflow, tag, and source commit. Publishing the exact tarball preserves that byte identity. npm OIDC provenance begins with later stage-only trusted publications because npm cannot configure that trust relationship before the package exists.
Configure trusted staged publishing
Section titled “Configure trusted staged publishing”Immediately after the first publication, configure the npm package's trusted publisher with these exact boundaries:
- Provider: GitHub Actions
- Repository owner:
j-256 - Repository:
guildcontrol - Workflow:
release.yml - Environment:
release - Permission: allow staged publishing and disallow direct publishing
Set package publishing access to require two-factor authentication and disallow tokens. The trusted publisher may run npm stage publish; a human still supplies 2FA for npm stage approve. No npm token belongs in any release workflow.
Prepare a version
Section titled “Prepare a version”- Update
versioninpackage.json, the lockfile root,CONNECTOR_VERSIONinsrc/constants.ts, the npm version and OCI image tag inserver.json, the runtimeVERSIONdefault inDockerfile, the MCPB manifest version, and every versioned public URL including the icon, privacy support link, and MCPB release asset. - Set
mcpb/reproducible-build.jsonto the release's reviewed UTC build epoch. Runnode scripts/build-mcpb.mjs --allow-registry-mismatch, copy the reported raw SHA-256 into the MCPB package inserver.json, then runnpm run mcpb:verifyand require the digest to remain identical. The normal verifier fails when the generated bytes differ from Registry metadata; the mismatch option is only for this reviewed release-preparation step. The URL must nameguildcontrol-MAJOR.MINOR.PATCH.mcpbunder the exactvMAJOR.MINOR.PATCHGitHub Release. - Run the complete local gate:
npm run deps:lockednpm run metadata:checknpm run config:schema:checknpm run typechecknpm testnpm run test:coveragenpm run buildnpm run pack:verifynpm run mcpb:verifynpm run container:verifynpm run container:index:verifynpm run security:checknpm run --silent sbom -- --output sbom.spdx.json- Commit the version as a release preparation change and let every CI job pass on
main, includingPublish documentation portal. - Run
node scripts/check-public-documentation.mjsfrom that exact commit and require an exact result. - Create the exact
vMAJOR.MINOR.PATCHtag on that commit and push the tag.
Do not include sbom.spdx.json from the local command in the commit. The workflow reconstructs its own SBOM and release archive.
Stage and approve npm
Section titled “Stage and approve npm”- Dispatch the protected workflow:
gh workflow run release.yml --ref vMAJOR.MINOR.PATCH -f operation=stage -f tag=vMAJOR.MINOR.PATCH- Review the workflow's npm archive, MCPB, deterministic catalog evidence, SPDX artifacts, and GitHub attestation summary. The workflow verifies source, dependency locks, registry signatures, vulnerabilities, the public versioned icon, official MCP and MCPB manifest validation, byte-for-byte repeatability, installed CLI behavior, an unpacked bundle handshake, and a content-free installed MCP handshake before staging.
- Inspect the private npm stage from a maintainer workstation:
npm stage list guildcontrolnpm stage view STAGE_IDnpm stage download STAGE_ID- Compare the downloaded stage with the workflow artifact. Reject it if any digest or metadata differs.
- Approve with human 2FA:
npm stage approve STAGE_IDIf the candidate is wrong, use npm stage reject STAGE_ID, fix the source, and create a new version. A staged semantic version cannot be reused until the rejected stage is removed.
Publish and verify OCI
Section titled “Publish and verify OCI”Publish the OCI image only after npm exposes the exact approved version:
gh workflow run release.yml --ref vMAJOR.MINOR.PATCH -f operation=image -f tag=vMAJOR.MINOR.PATCHThe protected image operation reconstructs the npm archive from the tag and requires it to match the public npm integrity before inspecting the exact OCI tag through an authenticated registry request. If the tag is absent, it builds and publishes linux/amd64 and linux/arm64 manifests under ghcr.io/j-256/guildcontrol:MAJOR.MINOR.PATCH. Both stages use the same reviewed digest-pinned Node.js base. The image runs as an unprivileged user, defaults to credential-free catalog mode, and contains only the compiled server, production dependencies, package metadata, and license.
Before publishing, the workflow exports and validates the complete multi-architecture OCI layout with digest-pinned BuildKit, architecture-emulation, and SBOM-generator images. It verifies every referenced blob and requires the exact platform, annotation, configuration, layer-binding, provenance, and SPDX structure. BuildKit evidence may use its legacy compatibility image config or its OCI artifact encoding. The artifact form must name the exact runnable manifest as its subject and use OCI's canonical empty JSON config descriptor. The release build then binds BuildKit provenance and SPDX records for both platform manifests into the root index and pushes signed GitHub provenance for that exact root digest. It requires the public index to match the preflight invariants, runs the pulled image with a read-only root filesystem and no network or Linux capabilities, compares its catalog evidence to the source contract, and verifies the signed root claim against the exact repository, workflow, tag ref, and source commit.
BuildKit mode=max identifies the provenance record's detail level, not a maximum security or SLSA assurance level. The provenance, SBOM, and attestation boundaries distinguish the build receipt, detected software inventory, signed artifact binding, and guarantees that still require independent review.
GitHub creates a new personal container package as private by default. On the first image publication, the workflow may publish and attest the immutable tag and then fail its anonymous-read check with a recovery instruction. Open the package settings, confirm that the source repository is linked, review the package contents and permissions, and change visibility to Public. Visibility changes are consequential and may be irreversible, so do this only after the review. Rerun the same protected image operation. It detects the existing exact tag, does not overwrite or re-attest it, and completes only if the now-public digest and behavior match the release.
If the exact tag already exists but any digest, platform, annotation, image configuration, attestation, or runtime proof differs, the operation fails closed. Fix the source and publish a new semantic version; never replace a published tag.
Publish the immutable GitHub Release
Section titled “Publish the immutable GitHub Release”After npm and the public OCI image expose the same exact version, dispatch:
gh workflow run release.yml --ref vMAJOR.MINOR.PATCH -f operation=github-release -f tag=vMAJOR.MINOR.PATCHThe read-only release job reconstructs the npm archive, MCPB, catalog evidence, and SPDX SBOM, verifies the package and image as one public identity, and requires the Registry version to be absent or already exact. It signs the reconstructed evidence and retains it for the publisher job. The dependent publisher job alone receives contents: write, plus read-only access to that run's artifacts and GitHub attestations. It installs the exact SHA-256-pinned GitHub CLI archive, downloads only that run's evidence, resolves the protected tag to the exact source commit, and generates deterministic release notes and SHA256SUMS. GitHub does not expose its repository-level immutable-Releases setting to GITHUB_TOKEN, so the protected environment reviewer must confirm that setting before approving this job; automation receives no standing repository-administration credential.
An absent Release is created as a draft. An existing draft may be reconciled only for the exact asset set below, then its tag, title, notes, asset names, sizes, SHA-256 digests, download URLs, and source commit are verified before publication:
guildcontrol-MAJOR.MINOR.PATCH.tgzguildcontrol-MAJOR.MINOR.PATCH.mcpbcatalog-evidence.jsonrelease-notes.mdsbom.spdx.jsonSHA256SUMS
Publishing locks the tag and assets when repository-level immutable Releases remain enabled and creates GitHub's Release attestation over the tag, commit, and assets. GitHub still permits the displayed Release title and body to be edited, so release-notes.md is the canonical immutable copy and is covered by SHA256SUMS. The workflow waits for the immutable postcondition, verifies the Release attestation and every asset attestation, downloads every public asset, compares each byte-for-byte with the protected evidence, and verifies the checksum manifest. A matching immutable Release is a successful no-op. A mutable published Release, mismatched immutable Release, prerelease, unexpected asset, or draft that cannot be reconciled exactly fails closed. The workflow never deletes a Release or an unexpected asset.
Register the promoted version
Section titled “Register the promoted version”Only after the immutable GitHub Release exposes the exact MCPB bytes and digest, dispatch the final protected operation:
gh workflow run release.yml --ref vMAJOR.MINOR.PATCH -f operation=register -f tag=vMAJOR.MINOR.PATCHThe register operation reconstructs the npm archive, MCPB, catalog evidence, notes, checksums, and SBOM from the tag. It requires the npm archive's SHA-512 to equal published npm integrity, requires the public OCI index and every platform configuration to match the same version and source commit, and verifies the exact immutable GitHub Release and every public asset byte before Registry authentication. It then downloads MCP Registry publisher v1.8.1 from the official release, verifies the pinned Linux archive SHA-256, validates server.json, authenticates with GitHub OIDC, and publishes only when the exact Registry version is absent.
Metadata checks require the npm entry to pass one config-file argument, the OCI entry to use one read-only config mount and hardened operational command, and the MCPB entry to name the exact immutable Release URL and SHA-256. Only the MCPB entry asks for the non-secret config file and sensitive bot token; the runtime policy remains inside that selected file. An already matching Registry entry is a successful no-op. Existing mismatched metadata, mutable or mismatched Release state, or altered public asset fails closed.
Independent verification
Section titled “Independent verification”Verify the immutable GitHub Release, download its exact assets, verify each local asset against GitHub's Release attestation, and check the checksum manifest:
mkdir guildcontrol-releasegh release verify vMAJOR.MINOR.PATCH --repo j-256/guildcontrolgh release download vMAJOR.MINOR.PATCH --repo j-256/guildcontrol --dir guildcontrol-releasegh release verify-asset vMAJOR.MINOR.PATCH guildcontrol-release/guildcontrol-MAJOR.MINOR.PATCH.tgz --repo j-256/guildcontrolgh release verify-asset vMAJOR.MINOR.PATCH guildcontrol-release/guildcontrol-MAJOR.MINOR.PATCH.mcpb --repo j-256/guildcontrolgh release verify-asset vMAJOR.MINOR.PATCH guildcontrol-release/release-notes.md --repo j-256/guildcontrol(cd guildcontrol-release && shasum -a 256 -c SHA256SUMS)gh release verify establishes GitHub's signed binding among the immutable tag, source commit, and complete asset digest set. gh release verify-asset establishes that the local file is one exact attested asset. The checksum manifest makes the package, MCPB, catalog evidence, canonical notes, and SBOM directly comparable with ordinary local tools. These integrity checks establish identity and origin, not freedom from defects, vulnerabilities, malicious source, or scanner omissions.
Download the exact npm package and verify both provenance and its SBOM attestation:
npm pack guildcontrol@MAJOR.MINOR.PATCHgh attestation verify guildcontrol-MAJOR.MINOR.PATCH.tgz \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runnersgh attestation verify guildcontrol-MAJOR.MINOR.PATCH.tgz \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runners \ --predicate-type https://spdx.dev/Document/v2.3gh attestation verify catalog-evidence.json \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runnersgh attestation verify guildcontrol-release/guildcontrol-MAJOR.MINOR.PATCH.mcpb \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runnersgh attestation verify guildcontrol-release/guildcontrol-MAJOR.MINOR.PATCH.mcpb \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runners \ --predicate-type https://spdx.dev/Document/v2.3From an isolated consumer directory, install the downloaded archive without lifecycle scripts and save the credential-free catalog evidence:
npm install --ignore-scripts ./guildcontrol-MAJOR.MINOR.PATCH.tgz./node_modules/.bin/guildcontrol catalog --check --json > catalog-evidence.jsonThe evidence must be identical across repeated runs of the same installed archive. Review its exact inventories and accounting fields, including complete per-tool authentication, connector policy, Discord permission, conditional case, Gateway intent, hierarchy, curated setup, access-lifecycle, and live-verification contracts with zero unknown requirement entries. Preserve its contractDigest for contract comparison and its separate safetyResourceDigest for focused safety-guidance comparison. The report must state that credentials, Discord execution, Gateway access, telemetry export, activity persistence, authority grants, and target-readiness claims are disabled.
Authenticate the container client, pull the exact image, verify its signed root provenance from the OCI registry, inspect the root-bound per-platform SPDX records, and run its credential-free catalog under the recommended restrictions:
docker pull ghcr.io/j-256/guildcontrol:MAJOR.MINOR.PATCHgh attestation verify oci://ghcr.io/j-256/guildcontrol:MAJOR.MINOR.PATCH \ --repo j-256/guildcontrol \ --signer-workflow j-256/guildcontrol/.github/workflows/release.yml \ --source-ref refs/tags/vMAJOR.MINOR.PATCH \ --deny-self-hosted-runners \ --bundle-from-ocidocker run --rm -i \ --network=none \ --read-only \ --cap-drop=ALL \ --security-opt=no-new-privileges:true \ --pids-limit=64 \ ghcr.io/j-256/guildcontrol:MAJOR.MINOR.PATCH catalog --check --json > container-catalog-evidence.jsonReview the image index with docker buildx imagetools inspect ghcr.io/j-256/guildcontrol:MAJOR.MINOR.PATCH. It must expose only linux/amd64 and linux/arm64 as runnable platforms, retain the reviewed index description and source annotations, and bind BuildKit evidence to both manifests. The container catalog evidence must be byte-identical across repeated runs and match the installed npm package's evidence.
From a checkout of the same tag, compare npm and MCP Registry state with the same code used by release automation:
node scripts/check-published-artifacts.mjs \ --tarball guildcontrol-MAJOR.MINOR.PATCH.tgz \ --expect-package matching \ --expect-npm matching \ --expect-oci matching \ --expect-registry matchingThe exact registry response is also available from https://registry.modelcontextprotocol.io/v0.1/servers/io.github.j-256%2Fguildcontrol/versions/MAJOR.MINOR.PATCH.
Failed or compromised releases
Section titled “Failed or compromised releases”- Reject an unapproved npm stage and publish a corrected new version
- Deprecate a flawed public npm version and publish a corrected new version rather than overwriting it
- Publish a corrected OCI image under a new semantic version rather than overwriting or reusing an existing tag
- Leave a malformed draft unpublished for explicit maintainer cleanup; publish a corrected semantic version if an immutable GitHub Release is wrong
- Revoke a suspected credential immediately and preserve workflow logs without copying secrets into an issue
- Use npm unpublish only for a confirmed security emergency and after evaluating downstream breakage
- Publish corrected MCP Registry metadata under the corrected package version; never claim mismatched metadata is equivalent
Platform references
Section titled “Platform references”- npm staged publishing
- npm trusted publishers
- npm scoped public package publication
- npm bypass-2FA token restrictions
- GitHub workflow event refs and SHAs
- GitHub repository visibility consequences
- GitHub private vulnerability reporting
- GitHub security and analysis settings
- GitHub artifact and SBOM attestations
- GitHub immutable Releases
- GitHub Release integrity verification
- GitHub container registry
- GitHub package visibility and access
- Docker multi-platform images
- OCI annotations with Buildx
- BuildKit attestation storage
- MCPB format, manifest, and tooling
- MCP Registry publishing quickstart
- MCP Registry publisher commands
Canonical source: docs/releasing.md
Documentation generated for guildcontrol@0.0.0. Canonical source and edit history remain in the public repository. GuildControl is an independent project and is not affiliated with or endorsed by Discord Inc. Discord is used only to identify the platform that GuildControl connects to.