Skip to content

Release from candidates: rc-* tags deploy staging, v* tags publish the crates and the local MCP - #204

Merged
aterga merged 3 commits into
mainfrom
claude/rc-release-flow
Sep 24, 2026
Merged

aterga merged 3 commits into
mainfrom
claude/rc-release-flow

Conversation

@aterga

@aterga aterga commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A release now takes two human actions, both tag pushes, and one host.

Action Tag What runs
Cut a candidate rc-0.6.0-1 on a commit of main deploy-candidate.yml: verify (the tag names the version every Cargo.toml carries, the commit is on main), deploy to staging, publish a GitHub prerelease carrying the deployed binary
Promote v0.6.0 on the same commit publish-crate.yml publishes imcp2-core and imcp2 to crates.io; v-release.yml (dist) ships imcp2-local's archives, installers and Claude Desktop bundle on the v0.6.0 release page

Nothing in this repository deploys production: it embeds the published crate. Staging moves only on candidates, never on a merge to main, so the revision under test stays put while it is tested. A failed candidate is followed by a fix on main and rc-0.6.0-2; nothing is bumped after a candidate, so the promoted commit is the tested one. publish-crate.yml refuses a commit unless an rc-X.Y.Z-N tag on it has its prerelease with the deployed binary, which exists only if that candidate's staging deploy succeeded (a v tag pushed too early fails and is re-run once the deploy is done).

Why rc- rather than v0.6.0-rc.1: dist matches a tag's version against Cargo.toml exactly, so a prerelease suffix that the manifests do not carry fails its plan (verified locally with dist 0.31.0), and both dist's and the crate workflow's v triggers would otherwise catch candidates. With tag-namespace = "v", dist's trigger matches v0.6.0 and ignores rc-*, release-* and the old imcp2-local-v* tags; unset, it would also match release-2026-09-24-0.5.0 and fail to parse it. The regenerated workflow differs from the old one in its trigger pattern alone. dist's workflow is not gated on the candidate (it cannot be without hand-editing the generated file); the crate publish, the irreversible step, is.

Related issues

Follows the discussion after #203. Replaces the release-* flow, which no longer described what happens (production is deployed from the gateway repository).

