sciagent code + Gitea Actions CI/CD
CI/CD / backend (push) Failing after 2m8s
CI/CD / frontend (push) Failing after 1m40s
CI/CD / deploy (push) Has been skipped

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Thinh Lam
2026-06-30 09:38:30 +07:00
commit 688fac73e9
1167 changed files with 158244 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
# 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 36 happen when the stack is up.