hermes(chat): stage HUX sidecar activation topology
Activation (not yet pushed): per-tenant HUX sidecar on the exact live WebUI image, standalone Astreae RWX PVC with kubelet subPathExpr per-pod isolation, root init container that provisions 0700 tenant roots, a persistent HMAC context key, an immutable subject binding and tmpfs relay/worker keys, the vendored hux-runtime plugin ConfigMap, and observe-only defaults (HUX_TOOL_ENFORCEMENT=0). Delivery tests pin the whole boundary; image-automation tests bind the HUX metadata setters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
This commit is contained in:
parent
0b56d04e9d
commit
71c05cf9c0
@ -44,6 +44,7 @@ data:
|
||||
enabled:
|
||||
- atlas-broker
|
||||
- auto-router
|
||||
- hux-runtime
|
||||
model_catalog:
|
||||
enabled: true
|
||||
ttl_hours: 1
|
||||
|
||||
@ -15,6 +15,24 @@ spec:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
---
|
||||
# HUX tenants share the backing claim but only receive their pod-specific
|
||||
# subdirectory through kubelet subPath mounts.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: hermes-chat-hux-data
|
||||
namespace: hermes
|
||||
labels:
|
||||
app: hermes-chat-tenant
|
||||
ai.bstein.dev/data: tenant-hux-ledger
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
storageClassName: astreae
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
|
||||
@ -30,6 +30,7 @@ spec:
|
||||
ai.bstein.dev/isolation: one Hermes process and PVC per Keycloak subject
|
||||
ai.bstein.dev/model-policy: uniform automatic policy with per-user overrides
|
||||
ai.bstein.dev/config-rev: "20260816-telegram-topics"
|
||||
ai.bstein.dev/hux-config-rev: "20260824-hux-v1"
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/role: hermes-chat
|
||||
vault.hashicorp.com/agent-inject-secret-chat-relay-key: kv/data/atlas/hermes/chat-telegram
|
||||
@ -144,6 +145,121 @@ spec:
|
||||
resources:
|
||||
requests: {cpu: 25m, memory: 32Mi}
|
||||
limits: {cpu: 100m, memory: 64Mi}
|
||||
- name: init-hux-runtime
|
||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: [/bin/sh, -ec]
|
||||
args:
|
||||
- |
|
||||
umask 077
|
||||
tenant_root="/hux-data/${HOSTNAME}"
|
||||
export HUX_INIT_ROOT="${tenant_root}"
|
||||
mkdir -p \
|
||||
"${tenant_root}/binding" \
|
||||
"${tenant_root}/context" \
|
||||
"${tenant_root}/store" \
|
||||
/hux-relay \
|
||||
/hux-worker
|
||||
chown 10000:10000 \
|
||||
"${tenant_root}" \
|
||||
"${tenant_root}/binding" \
|
||||
"${tenant_root}/context" \
|
||||
"${tenant_root}/store" \
|
||||
/hux-relay \
|
||||
/hux-worker
|
||||
chmod 0700 \
|
||||
"${tenant_root}" \
|
||||
"${tenant_root}/binding" \
|
||||
"${tenant_root}/context" \
|
||||
"${tenant_root}/store" \
|
||||
/hux-relay \
|
||||
/hux-worker
|
||||
if [ ! -e "${tenant_root}/context/context-key" ]; then
|
||||
dd if=/dev/urandom of="${tenant_root}/context/.context-key.tmp" bs=32 count=1 2>/dev/null
|
||||
chown 10000:10000 "${tenant_root}/context/.context-key.tmp"
|
||||
chmod 0600 "${tenant_root}/context/.context-key.tmp"
|
||||
mv "${tenant_root}/context/.context-key.tmp" "${tenant_root}/context/context-key"
|
||||
fi
|
||||
test "$(wc -c < "${tenant_root}/context/context-key")" -eq 32
|
||||
chown 10000:10000 "${tenant_root}/context/context-key"
|
||||
chmod 0600 "${tenant_root}/context/context-key"
|
||||
ordinal="${HOSTNAME##*-}"
|
||||
HUX_INIT_SLOT="slot-${ordinal}" \
|
||||
/opt/hermes/.venv/bin/python - <<'PY'
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import stat
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(os.environ["HUX_INIT_ROOT"])
|
||||
key = (root / "context/context-key").read_bytes()
|
||||
slot = os.environ["HUX_INIT_SLOT"]
|
||||
subject = "usr_" + hmac.new(
|
||||
key,
|
||||
b"hux.subject.id.v1\0" + slot.encode("ascii"),
|
||||
hashlib.sha256,
|
||||
).hexdigest()
|
||||
target = root / "binding/subject"
|
||||
expected = (subject + "\n").encode("ascii")
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
|
||||
try:
|
||||
descriptor = os.open(target, flags, 0o440)
|
||||
except FileExistsError:
|
||||
info = target.lstat()
|
||||
if (
|
||||
not stat.S_ISREG(info.st_mode)
|
||||
or info.st_uid != 10000
|
||||
or stat.S_IMODE(info.st_mode) != 0o440
|
||||
or info.st_nlink != 1
|
||||
or target.read_bytes() != expected
|
||||
):
|
||||
raise SystemExit("persistent HUX subject binding is unsafe")
|
||||
else:
|
||||
try:
|
||||
os.write(descriptor, expected)
|
||||
os.fchown(descriptor, 10000, 10000)
|
||||
os.fchmod(descriptor, 0o440)
|
||||
os.fsync(descriptor)
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
PY
|
||||
if [ ! -e "${tenant_root}/context/redaction-canary" ]; then
|
||||
dd if=/dev/urandom bs=32 count=1 2>/dev/null \
|
||||
| sha256sum | cut -d ' ' -f 1 \
|
||||
> "${tenant_root}/context/.redaction-canary.tmp"
|
||||
chown 10000:10000 "${tenant_root}/context/.redaction-canary.tmp"
|
||||
chmod 0400 "${tenant_root}/context/.redaction-canary.tmp"
|
||||
mv "${tenant_root}/context/.redaction-canary.tmp" "${tenant_root}/context/redaction-canary"
|
||||
fi
|
||||
for target in /hux-relay/relay-key /hux-worker/worker-key; do
|
||||
if [ ! -e "${target}" ]; then
|
||||
dd if=/dev/urandom bs=32 count=1 2>/dev/null \
|
||||
| sha256sum | cut -d ' ' -f 1 > "${target}.tmp"
|
||||
chown 10000:10000 "${target}.tmp"
|
||||
chmod 0400 "${target}.tmp"
|
||||
mv "${target}.tmp" "${target}"
|
||||
fi
|
||||
test "$(wc -c < "${target}")" -eq 65
|
||||
chown 10000:10000 "${target}"
|
||||
chmod 0400 "${target}"
|
||||
done
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
add: [CHOWN, DAC_OVERRIDE, FOWNER]
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- {name: hux-data, mountPath: /hux-data}
|
||||
- {name: hux-relay-key, mountPath: /hux-relay}
|
||||
- {name: hux-worker-key, mountPath: /hux-worker}
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 16Mi}
|
||||
limits: {cpu: 50m, memory: 32Mi}
|
||||
- name: stage-runtime-access
|
||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
||||
imagePullPolicy: IfNotPresent
|
||||
@ -267,10 +383,15 @@ spec:
|
||||
API_SERVER_KEY="$(tr -d '\r\n' < /runtime-access/chat-relay-key)"
|
||||
test -n "${API_SERVER_KEY}"
|
||||
export API_SERVER_KEY
|
||||
export HUX_TENANT_SLOT="slot-${ordinal}"
|
||||
exec /opt/hermes/.venv/bin/hermes gateway run
|
||||
ports:
|
||||
- {name: api, containerPort: 8642, protocol: TCP}
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- {name: HERMES_HOME, value: /opt/data}
|
||||
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
||||
- {name: HOME, value: /opt/data/home}
|
||||
@ -288,6 +409,16 @@ spec:
|
||||
- {name: HERMES_IMAGE_BROKER_URL, value: 'http://hermes-image-broker.hermes.svc.cluster.local:9002'}
|
||||
- {name: HERMES_IMAGE_BROKER_KEY_FILE, value: /runtime-access/chat-relay-key}
|
||||
- {name: HERMES_AUTO_ROUTER_PROFILE, value: chat}
|
||||
- {name: HUX_BASE_URL, value: 'http://127.0.0.1:8790'}
|
||||
- {name: HUX_RUNTIME_ENABLED, value: "1"}
|
||||
# First rollout is observe-only until approval parking/resume is
|
||||
# connected to the upstream tool loop and proven live.
|
||||
- {name: HUX_TOOL_ENFORCEMENT, value: "0"}
|
||||
- {name: HUX_WORKER_KEY_FILE, value: /run/hermes-hux-worker/worker-key}
|
||||
- {name: HUX_SUBJECT_FILE, value: /run/hermes-hux-subject/subject}
|
||||
- {name: HUX_CONTEXT_KEY_FILE, value: /run/hermes-hux-context/context-key}
|
||||
- {name: HUX_PROJECT_SOURCE, value: 'profile:default'}
|
||||
- {name: HUX_TIMEOUT_SECONDS, value: "3"}
|
||||
volumeMounts:
|
||||
- {name: home, mountPath: /opt/data}
|
||||
- {name: workspace, mountPath: /opt/data/workspace}
|
||||
@ -300,6 +431,10 @@ spec:
|
||||
- {name: subprocess-secret-patch, mountPath: /opt/hermes/tools/process_registry.py, subPath: process_registry.py}
|
||||
- {name: image-plugin, mountPath: /opt/hermes/plugins/image_gen/atlas-broker, readOnly: true}
|
||||
- {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true}
|
||||
- {name: hux-runtime-plugin, mountPath: /opt/data/plugins/hux-runtime, readOnly: true}
|
||||
- {name: hux-worker-key, mountPath: /run/hermes-hux-worker, readOnly: true}
|
||||
- {name: hux-data, mountPath: /run/hermes-hux-context, subPathExpr: $(POD_NAME)/context, readOnly: true}
|
||||
- {name: hux-data, mountPath: /run/hermes-hux-subject, subPathExpr: $(POD_NAME)/binding, readOnly: true}
|
||||
readinessProbe:
|
||||
tcpSocket: {port: api}
|
||||
initialDelaySeconds: 30
|
||||
@ -333,6 +468,10 @@ spec:
|
||||
ports:
|
||||
- {name: webui, containerPort: 8787, protocol: TCP}
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- {name: HERMES_HOME, value: /opt/data}
|
||||
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
||||
- {name: HOME, value: /opt/data/home}
|
||||
@ -361,12 +500,16 @@ spec:
|
||||
- {name: HERMES_LOCAL_STT_COMMAND, value: "/opt/hermes/.venv/bin/python /opt/coordinator/hermes_stt_client.py {input_path} --output-dir {output_dir} --language {language} --model {model}"}
|
||||
- {name: HERMES_WEBUI_ATLAS_TTS_URL, value: 'http://hermes-tts.hermes.svc.cluster.local:9001/v1/audio/speech'}
|
||||
- {name: HERMES_WEBUI_ATLAS_TTS_STREAM_URL, value: 'http://hermes-tts.hermes.svc.cluster.local:9001/v1/audio/speech/stream'}
|
||||
- {name: HUX_CONTEXT_KEY_FILE, value: /run/hermes-hux-context/context-key}
|
||||
- {name: HUX_PROJECT_SOURCE, value: 'profile:default'}
|
||||
volumeMounts:
|
||||
- {name: home, mountPath: /opt/data}
|
||||
- {name: workspace, mountPath: /opt/data/workspace}
|
||||
- {name: runtime-access, mountPath: /runtime-access, readOnly: true}
|
||||
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
||||
- {name: tmp, mountPath: /tmp}
|
||||
- {name: hux-relay-key, mountPath: /run/hermes-webui-hux, readOnly: true}
|
||||
- {name: hux-data, mountPath: /run/hermes-hux-context, subPathExpr: $(POD_NAME)/context, readOnly: true}
|
||||
readinessProbe:
|
||||
httpGet: {path: /health, port: webui}
|
||||
initialDelaySeconds: 10
|
||||
@ -389,6 +532,76 @@ spec:
|
||||
resources:
|
||||
requests: {cpu: 100m, memory: 224Mi}
|
||||
limits: {cpu: 750m, memory: 1Gi}
|
||||
- name: hux
|
||||
image: registry.bstein.dev/bstein/hermes-webui:git-91eb4f92b7cf46e65dc615106aedfde232c9c670-build-18-release@sha256:df91f8a3cdb54d685f8fea023a4539d89d1d0b3f89024cca4dce87b0b4a7df9c # {"$imagepolicy": "hermes:hermes-webui-release"}
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: [/bin/sh, -ec]
|
||||
args:
|
||||
- |
|
||||
ordinal="${HOSTNAME##*-}"
|
||||
export HUX_TENANT_SLOT="slot-${ordinal}"
|
||||
exec /opt/hermes/.venv/bin/python -m hux.server
|
||||
ports:
|
||||
- {name: hux-loopback, containerPort: 8790, protocol: TCP}
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- {name: PYTHONPATH, value: /opt/hermes-hux}
|
||||
- {name: PYTHONDONTWRITEBYTECODE, value: "1"}
|
||||
- {name: HOME, value: /tmp}
|
||||
- {name: HUX_BIND, value: 127.0.0.1}
|
||||
- {name: HUX_PORT, value: "8790"}
|
||||
- {name: HUX_DATA_ROOT, value: /var/lib/hux/store}
|
||||
- {name: HUX_FLAGS, value: 'hux.foundation,hux.activity_timeline,hux.memory_control,hux.projects,hux.artifacts,hux.autonomy,hux.friendly_modes,hux.multimodal,hux.research,hux.onboarding,hux.privacy,hux.release_followthrough'}
|
||||
- {name: HUX_RELAY_KEY_FILE, value: /run/hermes-webui-hux/relay-key}
|
||||
- {name: HUX_WORKER_KEY_FILE, value: /run/hermes-hux-worker/worker-key}
|
||||
- {name: HUX_SUBJECT_BINDING_FILE, value: /var/lib/hux/binding/subject}
|
||||
- {name: HUX_CONTEXT_KEY_FILE, value: /var/lib/hux/context/context-key}
|
||||
- {name: HUX_CANARY_FILE, value: /var/lib/hux/context/redaction-canary}
|
||||
- {name: HUX_IMAGE_TAG, value: 'git-91eb4f92b7cf46e65dc615106aedfde232c9c670-build-18-release'} # {"$imagepolicy": "hermes:hermes-webui-release:tag"}
|
||||
- {name: HUX_IMAGE_DIGEST, value: 'sha256:df91f8a3cdb54d685f8fea023a4539d89d1d0b3f89024cca4dce87b0b4a7df9c'} # {"$imagepolicy": "hermes:hermes-webui-release:digest"}
|
||||
- {name: HUX_SWITCHYARD_ROUTE_CATALOG, value: 'atlas/manual/codex/luna,atlas/manual/codex/terra,atlas/manual/codex/sol,atlas/manual/claude/haiku,atlas/manual/claude/fable,atlas/manual/claude/sonnet,atlas/manual/claude/opus,atlas/manual/local/qwen-14b'}
|
||||
- {name: HUX_READS_PER_MINUTE, value: "600"}
|
||||
- {name: HUX_WRITES_PER_MINUTE, value: "120"}
|
||||
- {name: HUX_REQUEST_TIMEOUT_SECONDS, value: "10"}
|
||||
volumeMounts:
|
||||
- {name: hux-data, mountPath: /var/lib/hux, subPathExpr: $(POD_NAME)}
|
||||
- {name: hux-relay-key, mountPath: /run/hermes-webui-hux, readOnly: true}
|
||||
- {name: hux-worker-key, mountPath: /run/hermes-hux-worker, readOnly: true}
|
||||
- {name: hux-tmp, mountPath: /tmp}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /opt/hermes/.venv/bin/python
|
||||
- -c
|
||||
- "import json,urllib.request; body=json.load(urllib.request.urlopen('http://127.0.0.1:8790/healthz', timeout=2)); assert body['status']=='ok'"
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 12
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /opt/hermes/.venv/bin/python
|
||||
- -c
|
||||
- "import json,urllib.request; body=json.load(urllib.request.urlopen('http://127.0.0.1:8790/healthz', timeout=2)); assert body['status']=='ok'"
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 3
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: true
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
resources:
|
||||
requests: {cpu: 25m, memory: 64Mi}
|
||||
limits: {cpu: 250m, memory: 256Mi}
|
||||
- name: telegram-media
|
||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
||||
imagePullPolicy: IfNotPresent
|
||||
@ -452,9 +665,35 @@ spec:
|
||||
- name: image-plugin
|
||||
configMap:
|
||||
name: hermes-chat-image-plugin
|
||||
- name: hux-runtime-plugin
|
||||
configMap:
|
||||
name: hermes-hux-runtime-plugin
|
||||
items:
|
||||
- {key: __init__.py, path: __init__.py}
|
||||
- {key: context_ids.py, path: context_ids.py}
|
||||
- {key: runtime.py, path: runtime.py}
|
||||
- {key: tool_policy.py, path: tool_policy.py}
|
||||
- {key: plugin.yaml, path: plugin.yaml}
|
||||
- {key: hux-hook-init.py, path: hux_hook/__init__.py}
|
||||
- {key: hux-hook-client.py, path: hux_hook/client.py}
|
||||
- {key: hux-hook-hooks.py, path: hux_hook/hooks.py}
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
sizeLimit: 256Mi
|
||||
- name: hux-relay-key
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 1Mi
|
||||
- name: hux-worker-key
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 1Mi
|
||||
- name: hux-tmp
|
||||
emptyDir:
|
||||
sizeLimit: 64Mi
|
||||
- name: hux-data
|
||||
persistentVolumeClaim:
|
||||
claimName: hermes-chat-hux-data
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: home
|
||||
|
||||
@ -195,6 +195,19 @@ configMapGenerator:
|
||||
- dashboard-style.css=plugins/auto-router/dashboard/dist/style.css
|
||||
options:
|
||||
disableNameSuffixHash: true
|
||||
- name: hermes-hux-runtime-plugin
|
||||
namespace: hermes
|
||||
files:
|
||||
- __init__.py=plugins/hux-runtime/__init__.py
|
||||
- context_ids.py=plugins/hux-runtime/context_ids.py
|
||||
- runtime.py=plugins/hux-runtime/runtime.py
|
||||
- tool_policy.py=plugins/hux-runtime/tool_policy.py
|
||||
- plugin.yaml=plugins/hux-runtime/plugin.yaml
|
||||
- hux-hook-init.py=plugins/hux-runtime/hux_hook/__init__.py
|
||||
- hux-hook-client.py=plugins/hux-runtime/hux_hook/client.py
|
||||
- hux-hook-hooks.py=plugins/hux-runtime/hux_hook/hooks.py
|
||||
options:
|
||||
disableNameSuffixHash: true
|
||||
- name: hermes-chat-image-plugin
|
||||
namespace: hermes
|
||||
files:
|
||||
|
||||
190
testing/tests/test_hermes_hux_delivery.py
Normal file
190
testing/tests/test_hermes_hux_delivery.py
Normal file
@ -0,0 +1,190 @@
|
||||
"""Flux delivery gates for the per-tenant HUX service boundary."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SERVICE = ROOT / "services/hermes"
|
||||
CHAT = SERVICE / "chat-statefulset.yaml"
|
||||
CHAT_PVCS = SERVICE / "chat-pvcs.yaml"
|
||||
KUSTOMIZATION = SERVICE / "kustomization.yaml"
|
||||
WEBUI_IMAGE = "registry.bstein.dev/bstein/hermes-webui"
|
||||
ALL_FLAGS = {
|
||||
"hux.activity_timeline",
|
||||
"hux.artifacts",
|
||||
"hux.autonomy",
|
||||
"hux.foundation",
|
||||
"hux.friendly_modes",
|
||||
"hux.memory_control",
|
||||
"hux.multimodal",
|
||||
"hux.onboarding",
|
||||
"hux.privacy",
|
||||
"hux.projects",
|
||||
"hux.release_followthrough",
|
||||
"hux.research",
|
||||
}
|
||||
|
||||
|
||||
def _statefulset() -> dict:
|
||||
return yaml.safe_load(CHAT.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _named(items: list[dict], name: str) -> dict:
|
||||
return next(item for item in items if item["name"] == name)
|
||||
|
||||
|
||||
def _env(container: dict) -> dict[str, str]:
|
||||
return {item["name"]: item.get("value", "") for item in container["env"]}
|
||||
|
||||
|
||||
def _mounts(container: dict) -> dict[str, dict]:
|
||||
return {item["name"]: item for item in container.get("volumeMounts", [])}
|
||||
|
||||
|
||||
def test_hux_sidecar_is_loopback_only_and_uses_the_reviewed_webui_image() -> None:
|
||||
"""The browser BFF and HUX backend ship as one immutable reviewed artifact."""
|
||||
pod = _statefulset()["spec"]["template"]["spec"]
|
||||
webui = _named(pod["containers"], "webui")
|
||||
hux = _named(pod["containers"], "hux")
|
||||
values = _env(hux)
|
||||
|
||||
assert webui["image"] == hux["image"]
|
||||
assert webui["image"].startswith(WEBUI_IMAGE + ":")
|
||||
assert values["HUX_BIND"] == "127.0.0.1"
|
||||
assert values["HUX_PORT"] == "8790"
|
||||
assert values["HUX_DATA_ROOT"] == "/var/lib/hux/store"
|
||||
assert set(values["HUX_FLAGS"].split(",")) == ALL_FLAGS
|
||||
assert set(values["HUX_SWITCHYARD_ROUTE_CATALOG"].split(",")) == {
|
||||
"atlas/manual/codex/luna",
|
||||
"atlas/manual/codex/terra",
|
||||
"atlas/manual/codex/sol",
|
||||
"atlas/manual/claude/haiku",
|
||||
"atlas/manual/claude/fable",
|
||||
"atlas/manual/claude/sonnet",
|
||||
"atlas/manual/claude/opus",
|
||||
"atlas/manual/local/qwen-14b",
|
||||
}
|
||||
assert values["PYTHONDONTWRITEBYTECODE"] == "1"
|
||||
assert hux["securityContext"]["readOnlyRootFilesystem"] is True
|
||||
assert hux["securityContext"]["capabilities"]["drop"] == ["ALL"]
|
||||
for probe in ("readinessProbe", "livenessProbe"):
|
||||
command = " ".join(hux[probe]["exec"]["command"])
|
||||
assert "127.0.0.1" in command
|
||||
assert "8790" in command
|
||||
assert "/healthz" in command
|
||||
assert "tcpSocket" not in hux[probe]
|
||||
assert "8790" not in (SERVICE / "service.yaml").read_text(encoding="utf-8")
|
||||
policy = (SERVICE / "networkpolicy.yaml").read_text(encoding="utf-8")
|
||||
assert "port: 8790" not in policy
|
||||
|
||||
|
||||
def test_hux_storage_and_keys_are_mounted_by_least_privilege() -> None:
|
||||
"""Hermes receives only its key/context views, never the HUX ledger root."""
|
||||
stateful = _statefulset()
|
||||
pod = stateful["spec"]["template"]["spec"]
|
||||
hermes = _named(pod["containers"], "hermes")
|
||||
webui = _named(pod["containers"], "webui")
|
||||
hux = _named(pod["containers"], "hux")
|
||||
media = _named(pod["containers"], "telegram-media")
|
||||
hermes_mounts = hermes["volumeMounts"]
|
||||
webui_mounts = _mounts(webui)
|
||||
hux_mounts = _mounts(hux)
|
||||
|
||||
assert hux_mounts["hux-data"] == {
|
||||
"name": "hux-data",
|
||||
"mountPath": "/var/lib/hux",
|
||||
"subPathExpr": "$(POD_NAME)",
|
||||
}
|
||||
assert {
|
||||
item["subPathExpr"]
|
||||
for item in hermes_mounts
|
||||
if item["name"] == "hux-data"
|
||||
} == {"$(POD_NAME)/binding", "$(POD_NAME)/context"}
|
||||
assert webui_mounts["hux-data"]["subPathExpr"] == "$(POD_NAME)/context"
|
||||
assert not any(item.get("mountPath") == "/var/lib/hux" for item in hermes["volumeMounts"])
|
||||
assert not any(item.get("mountPath") == "/var/lib/hux" for item in webui["volumeMounts"])
|
||||
assert not any(item["name"].startswith("hux-") for item in media["volumeMounts"])
|
||||
assert not any(item["name"] == "hux-relay-key" for item in hermes_mounts)
|
||||
assert "hux-worker-key" not in webui_mounts
|
||||
|
||||
assert {item["metadata"]["name"] for item in stateful["spec"]["volumeClaimTemplates"]} == {
|
||||
"home",
|
||||
"workspace",
|
||||
}
|
||||
claims = {
|
||||
item["metadata"]["name"]: item
|
||||
for item in yaml.safe_load_all(CHAT_PVCS.read_text(encoding="utf-8"))
|
||||
}
|
||||
claim = claims["hermes-chat-hux-data"]["spec"]
|
||||
assert claim["accessModes"] == ["ReadWriteMany"]
|
||||
assert claim["storageClassName"] == "astreae"
|
||||
assert claim["resources"]["requests"]["storage"] == "10Gi"
|
||||
|
||||
|
||||
def test_hux_init_preserves_context_identity_and_rotates_transport_keys() -> None:
|
||||
"""Context identity is durable while relay/worker credentials are pod-local."""
|
||||
pod = _statefulset()["spec"]["template"]["spec"]
|
||||
init = _named(pod["initContainers"], "init-hux-runtime")
|
||||
script = init["args"][0]
|
||||
volumes = {item["name"]: item for item in pod["volumes"]}
|
||||
|
||||
assert 'tenant_root="/hux-data/${HOSTNAME}"' in script
|
||||
assert "chown -R" not in script
|
||||
assert 'if [ ! -e "${tenant_root}/context/context-key" ]' in script
|
||||
assert "bs=32 count=1" in script
|
||||
assert 'chmod 0600 "${tenant_root}/context/context-key"' in script
|
||||
assert "/hux-relay/relay-key /hux-worker/worker-key" in script
|
||||
assert "chmod 0400" in script
|
||||
assert 'ordinal="${HOSTNAME##*-}"' in script
|
||||
assert 'b"hux.subject.id.v1\\0" + slot.encode("ascii")' in script
|
||||
assert 'target = root / "binding/subject"' in script
|
||||
assert 'or stat.S_IMODE(info.st_mode) != 0o440' in script
|
||||
assert volumes["hux-relay-key"]["emptyDir"]["medium"] == "Memory"
|
||||
assert volumes["hux-worker-key"]["emptyDir"]["medium"] == "Memory"
|
||||
assert init["securityContext"]["capabilities"] == {
|
||||
"drop": ["ALL"],
|
||||
"add": ["CHOWN", "DAC_OVERRIDE", "FOWNER"],
|
||||
}
|
||||
|
||||
|
||||
def test_hux_identity_and_authentication_inputs_are_file_backed() -> None:
|
||||
"""No HUX shared key or subject is placed directly in an environment value."""
|
||||
pod = _statefulset()["spec"]["template"]["spec"]
|
||||
hermes = _env(_named(pod["containers"], "hermes"))
|
||||
webui = _env(_named(pod["containers"], "webui"))
|
||||
hux = _env(_named(pod["containers"], "hux"))
|
||||
|
||||
assert hermes["HUX_WORKER_KEY_FILE"] == "/run/hermes-hux-worker/worker-key"
|
||||
assert hermes["HUX_SUBJECT_FILE"] == "/run/hermes-hux-subject/subject"
|
||||
assert hermes["HUX_CONTEXT_KEY_FILE"] == "/run/hermes-hux-context/context-key"
|
||||
assert hermes["HUX_TOOL_ENFORCEMENT"] == "0"
|
||||
assert webui["HUX_CONTEXT_KEY_FILE"] == "/run/hermes-hux-context/context-key"
|
||||
assert hux["HUX_RELAY_KEY_FILE"] == "/run/hermes-webui-hux/relay-key"
|
||||
assert hux["HUX_WORKER_KEY_FILE"] == "/run/hermes-hux-worker/worker-key"
|
||||
assert hux["HUX_SUBJECT_BINDING_FILE"] == "/var/lib/hux/binding/subject"
|
||||
assert hux["HUX_CONTEXT_KEY_FILE"] == "/var/lib/hux/context/context-key"
|
||||
assert hux["HUX_IMAGE_TAG"].startswith("git-")
|
||||
assert hux["HUX_IMAGE_TAG"].endswith("-release")
|
||||
assert hux["HUX_IMAGE_DIGEST"].startswith("sha256:")
|
||||
assert not {"HUX_RELAY_KEY", "HUX_WORKER_KEY", "HUX_ROUTER_KEY"} & set(hux)
|
||||
|
||||
|
||||
def test_hux_runtime_plugin_renders_its_vendored_hook_package() -> None:
|
||||
"""The mounted plugin must contain its Kustomize-local Python package tree."""
|
||||
stateful = _statefulset()
|
||||
pod = stateful["spec"]["template"]["spec"]
|
||||
plugin = _named(pod["volumes"], "hux-runtime-plugin")["configMap"]
|
||||
items = {item["key"]: item["path"] for item in plugin["items"]}
|
||||
expected = {
|
||||
"hux-hook-init.py": "hux_hook/__init__.py",
|
||||
"hux-hook-client.py": "hux_hook/client.py",
|
||||
"hux-hook-hooks.py": "hux_hook/hooks.py",
|
||||
}
|
||||
|
||||
assert expected.items() <= items.items()
|
||||
rendered = KUSTOMIZATION.read_text(encoding="utf-8")
|
||||
assert "hux-hook-init.py=plugins/hux-runtime/hux_hook/__init__.py" in rendered
|
||||
assert "hux-hook-client.py=plugins/hux-runtime/hux_hook/client.py" in rendered
|
||||
assert "hux-hook-hooks.py=plugins/hux-runtime/hux_hook/hooks.py" in rendered
|
||||
@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import yaml
|
||||
|
||||
@ -73,6 +74,28 @@ def test_flux_updates_only_the_reviewed_hermes_image_digests() -> None:
|
||||
webui_marker = '"$imagepolicy": "hermes:hermes-webui-release"'
|
||||
assert chat.count(webui_marker) == 2
|
||||
assert dashboard.count(webui_marker) == 1
|
||||
assert chat.count(
|
||||
'"$imagepolicy": "hermes:hermes-webui-release:tag"'
|
||||
) == 1
|
||||
assert chat.count(
|
||||
'"$imagepolicy": "hermes:hermes-webui-release:digest"'
|
||||
) == 1
|
||||
chat_object = yaml.safe_load(chat)
|
||||
hux = next(
|
||||
item
|
||||
for item in chat_object["spec"]["template"]["spec"]["containers"]
|
||||
if item["name"] == "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):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user