fix(hermes): version chat release assets
This commit is contained in:
parent
55e21c9e08
commit
a68568d0c9
@ -226,6 +226,7 @@ spec:
|
||||
--context="dir://${WORKSPACE}" \
|
||||
--dockerfile="${WORKSPACE}/dockerfiles/Dockerfile.hermes-webui" \
|
||||
--destination="${destination}" \
|
||||
--build-arg="HERMES_WEBUI_RELEASE_ID=git-${source_revision}-build-${BUILD_NUMBER}" \
|
||||
--digest-file="${WORKSPACE}/build/hermes-webui.digest" \
|
||||
--image-name-tag-with-digest-file="${WORKSPACE}/build/hermes-webui.image" \
|
||||
--label="org.opencontainers.image.revision=${source_revision}" \
|
||||
|
||||
@ -4,6 +4,8 @@ FROM ghcr.io/nesquena/hermes-webui@sha256:a83a3893111dcb250e7aa7aa657d3d6f4570b0
|
||||
|
||||
FROM registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
||||
|
||||
ARG HERMES_WEBUI_RELEASE_ID
|
||||
|
||||
USER root
|
||||
|
||||
# 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-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-release-patch.py /tmp/hermes-webui-release-patch.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-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-router-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' \
|
||||
&& grep -Fq 'VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh")' \
|
||||
|
||||
@ -98,7 +98,7 @@ replace_between_exact(
|
||||
' <h2 data-i18n="empty_title">',
|
||||
' <div class="empty-logo">\n'
|
||||
' <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',
|
||||
)
|
||||
|
||||
|
||||
25
dockerfiles/hermes-webui-release-patch.py
Normal file
25
dockerfiles/hermes-webui-release-patch.py
Normal 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",
|
||||
)
|
||||
@ -7,6 +7,7 @@ import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
@ -22,6 +23,7 @@ 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"
|
||||
@ -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 class="hermes-agent-portrait" '
|
||||
'src="static/hermes-agent-192.png" alt="Hermes Agent">'
|
||||
'src="static/hermes-agent-512.png" alt="Hermes Agent">'
|
||||
) in index
|
||||
assert 'aria-label="Hermes caduceus"' 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 = empty_state.split('<div class="messages-inner"', 1)[0]
|
||||
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]
|
||||
|
||||
|
||||
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:
|
||||
"""The immutable image, not a runtime coordinator path, owns PWA assets."""
|
||||
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 "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 (
|
||||
"COPY dockerfiles/hermes-webui-manifest.json /tmp/hermes-webui-manifest.json"
|
||||
in dockerfile
|
||||
|
||||
@ -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-brand-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-stt-patch.py" in source
|
||||
assert "hermes-webui-atlas-voice.js" 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 "urljoin(manifest_url, source)" in (
|
||||
ROOT / "dockerfiles/hermes-webui-smoke.py"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user