--- status: active owner: platform last_reviewed: 2026-10-12 supersedes: [] --- # Release and merge policy **Goal:** Rapid, trustworthy iteration — local gates for deploy readiness, **GitHub Actions for merge readiness**. Never treat one as a substitute for the other. **Script SSOT:** Canonical ops scripts live under `scripts/` **in git** (edit on `develop`, merge to `main`). Do **not** set `skip-worktree` on them. After Block 2, `~/studio/scripts` matches `main`; agents always emit Blocks 1–3 via `bash scripts/print-pr-handoff.sh` (never ad-hoc `git checkout develop` on `~/studio`). --- ## Four gates (do not skip) | Gate | When | Command / rule | Blocks | |------|------|------------------|--------| | **Merge** | Before merging any PR to `main` | GitHub Actions **required checks success** via Block 1 + `make pre-merge` on VM | Shipping broken CI to `main` | | **Deploy** | During Block 3a | `bash scripts/block3-promote.sh ` → `promote-batch` (test-gate or `SKIP_TEST_GATE` **with green SHA proof**) | Broken prod | | **Post-promote E2E** | Block 3a after deploy | `scripts/run-studio-e2e-guards.sh` (baked into promote + explicit 3a gate) | Live routing/shell regressions after cut | | **UI quality** | Before calling UI work done | Visual check + `@studio/ui` / utility templates | Generic, half-finished screens | **E2E naming:** Block 1 **E2E Guards (prod baseline)** proves current production did not regress — it is **not** validation of the PR’s new UI. Post-cut validation is Block 3a only. **SKIP_TEST_GATE:** Never honor-system. `product-devops/common.sh` refuses `SKIP_TEST_GATE=1` unless GitHub shows required checks green for the commit **Block 1 actually watched**. It **polls until the checks settle** (cap `STUDIO_GATE_WAIT_SEC`, default 900s) instead of refusing on the first look — Block 3 runs seconds after the merge, so a single-shot check raced CI's queue and always aborted with `ok=0 total=1`. A path-filtered required check (`Platform test-fast` → **N/A**) is pass-equivalent, so `skipped` / `neutral` are accepted; a real bad conclusion (`failure`, `cancelled`, `timed_out`, `action_required`, `startup_failure`, `stale`) aborts immediately. **Which commit is verified (important):** `ci.yml` triggers on `pull_request` (develop|main) and `push` to **develop only** — it never runs on a push to `main`. So a merge commit carries almost no check-runs (`20ed9a75`: only `inventory-gate` + `static-gate`, no `CI`, no `E2E Guards`), while the required checks live on the **PR head SHA**. The guard resolves the merged PR from HEAD and verifies the PR head; HEAD itself is used when there is no PR. Approval = settled + nothing failed + the `CI` workflow green when it exists for that SHA. There is deliberately **no hard must-exist check list**: a check that will never be created must not stall a promote (that stalled one for the full 900s cap on 2026-10-17). If a SHA carries no check-runs at all, the guard refuses after `STUDIO_GATE_NOSIGNAL_SEC` (60s) and tells the operator to re-run the promote without `SKIP_TEST_GATE` so the local test gate runs. **Hard rule:** A passing `make test-gate` on the VM does **not** authorize merge. A red GitHub PR is **not** ready — even if git says “mergeable”. **Feature work rule:** Do **not** start new feature commits until Block 3b prints `BLOCK3 COMPLETE`. --- ## Three-block handoff (operator — copy one block at a time) Run all blocks from **`/home/ubuntu/studio`**. Prefer `bash scripts/print-pr-handoff.sh` to generate pastes (derives promote IDs from diff vs `products/registry.yaml`). ### Block 1 — required checks green ```bash cd /home/ubuntu/studio && bash scripts/block1-pr-checks.sh ``` **Required checks** (must be `success`; skipped / cancelled / missing = FAIL): - `CI` - `E2E Guards` (prod baseline) - `Platform test-fast` (job name `test-fast`) - `inventory-gate` (**Gate 12** — `docs/tech-stack/inventory.yaml` vs registry + UDS + codebase) Leave running until: `ALL GREEN — safe for Block 2 (sha: …)`. ### Block 2 — merge + ready main ```bash cd /home/ubuntu/studio && bash scripts/block2-merge-ready.sh ``` Must print `BLOCK2 OK main@`. Do not start feature work yet. ### Block 3 — promote + e2e + develop sync ```bash cd /home/ubuntu/studio && bash scripts/block3-promote.sh [id2 ...] ``` - **3a:** promote-batch + e2e → `BLOCK3a DONE` - **3b:** clean + `git pull --ff-only` on `~/studio-dev` develop → `BLOCK3b DONE` → `BLOCK3 COMPLETE` - **Always ends with a `BLOCK 3 SUMMARY`** block (`3a promote`, `3a e2e guards`, `3b develop sync` + SHAs, `3b develop push`, `elapsed`, `RESULT`) printed via an `EXIT` trap — pass **or** fail, so the verdict never has to be scrolled back for. --- ## Standard release flow (develop → main) ```bash cd ~/studio-dev # feature lane make pre-merge git push origin develop bash /home/ubuntu/studio/scripts/print-pr-handoff.sh # or ensure-develop-pr + handoff # Then on prod tree — Blocks 1 → 2 → 3 only (no ad-hoc gh/promote pastes) cd ~/studio bash scripts/block1-pr-checks.sh bash scripts/block2-merge-ready.sh bash scripts/block3-promote.sh ``` **Promote rule:** wrappers always sync **`main`** under `~/studio`. Do not promote from `develop` unless `ALLOW_PROD_DEPLOY=1` (emergency). --- ## Why CI differs from the VM | Condition | Oracle VM (`/home/ubuntu/studio`) | GitHub Actions | |-----------|-----------------------------------|----------------| | Folder name | `studio` | `venture-studio` | | Python deps | Pre-installed system/user packages | Clean runner — install via `platform/requirements-gate.txt` | | npm | Warm `node_modules` from prior builds | Fresh `npm ci` every run | | Lockfiles | Corrupted URLs may hide until `npm ci` | Fails immediately on bad tarball URLs | `make pre-merge` runs **`scripts/validate-ci-parity.sh`**, which simulates the GitHub layout and runs `npm ci --dry-run` on all gate frontends. --- ## Secret scrubbing **Never run automated secret scrubbers on:** - `package-lock.json` / `package.json` - `yarn.lock`, `pnpm.lock` - Any committed registry tarball URL Scrubbers that replace substrings (e.g. `sk` → `***REDACTED***`) **silently break npm ci**. Scrub **`.env` files only**; keep env templates in `.env.example`. --- ## Agent / operator checklist (release PR) Before telling the user a PR is ready for Block 1: - [ ] `make pre-merge` passed locally - [ ] Changes pushed to `develop` - [ ] Handoff printed with derived promote IDs verified - [ ] No `***REDACTED***` in lockfiles - [ ] UI changes: smoke/baselines updated in the **same PR** Before Block 3: - [ ] Block 1 ALL GREEN + Block 2 `BLOCK2 OK` - [ ] Promote IDs match registry paths touched by the PR --- ## UI / rapid development bar Platform UI work must use **`@studio/ui`**, utility templates, and design tokens. “Build passed” is not enough. --- ## CI minutes & batching - Prefer **one high-volume PR** per batch. - Merge gates use the **self-hosted runner on studio-vm**. See [GITHUB_ACTIONS_RUNNER.md](./GITHUB_ACTIONS_RUNNER.md). ## Related - [DEPLOYMENT.md](./DEPLOYMENT.md) — prod promotion commands - [GITHUB_ACTIONS_RUNNER.md](./GITHUB_ACTIONS_RUNNER.md) — self-hosted runner + disk cleanup - [GIT_AND_REPO_HYGIENE.md](./GIT_AND_REPO_HYGIENE.md) — what never goes in git - `Makefile` — `pre-merge`, `pre-release`, `test-gate` - `.github/pull_request_template.md` — PR checklist - `.cursor/rules/pr-handoff-and-promote.mdc` — agent paste rules (must match this file)