fix(hermes): version chat release assets

This commit is contained in:
jenkins 2026-08-23 19:22:26 -03:00
parent 55e21c9e08
commit a68568d0c9
6 changed files with 114 additions and 4 deletions

View File

@ -226,6 +226,7 @@ spec:
--context="dir://${WORKSPACE}" \ --context="dir://${WORKSPACE}" \
--dockerfile="${WORKSPACE}/dockerfiles/Dockerfile.hermes-webui" \ --dockerfile="${WORKSPACE}/dockerfiles/Dockerfile.hermes-webui" \
--destination="${destination}" \ --destination="${destination}" \
--build-arg="HERMES_WEBUI_RELEASE_ID=git-${source_revision}-build-${BUILD_NUMBER}" \
--digest-file="${WORKSPACE}/build/hermes-webui.digest" \ --digest-file="${WORKSPACE}/build/hermes-webui.digest" \
--image-name-tag-with-digest-file="${WORKSPACE}/build/hermes-webui.image" \ --image-name-tag-with-digest-file="${WORKSPACE}/build/hermes-webui.image" \
--label="org.opencontainers.image.revision=${source_revision}" \ --label="org.opencontainers.image.revision=${source_revision}" \

View File

@ -4,6 +4,8 @@ FROM ghcr.io/nesquena/hermes-webui@sha256:a83a3893111dcb250e7aa7aa657d3d6f4570b0
FROM registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107 FROM registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
ARG HERMES_WEBUI_RELEASE_ID
USER root USER root
# Keep WebUI and Hermes pinned together. The WebUI imports Hermes internals, # Keep WebUI and Hermes pinned together. The WebUI imports Hermes internals,
@ -23,6 +25,7 @@ COPY dockerfiles/hermes-webui-router-patch.py /tmp/hermes-webui-router-patch.py
COPY dockerfiles/hermes-webui-router.js /opt/hermes-webui/static/atlas-router.js COPY dockerfiles/hermes-webui-router.js /opt/hermes-webui/static/atlas-router.js
COPY dockerfiles/hermes-webui-brand-patch.py /tmp/hermes-webui-brand-patch.py COPY dockerfiles/hermes-webui-brand-patch.py /tmp/hermes-webui-brand-patch.py
COPY dockerfiles/hermes-webui-manifest-patch.py /tmp/hermes-webui-manifest-patch.py COPY dockerfiles/hermes-webui-manifest-patch.py /tmp/hermes-webui-manifest-patch.py
COPY dockerfiles/hermes-webui-release-patch.py /tmp/hermes-webui-release-patch.py
COPY dockerfiles/hermes-webui-smoke.py /tmp/hermes-webui-smoke.py COPY dockerfiles/hermes-webui-smoke.py /tmp/hermes-webui-smoke.py
COPY dockerfiles/hermes-webui-brand.css /opt/hermes-webui/static/hermes-brand.css COPY dockerfiles/hermes-webui-brand.css /opt/hermes-webui/static/hermes-brand.css
COPY dockerfiles/hermes-webui-manifest.json /tmp/hermes-webui-manifest.json COPY dockerfiles/hermes-webui-manifest.json /tmp/hermes-webui-manifest.json
@ -35,7 +38,9 @@ RUN /opt/hermes/.venv/bin/python /tmp/hermes-webui-base-patch.py \
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-telegram-project-patch.py \ && /opt/hermes/.venv/bin/python /tmp/hermes-webui-telegram-project-patch.py \
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-router-patch.py \ && /opt/hermes/.venv/bin/python /tmp/hermes-webui-router-patch.py \
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-brand-patch.py \ && /opt/hermes/.venv/bin/python /tmp/hermes-webui-brand-patch.py \
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-manifest-patch.py && /opt/hermes/.venv/bin/python /tmp/hermes-webui-manifest-patch.py \
&& HERMES_WEBUI_RELEASE_ID="${HERMES_WEBUI_RELEASE_ID}" \
/opt/hermes/.venv/bin/python /tmp/hermes-webui-release-patch.py
RUN /opt/hermes/.venv/bin/python -c 'import cryptography, yaml' \ RUN /opt/hermes/.venv/bin/python -c 'import cryptography, yaml' \
&& grep -Fq 'VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh")' \ && grep -Fq 'VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh")' \

View File

@ -98,7 +98,7 @@ replace_between_exact(
' <h2 data-i18n="empty_title">', ' <h2 data-i18n="empty_title">',
' <div class="empty-logo">\n' ' <div class="empty-logo">\n'
' <img class="hermes-agent-portrait" ' ' <img class="hermes-agent-portrait" '
'src="static/hermes-agent-192.png" alt="Hermes Agent">\n' 'src="static/hermes-agent-512.png" alt="Hermes Agent">\n'
' </div>\n', ' </div>\n',
) )

View File

