fix(pm): union sync must install member deps — add --all-packages

Probed live 2026-09-03: plain `uv sync --frozen` installs only the
ROOT project's dependencies — workspace-member deps are locked by
`uv lock` but silently never reach site-packages (a member's
pyfiglet stayed absent under plain --frozen, present under
--all-packages). The union's whole contract is that plugin deps ride
the venv; without the flag every member install is a green-looking
lock over an empty site-packages.

- lock_and_sync now passes --all-packages, with the probe rationale
  in place so the flag can never be 'simplified' away.
- _uv_binary() falls back to shutil.which('uv') when the pm store has
  no provisioned uv — store-first, PATH-second (the activate()
  precedence), fixing 'uv is not installed' lies on dev machines and
  test envs that have uv on PATH but no store.
- tests/pm/test_union_installs_members.py: two REAL end-to-end tests
  (mini workspace, real uv): member deps land in site-packages after
  lock_and_sync, and SURVIVE a second sync (the update-rebuild prune
  contract). These fail under the old flagless command by
  construction — the probe scenario can never silently return.

Found while porting mnemosyne to the union (the manifest-less pip
route prunes on resync; the union route needs member deps to
actually install). tests/pm: 190 passed, 0 failed.
This commit is contained in:
ethernet
2026-09-03 10:28:23 -04:00
parent ad433f8029
commit ef5a98ed57
2 changed files with 117 additions and 2 deletions

View File

@@ -297,7 +297,14 @@ def lock_and_sync(
if lock.returncode != 0:
raise InstallError("venv", f"uv lock exited {lock.returncode}: {lock.stderr[-600:]}")
cmd = [uv_bin, "sync", "--frozen"]
# --all-packages is REQUIRED: plain `uv sync --frozen` installs only the
# ROOT project's deps — workspace-member deps are locked by `uv lock`
# but silently never reach site-packages (probed live 2026-09-03: a
# member's pyfiglet stayed absent from site-packages under plain
# --frozen, present under --all-packages). The union's whole contract
# is that plugin deps ride the venv; without this flag every member
# install is a silent no-op.
cmd = [uv_bin, "sync", "--frozen", "--all-packages"]
for extra in sorted(set(extras or [])):
cmd += ["--extra", extra]
sync = subprocess.run(cmd, cwd=str(root), env=env, capture_output=True, text=True)
@@ -372,7 +379,14 @@ def _default_venv_dir() -> Path:
def _uv_binary() -> Optional[str]:
"""Store uv first (pinned), PATH uv second (dev machines, test envs).
A dev machine with uv on PATH but no provisioned store is the normal
pre-`pm install` state — 'uv is not installed' there is a lie."""
from pm.ensure import uv as pm_uv
uv_bin, _env = pm_uv(realize=False)
return uv_bin
if uv_bin:
return uv_bin
import shutil
return shutil.which("uv")