From d4cc9e453000b4ba919a74e7bfa2e1678fe4fddd Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Wed, 17 Jun 2026 06:47:33 +0000 Subject: [PATCH] fix(canon): promote the TESTED release version, not a re-derived latest tag Closes the head_version-vs-latest_version divergence: should_promote gates on head_version (code under test) but promote_canonical recorded latest_version(recipe_tags). In a manual RECIPE= run whose main checkout sits on a tag OLDER than the newest published tag, the gate would pass on the older tag yet promote the newer (never-tested) one. promote_canonical now takes the tested `version` (head_version, guaranteed a release tag by the tagged-gate) and records exactly that. Sweep path unaffected (head==tag by construction). Co-Authored-By: Claude Opus 4.8 --- runner/run_recipe_ci.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/runner/run_recipe_ci.py b/runner/run_recipe_ci.py index 1987446..745e9e8 100644 --- a/runner/run_recipe_ci.py +++ b/runner/run_recipe_ci.py @@ -921,30 +921,36 @@ def should_promote_canonical( return canonical.is_enrolled(recipe) and overall == 0 and not quick and not ref and tagged -def promote_canonical(recipe: str, head_ref: str | None) -> None: - """WC5: (re)seed the canonical at the green-verified LATEST. Deploy `warm-` at latest - (reattaching the retained canonical volume if one exists — an in-place version bump — else a fresh - install), wait healthy, undeploy, snapshot + record the registry (atomic replace of the - last-known-good). The OLD known-good is replaced ONLY here, after green (never lost on a red run).""" +def promote_canonical(recipe: str, head_ref: str | None, version: str | None) -> None: + """canon §2.A / WC5: (re)seed the canonical at the GREEN-VERIFIED TESTED RELEASE `version` — the + exact version under test (head_version), which the should_promote tagged-gate guarantees is a + published release tag. Deploy `warm-` at that version (reattaching the retained canonical + volume if one exists — an in-place version bump — else a fresh install), wait healthy, undeploy, + snapshot + record the registry (atomic replace of the last-known-good). + + Promotes EXACTLY the tested version — it no longer re-derives `latest_version(recipe_tags)`, which + could differ from the version actually exercised by the run (e.g. a manual `RECIPE=` run whose + `main` checkout sits on a tag older than the newest published tag): the canonical must record the + version the tier suite proved green, not a never-tested newer tag. The OLD known-good is replaced + ONLY here, after green (never lost on a red run).""" import warm_reconcile as wr domain = canonical.canonical_domain(recipe) - wr.fetch_recipe(recipe) - latest = wr.latest_version(wr.recipe_tags(recipe)) - if not latest: - print(f"WC5 promote: no version tags for {recipe} — skip", flush=True) + if not version: + print(f"WC5 promote: no tested release version for {recipe} — skip", flush=True) return + wr.fetch_recipe(recipe) # ensure the release tag is present locally for the pinned checkout meta = meta_mod.load(recipe) # The cold run's deploy-count was already asserted + the countfile removed; don't perturb it. os.environ.pop("CCCI_DEPLOY_COUNT_FILE", None) print( - f"\n===== WC5 promote-on-green-cold: (re)seed canonical {recipe} @ {latest} =====", + f"\n===== WC5 promote-on-green-cold: (re)seed canonical {recipe} @ {version} =====", flush=True, ) lifecycle.deploy_app( recipe, domain, - version=latest, + version=version, secrets=True, deploy_timeout=int(meta.DEPLOY_TIMEOUT), meta=meta, @@ -958,9 +964,9 @@ def promote_canonical(recipe: str, head_ref: str | None) -> None: ) abra.undeploy(domain) _wait_undeployed(domain) - canonical.seed_canonical(recipe, latest, commit=head_ref) + canonical.seed_canonical(recipe, version, commit=head_ref) print( - f"WC5 promote: canonical {recipe} advanced to known-good {latest} (idle, volume retained)", + f"WC5 promote: canonical {recipe} advanced to known-good {version} (idle, volume retained)", flush=True, ) @@ -1494,7 +1500,7 @@ def main() -> int: tagged = warm_reconcile.is_released_version(recipe, head_version) if should_promote_canonical(recipe, ref, overall, quick=False, tagged=tagged): try: - promote_canonical(recipe, head_ref) + promote_canonical(recipe, head_ref, head_version) except Exception as e: # noqa: BLE001 — promote is a post-green bonus; never fail a green run print( f"!! WC5 promote failed (non-fatal; known-good unchanged): {_scrub(str(e))}",