Stack — Python Backend
Coffer's backend is Python 3.12+.
Backend — Python / FastAPI / SQLite
Languages & Versions
- Python 3.12+
- FastAPI for HTTP surface
- Pydantic v2 for models + validation
- SQLite via SQLAlchemy 2 (async) +
aiosqlite(the exclusive data-access path) cryptography(Fernet) for the envelope-encrypted credential storekeyringfor OS keychain (credential module only — master key opt-in + legacy migration)anyio+asynciofor async + subprocess management
Feature-specific libraries (e.g. an MCP SDK) are added by the spec that first needs them, not pre-installed here.
Architecture
Layered DDD (under backend/coffer/):
surfaces/ — entry points; one subdir per surface, defined per spec
application/ — commands, queries, ports (interfaces), shared services
domain/ — entities, value objects, domain services (PURE)
infrastructure/— adapters for SQLite, keyring, and outbound I/OImport direction is one-way: surfaces → application → domain; infrastructure adapts to ports defined in application. domain/ is pure.
The layering import rules, the credential-access rule, and the "extract cross-cutting modules only after the second feature needs them" rule are invariants owned by .specify/memory/constitution.md. The Python-specific way they land in this codebase:
domain/stays pure Python + Pydantic only — no FastAPI, SQLAlchemy, httpx, or other external SDKs.application/defines ports;infrastructure/adapts to them.keyringis imported only by the credential module; everywhere else passes credential refs.
Code Style
- Ruff for lint + format. Config in
backend/pyproject.toml. - mypy --strict for the whole package.
- File size: ≤ 400 lines per Python file.
- Type hints on every function signature.
- Pydantic v2
BaseModelfor any data crossing a boundary (HTTP, MCP, SQLite I/O).
HTTP Contracts (Wire Format)
The authoritative wire contract for any feature is specs/<NNN>-<short-name>/contracts/*.openapi.yaml (hand-written, PR-reviewed). Backend Pydantic BaseModels are HAND-WRITTEN to match the yaml. Every HTTP route declares response_model=<Foo>Response against a Pydantic BaseModel — never dict[str, Any].
CI gate (lands when first feature spec lands): make verify-contract rejects PRs where the runtime OpenAPI dump structurally differs from any spec yaml.
Local Dev
make install # one-time setup
make dev # backend on :8000
.venv/bin/uvicorn coffer.main:app --reload --port 8000 # backend only
make lint / make verify-unit / make format / make verifyE2E — TypeScript / Playwright
The end-to-end tier lives in e2e/. The primary TypeScript surface is the frontend (frontend/src, ~18k lines of TS/TSX); e2e/ is an additional TypeScript tier on top of it.
- TypeScript 5.x, ESM modules (
tsconfig.jsonwith@playwright/test+nodetypes). - Playwright (
@playwright/test) as the e2e runner. - The MCP suite (
e2e/mcp/specs/*.spec.ts, confige2e/playwright.config.ts) exercises the full chain: a real MCP client →coffer-mcp-shim(stdio) → daemon (/mcpHTTP) → upstream MCP server → SQLite. Tests spawn the shim and daemon as OS subprocesses and drive JSON-RPC across them. - Run with
cd e2e && npm test(playwright test), ormake verify-e2e.