# Proposal 1: cost-first PR-to-prod pipeline (2026-09-30)
Angle: cut machine-minutes and review tokens. Run a small fast gate on every push, run the heavy suite once per merge-queue batch, and move everything else to a schedule or make it advisory. FACT = measured in the findings files. EST = estimate, basis stated.

## Diagram: target flow
```
 laptop/agent          PR (every push)             merge queue (per batch of <=4)     main tip            prod
 ------------          ---------------             ------------------------------     --------            ----
 review:self   --->    [plan] path filter  --->    full unit (4 shards)          --->  deploy web+worker ---> canary probe
 affected tests        lint + types (cached)       guard pins, regression-leasing       +lambdas (path)       auto-revert on red
 typecheck:scripts     affected unit tests         path-filtered evals                  NO re-run of CI       Slack note
                       nested lane                 => required "Queue Gate"
                       AI review (tiered, once)
                       => required "PR Gate"
 nightly/cron: semgrep, harnesses, drift checks, full evals, quality POC (advisory, open issue on red)
```

## Stages
| # | Where | What runs | Wall (EST) | Machine-min (EST) | Basis |
|---|---|---|---|---|---|
| 1 | Pre-push, local | `review:self`, affected vitest, `typecheck:scripts` when `scripts/` touched | 2-6 min | 0 (laptop) | existing hook; 4/4 Option D PRs paid a CI round it would have caught |
| 2 | PR, every push | ONE workflow: plan -> lint, types, affected unit, nested lane, body/size gate; one aggregated check "PR Gate" | ~6 | ~12 | FACT side checks median 12 min/push, CI fixed part; affected lane replaces full shards pre-merge |
| 3 | PR, AI review | Tiered: <100 substantive lines = Sonnet/low; else Opus high (no xhigh); one verdict per head, no re-review after 🟢/🟡; 🔴 only for reproduced correctness/safety | ~5-8 | ~2 runner + tokens | FACT 57% of 🔴 not bugs; 310 rounds after 🟢; 22% of PRs <100 lines |
| 4 | Merge queue | Full unit (4 shards), guard pins, regression leasing, path-filtered evals; batch 2-4, build concurrency 3 | ~12 | ~43 per batch, ~13/PR | FACT CI PR run 43.4 machine-min median, wall 11.2 |
| 5 | Post-merge | Deploy main tip (web, worker and lambdas by path); no duplicate push-CI on main (queue already tested the merged tree) | ~6 | ~3 | FACT Vercel build 4.4 min median; worker deploy 18 min median today |
| 6 | Prod verify | Synthetic probe + error-rate check 10 min; auto-revert via Revert PR `[urgent]` | ~10 | ~1 | none exists today (FACT) |
| 7 | Scheduled | Semgrep, 12 harness jobs, drift checks, full eval sets, quality POC: nightly, advisory, file an issue on red | - | off the PR path | inventory classes Sched |

## Gates today -> target
| Gate | Decision |
|---|---|
| Build, Type Check, Lint, Unit Tests (affected), Nested Gate | Fold into one required "PR Gate" |
| Unit Tests (full), Regression Gate - Leasing, Guard pins | Move to merge queue ("Queue Gate") |
| review (Claude Code Review) | Keep, tiered; fold Override, Recheck, Re-pin, rerun-on-infra into it |
| Auto-merge guard (3,516 lines) | Slim to: queue enqueue on PR Gate + review green; dangerous-diff hold kept |
| Push CI on main | Delete (queue tested the merge result) |
| Promote 30-min batching | Delete; deploy tip, keep cold-start warm step |
| review-verdict-watchdog | Delete once review is one workflow |
| Deleted: claude.yml on every event (narrow to @claude), docs-only-fence, dependabot gate job, quality-gate-poc, provision-western-slope-proto, ci-dispatch-guard (fold), main-lane-red-alert (dup of ci.yml alert), done experiments (sonnet5 A/B, effort sweep) | |
| Moved to schedule: semgrep, harnesses, phone-address/schedule drift, non-path evals | |
| Kept post-merge: sync-* ElevenLabs config, ci-docs, deploy-announce folded into deploy | |
| Runner spec pasted 163 times | One reusable composite/label alias |

