Files
hermes-agent/tests/scripts/test_channel_build_versions.py
ethernet b37acb8389 feat(release): dynamic R2-owned channels and preview retirement (rounds 1-2)
Checkpoint before round-3 reduction (two-tier retirement derived from
product identity). Includes:

- R2 channel protocol (release_channels.py, channel-protocol.ts): records,
  builds, manifests, retired channels with pinned destinationHead and
  receiverProtocol; fail-closed readers in both languages
- One shared native manifest/feed writer (scripts/bundles/channel_artifacts.py)
- Scoped/disposable R2 publication, fork isolation before credential
  access, canary bootstrap verification of its own promoted outputs
- Channel source CLI: typed SourceTarget, source-channel resolution,
  retirement downgrade refusal
- Desktop channel resolver/strategy, install-stamp/build-stamp receiver
  ownership (stable-owned S/T candidates), single-flight updater operation
- Cross-package retirement machinery (receiver/host/preservation/
  compatibility/connections/dialog/discovery, backup_migration strict
  snapshots with retained-link inventory, empty-dir preservation,
  connection-collision resolution, URL-credential rejection)
- Native install harness (tests/install/channel-retirement-*) and
  install-e2e retirement jobs
- checkout-source.test.ts transport shim now covers build_opener().open
  (was silently hitting the real network in CI)

Removed secondary certification protocol (channel_qualification.py) per
approved round-2 plan. All focused suites green at checkpoint; native
cross-package journeys unverified (to be deleted in round 3).
2026-09-14 10:26:36 -04:00

124 lines
6.6 KiB
Python

