atlas-iac/testing/tests/test_hermes_image_automation.py
jenkins 13359769dc ci(hermes): make HUX topology gates all-or-nothing adaptive
The delivery and image-automation gates now enforce whichever state the
chat StatefulSet is actually in: with no hux sidecar they require zero
partial HUX wiring (no containers, volumes, PVC, or HUX_* env); with the
sidecar staged they enforce the full strict boundary. This lets the
reviewed source chain merge and build before the activation topology
lands, without ever waiving an activated assertion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 04:17:02 -03:00

128 lines
5.3 KiB
Python

"""Contracts for automatic deployment of validated Hermes image releases."""
from __future__ import annotations
from pathlib import Path
import re
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-chat-router-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<build>[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")
router = (SERVICE / "chat-router.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"'
chat_object = yaml.safe_load(chat)
hux = next(
(
item
for item in chat_object["spec"]["template"]["spec"]["containers"]
if item["name"] == "hux"
),
None,
)
# Before activation the chat StatefulSet carries exactly one WebUI
# consumer and no HUX setters; after activation the HUX sidecar is the
# second consumer of the very same reviewed image line, with tag and
# digest setters binding its build metadata.
assert chat.count(webui_marker) == (2 if hux else 1)
assert dashboard.count(webui_marker) == 1
assert chat.count(
'"$imagepolicy": "hermes:hermes-webui-release:tag"'
) == (1 if hux else 0)
assert chat.count(
'"$imagepolicy": "hermes:hermes-webui-release:digest"'
) == (1 if hux else 0)
if hux:
hux_env = {
item["name"]: item["value"] for item in hux["env"] if "value" in item
}
release = re.fullmatch(
r"git-([0-9a-f]{40})-build-[1-9][0-9]*-release",
hux_env["HUX_IMAGE_TAG"],
)
assert release is not None
assert hux["image"].split(":git-", 1)[1].split("@", 1)[0] == hux_env[
"HUX_IMAGE_TAG"
].removeprefix("git-")
assert hux["image"].endswith("@" + hux_env["HUX_IMAGE_DIGEST"])
# 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
router_marker = '"$imagepolicy": "hermes:hermes-chat-router-release"'
assert router.count(router_marker) == 1
marked_line = next(line for line in router.splitlines() if router_marker in line)
assert "registry.bstein.dev/bstein/hermes-chat-router" in marked_line
assert "@sha256:" in marked_line