System Assurance Panel

Implementation Report v3.1 — Captions actually flowing now
May 2026  |  Aligned with: System Assurance Panel Spec v3 + Caption Policy Rules Matrix v2 + Follow-Up Enhancements v2 (Israelsohn)

What changed in v3.1 (post-v3 caption fixes)

Field-testing v3 surfaced six interactions between the rules matrix and real STT engines that needed reconciliation. All shipped:

Tests: 18/18 unit tests still passing for the rules matrix (added 2 new cases for the word-wrap path).

What changed in v3

v3 lands the bulk of Matt's Follow-Up Enhancements: Failure Specificity, STT Trace, and Logging Control v2. The panel evolves from "something is wrong" to "this exact condition failed, here is the evidence, and here is the next action."

What v3 deliberately defers

What changed in v2 (still live)

v1 stood up the panel UI, telemetry singleton, freshness checks, Safe Mode toggle, raw-STT leakage gate, and on-disk snapshots — all listening for signals that nothing was emitting.

v2 wires the captioning pipeline so those signals actually fire. Matt's Caption Policy Rules Matrix v2 (May 2026 spec) is implemented in full: 6 Class A rules, 4 Class B rules, 3 Class C rules, 1 Class D rule, plus the §7 decision flow (A fail → reject; B fail → delay; B4 force-commit window at >3000ms when A passes; C as warnings). The CaptionPipeline facade is now instantiated per STT session whenever any targeted text layer has captionStabilityEnabled = true, routing transcripts through CaptionStabilityEngine + CaptionPolicyGate before reaching the wall.

STAB and POLICY tiles now light up. Safe Mode auto-engages on repeated Class A failures (>50% reject ratio over rolling 20 evaluations). 16 unit tests covering spec §16 (Class A/B individual rules, valid commit, force-commit window F1, reject-after-timeout F2, correction event X1, precedence P1, determinism D1, Class C warnings) all pass.

Summary

Real-time operational cockpit for the captioning pipeline lives at the top of the Logs tab in the WallSpace desktop app. Five subsystem tiles (Audio / STT / Stability / Policy / Renderer) plus a global READY / DEGRADED / NOT READY badge surface the state of every link in the chain. Diagnostic Reasoning correlates failures into probable cause + suggested action. Safe Mode is real (not just a label) and gates the renderer to commit-only when engaged. Snapshot JSON copies to clipboard on demand and writes to disk automatically when the system enters NOT READY. Class A/B rules now drive caption commit decisions deterministically per Matt's matrix.

Net new surface in v2: captionPolicyGate.ts rewritten end-to-end (~280 LOC), captionStability.ts types extended with PolicyDecision / PolicyResult / CaptionEvaluationInput / buildEvaluationInput() helper, captionPipeline.ts updated to consume the new {decision, violations} result shape, App.tsx integration into makeTextCallback (wraps with pipeline routing when stability flag is on for any targeted layer), captionPolicyGate.test.ts (16 tests) under tests/unit/.

What it looks like

READY — all green

READY AUDIO STT STAB POLICY REND Safe · Debug · ⇪ Snapshot · 📁

DEGRADED — STT latency warning

DEGRADED AUDIO STT STAB POLICY REND

NOT READY — diagnosis surfaces

NOT READY AUDIO STT STAB POLICY REND
Cause: Audio input failure preventing STT.
Action: Check mic/device permissions before restarting STT.

Phase-by-phase status

