# JOURNAL — Phase 2b (reasoning; WHY) — confirm minimal deploy budget ## 2026-05-31 — Bootstrap + analysis (Builder) Operator manually kicked off Phase 2b (narrowed scope, plan §0): the ONLY task is to confirm the per-recipe test sequence uses the minimum number of deploys, and fix it if not, without weakening any test. Broad empirical-perf work is parked in IDEAS. Phase 2 is not yet `## DONE` (plausible/drone/Q5 remain), but B1–B4 are a property of the already-existing harness, so the analysis is independent of Phase-2 completion. ### Method Traced every `abra app deploy`/`upgrade`/`new` path through the harness. Key realization: the only thing that increments the DG4.1 deploy counter is `lifecycle._record_deploy()`, and it is called from exactly one place — inside `lifecycle.deploy_app` (`:211`). So "deploy count" == number of `deploy_app` calls in a run. Enumerated all `deploy_app` callers: base deploy (`run_recipe_ci.py:819`), per-dep (`deps.py:100`), and WC5 promote (`:699`, which pops the countfile first so it's outside the budget). ### Why the budget is minimal (and tighter than plan B1's nominal text) Plan B1 frames the minimum as `1 base + 1 upgrade + N_deps`, assuming the upgrade tier needs its own prior-version deploy. The cc-ci design avoids that: when the upgrade tier runs, the *base* deploy is done at the **previous published version** (`base = prev or target`, `:746-754`), and the upgrade is an **in-place chaos redeploy** of PR-head onto that same app (`perform_upgrade` → `chaos_redeploy`, which does NOT call `deploy_app`). So the prior-version deploy and the base deploy are the SAME deploy — the upgrade tier adds zero deploys. backup/restore also operate on the same app. Net: `1 + N_cold_deps`. This is the deploy-sharing the operator expected; nothing to remove because nothing is redundant. ### Why I trust the enforcement (B2 is real, not vacuous) `run_recipe_ci.py:1005-1010` turns `deploy_count != expected_deploy_count` into a non-zero exit. So every GREEN run is itself a proof the recipe stayed within `1 + N_cold_deps` — a redundant redeploy would push the count over and fail the run red. The historical Phase-2 runs (recorded in STATUS-2/REVIEW-2) corroborate: every recipe ran at `deploy-count = 1`, or `2 (expect 2)` for the one cold-dep recipe (lasuite-docs + cold keycloak). Warm keycloak (lasuite-meet) → 0 dep deploys → expect 1. ### Why B3 holds Sharing one deploy does not skip assertions: all five tiers still run their generic+overlay assertions against the shared app; upgrade is a real prev→PR-head crossover verified by `assert_upgraded`; P4 backup→restore is real data-integrity; per-run isolation/teardown is unchanged. Only the deploy COUNT is constrained, never the coverage. ### Cross-loop note The Adversary's independent pre-claim cold trace (REVIEW-2b @05:33Z) reached the identical conclusion and flagged exactly one completeness item: the B1/B4 doc must NAME the WC5 green-cold reseed (`run_recipe_ci.py:699`) — one additional uncounted `abra app new` for canonical warm-cache maintenance, outside the test-sequence budget. `docs/perf/deploys.md` addresses this in its "Out of scope of the budget (intentionally)" section, and STATUS-2b names it in verify-step (a). Claimed B1–B4 accordingly.