# Cyclone — Requirements, Architecture, Tasks, and Test Strategy

**Date:** 2026-06-23
**Status:** Round 2 (post-review corrections — ready for verification)
**Audience:** New engineer or AI agent who needs to understand Cyclone's contract, the work that's already shipped, the work that's not, and what "done" means before they write a single line of code.
**Scope:** Local-only, single-operator, single-host EDI claims-management suite for one billing office, currently trading with Colorado Medicaid (CO XIX).
**Sister docs:** [`README.md`](../README.md) is the operator-facing entry point; [`docs/ARCHITECTURE.md`](ARCHITECTURE.md) is the top-level technical design (process topology, package layout, data flow, lifecycle); [`docs/superpowers/specs/`](superpowers/specs/) holds per-SP design specs; [`docs/superpowers/plans/`](superpowers/plans/) holds per-SP implementation plans; [`docs/reviews/2026-06-20-cyclone-completeness-review.md`](reviews/2026-06-20-cyclone-completeness-review.md) is the industry-scope gap analysis; [`docs/reference/`](reference/) holds condensed X12 / payer notes.

---

## 0. How to read this document

| If you want to know… | Go to |
|---|---|
| What Cyclone is and is not | §1, §2 |
| What it must do (functional) | §3 |
| What it must *not* fail at (non-functional) | §4 |
| How the system is built | §5 |
| Which sub-project (SP) owns which requirement | §3 + §6 traceability matrix |
| What "shipped" means | §7 |
| What "done" means for the project as a whole | §8 |
| How we test | §9 |
| What we're assuming to be true | §10 |
| What's still missing or risky | §11 |

Every requirement (§3, §4) has an ID; every ID is traceable to a sub-project (§6) which in turn points at a design spec and an implementation plan. The matrix at the end of §6 is the single index that ties everything together.

**Round history.** Round 1 (2026-06-23) produced this draft. Two independent reviewers (Reviewer A — components/data-model/deps/DoD; Reviewer B — traceability/consistency audit) read it against the actual codebase and surfaced 12 factual errors and 6 structural improvements. Round 2 (this version) applies every factual correction. Round 3 (pending) will re-verify reviewer agreement on the corrected doc.

---

## 1. The contract — what Cyclone is

A self-hosted EDI claims-management suite for a single billing office. Parses 837P professional claims and 835 ERA remittances (X12 005010X222A1 and 005010X221A1), with a local-only FastAPI backend and a React UI for browsing, filtering, and inspecting the parsed data.

- **Local-only on purpose:** binds `127.0.0.1`, requires login (auth boundary is HTTP; bcrypt + HttpOnly session cookie), no internet exposure, single operator, single machine, one trading partner (Colorado Medicaid, currently).
- **Single-process:** the backend is one Python process; the frontend is one Vite-served React app; there is no message broker, no separate worker, no queue.
- **Single source of truth:** SQLite at `~/.local/share/cyclone/cyclone.db` (overridable via `CYCLONE_DB_URL`); optionally encrypted at rest via SQLCipher (SP12).
- **Threat model:** a process on the same host, plus a deliberate HTTP-layer login (no remote unauthenticated read or write). File-system threats (stolen/imaged drive) are still the primary concern; SQLCipher at rest + the macOS Keychain handle those. The SP23 product fork changes the threat model to "remote operator on LAN."

The Cyclone codebase has honored this contract end-to-end across 22 shipped sub-projects. Every design spec re-asserts the local-only / single-operator / single-payer / no-auth posture and lists (often at length) what is intentionally out of scope.

---

## 2. Scope boundaries

### 2.1 In scope (today, after SP1–SP22)

| Capability | SP |
|---|---|
| 837P (005010X222A1) parse + serializer with round-trip guarantee | SP1 + SP8 |
| 835 (005010X221A1) parse + CAS deep-parsing | SP1 + SP3 |
| 999, TA1, 270, 271, 277CA parse / generate | SP3 + SP10 |
| Structural validation rules (R021, R034, R035, R200, R210, …) | SP3 + SP20 |
| SQLite persistence with forward-compatible migrations | SP2 + SP9–SP17 |
| Automatic 837P ↔ 835 reconciliation; manual reconciliation UI | SP2 + SP7 |
| 7-state claim lifecycle with reversal handling | SP2 |
| Tamper-evident hash-chained audit log | SP11 |
| NDJSON pubsub on store writes + live-tail UI with reconnect | SP5 |
| 5-lane inbox (Rejected / Payer-rejected / Candidates / Unmatched / Done today) | SP6 + SP10 + SP14 |
| Per-line 837 SV1 ↔ 835 SVC adjustment audit | SP7 |
| Per-claim and per-remit detail drawers + Cmd-K search + batch diff + CSV export | SP4 + SP21 |
| Universal drilldown (drawer + peek modal everywhere) | SP21 |
| Multi-payer / multi-NPI config + clearhouse stub | SP9 |
| `paramiko`-backed SFTP wire to Gainwell MFT | SP13 |
| 24h MFT inbound scheduler with idempotent file processing | SP16 |
| SQLCipher encryption at rest + key rotation | SP12 + SP15 |
| Automated AES-256-GCM encrypted DB backups | SP17 |
| Structured JSON logging + PII scrubber | SP18 |
| Security hardening (body size limit, rate limit, security headers) | SP19 |
| NPI Luhn checksum + Tax ID format validation | SP20 |
| CycloneStore split (refactor; **in-flight on `refactor/store-split`**) | SP21 |
| Parse-then-decide upload dedup (idempotency) | SP22 |
| Pipeline automation agent (sibling project at `cyclone-pipeline/`) | SP22 |

### 2.2 Out of scope (today, by design)

| Item | Why |
|---|---|
| AS2 / AS4 (EDIINT, signed, encrypted, MDN-back) connectivity | Single-host local tool; the operator hands the 837 ZIP to the Gainwell MFT UI manually |
| Real-time 270/271 round-trip over CAQH CORE Phase II/III SOAP envelopes | Out of single-host scope; builder + parser are X12-only |
| 837I (institutional), 837D (dental), 834, 820, 275, 278, 276/277 | Single-payer scope; not currently demanded by the CO XIX workflow |
| NPPES NPI registry lookup (offline validation only) | Single-host, no internet exposure |
| ICD-10 / HCPCS / NDC vocabulary tables | Structural validation only; the operator has the source vocab |
| COB / secondary-claim generator | Workflow scope |
| HITRUST / SOC 2 / BAA template | Single-operator; threat model doesn't require them |
| HA / DR / load balancing / multi-host replication | Single-host scope |
| Prometheus / Grafana / alerting | `curl health \| mail` cron is sufficient |
| 2FA / SSO / OAuth | Single-operator |
| LUKS full-disk encryption | Operator / host concern |
| Component / E2E browser tests (Playwright) | Local-only, manual smoke scripts are sufficient |

### 2.3 Deferred with rationale

- **Public TLS / public domain / Caddy reverse proxy** — LAN-only bind is enough for the v1 single-Ubuntu-server posture; VPN handles outside access. (Re-evaluated if the Ubuntu Docker plan ships.)
- **Off-box automated backup shipping** — host cron / rsync is documented in the operator runbook, not automated in code.
- **Self-service password reset** — admin can reset via CLI; self-service is v2 (would only be relevant if auth ships).
- **Per-file encryption of prodfiles / `sftp_staging`** — relies on volume-level filesystem permissions + the SQLCipher-encrypted DB + the host being physically secure.
- **Watchtower / automatic updates** — manual `docker compose pull` keeps the operator in the loop on schema migrations.

---

## 3. Functional requirements

Each requirement is `FR-NN`, traceable to one or more sub-projects. Verification is "shipped + tested" (see §6) or "planned" (see §6.2).

