EPIC-4 final slice. Replaces the Logs/Exec placeholders shipped by R
(#1167) with target-state implementations and lays the surface for the
Guacamole-fronted recorded shell flow.
UI (catalyst-ui):
- widgets/cloud-list/LogViewer.tsx — xterm.js viewer for the X1
Pod-log WebSocket. Container picker (multi-container Pods),
search box (⌃F / ⌘F), 10k scrollback, reconnect-with-since on
disconnect (per X1 resume protocol).
- widgets/cloud-list/ExecPanel.tsx — Open Shell button → POST
/k8s/exec/.../session → Guacamole iframe. 5s iframe-load timeout
OR onError → falls through to xterm.js + X1-style fallback
WebSocket; banner explains "recording disabled" on fallback.
- pages/sovereign/sessions/SessionsPage.tsx — guacamole session list
+ filter (pod/user) + paginate + Replay modal. Mounted on both
/provision/$id/sessions (mothership) and /sessions (chroot).
- pages/sovereign/cloud-list/ResourceDetailPage.tsx — Logs tab now
renders LogViewer; Exec tab now renders ExecPanel. Non-Pod kinds
surface a "drill into Tree to find Pods" hint.
- resource.api.ts — adds logsWebSocketURL + execWebSocketURL +
createExecSession + listSessions + getSessionReplay helpers (single
URL truth per INVIOLABLE-PRINCIPLES #4).
API (catalyst-api):
- internal/handler/k8s_exec.go — three new endpoints:
POST /api/v1/sovereigns/{id}/k8s/exec/{ns}/{pod}/{container}/session
(tier-developer or higher; calls GuacamoleClient.CreateSession;
emits guacamole-session-opened audit)
GET /api/v1/sovereigns/{id}/sessions?from=&to=&pod=&user=&page=
(tier-admin or higher; paginated; reads from GuacamoleClient
OR in-memory fallback when no client is wired)
GET /api/v1/sovereigns/{id}/sessions/{sessionId}/replay
(admin/owner only — sessions.playback per EPIC-3 §6.2; emits
guacamole-session-replayed audit)
- internal/handler/k8s_exec_ws.go — direct WebSocket exec fallback
(bidi pump; xterm.js client) for when Guacamole iframe is blocked.
- GuacamoleClient interface + in-memory fallback session store: the
chroot Sovereign / CI flow renders cleanly even when Guacamole isn't
deployed; production wires the real client via SetGuacamoleClient.
- Audit-type predicate IsGuacamoleAuditType + 3 canonical type names
(guacamole-session-opened/closed/replayed). Reuses the EPIC-3 U5-U8
audit Bus + the slice K+P+X1+G's reservation per the canonical seam
map; future audit consumers filter via prefix `guacamole-*`.
Tests:
- 9 LogViewer / ExecPanel / SessionsPage vitest test files, 38 tests
passing in `pages/sovereign/cloud-list/` + `widgets/cloud-list/` +
`pages/sovereign/sessions/`.
- 22 Go test functions in k8s_exec_test.go + k8s_exec_ws_test.go
covering happy/forbidden/not-found/audit-emit/pagination/filter
paths. `go test -count=1 -race ./internal/handler/` clean.
- 6 Playwright snapshot tests at 1440x900 in
`e2e/logs-exec-sessions.spec.ts` covering LogViewer / search box /
ExecPanel idle / ExecPanel post-click / SessionsPage list / filter.
`npm run typecheck` clean. `go vet ./...` clean. Pre-existing UI test
failures (12 files, 99 tests) confirmed identical to main per canon §7.
Co-authored-by: hatiyildiz <hati.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>