Files
cyclone/docs/REQUIREMENTS.md
T
Tyler 00f11f84b6 feat(sp24): align CLAUDE.md + REQUIREMENTS.md + ARCHITECTURE.md with auth work + AUTH_DISABLED WARNING
Replaces every stale 'no auth' / 'no authentication' / 'no second party to
authenticate' claim in the three top-level docs with the v1 posture: the
auth boundary is HTTP (bcrypt + HttpOnly session cookie; first admin
bootstrapped from CYCLONE_ADMIN_USERNAME + CYCLONE_ADMIN_PASSWORD env
vars); the file-system posture (SQLCipher at rest, macOS Keychain) is
unchanged; the threat model is still a stolen/imaged drive.

Closes requirements §11 R-1 (was Open; now Closed by SP24 — auth shipped
via the origin/main merge on 2026-06-23, SP24 reconciled the docs).

Also:
- backend/src/cyclone/__main__.py — emit WARNING when AUTH_DISABLED is
  True at boot so a misconfigured production deploy fails loudly
- §6.1 migrations table in ARCHITECTURE.md — list 0013 (auth_users_and_
  sessions) and 0014 (audit_log_user_id) with the renumbering note
- §4.2 module map — add the cyclone.auth.* package
- Three skills addenda: cyclone-tests (AUTH_DISABLED conftest bypass is
  mandatory context), cyclone-api-router (every router needs
  Depends(matrix_gate)), cyclone-spec (spec template threat-model is now
  auth-aware)

This also tracks CLAUDE.md in git for the first time (was previously
untracked; the SP24 doc updates are in scope for the increment).

Spec: docs/superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md
Plan: docs/superpowers/plans/2026-06-23-cyclone-auth-posture-alignment.md
2026-06-23 14:49:31 -06:00

739 lines
72 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 SP1SP22)
| 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 + SP9SP17 |
| 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 (5074, 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 916 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.*