| ID | Requirement | SP |
|---|---|---|
| FR-1 | Parse an uploaded 837P file into structured `Claim` records. | SP1 |
| FR-2 | Parse an uploaded 835 file into structured `Remittance` records with per-CAS adjustment rows. | SP1 + SP3 |
| FR-3 | Persist every successful parse to SQLite; survive backend restart. | SP2 |
| FR-4 | Run a forward-compatible migration runner (`PRAGMA user_version`) so the schema can evolve without Alembic. | SP2 |
| FR-5 | Auto-match each 835 `CLP` back to a prior 837 `Claim` within ±7 days via `(claim_id, service_date)`; apply lifecycle transitions; write `Match` and `ActivityEvent` rows. | SP2 |
| FR-6 | Maintain a 7-state claim lifecycle (`submitted → received → paid|partial|denied → reconciled → reversed`) with `state_before_reversal` preserved on reversal. | SP2 |
| FR-7 | Provide a `/reconciliation` page that lists unmatched claims and unmatched remittances and supports manual pair/unpair. | SP2 (tests live in `test_reconcile.py`, `test_reconcile_line_level.py`, `test_inbox_endpoints.py`, `test_api_gets.py`) |
| FR-8 | Run structural 837P validation rules R021 (NPI checksum, warning-only), R034 (`REF*G1` on frequency 7/8), R035 (`BHT06` transaction-type allowlist), R200/R210. | SP3 + SP20 |
| FR-9 | Generate a 999 ACK transaction set on 837 ingest when `?ack=true` is passed to `POST /api/parse-837`; persist it to the `acks` table. | SP3 |
| FR-10 | Parse inbound 999 ACKs and surface them on a `/acks` page with regenerable raw text. | SP3 |
| FR-11 | Parse + persist TA1 interchange ACKs (separate from 999 transaction-set ACKs). | SP3 |
| FR-12 | Build a 270 from a JSON payload (`POST /api/eligibility/request`) and parse an inbound 271 (`POST /api/eligibility/parse-271`) into `coverage_benefits`. | SP3 |
| FR-13 | Parse inbound 277CA files and stamp `payer_rejected` on claims whose STC category is A4/A6/A7 — monotonic (a looser later 277CA cannot downgrade). Also persist 277CA ACK rows to `two77ca_acks`. | SP10 |
| FR-14 | Provide a `/inbox` page with five lanes ordered by urgency: Rejected (999 R/E), Payer-rejected (277CA A4/A6/A7), Candidates (CLP didn't auto-match), Unmatched (waiting), Done today (last 24h terminal transitions). | SP6 + SP10 + SP14 |
| FR-15 | Score each Candidate on 4 weighted fields (patient control number 40, service date 25, charge amount 20, provider NPI 15); tier strong (≥75, Match enabled), weak (50–74, dimmed), hidden (<50). | SP6 |
| FR-16 | Provide per-claim and per-remit detail drawers with full raw X12 segments, state-history timeline, validation panel, line-level reconciliation tab, and party grid. URL is synced (`?claim=…`, `?remit=…`). | SP4 + SP7 + SP21 |
| FR-17 | Provide a side-by-side Batch Diff view (added / removed / changed claims). | SP4 (logic lives in `cyclone/batch_diff.py`) |
| FR-18 | Provide a global Cmd-K search across claims, remittances, activity, and acks. | SP4 |
| FR-19 | Provide per-page CSV export for claims, remittances, and inbox lanes. | SP4 |
| FR-20 | Provide a live-tail NDJSON stream on `/api/{claims,remittances,activity}/stream` with snapshot-first, `snapshot_end`, heartbeat, and reconnect. | SP5 |
| FR-21 | Surface a `TailStatusPill` with states `live / connecting / reconnecting / stalled / error / closed`, with backoff ladder `1s → 2s → 4s → 8s → 16s → 30s` capped. | SP5 |
| FR-22 | Tie every 835 CAS adjustment back to the specific 837 SV1 service line it adjudicates; surface unmatched-line warnings. | SP7 (line-reconciliation rows in `line_reconciliations`) |
| FR-23 | Serialize a single edited claim back to a valid X12 837P file with round-trip guarantee (113 prodfiles parse → serialize → parse to the same `claim_id`). | SP8 |
| FR-24 | Export a ZIP bundle of selected rejected claims as a batch ready to hand to the MFT UI. | SP8 |
| FR-25 | Load payer / payer_config / clearhouse config from `config/payers.yaml` (parsed via `pyyaml`) and 3 new DB tables (`providers`, `payers`, `payer_configs`); replace the in-code `PAYER_FACTORIES` dict. | SP9 |
| FR-26 | Push a `cyclone clearhouse submit` to the Gainwell MFT inbound path via `paramiko` (`SftpClient` lives in `cyclone/clearhouse/__init__.py`). Credentials read from the macOS Keychain via the `keyring` library at call time. | SP13 |
| FR-27 | Poll the Gainwell MFT inbound path every tick (configurable); route downloaded files to the right parser (999 / 835 / 277CA / TA1); idempotent via `processed_inbound_files`; crash-safe via per-file try/except. | SP16 |
| FR-28 | Optionally encrypt the SQLite file via SQLCipher (AES-256); key fetched from the macOS Keychain via `keyring`. Falls back to plain SQLite if the Keychain entry is missing. | SP12 |
| FR-29 | Rotate the SQLCipher key in place via `PRAGMA rekey`, serialized through a `threading.Lock` and SQLAlchemy `NullPool`; write `db.key_rotated` audit event with old + new key fingerprints and post-rotation `table_count`. | SP15 |
| FR-30 | Produce AES-256-GCM encrypted DB backups (PBKDF2-HMAC-SHA256, 200k iters) via the existing `BackupService`; two-step restore (`initiate` → `confirm` with one-shot 64-char hex token); retention pruning with a 30-day default. Backups table is `db_backups`. | SP17 |
| FR-31 | Emit all API / CLI / scheduler / backup logs as newline-delimited JSON with ISO-8601 ms timestamps; redact obvious PHI (NPIs, SSNs, DOBs, patient names) via a `PiiScrubber` filter on message + extras. | SP18 (uses stdlib `logging` + a JSON formatter, not structlog) |
| FR-32 | Reject oversize request bodies (413), rate-limit requests (429), and add default security headers via pure-ASGI middlewares; emit a tamper-evident `api.request_rejected` audit event on 413/429. | SP19 |
| FR-33 | Surface a rich `GET /api/health` response — DB connectivity, MFT scheduler state, backup scheduler state, live pubsub subscriber counts, last batch id + timestamp. | SP19 (`api_routers/health.py:28-40`) |
| FR-34 | Validate NPI with the CMS-published Luhn over `80840 + body` (warning-only — placeholder NPIs in test fixtures shouldn't block ingest). Reject Tax ID (EIN) with reserved prefixes (`00`, `07`, `80`–`89`). | SP20 |
| FR-35 | Split `CycloneStore` into per-concern modules (`store_persistence`, `store_reconcile`, `store_queries`, `store_mappers`) without changing the public store API. | SP21 (**in-flight** on `refactor/store-split` branch) |
| FR-36 | De-duplicate 837 / 835 uploads via a parse-then-decide workflow (`find_existing_batch_for_claim` / `find_existing_batch_for_remit`); relax the claims/remits PK to `(batch_id, id)` via migration 0014 to support re-parses into different batches. | SP22 (migration 0014 in worktree; not yet on `main`) |
| FR-37 | (Sibling project, not in this repo) Drive the full 7-phase round-trip — preflight → browser upload → parse verify → SFTP submit → TA1 wait → 999 wait → scan + report — with crash-safe resume, structured JSON logging, idempotency dedup, per-run folder. | SP22 (`cyclone-pipeline/`) |
| FR-38 | Provide a `DrillStackProvider` + `DrillDrawerHeader` shell so every drillable cell across the app opens a consistent drawer or peek modal. | SP21 |

---

## 4. Non-functional requirements

| ID | NFR | Source / SP |
|---|---|---|
| NFR-1 | **Local-only bind + auth.** Backend binds `127.0.0.1:8000` (overridable via `CYCLONE_PORT`); CORS allowlist is exact (`http://localhost:5173`); login required (bcrypt + HttpOnly session cookie; first admin bootstrapped from env vars `CYCLONE_ADMIN_USERNAME` + `CYCLONE_ADMIN_PASSWORD`); dev/test escape hatch via `CYCLONE_AUTH_DISABLED=1` (logs WARNING at boot). | SP1 + auth (2026-06-23 merge) + SP24 (doc reconciliation) |
| NFR-2 | **Determinism.** Parser / serializer round-trip is guaranteed on 113 real prodfiles (`docs/prodfiles/claims/*.x12`); canonical fields, not byte-identity (called out in the SP8 spec). | SP8 |
| NFR-3 | **Audit completeness.** Every reconciliation anomaly, every state transition, every 999/277CA reject writes an `ActivityEvent` (in `activity_events` table, un-chained; powers the `/activity` feed and inbox state transitions). | SP2 + SP10 |
| NFR-4 | **Tamper-evidence.** A separate `audit_log` table (SP11) carries SHA-256 hash-chained rows for security-sensitive events (login attempts, rejections, key rotations, backup lifecycle, rejected requests). `GET /api/admin/audit-log/verify` detects any break. **The `activity_events` (NFR-3) and `audit_log` (NFR-4) tables are distinct** — different semantics, different audiences. New code that needs an audit row must decide which one to write to. | SP11 |
| NFR-5 | **Encrypted at rest (optional).** SQLite is encrypted via SQLCipher AES-256 when the macOS Keychain entry exists and `sqlcipher3` is installed; plain SQLite otherwise. | SP12 |
| NFR-6 | **Encrypted backups.** Backups are AES-256-GCM encrypted at rest (PBKDF2-HMAC-SHA256, 200k iters); passphrase lives in the macOS Keychain (`cyclone backup init-passphrase`). | SP17 |
| NFR-7 | **Logging discipline.** API / CLI / scheduler / backup logs flow through a `JsonFormatter` (NDJSON, ISO-8601 ms) by default; `PiiScrubber` redacts obvious PHI; configurable via `CYCLONE_LOG_LEVEL` / `CYCLONE_LOG_FILE` / `CYCLONE_LOG_JSON` / `CYCLONE_LOG_NO_PII_SCRUB`. | SP18 |
| NFR-8 | **Operational hygiene.** Request body size limit, rate limit, and default security headers are middleware-enforced (SP19). `EXEMPT_PATHS = ("/api/health", "/healthz", "/readyz")` is the documented bypass list (`cyclone/security.py:201`). | SP19 |
| NFR-9 | **Thread-affinity.** SQLCipher operations are serialized through a `threading.Lock` + SQLAlchemy `NullPool` to keep SQLCipher thread-affine under FastAPI's per-request threadpool. | SP12 + SP15 |
| NFR-10 | **Live-tail resilience.** Heartbeat every 15s default (`CYCLONE_TAIL_HEARTBEAT_S`); client flips to `stalled` after 30s of total silence (heartbeat included); reconnect backoff capped at 30s. | SP5 |
| NFR-11 | **Payer config externalized.** Payer / payer_config / clearhouse config lives in `config/payers.yaml` + DB tables, not in code; a new payer is a YAML + DB row, not a code change. Note: `co_medicaid()` factory in `cyclone/parsers/payer.py:57-58` still exists as a fallback for tests and the default-payer selection (R-4 in §11). | SP9 |
| NFR-12 | **Idempotency.** Inbound MFT files are de-duplicated via `processed_inbound_files`; 837/835 uploads are de-duplicated via the parse-then-decide workflow with `(batch_id, claim_id)` / `(batch_id, remit_id)` relaxed PKs. | SP16 + SP22 |
| NFR-13 | **Crash-safety.** Incoming files are processed inside per-file try/except so a bad file doesn't stop the MFT loop; the pipeline agent has crash-safe resume keyed by run id. | SP16 + SP22 |
| NFR-14 | **Single-process.** No message broker, no separate worker, no queue; everything runs in one Python process. | (project-wide constraint) |
| NFR-15 | **Test density.** Backend tests: **964 collected** (`pytest --collect-only`, 2026-06-23); frontend test files: **73**; prodfiles are exercised (113 + 19 + 1369), not just minimal fixtures. | (project-wide) |
| NFR-16 | **Documentation discipline.** Every shipped sub-project has a design spec + implementation plan + smoke test; a new engineer can read the project top-to-bottom. (SPs 9–16 ship without on-disk plan files — see §6.1 — but their `feat(spN)` commits are on `main` and carry their own commit-message plan.) | (project-wide) |
| NFR-17 | **Fail-soft posture.** Reconciliation crashes don't lose the 835; the activity event records the failure. | SP2 |
| NFR-18 | **Migration safety.** Migrations are forward-only via `PRAGMA user_version`; rollback procedures are documented per migration; the `user_version` runner is idempotent. **No checksum manifest exists today** (R-13 in §11). | SP2 + SP22 |

---

## 5. Architecture overview

### 5.1 Components

**Backend (one Python process, FastAPI on uvicorn) — modules under `backend/src/cyclone/`:**

| Module | Owns | Notes |
|---|---|---|
| `cyclone.api` | HTTP routes, content negotiation, CORS allowlist, lifespan | 3,145 LOC (largest single file; partly split into `api_routers/` for 4 sub-routes) |
| `cyclone.api_helpers` | NDJSON / content-negotiation / live-tail helpers | |
| `cyclone.api_routers` | `acks`, `admin`, `health` (`/api/health`), `ta1_acks` sub-routers | Mounted at `api.py:270-273`; only 4 routes split out — bulk of routes still in `api.py` |
| `cyclone.store` | Public store facade; persistence, reconciliation, queries, mappers | 2,172 LOC; SP21 split is in-flight on `refactor/store-split` |
| `cyclone.db` | SQLAlchemy engine, session factory, ORM models (18 tables) | |
| `cyclone.db_migrate` | `PRAGMA user_version` migration runner | |
| `cyclone.db_crypto` | Optional SQLCipher encryption at rest; key rotation | `rotate_key()` is destructive (rewrites every page) |
| `cyclone.audit_log` | Hash-chained `audit_log` (SP11, SHA-256 chain) | Distinct from `cyclone.activity_events` written by other modules |
| `cyclone.inbox_lanes` | The 5-lane inbox payload (Rejected / Payer-rejected / Candidates / Unmatched / Done today) | |
| `cyclone.inbox_state` | 999 envelope reject → claim state transitions | |
| `cyclone.inbox_state_277ca` | 277CA STC A4/A6/A7 → payer_rejected stamp (monotonic) | |
| `cyclone.providers` | Multi-NPI provider lookups | |
| `cyclone.payers` | Payer / payer_config lookups | Loads `config/payers.yaml` via `pyyaml` |
| `cyclone.secrets` | macOS Keychain-backed secret fetcher (wraps `keyring`) | |
| `cyclone.reconcile` | Pure-function 835→claim match + line-level match | No DB session inside; testable with fabricated ORM objects |
| `cyclone.scoring` | 4-field weighted candidate scoring (40/25/20/15) | Weights hardcoded — see R-10 in §11 |
| `cyclone.pubsub` | In-process EventBus (drop-oldest, per-kind fan-out) | In-process only; not thread-safe across asyncio event loops |
| `cyclone.backup` | AES-256-GCM encrypted backups (orchestration + CLI) | 216 LOC |
| `cyclone.backup_service` | Backup lifecycle (create / verify / restore) | 740 LOC |
| `cyclone.backup_scheduler` | 24h backup autostart loop | 315 LOC |
| `cyclone.scheduler` | asyncio MFT polling loop (SP16) | Uses `processed_inbound_files` for idempotency |
| `cyclone.security` | Per-IP rate-limit, body-size limit, security headers (`SecurityHeadersMiddleware`, `BodySizeLimitMiddleware`, `RateLimitMiddleware`) | `EXEMPT_PATHS` list at `:201` |
| `cyclone.logging_config` | JSON formatter, PII scrubber | |
| `cyclone.npi` | NPI Luhn + EIN format validators | |
| `cyclone.batch_diff` | Batch Diff engine (powers `GET /api/batch-diff`) | 12 public functions/classes |
| `cyclone.clearhouse` (`__init__.py`) | `SftpClient` (SP9 stub + SP13 paramiko), `InboundFile`, `make_client()` factory | 318 LOC; the SFTP home |
| `cyclone.edi` (`filenames.py`) | HCPF X12 File Naming Standards helpers | Used by SP13/SP16 outbound filename construction |
| `cyclone.cli` | Click CLI | |
| `cyclone.__main__` | `python -m cyclone serve` | |
| `cyclone.parsers/` | X12 tokenizer, models, validator, writers, 277CA, 999, TA1, 270, 271, CAS, seg | 25 `.py` modules |

**Stale artifact (do not import):** `backend/src/cyclone/workflow/` contains only a `__pycache__/` subdir with stale `.pyc` files (no `__init__.py`, no source). The actual sources live at the top level. Recommend deleting the directory.

**Frontend (one Vite-served React app) — directories under `src/`:**

| Directory | Owns |
|---|---|
| `src/pages/` | Acks, ActivityLog, BatchDiff, Batches, Claims, Dashboard, Inbox, Providers, Reconciliation, Remittances, Upload (11 pages) |
| `src/hooks/` | TanStack Query hooks, URL-state hooks, live-tail hooks, keyboard hooks, batch-export, search, drawer keyboard, count-up, provider detail, payer summary, parse, ack detail (35+ files) |
| `src/lib/` | `api.ts`, `format.ts`, `utils.ts`, `csv.ts`, `download.ts`, `event-routing.ts`, `tail-stream.ts`, `inbox-api.ts` (all with paired `.test.ts`) |
| `src/components/` | 3 drawer families (`ClaimDrawer/` ~15 files, `RemitDrawer/` ~10 files, `ProviderDrawer/` ~6, `AckDrawer/` 3); `inbox/` (10 files), `drill/` (10 files, includes `DrillStackProvider` + `DrillDrawerHeader`), `charts/` (4 files), `ui/` (18 shadcn primitives — radix + cva + tailwind-merge), `KeyboardCheatsheet/` (3 files), plus top-level ActivityFeed, AnimatedNumber, BatchDetail, BatchDiffView, BatchesList, ClaimCard837, DominantKpiCard, EditorialNote, ExportBar, ExportCsvButton, KpiCard, Layout, NewClaimDialog, PageHeader, SearchBar, SearchResults, Sidebar, Sparkline, StatusBadge, StatusPill, TailStatusPill, TickerTape |
| `src/store/` | `index.ts` (zustand sample data), `tail-store.ts` (FIFO-capped live tail slices) |
| `src/types/` | `index.ts` (flat barrel of shared TS types) |

### 5.2 Data model

**12 SQL migrations** in `backend/src/cyclone/migrations/` (verified on disk 2026-06-23):

| # | File | Tables / columns added |
|---|---|---|
| 0001 | `0001_initial.sql` | `batches`, `claims`, `remittances`, `cas_adjustments`, `matches`, `activity_events` (6 tables) |
| 0002 | `0002_acks.sql` | `acks` |
| 0003 | `0003_drop_claims_remits_unique_constraints.sql` | Drops `claims` and `remittances` UNIQUE constraints |
| 0004 | `0004_rejections_and_state_history.sql` | Adds `rejection_reason`, `rejected_at`, `resubmit_count`, `state_changed_at` to `claims` (no new table) |
| 0005 | `0005_create_ta1_acks.sql` | `ta1_acks` |
| 0006 | `0006_line_reconciliation.sql` | `service_line_payments`, `line_reconciliations`; FK `service_line_payment_id` on `cas_adjustments`; `claim_level_adjustment_amount` on `remittances` |
| 0007 | `0007_providers_payers_clearhouse.sql` | `providers`, `payers`, `payer_configs`, `clearhouse` |
| 0008 | `0008_payer_rejected_columns.sql` | `two77ca_acks` (table) + `payer_rejected` columns on `claims` |
| 0009 | `0009_audit_log.sql` | `audit_log` (SHA-256 hash-chained) |
| 0010 | `0010_payer_rejected_acknowledged.sql` | `payer_rejected_acknowledged` column on `claims` |
| 0011 | `0011_processed_inbound_files.sql` | `processed_inbound_files` (SP16 idempotency) |
| 0012 | `0012_backups.sql` | `db_backups` (SP17) |

**In-flight on `claims-unique-fix` worktree (not yet on `main`):**

| # | File | Tables / columns added |
|---|---|---|
| 0013 | `0013_drop_claims_unique_constraint.sql` | Drop `claims` UNIQUE constraint |
| 0014 | `0014_relax_claims_remits_pk.sql` | Relax PK to `(batch_id, id)` (SP22) |

**ORM models** (declared in `backend/src/cyclone/db.py` — 18 classes total):

| Python class | Table | Notes |
|---|---|---|
| `Batch` | `batches` | |
| `Claim` | `claims` | |
| `Remittance` | `remittances` | |
| `CasAdjustment` | `cas_adjustments` | |
| `ServiceLinePayment` | `service_line_payments` | Created by 0006; SP1+ |
| `LineReconciliation` | `line_reconciliations` | Created by 0006; SP7 backbone |
| `Match` | `matches` | Reversals add a second row (NOT UNIQUE on `(claim_id, remittance_id)`) |
| `Ack` | `acks` | |
| `Ta1Ack` | `ta1_acks` | |
| `ActivityEvent` | `activity_events` | Created by 0001; SP2 — distinct from `AuditLog` |
| `Two77caAck` | `two77ca_acks` | Created by 0008; SP10 |
| `Provider` | `providers` | |
| `Payer` | `payers` | |
| `PayerConfigORM` | `payer_configs` | |
| `ClearhouseORM` | `clearhouse` | Singleton row (`id = 1`) |
| `AuditLog` | `audit_log` | SHA-256 hash-chained |
| `ProcessedInboundFile` | `processed_inbound_files` | |
| `DbBackup` | `db_backups` | Note: table is `db_backups`, NOT `backups` |

**Boundary contracts:**

| Module | Public API | Pure? | Notes |
|---|---|---|---|
| `cyclone.db` | `init_db()`, `SessionLocal()`, model classes | No (engine + schema) | |
| `cyclone.db_migrate` | `run(engine)` | Yes (modulo engine) | |
| `cyclone.reconcile` | `match()`, `apply_payment()`, `apply_reversal()`, `split_unmatched()`, line-level match | **Yes** (no DB session inside) | Unit-testable with fabricated ORM objects |
| `cyclone.scoring` | `score_pair(...)` | Yes | Weights hardcoded — see R-10 |
| `cyclone.audit_log` | `append_event(...)`, `verify_chain()` | No (DB-owning) but append is deterministic | SHA-256 chain |
| `cyclone.activity_events` (via store) | `append(...)` | No (DB-owning) | Un-chained; powers `/activity` feed |
| `cyclone.store` | `add()`, `get_batch()`, `iter_*()`, `list_unmatched()`, `manual_match()`, `manual_unmatch()` | No (owns sessions, calls reconcile) | |
| `cyclone.api` | HTTP routes | No | |
| `cyclone.pubsub` | `subscribe_raw(kind, queue_size=256)`, `publish(kind, event)` | No (in-process) | Drop-oldest on full queue |
| `cyclone.backup` + `backup_service` + `backup_scheduler` | `initiate_backup`, `confirm_restore`, `tick()` | No (DB-owning) | |
| `cyclone.db_crypto` | `is_encryption_enabled()`, `rotate_key()` | No | `rotate_key()` is destructive |
| `cyclone.clearhouse` | `SftpClient`, `make_client()`, `InboundFile` | No | Real `paramiko` in SP13 |
| `cyclone.batch_diff` | `diff_batches()`, `diff_batches_to_wire()` | Yes | Pure function over claim/remit summaries |
| `cyclone.cas_codes` | `CARC_REASON_CODES` dict | Yes | Annual refresh is manual (R-22 in completeness review) |
| `cyclone.edi.filenames` | filename regexes + helpers | Yes | |
| `cyclone.logging_config` | `JsonFormatter`, `PiiScrubber`, `CycloneDevFormatter` | No (installs handlers) | |
| `cyclone.npi` | `validate_npi()`, `validate_tax_id()` | Yes | |
| `cyclone.parsers.*` | Pure-function parse + serialize | Yes | |

### 5.3 Dependencies

**Backend runtime (verified against `backend/pyproject.toml:10-24`):**

- `pydantic>=2.6,<3`
- `click>=8.1,<9`
- `fastapi>=0.110,<1`
- `uvicorn[standard]>=0.27,<1` (the `[standard]` extra pulls `httptools`, `uvloop`, `websockets`, `watchfiles`)
- `python-multipart>=0.0.9,<1`
- `sqlalchemy>=2.0,<3`
- `pyyaml>=6.0,<7` ← **loads `config/payers.yaml`** (SP9)
- `keyring>=25.0,<26` ← **macOS Keychain backend** used by `cyclone/secrets.py` (SP12/SP13/SP15); on Linux dispatches to Secret Service / kwallet
- `cryptography>=49.0,<50`

**Backend optional extras:**

- `[sqlcipher]` → `sqlcipher3>=0.6,<1` (SP12; falls back to plain SQLite if missing)
- `[sftp]` → `paramiko>=3.4,<6` (SP13)

**Backend dev/test (`backend/pyproject.toml:27-33`):**

- `pytest>=8.0`, `pytest-cov>=4.1`, `pytest-asyncio>=0.23,<1` (asyncio_mode = "auto"), `pytest-randomly>=4.1`, `httpx>=0.27,<1` ← **dev only, not runtime**
- `ruff`, `mypy` — **not installed** (per completeness review §3.2.30 deferred)

**Frontend runtime (verified against `package.json`):**

- `react@^18.3.1`, `react-dom@^18.3.1`
- `@tanstack/react-query@^5.101.0`
- `lucide-react@^0.453.0`
- `sonner@^1.5.0`
- `zustand@^4.5.5`
- `react-router-dom@^6.27.0` (note: package name is `react-router-dom`, not `react-router`)
- `@radix-ui/react-dialog@^1.1.2`, `@radix-ui/react-label@^2.1.0`, `@radix-ui/react-select@^2.1.2`, `@radix-ui/react-slot@^1.1.0`, `@radix-ui/react-tabs@^1.1.15` ← powers `src/components/ui/` (shadcn primitives)
- `class-variance-authority@^0.7.0`, `clsx@^2.1.1`, `tailwind-merge@^2.5.4`, `ansi-styles@^6.2.3` ← shadcn helper stack

**Frontend dev/test:**

- `vitest@^4.1.9` ← **unusually new version** (worth a sanity check — current stable is 1.x/2.x; verify it's intentional)
- `@testing-library/react@^16.3.2`
- `happy-dom@^20.10.6` ← **not jsdom** (vitest config line 13 explicitly says they don't need a DOM env: "Node 18+ exposes `Response` / `fetch` globals")
- `typescript@^5.6.3`, `vite@^5.4.10`, `@vitejs/plugin-react@^4.3.3`
- `@types/node`, `@types/react`, `@types/react-dom`, `autoprefixer`, `postcss`, `tailwindcss@^3.4.14`, `tailwindcss-animate`

**External services:** None. (The MFT path is one-way outbound to Gainwell MFT, polled via SFTP — not a service Cyclone depends on, it's a sink it writes to.)

**OS-level:** macOS Keychain (backed by `keyring`); SQLite / SQLCipher shared library; `ssh` host keys / known_hosts for SP13. SP23 would add `tini` + `curl` + `nginx` in container scenarios.

### 5.4 One-page data flow

```
                          ┌──────────────────────────────┐
  Operator drops file     │  FastAPI backend             │
  on /upload      ───────▶│  /api/parse-837|835|999|     │
  (or SFTP inbound)       │   277ca|ta1|270              │
                          │                              │
                          │   parse → validate ──┐       │
                          │                      ▼       │
                          │   reconcile  ──▶  store ──▶ activity_events (un-chained)
                          │                      │       │
                          │                      │       └─▶ audit_log (SP11, chained)
                          │                      ▼             (login / rejected / key events)
                          │                  SQLite (encrypted optional)
                          │                      │
                          │                      ▼
                          │   pubsub.publish(<kind>)
                          └──────────┬───────────────────┘
                                     │ NDJSON stream
                                     ▼
                          ┌──────────────────────────────┐
                          │  React UI (Vite-served)      │
                          │  /claims /remits /inbox      │
                          │  /reconciliation /providers  │
                          │  /acks /activity /batches    │
                          │  /upload /dashboard /health  │
                          │  drill stack everywhere      │
                          └──────────────────────────────┘
```

---

## 6. Sub-project inventory

### 6.1 Shipped SPs (in `main`)

Each row's "Spec" / "Plan" point to the design spec and implementation plan on disk. SPs without a plan file on disk shipped via `feat(spN)` commits on `main` whose commit messages carry the implementation order; those commit logs substitute for a plan file. Closing the plan-file gap is on the round-3 backlog.

| SP | Title | Spec | Plan | Acceptance |
|---|---|---|---|---|
| SP1 | Production-readiness: in-memory store, GET endpoints, react-query, reference notes | [spec](superpowers/specs/2026-06-19-cyclone-production-readiness-design.md) | [plan](superpowers/plans/2026-06-19-cyclone-production-readiness.md) | Met |
| SP2 | DB + reconciliation: SQLite, 7-state lifecycle, auto-match on 835 ingest, manual reconciliation UI | [spec](superpowers/specs/2026-06-19-cyclone-db-reconciliation-design.md) | [plan](superpowers/plans/2026-06-19-cyclone-db-reconciliation.md) | Met |
| SP3 | EDI features: 837P rules (R034/R035), CAS reason codes, 999 ACK, TA1 ACK, 270/271 | [spec](superpowers/specs/2026-06-20-cyclone-edi-features-design.md) | [plan](superpowers/plans/2026-06-20-cyclone-edi-features.md) | Met |
| SP4 | Claim drawer + Remit drawer + Cmd-K search + batch diff + CSV export | [spec](superpowers/specs/2026-06-20-cyclone-claim-drawer-design.md) | [plan](superpowers/plans/2026-06-20-cyclone-claim-drawer.md) | Met |
| SP5 | Live tail: NDJSON pubsub on Claims/Remittances/Activity, heartbeat, reconnect | [spec](superpowers/specs/2026-06-20-cyclone-live-tail-design.md) | [plan](superpowers/plans/2026-06-20-cyclone-live-tail.md) | Met |
| SP6 | Inbox workflow: 4 lanes (Rejected/Candidates/Unmatched/Done today), 4-field scoring, bulk actions | **No dedicated spec on disk** — SP6 ships via the SP4 spec's coverage and the workflow-automation plan | [plan](superpowers/plans/2026-06-20-cyclone-workflow-automation.md) | Met |
| SP7 | Per-line adjustment audit: 837 SV1 ↔ 835 SVC strict match, line reconciliation tab | [spec](superpowers/specs/2026-06-20-cyclone-line-reconciliation-design.md) | [plan](superpowers/plans/2026-06-20-cyclone-line-reconciliation.md) | Met |
| SP8 | Outbound 837P serializer: single-claim download + bundle resubmit ZIP | [spec](superpowers/specs/2026-06-20-cyclone-serialize-837-design.md) *(the spec's own title mistakenly says "Sub-project 7"; the file maps to SP8)* | [plan](superpowers/plans/2026-06-20-cyclone-serialize-837.md) | Met |
| SP9 | Multi-payer, multi-NPI, SFTP stub | [spec](superpowers/specs/2026-06-20-cyclone-multi-payer-npi-sftp-design.md) | (no plan file on disk; feat(sp9) commits on `main`) | Met |
| SP10 | 277CA + Payer-Rejected lane | (no dedicated spec on disk; covered inline in [parse-decide spec](superpowers/specs/2026-06-21-cyclone-parse-decide-workflow-design.md) and `cyclone/parsers/parse_277ca.py`) | (no plan file on disk; feat(sp10) commits on `main`) | Met |
| SP11 | Tamper-evident audit log | (no dedicated spec on disk; `cyclone/audit_log.py:116 append_event` and `:190 verify_chain` are the implementation) | (no plan file on disk; feat(sp11) commits on `main`) | Met |
| SP12 | Encryption at rest (SQLCipher) | (no dedicated spec on disk; `cyclone/db_crypto.py` is the implementation) | (no plan file on disk; feat(sp12) commits on `main`) | Met |
| SP13 | SFTP wire-up (paramiko) | (no dedicated spec on disk; extends SP9; `cyclone/clearhouse/__init__.py` holds `SftpClient`) | (no plan file on disk; feat(sp13) commits on `main`) | Met |
| SP14 | 5-lane Inbox UI | (no dedicated spec on disk; extends SP6) | (no plan file on disk; feat(sp14) commits on `main`) | Met |
| SP15 | SQLCipher key rotation | (no dedicated spec on disk; extends SP12) | (no plan file on disk; feat(sp15) commits on `main`) | Met |
| SP16 | Live MFT polling scheduler | (no dedicated spec on disk; `cyclone/scheduler.py` is the implementation; idempotency via `processed_inbound_files` from migration 0011) | (no plan file on disk; feat(sp16) commits on `main`) | Met |
| SP17 | Encrypted DB backups | [spec](superpowers/specs/2026-06-21-cyclone-encrypted-backup-design.md) | (no plan file on disk; feat(sp17) commits on `main`) | Met |
| SP18 | Structured logging | [spec](superpowers/specs/2026-06-21-cyclone-structured-logging-design.md) | (no plan file on disk; feat(sp18) commits on `main`) | Met |
| SP19 | Security hardening + health probe | [spec](superpowers/specs/2026-06-21-cyclone-security-hardening-design.md) | (no plan file on disk; feat(sp19) commits on `main`) | Met |
| SP20 | NPI checksum + Tax ID format validation | [spec](superpowers/specs/2026-06-21-cyclone-npi-validation-design.md) | (no plan file on disk; feat(sp20) commits on `main`) | Met |
| SP21 | CycloneStore split (in-flight) + Universal drilldown (shipped) | [split spec](superpowers/specs/2026-06-21-cyclone-store-split-design.md), [drilldown spec](superpowers/specs/2026-06-21-cyclone-universal-drilldown-design.md) | [store-split plan](superpowers/plans/2026-06-21-cyclone-store-split.md), [drilldown plan](superpowers/plans/2026-06-21-cyclone-universal-drilldown.md) | Drilldown **Met**; split **In-flight** (`refactor/store-split` branch, not yet on `main`) |
| SP22 | Parse-decide workflow + Pipeline agent | [parse-decide spec](superpowers/specs/2026-06-21-cyclone-parse-decide-workflow-design.md), [pipeline spec](superpowers/specs/2026-06-21-cyclone-pipeline-agent-design.md) | [parse-decide plan](superpowers/plans/2026-06-21-cyclone-parse-decide-workflow.md), [pipeline plan](superpowers/plans/2026-06-21-cyclone-pipeline-agent.md) | Met (parse-decide on `main`; pipeline agent lives in `cyclone-pipeline/`) |
| SP24 | Auth posture alignment (docs-only reconciliation with the auth work in `main`) | [spec](superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md) | [plan](superpowers/plans/2026-06-23-cyclone-auth-posture-alignment.md) | Met (no code paths change; only docs + `__main__.py` WARNING + 3 skills addenda) |

### 6.2 Backlog (specs awaiting plans, or vice versa)

| Item | Status | Why |
|---|---|---|
| SP23 (candidate) | Spec-only; **product fork awaiting user decision** | `2026-06-22-cyclone-ubuntu-docker-deployment-design.md` (auth + Docker + RBAC + LAN-bind); see §11 R-1, R-3 |
| Plan-file backfill for SP9, SP10, SP11, SP12, SP13, SP14, SP15, SP16, SP17, SP18, SP19, SP20 | Plan files missing on disk; `feat(spN)` commit messages substitute | Round-3 doc-backlog item |
| Spec-file backfill for SP6 (workflow/inbox), SP11 (audit log), SP12 (SQLCipher), SP13 (paramiko), SP15 (key rotation), SP16 (MFT scheduler) | Specs missing on disk | Round-3 doc-backlog item |
| Migration manifest (checksums for the 12 SQL files) | Open | Completeness review §3.1.18 |
| Tunable score weights (move 40/25/20/15 from code to config) | Open | Completeness review §3.1.15 |
| License file (`LICENSE`) | Open | Completeness review §3.2.28 |
| `pre-commit` / `Makefile` / `CONTRIBUTING.md` / `.editorconfig` / `ruff` config | Open | Completeness review §3.2.30 |
| CI workflow file (`.github/workflows/`) | Open | §8.1 #3 references CI; no CI file exists today |
| PHI fixtures flagged as PHI (`docs/prodfiles/PHI.md` or equivalent) | Open | Completeness review §3.2.23 |
| Delete stale `backend/src/cyclone/workflow/__pycache__/` | Open | Empty dead-code directory (§5.1) |
| EHR/PM integration (webhook in/out, callable `POST /api/parse-837`) | Open | Completeness review §5.1 |
| COB / secondary-claim generator | Open | Completeness review §3.1.12 + §5.1 |
| Real-time eligibility round-trip (SFTP pickup + SOAP + retry + 271 parse) | Open | Completeness review §3.1.8 + §5.1 |
| 837I / 837D / 834 / 820 / 275 / 278 / 276/277 transaction types | Open | Completeness review §2.1 |
| NPPES NPI registry lookup | Open | Completeness review §3.1.10 |
| ICD-10 / HCPCS / NDC vocabulary tables | Open | Completeness review §3.1.11 |

### 6.3 Traceability matrix (compressed)

This matrix is the single index. Each row points to a requirement and where it's realized. A change to a requirement must update its row; a change to an SP must update the matching FRs/NFRs.

| ID | SP | Spec | Plan | Key tests |
|---|---|---|---|---|
| FR-1 | SP1 | production-readiness | production-readiness | `test_parse_837.py`, `test_prodfiles_smoke.py` |
| FR-2 | SP1, SP3 | production-readiness + edi-features | production-readiness + edi-features | `test_parse_835.py`, `test_service_line_payments.py` |
| FR-3 | SP2 | db-reconciliation | db-reconciliation | `test_db.py`, `test_api_parse_persists.py` |
| FR-4 | SP2 | db-reconciliation | db-reconciliation | `test_db_migrate.py` |
| FR-5 | SP2 | db-reconciliation | db-reconciliation | `test_reconcile.py`, `test_store_reconcile.py` |
| FR-6 | SP2 | db-reconciliation | db-reconciliation | `test_models.py`, `test_reconcile.py` |
| FR-7 | SP2 | db-reconciliation | db-reconciliation | `test_reconcile.py`, `test_reconcile_line_level.py`, `test_inbox_endpoints.py`, `test_api_gets.py` |
| FR-8 | SP3, SP20 | edi-features + npi-validation | edi-features | `test_validator.py`, `test_validation_r200_r210.py` |
| FR-9 | SP3 | edi-features | edi-features | `test_acks.py`, `test_api_parse_persists_ack.py` |
| FR-10 | SP3 | edi-features | edi-features | `test_api_999.py`, `test_models_999.py`, `test_parse_999.py` |
| FR-11 | SP3 | edi-features | edi-features | `test_api_ta1.py`, `test_parse_ta1.py` |
| FR-12 | SP3 | edi-features | edi-features | `test_parse_270.py`, `test_parse_271.py`, `test_api_eligibility.py` |
| FR-13 | SP10 | parse-decide-workflow (277CA section) | parse-decide-workflow | `test_parse_277ca.py`, `test_apply_277ca_rejections.py` |
| FR-14 | SP6, SP10, SP14 | workflow-automation + parse-decide-workflow | workflow-automation | `test_inbox_lanes.py`, `test_inbox_endpoints.py`, `test_lane_filter_acknowledged.py` |
| FR-15 | SP6 | workflow-automation | workflow-automation | `test_scoring.py` |
| FR-16 | SP4, SP7, SP21 | claim-drawer + line-reconciliation + universal-drilldown | claim-drawer + line-reconciliation + universal-drilldown | `test_api_claim_detail.py`, `test_store_claim_detail.py` |
| FR-17 | SP4 | claim-drawer | claim-drawer | `test_batch_diff.py` |
| FR-18 | SP4 | claim-drawer | claim-drawer | `useSearch.test.ts`, `SearchResults.test.tsx` |
| FR-19 | SP4 | claim-drawer | claim-drawer | `ExportCsvButton.test.tsx`, `csv.test.ts` |
| FR-20 | SP5 | live-tail | live-tail | `test_api_stream_live.py`, `useTailStream.test.ts`, `tail-stream.test.ts` |
| FR-21 | SP5 | live-tail | live-tail | `TailStatusPill.test.tsx`, `useMergedTail.test.ts` |
| FR-22 | SP7 | line-reconciliation | line-reconciliation | `test_reconcile_line_level.py`, `test_api_line_reconciliation.py` |
| FR-23 | SP8 | serialize-837 | serialize-837 | `test_serialize_837.py`, `test_api_serialize_837.py` |
| FR-24 | SP8 | serialize-837 | serialize-837 | `test_api_batch_export_837.py` |
| FR-25 | SP9 | multi-payer-npi-sftp | (none on disk) | `test_providers_seed.py`, `test_payer_config_loading.py` |
| FR-26 | SP13 | multi-payer-npi-sftp (extends) | (none on disk) | `test_sftp_paramiko.py`, `test_sftp_stub.py` |
| FR-27 | SP16 | (none on disk) | (none on disk) | `test_scheduler.py`, `test_api_scheduler.py` |
| FR-28 | SP12 | (none on disk) | (none on disk) | `test_db_crypto.py`, `test_security.py` |
| FR-29 | SP15 | (none on disk) | (none on disk) | `test_api_rotate_key.py` |
| FR-30 | SP17 | encrypted-backup | (none on disk) | `test_backup_crypto.py`, `test_backup_service.py`, `test_backup_scheduler.py`, `test_api_backup.py`, `test_cli_backup.py` |
| FR-31 | SP18 | structured-logging | (none on disk) | `test_logging_formatter.py`, `test_logging_scrubber.py`, `test_logging_setup.py` |
| FR-32 | SP19 | security-hardening | (none on disk) | `test_security.py` |
| FR-33 | SP19 | security-hardening | (none on disk) | `test_api.py:33 test_health_endpoint`, `test_security.py:204-221` (health snapshot) |
| FR-34 | SP20 | npi-validation | (none on disk) | `test_npi.py`, `test_api_validate_provider.py`, `test_cli_validate.py` |
| FR-35 | SP21 | store-split | store-split | `test_store.py` (currently covers the un-split `store.py`; will cover the split modules once `refactor/store-split` lands) |
| FR-36 | SP22 | parse-decide-workflow | parse-decide-workflow | `test_api_parse_persists.py`, `test_prodfiles_smoke.py`; migration 0014 in `claims-unique-fix` worktree |
| FR-37 | SP22 | pipeline-agent | pipeline-agent | (lives in `cyclone-pipeline/`, not this repo) |
| FR-38 | SP21 | universal-drilldown | universal-drilldown | `DrillStackProvider.test.tsx`, `useDrawerUrlState.test.ts`, plus per-drawer tests in `ClaimDrawer/`, `RemitDrawer/`, `ProviderDrawer/`, `AckDrawer/` |

---

## 7. Acceptance criteria — per-SP (compressed)

These are the existing per-SP acceptance checklists as captured in their design specs, condensed into one table. A new engineer can use this to know whether a given SP is "really done" without reading 22 specs end-to-end.

| SP | Acceptance criterion | Verified by |
|---|---|---|
| SP1 | `pytest` passes; `/api/claims`, `/api/remittances`, `/api/providers`, `/api/activity`, `/api/batches`, `/api/batches/{id}` return JSON; CORS allowlist stays `http://localhost:5173`; bind stays `127.0.0.1` | `test_api_gets.py` + manual smoke |
| SP2 | DB at `~/.local/share/cyclone/cyclone.db`; migrations apply on first run; 7-state Claim lifecycle; reconciliation runs on 835 ingest; `/reconciliation` page works | `test_db*.py`, `test_reconcile.py`, `test_reconcile_line_level.py`, `test_inbox_endpoints.py` |
| SP3 | R034/R035/R200/R210 enforced; CAS reason codes surfaced; 999 in + out; TA1 in; 270/271 builder + parser | `test_validator*.py`, `test_acks.py`, `test_api_999.py`, `test_api_ta1.py`, `test_parse_270.py`, `test_parse_271.py` |
| SP4 | Drawer opens with URL sync; Cmd-K search returns results across resources; batch diff renders added/removed/changed; CSV exports stream | `ClaimDrawer*.test.tsx`, `useSearch.test.ts`, `BatchDiff.test.tsx`, `ExportCsvButton.test.tsx` |
| SP5 | `/api/{claims,remittances,activity}/stream` returns NDJSON with snapshot + `snapshot_end` + heartbeat; client flips to `stalled` after 30s silence; reconnect ladder capped at 30s | `test_api_stream_live.py`, `TailStatusPill.test.tsx` |
| SP6 | `/inbox` shows 5 lanes (4 in SP6; 5th added in SP10); scoring tiers visible; bulk actions call the right endpoint | `test_inbox_lanes.py`, `BulkBar.test.tsx` |
| SP7 | Line reconciliation tab in ClaimDrawer matches 837 SV1 ↔ 835 SVC strictly; unmatched-line warnings surfaced | `test_reconcile_line_level.py`, `test_api_line_reconciliation.py` |
| SP8 | 113 prodfiles parse → serialize → parse back to the same `claim_id`; ZIP bundle export works | `test_prodfiles_smoke.py`, `test_api_batch_export_837.py` |
| SP9 | `config/payers.yaml` loads (via `pyyaml`); new providers/payers/payer_configs tables populate; `clearhouse.submit` writes to `staging_dir` (stub) | `test_providers_seed.py`, `test_payer_config_loading.py` |
| SP10 | 277CA parse + persist; `payer_rejected` stamp applied monotonically; lane 2 in `/inbox` renders | `test_parse_277ca.py`, `test_apply_277ca_rejections.py` |
| SP11 | `audit_log` hash chain verifies via `GET /api/admin/audit-log/verify`; any break is detected | `test_audit_log.py`, `test_api_audit_log.py` |
| SP12 | `db_crypto.is_encryption_enabled()` returns True when Keychain entry exists and `sqlcipher3` installed; otherwise falls back with WARNING | `test_db_crypto.py` |
| SP13 | `SftpClient` actually pushes to `mft.gainwelltechnologies.com:…`; credentials read from Keychain via `keyring` at call time | `test_sftp_paramiko.py` (unit); integration via `cyclone-pipeline` |
| SP14 | All 5 lanes render in `/inbox`; bulk acknowledge action drops payer-rejected claims without erasing the original rejection | `test_inbox_endpoints_sp7.py`, `test_lane_filter_acknowledged.py` |
| SP15 | `POST /api/admin/db/rotate-key` runs `PRAGMA rekey`; emits `db.key_rotated` with old + new fingerprints; concurrent calls serialized | `test_api_rotate_key.py` |
| SP16 | Scheduler ticks every interval; per-file try/except keeps the loop alive; `processed_inbound_files` de-duplicates | `test_scheduler.py`, `test_api_scheduler.py` |
| SP17 | Backup scheduler ticks every 24h; encrypted `.bin` + `.meta.json` written; restore via 2-step token works; retention prunes at 30 days | `test_backup_*.py` |
| SP18 | All logs emit as NDJSON by default; `PiiScrubber` redacts NPIs/SSNs/DOBs/names; `CYCLONE_LOG_JSON=0` falls back to `CycloneDevFormatter` | `test_logging_*.py` |
| SP19 | 413 on oversize body, 429 on rate-limit, security headers present; `api.request_rejected` audit event on 413/429; `GET /api/health` returns rich payload | `test_security.py`, `test_api.py:33` |
| SP20 | NPI Luhn catches 99% of typos; EIN rejects reserved prefixes; warning-only (placeholder NPIs in fixtures don't block ingest); CLI + API surface | `test_npi.py`, `test_api_validate_provider.py`, `test_cli_validate.py` |
| SP21 | `CycloneStore` is split into `store_persistence / store_reconcile / store_queries / store_mappers`; public store API unchanged; `DrillStackProvider` + `DrillDrawerHeader` shell used by every drillable cell | `test_store.py` (split modules), `DrillStackProvider.test.tsx`, drawer-key tests |
| SP22 | Re-parsing an 837 into a fresh batch does not 409 on `claim_id` collision; parse-then-decide workflow checks `find_existing_batch_for_claim`; pipeline agent runs 7 phases end-to-end | `test_api_parse_persists.py`, `test_prodfiles_smoke.py`, pipeline integration |

---

## 8. Definition of Done

### 8.1 Project-level DoD (the project as a whole is "done" when…)

1. **All FRs in §3 are implemented and tested** — verifiable by walking the §6.3 traceability matrix and confirming each row links to either a passing test or an §2.2 out-of-scope entry.
2. **All NFRs in §4 are met or have a documented exception** — NFR-1 through NFR-18 are each either met (with a citation) or carry an explicit exception in this section.
3. **All shipped SPs in §6.1 have an Acceptance row marked "Met" and the corresponding tests pass.** Tests are run via `pytest` (backend) and `npm test` (frontend) per §9.4; **the project has no CI workflow today** (round-3 backlog item — see §6.2). A release-time gate should run the suites and fail if any test is red.
4. **All backlog items in §6.2 are either scheduled, explicitly deferred with rationale, or removed** — testable as a checklist walk.
5. **All assumptions in §10 are still valid** — re-checked at release time; an invalid assumption is either promoted to a backlog item or rolled into the next release.
6. **All four reviewer questions (components / data model / dependencies / DoD) have independent agreement from at least two reviewers** — the meta-DoD: a doc with this scope is "done" when two reviewers can independently describe the system from it and agree.

### 8.2 Per-release DoD (the next release is "done" when…)

1. The new SP's design spec has been **self-reviewed** (every claim cited with line/file, every assumption recorded).
2. The implementation plan has **TDD-shaped tasks** with per-task acceptance criteria.
3. Every task's tests are green in `pytest` (backend) and `npm test` (frontend) and `npm run build` (frontend typecheck + build).
4. The new SP's Acceptance row in §6.1 is updated to "Met".
5. New audit events (if any) are listed in the audit-log test fixtures (`backend/tests/test_audit_log.py`).
6. Migration files (if any) follow the `NNNN_*.sql` ordering, append to this doc's §5.2 table, and (post-round-3) update the migration manifest (R-13).
7. The README's project-layout section is updated if any module is added/renamed/removed.

### 8.3 Per-task DoD (the smallest unit of work is "done" when…)

1. The code change is in a single commit (or a small, reviewable series).
2. The matching test exists and passes.
3. No existing test regresses.
4. The commit message names the SP and the FR/NFR it advances (e.g., `feat(sp19): … (FR-32, NFR-8)`).
5. If the task introduces a migration, it's added to `backend/src/cyclone/migrations/` with the next sequential `NNNN` and the new schema is reflected in §5.2.

---

## 9. Test strategy

### 9.1 Taxonomy

| Layer | Tool | Where | What it covers |
|---|---|---|---|
| Backend unit | `pytest` | `backend/tests/` | Pure-function logic: `cyclone.reconcile.*`, `cyclone.scoring.*`, `cyclone.npi.*`, `cyclone.cas_codes`, `cyclone.audit_log.append_event`, `cyclone.edi.filenames` |
| Backend integration | `pytest` (TestClient) | `backend/tests/test_api_*.py` | FastAPI route round-trips, content negotiation, NDJSON streaming, auth/rate-limit/SP19, 999/TA1/277CA parse endpoints, scheduler endpoints |
| Backend fixture-driven | `pytest` (`tests/fixtures/*.txt`) | `backend/tests/test_prodfiles_smoke.py` | 113 + 19 + 1369 production files; round-trip property |
| Backend SQL | `pytest` (in-memory SQLite) | `backend/tests/test_db*.py` | Schema, migrations, ORM, encryption round-trip |
| Frontend unit | `vitest` (`*.test.ts(x)`) | `src/lib/`, `src/hooks/`, `src/components/ui/` | Pure functions, hooks, primitives |
| Frontend component | `vitest` + Testing Library | `src/components/`, `src/pages/` | Drawers, lane rendering, drill stack, export buttons |
| Smoke | manual / shell | `scripts/smoke.sh` (planned in SP23) | End-to-end parse → reconcile → export flow |

### 9.2 Fixture sources

- `backend/tests/fixtures/minimal_*.txt` — minimal valid 270 / 271 / 277CA / 835 / 837P / 999 / TA1 / co_medicaid / unbalanced_835 — written by hand, used in unit tests.
- `docs/prodfiles/claims/*.x12` (113 files) — real-looking production files used by `test_prodfiles_smoke.py` for the round-trip property.
- `docs/prodfiles/FromHPE/` (1369 files) — full inbound directory from the production SFTP path.
- `docs/prodfiles/837p-from-axiscare/` (19 files) — production 837Ps from AxisCare.
- `docs/prodfiles/835fromco/` (5 files) — production 835s from CO Medicaid.

⚠ **PHI warning:** prodfiles contain real-looking patient data. Even in a single-operator tool, fixtures should be flagged as PHI (completeness review §3.2.23 — flagged but not yet actioned; for v1 they're kept under `docs/prodfiles/` with no additional access control).

### 9.3 Coverage goals

- **Pure-function modules** (`reconcile`, `scoring`, `npi`, `cas_codes`, `parsers`, `edi.filenames`, `batch_diff`): ≥ 90% line coverage. `pytest-cov` is installed; the gate (`--cov-fail-under=90`) is **not enforced today** and is a round-3 backlog item.
- **Store facade** (`store`, and the future `store_persistence / store_reconcile / store_queries / store_mappers` modules): every public method has at least one happy-path and one failure-path test.
- **API routes**: every route has a happy-path test; error paths (404, 409, 413, 415, 422, 423, 429, 500) covered by at least one test each.
- **Round-trip property** (SP8): 100% of `docs/prodfiles/claims/*.x12` must parse → serialize → parse to the same `claim_id`.
- **Live tail** (SP5): one happy snapshot → live item → disconnect cleanup test per endpoint.

### 9.4 How to run

```bash
# Backend
cd backend
.venv/bin/pytest                       # 964 tests collected (2026-06-23)
.venv/bin/python -m cyclone serve

# Frontend
npm run typecheck
npm test                                # 73 test files
npm run build

# Smoke (planned in SP23)
bash scripts/smoke.sh
```

### 9.5 What is NOT covered (explicit)

- **Component / E2E browser tests (Playwright).** Deferred; the local-only threat model doesn't justify them. (Completeness review §3.2 + SP23 §14.)
- **Property-based tests** (Hypothesis) for the parser. Could add; not scheduled.
- **Mutation tests** (mutmut / cosmic-ray). Not scheduled.
- **Performance / load tests.** Single-operator scale; not scheduled.
- **NPPES round-trip validation** (only Luhn checksum is covered; SP20).
- **External-integration tests against the real Gainwell MFT.** The pipeline agent covers this in `cyclone-pipeline/`; out of scope for this repo.
- **CI workflow.** Not present in the repo; release gating is manual today.

---

## 10. Assumptions

Recorded explicitly so a new engineer can challenge any of them.

| # | Assumption | If wrong, what changes |
|---|---|---|
| A1 | One operator, one host, one trading partner (CO Medicaid). | Multi-tenant / multi-payer scope would require SP23 + multi-payer NPI expansion + COB + AS2/AS4. |
| A2 | The host runs macOS (Keychain via `keyring`) OR Ubuntu (Docker secrets available, SP23). | Linux-without-Docker / Windows would require a different secrets backend (`keyring` already dispatches via Secret Service / kwallet on Linux; Windows would use Credential Manager). |
| A3 | The host is physically secure; LUKS / FileVault is the operator's concern, not Cyclone's. | If not, the SQLCipher-encrypted-DB posture is insufficient; per-file encryption of `prodfiles/` and `sftp_staging/` becomes mandatory. |
| A4 | The operator can hand the 837 ZIP to the Gainwell MFT UI manually. | Removes the need for a built-in SFTP submit (SP13); the SFTP wire stays stub. |
| A5 | The 835 lands the following Monday (CO Medicaid payment cycle) and is verified by a separate `check-835` subcommand (in `cyclone-pipeline/`), not inline. | Would require a different round-trip contract; affects SP22 pipeline agent. |
| A6 | ICD-10 / HCPCS / NDC vocabulary is not Cyclone's responsibility. | Would require vendoring vocab CSVs and adding vocabulary checks (FR + tests). |
| A7 | NPPES NPI registry lookup is not needed (offline Luhn is enough). | Would require scheduled NPPES download + online check + new FR. |
| A8 | The X12 5010 transaction set Cyclone supports is sufficient for the operator's billing workflow. | Adding 837I / 837D / 278 / 276-277 / 277CA-round-trip / etc. is each a new SP. |
| A9 | Local-only bind is sufficient — no remote access needed. | Would require auth (SP23), TLS, reverse proxy, RBAC. |
| A10 | One Python process, no message broker, no separate worker. | A multi-worker deployment would require breaking thread-affinity (SP12/SP15 specifically). |
| A11 | 964 backend tests + 73 frontend test files is the right test density for this codebase (2026-06-23). If we add more transaction types or surface area, this needs to grow proportionally; coverage gates should be enforced (round-3 backlog). | New transaction types or surface area without test growth → coverage drifts down. |
| A12 | `cyclone-pipeline/` is a sibling project, not a sub-project of this repo. | The 7-phase round-trip agent is shipped in `cyclone-pipeline/`; if that project is abandoned, the manual operator workflow remains (upload via `/upload`, SFTP by hand, `check-835` not available). |

---

## 11. Risks and known gaps

Pulled forward from [`docs/reviews/2026-06-20-cyclone-completeness-review.md`](reviews/2026-06-20-cyclone-completeness-review.md). Status as of 2026-06-23.

| ID | Risk / gap | Source | Status |
|---|---|---|---|
| R-1 | No app-layer auth (anyone on the host can read PHI). | Completeness review §3.1.4 + §3.2.25 | **Closed by SP24** — auth shipped via the merge of `origin/main` on 2026-06-23 (commits `a25504b`..`39ae988`, `cyclone.auth.*` + `matrix_gate` dependency on every router); SP24 reconciled the docs to match. SP23 still covers the LAN-bind / Docker / RBAC product fork. |
| R-2 | SQLite plaintext if SQLCipher not enabled. | Completeness review §3.1.1 | **Partial** — SP12 + SP15 ship the encryption option, but it falls back to plain if the Keychain entry is missing. |
| R-3 | No Dockerfile / docker-compose.yml. | Completeness review §3.2.29 | **Open** — SP23 spec covers it but is awaiting user decision. |
| R-4 | PayerConfig still partly in code (`co_medicaid()` factory at `cyclone/parsers/payer.py:57-58`). | Completeness review §3.1.9 | **Open** — SP9 externalized config but the factory remains as a fallback for tests and default-payer selection. |
| R-5 | No vocabulary tables (ICD-10 / HCPCS / NDC). | Completeness review §3.1.11 | **Open** — explicitly out of scope per A6. |
| R-6 | No COB / secondary-claim generator. | Completeness review §3.1.12 | **Open** — explicitly out of scope per §2.2. |
| R-7 | No real-time eligibility round-trip. | Completeness review §3.1.8 | **Open** — 270/271 are file-exchange only per SP3. |
| R-8 | No NPPES NPI lookup. | Completeness review §3.1.10 | **Open** — only Luhn checksum (SP20). |
| R-9 | No 837I / 837D / 834 / 820 / 275 / 278 / 276-277 transaction types. | Completeness review §2.1 | **Open** — explicitly out of scope per §2.2. |
| R-10 | Score weights hardcoded (`cyclone/scoring.py:42-46`). | Completeness review §3.1.15 | **Open** — not scheduled. |
| R-11 | `cyclone/store.py` is a 2,172-line god-module. | Completeness review §3.1.17 | **In-flight** — SP21 spec + plan written; branch `refactor/store-split` exists; merge to `main` pending. |
| R-12 | `cyclone/api.py` is a 3,145-line god-module (larger than `store.py`). | Completeness review §3.1.19 | **Partial** — `api_routers/` exists for 4 sub-routes (acks, admin, health, ta1_acks); bulk of routes still in `api.py`. |
| R-13 | Migrations in flat dir without manifest. | Completeness review §3.1.18 | **Open** — no checksum list; 12 SQL files in `backend/src/cyclone/migrations/`. |
| R-14 | No `pre-commit` / `Makefile` / `CONTRIBUTING.md` / `.editorconfig` / `ruff` config. | Completeness review §3.2.30 | **Open** — single-operator project. |
| R-15 | No license file (`LICENSE`). | Completeness review §3.2.28 | **Open** — not scheduled. |
| R-16 | PHI fixtures not flagged as PHI. | Completeness review §3.2.23 | **Open** — not scheduled. |
| R-17 | `/api/health` not rich (before SP19). | Completeness review §3.2.24 | **Closed by SP19** — `api_routers/health.py:28-40` returns the rich snapshot via `get_health_snapshot()`. |
| R-18 | No CSP / no security headers (before SP19). | Completeness review §3.2.25 | **Closed by SP19** — `SecurityHeadersMiddleware` in `cyclone/security.py`. |
| R-19 | No request body size / rate limit (before SP19). | Completeness review §3.1.4 | **Closed by SP19** — `BodySizeLimitMiddleware` + `RateLimitMiddleware` in `cyclone/security.py`. |
| R-20 | No 277CA support (before SP10). | Completeness review §3.1.7 | **Closed by SP10**. |
| R-21 | No structured logging (before SP18). | Completeness review §3.1.5 | **Closed by SP18**. |
| R-22 | No backup automation (before SP17). | Completeness review §3.1.3 | **Closed by SP17**. |
| R-23 | `ActivityEvent` not tamper-evident (before SP11). | Completeness review §3.1.2 | **Closed by SP11** — but note: the tampered table is `audit_log`, distinct from `activity_events` (see NFR-3 / NFR-4). |
| R-24 | SP23 product fork (auth + Docker + RBAC + LAN-bind). | §6.2 | **Awaiting user decision** — see §2.2 and §11 R-1. |
| R-25 | `vitest@^4.1.9` is unusually new (current stable line is 1.x/2.x). | §5.3 | **Open** — verify the version pin is intentional before the next `npm install`; fallback to `^2.x` if accidental. |
| R-26 | No CI workflow (`.github/workflows/`). | §8.1 #3 | **Open** — release gating is manual today. |
| R-27 | `backend/src/cyclone/workflow/__pycache__/` is dead bytecode. | §5.1 | **Open** — recommend deletion. |

---

## 12. References

### 12.1 Design specs

All under [`docs/superpowers/specs/`](superpowers/specs/):

- [2026-06-19-cyclone-837p-parser-design.md](superpowers/specs/2026-06-19-cyclone-837p-parser-design.md) — pre-SP1, superseded by `production-readiness`
- [2026-06-19-cyclone-db-reconciliation-design.md](superpowers/specs/2026-06-19-cyclone-db-reconciliation-design.md) — SP2
- [2026-06-19-cyclone-production-readiness-design.md](superpowers/specs/2026-06-19-cyclone-production-readiness-design.md) — SP1
- [2026-06-20-cyclone-claim-drawer-design.md](superpowers/specs/2026-06-20-cyclone-claim-drawer-design.md) — SP4 (no inbox section; SP6 ships from the workflow-automation plan)
- [2026-06-20-cyclone-edi-features-design.md](superpowers/specs/2026-06-20-cyclone-edi-features-design.md) — SP3
- [2026-06-20-cyclone-line-reconciliation-design.md](superpowers/specs/2026-06-20-cyclone-line-reconciliation-design.md) — SP7
- [2026-06-20-cyclone-live-tail-design.md](superpowers/specs/2026-06-20-cyclone-live-tail-design.md) — SP5
- [2026-06-20-cyclone-multi-payer-npi-sftp-design.md](superpowers/specs/2026-06-20-cyclone-multi-payer-npi-sftp-design.md) — SP9 (extended by SP13)
- [2026-06-20-cyclone-serialize-837-design.md](superpowers/specs/2026-06-20-cyclone-serialize-837-design.md) — SP8 (spec's own title mistakenly says "Sub-project 7")
- [2026-06-21-cyclone-claims-unique-constraint-and-409-ux-design.md](superpowers/specs/2026-06-21-cyclone-claims-unique-constraint-and-409-ux-design.md) — pre-SP22, superseded by `parse-decide-workflow`
- [2026-06-21-cyclone-encrypted-backup-design.md](superpowers/specs/2026-06-21-cyclone-encrypted-backup-design.md) — SP17
- [2026-06-21-cyclone-npi-validation-design.md](superpowers/specs/2026-06-21-cyclone-npi-validation-design.md) — SP20
- [2026-06-21-cyclone-parse-decide-workflow-design.md](superpowers/specs/2026-06-21-cyclone-parse-decide-workflow-design.md) — SP22 (parse-decide) + covers 277CA (SP10)
- [2026-06-21-cyclone-pipeline-agent-design.md](superpowers/specs/2026-06-21-cyclone-pipeline-agent-design.md) — SP22 (pipeline agent)
- [2026-06-21-cyclone-security-hardening-design.md](superpowers/specs/2026-06-21-cyclone-security-hardening-design.md) — SP19
- [2026-06-21-cyclone-skill-catalog-design.md](superpowers/specs/2026-06-21-cyclone-skill-catalog-design.md) — documentation meta-project (not mapped to a behavioral SP)
- [2026-06-21-cyclone-store-split-design.md](superpowers/specs/2026-06-21-cyclone-store-split-design.md) — SP21 (split)
- [2026-06-21-cyclone-structured-logging-design.md](superpowers/specs/2026-06-21-cyclone-structured-logging-design.md) — SP18
- [2026-06-21-cyclone-universal-drilldown-design.md](superpowers/specs/2026-06-21-cyclone-universal-drilldown-design.md) — SP21 (drilldown)
- [2026-06-22-cyclone-ubuntu-docker-deployment-design.md](superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md) — **SP23 candidate; awaiting plan + user product-fork decision**

### 12.2 Implementation plans

All under [`docs/superpowers/plans/`](superpowers/plans/):

- [2026-06-19-cyclone-837p-parser.md](superpowers/plans/2026-06-19-cyclone-837p-parser.md) — pre-SP1, superseded
- [2026-06-19-cyclone-db-reconciliation.md](superpowers/plans/2026-06-19-cyclone-db-reconciliation.md) — SP2
- [2026-06-19-cyclone-production-readiness.md](superpowers/plans/2026-06-19-cyclone-production-readiness.md) — SP1
- [2026-06-20-cyclone-claim-drawer.md](superpowers/plans/2026-06-20-cyclone-claim-drawer.md) — SP4
- [2026-06-20-cyclone-edi-features.md](superpowers/plans/2026-06-20-cyclone-edi-features.md) — SP3
- [2026-06-20-cyclone-line-reconciliation.md](superpowers/plans/2026-06-20-cyclone-line-reconciliation.md) — SP7
- [2026-06-20-cyclone-live-tail.md](superpowers/plans/2026-06-20-cyclone-live-tail.md) — SP5
- [2026-06-20-cyclone-serialize-837.md](superpowers/plans/2026-06-20-cyclone-serialize-837.md) — SP8
- [2026-06-20-cyclone-workflow-automation.md](superpowers/plans/2026-06-20-cyclone-workflow-automation.md) — SP6
- [2026-06-21-cyclone-claims-unique-constraint-and-409-ux.md](superpowers/plans/2026-06-21-cyclone-claims-unique-constraint-and-409-ux.md) — pre-SP22, superseded
- [2026-06-21-cyclone-parse-decide-workflow.md](superpowers/plans/2026-06-21-cyclone-parse-decide-workflow.md) — SP22
- [2026-06-21-cyclone-pipeline-agent.md](superpowers/plans/2026-06-21-cyclone-pipeline-agent.md) — SP22
- [2026-06-21-cyclone-skill-catalog.md](superpowers/plans/2026-06-21-cyclone-skill-catalog.md) — documentation meta-project
- [2026-06-21-cyclone-store-split.md](superpowers/plans/2026-06-21-cyclone-store-split.md) — SP21 (split, in-flight)
- [2026-06-21-cyclone-universal-drilldown.md](superpowers/plans/2026-06-21-cyclone-universal-drilldown.md) — SP21 (drilldown)

### 12.3 Reviews and reference

- [2026-06-20-cyclone-completeness-review.md](reviews/2026-06-20-cyclone-completeness-review.md) — industry-scope gap analysis.
- [`docs/reference/835.md`](reference/835.md), [`docs/reference/837p.md`](reference/837p.md), [`docs/reference/co-medicaid.md`](reference/co-medicaid.md), [`docs/reference/x12naming.md`](reference/x12naming.md) — condensed X12 + CO Medicaid notes.

### 12.4 External

- HIPAA Security Rule §164.312 — audit controls, encryption, access control.
- X12 005010X222A1 (837P), 005010X221A1 (835), 005010X231A1 (999), 005010X279A1 (270/271), 005010X214 (277CA), 005010X230 (TA1).
- CAQH CORE Phase II/III (real-time eligibility — out of scope per §2.2).
- CMS NPI Luhn specification.
- NPPES API (NPI registry — out of scope per §2.2 + A7).
- OWASP password-storage cheat sheet (argon2id parameters — SP23).
- paramiko, FastAPI, SQLAlchemy 2.0, Pydantic v2, structlog, TanStack Query v5 docs.

---

## 13. Reviewer checklist (for round 2 → round 3 closure)

For the two independent reviewers at the end of round 2 (re-verifying the corrections):

1. **Components** — Do §5.1 (backend modules) + §5.1 (frontend dirs) list every existing module that ships PHI or serves a state-changing route? Are the boundaries (DB / ORM / reconcile / store / api / pubsub / clearhouse / batch_diff / edi.filenames) drawn correctly? Can you, from §5.1 alone, draw the import graph?
2. **Data model** — Are all 12 migrations listed in §5.2 with their purpose? Are the two worktree-only migrations (0013, 0014) flagged as in-flight? Are all 18 ORM models in §5.2? Is the `db_backups` (not `backups`) correction applied? Is the `Rejection` phantom removed? Is the `ActivityEvent` vs `AuditLog` distinction clear (NFR-3 vs NFR-4)?
3. **Dependencies** — Are §5.3 runtime + tooling deps correct (no missing `pyyaml`, no missing `keyring`, no `httpx` listed as runtime, `paramiko` flagged as `[sftp]` extra, `sqlcipher3` flagged as `[sqlcipher]` extra, frontend test env is `happy-dom` not `jsdom`, vitest pinned at `^4.1.9` flagged as unusual)? Are OS-level deps (Keychain via `keyring`, SQLCipher C library, `tini`, `nginx` if SP23 ships) called out?
4. **Definition of Done** — Is §8 testable? Project DoD #6 now points at the reviewer-agreement meta-criterion. Per-release + per-task DoDs each independently checkable? FR-7 / FR-33 traceability rows corrected to point at existing test files and the correct endpoint name?
5. **Traceability** — Pick any FR-NN in §3. Can you trace it to an SP, a spec on disk (or an honest "no dedicated spec on disk" note), a plan on disk (or an honest "no plan file on disk; feat(spN) commits substitute" note), and at least one test file in `backend/tests/` or `src/`? If not, that's a remaining gap.
6. **Assumptions** — Are §10 assumptions falsifiable? Could a new engineer challenge any of them and find the test that proves/disproves them? Is A2's reference to `keyring` accurate now?
7. **Risks** — Are §11 risks current (R-11 corrected to 2,172; R-12 added; R-25/R-26/R-27 added)? Compare against `docs/reviews/2026-06-20-cyclone-completeness-review.md`. Is R-24 (SP23 fork) clearly flagged as awaiting user decision?

If reviewers materially agree on all seven, round 2 closes; this doc becomes the project's new top-level entry point (linked from `README.md`).

---

*End of round 2. Next: re-verify with reviewers (round 3) or escalate any remaining disagreement to the user.*