Files
hermes-agent/tests/scripts/test_release_version_from_ref.py
ethernet8023 792ba0a264 fix(release): read a tag's record, not its signature
git keeps an annotated tag's signature inside the message body, so
`git tag -l --format=%(contents)` returns the JSON record followed by an
armored block, and `json.loads` of the whole message raises "Extra data: line 2
column 1" at the armor's first byte. Claim tags, final receipts and build
receipts are created on a host whose git signs tags, so stable admission
(`python -m scripts.releases.stable admit`), ordered reconciliation
(`python -m scripts.releases.sequencer`) and tagless build receipts each
refused to read their own tag.

Add `versioning.tag_record()` as the one reader of a tag's record — the text
before the armor, unchanged for an unsigned tag or a detached signature — and
route every `%(contents)` read through it in stable.py, sequencer.py and
commit_build.py. commit_build imports it inside the function so its module
surface stays stdlib-only for the isolated checkout-admission step.

The reads inside the release tests use the same reader, and the one fixture
that creates a plain final tag says so explicitly, so the release suite passes
on a host whose git signs tags by default.
2026-09-29 14:55:15 -04:00

183 lines
6.9 KiB
Python

"""A ref names a version, and the next version is derived, never read from the tree.
Derivation reads the published stable head, or the seed ``0.21.4`` before one
exists. Attempt refs number attempts within a version and never move the line.
"""
import json
import pytest
from scripts.releases.versioning import derive_next_version, next_attempt, tag_record, version_from_tag
SEED = "0.21.4"
def test_final_tag_is_its_version():
assert version_from_tag("v0.21.5") == "0.21.5"
@pytest.mark.parametrize("ref", [
"v0.21.5-rc",
"v0.21.4+canary.20260922T001400Z",
"v2026.9.21",
"canary-0.21.4+canary.20260922T001400Z",
"v0.0.7+channel.20260922T001400Z.98765",
"v0.0.0+commit.20260922T001400Z.98766",
])
def test_non_final_refs_are_not_versions(ref):
assert version_from_tag(ref) is None
def test_attempt_ref_carries_version_and_attempt():
from scripts.releases.versioning import parse_attempt_ref
assert parse_attempt_ref("rc.1-v0.21.5") == ("0.21.5", 1)
assert parse_attempt_ref("rc.12-v1.0.0") == ("1.0.0", 12)
def test_marker_ref_names_the_attempt_it_clears():
from scripts.releases.versioning import parse_marker_ref
assert parse_marker_ref("abandoned-rc.1-v0.21.5") == ("0.21.5", 1)
assert parse_marker_ref("rc.1-v0.21.5") is None
@pytest.mark.parametrize("ref", [
"v0.21.5-rc", "v0.21.5-rc.1", "rc.01-v0.21.5", "rc.0-v0.21.5",
"rc.1-v2026.9.21", "v0.21.5", "abandoned-rc.1-v0.21.5",
"rc.1-v0.21.5+canary.20260922T001400Z", "rc.1-v0.21", "rc.-v0.21.5",
])
def test_attempt_ref_rejects_other_shapes(ref):
from scripts.releases.versioning import parse_attempt_ref
assert parse_attempt_ref(ref) is None
def test_attempt_and_marker_refs_round_trip():
from scripts.releases.versioning import attempt_ref, marker_ref, parse_attempt_ref, parse_marker_ref
assert parse_attempt_ref(attempt_ref("0.21.5", 3)) == ("0.21.5", 3)
assert parse_marker_ref(marker_ref("0.21.5", 3)) == ("0.21.5", 3)
@pytest.mark.parametrize("version, attempt", [("2026.9.21", 1), ("0.21.5", 0), ("0.21", 1)])
def test_attempt_ref_refuses_what_it_could_not_parse(version, attempt):
from scripts.releases.versioning import attempt_ref
with pytest.raises(ValueError):
attempt_ref(version, attempt)
def test_next_attempt_counts_cleared_attempts_and_skips_other_shapes():
refs = ["rc.1-v0.21.5", "abandoned-rc.1-v0.21.5", "rc.2-v0.21.6",
"v0.21.5-rc", "v2026.9.21", "abandoned-rc.4-v0.21.5"]
assert next_attempt("0.21.5", refs) == 2
assert next_attempt("0.21.6", refs) == 3
assert next_attempt("0.21.7", refs) == 1
def test_canary_compares_equal_to_its_stable():
from scripts.releases.semver import compare, is_canary_version
assert is_canary_version("0.21.4+canary.20260922T001400Z")
assert compare("0.21.4+canary.20260922T001400Z", "0.21.4") == 0
def test_empty_head_seeds_the_line():
assert derive_next_version(published=None, bump="patch") == "0.21.5"
assert derive_next_version(published=None, bump="minor") == "0.22.0"
assert derive_next_version(published=None, bump="major") == "1.0.0"
def test_published_head_spends_its_version():
assert derive_next_version(published="0.21.5", bump="patch") == "0.21.6"
def test_canary_base_comes_from_the_validated_protected_stable_head():
from scripts.releases.versioning import published_stable_version
class Reader:
def __init__(self, base, repository):
assert base == "https://assets.example"
assert repository == "example/hermes-agent"
def resolve(self, name):
assert name == "stable"
return type("Resolution", (), {
"terminal": {"policy": "stable-release"},
"manifest": {"request": {"version": "0.21.7", "commit": "a" * 40}},
})()
assert published_stable_version(
"example/hermes-agent", base_url="https://assets.example", reader_type=Reader,
run=lambda argv: "[[]]",
) == "0.21.7"
def test_a_newer_published_release_outranks_the_protected_head():
"""A release that skipped bundles never moves the R2 head, but it still spends its version."""
from scripts.releases.versioning import published_stable_identity
class Reader:
def __init__(self, base, repository):
pass
def resolve(self, name):
return type("Resolution", (), {
"terminal": {"policy": "stable-release"},
"manifest": {"request": {"version": "0.21.7", "commit": "a" * 40}},
})()
releases = [
{"tag_name": "v0.21.8", "draft": False, "prerelease": False},
# Drafts, prereleases and CalVer labels are not published stable releases.
{"tag_name": "v0.21.9", "draft": True, "prerelease": False},
{"tag_name": "v0.21.8+canary.20260924T000000Z", "draft": False, "prerelease": True},
{"tag_name": "v2026.9.24", "draft": False, "prerelease": False},
]
def run(argv):
if argv[:4] == ["gh", "api", "--paginate", "--slurp"]:
return json.dumps([releases])
assert argv[:2] == ["gh", "api"] and argv[3:] == ["--jq", ".sha"]
return {"repos/example/hermes-agent/commits/v0.21.8": "b" * 40,
"repos/example/hermes-agent/commits/v0.21.6": "c" * 40}[argv[2]]
assert published_stable_identity(
"example/hermes-agent", base_url="https://assets.example", reader_type=Reader, run=run,
) == ("0.21.8", "b" * 40)
releases[0]["tag_name"] = "v0.21.6"
assert published_stable_identity(
"example/hermes-agent", base_url="https://assets.example", reader_type=Reader, run=run,
) == ("0.21.7", "a" * 40)
def test_outstanding_attempts_is_the_one_shared_predicate():
from scripts.releases.versioning import outstanding_attempts
refs = ["rc.1-v0.21.5", "abandoned-rc.1-v0.21.5", "rc.2-v0.21.5",
"rc.1-v0.21.6", "v0.21.5", "rc.1-v0.21.7"]
published = {"0.21.6", "0.21.7"}
def is_published(version):
return version in published
assert outstanding_attempts(refs, is_published) == [("0.21.5", 2, "rc.2-v0.21.5")]
def test_tag_record_reads_the_record_a_signature_is_appended_to():
"""git keeps a tag signature inside the message body, so ``%(contents)`` is
the record followed by the armor. The record is what precedes it."""
record = {"attempt": 19, "autopublish": False, "claimEpoch": 1790701941,
"commit": "3" * 40, "schema": 1, "skipBundles": False, "skipTests": True,
"version": "0.21.5"}
record_text = json.dumps(record, sort_keys=True, separators=(",", ":"))
assert tag_record(record_text) == record
for armor in ("-----BEGIN SSH SIGNATURE-----", "-----BEGIN PGP SIGNATURE-----"):
assert tag_record(f"{record_text}\n{armor}\nAAAA\n") == record
def test_tag_record_refuses_a_message_that_is_not_a_record():
with pytest.raises(json.JSONDecodeError):
tag_record("not a record\n-----BEGIN SSH SIGNATURE-----\nAAAA\n")