# Proposal 0: throughput-first PR-to-prod pipeline (2026-09-30)
Angle: most merges/day at equal quality. One CI workflow, fast PR lane, merge queue with small batches runs the heavy suite, main tip deploys continuously with canary + auto-rollback.
Labels: FACT = from arch/*.md findings (fetched today); EST = estimate, basis given.

## Diagram: target flow
```
 pre-push (local)        PR lane (per push)            merge queue (per batch of 2-4)      post-merge (main tip)              prod verify
 ┌──────────────┐   ┌──────────────────────────┐   ┌──────────────────────────────┐   ┌──────────────────────────┐   ┌──────────────────────────┐
 │ review:self  │──▶│ ci.yml "fast": plan,lint,│──▶│ ci.yml on merge_group:       │──▶│ NO re-run of full suite  │──▶│ canary: error rate+synth │
 │ typecheck    │   │ types, affected unit,    │   │ full unit 4 shards, guard    │   │ deploy tip: web, lambdas,│   │ calls 10 min; fail =>    │
 │ affected unit│   │ nested lane  (~8 min)    │   │ pins, regression-leasing,    │   │ worker (always main tip) │   │ auto-rollback all lanes  │
 └──────────────┘   │ + Claude review (1 wf)   │   │ build, drift registries      │   │ nightly: evals, semgrep, │   │ + auto revert PR         │
                    └──────────────────────────┘   │ (~12 min); red => eject PR   │   │ e2e, mutation on diffs   │   └──────────────────────────┘
                                                   └──────────────────────────────┘   └──────────────────────────┘
```

## Stages
| Stage | Runs | Where | Wall EST |
|---|---|---|---|
| 1 Pre-push | review:self, typecheck:scripts, affected unit | local | 2-6 min (FACT: suite ~6 min) |
| 2 PR fast lane | one `ci.yml` job graph: plan (path filter replaces docs fence, eval-diff, type-sync), lint, types, affected unit, nested lane | RunsOn spot 2/8-core | ~8 min (EST: today's CI 11.2 min wall minus full shards) |
| 3 PR review | one claude-code-review.yml (override/recheck/repin/rerun folded); 🔴 only for correctness; cancel on new push before model call; diff-only re-review | 2-core | ~8 min (FACT median 9.7) |
| 4 Merge queue | `merge_group`: full unit 4 shards, guard pins 6, regression-leasing, build, drift registries; batch min 1 max 4, wait 3 min, concurrency 4 | RunsOn spot | ~12 min (EST: CI wall 11.2 FACT) |
| 5 Post-merge | deploy queue-tested SHA = main tip to web + 17 lambdas + worker; no second full CI on main; nightly evals/semgrep/e2e | ECS/Vercel/Lambda | ~5 min (FACT Vercel build 4.4) |
| 6 Prod verify | 10-min canary: Vercel/Sentry error rate vs baseline, scripts/canary.sh, synthetic Willows call; fail = auto-rollback (Vercel instant, lambda alias, prior worker image) + auto revert PR | GitHub job + CloudWatch | 10 min |

## Today's gates: delete / fold / keep
| Gate | Action |
|---|---|
| Claude Code (@claude) 2.56 runs/push, 100% skip | Delete trigger (mentions only) |
| Docs-only fence, Dependabot gate, Quality Gate POC | Delete |
| Review override, recheck, re-pin, rerun-on-infra, verdict watchdog | Fold into claude-code-review.yml, delete watchdog after |
| auto-merge-all.yml (3,516 lines, 3.88 runs/push) | Delete; replaced by native queue + auto-merge |
| CI Dispatch Guard, PR size tier, PR body gate, self-review | Fold into CI `plan` job |
| ci-nested.yml | Fold as lane; keep "Nested Gate" name |
| Branch preview domain | Keep, `opened` only |
| Semgrep, phone drift, path evals | Nightly / path-filtered in queue |
| Push:main full CI (5,440 commits/30d) | Delete (queue already tested exact SHA) |
| 30-min promote batching | Delete; kept only as cold-start backstop |
| Required: Build, Type Check, Unit Tests, Regression, Lint, review, Nested Gate | Keep names; Unit/Regression/Build move to merge_group |

## Numbers
| Metric | Today | Target | Basis |
|---|---|---|---|
| Runs per push | 22.0 (FACT) | 3 (CI, review, preview on open) | design |
| Machine-min per PR | ~220 EST (55/push x 3.2 pushes FACT + 43 main CI) | ~60 EST (12 fast x 3.2 + 43 queue / batch ~2.5 + 5 deploy) | FACT per-job medians |
| PR open -> prod median | ~1.5 h EST (0.36 open->merge FACT + 1.13 h merge->prod earlier audit) | ~0.9 h EST (0.36 + 0.25 queue + 0.25 deploy+canary) | |
| Runner $/month | ~$6.6k (FACT AWS CE Sep 29 x30, 59% on-demand) | ~$1.6k EST (73% fewer min, spot share to ~85% on $0.05 vs $0.10/hr) | Cost Explorer |
| Review | ~1,266 runs/day (FACT 09-29), tokens not logged | ~40% fewer runs EST (cancel-before-model, diff-only, light path for tiny) | |
| Red main (main CI fail) | 16% of main runs (FACT 4/25); 780 fails/30d | ~3% EST, -80% | per-cause below |
| Merges/day capacity | ~181/day now (FACT) | ~1,000/day EST (4 concurrent batches x 3 PRs x 5/h x ~17 busy h) | queue math |

## Red main: mechanism per cause
| Cause (FACT from red/ logs) | Mechanism |
|---|---|
| Semantic merge collisions: drift/registry pins (user-route-inventory, property-scope registry) fail after two green PRs both add routes | Queue tests each PR on top of the ones ahead; failures eject the PR, main never sees it |
| Infra: 320 vCPU cap (EST 1 in 6 failed runs) | Fewer runs; infra failure = retry, never a red verdict |
| Flaky tests | Quarantine lane; new tests soak before gating |
| Restated/derived failures | One workflow, one verdict |
| Bad deploy that CI missed (Sep 16 calls outage) | Canary + auto-rollback (not red main, but shortens MTTR) |

## Migration (8 PRs)
1. Delete no-op satellites (claude.yml trigger, docs fence, Dependabot gate, Quality POC); preview on `opened`.
2. Fold review side-workflows into claude-code-review.yml; cancel before model call; 🔴 = correctness only.
3. Add `merge_group` trigger to ci.yml + ci-nested; split jobs into fast (PR) vs full (queue); publish same required names.
4. Enable GitHub merge queue on main (batch 1-4, wait 3 min, concurrency 4); keep auto-merge-all in shadow one day.
5. Delete auto-merge-all.yml + verdict watchdog; sessions use `gh pr merge --auto` into queue.
6. Deploy main tip for lambdas (fix the #14289 class) and skip push:main full CI.
7. Canary job + auto-rollback for web, 7 alias lambdas, worker; auto revert PR; convert remaining 10 lambdas to aliases.
8. Remove 30-min batching (backstop only); nightly lane for evals/semgrep/mutation; flaky quarantine.

## Risks
| Risk | Mitigation |
|---|---|
| Bad batch ejects innocent PRs, bisect cost | Max batch 4; GitHub re-queues rest automatically |
| Queue becomes the bottleneck on bursts (129 deploys/day peak FACT) | Concurrency 4, tune after a week |
| Dropping full suite from PR finds bugs later (in queue) | Affected-unit is the pre-merge filter; queue fail is cheap, main stays green |
| Auto-rollback flaps on noisy error rate | Baseline window + synthetic check must both fail |
| Required-check names must exist on merge_group or queue stalls | PR 3 publishes same names on both events |
| Guard rules (dangerous-diff hold) lost with auto-merge-all | Keep as one small `plan` check that fails on dangerous paths without label |
| Deploy of every tip strains Vercel/lambdas | Coalesce: deploy newest tip, cancel older (fixes pin bug) |
