From b40dcb504c21361b78e9d65dc50cc8a144c63e3c Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Wed, 17 Jun 2026 03:50:49 +0000 Subject: [PATCH] plan: queue samever (older-base fallback when last-green==head, opus); IDEAS: defer canonical-history (B) Operator 2026-06-17. Closes the prevb resolver gap: when the last-green warm-canonical base version equals the PR head version, step back to the newest published version strictly older than head (design A) instead of a same-version no-op or a skip. Design B (canonical history for a green-verified older base) saved to IDEAS. Auto-runs after regall (watchdog advances + switches to opus). --- cc-ci-plan/IDEAS.md | 19 ++++ cc-ci-plan/agents.toml | 2 + .../plan-phase-samever-older-base-fallback.md | 92 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 cc-ci-plan/plan-phase-samever-older-base-fallback.md diff --git a/cc-ci-plan/IDEAS.md b/cc-ci-plan/IDEAS.md index 7b78228..d44637f 100644 --- a/cc-ci-plan/IDEAS.md +++ b/cc-ci-plan/IDEAS.md @@ -189,3 +189,22 @@ item into the project `BACKLOG.md` as `[idea]` if/when it becomes relevant. harness `finally` tears down its stack). - *When to revisit:* if CI-queue starvation recurs (several recipes in flight, or a legitimately long deploy wedging others). *Added:* 2026-06-09. + +- [ ] **Canonical *history* for a green-verified older upgrade base (design B).** *(operator-flagged + 2026-06-17; deferred from the `samever` fix — design A shipped instead)* + Context: the dynamic upgrade-base resolver (phase `prevb`) uses the last-green warm-canonical as the + base. When that canonical version **equals the PR head version** (a non-version-bump PR, or a re-test + after the canonical advanced), phase `samever` (design A) steps back to the **newest published version + strictly older than head** (from `recipe_tags`). That older *published* version isn't guaranteed to + have passed green on cc-ci. + **The improvement (B):** keep a short **history** in the warm-canonical registry — the last N green + `{version, commit}` records, not just the single current one (`canonical.read_registry`/`write_registry` + are single-record today). When stepping back, prefer the most-recent **prior canonical** (green-verified) + over a raw published tag; fall back to design A's published-tag only when no prior canonical exists. + - *Why deferred:* history only accrues going forward (existing recipes have none until they've gone + green ≥2× on cold-on-latest), so design A (published-tag) is the always-present floor and must exist + anyway. B is a quality refinement on top. Requires a registry schema change (single record → bounded + history) + `promote_canonical`/`write_registry` appending instead of overwriting. + - *Re-entry trigger:* when the published-tag fallback proves insufficient — e.g. a recipe's + newest-older-published version is itself undeployable, so we'd rather upgrade from a known-green prior + canonical — or operator request. *Added:* 2026-06-17. diff --git a/cc-ci-plan/agents.toml b/cc-ci-plan/agents.toml index eff998c..c39bc97 100644 --- a/cc-ci-plan/agents.toml +++ b/cc-ci-plan/agents.toml @@ -158,4 +158,6 @@ phases = [ { id = "prevb", plan = "plan-phase-prevb-previous-dynamic-base.md", status = "STATUS-prevb.md", models = { builder = "claude-opus-4-8", adversary = "claude-opus-4-8" } }, # full all-recipe regression after prevb (sonnet) — see plan-phase-regall-*.md (operator 2026-06-16) { id = "regall", plan = "plan-phase-regall-recipe-regression.md", status = "STATUS-regall.md", models = { builder = "claude-sonnet-4-6", adversary = "claude-sonnet-4-6" } }, + # same-version upgrade-base gap: step back to newest-older-published when last-green==head (opus, design A; B in IDEAS) — see plan-phase-samever-*.md (operator 2026-06-17) + { id = "samever", plan = "plan-phase-samever-older-base-fallback.md", status = "STATUS-samever.md", models = { builder = "claude-opus-4-8", adversary = "claude-opus-4-8" } }, ] diff --git a/cc-ci-plan/plan-phase-samever-older-base-fallback.md b/cc-ci-plan/plan-phase-samever-older-base-fallback.md new file mode 100644 index 0000000..3e60d9c --- /dev/null +++ b/cc-ci-plan/plan-phase-samever-older-base-fallback.md @@ -0,0 +1,92 @@ +# Phase `samever` — step back to an older base when last-green == head (no same-version upgrade) + +**Mission (operator-specified 2026-06-17):** close a gap in the `prevb` dynamic upgrade-base resolver. +When the resolved **last-green (warm-canonical) base version equals the PR head version**, the upgrade +tier currently deploys the **same version** as base and head — a vacuous, non-upgrade "upgrade." Instead +of skipping the tier, **step back to a genuinely older base**: the **newest published version strictly +older than the head version**. The upgrade must always cross a real version delta when an older version +exists. (This is design **A**; design B "canonical history" is deferred — see `cc-ci-plan/IDEAS.md`.) + +State files: `STATUS-samever.md`, `BACKLOG-samever.md`, `REVIEW-samever.md`, `JOURNAL-samever.md`. DECISIONS.md shared. + +## 1. Background / root cause + +`resolve_upgrade_base` (`runner/run_recipe_ci.py:111`) resolves the upgrade base. Its two paths are +guarded **unequally**: +- **ref (main-tip) path — guarded:** `if main_tip == head_ref → skip "head == main tip (no predecessor + delta)"`. +- **version (last-green canonical) path — NOT guarded:** it returns `BasePlan("version", rec["version"], + …)` without checking that the canonical version differs from the head. The resolver isn't even given + the head's *version* (only `head_ref`, a commit), so it currently can't compare. + +When does the canonical equal the head version? The canonical advances **only** on a GREEN + COLD + +LATEST run of a `WARM_CANONICAL`-enrolled recipe (`should_promote_canonical` = `is_enrolled and +overall==0 and not quick and not ref`; a PR `!testme` carries `ref` so it NEVER promotes). So the +canonical is always the latest-published version that last passed a cold sweep. The gap therefore bites +when a PR **does not bump the version** (head version == latest published == canonical), or on a re-test +after the canonical advanced to the head's version — base and head become the same version. + +## 2. Design (A) + +Give the resolver the **head version** (read the `coop-cloud.*.version` label from the head's +`compose.yml` — the head checkout already exists), and extend the chain: + +1. explicit `UPGRADE_BASE_VERSION` override → use it (unchanged). +2. **last-green canonical, IF its version ≠ head version** → use it (`kind="version"`; the green-verified + primary). +3. **last-green canonical version == head version** → **do NOT skip.** Step back: from the recipe's + published version tags (`warm_reconcile.recipe_tags` + the existing version-ordering used by + `latest_version`), pick the **newest published version STRICTLY OLDER than the head version** and use + it (`kind="version"`). `previous/` still applies version-guarded against whatever base version is + chosen. +4. no canonical at all → existing **main-tip ref** path (use if `main_tip ≠ head_ref`, else skip) — + unchanged. +5. **only if no older published version exists** (genuinely the first version / no predecessor) → skip + with a declared reason (`"base == head and no older published predecessor"`). + +Constraints: +- "Strictly older" — exclude any tag equal to the head version; reuse the existing coop-cloud version + ordering, do not hand-roll semver. If the head version isn't in the published tag list (a brand-new + version above all tags), the canonical-≠-head branch already handles it — the step-back only triggers + when canonical == head. +- Preserve the **F1d-2** protections: the chosen older base must actually deploy that *pinned* version + (checkout the tag so the on-disk tree matches), never LATEST. +- Pure resolver change where possible; keep the `ref` and `skip` paths' behavior identical for all other + cases (don't perturb discourse #4 or any version-bump PR). + +## 3. Gates + +**M1 — implemented + unit-tested.** Resolver reads the head version and implements the chain above. +Unit tests (extend `tests/unit/test_upgrade_base.py`): canonical==head → resolves to the newest-older +published version (assert it's strictly older); canonical≠head → uses canonical (unchanged); no +older-published → declared skip with the new reason; head-version parsed from compose; version ordering +picks the correct strictly-older tag; override + ref + existing-skip paths unchanged. Adversary +cold-verifies from a clean checkout: a same-version PR now upgrades from a **real older version** (base +version < head version, evidenced), not a no-op and not a skip; teeth (a broken head still RED); the +version-bump path (canonical→head) is untouched. + +**M2 — proven in real CI.** Demonstrate on a **same-version scenario**: a recipe+PR where head version == +canonical version (a non-version-bump PR, or construct it by pointing the canonical at the head's +version), and show the upgrade tier deploys an **older published base → head** with evidence +`base_version < head_version` (a genuine delta, HC1 confirms head is head). Confirm a normal version-bump +PR — re-run **discourse #4** or an equivalent — is **UNAFFECTED** (still canonical→head). Spot-check ≥1 +other enrolled recipe. Fresh Adversary PASS on both milestones → `## DONE`. + +## 4. Guardrails + +- **Never a same-version no-op, and never a needless skip when an older base exists.** Skip only when + there is genuinely no older published predecessor. +- **The base must be strictly older than the head version.** +- **Don't regress the version-bump path** — the common upgrade-PR case (canonical → head) must behave + exactly as before; discourse #4 must still test the official-image migration. +- Never weaken a test; minimal, well-scoped resolver change; `previous/` stays the last resort. +- Commit author `autonomic-bot `; push every commit; abra over a + pseudo-TTY. Recipe mirrors PR-only; never merge. + +## 5. Definition of Done + +The resolver steps back to the newest-published-version-older-than-head whenever the last-green canonical +equals the head version — never a same-version no-op, never a needless skip when an older base exists; +unit-tested; proven in real CI on a same-version scenario with evidence of a real base