Files
ruvnet--RuView/docs/adr/ADR-251-gamma-clinical-dashboard.md
T
Claude 41d52311bd feat(clinic): ADR-251 clinical dashboard + hash-chained RuVector store
Read-only research/clinical instrumentation over the ADR-250 adaptive-gamma
platform, closing two operational gaps: no durable cohort memory, no
clinician surface.

Store (store.rs): append-only JSONL holding anonymized profiles, witnessed
session summaries, and acceptance verdicts. Every line is hash-chained
(entry_hash = SHA-256(prev_hash || raw record bytes)), so any retroactive
edit, deletion, or reorder breaks the chain — the store fails closed (refuses
to open tampered data) and rebuilds the RuVector kNN/clustering layer on open
so cohort warm-start survives restarts. The chain hashes the exact on-disk
bytes via serde_json RawValue, because serde_json's default float parse is
lossy and re-serialization is not byte-stable (this bit the first cut: a
9-session ingest self-corrupted on reopen).

Dashboard (server.rs + embedded dependency-free dashboard.html): Axum surface
with GET routes for participants, per-participant frequency-response map +
session trend with safety-stop markers, cohort clusters, per-program
acceptance verdicts, and a live chain-integrity badge. Strictly read-only — a
test asserts no route accepts POST. Claim discipline inherited: acceptance
payloads carry AcceptanceReport::released_claim (NO_CLAIM on failure), never a
raw program claim.

gamma-clinic binary; ingest_governor bridges the live ADR-250 loop into the
store (pseudonymous, dedup by witness hash). Pseudonymity asserted: the
person_id never reaches disk.

20 tests (13 store/lib + 7 server) + 1 doctest; live binary smoke-tested.
Workspace gate: 2,935 passed / 0 failed.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH
2026-06-11 00:32:38 +00:00

4.5 KiB

ADR-251: Clinical Dashboard + Persistent RuVector Store for Adaptive Gamma

Field Value
Status Proposed
Date 2026-06-11
Owner RuView, RuVector, RuFlo clinical systems
Decision type Architecture, clinical tooling
Relates to ADR-250 (Adaptive Gamma Entrainment — platform, programs, acceptance gate)
Codebase target v2/crates/ruview-gamma-clinic (this ADR's implementation)

Not a medical device. Read-only research/clinical instrumentation over the ADR-250 platform. It surfaces only gated claims and witnessed records; it can neither start stimulation nor widen any safety envelope.

1. Context

ADR-250 shipped the governed adaptive-neuromodulation engine: per-program safety envelopes, the RuVector self-learning layer (anonymized profiles, kNN warm-start, drift detection, clustering), witnessed session records, and the executable acceptance gate. Two operational gaps remain for clinical use:

  1. No durable store. ProfileStore and the governor's audit log are in-memory; a clinic restart loses cohort memory, and there is no tamper-evident persistence matching the platform's proof discipline.
  2. No clinician surface. RuFlo's clinician export exists as a struct (ClinicianReport), but there is no way for a clinician/trial monitor to see a participant's frequency-response curve, session trend, safety events, drift status, or a program's acceptance verdict.

2. Decision

Build ruview-gamma-clinic, a separate crate (keeping ruview-gamma a dependency-light deterministic leaf) with two components:

2.1 Persistent RuVector store (store.rs)

Append-only JSON-lines file per clinic, holding three record kinds — anonymized profiles, witnessed session summaries, and acceptance reports — each line hash-chained (entry_hash = SHA-256(prev_hash ‖ canonical_json)) so any retroactive edit breaks the chain (verify_chain()); the RuVector in-memory layer (ProfileStore kNN, clustering) is rebuilt from the file at open. Pseudonymity is preserved: records carry only the one-way profile tags from ADR-250 §10.

2.2 Read-only clinical dashboard (server.rs + embedded dashboard.html)

Axum surface, strictly read-only (no POST mutates stimulation state):

Route Payload
GET / embedded single-file HTML dashboard (no build step; SVG charts)
GET /api/clinic/participants tag, session count, mean entrainment, safety stops, adverse flag, drift status
GET /api/clinic/participants/{tag} response vector, frequency→score curve, session trend
GET /api/clinic/cohort deterministic k-means clusters over the stored profiles
GET /api/clinic/acceptance per-program acceptance reports with the gated claim
GET /api/clinic/integrity hash-chain verification result + record count

Claim discipline is inherited, not re-implemented: acceptance payloads embed AcceptanceReport::released_claim (the gate's output), never the program's raw claim. The dashboard renders what the gate released — nothing stronger.

2.3 Visualization (embedded, dependency-free)

One static HTML file (include_str!) with vanilla JS + inline SVG: participant list → per-participant frequency-response curve (the personal response map), entrainment/comfort session trend, safety-event markers, cohort cluster table, and an integrity badge (green only when verify_chain passes). No JS framework, no CDN, no build step — auditable by reading one file.

3. Consequences

  • Clinic restarts no longer lose cohort memory; warm-start works across runs.
  • Tampering with stored records is detectable by one endpoint call.
  • A clinician can inspect a trial without shell access; the surface cannot actuate anything.
  • The store is JSONL, not a database server: at research-cohort scale this is deliberate (greppable, diffable, witness-friendly). An HNSW/ruvector-crate backend remains the drop-in path past ~10⁵ profiles (ADR-250 §10).

4. Acceptance criteria (tested)

Criterion Test
Store round-trips all three record kinds across reopen store::tests
Hash chain detects any line edit/deletion/reorder tampered_chain_is_detected
kNN warm-start works from a reloaded store knn_survives_reload
Every API route serves and is read-only server::tests (oneshot)
Acceptance payload carries only the gated claim acceptance_payload_uses_gated_claim
Dashboard HTML embeds and serves dashboard_html_served