Skip to content
Merged
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
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ dependencies = [
"prompt_toolkit==3.0.52",
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
"croniter==6.0.0",
# ``packaging`` is imported directly on three production paths but was never
# declared, so it only reached users transitively (pip/uv pull it for other
# tools). The slim official Docker image ships without it, where the
# try/except-ImportError fallbacks silently degrade: Hindsight's
# ``_meets_minimum_version`` disables update_mode='append' (#40503),
# tools/lazy_deps.py treats every version constraint as satisfied, and
# hermes_cli/main.py drops to naive requirement parsing. Pure-Python
# py3-none-any wheel, no compiled extensions — safe to ship everywhere.
# Pinned to the version already resolved in uv.lock (no resolution churn).
"packaging==26.0",
# Markdown -> HTML conversion for rich message delivery (Matrix
# `formatted_body`, and the `send_message` tool's HTML path). Now on the
# DEFAULT delivery path, not matrix-specific: without it both
Expand Down
38 changes: 38 additions & 0 deletions tests/test_packaging_metadata.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pathlib import Path
import re
import tomllib

import pytest
Expand All @@ -14,6 +15,22 @@
REPO_ROOT = Path(__file__).resolve().parents[1]


def _distribution_name(requirement: str) -> str:
"""Extract the PEP 508 distribution name from a requirement string.

Robust to markers (``; python_version < '3.12'``), direct references
(``name @ https://...``), extras (``name[extra]``) and every version
operator (``==``, ``>=``, ``<=``, ``~=``, ``!=``, ``<``, ``>``), so a
future dep declared with any valid specifier shape doesn't silently
mis-parse here.
"""
spec = requirement.split(";", 1)[0] # drop environment markers
spec = spec.split("@", 1)[0] # drop direct-reference URLs
spec = spec.split("[", 1)[0] # drop extras
spec = re.split(r"[=<>!~]", spec, maxsplit=1)[0] # drop any version operator
return spec.strip().lower()


def _packages_find_include():
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
return data["tool"]["setuptools"]["packages"]["find"]["include"]
Expand Down Expand Up @@ -61,6 +78,27 @@ def test_every_on_disk_subpackage_is_covered_by_packages_find():
)


def test_packaging_declared_as_core_dependency():
"""Regression for #40503.

``packaging`` is imported directly on three production paths
(plugins/memory/hindsight/__init__.py, tools/lazy_deps.py,
hermes_cli/main.py) yet was undeclared, so it only reached users
transitively. The slim Docker image shipped without it, silently
disabling Hindsight append-mode and version-constraint checks. It must
be a declared core dependency so it installs everywhere and the
update-repair step (``_verify_core_dependencies_installed``) guards it.
"""
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
core = data["project"]["dependencies"]
names = {_distribution_name(dep) for dep in core}
assert "packaging" in names, (
"packaging is imported on production paths (hindsight version compare, "
"lazy_deps version constraints, requirement parsing) and must be a "
"declared core dependency, not a transitive — see #40503"
)


def test_faster_whisper_is_not_a_base_dependency():
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
deps = data["project"]["dependencies"]
Expand Down
2 changes: 2 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading