feat(samever): step back to older base when last-green canonical == head version
resolve_upgrade_base now reads the head's published version (abra.head_compose_version, the coop-cloud.<stack>.version label) and, when the last-green warm-canonical version equals it, steps back to the newest published version strictly older than head instead of deploying a same-version no-op. warm_reconcile gains version_key + newest_older_version (single coop-cloud ordering source; sort_versions refactored onto version_key, no behavior change). Skip only when no older published predecessor exists. Step-back returns kind=version so it inherits F1d-2 pinned-tag checkout. Extends tests/unit/test_upgrade_base.py (13 pass).
This commit is contained in:
@@ -145,14 +145,31 @@ def is_version_tag(tag: str) -> bool:
|
||||
return bool(_VER_RE.match(tag.strip()))
|
||||
|
||||
|
||||
def version_key(tag: str):
|
||||
"""Ordering key for a coop-cloud version tag: (recipe-semver tuple, app-version tuple). The single
|
||||
source of version ordering — sort_versions / newest_older_version both key on this so the
|
||||
upgrade-base step-back (phase samever) never hand-rolls semver comparison."""
|
||||
recipe, _, app = tag.partition("+")
|
||||
return (_numtuple(recipe), _numtuple(app))
|
||||
|
||||
|
||||
def sort_versions(tags) -> list[str]:
|
||||
"""Sort coop-cloud version tags ascending by (recipe-semver tuple, app-version tuple)."""
|
||||
return sorted([t for t in tags if is_version_tag(t)], key=version_key)
|
||||
|
||||
def key(t: str):
|
||||
recipe, _, app = t.partition("+")
|
||||
return (_numtuple(recipe), _numtuple(app))
|
||||
|
||||
return sorted([t for t in tags if is_version_tag(t)], key=key)
|
||||
def newest_older_version(tags, version: str | None) -> str | None:
|
||||
"""The newest published version tag STRICTLY OLDER than `version` (coop-cloud ordering), or None
|
||||
when no such tag exists. Used by the upgrade-base resolver (phase samever): when the last-green
|
||||
warm-canonical version equals the head version, the resolver steps back to this older base so the
|
||||
upgrade tier always crosses a real version delta instead of deploying a same-version no-op.
|
||||
"Strictly older" excludes any tag whose ordering key equals the head's (so a re-published or
|
||||
differently-formatted equal version is never chosen)."""
|
||||
if not version:
|
||||
return None
|
||||
target = version_key(version)
|
||||
older = [t for t in sort_versions(tags) if version_key(t) < target]
|
||||
return older[-1] if older else None
|
||||
|
||||
|
||||
def _numtuple(s: str) -> tuple:
|
||||
|
||||
Reference in New Issue
Block a user