PhaseWhatStatusFiles
1 Skeleton: telemetry singleton + hook + panel + Logs-tab mount Done services/assuranceTelemetry.ts, hooks/useAssurance.ts, components/AssurancePanel.tsx, App.tsx
2 Status wiring: 5 subsystems push real signals (4Hz audio heartbeat, STT latency, stability churn rolling window, policy block ratio, renderer frame age + raw-STT leak detector) Done audioEngine.ts, transcriptionService.ts, captionStabilityEngine.ts, captionPolicyGate.ts, captionRenderer.ts
3 Diagnostic Reasoning: 5 IF/THEN correlation rules implemented verbatim per spec; rolling 50-event log appended on every status transition Done assuranceTelemetry.ts (evaluateDiagnosis())
4 Copy Diagnostic Snapshot: clipboard JSON matching spec shape (system, subsystems, captionState, STT, stability, policy, renderer, diagnosis, events) Done assuranceTelemetry.getSnapshot(), panel header button
5 UI refinement: 150ms tile color transitions, click-to-expand detail row, aria-live="polite" + role="status", ≤2s glance-readable layout Done AssurancePanel.tsx
6 Safe Mode: real wiring (not a label). Toggle in panel + auto-engage when STT/Policy stress sustained for 10s; commit-only gate at _emitIfAllowed() filters provisional/stabilising captions out of the renderer. Done captionStabilityEngine.setSafeMode(), captionPipeline.ts bridge, telemetry auto-engage rule
7 Raw-STT leakage gate. Enforces "Renderer must NEVER display raw STT output" by short-circuiting renderCaption() for non-committed presentation states. Shipped behind debugMode toggle for one-release behavior comparison; flag will be removed next release. Done captionRenderer.ts:631 (replaced opacity-fade with hard gate)
8 Auto-snapshot on degradation: writes JSON to ~/Library/Application Support/crt-wall-controller/diagnostics/ when global state enters NOT READY (rate-limited 1/min, prunes to 50 files). "Open Diagnostics Folder" button in panel header. Done main/diagnosticsBridge.ts, preload IPC, telemetry auto-trigger
9 This report — v2. Live assets/web-portal/system-assurance-panel-report.html
10 Rules Matrix v2 — full A/B/C/D rule set implemented per Matt's spec; new types (PolicyDecision, PolicyResult, CaptionEvaluationInput); decision flow per §7 (A → reject; B → delay; B4 force-commit at >3000ms; C as warnings). Done captionPolicyGate.ts rewritten, types/captionStability.ts extended
11 CaptionPipeline integration — pipelines instantiated per STT session keyed by engine:source when any targeted layer has captionStabilityEnabled=true. Lifecycle disposed when session ends. Pipeline.onCommitted fires existing delivery (transcript save, OSC, Scope, translation, etc.) only when rules pass. Done App.tsx makeTextCallback + captionPipelinesRef
12 Safe Mode auto-engage on Class A failures — >50% reject ratio over rolling 20 evaluations triggers Safe Mode (per spec §11). Existing STT-error and churn-warning triggers retained. Done assuranceTelemetry.ts classARejectRatio
13 Unit tests (spec §16) — 16 tests covering A1-A6, B1-B3, C1, F1 (force-commit), F2 (reject-after-timeout), X1 (correction), P1 (precedence), D1 (determinism). All passing. Done tests/unit/captionPolicyGate.test.ts

Rules Matrix v2 — what's live

RuleClassConditionAction
A1 No Raw STTAcaption not committedMUST NOT render (renderer gate, Phase 7)
A2 No Mutation After CommitAcaption is committedREJECT
A3 Maximum LinesAlineCount > 2REJECT
A4 Maximum Chars/LineAany chars/line > 42REJECT
A5 Minimum ConfidenceAconfidence < 0.60REJECT
A6 Meaning StabilityAmeaningChangeRisk > 0.30REJECT
B1 Stability RequirementBstableFrameCount < 3DELAY
B2 Churn LimitBchurnCount > 3DELAY
B3 Min Display DurationBdurationMs < 800DELAY
B4 Max Stabilisation WindowBnot committed AND durationMs > 3000FORCE commit if A passes, else REJECT
C1 Preferred Line LengthCchars/line > 36ALLOW + warning
C2 Punctuation CompletenessCmissing trailing punctuationALLOW + warning
C3 Phrase CompletionCsentence ends mid-phraseALLOW + warning
D1 CapitalisationDfirst char not capitalisedALLOW (no decision impact)

Spec compliance checklist

Diagnostic Reasoning rules (verbatim)

1. audio.error AND stt.error
   → Cause:  Audio input failure preventing STT.
   → Action: Check mic/device permissions before restarting STT.

