fix(canon): promote the TESTED release version, not a re-derived latest tag
All checks were successful
continuous-integration/drone/push Build is passing
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:
@ -921,30 +921,36 @@ def should_promote_canonical(
|
|||||||
return canonical.is_enrolled(recipe) and overall == 0 and not quick and not ref and tagged
|
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:
|
def promote_canonical(recipe: str, head_ref: str | None, version: str | None) -> None:
|
||||||
"""WC5: (re)seed the canonical at the green-verified LATEST. Deploy `warm-<recipe>` at latest
|
"""canon §2.A / WC5: (re)seed the canonical at the GREEN-VERIFIED TESTED RELEASE `version` — the
|
||||||
(reattaching the retained canonical volume if one exists — an in-place version bump — else a fresh
|
exact version under test (head_version), which the should_promote tagged-gate guarantees is a
|
||||||
install), wait healthy, undeploy, snapshot + record the registry (atomic replace of the
|
published release tag. Deploy `warm-<recipe>` at that version (reattaching the retained canonical
|
||||||
last-known-good). The OLD known-good is replaced ONLY here, after green (never lost on a red run)."""
|
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
|
import warm_reconcile as wr
|
||||||
|
|
||||||
domain = canonical.canonical_domain(recipe)
|
domain = canonical.canonical_domain(recipe)
|
||||||
wr.fetch_recipe(recipe)
|
if not version:
|
||||||
latest = wr.latest_version(wr.recipe_tags(recipe))
|
print(f"WC5 promote: no tested release version for {recipe} — skip", flush=True)
|
||||||
if not latest:
|
|
||||||
print(f"WC5 promote: no version tags for {recipe} — skip", flush=True)
|
|
||||||
return
|
return
|
||||||
|
wr.fetch_recipe(recipe) # ensure the release tag is present locally for the pinned checkout
|
||||||
meta = meta_mod.load(recipe)
|
meta = meta_mod.load(recipe)
|
||||||
# The cold run's deploy-count was already asserted + the countfile removed; don't perturb it.
|
# The cold run's deploy-count was already asserted + the countfile removed; don't perturb it.
|
||||||
os.environ.pop("CCCI_DEPLOY_COUNT_FILE", None)
|
os.environ.pop("CCCI_DEPLOY_COUNT_FILE", None)
|
||||||
print(
|
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,
|
flush=True,
|
||||||
)
|
)
|
||||||
lifecycle.deploy_app(
|
lifecycle.deploy_app(
|
||||||
recipe,
|
recipe,
|
||||||
domain,
|
domain,
|
||||||
version=latest,
|
version=version,
|
||||||
secrets=True,
|
secrets=True,
|
||||||
deploy_timeout=int(meta.DEPLOY_TIMEOUT),
|
deploy_timeout=int(meta.DEPLOY_TIMEOUT),
|
||||||
meta=meta,
|
meta=meta,
|
||||||
@ -958,9 +964,9 @@ def promote_canonical(recipe: str, head_ref: str | None) -> None:
|
|||||||
)
|
)
|
||||||
abra.undeploy(domain)
|
abra.undeploy(domain)
|
||||||
_wait_undeployed(domain)
|
_wait_undeployed(domain)
|
||||||
canonical.seed_canonical(recipe, latest, commit=head_ref)
|
canonical.seed_canonical(recipe, version, commit=head_ref)
|
||||||
print(
|
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,
|
flush=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1494,7 +1500,7 @@ def main() -> int:
|
|||||||
tagged = warm_reconcile.is_released_version(recipe, head_version)
|
tagged = warm_reconcile.is_released_version(recipe, head_version)
|
||||||
if should_promote_canonical(recipe, ref, overall, quick=False, tagged=tagged):
|
if should_promote_canonical(recipe, ref, overall, quick=False, tagged=tagged):
|
||||||
try:
|
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
|
except Exception as e: # noqa: BLE001 — promote is a post-green bonus; never fail a green run
|
||||||
print(
|
print(
|
||||||
f"!! WC5 promote failed (non-fatal; known-good unchanged): {_scrub(str(e))}",
|
f"!! WC5 promote failed (non-fatal; known-good unchanged): {_scrub(str(e))}",
|
||||||
|
|||||||
Reference in New Issue
Block a user