"""Contracts for automatic deployment of validated Hermes image releases.""" from __future__ import annotations from pathlib import Path import yaml ROOT = Path(__file__).resolve().parents[2] SERVICE = ROOT / "services/hermes" APPLICATIONS = ROOT / "clusters/atlas/flux-system/applications" def test_image_policies_observe_only_validated_release_tags() -> None: """Candidates remain invisible until Jenkins publishes the release suffix.""" documents = list( yaml.safe_load_all((SERVICE / "image.yaml").read_text(encoding="utf-8")) ) repositories = { item["metadata"]["name"]: item for item in documents if item["kind"] == "ImageRepository" } policies = { item["metadata"]["name"]: item for item in documents if item["kind"] == "ImagePolicy" } assert set(repositories) == { "hermes-agent-release", "hermes-webui-release", "hermes-stt-release", "hermes-tts-release", } assert set(policies) == set(repositories) for name, policy in policies.items(): assert policy["metadata"]["namespace"] == "hermes" assert policy["spec"]["imageRepositoryRef"]["name"] == name assert policy["spec"]["filterTags"] == { "pattern": ("^git-[0-9a-f]{40}-build-" "(?P[1-9][0-9]*)-release$"), "extract": "$build", } assert policy["spec"]["policy"] == {"numerical": {"order": "asc"}} assert policy["spec"]["digestReflectionPolicy"] == "Always" def test_flux_updates_only_the_reviewed_hermes_image_digests() -> None: """Flux persists selected digests to Git and rolls all matching workloads.""" service_kustomization = (SERVICE / "kustomization.yaml").read_text(encoding="utf-8") applications_kustomization = (APPLICATIONS / "kustomization.yaml").read_text( encoding="utf-8" ) automation = yaml.safe_load( (APPLICATIONS / "hermes/image-automation.yaml").read_text(encoding="utf-8") ) agent = (SERVICE / "kustomization.yaml").read_text(encoding="utf-8") chat = (SERVICE / "chat-statefulset.yaml").read_text(encoding="utf-8") dashboard = (SERVICE / "deployment.yaml").read_text(encoding="utf-8") voice = (SERVICE / "voice-deployment.yaml").read_text(encoding="utf-8") assert " - image.yaml" in service_kustomization assert " - hermes/image-automation.yaml" in applications_kustomization assert automation["spec"]["git"]["checkout"]["ref"]["branch"] == "main" assert automation["spec"]["git"]["push"]["branch"] == "main" assert automation["spec"]["update"] == { "strategy": "Setters", "path": "services/hermes", } assert agent.count('"$imagepolicy": "hermes:hermes-agent-release:digest"') == 1 webui_marker = '"$imagepolicy": "hermes:hermes-webui-release"' assert chat.count(webui_marker) == 1 assert dashboard.count(webui_marker) == 1 # A digest-only setter replaces the complete YAML scalar with ``sha256:...``. # Whole-image setters must retain the registry and repository in pod specs. for workload in (chat, dashboard): marked_line = next( line for line in workload.splitlines() if webui_marker in line ) assert "registry.bstein.dev/bstein/hermes-webui:" in marked_line assert "@sha256:" in marked_line for component in ("stt", "tts"): marker = f'"$imagepolicy": "hermes:hermes-{component}-release"' assert voice.count(marker) == 1 marked_line = next(line for line in voice.splitlines() if marker in line) assert f"registry.bstein.dev/bstein/hermes-jetson-{component}" in marked_line assert "@sha256:" in marked_line