diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 297549f7..ae0017ee 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -2,22 +2,22 @@ Plan path: `.claude/plans/splat-native-ultrasound-v1.md`. Companions: ndarray `.claude/plans/splat-native-ultrasound-simd-substrate-v1.md`; OGAR `docs/SPLAT-NATIVE-CUSTOMER.md`; MedCare-rs `.claude/handovers/2026-06-05-splat-native-medcare-hipaa-wire.md`. Customer of OGAR PR #30 §6 FMA bones-rendering litmus + ADR-022 SaMD audit-controls evidence base. -| D-id | Title | Crate(s) / repo | ~LOC | Risk | Status | PR / Evidence | -|---|---|---|---|---|---|---| -| D-SPLAT-1 | `Gaussian3D` carrier (`mu`/`sigma_packed`/`amplitude`/`opacity`/`sh[16]`/`frame_idx`/`class_id`; 80 B/row) | `lance-graph-contract::splat` | 120 | LOW | **Queued — P1 sprint 1-2** | gates on `MailboxSoAHeader` (D-MBX-10) or own feature flag | -| D-SPLAT-2 | `ndarray::simd::splat` batch ops — `batched_cholesky_3x3` / `batched_mahalanobis` / `batched_opacity_blend` / `batched_sh_eval_l3` / `batched_se3_transform`; all three backends (AVX-512/NEON/scalar) | `ndarray::src/simd_splat.rs` | 600 | MED | **Queued — P1 sprint 1-2** | foundation; none | -| D-SPLAT-3 | `SplatBatch` SoA carrier (per-column slices for SIMD sweep; inherits MailboxSoAHeader versioning) | `lance-graph-contract::splat` | 150 | LOW | **Queued — P1 sprint 1-2** | gates on D-SPLAT-1 | -| D-SPLAT-4 | SH-aware palette extension in `crates/bgz17` (256×256×2B compose table; SH-basis-id per centroid) | `bgz17::sh_palette` | 250 | MED | **Queued — P3 sprint 4-5** | gates on D-SPLAT-1 | -| D-SPLAT-5 | Splat-to-splat registration math — Σ-sandwich Mahalanobis ICP + SE(3) Levenberg-Marquardt | `lance-graph::splat::registration` | 400 | HIGH | **Queued — P4 sprint 6-7** | gates on D-SPLAT-2 + D-SPLAT-3 | -| D-SPLAT-6 | `crates/splat-fit` engine — RF/IQ → beamformed → local-maxima → PSF estimate → SH projection → emit Gaussian3D batch | `crates/splat-fit` (new standalone, 0-dep, ndarray-hpc feature) | 1500 | HIGH | **Queued — P2 sprint 3** | gates on D-SPLAT-1 + D-SPLAT-2 + OQ-SPLAT-3 | -| D-SPLAT-7 | Splat actors — `SplatFitActor`/`PoseAccumulatorActor`/`RegistrationActor`, each owns one `MailboxSoA`; consumes bardioc #17 Rubicon kanban verbatim | `crates/splat-actors` (or `ractor_actors`) | 500 | MED | **Queued — P3 sprint 4-5** | gates on D-SPLAT-3 + D-SPLAT-6 + bardioc #17 (shipped) | -| D-SPLAT-8 | FMA atlas hydrator — TTL → `fma_class.lance` + `fma_relation.lance` + `fma_atlas_splat.lance` (~150M Gaussians full body) | `lance-graph-ontology` + `crates/fma-hydrator` | 800 | HIGH | **Queued — P4 sprint 7-8** | gates on OGAR PR #30 Phase 8 + D-SPLAT-3 + ndarray PR #189 (shipped) | -| D-SPLAT-9 | `fma_blueprint::style_recipe` D-Atom catalogue (AnatomicalRegion, OrganSystem, Innervation, Vasculature, Joint, Muscle, Bone, OrganParenchyma, Tract); mirrors PR #433 Odoo pattern | `lance-graph-ontology::fma_blueprint` | 400 | LOW | **Queued — P4 sprint 7-8** | gates on D-SPLAT-8 | -| D-SPLAT-10 | `memory.ultrasound_frame.lance` + `memory.ultrasound_splat.lance` datasets via `soa_mapping.rs`; new `SensitivityReason::UltrasoundRawPHI`/`UltrasoundAnonymized` variants in `column_mask_bridge` | MedCare-rs `crates/medcare-analytics` | 250 | MED | **Queued — P5 sprint 9-10** | gates on D-SPLAT-3 + MedCare PR #162 (shipped) | -| D-SPLAT-11 | `commit_event` audit chain for splat ingest via `LanceMembrane::commit_event` (callcenter PR #467, sole-writer membrane); `KnowableFromStore::register("ogit-medcare/ultrasound_ingest", Some(ddl_hint))` | MedCare-rs `crates/medcare-analytics` | 100 | LOW | **Queued — P5 sprint 9-10** | gates on D-SPLAT-10 + PR #467 (shipped) + OGAR #25/#31 (shipped) | -| D-SPLAT-12 | AR splat renderer — HoloLens OpenXR (clinical AR target) + Cesium ion + Three.js (browser fallback) + headless PNG (regression); CPU does math, GPU only paints | `crates/splat-render` (new) | 1200 | HIGH | **Queued — P6 sprint 11-13** | gates on D-SPLAT-2 + D-SPLAT-3 + D-SPLAT-5 | -| D-SPLAT-13 | IMU/POSE 4D accumulator — VIO against splat features at IMU rate (~200 Hz); splat-corrected pose at frame rate (~30 Hz); Planning-column readiness at t = −550ms | `splat-actors::PoseAccumulatorActor` | 200 | MED | **Queued — P3 sprint 4-5** | gates on D-SPLAT-7 | -| D-SPLAT-14 | SaMD documentation track — research-tool → clinical-study → Class IIa (IEC 62366 / IEC 80001 / ISO 14971 / IVD-MDR Rule 11). ADR-022 firewall IS the audit-controls evidence base | `q2`/`quarto` or `docs/` | 600 | LOW | **Queued — P7 sprint 14+ (parallel through P4-P6)** | gates on none architecturally; v1/v2/v3 phased | +| D-id | Title | Crate(s) / repo | ~LOC | Risk | Sprint | Status | PR / Evidence | +|---|---|---|---|---|---|---|---| +| D-SPLAT-1 | `Gaussian3D` carrier (`mu`/`sigma_packed`/`amplitude`/`opacity`/`sh[16]`/`frame_idx`/`class_id`; 80 B/row) | `lance-graph-contract::splat` | 120 | LOW | P1 sprint 1-2 | **Queued** | gates on `MailboxSoAHeader` (D-MBX-10) or own feature flag | +| D-SPLAT-2 | `ndarray::simd::splat` batch ops — `batched_cholesky_3x3` / `batched_mahalanobis` / `batched_opacity_blend` / `batched_sh_eval_l3` / `batched_se3_transform`; all three backends (AVX-512/NEON/scalar) | `ndarray::src/simd_splat.rs` | 600 | MED | P1 sprint 1-2 | **Queued** | foundation; none | +| D-SPLAT-3 | `SplatBatch` SoA carrier (per-column slices for SIMD sweep; inherits MailboxSoAHeader versioning) | `lance-graph-contract::splat` | 150 | LOW | P1 sprint 1-2 | **Queued** | gates on D-SPLAT-1 | +| D-SPLAT-4 | SH-aware palette extension in `crates/bgz17` (256×256×2B compose table; SH-basis-id per centroid) | `bgz17::sh_palette` | 250 | MED | P3 sprint 4-5 | **Queued** | gates on D-SPLAT-1 | +| D-SPLAT-5 | Splat-to-splat registration math — Σ-sandwich Mahalanobis ICP + SE(3) Levenberg-Marquardt | `lance-graph::splat::registration` | 400 | HIGH | P4 sprint 6-7 | **Queued** | gates on D-SPLAT-2 + D-SPLAT-3 | +| D-SPLAT-6 | `crates/splat-fit` engine — RF/IQ → beamformed → local-maxima → PSF estimate → SH projection → emit Gaussian3D batch | `crates/splat-fit` (new standalone, 0-dep, ndarray-hpc feature) | 1500 | HIGH | P2 sprint 3 | **Queued** | gates on D-SPLAT-1 + D-SPLAT-2 + OQ-SPLAT-3 | +| D-SPLAT-7 | Splat actors — `SplatFitActor`/`PoseAccumulatorActor`/`RegistrationActor`, each owns one `MailboxSoA`; consumes bardioc #17 Rubicon kanban verbatim | `crates/splat-actors` (or `ractor_actors`) | 500 | MED | P3 sprint 4-5 | **Queued** | gates on D-SPLAT-3 + D-SPLAT-6 + bardioc #17 (shipped) | +| D-SPLAT-8 | FMA atlas hydrator — TTL → `fma_class.lance` + `fma_relation.lance` + `fma_atlas_splat.lance` (~150M Gaussians full body) | `lance-graph-ontology` + `crates/fma-hydrator` | 800 | HIGH | P4 sprint 7-8 | **Queued** | gates on OGAR PR #30 Phase 8 + D-SPLAT-3 + ndarray PR #189 (shipped) | +| D-SPLAT-9 | `fma_blueprint::style_recipe` D-Atom catalogue (AnatomicalRegion, OrganSystem, Innervation, Vasculature, Joint, Muscle, Bone, OrganParenchyma, Tract); mirrors PR #433 Odoo pattern | `lance-graph-ontology::fma_blueprint` | 400 | LOW | P4 sprint 7-8 | **Queued** | gates on D-SPLAT-8 | +| D-SPLAT-10 | `memory.ultrasound_frame.lance` + `memory.ultrasound_splat.lance` datasets via `soa_mapping.rs`; new `SensitivityReason::UltrasoundRawPHI`/`UltrasoundAnonymized` variants in `column_mask_bridge` | MedCare-rs `crates/medcare-analytics` | 250 | MED | P5 sprint 9-10 | **Queued** | gates on D-SPLAT-3 + MedCare PR #162 (shipped) | +| D-SPLAT-11 | `commit_event` audit chain for splat ingest via `LanceMembrane::commit_event` (callcenter PR #467, sole-writer membrane); `KnowableFromStore::register("ogit-medcare/ultrasound_ingest", Some(ddl_hint))` | MedCare-rs `crates/medcare-analytics` | 100 | LOW | P5 sprint 9-10 | **Queued** | gates on D-SPLAT-10 + PR #467 (shipped) + OGAR #25/#31 (shipped) | +| D-SPLAT-12 | AR splat renderer — HoloLens OpenXR (clinical AR target) + Cesium ion + Three.js (browser fallback) + headless PNG (regression); CPU does math, GPU only paints | `crates/splat-render` (new) | 1200 | HIGH | P6 sprint 11-13 | **Queued** | gates on D-SPLAT-2 + D-SPLAT-3 + D-SPLAT-5 | +| D-SPLAT-13 | IMU/POSE 4D accumulator — VIO against splat features at IMU rate (~200 Hz); splat-corrected pose at frame rate (~30 Hz); Planning-column readiness at t = −550ms | `splat-actors::PoseAccumulatorActor` | 200 | MED | P3 sprint 4-5 | **Queued** | gates on D-SPLAT-7 | +| D-SPLAT-14 | SaMD documentation track — research-tool → clinical-study → Class IIa (IEC 62366 / IEC 80001 / ISO 14971 / MDR Annex VIII Rule 11). ADR-022 firewall IS the audit-controls evidence base | `q2`/`quarto` or `docs/` | 600 | LOW | P7 sprint 14+ (parallel through P4-P6) | **Queued** | gates on none architecturally; v1/v2/v3 phased | --- diff --git a/.claude/plans/splat-native-ultrasound-v1.md b/.claude/plans/splat-native-ultrasound-v1.md index 9d717ffa..3cb2bf93 100644 --- a/.claude/plans/splat-native-ultrasound-v1.md +++ b/.claude/plans/splat-native-ultrasound-v1.md @@ -259,13 +259,13 @@ New SoA mapping entry per the existing pattern in `soa_mapping.rs`. Columns: - `patient_ref: FixedSizeBinary(8)` — anonymized patient ID (per `column_mask_bridge` Hash mode for unauthorized roles) - `frame_idx: u32` — monotonic frame counter - `acquisition_ts: TimestampMicros` — frame acquisition time -- `pose_se3: FixedSizeBinary(16)` — packed SE(3) pose (3 translation + 9 rotation matrix, both `f16`) +- `pose_se3: FixedSizeBinary(24)` — packed SE(3) pose (3 translation + 9 rotation matrix, both `f16`; 12 × 2 = 24 B) - `splat_batch_handle: Binary` — pointer into the splat-volume storage (NOT the splats themselves; those live in `ultrasound_splat.lance` to keep this table queryable) - `splat_count: u32` - `mean_amplitude: f32` - `quality_score: f32` — ICP residual after registration; below threshold ⇒ frame is pruned (kanban col 4) -**Raw RF/IQ stays out of MedCare-rs entirely** — only fitted splats land. PHI is in the splat coordinates + atlas-aligned annotations, not in the raw signal. The `column_mask_bridge` extension adds: +**Raw RF/IQ stays out of MedCare-rs entirely** — only fitted splats land. Per **NR-SPLAT-PHI** (normative rule, see below): scanner-frame splat geometry is non-identifying on its own; the link `patient_ref ↔ splat_volume` in `memory.ultrasound_frame.lance` IS PHI; atlas-aligned anatomical annotations carrying patient-specific landmarks ARE PHI. The `column_mask_bridge` extension adds: ```rust pub enum SensitivityReason { @@ -283,6 +283,8 @@ pub enum SensitivityReason { **Owner:** `MedCare-rs` (`crates/medcare-analytics`). **~100 LOC + tests.** **Risk: LOW.** +**NR-SPLAT-PHI (normative rule, single source of truth):** *Scanner-frame splat geometry (the `mu`/`sigma_packed` columns; `pose_se3` in scanner frame) is non-identifying on its own. The link between `patient_ref` and a splat volume — i.e. the row in `memory.ultrasound_frame.lance` — IS PHI. Atlas-aligned annotations that name patient-specific landmarks (e.g. `class_id` resolving to "this patient's left biceps brachii at scanner-pose P") are PHI. Raw RF/IQ is PHI by default and is never persisted (it does not enter the MedCare wire). All §3.10 (`column_mask_bridge`) and §7 (risk-matrix HIPAA row) policy decisions cite this rule.* + Every splat ingest writes one `CognitiveEventRow` via `LanceMembrane::commit_event` (callcenter PR #467, the sole-writer membrane). Carries: `actor` (the `SplatFitActor`), `action: "ultrasound_ingest"`, `target_class: "memory.ultrasound_frame"`, `target_row_id`, `version` (the Lance row-version from `KnowableFromStore::register`), `subject` (clinician identity), `consent_evidence` (the patient consent chain). Inner-side; no JSONL audit sink (per MedCare CLAUDE.md Iron Rule 7). **Tests:** every ingest produces exactly one `CognitiveEventRow`; row-version monotonic; consent-chain non-empty. @@ -400,7 +402,7 @@ Three docs: ## 5. Dependencies graph (textual) -``` +```text OGAR #30 Phase 8 (FMA hydrate prep) ─────────┐ ▼ ndarray D-SPLAT-2 (SIMD splat ops) ──► D-SPLAT-8 (FMA atlas) ──┐ @@ -440,7 +442,7 @@ D-SPLAT-4 (SH palette) ─────────────────── | FMA atlas splat pre-computation too large (~5 GB) | MED | Region-on-demand loading (per OQ-SPLAT-X); only the regions matching `class_id` of fitted live splats need to be paged in. Lance versioning + `KnowableFromStore` registry handles the on-demand path. | | Registration convergence basin too narrow (live ≠ atlas anatomy) | HIGH | Coarse-to-fine multi-resolution ICP (lower SH degree first); patient-specific atlas pre-registration during clinical-study phase. | | HoloLens OpenXR compute budget exhausted | MED | Render-at-IMU-rate (200 Hz) is not required; render-at-frame-rate (30 Hz) is. Backpressure via the renderer mailbox. | -| HIPAA PHI leak via splat coordinates | HIGH | Splat coordinates are in *scanner frame*, not patient-identifying frame. Patient identity stays in `patient_ref` (Hashed for non-clinical roles per `column_mask_bridge`). | +| HIPAA PHI leak via splat coordinates | MEDIUM | Per **NR-SPLAT-PHI** (§3.10): scanner-frame splat coordinates are non-identifying on their own; PHI lives in the `patient_ref ↔ splat_volume` link and in atlas-aligned annotations carrying patient-specific landmarks. Default `column_mask_bridge` mode for `patient_ref` is `Hash` for non-clinical roles; splat coordinate columns require no masking. | | SaMD Class IIa technical-file gap | MED | Build the audit-controls evidence chain during P5 (HIPAA wire); the chain IS the certification evidence base. | --- @@ -600,7 +602,7 @@ This plan touches **seven repositories**. Each owns a piece of the splat-native **Critical interconnect:** - **Raw RF/IQ NEVER enters MedCare storage** — verified by `grep` audit. This is a hard invariant (Iron Rule 7 + Iron Rule from MedCare CLAUDE.md): audit witness stays inner; raw signal stays at the splat-fit boundary; only fitted Gaussians cross into MedCare. - The MedCare ultrasound dataset is **append-only from the SplatFitActor's perspective** — the actor is the sole writer; MedCare only reads + redacts on query. This preserves the sole-writer membrane discipline from PR #467. -- The patient-identity boundary is `patient_ref` only; splat coordinates are in *scanner frame*, not patient-identifying frame. No PHI in the geometry itself. +- Per **NR-SPLAT-PHI** (§3.10): the patient-identity boundary is the `patient_ref ↔ splat_volume` LINK (Hashed for non-clinical roles); splat coordinates are in *scanner frame* and are not patient-identifying on their own; atlas-aligned annotations carrying patient-specific landmarks ARE PHI and inherit `patient_ref`'s sensitivity class via foreign-key join. **Sprint commitment:** sprint 9-10 (P5, after FMA atlas + registration land upstream). 1 PR (D-SPLAT-10 + D-SPLAT-11 bundled). @@ -656,7 +658,7 @@ This plan touches **seven repositories**. Each owns a piece of the splat-native ## 10.8 The interconnect map (visual) -``` +```text ┌──────────────────────────────────────────┐ │ ndarray (D-SPLAT-2) │ │ SIMD substrate: Cholesky, Mahalanobis, │ @@ -750,11 +752,11 @@ This plan touches **seven repositories**. Each owns a piece of the splat-native Per the workspace's standing autoattend pattern + lance-graph's two-layer A2A discipline: -**Layer-1 (runtime / code-level):** the `OrchestrationBridge` + `StepDomain` taxonomy already handles cross-domain dispatch. Splat-native adds two `StepDomain` variants: +**Layer-1 (runtime / code-level):** the `OrchestrationBridge` + `StepDomain` taxonomy already handles cross-domain dispatch. The shipped contract (`crates/lance-graph-contract/src/orchestration.rs:37`) defines exactly eight variants: `Crew, Ladybug, N8n, LanceGraph, Ndarray, Smb, Medcare, Kanban`. Splat-native lands as **two new `StepDomain` variants ADDED to that shipped enum** (extending the single-source-of-truth taxonomy, not parallel to it): - `StepDomain::SplatFit` (the engine consumes RF/IQ → emits SoA) - `StepDomain::SplatRender` (the renderer consumes SoA → emits pixels) -These compose with existing `StepDomain::{Codec, Thinking, Query, Semantic, Persistence, Inference, Learning}` per OrchestrationBridge contract. +These compose with the existing variants per the `from_step_type` dispatch — splat ingest routes `SplatFit → Ndarray → LanceGraph → Medcare` (fit → SIMD → registration → HIPAA wire); splat render routes `SplatRender ← LanceGraph` (read-only consumer). The two new variants land in the same PR as D-SPLAT-1 (`Gaussian3D` carrier) so the contract stays single source of truth. **Step-type prefixes** for `from_step_type`: `splat_fit.*` → `SplatFit`, `splat_render.*` → `SplatRender`. **Layer-2 (session / Claude-code-level):** each per-repo doc has a `READ BY:` header naming which session-tier agents bootload it: