feat(canon): M1.1 tagged-promote gate — canonical only advances to a published release tag
continuous-integration/drone/push Build is passing

- should_promote_canonical gains a `tagged` requirement (canon §2.A): a green cold
  latest run promotes only when the tested head version is a published release tag;
  an untagged main commit never becomes a canonical.
- warm_reconcile.is_released_version(recipe, version): release-tag membership (exact or
  by version_key). Caller computes `tagged` so the gate stays pure.
- unit tests: untagged -> no promote; is_released_version cases.
- drive-by (pre-existing reds, unrelated to canon, now green): test_warm_reconcile
  traefik assertion was stale vs the phase-pxgate spec (probes /api/version, no
  health_domain); meta.py UPGRADE_BASE_VERSION KEYS help synced to the prevb doc text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
autonomic-bot
2026-06-17 06:34:09 +00:00
co-authored by Claude Opus 4.8
parent 23c02c59b6
commit 27e06289f8
5 changed files with 74 additions and 18 deletions
+14
View File
@@ -185,6 +185,20 @@ def latest_version(tags) -> str | None:
return s[-1] if s else None
def is_released_version(recipe: str, version: str | None) -> bool:
"""True iff `version` corresponds to a PUBLISHED RELEASE TAG of the recipe (phase canon §2.A:
the canonical may only ever advance to a real release — never an arbitrary untagged `main`
commit). Match is exact, or by `version_key` so a re-formatted-but-equal version still counts.
A recipe with no release tags, or a `version` that matches no tag, is NOT a release."""
if not version:
return False
tags = recipe_tags(recipe)
if version in tags:
return True
vk = version_key(version)
return any(is_version_tag(t) and version_key(t) == vk for t in tags)
def _major(semver: str) -> int:
return _numtuple(semver)[0] if semver else 0