fix(pm): prepare platform trust before bootstrap downloads

Standalone Python cannot locate the system CA bundle on this NixOS host.
Use the shell-staged uv to prepare the independent PM runtime before PM
fetches managed Python. Declare and lock truststore in that runtime, then
activate it before CLI and worker imports construct HTTPS clients.

Make setup's awk pin reader follow object nesting rather than indentation.
Use the same reader for tool versions and artifact fields.

Verified cold activation with CA overrides removed, pinned Python and uv
downloads, pm doctor, and a public worker HTTPS install. The targeted suite
passed 56 tests with one Windows-only skip. The TLS regression fails when
truststore is installed but entrypoint activation is removed. Bash syntax
and Ruff checks passed. The full suite was not run.

Two additional setup-toolchain tests fail on unchanged HEAD because their
fixtures reach the real home before home isolation. CI bootstrap unification
is not part of this change.
This commit is contained in:
ethernet
2026-09-11 13:31:41 -04:00
parent fffccbb2ae
commit 018d2b39d8
13 changed files with 364 additions and 37 deletions

View File

@@ -2,6 +2,10 @@
from pathlib import Path
import sys
import truststore
# PM's import closure constructs HTTPS clients; install platform trust first.
truststore.inject_into_ssl()
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from pm.cli import main

View File

@@ -6,6 +6,7 @@ dependencies = [
"packaging==26.0",
"tomli-w==1.2.0",
"ruamel.yaml==0.18.17",
"truststore==0.10.4",
]
[tool.uv]

View File

@@ -92,7 +92,7 @@ def _validate(python: Path, env: dict[str, str]) -> str:
try:
checked = subprocess.run(
[str(python), "-I", "-B", "-c",
"import packaging, tomli_w; from ruamel.yaml import YAML"],
"import packaging, tomli_w, truststore; from ruamel.yaml import YAML"],
env=env, capture_output=True, text=True, timeout=30,
)
except (OSError, subprocess.TimeoutExpired) as exc:
@@ -160,8 +160,22 @@ def runtime_python(*, bootstrap: bool = True) -> Path:
if not bootstrap:
raise InstallError("pm-runtime", "not installed and lazy installs are disabled",
"run `hermes pm install` to prepare the independent PM runtime")
# This closure is deliberately stdlib-only: uv + Python, never Venv.
uv, env = managed_uv(explicit=True)
from pm.lock import Lockfile
from pm.paths import lockfile_path, store_root
from pm.registry import get_package
from pm.store import current_target
# Setup has already verified/extracted uv, but there are no PM facts
# yet. Use it to acquire PM's TLS support BEFORE downloading Python.
package = get_package("uv")
version = Lockfile(lockfile_path()).version("uv")
target = current_target()
staged = package.binary(store_root() / package.store_entry(version, target), target) if version else None
if staged is not None and staged.is_file():
uv, env = str(staged), {"UV_PYTHON": sys.executable}
else:
# Non-shell bootstrap callers (CI) already have a host interpreter.
uv, env = managed_uv(explicit=True)
if uv is None:
raise InstallError("pm-runtime", "pinned uv and Python are unavailable")
return prepare_runtime(Path(uv), Path(env["UV_PYTHON"]), install_state_dir(project) / "pm-runtime",

View File

@@ -63,7 +63,7 @@ def stage_runtime(uv: Path, python: Path, destination: Path, *,
if result.returncode:
raise InstallError("pm-runtime", f"{command[1]} exited {result.returncode}")
checked = subprocess.run(
[str(executable), "-I", "-B", "-c", "import packaging, tomli_w; from ruamel.yaml import YAML"],
[str(executable), "-I", "-B", "-c", "import packaging, tomli_w, truststore; from ruamel.yaml import YAML"],
env=env, capture_output=True, text=True, timeout=30,
)
if checked.returncode:

11
pm/uv.lock generated
View File

@@ -10,6 +10,7 @@ dependencies = [
{ name = "packaging" },
{ name = "ruamel-yaml" },
{ name = "tomli-w" },
{ name = "truststore" },
]
[package.metadata]
@@ -17,6 +18,7 @@ requires-dist = [
{ name = "packaging", specifier = "==26.0" },
{ name = "ruamel-yaml", specifier = "==0.18.17" },
{ name = "tomli-w", specifier = "==1.2.0" },
{ name = "truststore", specifier = "==0.10.4" },
]
[[package]]
@@ -66,3 +68,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" },
]
[[package]]
name = "truststore"
version = "0.10.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
]

