atlas-iac/testing/tests/test_hermes_chat_images.py
Hermes Agent 03b10ff8b1 Merge PR 15 (CLI lane decomposition) on top of PR 14 (Atlas SCM broker)
The distributed worker pool stacks on exactly two open pull requests, in this
order: PR 14 supplies the broker-only SCM boundary the mediators route through
and the hermes-scm-boundary-v2 ConfigMap they mount, and PR 15 supplies the
cli_lane_* decomposition -- including canonical_run_id and the eligibility
predicate on claim_ready -- that the coordinator depends on. Neither can be
dropped without breaking a fixed P0 boundary, so both are carried here as
prerequisites and this branch must not merge before them.

PR 16 (agent image release lane) and PR 19 (full-handoff acceptance harness)
are NOT prerequisites and are deliberately absent, so reviewing this branch no
longer means approving them.

PR 14 and PR 15 conflict with each other in nine paths. Each is resolved to the
resolution already reviewed on this branch at 4d4cf1bd.
2026-08-17 16:29:29 +00:00

283 lines
11 KiB
Python

"""Image generation and sandbox-boundary contracts for Hermes chat."""
from __future__ import annotations
import importlib.util
import sys
import time
from types import SimpleNamespace
import yaml
from testing.tests.test_hermes_chat_support import (
HERMES,
VAULT,
_documents,
)
def test_chat_image_generation_uses_private_owner_broker():
"""Family pods get image bytes without receiving the owner's OAuth file."""
configmap = _documents(HERMES / "chat-configmap.yaml")[0]
assert "shared desktop/Wolf lane" in configmap["data"]["SOUL.md"]
assert "local FLUX waits" in configmap["data"]["SOUL.md"]
assert "Use `image_generate_local`" in configmap["data"]["SOUL.md"]
assert "Use `image_generate_hosted`" in configmap["data"]["SOUL.md"]
assert "ComfyUI endpoint" in configmap["data"]["SOUL.md"]
assert "`MEDIA:` path" in configmap["data"]["SOUL.md"]
assert "`image_edit_latest`" in configmap["data"]["SOUL.md"]
assert (
"Preserve the most recently selected image lane" in configmap["data"]["SOUL.md"]
)
config = yaml.safe_load(configmap["data"]["config.yaml"])
assert config["image_gen"] == {
"provider": "atlas-broker",
"model": "atlas-image-auto-high",
}
assert config["plugins"]["enabled"] == ["atlas-broker", "auto-router"]
statefulset = _documents(HERMES / "chat-statefulset.yaml")[0]
pod = statefulset["spec"]["template"]["spec"]
hermes = next(item for item in pod["containers"] if item["name"] == "hermes")
mounts = {item["name"]: item for item in hermes["volumeMounts"]}
assert mounts["image-plugin"]["mountPath"] == (
"/opt/hermes/plugins/image_gen/atlas-broker"
)
assert mounts["runtime-access"]["mountPath"] == "/runtime-access"
assert "provider-auth" not in mounts
assert not any(
mount["name"] == "home" and "agent" in str(mount)
for mount in hermes["volumeMounts"]
)
env = {item["name"]: item["value"] for item in hermes["env"]}
assert env["HERMES_IMAGE_BROKER_URL"].startswith("http://hermes-image-broker.")
plugin = (HERMES / "plugins" / "image-gen-broker" / "__init__.py").read_text()
assert '"local": "flux-2-klein-4b-local"' in plugin
assert '"hosted": "gpt-image-2-high"' in plugin
assert 'name="image_generate_local"' in plugin
assert 'name="image_generate_hosted"' in plugin
assert '"name": "image_edit_latest"' in plugin
assert '"name": "image_edit_latest_local"' in plugin
assert '"name": "image_edit_latest_hosted"' in plugin
assert "def _latest_generated_image" in plugin
assert "newest MEDIA: path from the conversation" in plugin
assert 'candidate.upper().startswith("MEDIA:")' in plugin
assert "override=True" not in plugin
agent = _documents(HERMES / "agent-deployment.yaml")[0]
containers = agent["spec"]["template"]["spec"]["containers"]
broker = next(item for item in containers if item["name"] == "image-broker")
assert broker["ports"] == [
{"name": "image-broker", "containerPort": 9002, "protocol": "TCP"}
]
assert broker["securityContext"]["readOnlyRootFilesystem"] is True
assert broker["securityContext"]["runAsNonRoot"] is True
services = _documents(HERMES / "service.yaml")
service = next(
item for item in services if item["metadata"]["name"] == "hermes-image-broker"
)
assert service["spec"]["selector"] == {"app": "hermes-agent"}
oauth_store = _documents(HERMES / "oauth-session-store.yaml")
redis = next(item for item in oauth_store if item["kind"] == "Deployment")
assert redis["spec"]["strategy"]["type"] == "Recreate"
assert "--appendonly" in redis["spec"]["template"]["spec"]["containers"][0]["args"]
policies = _documents(HERMES / "networkpolicy.yaml")
agent_policy = next(
item
for item in policies
if item["metadata"]["name"] == "hermes-agent-isolation"
)
broker_ingress = next(
rule
for rule in agent_policy["spec"]["ingress"]
if {port["port"] for port in rule["ports"]} == {9002, 9003}
)
assert broker_ingress["from"][0]["podSelector"]["matchLabels"] == {
"app": "hermes-chat-tenant"
}
vault_policy = (VAULT / "scripts" / "vault_k8s_auth_configure.sh").read_text()
agent_role = vault_policy[
vault_policy.index('write_policy_and_role "hermes-agent"') : vault_policy.index(
"write_policy_and_role",
vault_policy.index('write_policy_and_role "hermes-agent"') + 1,
)
]
assert "hermes/developer-gitea" not in agent_role
assert 'write_policy_and_role "hermes-scm-broker" "hermes-scm"' in vault_policy
assert (
'write_policy_and_role "hermes-node-ssh" "hermes" '
'"hermes-node-ssh-access"' in vault_policy
)
def test_compact_image_edit_resolves_latest_tenant_artifact(tmp_path, monkeypatch):
"""Follow-up edits resolve the source server-side and keep tool JSON small."""
provider_module = SimpleNamespace(
DEFAULT_ASPECT_RATIO="square",
ImageGenProvider=object,
error_response=lambda **value: value,
normalize_reference_images=lambda value: value,
resolve_aspect_ratio=lambda value: value,
save_b64_image=lambda *_args, **_kwargs: tmp_path / "saved.png",
success_response=lambda **value: value,
)
monkeypatch.setitem(sys.modules, "agent", SimpleNamespace())
monkeypatch.setitem(sys.modules, "agent.image_gen_provider", provider_module)
spec = importlib.util.spec_from_file_location(
"hermes_image_plugin",
HERMES / "plugins" / "image-gen-broker" / "__init__.py",
)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
image_dir = tmp_path / "cache" / "images"
image_dir.mkdir(parents=True)
older = image_dir / "atlas_flux-old.png"
newest = image_dir / "atlas_gpt-image-new.png"
older.write_bytes(b"older")
newest.write_bytes(b"newest")
older.touch()
time.sleep(0.001)
newest.touch()
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
calls = []
monkeypatch.setattr(
module,
"_handle_image_generate",
lambda args, route: calls.append((args, route)) or "ok",
)
assert module._handle_hosted_edit({"prompt": "make it a clown"}) == "ok"
assert calls == [
(
{
"prompt": "make it a clown",
"image_url": str(newest.resolve()),
},
"hosted",
)
]
def test_chat_reasoning_uses_switchyard_without_owner_credentials():
"""Family pods use AUTO/manual routes without mounting owner credentials."""
configmap = _documents(HERMES / "chat-configmap.yaml")[0]
config = yaml.safe_load(configmap["data"]["config.yaml"])
assert config["model"] == {
"provider": "atlas-switchyard",
"default": "atlas/auto/fast",
"model": "atlas/auto/fast",
}
assert config["providers"]["atlas-switchyard"] == {
"name": "Automatic Router",
"api": "http://hermes-switchyard.hermes.svc.cluster.local:9005/v1",
"api_key": "atlas-switchyard",
"default_model": "atlas/auto/fast",
"transport": "chat_completions",
}
assert config["platforms"]["api_server"]["extra"]["model_routes"] == {
route: {"provider": "atlas-switchyard", "model": route}
for route in [
"atlas/auto/fast",
"atlas/auto/balanced",
"atlas/auto/deep",
"atlas/auto/maximum",
"atlas/manual/codex/luna",
"atlas/manual/codex/terra",
"atlas/manual/codex/sol",
"atlas/manual/claude/haiku",
"atlas/manual/claude/fable",
"atlas/manual/claude/sonnet",
"atlas/manual/claude/opus",
"atlas/manual/local/qwen-14b",
]
}
agent = _documents(HERMES / "agent-deployment.yaml")[0]
containers = agent["spec"]["template"]["spec"]["containers"]
broker = next(item for item in containers if item["name"] == "codex-broker")
assert broker["ports"] == [
{"name": "codex-broker", "containerPort": 9003, "protocol": "TCP"}
]
assert broker["securityContext"]["readOnlyRootFilesystem"] is True
assert broker["securityContext"]["runAsNonRoot"] is True
assert {item["name"]: item["value"] for item in broker["env"]}.items() >= {
"PYTHONPATH": "/opt/hermes",
"HERMES_CODEX_BROKER_LISTEN_PORT": "9003",
"HERMES_ROUTING_CATALOG_PATH": "/routing-catalog/catalog.json",
}.items()
services = _documents(HERMES / "service.yaml")
service = next(
item for item in services if item["metadata"]["name"] == "hermes-codex-broker"
)
assert service["spec"]["selector"] == {"app": "hermes-agent"}
assert service["spec"]["ports"] == [
{
"name": "http",
"port": 9003,
"targetPort": "codex-broker",
"protocol": "TCP",
}
]
statefulset = _documents(HERMES / "chat-statefulset.yaml")[0]
assert (
statefulset["spec"]["template"]["metadata"]["annotations"][
"ai.bstein.dev/config-rev"
]
== "20260816-telegram-topics"
)
pod_spec = statefulset["spec"]["template"]["spec"]
patch_init = next(
item
for item in pod_spec["initContainers"]
if item["name"] == "patch-stream-recovery"
)
assert patch_init["command"][-2:] == [
"/opt/hermes/agent/conversation_loop.py",
"/patched/conversation_loop.py",
]
hermes = next(item for item in pod_spec["containers"] if item["name"] == "hermes")
assert {
"name": "stream-recovery-patch",
"mountPath": "/opt/hermes/agent/conversation_loop.py",
"subPath": "conversation_loop.py",
} in hermes["volumeMounts"]
api_session_init = next(
item
for item in pod_spec["initContainers"]
if item["name"] == "patch-api-server-sessions"
)
assert "patch_api_server_sessions.py" in api_session_init["args"][0]
assert "migrate_telegram_api_sessions.py" in api_session_init["args"][0]
assert {
"name": "api-server-patch",
"mountPath": "/opt/hermes/gateway/platforms/api_server.py",
"subPath": "api_server.py",
} in hermes["volumeMounts"]
assert any(volume["name"] == "api-server-patch" for volume in pod_spec["volumes"])
assert not any(
mount["mountPath"].endswith("/.codex") for mount in hermes["volumeMounts"]
)
policies = _documents(HERMES / "networkpolicy.yaml")
agent_policy = next(
item
for item in policies
if item["metadata"]["name"] == "hermes-agent-isolation"
)
broker_ingress = next(
rule
for rule in agent_policy["spec"]["ingress"]
if {port["port"] for port in rule["ports"]} == {9002, 9003}
)
assert broker_ingress["from"][0]["podSelector"]["matchLabels"] == {
"app": "hermes-chat-tenant"
}