"""Canonical icon, PWA, and fail-closed Hermes WebUI identity contracts.""" from __future__ import annotations import hashlib import importlib.util import json import os from pathlib import Path import re import shutil import struct import subprocess import sys from urllib.parse import urlsplit import pytest ROOT = Path(__file__).resolve().parents[2] DOCKERFILES = ROOT / "dockerfiles" FIXTURE = ROOT / "testing/fixtures/hermes-webui-0.52.181" AGENT_FIXTURE = ROOT / "testing/fixtures/hermes-agent" ATLAS_PATCHER = DOCKERFILES / "hermes-webui-atlas-patch.py" BRAND_PATCHER = DOCKERFILES / "hermes-webui-brand-patch.py" MANIFEST_PATCHER = DOCKERFILES / "hermes-webui-manifest-patch.py" RELEASE_PATCHER = DOCKERFILES / "hermes-webui-release-patch.py" SMOKE = DOCKERFILES / "hermes-webui-smoke.py" ASSETS = DOCKERFILES / "hermes-webui-assets" MANIFEST = DOCKERFILES / "hermes-webui-manifest.json" BRAND_CSS = DOCKERFILES / "hermes-webui-brand.css" EXPECTED_HASHES = { "hermes-agent.ico": "aefe65e6574e6f46d3382588f46c508e1b3f3b3c9ce3dec6d335403a5374add9", "hermes-agent-192.png": "0e4102cc715372058dd6ab55e9cde2567e46fee5ab6557b564cdd212ccd2616f", "hermes-agent-512.png": "6661e5ca0ecc690af213b9f85f961541e6d3d0e36946ede9d3c2c97dfbd3c23d", } def _load(path: Path, name: str): spec = importlib.util.spec_from_file_location(name, path) assert spec is not None and spec.loader is not None module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def _patched_fixture(tmp_path: Path) -> Path: target = tmp_path / "hermes-webui" agent_target = tmp_path / "hermes-agent" shutil.copytree(FIXTURE, target) shutil.copytree(AGENT_FIXTURE, agent_target) env = os.environ.copy() env["HERMES_WEBUI_PATCH_ROOT"] = str(target) env["HERMES_AGENT_PATCH_ROOT"] = str(agent_target) env["HERMES_WEBUI_MANIFEST_SOURCE"] = str(MANIFEST) for patcher in (ATLAS_PATCHER, BRAND_PATCHER, MANIFEST_PATCHER): subprocess.run( [sys.executable, str(patcher)], cwd=ROOT, env=env, check=True, capture_output=True, text=True, ) return target def _png_size(path: Path) -> tuple[int, int]: payload = path.read_bytes() assert payload.startswith(b"\x89PNG\r\n\x1a\n") assert payload[12:16] == b"IHDR" return struct.unpack(">II", payload[16:24]) def test_canonical_icon_provenance_format_and_pwa_derivatives() -> None: """The supplied persona is tracked exactly and only resized for PWA use.""" for name, expected in EXPECTED_HASHES.items(): payload = (ASSETS / name).read_bytes() assert hashlib.sha256(payload).hexdigest() == expected ico = (ASSETS / "hermes-agent.ico").read_bytes() reserved, image_type, count = struct.unpack_from(" None: """The app has both mandatory icon sizes and no remote or secret-bearing data.""" manifest = json.loads(MANIFEST.read_text(encoding="utf-8")) assert manifest["id"] == "./" assert manifest["name"] == "Hermes Chat" assert manifest["short_name"] == "Hermes" assert manifest["start_url"].startswith("./") assert manifest["scope"] == "./" assert manifest["display"] == "standalone" assert manifest["background_color"] == "#070A12" assert manifest["theme_color"] == "#0D1420" icons = manifest["icons"] assert {(icon["sizes"], icon["type"]) for icon in icons} == { ("192x192", "image/png"), ("512x512", "image/png"), } assert {icon["src"] for icon in icons} == { "static/hermes-agent-192.png", "static/hermes-agent-512.png", } assert all(icon["purpose"] == "any" for icon in icons) serialized = json.dumps(manifest).lower() assert "http:" not in serialized and "https:" not in serialized assert "secret" not in serialized and "token" not in serialized def test_production_patchers_apply_title_icons_theme_and_cache_contract( tmp_path: Path, ) -> None: """Exercise the shipped patchers against pinned upstream source fragments.""" target = _patched_fixture(tmp_path) index = (target / "static/index.html").read_text(encoding="utf-8") worker = (target / "static/sw.js").read_text(encoding="utf-8") assert index.count("Hermes Chat") == 1 assert index.count('id="hermesBrandStyles"') == 1 assert 'href="static/hermes-agent.ico"' in index assert 'sizes="192x192" href="static/hermes-agent-192.png"' in index assert 'sizes="512x512" href="static/hermes-agent-512.png"' in index assert '' in index assert 'Hermes Chat' in index assert '' in index assert ( 'Hermes Agent' ) in index assert 'aria-label="Hermes caduceus"' not in index assert "favicon.svg" not in index assert "favicon-32.png" not in index assert worker.count("'./static/hermes-brand.css' + VQ") == 1 for name in EXPECTED_HASHES: assert worker.count(f"'./static/{name}'") == 1 assert "favicon.svg" not in worker assert "favicon-32.png" not in worker def test_brand_patch_rejects_upstream_drift_before_partial_success( tmp_path: Path, ) -> None: """A changed pinned title/favicon context cannot silently ship partial branding.""" target = _patched_fixture(tmp_path) index = target / "static/index.html" index.write_text( index.read_text(encoding="utf-8").replace( "Hermes Chat", "Upstream changed", 1 ), encoding="utf-8", ) env = os.environ.copy() env["HERMES_WEBUI_PATCH_ROOT"] = str(target) result = subprocess.run( [sys.executable, str(BRAND_PATCHER)], cwd=ROOT, env=env, check=False, capture_output=True, text=True, ) assert result.returncode != 0 assert "brand patch context changed" in result.stderr def test_manifest_patch_rejects_drift_before_wholesale_replacement( tmp_path: Path, ) -> None: """The branded file cannot hide a changed manifest in the pinned image.""" target = tmp_path / "hermes-webui" shutil.copytree(FIXTURE, target) installed = target / "static/manifest.json" installed.write_text( installed.read_text(encoding="utf-8").replace( '"name": "Hermes"', '"name": "Upstream drift"', 1 ), encoding="utf-8", ) before = installed.read_bytes() env = os.environ.copy() env["HERMES_WEBUI_PATCH_ROOT"] = str(target) env["HERMES_WEBUI_MANIFEST_SOURCE"] = str(MANIFEST) result = subprocess.run( [sys.executable, str(MANIFEST_PATCHER)], cwd=ROOT, env=env, check=False, capture_output=True, text=True, ) assert result.returncode != 0 assert "pinned upstream SHA-256 mismatch" in result.stderr assert installed.read_bytes() == before class _SmokeResponse: status = 200 def __init__(self, body: bytes, url: str) -> None: self.body = body self.url = url def __enter__(self): return self def __exit__(self, *_args): return None def read(self) -> bytes: return self.body def geturl(self) -> str: return self.url def test_server_smoke_resolves_served_manifest_icons_and_checks_root() -> None: """The image gate validates HTTP responses, not its own source files.""" module = _load(SMOKE, "hermes_webui_smoke_contract") manifest = json.loads(MANIFEST.read_text(encoding="utf-8")) release_identity = "exp-v0.52.181-git-0123456789abcdef-build-13" root = ( b'' b'' b'' b'' + b"".join(f''.encode() for item in module.HUX_LINKS) + ( "" ).encode() ) calls = [] def open_fixture(request, *, timeout): assert timeout == 5 url = request.full_url calls.append(url) path = urlsplit(url).path if path == "/manifest.json": return _SmokeResponse( json.dumps(manifest).encode(), "http://assets.hermes.test/pwa/manifest.json", ) if path == "/": return _SmokeResponse(root, url) if path == "/api/settings": return _SmokeResponse( json.dumps( {"webui_bundle_version": release_identity} ).encode(), url, ) if path == "/sw.js": return _SmokeResponse( f"const CACHE_NAME='hermes-shell-{release_identity}';".encode(), url, ) if path.startswith("/static/hux/"): return _SmokeResponse(b"/* reviewed HUX asset */", url) if path in ( "/pwa/static/hermes-agent-192.png", "/pwa/static/hermes-agent-512.png", "/static/hermes-agent-192.png", ): return _SmokeResponse(module.PNG_MAGIC + b"fixture", url) raise AssertionError(f"unexpected smoke URL: {url}") result = module.smoke("http://hermes.test/", opener=open_fixture) assert result["manifest"] == "http://assets.hermes.test/pwa/manifest.json" assert result["icon_requests"] == 3 assert result["release_identity"] == release_identity assert result["hux_assets"] == len(module.HUX_LINKS) assert calls.count("http://assets.hermes.test/pwa/static/hermes-agent-192.png") == 2 assert "http://assets.hermes.test/pwa/static/hermes-agent-512.png" in calls assert calls.count("http://hermes.test/static/hermes-agent-192.png") == 1 def test_server_smoke_rejects_a_client_server_release_mismatch() -> None: """A build cannot publish the permanently stale banner regression.""" module = _load(SMOKE, "hermes_webui_smoke_release_mismatch") manifest = json.loads(MANIFEST.read_text(encoding="utf-8")) client = "exp-v0.52.181-git-0123456789abcdef-build-13" root = ( b'' b'' b'' b'' + b"".join(f''.encode() for item in module.HUX_LINKS) + ( "" ).encode() ) def open_mismatch(request, *, timeout): assert timeout == 5 url = request.full_url path = urlsplit(url).path if path == "/manifest.json": return _SmokeResponse(json.dumps(manifest).encode(), url) if path == "/": return _SmokeResponse(root, url) if path == "/api/settings": return _SmokeResponse( json.dumps( {"webui_bundle_version": client + "-different"} ).encode(), url, ) if path.startswith("/static/hux/"): return _SmokeResponse(b"/* reviewed HUX asset */", url) if path.endswith(".png"): return _SmokeResponse(module.PNG_MAGIC + b"fixture", url) raise AssertionError(f"unexpected smoke URL: {url}") with pytest.raises(RuntimeError, match="identities do not match"): module.smoke("http://hermes.test/", opener=open_mismatch) def test_server_smoke_rejects_unbranded_served_manifest() -> None: """A successful health check cannot mask the upstream PWA identity.""" module = _load(SMOKE, "hermes_webui_smoke_unbranded") def open_upstream(request, *, timeout): assert timeout == 5 payload = (FIXTURE / "static/manifest.json").read_bytes() return _SmokeResponse(payload, request.full_url) with pytest.raises(RuntimeError, match="not branded"): module.smoke("http://hermes.test/", opener=open_upstream) def test_brand_css_is_accessible_dark_and_reduced_motion_aware() -> None: """Identity colors retain system controls and disable cosmetic motion.""" css = BRAND_CSS.read_text(encoding="utf-8") dark = css.split(":root.dark {", 1)[1] assert "color-scheme: dark" in dark assert "--bg: #070a12" in dark assert "--text: #e8f1f4" in dark assert "--accent: #48cfcc" in dark assert "--voice-accent: 72, 207, 204" in css assert "--voice-accent-secondary: 76, 164, 205" in css assert "--hermes-violet: #9d8ee0" in dark assert "--hermes-grid: rgba(72, 207, 204, 0.025)" in dark assert ".empty-logo .hermes-agent-portrait" in css assert "width: 112px" in css assert ":root.dark .messages-shell" in css assert "background-size: 32px 32px, 32px 32px, auto, auto" in css assert "background-repeat: repeat, repeat, no-repeat, no-repeat" in css messages = css.rsplit(":root.dark .messages {", 1)[1].split("}", 1)[0] assert "background-image: none" in messages assert "radial-gradient" not in messages assert ":root.dark .session-item.active" in css assert ":root.dark .suggestion:focus-visible" in css assert "@media (prefers-reduced-motion: reduce)" in css reduced = css.split("@media (prefers-reduced-motion: reduce)", 1)[1] assert "transition: none !important" in reduced assert "transform: none !important" in reduced assert "animation:" not in css # The composer reserves the device bottom safe-area (home indicator / gesture # bar) so its control row is not hidden behind it, additive with the app's # keyboard inset. env() is 0 on inset-less devices, so this is desktop-inert. assert ".composer-wrap" in css composer = css.rsplit(".composer-wrap {", 1)[1].split("}", 1)[0] assert "env(safe-area-inset-bottom, 0px)" in composer assert "var(--keyboard-bottom-inset, 0px)" in composer def test_base_patch_and_dockerfile_opt_into_edge_to_edge_safe_area() -> None: """viewport-fit=cover is required for env(safe-area-inset-*) to carry real values; the base patch adds it and the Dockerfile verifies it landed.""" base_patch = (DOCKERFILES / "hermes-webui-base-patch.py").read_text(encoding="utf-8") assert "viewport-fit=cover" in base_patch assert "user-scalable=no, viewport-fit=cover" in base_patch dockerfile = (DOCKERFILES / "Dockerfile.hermes-webui").read_text(encoding="utf-8") assert "grep -Fq 'user-scalable=no, viewport-fit=cover'" in dockerfile def test_empty_state_uses_canonical_character_without_inline_staff( tmp_path: Path, ) -> None: """The new-chat identity reuses the established full character artwork.""" index = (_patched_fixture(tmp_path) / "static/index.html").read_text( encoding="utf-8" ) empty_state = index.split('
', 1)[1] empty_state = empty_state.split('
', 1)[0] def test_release_patch_gives_every_shell_url_an_immutable_build_token( tmp_path: Path, ) -> None: """Atlas releases share one server/client identity and browser cache key.""" target = _patched_fixture(tmp_path) env = os.environ.copy() env["HERMES_WEBUI_PATCH_ROOT"] = str(target) env["HERMES_WEBUI_RELEASE_ID"] = "git-0123456789abcdef-build-11" subprocess.run( [sys.executable, str(RELEASE_PATCHER)], cwd=ROOT, env=env, check=True, capture_output=True, text=True, ) index = (target / "static/index.html").read_text(encoding="utf-8") worker = (target / "static/sw.js").read_text(encoding="utf-8") routes = (target / "api/routes.py").read_text(encoding="utf-8") panels = (target / "static/panels.js").read_text(encoding="utf-8") token = "__WEBUI_VERSION__-git-0123456789abcdef-build-11" assert token in index assert token in worker assert not re.search(r"__WEBUI_VERSION__(?!-git-0123456789abcdef-build-11)", index) assert not re.search(r"__WEBUI_VERSION__(?!-git-0123456789abcdef-build-11)", worker) assert ( 'settings["webui_bundle_version"] = ' 'f"{WEBUI_VERSION}-git-0123456789abcdef-build-11"' ) in routes assert ( "settings.webui_bundle_version||settings.webui_version" ) in panels assert "if(client===server) return;" not in panels assert "if(banner) banner.style.display='none';" in panels assert "if(_isBannerVisible()) return;" not in panels assert ( "if(_isBannerVisible()){ clearInterval(_pollTimer);" not in panels ) @pytest.mark.parametrize("release_id", ["", "../escape", "UPPERCASE", "a" * 129]) def test_release_patch_rejects_missing_or_unsafe_tokens( tmp_path: Path, release_id: str ) -> None: """A malformed build token cannot silently collapse browser cache keys.""" target = _patched_fixture(tmp_path) env = os.environ.copy() env["HERMES_WEBUI_PATCH_ROOT"] = str(target) env["HERMES_WEBUI_RELEASE_ID"] = release_id completed = subprocess.run( [sys.executable, str(RELEASE_PATCHER)], cwd=ROOT, env=env, capture_output=True, text=True, ) assert completed.returncode != 0 assert "safe immutable release token" in completed.stderr def test_release_patch_rejects_upstream_without_version_markers( tmp_path: Path, ) -> None: """Pinned upstream drift fails the image build before publication.""" target = _patched_fixture(tmp_path) index = target / "static/index.html" index.write_text( index.read_text(encoding="utf-8").replace("__WEBUI_VERSION__", "fixed"), encoding="utf-8", ) env = os.environ.copy() env["HERMES_WEBUI_PATCH_ROOT"] = str(target) env["HERMES_WEBUI_RELEASE_ID"] = "git-0123456789abcdef-build-11" completed = subprocess.run( [sys.executable, str(RELEASE_PATCHER)], cwd=ROOT, env=env, capture_output=True, text=True, ) assert completed.returncode != 0 assert "release patch context changed" in completed.stderr def test_dockerfile_copies_and_verifies_every_tracked_brand_asset() -> None: """The immutable image, not a runtime coordinator path, owns PWA assets.""" dockerfile = (DOCKERFILES / "Dockerfile.hermes-webui").read_text(encoding="utf-8") assert "/opt/hermes/web/public/favicon.ico" not in dockerfile assert "COPY dockerfiles/hermes-webui-brand-patch.py" in dockerfile assert "python /tmp/hermes-webui-brand-patch.py" in dockerfile assert "COPY dockerfiles/hermes-webui-manifest-patch.py" in dockerfile assert "python /tmp/hermes-webui-manifest-patch.py" in dockerfile assert "COPY dockerfiles/hermes-webui-release-patch.py" in dockerfile assert "python /tmp/hermes-webui-release-patch.py" in dockerfile assert 'settings["webui_bundle_version"]' in dockerfile assert ( "settings.webui_bundle_version||settings.webui_version" in dockerfile ) assert ( "COPY dockerfiles/hermes-webui-manifest.json /tmp/hermes-webui-manifest.json" in dockerfile ) assert ( "COPY dockerfiles/hermes-webui-manifest.json /opt/hermes-webui/static/manifest.json" not in dockerfile ) assert "python /tmp/hermes-webui-smoke.py http://127.0.0.1:18787/" in dockerfile assert "COPY dockerfiles/hermes-webui-brand.css" in dockerfile for name, digest in EXPECTED_HASHES.items(): assert f"COPY dockerfiles/hermes-webui-assets/{name}" in dockerfile assert digest in dockerfile