View File

@@ -44,6 +44,9 @@ def _read_controls(messages, pause):
def main():
import truststore
truststore.inject_into_ssl()
# Capture the protocol FD before redirecting even native/subprocess stdout.
wire = os.fdopen(os.dup(sys.stdout.fileno()), "w", encoding="utf-8", buffering=1)
os.dup2(sys.stderr.fileno(), sys.stdout.fileno())

View File

@@ -82,24 +82,24 @@ else
fi
target="$os-$arch"
# lock.json is machine-written (sorted keys, 2-space indent): read the uv
# pin's version + this target's url/sha256 with awk — no python yet.
pin() { # $1 = field (url | sha256)
awk -F '"' -v target="$target" -v field="$1" '
/^ "uv": \{/ { in_uv = 1 }
in_uv && /^ }/ { exit }
in_uv && /^ "/ { in_t = ($2 == target) }
in_t && $2 == field { print $4; exit }' "$lock"
# The machine-written lock has one member per line. Follow object names and
# braces, not indentation, to read pins before Python is available.
pin() { # $1 = field (url | sha256 | version), $2 = package (default: uv)
awk -F '"' -v package="${2:-uv}" -v target="$target" -v field="$1" '
/^[[:space:]]*("[^"]+"[[:space:]]*:[[:space:]]*)?\{[[:space:]]*$/ {
path[++depth] = $2; next
}
/^[[:space:]]*}[[:space:]]*,?[[:space:]]*$/ {
delete path[depth--]; next
}
path[2] == "packages" && path[3] == package && $2 == field &&
((field == "version" && depth == 3) ||
(depth == 5 && path[4] == "artifacts" && path[5] == target)) {
print $4; exit
}' "$lock"
}
uv_version="$(awk -F '"' '
/^ "uv": \{/ { in_uv = 1 }
in_uv && /^ }/ { exit }
in_uv && $2 == "version" { print $4; exit }' "$lock")"
py_version="$(awk -F '"' '
/^ "python": \{/ { in_py = 1 }
in_py && /^ }/ { exit }
in_py && $2 == "version" { print $4; exit }' "$lock" \
| cut -d+ -f1 | cut -d. -f1,2)"
uv_version="$(pin version)"
py_version="$(pin version python | cut -d+ -f1 | cut -d. -f1,2)"
[ -n "$uv_version" ] || { echo -e "${RED}✗${NC} no uv pin in pm/lock.json" >&2; exit 1; }
store="${HERMES_RUNTIME_DIR:-$HOME/.hermes/tools}"

View File

