hermes(chat): activate HUX foundation canary on ordinal 3
Some checks failed
Tests / Declarative: Post Actions failed: 71, skipped: 28, passed: 3680
Some checks failed
Tests / Declarative: Post Actions failed: 71, skipped: 28, passed: 3680
Re-applies the staged HUX topology pinned to the reviewed build-21 image (git-2f535d3a...-build-21-release@sha256:e5b9b2fa...), with the first-activation posture: HUX_FLAGS=hux.foundation only, HUX_TOOL_ENFORCEMENT=0, and a RollingUpdate partition of 3 so only hermes-chat-tenant-3 rolls. Adds the /healthz auth bypass on the chat proxy so HUX-12 health receipts can observe a real 200, points the evidence policy at it, and makes the delivery flag gate progressive (foundation first, cards enabled per lifecycle acceptance; unknown flags still never ship). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
This commit is contained in:
parent
1d29c25c9b
commit
39a544828f
@ -44,6 +44,7 @@ data:
|
|||||||
enabled:
|
enabled:
|
||||||
- atlas-broker
|
- atlas-broker
|
||||||
- auto-router
|
- auto-router
|
||||||
|
- hux-runtime
|
||||||
model_catalog:
|
model_catalog:
|
||||||
enabled: true
|
enabled: true
|
||||||
ttl_hours: 1
|
ttl_hours: 1
|
||||||
|
|||||||
@ -15,6 +15,24 @@ spec:
|
|||||||
requests:
|
requests:
|
||||||
storage: 10Gi
|
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
|
apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
|
|||||||
@ -17,6 +17,8 @@ spec:
|
|||||||
whenScaled: Retain
|
whenScaled: Retain
|
||||||
updateStrategy:
|
updateStrategy:
|
||||||
type: RollingUpdate
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
partition: 3
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: hermes-chat-tenant
|
app: hermes-chat-tenant
|
||||||
@ -30,6 +32,7 @@ spec:
|
|||||||
ai.bstein.dev/isolation: one Hermes process and PVC per Keycloak subject
|
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/model-policy: uniform automatic policy with per-user overrides
|
||||||
ai.bstein.dev/config-rev: "20260816-telegram-topics"
|
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/agent-inject: "true"
|
||||||
vault.hashicorp.com/role: hermes-chat
|
vault.hashicorp.com/role: hermes-chat
|
||||||
vault.hashicorp.com/agent-inject-secret-chat-relay-key: kv/data/atlas/hermes/chat-telegram
|
vault.hashicorp.com/agent-inject-secret-chat-relay-key: kv/data/atlas/hermes/chat-telegram
|
||||||
@ -144,6 +147,152 @@ spec:
|
|||||||
resources:
|
resources:
|
||||||
requests: {cpu: 25m, memory: 32Mi}
|
requests: {cpu: 25m, memory: 32Mi}
|
||||||
limits: {cpu: 100m, memory: 64Mi}
|
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-hux-evidence
|
||||||
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: [/bin/sh, -ec]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
# HUX-12 enablement is fail-closed: until the Vault annotation
|
||||||
|
# projects hux-evidence-key, nothing is staged and the evidence
|
||||||
|
# capability stays off. The key is staged only for the hux
|
||||||
|
# service and producer containers, never for hermes or webui.
|
||||||
|
if [ -s /vault/secrets/hux-evidence-key ]; then
|
||||||
|
umask 077
|
||||||
|
tr -d '\r\n' < /vault/secrets/hux-evidence-key > /hux-evidence/.evidence-key.tmp
|
||||||
|
chown 10000:10000 /hux-evidence/.evidence-key.tmp
|
||||||
|
chmod 0400 /hux-evidence/.evidence-key.tmp
|
||||||
|
mv /hux-evidence/.evidence-key.tmp /hux-evidence/evidence-key
|
||||||
|
fi
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
add: [CHOWN, DAC_OVERRIDE, FOWNER]
|
||||||
|
runAsUser: 0
|
||||||
|
runAsGroup: 0
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
volumeMounts:
|
||||||
|
- {name: hux-evidence-key, mountPath: /hux-evidence}
|
||||||
|
resources:
|
||||||
|
requests: {cpu: 10m, memory: 16Mi}
|
||||||
|
limits: {cpu: 50m, memory: 32Mi}
|
||||||
- name: stage-runtime-access
|
- name: stage-runtime-access
|
||||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
@ -267,10 +416,15 @@ spec:
|
|||||||
API_SERVER_KEY="$(tr -d '\r\n' < /runtime-access/chat-relay-key)"
|
API_SERVER_KEY="$(tr -d '\r\n' < /runtime-access/chat-relay-key)"
|
||||||
test -n "${API_SERVER_KEY}"
|
test -n "${API_SERVER_KEY}"
|
||||||
export API_SERVER_KEY
|
export API_SERVER_KEY
|
||||||
|
export HUX_TENANT_SLOT="slot-${ordinal}"
|
||||||
exec /opt/hermes/.venv/bin/hermes gateway run
|
exec /opt/hermes/.venv/bin/hermes gateway run
|
||||||
ports:
|
ports:
|
||||||
- {name: api, containerPort: 8642, protocol: TCP}
|
- {name: api, containerPort: 8642, protocol: TCP}
|
||||||
env:
|
env:
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
- {name: HERMES_HOME, value: /opt/data}
|
- {name: HERMES_HOME, value: /opt/data}
|
||||||
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
||||||
- {name: HOME, value: /opt/data/home}
|
- {name: HOME, value: /opt/data/home}
|
||||||
@ -288,6 +442,16 @@ spec:
|
|||||||
- {name: HERMES_IMAGE_BROKER_URL, value: 'http://hermes-image-broker.hermes.svc.cluster.local:9002'}
|
- {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_IMAGE_BROKER_KEY_FILE, value: /runtime-access/chat-relay-key}
|
||||||
- {name: HERMES_AUTO_ROUTER_PROFILE, value: chat}
|
- {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:
|
volumeMounts:
|
||||||
- {name: home, mountPath: /opt/data}
|
- {name: home, mountPath: /opt/data}
|
||||||
- {name: workspace, mountPath: /opt/data/workspace}
|
- {name: workspace, mountPath: /opt/data/workspace}
|
||||||
@ -300,6 +464,10 @@ spec:
|
|||||||
- {name: subprocess-secret-patch, mountPath: /opt/hermes/tools/process_registry.py, subPath: process_registry.py}
|
- {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: 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: 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:
|
readinessProbe:
|
||||||
tcpSocket: {port: api}
|
tcpSocket: {port: api}
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 30
|
||||||
@ -333,6 +501,10 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- {name: webui, containerPort: 8787, protocol: TCP}
|
- {name: webui, containerPort: 8787, protocol: TCP}
|
||||||
env:
|
env:
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
- {name: HERMES_HOME, value: /opt/data}
|
- {name: HERMES_HOME, value: /opt/data}
|
||||||
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
- {name: HERMES_AUTH_FILE, value: /runtime-access/hermes-auth.json}
|
||||||
- {name: HOME, value: /opt/data/home}
|
- {name: HOME, value: /opt/data/home}
|
||||||
@ -361,12 +533,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_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_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: 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:
|
volumeMounts:
|
||||||
- {name: home, mountPath: /opt/data}
|
- {name: home, mountPath: /opt/data}
|
||||||
- {name: workspace, mountPath: /opt/data/workspace}
|
- {name: workspace, mountPath: /opt/data/workspace}
|
||||||
- {name: runtime-access, mountPath: /runtime-access, readOnly: true}
|
- {name: runtime-access, mountPath: /runtime-access, readOnly: true}
|
||||||
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
||||||
- {name: tmp, mountPath: /tmp}
|
- {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:
|
readinessProbe:
|
||||||
httpGet: {path: /health, port: webui}
|
httpGet: {path: /health, port: webui}
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 10
|
||||||
@ -389,6 +565,137 @@ spec:
|
|||||||
resources:
|
resources:
|
||||||
requests: {cpu: 100m, memory: 224Mi}
|
requests: {cpu: 100m, memory: 224Mi}
|
||||||
limits: {cpu: 750m, memory: 1Gi}
|
limits: {cpu: 750m, memory: 1Gi}
|
||||||
|
- name: hux
|
||||||
|
image: registry.bstein.dev/bstein/hermes-webui:git-2f535d3a30633c7f3d27c8020c5ca21c0d0f9503-build-21-release@sha256:e5b9b2fa8296a7b8a6065c63e7b288cd3305d70a3acc4312055b819bb5f64c04 # {"$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'}
|
||||||
|
- {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-2f535d3a30633c7f3d27c8020c5ca21c0d0f9503-build-21-release'} # {"$imagepolicy": "hermes:hermes-webui-release:tag"}
|
||||||
|
- {name: HUX_IMAGE_DIGEST, value: 'sha256:e5b9b2fa8296a7b8a6065c63e7b288cd3305d70a3acc4312055b819bb5f64c04'} # {"$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_RELEASE_EVIDENCE_KEY_FILE, value: /run/hermes-hux-evidence/evidence-key}
|
||||||
|
- {name: HUX_RELEASE_EVIDENCE_POLICY_FILE, value: /etc/hux-evidence/policy.json}
|
||||||
|
- {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}
|
||||||
|
- {name: hux-evidence-key, mountPath: /run/hermes-hux-evidence, readOnly: true}
|
||||||
|
- {name: hux-evidence-policy, mountPath: /etc/hux-evidence, readOnly: true}
|
||||||
|
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: hux-evidence-producer
|
||||||
|
image: registry.bstein.dev/bstein/hermes-webui:git-2f535d3a30633c7f3d27c8020c5ca21c0d0f9503-build-21-release@sha256:e5b9b2fa8296a7b8a6065c63e7b288cd3305d70a3acc4312055b819bb5f64c04 # {"$imagepolicy": "hermes:hermes-webui-release"}
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: [/bin/sh, -ec]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
ordinal="${HOSTNAME##*-}"
|
||||||
|
export HUX_TENANT_SLOT="slot-${ordinal}"
|
||||||
|
while true; do
|
||||||
|
if [ -s /run/hermes-hux-evidence/evidence-key ] \
|
||||||
|
&& [ -s /run/hermes-hux-subject/subject ] \
|
||||||
|
&& [ -n "${HUX_PRODUCER_PROJECT_ID:-}" ] \
|
||||||
|
&& [ -n "${HUX_PRODUCER_CONVERSATION_ID:-}" ]; then
|
||||||
|
HUX_PRODUCER_SUBJECT="$(tr -d '\r\n' < /run/hermes-hux-subject/subject)" \
|
||||||
|
/opt/hermes/.venv/bin/python -c \
|
||||||
|
'from hux_producer import run_once; run_once()' || true
|
||||||
|
fi
|
||||||
|
sleep 60
|
||||||
|
done
|
||||||
|
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_BASE_URL, value: 'http://127.0.0.1:8790'}
|
||||||
|
- {name: HUX_PRODUCER_WORKLOAD, value: hermes-webui}
|
||||||
|
- {name: HUX_PRODUCER_NAMESPACE, value: hermes}
|
||||||
|
- {name: HUX_PRODUCER_POD_SELECTOR, value: 'app=hermes-chat-tenant'}
|
||||||
|
- {name: HUX_PRODUCER_WORKLOAD_KIND, value: statefulset}
|
||||||
|
- {name: HUX_PRODUCER_WORKLOAD_NAME, value: hermes-chat-tenant}
|
||||||
|
- {name: HUX_PRODUCER_TIMEOUT_SECONDS, value: "10"}
|
||||||
|
- {name: HUX_RELEASE_EVIDENCE_KEY_FILE, value: /run/hermes-hux-evidence/evidence-key}
|
||||||
|
- {name: HUX_RELEASE_EVIDENCE_POLICY_FILE, value: /etc/hux-evidence/policy.json}
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: hermes-hux-evidence-scope
|
||||||
|
optional: true
|
||||||
|
volumeMounts:
|
||||||
|
- {name: hux-evidence-key, mountPath: /run/hermes-hux-evidence, readOnly: true}
|
||||||
|
- {name: hux-evidence-policy, mountPath: /etc/hux-evidence, readOnly: true}
|
||||||
|
- {name: hux-data, mountPath: /run/hermes-hux-subject, subPathExpr: $(POD_NAME)/binding, readOnly: true}
|
||||||
|
- {name: hux-tmp, mountPath: /tmp}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsUser: 10000
|
||||||
|
runAsGroup: 10000
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
resources:
|
||||||
|
requests: {cpu: 10m, memory: 48Mi}
|
||||||
|
limits: {cpu: 100m, memory: 128Mi}
|
||||||
- name: telegram-media
|
- name: telegram-media
|
||||||
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
image: registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
@ -452,9 +759,44 @@ spec:
|
|||||||
- name: image-plugin
|
- name: image-plugin
|
||||||
configMap:
|
configMap:
|
||||||
name: hermes-chat-image-plugin
|
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: emitters.py, path: emitters.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
|
- name: tmp
|
||||||
emptyDir:
|
emptyDir:
|
||||||
sizeLimit: 256Mi
|
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-evidence-key
|
||||||
|
emptyDir:
|
||||||
|
medium: Memory
|
||||||
|
sizeLimit: 1Mi
|
||||||
|
- name: hux-evidence-policy
|
||||||
|
configMap:
|
||||||
|
name: hermes-hux-evidence-policy
|
||||||
|
defaultMode: 0444
|
||||||
|
- name: hux-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: hermes-chat-hux-data
|
||||||
volumeClaimTemplates:
|
volumeClaimTemplates:
|
||||||
- metadata:
|
- metadata:
|
||||||
name: home
|
name: home
|
||||||
|
|||||||
13
services/hermes/hux-evidence-policy.json
Normal file
13
services/hermes/hux-evidence-policy.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"schema": "hux.release_evidence_policy.v1",
|
||||||
|
"max_evidence_age_seconds": 900,
|
||||||
|
"workloads": {
|
||||||
|
"hermes-webui": {
|
||||||
|
"review_url_prefix": "https://scm.bstein.dev/atlas/titan-iac/pulls/",
|
||||||
|
"jenkins_job_url": "https://jenkins.bstein.dev/job/hermes-webui-image",
|
||||||
|
"image_repository": "registry.bstein.dev/bstein/hermes-webui",
|
||||||
|
"flux_kustomization": "hermes",
|
||||||
|
"health_url": "https://chat.bstein.dev/healthz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
services/hermes/hux-evidence-rbac.yaml
Normal file
54
services/hermes/hux-evidence-rbac.yaml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# services/hermes/hux-evidence-rbac.yaml
|
||||||
|
# Read-only facts for the HUX-12 evidence producer sidecar: Ready pod
|
||||||
|
# imageIDs and the desired StatefulSet image in this namespace, plus the
|
||||||
|
# single named Flux Kustomization's applied revision. The pod service
|
||||||
|
# account gains nothing writable and nothing secret.
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: hermes-hux-evidence-read
|
||||||
|
namespace: hermes
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [pods]
|
||||||
|
verbs: [get, list]
|
||||||
|
- apiGroups: [apps]
|
||||||
|
resources: [statefulsets]
|
||||||
|
verbs: [get]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: hermes-hux-evidence-read
|
||||||
|
namespace: hermes
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: hermes-hux-evidence-read
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: hermes-chat
|
||||||
|
namespace: hermes
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: hermes-hux-evidence-kustomization-read
|
||||||
|
rules:
|
||||||
|
- apiGroups: [kustomize.toolkit.fluxcd.io]
|
||||||
|
resources: [kustomizations]
|
||||||
|
resourceNames: [hermes]
|
||||||
|
verbs: [get]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: hermes-hux-evidence-kustomization-read
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: hermes-hux-evidence-kustomization-read
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: hermes-chat
|
||||||
|
namespace: hermes
|
||||||
@ -43,6 +43,7 @@ resources:
|
|||||||
- agent-certificate.yaml
|
- agent-certificate.yaml
|
||||||
- agent-ingress.yaml
|
- agent-ingress.yaml
|
||||||
- execution-worker-rbac.yaml
|
- execution-worker-rbac.yaml
|
||||||
|
- hux-evidence-rbac.yaml
|
||||||
- execution-worker-statefulset.yaml
|
- execution-worker-statefulset.yaml
|
||||||
- execution-mediator.yaml
|
- execution-mediator.yaml
|
||||||
- execution-worker-networkpolicy.yaml
|
- execution-worker-networkpolicy.yaml
|
||||||
@ -196,6 +197,26 @@ configMapGenerator:
|
|||||||
- dashboard-style.css=plugins/auto-router/dashboard/dist/style.css
|
- dashboard-style.css=plugins/auto-router/dashboard/dist/style.css
|
||||||
options:
|
options:
|
||||||
disableNameSuffixHash: true
|
disableNameSuffixHash: true
|
||||||
|
- name: hermes-hux-evidence-policy
|
||||||
|
namespace: hermes
|
||||||
|
files:
|
||||||
|
- policy.json=hux-evidence-policy.json
|
||||||
|
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
|
||||||
|
- emitters.py=plugins/hux-runtime/emitters.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
|
- name: hermes-chat-image-plugin
|
||||||
namespace: hermes
|
namespace: hermes
|
||||||
files:
|
files:
|
||||||
|
|||||||
@ -406,6 +406,22 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- {protocol: TCP, port: 9005}
|
- {protocol: TCP, port: 9005}
|
||||||
- {protocol: TCP, port: 9009}
|
- {protocol: TCP, port: 9009}
|
||||||
|
# HUX-12 evidence producer: the Kubernetes API (ClusterIP) for pod and
|
||||||
|
# Flux facts, and the ingress edge for Jenkins/Harbor/health receipts.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.43.0.1/32
|
||||||
|
ports:
|
||||||
|
- {protocol: TCP, port: 443}
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: traefik
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik
|
||||||
|
ports:
|
||||||
|
- {protocol: TCP, port: 443}
|
||||||
- to:
|
- to:
|
||||||
- podSelector:
|
- podSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
|
|||||||
@ -204,6 +204,7 @@ spec:
|
|||||||
- --redis-connection-url=redis://hermes-oauth-sessions.hermes.svc.cluster.local:6379/1
|
- --redis-connection-url=redis://hermes-oauth-sessions.hermes.svc.cluster.local:6379/1
|
||||||
- --custom-templates-dir=/etc/oauth2-proxy/templates
|
- --custom-templates-dir=/etc/oauth2-proxy/templates
|
||||||
- '--skip-auth-route=GET=^/sw[.]js([?].*)?$'
|
- '--skip-auth-route=GET=^/sw[.]js([?].*)?$'
|
||||||
|
- '--skip-auth-route=GET=^/healthz$'
|
||||||
- --api-route=^/api/
|
- --api-route=^/api/
|
||||||
- --api-route=^/health$
|
- --api-route=^/health$
|
||||||
- --upstream=http://hermes-chat-router.hermes.svc.cluster.local:8080
|
- --upstream=http://hermes-chat-router.hermes.svc.cluster.local:8080
|
||||||
|
|||||||
@ -95,7 +95,11 @@ def test_hux_sidecar_is_loopback_only_and_uses_the_reviewed_webui_image() -> Non
|
|||||||
assert values["HUX_BIND"] == "127.0.0.1"
|
assert values["HUX_BIND"] == "127.0.0.1"
|
||||||
assert values["HUX_PORT"] == "8790"
|
assert values["HUX_PORT"] == "8790"
|
||||||
assert values["HUX_DATA_ROOT"] == "/var/lib/hux/store"
|
assert values["HUX_DATA_ROOT"] == "/var/lib/hux/store"
|
||||||
assert set(values["HUX_FLAGS"].split(",")) == ALL_FLAGS
|
# Progressive enablement: the canary starts foundation-only and cards are
|
||||||
|
# switched on one lifecycle gate at a time; unknown flags never ship.
|
||||||
|
flags = {flag for flag in values["HUX_FLAGS"].split(",") if flag}
|
||||||
|
assert "hux.foundation" in flags
|
||||||
|
assert flags <= ALL_FLAGS
|
||||||
assert set(values["HUX_SWITCHYARD_ROUTE_CATALOG"].split(",")) == {
|
assert set(values["HUX_SWITCHYARD_ROUTE_CATALOG"].split(",")) == {
|
||||||
"atlas/manual/codex/luna",
|
"atlas/manual/codex/luna",
|
||||||
"atlas/manual/codex/terra",
|
"atlas/manual/codex/terra",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user