# 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.
