# SQLite schema `lib/db.ts` uses `node:sqlite` — no ORM, no driver, no native build. Four tables: - `users` — id, unique email, `salt:hash` scrypt password, created_at - `sessions` — hashed token, user id, expiry - `settings` — one row per user: analyst base URL, model, sealed key, extra JSON, Jev key, Jev model, email, onboarded - `analyses` — id, user id, title, raw XML, oul, verdict JSON, status, error, timestamps `patchAnalysis` only accepts keys from a `PATCHABLE` whitelist, because building `SET` clauses straight from object keys is how an object key becomes SQL. The `saveSettings` upsert uses `COALESCE(NULLIF(excluded.analyst_key, ''), settings.analyst_key)` so that a form submitting a blank secret keeps the stored one. Blank means "unchanged", never "clear". Related: [[Lazy database open]], [[Analysis row lifecycle]], [[Credential vault]].