@@ -190,6 +190,9 @@ test "${PYTHONPATH-}" = "$prior_pythonpath" || exit 96
def operations(name):
return [line for line in calls.read_text().splitlines() if line.split()[0] == name]
def app_syncs():
return [line for line in operations("sync") if "--frozen --all-packages" in line]
cold = activate()
first = selection()
probe = json.loads(cold.stdout)
@@ -198,7 +201,7 @@ test "${PYTHONPATH-}" = "$prior_pythonpath" || exit 96
assert probe["pythonpath"].split(os.pathsep)[0] == str(core)
# Cold activation builds both PM's isolated runtime and the app environment.
assert "Preparing the isolated PM runtime" in cold.stderr
assert len(operations("venv")) == len(operations("sync")) == 2
assert len(app_syncs()) == 1
facts = json.loads((runtime / "facts.json").read_text())["packages"]
assert facts["python"]["artifacts"] == [first_digest]
assert facts["uv"]["artifacts"] == [uv_digest]
@@ -209,8 +212,14 @@ test "${PYTHONPATH-}" = "$prior_pythonpath" || exit 96
untouched = _snapshot(protected)
activate()
assert selection() == first
assert len(operations("venv")) == len(operations("sync")) == 2
assert len([line for line in operations("python") if line.startswith("python install ")]) == 2
# The first warm launch rebinds PM from bootstrap Python to its managed
# interpreter. The application selection is unchanged; later launches reuse both.
assert len(app_syncs()) == 1
prepared = operations("sync")
activate()
assert selection() == first
assert operations("sync") == prepared
assert len([line for line in operations("python") if line.startswith("python install ")]) == 3
second_digest = pin_python("second")
activate()
@@ -218,7 +227,7 @@ test "${PYTHONPATH-}" = "$prior_pythonpath" || exit 96
assert second["stamp"] != first["stamp"]
assert second["environment"] != first["environment"]
assert Path(first["environment"]).is_dir()
assert len(operations("venv")) == len(operations("sync")) == 3
assert len(app_syncs()) == 2
facts = json.loads((runtime / "facts.json").read_text())["packages"]
assert facts["python"]["artifacts"] == [second_digest]
assert (core / "uv.lock").read_bytes() == dependency_lock
@@ -231,5 +240,5 @@ test "${PYTHONPATH-}" = "$prior_pythonpath" || exit 96
assert "setup failed" in failed.stderr
assert "CALLER_SURVIVED:" in failed.stdout
assert selection() == second
assert len(operations("sync")) == 4
assert len([line for line in operations("python") if line.startswith("python install ")]) == 4
assert len(app_syncs()) == 3
assert len([line for line in operations("python") if line.startswith("python install ")]) == 5

View File