@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""Stamp Atlas WebUI shell URLs with the immutable image release identity."""
from __future__ import annotations
import os
from pathlib import Path
import re
ROOT = Path(os.environ.get("HERMES_WEBUI_PATCH_ROOT", "/opt/hermes-webui"))
RELEASE_ID = os.environ.get("HERMES_WEBUI_RELEASE_ID", "").strip()
if not re.fullmatch(r"[a-z0-9][a-z0-9.-]{0,127}", RELEASE_ID):
raise SystemExit("HERMES_WEBUI_RELEASE_ID must be a safe immutable release token")
for relative in ("static/index.html", "static/sw.js"):
path = ROOT / relative
source = path.read_text(encoding="utf-8")
if "__WEBUI_VERSION__" not in source:
raise SystemExit(f"Hermes release patch context changed in {path}")
path.write_text(
source.replace("__WEBUI_VERSION__", f"__WEBUI_VERSION__-{RELEASE_ID}"),
encoding="utf-8",
)

View File

@ -7,6 +7,7 @@ import importlib.util
import json import json
import os import os
from pathlib import Path from pathlib import Path
import re
import shutil import shutil
import struct import struct
import subprocess import subprocess
@ -22,6 +23,7 @@ AGENT_FIXTURE = ROOT / "testing/fixtures/hermes-agent"
ATLAS_PATCHER = DOCKERFILES / "hermes-webui-atlas-patch.py" ATLAS_PATCHER = DOCKERFILES / "hermes-webui-atlas-patch.py"
BRAND_PATCHER = DOCKERFILES / "hermes-webui-brand-patch.py" BRAND_PATCHER = DOCKERFILES / "hermes-webui-brand-patch.py"
MANIFEST_PATCHER = DOCKERFILES / "hermes-webui-manifest-patch.py" MANIFEST_PATCHER = DOCKERFILES / "hermes-webui-manifest-patch.py"
RELEASE_PATCHER = DOCKERFILES / "hermes-webui-release-patch.py"
SMOKE = DOCKERFILES / "hermes-webui-smoke.py" SMOKE = DOCKERFILES / "hermes-webui-smoke.py"
ASSETS = DOCKERFILES / "hermes-webui-assets" ASSETS = DOCKERFILES / "hermes-webui-assets"
MANIFEST = DOCKERFILES / "hermes-webui-manifest.json" MANIFEST = DOCKERFILES / "hermes-webui-manifest.json"
@ -144,7 +146,7 @@ def test_production_patchers_apply_title_icons_theme_and_cache_contract(
assert '<img src="static/hermes-agent-192.png" alt="">' in index assert '<img src="static/hermes-agent-192.png" alt="">' in index
assert ( assert (
'<img class="hermes-agent-portrait" ' '<img class="hermes-agent-portrait" '
'src="static/hermes-agent-192.png" alt="Hermes Agent">' 'src="static/hermes-agent-512.png" alt="Hermes Agent">'
) in index ) in index
assert 'aria-label="Hermes caduceus"' not in index assert 'aria-label="Hermes caduceus"' not in index
assert "favicon.svg" not in index assert "favicon.svg" not in index
@ -321,10 +323,79 @@ def test_empty_state_uses_canonical_character_without_inline_staff(
empty_state = index.split('<div class="empty-state" id="emptyState">', 1)[1] empty_state = index.split('<div class="empty-state" id="emptyState">', 1)[1]
empty_state = empty_state.split('<div class="messages-inner"', 1)[0] empty_state = empty_state.split('<div class="messages-inner"', 1)[0]
assert empty_state.count('class="hermes-agent-portrait"') == 1 assert empty_state.count('class="hermes-agent-portrait"') == 1
assert empty_state.count('src="static/hermes-agent-192.png"') == 1 assert empty_state.count('src="static/hermes-agent-512.png"') == 1
assert "<svg" not in empty_state.split('<h2 data-i18n="empty_title">', 1)[0] assert "<svg" not in empty_state.split('<h2 data-i18n="empty_title">', 1)[0]
def test_release_patch_gives_every_shell_url_an_immutable_build_token(
tmp_path: Path,
) -> None:
"""Atlas image releases never share a 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")
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)
@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: def test_dockerfile_copies_and_verifies_every_tracked_brand_asset() -> None:
"""The immutable image, not a runtime coordinator path, owns PWA assets.""" """The immutable image, not a runtime coordinator path, owns PWA assets."""
dockerfile = (DOCKERFILES / "Dockerfile.hermes-webui").read_text(encoding="utf-8") dockerfile = (DOCKERFILES / "Dockerfile.hermes-webui").read_text(encoding="utf-8")
@ -333,6 +404,8 @@ def test_dockerfile_copies_and_verifies_every_tracked_brand_asset() -> None:
assert "python /tmp/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 "COPY dockerfiles/hermes-webui-manifest-patch.py" in dockerfile
assert "python /tmp/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 ( assert (
"COPY dockerfiles/hermes-webui-manifest.json /tmp/hermes-webui-manifest.json" "COPY dockerfiles/hermes-webui-manifest.json /tmp/hermes-webui-manifest.json"
in dockerfile in dockerfile

View File

@ -139,10 +139,16 @@ def test_webui_dockerfile_is_kaniko_safe_and_uses_reviewed_repo_source() -> None
assert "hermes-webui-base-patch.py" in source assert "hermes-webui-base-patch.py" in source
assert "hermes-webui-brand-patch.py" in source assert "hermes-webui-brand-patch.py" in source
assert "hermes-webui-manifest-patch.py" in source assert "hermes-webui-manifest-patch.py" in source
assert "hermes-webui-release-patch.py" in source
assert "hermes-webui-smoke.py" in source assert "hermes-webui-smoke.py" in source
assert "hermes-webui-stt-patch.py" in source assert "hermes-webui-stt-patch.py" in source
assert "hermes-webui-atlas-voice.js" in source assert "hermes-webui-atlas-voice.js" in source
assert "hermes-webui-manifest.json" in source assert "hermes-webui-manifest.json" in source
assert (
'--build-arg="HERMES_WEBUI_RELEASE_ID='
'git-${source_revision}-build-${BUILD_NUMBER}"'
in PIPELINE.read_text(encoding="utf-8")
)
assert source.count("RUN /opt/hermes/.venv/bin/python /tmp/hermes-webui-") == 1 assert source.count("RUN /opt/hermes/.venv/bin/python /tmp/hermes-webui-") == 1
assert "urljoin(manifest_url, source)" in ( assert "urljoin(manifest_url, source)" in (
ROOT / "dockerfiles/hermes-webui-smoke.py" ROOT / "dockerfiles/hermes-webui-smoke.py"