# Backend — Clean Architecture + DDD (`be0`) Incremental re-layering of the FastAPI monolith (`be0/main.py`, ~3.7k LOC) into DDD bounded contexts. **Strangler-fig**: the monolith keeps running; each context is peeled out and cut over one endpoint at a time. No big-bang rewrite. ## Layers — the dependency rule points INWARD ``` api ─────────────► application ─────────────► domain ◄───────── infrastructure (FastAPI, Pydantic) (use cases, ports) (pure model) (adapters: SQLAlchemy, ▲ argon2, jwt, mail, S3…) shared_kernel ``` - **`domain//`** — entities, value objects, domain services, repository **ports**, errors. **Pure Python**: no FastAPI, SQLAlchemy, jwt, argon2, aioboto3, or `os.getenv`. - **`application//`** — use cases orchestrating the domain via **ports** (Protocols); DTOs. No framework imports. One module per use case. - **`infrastructure//`** — adapters that *implement* the ports (SQLAlchemy repositories, Argon2 hasher, JWT issuer, SMTP mailer, rate limiter, audit sink) + `persistence/` (engine/session) + existing `vector_db/` (Qdrant). - **`api//`** — FastAPI routers + Pydantic schemas + dependencies. The **only** layer that imports FastAPI and maps domain errors to HTTP. - **`composition/`** — wires use cases from concrete adapters (constructor injection; no DI framework). - **`shared_kernel/`** — `Entity`/`AggregateRoot`, `ValueObject`, and the `DomainError` hierarchy. ### DomainError → HTTP (mapped only in the api layer) `ValidationError` → 400 · `AuthenticationError` → 401 · `AuthorizationError` → 403 · `NotFoundError` → 404 · `ConflictError` → 409 · `RateLimited` → 429. Inner layers raise these, never `HTTPException`. ## Bounded contexts & extraction order (strangler-fig) **Identity (1st)** → Admin → AI → Evidence/Files → **Initiative** (resolve the dual-submission-model decision: `initiatives/drafts` vs `application_workflow/application_artifacts`) → **Review** (last — most globals-coupled). ## Status | Context | domain | application | infrastructure | api | live cut-over | |---|---|---|---|---|---| | **Identity** | ✅ tested | ✅ Login tested | ⏳ | ⏳ | ⏳ (needs DB) | | Admin · AI · Files · Initiative · Review | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ | Identity domain + application are extracted **verbatim** (behavior-preserving) from `src/auth_api.py` and covered by 32 unit tests (`tests/test_identity_domain.py`, `tests/test_authenticate_user.py`) that run with **no DB**. ## Per-endpoint cut-over procedure 1. Extract pure rules → `domain` + write unit tests. ✅ (pattern established) 2. Write the use case → `application` (ports + fakes-tested). ✅ (Login) 3. Implement adapters → `infrastructure` (wrap the existing battle-tested primitives — `auth_jwt`, `auth_mail`, `auth_rate_limit`, `PasswordHasher`; do **not** rewrite security code). 4. Add the FastAPI router → `api`, wired via `composition`. Mount under a parallel prefix first. 5. **With the stack up** (`docker compose up`), run the DB-backed auth tests against the new router and confirm byte-parity with the old handler. 6. Replace the old route in `auth_api.py` with the new router in `main.py`; re-run tests. Repeat until `auth_api.py` is empty → delete it. ## Why infrastructure/api are deferred this chunk The auth tests are DB-backed (`INITIATIVE_DATABASE_URL` + Postgres). With the stack down, the DB-touching adapters/router can't be *verified*, and an unverified swap of live auth is unsafe. So this chunk ships the **verified** pure layers (domain + application) + the dead-scaffold cleanup; steps 3–6 happen when the stack is up.