feat(sp24): align CLAUDE.md + REQUIREMENTS.md + ARCHITECTURE.md with auth work + AUTH_DISABLED WARNING
Replaces every stale 'no auth' / 'no authentication' / 'no second party to authenticate' claim in the three top-level docs with the v1 posture: the auth boundary is HTTP (bcrypt + HttpOnly session cookie; first admin bootstrapped from CYCLONE_ADMIN_USERNAME + CYCLONE_ADMIN_PASSWORD env vars); the file-system posture (SQLCipher at rest, macOS Keychain) is unchanged; the threat model is still a stolen/imaged drive. Closes requirements §11 R-1 (was Open; now Closed by SP24 — auth shipped via the origin/main merge on 2026-06-23, SP24 reconciled the docs). Also: - backend/src/cyclone/__main__.py — emit WARNING when AUTH_DISABLED is True at boot so a misconfigured production deploy fails loudly - §6.1 migrations table in ARCHITECTURE.md — list 0013 (auth_users_and_ sessions) and 0014 (audit_log_user_id) with the renumbering note - §4.2 module map — add the cyclone.auth.* package - Three skills addenda: cyclone-tests (AUTH_DISABLED conftest bypass is mandatory context), cyclone-api-router (every router needs Depends(matrix_gate)), cyclone-spec (spec template threat-model is now auth-aware) This also tracks CLAUDE.md in git for the first time (was previously untracked; the SP24 doc updates are in scope for the increment). Spec: docs/superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md Plan: docs/superpowers/plans/2026-06-23-cyclone-auth-posture-alignment.md
This commit is contained in:
@@ -19,6 +19,10 @@ content negotiation + `tail_events`), and ~30 routes still inlined
|
|||||||
in `backend/src/cyclone/api.py`. The next refactor target is the
|
in `backend/src/cyclone/api.py`. The next refactor target is the
|
||||||
parse endpoints.
|
parse endpoints.
|
||||||
|
|
||||||
|
## Auth gate (SP24)
|
||||||
|
|
||||||
|
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)])`.
|
||||||
|
|
||||||
## When to use
|
## When to use
|
||||||
|
|
||||||
- **Adding an endpoint.** You're adding a new GET / POST handler —
|
- **Adding an endpoint.** You're adding a new GET / POST handler —
|
||||||
|
|||||||
@@ -10,9 +10,15 @@ a spec, a plan, an implementation branch, and a single atomic merge commit
|
|||||||
into `main`. This skill encodes the conventions so every increment follows
|
into `main`. This skill encodes the conventions so every increment follows
|
||||||
the same shape and the commit history stays auditable.
|
the same shape and the commit history stays auditable.
|
||||||
|
|
||||||
As of this writing: **16 specs** in `docs/superpowers/specs/`, **12 plans**
|
As of this writing: **17 specs** in `docs/superpowers/specs/`, **13 plans**
|
||||||
in `docs/superpowers/plans/`, and SP numbers used through **SP21** (the
|
in `docs/superpowers/plans/`, and SP numbers used through **SP22**. **SP23**
|
||||||
universal-drilldown design in progress). The next increment is **SP22**.
|
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.
|
||||||
|
|
||||||
|
## Auth-aware spec template (SP24)
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
## When to use
|
## When to use
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ description: "Cyclone pytest + vitest fixture patterns, prodfiles layout, backen
|
|||||||
|
|
||||||
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** (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.
|
||||||
|
|
||||||
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 **SP22**.
|
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).
|
||||||
|
|
||||||
|
## Auth flag (SP24)
|
||||||
|
|
||||||
|
The autouse `conftest.py` fixture at `backend/tests/conftest.py` flips `cyclone.auth.deps.AUTH_DISABLED = True` for the entire test session, so every test runs without a login round-trip. **Any new test that reads `cyclone.auth.deps.AUTH_DISABLED` directly will see `True`** — that's the test-suite reality, not a production reality. If you need a test that exercises the real gate, import `from cyclone.auth.deps import matrix_gate` and call it directly with a `Request` whose `state` carries a real session, or flip the flag back inside the test and reset it on teardown. The startup WARNING (`backend/src/cyclone/__main__.py`) is silent in tests by default because the conftest sets the flag before `bootstrap.run()` is called via `import`.
|
||||||
|
|
||||||
## When to use
|
## When to use
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
Cyclone is a self-hosted X12 EDI claims-management suite for a single billing office (Colorado Medicaid currently). It parses 837P professional claims and 835 ERA remittances (X12 005010X222A1 / 005010X221A1) and also handles 999, TA1, 270, 271, and 277CA. Local-only by design: binds to `127.0.0.1`, requires login (auth boundary is HTTP; bcrypt + HttpOnly session cookie; first admin bootstrapped from `CYCLONE_ADMIN_USERNAME` + `CYCLONE_ADMIN_PASSWORD` env vars; see SP24 spec for the full posture), no internet exposure.
|
||||||
|
|
||||||
|
Stack: one Python process (FastAPI + uvicorn, port 8000) + one Node process in dev (Vite, port 5173). The authoritative state is a single SQLite file at `~/.local/share/cyclone/cyclone.db` (or SQLCipher at the same path when the macOS Keychain entry + `sqlcipher3` are both present).
|
||||||
|
|
||||||
|
For the day-1 architecture read, see `docs/ARCHITECTURE.md` (process topology, module map, store facade, parser pipeline, pubsub). For the what-it-does read, see `docs/REQUIREMENTS.md` (FRs + NFRs + DoD).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backend (Python 3.11+)
|
||||||
|
cd backend
|
||||||
|
python -m venv .venv
|
||||||
|
.venv/bin/pip install -e '.[dev]'
|
||||||
|
|
||||||
|
# Frontend (Node 20+)
|
||||||
|
cd ..
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional backend extras: `pip install -e '.[sqlcipher]'` (encryption at rest, SP12) and `pip install -e '.[sftp]'` (real SFTP, SP13).
|
||||||
|
|
||||||
|
## Dev (two terminals)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 1 — backend
|
||||||
|
cd backend
|
||||||
|
.venv/bin/python -m cyclone serve # default 127.0.0.1:8000
|
||||||
|
# CYCLONE_PORT=... overrides port; CYCLONE_RELOAD=1 enables uvicorn --reload
|
||||||
|
# Or: .venv/bin/uvicorn cyclone.api:app --reload --port 8000
|
||||||
|
|
||||||
|
# Terminal 2 — frontend
|
||||||
|
npm run dev # Vite on http://localhost:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
Vite proxies `/api/*` to the backend at `http://127.0.0.1:${CYCLONE_PORT:-8000}` so relative-URL fetchers (the live-tail NDJSON streams in particular) resolve through the same origin. Override the backend port with `CYCLONE_PORT` in the frontend terminal too.
|
||||||
|
|
||||||
|
Create `.env.local` at the repo root with `VITE_API_BASE_URL=http://127.0.0.1:8000`. Without it, the UI runs against the in-memory zustand store and real EDI parsing is disabled.
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backend — full suite
|
||||||
|
cd backend && .venv/bin/pytest
|
||||||
|
|
||||||
|
# Backend — one file
|
||||||
|
cd backend && .venv/bin/pytest tests/test_api_999.py -v
|
||||||
|
|
||||||
|
# Backend — one test by node id
|
||||||
|
cd backend && .venv/bin/pytest tests/test_api_999.py::test_parse_999_endpoint_happy_path -v
|
||||||
|
|
||||||
|
# Frontend — full suite
|
||||||
|
npm test # alias for `vitest run`
|
||||||
|
|
||||||
|
# Frontend — one file
|
||||||
|
npx vitest run src/hooks/useFoo.test.ts
|
||||||
|
|
||||||
|
# Frontend — typecheck
|
||||||
|
npm run typecheck
|
||||||
|
|
||||||
|
# Frontend — build (tsc -b + vite build)
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Frontend — lint
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
**Conventions** (full detail in `.superpowers/skills/cyclone-tests/SKILL.md`):
|
||||||
|
|
||||||
|
- Backend tests live under `backend/tests/test_*.py`. Two flavors: `test_api_<topic>_<verb>.py` (FastAPI integration via `fastapi.testclient.TestClient`) and `test_<module>_<behavior>.py` (pure-unit). Autouse `conftest.py` points `CYCLONE_DB_URL` at `tmp_path/test.db`, calls `db._reset_for_tests()` + `db.init_db()`, and wires a fresh `EventBus` onto `app.state`.
|
||||||
|
- Prodfiles (real EDI samples under `docs/prodfiles/<source>/`) are never read directly from a test — copy to `backend/tests/fixtures/<descriptive-name>.txt` first and reference as a module-level `Path` constant. The `fixtures/` dir is flat (no per-test subdirs).
|
||||||
|
- Frontend tests are siblings: `useFoo.ts` → `useFoo.test.ts`, `ClaimDrawer.tsx` → `ClaimDrawer.test.tsx`. Setup is `// @vitest-environment happy-dom` plus `(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;`. Mock the API at the module boundary with `vi.mock("@/lib/api", ...)`; stub fetch with `vi.stubGlobal("fetch", vi.fn().mockResolvedValue(...))`. `vitest.config.ts` sets `VITE_API_BASE_URL=http://test.local` so the `api` module doesn't throw `notConfiguredError` before the mock fires.
|
||||||
|
- Time-sensitive tests: frontend uses `vi.useFakeTimers()` + `vi.setSystemTime(...)` + `vi.advanceTimersByTime(ms)`. Backend passes explicit `datetime(...)` values. Don't add `await new Promise((r) => setTimeout(r, N))` — it's the legacy flaky pattern.
|
||||||
|
|
||||||
|
## Project-scoped skills (`.superpowers/skills/`)
|
||||||
|
|
||||||
|
Cyclone ships 8 skills under `.superpowers/skills/`. They auto-load by description match — no slash command needed. **Read the relevant skill before touching the matching subsystem.**
|
||||||
|
|
||||||
|
| Skill | Owns |
|
||||||
|
|---|---|
|
||||||
|
| `cyclone-spec` | The SP-N spec → plan → implement → merge flow (branch shape, file paths, commit prefixes, PR title, merge shape). |
|
||||||
|
| `cyclone-tests` | pytest + vitest fixture patterns, prodfiles drop-in rule, determinism rules. |
|
||||||
|
| `cyclone-edi` | EDI parser/validator conventions (837P/835/999/270/271/277CA/TA1, R-codes, CAS mapping). |
|
||||||
|
| `cyclone-tail` | Live-tail streaming wire format and the `useTailStream` + `useMergedTail` + `TailStatusPill` hook triplet. |
|
||||||
|
| `cyclone-store` | `CycloneStore` facade, write-paths, pubsub event contract, SP21 split map. |
|
||||||
|
| `cyclone-api-router` | FastAPI router conventions (`api_routers/`, `api_helpers.py`), response/error-envelope shapes. |
|
||||||
|
| `cyclone-frontend-page` | React page conventions (TanStack Query `use<X>` hook, drawer, URL state, sibling test). |
|
||||||
|
| `cyclone-cli` | CLI subcommand conventions (`cli.py`, exit codes, smoke tests). |
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
- **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.
|
||||||
|
- **Plan path:** `docs/superpowers/plans/YYYY-MM-DD-cyclone-<topic>.md`, header per `superpowers:writing-plans` with `Goal / Architecture / Tech Stack / Spec` metadata + numbered `- [ ] Step N:` tasks.
|
||||||
|
- **Commit prefixes:** `feat(sp<N>): …`, `docs(spec): …`, `docs(plan): …`, `merge: SP<N> <topic> into main`.
|
||||||
|
- **PR title:** `SP<N> <Topic>` (matches the merge-commit subject).
|
||||||
|
- **Merge shape:** single atomic merge commit. **No squash** (collapses the audit trail) and **no rebase** (rewrites the SHAs the review was performed against). The SP-N merge commit *is* the record of the increment landing.
|
||||||
|
|
||||||
|
The matching skill to load alongside `cyclone-spec` depends on the subsystem the SP-N touches (see the "Related skills" section at the bottom of each skill file).
|
||||||
|
|
||||||
|
## Live-tail wire format
|
||||||
|
|
||||||
|
The Claims, Remittances, and Activity pages stay current without manual refresh. The backend publishes an internal event on every store write, the page opens a streaming HTTP connection to the matching `/api/<resource>/stream` endpoint, and new rows append to the table the moment they hit the database.
|
||||||
|
|
||||||
|
Endpoints (all accept the same query params as their non-streaming counterparts; `Content-Type: application/x-ndjson`):
|
||||||
|
|
||||||
|
| Method | Path | Subscribes to | Default sort |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 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) |
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
Status pill states (rendered by `<TailStatusPill>` in `src/components/TailStatusPill.tsx`): `live` (success), `connecting` (warning), `reconnecting` (warning), `stalled` (destructive, ↻ Reconnect button), `error` (destructive, ↻ Reconnect button), `closed` (destructive). Backoff on error: `1s → 2s → 4s → 8s → 16s → 30s` capped. `STALL_TIMEOUT_MS = 30_000` in `src/hooks/useTailStream.ts:53`. Heartbeat interval is `CYCLONE_TAIL_HEARTBEAT_S` env var, default 15s.
|
||||||
|
|
||||||
|
Frontend triplet for any live page: `use<X>(params)` (initial fetch) + `useTailStream(resource)` (opens the NDJSON stream, drives backoff/stall) + `useMergedTail(resource, baseItems, filterFn?)` (merges snapshot + tail, dedup'd by id). The subscription lives on the page, not inside the data hook — see `cyclone-frontend-page` for why.
|
||||||
|
|
||||||
|
## 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), `workflow/` (placeholder for future sub-project 6).
|
||||||
|
|
||||||
|
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 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`).
|
||||||
|
|
||||||
|
The pubsub is `cyclone.pubsub.EventBus` — an in-process async fan-out broker. Publishers call `publish(kind, payload)`; subscribers receive via an async iterator. If a subscriber's per-kind queue is full, the oldest event is dropped so a slow consumer can't stall the producer. Bus is single-event-loop only (matches FastAPI/uvicorn).
|
||||||
|
|
||||||
|
Config: `config/payers.yaml` is the on-disk source for providers / payers / clearhouse, schema-validated at boot against a Pydantic model. Reload with `POST /api/admin/reload-config`. Original in-code `PAYER_FACTORIES` dict in `cli.py` is kept as a fallback for ad-hoc testing.
|
||||||
|
|
||||||
|
Secrets live in the macOS Keychain (via `keyring` + `cyclone.secrets`): SQLCipher key (service `cyclone`, account `cyclone.db.key`), SFTP password, backup passphrase. No secrets on disk in plaintext.
|
||||||
|
|
||||||
|
## 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/`.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
UI primitives in `src/components/ui/` are Radix-backed (`button`, `dialog`, `table`, `select`, `pagination`, `empty-state`, `error-state`, `filter-chips`, `skeleton`, `input`, `label`, `card`, `badge`, `skip-link`, `claim-state-badge`). Don't import a new UI library without discussion.
|
||||||
|
|
||||||
|
Path alias `@/` → `src/`. Configured in `vite.config.ts`, `vitest.config.ts`, and `tsconfig.app.json`.
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
python -m cyclone.cli validate-tin 721587149
|
||||||
|
|
||||||
|
# Other
|
||||||
|
python -m cyclone serve # uvicorn
|
||||||
|
python -m cyclone backup list
|
||||||
|
python -m cyclone backup create --reason manual
|
||||||
|
```
|
||||||
|
|
||||||
|
Exit codes are documented per subcommand in `cyclone-cli` — `0` for success, `2` for file-level failure, `1` for unexpected exceptions.
|
||||||
|
|
||||||
|
## Things that are easy to get wrong
|
||||||
|
|
||||||
|
- **`VITE_API_BASE_URL` matters.** With it empty, every `api` method throws `notConfiguredError()` and the UI falls back to the in-memory zustand store — parses are disabled and the live-tail streams never open.
|
||||||
|
- **Prodfiles vs fixtures.** Tests must reference `backend/tests/fixtures/<name>.txt`, not `docs/prodfiles/<source>/<file>.txt`. The prodfiles dir is the source-of-truth archive; the fixtures dir is the stable test surface.
|
||||||
|
- **SP-N merge shape.** No squash, no rebase. The merge commit *is* the audit trail. Squash collapses the per-commit history and breaks the SP-N audit trail.
|
||||||
|
- **Don't put domain logic in JSX.** Conditional renderings, table sorting, and KPI math all belong in the `use<X>` hook or a pure helper under `src/lib/`.
|
||||||
|
- **Don't open a drawer via local `useState`.** Use `useDrawerUrlState()` so the URL is the single source of truth — deep-links and reload-restore depend on it.
|
||||||
|
- **Don't call `useTailStream` from inside a `use<X>` hook.** The subscription lives on the page so the lifecycle ties to whoever mounts the hook, not to whoever happens to call it.
|
||||||
|
- **The store facade.** The public API of `cyclone.store` is preserved through SP21's split — call through the facade, not directly into the underlying modules.
|
||||||
|
- **Encryption is optional, not required.** When the Keychain entry is missing **or** `sqlcipher3` is not installed, the DB falls back to plain SQLite. Don't fail boot on missing encryption.
|
||||||
|
- **Local-only by design.** The backend binds to `127.0.0.1`, requires login (bcrypt + HttpOnly session cookie; see SP24 spec), and the threat model is still a stolen/imaged drive — SQLCipher at rest and the macOS Keychain handle that. The auth boundary is the HTTP layer; the file-system posture is unchanged. Don't add internet exposure. Don't disable auth without an explicit `CYCLONE_AUTH_DISABLED=1` env var (the escape hatch logs a WARNING at boot).
|
||||||
|
</content>
|
||||||
|
</invoke>
|
||||||
@@ -24,6 +24,20 @@ def main() -> None:
|
|||||||
from cyclone.auth import bootstrap
|
from cyclone.auth import bootstrap
|
||||||
bootstrap.run()
|
bootstrap.run()
|
||||||
|
|
||||||
|
# SP24: if the AUTH_DISABLED escape hatch is on, scream at boot so a
|
||||||
|
# misconfigured production deploy fails loudly. The flag is flipped by
|
||||||
|
# ``CYCLONE_AUTH_DISABLED=1`` (see ``cyclone.auth.bootstrap``) and by the
|
||||||
|
# pytest conftest autouse fixture (see ``.superpowers/skills/cyclone-tests``).
|
||||||
|
from cyclone.auth import deps as _auth_deps
|
||||||
|
|
||||||
|
if _auth_deps.AUTH_DISABLED:
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.getLogger("cyclone").warning(
|
||||||
|
"AUTH_DISABLED is set (CYCLONE_AUTH_DISABLED=1) — all requests "
|
||||||
|
"treated as admin, dev only. Do NOT enable this in production."
|
||||||
|
)
|
||||||
|
|
||||||
if len(sys.argv) >= 2 and sys.argv[1] == "serve":
|
if len(sys.argv) >= 2 and sys.argv[1] == "serve":
|
||||||
port = os.environ.get("CYCLONE_PORT", "8000")
|
port = os.environ.get("CYCLONE_PORT", "8000")
|
||||||
reload = os.environ.get("CYCLONE_RELOAD", "0") == "1"
|
reload = os.environ.get("CYCLONE_RELOAD", "0") == "1"
|
||||||
|
|||||||
+10
-7
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
## 1. System overview
|
## 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 has no authentication because the threat model is a stolen or imaged drive, not a remote attacker.
|
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 processes three X12 transaction pairs end-to-end:
|
Cyclone processes three X12 transaction pairs end-to-end:
|
||||||
|
|
||||||
@@ -199,8 +199,9 @@ backend/src/cyclone/
|
|||||||
|
|
||||||
| Module | Responsibility |
|
| Module | Responsibility |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `__main__` | Dispatch `serve` vs CLI; honor `CYCLONE_PORT` / `CYCLONE_RELOAD` |
|
| `__main__` | Dispatch `serve` vs CLI; honor `CYCLONE_PORT` / `CYCLONE_RELOAD`; emit `AUTH_DISABLED` WARNING if `cyclone.auth.deps.AUTH_DISABLED` is True at boot |
|
||||||
| `api` | FastAPI app, lifespan, route includes, 999/270/271 endpoints, exception → HTTP |
|
| `api` | FastAPI app, lifespan, route includes, 999/270/271 endpoints, exception → HTTP |
|
||||||
|
| `auth.*` | HTTP-layer login (bcrypt + HttpOnly session cookie). Submodules: `deps` (the `matrix_gate` FastAPI dependency, `AUTH_DISABLED` flag), `routes` (`/api/auth/login`, `/api/auth/logout`, `/api/auth/me`), `users`, `sessions`, `permissions` (role matrix), `bootstrap` (env-var first-admin), `cli` (`cyclone admin create-user` / `rotate-password`) |
|
||||||
| `api_helpers` | Error → status code mapping, common response shapes |
|
| `api_helpers` | Error → status code mapping, common response shapes |
|
||||||
| `audit_log` | Append + verify a SHA-256 hash-chained log (SP11) |
|
| `audit_log` | Append + verify a SHA-256 hash-chained log (SP11) |
|
||||||
| `backup` | Pure crypto + file I/O for SP17 backup format |
|
| `backup` | Pure crypto + file I/O for SP17 backup format |
|
||||||
@@ -338,9 +339,9 @@ The client uses `fetch` + `ReadableStream` to consume the body and dispatches ea
|
|||||||
|
|
||||||
## 6. Data model
|
## 6. Data model
|
||||||
|
|
||||||
### 6.1 Migrations 0001–0012
|
### 6.1 Migrations 0001–0014
|
||||||
|
|
||||||
The schema evolves in 12 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 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.
|
||||||
|
|
||||||
| # | File | What it adds |
|
| # | File | What it adds |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -356,8 +357,10 @@ The schema evolves in 12 ordered SQL files under `backend/src/cyclone/migrations
|
|||||||
| 10 | `0010_payer_rejected_acknowledged.sql` | `claim.payer_rejected_acknowledged_*` columns (SP14 hand-off) |
|
| 10 | `0010_payer_rejected_acknowledged.sql` | `claim.payer_rejected_acknowledged_*` columns (SP14 hand-off) |
|
||||||
| 11 | `0011_processed_inbound_files.sql` | `processed_inbound_files` idempotency table (SP16) |
|
| 11 | `0011_processed_inbound_files.sql` | `processed_inbound_files` idempotency table (SP16) |
|
||||||
| 12 | `0012_backups.sql` | `db_backups` table (SP17) |
|
| 12 | `0012_backups.sql` | `db_backups` table (SP17) |
|
||||||
|
| 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 |
|
||||||
|
|
||||||
A 13th (`0013_claims_unique_constraint.sql`) and 14th (`0014_409_ux.sql`) are in-flight on the `claims-unique-fix` worktree but not yet on `main`.
|
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.
|
||||||
|
|
||||||
### 6.2 ERD (high level)
|
### 6.2 ERD (high level)
|
||||||
|
|
||||||
@@ -614,7 +617,7 @@ Every API error is a JSON object with `{"error": "<code>", "message": "<human>",
|
|||||||
| `ValidationError` (R-codes) | 422 | `validation_failed` (issues in `detail`) |
|
| `ValidationError` (R-codes) | 422 | `validation_failed` (issues in `detail`) |
|
||||||
| `FileNotFoundError` | 404 | `not_found` |
|
| `FileNotFoundError` | 404 | `not_found` |
|
||||||
| `ValueError` | 400 | `bad_request` |
|
| `ValueError` | 400 | `bad_request` |
|
||||||
| `PermissionError` | 403 | `forbidden` (reserved — no auth today) |
|
| `PermissionError` | 403 | `forbidden` (raised by `matrix_gate` when the role is below the endpoint's required role) |
|
||||||
| (anything else) | 500 | `internal` (with a request id for log correlation) |
|
| (anything else) | 500 | `internal` (with a request id for log correlation) |
|
||||||
|
|
||||||
The 409-vs-422-vs-400 split is intentional: 409 for state-machine violations (the operator's input is well-formed but conflicts with current state), 422 for parse / validation, 400 for bad input shape.
|
The 409-vs-422-vs-400 split is intentional: 409 for state-machine violations (the operator's input is well-formed but conflicts with current state), 422 for parse / validation, 400 for bad input shape.
|
||||||
@@ -700,7 +703,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:
|
The following are explicitly NOT built in v1, by design:
|
||||||
|
|
||||||
- **Multi-user / multi-host** — no auth, no LAN-bind, no Docker, no reverse proxy. The operator is one person, the host is one machine. (SP23 fork if this changes.)
|
- **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.)
|
||||||
- **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.
|
- **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.
|
- **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.
|
- **NPPES real-time lookup** — only local NPI checksum validation (SP20). The operator who wants real registry lookup has to wire it in later.
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ 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.
|
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`, no auth, no internet exposure, single operator, single machine, one trading partner (Colorado Medicaid, currently).
|
- **Local-only on purpose:** binds `127.0.0.1`, requires login (auth boundary is HTTP; bcrypt + HttpOnly session cookie), no internet exposure, single operator, single machine, one trading partner (Colorado Medicaid, currently).
|
||||||
- **Single-process:** the backend is one Python process; the frontend is one Vite-served React app; there is no message broker, no separate worker, no queue.
|
- **Single-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).
|
- **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. No second party to authenticate; no second host to harden against.
|
- **Threat model:** a process on the same host, plus a deliberate HTTP-layer login (no remote unauthenticated read or write). File-system threats (stolen/imaged drive) are still the primary concern; SQLCipher at rest + the macOS Keychain handle those. The SP23 product fork changes the threat model to "remote operator on LAN."
|
||||||
|
|
||||||
The Cyclone codebase has honored this contract end-to-end across 22 shipped sub-projects. Every design spec re-asserts the local-only / single-operator / single-payer / no-auth posture and lists (often at length) what is intentionally out of scope.
|
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.
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ Each requirement is `FR-NN`, traceable to one or more sub-projects. Verification
|
|||||||
|
|
||||||
| ID | NFR | Source / SP |
|
| ID | NFR | Source / SP |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| NFR-1 | **Local-only bind.** Backend binds `127.0.0.1:8000` (overridable via `CYCLONE_PORT`); CORS allowlist is exact (`http://localhost:5173`); no auth, no API key. | SP1 |
|
| NFR-1 | **Local-only bind + auth.** Backend binds `127.0.0.1:8000` (overridable via `CYCLONE_PORT`); CORS allowlist is exact (`http://localhost:5173`); login required (bcrypt + HttpOnly session cookie; first admin bootstrapped from env vars `CYCLONE_ADMIN_USERNAME` + `CYCLONE_ADMIN_PASSWORD`); dev/test escape hatch via `CYCLONE_AUTH_DISABLED=1` (logs WARNING at boot). | SP1 + auth (2026-06-23 merge) + SP24 (doc reconciliation) |
|
||||||
| NFR-2 | **Determinism.** Parser / serializer round-trip is guaranteed on 113 real prodfiles (`docs/prodfiles/claims/*.x12`); canonical fields, not byte-identity (called out in the SP8 spec). | SP8 |
|
| NFR-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-3 | **Audit completeness.** Every reconciliation anomaly, every state transition, every 999/277CA reject writes an `ActivityEvent` (in `activity_events` table, un-chained; powers the `/activity` feed and inbox state transitions). | SP2 + SP10 |
|
||||||
| NFR-4 | **Tamper-evidence.** A separate `audit_log` table (SP11) carries SHA-256 hash-chained rows for security-sensitive events (login attempts, rejections, key rotations, backup lifecycle, rejected requests). `GET /api/admin/audit-log/verify` detects any break. **The `activity_events` (NFR-3) and `audit_log` (NFR-4) tables are distinct** — different semantics, different audiences. New code that needs an audit row must decide which one to write to. | SP11 |
|
| NFR-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 |
|
||||||
@@ -406,6 +406,7 @@ Each row's "Spec" / "Plan" point to the design spec and implementation plan on d
|
|||||||
| 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 |
|
| 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 (in-flight) + Universal drilldown (shipped) | [split spec](superpowers/specs/2026-06-21-cyclone-store-split-design.md), [drilldown spec](superpowers/specs/2026-06-21-cyclone-universal-drilldown-design.md) | [store-split plan](superpowers/plans/2026-06-21-cyclone-store-split.md), [drilldown plan](superpowers/plans/2026-06-21-cyclone-universal-drilldown.md) | Drilldown **Met**; split **In-flight** (`refactor/store-split` branch, not yet on `main`) |
|
||||||
| SP22 | Parse-decide workflow + Pipeline agent | [parse-decide spec](superpowers/specs/2026-06-21-cyclone-parse-decide-workflow-design.md), [pipeline spec](superpowers/specs/2026-06-21-cyclone-pipeline-agent-design.md) | [parse-decide plan](superpowers/plans/2026-06-21-cyclone-parse-decide-workflow.md), [pipeline plan](superpowers/plans/2026-06-21-cyclone-pipeline-agent.md) | Met (parse-decide on `main`; pipeline agent lives in `cyclone-pipeline/`) |
|
| SP22 | Parse-decide workflow + Pipeline agent | [parse-decide spec](superpowers/specs/2026-06-21-cyclone-parse-decide-workflow-design.md), [pipeline spec](superpowers/specs/2026-06-21-cyclone-pipeline-agent-design.md) | [parse-decide plan](superpowers/plans/2026-06-21-cyclone-parse-decide-workflow.md), [pipeline plan](superpowers/plans/2026-06-21-cyclone-pipeline-agent.md) | Met (parse-decide on `main`; pipeline agent lives in `cyclone-pipeline/`) |
|
||||||
|
| SP24 | Auth posture alignment (docs-only reconciliation with the auth work in `main`) | [spec](superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md) | [plan](superpowers/plans/2026-06-23-cyclone-auth-posture-alignment.md) | Met (no code paths change; only docs + `__main__.py` WARNING + 3 skills addenda) |
|
||||||
|
|
||||||
### 6.2 Backlog (specs awaiting plans, or vice versa)
|
### 6.2 Backlog (specs awaiting plans, or vice versa)
|
||||||
|
|
||||||
@@ -625,7 +626,7 @@ Pulled forward from [`docs/reviews/2026-06-20-cyclone-completeness-review.md`](r
|
|||||||
|
|
||||||
| ID | Risk / gap | Source | Status |
|
| 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 | **Open** — addressed only if/when SP23 ships. |
|
| R-1 | No app-layer auth (anyone on the host can read PHI). | Completeness review §3.1.4 + §3.2.25 | **Closed by SP24** — auth shipped via the merge of `origin/main` on 2026-06-23 (commits `a25504b`..`39ae988`, `cyclone.auth.*` + `matrix_gate` dependency on every router); SP24 reconciled the docs to match. SP23 still covers the LAN-bind / Docker / RBAC product fork. |
|
||||||
| R-2 | SQLite plaintext if SQLCipher not enabled. | Completeness review §3.1.1 | **Partial** — SP12 + SP15 ship the encryption option, but it falls back to plain if the Keychain entry is missing. |
|
| R-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 | **Open** — SP23 spec covers it but is awaiting user decision. |
|
||||||
| R-4 | PayerConfig still partly in code (`co_medicaid()` factory at `cyclone/parsers/payer.py:57-58`). | Completeness review §3.1.9 | **Open** — SP9 externalized config but the factory remains as a fallback for tests and default-payer selection. |
|
| R-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. |
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Cyclone Auth Posture Alignment — Design
|
# Cyclone Auth Posture Alignment — Design
|
||||||
|
|
||||||
**Date:** 2026-06-23
|
**Date:** 2026-06-23
|
||||||
**Status:** Draft, awaiting user sign-off
|
**Status:** Approved (in implementation on `sp24-doc-posture-alignment`, 2026-06-23)
|
||||||
**Branch:** `main` (auth code already landed; this SP aligns docs + decisions)
|
**Branch:** `sp24-doc-posture-alignment` (off `css-reduction` — `docs/REQUIREMENTS.md` and `docs/ARCHITECTURE.md` were added on `css-reduction` and do not yet exist on `main`)
|
||||||
**Depends on:** The auth work that landed in main via the merge of `origin/main` on 2026-06-23 (commits `a25504b`..`39ae988`, 14 commits, the `feat(auth):`/`fix(auth):`/etc. series on `auth-rebased`).
|
**Depends on:** The auth work that landed in main via the merge of `origin/main` on 2026-06-23 (commits `a25504b`..`39ae988`, 14 commits, the `feat(auth):`/`fix(auth):`/etc. series on `auth-rebased`).
|
||||||
**Replaces:** The "no auth, no LAN-bind, single operator" claim in `CLAUDE.md`, `docs/REQUIREMENTS.md`, and `docs/ARCHITECTURE.md` — the codebase no longer matches the doc and the divergence needs to be resolved one way or the other.
|
**Replaces:** The "no auth, no LAN-bind, single operator" claim in `CLAUDE.md`, `docs/REQUIREMENTS.md`, and `docs/ARCHITECTURE.md` — the codebase no longer matches the doc and the divergence needs to be resolved one way or the other.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user