Release runbook
GuildControl MCP uses two normal operator phases: protected staged npm publication, followed by protected promotion through immutable OCI publication, an immutable GitHub Release, and MCP Registry registration. A credential-free first-publication candidate and a narrowly bounded GitHub Release recovery operation remain available outside the normal path. 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://docs.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 that restricts creation of
v*tags to the repository owner and prevents every update or deletion. 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 protectedv*tags, and do not allow administrators to bypass the review gate. - Create a GitHub Actions environment named
release-credentialswith no reviewer, allow deployments only from protectedv*tags, and add only theMCP_REGISTRY_DNS_PRIVATE_KEYenvironment secret. This environment is an unattended credential boundary behind the separately reviewed promotion dependency, not an approval gate. Do not use a repository secret for the Registry key. - Enable two-factor authentication on the npm maintainer account.
- Confirm that the unscoped npm name
guildctlis 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.
Candidate, stage, and promote operations must be dispatched at the same tag supplied as their input. This makes GitHub and npm provenance identify the commit that produced the package rather than the default branch's dispatch commit. These operations accept 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 ordinary release operation also requires the canonical public documentation manifest to contain that version and exact hashes for the release's documentation source frontier. The narrowly bounded publisher recovery operation described below is the only exception: it executes corrected workflow code from a separate protected audit tag while checking out and verifying the unchanged stable source tag and previously attested evidence.
Registry DNS credential bootstrap and rotation
Section titled “Registry DNS credential bootstrap and rotation”The canonical MCP Registry name is app.lasers.guildcontrol/discord. MCP Registry DNS authentication reverses the verified domain, so the proof at guildcontrol.lasers.app grants only app.lasers.guildcontrol/* and descendant namespaces. Do not move the proof to lasers.app: an apex proof would grant the much broader app.lasers/* organization namespace.
Generate one Ed25519 key with OpenSSL 3, retain the 32-byte private seed as exactly 64 lowercase hexadecimal characters in a protected private credential store, and publish only the derived public key. The DNS record at exactly guildcontrol.lasers.app has this shape:
v=MCPv1; k=ed25519; p=BASE64_PUBLIC_KEYThe proof belongs at the product hostname itself, not _mcp-auth.guildcontrol.lasers.app or another selector. Keep exactly one MCP proof record at that name. The Registry's verifier may try a stale record first, so overlapping old and new proofs can make authentication fail even when one key is valid.
Create the credential environment and load the raw seed without placing it in shell arguments or logs:
gh api --method PUT repos/j-256/guildcontrol/environments/release-credentials \ -F 'deployment_branch_policy[custom_branch_policies]=true' \ -F 'deployment_branch_policy[protected_branches]=false'gh api --method POST repos/j-256/guildcontrol/environments/release-credentials/deployment-branch-policies \ -f name='v*' \ -f type=tagtr -d '\r\n' < PRIVATE_KEY_FILE | gh secret set MCP_REGISTRY_DNS_PRIVATE_KEY \ --repo j-256/guildcontrol \ --env release-credentialsVerify through the GitHub API that the environment has no reviewers, its only deployment policy is the v* tag pattern, and its only secret name is MCP_REGISTRY_DNS_PRIVATE_KEY. Verify public DNS independently with dig +short TXT guildcontrol.lasers.app. Never print the private file, command-substitute it into a logged command, upload it as workflow evidence, or put it in the public repository.
For ordinary restoration after loss of the GitHub secret, reload the retained seed over stdin and verify the environment metadata. For suspected compromise or loss of every seed copy, generate a new key, replace the DNS TXT value without overlap, wait for public resolvers to return only the new proof, replace the Actions secret over stdin, and run a Registry login and exact no-op verification. Domain control is the root of trust, so loss of the seed does not lose the namespace. A copied seed can authenticate until cached DNS proof expires; inspect Registry versions created during that interval and report any unauthorized entry.
Approval and credential risk
Section titled “Approval and credential risk”The release environment remains the human decision boundary. Stage and promotion are separate workflow runs, so approving npm cannot authorize later public channels. Within promotion, the approval job has no repository permission; OCI, GitHub Release, and Registry jobs depend on it while retaining separate least-privilege tokens. Combining those publishers into one job would reduce YAML but would give every step the union of package, repository, attestation, and Registry authority.
The release-credentials environment deliberately has no reviewer. Adding one would make the dependency-chained Registry job ask for a second promotion approval after the operator already approved the exact preflight. Its residual risk is that workflow code on an allowed tag can request the seed. The v* ruleset therefore restricts tag creation to the repository owner and prevents tag movement or deletion, while protected main review governs the workflow code that can be tagged. A repository administrator can still weaken those controls; suspected administrative bypass requires DNS key rotation and an audit of environment, ruleset, workflow, and Registry changes.
The seed is stored directly rather than behind a hosted KMS because this one credential grants only the product namespace and DNS provides a simple rotation root. A hosted KMS would remove the raw seed from Actions but add another provider, workload identity, policy surface, billing dependency, and outage mode. Reconsider that tradeoff if more products share a key, more maintainers can trigger release workflows, or Registry publication becomes frequent enough to justify operating an external signer.
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- Confirm that the ungated candidate job completed for the exact tag and source commit with an absent npm package, OCI tag, and MCP Registry version. The candidate receives no npm, package, repository-write, or Registry credential, so evidence generation does not consume an operator approval. It 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 guildctl-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 guildctl-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 ./guildctl-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 ./guildctl-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.
- After the published
guildctlpackage passes the matching checks, deprecate the superseded package without removing its release:
npm deprecate 'guildcontrol@*' 'Package renamed to guildctl. Run npx guildctl to get started.'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 preflight job's npm archive, MCPB, deterministic catalog evidence, SPDX artifacts, and GitHub attestation summary. The preflight 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.
- Approve the
releaseenvironment after confirming the exact tag, source commit, and absent npm version, OCI tag, and Registry version. The gated stage job has only read access to the retained evidence and OIDC authority for npm, reverifies the tag and public state, and submits that exact tarball to npm's private staging area. - Inspect the private npm stage from a maintainer workstation:
npm stage list guildctlnpm 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.
Promote OCI, GitHub Release, and MCP Registry
Section titled “Promote OCI, GitHub Release, and MCP Registry”After npm exposes the exact approved version, dispatch one promotion run:
gh workflow run release.yml --ref vMAJOR.MINOR.PATCH -f operation=promote -f tag=vMAJOR.MINOR.PATCHThe ungated preflight reconstructs and attests the npm archive, MCPB, catalog evidence, and SPDX SBOM. It requires public npm integrity to match, permits OCI and Registry state only when absent or already exact, and retains the evidence before asking for approval. Approve the release environment once after reviewing the exact tag, source commit, npm integrity, artifact attestations, absent-or-matching downstream state, and repository-level immutable-Releases setting. The approval job itself has no repository permission.
The dependent image job reconstructs the npm archive from the tag and requires it to match public npm 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 promote 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 image tag already exists but any digest, platform, annotation, image configuration, attestation, or runtime proof differs, the image job fails closed and the GitHub Release and Registry jobs do not run. Fix the source and publish a new semantic version; never replace a published tag.
After exact image verification, the dependent GitHub Release 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 the promotion 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 promotion reviewer must confirm that setting before approval; 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:
guildctl-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.
Only after the immutable Release and every public asset verify does the Registry job receive the release-credentials environment. It reconstructs notes and checksums, verifies public npm and OCI identity, verifies the exact immutable Release again, downloads MCP Registry publisher v1.8.1 from the official release, verifies the pinned Linux archive SHA-256, validates server.json, and authenticates the product namespace with the DNS Ed25519 seed. It 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.
Recover a failed publisher job
Section titled “Recover a failed publisher job”Use the recovery operation only when a promotion preflight completed and retained exact evidence, but its dependent github_release job failed before completing the immutable Release. Fix the workflow defect on main through the normal pull-request and required-check path. Do not move the release tag, replace the retained artifact, or publish locally.
Create and push a new annotated recovery audit tag on the exact green fix commit. The tag must use the form vMAJOR.MINOR.PATCH-recovery.N, where N starts at 1 and increases for each distinct recovery attempt. Retain every recovery tag as public audit evidence and never move or delete one.
Dispatch the corrected workflow at that recovery tag while naming the original stable release tag and failed run:
gh workflow run release.yml \ --ref vMAJOR.MINOR.PATCH-recovery.N \ -f operation=github-release-recovery \ -f tag=vMAJOR.MINOR.PATCH \ -f evidence_run_id=FAILED_RUN_IDThe protected recovery job checks that its execution ref is a distinct recovery tag whose commit is on main, checks that the source release tag still resolves to the exact package source, and accepts only a completed failed release.yml workflow-dispatch run from that source tag and commit. It downloads only that run's exact release-evidence-promote-vMAJOR.MINOR.PATCH artifact, rejects missing, duplicate, expired, oversized, cross-repository, cross-tag, or cross-commit evidence, and verifies the npm archive, MCPB, catalog, and both SPDX claims against the release workflow, source ref, source commit, and GitHub-hosted runner boundary. The unchanged source checkout reconstructs the deterministic evidence, while release inspection and verification run from the exact protected recovery audit tag after its package version is proven equal to the source version. This separation lets a reviewed recovery fix correct publisher validation without changing the release source or retained bytes. The job then repeats the package, npm, OCI, MCP Registry, release-note, checksum, draft, immutable-Release, public-download, and asset-attestation checks used by the normal publisher job.
Approve the release environment only after reviewing the recovery commit, audit tag, original release tag and commit, failed run ID and logs, retained artifact, and repository-level immutable-Releases setting. If the prior evidence is unavailable or fails any binding, or if public state is mutable or mismatched, do not weaken recovery validation. Publish a corrected semantic version instead.
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/guildctl-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 guildctl@MAJOR.MINOR.PATCHgh attestation verify guildctl-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 guildctl-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 ./guildctl-MAJOR.MINOR.PATCH.tgz./node_modules/.bin/guildctl 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 guildctl-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/app.lasers.guildcontrol%2Fdiscord/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
- Rerun the same promotion after a transient downstream failure; every already matching immutable target is a no-op and every mismatch fails closed
- Leave a malformed draft unpublished for explicit maintainer cleanup; publish a corrected semantic version if an immutable GitHub Release is wrong
- Use the protected recovery operation only for exact retained evidence from a failed GitHub Release publisher job; never move the stable release tag or substitute local evidence
- Rotate a suspected Registry key through DNS and the credential environment, inspect versions created during DNS cache expiry, 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
- GitHub deployment environments
- GitHub repository rulesets
- Docker multi-platform images
- OCI annotations with Buildx
- BuildKit attestation storage
- MCPB format, manifest, and tooling
- MCP Registry publishing quickstart
- MCP Registry authentication
- MCP Registry publisher commands
Canonical source: docs/releasing.md
Documentation generated for guildctl@0.3.1. 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.