worker.bstein.dev (the hermes-agent Deployment) gains the same HUX shape as chat, staged and inert: a foundation-only hux sidecar on the reviewed WebUI image line (Flux setters bound, 5s probe budgets), an init that provisions the HMAC identity as slot-100 on the durable home subtree (create-once context key, O_EXCL subject binding, per-pod worker key; no relay/router/evidence keys so those trusts fail closed), and observe-only hook env in the agent container with the runtime plugin mounted but deliberately NOT enabled - activation is a reviewed one-line flip per docs/hux/WORKER-PLAN.md, which carries the rollout, verification gates, canary/rollback ladder and open questions. Cross-surface continuity remains unclaimed until the live gates pass. 7 new topology-adaptive delivery gates green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
276 lines
11 KiB
Python
276 lines
11 KiB
Python
"""Flux delivery gates for the Worker (hermes-agent) HUX service boundary.
|
|
|
|
Topology-adaptive like the chat gates in ``test_hermes_hux_delivery.py``:
|
|
while the agent Deployment carries no ``hux`` sidecar the manifests must
|
|
contain zero partial Worker HUX wiring; once the sidecar exists, the full
|
|
Worker boundary below is enforced strictly. The Worker instance is the
|
|
``hermes-agent`` Deployment behind worker.bstein.dev (via the
|
|
``oauth2-proxy-hermes-agent`` Service), NOT the dashboard ``hermes``
|
|
Deployment. See ``docs/hux/WORKER-PLAN.md``.
|
|
"""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SERVICE = ROOT / "services/hermes"
|
|
AGENT = SERVICE / "agent-deployment.yaml"
|
|
AGENT_CONFIG = SERVICE / "agent-configmap.yaml"
|
|
CHAT = SERVICE / "chat-statefulset.yaml"
|
|
KUSTOMIZATION = SERVICE / "kustomization.yaml"
|
|
WEBUI_IMAGE = "registry.bstein.dev/bstein/hermes-webui"
|
|
WEBUI_MARKER = '"$imagepolicy": "hermes:hermes-webui-release"'
|
|
TAG_MARKER = '"$imagepolicy": "hermes:hermes-webui-release:tag"'
|
|
DIGEST_MARKER = '"$imagepolicy": "hermes:hermes-webui-release:digest"'
|
|
WORKER_SLOT = "slot-100"
|
|
|
|
|
|
def _deployment() -> dict:
|
|
return yaml.safe_load(AGENT.read_text(encoding="utf-8"))
|
|
|
|
|
|
def _pod() -> dict:
|
|
return _deployment()["spec"]["template"]["spec"]
|
|
|
|
|
|
def _hux_active() -> bool:
|
|
return any(item["name"] == "hux" for item in _pod()["containers"])
|
|
|
|
|
|
def _require_activation() -> None:
|
|
if not _hux_active():
|
|
pytest.skip("Worker HUX activation topology is not staged in this tree")
|
|
|
|
|
|
def _named(items: list[dict], name: str) -> dict:
|
|
return next(item for item in items if item["name"] == name)
|
|
|
|
|
|
def _env(container: dict) -> dict[str, str]:
|
|
return {item["name"]: item.get("value", "") for item in container.get("env", [])}
|
|
|
|
|
|
def _mounts(container: dict) -> list[dict]:
|
|
return container.get("volumeMounts", [])
|
|
|
|
|
|
def test_worker_hux_topology_is_all_or_nothing() -> None:
|
|
"""A partial Worker HUX rollout (wiring without the sidecar) never ships."""
|
|
pod = _pod()
|
|
if _hux_active():
|
|
return
|
|
assert not any(item["name"].startswith("hux") for item in pod["containers"])
|
|
assert not any(
|
|
item["name"] == "init-hux-runtime" for item in pod.get("initContainers", [])
|
|
)
|
|
assert not any(item["name"].startswith("hux-") for item in pod["volumes"])
|
|
for container in pod["containers"]:
|
|
assert not any(name.startswith("HUX_") for name in _env(container))
|
|
assert WEBUI_IMAGE not in AGENT.read_text(encoding="utf-8")
|
|
|
|
|
|
def test_worker_hux_sidecar_is_loopback_only_on_the_reviewed_webui_image() -> None:
|
|
"""One reviewed WebUI artifact serves chat and Worker; loopback is the only path."""
|
|
_require_activation()
|
|
pod = _pod()
|
|
hux = _named(pod["containers"], "hux")
|
|
values = _env(hux)
|
|
raw = AGENT.read_text(encoding="utf-8")
|
|
|
|
assert hux["image"].startswith(WEBUI_IMAGE + ":")
|
|
assert "@sha256:" in hux["image"]
|
|
# Exactly one WebUI consumer in this Deployment, on the Flux-managed line.
|
|
assert raw.count(WEBUI_MARKER) == 1
|
|
marked_line = next(line for line in raw.splitlines() if WEBUI_MARKER in line)
|
|
assert hux["image"] in marked_line
|
|
# The same reviewed image line as the chat tenants: the repo-wide Flux
|
|
# Setters run (update.path services/hermes) rewrites both files together.
|
|
chat_pod = yaml.safe_load(CHAT.read_text(encoding="utf-8"))["spec"]["template"][
|
|
"spec"
|
|
]
|
|
assert _named(chat_pod["containers"], "webui")["image"] == hux["image"]
|
|
|
|
assert hux["command"] == ["/opt/hermes/.venv/bin/python", "-m", "hux.server"]
|
|
assert values["PYTHONPATH"] == "/opt/hermes-hux"
|
|
assert values["PYTHONDONTWRITEBYTECODE"] == "1"
|
|
assert values["HUX_BIND"] == "127.0.0.1"
|
|
assert values["HUX_PORT"] == "8790"
|
|
assert values["HUX_TENANT_SLOT"] == WORKER_SLOT
|
|
assert values["HUX_DATA_ROOT"] == "/var/lib/hux/store"
|
|
# Foundation-only start: widening the Worker flag set is a deliberate,
|
|
# reviewed change gated on the WORKER-PLAN lifecycle checks, so this pin
|
|
# is updated in the same commit that turns each card on.
|
|
assert {flag for flag in values["HUX_FLAGS"].split(",") if flag} == {
|
|
"hux.foundation"
|
|
}
|
|
assert hux["securityContext"]["readOnlyRootFilesystem"] is True
|
|
assert hux["securityContext"]["capabilities"]["drop"] == ["ALL"]
|
|
assert hux["securityContext"]["runAsUser"] == 10000
|
|
for probe in ("readinessProbe", "livenessProbe"):
|
|
assert "tcpSocket" not in hux[probe]
|
|
command = " ".join(hux[probe]["exec"]["command"])
|
|
assert "127.0.0.1" in command
|
|
assert "8790" in command
|
|
assert "/healthz" in command
|
|
# The chat canary's transient exit-137s were probe kills under a 2s
|
|
# budget; the Worker ships with the corrected 5s budget from day one.
|
|
assert hux[probe]["timeoutSeconds"] >= 5
|
|
# Loopback only: no Service, proxy upstream, or NetworkPolicy exposes 8790.
|
|
assert "8790" not in (SERVICE / "service.yaml").read_text(encoding="utf-8")
|
|
assert "8790" not in (SERVICE / "oauth2-proxy.yaml").read_text(encoding="utf-8")
|
|
assert "port: 8790" not in (SERVICE / "networkpolicy.yaml").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
|
|
|
|
def test_worker_hux_store_is_a_private_durable_subtree() -> None:
|
|
"""The Worker owns its own store subtree and never shares chat's claim."""
|
|
_require_activation()
|
|
pod = _pod()
|
|
hux = _named(pod["containers"], "hux")
|
|
hermes = _named(pod["containers"], "hermes")
|
|
|
|
# The store lives on the durable hermes-agent-home claim under the fixed
|
|
# "hux" subtree (Deployment pod names churn, so no $(POD_NAME) scoping).
|
|
assert {
|
|
"name": "home",
|
|
"mountPath": "/var/lib/hux",
|
|
"subPath": "hux",
|
|
} in _mounts(hux)
|
|
assert not any("subPathExpr" in item for item in _mounts(hux))
|
|
# The agent gets only its read-only key/context views of that subtree;
|
|
# ledger records are reachable from the agent only through loopback HTTP.
|
|
assert {
|
|
"name": "home",
|
|
"mountPath": "/run/hermes-hux-context",
|
|
"subPath": "hux/context",
|
|
"readOnly": True,
|
|
} in _mounts(hermes)
|
|
assert {
|
|
"name": "home",
|
|
"mountPath": "/run/hermes-hux-subject",
|
|
"subPath": "hux/binding",
|
|
"readOnly": True,
|
|
} in _mounts(hermes)
|
|
for container in pod["containers"]:
|
|
if container["name"] == "hux":
|
|
continue
|
|
assert not any(
|
|
item.get("mountPath") == "/var/lib/hux" for item in _mounts(container)
|
|
)
|
|
if container["name"] != "hermes":
|
|
assert not any(
|
|
item["name"].startswith("hux-") for item in _mounts(container)
|
|
)
|
|
volumes = {item["name"]: item for item in pod["volumes"]}
|
|
assert volumes["hux-worker-key"]["emptyDir"]["medium"] == "Memory"
|
|
# Single surface, single operator: no relay key and no evidence trust on
|
|
# the Worker instance — those trusts fail closed by absence.
|
|
assert "hux-relay-key" not in volumes
|
|
assert "hux-evidence-key" not in volumes
|
|
raw = AGENT.read_text(encoding="utf-8")
|
|
assert "hermes-chat-hux-data" not in raw
|
|
|
|
|
|
def test_worker_hux_init_provisions_durable_identity_and_rotating_key() -> None:
|
|
"""Identity survives pod replacement; the transport key rotates with it."""
|
|
_require_activation()
|
|
pod = _pod()
|
|
init = _named(pod["initContainers"], "init-hux-runtime")
|
|
script = init["args"][0]
|
|
|
|
assert 'worker_root="/hux-data/hux"' in script
|
|
assert "${HOSTNAME}" not in script
|
|
assert "$(POD_NAME)" not in script
|
|
assert 'HUX_INIT_SLOT="slot-100"' in script
|
|
assert "chown -R" not in script
|
|
assert 'if [ ! -e "${worker_root}/context/context-key" ]' in script
|
|
assert "bs=32 count=1" in script
|
|
assert 'chmod 0600 "${worker_root}/context/context-key"' in script
|
|
assert 'b"hux.subject.id.v1\\0" + slot.encode("ascii")' in script
|
|
assert 'target = root / "binding/subject"' in script
|
|
assert "os.O_EXCL" in script
|
|
assert "or stat.S_IMODE(info.st_mode) != 0o440" in script
|
|
assert "target=/hux-worker/worker-key" in script
|
|
assert "chmod 0400" in script
|
|
assert "relay" not in script
|
|
assert init["securityContext"]["capabilities"] == {
|
|
"drop": ["ALL"],
|
|
"add": ["CHOWN", "DAC_OVERRIDE", "FOWNER"],
|
|
}
|
|
assert {"name": "home", "mountPath": "/hux-data"} in _mounts(init)
|
|
assert {"name": "hux-worker-key", "mountPath": "/hux-worker"} in _mounts(init)
|
|
|
|
|
|
def test_worker_hux_hook_env_is_file_backed_and_observe_only() -> None:
|
|
"""The agent hook is wired for observation: file-backed keys, enforcement 0."""
|
|
_require_activation()
|
|
pod = _pod()
|
|
hermes = _env(_named(pod["containers"], "hermes"))
|
|
|
|
assert hermes["HUX_BASE_URL"] == "http://127.0.0.1:8790"
|
|
assert hermes["HUX_RUNTIME_ENABLED"] == "1"
|
|
# Observe-only pin: flipping enforcement on the Worker is its own gated
|
|
# change (approvals need a human surface this instance does not have yet).
|
|
assert hermes["HUX_TOOL_ENFORCEMENT"] == "0"
|
|
assert hermes["HUX_TENANT_SLOT"] == WORKER_SLOT
|
|
assert hermes["HUX_WORKER_KEY_FILE"] == "/run/hermes-hux-worker/worker-key"
|
|
assert hermes["HUX_SUBJECT_FILE"] == "/run/hermes-hux-subject/subject"
|
|
assert hermes["HUX_CONTEXT_KEY_FILE"] == "/run/hermes-hux-context/context-key"
|
|
assert hermes["HUX_TIMEOUT_SECONDS"] == "3"
|
|
forbidden = {"HUX_WORKER_KEY", "HUX_RELAY_KEY", "HUX_ROUTER_KEY", "HUX_SUBJECT"}
|
|
for container in pod["containers"]:
|
|
assert not forbidden & set(_env(container))
|
|
|
|
|
|
def test_worker_hux_runtime_plugin_is_staged_but_inert() -> None:
|
|
"""The vendored hook package is mounted, yet nothing activates it."""
|
|
_require_activation()
|
|
pod = _pod()
|
|
hermes = _named(pod["containers"], "hermes")
|
|
plugin = _named(pod["volumes"], "hux-runtime-plugin")["configMap"]
|
|
items = {item["key"]: item["path"] for item in plugin["items"]}
|
|
|
|
assert plugin["name"] == "hermes-hux-runtime-plugin"
|
|
assert {
|
|
"hux-hook-init.py": "hux_hook/__init__.py",
|
|
"hux-hook-client.py": "hux_hook/client.py",
|
|
"hux-hook-hooks.py": "hux_hook/hooks.py",
|
|
}.items() <= items.items()
|
|
assert {
|
|
"name": "hux-runtime-plugin",
|
|
"mountPath": "/opt/data/plugins/hux-runtime",
|
|
"readOnly": True,
|
|
} in _mounts(hermes)
|
|
rendered = KUSTOMIZATION.read_text(encoding="utf-8")
|
|
assert "hux-hook-client.py=plugins/hux-runtime/hux_hook/client.py" in rendered
|
|
# Staged, not active: the activation commit adds hux-runtime to
|
|
# plugins.enabled (docs/hux/WORKER-PLAN.md); until then the wiring is inert.
|
|
configmap = yaml.safe_load(AGENT_CONFIG.read_text(encoding="utf-8"))
|
|
config = yaml.safe_load(configmap["data"]["config.yaml"])
|
|
assert "hux-runtime" not in config["plugins"]["enabled"]
|
|
|
|
|
|
def test_worker_hux_build_bindings_use_flux_setters() -> None:
|
|
"""Sidecar build provenance is bound by the same Flux setters as chat."""
|
|
_require_activation()
|
|
raw = AGENT.read_text(encoding="utf-8")
|
|
hux = _env(_named(_pod()["containers"], "hux"))
|
|
|
|
assert raw.count(TAG_MARKER) == 1
|
|
assert raw.count(DIGEST_MARKER) == 1
|
|
release = re.fullmatch(
|
|
r"git-([0-9a-f]{40})-build-[1-9][0-9]*-release", hux["HUX_IMAGE_TAG"]
|
|
)
|
|
assert release is not None
|
|
assert hux["HUX_IMAGE_DIGEST"].startswith("sha256:")
|
|
image = _named(_pod()["containers"], "hux")["image"]
|
|
assert image.split(":git-", 1)[1].split("@", 1)[0] == hux[
|
|
"HUX_IMAGE_TAG"
|
|
].removeprefix("git-")
|
|
assert image.endswith("@" + hux["HUX_IMAGE_DIGEST"])
|