Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions tests/tools/test_lazy_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,53 @@ def test_extras_block_mismatch_returns_false(self, monkeypatch):
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)
# ---------------------------------------------------------------------------
Expand Down
54 changes: 52 additions & 2 deletions tools/lazy_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,17 @@
"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. (See also the
# no-downgrade guard in _is_satisfied, which backstops this for any
# shared-dep pin.)
"tool.trace_upload": ("huggingface-hub>=1.5.0,<2.0",),
}


Expand Down Expand Up @@ -544,12 +554,52 @@ def _is_satisfied(spec: str) -> bool:
return True

try:
return Version(installed) in SpecifierSet(spec_tail)
iv = Version(installed)
ss = SpecifierSet(spec_tail)
if iv in ss:
return True
# No-downgrade guard. The installed version is outside the spec — but if
# installing this spec would move the package BACKWARDS, refuse. A lazy,
# opt-in backend must never downgrade a package that the core (or another
# backend) already installed at a higher version: that is exactly how an
# exact "==" pin on a SHARED transitive dependency (e.g. huggingface-hub,
# pulled by transformers) silently bricks an unrelated core feature on
# `hermes update`. Treat "already newer than this pin allows" as
# satisfied, leave the higher version in place, and warn the maintainer
# to widen the pin instead of churning a shared dependency.
if _would_downgrade(iv, ss):
logger.warning(
"Lazy spec %r would DOWNGRADE already-installed %s==%s; leaving the "
"installed version in place. Widen this pin to a range that includes "
"the installed version if the downgrade is not intended.",
spec, pkg, installed,
)
return True
return False
except (InvalidSpecifier, InvalidVersion, Exception):
# Malformed spec or installed version we can't parse — don't churn.
return True


def _would_downgrade(installed, spec_set) -> bool:
"""True if ``spec_set`` permits no version >= ``installed``.

``installed`` is already known to fall outside ``spec_set``. If every
upper-bounding operator (``==``, ``<``, ``<=``, ``~=``) in the spec sits
below the installed version, the only way to satisfy the spec is to install
something older — a downgrade. Purely local version arithmetic; no network.
"""
try:
from packaging.version import Version
for s in spec_set:
if s.operator in ("==", "<", "<=", "~="):
if installed > Version(s.version):
return True
return False
except Exception:
return False


def _is_present(spec: str) -> bool:
"""Cheap presence-only check (package name installed at any version).

Expand Down
Loading