diff --git a/tests/tools/test_lazy_deps.py b/tests/tools/test_lazy_deps.py index 2b319ae049d..b067c0ff4db 100644 --- a/tests/tools/test_lazy_deps.py +++ b/tests/tools/test_lazy_deps.py @@ -294,6 +294,53 @@ class TestIsSatisfiedVersionAware: assert ld._is_satisfied("mautrix[encryption]==0.21.0") is False +class TestSharedDependencyPins: + """Pins on packages other Hermes features install transitively must be + ranges, not == pins: active_features() flags a feature "active" from mere + package presence, so a hard pin makes the post-update lazy refresh + downgrade the shared package underneath its other consumers (#60783).""" + + # transformers (via sentence-transformers, the Hindsight local-embedding + # provider) requires this range of huggingface-hub. + _TRANSFORMERS_HF_HUB_REQ = ">=1.5.0,<2.0" + + def _hub_spec_tail(self): + (spec,) = ld.LAZY_DEPS["tool.trace_upload"] + assert ld._pkg_name_from_spec(spec) == "huggingface-hub" + return ld._specifier_from_spec(spec) + + def test_trace_upload_hub_pin_admits_transformers_range(self): + """A version satisfying transformers' floor must satisfy our spec too, + so the refresh pass never downgrades it out from under transformers.""" + from packaging.specifiers import SpecifierSet + from packaging.version import Version + + ours = SpecifierSet(self._hub_spec_tail()) + # Representative versions across transformers' accepted range. + for v in ("1.5.0", "1.22.0", "1.99.0"): + assert Version(v) in SpecifierSet(self._TRANSFORMERS_HF_HUB_REQ) + assert Version(v) in ours, ( + f"tool.trace_upload huggingface-hub spec {ours!r} rejects " + f"{v}, which transformers accepts — the lazy refresh would " + f"downgrade the shared package and break Hindsight (#60783)" + ) + + def test_transformers_compatible_hub_version_is_satisfied(self, monkeypatch): + """hermes update must treat an already-compatible newer hub version + as current instead of reinstalling the old pin.""" + from importlib.metadata import PackageNotFoundError + + def _version(pkg): + if pkg == "huggingface-hub": + return "1.22.0" + raise PackageNotFoundError(pkg) + + import importlib.metadata as _md + monkeypatch.setattr(_md, "version", _version) + + assert ld.feature_missing("tool.trace_upload") == () + + # --------------------------------------------------------------------------- # active_features + refresh_active_features (Piece A — hermes update wiring) # --------------------------------------------------------------------------- diff --git a/tools/lazy_deps.py b/tools/lazy_deps.py index ec5692ecd55..03735d07727 100644 --- a/tools/lazy_deps.py +++ b/tools/lazy_deps.py @@ -239,7 +239,15 @@ LAZY_DEPS: dict[str, tuple[str, ...]] = { "starlette==1.0.1", # CVE-2026-48710 — keep in sync with pyproject [computer-use] ), # HF Agent Trace Viewer upload (hermes trace upload / /upload-trace). - "tool.trace_upload": ("huggingface-hub==1.2.3",), + # RANGE, not an == pin: huggingface-hub is shared with transformers / + # sentence-transformers (Hindsight local embeddings), which require + # huggingface-hub>=1.5.0,<2.0. A hard pin makes the post-update lazy + # refresh downgrade the shared package underneath them and break their + # import (#60783) — active_features() flags this feature "active" from + # mere package presence, so the downgrade fires even for users who never + # ran a trace upload. The HfApi surface used here (whoami / create_repo / + # upload_file) is stable across the whole 1.x line. + "tool.trace_upload": ("huggingface-hub>=1.2.3,<2.0",), }