Changes

  • .github/workflows/deploy-candidate.yml (new) — rc-* tags and a rollback dispatch (an earlier rc-* tag or a full SHA, validated as before). Adds a verify job; the deploy and the release-page publish are the ones deploy-release.yml had, the page now a prerelease and its binary the marker promotion checks. Environment staging, the DEPLOY_* secrets.
  • .github/workflows/deploy.yml, .github/workflows/deploy-release.yml — removed.
  • .github/workflows/publish-crate.yml — header describes promotion; the version check covers imcp2-local too; new step "Check the tag promotes a deployed candidate" (reads the candidate's prerelease with the workflow token; the verify job's permissions are unchanged); the release environment gate is unchanged.
  • dist-workspace.toml — tag-namespace = "v"; .github/workflows/imcp2-local-release.yml regenerated as v-release.yml (trigger pattern is the only difference).
  • .github/scripts/build-mcpb.sh, imcp2-local-mcpb.yml, imcp2-local-install-note.yml — accept vX.Y.Z tags and name v-release.yml as the attestation signer.
  • deploy-native.yml — header names its one caller.
  • Docs: CONTRIBUTING.md ("Releasing" rewritten as the two-step flow), deploy/native/README.md (one host, candidates, promotion, rollback; secrets table without the production column; approval-gate section), crates/imcp2-local/README.md (releases/latest resolves directly now that candidates are prereleases; signer workflow; maintainers' section; a note on installing the pre-0.6.0 imcp2-local-v* releases, which latest does not resolve to and which imcp2-local-release.yml attested), README.md, SECURITY.md, docs/anthropic-directory-submission.md, crates/imcp2-local/Cargo.toml comment.

Repository settings to change alongside (not code):

  • Extend the protected-tag ruleset to rc-* (it covers v*; imcp2-local-v* can drop out).
  • The production environment and the PROD_* secrets are no longer read by any workflow and can be deleted.
  • Until the first v0.6.0 release exists, releases/latest still points at the newest release-* page. Marking the old release-* releases as prereleases, or waiting for 0.6.0, fixes that; the README's note covers installing 0.5.0 meanwhile.
  • crates.io trusted publishing is untouched: publish-crate.yml keeps its name and environment.

First version under the scheme: 0.6.0, cut by a version-bump PR, then rc-0.6.0-1.

Testing

  • dist generate from the new config; diff against the previous generated workflow is the trigger pattern only
  • dist plan --tag v0.5.0 passes on the new config; dist plan --tag rc-0.5.0-1 fails, as intended and as the trigger prevents
  • Every workflow file parses as YAML; bash -n on the bundle script
  • The promotion check run against a stubbed gh and a repository with three candidate tags: a commit with one undeployed and one deployed rc-0.6.0-N promotes; a commit whose only candidate has no prerelease, or has candidates for another version only, is refused
  • .github/scripts/scan-internal-identifiers.sh origin/main...HEAD — clean (re-run on bf66af4)
  • CI green on 96564ae (test, End-to-end, Formatting, Scan, dist plan, bot policies); bf66af4 changes one README sentence
  • No Rust or dashboard code changes; cargo test and npm test unaffected.

Not exercised here: an actual rc-* push against staging. The first candidate will be the live test of deploy-candidate.yml; its deploy and publish jobs are the previous workflow's, moved.

Review rounds (Copilot): round 1, on 6206f2c, found that the promotion check proved only that a candidate tag existed (fixed in 96564ae: the candidate's prerelease with the deployed binary is required) and that the local crate's install instructions did not fit the pre-0.6.0 releases (fixed in 96564ae: a note on installing those). Round 2, on 96564ae, posted no findings; its summary noted the local crate's README said the version check runs "before the tag can exist", which a tag-triggered workflow cannot do (fixed in bf66af4: before the staging deploy and the prerelease).

Checklist

  • I have read the Contributing guidelines.
  • Docs (README / comments) updated for any user-visible change.
  • No secrets, credentials, or internal-only information are included.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Xd7VT72Qt16qiAJynu9EKj

…s and the local MCP

Two human actions now cut a release, both tag pushes. An `rc-X.Y.Z-N` tag on
a commit of main deploys that commit to staging and publishes a GitHub
prerelease carrying the deployed binary; the matching `vX.Y.Z` tag on the same
commit publishes imcp2-core and imcp2 to crates.io and runs dist, which ships
imcp2-local's binaries, installers and Claude Desktop bundle on the `vX.Y.Z`
release page. Nothing in this repository deploys production: it embeds the
published crate.

Staging therefore moves only on candidates, never on a merge to main, so the
revision under test stays put while it is tested. deploy.yml (main -> staging)
and deploy-release.yml (release-* -> the host once called production) are
replaced by deploy-candidate.yml, which keeps the rollback dispatch and the
release-page publish (as a prerelease) and adds a verify job: the tag must
name the version every Cargo.toml carries, and the commit must be on main.
publish-crate.yml checks imcp2-local's version too and refuses a commit that
no rc-X.Y.Z-N tag points at, so a release is always a tested candidate.

dist's tag namespace becomes `v`, so plain version tags trigger it and
candidate tags do not (dist matches a tag's version against Cargo.toml exactly
and would fail on a candidate); the generated workflow is regenerated as
v-release.yml, and the bundle script and the attestation instructions name it.
With candidates as prereleases, GitHub's `latest` release is always a promoted
version, so the local crate's README resolves it directly instead of paging
through the release list.

CONTRIBUTING.md, the deploy README, SECURITY.md, the top-level README and the
directory-submission notes describe the flow; the deploy README drops the
production column from its secrets table and the `PROD_*` set can be deleted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd7VT72Qt16qiAJynu9EKj

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Promotion can publish artifacts without a successfully deployed candidate, and transitional installation verification is broken.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Reworks releases into candidate deployment (rc-*) followed by promotion (v*), separating staging deployment from downstream production adoption.

Changes:

  • Adds candidate validation, staging deployment, and prerelease publishing.
  • Unifies crate and local MCP releases under vX.Y.Z.
  • Removes legacy continuous-staging and production deployment workflows.
File Description
SECURITY.md Updates security-release terminology.
README.md Documents candidate and promotion flow.
CONTRIBUTING.md Defines the two-step release process.
docs/​anthropic-directory-submission.md Updates production verification guidance.
dist-workspace.toml Changes dist namespace to v.
deploy/​native/​README.md Documents candidate-only staging deployment.
crates/​imcp2-local/​README.md Updates installation and release instructions.
crates/​imcp2-local/​Cargo.toml Updates dist-release comment.
.github/​workflows/​v-release.yml Retargets dist releases to v*.
.github/​workflows/​publish-crate.yml Adds version and candidate checks.
.github/​workflows/​imcp2-local-mcpb.yml Updates bundle release terminology.
.github/​workflows/​imcp2-local-install-note.yml Updates release-note terminology.
.github/​workflows/​deploy.yml Removes automatic staging deployment.
.github/​workflows/​deploy-release.yml Removes production deployment.
.github/​workflows/​deploy-native.yml Documents its candidate caller.
.github/​workflows/​deploy-candidate.yml Adds candidate verification, deployment, and prerelease publishing.
.github/​scripts/​build-mcpb.sh Supports vX.Y.Z artifacts and attestations.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/publish-crate.yml Outdated
Comment thread crates/imcp2-local/README.md
…-0.6.0 releases

Copilot's review of the candidate flow, two findings:

The crate publish accepted any commit an rc-X.Y.Z-N tag pointed at, which
proves a tag was pushed, not that deploy-candidate.yml succeeded (or had
finished). It now requires that candidate's prerelease to carry the deployed
binary, which the candidate workflow uploads only after the staging deploy
succeeded; a v tag pushed too early fails with instructions to re-run once
the deploy is done. The step reads the release with the workflow's own
token, so the verify job's permissions are unchanged. The candidate
workflow's comment names the asset as the marker the check depends on, and
CONTRIBUTING.md and the deploy README describe the rule as it now is.

The local crate's README resolved `latest` and named v-release.yml as the
attestation signer, neither of which fits a release cut before 0.6.0
(tagged imcp2-local-vX.Y.Z, attested by imcp2-local-release.yml). A note
says how to install one of those.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd7VT72Qt16qiAJynu9EKj

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It rewires deployment and irreversible publishing automation, while the first end-to-end candidate deployment remains untested.

Review effort: Balanced
Findings: None

Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Low severity Version check occurs after tag creation

crates/​imcp2-local/​README.md:210

The candidate workflow runs only after the rc-* tag is pushed, so it cannot check the version “before the tag can exist”; an invalid tag still exists even though its deployment fails. Reword this to say the check happens before staging deployment/prerelease publication.

The local crate's README said the candidate step checks the version
"before the tag can exist"; a workflow runs after the tag is pushed, so the
check comes before the staging deploy and the prerelease, which is what
keeps a mismatched candidate from being promoted. Copilot's second round.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd7VT72Qt16qiAJynu9EKj
@aterga
aterga marked this pull request as ready for review September 24, 2026 14:31
@aterga
aterga requested review from a team and Copilot September 24, 2026 14:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The release and deployment redesign depends on repository settings and its candidate workflow has not yet been exercised against staging.

Review effort: Balanced
Findings: None

@aterga
aterga merged commit 0cc1c54 into main Sep 24, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants