"""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", } # atlas-broker and auto-router lead; hux-runtime and cluster-read were # added by the HUX rollout and read-only cluster visibility. assert config["plugins"]["enabled"][:2] == ["atlas-broker", "auto-router"] assert set(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"] if "value" in item} 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/auto", "atlas/manual/codex/luna", "atlas/manual/codex/terra", "atlas/manual/codex/sol", "atlas/manual/claude/auto", "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" ] == "20260913-provider-model-catalog-v2" ) 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" }