diff --git a/runner/run_recipe_ci.py b/runner/run_recipe_ci.py index 19d8167..e2dbc40 100644 --- a/runner/run_recipe_ci.py +++ b/runner/run_recipe_ci.py @@ -175,6 +175,36 @@ def resolve_upgrade_base( flush=True, ) rec = None + # STALE-CANONICAL GUARD (phase relbase): the canonical is only "last-green", NOT "current". Its + # promotion can fail for reasons unrelated to any PR (the WC5 promote deploys a warm- app + # and health-checks it), and a failed promote leaves the canonical pinned at an OLD release + # indefinitely — gitea sat at 3.5.3+1.24.2 from 2026-06-17 to 2026-08-10 because warm-gitea + # crash-looped on a read-only app.ini. Basing the upgrade tier on that stale release tests a + # transition no deployment performs, and can silently MISS breaks in the transition users do + # perform: gitea 3.5.3→head crosses an APP_INI_VERSION change (v21→v22) so Swarm creates a fresh + # config, while the real 3.6.1→3.6.2 upgrade keeps v22 and aborts on Swarm's immutable-config + # rule. Prefer the newest published release older than head whenever it is newer than the + # canonical: that is what real installs upgrade from. + if rec and rec.get("version") and not skip_canonicals and head_version: + _rel_tags = warm_reconcile.recipe_tags(recipe) + if floor: + _rel_tags = [t for t in _rel_tags if not _below_floor(t)] + newest_rel = warm_reconcile.newest_older_version(_rel_tags, head_version) + if newest_rel and warm_reconcile.version_key(newest_rel) > warm_reconcile.version_key( + rec["version"] + ): + print( + f"== upgrade base: newest published release {newest_rel} is NEWER than the " + f"last-green canonical {rec['version']} — using the release (what deployments " + f"actually upgrade from); canonical is stale, check its WC5 promote", + flush=True, + ) + return BasePlan( + "version", + newest_rel, + None, + f"newest published release older than head (canonical {rec['version']} is stale)", + ) if rec and rec.get("version") and not skip_canonicals: canon = rec["version"] same = head_version is not None and warm_reconcile.version_key(