"""pm.features: the frozen bundle feature set (enabled-features.json). Lazy installs OFF = the bundle's feature list is FROZEN to the file the bundle wrote (the EXACT extras that installed on that target); pm sync never deviates and never installs a plugin member. """ from __future__ import annotations import pytest import pm.features as feats @pytest.fixture def rooted(tmp_path, monkeypatch): """Point features_path at a temp runtime dir (store_root().parent).""" store = tmp_path / "tools" store.mkdir() monkeypatch.setattr("pm.paths.store_root", lambda: store) return tmp_path def test_write_then_read_roundtrip(rooted): assert feats.read_features() is None path = feats.write_features(["web", "acp", "web"]) assert path.is_file() got = feats.read_features() assert got == ["acp", "web"] # sorted, deduped path.write_text("{ not json", encoding="utf-8") assert feats.read_features() is None def test_features_path_in_bundle_uses_payload_root(rooted): payload = rooted / "payload" payload.mkdir() assert feats.features_path(payload) == payload / "enabled-features.json" def test_sync_venv_refuses_outside_frozen_extras(rooted, monkeypatch): feats.write_features(["web", "acp"]) import pm.install as ensure_mod from pm.package import InstallError monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: False) with pytest.raises(InstallError) as exc: ensure_mod.sync_venv(["slack"], explicit=True) assert "frozen" in str(exc.value) or "outside" in str(exc.value) from pm import receipt saved = receipt.latest() assert saved["refusal"]["code"] == "lazy-install" assert saved["outcome"] == "failed" and saved["exit_code"] != 0 assert saved["steps"][-1]["ok"] is False assert "slack" in saved["steps"][-1]["detail"] assert "hermes pm install" in saved["steps"][-1]["detail"] def test_sync_venv_allows_frozen_extras_when_lazy_off(rooted, monkeypatch): feats.write_features(["web"]) from pm import paths from pm.lock import Facts from pm.environments import install_state_dir, runtime_facts_path import pm.install as ensure_mod # Matching stamp alone cannot certify a vanished environment. Reuse only # the recorded selection while retaining the disabled acquisition policy. repo = rooted / "repo" repo.mkdir() monkeypatch.setattr(paths, "repo_root", lambda: repo) environment = install_state_dir(repo) / "environments" / "frozen" / "venv" environment.mkdir(parents=True) (environment / "pyvenv.cfg").write_text("home = fixture\n") Facts(runtime_facts_path(repo)).record_state("venv", "stamp", ["web"], environment=environment) monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: False) venv_pkg = ensure_mod.get_package("venv") monkeypatch.setattr( venv_pkg, "expected_stamp", lambda extras: "stamp" ) monkeypatch.setattr(venv_pkg, "apply", lambda *args, **kwargs: pytest.fail("current frozen environment rebuilt")) ensure_mod.sync_venv(["web"]) (environment / "pyvenv.cfg").unlink() from pm.package import InstallError with pytest.raises(InstallError, match="lazy installs are disabled"): ensure_mod.sync_venv(["web"]) def test_lazy_sync_never_creates_the_first_selection_for_a_foreign_interpreter(rooted, monkeypatch): """A process running from an environment PM did not select (a build_environment test venv, a dev venv, nix) imports an adapter whose extra is missing. Letting that lazy sync commit the install's FIRST selection strands every later process: they boot into a generation that lacks whatever the foreign interpreter carried (the CI "anthropic/aiohttp vanished" class). Only an explicit install may create it. Exercised at the client seam every ensure_import caller goes through, in the in-process (is_runtime) shape.""" import pm.client as client import pm.install as ensure_mod from pm import paths from pm.package import InstallError from pm.environments import runtime_facts_path repo = rooted / "repo" repo.mkdir() (repo / "venv").mkdir() # the install's own base venv, which this pytest process is NOT running from monkeypatch.setattr(paths, "repo_root", lambda: repo) monkeypatch.setattr(client, "is_runtime", lambda: True) monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: True) venv_pkg = ensure_mod.get_package("venv") monkeypatch.setattr(venv_pkg, "expected_stamp", lambda extras, **kwargs: "stamp") monkeypatch.setattr( venv_pkg, "apply", lambda *args, **kwargs: pytest.fail("lazy sync built a generation for a foreign interpreter")) assert not runtime_facts_path(repo).exists() with pytest.raises(InstallError, match="not running from the install's dependency environment"): client.sync_venv(["bedrock"]) assert not runtime_facts_path(repo).exists(), "a refused sync must not commit a selection" # The remedy the refusal names still works: an explicit install creates the selection. monkeypatch.setattr(venv_pkg, "apply", lambda *args, **kwargs: {}) client.sync_venv(["bedrock"], explicit=True) assert runtime_facts_path(repo).is_file()