@@ -39,8 +39,9 @@ only needs to be on PATH of whatever runs the agent).
3939│ protocol.py JSONL line parsing │
4040│ runner.py ServerRunner: one resident harness │
4141│ process per conversation │
42- │ infra/ config (PAW_* env) · security (JWT, Fernet) │
43- │ models/ SQLite (SQLAlchemy): users, conversations, runs, │
42+ │ infra/ config (PAW_* env) · db (engine + migrations) · │
43+ │ security (JWT, Fernet) │
44+ │ models/ ORM entities: users, conversations, runs, │
4445│ files, usage ledger, secrets, sandboxes │
4546└───────────────┬──────────────────────────────────────────────────────┘
4647 │ stdin/stdout pipes — bidirectional JSONL
@@ -68,47 +69,6 @@ fanned out to in-memory subscribers and re-emitted as an SSE `data:`
6869frame, so the browser sees the same events the harness TUI renders (tool
6970progress, todos, errors, mid-run questions).
7071
71- ## Layout
72-
73- ```
74- app/
75- main.py FastAPI app factory + routers + static UI
76- models/ (model layer)
77- __init__.py ORM models: User, Conversation, ConversationFile, Run,
78- UsageEvent, Secret, Sandbox
79- db.py SQLite engine/session (SQLAlchemy ORM)
80- validation/ (request/response validation)
81- schemas.py Pydantic request/response models
82- routes/ (view layer, HTTP only — no business logic)
83- auth.py POST /auth/register /auth/login /auth/refresh /auth/me
84- conversations.py CRUD + POST /{id}/files (upload) + GET/DELETE
85- /{id}/artifacts (agent outputs) + POST /{id}/runs
86- (start) + POST /{id}/answer + GET /{id}/stream (SSE)
87- billing.py usage summary (token ledger)
88- secrets.py CRUD (write-only read: value never returned)
89- controllers/ (all business logic)
90- errors.py DomainError hierarchy; main.py renders it as
91- {"detail": ...} so controllers never mention HTTP
92- accounts.py register/login rules, token identity, admin check
93- conversations.py ownership, CRUD, workspace teardown
94- files.py upload sanitizing + size cap, uploads-vs-artifacts,
95- traversal-safe artifact paths
96- runs.py harness prompt augmentation, run access, cancel/answer
97- secrets.py upsert + encryption-at-rest rules
98- usage.py token ledger aggregation
99- protocol.py Parse harness event lines (seq/run_id/result/usage)
100- manager.py Controller: start_run, event pump, subscribe, cancel, answer
101- runner.py Runner protocol + ServerRunner (resident serve process,
102- per-conversation workspace cwd)
103- views/ (static UI)
104- index.html Chat UI: upload, task cards, SSE progress (tool status,
105- todos, ask/answer), artifact downloads
106- infra/ (cross-cutting infrastructure)
107- config.py Settings (pydantic-settings, env prefix PAW_)
108- security.py JWT auth (access/refresh) + password hashing
109- secrets_store.py Fernet-encrypted secret values
110- ```
111-
11272## Quick start
11373
11474``` bash
@@ -123,6 +83,26 @@ uvicorn app.main:app --reload # http://127.0.0.1:8000 (UI at /)
12383Auth: create a user via ` /auth/register ` , then log in; the UI does this
12484for you.
12585
86+ ### Database & migrations
87+
88+ The schema is owned by Alembic (` migrations/ ` ). Startup runs
89+ ` upgrade_to_head() ` automatically, so a fresh ` uvicorn ` run creates the
90+ schema and stamps ` alembic_version ` — no manual step for dev. Tests
91+ migrate each throwaway database the same way, so the whole suite runs
92+ against real migrations.
93+
94+ After changing a model, generate and review a migration:
95+
96+ ``` bash
97+ alembic revision --autogenerate -m " describe the change" # writes migrations/versions/*
98+ alembic upgrade head # apply it
99+ alembic check # models ⇆ migrations in sync
100+ ```
101+
102+ ` alembic check ` reporting "No new upgrade operations detected" means the
103+ migrations match the models. SQLite uses batch mode for ALTERs; the
104+ migration URL comes from ` PAW_DB_URL ` (see ` migrations/env.py ` ).
105+
126106## The serve protocol (harness side)
127107
128108One sandbox = one long-lived ` serve ` process.
@@ -212,3 +192,14 @@ session title), which says nothing about what the agent is doing.
212192- ** Billing** is a token ledger: the controller snapshots ` result.usage `
213193 (input/output/rounds) from the harness into ` usage_events ` , attributed
214194 to the user and conversation.
195+ - ** Observability** : every request gets an ` X-Request-ID ` (echoed if the
196+ client sent one), bound to a contextvar so every ` paw.* ` log line
197+ carries it; unhandled errors are logged with a traceback and returned
198+ as a 500 quoting the id. ` PAW_LOG_JSON=true ` switches to JSON lines.
199+ - ** Rate limiting** : a process-local sliding-window limiter throttles the
200+ run endpoint per user (each run spends tokens) and login/register per
201+ IP, returning 429 + ` Retry-After ` . Per-instance only — a multi-instance
202+ deploy would need a shared store.
203+ - ** Token revocation** : tokens carry a ` ver ` claim matched against the
204+ user's ` token_version ` ; logout and password change bump it, so every
205+ outstanding token (all sessions) is rejected on next use.
0 commit comments