mirror of
https://github.com/ruvnet/RuView
synced 2026-07-21 17:13:19 +00:00
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
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# 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` |
|
||||
Reference in New Issue
Block a user