Files
hermes-agent/tests/pm/test_uv_cache.py
ethernet ba39525c39 feat(pm): ship the uv cache + mutable-venv bootstrap seed for sealed installs
The blow-the-venv-on-update contract needs rebuilds to be cheap, and
sealed installs need a writable venv at all (settled 2026-09-02 plan,
task 7):

- uv_cache_dir(): hermes-owned machine cache at
  <default hermes root>/cache/uv — content-addressed, shared across
  profiles. uv_env() ALWAYS pins UV_CACHE_DIR there (ambient UV_*
  stripped), so the cache that ships is the cache that gets used.
  First call on a sealed install seeds it from the payload's shipped
  uv-cache/ (read-only payload can't serve uv's working cache); the
  .seeded marker makes it once-only and non-clobbering.
- pm bundle stages the warmed cache into the payload after the venv
  sync (uv-cache/ beside manifest.json) — warm 'uv sync --offline'
  rebuilds probed at 0.4s vs 1.2s cold.
- Venv.venv_dir(): sealed installs resolve the MUTABLE venv to the
  machine hermes root (<root>/venv), not the read-only payload;
  dev/source installs keep the repo-local venv unchanged.
- Venv.seed_mutable_venv(): the bootstrap seed — lazy-off installs
  copy the payload's shipped venv out as the starting point; lazy-on
  installs skip the copy (first sync builds fresh from the shipped
  cache). adopt() triggers it (KeyError-guarded, failure reported
  never fatal — a cold sync still converges).

tests/pm/test_uv_cache.py: 6 tests (env pinning + ambient strip,
payload seed + marker once-only, cold machine, sealed venv_dir, seed
copy idempotence, lazy-on skip). tests/pm: 175 passed, 0 failed.
2026-09-02 20:00:34 -04:00

129 lines
4.4 KiB
Python

"""pm uv cache: hermes-owned UV_CACHE_DIR + sealed-venv bootstrap seed.
The cache ships with bundles and is copied out to the writable machine
cache on first use; every pm-internal uv invocation pins UV_CACHE_DIR.
The mutable venv on sealed installs lives in the machine hermes root,
seeded from the payload's shipped venv when lazy installs are off.
"""
from __future__ import annotations
from pathlib import Path
import pytest
import pm.packages as pkgs
def test_uv_env_pins_cache_dir(monkeypatch, tmp_path):
monkeypatch.setattr(pkgs, "uv_cache_dir", lambda: tmp_path / "c")
env = pkgs.uv_env({"UV_CACHE_DIR": "/ambient/user/cache", "PATH": "x"})
assert env["UV_CACHE_DIR"] == str(tmp_path / "c")
# ambient UV_ vars are stripped, not inherited
assert "UV_PROJECT_ENVIRONMENT" not in env
def test_uv_cache_dir_seeds_from_payload(monkeypatch, tmp_path):
home = tmp_path / "home"
home.mkdir()
payload = tmp_path / "payload"
(payload / "uv-cache" / "wheels-v5").mkdir(parents=True)
(payload / "uv-cache" / "wheels-v5" / "some.pkg").write_text("x", encoding="utf-8")
import hermes_constants
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
import pm.paths as paths_mod
monkeypatch.setattr(paths_mod, "store_root", lambda: payload / "tools")
machine = pkgs.uv_cache_dir()
assert machine == home / "cache" / "uv"
# seed copied out
assert (machine / "wheels-v5" / "some.pkg").read_text(encoding="utf-8") == "x"
# seeded marker written → second call doesn't re-copy
assert (machine / ".seeded").is_file()
(payload / "uv-cache" / "wheels-v5" / "some.pkg").write_text("changed", encoding="utf-8")
pkgs.uv_cache_dir()
assert (machine / "wheels-v5" / "some.pkg").read_text(encoding="utf-8") == "x"
def test_uv_cache_dir_cold_machine_no_payload(monkeypatch, tmp_path):
home = tmp_path / "home"
home.mkdir()
import hermes_constants
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
import pm.paths as paths_mod
monkeypatch.setattr(paths_mod, "store_root", lambda: tmp_path / "nowhere" / "tools")
machine = pkgs.uv_cache_dir()
assert machine == home / "cache" / "uv"
assert (machine / ".seeded").is_file()
def test_venv_dir_sealed_goes_to_machine_root(monkeypatch, tmp_path):
home = tmp_path / "home"
home.mkdir()
import sys
import hermes_constants
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
ensure_mod = sys.modules["pm.ensure"]
monkeypatch.setattr(ensure_mod, "sealed", lambda: True)
venv = pkgs.Venv()
assert venv.venv_dir() == home / "venv"
def test_seed_mutable_venv_copies_payload_venv(monkeypatch, tmp_path):
home = tmp_path / "home"
home.mkdir()
payload = tmp_path / "payload"
(payload / "venv" / "Scripts").mkdir(parents=True)
(payload / "venv" / "Scripts" / "python.exe").write_text("bin", encoding="utf-8")
import hermes_constants
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
import sys
ensure_mod = sys.modules["pm.ensure"]
import pm.paths as paths_mod
monkeypatch.setattr(ensure_mod, "sealed", lambda: True)
monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: False)
monkeypatch.setattr(paths_mod, "store_root", lambda: payload / "tools")
venv = pkgs.Venv()
reason = venv.seed_mutable_venv()
assert reason is None
seeded = home / "venv"
assert (seeded / "Scripts" / "python.exe").read_text(encoding="utf-8") == "bin"
# idempotent: second call is a no-op
assert venv.seed_mutable_venv() is None
def test_seed_mutable_venv_lazy_on_skips_copy(monkeypatch, tmp_path):
home = tmp_path / "home"
home.mkdir()
payload = tmp_path / "payload"
(payload / "venv").mkdir(parents=True)
import hermes_constants
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
import sys
ensure_mod = sys.modules["pm.ensure"]
import pm.paths as paths_mod
monkeypatch.setattr(ensure_mod, "sealed", lambda: True)
monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: True)
monkeypatch.setattr(paths_mod, "store_root", lambda: payload / "tools")
venv = pkgs.Venv()
assert venv.seed_mutable_venv() is None
assert not (home / "venv").exists() # builds fresh later, no seed copy