@@ -0,0 +1,177 @@
"""The shell-staged uv can prepare TLS support before PM downloads Python."""
from __future__ import annotations
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import pytest
@pytest.mark.platforms("posix")
def test_staged_uv_prepares_pm_before_any_tool_download(tmp_path):
from pm.packages import Uv
from pm.store import current_target
uv = shutil.which("uv")
assert uv, "this bootstrap contract requires real uv"
repo = Path(__file__).resolve().parents[2]
stage = tmp_path / "source"
for name in ("pm", "hermes_cli"):
shutil.copytree(repo / name, stage / name, ignore=shutil.ignore_patterns("__pycache__"))
shutil.copy2(repo / "hermes_constants.py", stage / "hermes_constants.py")
home = tmp_path / "home"
store = home / "tools"
target = current_target()
# As in setup: uv has been verified/extracted, but PM has no installed facts.
entry = store / Uv().store_entry("bootstrap-fixture", target)
binary = Uv().binary(entry, target)
assert binary is not None
binary.parent.mkdir(parents=True)
shutil.copy2(uv, binary)
(stage / "pm" / "lock.json").write_text(json.dumps({"schema": 1, "packages": {
name: {"version": "bootstrap-fixture", "artifacts": {target: {
"url": f"https://must-not-fetch.invalid/{name}.tar.gz", "sha256": "a" * 64,
}}} for name in ("uv", "python")
}}))
env = {key: value for key, value in os.environ.items()
if not key.startswith(("PYTHON", "UV_"))}
env.update(HERMES_HOME=str(home), HERMES_RUNTIME_DIR=str(store))
code = """
import sys, subprocess
from pathlib import Path
sys.path.insert(0, sys.argv[1])
from pm.runtime import runtime_command, runtime_environment
result = subprocess.run(runtime_command(Path(sys.argv[2])), env=runtime_environment(),
capture_output=True, text=True)
assert result.returncode == 0, result.stdout + result.stderr
print(result.stdout)
"""
probe = tmp_path / "probe.py"
probe.write_text("import json, truststore; print(json.dumps({'tls': truststore.__file__}))")
command = [sys.executable, "-I", "-S", "-c", code, str(stage), str(probe)]
result = subprocess.run(command, cwd=tmp_path, env=env, capture_output=True, text=True, timeout=180)
assert result.returncode == 0, result.stdout + result.stderr
assert Path(json.loads(result.stdout)["tls"]).is_relative_to(home)
assert "Preparing the isolated PM runtime" in result.stderr
assert "must-not-fetch.invalid" not in result.stderr
warm = subprocess.run(command, cwd=tmp_path, env=env, capture_output=True, text=True, timeout=30)
assert warm.returncode == 0, warm.stdout + warm.stderr
assert "Preparing the isolated PM runtime" not in warm.stderr
assert warm.stdout == result.stdout
assert not (store / "facts.json").exists(), "preparing PM must not realize its tool closure"
assert not list(store.glob("python-*"))
assert not list(home.glob("installs/*/environments")), "no app environment during PM bootstrap"
@pytest.mark.platforms("linux")
def test_pm_cli_verifies_tls_with_platform_trust(tmp_path, monkeypatch):
from datetime import datetime, timedelta, timezone
import hashlib
from http.server import ThreadingHTTPServer
from ipaddress import ip_address
import io
import ssl
import tarfile
import threading
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from pm.runtime import prepare_runtime, runtime_environment
from tests.pm._range_server import RangeHandler
home = tmp_path / "home"
monkeypatch.setattr(Path, "home", lambda: tmp_path)
monkeypatch.setenv("HERMES_HOME", str(home))
monkeypatch.setenv("HERMES_RUNTIME_DIR", str(home / "tools"))
uv = shutil.which("uv")
assert uv
python = prepare_runtime(Path(uv), Path(sys.executable), tmp_path / "runtime")
source = Path(__file__).resolve().parents[2]
repo = tmp_path / "source"
for name in ("pm", "hermes_cli"):
shutil.copytree(source / name, repo / name, ignore=shutil.ignore_patterns("__pycache__"))
shutil.copy2(source / "hermes_constants.py", repo / "hermes_constants.py")
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PM test CA")])
now = datetime.now(timezone.utc)
cert = (x509.CertificateBuilder().subject_name(subject).issuer_name(subject)
.public_key(key.public_key()).serial_number(x509.random_serial_number())
.not_valid_before(now - timedelta(days=1)).not_valid_after(now + timedelta(days=1))
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
.add_extension(x509.SubjectAlternativeName([x509.IPAddress(ip_address("127.0.0.1"))]), critical=False)
.sign(key, hashes.SHA256()))
bundle, private = tmp_path / "ca.pem", tmp_path / "server.key"
bundle.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
private.write_bytes(key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8,
serialization.NoEncryption()))
payload = b"verified through platform trust"
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode="w:gz") as archive:
member = tarfile.TarInfo("payload.txt")
member.size = len(payload)
archive.addfile(member, io.BytesIO(payload))
body = stream.getvalue()
class Handler(RangeHandler):
payloads = {"/tool.tar.gz": body}
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(bundle, private)
server.socket = context.wrap_socket(server.socket, server_side=True)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
(repo / "pm" / "lock.json").write_text(json.dumps({"schema": 1, "packages": {
"tls-test": {"version": "1", "artifacts": {"any": {
"url": f"https://127.0.0.1:{server.server_port}/tool.tar.gz",
"sha256": hashlib.sha256(body).hexdigest(),
}}},
}}))
# Inject missing OpenSSL defaults, not a fake platform. Only truststore's
# real Linux CA discovery can find the test CA; urllib alone must reject it.
package = repo / "pm" / "tls_fixture.py"
package.write_text("from pm import Package, register\n@register\nclass Tool(Package):\n name = 'tls-test'\n")
driver = """
import runpy, ssl, sys
import truststore._openssl as platform_tls
defaults = ssl.get_default_verify_paths()
ssl.get_default_verify_paths = lambda: defaults._replace(cafile=None, capath=None)
platform_tls._CA_FILE_CANDIDATES = [sys.argv.pop(1)]
script = sys.argv.pop(1)
sys.path.insert(0, sys.argv.pop(1))
sys.argv[0] = script
# Register only the fixture package; the real entrypoint owns TLS activation.
module = runpy.run_path(script, run_name='tls_entrypoint_test')
import pm.tls_fixture
raise SystemExit(module['main']())
"""
env = runtime_environment()
env.pop("SSL_CERT_FILE", None)
env.pop("SSL_CERT_DIR", None)
env["NO_PROXY"] = "127.0.0.1"
try:
command = [str(python), "-I", "-B", "-c", driver, str(tmp_path / "missing-ca"),
str(repo / "pm" / "launch.py"), str(repo), "install", "tls-test"]
rejected = subprocess.run(command, cwd=tmp_path, env=env,
capture_output=True, text=True, timeout=60)
assert rejected.returncode == 1, rejected.stdout + rejected.stderr
assert "CERTIFICATE_VERIFY_FAILED" in rejected.stdout + rejected.stderr
assert not list((home / "tools").glob("tls-test-*/payload.txt"))
command[5] = str(bundle)
result = subprocess.run(command, cwd=tmp_path, env=env,
capture_output=True, text=True, timeout=60)
assert result.returncode == 0, result.stdout + result.stderr
installed = list((home / "tools").glob("tls-test-*/payload.txt"))
assert len(installed) == 1, result.stdout + result.stderr
assert installed[0].read_bytes() == payload
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)

