docs(sp42): skills cyclone-api-router/frontend-page/edi — refresh router count, page count, SP number

This commit is contained in:
Nora
2026-07-08 23:20:46 -06:00
parent 542a55fdbe
commit 0156051ca7
3 changed files with 38 additions and 26 deletions
+35 -23
View File
@@ -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
+1 -1
View File
@@ -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