diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 80bab88..fd20afa 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -26,9 +26,11 @@ user layer or files a Changes-inbox event. HTTP request (`apps/server`): 1. `Bun.serve` routes `/api/*` and `/ingest/*` to `app.fetch`; everything else gets the web app (HTML import). 2. `apiHandler` matches method + path in the route table, applies the per-IP rate limit (login/register), authenticates - (`session` cookie `stt_session`, Bearer app token with a scope, or either), checks `X-Requested-With: sttracker` on - cookie-authenticated writes, reads the body (JSON ≤ 1 MB, multipart ≤ 50 MB) and validates it with zod. -3. The handler calls one core function with the caller's `userId` and actor `"user"`. Core enforces ownership + (`session` cookie `stt_session`, Bearer app token with a scope, or either), requires `X-Requested-With: sttracker` on + every non-Bearer write (login included), reads the body (JSON ≤ 1 MB with `Content-Type: application/json`, + multipart ≤ 50 MB) and validates it with zod. +3. The handler calls one core function with the caller's `userId` and actor (`token:` for Bearer, else `"user"`; + `field_sources` stores both as `"user"`). Core enforces ownership (`NotFoundError` → 404). Errors become `{ error: { code, message } }`; unknown errors are logged and return 500 `internal`. 4. `POST sources/:id/run|import` only enqueue a `sync` job (lockKey `sync:`, 409 when held); the worker runs it. diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 2fb29c6..869d0dd 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -4,6 +4,24 @@ Append-only. Never edit or delete an entry. Supersede it with a new one that lin One entry per decision a future maintainer would otherwise have to reverse-engineer. No index: `grep -n '^## ' docs/DECISIONS.md` lists every entry (date, title, line number). +## 2026-10-30: Token actor in history, connection deleted_at, CSRF on every browser write +**Status:** accepted. Supersedes the **Actor**, **CSRF** and **Deleted connections** bullets of +"HTTP API: auth modes, CSRF header, error mapping, actor" below. +**Context:** Task 16 fix round 1 (controller rulings). +**Decision:** +- **Actor vs. owner.** API writes pass actor `token:` for Bearer requests and `"user"` for sessions, so history + shows which script changed what (spec §6.9). `field_sources` stores `fieldOwner(actor)`: every actor that does not start + with `plugin:` is stored as `"user"`. A token edit is the user's edit, and syncs file a pending change instead of + overwriting it. List ownership, which is read from history, applies the same mapping. +- **CSRF.** `X-Requested-With: sttracker` is required on every non-GET request that has no Bearer token, including the + `auth: "none"` routes login and register (login CSRF). JSON-body routes also require `Content-Type: application/json` + (415 `unsupported_media_type` otherwise), so a cross-site simple form post cannot pass as JSON. +- **Deleted connections** carry `source_connections.deleted_at` (migration 0003). `deleteConnection` sets it and still pauses + the connection and wipes its credentials. Listing, lookups, updates, the scheduler and the sync handler ignore deleted rows. + A user-paused connection without credentials is a normal, visible, resumable connection. +- **Dev mode** (`Bun.serve({ development })`) is on only with `NODE_ENV=development` or `bun --hot` (detected through + `process.execArgv`; `Bun.argv` does not carry runtime flags). Everything else runs in production mode. + ## 2026-10-30: HTTP API: auth modes, CSRF header, error mapping, actor **Status:** accepted **Context:** Task 16 (`apps/server`). diff --git a/docs/TODO.md b/docs/TODO.md index 0f8ce4f..0ef72bc 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -11,10 +11,11 @@ Small, specific, independently actionable. Remove only when actually done. ## Next - [ ] API: response schemas in the OpenAPI document (requests only today). +- [ ] Deployment docs (Task 19): the server runs `Bun.serve` in production mode unless `NODE_ENV=development` or `bun --hot`, so Docker needs no `NODE_ENV`. Document `NODE_ENV=development` for local debugging. +- [ ] API: `restoreVersion`, `acceptSuggestion` and `resolveChange` take no actor, so history records `"user"` for them even when an app token calls them. Thread the actor through if script provenance matters there. - [ ] API: login/register rate limit is per process and keyed on the socket IP; behind a reverse proxy every client shares one key. Add a trusted-proxy setting for `X-Forwarded-For`. - [ ] API: file imports are base64 in the `jobs.payload` row (up to ~67 MB of text). Move uploads to `DATA_DIR/uploads/` and pass a path. - [ ] API: `PATCH list-items/:id` only moves items; editing an item's note needs a core function. -- [ ] Connections: a user-paused connection that never had credentials looks deleted (`paused` + no credentials) and disappears from `GET sources`. Add an explicit `deleted_at`. - [ ] Phase 2 and Phase 3 plans (spec §12). - [ ] Generate image thumbnails for attachments (Phase 1 sizes images with CSS).