Branching & Promotion Model
How code flows to production, and the guardrails that keep main and staging
from diverging. Adopted 2026-05-30 after repeated direct-to-main merges
silently broke the daily promotion (see PR #1317).
Branches
Section titled βBranchesβ| Branch | Role |
|---|---|
main |
Production. Prod images build from main. No one merges to main directly β it is updated only by the automated fast-forward promotion from staging. |
staging |
Integration. All feature work lands here via PR; continuously deployed to staging. |
feature/*, claude/*, β¦ |
Short-lived; PR into staging. |
feature --PR + CI + review--> staging --automated fast-forward--> main --approved deploy--> prodThe core invariant
Section titled βThe core invariantβmain is always a fast-forward of staging β i.e. main is an exact,
already-CIβd staging commit (main β staging). Because promotion is a
fast-forward (never a squash or a fresh merge commit on main), the SHAs match
and divergence is structurally impossible from the promotion side. Combined
with βno direct merges to main,β main can never drift from staging again.
git merge-base --is-ancestor origin/main origin/staging should therefore
always succeed. If it doesnβt, something pushed to main out-of-band.
- PR required; force-push and deletion blocked; enforced for admins.
- Pushes/merges restricted to the promotion automation only β humans never
update
maindirectly. - Required status checks: the full build/test/validate suite.
- The production human gate is at deploy time (a GitHub Environment required reviewer on the prod deploy job), not at branch-merge time.
staging
Section titled βstagingβ- PR required.
- Required status checks: the full build/test/validate suite β so broken code
canβt reach
staging, and therefore canβt be promoted.
Promotion (staging β main)
Section titled βPromotion (staging β main)βOwned by .github/workflows/auto-deploy-main.yml:
- Daily cron schedule: Runs a divergence check only (does not auto-promote
stagingtomain). - Manual
workflow_dispatch(gh workflow run auto-deploy-main.yml --ref staging): Promotesstagingβmainonly whenmainis an ancestor ofstaging, via a fast-forward push β no merge commit, no squash. - If
mainhas any commit not onstaging(divergence), the job fails loudly and opens a tracking issue instead of silently skipping. (Hardened in #1317.)
Hotfixes
Section titled βHotfixesβ- Normal: fix on a branch β PR to
stagingβ rides the next promotion tomain. - Emergency (prod-only, canβt wait): break-glass β a repo admin temporarily
relaxes
mainprotection, lands the fix, restores protection, and immediately back-merges tostagingso the branches donβt diverge.
Rollout status (Completed June 2026)
Section titled βRollout status (Completed June 2026)β-
mainbaseline protection β PR required, force-push/deletion blocked, enforce-admins. - Reconcile
mainβstagingso promotion can resume β PR #1317 (keystone; must land first so the workflow changes below can reachmainvia the first promotion). - Fast-forward promotion in
auto-deploy-main.yml+ restrictmainpushes to the promotion automation. - Required status checks on
mainandstaging. - Prod deploy-time approval (manual
workflow_dispatchofauto-deploy-main.yml --ref staging).