Phase 2b confirm-and-document outcome: per-recipe test-sequence deploy budget is already minimal — `deploys == 1 (base, shared by all 5 tiers) + N_cold_deps` — and tighter than plan B1's nominal `1+1(upgrade)+N` because the upgrade is an in-place chaos redeploy of the prev-version base, not a separate deploy. Enforced as a hard failure by DG4.1 (expected = 1 + deps_deployed_count, run_recipe_ci.py:1005-1010). No redundant deploy found; none removed (none existed). - docs/perf/deploys.md: the budget record (B4), names the out-of-budget WC5 reseed - STATUS-2b.md: B1-B4 claim with WHAT/HOW/EXPECTED/WHERE for cold verify - JOURNAL-2b.md / BACKLOG-2b.md / DECISIONS.md: reasoning + settled note - consume machine-docs/BUILDER-INBOX.md (Adversary heads-up processed) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.2 KiB
Per-recipe deploy budget (Phase 2b)
Question: does a recipe's full CI test sequence redeploy more than necessary?
Answer: No. The budget is already minimal — and in fact tighter than the nominal
1 base + 1 upgrade + N_deps — because the upgrade tier shares the base deployment.
The budget
For one cold !testme/run_recipe_ci.py run of a recipe:
deploys == 1 (base) + N_cold_deps
- 1 base deploy, shared by install → upgrade → backup → restore → custom/functional.
All five tiers run against this single deployment. (
run_recipe_ci.py:819,lifecycle.deploy_app→_record_deploy.) - + 1 per COLD declared dependency (e.g. an SSO provider deployed in-run), each deployed
once and reused (
deps.py:81-120, onedeploy_appper dep). A live-warm dep (e.g. a resident keycloak that only gets a per-run realm, not a fresh deploy) contributes 0. - The upgrade tier adds NO deploy. When the upgrade tier runs, the base deploy is done at
the previous published version (
run_recipe_ci.py:746-754:base = prev or target), and the upgrade is an in-placeabra app deploy --chaosredeploy of the PR-head code onto that same running app (generic.perform_upgrade→lifecycle.chaos_redeploy).chaos_redeploydoes not calldeploy_app, so it is not counted — and it is the real upgrade the PR's changes are exercised by (HC1), verified byassert_upgradedon the chaos-version label. - backup and restore add NO deploy. They operate on the same running app
(
perform_backup/perform_restore→backup_app/restore_app); neither callsdeploy_app.
Reconciliation with the plan's nominal budget
Plan B1 states the nominal minimum as 1 (base) + 1 (upgrade tier) + N_deps, assuming the upgrade
tier needs its own prior-version deploy. The cc-ci design is stricter: the base deploy is the
prior-version deploy (when upgrade runs), and the upgrade is performed in place. So the
prior-version deploy and the base deploy are the same deploy — there is no separate upgrade
deploy. Net actual budget: 1 + N_cold_deps. This is the deploy-sharing the operator expected.
Enforcement (not just claimed)
The harness counts every deploy_app() (the only caller of _record_deploy, lifecycle.py:107-211)
into a per-run countfile and hard-fails on a mismatch:
expected_deploy_count = 1 + deps_deployed_count—run_recipe_ci.py:984(deps_deployed_countexcludes warm deps,:982-983).- RUN SUMMARY prints
deploy-count = N (expect M)—run_recipe_ci.py:986. if deploy_count != expected_deploy_count: … overall = 1(DG4.1 violation, non-zero exit) —run_recipe_ci.py:1005-1010.
So every green run is a proof that the recipe stayed within budget: a redundant redeploy would
push deploy_count above expected and turn the run red. No recipe can silently exceed the budget.
Verify from a cold clone
RECIPE=ghost STAGES=install,upgrade,backup,restore,custom cc-ci-run runner/run_recipe_ci.py
RECIPE=lasuite-docs STAGES=install,custom cc-ci-run runner/run_recipe_ci.py
Expected RUN SUMMARY lines:
- no-dep recipe (ghost):
deploy-count = 1 (expect 1), all tierspass. - cold-dep recipe (lasuite-docs + cold keycloak):
deploy-count = 2 (expect 2)—deps deployed: ['keycloak']— all tierspass,DEPS teardownclean. - warm-dep recipe (lasuite-meet, live-warm keycloak):
deploy-count = 1 (expect 1),deps deployed: ['keycloak'].
Observed across all Phase 2 recipe runs: every recipe ran at deploy-count = 1 (no/warm deps)
or deploy-count = 2 (expect 2) (one cold dep). No run exceeded 1 + N_cold_deps.
No test weakened to share the deploy
Sharing one deployment does not skip or soften any check:
- install, upgrade, backup, restore, custom each still run their real generic + overlay
assertions against the shared app (
run_lifecycle_tier,ALL_STAGES). - the upgrade is a real prev→PR-head crossover (
assert_upgradedon the chaos-version label), not a no-op. - backup→restore is real data-integrity (P4: seed → backup → mutate → restore → assert the seeded data survived), not health-only.
- per-run isolation/teardown is unchanged (
DEPS teardown, app undeploy, volume/secret cleanup).
Only the deploy count is constrained; coverage is untouched.
Out of scope of the budget (intentionally)
- WC5 canonical promote (
promote_canonical,run_recipe_ci.py:682-707) deploys a separatewarm-<recipe>app to (re)seed the warm-cache canonical. It runs only on a green cold run on LATEST, after the deploy-count assertion, and explicitly popsCCCI_DEPLOY_COUNT_FILE(:697) so it does not perturb the per-run test budget. It is warm-cache maintenance, not a test deploy. --quickfast lane (run_quick) reuses an existing data-warm canonical and is a separate optimization path; the cold full run above is the budget of record.
Conclusion
The per-recipe deploy budget is already minimal and enforced: 1 + N_cold_deps, with the
upgrade tier sharing the base deploy in place. No redundant deploy was found; none was removed
because none existed. (Phase 2b, 2026-05-31.)