# Architecture Rules

## Layer boundaries

Four layers, strict downward dependency:

1. **HTTP** (routers, schemas) — Pydantic models, FastAPI deps
2. **Pipeline** (context, pipeline) — attrs domain models, orchestration
3. **Language** (optimizer, postprocess, validator) — libcst, tree-sitter
4. **Infrastructure** (llm, db, observability) — asyncpg, openai, anthropic

Never import upward. Routers never import from each other. Language modules never import from routers.

## Models

- **Pydantic** for request/response schemas only (files named `schemas.py`)
- **attrs** for everything else — frozen by default, define when mutation needed
- Never mix: a Pydantic model should not contain attrs classes or vice versa. Convert at the boundary.

## Dependencies

Use FastAPI `Depends()` for cross-cutting concerns (auth, rate limiting, usage tracking). Never use ASGI middleware for request-specific logic — middleware is for truly global concerns (CORS, healthcheck short-circuit).

## Async

Everything is async. No sync database calls, no sync LLM calls, no `run_in_executor` unless wrapping a library that has no async API (like libcst parsing).

## No ORM

Raw SQL via asyncpg. Queries live in `db/_queries.py`. Table schemas documented as attrs classes in `db/models.py` but never used as ORM models.