2. audio.ok AND stt.error
   → Cause:  STT engine, API, or connection failure.
   → Action: Restart STT engine; check API key and network.

3. stt.ok AND renderer.error
   → Cause:  Caption pipeline or renderer failure.
   → Action: Inspect CanonicalCaptionLite creation and renderer input.

4. stability.warning AND detail contains 'churn'
   → Cause:  Unstable STT hypotheses.
   → Action: Increase stability threshold, reduce update rate, or use final-only mode.

5. policy.warning OR policy.error
   → Cause:  Captions rejected by confidence/churn/meaning rules.
   → Action: Inspect policy violations and active preset thresholds.

Snapshot JSON shape

{
  "ts": 1714780800000,
  "system": { "global": "DEGRADED", "safeMode": false, "debugMode": false },
  "subsystems": {
    "audio":     { "status": "ok",      "lastUpdate": ..., "detail": "RMS 0.042" },
    "stt":       { "status": "warning", "lastUpdate": ..., "latencyMs": 1820,    "detail": "Latency 1820ms" },
    "stability": { "status": "ok",      "lastUpdate": ..., "detail": "Stable 7, sim 0.94" },
    "policy":    { "status": "ok",      "lastUpdate": ..., "detail": "1/20 blocked" },
    "renderer":  { "status": "ok",      "lastUpdate": ..., "detail": "wall-banner" }
  },
  "captionState": "stabilising",
  "stt": { "engine": "web-speech", "lastResultAt": ..., "latencyMs": 1820 },
  "diagnosis": null,
  "events": [
    { "ts": ..., "subsystem": "stt", "from": "ok", "to": "warning", "message": "Latency 1820ms" },
    ...
  ]
}

Known gaps / not done

Awaiting your inputs, Matt

Future work

Spec items deliberately deferred

Implementation file inventory

FileLOCRole
src/renderer/services/assuranceTelemetry.ts~300Singleton store, debounce, freshness, rules engine, snapshot, auto-engage Safe Mode
src/renderer/hooks/useAssurance.ts8React subscription hook
src/renderer/components/AssurancePanel.tsx~180Layer 1+2+3+4 UI
src/main/diagnosticsBridge.ts50IPC for snapshot file write + open folder
src/renderer/services/audioEngine.ts+404Hz heartbeat → reportAudio
src/renderer/services/transcriptionService.ts+20reportSTT on status + result
src/renderer/services/captionStabilityEngine.ts+25reportStability + setSafeMode
src/renderer/services/captionPolicyGate.ts+15reportPolicy with rolling block ratio
src/renderer/utils/captionRenderer.ts+30reportRenderer + Phase 7 commit-only gate
src/renderer/services/captionPipeline.ts+15Bridge Safe Mode telemetry → engine
src/main/index.ts + preload.ts+10Bridge registration + IPC surface
src/renderer/App.tsx+5Mount AssurancePanel above LogPanel in Logs tab

Next steps for review

  1. Enable captionStabilityEnabled on a text layer, drive it with Google STT, and watch STAB + POLICY tiles light up. Verify duplicate captions (the prior Force-final repeat-emit pattern) are now suppressed by A2.
  2. Validate the rules matrix against your test corpus — run npx vitest run tests/unit/captionPolicyGate.test.ts for §16 cases. All 16 currently pass; if you add more spec cases I'll wire them too.
  3. Confirm the 0.60 confidence floor (A5) is right for your engine mix — Google STT typically reports 0.5–0.95, Whisper doesn't report at all (defaults to 1.0 trust). May need per-engine tuning.
  4. Decide on profile-switching UX (5 profiles from vocal-forensics-v3) — needs joint design before code.
  5. Decide whether to keep the Phase 7 debugMode flag for >1 release or drop it sooner — current plan removes it next release.
  6. Tell us where the next slice should land: (a) profile-switching UI, (b) token-level critical-term locking, (c) live remote dashboard at wallspace.studio/assurance, (d) Decision Log bridge to Echo.