Files
hermes-agent/tests/tools/test_dockerfile_immutable_install.py
ethernet 3d12e86ef1 feat(pm): unified package manager — pm store foundation
Introduce the pm store: a unified, hash-verified package store that
replaces lazy_deps and the old installer's ad-hoc tool downloads.
Store tools are provisioned on PATH (ffmpeg, node/npm via pinned uv),
with a resumable 8-way downloader, verify() returning failure reasons,
and adopt() made EPERM-safe. chromium ships in the payload for every
target. The 3600-line install.sh is replaced by a staged bootstrapper
(heavy deps are pm's job after this); setup-hermes.sh, Dockerfile and
nix pin tables are rewired onto the store. Old install-script tests,
lazy_deps/managed_uv/build_info, and the ps1/bash installer test
batteries are removed with the machinery they tested.

Rebuilt from ethie/pm onto upstream/main (ac6c8028e0) after the
utf-8-sig sweep. 16 hot files (main also churned them) hand-merged:
platform adapters, main.py, electron/main.ts, tui_gateway/server.py,
cua_backend, installer-tests workflow, install.sh (full rewrite),
setup-hermes.sh, plugins doc.
2026-08-31 18:00:48 -04:00

94 lines
3.8 KiB
Python

"""Contract tests for the Docker image's immutable /opt/hermes install tree."""
from __future__ import annotations
import re
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
DOCKERFILE = REPO_ROOT / "Dockerfile"
def _dockerfile_text() -> str:
return DOCKERFILE.read_text()
def test_dockerfile_makes_opt_hermes_readonly_for_hermes_user() -> None:
text = _dockerfile_text()
# --chmod on the source COPY bakes read-only perms at copy time instead
# of a separate chmod -R pass (which walked ~30k files — #49113).
assert "COPY --link --chmod=a+rX,go-w . ." in text
# The old tree-walking passes must not be present.
assert "chown -R root:root /opt/hermes" not in text
assert "chmod -R a+rX /opt/hermes" not in text
assert "chmod -R a-w /opt/hermes" not in text
def test_dockerfile_does_not_chown_install_trees_to_hermes() -> None:
text = _dockerfile_text()
forbidden_patterns = (
r"chown\s+-R\s+hermes:hermes\s+/opt/hermes/\.venv",
r"chown\s+-R\s+hermes:hermes\s+/opt/hermes/ui-tui",
r"chown\s+-R\s+hermes:hermes\s+/opt/hermes/gateway",
r"chown\s+-R\s+hermes:hermes\s+/opt/hermes/node_modules",
)
for pattern in forbidden_patterns:
assert not re.search(pattern, text), (
"runtime install trees under /opt/hermes must stay immutable; "
f"found forbidden pattern {pattern!r}"
)
def test_dockerfile_bakes_code_scoped_install_method_stamp() -> None:
"""The 'docker' install-method stamp is baked next to the code.
detect_install_method() reads the code-scoped stamp
(/opt/hermes/.install_method) first; baking it at build time keeps the
published image self-identifying as 'docker' WITHOUT writing into the
shared $HERMES_HOME data volume (which a host install may also use).
The stamp is created by root in the shim-wiring RUN block; the hermes
user can't modify it (go-w from the --chmod on the source COPY).
"""
text = _dockerfile_text()
assert "printf 'docker\\n' > /opt/hermes/.install_method" in text
# The stamp must be in the RUN block that wires the exec shim.
shim_block = re.search(
r"RUN mkdir -p /opt/hermes/bin && \\\n"
r"(?:.*\\\n)+?"
r"\s+printf 'docker\\n' > /opt/hermes/\.install_method",
text,
)
assert shim_block, "install-method stamp must be in the shim-wiring RUN block"
def test_dockerfile_disables_lazy_installs() -> None:
"""The published image fully disables runtime lazy installs so nothing
can mutate the sealed venv (the old durable-target redirect machinery is
gone — no code reads HERMES_LAZY_INSTALL_TARGET anymore).
"""
assert "ENV HERMES_DISABLE_LAZY_INSTALLS=1" in _dockerfile_text()
assert "HERMES_LAZY_INSTALL_TARGET" not in _dockerfile_text()
def test_dockerfile_bakes_photon_sidecar_deps() -> None:
"""The Photon sidecar's node_modules must be baked at build time (NS-606).
The install tree is immutable at runtime, so a lazy `npm ci` on first
connect would hit EROFS. Baking the deps (from the committed lockfile,
which also runs the spectrum-ts postinstall patch) makes the hosted
happy path install-free. Guards the contract between the Dockerfile
and plugins/platforms/photon/sidecar_paths.resolve_sidecar_dir, which
runs in place only when the baked deps exist and match the lockfile.
"""
text = _dockerfile_text()
assert "plugins/platforms/photon/sidecar/package-lock.json" in text
assert re.search(
r"RUN cd plugins/platforms/photon/sidecar && \\\n\s+npm ci", text
), "sidecar deps must be installed with `npm ci` (deterministic, runs postinstall patch)"
# Immutability contract: never chown the sidecar tree to the runtime user.
assert not re.search(
r"chown\s+-R\s+hermes:hermes\s+/opt/hermes/plugins", text
)