View File

@@ -0,0 +1,95 @@
"""Exercise setup's pre-Python pin reader through real downloads and extraction.
Only the downloaded uv executable and the PM command at the handoff are fixtures;
the copied setup script, curl, hashing, archive staging, and Python process are real.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import sys
import pytest
from pm.store import current_target
from tests.pm.test_pm_core import make_tar, served # noqa: F401 -- shared HTTP fixture
pytestmark = pytest.mark.platforms("posix")
REPO = Path(__file__).resolve().parents[2]
@pytest.mark.parametrize("indent,blank_lines", [(2, False), (4, False), (0, False), ("\t", False), (4, True)],
ids=["two-spaces", "four-spaces", "no-indent", "tabs", "blank-lines"])
def test_setup_reads_pins_independent_of_indentation(tmp_path, served, indent, blank_lines):
bash = shutil.which("bash")
assert bash, "the shell bootstrap contract requires Bash"
core = tmp_path / "checkout with spaces"
(core / "pm").mkdir(parents=True)
shutil.copy2(REPO / "setup-hermes.sh", core / "setup-hermes.sh")
home = tmp_path / "home"
home.mkdir()
runtime = tmp_path / "runtime"
interpreter = str(Path(sys._base_executable).resolve())
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
uv_version = "fixture-pin"
calls = tmp_path / "uv-calls"
receipt = core / "handoff.json"
(core / "pm" / "__init__.py").touch()
(core / "pm" / "cli.py").write_text(
"import json, pathlib, sys\n"
"assert sys.argv[1:] == ['install'], sys.argv\n"
f"pathlib.Path({str(receipt)!r}).write_text(json.dumps(sys.argv[1:]))\n",
encoding="utf-8",
)
uv_script = (
f"#!{bash}\nset -eu\n"
f"printf '%s\\n' \"$*\" >> {shlex.quote(str(calls))}\n"
'case "$*" in\n'
f' --version) printf \'%s\\n\' "uv {uv_version}" ;;\n'
f' "python install --no-bin {py_version}") ;;\n'
f' "python find --managed-python {py_version}") printf \'%s\\n\' {shlex.quote(interpreter)} ;;\n'
' *) exit 91 ;;\nesac\n'
)
docroot, base_url = served
filename, digest = make_tar(docroot, "uv.tar.gz", {"uv-fixture/uv": uv_script})
artifact = {"sha256": digest, "url": f"{base_url}/{filename}"}
decoy = {"sha256": "0" * 64, "url": f"{base_url}/wrong-target.tar.gz"}
target = current_target()
data = {"packages": {
"before": {"artifacts": {target: decoy}, "version": "wrong-before"},
"python": {"artifacts": {target: decoy}, "version": f"{py_version}.7+fixture"},
"uv": {"artifacts": {"before-target": decoy, target: artifact, "after-target": decoy},
"version": uv_version},
"after": {"artifacts": {target: decoy}, "version": "wrong-after"},
}}
content = json.dumps(data, indent=indent)
if blank_lines:
content = content.replace("\n", "\n \t\n")
(core / "pm" / "lock.json").write_text(content + "\n", encoding="utf-8")
(core / "pm" / "artifact-mirror.json").write_text(
json.dumps({"origin": base_url, "prefix": "mirror/"}, indent=2), encoding="utf-8",
)
env = {"PATH": os.environ["PATH"], "HOME": str(home),
"HERMES_HOME": str(home / ".hermes"), "HERMES_RUNTIME_DIR": str(runtime),
"PYTHONNOUSERSITE": "1"}
result = subprocess.run(
[bash, str(core / "setup-hermes.sh"), "--runtime-only"], cwd=tmp_path,
env=env, capture_output=True, text=True, timeout=30,
)
assert result.returncode == 0, result.stdout + result.stderr
assert (runtime / f"uv-{uv_version}-{target}" / "uv").read_text() == uv_script
assert json.loads(receipt.read_text()) == ["install"]
assert calls.read_text().splitlines() == [
"--version", f"python install --no-bin {py_version}",
f"python find --managed-python {py_version}",
]
assert not (home / ".local").exists()
assert not (core / ".env").exists()
assert not (home / ".hermes" / "skills").exists()
print(f"runtime-only bootstrap: indent={indent!r}, blank_lines={blank_lines}: exit {result.returncode}")
print(result.stdout)

View File

@@ -5,8 +5,8 @@ import importlib
import os
from pathlib import Path
import subprocess
import venv
import shutil
import sys
import pytest
@@ -18,9 +18,12 @@ from tests.pm._range_server import RangeHandler, dl_server, url # noqa: F401
@pytest.fixture(scope="module")
def isolated_python(tmp_path_factory):
from pm.runtime_stage import stage_runtime
root = tmp_path_factory.mktemp("pm-python")
venv.EnvBuilder(with_pip=False).create(root)
python = root / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
uv = shutil.which("uv")
assert uv, "the worker contract requires real uv"
python = stage_runtime(Path(uv), Path(sys.executable), root)
probe = subprocess.run(
[str(python), "-I", "-c", "import importlib.util; assert importlib.util.find_spec('yaml') is None"],
capture_output=True, text=True, timeout=30,

View File

@@ -10,9 +10,9 @@ import os
from pathlib import Path
import subprocess
import sys
import shutil
import tarfile
import textwrap
import venv
import pytest
@@ -28,9 +28,12 @@ def restore_registry(monkeypatch):
@pytest.fixture(scope="module")
def worker_python(tmp_path_factory):
from pm.runtime_stage import stage_runtime
environment = tmp_path_factory.mktemp("registry-worker-python")
venv.EnvBuilder(with_pip=False).create(environment)
return environment / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
uv = shutil.which("uv")
assert uv, "the worker contract requires real uv"
return stage_runtime(Path(uv), Path(sys.executable), environment)
@pytest.mark.parametrize("operation", ["ensure", "stage_only"])

View File

@@ -177,19 +177,26 @@ installation work: shell configuration, launchers, `.env`, and bundled skills.
Run the setup script separately if you want that full installation workflow.
The bootstrap uses uv to install and locate Python, then waits for uv to exit.
PM prepares its own small, locked Python environment before reading plugin
configuration or resolving application dependencies. Its project is deliberately
PM uses the staged uv to prepare its own small, locked Python environment
before downloading its managed tools, reading plugin configuration, or resolving
application dependencies. Its project is deliberately
independent of the application workspace: a broken application dependency must
not prevent its dependency manager from starting. Each uv subprocess exits before
PM runs, so it cannot hold the uv executable that PM needs to replace.
PM's runtime contains `ruamel.yaml`, `packaging`, and `tomli-w`, not the application
PM's runtime contains `ruamel.yaml`, `packaging`, `tomli-w`, and `truststore`, not the application
dependency tree. CLI commands and application-requested installs and repairs run
there; read-only path and installed-environment lookups remain local. PM never
adds its dependencies to an already-running agent's imports. First-party YAML
readers and writers use ruamel; third-party packages can still require PyYAML in
the application environment. Failure receipts remain stdlib-only.
PM's CLI and worker activate `truststore` before importing their HTTPS clients.
This uses the platform certificate store even when bootstrap Python's compiled-in
OpenSSL paths do not locate it. No application dependencies or certificate-path
override are required. After the first install, PM rebuilds its small environment
against the managed Python on the next invocation; subsequent invocations reuse it.
When lazy installs are disabled, an existing PM runtime can still check whether
the application environment is current. If PM itself is missing or outdated,
the request fails without downloading tools or dependencies. Run an explicit