---
name: rcand
description: Promote development to a release-candidate branch.
---

**Host-native invocation:** Claude `/mmi:rcand` · Codex `$mmi:rcand` · Cursor `/rcand` · jervcode `/rcand` · Hermes skill_view. A backticked `/name` in this doc names the matching workflow (this skill or a sibling), not a literal command.

**Argument:** `/rcand minor`, `/rcand major`, or `/rcand patch` — a bare bump word, never a `--flag` or env var. No argument → `patch`; declare `minor` or `major` for a planned feature cycle.

# /rcand — promote development to release-candidate

Merge `development → rc`, tag `vX.Y.0-rc.N`, push the tag first, wait for the required checks, push the protected `rc` (the GitHub authority gate), then start the rc deploy. `rc` is a deploy stage, not a lane, and it is ephemeral: `/rcand` creates its runtime, `/release` ships exactly what is on `rc` and retires it. Train-authority gated: the repo's project-admin or the master. The board needs no touch: items reached `Done` when their PRs merged to `development`.

## Lane

Read META and name the lane before describing or executing anything:

```bash
mmi-cli oracle org project get {owner}/{repo} --json
```

`releaseTrack: direct` (product repos with explicit META, plus MMI-Hub, and every `deployModel: vercel` repo) has no `rc`: stop here and run `/release` from `development`; `rcand --apply` refuses direct-track at its own preflight. Full-track runs from `development`, and a full-track repo may skip rc entirely with `/release --dev`. Unreadable META → stop; never infer the lane from branch names, a stale `rc` ref, or release history.

Authority is structural and server-checked: the doctor asks the Hub, and the protected `rc` push allowlist carries the same people (master + that repo's project-admins). Gate ordering: the tag lands the rc SHA for checks, and every deploy side-effect waits until the protected `rc` push accepts that checked SHA.

## Preflight

```bash
mmi-cli devops train doctor --lane rcand --heal --json
```

Read `ready`, then `findings[]`: each carries `code`, `severity` (`blocker` · `warning` · `healed` · `info`), `source` (`local` · `origin`), `remedy` and `anchor`. `ready: false` → stop and quote every blocker's `code` and `remedy`; an unread origin is never green. `--apply` re-runs this doctor as its step 0 with heal on.

## Apply

Run from the primary checkout on `development`. Export the bump intent once from the bare argument — `MMI_BUMP_INTENT=minor|major|patch`, unset → `patch` — and keep it exported through the following `/release`; `MMI_RELEASE_VERSION=X.Y.Z` opens an exact cycle for both `/rcand` and `/release` only when the tag math cannot derive it.

```bash
mkdir -p .jerv/tmp && r=$(mktemp .jerv/tmp/rcand-receipt.XXXXXX)
mmi-cli devops rcand --apply --json --out "$r"
```

Add `--watch` to block on the rc deploy; without it the deploy runs while you report and its verdict is
collected in Verify — verification is never skipped, only un-blocked. The one command fetches, merges `development → rc` (version-manifest conflicts resolve to the `development` side; any other conflicted path refuses), tags the next `-rc.N` (re-run-safe), pushes the tag first, discovers and waits for the required contexts on `rc`, pushes `rc`, and starts the deploy by model — `tenant-deploy.yml` centrally (keyless) for tenant-container; nothing for serverless, registry-publish or solo-container, whose own push-triggered workflow deploys. Never `git push` rc by hand, never force, never commit to `rc` outside that merge.

## Verify

Read the receipt at `$r`, never the exit code (exit `1` = the deploy failed after promotion; an unresolved deploy exits `0`, so a clean exit alone never means every leg resolved):

- `promoted` — `true` even when the deploy failed; promotion and deploy are separate outcomes.
- `tag` — the minted `vX.Y.0-rc.N`; `resumeNote` says an existing rc tag was resumed rather than minted.
- `deployStatus` — `pending` is never terminal. `runId` / `runUrl` / `workflowRuns` name the run: the dispatched `tenant-deploy.yml` for tenant-container, the auto-fired `deploy.yml` for serverless. Watch each non-success entry to conclusion: `gh run watch <run-id> --repo {owner}/{repo} --exit-status`.
- `environments` — the rc edge domain(s) from META, the env URL the report names.
- `checkout`, `localSync`, `ledger` — `returned` and phases-green, or the named reason you are still on `rc`.

Then confirm origin: `git rev-list --count origin/rc..origin/development` is `0` and the rc tag is on origin. There is no GitHub Release at rc; the bounded Latest read belongs to `/release`. A red deploy after a green promotion is a deploy retry of the existing rc ref, never a re-tag or re-merge.

## Recovery

Every refusal and every doctor blocker names its `code` and `anchor`: open `docs/Guides/train-troubleshooting.md#<code>` and follow that entry, nothing else. That guide is test-parsed, never free text — the infra drift and plugin-set suites validate every anchor cited here and assert policy from its wording — so an edit to it re-runs the infra suite (`npm --prefix infra test`) in the same PR: a heading or slug change is a code change. A candidate whose immutable rc tag passed policy but whose `origin/rc` was not pushed continues with `mmi-cli devops rcand --resume --watch --json --out <fresh-receipt>` — the only continuation form: `--resume` never re-tags, and `--apply` mints the next `-rc.N` for a new cut only. A refused train is a stop (Merge floor): fix the cause on `development` through a normal PR, then rerun; a rejected protected push leaves the local tag for an authorized rerun, never a force.

## Merge floor

- **Never squash-merge a tagged commit (#3167).** A squash re-mints the SHA and orphans the tag from the branch lineage, so every tag-anchored check mis-reads forever after. Any merge that carries an already-tagged commit — the `main → development` roll-forward, an alignment PR, a hotfix fold PR — lands as a true merge: `mmi-cli devops pr merge <number> --auto --merge` (or `--wait --merge`); `--wait` alone still squashes. The hotfix main-base PR itself may squash: its tag lands after, on the merged `main` HEAD, and the `-x` trailer must survive the squash message.
- **A refused train is a stop, never a license to finish by hand.** Every fail-closed halt — doctor blocker, authority probe, coverage guard, untolerated conflict, required checks, stray tag, ledger — ends the run. Fix the cause on `development` through a normal PR and rerun the train; never hand-resolve on `main`, hand-cherry-pick, bare-push a train branch, ack to save time, or bypass a check.
- **Version probes anchor on the latest Release tag, not a branch (#2734).** A branch manifest or a stale local tag is not a version source; a surviving local tag silently mints the next version.
- **One train at a time on a repo (#5380).** Two overlapping runs race the shared tag namespace; the ledger refuses the second — never start one to help the first.
- **Tag and Release deletion are deletion-class:** the authorized human's explicit per-turn go, named in the guide entry that prescribes it.

## Report

Title first, verdict first. Name the resolved tag (never a placeholder like `vX.Y.0`) and the Release URL, the receipt's status fields (`releaseStatus`, `followUpStatus`, `deployStatus`, `promoted`), and every workflow run row with its conclusion and evidence — use its URL when emitted, otherwise the receipt's explicit no-run-URL note — saying which run is red, and whether it is a deploy/publish verdict or ordinary CI — `announceNote`, the alignment note (`devRollForward`, `rcAlignment`, or the hotfix fold PR and whether it landed), the `checkout` clause, and the sanctioned dispatch mechanism you used. A pending follow-up is reported as unverified, never as success. Product names appear only when releasing that product's repo.

## Retro

Before the final report, answer one question honestly: did this skill's own instructions misfire this run — ambiguous wording, a misleading message, or an environment failure it should have warned about? Process only, never the user's code or task. If yes, file one lesson and move on; a clean run is silent (hard cap: one per run). It lands on the owning board, deduplicated, and is fixed only through a reviewed PR — never edit the skill live. The retro is advisory: if the call fails, note it and continue. File through MMI with the command below. The amendment itself stays reference-free: the skill-drift gate rejects issue references in any section outside Merge floor, so carry the evidence in words or point at the guide entry — never inline reference markers.

```bash
mmi-cli learning skill-lesson --skill rcand --title "<what misfired>" --body "<what; evidence; proposed amendment>"
```
