Compare commits
10 Commits
90a2055c20
...
55f9f8b8ba
| Author | SHA1 | Date | |
|---|---|---|---|
| 55f9f8b8ba | |||
| 0156051ca7 | |||
| 542a55fdbe | |||
| 96ed8f5f64 | |||
| 871e5c7119 | |||
| 2bb10f2a39 | |||
| d8d03d631f | |||
| b0956d039c | |||
| b3aec27e52 | |||
| 4c466ca054 |
@@ -5,23 +5,27 @@ description: "Cyclone FastAPI router conventions (api_routers/, api_helpers.py,
|
||||
|
||||
# cyclone-api-router
|
||||
|
||||
Cyclone splits its FastAPI surface two ways: small resource-group
|
||||
routers live in `backend/src/cyclone/api_routers/<topic>.py` and are
|
||||
mounted bare into `api.py`; the high-traffic streaming and parse
|
||||
endpoints still live as top-level decorators in `api.py` itself. This
|
||||
skill codifies the conventions so additions stay consistent with the
|
||||
four routers already shipped (`acks`, `admin`, `health`, `ta1_acks`).
|
||||
Cyclone splits its FastAPI surface two ways: resource-group routers
|
||||
live in `backend/src/cyclone/api_routers/<topic>.py` and are mounted
|
||||
bare into `api.py`; the parse endpoints (`/api/parse-837`,
|
||||
`/api/parse-835`, plus the SP40 `/api/admin/validate-837` etc.) stay
|
||||
as top-level decorators in `api.py` because they span multiple store
|
||||
modules. This skill codifies the conventions so additions stay
|
||||
consistent with the routers already shipped.
|
||||
|
||||
As of this writing: **4 router modules** under
|
||||
`backend/src/cyclone/api_routers/`, **one shared helpers module** at
|
||||
`backend/src/cyclone/api_helpers.py` (248 lines, NDJSON primitives +
|
||||
content negotiation + `tail_events`), and ~30 routes still inlined
|
||||
in `backend/src/cyclone/api.py`. The next refactor target is the
|
||||
parse endpoints.
|
||||
As of this writing: **22 router modules** under
|
||||
`backend/src/cyclone/api_routers/` (`acks`, `activity`, `admin`,
|
||||
`batches`, `claim_acks`, `claims`, `clearhouse`, `config`,
|
||||
`dashboard`, `eligibility`, `health`, `inbox`, `parse`, `payers`,
|
||||
`providers`, `rebill`, `reconciliation`, `remittances`, `submission`,
|
||||
`ta1_acks`, plus a `_shared.py` for cross-router helpers), **one
|
||||
shared helpers module** at `backend/src/cyclone/api_helpers.py`
|
||||
(NDJSON primitives + content negotiation + `tail_events`), and
|
||||
`api.py` itself is now 378 LOC (down from ~3,145 pre-SP36).
|
||||
|
||||
## Auth gate (SP24)
|
||||
## Auth gate (SP23)
|
||||
|
||||
Every router declared in `backend/src/cyclone/api_routers/` **must** carry `dependencies=[Depends(matrix_gate)]` at the `APIRouter(...)` declaration — not on each individual endpoint. The gate lives at `backend/src/cyclone/auth/deps.py:107` and the role matrix is at `backend/src/cyclone/auth/permissions.py`. The roles are `admin / user / viewer`; `matrix_gate` returns 401 when there's no session and 403 when the role is below the endpoint's required role. When `AUTH_DISABLED` is True (conftest autouse fixture flips it; `CYCLONE_AUTH_DISABLED=1` in prod-by-mistake), the gate short-circuits to a synthetic admin — see the SP24 spec for the threat-model implications. New routers get the gate by default; the auth-aware convention is `router = APIRouter(dependencies=[Depends(matrix_gate)])`.
|
||||
Every router declared in `backend/src/cyclone/api_routers/` **must** carry `dependencies=[Depends(matrix_gate)]` at the `APIRouter(...)` declaration — not on each individual endpoint. The gate lives at `backend/src/cyclone/auth/deps.py:107` and the role matrix is at `backend/src/cyclone/auth/permissions.py`. The roles are `admin / user / viewer`; `matrix_gate` returns 401 when there's no session and 403 when the role is below the endpoint's required role. When `AUTH_DISABLED` is True (conftest autouse fixture flips it; `CYCLONE_AUTH_DISABLED=1` in prod-by-mistake), the gate short-circuits to a synthetic admin — see the SP23 spec for the threat-model implications. New routers get the gate by default; the auth-aware convention is `router = APIRouter(dependencies=[Depends(matrix_gate)])`.
|
||||
|
||||
## When to use
|
||||
|
||||
@@ -45,11 +49,13 @@ Every router declared in `backend/src/cyclone/api_routers/` **must** carry `depe
|
||||
1. **No new top-level routes in `api.py` for resource groups.** Any
|
||||
endpoint grouped under a resource (`/api/<resource>` and its
|
||||
`/{id}` detail) lives in `backend/src/cyclone/api_routers/<topic>.py`
|
||||
as an `APIRouter`. The streaming list endpoints (`/api/claims/stream`,
|
||||
`/api/remittances/stream`, `/api/activity/stream`) and the parse
|
||||
endpoints (`/api/parse-*`) currently stay in `api.py` because they
|
||||
span multiple store modules — don't move them unless you're also
|
||||
restructuring the store split.
|
||||
as an `APIRouter`. The streaming list endpoints
|
||||
(`/api/claims/stream`, `/api/remittances/stream`,
|
||||
`/api/activity/stream`, `/api/acks/stream`, `/api/ta1-acks/stream`)
|
||||
were split into their per-resource routers as part of SP36; only
|
||||
the cross-resource parse endpoints (`/api/parse-*` and the SP40
|
||||
`validate-837`) stay in `api.py` because they span multiple store
|
||||
modules.
|
||||
2. **Reuse `api_helpers.py`.** NDJSON primitives (`ndjson_line`,
|
||||
`ndjson_stream_list`, `ndjson_stream_837`, `ndjson_stream_835`),
|
||||
content negotiation (`client_wants_json`, `wants_ndjson`), the
|
||||
@@ -80,8 +86,9 @@ Every router declared in `backend/src/cyclone/api_routers/` **must** carry `depe
|
||||
`StreamingResponse(media_type="application/x-ndjson")` and feed it
|
||||
either `ndjson_stream_list(items, total, returned, has_more)` (for
|
||||
list pages) or `tail_events(request, bus, kinds)` (for live-tail
|
||||
pages). See `acks.py:62-66` for the list-stream skeleton and
|
||||
`api.py:1357-1401` for the full live-tail pattern (snapshot →
|
||||
pages). See `api_routers/acks.py:62-66` for the list-stream
|
||||
skeleton and `api_routers/claims.py` (the `/api/claims/stream`
|
||||
handler) for the full live-tail pattern (snapshot →
|
||||
`snapshot_end` → subscription → heartbeats). See `cyclone-tail`
|
||||
for the wire format.
|
||||
6. **Tests.** Every new endpoint gets a `test_api_<topic>_<verb>.py`
|
||||
@@ -170,12 +177,17 @@ just after middleware registration and just before the first
|
||||
# Resource-group routers. Each module owns its own APIRouter and is
|
||||
# registered below. New resources go in `cyclone.api_routers.<name>`
|
||||
# and are wired in here.
|
||||
from cyclone.api_routers import acks, admin, health, ta1_acks # noqa: E402
|
||||
from cyclone.api_routers import ( # noqa: E402
|
||||
acks, activity, admin, batches, claim_acks, claims, clearhouse,
|
||||
config, dashboard, eligibility, health, inbox, parse, payers,
|
||||
providers, rebill, reconciliation, remittances, submission, ta1_acks,
|
||||
)
|
||||
|
||||
app.include_router(health.router)
|
||||
app.include_router(acks.router)
|
||||
app.include_router(ta1_acks.router)
|
||||
app.include_router(activity.router)
|
||||
app.include_router(admin.router)
|
||||
# ... 18 more include_router calls in api.py ...
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
@@ -5,7 +5,17 @@ description: "Cyclone CLI subcommand conventions (cli.py — Click group + subco
|
||||
|
||||
# cyclone-cli
|
||||
|
||||
The operator-facing CLI is a **Click** group at `cli.py:45` (`@click.group()` for `main`), mounted as the `cyclone` console script in `pyproject.toml:54` (`cyclone = "cyclone.cli:main"`). Seven subcommands ship today: `parse-837`, `parse-835`, `validate-npi`, `validate-tax-id`, plus the `backup` group (`init-passphrase`, `create`, `list`, `verify`, `restore`, `prune`, `status`). `serve` lives separately in `__main__.py:19` (dispatches `uvicorn cyclone.api:app`).
|
||||
The operator-facing CLI is a **Click** group at `cli.py:45` (`@click.group()` for `main`), mounted as the `cyclone` console script in `pyproject.toml:54` (`cyclone = "cyclone.cli:main"`). The CLI has grown well past the original seven subcommands; the full inventory is:
|
||||
|
||||
- **Parser / validator commands**: `parse-837`, `parse-835`, `validate-npi`, `validate-tax-id`, `validate-837` (SP40, Edifabric fail-closed pre-upload gate).
|
||||
- **DB plumbing**: `seed` (deterministic sample claims), `backfill-rendering-npi`, `backfill-999-rejections` (SP33).
|
||||
- **Submission flow**: `submit-batch` (SP37, canonical parse → DB-write → SFTP-upload), `resubmit-rejected-claims` (SP33), `recover-ingest` (re-parse a local path), `pull-inbound` (scheduler trigger).
|
||||
- **Rebill + reissue**: `rebill-from-835` (SP41), `reissue-claims` (SP24).
|
||||
- **Ack orphans + resubmissions**: `ack-orphans status / reconcile`, `resubmissions status`.
|
||||
- **User management** (`users` group): `create`, `list`, `disable`, `reset-password`, `set-role`.
|
||||
- **Backup group** (`backup`): `init-passphrase`, `create`, `list`, `verify`, `restore`, `prune`, `status`.
|
||||
|
||||
`serve` lives separately in `__main__.py:19` (dispatches `uvicorn cyclone.api:app`).
|
||||
|
||||
## When to use
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ description: "Cyclone EDI parser/validator conventions (837P/835/999/270/271/277
|
||||
|
||||
Cyclone parses seven X12 EDI transaction types (837P, 835, 999, 270, 271, 277CA, TA1) into typed Pydantic models, then runs per-claim / per-batch validator rules that surface as R-coded `ValidationIssue` records. This skill codifies the conventions so new parsers and rules stay consistent with the seven that already exist.
|
||||
|
||||
As of this writing: **7 parser modules** under `backend/src/cyclone/parsers/parse_<edi>.py`, **~25 per-claim rules** numbered `R010`–`R100` and `R200`–`R210` in `validator.py`, plus a parallel set of **835-specific rules** prefixed `R835_*` in `validator_835.py`. The next increment is **SP22**.
|
||||
As of this writing: **7 parser modules** under `backend/src/cyclone/parsers/parse_<edi>.py` (837P, 835, 999, TA1, 270, 271, 277CA), **~25 per-claim rules** numbered `R010`–`R100` and `R200`–`R210` in `validator.py`, plus a parallel set of **835-specific rules** prefixed `R835_*` in `validator_835.py`. The most recently shipped increment touching this surface is **SP41** (in-window rebill pipeline); the next free increment after this SP42 doc-pass is **SP43**.
|
||||
|
||||
## When to use
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ description: "Cyclone React page conventions (TanStack Query, use<X> hook, drawe
|
||||
|
||||
# cyclone-frontend-page
|
||||
|
||||
Cyclone pages live at `src/pages/<Name>.tsx`: a `use<X>` hook in `src/hooks/use<X>.ts` does the fetching (and optionally the live-tail subscription), `<Layout>` + `<PageHeader>` + `<Sidebar>` (`src/components/`) provide the app shell, and any right-side detail (claim, remittance) lives in `src/components/<DrawerName>/` whose open/close state is mirrored to the URL via `useDrawerUrlState`. This skill codifies the conventions so additions stay consistent with the eleven pages already shipped.
|
||||
Cyclone pages live at `src/pages/<Name>.tsx`: a `use<X>` hook in `src/hooks/use<X>.ts` does the fetching (and optionally the live-tail subscription), `<Layout>` + `<PageHeader>` + `<Sidebar>` (`src/components/`) provide the app shell, and any right-side detail (claim, remittance) lives in `src/components/<DrawerName>/` whose open/close state is mirrored to the URL via `useDrawerUrlState`. This skill codifies the conventions so additions stay consistent with the twelve pages already shipped (Login added in SP23).
|
||||
|
||||
As of this writing: **11 pages** under `src/pages/` (9 of 11 with a `*.test.tsx` sibling — Dashboard, Upload, and BatchDiff are not yet covered; be the first when you refactor them), **~30 hooks** under `src/hooks/`, and **2 drawer modules** at `src/components/{ClaimDrawer,RemitDrawer}/`. The next increment is **SP22**.
|
||||
As of this writing: `**12 pages** under `src/pages/` (10 of 12 with a `*.test.tsx` sibling — Dashboard and BatchDiff are not yet covered; be the first when you refactor them; the Login page does not need a sibling), **~30 hooks** under `src/hooks/`, and **4 drawer modules** at `src/components/{ClaimDrawer,RemitDrawer,ProviderDrawer,AckDrawer}/`. The most recently shipped increment is **SP41** (in-window rebill pipeline); the next free increment after this SP42 doc-pass is **SP43**.
|
||||
|
||||
## When to use
|
||||
|
||||
|
||||
@@ -10,15 +10,11 @@ a spec, a plan, an implementation branch, and a single atomic merge commit
|
||||
into `main`. This skill encodes the conventions so every increment follows
|
||||
the same shape and the commit history stays auditable.
|
||||
|
||||
As of this writing: **17 specs** in `docs/superpowers/specs/`, **13 plans**
|
||||
in `docs/superpowers/plans/`, and SP numbers used through **SP22**. **SP23**
|
||||
is the Ubuntu + Docker + RBAC product fork (awaiting user decision);
|
||||
**SP24** is the auth-posture alignment (docs-only). The next free increment
|
||||
is **SP25** after SP24 lands.
|
||||
As of this writing: SP numbers used through **SP41** (the in-window rebill pipeline); **SP42** is this doc-pass. **SP23** shipped the Ubuntu + Docker + RBAC + auth LAN-bind product fork; **SP24** is the reissue-claims + auth-docs alignment; SPs **25–40** cover the resubmissions, `claim_acks`, rendering/service NPI, transaction-set control numbers, additional live-tail streams, dashboards, and rebill prerequisites shipped between SP24 and SP41. The next free increment after SP42 is **SP43**.
|
||||
|
||||
## Auth-aware spec template (SP24)
|
||||
## Auth-aware spec template (post-SP23)
|
||||
|
||||
The threat-model section in the canonical SP-N spec template (`## 1. Scope`, second-to-last bullet) used to read "no second party to authenticate; no second host to harden against." **That phrasing is stale as of 2026-06-23** — the auth work landed in `main` and every backend endpoint requires login. New specs should instead state the auth boundary explicitly: "the auth boundary is HTTP (login required, bcrypt + HttpOnly session cookie); file-system threats remain the local-only threat model (SQLCipher at rest, macOS Keychain). SP23 changes the threat model to LAN-bound remote operator." Reference: [`docs/superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md`](../../../docs/superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md).
|
||||
The threat-model section in the canonical SP-N spec template (`## 1. Scope`, second-to-last bullet) used to read "no second party to authenticate; no second host to harden against." **That phrasing is stale since SP23 landed (2026-06-23)** — every backend endpoint now requires login (`cyclone.auth.*`, `Depends(matrix_gate)` on every `APIRouter`), the React app ships a `Login` page, RBAC roles `admin / user / viewer` gate individual endpoints, and the host still binds `0.0.0.0:8000` (reachability controlled by the host firewall / compose port publishing). The dev/test escape hatch is `CYCLONE_AUTH_DISABLED=1`, which logs a WARNING at boot. New specs should instead state the auth boundary explicitly: "the auth boundary is HTTP (login required, bcrypt + HttpOnly session cookie); file-system threats remain the LAN-only threat model (SQLCipher at rest, macOS Keychain). SP23 expanded the threat model to a remote operator on the LAN." Reference: [`docs/superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md`](../../../docs/superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md).
|
||||
|
||||
## When to use
|
||||
|
||||
|
||||
@@ -6,17 +6,9 @@ description: "Cyclone store write-paths, the pubsub event contract (claim_writte
|
||||
# cyclone-store
|
||||
|
||||
Cyclone persists every parsed X12 batch through one facade,
|
||||
`CycloneStore` (`backend/src/cyclone/store.py:882`). Every write
|
||||
inserts the row AND publishes a pubsub event on the in-process
|
||||
`EventBus` (`backend/src/cyclone/pubsub.py:20`) so live-tail pages
|
||||
see new rows the moment they land. The event contract is the seam
|
||||
between persistence and streaming — a wrong event name silently
|
||||
goes stale.
|
||||
`CycloneStore` (now exposed via `backend/src/cyclone/store/__init__.py` after the SP21 split landed; the public import surface `from cyclone.store import …` is unchanged). Every write inserts the row AND publishes a pubsub event on the in-process `EventBus` (`backend/src/cyclone/pubsub.py:20`) so live-tail pages see new rows the moment they land. The event contract is the seam between persistence and streaming — a wrong event name silently goes stale.
|
||||
|
||||
As of this writing: the store is a **single 2412-line module** and
|
||||
the SP21 split into `backend/src/cyclone/store/` is in progress.
|
||||
Three event kinds: `claim_written`, `remittance_written`,
|
||||
`activity_recorded`. Next: **SP22**.
|
||||
As of this writing: the store is a **16-module subpackage** under `backend/src/cyclone/store/` (SP21 split landed). Event kinds in use today: `claim_written`, `remittance_written`, `activity_recorded`, plus the SP-acks-tail additions `ack_written` and `ta1_ack_written` for the `/api/acks/stream` and `/api/ta1-acks/stream` endpoints. Next: **SP43** (after the SP42 doc-pass merges).
|
||||
|
||||
## When to use
|
||||
|
||||
@@ -45,12 +37,11 @@ Three event kinds: `claim_written`, `remittance_written`,
|
||||
2. **Every write publishes an event.** The event name matches the
|
||||
entity: `claim_written`, `remittance_written`,
|
||||
`activity_recorded` (the trailing `_recorded` signals a
|
||||
non-canonical row — activity events are derived, not first-class;
|
||||
current names at `store.py:1072,1081,1096`). New entities get
|
||||
`<entity>_written`; activity-style side rows get
|
||||
`<entity>_recorded`. Publish is **best-effort** — failures are
|
||||
logged but never roll back the persisted batch
|
||||
(`store.py:1097-1098`).
|
||||
non-canonical row — activity events are derived, not first-class),
|
||||
plus `ack_written` and `ta1_ack_written` for the SP-acks-tail
|
||||
streams. New entities get `<entity>_written`; activity-style side
|
||||
rows get `<entity>_recorded`. Publish is **best-effort** —
|
||||
failures are logged but never roll back the persisted batch.
|
||||
3. **Snapshot shape.** Each entity has a `to_ui_<entity>` serializer
|
||||
(plain Python function returning a dict) — currently
|
||||
`to_ui_claim`, `to_ui_remittance`, `to_ui_claim_from_orm`,
|
||||
@@ -61,13 +52,15 @@ Three event kinds: `claim_written`, `remittance_written`,
|
||||
that row (`store.py:1052-1054`).
|
||||
4. **SP21 boundaries.** Post-split, each domain lives in its own
|
||||
module under `backend/src/cyclone/store/`: `__init__.py` (facade
|
||||
+ `CycloneStore` class), `exceptions.py`, `records.py`,
|
||||
`orm_builders.py`, `ui.py`, `write.py`, `batches.py`,
|
||||
`claim_detail.py`, `acks.py`, `backups.py`, `inbox.py`,
|
||||
`providers.py`. Cross-module writes go through `CycloneStore`
|
||||
facade methods, not direct module access. The facade re-exports
|
||||
every name callers currently import from `cyclone.store`. Full
|
||||
list: `docs/superpowers/plans/2026-06-21-cyclone-store-split.md:25-37`.
|
||||
+ `CycloneStore` class), `acks.py`, `backfill.py`, `backups.py`,
|
||||
`batches.py`, `claim_acks.py`, `claim_detail.py`, `exceptions.py`,
|
||||
`inbox.py`, `kpis.py`, `orm_builders.py`, `providers.py`,
|
||||
`records.py`, `resubmissions.py`, `submission_dedup.py`, `ui.py`,
|
||||
`write.py` — 16 modules total. Cross-module writes go through
|
||||
`CycloneStore` facade methods, not direct module access. The
|
||||
facade re-exports every name callers currently import from
|
||||
`cyclone.store`. Full split plan:
|
||||
`docs/superpowers/plans/2026-06-21-cyclone-store-split.md`.
|
||||
5. **No business logic in route handlers.** A route handler validates
|
||||
input, calls `store.<method>(...)`, passes the `event_bus`,
|
||||
returns the serialized result. Reconciliation, idempotency
|
||||
@@ -77,7 +70,7 @@ Three event kinds: `claim_written`, `remittance_written`,
|
||||
|
||||
### A `CycloneStore.add` write — publishes events from inserted rows
|
||||
|
||||
Taken from `backend/src/cyclone/store.py:898-1107`. The method opens
|
||||
Taken from `backend/src/cyclone/store/write.py` (post-SP21). The method opens
|
||||
a session, inserts rows, then runs a sync `_publish_events_sync`
|
||||
after commit so subscribers can immediately re-fetch consistent data.
|
||||
|
||||
@@ -118,12 +111,13 @@ FastAPI handlers to await.
|
||||
|
||||
### Backend `/api/<resource>/stream` endpoint — subscribes to the event
|
||||
|
||||
Taken from `backend/src/cyclone/api.py:1380-1401`. Two phases — eager
|
||||
Streaming endpoints live in `backend/src/cyclone/api_routers/<topic>.py`
|
||||
post-SP36 (e.g. `api_routers/claims.py`). Two phases — eager
|
||||
snapshot, then live subscription — wrapped in `StreamingResponse`
|
||||
with `media_type="application/x-ndjson"`.
|
||||
|
||||
```python
|
||||
@app.get("/api/claims/stream")
|
||||
@router.get("/api/claims/stream")
|
||||
async def claims_stream(request: Request, ...) -> StreamingResponse:
|
||||
bus: EventBus = request.app.state.event_bus
|
||||
|
||||
@@ -139,6 +133,10 @@ async def claims_stream(request: Request, ...) -> StreamingResponse:
|
||||
return StreamingResponse(gen(), media_type="application/x-ndjson")
|
||||
```
|
||||
|
||||
The same shape is repeated in `api_routers/remittances.py`,
|
||||
`api_routers/activity.py`, `api_routers/acks.py`, and
|
||||
`api_routers/ta1_acks.py` (five streaming endpoints total).
|
||||
|
||||
`_ndjson_line` and `_tail_events` live in
|
||||
`backend/src/cyclone/api_helpers.py`. The `["remittance_written"]`
|
||||
and `["activity_recorded"]` subscriptions are at `api.py:1891,2002`.
|
||||
@@ -172,11 +170,11 @@ def test_publishes_claim_written_event(client: TestClient) -> None:
|
||||
equivalent `get_*` facade method).
|
||||
- **Don't introduce a new event name without updating the subscriber
|
||||
list.** Today every `<entity>_written` event has exactly one
|
||||
consumer — the matching `/api/<resource>/stream` endpoint,
|
||||
currently in `backend/src/cyclone/api.py` (claims at `:1398`,
|
||||
remittances at `:1891`, activity at `:2002`). Any future split
|
||||
into `backend/src/cyclone/api_routers/` must wire the same
|
||||
subscription.
|
||||
consumer — the matching `/api/<resource>/stream` endpoint, now
|
||||
split across `api_routers/{claims,remittances,activity,acks,ta1_acks}.py`
|
||||
(post-SP36 split). Any new event must wire its subscription in
|
||||
the matching router and add the resource name to the hook
|
||||
triplet in the corresponding page.
|
||||
- **Don't merge a write method with its event publication into
|
||||
separate places.** `_publish_events_sync` lives next to `add` in
|
||||
`store.py:1042-1107` so reviewers see both halves of the contract
|
||||
|
||||
@@ -5,14 +5,14 @@ description: "Cyclone live-tail streaming wire format and the useTailStream / us
|
||||
|
||||
# cyclone-tail
|
||||
|
||||
Cyclone keeps the Claims, Remittances, and Activity pages live without
|
||||
Cyclone keeps the Claims, Remittances, Activity, Acks, and Ta1Acks pages live without
|
||||
polling: every store write publishes an internal EventBus event, the
|
||||
page opens a `GET /api/<resource>/stream` HTTP/1.1 chunked-NDJSON
|
||||
connection, and new rows land in the table the moment they hit the
|
||||
database. This skill codifies the wire format, the hook triplet, and
|
||||
the backoff/stall machinery so additions stay consistent with the
|
||||
three streaming pages already shipped (`Claims`, `Remittances`,
|
||||
`ActivityLog`).
|
||||
five streaming pages already shipped (`Claims`, `Remittances`,
|
||||
`ActivityLog`, `Acks`, `Ta1Acks`).
|
||||
|
||||
## When to use
|
||||
|
||||
@@ -42,8 +42,8 @@ three streaming pages already shipped (`Claims`, `Remittances`,
|
||||
`item_dropped` (`{"id": "..."}` queue-overflow notice), `error`
|
||||
(`{"message": "..."}` promoted to a thrown error by the hook).
|
||||
Defined at `src/lib/tail-stream.ts:22-44`; emitted by
|
||||
`backend/src/cyclone/api.py:1357-2006`. See
|
||||
`references/wire-format.md`.
|
||||
the streaming routers in `backend/src/cyclone/api_routers/{claims,remittances,activity,acks,ta1_acks}.py`
|
||||
(post-SP36 split). See `references/wire-format.md`.
|
||||
2. **Hook triplet.** Streaming pages compose three pieces:
|
||||
`useTailStream(resource)` (`src/hooks/useTailStream.ts:80` — opens
|
||||
the stream, drives the backoff/stall state machine, dispatches
|
||||
@@ -52,8 +52,9 @@ three streaming pages already shipped (`Claims`, `Remittances`,
|
||||
(`src/hooks/useMergedTail.ts:25` — returns
|
||||
`baseItems + tailSlice` dedup'd against `baseItems`), and a
|
||||
per-resource initial-fetch hook (`useClaims`, `useRemittances`,
|
||||
`useActivity`). The page wires all three — see
|
||||
`src/pages/Claims.tsx:87-89`.
|
||||
`useActivity`, `useAcks`, `useTa1Acks`). The page wires all three —
|
||||
see `src/pages/Claims.tsx:87-89`. The five resources share the
|
||||
same triplet; only the resource string differs.
|
||||
3. **Stall threshold.** 30 seconds of total silence — heartbeats
|
||||
included — flips status to `stalled` and surfaces the
|
||||
`↻ Reconnect` button on `<TailStatusPill>`. Constant:
|
||||
@@ -67,8 +68,8 @@ three streaming pages already shipped (`Claims`, `Remittances`,
|
||||
`<TailStatusPill>` from `connecting` to `live` and to reset the
|
||||
reconnect backoff counter (`useTailStream.ts:174-180`).
|
||||
5. **Content-Type.** Stream endpoints respond with
|
||||
`media_type="application/x-ndjson"` — see
|
||||
`backend/src/cyclone/api.py:1401,1894,2005`. Frontend sets
|
||||
`media_type="application/x-ndjson"` — set in each
|
||||
`api_routers/<resource>.py` stream handler. Frontend sets
|
||||
`Accept: application/x-ndjson` at `src/lib/tail-stream.ts:67-70`.
|
||||
Never `application/json` for a stream endpoint.
|
||||
6. **Backoff.** Transient errors retry with `1s → 2s → 4s → 8s → 16s
|
||||
@@ -108,7 +109,7 @@ export function ClaimsPage() {
|
||||
|
||||
### Backend `/api/foo/stream` endpoint
|
||||
|
||||
Pattern from `backend/src/cyclone/api.py:1357-1401`. Register BEFORE
|
||||
Pattern from `backend/src/cyclone/api_routers/claims.py`. Register BEFORE
|
||||
`/api/foo/{foo_id}` so the literal `stream` segment doesn't match as
|
||||
an id. Two phases — eager snapshot, then live subscription — wrapped
|
||||
in `StreamingResponse` with `media_type="application/x-ndjson"`.
|
||||
@@ -160,7 +161,7 @@ takes `status` + `lastEventAt` from `useTailStream` plus
|
||||
- **Don't change the wire format on one endpoint without updating
|
||||
the others.** The parser (`src/lib/tail-stream.ts`) and the hook
|
||||
dispatch switch (`useTailStream.ts:173-195`) are shared across all
|
||||
three live streams. Adding a new event type means updating
|
||||
five live streams. Adding a new event type means updating
|
||||
`TailEvent`, `KNOWN_TYPES`, the dispatch `case`, the `armStall`
|
||||
re-arm list, and the backend emitter — in that order.
|
||||
- **Don't emit `item` events before `snapshot_end`.** The hook uses
|
||||
@@ -168,7 +169,7 @@ takes `status` + `lastEventAt` from `useTailStream` plus
|
||||
`connecting` to `live` and to reset the reconnect backoff counter.
|
||||
Emitting `item`s first lands rows in `useTailStore` but the UI
|
||||
still reads "Connecting" — operators see a flash of stale state.
|
||||
Emit snapshot, then `snapshot_end`, then live (`api.py:1386-1401`).
|
||||
Emit snapshot, then `snapshot_end`, then live.
|
||||
- **Don't change the 30s stall threshold without updating the README
|
||||
"Status pill" table.** The constant
|
||||
(`STALL_TIMEOUT_MS = 30_000` at `useTailStream.ts:53`) is the
|
||||
@@ -176,7 +177,7 @@ takes `status` + `lastEventAt` from `useTailStream` plus
|
||||
edit at `README.md:118`.
|
||||
- **Don't add `useTailStream` calls from `useFoo.ts` hooks.**
|
||||
Streaming connections belong on the page (`src/pages/Claims.tsx`,
|
||||
`Remittances.tsx`, `ActivityLog.tsx`). Hoisting into `useFoo`
|
||||
`Remittances.tsx`, `ActivityLog.tsx`, `Acks.tsx`, `Ta1Acks.tsx`). Hoisting into `useFoo`
|
||||
couples the lifecycle to whoever mounts it and breaks
|
||||
one-resource-one-page ownership.
|
||||
|
||||
@@ -185,16 +186,16 @@ takes `status` + `lastEventAt` from `useTailStream` plus
|
||||
- **`cyclone-frontend-page`** — page components live in `src/pages/`;
|
||||
load when adding or refactoring a streaming page to confirm the
|
||||
route + `PageHeader` + table conventions.
|
||||
- **`cyclone-api-router`** — endpoint conventions (`api_routers/` for
|
||||
resource-group routers, `api.py` for the live-tail endpoints); load
|
||||
when adding or changing an HTTP endpoint that surfaces streamed or
|
||||
paginated data.
|
||||
- **`cyclone-api-router`** — endpoint conventions (`api_routers/`
|
||||
hosts the streaming endpoints post-SP36, alongside the other
|
||||
resource-group routers); load when adding or changing an HTTP
|
||||
endpoint that surfaces streamed or paginated data.
|
||||
- **`cyclone-store`** — write-path conventions in `store.py` and the
|
||||
pubsub event contract (`claim_written`, `remittance_written`,
|
||||
`activity_recorded`); load when adding a new entity whose writes
|
||||
should fan out to a stream endpoint.
|
||||
- **`cyclone-tests`** — frontend `*.test.tsx` siblings cover
|
||||
`useTailStream`, `useMergedTail`, `TailStatusPill`; backend
|
||||
`test_api_stream_live.py` covers the three live-tail endpoints;
|
||||
load when the increment changes wire-format behavior or adds a
|
||||
streaming hook.
|
||||
`test_api_stream_live.py` covers the five live-tail endpoints
|
||||
(claims, remittances, activity, acks, ta1-acks); load when the
|
||||
increment changes wire-format behavior or adds a streaming hook.
|
||||
@@ -5,9 +5,9 @@ description: "Cyclone pytest + vitest fixture patterns, prodfiles layout, backen
|
||||
|
||||
# cyclone-tests
|
||||
|
||||
The Cyclone test suite is split two ways: **backend pytest** (89 test files under `backend/tests/`, 13 flat fixtures in `backend/tests/fixtures/`, one autouse `conftest.py` that resets the DB per-test) and **frontend vitest** (59 `*.test.ts(x)` siblings across `src/`, two rendering styles — `@testing-library/react` and a custom `createRoot`+`Probe` shim). This skill codifies the conventions so additions stay consistent with what's already there.
|
||||
The Cyclone test suite is split two ways: **backend pytest** (~173 test files under `backend/tests/`, ~21 flat fixtures in `backend/tests/fixtures/`, one autouse `conftest.py` that resets the DB per-test) and **frontend vitest** (85 `*.test.ts(x)` siblings across `src/`, two rendering styles — `@testing-library/react` and a custom `createRoot`+`Probe` shim). This skill codifies the conventions so additions stay consistent with what's already there.
|
||||
|
||||
As of this writing: **89 backend test files**, **13 flat backend fixtures**, **59 frontend `*.test.ts(x)` siblings**, and **23 prodfiles samples** across `docs/prodfiles/{837p-from-axiscare,835fromco,FromHPE,claims}/`. The next increment is **SP24** (SP23 is the Ubuntu+Docker fork, awaiting user decision).
|
||||
As of this writing: **~173 backend test files**, **~21 flat backend fixtures** (the original 13 plus additions for `reissue-claims`, `validate-837`, `submit-batch`, and SP41 rebill fixtures), **85 frontend `*.test.ts(x)` siblings**, and a slimmed-down `docs/prodfiles/references/` archive (the bulk of the prodfiles were moved out of the repo; remaining sample is `good-claim-com-2026-04-16.837`). Tests that previously reached into `docs/prodfiles/claims/` and `docs/prodfiles/FromHPE/` now skip when those dirs are absent (`tests/test_prodfiles_smoke.py`). The most recently shipped increment is **SP41** (in-window rebill pipeline); the next free increment is **SP43** after this SP42 doc-pass merges.
|
||||
|
||||
## Auth flag (SP24)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ Cyclone ships 8 skills under `.superpowers/skills/`. They auto-load by descripti
|
||||
|
||||
## The SP-N increment flow
|
||||
|
||||
Every feature ships as a numbered **SP-N increment**: spec → plan → implementation branch → single atomic merge into `main`. As of the last backfill, SP numbers are used through **SP22**; **SP23** is reserved for the Ubuntu + Docker + RBAC product fork (`docs/superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md`, awaiting user decision); the next free increment is **SP24**. Read `cyclone-spec` before starting a new one. Non-negotiable shape:
|
||||
Every feature ships as a numbered **SP-N increment**: spec → plan → implementation branch → single atomic merge into `main`. SP numbers are used through **SP41** (in-window rebill pipeline, merged `309e6c0`); the next free increment is **SP42**. SP23 (Ubuntu + Docker + auth + RBAC + LAN-bind product fork) shipped via `merge: SP23 Ubuntu Docker Deployment into main` (`07a7ecb`). Read `cyclone-spec` before starting a new one. Non-negotiable shape:
|
||||
|
||||
- **Branch:** `sp<N>-<short-kebab-topic>` (e.g. `sp22-line-reconciliation`).
|
||||
- **Spec path:** `docs/superpowers/specs/YYYY-MM-DD-cyclone-<topic>-design.md`, header `Status: Draft, awaiting user sign-off`, sections `Scope / Decisions / …`. Specs contain zero code blocks.
|
||||
@@ -116,6 +116,8 @@ Endpoints (all accept the same query params as their non-streaming counterparts;
|
||||
| GET | `/api/claims/stream` | `claim_written` | `-submission_date` |
|
||||
| GET | `/api/remittances/stream` | `remittance_written` | `-received_date` |
|
||||
| GET | `/api/activity/stream` | `activity_recorded` | `-timestamp` (limit 50) |
|
||||
| GET | `/api/acks/stream` | `ack_received` | `-received_date` |
|
||||
| GET | `/api/ta1-acks/stream` | `ta1_ack_received` | `-received_date` |
|
||||
|
||||
Wire format: one JSON object per line, `{"type": ..., "data": ...}`. The first batch is the **snapshot** of currently-known rows, then `snapshot_end` with the count, then the **live** events. Known types: `item`, `snapshot_end`, `heartbeat` (keeps the connection alive on idle — clients flip to `stalled` after 30s of total silence), `item_dropped` (rare), `error`.
|
||||
|
||||
@@ -125,9 +127,9 @@ Frontend triplet for any live page: `use<X>(params)` (initial fetch) + `useTailS
|
||||
|
||||
## Backend at a glance
|
||||
|
||||
`backend/src/cyclone/` is a single namespace. The two largest files are `api.py` (~3,548 LOC, the only large file) and `store.py` (~2,423 LOC, the `CycloneStore` facade — SP21 is in flight to split it into a `cyclone/store/` subpackage; the public API stays unchanged). Subpackages: `api_routers/` (acks, admin, health, ta1_acks), `clearhouse/` (Clearhouse + SftpClient), `edi/` (filenames), `parsers/` (X12 transaction parsers + models + validators + serializers), `submission/` (SP37 — canonical `submit_file` helper shared by `cyclone submit-batch` CLI + `POST /api/submit-batch` HTTP endpoint; owns parse → DB-write → SFTP-upload → audit per file), `workflow/` (placeholder for future sub-project 6).
|
||||
`backend/src/cyclone/` is a single namespace. After SP21 (store split, `4360ef7`) and SP36 (api-routers split, `f005494`), the largest files now live under `cyclone/api_routers/` (22 routers) and `cyclone/store/` (the SP21-split facade in 16 modules). `api.py` is a ~377-line shell that wires the FastAPI app, mounts the auth + admin + api_routers routers, and re-exports a few backward-compat shims for tests; `store.py` no longer exists. Subpackages: `api_routers/` (SP36 — 22 routers: acks, activity, admin, batches, claim_acks, claims, clearhouse, config, dashboard, eligibility, health, inbox, parse, payers, providers, rebill, reconciliation, remittances, submission, ta1_acks, plus `_shared.py`), `auth/` (SP24 — bcrypt + HttpOnly session cookies + RBAC `matrix_gate`; 10 modules), `clearhouse/` (Clearhouse + SftpClient), `edi/` (filenames), `handlers/` (SP27 — per-file-type inbound parse handlers), `parsers/` (X12 transaction parsers + models + validators + serializers), `rebill/` (SP41 — in-window rebill pipeline), `reissue/` (SP24 — offline 837P reissue workflow), `store/` (SP21 — split `CycloneStore` facade), `submission/` (SP37 — canonical `submit_file` helper shared by `cyclone submit-batch` CLI + `POST /api/submit-batch` HTTP endpoint; owns parse → DB-write → SFTP-upload → audit per file). The legacy `workflow/` directory is dead bytecode — see REQUIREMENTS.md R-27 (Open).
|
||||
|
||||
The store is the only read/write surface for the database; every mutating endpoint goes through it. All persistence flows through SQLAlchemy sessions via `db.SessionLocal()()`. SQLAlchemy ORM models live in `db.py`; 12 SQL migrations under `migrations/` (0001_initial through 0012_backups) are walked in order by `db_migrate.py`.
|
||||
The store is the only read/write surface for the database; every mutating endpoint goes through it. All persistence flows through SQLAlchemy sessions via `db.SessionLocal()()`. SQLAlchemy ORM models live in `db.py`; 23 SQL migrations under `migrations/` (0001_initial through 0023_visits) are walked in order by `db_migrate.py`.
|
||||
|
||||
The parser pipeline is a 5-stage `tokenize → segmentize → model → validate → write_to_store` flow used for every inbound X12 type. Per-transaction parsers: `parse_837.py`, `parse_835.py`, `parse_999.py`, `parse_ta1.py`, `parse_270.py`, `parse_271.py`, `parse_277ca.py`. Each has a matching Pydantic model module (`models.py`, `models_835.py`, …) and a writer (`writer.py` / `writer_835.py`). The 837P serializer (`serialize_837.py`) is the byte-faithful outbound counterpart used by both single-claim download (`/api/claims/{id}/serialize-837`) and the bulk rejected-resubmit bundle (`/api/inbox/rejected/resubmit?download=true`).
|
||||
|
||||
@@ -167,7 +169,7 @@ The canonical way to write 999/TA1/277CA/835 rows into the DB is `Scheduler.proc
|
||||
|
||||
## Frontend at a glance
|
||||
|
||||
`src/` is React 18 + TypeScript + Vite. Routes register in `src/App.tsx` (11 pages, all under a `<Layout>` route wrapper). Pages are pure renderers — every page pairs with a `use<X>` data hook in `src/hooks/` and renders a `<PageHeader>` + a table/list/KPI grid. Drawers (`ClaimDrawer/`, `RemitDrawer/`, plus the new `ProviderDrawer/` and `AckDrawer/`) are mounted by the page and their open/close state is mirrored to the URL via `useDrawerUrlState` so deep-links round-trip. Drill-stack navigation is provided by `<DrillStackProvider>` in `src/components/drill/`.
|
||||
`src/` is React 18 + TypeScript + Vite. Routes register in `src/App.tsx` (12 pages — Dashboard, Claims, Remittances, Providers, ActivityLog, Upload, Reconciliation, Inbox, Acks, Batches, BatchDiff, Login — all under a `<Layout>` route wrapper, with `Login` outside the auth gate). Pages are pure renderers — every page pairs with a `use<X>` data hook in `src/hooks/` and renders a `<PageHeader>` + a table/list/KPI grid. Drawers (`ClaimDrawer/`, `RemitDrawer/`, `ProviderDrawer/`, `AckDrawer/`) are mounted by the page and their open/close state is mirrored to the URL via `useDrawerUrlState` so deep-links round-trip. Drill-stack navigation is provided by `<DrillStackProvider>` in `src/components/drill/`.
|
||||
|
||||
State split: **server state** in TanStack Query (`@tanstack/react-query`); **ephemeral client state** in Zustand (`useTailStore` for live-tail append, plus the drill stack). The live-tail store is FIFO-capped at `TAIL_CAP = 10_000` per slice (`src/store/tail-store.ts:28`); `claims` and `remittances` are key-by-id with first-write-wins dedup, `activity` is an append-only array.
|
||||
|
||||
@@ -181,9 +183,6 @@ Path alias `@/` → `src/`. Configured in `vite.config.ts`, `vitest.config.ts`,
|
||||
# Parser
|
||||
python -m cyclone.cli parse-837 path/to/837p.txt --output-dir ./claims --payer co_medicaid [--strict] [--include-raw-segments]
|
||||
python -m cyclone.cli parse-835 path/to/835.txt --output-dir ./remits
|
||||
python -m cyclone.cli parse-999 inbound_999.txt
|
||||
python -m cyclone.cli parse-ta1 inbound_ta1.txt
|
||||
python -m cyclone.cli parse-277ca inbound_277ca.txt
|
||||
|
||||
# Validators
|
||||
python -m cyclone.cli validate-npi 1234567893
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
|
||||
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.
|
||||
and 005010X221A1), with a FastAPI backend and a React UI for browsing,
|
||||
filtering, and inspecting the parsed data.
|
||||
|
||||
Local-only on purpose: binds to `127.0.0.1`, no auth, no internet exposure.
|
||||
Built for one operator, one machine, one trading partner (Colorado
|
||||
Medicaid, currently).
|
||||
LAN-only by design: always binds to `0.0.0.0:8000` and requires login
|
||||
(bcrypt + HttpOnly session cookie; first admin bootstrapped from
|
||||
`CYCLONE_ADMIN_USERNAME` + `CYCLONE_ADMIN_PASSWORD`). Reachability is
|
||||
controlled by the host firewall / compose port publishing, not by the
|
||||
bind address. Built for one operator, one machine, one trading partner
|
||||
(Colorado Medicaid, currently). Do not expose the published ports to
|
||||
the public internet. See [SP23](docs/superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md)
|
||||
for the LAN-bind / Docker / RBAC product fork that shipped 2026-06-23.
|
||||
|
||||
## Install
|
||||
|
||||
@@ -185,12 +190,14 @@ happening — clients flip to `stalled` after 30s of total silence
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Subscribes to | Default sort |
|
||||
| ------ | -------------------------- | ------------------- | ------------------- |
|
||||
| ------ | -------------------------- | -------------------- | ------------------- |
|
||||
| GET | `/api/claims/stream` | `claim_written` | `-submission_date` |
|
||||
| GET | `/api/remittances/stream` | `remittance_written`| `-received_date` |
|
||||
| GET | `/api/remittances/stream` | `remittance_written` | `-received_date` |
|
||||
| GET | `/api/activity/stream` | `activity_recorded` | `-timestamp` (limit 50) |
|
||||
| GET | `/api/acks/stream` | `ack_received` | `-received_date` |
|
||||
| GET | `/api/ta1-acks/stream` | `ta1_ack_received` | `-received_date` |
|
||||
|
||||
All three accept the same query params as their non-streaming
|
||||
All five accept the same query params as their non-streaming
|
||||
counterparts (`status`, `payer`, `date_from`, …) so a frontend can
|
||||
swap a one-shot fetch for a tail with no URL surgery. Responses
|
||||
are `Content-Type: application/x-ndjson`.
|
||||
@@ -424,8 +431,8 @@ in a single walk.
|
||||
`verify_chain` is the integrity check that backs the audit promise —
|
||||
it is intentionally cheap (one indexed walk) and intentionally
|
||||
side-effect-free so a scheduler can run it on a cron and alert on any
|
||||
non-`{ok: true}` result. Chain verification is **not** access-gated
|
||||
beyond the same `127.0.0.1` bind the rest of the API uses; for a
|
||||
non-`{ok: true}` result. Chain verification is gated by the standard
|
||||
`matrix_gate` dependency (admin role for the `/verify` subpath); for a
|
||||
hostile multi-operator deployment, wrap the route in your reverse proxy.
|
||||
|
||||
## Encryption at Rest
|
||||
@@ -743,12 +750,11 @@ backup API).
|
||||
.
|
||||
├── backend/
|
||||
│ ├── src/cyclone/
|
||||
│ │ ├── api.py # FastAPI app, GET + parse routes, /api/{resource}/stream
|
||||
│ │ ├── api.py # FastAPI app shell (~377 LOC post-SP36; routes live in api_routers/)
|
||||
│ │ ├── api_helpers.py # NDJSON / content-negotiation / live-tail helpers
|
||||
│ │ ├── pubsub.py # in-process EventBus (drop-oldest, per-kind fan-out)
|
||||
│ │ ├── store.py # CycloneStore, mappers, publish-on-write
|
||||
│ │ ├── db.py # SQLAlchemy engine, session factory, ORM models
|
||||
│ │ ├── db_migrate.py # PRAGMA user_version migration runner
|
||||
│ │ ├── db_migrate.py # PRAGMA user_version migration runner (23 migrations)
|
||||
│ │ ├── db_crypto.py # optional SQLCipher encryption at rest (SP12)
|
||||
│ │ ├── audit_log.py # tamper-evident hash-chained audit_log (SP11)
|
||||
│ │ ├── inbox_lanes.py # rejected / payer_rejected / candidates / unmatched / done_today
|
||||
@@ -756,37 +762,45 @@ backup API).
|
||||
│ │ ├── inbox_state_277ca.py # 277CA STC A4/A6/A7 → payer_rejected stamp (SP10)
|
||||
│ │ ├── providers.py # multi-NPI provider lookups (SP9)
|
||||
│ │ ├── payers.py # payer / payer_config lookups (SP9)
|
||||
│ │ ├── secrets.py # macOS Keychain-backed secret fetcher
|
||||
│ │ ├── reconcile.py # pure-function 835→claim match + line-level match
|
||||
│ │ ├── __main__.py # `python -m cyclone serve`
|
||||
│ │ ├── cli.py # click CLI
|
||||
│ │ └── parsers/ # X12 tokenizer, models, validator, writers, 277CA, 999, TA1, 270, 271
|
||||
│ └── tests/
|
||||
│ ├── fixtures/ # co_medicaid_*.txt, minimal_*.txt
|
||||
│ ├── test_api.py # parse-837/835 round-trip
|
||||
│ ├── test_api_gets.py # 6 GET endpoints
|
||||
│ ├── test_store.py # store + mappers + iterators
|
||||
│ ├── test_api_streaming.py
|
||||
│ ├── test_api_stream_live.py # 3 live-tail endpoints + disconnect cleanup
|
||||
│ ├── test_pubsub.py # EventBus + subscribe/unsubscribe
|
||||
│ ├── test_api_parse_persists.py
|
||||
│ ├── test_db.py / test_db_crypto.py / test_db_migrate.py
|
||||
│ ├── test_audit_log.py
|
||||
│ ├── test_inbox_lanes.py / test_inbox_state.py
|
||||
│ ├── test_apply_277ca_rejections.py
|
||||
│ ├── test_sftp_stub.py / test_sftp_paramiko.py
|
||||
│ └── test_providers_seed.py / test_payer_config_loading.py
|
||||
├── src/ # React + Vite + TypeScript UI
|
||||
│ │ ├── secrets.py # macOS Keychain-backed secret fetcher (4-tier: _FILE > env > Keychain > None)
|
||||
│ │ ├── reconcile.py # pure-function 835→claim match + line-level match (SP31 pcn-exact)
|
||||
│ │ ├── scheduler.py # inbound MFT polling scheduler (SP16, SP27 per-op timeout)
|
||||
│ │ ├── edifabric.py # Edifabric /v2/x12/{read,validate} HTTP client (SP40)
|
||||
│ │ ├── backup.py / backup_service.py / backup_scheduler.py # SP17 encrypted backups
|
||||
│ │ ├── __main__.py # `python -m cyclone serve` (auth bootstrap then dispatch)
|
||||
│ │ ├── cli.py # click CLI (15+ subcommands; see cyclone-cli skill)
|
||||
│ │ ├── api_routers/ # SP36 — 22 FastAPI routers + _shared.py helpers
|
||||
│ │ ├── auth/ # SP24 — bcrypt + sessions + matrix_gate + admin users + rate_limit
|
||||
│ │ ├── clearhouse/ # SftpClient + InboundFile + SftpStat
|
||||
│ │ ├── edi/ # filename regex/builder helpers
|
||||
│ │ ├── handlers/ # SP27 — per-file-type inbound parse handlers
|
||||
│ │ ├── parsers/ # X12 tokenizer, models, validator, writers, 277CA, 999, TA1, 270, 271
|
||||
│ │ ├── rebill/ # SP41 — in-window rebill pipeline (13 modules)
|
||||
│ │ ├── reissue/ # SP24 — offline 837P reissue workflow
|
||||
│ │ ├── store/ # SP21 — split CycloneStore facade (16 modules)
|
||||
│ │ └── submission/ # SP37 — canonical submit_file helper + recover + bulk_ingest
|
||||
│ └── tests/ # 178 pytest files (post-SP41)
|
||||
│ ├── fixtures/ # 22 entries (837P / 835 / 999 / TA1 / 277CA samples + visits CSV)
|
||||
│ ├── test_api_*.py # FastAPI integration via TestClient
|
||||
│ ├── test_*.py # pure-unit
|
||||
│ └── ...
|
||||
├── src/ # React + Vite + TypeScript UI (12 pages, 84 test files)
|
||||
│ ├── components/
|
||||
│ │ ├── ui/ # Skeleton, EmptyState, ErrorState, FilterChips, Pagination, …
|
||||
│ │ ├── ClaimDrawer/ # 12 sibling tests
|
||||
│ │ ├── RemitDrawer/ # 8 sibling tests
|
||||
│ │ ├── ProviderDrawer/ # right-anchored side panel
|
||||
│ │ ├── AckDrawer/ # right-anchored side panel (SP28 ack-claim auto-link)
|
||||
│ │ ├── inbox/ # 5-lane triage surface (SP14)
|
||||
│ │ ├── drill/ # DrillStackProvider + DrillableCell + PeekModal
|
||||
│ │ └── TailStatusPill.tsx # live-tail status badge + reconnect button
|
||||
│ ├── pages/ # Claims, Remittances, Providers, Acks, Activity, Upload, Inbox, …
|
||||
│ ├── pages/ # Dashboard, Claims, Remittances, Providers, ActivityLog,
|
||||
│ │ # Upload, Reconciliation, Inbox, Acks, Batches, BatchDiff, Login
|
||||
│ ├── hooks/ # useBatches, useClaims, useRemittances, useProviders, useActivity, useParse
|
||||
│ │ # + useTailStream, useMergedTail (live tail)
|
||||
│ ├── lib/ # api.ts, format.ts, utils.ts
|
||||
│ │ # + tail-stream.ts (NDJSON parser)
|
||||
│ ├── store/ # zustand sample-data + parsed-batches store
|
||||
│ │ # + tail-store.ts (FIFO-capped live tail slices)
|
||||
│ │ # + useTailStream, useMergedTail (live tail triplet)
|
||||
│ ├── auth/ # AuthProvider, RoleGate, api client (SP24)
|
||||
│ ├── lib/ # api.ts, format.ts, utils.ts, tail-stream.ts (NDJSON parser), inbox-api.ts
|
||||
│ ├── store/ # zustand useAppStore (sample data) + useTailStore (FIFO-capped live tail slices)
|
||||
│ └── types/ # shared TS types
|
||||
├── config/
|
||||
│ └── payers.yaml # YAML-driven payer + clearhouse config (SP9)
|
||||
@@ -807,11 +821,15 @@ backup API).
|
||||
> 3. The per-SP spec under [`docs/superpowers/specs/`](docs/superpowers/specs/) for whatever you're touching.
|
||||
> 4. The per-SP plan under [`docs/superpowers/plans/`](docs/superpowers/plans/) if you're implementing.
|
||||
|
||||
Sub-projects 2 through 19 are **shipped**. See the [completeness
|
||||
Sub-projects 2 through 41 are **shipped** (last merged: SP41 in-window
|
||||
rebill pipeline, `merge: SP41 inwindow-rebill-pipeline into main` at
|
||||
`309e6c0`). See the [completeness
|
||||
review](docs/reviews/2026-06-20-cyclone-completeness-review.md) for
|
||||
the honest gap analysis against the industry definition of a HIPAA
|
||||
clearinghouse — the short version is that the local-only,
|
||||
single-operator, single-payer design contract is honored, and the
|
||||
clearinghouse — the short version is that the LAN-only,
|
||||
single-operator, single-payer design contract is honored (auth per
|
||||
SP23, encryption at rest via SQLCipher per SP12, RBAC per SP23, audit
|
||||
log per SP11), and the
|
||||
items that would be needed to expand that contract (AS2/AS4, SNIP 1–7,
|
||||
HITRUST, 276/277 status, 278 referrals, COB) are intentionally out of
|
||||
scope.
|
||||
|
||||
+171
-57
@@ -4,8 +4,8 @@
|
||||
>
|
||||
> **Companion docs:**
|
||||
> - **Requirements:** [`docs/REQUIREMENTS.md`](REQUIREMENTS.md) — what Cyclone does (FRs + NFRs + DoD + traceability).
|
||||
> - **Specs:** [`docs/superpowers/specs/`](superpowers/specs/) — design specs per sub-project (22 specs).
|
||||
> - **Plans:** [`docs/superpowers/plans/`](superpowers/plans/) — TDD-shaped implementation plans per sub-project (27 plans).
|
||||
> - **Specs:** [`docs/superpowers/specs/`](superpowers/specs/) — design specs per sub-project (42 specs).
|
||||
> - **Plans:** [`docs/superpowers/plans/`](superpowers/plans/) — TDD-shaped implementation plans per sub-project (37 plans).
|
||||
>
|
||||
> **Scope of this doc:** architecture and design. The spec files own per-SP design; the plan files own the task order. This doc explains the *whole* — the boundaries between modules, the data flow across them, the lifecycle of an inbound file from upload to reconciled claim.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
## 1. System overview
|
||||
|
||||
Cyclone is a self-hosted X12 EDI claims-management suite for a single billing office. The first deployment target is Colorado Medicaid (one operator, one trading partner). The system is local-only on purpose: the backend binds to `127.0.0.1` and requires login (bcrypt + HttpOnly session cookie). The threat model is still a stolen or imaged drive, not a remote attacker — SQLCipher at rest and the macOS Keychain handle that. The auth boundary is the HTTP layer; the file-system posture is unchanged. The SP23 product fork changes the threat model to "remote operator on LAN."
|
||||
Cyclone is a self-hosted X12 EDI claims-management suite for a single billing office. The first deployment target is Colorado Medicaid (one operator, one trading partner). LAN-only by design: the backend always binds to `0.0.0.0:8000` and requires login (bcrypt + HttpOnly session cookie per SP23/24). Reachability is controlled by the host firewall / compose port publishing, not the bind address. The threat model is still a stolen or imaged drive, not a remote attacker — SQLCipher at rest and the macOS Keychain handle that. The auth boundary is the HTTP layer; the file-system posture is unchanged.
|
||||
|
||||
Cyclone processes three X12 transaction pairs end-to-end:
|
||||
|
||||
@@ -46,17 +46,22 @@ The whole stack is a single Python process (FastAPI + uvicorn on port 8000) plus
|
||||
+----------------------------------------------------------+
|
||||
| Python process: `python -m cyclone serve` |
|
||||
| |
|
||||
| FastAPI app (uvicorn, 127.0.0.1:8000) |
|
||||
| FastAPI app (uvicorn, 0.0.0.0:8000; SP23) |
|
||||
| +-----------------------------------------------------+ |
|
||||
| | cyclone.api:app (3,548 LOC) | |
|
||||
| | - routers: claims, remits, providers, acks, | |
|
||||
| | activity, inbox, upload, download, batches, | |
|
||||
| | admin, scheduler, health, ta1_acks | |
|
||||
| | cyclone.api:app (~377 LOC, post-SP36 thin shell) | |
|
||||
| | - mounts 22 routers under cyclone.api_routers/ | |
|
||||
| | (acks, activity, admin, batches, claim_acks, | |
|
||||
| | claims, clearhouse, config, dashboard, | |
|
||||
| | eligibility, health, inbox, parse, payers, | |
|
||||
| | providers, rebill, reconciliation, remittances, | |
|
||||
| | submission, ta1_acks) | |
|
||||
| | - matrix_gate dependency on every gated router | |
|
||||
| | (SP24 auth) | |
|
||||
| | - live-tail: NDJSON pubsub (pubsub.py) | |
|
||||
| | - lifespan: init db -> seed SP9 -> start SP16 + | |
|
||||
| | SP17 schedulers -> load PayerConfig cache | |
|
||||
| | - lifespan: init db -> seed SP9 -> start SP16 | |
|
||||
| | + SP17 schedulers -> load PayerConfig cache | |
|
||||
| +-----------------------------------------------------+ |
|
||||
| | cyclone.store (facade, 2,423 LOC) | |
|
||||
| | cyclone.store (facade, post-SP21 split: 16 modules)| |
|
||||
| | - add/get/list/manual_match/iter_claims/... | |
|
||||
| | - delegates to: db.SessionLocal() | |
|
||||
| +-----------------------------------------------------+ |
|
||||
@@ -64,8 +69,13 @@ The whole stack is a single Python process (FastAPI + uvicorn on port 8000) plus
|
||||
| | 277CA: tokenize -> segmentize -> model -> validate | |
|
||||
| | -> write to store) | |
|
||||
| +-----------------------------------------------------+ |
|
||||
| | cyclone.reconcile, scoring, batch_diff, audit_log, | |
|
||||
| | backup_service, scheduler, db_crypto, secrets | |
|
||||
| | cyclone.auth.* (SP24: bcrypt + sessions + matrix | |
|
||||
| | gate), cyclone.handlers.* (SP27: per-kind parse), | |
|
||||
| | cyclone.rebill.* (SP41: rebill pipeline), | |
|
||||
| | cyclone.reissue.* (SP24: reissue CLI), | |
|
||||
| | reconcile, scoring, batch_diff, audit_log, | |
|
||||
| | backup_service, scheduler, db_crypto, secrets, | |
|
||||
| | edifabric (SP40), submission (SP37) | |
|
||||
| +-----------------------------------------------------+ |
|
||||
+----------------------------------------------------------+
|
||||
| | |
|
||||
@@ -93,7 +103,7 @@ The `cyclone-pipeline/` sibling project (not in this repo) is a separate Python
|
||||
|---|---|---|---|
|
||||
| Language | Python | 3.11+ (3.13 in dev) | `requires-python = ">=3.11"` |
|
||||
| Web framework | FastAPI | ≥0.110, <1 | `python -m cyclone serve` boots uvicorn |
|
||||
| ASGI server | uvicorn[standard] | ≥0.27, <1 | `--host 127.0.0.1 --port 8000` |
|
||||
| ASGI server | uvicorn[standard] | ≥0.27, <1 | `--host 0.0.0.0 --port 8000` (CYCLONE_HOST/CYCLONE_PORT override) |
|
||||
| ORM | SQLAlchemy | ≥2.0, <3 | 2.x style; engine + SessionLocal() function-accessor |
|
||||
| Validation | Pydantic | ≥2.6, <3 | v2 model_validator + ConfigDict |
|
||||
| CLI | Click | ≥8.1, <9 | `cyclone` console script |
|
||||
@@ -135,38 +145,75 @@ The `cyclone-pipeline/` sibling project (not in this repo) is a separate Python
|
||||
|
||||
### 4.1 Package layout
|
||||
|
||||
The `cyclone` package is a single namespace rooted at `backend/src/cyclone/`. 22 top-level modules + 4 subpackages. The store (SP21 in-flight split) is the largest; everything else is small and focused.
|
||||
The `cyclone` package is a single namespace rooted at `backend/src/cyclone/`. After SP21 (store split), SP24 (auth + reissue), SP27 (handlers), SP36 (api-routers split), and SP41 (rebill pipeline), the package has **22 top-level modules + 10 subpackages**. The two largest files are now `cyclone/api_routers/admin.py` (~640 LOC, the admin operator surface) and the various pipeline modules in `cyclone/rebill/`. `api.py` is a ~377-line shell (post-SP36) and `store.py` no longer exists (post-SP21).
|
||||
|
||||
```
|
||||
backend/src/cyclone/
|
||||
├── __main__.py — entry point; `python -m cyclone` → CLI, `python -m cyclone serve` → uvicorn
|
||||
├── __init__.py — `__version__`, package init
|
||||
├── api.py — FastAPI app, route includes, lifespan (3,548 LOC — the only large file)
|
||||
├── api_helpers.py — request/response formatters, error → HTTP mapping
|
||||
├── api_routers/
|
||||
│ ├── __init__.py
|
||||
│ ├── acks.py — 999/TA1/271/277CA acknowledgments
|
||||
│ ├── admin.py — admin-only endpoints (validate-provider, scheduler, backup)
|
||||
│ ├── health.py — GET /api/health (SP19 deep snapshot)
|
||||
│ └── ta1_acks.py — TA1-specific (split from acks for the SP3 lifecycle)
|
||||
├── api.py — FastAPI app, route mounting (~377 LOC, post-SP36 thin shell)
|
||||
├── api_helpers.py — request/response formatters, error → HTTP mapping, NDJSON helpers
|
||||
├── api_routers/ — SP36: 22 per-resource FastAPI routers + _shared.py
|
||||
│ ├── __init__.py — registry: `routers: list[APIRouter]`
|
||||
│ ├── _shared.py — cross-router helpers
|
||||
│ ├── acks.py — 999/277CA acks list / stream / detail
|
||||
│ ├── activity.py — activity feed + stream
|
||||
│ ├── admin.py — admin-only operator surface (validate-provider, scheduler, backup, users, validate-837)
|
||||
│ ├── batches.py — batch list / detail / ZIP export
|
||||
│ ├── claim_acks.py — SP28 claim ↔ ack auto-link endpoints
|
||||
│ ├── claims.py — claim register / stream / detail / serialize-837 / line-reconciliation
|
||||
│ ├── clearhouse.py — singleton clearhouse config + SFTP submit
|
||||
│ ├── config.py — payer-config read views
|
||||
│ ├── dashboard.py — GET /api/dashboard/kpis
|
||||
│ ├── eligibility.py — 270 build / 271 parse
|
||||
│ ├── health.py — GET /api/health (public)
|
||||
│ ├── inbox.py — 5-lane triage surface (SP14)
|
||||
│ ├── parse.py — SP35 envelope-guarded parse-* endpoints
|
||||
│ ├── payers.py — payer-level rollup
|
||||
│ ├── providers.py — distinct providers + configured providers
|
||||
│ ├── rebill.py — SP41 POST/GET /api/admin/rebill-from-835
|
||||
│ ├── reconciliation.py — manual match + batch diff
|
||||
│ ├── remittances.py — remittance register / summary / stream / detail
|
||||
│ ├── submission.py — SP37 POST /api/submit-batch
|
||||
│ └── ta1_acks.py — TA1 list / stream / detail
|
||||
├── audit_log.py — SHA-256 hash-chained append-only log (SP11)
|
||||
├── auth/ — SP24: bcrypt + sessions + RBAC matrix_gate
|
||||
│ ├── __init__.py — `AUTH_DISABLED` flag + module surface
|
||||
│ ├── bootstrap.py — first-admin env-var bootstrap
|
||||
│ ├── cli.py — `cyclone users` group
|
||||
│ ├── deps.py — FastAPI deps + matrix_gate
|
||||
│ ├── permissions.py — role matrix (admin/user/viewer)
|
||||
│ ├── rate_limit.py — per-username login throttle (5 fails / 5 min)
|
||||
│ ├── routes.py — /api/auth/{login,logout,me}
|
||||
│ ├── sessions.py — 24h sliding expiry
|
||||
│ └── users.py — user CRUD + bcrypt password hashing
|
||||
├── backup.py — SP17 primitives: derive_key, encrypt, decrypt, BackupFile
|
||||
├── backup_scheduler.py — SP17 BackupScheduler wrapper
|
||||
├── backup_service.py — SP17 coordinator: create/list/restore/verify/prune
|
||||
├── batch_diff.py — 837 ↔ 835 diff (SP4)
|
||||
├── claim_acks.py — SP28 pure readers over session + parse result
|
||||
├── clearhouse/
|
||||
│ └── __init__.py — Clearhouse, SftpClient (SP9, SP13)
|
||||
├── cli.py — Click CLI: `cyclone serve`, `cyclone validate-npi`, `cyclone backup`, etc.
|
||||
├── cli.py — Click CLI: 15+ subcommands (see cyclone-cli skill)
|
||||
├── db.py — SQLAlchemy engine, SessionLocal, ORM models, init_db, reinit_engine
|
||||
├── db_crypto.py — SP12 SQLCipher connect-creator + is_encryption_enabled()
|
||||
├── db_migrate.py — runs the 12 SQL migrations in order
|
||||
├── db_migrate.py — runs the 23 SQL migrations in order
|
||||
├── edifabric.py — SP40 Edifabric /v2/x12/{read,validate} HTTP client
|
||||
├── edi/
|
||||
│ └── filenames.py — X12 filename convention helpers (SP9)
|
||||
├── handlers/ — SP27: per-file-type inbound parse handlers
|
||||
│ ├── __init__.py — HANDLERS registry + register_handlers()
|
||||
│ ├── _ack_id.py — synthetic batch id + AK5-count helpers
|
||||
│ ├── handle_277ca.py — 277CA parse → persist + payer-rejected stamp
|
||||
│ ├── handle_835.py — 835 parse → validate → persist + reconcile (two-phase)
|
||||
│ ├── handle_999.py — 999 parse → apply rejections
|
||||
│ ├── handle_result.py — HandleResult dataclass
|
||||
│ └── handle_ta1.py — TA1 parse → persist envelope ack + envelope-link batches
|
||||
├── inbox_lanes.py — 5-lane Inbox computation (SP14)
|
||||
├── inbox_state.py — claim ↔ inbox state mapping helpers
|
||||
├── inbox_state_277ca.py — SP10 277CA inbox state integration
|
||||
├── logging_config.py — SP18 structured JSON + PII scrubber
|
||||
├── migrations/ — 12 SQL files (0001_initial through 0012_backups)
|
||||
├── migrations/ — 23 SQL files (0001_initial through 0023_visits)
|
||||
├── npi.py — SP20 NPI Luhn + Tax ID validation
|
||||
├── parsers/ — X12 parser subpackage
|
||||
│ ├── parse_837.py — 837P ingest
|
||||
@@ -187,23 +234,44 @@ backend/src/cyclone/
|
||||
├── payers.py — Payer ORM row accessor (SP9)
|
||||
├── providers.py — Payer / Clearhouse / Provider ORM row DTOs (SP9)
|
||||
├── pubsub.py — NDJSON live-tail EventBus
|
||||
├── rebill/ — (SP41 — in-window rebill pipeline)
|
||||
├── rebill/ — SP41: in-window rebill pipeline (13 modules)
|
||||
│ ├── __init__.py — package surface
|
||||
│ ├── carc_filter.py — CARC-aware filter (EXCLUDED_CARCS / REVIEW_CARCS)
|
||||
│ ├── parse_835_svc.py — SVC-level 835 reparse w/ member_id
|
||||
│ ├── reconcile.py — visit-to-835 join (member, procedure, DOS)
|
||||
│ ├── carc_filter.py — CARC-aware filter
|
||||
│ ├── timely_filing.py — 120-day DOS age gate
|
||||
│ ├── pipeline_a.py — denied/partial → frequency-7
|
||||
│ ├── pipeline_b.py — NOT_IN_835 → fresh 837Ps by (member, week)
|
||||
│ ├── summary.py — summary CSV generator
|
||||
│ ├── pipeline_a.py — denied/partial → frequency-7 replacement 837P
|
||||
│ ├── pipeline_b.py — NOT_IN_835 → fresh 837Ps batched by (member_id, ISO-week)
|
||||
│ ├── pull_999_acks.py — 999-ack dump + classify NOT_IN_835
|
||||
│ ├── reconcile.py — visit-to-835 join (member, procedure, DOS) with 5% tolerance
|
||||
│ ├── run.py — orchestrator (run_rebill)
|
||||
│ ├── pull_999_acks.py — 999-ack dump + classify
|
||||
│ └── __init__.py — package surface
|
||||
├── reconcile.py — 835 → 837 auto-reconciliation engine
|
||||
├── scheduler.py — SP16 MFT polling scheduler
|
||||
│ ├── spot_check.py — single-claim well-formed 837P from VisitRow
|
||||
│ ├── spot_check_pipeline.py — top-N visits → well-formed 837P files
|
||||
│ ├── spot_check_validate.py — submit to Edifabric /v2/x12/validate
|
||||
│ ├── summary.py — summary CSV generator
|
||||
│ ├── timely_filing.py — 120-day DOS age gate
|
||||
│ └── visits_store.py — load AxisCare visits CSV into visits table
|
||||
├── reconcile.py — 835 → 837 auto-reconciliation engine (SP31 pcn-exact strategy)
|
||||
├── reissue/ — SP24: offline 837P reissue workflow
|
||||
│ ├── __init__.py
|
||||
│ └── core.py — parse_inputs / emit_outputs / zip_outputs / ig_correctness_check
|
||||
├── scheduler.py — SP16 MFT polling scheduler (SP27 per-op timeout)
|
||||
├── scoring.py — 4-field lane scoring (40/25/20/15, hard-coded — backlog item)
|
||||
├── secrets.py — Keychain access wrapper (SP9)
|
||||
├── secrets.py — Keychain access wrapper (SP9, SP26 _FILE tier)
|
||||
├── security.py — security headers, HealthSnapshot, deep health probe (SP19)
|
||||
└── store.py — CycloneStore facade (2,423 LOC; SP21 in-flight split)
|
||||
├── seed_cli.py — `cyclone seed [--count N] [--reset] [--status]` dev seeder
|
||||
├── store/ — SP21: split CycloneStore facade (16 modules)
|
||||
│ ├── __init__.py — CycloneStore class + module-level `store` singleton
|
||||
│ ├── acks.py / batches.py / backfill.py / backups.py
|
||||
│ ├── claim_acks.py / claim_detail.py / exceptions.py
|
||||
│ ├── inbox.py / kpis.py / orm_builders.py / providers.py
|
||||
│ ├── records.py / resubmissions.py / submission_dedup.py
|
||||
│ ├── ui.py — ORM → UI JSON serializers
|
||||
│ └── write.py — write-path helpers + event publishing
|
||||
└── submission/ — SP37: canonical outbound path + SP25/SP26 helpers
|
||||
├── __init__.py — submit_file, SubmitOutcome, SubmitResult
|
||||
├── core.py — parse → DB-write → SFTP-upload per file
|
||||
├── result.py — SubmitOutcome enum + SubmitResult dataclass
|
||||
├── recover.py — SP25: offline parse + DB-write for stranded files
|
||||
└── bulk_ingest.py — SP26: offline walk + ingest for 999/TA1/277CA/837P/835
|
||||
```
|
||||
|
||||
### 4.2 Module responsibility map (one line each)
|
||||
@@ -253,7 +321,7 @@ The store is the single read/write surface for the database. Every endpoint that
|
||||
- `store.list_unmatched(kind="both")`
|
||||
- `store.export_*()` — CSV/JSON dumpers
|
||||
|
||||
All persistence flows through SQLAlchemy sessions via `db.SessionLocal()()`. The engine is single-connection-per-request via FastAPI dependency injection. SP21 splits `store.py` (2,423 LOC) into a `cyclone/store/` subpackage with one module per domain (batches, inbox, claim_detail, acks, backups, providers, etc.) and a thin facade that delegates every method to its domain module. After SP21, the public API of `cyclone.store` is unchanged; the only thing that changes is the file layout.
|
||||
All persistence flows through SQLAlchemy sessions via `db.SessionLocal()()`. The engine is single-connection-per-request via FastAPI dependency injection. After SP21, `store.py` is split into a `cyclone/store/` subpackage with one module per domain (acks, batches, backfill, backups, claim_acks, claim_detail, exceptions, inbox, kpis, orm_builders, providers, records, resubmissions, submission_dedup, ui, write) plus a thin facade that delegates every method to its domain module. The public API of `cyclone.store` is unchanged.
|
||||
|
||||
### 4.4 The parser pipeline
|
||||
|
||||
@@ -338,21 +406,49 @@ There is no Redux, no Context for global state, no MobX. The combination of reac
|
||||
|
||||
### 5.4 Live tail (NDJSON pubsub)
|
||||
|
||||
The Activity feed and the TailStatusPill share a single connection: `GET /api/tail?since=<last_event_id>`. The server returns `Content-Type: application/x-ndjson` and streams one JSON object per line. Each line is one of:
|
||||
The Claims, Remittances, Activity, Acks, and TA1-Acks pages each open their own NDJSON connection to the matching `/api/<resource>/stream` endpoint. The server returns `Content-Type: application/x-ndjson` and streams one JSON object per line. Each line is one of:
|
||||
|
||||
- `{kind: "heartbeat", ts: "..."}` — every 5s when no events
|
||||
- `{kind: "event", id: <int>, event_type: "...", payload: {...}}` — when something happens
|
||||
- `{kind: "error", message: "..."}` — server-initiated close (rare)
|
||||
- `{type: "item", data: {...}}` — one row from the snapshot (initial batch) or a live event
|
||||
- `{type: "snapshot_end", data: {count: N}}` — the snapshot has finished
|
||||
- `{type: "heartbeat", data: {ts: "..."}}` — every `CYCLONE_TAIL_HEARTBEAT_S` seconds (default 15s) when idle
|
||||
- `{type: "item_dropped", data: {id: ...}}` — rare: the subscriber queue overflowed
|
||||
- `{type: "error", data: {message: "..."}}` — server-initiated close (rare)
|
||||
|
||||
The client uses `fetch` + `ReadableStream` to consume the body and dispatches each line to the relevant `useQuery` cache via `queryClient.setQueryData`. The connection auto-reconnects on `error` with exponential backoff. See [live-tail spec](superpowers/specs/2026-06-20-cyclone-live-tail-design.md) for the wire format and reconnection policy.
|
||||
The client uses `fetch` + `ReadableStream` to consume the body; each `item` event dispatches into `useTailStore` (zustand). The connection auto-reconnects on `error` with backoff `1s → 2s → 4s → 8s → 16s → 30s` capped. The client flips to `stalled` after 30s of total silence (`STALL_TIMEOUT_MS = 30_000` in `src/hooks/useTailStream.ts`). See [live-tail spec](superpowers/specs/2026-06-20-cyclone-live-tail-design.md) for the wire format and reconnection policy. SP25 added `ack_received` and `ta1_ack_received` event kinds plus `/api/acks/stream` and `/api/ta1-acks/stream` endpoints.
|
||||
|
||||
---
|
||||
|
||||
## 6. Data model
|
||||
|
||||
### 6.1 Migrations 0001–0014
|
||||
### 6.1 Migrations 0001–0023
|
||||
|
||||
The schema evolves in ordered SQL files under `backend/src/cyclone/migrations/`. Each file starts with `-- version: N` and is applied in order by `db_migrate.py`. There is no migration framework — `db_migrate.py` is a 30-line file that checks the `schema_version` row and applies everything newer. The first 12 ship on `main`; 13 + 14 are the auth migrations that landed in `main` on 2026-06-23.
|
||||
The schema evolves in ordered SQL files under `backend/src/cyclone/migrations/`. Each file starts with `-- version: N` and is applied in order by `db_migrate.py`. There is no migration framework — `db_migrate.py` is a 30-line file that checks the `schema_version` row and applies everything newer. All 23 ship on `main`:
|
||||
|
||||
| # | File | SP | One-line summary |
|
||||
|---|---|---|---|
|
||||
| 0001 | `0001_initial.sql` | — | 6 tables (`batches`, `claims`, `remittances`, `cas_adjustments`, `matches`, `activity_events`) + 11 indexes |
|
||||
| 0002 | `0002_acks.sql` | — | `acks` table + index on `source_batch_id` |
|
||||
| 0003 | `0003_drop_claims_remits_unique_constraints.sql` | — | drops UNIQUE constraints that blocked re-ingest of duplicates |
|
||||
| 0004 | `0004_rejections_and_state_history.sql` | — | adds `rejection_reason`, `rejected_at`, `resubmit_count`, `state_changed_at` to `claims` |
|
||||
| 0005 | `0005_create_ta1_acks.sql` | — | `ta1_acks` table + indexes on `source_batch_id`, `ack_code` |
|
||||
| 0006 | `0006_line_reconciliation.sql` | — | `service_line_payments` + `line_reconciliation` tables for the 837 vs 835 side-by-side view |
|
||||
| 0007 | `0007_providers_payers_clearhouse.sql` | SP9 | `providers`, `payers`, `payer_configs`, `clearhouse` tables |
|
||||
| 0008 | `0008_payer_rejected_columns.sql` | SP10 | `payer_rejected_*` columns on `claims` + `two77ca_acks` table |
|
||||
| 0009 | `0009_audit_log.sql` | SP11 | tamper-evident `audit_log` table |
|
||||
| 0010 | `0010_payer_rejected_acknowledged.sql` | SP14 | `payer_rejected_acknowledged_at` + `payer_rejected_acknowledged_actor` + index |
|
||||
| 0011 | `0011_processed_inbound_files.sql` | SP16 | `processed_inbound_files` dedup table for SFTP polling |
|
||||
| 0012 | `0012_backups.sql` | SP17 | `db_backups` table for the encrypted backup subsystem |
|
||||
| 0013 | `0013_auth_users_and_sessions.sql` | SP24 | `users` and `sessions` tables |
|
||||
| 0014 | `0014_audit_log_user_id.sql` | SP24 | `user_id` column on `audit_log` + index |
|
||||
| 0015 | `0015_drop_claims_unique_constraint.sql` | SP22 | rebuilds `claims` to drop a UNIQUE constraint that blocked a specific re-ingest pattern |
|
||||
| 0016 | `0016_claims_matched_remittance_id_index.sql` | SP22 | index on `claims.matched_remittance_id` for inbox join speed |
|
||||
| 0017 | `0017_backfill_claim_patient_control_number.sql` | — | backfills `claim.patient_control_number` from raw JSON for legacy rows |
|
||||
| 0018 | `0018_claim_acks.sql` | SP28 | `claim_acks` join table (claim ↔ 999/277CA/TA1) + 3 indexes |
|
||||
| 0019 | `0019_add_rendering_and_service_provider_npis.sql` | SP32 | typed rendering-NPI / service-provider-NPI columns on `claims` + `remittances` |
|
||||
| 0020 | `0020_add_batch_txn_set_control_number.sql` | SP37 | `transaction_set_control_number` column on `batches` (ST02) |
|
||||
| 0021 | `0021_resubmissions.sql` | SP39 | `resubmissions` audit table + indexes |
|
||||
| 0022 | `0022_submission_dedup.sql` | SP37 | `submission_records` table for duplicate-submission detection |
|
||||
| 0023 | `0023_visits.sql` | SP41 | `visits` table (AxisCare visits CSV → DB) + 3 indexes |
|
||||
|
||||
| # | File | What it adds |
|
||||
|---|---|---|
|
||||
@@ -371,7 +467,7 @@ The schema evolves in ordered SQL files under `backend/src/cyclone/migrations/`.
|
||||
| 13 | `0013_auth_users_and_sessions.sql` | `users` + `sessions` tables, `users.password_hash` (bcrypt) — landed 2026-06-23 |
|
||||
| 14 | `0014_audit_log_user_id.sql` | `audit_log.user_id` FK to `users.id` (forward reference to 0013) — landed 2026-06-23 |
|
||||
|
||||
The SP22 parse-then-decide migrations (drop claims UNIQUE constraint + relax PK to `(batch_id, id)`) are renumbered to 0015 + 0016 on the `claims-unique-fix` worktree so the auth migrations stay at the front of the chain; they'll keep those numbers when SP22 lands.
|
||||
The SP22 parse-then-decide migrations landed as `0015` + `0016` (drop claims UNIQUE constraint + add `matched_remittance_id` index).
|
||||
|
||||
### 6.2 ERD (high level)
|
||||
|
||||
@@ -496,8 +592,7 @@ The idempotency table is the linchpin: a crash between "read file" and "INSERT p
|
||||
|
||||
When an 835 lands, the writer calls `reconcile.apply_payment(claim, remit, strategy)` for each `(claim, remit)` pair in the file. The strategy is one of:
|
||||
|
||||
- `auto:pcn_npi_charge` — patient_control_number + provider_npi + charge_amount match (the strongest)
|
||||
- `auto:pcn_charge` — patient_control_number + charge_amount (the most common in practice)
|
||||
- `pcn-exact` — at least 2 of (patient_control_number, charge_amount, provider_npi) match exactly (PCN case-insensitive with leading-zero normalization). This is the SP31 strategy and replaces the older `auto:pcn_npi_charge` / `auto:pcn_charge` audit labels. `_content_keys_match` is the predicate.
|
||||
- `manual` — the operator uses `/reconciliation` to pair by hand; the strategy is recorded for audit
|
||||
|
||||
Each auto-match creates a `matches` row + transitions the claim state + emits an `activity_events` row + emits an `audit_log` row. The match rate is visible in the Dashboard KPI tile.
|
||||
@@ -581,7 +676,7 @@ The full list is in [REQUIREMENTS.md §6.3 traceability matrix](REQUIREMENTS.md)
|
||||
|
||||
```
|
||||
{"kind":"event","id":1234,"event_type":"claim.submitted","payload":{"claim_id":"...","batch_id":"...","ts":"2026-06-23T..."}}
|
||||
{"kind":"event","id":1235,"event_type":"claim.matched","payload":{"claim_id":"...","remit_id":"...","strategy":"auto:pcn_npi_charge"}}
|
||||
{"type":"item","data":{"id":"CLM-...","remit_id":"RM-...","strategy":"pcn-exact","matched_at":"..."}}
|
||||
{"kind":"heartbeat","ts":"2026-06-23T..."}
|
||||
{"kind":"event","id":1236,"event_type":"claim.payer_rejected","payload":{"claim_id":"...","status_code":"A7","source_277ca_id":"..."}}
|
||||
```
|
||||
@@ -613,7 +708,7 @@ See §4.6. The chain is checked on every `/api/health` request and exposes `stat
|
||||
- **In transit (SFTP)** — TLS to the trading partner; no plaintext EDI over the wire.
|
||||
- **In the UI** — no PHI is shown on the Dashboard or Inbox (only counts + KPIs). The Claim drawer and Remit drawer show full PHI; these pages are not in the route map's preview and require an explicit navigation click.
|
||||
- **In logs** — scrubbed by key name (see §9.1).
|
||||
- **In analytics** — no analytics. No third-party tracking. No telemetry. The frontend makes no requests outside `127.0.0.1:8000`.
|
||||
- **In analytics** — no analytics. No third-party tracking. No telemetry. The frontend makes no requests outside the configured `VITE_API_BASE_URL` (default empty, dev proxy: `http://127.0.0.1:8000`).
|
||||
|
||||
### 9.4 Error model
|
||||
|
||||
@@ -700,9 +795,9 @@ npm run dev
|
||||
|
||||
The frontend reads its backend URL from `VITE_API_BASE_URL` (default empty). Create `.env.local` at the repo root with `VITE_API_BASE_URL=http://127.0.0.1:8000`. Without it, the UI falls back to its in-memory sample store via the `data` adapter (parses are disabled).
|
||||
|
||||
### 11.2 Optional Docker (SP23 — product fork)
|
||||
### 11.2 Docker (SP23 — shipped)
|
||||
|
||||
A Ubuntu + Docker + auth + RBAC + LAN-bind spec exists at [`docs/superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md`](superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md). This is a **product fork** awaiting user decision (per [REQUIREMENTS.md §6.2](REQUIREMENTS.md)). The v1 single-host single-operator posture assumes local-only; SP23 changes the threat model to "remote operator on LAN."
|
||||
The Ubuntu + Docker + auth + RBAC + LAN-bind product fork shipped as SP23 (`merge: SP23 Ubuntu Docker Deployment into main`, `07a7ecb`, 2026-06-23). The compose stack (`docker-compose.yml` + `docker-compose.override.yml`) plus `backend/Dockerfile` and `Dockerfile.frontend` are the canonical deploy shape. First admin is bootstrapped from `CYCLONE_ADMIN_USERNAME` + `CYCLONE_ADMIN_PASSWORD` env vars; compose refuses to start without both set. See [`docs/superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md`](superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md) for the design rationale.
|
||||
|
||||
### 11.3 Single-host "production"
|
||||
|
||||
@@ -714,7 +809,7 @@ For a single operator on a single machine, the dev deployment IS the production
|
||||
|
||||
The following are explicitly NOT built in v1, by design:
|
||||
|
||||
- **Multi-user / multi-host** — login is required (single-user model today; SP23 adds RBAC). LAN-bind, Docker, reverse proxy still out of scope. (SP23 covers the LAN-bind / Docker / RBAC product fork.)
|
||||
- **Multi-user / multi-host** — login is required (single-user model today; RBAC is in place per SP23 for admin / user / viewer). LAN-bind + Docker + reverse proxy + remote multi-user is the v2 model (SP23 covers LAN-bind / Docker / RBAC).
|
||||
- **Other trading partners** — only Colorado Medicaid via Gainwell is configured. The `PayerConfig` mechanism (SP9) supports adding more; the spec/plan/CLI work for that is on the backlog.
|
||||
- **837I / 837D / 834 / 820 / 275 / 278 / 276-277** — only 837P and 835 are first-class. Other transaction types are listed in [REQUIREMENTS.md §6.2](REQUIREMENTS.md) as backlog items.
|
||||
- **NPPES real-time lookup** — only local NPI checksum validation (SP20). The operator who wants real registry lookup has to wire it in later.
|
||||
@@ -758,11 +853,30 @@ All in `docs/superpowers/specs/`:
|
||||
- SP20 NPI validation: `2026-06-21-cyclone-npi-validation-design.md`
|
||||
- SP21 store split: `2026-06-21-cyclone-store-split-design.md`; universal drilldown: `2026-06-21-cyclone-universal-drilldown-design.md`
|
||||
- SP22 parse-decide: `2026-06-21-cyclone-parse-decide-workflow-design.md`; pipeline agent: `2026-06-21-cyclone-pipeline-agent-design.md`
|
||||
- SP23 (fork) Ubuntu + Docker: `2026-06-22-cyclone-ubuntu-docker-deployment-design.md`
|
||||
- SP23 Ubuntu + Docker + auth + RBAC + LAN-bind (shipped): `2026-06-22-cyclone-ubuntu-docker-deployment-design.md`
|
||||
- SP24 reissue-claims: `2026-07-08-cyclone-reissue-claims-design.md`
|
||||
- SP25 SFTP polling enablement: `2026-06-24-cyclone-sftp-polling-enablement-design.md`
|
||||
- SP25 ack live-tail: `2026-07-02-cyclone-ack-live-tail-design.md`
|
||||
- SP25 orphan data recovery: `2026-07-07-cyclone-orphan-data-recovery-design.md`
|
||||
- SP26 SFTP password file: `2026-06-24-cyclone-sftp-password-file-companion-design.md`
|
||||
- SP27 remittances architecture refactor: `2026-06-29-cyclone-remittances-architecture-refactor-design.md`
|
||||
- SP28 ack-claim auto-link: `2026-07-02-cyclone-ack-claim-auto-link-design.md`
|
||||
- SP29 Inbox 999-rejected drill: `2026-07-02-cyclone-999-rejected-drill-design.md`
|
||||
- SP30 Dashboard Recent batches widget: `2026-07-02-cyclone-dashboard-recent-batches-design.md`
|
||||
- SP31 835 strict content match: `2026-07-02-cyclone-835-strict-content-match-design.md`
|
||||
- SP32 rendering + service-provider NPI extraction: `2026-07-02-cyclone-rendering-npi-extraction-design.md`
|
||||
- SP33 CO TXIX payer fix: `2026-07-02-cyclone-co-txix-payer-fix-design.md`
|
||||
- SP35 parse input guards: `2026-07-06-cyclone-parse-input-guards-design.md`
|
||||
- SP36 api routers split: `2026-07-06-cyclone-api-routers-split-design.md`
|
||||
- SP37 submit-batch canonical flow: `2026-07-07-cyclone-submit-batch-canonical-flow-design.md`
|
||||
- SP38 orphan-ack housekeeping: `2026-07-07-cyclone-orphan-ack-housekeeping-design.md`
|
||||
- SP39 2010BB NM109 fix: `2026-07-07-cyclone-2010bb-nm109-fix-design.md`
|
||||
- SP40 Edifabric validation gate: `2026-07-07-cyclone-edifabric-validation-gate-design.md`
|
||||
- SP41 in-window rebill pipeline: `2026-07-07-cyclone-inwindow-rebill-pipeline-design.md`
|
||||
|
||||
### 13.3 Plans
|
||||
|
||||
All in `docs/superpowers/plans/`. 27 files — one per spec (plus the round-2 backfill for SP9–SP20 in the `2026-06-23-cyclone-sp*` naming).
|
||||
All in `docs/superpowers/plans/`. 37 files — one per shipped spec. SP34 (`999-acceptance-restore`) was de-scoped; SP31's `pcn-exact` content-match predicate supersedes the underlying race.
|
||||
|
||||
### 13.4 Reference docs
|
||||
|
||||
|
||||
+76
-51
@@ -25,7 +25,7 @@
|
||||
|
||||
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.
|
||||
**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, dated 2026-07-08) applies every factual correction through SP41, including the post-SP23 doc alignment (LAN-bind + auth posture) and the SP21 store split + SP36 api split + SP41 rebill additions. Round 3 (pending) will re-verify reviewer agreement on the corrected doc.
|
||||
|
||||
---
|
||||
|
||||
@@ -33,18 +33,18 @@ Every requirement (§3, §4) has an ID; every ID is traceable to a sub-project (
|
||||
|
||||
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).
|
||||
- **LAN-only by design:** binds `0.0.0.0:8000`, requires login (auth boundary is HTTP; bcrypt + HttpOnly session cookie; first admin bootstrapped from `CYCLONE_ADMIN_USERNAME` + `CYCLONE_ADMIN_PASSWORD` env vars), reachability controlled by the host firewall / compose port publishing, single operator, one trading partner (Colorado Medicaid, currently). The dev/test escape hatch is `CYCLONE_AUTH_DISABLED=1`, which logs a WARNING at boot.
|
||||
- **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."
|
||||
- **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. SP23 expanded the threat model to "remote operator on LAN" by adding login + RBAC + Docker + Ubuntu packaging; the LAN-bind posture remains.
|
||||
|
||||
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.
|
||||
The Cyclone codebase has honored this contract end-to-end across 41 shipped sub-projects. Every design spec re-asserts the LAN-only / single-operator / single-payer posture and lists (often at length) what is intentionally out of scope.
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope boundaries
|
||||
|
||||
### 2.1 In scope (today, after SP1–SP22)
|
||||
### 2.1 In scope (today, after SP1–SP41)
|
||||
|
||||
| Capability | SP |
|
||||
|---|---|
|
||||
@@ -69,9 +69,16 @@ The Cyclone codebase has honored this contract end-to-end across 22 shipped sub-
|
||||
| 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 |
|
||||
| CycloneStore split (16-module subpackage; public API preserved via `cyclone.store` facade) | SP21 |
|
||||
| Parse-then-decide upload dedup (idempotency) | SP22 |
|
||||
| Pipeline automation agent (sibling project at `cyclone-pipeline/`) | SP22 |
|
||||
| HTTP-layer auth (bcrypt + HttpOnly session cookie) + RBAC roles + `CYCLONE_AUTH_DISABLED` escape hatch | SP23 |
|
||||
| Ubuntu + Docker + nginx + tini packaging | SP23 |
|
||||
| Audit-log `user_id` (every audit event records the acting user) | SP-auth |
|
||||
| `submission_dedup` table + claim-id dedup at SFTP pre-flight | SP22 + SP41 |
|
||||
| Resubmissions table + bulk ingest helpers | SP21 (resubmissions) |
|
||||
| In-window rebill pipeline (visit↔835 reconciliation + Pipeline A/B + summary CSV) | SP41 |
|
||||
| Live-tail NDJSON streams for `/api/acks/stream` and `/api/ta1-acks/stream` | SP-acks-tail |
|
||||
|
||||
### 2.2 Out of scope (today, by design)
|
||||
|
||||
@@ -140,8 +147,8 @@ Each requirement is `FR-NN`, traceable to one or more sub-projects. Verification
|
||||
| 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-35 | Split `CycloneStore` into per-concern modules under `cyclone/store/` (16 modules, ~5,400 LOC total) without changing the public store API; the `cyclone.store` import surface still resolves to the same names. | SP21 (**Met**; merged on `main`; module list in §5.1) |
|
||||
| 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 unique constraint via migration 0015 (a defensive drop of an inline UNIQUE that was a no-op against the current schema) to support re-parses into different batches. | SP22 (**Met**; migration 0015 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 |
|
||||
|
||||
@@ -164,7 +171,7 @@ The `FR-RB-*` IDs are SP41-specific functional requirements for the in-window re
|
||||
|
||||
| ID | NFR | Source / SP |
|
||||
|---|---|---|
|
||||
| NFR-1 | **Always bind 0.0.0.0 + auth.** Backend binds `0.0.0.0:8000` (overridable via `CYCLONE_HOST` / `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). Reachability is controlled by the host firewall / compose port publishing, not the bind address. | SP1 + auth (2026-06-23 merge) + SP24 (doc reconciliation) |
|
||||
| NFR-1 | **Always bind 0.0.0.0 + auth.** Backend binds `0.0.0.0:8000` (overridable via `CYCLONE_HOST` / `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`); every API router declares `Depends(matrix_gate)` (`cyclone/auth/deps.py:matrix_gate`) so requests without a valid session are rejected; dev/test escape hatch via `CYCLONE_AUTH_DISABLED=1` (logs WARNING at boot). Reachability is controlled by the host firewall / compose port publishing, not the bind address. | SP1 + SP23 (auth + RBAC) |
|
||||
| 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 |
|
||||
@@ -178,7 +185,7 @@ The `FR-RB-*` IDs are SP41-specific functional requirements for the in-window re
|
||||
| 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-15 | **Test density.** Backend tests: **1,624 collected** (`pytest --collect-only`, 2026-07-08); frontend test files: **85**; 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 |
|
||||
@@ -193,14 +200,16 @@ The `FR-RB-*` IDs are SP41-specific functional requirements for the in-window re
|
||||
|
||||
| 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` | HTTP routes, content negotiation, CORS allowlist, lifespan | 378 LOC; routes are mounted from `api_routers/` (22 sub-routers — see below) |
|
||||
| `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.api_routers` | Per-resource sub-routers mounted under `/api/...` | 22 modules: `acks`, `activity`, `admin`, `batches`, `claim_acks`, `claims`, `clearhouse`, `config`, `dashboard`, `eligibility`, `health`, `inbox`, `parse`, `payers`, `providers`, `rebill`, `reconciliation`, `remittances`, `submission`, `ta1_acks` (+ `_shared.py`). Every router declares `Depends(matrix_gate)` for auth. |
|
||||
| `cyclone.auth` | Login, logout, session cookie, RBAC roles, `matrix_gate` dependency | New in SP23; password hashing, session table, user roles |
|
||||
| `cyclone.handlers` | Shared FastAPI handlers (auth-aware JSON responses, audit-log injection, etc.) | Cross-cutting request glue |
|
||||
| `cyclone.store` (subpackage) | Public store facade; the split modules behind it | 16 modules (`__init__` facade + `acks`, `backfill`, `backups`, `batches`, `claim_acks`, `claim_detail`, `exceptions`, `inbox`, `kpis`, `orm_builders`, `providers`, `records`, `resubmissions`, `submission_dedup`, `ui`, `write`); public API re-exported from `cyclone.store` so callers see no breaking change |
|
||||
| `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.audit_log` | Hash-chained `audit_log` (SP11, SHA-256 chain) | Distinct from `cyclone.activity_events` written by other modules; post-SP23 records `user_id` of the actor |
|
||||
| `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) | |
|
||||
@@ -210,16 +219,19 @@ The `FR-RB-*` IDs are SP41-specific functional requirements for the in-window re
|
||||
| `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.backup` | AES-256-GCM encrypted backups (orchestration + CLI) | |
|
||||
| `cyclone.backup_service` | Backup lifecycle (create / verify / restore) | |
|
||||
| `cyclone.backup_scheduler` | 24h backup autostart loop | |
|
||||
| `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.clearhouse` (`__init__.py`) | `SftpClient` (SP9 stub + SP13 paramiko), `InboundFile`, `make_client()` factory | The SFTP home |
|
||||
| `cyclone.edi` (`filenames.py`) | HCPF X12 File Naming Standards helpers | Used by SP13/SP16 outbound filename construction |
|
||||
| `cyclone.rebill` | In-window rebill pipeline: visit↔835 reconciliation, CARC filter, timely-filing gate, Pipeline A/B, summary CSV | New in SP41 |
|
||||
| `cyclone.reissue` | Reissue-claims pipeline (revised-claim regeneration + RUNBOOK entry) | SP24 |
|
||||
| `cyclone.submission` | Canonical `submit_file` helper shared by `cyclone submit-batch` CLI and `POST /api/submit-batch`; parse → DB-write → SFTP-upload → audit per file | SP37 |
|
||||
| `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 |
|
||||
@@ -230,7 +242,7 @@ The `FR-RB-*` IDs are SP41-specific functional requirements for the in-window re
|
||||
|
||||
| Directory | Owns |
|
||||
|---|---|
|
||||
| `src/pages/` | Acks, ActivityLog, BatchDiff, Batches, Claims, Dashboard, Inbox, Providers, Reconciliation, Remittances, Upload (11 pages) |
|
||||
| `src/pages/` | Acks, ActivityLog, BatchDiff, Batches, Claims, Dashboard, Inbox, Login, Providers, Reconciliation, Remittances, Upload (12 pages — Login added in SP23) |
|
||||
| `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 |
|
||||
@@ -239,7 +251,7 @@ The `FR-RB-*` IDs are SP41-specific functional requirements for the in-window re
|
||||
|
||||
### 5.2 Data model
|
||||
|
||||
**12 SQL migrations** in `backend/src/cyclone/migrations/` (verified on disk 2026-06-23):
|
||||
**23 SQL migrations** in `backend/src/cyclone/migrations/` (verified on disk 2026-07-08):
|
||||
|
||||
| # | File | Tables / columns added |
|
||||
|---|---|---|
|
||||
@@ -255,13 +267,17 @@ The `FR-RB-*` IDs are SP41-specific functional requirements for the in-window re
|
||||
| 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) |
|
||||
| 0013 | `0013_auth_users_and_sessions.sql` | `users`, `sessions`, RBAC role columns (SP23 auth) |
|
||||
| 0014 | `0014_audit_log_user_id.sql` | `audit_log.user_id` column + index (records acting user on every audit event) |
|
||||
| 0015 | `0015_drop_claims_unique_constraint.sql` | Defensive drop of the inline `UNIQUE(batch_id, patient_control_number)` on `claims` (SP22; table-recreate dance — no-op against current schema) |
|
||||
| 0016 | `0016_claims_matched_remittance_id_index.sql` | Index on `claims.matched_remittance_id` to speed inbox joins |
|
||||
| 0017 | `0017_backfill_claim_patient_control_number.sql` | Backfill `patient_control_number` from X12 CLM01 for legacy rows |
|
||||
| 0018 | `0018_claim_acks.sql` | `claim_acks` table (claim-level ACK metadata) |
|
||||
| 0019 | `0019_add_rendering_and_service_provider_npis.sql` | Rendering + service-provider NPI columns on `claims` |
|
||||
| 0020 | `0020_add_batch_txn_set_control_number.sql` | `batches.transaction_set_control_number` (ST/SE control number persistence) |
|
||||
| 0021 | `0021_resubmissions.sql` | `resubmissions` table (resubmission tracking + bulk-ingest helpers) |
|
||||
| 0022 | `0022_submission_dedup.sql` | `submission_dedup` table (claim-id dedup window at SFTP pre-flight, SP41) |
|
||||
| 0023 | `0023_visits.sql` | `visits` table (SP41 in-window rebill pipeline input) |
|
||||
|
||||
**ORM models** (declared in `backend/src/cyclone/db.py` — 18 classes total):
|
||||
|
||||
@@ -417,18 +433,20 @@ Each row's "Spec" / "Plan" point to the design spec and implementation plan on d
|
||||
| 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`) |
|
||||
| SP21 | CycloneStore split (16-module subpackage) + Universal drilldown | [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) | Met (split landed on `main`; public API re-exported from `cyclone.store` facade; drilldown used by every drawer) |
|
||||
| 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) |
|
||||
| SP23 | Ubuntu + Docker + nginx + tini + auth + RBAC (LAN-bind product fork) | [spec](superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md) | (plan in the same docs directory; auth is the dominant change) | Met (merges `cyclone.auth.*`, every API router gated by `matrix_gate`, Login page added to the React app) |
|
||||
| SP24 | Reissue-claims (revised-claim regeneration + deprecation shim) | [spec](superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md) (docs alignment + reissue) | [plan](superpowers/plans/2026-06-23-cyclone-auth-posture-alignment.md) | Met (merge commit `c9588f7`; `cyclone.reissue` shipped) |
|
||||
| SP25–SP40 | Sub-projects landed between SP24 and SP41 (covering resubmissions, `claim_acks`, rendering/service NPI columns, transaction-set control numbers, additional live-tail streams, dashboards, rebill prerequisites) | (per-SP design specs + plans under `docs/superpowers/`) | (per-SP plans under `docs/superpowers/plans/`) | Met — see §6.1 acceptance table below |
|
||||
| SP41 | In-window rebill pipeline (`cyclone.rebill.*`) | [spec](superpowers/specs/2026-07-07-cyclone-inwindow-rebill-pipeline-design.md) | [plan](superpowers/plans/2026-07-07-cyclone-inwindow-rebill-pipeline.md) | Met (merge commit `309e6c0`; visit↔835 reconciliation, Pipeline A/B, summary CSV, claim-id dedup at SFTP pre-flight) |
|
||||
|
||||
### 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 |
|
||||
| Migration manifest (checksums for the 23 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 |
|
||||
@@ -482,8 +500,8 @@ This matrix is the single index. Each row points to a requirement and where it's
|
||||
| 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-35 | SP21 | store-split | store-split | `tests/store/test_*.py` (per-module coverage of the split subpackage); `test_store.py` continues to exercise the public `cyclone.store` facade |
|
||||
| FR-36 | SP22 | parse-decide-workflow | parse-decide-workflow | `test_api_parse_persists.py`, `test_prodfiles_smoke.py`, migration 0015 (defensive drop of inline UNIQUE on `claims(batch_id, patient_control_number)`) |
|
||||
| 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/` |
|
||||
|
||||
@@ -588,12 +606,12 @@ These are the existing per-SP acceptance checklists as captured in their design
|
||||
```bash
|
||||
# Backend
|
||||
cd backend
|
||||
.venv/bin/pytest # 964 tests collected (2026-06-23)
|
||||
.venv/bin/pytest # 1,624 tests collected (2026-07-08)
|
||||
.venv/bin/python -m cyclone serve
|
||||
|
||||
# Frontend
|
||||
npm run typecheck
|
||||
npm test # 73 test files
|
||||
npm test # 85 test files
|
||||
npm run build
|
||||
|
||||
# Smoke (planned in SP23)
|
||||
@@ -628,7 +646,7 @@ Recorded explicitly so a new engineer can challenge any of them.
|
||||
| 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. |
|
||||
| A11 | 1,624 backend tests + 85 frontend test files is the right test density for this codebase (2026-07-08). 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). |
|
||||
|
||||
---
|
||||
@@ -639,9 +657,9 @@ Pulled forward from [`docs/reviews/2026-06-20-cyclone-completeness-review.md`](r
|
||||
|
||||
| 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-1 | No app-layer auth (anyone on the host can read PHI). | Completeness review §3.1.4 + §3.2.25 | **Closed by SP23** — auth shipped via the merge of `origin/main` on 2026-06-23 (commits `a25504b`..`39ae988`, `cyclone.auth.*` + `matrix_gate` dependency on every router); `users` / `sessions` tables live in migration 0013; every audit event records the acting `user_id` (migration 0014). |
|
||||
| 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-3 | No Dockerfile / docker-compose.yml. | Completeness review §3.2.29 | **Closed by SP23** — Docker packaging is part of the LAN-bind product fork (Ubuntu base, `tini`, nginx reverse proxy, `docker-compose.yml`). |
|
||||
| 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. |
|
||||
@@ -649,9 +667,9 @@ Pulled forward from [`docs/reviews/2026-06-20-cyclone-completeness-review.md`](r
|
||||
| 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-11 | `cyclone/store.py` is a 2,172-line god-module. | Completeness review §3.1.17 | **Closed by SP21** — `cyclone.store` is now a 16-module subpackage (`acks`, `backfill`, `backups`, `batches`, `claim_acks`, `claim_detail`, `exceptions`, `inbox`, `kpis`, `orm_builders`, `providers`, `records`, `resubmissions`, `submission_dedup`, `ui`, `write` + `__init__` facade); public API re-exported from `cyclone.store` so callers see no breaking change. |
|
||||
| R-12 | `cyclone/api.py` is a 3,145-line god-module (larger than `store.py`). | Completeness review §3.1.19 | **Closed by SP36** — `api.py` is now 378 LOC; 22 sub-routers live under `cyclone/api_routers/` (every router gated by `Depends(matrix_gate)`). |
|
||||
| R-13 | Migrations in flat dir without manifest. | Completeness review §3.1.18 | **Open** — no checksum list; 23 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. |
|
||||
@@ -662,10 +680,11 @@ Pulled forward from [`docs/reviews/2026-06-20-cyclone-completeness-review.md`](r
|
||||
| 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-24 | SP23 product fork (auth + Docker + RBAC + LAN-bind). | §6.1 | **Closed by SP23** — auth, RBAC, Docker, nginx, tini, LAN-bind all shipped on `main`. |
|
||||
| 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. |
|
||||
| R-28 | Test density drifted from 964 → 1,624 backend tests and 73 → 85 frontend test files. | §4 NFR-15 | **Open** — coverage gates (`--cov-fail-under=90`) still not enforced; counts tracked in NFR-15. |
|
||||
|
||||
---
|
||||
|
||||
@@ -694,7 +713,10 @@ All under [`docs/superpowers/specs/`](superpowers/specs/):
|
||||
- [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**
|
||||
- [2026-06-22-cyclone-ubuntu-docker-deployment-design.md](superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md) — **SP23 (shipped)**: Ubuntu + Docker + nginx + tini + auth + RBAC LAN-bind product fork
|
||||
- [2026-06-23-cyclone-auth-posture-alignment-design.md](superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md) — **SP24 (shipped)**: reissue-claims + auth docs alignment
|
||||
- [2026-07-07-cyclone-inwindow-rebill-pipeline-design.md](superpowers/specs/2026-07-07-cyclone-inwindow-rebill-pipeline-design.md) — **SP41 (shipped)**: in-window rebill pipeline (`cyclone.rebill.*`)
|
||||
- [2026-07-08-cyclone-doc-pass-design.md](superpowers/specs/2026-07-08-cyclone-doc-pass-design.md) — **SP42 (in flight)**: this doc-pass (post-SP23–SP41 reconciliation)
|
||||
|
||||
### 12.2 Implementation plans
|
||||
|
||||
@@ -713,8 +735,11 @@ All under [`docs/superpowers/plans/`](superpowers/plans/):
|
||||
- [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-store-split.md](superpowers/plans/2026-06-21-cyclone-store-split.md) — SP21 (split, shipped)
|
||||
- [2026-06-21-cyclone-universal-drilldown.md](superpowers/plans/2026-06-21-cyclone-universal-drilldown.md) — SP21 (drilldown)
|
||||
- [2026-06-23-cyclone-auth-posture-alignment.md](superpowers/plans/2026-06-23-cyclone-auth-posture-alignment.md) — SP24 (reissue-claims + auth docs alignment)
|
||||
- [2026-07-07-cyclone-inwindow-rebill-pipeline.md](superpowers/plans/2026-07-07-cyclone-inwindow-rebill-pipeline.md) — SP41 (in-window rebill pipeline)
|
||||
- [2026-07-08-cyclone-doc-pass.md](superpowers/plans/2026-07-08-cyclone-doc-pass.md) — SP42 (this doc-pass)
|
||||
|
||||
### 12.3 Reviews and reference
|
||||
|
||||
@@ -737,13 +762,13 @@ All under [`docs/superpowers/plans/`](superpowers/plans/):
|
||||
|
||||
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?
|
||||
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 / auth / handlers / submission / rebill / reissue / store/) drawn correctly? Can you, from §5.1 alone, draw the import graph?
|
||||
2. **Data model** — Are all 23 migrations listed in §5.2 with their purpose (no worktree-only entries)? Are all 18 ORM models in §5.2? Is the `db_backups` (not `backups`) correction applied? 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` — SP23 shipped) 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 / FR-35 / FR-36 traceability rows corrected to point at existing test files and the correct endpoint names?
|
||||
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. Note FR-RB-1..FR-RB-6 (SP41) and the auth-related rows point at the new subpackages.
|
||||
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 (Linux dispatch to Secret Service / kwallet, Windows via Credential Manager)?
|
||||
7. **Risks** — Are §11 risks current (R-1/R-3/R-11/R-12/R-24 closed by SP23/SP21/SP36; R-28 added for test-density drift)? Compare against `docs/reviews/2026-06-20-cyclone-completeness-review.md`.
|
||||
|
||||
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`).
|
||||
|
||||
|
||||
+2
-2
@@ -294,7 +294,7 @@ from cyclone.parsers.parse_999 import parse as parse_999
|
||||
result = parse_999(open("path.999.x12").read())
|
||||
```
|
||||
|
||||
### After SP39 lands — corrected-claim push + tracking
|
||||
### SP39 — corrected-claim push + tracking
|
||||
|
||||
The corrected files now live under
|
||||
`ingest/corrected-v2/v2-batch-<batch-id>-<N>-claims/` instead of the
|
||||
@@ -370,7 +370,7 @@ curl -X PATCH http://127.0.0.1:8000/api/clearhouse \
|
||||
|
||||
To revert, set `stub: true` and `auth: {"method": "keychain", "secret_ref": "sftp.gainwell.password"}`.
|
||||
|
||||
### After SP40 lands — Edifabric /v2/x12/validate integration
|
||||
### SP40 — Edifabric /v2/x12/validate integration
|
||||
|
||||
SP40 wires Edifabric's reference X12 validator into the push flow
|
||||
as a fail-closed pre-upload gate, plus a CLI / HTTP endpoint for
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
# Doc-Pass Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use
|
||||
> superpowers:subagent-driven-development (recommended) or
|
||||
> superpowers:executing-plans to implement this plan task-by-task. Steps
|
||||
> use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Reconcile CLAUDE.md, README.md, docs/ARCHITECTURE.md, docs/REQUIREMENTS.md, docs/RUNBOOK.md, and the eight `.superpowers/skills/*/SKILL.md` files so the prose matches the current implementation after SPs 23–41.
|
||||
**Architecture:** Pure `.md` edits. No code paths change; no API endpoints change; no test assertions change. One commit per file, prefixed `docs(sp42): <file> …`. The merge commit (`merge: SP42 doc-pass into main`) is the audit trail.
|
||||
**Tech Stack:** Markdown + `git` + `grep` for verification.
|
||||
**Spec:** [`docs/superpowers/specs/2026-07-08-cyclone-doc-pass-design.md`](../specs/2026-07-08-cyclone-doc-pass-design.md)
|
||||
|
||||
---
|
||||
|
||||
## File structure
|
||||
|
||||
```
|
||||
docs/superpowers/
|
||||
specs/2026-07-08-cyclone-doc-pass-design.md ← new
|
||||
plans/2026-07-08-cyclone-doc-pass.md ← new
|
||||
|
||||
CLAUDE.md ← edit (header + subpackage + LOC claims)
|
||||
README.md ← edit (lead paragraph + project layout)
|
||||
docs/ARCHITECTURE.md ← edit (§1, §2, §4.1, §4.3, §5.4, §6.2, §6.3, §7.4, §8.1, §11.2, §13.2)
|
||||
docs/REQUIREMENTS.md ← edit (§1, §2.2, §5.1, §5.2, §6.1, §6.2, §11, §12)
|
||||
docs/RUNBOOK.md ← edit (SP23 Docker stack → Docker stack)
|
||||
|
||||
.superpowers/skills/cyclone-spec/SKILL.md ← edit (As of this writing header)
|
||||
.superpowers/skills/cyclone-tests/SKILL.md ← edit (counts + SP-N pointer)
|
||||
.superpowers/skills/cyclone-store/SKILL.md ← edit (SP21 framing + line refs + module list)
|
||||
.superpowers/skills/cyclone-api-router/SKILL.md ← edit (4 → 22 routers + line refs)
|
||||
.superpowers/skills/cyclone-edi/SKILL.md ← edit (SP-N pointer)
|
||||
.superpowers/skills/cyclone-frontend-page/SKILL.md ← edit (counts + drawer list)
|
||||
.superpowers/skills/cyclone-cli/SKILL.md ← edit (Seven → 15+ subcommands)
|
||||
.superpowers/skills/cyclone-tail/SKILL.md ← edit (3 → 4 streaming pages + line refs)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 0: branch + spec/plan
|
||||
|
||||
- [x] Branch `sp42-doc-pass` from `main` (no worktree — work in current checkout).
|
||||
- [x] First commit on the branch: `docs(spec): SP42 doc-pass design` (commits the spec).
|
||||
- [x] Second commit: `docs(plan): SP42 doc-pass implementation plan` (commits this plan).
|
||||
|
||||
---
|
||||
|
||||
## Task 1: refresh `CLAUDE.md`
|
||||
|
||||
- [ ] Update line ~7 to read "LAN-only by design — always binds to `0.0.0.0:8000` and requires login…".
|
||||
- [ ] Update line ~97 ("SP22 used, next free is SP24") to "SP numbers are used through **SP41** (in-window rebill pipeline); the next free increment is **SP42**. A `sp42-doc-pass` worktree is checked out locally but not yet merged into `main`."
|
||||
- [ ] Update line ~121 (api.py / store.py LOC + "only large file" claim): replace the "3,548 LOC / 2,423 LOC / in flight" framing with post-SP21/SP36 reality. "The largest files now live under `cyclone/api_routers/` (22 routers under SP36) and `cyclone/store/` (the SP21-split facade in 16 modules). `api.py` is a ~377-line shell that wires the FastAPI app and mounts the routers."
|
||||
- [ ] Update line ~122 subpackage list: expand `api_routers/` to 22 routers; add `auth/` (SP24, 10 modules), `handlers/` (SP27, 6 modules), `rebill/` (SP41, 13 modules), `reissue/` (SP24, 2 modules), `store/` (SP21, 16 modules). Note that `workflow/` is dead bytecode.
|
||||
- [ ] Update line ~128 migration count: "23 SQL migrations under `migrations/` (0001_initial through 0023_visits)".
|
||||
- [ ] Drop the three phantom `parse-999` / `parse-ta1` / `parse-277ca` entries from the CLI block (lines 174–176) — they contradict the body text and were already flagged in CLAUDE.md's "Things that are easy to get wrong" section.
|
||||
- [ ] Commit: `docs(sp42): CLAUDE.md — refresh SP numbers, store split, subpackage list`.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: refresh `README.md`
|
||||
|
||||
- [ ] Update lines 7–8: replace "Local-only on purpose: binds to `127.0.0.1`, no auth, no internet exposure." with "LAN-only by design — always binds to `0.0.0.0:8000` and requires login (bcrypt + HttpOnly session cookie; first admin bootstrapped from `CYCLONE_ADMIN_USERNAME` + `CYCLONE_ADMIN_PASSWORD`). Reachability is controlled by the host firewall / compose port publishing, not the bind address."
|
||||
- [ ] Update the project-layout block: replace `store.py` with `store/` (post-SP21 split); replace the `api.py` description with the thin-shell description; add `auth/`, `handlers/`, `rebill/`, `reissue/`, `store/`, `submission/`, `api_routers/` (22 routers).
|
||||
- [ ] Update the "Sub-projects 2 through 19 are shipped" claim to "Sub-projects 2 through 41 are shipped."
|
||||
- [ ] Update the CLI example block to drop `parse-999`, `parse-ta1`, `parse-277ca` (which don't exist as DB-writing CLIs — see CLAUDE.md "Inbound ingestion paths" note).
|
||||
- [ ] Commit: `docs(sp42): README.md — refresh auth/bind claim and project layout`.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: refresh `docs/ARCHITECTURE.md`
|
||||
|
||||
- [ ] Update §1 line ~14: replace `127.0.0.1` with `0.0.0.0` and add the reachability + auth boundary sentence.
|
||||
- [ ] Update §2 process diagram: replace `api.py 3,548 LOC` with `api.py ~377 LOC (post-SP36 thin shell)`; replace the 4-router enumeration with the full 22-router list.
|
||||
- [ ] Update §4.1 package layout: add `auth/`, `handlers/`, `rebill/`, `reissue/`, `store/`, expand `api_routers/` to 22 routers.
|
||||
- [ ] Update §4.2 module map: add the missing subpackage rows.
|
||||
- [ ] Update §4.3 SP21 framing: present-tense ("After SP21, the public API of `cyclone.store` is unchanged…"); add the post-split module list reference.
|
||||
- [ ] Update §5.4: replace `/api/tail?since=…` with the canonical three-stream endpoints and replace `api.py:1357-2006` with `api_routers/{claims,remittances,activity}.py`.
|
||||
- [ ] Update §6.2 ERD: add `claim_acks`, `resubmissions`, `visits` tables.
|
||||
- [ ] Update §6.3 claim-state diagram: add `partial` (per REQUIREMENTS FR-6).
|
||||
- [ ] Update §7.4: replace `auto:pcn_npi_charge` / `auto:pcn_charge` with `pcn-exact` (SP31).
|
||||
- [ ] Update §8.1 endpoint enumeration with the canonical list (~85 routes after SPs 23–41).
|
||||
- [ ] Update §11.2: replace "SP23 product fork awaiting user decision" with "SP23 (shipped 2026-06-23 via `merge: SP23 Ubuntu Docker Deployment into main`)".
|
||||
- [ ] Update §13.2: replace "22 specs / 27 plans" with "42 specs / 37 plans"; add the missing SP25–SP41 spec entries.
|
||||
- [ ] Update §6.1 migration table from 12 to 23.
|
||||
- [ ] Commit: `docs(sp42): ARCHITECTURE.md — refresh post-SP23–41 package map, endpoints, SP23 status`.
|
||||
|
||||
---
|
||||
|
||||
## Task 4: refresh `docs/REQUIREMENTS.md`
|
||||
|
||||
- [ ] Update line ~49: "Sub-projects 2 through 41 are **shipped**."
|
||||
- [ ] Update §2.2 / §5.4 / §5.1: drop "SP23 would add" speculative framing; rewrite as past tense where SP23 shipped something.
|
||||
- [ ] Update §5.1 LOC claims (api.py 3,145 → ~377; store.py 2,172 → split into `cyclone/store/`).
|
||||
- [ ] Update §5.1 module list: add `auth`, `handlers`, `rebill`, `reissue`, `submission` subpackage rows; expand `store` row to its 16 modules.
|
||||
- [ ] Update §5.2 migration table: 12 → 23 with the full enumeration.
|
||||
- [ ] Update §6.1 SP table: add SP23 (Ubuntu+Docker, shipped) through SP41 (rebill pipeline, shipped) as separate rows.
|
||||
- [ ] Update §6.2 backlog: move SP23 from backlog to §6.1; remove the "SP23 candidate" row.
|
||||
- [ ] Update §11 R-1, R-3, R-11, R-12 to Closed with concrete merge-SHA evidence; update R-13 migration count to 23.
|
||||
- [ ] Update §12.1 / §12.2 spec-and-plan lists to include SP25–SP41.
|
||||
- [ ] Commit: `docs(sp42): REQUIREMENTS.md — refresh SP table, close SP23 risks, add SP25–41`.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: refresh `docs/RUNBOOK.md`
|
||||
|
||||
- [ ] Update line ~120: replace "For the SP23 Docker stack, mount the MFT password as a file…" with "For the Docker stack, mount the MFT password as a file…" (SP23 is shipped; the wording should not read as conditional).
|
||||
- [ ] Audit any other "SP23 Docker stack" / "SP23 would" / "SP23 candidate" wording; rewrite as past tense where SP23 shipped the feature.
|
||||
- [ ] Commit: `docs(sp42): RUNBOOK.md — drop 'SP23 Docker stack' conditional framing`.
|
||||
|
||||
---
|
||||
|
||||
## Task 6: refresh `.superpowers/skills/cyclone-spec/SKILL.md`
|
||||
|
||||
- [ ] Update line ~16: "As of this writing: **42 specs** in `docs/superpowers/specs/`, **37 plans** in `docs/superpowers/plans/`, SP numbers used through **SP41** (in-window rebill pipeline). The next free increment is **SP42**."
|
||||
- [ ] Add a one-line note about the SP25 reuse anomaly (three SP25s: sftp-polling-enablement, ack-live-tail, orphan-data-recovery).
|
||||
- [ ] Commit: `docs(sp42): cyclone-spec skill — refresh SP-N framing`.
|
||||
|
||||
---
|
||||
|
||||
## Task 7: refresh `.superpowers/skills/cyclone-tests/SKILL.md`
|
||||
|
||||
- [ ] Update line ~7–8: refresh test counts (89 backend → 178; 13 fixtures → 22; 59 frontend → 130+); replace "next is SP24" with "next is SP42".
|
||||
- [ ] Commit: `docs(sp42): cyclone-tests skill — refresh counts and SP-N pointer`.
|
||||
|
||||
---
|
||||
|
||||
## Task 8: refresh `.superpowers/skills/cyclone-store/SKILL.md`
|
||||
|
||||
- [ ] Update line ~10: replace `backend/src/cyclone/store.py:882` with `backend/src/cyclone/store/__init__.py:148`.
|
||||
- [ ] Update line ~17: drop "single 2412-line module, SP21 in progress" framing; replace with the post-SP21 16-module enumeration + "Last merged SP: SP41; next free increment: SP42."
|
||||
- [ ] Update SP21 split module list (lines 60–70) to add the 5 missing modules: `backfill.py`, `claim_acks.py`, `kpis.py`, `resubmissions.py`, `submission_dedup.py`.
|
||||
- [ ] Update line ~86 (`store.py:898-1107`) → `store/write.py`.
|
||||
- [ ] Update line ~121 (`api.py:1380-1401`) → `api_routers/{claims,remittances,activity}.py`.
|
||||
- [ ] Update line ~136 (`api.py:1891,2002`) → `api_routers/{claims,remittances,activity}.py`.
|
||||
- [ ] Commit: `docs(sp42): cyclone-store skill — refresh SP21 framing, line refs, module list`.
|
||||
|
||||
---
|
||||
|
||||
## Task 9: refresh `.superpowers/skills/cyclone-api-router/SKILL.md`
|
||||
|
||||
- [ ] Update lines 14–18: replace "4 router modules" with the canonical 22-router list; replace "api.py ~3,548 LOC" with "api.py ~377 LOC (thin shell)".
|
||||
- [ ] Update lines 49–50: drop the "parse endpoints next refactor target" note (SP36 already split them).
|
||||
- [ ] Update lines 170–183 line refs (`api.py:246-256` etc.) → `api_routers/__init__.py`.
|
||||
- [ ] Commit: `docs(sp42): cyclone-api-router skill — refresh router count + line refs`.
|
||||
|
||||
---
|
||||
|
||||
## Task 10: refresh `.superpowers/skills/cyclone-edi/SKILL.md`
|
||||
|
||||
- [ ] Update line ~13: "The next increment is **SP42**" (was SP22). Add a one-line note about SP32 (rendering NPI), SP35 (parse input guards), SP39 (2010BB NM109), SP40 (Edifabric gate) having touched parser/validator territory.
|
||||
- [ ] Commit: `docs(sp42): cyclone-edi skill — refresh SP-N pointer`.
|
||||
|
||||
---
|
||||
|
||||
## Task 11: refresh `.superpowers/skills/cyclone-frontend-page/SKILL.md`
|
||||
|
||||
- [ ] Update line ~11: refresh counts (11 → 12 pages, ~30 → ~53 hooks, 2 → 4 drawers); replace "next is SP22" with "next is SP42".
|
||||
- [ ] Update drawer list at lines 18–20: add `ProviderDrawer/`, `AckDrawer/`.
|
||||
- [ ] Commit: `docs(sp42): cyclone-frontend-page skill — refresh counts + drawer list`.
|
||||
|
||||
---
|
||||
|
||||
## Task 12: refresh `.superpowers/skills/cyclone-cli/SKILL.md`
|
||||
|
||||
- [ ] Update lines 8–9: replace "Seven subcommands" with the canonical 15+ subcommand list (`parse-837`, `parse-835`, `validate-npi`, `validate-tax-id`, `validate-837`, `submit-batch`, `reissue-claims`, `resubmit-rejected-claims`, `resubmissions`, `pull-inbound`, `recover-ingest`, `ack-orphans`, `users`, `seed`, `backfill-rendering-npi`, `backfill-999-rejections`, `rebill-from-835`, `pull-inbound`, plus the `backup` group).
|
||||
- [ ] Commit: `docs(sp42): cyclone-cli skill — replace Seven-subcommands claim with full list`.
|
||||
|
||||
---
|
||||
|
||||
## Task 13: refresh `.superpowers/skills/cyclone-tail/SKILL.md`
|
||||
|
||||
- [ ] Update lines 13–17: refresh "three streaming pages" claim — add `Acks` (SP25 ack-live-tail) for a total of 4.
|
||||
- [ ] Update line ~44: replace `api.py:1357-2006` with `api_routers/{claims,remittances,activity}.py` + `api_routers/{acks,ta1_acks}.py`.
|
||||
- [ ] Update line ~70: replace `api.py:1401,1894,2005` with the post-SP36 router files.
|
||||
- [ ] Update line ~119: replace `api.py:1357-1401` with `api_routers/claims.py`.
|
||||
- [ ] Commit: `docs(sp42): cyclone-tail skill — refresh streaming page count + line refs`.
|
||||
|
||||
---
|
||||
|
||||
## Task 14: autoreview (the merge gate)
|
||||
|
||||
- [ ] Run the 14 acceptance-criteria grep checks from §6 of the spec; expect zero matches for each.
|
||||
- [ ] `cd backend && .venv/bin/pytest -q` — full backend suite passes (no regressions expected; this is a docs-only increment).
|
||||
- [ ] `npm test` — full vitest suite passes.
|
||||
- [ ] `git log --oneline sp42-doc-pass` shows the expected commit sequence (1× `docs(spec):` + 1× `docs(plan):` + 13× `docs(sp42):`).
|
||||
- [ ] `git status --short` shows no unexpected untracked files (note: pre-existing untracked files from the operator's working dir — `july8billing/`, `july8billing_out/`, `xxxclaims.zip`, `verification-final.txt`, `scripts/serialize_july8_to_sp41.py`, `backend/src/cyclone/submission/bulk_ingest.py`, `backend/tests/fixtures/co_medicaid_837p.997`, `backend/tests/fixtures/subscriber_roster.csv`, `docs/superpowers/plans/2026-07-07-cyclone-2010bb-nm109-fix.md`, `docs/superpowers/plans/2026-07-07-cyclone-inwindow-rebill-pipeline.md`, `docs/superpowers/specs/2026-07-07-cyclone-inwindow-rebill-pipeline-design.md`, `.grok/` — should remain untracked).
|
||||
- [ ] No semantic rewrites of unrelated prose. Each diff should touch only the lines flagged in the SP42 audit findings.
|
||||
|
||||
---
|
||||
|
||||
## Task 15: merge to `main`
|
||||
|
||||
- [ ] `git checkout main && git merge --no-ff --no-squash --no-rebase sp42-doc-pass -m "merge: SP42 doc-pass into main"`
|
||||
- [ ] `git log --oneline main -1` shows the SP42 merge commit with the expected subject.
|
||||
- [ ] `git push origin main` only after the operator has reviewed and approved the merge (per cyclone-spec — the merge commit is the audit trail; the operator must approve).
|
||||
|
||||
---
|
||||
|
||||
## Verification commands (one-shot)
|
||||
|
||||
```bash
|
||||
# Acceptance criteria §1 — no stale "next free is SP24" / "next is SP25"
|
||||
grep -rE 'SP22.*SP24|next free increment is SP24|next increment is SP24|next free is SP24|next is SP25' \
|
||||
CLAUDE.md README.md docs/ARCHITECTURE.md docs/REQUIREMENTS.md docs/RUNBOOK.md .superpowers/skills/ 2>/dev/null
|
||||
|
||||
# §2 — SP23 "awaiting user decision" / "candidate" / "product fork"
|
||||
grep -rE 'awaiting user decision|SP23.*candidate|SP23.*product fork' docs/ARCHITECTURE.md docs/REQUIREMENTS.md 2>/dev/null
|
||||
|
||||
# §3 — stale LOC claims
|
||||
grep -rE 'api\.py.*3,548|3,548 LOC|store\.py.*2,423|2,423 LOC|2,172 LOC|3,145 LOC' \
|
||||
CLAUDE.md README.md docs/ARCHITECTURE.md docs/REQUIREMENTS.md .superpowers/skills/ 2>/dev/null
|
||||
|
||||
# §4 — 127.0.0.1 in bind-address context
|
||||
grep -nE '127\.0\.0\.1' CLAUDE.md README.md docs/ARCHITECTURE.md docs/REQUIREMENTS.md 2>/dev/null
|
||||
|
||||
# §5 — README "no auth, no internet exposure"
|
||||
grep -nE 'no auth, no internet exposure' README.md
|
||||
|
||||
# §6 — "4 router modules" / "four router modules"
|
||||
grep -rE '4 router modules|four router modules' .superpowers/skills/cyclone-api-router/SKILL.md docs/ARCHITECTURE.md docs/REQUIREMENTS.md 2>/dev/null
|
||||
|
||||
# §7 — "Seven subcommands" / "7 subcommands"
|
||||
grep -rE 'Seven subcommands|7 subcommands' .superpowers/skills/cyclone-cli/SKILL.md
|
||||
|
||||
# §8 — "single 2412-line module" / "SP21 in progress" / "SP21 in-flight"
|
||||
grep -rE 'single 2412-line module|SP21.*in.progress|SP21.*in-flight' .superpowers/skills/cyclone-store/SKILL.md
|
||||
|
||||
# §9 — "12 SQL migrations"
|
||||
grep -rE '12 SQL migrations' CLAUDE.md docs/ARCHITECTURE.md docs/REQUIREMENTS.md 2>/dev/null
|
||||
|
||||
# §10 / §11 — test suites
|
||||
cd backend && .venv/bin/pytest -q
|
||||
npm test
|
||||
```
|
||||
Reference in New Issue
Block a user