diff --git a/context/current-state.md b/context/current-state.md index 02ee9ca..7a07427 100644 --- a/context/current-state.md +++ b/context/current-state.md @@ -1,34 +1,35 @@ # current-state -ACTIVE: Build shared context + knowledge pipeline (Jun 20 design). -NEXT: Hand current-state.md + support-manual.md to Claude Code for build. +ACTIVE: Finalizing architecture for STTIL Shield + AI Consent Addendum. Claude Code to execute Build Spec Phase 1 (TriLane memory). +NEXT: Claude Code to execute Build Spec Phase 1 (TriLane memory category). ## Decisions (last 5) +- 2026-06-21 — STTIL Shield is a standalone revenue product, not Signal add-on. +- 2026-06-21 — AI Consent Addendum is open-source brand builder + Shield lead magnet. +- 2026-06-21 — Game Center token naming: fun/professional names (e.g., 'Dwight Schrute', 'Grogu') for tokenized entities, no PII/PHI risk. - 2026-06-20 — current-state.md bridges Pi and Claude Code. Both read at start, both write at wrap-up. - 2026-06-18 — SSA principle adopted: sparse, content-dependent, time-weighted retrieval. -- 2026-06-18 — TriLane Memory = permanent insight layer. research_insight category needed. -- 2026-06-18 — Graphify = monthly scan only. Not a daily driver. -- 2026-06-18 — Granola transcripts stay in Sessions workspace. Signal workspace = library. No mixing. ## Open Threads (max 5) -- Firecrawl key in .zshrc — needs pi restart from sourced shell -- 9 session summaries in Signal workspace — move to Sessions -- Insight Engine (Jul 4) — Railway deploy blocked on interactive auth -- labABLE Jun 26 call — deck built, needs review -- Clerk production — deferred, needs manual Google OAuth setup +- Clerk Production: DNS records propagating; app still not loading at signal-ui-xi.vercel.app. +- Pi's Firecrawl Env Var: Needs pi restart from sourced shell (this session using `curl` workaround). +- LabABLE Meeting: This week; pitch JV on Shield + AI Consent as their compliance solution. +- Claude Code's Pilot E2E: Unbreak live app (P0-1) is top priority pending Clerk DNS. ## Stack -- signal-api: Railway (signal-api-production-91c2) -- signal-ui: Vercel (signal-ui-xi.vercel.app) +- Signal: PHI contained in `patient_id` field (name, MRN, account #s) — requires tokenization. +- Model for Pi: `openrouter/google/gemini-2.5-flash` (image-capable, cheap, agentic). +- Signal-api: Railway (signal-api-production-91c2) +- Signal-ui: Vercel (signal-ui-xi.vercel.app) - Supabase: itmospnregdyiatbbdwl (JWT key) - TriLane: localhost:8000 (signal/sessions/scale-software workspaces) -- Clerk: sandbox only +- Clerk: production key deployed to Vercel, auth to `clerk.sttilsolutions.com`, Google OAuth configured. ## Tip -TriLane Memory: 0 items. The permanent insight layer is empty. Every session brain should plant at least one. +TriLane Memory has 0 items. Phase 1.1 (Claude Code) will add `research_insight` category. Then plant the insights from this session via Claude Code. -## Updated: 2026-06-20 15:30 UTC +## Updated: 2026-06-21 09:30 UTC diff --git a/docs/pilot-e2e-validation-2026-06-21.md b/docs/pilot-e2e-validation-2026-06-21.md new file mode 100644 index 0000000..5a3fcae --- /dev/null +++ b/docs/pilot-e2e-validation-2026-06-21.md @@ -0,0 +1,127 @@ +# Signal Pilot — End-to-End Validation + +**Date:** 2026-06-21 +**Author:** Claude Code (live validation + two parallel audit lanes) +**Purpose:** An honest, evidence-backed answer to "how ready is the pilot, end to end" — not a checklist re-read. This document is the input for Pi to design a remediation plan; Claude Code then builds it. +**Method:** Live exercise of the deployed stack (Vercel front-end, Railway API, Supabase, Clerk) + a backend-correctness audit + a compliance/production audit. Every claim below is backed by a live result or a `file:line`. + +--- + +## 1. Verdict in one paragraph + +The Signal **engine** is genuinely good: the deployed API scores 5,000 patients with zero skips, returns sensible worklist reasons and actions, keeps patient data to `patient_id` only, hashes identifiers, and the Confirm-Visit and Export paths work against the live database. But the pilot is **not operable today and not yet safe for a real supplier's real data**, for three independent reasons: + +1. **The live app is down.** `https://signal-ui-xi.vercel.app` renders a blank screen for everyone because a half-finished Clerk production migration points the front-end at a custom domain that doesn't resolve. +2. **The core gap logic has three correctness blockers** that only surface on messy real-world data (Medicare Advantage misclassification, duplicate-patient collisions, and staff overrides that don't actually clear the gap). On clean demo data they're invisible; on a real Brightree export they produce visibly wrong answers. +3. **The compliance and trust layer has open items**, including a published privacy page that claims a BAA that does not exist, an audit log that is never actually written, and unexecuted LOI/NDA. + +"100% pilot-ready (13/13)" is true only for the **product build checklist**, and even there one item is overstated. It is not true for live operability or compliance. **Bottom line: with synthetic/de-identified data and the fixes below, a controlled Gaboro pilot is reachable. Real patient data is gated to the separate Production Readiness Whitepaper.** + +--- + +## 2. What was tested live (evidence) + +| Test | Result | +|---|---| +| Backend `/health` | `200 {"status":"ok"}` | +| Backend `/health/db` (Supabase) | `200 {"status":"ok","org_count":1}` — DB connected | +| Upload **without** auth | `401 Authentication required` — auth genuinely enforced in prod ✓ | +| Upload `sample-green.csv` (authorized) | `200` — 1 record, `RESUPPLY_READY → "Clear to Ship"`, correct | +| Upload `gaboro-stress-test.csv` (authorized) | `200` — **5,000 patients, 0 skipped**, full flag distribution (994 lapsed, 1,161 resupply-ready, 1,994 prescriber-action, etc.) | +| PHI in API response | **None** — `patient_id` only, no name/DOB/SSN ✓ | +| `/api/export` | `200`, `text/csv`, correct filename header, clean header + data rows ✓ | +| `/api/confirm-visit` | `200` — `GAB-000001` moved `RENEWAL_CRITICAL/"Escalate"` (est.) → `ACTIVE/"On Track"` (confidence: **confirmed**), persisted to Supabase ✓ | +| Front-end `https://signal-ui-xi.vercel.app` | `200` HTTP but **blank render, 8 console errors** (see §3, P0-1) | +| `/privacy` page | `200` (published, but see §5 for a false BAA claim) | + +> Side effect to clean up: the uploads + confirm-visit wrote synthetic rows (`PT-GREEN-1`, `GAB-*`) into the pilot Supabase. This ties to open item C-7 (deletion procedure never tested). + +--- + +## 3. P0 — Pilot cannot proceed until these are fixed + +### P0-1. The live app is a blank screen (broken Clerk production migration) +- **Symptom:** `https://signal-ui-xi.vercel.app` renders nothing. Console: 8× `Failed to load Clerk JS … ERR_CONNECTION_CLOSED` from `https://clerk.sttilsolutions.com/npm/@clerk/clerk-js@6/...`. +- **Root cause (verified by DNS):** `clerk.sttilsolutions.com` is CNAME'd to **Vercel** (`signal.sttilsolutions.com → cname.vercel-dns.com → 76.76.21.142`), not to Clerk's `frontend-api.clerk.services`. The deployed front-end was rebuilt with a **production** Clerk key (`pk_live_`, custom domain `clerk.sttilsolutions.com`), but that domain never got the correct Clerk DNS. `accounts.sttilsolutions.com` **is** correctly pointed at Clerk — so exactly one of the required records is wrong, and it's the one that loads the auth library. +- **Second, independent defect:** the **back-end** still validates tokens against the **development** Clerk instance (`CLERK_JWKS_URL = https://eternal-goblin-1.clerk.accounts.dev/...`). Even with front-end DNS fixed, production Clerk tokens would fail backend validation. The two halves are on different Clerk instances. +- **Fix paths (decision for Kisa, Clerk work owned by Pi):** + - **A — Restore the working demo fast (recommended for an imminent Gaboro demo):** repoint the deployed front-end to the **dev** Clerk key (`pk_test_`, `eternal-goblin-1`), matching the backend, and redeploy. Live demo works again in minutes. Production Clerk is then completed deliberately later. + - **B — Finish the production migration properly:** fix `clerk.sttilsolutions.com` CNAME → `frontend-api.clerk.services`, add the remaining Clerk DNS records, switch the **backend** `CLERK_JWKS_URL` to the production instance, redeploy both. This is the Production Whitepaper path; do it as a project, not under call pressure. +- **Acceptance:** the public URL renders the sign-in screen, a test user logs in, and an authenticated upload round-trips through the UI. + +### P0-2. Medicare Advantage plans misclassify and drop the PA requirement (VERIFIED LIVE) +- **Evidence (run live against `coverage_calculator._normalize_payer`):** + - `WellCare Medicare` → `medicare` → doc-state `pa = "Not Required"` ❌ (MA requires PA) + - `Aetna Medicare` → `medicare` → `pa = "Not Required"` ❌ + - `Humana Gold Plus HMO`, `UnitedHealthcare Dual Complete`, `Anthem MediBlue`, `Cigna HealthSpring`, `Kaiser Senior Advantage` → `commercial` → PA required but `pecos = "N/A"` (right answer, wrong reason; MA needs PECOS) +- **File:** `python-backend/core/coverage_calculator.py:144-148` — only the literal phrase "medicare advantage" is detected; real MA plan brand names are not. +- **Why it's a blocker:** Signal's entire job is to flag the documentation gap that causes a denial. For an MA patient mislabeled as Medicare FFS, Signal says "no PA needed" and would show **Clear to Ship on a claim that will be denied**. This is the product failing at its core promise. +- **Acceptance:** a CSV of common MA brand names classifies as `medicare_advantage` (PA required, PECOS required); when ambiguous, default to PA-required and surface "verify payer type" rather than silently clearing it. + +### P0-3. Duplicate patient_id collides — wrong order's docs attach to a row +- **File:** `python-backend/api/main.py:429-442` — `record_lookup = {r.patient_id: r for r in records}` keeps only the **last** order per patient. +- **Why it's a blocker:** A real Brightree export routinely has multiple orders per patient (sensor + transmitter, refills). Every row for that patient then shows the SWO/PA/visit/device/order-number of a **different** order. In a demo this is a visibly wrong document status next to the wrong order number. +- **Acceptance:** key the pairing on the source order (e.g. index or `(patient_id, shipment_date, device, order_number)`), verified against a multi-order CSV. + +### P0-4. Staff doc-status override updates the badge but not the gap or the next step +- **Files:** `python-backend/api/main.py:307-313, 337`; `core/doc_state_machine.py`. +- **Behavior:** after a staff member marks SWO "On File," the badge turns green, but `cascade` and `recommended_next_step_code` are still computed from the un-overridden state, so Signal still says "obtain SWO from prescriber" and the export carries the contradiction. +- **Why it's a blocker:** the override workflow is a named pilot feature ("Signal's core persistent value"). It currently contradicts itself the moment a user does what Signal asked. +- **Acceptance:** overrides feed the recompute (cascade + next-step), not just the label; the status vocab (`requested`/`on_file` vs `on file`/`pending`/`expired`) is reconciled. + +--- + +## 4. P1 — Should fix before a real supplier, not demo-blocking + +| # | Finding | File | Fix / acceptance | +|---|---|---|---| +| P1-1 | Mapping **override dropdown is a no-op** (checklist counts it PASS) | `signal-ui/src/CSVImport.jsx:117-123` | Wire the override to actually remap, or correct the checklist claim | +| P1-2 | Visit window inconsistent: 180 (rules) vs 183 (confirm-visit) | `payer_rules.json`, `coverage_calculator.py:309` (hardcoded 180), `main.py:584-596` (183) | One canonical window (183 = "6 months"), read from config everywhere; remove hardcoded 180 | +| P1-3 | Ambiguous date parsing defaults US `%m/%d/%Y` silently | `api/normalizer.py:154-166` | Detect day>12 to lock format per batch; warn when ambiguous | +| P1-4 | No upload size cap; whole file read + decoded twice (OOM risk) | `api/main.py:400-404`, `persistence.py:104` | Reject > N MB (413) + row cap | +| P1-5 | `NO_RECENT_SHIPMENT` priority (5) sorts above clean `ACTIVE` (0) | `coverage_calculator.py:301` | Route through `_compute_priority` or set below ACTIVE | +| P1-6 | `patient_id` aliases include bare `id`/`patient` (could mis-map a name column to the crosswalk key) | `api/normalizer.py:20-26` | Drop overly generic aliases or flag low-confidence patient_id mapping | +| P1-7 | Root `vercel.json` serves a **static demo** (`signal-ui/demo/`), not the live React app | `vercel.json` | Reconcile: the live deploy uses a different config; root file is stale/misleading and should match what actually ships | + +--- + +## 5. Compliance & trust — open before any supplier uploads + +**Genuinely solid (verified):** PHI allowlist at the normalizer (name/DOB/SSN never mapped or stored), `patient_id` SHA-256 hashed before storage and logging, visit-date priority chain correct, backend auth enforced, empty/malformed CSV handled safely. + +**Must close (verified gaps):** +- **C-1. Privacy page claims a BAA that does not exist.** `privacy-policy.md:88` / `Privacy.jsx` assert an executed BAA with cloud providers. No BAA exists. This is a material misrepresentation a supplier (or regulator) could rely on. **Fix the copy now.** Also reconcile retention (Privacy.jsx says 6 years, privacy-policy.md says 7). +- **C-2. The audit log is never written to the database.** `audit_logger.log_event()` only persists when a `db_conn` is passed, and no caller in `main.py` ever passes one. Entries go to stdout only. A "6-year audit retention sink" cannot be claimed yet. Live calls also hardcode `"demo_user"`/`"0.0.0.0"`. +- **C-3. WORM is not effective.** The RLS migration exists (`supabase/migrations/20260607000001_audit_log_worm.sql`) but the app connects with the Supabase **service role**, which bypasses RLS; and the table receives no rows (C-2). Docs also disagree on the table name (`audit_log` vs `audit_events`). +- **C-4. Latent master-key leak path.** `signal-ui/src/lib/api.js:8` reads `VITE_SIGNAL_API_KEY` and sends it as `X-API-Key`, which the backend treats as a **full-access service account** bypassing Clerk. It's empty today (safe), but if anyone ever sets it in Vercel, the master key ships in public JS. **Delete this code path.** +- **C-5. MFA not verified** on any admin console (Supabase, Railway, Vercel, Clerk, GitHub/Forgejo). ~30 min, no engineering. Required even at zero-PHI — these consoles hold the keys. +- **C-6. PHI-at-normalizer is implicit, not enforced.** PHI columns are dropped (good) but an upload containing `name`/`dob`/`ssn` succeeds silently rather than being refused. Downgrade checklist PASS → PARTIAL; add an explicit reject-on-PHI guard. +- **C-7. Deletion procedure never tested** (policy written, never exercised) — and synthetic rows from this validation are now in the DB to delete. +- **C-8. Tabletop incident exercise** not run; **production access scope** not documented. +- **C-9. LOI + NDA not executed** with Gaboro, and **FDA CDS exemption memo** not written/attorney-reviewed. + +--- + +## 6. Readiness by audience + +- **Internal demo on clean data:** works the moment P0-1 is resolved (revert to dev Clerk). The engine is demo-quality. +- **Controlled pilot, Gaboro, synthetic/de-identified data:** reachable after P0-1..P0-4 + C-1, C-4, C-5, C-6, C-7, C-9, and confirming the Railway env vars are set. No BAA/host change required at zero-PHI. +- **Real patient data:** **not on this stack.** Gated entirely to the Production Readiness Whitepaper (BAA, host, Clerk prod, FIDO2 MFA, effective WORM, attorney review). + +--- + +## 7. Remediation backlog (priority order — for Pi to design, Claude Code to build) + +1. **Restore the live app** (P0-1, decision A or B from Kisa). +2. **Fix MA payer classification** (P0-2) — highest product-credibility risk. +3. **Fix duplicate-patient record pairing** (P0-3). +4. **Make doc-status overrides recompute the gap** (P0-4). +5. **Correct the privacy page** (C-1) and **delete the `VITE_SIGNAL_API_KEY` path** (C-4) — fast, high-trust-impact. +6. **Wire the audit log to actually write + real user/IP** (C-2), then **effective WORM** (C-3). +7. **Confirm MFA on all consoles** (C-5), **test deletion** (C-7), **run tabletop** (C-8), **document access scope** (C-8). +8. **P1 correctness cleanups** (override dropdown, 180/183, date parsing, size cap, priority, aliases, vercel.json). +9. **Paperwork:** execute LOI + NDA, write FDA CDS memo (C-9) — attorney-gated, parallel track. + +--- + +*Cross-reference: an internal quality review at `docs/reviews/2026-06-11-quality-review.md` independently flagged several of these (audit-log wiring, demo_user placeholders, override dropdown no-op, VITE_SIGNAL_API_KEY, implicit PHI guard). This validation confirms them live and adds the P0-1 live-outage and P0-2 MA-classification findings.* diff --git a/docs/production-readiness-whitepaper-2026-06-21.md b/docs/production-readiness-whitepaper-2026-06-21.md new file mode 100644 index 0000000..a8e74fa --- /dev/null +++ b/docs/production-readiness-whitepaper-2026-06-21.md @@ -0,0 +1,97 @@ +# Signal — Production Readiness Whitepaper + +**Date:** 2026-06-21 +**Author:** Claude Code +**Companion to:** `pilot-e2e-validation-2026-06-21.md` (validate the pilot first; this is the road from there to production) +**Purpose:** Define exactly what it takes to move Signal from its current posture to (1) a controlled pilot and (2) full production where a supplier's own staff log in and rely on Signal with **real patient identifiers**. For Pi to design the sequence; Claude Code builds it. + +--- + +## 1. Where we are today (verified 2026-06-21) + +- **Auth:** Clerk migration is **half-done and broken**. Front-end deployed with a production key pointing at `clerk.sttilsolutions.com`, but that domain's DNS is wrong (CNAME'd to Vercel, not Clerk), so the live app is a blank screen. Backend still validates against the **dev** Clerk instance. (See pilot validation P0-1.) +- **Database:** Supabase **Pro** ($25/mo). No BAA. Reachable, 1 org. +- **Host:** Railway. No BAA available. +- **MFA:** not confirmed on any admin console. +- **Data:** synthetic / de-identified only. No live PHI has entered the system. +- **Audit log:** code exists but never writes to the DB. + +The single most important framing: **Signal has two very different finish lines**, and conflating them is what produced the "100% ready" overstatement. Keep them separate. + +--- + +## 2. Two finish lines + +### Finish line A — Controlled pilot (zero live PHI) + +The supplier substitutes synthetic patient IDs and offsets dates **before** export, keeping the real crosswalk on their side. Because no PHI reaches STTIL, the full HIPAA Security Rule build is **not triggered**. Reference: `docs/hipaa-deployment-analysis-v1.md` (zero-PHI path) and `docs/pilot-guide-v1.md`. + +**Required for Finish line A:** +- **Clerk:** A production instance is **strongly recommended** (dev `pk_test_` instances carry usage caps, a dev banner, and can be reset — not appropriate for a real supplier's staff). Custom domain + full DNS are *not strictly required* at zero-PHI, but the front-end/back-end Clerk instances must **match** and the app must actually load. Fastest correct state: either finish the prod instance properly, or revert both halves to one working instance. +- **BAA:** not required (no ePHI). The LOI must state plainly that no live ePHI is transmitted. +- **Host:** Railway is fine. +- **MFA:** required on all admin consoles; any method acceptable at zero-PHI. +- **Config:** confirm `ALLOWED_ORIGINS` is locked to the real Vercel domain (today the regex accepts **any** `*.vercel.app` with credentials), `CLERK_JWKS_URL` and `SIGNAL_API_KEY` set, and the deployed URL serves the Clerk-gated React app (not the static demo in `vercel.json`). +- **Trust copy:** remove the false BAA claim from the privacy page. +- **Operational proof:** deletion test run, incident tabletop run, access scope documented. +- **Paperwork:** LOI + NDA executed (attorney-reviewed), FDA CDS exemption memo written. + +### Finish line B — Full production (real patient identifiers / ePHI) + +This crosses into ePHI and turns on the **entire** Real-PHI gate. Reference: `docs/hipaa-deployment-analysis-v1.md` (Option B "HIPAA SaaS") and the Real-PHI checklist in `CLAUDE.md`. + +**Required for Finish line B (all mandatory):** + +1. **Business Associate Agreements** — per relationship: STTIL ↔ supplier (before any ePHI, §164.308(b)); STTIL ↔ host; STTIL ↔ Anthropic if any AI layer touches ePHI. **Supabase Pro has no BAA** — requires Supabase **Team** ($599/mo) or migration to AWS RDS. +2. **BAA-capable host** — Railway must be replaced (AWS ECS Fargate / Atlantic.Net / equivalent). Railway offers no HIPAA BAA. +3. **Clerk production, properly** — `pk_live_`, custom domain (`clerk.sttilsolutions.com`), and the full Clerk DNS set: + - `clerk` → `frontend-api.clerk.services` ← **this is the record currently wrong** + - `accounts` → `accounts.clerk.services` ← already correct + - `clkmail` → `mail..clerk.services` + - `clk._domainkey` → `dkim1..clerk.services` + - `clk2._domainkey` → `dkim2..clerk.services` + - and switch the **backend** `CLERK_JWKS_URL` to the production instance so both halves match. +4. **Phishing-resistant MFA (FIDO2/WebAuthn)** — mandatory under 2026 §164.312(d). SMS OTP does not satisfy it. +5. **Encryption** — AES-256 at rest confirmed/attested (RDS or Supabase Team), TLS 1.3 enforced. +6. **Effective, immutable audit logging** — DB-trigger immutability or nightly export to object storage with Object Lock (RLS alone is bypassed by the service role), AND the audit table must actually receive rows. +7. **Governance** — documented annual risk assessment (HHS SRA Tool), tested incident response, tested 72-hour restoration, continuous monitoring/alerting, full administrative-safeguards review with attorney. +8. **FDA CDS exemption memo** finalized and attorney-reviewed. + +**Timeline estimate (from `hipaa-deployment-analysis-v1.md`):** ~6–11 weeks to compliant first-ePHI contact; critical path = host BAA execution + FIDO2 MFA rollout. + +--- + +## 3. One-line distinction + +> **Controlled pilot** = paperwork + MFA + correct env config + corrected privacy copy on the existing zero-PHI stack. +> **Full production** = a new BAA-capable host, Supabase Team/RDS under a BAA, Clerk production with custom domain + DNS, phishing-resistant MFA, effective WORM audit logging, encryption attestation, and an attorney-reviewed Security Rule posture. + +--- + +## 4. Recommended sequence (for Pi to design) + +**Phase 0 — Unbreak (today):** restore the live app to one consistent, working Clerk instance (pilot validation P0-1). This is a prerequisite for any demo and must not wait on the full prod build. + +**Phase 1 — Production Clerk, done right:** stand up the prod instance, fix the `clerk` CNAME → `frontend-api.clerk.services`, add the remaining DNS records, migrate the **backend** JWKS to prod, verify login end-to-end on `signal.sttilsolutions.com`. (This is the Clerk work Kisa is routing to Pi.) + +**Phase 2 — Trust & security hardening (no host change):** correct privacy copy, delete the `VITE_SIGNAL_API_KEY` path, wire the audit log to write real rows, confirm MFA, lock CORS to the real domain. + +**Phase 3 — Compliance build for ePHI (the heavy lift):** BAA-capable host migration, Supabase Team/RDS + BAA, FIDO2 MFA, effective WORM, encryption attestation, risk assessment, attorney review. + +Phases 0–2 are reachable quickly and unlock a credible controlled pilot. Phase 3 is a 6–11 week project that should begin only when a real-PHI contract is in sight and funded. + +--- + +## 5. Cost deltas to anticipate + +| Item | Today | Production | +|---|---|---| +| Database | Supabase Pro $25/mo | Supabase Team **$599/mo** (BAA) or AWS RDS | +| Host | Railway (no BAA) | AWS ECS Fargate / Atlantic.Net (BAA) | +| Auth | Clerk dev (free tier) | Clerk production (paid tier as users grow) | +| MFA | none | FIDO2 hardware/passkeys | +| Legal | — | BAAs, Security Rule review, FDA CDS memo | + +--- + +*All technical claims here are corroborated by the live 2026-06-21 validation (`pilot-e2e-validation-2026-06-21.md`) and `docs/hipaa-deployment-analysis-v1.md`.*