diff --git a/docs/API.md b/docs/API.md
index ce1538b..c000124 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -15,6 +15,8 @@ Health check.
{"ok": true}
```
+`controlAudit` mirrors the control plane's HTTP reject counters (405/415/413/400) so runtime telemetry can spot abusive clients and the UI can keep ops aware of guardrail hits.
+
`engine.state` spiegelt jetzt die Runtime-State-Maschine wider (idle, arming, prebuffering, running, degraded, muted, faulted, stopping) und bietet eine erste beobachtbare Basis für Fault-Transitions.
@@ -98,6 +100,12 @@ Live engine and driver telemetry. Only populated when TX is active.
"underrunStreak": 0,
"maxUnderrunStreak": 0,
"effectiveSampleRateHz": 2280000
+ },
+ "controlAudit": {
+ "methodNotAllowed": 0,
+ "unsupportedMediaType": 0,
+ "bodyTooLarge": 0,
+ "unexpectedBody": 0
}
}
```
diff --git a/internal/control/ui.html b/internal/control/ui.html
index eb211cd..38f1170 100644
--- a/internal/control/ui.html
+++ b/internal/control/ui.html
@@ -1193,6 +1193,19 @@ input.input-error {
+
+
+
Shortcuts
@@ -1974,6 +1987,7 @@ function render() {
updateText('info-live', engine.state ? `${String(engine.state).toUpperCase()} / ${state.server.runtimeOk ? 'runtime ok' : 'runtime pending'}` : (state.server.configOk ? 'config only' : '--'));
updateHealth(engine, driver, audioStream);
+ updateControlAudit(runtime.controlAudit);
updateFaultHistory(engine);
updateTransitionHistory();
updateResetHint(engine);
@@ -2248,6 +2262,40 @@ function updateHealth(engine, driver, audioStream) {
}
+
+function updateControlAudit(audit) {
+ const entries = [
+ { key: 'methodNotAllowed', id: 'audit-methodNotAllowed' },
+ { key: 'unsupportedMediaType', id: 'audit-unsupportedMediaType' },
+ { key: 'bodyTooLarge', id: 'audit-bodyTooLarge' },
+ { key: 'unexpectedBody', id: 'audit-unexpectedBody' },
+ ];
+ let total = 0;
+ let hasData = false;
+ entries.forEach(({ key, id }) => {
+ const raw = audit && typeof audit[key] !== 'undefined' ? Number(audit[key]) : NaN;
+ const value = Number.isFinite(raw) ? raw : null;
+ if (value != null) {
+ hasData = true;
+ total += value;
+ }
+ setAuditValue(id, value);
+ });
+ setAuditValue('audit-total', hasData ? total : null);
+}
+
+function setAuditValue(id, count) {
+ const el = $(id);
+ if (!el) return;
+ if (count == null) {
+ el.textContent = '--';
+ el.className = 'val';
+ return;
+ }
+ el.textContent = String(count);
+ el.className = 'val ' + (count > 0 ? 'warn' : 'good');
+}
+
function updateFaultHistory(engine) {
const container = $('fault-history');
if (!container) return;