diff --git a/pyproject.toml b/pyproject.toml index b9578f3fc53e..5509e7aafc71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,6 +115,11 @@ dependencies = [ # imports win32security/win32file/etc. directly — declare pywin32 rather than # relying on the concurrent-log-handler → portalocker transitive chain. "pywin32>=306,<312; sys_platform == 'win32'", + # Host-projected plugins use cross-process locks on every platform. Keep + # portalocker direct: its existing lockfile entries are otherwise reachable + # only through platform/optional transitive dependencies, so a fresh lean + # Linux install can load the plugin but fail closed on its first lock. + "portalocker>=2.8.0,<4", # Image resize recovery for the vision tools. Pillow shrinks oversized images # (>5 MB or >8000px) at embed time; without it the byte AND pixel-dimension # shrink paths no-op, so an oversized image bakes into immutable history and diff --git a/tests/test_packaging_metadata.py b/tests/test_packaging_metadata.py index f646e05a19cb..464d283b7133 100644 --- a/tests/test_packaging_metadata.py +++ b/tests/test_packaging_metadata.py @@ -46,6 +46,22 @@ def test_packaging_declared_as_core_dependency(): ) +def test_portalocker_declared_as_core_dependency(): + """Host-projected plugins must not depend on incidental lock packages. + + ``portalocker`` was present in ``uv.lock`` only through Windows and optional + dependency chains. A fresh lean Linux sync therefore omitted it, allowing + lock-using plugins to load before failing closed on their first operation. + """ + 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 "portalocker" in names, ( + "portalocker is required by host-projected runtime plugins and must be " + "a declared core dependency, not an optional or platform-only transitive" + ) + + 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"] diff --git a/uv.lock b/uv.lock index 592d5db2612f..05be0013772c 100644 --- a/uv.lock +++ b/uv.lock @@ -1576,6 +1576,7 @@ dependencies = [ { name = "packaging" }, { name = "pathspec" }, { name = "pillow" }, + { name = "portalocker" }, { name = "prompt-toolkit" }, { name = "psutil" }, { name = "ptyprocess", marker = "sys_platform != 'win32'" }, @@ -1887,6 +1888,7 @@ requires-dist = [ { name = "parallel-web", marker = "extra == 'parallel-web'", specifier = "==0.4.2" }, { name = "pathspec", specifier = "==1.1.1" }, { name = "pillow", specifier = "==12.3.0" }, + { name = "portalocker", specifier = ">=2.8.0,<4" }, { name = "prompt-toolkit", specifier = "==3.0.52" }, { name = "psutil", specifier = "==7.2.2" }, { name = "ptyprocess", marker = "sys_platform != 'win32'", specifier = ">=0.7.0,<1" },