Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions docs/release/runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,60 @@ Then **verify** (§4).

---

## 2c. Staged canary / ring rollout

For a higher-risk release, stage it through the ring channels instead of promoting
`stable` in one move. Each ring's callers pin **once** to their channel (see
[`versioning.md` → Ring channels](./versioning.md#ring-channels-live-for-dev-lead));
a rollout is a sequence of single tag moves, validating at each step. `dev-lead`
has live `next`/`ring0`/`ring1` channels; `pr-review` is `stable`-only for now (#499).

```bash
git fetch origin --tags
# Cut the immutable release once (ungated):
scripts/cut-release.sh dev-lead 1.5.0 --ref origin/main --push
TARGET=$(git rev-parse 'dev-lead/v1.5.0^{commit}')

# Stage it outward, one ring at a time. After EACH move, verify (§4) and let the
# ring soak — confirm its callers' runs are healthy before advancing the next.

# Promote to 'next' (canary/self-host)
git tag -f dev-lead/next "$TARGET"
git push --force origin dev-lead/next
# → verify (§4) + soak on 'next' before continuing

# Promote to 'ring0'
git tag -f dev-lead/ring0 "$TARGET"
git push --force origin dev-lead/ring0
# → verify (§4) + soak on 'ring0' before continuing

# Promote to 'ring1'
git tag -f dev-lead/ring1 "$TARGET"
git push --force origin dev-lead/ring1
# → verify (§4) + soak on 'ring1' before continuing

# Promote to 'stable' (production)
git tag -f dev-lead/stable "$TARGET"
git push --force origin dev-lead/stable
# → verify (§4) + soak on 'stable'
```

- **Promotion is gated at every ring** — advancing each channel (including `next`)
is a channel-tag move, so it is human-authorized (Roles & gating). Don't script
the whole loop unattended; advance a ring only after the previous ring is healthy.
- **Validate the candidate, don't trust it.** Treat a ring's failures as the
release until proven otherwise — classify them (regression vs. pre-existing
class) before advancing. A clean canary across rings is the gate for `stable`.
- **Rollback at any stage** is the same single move in reverse against the prior
immutable `vX.Y.Z` (§3) — for that ring's channel, or for `stable` if already
promoted.
- The fully automated, health-gated version of this loop is issue #501; today it
is a human-driven sequence of the moves above.

Then **verify** (§4).

---

## 3. Roll back (< 5 minutes)

Rollback is promotion in reverse: move `<agent>/stable` **back** to the previous
Expand Down
39 changes: 36 additions & 3 deletions docs/release/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,43 @@ Two kinds of tag, per agent:
Channels (Phase 1 defines `stable`; Phase 2 adds `next` and per-ring channels):

- `<agent>/stable` — the production channel (blue). Callers in production pin here.
- `<agent>/next` — the candidate channel (green). *(Phase 2, #499.)*
- `<agent>/ring1`, … — per-ring channels for staged promotion. *(Phase 2, #499/#500.)*
- `<agent>/next` — the candidate channel (green). **Live for `dev-lead`** (#499).
- `<agent>/ring0`, `<agent>/ring1`, … — per-ring channels for staged promotion. **Live for `dev-lead`** (#499/#500).

Examples: `pr-review/v1.0.0`, `pr-review/stable`, `dev-lead/v1.0.0`, `dev-lead/stable`.
Examples: `pr-review/v1.0.0`, `pr-review/stable`, `dev-lead/v1.0.0`, `dev-lead/stable`,
`dev-lead/next`, `dev-lead/ring0`, `dev-lead/ring1`.

### Ring channels (live for `dev-lead`)

The candidate (`next`) and ring channels are real moving tags, created alongside `stable`. A caller
pins **once** to its ring channel and is never edited again; a release flows outward ring-by-ring as
each ring's tag is advanced.

Ring membership (canonical model — see [#500](https://github.com/petry-projects/.github-private/issues/500)).
`next` is **host-relative**: it always resolves to the repo that *hosts* the reusable, and `ring0`
covers the other org-infra repo, so `next` + `ring0` always span `.github` + `.github-private`,
partitioned by which one is the host:

| Ring | Channel | Members (general) | Role |
|---|---|---|---|
| **next** | `<agent>/next` | the repo that **hosts** the reusable | canary / dogfood at the source |
| **ring0** | `<agent>/ring0` | `.github` **and** `.github-private` (host already in `next`) | org-infra self-host |
| **ring1** | `<agent>/ring1` | `TalkTerm`, `bmad-bgreat-suite` | named low-traffic consumers |
| **stable** | `<agent>/stable` | everything else | full-fleet production |

Concretely for **`dev-lead`** (hosted in `.github-private`): `next` = `.github-private`,
`ring0` = `.github`, `ring1` = `{TalkTerm, bmad-bgreat-suite}`, `stable` = the rest.
**Production self-review/dev duty stays pinned to `stable` even within ring 0** — the agent validating
fixes is never the unvalidated candidate (the circular-dependency fix #500 targets). The intended
machine-readable source of truth is `standards/canary-rings.json`, consumed by the promotion
automation (#501).

A staged rollout advances the channels in order — `next` → `ring0` → `ring1` → `stable` — validating
at each step (see [`runbook.md` §2c](./runbook.md#2c-staged-canary--ring-rollout)). All four channels
may sit at the same commit between releases; they diverge while a candidate is being staged. The
`check_dev_lead_stub` compliance audit accepts any `dev-lead/{stable,next,ring<N>}` channel pin (it
rejects `@main` and frozen `@vX.Y.Z`/`@<sha>` — callers must pin a *moving* channel). pr-review still
uses `stable` only; its ring channels are pending under #499.

### Semantic versioning

Expand Down
Loading