# 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/<context>/`** — entities, value objects, domain services, repository **ports**, errors. **Pure Python**: no FastAPI, SQLAlchemy, jwt, argon2, aioboto3, or `os.getenv`.
- **`application/<context>/`** — use cases orchestrating the domain via **ports** (Protocols); DTOs. No framework imports. One module per use case.
- **`infrastructure/<context>/`** — 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/<context>/`** — 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.
