Files
hermes-agent/tests/install/e2e-assets/source_driver.py
ethernet 6a5a6a05d2 refactor(pm): rename the pm.ensure submodule to pm.install; lazy_deps back to the shim
`import pm.ensure` bound the submodule onto the package, shadowing the facade's
`pm.ensure()` function for every later caller in the process (photon's sidecar
start hit `'module' object is not callable`). The module is pm.install now; the
function keeps its name. The facade resolves through `__import__` rather than
`importlib.import_module` so a test that patches import_module globally does not
break attribute access on pm.

tools/lazy_deps.py returns to the 16-line stop_for_relaunch shim the branch wrote
(an origin/main merge had replaced it with main's 775-line implementation); the
project-metadata tests follow. update_cmd re-exports the four old_updater_deps
names the shim tests resolve through hermes_cli.update_cmd.
2026-09-18 23:27:05 -04:00

105 lines
5.0 KiB
Python

"""Read-only source-install acceptance, before a CLI launch can repair it.
The driver owns this file; probes import only the installed tree's passive
readers. No bootstrap, installer, build, PM worker, or application entry point.
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import subprocess
import sys
def desktop_outputs(root: Path) -> list[Path]:
desktop = root / "apps/desktop"
return [path for pattern in (
"release/*/resources/app.asar.unpacked/dist",
"release/mac*/Hermes.app/Contents/Resources/app.asar.unpacked/dist",
) for path in desktop.glob(pattern)]
def verify_products(root: Path, desktop: str, node: Path | None = None) -> None:
app = root / "apps/desktop"
outputs = desktop_outputs(root)
has_desktop = (app / "dist/index.html").exists() or any((app / "release").glob("*"))
if desktop == "absent" and has_desktop:
raise RuntimeError("unexpected desktop output in a no-desktop scenario")
if desktop == "present":
if not outputs or not any((out / "index.html").is_file() for out in outputs):
raise RuntimeError("desktop packaged renderer is missing or incomplete")
executables = [path for pattern in (
"release/*/Hermes.exe", "release/*/Hermes", "release/*/hermes",
"release/mac*/Hermes.app/Contents/MacOS/Hermes",
) for path in app.glob(pattern) if path.is_file() and path.stat().st_size]
if not executables:
raise RuntimeError("desktop executable is missing or empty")
if node is None:
return # Historical builds have no compiler receipt contract.
products = [("tui", root / "ui-tui/dist"), ("web", root / "hermes_cli/web_dist")]
if desktop == "present":
products.extend(("desktop", out) for out in outputs)
for product, out in products:
result = subprocess.run(
[str(node), str(root / "scripts/build/freshness.mjs"),
"--source", str(root), "--product", product, "--out", str(out)],
cwd=root, capture_output=True, text=True, check=True, timeout=120,
)
if result.stdout.strip() != "true":
raise RuntimeError(f"{product} output is missing, stale, or damaged: {out}")
def probe_pm(root: Path, desktop: str, command: list[str]) -> None:
# Use the launcher's Python ABI, but deliberately do NOT execute its
# bootstrap: that would complete dependencies or recover markers for it.
sys.path.insert(0, str(root))
from hermes_cli._launchers import runtime_command
from pm.environments import selected_venv, site_packages
if command != runtime_command(root):
raise RuntimeError("published launcher belongs to another installation or Python")
if any((root / marker).exists() for marker in (".update-incomplete", ".lazy-refresh-incomplete")):
raise RuntimeError("source update left an incomplete marker; refusing launch-time recovery")
sys.path.insert(1, str(site_packages(selected_venv(root))))
from pm.install import installed_package, venv_is_current
if not venv_is_current(project_root=root):
raise RuntimeError("installed dependency generation is not current")
node = installed_package("node")
if node is None or node.binary is None:
raise RuntimeError("installed Node is missing or does not match the target pin")
verify_products(root, desktop, node.binary)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--launcher", type=Path, required=True)
parser.add_argument("--desktop", choices=("absent", "present"), required=True)
parser.add_argument("--runtime-command", help=argparse.SUPPRESS)
args = parser.parse_args()
root = args.root.resolve()
if args.runtime_command:
probe_pm(root, args.desktop, json.loads(args.runtime_command))
elif (root / "pm/lock.json").is_file():
env = dict(os.environ, PYTHONDONTWRITEBYTECODE="1", HERMES_DISABLE_LAZY_INSTALLS="1")
result = subprocess.run([str(args.launcher), "--print-runtime-command"],
capture_output=True, text=True, check=True, env=env, timeout=30)
command = json.loads(result.stdout)
if not isinstance(command, list) or not command or not all(isinstance(s, str) for s in command):
raise RuntimeError("invalid published runtime command")
subprocess.run([command[0], "-I", "-B", str(Path(__file__).resolve()),
"--root", str(root), "--launcher", str(args.launcher),
"--desktop", args.desktop, "--runtime-command", json.dumps(command)],
env=env, check=True, timeout=300)
else:
verify_products(root, args.desktop)
print("Historical install: artifact presence only (no PM/compiler receipt capability)")
print(f"Read-only source verification passed: {root} (desktop={args.desktop})")
if __name__ == "__main__":
main()