mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
fix(update): stable is the latest published GitHub release, never an R2 record
Publishing a vX.Y.Z GitHub release is the whole promotion: the source updater's stable channel now reads only GitHub's latest published release (non-draft, non-prerelease, strict vX.Y.Z) and verifies the tag on origin. It no longer asks R2 for releases/channels/stable.json first, and it ignores the R2 release-candidates pointer, which the stable pipeline writes before the GitHub draft is published.
This commit is contained in:
1 parent
6a92a2aa49
commit
158cd52a6b
7 files changed
+102
-121
No files matched your search
@@ -119,17 +119,11 @@ def _resolve_source_target(channel: str, git_cmd=None, cwd=None, *, repository=N
|
||||
|
||||
validate_name(channel)
|
||||
repository = repository or source_repository(git_cmd, cwd)
|
||||
if channel == "stable":
|
||||
return _resolve_stable(repository, git_cmd, cwd)
|
||||
try:
|
||||
resolved = _resolve_channel(channel, repository)
|
||||
except ChannelNotFound:
|
||||
if channel == "stable":
|
||||
# Until R2 publishes a stable record, stable is the latest published
|
||||
# GitHub release (non-draft, non-prerelease, strict vX.Y.Z, origin
|
||||
# tag verified). Never a branch tip or an arbitrary tag.
|
||||
tag, commit = resolve_source_release("stable", git_cmd, cwd, repository=repository)
|
||||
if commit is None:
|
||||
raise ValueError("No published stable release could be verified") from None
|
||||
return SourceTarget(channel, channel, repository, commit=commit, version=str(tag).removeprefix("v"))
|
||||
if channel != "main":
|
||||
raise
|
||||
# main IS the source branch; its record can only add a retirement.
|
||||
@@ -159,6 +153,17 @@ def _resolve_source_target(channel: str, git_cmd=None, cwd=None, *, repository=N
|
||||
version=request["sourceVersion"], build_id=request["buildId"])
|
||||
|
||||
|
||||
def _resolve_stable(repository: str, git_cmd=None, cwd=None) -> SourceTarget:
|
||||
"""Stable IS the repository's latest published GitHub release: the tag a release
|
||||
publishes (non-draft, non-prerelease, strict vX.Y.Z), verified against origin.
|
||||
No R2 record or pointer is consulted; publishing the release is the only promotion.
|
||||
"""
|
||||
tag, commit = resolve_source_release("stable", git_cmd, cwd, repository=repository, pointer=False)
|
||||
if commit is None:
|
||||
raise ValueError("No published stable release could be verified")
|
||||
return SourceTarget("stable", "stable", repository, commit=commit, version=str(tag).removeprefix("v"))
|
||||
|
||||
|
||||
def _refuse_retirement_downgrade(request: dict, terminal: dict, git_cmd, cwd) -> None:
|
||||
"""Qualification of preview data is not permission to roll back newer source."""
|
||||
from pathlib import Path
|
||||
@@ -289,12 +294,13 @@ def _release_pointer(channel: str) -> tuple[str | None, str | None]:
|
||||
return page.tags[0], None
|
||||
|
||||
|
||||
def resolve_source_release(channel: str, git_cmd=None, cwd=None, *, repository=None) -> tuple[str | None, str | None]:
|
||||
def resolve_source_release(channel: str, git_cmd=None, cwd=None, *, repository=None,
|
||||
pointer: bool = True) -> tuple[str | None, str | None]:
|
||||
"""Read published stable/canary release metadata (not channel discovery).
|
||||
|
||||
Runtime check/apply go through ``resolve_source_target``; only an unpublished
|
||||
R2 ``stable`` record falls back here.
|
||||
Channel pointers outrank GitHub's release listing. A malformed pointer,
|
||||
The source updater's ``stable`` reads it with ``pointer=False``: GitHub's latest
|
||||
published release only. With ``pointer`` the R2 channel pointers outrank GitHub's
|
||||
release listing. A malformed pointer,
|
||||
draft, or tag/commit mismatch is not permission to select a different build.
|
||||
``git_cmd`` resolves the selected tag on origin; ZIP callers omit it and
|
||||
resolve the same tag through GitHub's commit endpoint.
|
||||
@@ -305,7 +311,7 @@ def resolve_source_release(channel: str, git_cmd=None, cwd=None, *, repository=N
|
||||
repository = repository or source_repository(git_cmd, cwd)
|
||||
base = f"https://api.github.com/repos/{repository}"
|
||||
tag, pinned_sha = (_release_pointer(channel)
|
||||
if repository.lower() == OFFICIAL_REPOSITORY.lower() else (None, None))
|
||||
if pointer and repository.lower() == OFFICIAL_REPOSITORY.lower() else (None, None))
|
||||
if tag is None:
|
||||
release = _published_fallback(channel, base)
|
||||
tag = release["tag_name"]
|
||||
|
||||
@@ -246,26 +246,20 @@ def _identity(name: str, token: str) -> dict:
|
||||
"windowsExecutableName": pascal, "msixAppIdWithOrg": f"NousResearch.{pascal}"}
|
||||
|
||||
|
||||
def stable_objects(commit: str, *, version: str = "2099.1.1", build_id: str = "c" * 32,
|
||||
repository: str = REPOSITORY) -> dict[str, bytes]:
|
||||
"""A published stable release pinned at ``commit``: the channel record and its build manifest."""
|
||||
identity = _identity("stable", "5" * 16)
|
||||
prefix = f"releases/channel-builds/{build_id}/"
|
||||
request = {"schema": 1, "buildId": build_id, "channel": "stable", "sequence": 1,
|
||||
"repository": repository, "commit": commit, "sourceVersion": version, "version": version,
|
||||
"windowsVersion": version + ".0", "releaseTag": "v" + version, "identity": identity,
|
||||
"bundleEnv": {}, "publicBase": f"https://{ASSETS}"}
|
||||
manifest = {"schema": 1, "receiverProtocol": 1, "request": request, "packages": [
|
||||
{"platform": "darwin", "arch": "arm64", "variant": "bundled", "identity": identity["appId"],
|
||||
"version": version, "teamId": "ABCDEFGHIJ",
|
||||
"artifact": {"key": prefix + "Hermes.dmg", "sha256": "d" * 64, "size": 100},
|
||||
"feed": {"key": prefix + "stable-mac.yml", "channel": "stable"}}]}
|
||||
body = canonical(manifest)
|
||||
record = {"schema": 1, "name": "stable", "repository": repository, "policy": "stable-release",
|
||||
"state": "active", "revision": 1, "nextSequence": 2, "identity": identity,
|
||||
"head": {"buildId": build_id, "sequence": 1, "manifestKey": prefix + "build.json",
|
||||
"sha256": hashlib.sha256(body).hexdigest()}}
|
||||
return {"/releases/channels/stable.json": canonical(record), f"/{prefix}build.json": body}
|
||||
GITHUB_API = "api.github.com"
|
||||
|
||||
|
||||
def stable_release(inst: Installed, commit: str, *, tag: str = "v99.1.1", draft: bool = False,
|
||||
prerelease: bool = False, api_commit: str | None = None) -> dict[str, bytes]:
|
||||
"""A published GitHub release ``tag`` at ``commit``: the tag on origin plus the API objects.
|
||||
|
||||
``api_commit`` makes GitHub report a different commit than the tag on origin points at.
|
||||
"""
|
||||
I.git("tag", "-f", tag, commit, cwd=inst.origin)
|
||||
release = {"tag_name": tag, "draft": draft, "prerelease": prerelease}
|
||||
base = f"/repos/{REPOSITORY}"
|
||||
return {f"{base}/releases/latest": canonical(release), f"{base}/releases/tags/{tag}": canonical(release),
|
||||
f"{base}/commits/{tag}": canonical({"sha": api_commit or commit})}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
"""Release-channel records: the updater follows the stable record exactly, or refuses.
|
||||
"""Stable releases: the updater follows the latest published GitHub release exactly, or refuses.
|
||||
|
||||
A source install subscribed to ``stable`` resolves its target from the channel record at
|
||||
``https://hermes-assets.nousresearch.com/releases/channels/stable.json`` and the build manifest
|
||||
it names (digest-pinned). The edge serves those objects (a fake of the R2 bucket, behind the
|
||||
TLS-inspecting proxy that is the namespace's only egress) and the git server behind the same
|
||||
proxy serves the source. In every cell origin/main has moved past what stable pins, so "the
|
||||
updater silently followed main" is visible as a HEAD that landed on the main tip.
|
||||
A source install subscribed to ``stable`` resolves its target from GitHub's latest published
|
||||
release (``api.github.com/repos/<repo>/releases/latest``: non-draft, non-prerelease, strict
|
||||
``vX.Y.Z``) and verifies the tag on origin points at the commit GitHub reports. No R2 channel
|
||||
record is read. The edge serves the API (behind the TLS-inspecting proxy that is the
|
||||
namespace's only egress) and the git server behind the same proxy serves the source. In every
|
||||
cell origin/main has moved past the release, so "the updater silently followed main" is
|
||||
visible as a HEAD that landed on the main tip.
|
||||
|
||||
Classes: the record is valid (land exactly on its commit), unavailable (404 / 503 / tunnel cut),
|
||||
malformed (not JSON, manifest digest mismatch, another repository's record), or names a commit
|
||||
the forge does not have. #124309 is the missing live record; this suite pins that the updater
|
||||
then refuses stable with a clear message instead of moving to main.
|
||||
Classes: the release is valid (land exactly on its commit), unavailable (404 / 503 / tunnel cut),
|
||||
unacceptable (draft, prerelease, not JSON), or its commit disagrees with the tag on origin.
|
||||
In each failure the updater refuses stable with a clear message instead of moving to main.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
@@ -35,7 +34,7 @@ pytestmark = [
|
||||
pytest.mark.skipif(I.real_uv() is None, reason="uv required"),
|
||||
]
|
||||
|
||||
STABLE = "/releases/channels/stable.json"
|
||||
LATEST = f"/repos/{S.REPOSITORY}/releases/latest"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@@ -47,30 +46,32 @@ def inst(tmp_path_factory):
|
||||
|
||||
|
||||
def _release_and_main_tip(inst: S.Installed, tag: str) -> tuple[str, str]:
|
||||
"""A stable candidate commit on top of the install, then a newer main tip on top of it."""
|
||||
"""A release commit on top of the install, then a newer main tip on top of it."""
|
||||
release = inst.publish(f"{tag}-release")
|
||||
tip = I.publish_commit(inst.origin, inst.root, f"e2e network: {tag} main tip",
|
||||
{f"E2E_NETWORK_{tag}_TIP.txt": "unreleased\n"})
|
||||
return release, tip
|
||||
|
||||
|
||||
def _update(inst: S.Installed, assets: N.App | None = None, **edge_kw) -> S.Result:
|
||||
edge = inst.edge(assets=assets, **edge_kw)
|
||||
def _update(inst: S.Installed, api: N.App | None = None, **edge_kw) -> S.Result:
|
||||
edge = inst.edge(extra={S.GITHUB_API: api or N.static_app({})}, **edge_kw)
|
||||
try:
|
||||
return inst.hermes("update", "--yes", edge=edge, timeout=600)
|
||||
finally:
|
||||
edge.close()
|
||||
|
||||
|
||||
def test_valid_stable_record_lands_exactly_on_its_commit(inst):
|
||||
def test_published_release_lands_exactly_on_its_commit_without_reading_r2(inst):
|
||||
release, tip = _release_and_main_tip(inst, "valid")
|
||||
r = _update(inst, N.static_app(S.stable_objects(release)))
|
||||
assert r.rc == 0, "update to a valid stable record failed\n" + r.report(inst)
|
||||
r = _update(inst, N.static_app(S.stable_release(inst, release)))
|
||||
assert r.rc == 0, "update to the published stable release failed\n" + r.report(inst)
|
||||
head = inst.head()
|
||||
assert head != tip, "stable subscriber was moved to the unreleased main tip\n" + r.report(inst)
|
||||
assert head == release, f"stable update landed on {head}, not the released {release}\n" + r.report(inst)
|
||||
reads = [h.path for h in r.edge.proxy.requests(S.ASSETS)] if r.edge else []
|
||||
assert STABLE in reads, f"the stable record was not read through the proxy: {reads}\n" + r.report(inst)
|
||||
reads = [h.path for h in r.edge.proxy.requests(S.GITHUB_API)] if r.edge else []
|
||||
assert LATEST in reads, f"the latest release was not read through the proxy: {reads}\n" + r.report(inst)
|
||||
r2 = [h.path for h in r.edge.proxy.requests(S.ASSETS)] if r.edge else []
|
||||
assert not any("/releases/" in path for path in r2), f"stable read R2 release objects: {r2}\n" + r.report(inst)
|
||||
|
||||
|
||||
def _refused(inst: S.Installed, r: S.Result, before: dict, what: str) -> None:
|
||||
@@ -80,46 +81,31 @@ def _refused(inst: S.Installed, r: S.Result, before: dict, what: str) -> None:
|
||||
|
||||
|
||||
UNAVAILABLE = {
|
||||
"not-published-404": dict(assets=N.static_app({})),
|
||||
"outage-503": dict(assets=N.static_app({}, always={STABLE: N.Response(503, b"down\n")})),
|
||||
"tunnel-cut": dict(eof_hosts=[S.ASSETS]),
|
||||
"no-release-404": dict(api=N.static_app({})),
|
||||
"outage-503": dict(api=N.static_app({}, always={LATEST: N.Response(503, b"down\n")})),
|
||||
"tunnel-cut": dict(eof_hosts=[S.GITHUB_API]),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fault", sorted(UNAVAILABLE))
|
||||
def test_unavailable_stable_record_refuses_and_never_moves_to_main(inst, fault):
|
||||
def test_unavailable_release_refuses_and_never_moves_to_main(inst, fault):
|
||||
_release_and_main_tip(inst, f"unavailable-{fault}")
|
||||
before = inst.state()
|
||||
r = _update(inst, **UNAVAILABLE[fault])
|
||||
_refused(inst, r, before, f"stable record {fault}")
|
||||
_refused(inst, r, before, f"stable release {fault}")
|
||||
|
||||
|
||||
def _malformed(release: str) -> dict[str, dict[str, bytes]]:
|
||||
good = S.stable_objects(release)
|
||||
manifest_key = next(k for k in good if k != STABLE)
|
||||
tampered = dict(good)
|
||||
manifest = json.loads(good[manifest_key])
|
||||
manifest["request"]["commit"] = "f" * 40 # a different build than the record's digest pins
|
||||
tampered[manifest_key] = S.canonical(manifest)
|
||||
return {
|
||||
"not-json": {STABLE: b"<html>502 Bad Gateway</html>\n"},
|
||||
"manifest-digest-mismatch": tampered,
|
||||
"other-repository": S.stable_objects(release, repository="someone-else/hermes-agent"),
|
||||
}
|
||||
def _unacceptable(inst: S.Installed, release: str, fault: str) -> dict[str, bytes]:
|
||||
if fault == "not-json":
|
||||
return {LATEST: b"<html>502 Bad Gateway</html>\n"}
|
||||
if fault == "origin-tag-mismatch":
|
||||
return S.stable_release(inst, release, api_commit="f" * 40)
|
||||
return S.stable_release(inst, release, **{fault: True})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fault", ["manifest-digest-mismatch", "not-json", "other-repository"])
|
||||
def test_malformed_stable_record_refuses(inst, fault):
|
||||
release, _ = _release_and_main_tip(inst, f"malformed-{fault}")
|
||||
@pytest.mark.parametrize("fault", ["draft", "not-json", "origin-tag-mismatch", "prerelease"])
|
||||
def test_unacceptable_release_refuses(inst, fault):
|
||||
release, _ = _release_and_main_tip(inst, f"unacceptable-{fault}")
|
||||
before = inst.state()
|
||||
r = _update(inst, N.static_app(_malformed(release)[fault]))
|
||||
_refused(inst, r, before, f"stable record {fault}")
|
||||
|
||||
|
||||
def test_stable_record_naming_a_commit_the_forge_does_not_have(inst):
|
||||
"""The record was published for a build whose commit never reached the forge (or was
|
||||
force-pushed away): the fetch of that exact commit fails and nothing may change."""
|
||||
_release_and_main_tip(inst, "unpublished-commit")
|
||||
before = inst.state()
|
||||
r = _update(inst, N.static_app(S.stable_objects("0123456789abcdef0123456789abcdef01234567")))
|
||||
S.assert_nothing_changed(inst, before, r, "stable record naming an unpublished commit")
|
||||
r = _update(inst, N.static_app(_unacceptable(inst, release, fault)))
|
||||
_refused(inst, r, before, f"stable release {fault}")
|
||||
@@ -97,6 +97,9 @@ def _source_channels_resolve_locally(request, monkeypatch):
|
||||
return ChannelResolution(record, record, None)
|
||||
|
||||
monkeypatch.setattr(source_releases, "_resolve_channel", resolve)
|
||||
# stable reads GitHub releases directly; keep it off the network the same way.
|
||||
monkeypatch.setattr(source_releases, "_resolve_stable", lambda repository, *_: source_releases.SourceTarget(
|
||||
"stable", "stable", repository, branch="stable"))
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
@@ -332,6 +332,9 @@ def test_offline_retirement_uses_qualified_build_before_current_stable(
|
||||
update_cmd._cmd_update_impl(args, False)
|
||||
assert git(source.root, "rev-parse", "HEAD") == source.commits[1]
|
||||
assert saved(source)["channel"] == "stable"
|
||||
# The adopted stable subscription follows the published GitHub release, not R2.
|
||||
monkeypatch.setattr(source_releases, "_resolve_stable", lambda repository, *_: source_releases.SourceTarget(
|
||||
"stable", "stable", repository, commit=source.commits[2], version="1.2.4"))
|
||||
status = source_check.check_for_updates(install_root=source.root, home=source.home, force=True)
|
||||
assert status["targetSha"] == source.commits[2], status
|
||||
update_cmd._cmd_update_impl(args, False)
|
||||
|
||||
@@ -13,6 +13,9 @@ from hermes_cli import main, update_cmd
|
||||
from hermes_cli.source_releases import resolve_source_release
|
||||
from hermes_cli.update_channel import set_install_channel
|
||||
|
||||
# The fixture serves release metadata over loopback; stable's real GitHub path is the subject.
|
||||
pytestmark = pytest.mark.real_release_channels
|
||||
|
||||
|
||||
def git(root, *args):
|
||||
return subprocess.run(
|
||||
@@ -62,6 +65,9 @@ def releases(tmp_path, monkeypatch, request):
|
||||
responses[f"/repos/NousResearch/hermes-agent/commits/{tag}"] = {
|
||||
"sha": commits[1 if channel == "stable" else 2],
|
||||
}
|
||||
responses["/repos/NousResearch/hermes-agent/releases/latest"] = {
|
||||
"tag_name": tags["stable"], "draft": False, "prerelease": False,
|
||||
}
|
||||
responses["/releases/stable/release-candidates.json"] = {
|
||||
"tag": tags["stable"], "commit": commits[1],
|
||||
}
|
||||
@@ -253,23 +259,20 @@ def _track_official_origin(releases):
|
||||
git(releases.root, "config", f"url.{releases.origin.as_uri()}.insteadOf", official)
|
||||
|
||||
|
||||
def test_unpublished_stable_record_resolves_the_published_github_release(releases):
|
||||
"""Until R2 carries a stable record, stable is the latest published GitHub release."""
|
||||
def test_stable_is_the_latest_published_github_release_without_r2(releases):
|
||||
"""Publishing a GitHub release is the only promotion stable needs: no R2 record or pointer."""
|
||||
from hermes_cli import source_releases
|
||||
from hermes_cli.release_channels import ChannelNotFound
|
||||
|
||||
_track_official_origin(releases)
|
||||
releases.responses["/repos/NousResearch/hermes-agent/releases/latest"] = {
|
||||
"tag_name": releases.tags["stable"], "draft": False, "prerelease": False}
|
||||
def no_r2(name, repository):
|
||||
raise AssertionError("stable must not read an R2 channel record")
|
||||
|
||||
def unpublished(name, repository):
|
||||
raise ChannelNotFound(f"Channel object not found: releases/channels/{name}.json")
|
||||
|
||||
source_releases._resolve_channel = unpublished
|
||||
source_releases._resolve_channel = no_r2
|
||||
# A pointer naming a different build must not outrank the published release.
|
||||
releases.responses["/releases/stable/release-candidates.json"] = {
|
||||
"tag": "v99.0.0", "commit": releases.commits[3]}
|
||||
target = source_releases.resolve_source_target("stable", ["git"], releases.root)
|
||||
assert (target.commit, target.version) == (releases.commits[1], "1.2.3")
|
||||
with pytest.raises(ChannelNotFound):
|
||||
source_releases.resolve_source_target("canary", ["git"], releases.root)
|
||||
assert not any(path.startswith("/releases/") for path in releases.requests)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("start,expected", [(0, 1), (3, 3)], ids=["behind-release", "newer-than-release"])
|
||||
|
||||
@@ -88,6 +88,14 @@ def update_tree(tmp_path, monkeypatch):
|
||||
args=args, resumed=resumed, requests=requests, plans=plans)
|
||||
|
||||
|
||||
def _publish_stable(monkeypatch, t):
|
||||
"""stable = the latest published GitHub release; its resolver pins t.wanted (v1.1.0)."""
|
||||
from hermes_cli import source_releases
|
||||
|
||||
monkeypatch.setattr(source_releases, '_resolve_stable', lambda repository, *_: source_releases.SourceTarget(
|
||||
'stable', 'stable', repository, commit=t.wanted, version='1.1.0'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('case', ['main', 'explicit', 'missing', 'no-move', 'wrong-branch',
|
||||
'fork-no-upstream', 'fork-upstream', 'fork-upstream-push-ok',
|
||||
'fork-upstream-wrong-branch', 'fork-upstream-reverted',
|
||||
@@ -205,20 +213,8 @@ def test_stable_git_uses_remote_identity_without_moving_local_tags(update_tree,
|
||||
"""A stable update is pinned to the channel's exact commit: no tag lookup on
|
||||
origin, the stale local ``v1.1.0`` never moves, and an explicit --branch
|
||||
bypasses the channel."""
|
||||
from hermes_cli import source_releases
|
||||
from hermes_cli.release_channels import ChannelResolution
|
||||
|
||||
t = update_tree
|
||||
# The stable channel is an R2 record whose published build pins t.wanted
|
||||
# (the documented reader seam; see test_source_channel_integration).
|
||||
record = {"schema": 1, "name": "stable", "repository": "NousResearch/hermes-agent",
|
||||
"policy": "stable-release", "state": "active", "identity": {}, "nextSequence": 2,
|
||||
"head": {"buildId": "build-fixture", "sequence": 1}}
|
||||
manifest = {"schema": 1, "request": {"buildId": "build-fixture", "channel": "stable", "sequence": 1,
|
||||
"repository": "NousResearch/hermes-agent", "commit": t.wanted, "sourceVersion": "1.1.0",
|
||||
"version": "0.0.1", "identity": {}, "bundleEnv": {}}, "packages": []}
|
||||
monkeypatch.setattr(source_releases, '_resolve_channel',
|
||||
lambda name, repository: ChannelResolution(record, record, manifest))
|
||||
_publish_stable(monkeypatch, t)
|
||||
expected = t.wanted
|
||||
if server in {'at-release', 'ahead-release'}:
|
||||
git(t.clone, 'fetch', '--no-tags', 'origin', t.wanted)
|
||||
@@ -279,23 +275,13 @@ def test_stable_git_uses_remote_identity_without_moving_local_tags(update_tree,
|
||||
@pytest.mark.platforms('windows')
|
||||
@pytest.mark.parametrize('transport', ['gitless', 'no-git', 'git-error', 'dirty'])
|
||||
def test_stable_zip_consumes_the_same_commit_through_the_real_swap(update_tree, monkeypatch, tmp_path, transport):
|
||||
from hermes_cli import source_releases
|
||||
from hermes_cli.release_channels import ChannelResolution
|
||||
|
||||
t = update_tree
|
||||
monkeypatch.setattr(cli_main, '_pause_windows_gateways_for_update',
|
||||
lambda: {"resume_needed": True})
|
||||
archive = tmp_path / 'source.zip'
|
||||
git(t.origin, 'archive', '--format=zip', '--prefix=hermes-agent-source/', f'--output={archive}', t.wanted)
|
||||
archive_bytes = archive.read_bytes()
|
||||
record = {"schema": 1, "name": "stable", "repository": "NousResearch/hermes-agent",
|
||||
"policy": "stable-release", "state": "active", "identity": {}, "nextSequence": 2,
|
||||
"head": {"buildId": "build-fixture", "sequence": 1}}
|
||||
manifest = {"schema": 1, "request": {"buildId": "build-fixture", "channel": "stable", "sequence": 1,
|
||||
"repository": "NousResearch/hermes-agent", "commit": t.wanted, "sourceVersion": "1.1.0",
|
||||
"version": "0.0.1", "identity": {}, "bundleEnv": {}}, "packages": []}
|
||||
monkeypatch.setattr(source_releases, '_resolve_channel',
|
||||
lambda name, repository: ChannelResolution(record, record, manifest))
|
||||
_publish_stable(monkeypatch, t)
|
||||
routes = {
|
||||
f'/NousResearch/hermes-agent/archive/{t.wanted}.zip': archive_bytes,
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user