release: one strict stable-tag grammar shared by every selector

scripts/releases/semver.STABLE_TAG accepted any-width majors, so
is_valid_version('2026.9.21') was True and docker.require_stable_tag /
stable.py / release.py admitted the legacy CalVer tags that
hermes_cli.source_releases and get_last_tag() already refused. A
workflow_call carrying GitHub's current 'latest' (v2026.9.21) would have
passed the docker publish gate.

hermes_cli.update_channel already owns the canary tag shape; it now owns
STABLE_TAG_RE too (three-digit major cap, no leading zeros, no suffix)
and every stable selector imports it. release.py drops its private
_SEMVER_TAG_RE + CalVer exclusion pair, which the capped major makes
redundant.
This commit is contained in:
ethernet
2026-09-21 18:42:01 -04:00
parent 3dc75f5ab7
commit d7466aa3ba
9 changed files with 50 additions and 31 deletions

View File

@@ -36,7 +36,7 @@ from pathlib import Path
# is import-light: only os/sys + version constants).
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from hermes_cli.update_channel import _CANARY_TAG_RE, canary_tag_for_date # noqa: E402
from hermes_cli.update_channel import _CANARY_TAG_RE, STABLE_TAG_RE, canary_tag_for_date # noqa: E402
REPO_ROOT = Path(__file__).resolve().parent.parent
VERSION_FILE = REPO_ROOT / "hermes_cli" / "__init__.py"
@@ -2163,8 +2163,7 @@ def dispatch_desktop_build(tag: str, gh_repo: str | None) -> bool:
Explicit dispatch also works for tags created by GITHUB_TOKEN.
"""
canary = _CANARY_TAG_RE.fullmatch(tag) is not None
from scripts.releases.semver import STABLE_TAG
if not canary and not STABLE_TAG.fullmatch(tag):
if not canary and not STABLE_TAG_RE.fullmatch(tag):
raise ValueError("Expected an exact stable or canary release tag")
workflow = "desktop-bundled-release.yml" if canary else "stable-release.yml"
cmd = ["gh", "workflow", "run", workflow, "--ref", "main" if canary else tag,
@@ -2249,15 +2248,12 @@ def remote_github_repo(remote: str) -> str | None:
return match.group(1) if match else None
# Cap the major at three digits, as in scripts/write_install_stamp.py.
# The legacy CalVer tags (v2026.7.20) must never match as SemVer.
_SEMVER_TAG_RE = re.compile(r"v(?:0|[1-9]\d{0,2})\.\d+\.\d+$")
_LEGACY_CALVER_TAG_RE = re.compile(r"v20\d{2}\.\d+\.\d+(?:\.\d+)?$")
# Canary prerelease tags are matched with _CANARY_TAG_RE, imported from
# hermes_cli.update_channel — the single authority for the canary tag
# shape (v<major>.<minor>.<any patch>-canary.<YYYYMMDDHHMMSS>, plus the
# legacy date-only form). The suffix keeps them out of every stable
# selector (all of which require the no-suffix SemVer shape above).
# Stable tags are matched with STABLE_TAG_RE and canary prerelease tags
# with _CANARY_TAG_RE, both imported from hermes_cli.update_channel — the
# single authority for both tag shapes (the stable major is capped at three
# digits so legacy CalVer tags like v2026.7.20 never match; the canary shape
# is v<major>.<minor>.<any patch>-canary.<YYYYMMDDHHMMSS>, plus the legacy
# date-only form). The suffix keeps canaries out of every stable selector.
# Second precision so manual fires can publish several canaries per day;
# the identifier is pure numeric and fixed-length, so semver prerelease
# comparison (numeric) and lexical sort both order it chronologically.
@@ -2274,7 +2270,7 @@ def get_last_tag():
if tags:
tag_list = tags.split("\n")
for tag in tag_list:
if _SEMVER_TAG_RE.fullmatch(tag) and not _LEGACY_CALVER_TAG_RE.fullmatch(tag):
if STABLE_TAG_RE.fullmatch(tag):
return tag
legacy_tags = git("tag", "--list", "v20*", "--sort=-v:refname")

View File

@@ -10,7 +10,7 @@ import sys
MANIFEST_SCHEMA = 1
SHA256 = re.compile(r"[a-f0-9]{64}")
GIT_SHA = re.compile(r"[a-f0-9]{40}")
from scripts.releases.semver import STABLE_TAG
from hermes_cli.update_channel import STABLE_TAG_RE
ARCHES = ("amd64", "arm64")
class DockerReleaseError(ValueError):
@@ -18,7 +18,7 @@ class DockerReleaseError(ValueError):
def require_stable_tag(tag: str) -> str:
if not isinstance(tag, str) or not STABLE_TAG.fullmatch(tag or ""):
if not isinstance(tag, str) or not STABLE_TAG_RE.fullmatch(tag or ""):
raise DockerReleaseError(f"Not a stable release tag: {tag!r}")
return tag

View File

@@ -1,10 +1,7 @@
"""Compare the stable and canary versions accepted by release feeds."""
from __future__ import annotations
import re
from hermes_cli.update_channel import _CANARY_TAG_RE
STABLE_TAG = re.compile(r"v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)")
from hermes_cli.update_channel import _CANARY_TAG_RE, STABLE_TAG_RE
def is_valid_version(version: str) -> bool:
@@ -14,10 +11,10 @@ def is_valid_version(version: str) -> bool:
if not isinstance(version, str):
return False
core, sep, tail = version.partition("-")
if sep and not STABLE_TAG.fullmatch("v" + core):
if sep and not STABLE_TAG_RE.fullmatch("v" + core):
return False
if not sep:
return bool(STABLE_TAG.fullmatch("v" + version))
return bool(STABLE_TAG_RE.fullmatch("v" + version))
if not _CANARY_TAG_RE.fullmatch("v" + version):
return False
# Canary timestamp is a fixed-length 14-digit numeric stamp.

View File

@@ -13,7 +13,7 @@ import urllib.request
from pathlib import Path
from urllib.parse import unquote, urlsplit
from scripts.releases.semver import STABLE_TAG
from hermes_cli.update_channel import STABLE_TAG_RE
SHA = re.compile(r"[a-f0-9]{40}")
DIGEST = re.compile(r"[a-f0-9]{64}")
DESKTOP_TARGETS = ("windows/x64", "windows/arm64", "macos/x64", "macos/arm64")
@@ -25,7 +25,7 @@ SMOKE_JOBS = {
def require_stable_identity(tag: str, commit: str, ref: str) -> None:
if not isinstance(tag, str) or not STABLE_TAG.fullmatch(tag) or not SHA.fullmatch(commit or "") or ref != f"refs/tags/{tag}":
if not isinstance(tag, str) or not STABLE_TAG_RE.fullmatch(tag) or not SHA.fullmatch(commit or "") or ref != f"refs/tags/{tag}":
raise ValueError("Stable release must run on its exact stable tag and commit")