"""A channel request is a complete, immutable input, not a commit flavor."""
from __future__ import annotations
import json
from pathlib import Path
import subprocess
import sys
import pytest
from scripts.bundles.desktop_prepare import BuildRequest
from tests.scripts.test_desktop_preparation import _project
def channel_request(commit: str, sequence: int = 65536) -> dict:
token = "ab12cd34ef56ab78"
return {
"schema": 1, "buildId": "a" * 32, "channel": "unregistered-preview",
"sequence": sequence, "repository": "fixture/project", "commit": commit,
"sourceVersion": "1.2.3", "version": f"0.0.{sequence}",
"windowsVersion": f"0.{sequence // 65536}.{sequence % 65536}.0",
"identity": {
"token": token, "displayName": "Hermes unregistered-preview",
"appId": f"com.nousresearch.hermes-channel-{token}",
"appNamePascal": f"HermesChannel{token}", "artifactNamePascal": "HermesBundled",
"cliName": "hermes-unregistered-preview", "windowsExecutableName": "hermes-unregistered-preview",
"msixAppIdWithOrg": f"NousResearch.HermesChannel{token}",
},
"bundleEnv": {"HERMES_GUEST_ONBOARDING": "1", "HERMES_HOME": None},
"publicBase": "https://builds.example.test",
}
def test_preparation_preserves_exact_channel_inputs_without_source_version_ordering(tmp_path):
source, commit = _project(tmp_path)
admitted = channel_request(commit)
request = BuildRequest.create(source, tag=None, commit=commit, variant="bundled",
work=tmp_path / "work", cache=tmp_path / "cache",
bundle_env={}, channel_request=admitted)
assert request.version == admitted["version"] != admitted["sourceVersion"]
assert request.tag is None
assert request.bundle_env == admitted["bundleEnv"]
restored = BuildRequest.from_data(json.loads(json.dumps(request.data())))
assert restored.channel_request == admitted
admitted["identity"]["cliName"] = "changed"
assert restored.channel_request == request.channel_request
assert request.channel_request["identity"]["cliName"] != "changed"
for changes in ({"sourceVersion": "0.9.0"}, {"commit": "b" * 40}, {"version": "1.2.3"},
{"sequence": 2**32}, {"identity": {**channel_request(commit)["identity"], "cliName": "../bad"}}):
invalid = {**channel_request(commit), **changes}
with pytest.raises(ValueError):
BuildRequest.create(source, tag=None, commit=commit, variant="bundled",
work=tmp_path / "work", cache=tmp_path / "cache",
bundle_env={}, channel_request=invalid)
for variant in ("light", "store"):
with pytest.raises(ValueError, match="bundled"):
BuildRequest.create(source, tag=None, commit=commit, variant=variant,
work=tmp_path / "work", cache=tmp_path / "cache",
bundle_env={}, channel_request=channel_request(commit))
for args in (["-c.extraMetadata.version=9.9.9"], ["--config.extraMetadata.shortVersionWindows=1.0.0.0"]):
from scripts.bundles.desktop_inputs import validate_builder_identity
with pytest.raises(ValueError, match="channel"):
validate_builder_identity(request, args)
assert not (tmp_path / "work").exists()
def test_prepare_cli_accepts_request_path_but_refuses_mismatched_checkout_before_provisioning(tmp_path):
source, commit = _project(tmp_path)
request = channel_request(commit)
request["commit"] = "b" * 40
path = tmp_path / "channel.json"
path.write_text(json.dumps(request), encoding="utf-8")
repo = Path(__file__).resolve().parents[2]
result = subprocess.run([sys.executable, str(repo / "scripts/bundles/desktop.py"),
"--prepare-only", "--repo", str(source), "--channel-request", str(path)],
capture_output=True, text=True, timeout=30)
assert result.returncode != 0
assert "unrecognized arguments" not in result.stderr
assert "checkout" in result.stderr
assert not (source / ".build").exists()
def test_channel_environment_and_stamp_bind_the_request_not_ambient_oneoff_identity(tmp_path, monkeypatch):
from scripts.bundles.desktop_inputs import identity_environment
from scripts import write_install_stamp
source, commit = _project(tmp_path)
admitted = channel_request(commit)
request = BuildRequest.create(source, tag=None, commit=None, variant="bundled",
work=tmp_path / "work", cache=tmp_path / "cache",
bundle_env={}, channel_request=admitted)
inherited = {"HERMES_BUILD_COMMIT": "b" * 40, "HERMES_PAYLOAD_TAG": "v9.9.9",
"GITHUB_SHA": "b" * 40, "GITHUB_REF_NAME": "workflow-branch", "BUILD_NUMBER": "999"}
env = identity_environment(request, "bundled", inherited)
assert json.loads(env["_HERMES_CHANNEL_REQUEST_JSON"]) == admitted
assert env["HERMES_PAYLOAD_VERSION"] == admitted["version"]
assert env["GITHUB_SHA"] == commit
assert not {"HERMES_BUILD_COMMIT", "HERMES_PAYLOAD_TAG", "GITHUB_REF_NAME", "BUILD_NUMBER"} & env.keys()
assert inherited["HERMES_BUILD_COMMIT"] == "b" * 40
changed = request.data()
changed["channel_request"]["bundleEnv"] = changed["bundle_env"] = {}
assert BuildRequest.from_data(changed).identity_digest() != request.identity_digest()
from scripts.bundles.desktop_prepare import PreparedDesktop
prepared = PreparedDesktop(request, Path(sys.executable), Path(sys.executable), Path(sys.executable),
tmp_path / "native", tmp_path / "packager", None, {}, "fixture",
request.identity_digest())
assert request.channel_request is not None
request.channel_request["buildId"] = "c" * 32
with pytest.raises(ValueError, match="identity changed"):
prepared.validate()
monkeypatch.setattr(write_install_stamp, "_REPO_ROOT", source)
monkeypatch.setenv("HERMES_DESKTOP_VARIANT", "bundled")
# Python receives the request explicitly, not a new runtime setting.
stamp = write_install_stamp.build_stamp(update_mechanism="electron-updater", channel_request=admitted)
assert stamp["source"] == "channel-build"
assert stamp["dirty"] is False
assert stamp["channelBuild"] == admitted
assert stamp["tag"] is None and stamp["branch"] is None
assert stamp["baseVersion"] == admitted["sourceVersion"]
assert stamp["updateMechanism"] == "electron-updater"
admitted["bundleEnv"].clear()
assert stamp["channelBuild"]["bundleEnv"]