fix(canon): promote the TESTED release version, not a re-derived latest tag
All checks were successful
continuous-integration/drone/push Build is passing

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=<r> 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 <noreply@anthropic.com>
This commit is contained in:
autonomic-bot
2026-06-17 06:47:33 +00:00
parent a20890a363
commit d4cc9e4530

View File

@ -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-<recipe>` 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-<recipe>` 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=<r>` 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))}",