Rename two public Hermes hostnames across the repo:
chat.hermes.bstein.dev -> hermes.bstein.dev
triage.hermes.bstein.dev -> triage.bstein.dev
agent.hermes.bstein.dev is left untouched. That rename ships separately and
must merge last, because it is the coordinator's own access point.
Covers CoreDNS host entries, the shared hermes-sites certificate SANs and
the ingress rules, Keycloak OIDC redirect/origin/post-logout URLs,
oauth2-proxy redirect URLs, CORS and allowed-origin settings, dashboard
public URLs, the Ariadne proposal UI link, ZAP baseline scan targets, the
operator and agent instruction text, the Telegram sign-in prompts, and the
Docker/UI references.
Knowledge catalogs and the HTTP diagram were regenerated with
scripts/render/knowledge_render_atlas.py rather than hand-edited. Only the
rename delta is carried here: the committed catalogs on main are already
stale by roughly 1650 lines, and that drift is left for a separate cleanup.
Repo-only change. After merge it needs the Keycloak ensure script re-run and
certificate reissuance; existing oauth2-proxy sessions are invalidated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Returning to chat.hermes.bstein.dev after a Keycloak logout/login showed
"This session is unavailable to this account. Start a new chat." even
though the session was intact and owned by the same subject.
The banner comes from the continuity fallback the router injects into
every chat page. It polled `/api/sessions/<id>` and
`/api/sessions/<id>/messages` — routes that belong to the Hermes agent
dashboard (added by scripts/patch_web_session_activity.py, applied only
in agent-deployment.yaml). The router proxies browser traffic to the
tenant Hermes WebUI instead, whose only session read is
`GET /api/session?session_id=<id>`; the dashboard paths are unrouted
there, so server.py answered its generic 404 for every poll and the
fallback reported a false ownership failure.
The script runs only on a full document load of `/session/<id>`, which is
exactly what the OIDC round-trip produces when oauth2-proxy returns the
browser to `rd=/session/<id>` — hence the "only after relogin" symptom.
Poll the WebUI contract instead, and let its own answers decide what the
banner claims: 409 `session_profile_mismatch` is the single response that
means the session is outside this account's active scope, 404 now means
the conversation is no longer stored, and 401/403 still re-enter OIDC.
The steady-state poll drops to one request and backs off to 3s/15s now
that it reaches a real endpoint on the tenant Raspberry Pi.
`boundSessionSnapshot` follows the same move: it caps the WebUI envelope
`{"session": {..., "messages": [...]}}`, relaying every other session key
verbatim rather than re-serializing a fixed struct that would silently
drop metadata the banner depends on.
Isolation is unchanged and now covered: the router still resolves the
slot from the salted Keycloak subject, overwrites any client-supplied
X-Hermes-Tenant-Identity, and forwards only the two tenant cookies.
Tests: relogin keeps a stable slot and resolves the durable session; a
second subject replaying the owner's session id, WebUI cookie and a
forged tenant header gets 404 from its own backend and never reaches the
owner's; the legacy dashboard paths are pinned as permanent 404s against
a stub of the deployed WebUI dispatch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Extend the chat router HTML-injection bridge so tenants see a clean
chat + conversation-history experience instead of the full agent
cockpit. hideChatAdministration now hides the rail buttons and panels
for logs, insights, memory, skills, workspaces, todos, tasks and
profiles (alongside the existing Kanban), and bridgeCSS hides the same
[data-panel] targets pre-JS to avoid a flash. Also hide the redundant
legacy settings model dropdown (#settingsModel) via bridgeCSS while
keeping the composer routing chip. Chat, history/sessions, new-chat,
composer and the model chip are untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>