diff --git a/machine-docs/BACKLOG-prevb.md b/machine-docs/BACKLOG-prevb.md new file mode 100644 index 0000000..a4fbb44 --- /dev/null +++ b/machine-docs/BACKLOG-prevb.md @@ -0,0 +1,31 @@ +# BACKLOG — phase `prevb` + +SSOT: `/srv/cc-ci/cc-ci-plan/plan-phase-prevb-previous-dynamic-base.md`. + +## Build backlog + +### M1 — implemented + green locally +- [ ] B1. Dynamic upgrade-base resolution: last-green (warm canonical registry version) → fallback + target-branch (`main`) tip → else skip (declared reason). Replace the static + `previous_version(vers[-2])` default in `run_recipe_ci.upgrade_base`. Wire into `main()` deploy. +- [ ] B2. `tests//previous/` mechanism: discovery, declared-target-version marker, base-only + application (added to base deploy's COMPOSE_FILE), head exclusion (never applied to PR head), + version-guard + stale-flag on mismatch. +- [ ] B3. Discourse migration: shrink `compose.ccci.yml` to environmental-only + (`order: stop-first`), delete bitnamilegacy image pins + sidekiq block; remove + `UPGRADE_BASE_VERSION` from `tests/discourse/recipe_meta.py`. (Expect NO `previous/`.) +- [ ] B4. Unit tests for the new surface: base resolution (last-green / main-tip / skip), `previous/` + match / skip / stale, environmental-vs-version overlay layering. Update `test_upgrade_base.py` + to the new resolver API without weakening coverage. +- [ ] B5. Discourse upgrade tier GREEN locally: base (bitnamilegacy:3.5.0) → head; assert deployed + `app` image == `discourse/discourse:3.5.3` (NOT bitnamilegacy) and no `sidekiq` service post-deploy. +- [ ] B6. CLAIM M1 (clean tree + STATUS verification block). + +### M2 — proven in real CI + spot-check +- [ ] B7. discourse PR #4 `!testme` GREEN in real CI; head ran `discourse/discourse:3.5.3`, migration exercised. +- [ ] B8. Spot-check ≥3 other upgrade-tier recipes (warm-canonical / published-predecessor / ex-`.ccci` + e.g. keycloak/cryptpad/ghost) still green under dynamic base. Reconcile levels/records. +- [ ] B9. CLAIM M2 → `## DONE` after fresh Adversary PASS on M1+M2. + +## Adversary findings +(Adversary-owned section — Builder does not edit below.) diff --git a/machine-docs/DECISIONS.md b/machine-docs/DECISIONS.md index ea0cac6..71cd50f 100644 --- a/machine-docs/DECISIONS.md +++ b/machine-docs/DECISIONS.md @@ -1441,3 +1441,24 @@ but stays well within the ≤90 s budget. Acceptable. (same model as keycloak realm marker). Idempotent creation (409 = already exists → OK). `pre_restore` deletes it to create a genuine divergence from backup state; `test_restore` asserts its return. The sqlite3 DB is the persistence layer being tested. + +- **Dynamic upgrade base — SETTLED (2026-06-17, phase prevb).** The upgrade tier's BASE version is + resolved at run time, replacing the static `previous_version(vers[-2])` default. Resolution order: + (1) **last-green** = the warm-canonical registry record (`canonical.read_registry(recipe).version`, + status warm/idle) when present; (2) fallback **target-branch (`main`) tip** = the recipe repo's + `main` HEAD (a git ref, chaos-deployed) — the true predecessor the PR merges onto; (3) **else skip** + the upgrade tier with a declared reason (new recipe / no predecessor / head==main). EXPECTED_NA[upgrade] + and `upgrade∉stages` still short-circuit to skip first. `UPGRADE_BASE_VERSION` is RETAINED as an + optional explicit override (wins when set) for the rare PR-adds-version-above-newest-tag case, but is + no longer the default and is removed from discourse. This intentionally changes every recipe's default + base from `vers[-2]` to last-green/main-tip (plan-mandated; M2 spot-check validates non-regression). + +- **Per-recipe `previous/` overlay — SETTLED (2026-06-17, phase prevb).** `tests//previous/` + optionally holds the minimal config to deploy the *previous (last-green) version* when it can't deploy + as-published (e.g. `compose.previous.yml` for an image relocation). It declares the version it targets + (a `previous/VERSION` marker line) and the harness applies it **only to the base deploy and only when + the resolved base is that exact published version**; it is NEVER applied to the PR head, and on a + main-tip base or version mismatch it is SKIPPED and flagged stale ("previous/ targets X, base is Y — + remove it"). The all-deploys `compose.ccci.yml` overlay is now ENVIRONMENTAL-only (node-reality tweaks, + no version-specific image pins or service add/drop); version-specific repairs live in `previous/`. + Discourse ships NO `previous/` (base bitnamilegacy:3.5.0 deploys clean). diff --git a/machine-docs/JOURNAL-prevb.md b/machine-docs/JOURNAL-prevb.md new file mode 100644 index 0000000..339cb84 --- /dev/null +++ b/machine-docs/JOURNAL-prevb.md @@ -0,0 +1,37 @@ +# JOURNAL — phase `prevb` (Builder reasoning; append-only) + +## 2026-06-17 — Bootstrap + recon + +Read SSOT (plan-phase-prevb), plan.md §6.1/§7/§9, Adversary's REVIEW-prevb (live, idle awaiting M1 claim). + +**Mapped the harness upgrade flow** (`runner/run_recipe_ci.py`, `harness/lifecycle.py`, +`harness/generic.py`, `harness/meta.py`, `harness/canonical.py`): +- Base decision: `upgrade_base(stages, meta, recipe)` → `None` if upgrade∉stages or EXPECTED_NA[upgrade], + else `meta.UPGRADE_BASE_VERSION or lifecycle.previous_version(recipe)` (= `recipe_versions[-2]`). + `base = prev or target`; `prev` also gates whether the upgrade tier runs. +- Deploy: `deploy_app(version=base)` → pinned `recipe_checkout(version)` + (auto-chaos if overlay/lightweight tag); + `version=None` → chaos deploy of the current (head) checkout. +- Overlay `compose.ccci.yml`: copied into the checkout (`provide_ccci_overlay`), referenced by + `EXTRA_ENV.COMPOSE_FILE`, persists untracked across the head re-checkout → applies to ALL deploys. +- Upgrade op (`generic.perform_upgrade`): `recipe_checkout_ref(head_ref)` then chaos redeploy; the + ccci overlay persists → leaks version-specific pins onto the head. **That is the bug.** +- Last-green source: `canonical.read_registry(recipe)` → `{version, commit, status}` (promoted only on + GREEN LATEST cold runs for `WARM_CANONICAL` recipes). No separate "last-green" file. + +**Ground-truth discourse facts** (gitea API, verified — see STATUS for the table). Key correction vs +plan §3 prose: main is `bitnamilegacy/discourse:3.5.0` (not 3.3.1 — main advanced). Thesis holds: base +(last-green/main = bitnamilegacy 3.5.0, deployable) → head (PR #4 = official discourse/discourse:3.5.3, +sidekiq dropped). So discourse needs NO `previous/`; the env overlay shrinks to `order: stop-first`. + +**Design decisions (WHY):** +- *Resolution order* last-green → main-tip → skip. main-tip = the recipe's `main` branch HEAD = the true + predecessor the PR merges onto (more faithful than the old `vers[-2]`, which could span 2 version jumps). + This intentionally changes EVERY recipe's default base from `vers[-2]` to main-tip — plan-mandated, not a + regression; M2 spot-check validates representative recipes still go green. +- *Keep `UPGRADE_BASE_VERSION` as an optional explicit override* (still wins when set), but remove it from + discourse and make the DEFAULT dynamic. Rationale: fully deleting the meta field would break `plausible` + (its meta sets it) and the documented "PR adds a version above newest tag" escape hatch, without a deploy + test — risk vs guardrail "don't regress other recipes". The plan's "UPGRADE_BASE_VERSION removed" is in the + discourse-migration context; the normal/discourse path is now hardcode-free. Recorded in DECISIONS. +- *`previous/` scoped to last-green (published-version) base only* — version-guarded by a declared target; + on a main-tip base or version mismatch it is skipped + flagged stale. Discourse ships none (base deploys clean). diff --git a/machine-docs/STATUS-prevb.md b/machine-docs/STATUS-prevb.md new file mode 100644 index 0000000..7543b52 --- /dev/null +++ b/machine-docs/STATUS-prevb.md @@ -0,0 +1,27 @@ +# STATUS — phase `prevb` (dynamic upgrade base + per-recipe `previous/`) + +SSOT: `/srv/cc-ci/cc-ci-plan/plan-phase-prevb-previous-dynamic-base.md`. +State files: this + BACKLOG-prevb.md, REVIEW-prevb.md (Adversary), JOURNAL-prevb.md. DECISIONS.md shared. + +## Phase +Started 2026-06-17. Gates: **M1** (implemented + green locally), **M2** (proven in real CI + spot-check). + +## Now +- In flight: M1 implementation (dynamic base resolution + `previous/` mechanism + discourse migration + unit tests). +- No gate CLAIMED yet. + +## Ground-truth facts (verified 2026-06-17, recorded for Adversary) +- `recipe-maintainers/discourse` PR **#4** (`discourse-official-image` `ae5a8180` → `main` `f87c612d`), open. + - **main** (`compose.yml`): `app`/`sidekiq` image = `bitnamilegacy/discourse:3.5.0`; `app` healthcheck + `start_period: 20m`; `app.deploy.update_config.order: start-first`; `sidekiq` service present. + - **PR #4 head**: `app.image = discourse/discourse:3.5.3` (official), **`sidekiq` service deleted**, + loadbalancer port 3000→80, official-image entrypoint wrappers added. (PR `.diff` confirms both.) + - Published tags max = `0.7.0+3.3.1`; main (3.5.0) is AHEAD of all tags → main-tip is a branch ref, not a tag. +- Current `tests/discourse/compose.ccci.yml` re-pins `app`+`sidekiq` to `bitnamilegacy/discourse:3.3.1`, + re-adds `sidekiq`, sets `start_period:20m`, `order:stop-first` — applied to ALL deploys via + `EXTRA_ENV.COMPOSE_FILE` → forces the PR head back to bitnamilegacy:3.3.1 + sidekiq (the bug). +- Note vs plan §3 prose: main is `bitnamilegacy:3.5.0`, not `3.3.1` (main advanced); thesis unchanged — + the base (last-green/main, bitnamilegacy 3.5.0) deploys clean, NO `previous/` needed for discourse. + +## Blocked +(none)