"""Exercise publisher/reader through the real signed HTTP transport.""" from __future__ import annotations from contextlib import contextmanager import hashlib from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import json import socket from threading import Lock, Thread from urllib.parse import parse_qs, urlsplit import xml.sax.saxutils import pytest @contextmanager def object_server(): objects, headers, requests = {}, {}, [] lock = Lock() faults = {"lose_put": False, "stale_public": None, "conflict": None, "bad_pagination": False} class Handler(BaseHTTPRequestHandler): def log_message(self, *args): pass def do_GET(self): parsed = urlsplit(self.path) query = parse_qs(parsed.query) with lock: requests.append(("GET", self.path)) if "list-type" in query: prefix = query.get("prefix", [""])[0] keys = sorted(k for k in objects if k.startswith(prefix)) start = int(query.get("continuation-token", ["0"])[0]) page = keys[start:start + 2] more = start + 2 < len(keys) body = ("" + "".join( f"{xml.sax.saxutils.escape(k)}" for k in page) + f"{str(more).lower()}" + (f"{start + 2}" if more else "") + "").encode() if faults["bad_pagination"]: body = b"true" etag = None else: key = parsed.path.removeprefix("/bucket/") body = objects.get(key) if not self.headers.get("Authorization") and faults["stale_public"] is not None: body = faults["stale_public"] etag = '"' + hashlib.sha256(body).hexdigest() + '"' if body is not None else None self.send_response(200 if body is not None else 404) if etag: self.send_header("ETag", etag) self.end_headers() if body is not None: self.wfile.write(body) def do_PUT(self): body = self.rfile.read(int(self.headers["Content-Length"])) key = self.path.removeprefix("/bucket/") with lock: requests.append(("PUT", key)) if faults["conflict"]: conflict = faults["conflict"] faults["conflict"] = None conflict(objects, key) old = objects.get(key) etag = '"' + hashlib.sha256(old).hexdigest() + '"' if old is not None else None conflict = ((self.headers.get("If-None-Match") == "*" and old is not None) or (self.headers.get("If-Match") is not None and self.headers["If-Match"] != etag)) if conflict: self.send_response(412) self.end_headers() return objects[key] = body headers[key] = dict(self.headers) if faults["lose_put"]: faults["lose_put"] = False self.connection.shutdown(socket.SHUT_RDWR) self.connection.close() return self.send_response(200) self.end_headers() server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) thread = Thread(target=server.serve_forever, daemon=True) thread.start() try: yield f"http://127.0.0.1:{server.server_port}", objects, headers, requests, faults finally: server.shutdown() server.server_close() thread.join() def publisher(url, **kwargs): from scripts.releases.channels import ChannelPublisher, R2ChannelStore store = R2ChannelStore({"access_key_id": "fixture", "secret_key": "fixture"}, url, "bucket") return ChannelPublisher(store, "example/hermes-agent", url + "/bucket", authorize=kwargs.pop("authorize", lambda action, record: None), **kwargs) def test_unknown_channel_created_over_http_retains_identity_and_immutable_requests(): from hermes_cli.release_channels import ChannelReader, ChannelNotFound with object_server() as (url, objects, headers, requests, faults): pub = publisher(url) reader = ChannelReader(url + "/bucket", repository="example/hermes-agent") with pytest.raises(ChannelNotFound): reader.resolve("not-registered-in-code") record = pub.create("not-registered-in-code") assert reader.resolve(record["name"]).manifest is None one = pub.allocate(record["name"], "a" * 40, "1.2.3", {"HERMES_GUEST_ONBOARDING": "1"}) two = pub.allocate(record["name"], "a" * 40, "1.2.3", {"HERMES_GUEST_ONBOARDING": "0"}) assert one["identity"] == two["identity"] == record["identity"] assert one["buildId"] != two["buildId"] assert one["sequence"] < two["sequence"] assert pub.request(one["buildId"]) == one assert reader.resolve(record["name"]).terminal["nextSequence"] == two["sequence"] + 1 assert headers[f"releases/channels/{record['name']}.json"]["Cache-Control"] == "no-store" assert "immutable" in headers[f"releases/channel-builds/{one['buildId']}/request.json"]["Cache-Control"] def put_build(objects, request): from hermes_cli.release_channels import build_prefix, canonical_json prefix = build_prefix(request["buildId"]) data = b"fixture native artifact" objects[prefix + "darwin/package.zip"] = data manifest = {"schema": 1, "receiverProtocol": 1, "request": request, "packages": [{"platform": "darwin", "arch": "arm64", "variant": "bundled", "artifact": {"key": prefix + "darwin/package.zip", "sha256": hashlib.sha256(data).hexdigest(), "size": len(data)}, "version": request["version"], "identity": request["identity"]["appId"], "teamId": "ABCDEFGHIJ", "feed": {"key": prefix + "darwin/stable-mac.yml", "channel": "stable"}}]} objects[prefix + "build.json"] = canonical_json(manifest) return manifest def test_concurrent_allocations_reverse_completion_retirement_and_readback(): from concurrent.futures import ThreadPoolExecutor from hermes_cli.release_channels import ChannelError, canonical_json from scripts.releases.channels import PublicVisibilityError with object_server() as (url, objects, headers, requests, faults): with ThreadPoolExecutor(max_workers=2) as pool: pub = publisher(url, verify_build=lambda request, manifest: True) created = list(pool.map(pub.create, ["race-preview"] * 2)) assert created[0]["identity"] == created[1]["identity"] with ThreadPoolExecutor(max_workers=2) as pool: allocated = list(pool.map(lambda _: pub.allocate("race-preview", "a" * 40, "1.0.0"), range(2))) one, two = sorted(allocated, key=lambda r: r["sequence"]) assert one["sequence"] != two["sequence"], "concurrent allocations must be distinct" for request in (one, two): put_build(objects, request) pub.promote(two["buildId"]) with pytest.raises(ChannelError, match="newer|stale"): pub.promote(one["buildId"]) assert pub.reader.resolve("race-preview").manifest["request"] == two # The protected destination is seeded from accepted existing metadata, not a preview masquerade. pub.create("destination") target = pub._read("destination")[0] target["policy"] = "stable-release" stable = dict(two, channel="destination", identity=target["identity"], releaseTag="v2.0.0", version="2.0.0", windowsVersion="2.0.0.0", sourceVersion="2.0.0", buildId="e" * 32) stable_manifest = put_build(objects, stable) raw = canonical_json(stable_manifest) target.update(nextSequence=stable["sequence"] + 1, head={"buildId": stable["buildId"], "sequence": stable["sequence"], "manifestKey": "releases/channel-builds/" + stable["buildId"] + "/build.json", "sha256": hashlib.sha256(raw).hexdigest()}) objects["releases/channels/destination.json"] = canonical_json(target) unsupported = dict(stable_manifest) del unsupported["receiverProtocol"] objects[target["head"]["manifestKey"]] = canonical_json(unsupported) old_target = {**target, "head": {**target["head"], "sha256": hashlib.sha256(canonical_json(unsupported)).hexdigest()}} objects["releases/channels/destination.json"] = canonical_json(old_target) with pytest.raises(ChannelError, match="receiver support"): pub.retire("race-preview", "destination", "2.0.0") assert pub._read("race-preview")[0]["state"] == "active" objects[target["head"]["manifestKey"]] = raw objects["releases/channels/destination.json"] = canonical_json(target) retired = pub.retire("race-preview", "destination", "2.0.0") assert retired["destinationHead"] == target["head"] assert retired["lastHead"]["buildId"] == two["buildId"] with pytest.raises(ChannelError, match="Retired"): pub.promote(two["buildId"]) with pytest.raises(ChannelError, match="Retired"): pub.create("race-preview") pub.create("visibility") faults["lose_put"] = True lost = pub.allocate("visibility", "b" * 40, "1.0.0") assert pub.request(lost["buildId"]) == lost faults["stale_public"] = b"{}" with pytest.raises(PublicVisibilityError, match="Committed"): pub.allocate("visibility", "b" * 40, "1.0.0") assert json.loads(objects["releases/channels/visibility.json"])["nextSequence"] > lost["sequence"] + 1 def test_list_bootstrap_protected_roles_and_qualification_gate(): from hermes_cli.release_channels import ChannelError with object_server() as (url, objects, headers, requests, faults): pub = publisher(url) main = {"schema": 1, "name": "main", "repository": "example/hermes-agent", "policy": "source-branch", "state": "active", "revision": 1, "nextSequence": 1, "identity": None, "head": None, "delivery": {"kind": "source-branch", "branch": "main"}} assert pub.bootstrap(main) == main and not objects pub.bootstrap(main, publish=True) for name in ("first", "second", "third"): pub.create(name) assert {r["name"] for r in pub.list()} == {"main", "first", "second", "third"} assert any("continuation-token" in path for method, path in requests) faults["bad_pagination"] = True with pytest.raises(ChannelError, match="pagination"): pub.list() faults["bad_pagination"] = False assert pub.reader.resolve("main").manifest is None with pytest.raises(ChannelError, match="Protected"): pub.allocate("main", "a" * 40, "1.0.0") request = pub.allocate("first", "a" * 40, "1.0.0") put_build(objects, request) with pytest.raises(ChannelError, match="qualification"): pub.promote(request["buildId"]) assert pub.reader.resolve("first").terminal["head"] is None def test_retirement_race_requires_a_new_explicit_attempt(): from hermes_cli.release_channels import ChannelError, canonical_json from scripts.releases.channels import ChannelConflict with object_server() as (url, objects, headers, requests, faults): pub = publisher(url, verify_build=lambda request, manifest: True) pub.create("preview") first = pub.allocate("preview", "a" * 40, "1.0.0") second = pub.allocate("preview", "b" * 40, "1.0.0") put_build(objects, first) put_build(objects, second) pub.promote(first["buildId"]) pub.create("stable") target = pub._read("stable")[0] target["policy"] = "stable-release" stable = dict(first, channel="stable", identity=target["identity"], releaseTag="v2.0.0", version="2.0.0", windowsVersion="2.0.0.0", sourceVersion="2.0.0", buildId="f" * 32) manifest = put_build(objects, stable) target.update(nextSequence=2, head={"buildId": stable["buildId"], "sequence": 1, "manifestKey": "releases/channel-builds/" + stable["buildId"] + "/build.json", "sha256": hashlib.sha256(canonical_json(manifest)).hexdigest()}) objects["releases/channels/stable.json"] = canonical_json(target) def race(store, object_key): record = json.loads(store[object_key]) record["revision"] += 1 record["head"] = {"buildId": second["buildId"], "sequence": second["sequence"], "manifestKey": "releases/channel-builds/" + second["buildId"] + "/build.json", "sha256": hashlib.sha256(store["releases/channel-builds/" + second["buildId"] + "/build.json"]).hexdigest()} store[object_key] = canonical_json(record) faults["conflict"] = race with pytest.raises(ChannelConflict): pub.retire("preview", "stable", "2.0.0") assert pub.reader.resolve("preview").manifest["request"] == second with pytest.raises(ChannelError, match="cycle"): pub.retire("preview", "preview", "2.0.0") assert pub.retire("preview", "stable", "2.0.0")["lastHead"]["buildId"] == second["buildId"] def test_retire_derives_receiver_kind_from_channel_identity_match(): """The pinned kind is derived from identity comparison, never caller-asserted.""" from hermes_cli.release_channels import canonical_json with object_server() as (url, objects, headers, requests, faults): pub = publisher(url, verify_build=lambda request, manifest: True) for name in ("mainline-preview", "suffixed-preview", "stable"): pub.create(name) # A mainline-like prerelease shares the destination stable identity. preview = pub._read("mainline-preview")[0] first = pub.allocate("mainline-preview", "a" * 40, "1.0.0") put_build(objects, first) pub.promote(first["buildId"]) target = pub._read("stable")[0] target["policy"] = "stable-release" target["identity"] = preview["identity"] stable = dict(first, channel="stable", identity=target["identity"], releaseTag="v2.0.0", version="2.0.0", windowsVersion="2.0.0.0", sourceVersion="2.0.0", buildId="e" * 32) manifest = put_build(objects, stable) target.update(nextSequence=stable["sequence"] + 1, head={"buildId": stable["buildId"], "sequence": stable["sequence"], "manifestKey": "releases/channel-builds/" + stable["buildId"] + "/build.json", "sha256": hashlib.sha256(canonical_json(manifest)).hexdigest()}) objects["releases/channels/stable.json"] = canonical_json(target) in_place = pub.retire("mainline-preview", "stable", "2.0.0") assert in_place["receiver"] == {"kind": "in-place"} assert pub.reader.resolve("mainline-preview").requested["receiver"] == {"kind": "in-place"} # A suffixed channel identity can never match stable's. second = pub.allocate("suffixed-preview", "b" * 40, "1.0.0") put_build(objects, second) pub.promote(second["buildId"]) discontinued = pub.retire("suffixed-preview", "stable", "2.0.0") assert discontinued["receiver"] == {"kind": "discontinued"} assert pub.reader.resolve("suffixed-preview").requested["receiver"] == {"kind": "discontinued"} def test_mutable_read_loss_recovery_never_clones_another_allocation(): from scripts.releases.channels import ChannelConflict from hermes_cli.release_channels import canonical_json with object_server() as (url, objects, headers, requests, faults): pub = publisher(url) pub.create("nonce-check") initial = pub._read("nonce-check")[0] def compete(store, key): winner = dict(initial, revision=2, nextSequence=2, lastAllocation={"buildId": "b" * 32, "sequence": 1}) store[key] = canonical_json(winner) faults["conflict"] = compete request = pub.allocate("nonce-check", "a" * 40, "1.0.0") assert request["sequence"] == 2 key = "releases/channel-builds/" + request["buildId"] + "/request.json" with pytest.raises(ChannelConflict): pub.store.put(key, canonical_json(dict(request, commit="b" * 40))) assert pub.request(request["buildId"]) == request def test_protected_releases_bootstrap_retry_and_refuse_late_or_ungated_promotion(): from hermes_cli.release_channels import ChannelError, canonical_json from scripts.releases.channels import preview_identity with object_server() as (url, objects, headers, requests, faults): pub = publisher(url, verify_build=lambda request, manifest: True) accepted = True gate = lambda request: accepted identity = preview_identity("official", "1" * 16) def allocate(version, commit): return pub.allocate_protected( "official", commit, version, release_tag="v" + version, version=version, windows_version=version + ".0", identity=identity, policy="stable-release", release_gate=gate) # A failed request PUT is not recovered by sequence: retrying the same # release adopts a fresh sequence gap, while the deterministic build ID # keeps the immutable request idempotent across retries. original_write = pub._write def lose_request(key, value, etag=None): if key.endswith("request.json"): raise OSError("request upload interrupted") return original_write(key, value, etag) pub._write = lose_request with pytest.raises(OSError): allocate("1.0.0", "a" * 40) pub._write = original_write allocate("0.5.0", "e" * 40) first = allocate("1.0.0", "a" * 40) assert allocate("1.0.0", "a" * 40) == first assert len(first["buildId"]) == 32 assert pub.reader.resolve("official").manifest is None put_build(objects, first) accepted = False with pytest.raises(ChannelError, match="release gate"): pub.promote_protected(first["buildId"], policy="stable-release", release_gate=gate) assert pub.reader.resolve("official").manifest is None accepted = True pub.promote_protected(first["buildId"], policy="stable-release", release_gate=gate) second = allocate("2.0.0", "b" * 40) late = allocate("1.5.0", "c" * 40) for request in (second, late): put_build(objects, request) pub.promote_protected(second["buildId"], policy="stable-release", release_gate=gate) assert pub.promote_protected(second["buildId"], policy="stable-release", release_gate=gate)["head"]["buildId"] == second["buildId"] with pytest.raises(ChannelError, match="version|newer|stale"): pub.promote_protected(late["buildId"], policy="stable-release", release_gate=gate) with pytest.raises(ChannelError, match="Protected"): pub.promote(second["buildId"]) assert pub.reader.resolve("official").manifest["request"] == second # A competing allocation must not turn a protected promotion into an overwrite. def contend(store, key): record = json.loads(store[key]) record["revision"] += 1 record["nextSequence"] += 1 store[key] = canonical_json(record) third = allocate("3.0.0", "d" * 40) put_build(objects, third) faults["conflict"] = contend pub.promote_protected(third["buildId"], policy="stable-release", release_gate=gate) assert pub.reader.resolve("official").manifest["request"] == third def test_accepted_release_receipts_feed_the_protected_head_without_rebuilding(tmp_path, monkeypatch): from scripts.releases import channel_releases from hermes_cli.release_channels import ChannelError, canonical_json from scripts.releases.channels import preview_identity from scripts.releases.handoff import receipt_name from copy import deepcopy import zipfile identity = preview_identity("released", "2" * 16) tag, commit = "v2.0.0", "d" * 40 with object_server() as (url, objects, headers, requests, faults): pub = publisher(url) base = pub.public_base prefix = f"releases/tag/{tag}/" for platform in ("darwin", "win32"): for arch in ("arm64", "x64"): native = "macos" if platform == "darwin" else "windows" version = "2.0.0" if platform == "darwin" else "2.0.0.0" metadata = {"platform": native, "arch": arch, "tag": tag, "commit": commit, "version": version, "identity": identity["appId" if platform == "darwin" else "msixAppIdWithOrg"]} files = {} if platform == "darwin": metadata["teamId"] = "ABCDEFGHIJ" for suffix in ("zip", "dmg", "zip.blockmap", "dmg.blockmap"): name = f"{identity['artifactNamePascal']}-2.0.0-mac-{arch}.{suffix}" files[name] = (name + " fixture bytes").encode() metadata["filename"] = f"{identity['artifactNamePascal']}-2.0.0-mac-{arch}.zip" else: metadata.update(publisher="CN=Fixture", applicationId=identity["appNamePascal"]) files[f"{identity['artifactNamePascal']}-2.0.0-win-{arch}.msix"] = b"fixture msix" files[f"metadata-{native}-{arch}.json"] = canonical_json(metadata) rows = [] for name, body in files.items(): objects[prefix + name] = body (tmp_path / name).write_bytes(body) rows.append({"path": name, "size": len(body), "sha256": hashlib.sha256(body).hexdigest()}) receipt = {"schema": 1, "tag": tag, "commit": commit, "name": f"{platform}-{arch}", "files": rows} (tmp_path / receipt_name(receipt["name"])).write_bytes(canonical_json(receipt)) objects[prefix + receipt_name(receipt["name"])] = canonical_json(receipt) bundle_name = f"{identity['artifactNamePascal']}-2.0.0.0-win.msixbundle" with zipfile.ZipFile(tmp_path / bundle_name, "w") as archive: archive.writestr("AppxMetadata/AppxBundleManifest.xml", f'') body = (tmp_path / bundle_name).read_bytes() objects[prefix + bundle_name] = body receipt = {"schema": 1, "tag": tag, "commit": commit, "name": "windows-universal", "files": [{"path": bundle_name, "size": len(body), "sha256": hashlib.sha256(body).hexdigest()}]} (tmp_path / receipt_name(receipt["name"])).write_bytes(canonical_json(receipt)) objects[prefix + receipt_name(receipt["name"])] = canonical_json(receipt) # An R2 policy record chooses this name, not the legacy default selector. record = {"schema": 1, "name": "released", "repository": pub.repository, "policy": "stable-release", "state": "active", "revision": 1, "nextSequence": 1, "head": None, "identity": identity} objects["releases/channels/released.json"] = canonical_json(record) assert channel_releases.select_channel(pub, "stable-release") == "released" native = channel_releases.read_native_receipts(tmp_path, tag, commit) request = pub.allocate_protected("released", commit, "2.0.0", release_tag=tag, version="2.0.0", windows_version="2.0.0.0", identity=identity, policy="stable-release", release_gate=lambda request: True) from scripts.bundles.channel_artifacts import assemble manifest, feeds = assemble(request, native, tmp_path, artifact_prefix=prefix) assert {p["arch"] for p in manifest["packages"]} == {"arm64", "x64"} assert all(p["artifact"]["key"].startswith(prefix) for p in manifest["packages"]) assert all(f.is_file() for f in feeds) accepted = {"packages": []} for row in manifest["packages"]: accepted["packages"].append({"platform": "macos" if row["platform"] == "darwin" else "windows", "arch": row["arch"], "identity": row["identity"], "version": row["version"], "artifact": {"url": base + "/" + row["artifact"]["key"], "sha256": row["artifact"]["sha256"]}, **{k: row[k] for k in ("teamId", "publisher") if k in row}}) if row["platform"] == "win32": accepted["packages"][-1]["applicationId"] = identity["appNamePascal"] channel_releases.match_accepted_packages(manifest, accepted) wrong = deepcopy(accepted) wrong["packages"][0]["artifact"]["sha256"] = "0" * 64 with pytest.raises(ChannelError, match="accepted"): channel_releases.match_accepted_packages(manifest, wrong) with pytest.raises(ChannelError, match="every native"): channel_releases.match_accepted_packages(dict(manifest, packages=manifest["packages"][:1]), accepted) # Exercise the real controller, HTTP receipt downloader, immutable feeds, # manifest and final CAS; only GitHub admission and generated product facts # are fixture inputs (no native signature acceptance is claimed here). from scripts.releases import r2 monkeypatch.setattr(channel_releases, "admit_transaction", lambda policy, env, **_kwargs: (tag, commit)) monkeypatch.setattr(channel_releases.stable, "final_context", lambda env: (tag, commit, {"claim_epoch": 1_787_965_323, "skip_bundles": False, "skip_tests": False})) monkeypatch.setattr(channel_releases, "accepted_stable", lambda *args, **kwargs: accepted) promotion_attempts = [0] def promote_stable_feeds(*args): promotion_attempts[0] += 1 if promotion_attempts[0] == 1: raise ChannelError("fixture feed failure") monkeypatch.setattr(channel_releases, "promote_stable_feeds", promote_stable_feeds) monkeypatch.setattr(channel_releases, "product_identity", lambda tag: dict(identity)) monkeypatch.setattr(r2, "credentials", lambda: (pub.store.creds, url, "bucket")) monkeypatch.setattr(r2, "public_base_url", lambda: base) def put(**kwargs): pub.store.put(kwargs["key"], __import__("pathlib").Path(kwargs["file"]).read_bytes()) monkeypatch.setattr(r2, "put", put) with pytest.raises(ChannelError, match="fixture feed failure"): channel_releases.publish_release("stable-release", {"GITHUB_REPOSITORY": pub.repository}, tmp_path / "failed") assert pub._read("released")[0]["head"] is None result = channel_releases.publish_release("stable-release", {"GITHUB_REPOSITORY": pub.repository}, tmp_path / "downloaded") assert result["name"] == "released" assert pub.reader.resolve("released").manifest == manifest assert manifest["request"]["sourceVersion"] == "2.0.0" before = dict(objects) assert channel_releases.publish_release("stable-release", {"GITHUB_REPOSITORY": pub.repository}, tmp_path / "retry") == result assert objects == before (tmp_path / bundle_name).write_bytes(b"corrupt") with pytest.raises(ChannelError, match="receipt"): channel_releases.read_native_receipts(tmp_path, tag, commit) def test_protected_transaction_refuses_custom_workflow_and_unpublished_release(monkeypatch): from scripts.releases import channel_releases from hermes_cli.release_channels import ChannelError attempt, tag, commit = "rc.1-v2.0.0", "v2.0.0", "a" * 40 env = {"GITHUB_ACTIONS": "true", "GITHUB_EVENT_NAME": "workflow_dispatch", "GITHUB_REPOSITORY": "example/hermes-agent", "RELEASE_TAG": attempt, "RELEASE_COMMIT": commit, "RELEASE_CLAIM_TAG": attempt, "RELEASE_CLAIM_OBJECT": "b" * 40, "GITHUB_WORKFLOW_REF": "example/hermes-agent/.github/workflows/stable-release.yml@refs/tags/" + attempt} published = [True] def final_context(_env, run): if not published[0]: raise ValueError("Stable channel requires the published final release") return tag, commit, {} monkeypatch.setattr(channel_releases.stable, "final_context", final_context) def run(command): if command[-1] == ".default_branch": return "main" return "" assert channel_releases.admit_transaction("stable-release", env, run=run) == (attempt, commit) published[0] = False with pytest.raises(ChannelError, match="published"): channel_releases.admit_transaction("stable-release", env, run=run) published[0] = True with pytest.raises(ChannelError, match="controller"): channel_releases.admit_transaction("stable-release", dict(env, GITHUB_WORKFLOW_REF="custom.yml"), run=run) with pytest.raises(ChannelError, match="protected release tag"): channel_releases.admit_transaction( "stable-release", dict(env, RELEASE_TAG=tag + "-rc"), run=run) def test_stable_admission_requires_an_attempt_ref_release_tag(monkeypatch): from scripts.releases import channel_releases from hermes_cli.release_channels import ChannelError attempt, commit = "rc.2-v1.2.3", "c" * 40 env = {"GITHUB_ACTIONS": "true", "GITHUB_EVENT_NAME": "workflow_dispatch", "GITHUB_REPOSITORY": "example/hermes-agent", "RELEASE_TAG": attempt, "RELEASE_COMMIT": commit, "RELEASE_CLAIM_TAG": attempt, "RELEASE_CLAIM_OBJECT": "b" * 40, "GITHUB_WORKFLOW_REF": "example/hermes-agent/.github/workflows/stable-release.yml@refs/tags/" + attempt} monkeypatch.setattr(channel_releases.stable, "final_context", lambda _env, run: ("v1.2.3", commit, {})) def run(command): if command[-1] == ".default_branch": return "main" return "" assert channel_releases.admit_transaction("stable-release", env, run=run) == (attempt, commit) # The v-tag the final receipt binds is derived from the attempt ref, so the # candidate manifest custody is checked against the claim's own version. seen = {} def final_context_checked(patched_env, run): seen["RELEASE_TAG"] = patched_env["RELEASE_TAG"] return "v1.2.3", commit, {} monkeypatch.setattr(channel_releases.stable, "final_context", final_context_checked) channel_releases.admit_transaction("stable-release", env, run=run) assert seen["RELEASE_TAG"] == "v1.2.3" with pytest.raises(ChannelError, match="protected release tag"): channel_releases.admit_transaction("stable-release", dict(env, RELEASE_TAG="v1.2.3"), run=run) def test_accepted_stable_reads_the_release_archive_by_tag(monkeypatch): from scripts.releases import channel_releases from hermes_cli.release_channels import ChannelError, canonical_json tag, commit = "v2.0.0", "c" * 40 attempt = "rc.1-v2.0.0" with object_server() as (url, objects, headers, requests, faults): pub = publisher(url) # Exercise HTTPS authority validation through the loopback transport. pub.public_base = "https://releases.example" release_epoch = 1_787_965_323 candidate = {"schema": 2, "tag": tag, "commit": commit, "releaseEpoch": release_epoch, "archive": attempt, "smoke_results": {job: {"result": "success"} for job in channel_releases.stable.SMOKE_JOBS}, "packages": []} for platform in ("macos", "windows"): for arch in ("arm64", "x64"): candidate["packages"].append({"platform": platform, "arch": arch, "tag": tag, "commit": commit, "version": "2.0.0" if platform == "macos" else "2026.5761.123.0", "identity": "fixture.identity", **({"executableVersion": "2026.5761.123.0"} if platform == "windows" else {}), "teamId": "ABCDEFGHIJ", "publisher": "CN=Fixture", "applicationId": "Fixture", "artifact": {"url": f"{pub.public_base}/releases/tag/{attempt}/fixture-{arch}." + ("zip" if platform == "macos" else "msixbundle"), "sha256": "d" * 64}}) raw = canonical_json(candidate) key = f"releases/tag/{attempt}/release-candidates.json" objects[key] = raw candidate_env = {"CANDIDATE_MANIFEST_SHA256": hashlib.sha256(raw).hexdigest(), "CANDIDATE_MANIFEST_URL": pub.public_base + "/" + key} assert channel_releases.accepted_stable(pub, candidate_env, attempt, commit, release_epoch, skip_tests=False) == candidate # Passed smokes cannot stand behind a claim that skipped tests, or the reverse. with pytest.raises(ValueError, match="test policy"): channel_releases.accepted_stable(pub, candidate_env, attempt, commit, release_epoch, skip_tests=True) faults["stale_public"] = b"{}" with pytest.raises(ChannelError): channel_releases.accepted_stable(pub, candidate_env, attempt, commit, release_epoch, skip_tests=False) def test_request_inputs_are_rejected_before_allocating(): from hermes_cli.release_channels import ChannelError with object_server() as (url, objects, headers, requests, faults): pub = publisher(url) pub.create("validation") before = dict(objects) with pytest.raises(ValueError): pub.allocate("validation", "a" * 40, "1.0.0", []) assert objects == before with pytest.raises(ChannelError): pub.allocate("validation", "not-a-sha", "1.0.0") assert objects == before def test_canary_native_version_is_derived_from_the_current_tag(): from scripts.releases.channel_releases import canary_windows_version assert canary_windows_version("v0.27.1+canary.20260829T010203Z") == "26.829.1.203" def test_stable_requests_name_the_attempt_archive_only_when_given(): from hermes_cli.release_channels import ChannelError from scripts.releases.channels import preview_identity with object_server() as (url, objects, headers, requests, faults): pub = publisher(url) identity = preview_identity("archived", "3" * 16) gate = lambda request: True def allocate(commit, version, archive_ref): return pub.allocate_protected( "archived", commit, version, release_tag="v" + version, version=version, windows_version=version + ".0", identity=identity, policy="stable-release", release_gate=gate, archive_ref=archive_ref) request = allocate("a" * 40, "2.0.0", "rc.2-v2.0.0") assert request["archiveRef"] == "rc.2-v2.0.0" assert pub.request(request["buildId"]) == request bare = allocate("b" * 40, "2.1.0", None) assert "archiveRef" not in bare assert pub.request(bare["buildId"]) == bare with pytest.raises(ChannelError, match="(?i)archive ref"): allocate("c" * 40, "2.2.0", "rc.2-v2.9.9")