fix(lazy-deps): never downgrade a shared dependency; track a range for huggingface-hub

An exact ==1.2.3 pin on huggingface-hub (feature tool.trace_upload)
force-downgraded the shared venv on every lazy refresh whenever the core
embedding stack (transformers/sentence-transformers, used by
local/local_embedded Hindsight) had installed a newer version — transformers
5.x requires huggingface-hub>=1.5,<2.0, so the downgrade made
sentence_transformers unimportable and the embedded Hindsight daemon abort
at startup (silent memory loss until noticed).

Two layers:
- Track the compatibility range the trace-upload client actually needs
  (>=1.5,<2.0) instead of an exact pin, so an already-healthy shared
  version satisfies the spec and is left alone.
- Add a general no-downgrade guard in _is_satisfied: a lazy, opt-in
  backend must never move an already-installed package backwards; treat
  'installed newer than the pin allows' as satisfied and warn to widen
  the pin. Legitimate upgrades (installed below the spec) are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MN8RMDLwxCfFxwtADoEJJf
This commit is contained in:
spiky02plateau 2026-07-20 13:10:59 +02:00 • committed by Teknium
parent 21695a10bf
commit 3ab819dc87
No known key found for this signature in database

View file

@ -246,8 +246,10 @@ LAZY_DEPS: dict[str, tuple[str, ...]] = {
# 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",),
# 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",),
}
@ -552,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).