From cf723141a4a31882d2f67d80a785e07cb8ca9f44 Mon Sep 17 00:00:00 2001 From: Svrnty Date: Thu, 28 May 2026 21:38:50 -0400 Subject: [PATCH] Implement S23 runtime health slice --- CONNECTION-MAP.md | 7 +- plugin.py | 4 + routes/cortex_os_runtime_health.py | 142 ++++++++++++++++++ .../runtime-health/runtime_health.css | 72 +++++++++ .../runtime-health/runtime_health.js | 107 +++++++++++++ tests/integration/test_loader_contract.py | 3 + .../test_cortex_os_runtime_health_route.py | 87 +++++++++++ .../test_cortex_os_runtime_health_static.py | 45 ++++++ 8 files changed, 466 insertions(+), 1 deletion(-) create mode 100644 routes/cortex_os_runtime_health.py create mode 100644 static/cortex-os/runtime-health/runtime_health.css create mode 100644 static/cortex-os/runtime-health/runtime_health.js create mode 100644 tests/unit/test_cortex_os_runtime_health_route.py create mode 100644 tests/unit/test_cortex_os_runtime_health_static.py diff --git a/CONNECTION-MAP.md b/CONNECTION-MAP.md index 282eb1d..1b2b93e 100644 --- a/CONNECTION-MAP.md +++ b/CONNECTION-MAP.md @@ -2,7 +2,7 @@ **Upstream version:** v0.51.118 **Plugin version:** 0.5.0 -**Total dependencies:** 34 (25 public API · 0 forced internal · 9 frontend) +**Total dependencies:** 39 (29 public API · 0 forced internal · 10 frontend) > **Auto-generated by `scripts/ast-connection-map.py`. Do not hand-edit.** > To change a justification, edit the `# CONNECTION:` comment above the @@ -25,12 +25,16 @@ | `plugin.py:46` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/bte.js")` | | `plugin.py:48` | `api.inject_stylesheet` | `api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/umbrella_inline.css")` | | `plugin.py:49` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/umbrella_inline.js")` | +| `plugin.py:51` | `api.inject_stylesheet` | `api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/cortex-os/runtime-health/runtime_health.c` | +| `plugin.py:52` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/cortex-os/runtime-health/runtime_health.js")` | | `routes/adwright.py:68` | `api.logger` | `log = api.logger("svrnty.routes.adwright")` | | `routes/adwright.py:69` | `api.register_route` | `api.register_route(` | | `routes/adwright.py:71` | `api.register_route` | `api.register_route(` | | `routes/bte_proxy.py:90` | `api.logger` | `log = api.logger("svrnty.routes.bte_proxy")` | | `routes/bte_proxy.py:91` | `api.register_route` | `api.register_route("/api/bte/proxy", "GET", _handle_proxy)` | | `routes/bte_proxy.py:92` | `api.register_route` | `api.register_route("/api/bte/proxy", "POST", _handle_proxy)` | +| `routes/cortex_os_runtime_health.py:26` | `api.logger` | `log = api.logger("svrnty.routes.cortex_os_runtime_health")` | +| `routes/cortex_os_runtime_health.py:27` | `api.register_route` | `api.register_route(ROUTE_PATH, ROUTE_METHOD, _handle_runtime_health)` | | `routes/transcribe.py:37` | `api.logger` | `log = api.logger("svrnty.routes.transcribe")` | | `routes/transcribe.py:38` | `api.register_route` | `api.register_route("/api/transcribe", "POST", _handle_transcribe)` | | `routes/transcribe.py:39` | `api.register_audio_attachment_processor` | `api.register_audio_attachment_processor(_transcribe_audio_attachments)` | @@ -63,4 +67,5 @@ _None. Plugin uses only the public API._ ✓ | `static/adwright.js` | 606 | `/api/adwright/provision-creds` | | `static/umbrella.js` | 57 | `/api/umbrella` | | `static/app.js` | 165 | `/api/vault/status` | +| `static/cortex-os/runtime-health/runtime_health.js` | 4 | `/api/cortex-os/runtime-health` | diff --git a/plugin.py b/plugin.py index 3bb8eb1..0da9674 100644 --- a/plugin.py +++ b/plugin.py @@ -47,6 +47,9 @@ def register(api): # Inline Umbrella graph for the Hermes Workspace right panel. api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/umbrella_inline.css") api.inject_script(f"/plugins/{STATIC_PREFIX}/umbrella_inline.js") + # Cortex OS Runtime Health slice: read-only route status display. + api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/cortex-os/runtime-health/runtime_health.css") + api.inject_script(f"/plugins/{STATIC_PREFIX}/cortex-os/runtime-health/runtime_health.js") log.info("static + assets wired at /plugins/%s/", STATIC_PREFIX) # Routes — each feature lives in its own module under routes/. @@ -77,4 +80,5 @@ def _phase2_routes(): "adwright", # P2.C — Adwright tool panel routes (PRD §5+§6) ✓ "bte_proxy", # P2.D — BTE Command Center same-origin proxy (PRD §3) ✓ "umbrella", # P2.E — cortex-os umbrella graph viz (UMBRELLA-VIZ-PRD) ✓ + "cortex_os_runtime_health", # S23.0-I4 — Cortex OS Runtime Health read-only slice ✓ ] diff --git a/routes/cortex_os_runtime_health.py b/routes/cortex_os_runtime_health.py new file mode 100644 index 0000000..da93a97 --- /dev/null +++ b/routes/cortex_os_runtime_health.py @@ -0,0 +1,142 @@ +"""GET /api/cortex-os/runtime-health - Cortex OS Runtime Health slice. + +Public API surface used: api.register_route, api.logger. +No forced internal dependencies. This module does not import Hermes internals. +""" + +from __future__ import annotations + +import json +import re +from typing import Any + +ROUTE_PATH = "/api/cortex-os/runtime-health" +ROUTE_METHOD = "GET" +CONTRACT_ID = "runtime-health/v0.1" +CHECKED_AT = "2026-05-29T00:00:00Z" + +_FORBIDDEN_TEXT = re.compile( + r"(https?://|/home/|workspaces/|\b\d{2,5}\b|token|secret|cookie|traceback|exception|\.env)", + re.IGNORECASE, +) + + +def register(api: Any) -> None: + """Wire the read-only Runtime Health route.""" + log = api.logger("svrnty.routes.cortex_os_runtime_health") + api.register_route(ROUTE_PATH, ROUTE_METHOD, _handle_runtime_health) + log.info("cortex os runtime health endpoint registered") + + +def _handle_runtime_health(handler: Any, parsed: Any) -> bool: + """Handler signature matches the plugin loader contract.""" + if getattr(handler, "command", ROUTE_METHOD) != ROUTE_METHOD: + _write_json(handler, 405, _error_envelope("method_not_allowed", "read only route")) + return True + if getattr(parsed, "query", ""): + _write_json(handler, 400, _error_envelope("query_not_allowed", "query targets are not accepted")) + return True + + _write_json(handler, 200, {"ok": True, "result": runtime_health_payload(), "error": None}) + return True + + +def runtime_health_payload(host_signals: dict[str, Any] | None = None) -> dict[str, Any]: + """Return the host-neutral Runtime Health envelope.""" + signals = _summarize_host_signals(host_signals or {}) + status = _derive_status(signals) + return { + "contract_id": CONTRACT_ID, + "checked_at": CHECKED_AT, + "status": status, + "readiness": "runtime_not_started", + "summary": _summary_for(status), + "authority": { + "read_only": True, + "runtime_state_mutation": False, + "tool_callable_authority": False, + "mcp_exposure": False, + "profile_exposure_change": False, + "memory_domain_access": False, + "delegated_memory_grant": False, + "sharing": False, + "installer_automation": False, + "product_readiness_claim": False, + }, + "signals": signals, + "warnings": ["deterministic_host_surface_inputs_only"], + "errors": [], + "redactions": ["host_specific_values", "raw_paths", "raw_urls", "secrets", "raw_payloads"], + "source_trace": { + "host_adapter": "hermes", + "host_surfaces": ["health", "agent_health", "dashboard_status"], + "live_probe": False, + "raw_payload_passthrough": False, + }, + } + + +def _summarize_host_signals(host_signals: dict[str, Any]) -> list[dict[str, str]]: + names = ["health", "agent_health", "dashboard_status"] + if not host_signals: + return [ + {"name": name, "status": "unknown", "detail": "not_probed"} + for name in names + ] + return [ + { + "name": name, + "status": _clean_status(host_signals.get(name, "unknown")), + "detail": _bounded_text(host_signals.get(f"{name}_detail", "declared_surface")), + } + for name in names + ] + + +def _derive_status(signals: list[dict[str, str]]) -> str: + statuses = {signal["status"] for signal in signals} + if "unavailable" in statuses: + return "unavailable" + if "degraded" in statuses: + return "degraded" + if statuses == {"healthy"}: + return "healthy" + return "unknown" + + +def _clean_status(value: Any) -> str: + text = str(value).strip().lower() + return text if text in {"healthy", "degraded", "unavailable", "unknown"} else "unknown" + + +def _summary_for(status: str) -> str: + if status == "healthy": + return "Runtime Health signals are healthy." + if status == "degraded": + return "Runtime Health signals are degraded." + if status == "unavailable": + return "Runtime Health signals are unavailable." + return "Runtime Health has not been live-probed in this slice." + + +def _bounded_text(value: Any) -> str: + text = str(value).strip().replace("\n", " ") + if not text: + return "redacted" + if _FORBIDDEN_TEXT.search(text): + return "redacted" + return text[:80] + + +def _error_envelope(code: str, message: str) -> dict[str, Any]: + return {"ok": False, "result": None, "error": {"code": code, "message": message}} + + +def _write_json(handler: Any, status_code: int, payload: dict[str, Any]) -> None: + body = json.dumps(payload, sort_keys=True).encode("utf-8") + handler.send_response(status_code) + handler.send_header("Content-Type", "application/json; charset=utf-8") + handler.send_header("Content-Length", str(len(body))) + handler.send_header("Cache-Control", "no-store") + handler.end_headers() + handler.wfile.write(body) diff --git a/static/cortex-os/runtime-health/runtime_health.css b/static/cortex-os/runtime-health/runtime_health.css new file mode 100644 index 0000000..8ac21c6 --- /dev/null +++ b/static/cortex-os/runtime-health/runtime_health.css @@ -0,0 +1,72 @@ +.cortex-os-runtime-health { + border: 1px solid rgba(19, 82, 121, 0.22); + border-radius: 8px; + margin: 16px; + max-width: 520px; + padding: 14px 16px; + background: #f7fbff; + color: #18212b; + font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +.cortex-os-runtime-health h2 { + margin: 0 0 8px; + font-size: 15px; + font-weight: 700; +} + +.cortex-os-runtime-health__badge { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 0 8px; + border-radius: 999px; + background: #dde8f0; + color: #18212b; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; +} + +.cortex-os-runtime-health[data-state="healthy"] .cortex-os-runtime-health__badge { + background: #d8f1df; + color: #0d5b27; +} + +.cortex-os-runtime-health[data-state="degraded"] .cortex-os-runtime-health__badge, +.cortex-os-runtime-health[data-state="unknown"] .cortex-os-runtime-health__badge, +.cortex-os-runtime-health[data-state="loading"] .cortex-os-runtime-health__badge { + background: #fff0c9; + color: #6a4a00; +} + +.cortex-os-runtime-health[data-state="unavailable"] .cortex-os-runtime-health__badge, +.cortex-os-runtime-health[data-state="error"] .cortex-os-runtime-health__badge, +.cortex-os-runtime-health[data-state="redacted"] .cortex-os-runtime-health__badge { + background: #ffe0dd; + color: #7a1e17; +} + +.cortex-os-runtime-health__summary { + margin: 10px 0; +} + +.cortex-os-runtime-health__signals { + display: grid; + grid-template-columns: minmax(120px, 1fr) minmax(92px, auto); + gap: 6px 12px; + margin: 0; +} + +.cortex-os-runtime-health__signals dt, +.cortex-os-runtime-health__signals dd { + margin: 0; +} + +.cortex-os-runtime-health__signals dt { + color: #44515f; +} + +.cortex-os-runtime-health__signals dd { + font-weight: 700; +} diff --git a/static/cortex-os/runtime-health/runtime_health.js b/static/cortex-os/runtime-health/runtime_health.js new file mode 100644 index 0000000..5a5399f --- /dev/null +++ b/static/cortex-os/runtime-health/runtime_health.js @@ -0,0 +1,107 @@ +(function () { + "use strict"; + + var endpoint = "/api/cortex-os/runtime-health"; + var states = ["healthy", "degraded", "unavailable", "unknown", "loading", "error", "redacted"]; + + if (window.__cortexOsRuntimeHealthLoaded) { + return; + } + window.__cortexOsRuntimeHealthLoaded = true; + + function ensurePanel() { + var panel = document.querySelector("[data-cortex-os-runtime-health]"); + if (panel) { + return panel; + } + + panel = document.createElement("section"); + panel.className = "cortex-os-runtime-health"; + panel.setAttribute("data-cortex-os-runtime-health", "true"); + panel.setAttribute("data-state", "loading"); + + var title = document.createElement("h2"); + title.textContent = "Cortex OS Runtime Health"; + + var badge = document.createElement("span"); + badge.className = "cortex-os-runtime-health__badge"; + badge.setAttribute("data-role", "status"); + badge.textContent = "loading"; + + var summary = document.createElement("p"); + summary.className = "cortex-os-runtime-health__summary"; + summary.setAttribute("data-role", "summary"); + summary.textContent = "Checking Runtime Health."; + + var list = document.createElement("dl"); + list.className = "cortex-os-runtime-health__signals"; + list.setAttribute("data-role", "signals"); + + panel.appendChild(title); + panel.appendChild(badge); + panel.appendChild(summary); + panel.appendChild(list); + + var target = document.querySelector("main") || document.body; + target.appendChild(panel); + return panel; + } + + function setState(panel, state, summary, signals) { + var nextState = states.indexOf(state) >= 0 ? state : "unknown"; + panel.setAttribute("data-state", nextState); + panel.querySelector("[data-role='status']").textContent = nextState; + panel.querySelector("[data-role='summary']").textContent = summary || "redacted"; + renderSignals(panel.querySelector("[data-role='signals']"), signals || []); + } + + function renderSignals(list, signals) { + list.textContent = ""; + signals.slice(0, 3).forEach(function (signal) { + var name = document.createElement("dt"); + var value = document.createElement("dd"); + name.textContent = signal.name || "redacted"; + value.textContent = signal.status || "unknown"; + list.appendChild(name); + list.appendChild(value); + }); + } + + function renderPayload(panel, payload) { + if (!payload || payload.ok !== true || !payload.result) { + setState(panel, "error", "Runtime Health is unavailable.", []); + return; + } + setState(panel, payload.result.status, payload.result.summary, payload.result.signals); + } + + function loadRuntimeHealth() { + var panel = ensurePanel(); + setState(panel, "loading", "Checking Runtime Health.", []); + + fetch(endpoint, { + method: "GET", + headers: { Accept: "application/json" }, + credentials: "same-origin", + cache: "no-store" + }) + .then(function (response) { + if (!response.ok) { + throw new Error("runtime-health-unavailable"); + } + return response.json(); + }) + .then(function (payload) { + renderPayload(panel, payload); + }) + .catch(function () { + setState(panel, "error", "Runtime Health is unavailable.", []); + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", loadRuntimeHealth, { once: true }); + } else { + loadRuntimeHealth(); + } +})(); diff --git a/tests/integration/test_loader_contract.py b/tests/integration/test_loader_contract.py index 52b02fa..0bcdb25 100644 --- a/tests/integration/test_loader_contract.py +++ b/tests/integration/test_loader_contract.py @@ -62,12 +62,15 @@ def test_loader_register_wires_our_plugin(loader, monkeypatch): assert ("GET", "/api/vault/status") in loader._ROUTES assert ("GET", "/api/umbrella") in loader._ROUTES assert ("GET", "/api/umbrella/doc") in loader._ROUTES + assert ("GET", "/api/cortex-os/runtime-health") in loader._ROUTES # Static + injected URLs assert "svrnty" in loader._STATIC assert "/plugins/svrnty/app.css" in loader._STYLESHEETS assert "/plugins/svrnty/app.js" in loader._SCRIPTS assert "/plugins/svrnty/umbrella_inline.css" in loader._STYLESHEETS assert "/plugins/svrnty/umbrella_inline.js" in loader._SCRIPTS + assert "/plugins/svrnty/cortex-os/runtime-health/runtime_health.css" in loader._STYLESHEETS + assert "/plugins/svrnty/cortex-os/runtime-health/runtime_health.js" in loader._SCRIPTS # Audio processor for voice-message transcription assert len(loader._AUDIO_PROCESSORS) == 1 diff --git a/tests/unit/test_cortex_os_runtime_health_route.py b/tests/unit/test_cortex_os_runtime_health_route.py new file mode 100644 index 0000000..b6500ec --- /dev/null +++ b/tests/unit/test_cortex_os_runtime_health_route.py @@ -0,0 +1,87 @@ +import io +import json +from types import SimpleNamespace +from unittest.mock import Mock + +from routes import cortex_os_runtime_health as route + + +class FakeHandler: + def __init__(self, command="GET"): + self.command = command + self.status_code = None + self.headers = [] + self.wfile = io.BytesIO() + + def send_response(self, status_code): + self.status_code = status_code + + def send_header(self, name, value): + self.headers.append((name, value)) + + def end_headers(self): + return None + + def payload(self): + return json.loads(self.wfile.getvalue().decode("utf-8")) + + +def test_register_wires_get_runtime_health_route(): + api = Mock() + api.logger.return_value = Mock() + + route.register(api) + + api.register_route.assert_called_once_with( + "/api/cortex-os/runtime-health", + "GET", + route._handle_runtime_health, + ) + + +def test_get_returns_runtime_health_envelope(): + handler = FakeHandler() + + assert route._handle_runtime_health(handler, SimpleNamespace(query="")) is True + + payload = handler.payload() + assert handler.status_code == 200 + assert payload["ok"] is True + assert payload["result"]["contract_id"] == "runtime-health/v0.1" + assert payload["result"]["status"] == "unknown" + assert payload["result"]["source_trace"]["live_probe"] is False + assert payload["result"]["authority"]["runtime_state_mutation"] is False + + +def test_host_signal_mapping_is_closed_and_redacted(): + payload = route.runtime_health_payload( + { + "health": "healthy", + "agent_health": "degraded", + "dashboard_status": "unavailable", + "health_detail": "http://localhost:8000/raw", + "agent_health_detail": "/home/svrnty/private", + "dashboard_status_detail": "token=abc123", + } + ) + + assert payload["status"] == "unavailable" + assert {signal["detail"] for signal in payload["signals"]} == {"redacted"} + assert payload["source_trace"]["host_surfaces"] == [ + "health", + "agent_health", + "dashboard_status", + ] + + +def test_non_get_and_query_targets_are_rejected(): + post_handler = FakeHandler(command="POST") + query_handler = FakeHandler() + + route._handle_runtime_health(post_handler, SimpleNamespace(query="")) + route._handle_runtime_health(query_handler, SimpleNamespace(query="target=raw")) + + assert post_handler.status_code == 405 + assert post_handler.payload()["ok"] is False + assert query_handler.status_code == 400 + assert query_handler.payload()["error"]["code"] == "query_not_allowed" diff --git a/tests/unit/test_cortex_os_runtime_health_static.py b/tests/unit/test_cortex_os_runtime_health_static.py new file mode 100644 index 0000000..0fd32a6 --- /dev/null +++ b/tests/unit/test_cortex_os_runtime_health_static.py @@ -0,0 +1,45 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +JS = ROOT / "static" / "cortex-os" / "runtime-health" / "runtime_health.js" +CSS = ROOT / "static" / "cortex-os" / "runtime-health" / "runtime_health.css" + + +def test_runtime_health_assets_exist_and_render_all_states(): + js = JS.read_text(encoding="utf-8") + css = CSS.read_text(encoding="utf-8") + + assert "/api/cortex-os/runtime-health" in js + for state in ["healthy", "degraded", "unavailable", "unknown", "loading", "error", "redacted"]: + assert state in js + assert state in css + + +def test_runtime_health_display_has_no_hidden_write_surface(): + js = JS.read_text(encoding="utf-8") + forbidden = [ + "localStorage", + "sessionStorage", + "document.cookie", + "