## Numbers
| Metric | Today | Target | Basis |
|---|---|---|---|
| Runs per push | 22 (FACT, 551 runs/25 pushes); 15 after today's folds | 3-4 | stages 2-3 + guard |
| Machine-min per push | ~55 (FACT n=15) | ~14 | stage 2+3 |
| Pushes per PR | 3.2 mean (FACT) | ~2.2 (EST: review rules remove ~1 round on 3+ round PRs) | review-process est. |
| Machine-min per PR | ~220 (EST: 3.2x55 + 43 push-CI on main) | **~48** (2.2x14 + 13 queue + 3 deploy + 1 verify) | EST |
| Runner $/month | ~$6.6k run-rate on Sep 29 (FACT CE, 59% on-demand); $8-20 on normal days | **~$450** (EST: 341 merges/day x 48 min = 273 machine-h/day x $0.05 spot = $14/day ≈ $410, +10% on-demand margin) | CE + stated rates |
| Review model $ | unmeasured (1,266 review runs on 09-29, FACT) | est. -40% runs (one verdict per head, tiered model) | log tokens in footer first |
| GitHub API | 22 runs/push fan-out burned the 5,000/h budget (FACT) | ~80% fewer workflow events | runs per push |
| PR open -> prod | median open->merge 0.36-0.65 h + merge->prod 1.13 h (FACT/EST) | **~50 min median** (EST: 25 min open->queue, 12 queue, 6 deploy, 10 verify) | stage walls |
| Red main | 4 of 25 main runs failed/cancelled (16%, FACT) | **-70% (EST)** | per cause below |
| Merges/day capacity | ~341/day observed (Sep 29) | **~800/day** (EST: 3 concurrent batches x 4 PRs per 12-min run x 24 h = 1,440 theoretical; halved for bisects/fails) | GitHub queue docs |

## Red main by cause
| Cause (from red/ logs and inventory) | Mechanism |
|---|---|
| Semantic conflict: drift/inventory tests (`user-route-inventory`, `property-scope-surface-registry`) fail after two green PRs merge | Merge queue tests the combined tree before it lands |
| Infra: `VcpuLimitExceeded` at 320 vCPU (~1 in 6 failed runs) | 4x fewer jobs per push; spot-only pools with wide families; pacer past ~40 runs/h |
| Cancelled/superseded main runs | No push-CI on main; deploy tip only |
| Flaky tests | Nightly flake detector, quarantine list, new tests soak nightly before gating |
| Real defect escapes | Canary probe + auto-revert |

## Migration (8 PRs)
1. One reusable runner spec + delete dead workflows (docs-fence, POC, WS proto, experiments, dup red alert); narrow claude.yml.
2. Fold review satellites (override, recheck, re-pin, rerun) into claude-code-review; tiered model, no xhigh, token/$ footer.
3. Review rules: 🔴 only for reproduced defects, one verdict per head; fix the repo manual text that contradicts Fede's 09-17 rule.
4. Single "PR Gate" aggregate job in ci.yml (lint, types, affected unit, nested, body/size); update ruleset required contexts.
5. Add `merge_group` trigger + "Queue Gate" (full shards, guard pins, regression); enable GitHub merge queue, batch 2-4, concurrency 3.
6. Slim auto-merge guard to enqueue; drop push-CI on main.
7. Deploy-on-tip for web/worker/lambdas (path-scoped), drop 30-min batching, fold deploy-announce.
8. Prod verify: synthetic probe + auto-revert PR; move semgrep/harnesses/drift/evals to nightly advisory with issue-on-red.

## Risks
| Risk | Mitigation |
|---|---|
| Affected-test selection misses a dependency -> defect caught only in queue | Queue runs full suite; queue failure kicks one PR, not main |
| Spot eviction / vCPU cap stalls queue | Wide instance families, bounded retry x3, pacer; on-demand only for queue job |
| Queue batch failure costs bisect time at ~340 merges/day | Batch <=4; auto-drop culprit |
| Auto-revert reverts a good change on noisy probe | Probe requires 2 consecutive failures; revert is a PR, reversible |
| Moving evals/semgrep to nightly delays catching regressions by up to 24 h | Keep path-filtered evals in queue for prompt/voice/renewal dirs |
| Cheaper review model misses bugs on small PRs | Tiny PRs averaged 0.03 🔴 per PR (FACT); watch escape rate monthly |
| Runners are arm64 today; x86 spot pricing assumed per task brief | Cost scales with vCPU either way; re-price after PR 1 |
| Review token spend unmeasured, so $ claim for it is soft | PR 2 adds the footer; re-baseline after a week |
