feat(diagnostics): debug sessions — start, stop, the tag on every line, screenshots (E16 S1)

A session is one recording, capped at 60 minutes by the server. While it is
open, a request naming the caller's own session in X-Debug-Session is logged
at the session's level with every line tagged; a header naming anything else
is ignored. The browser's batches count a session as consent to its level and
keep the tag only on its own events. Screenshots are judged by their bytes,
capped in size and count, and served back to their owner only.

- debug_sessions / debug_screenshots tables, one open session per user
  (partial unique index), log_events.debug_session_id indexed
- /api/debug-sessions: start (joins a running one), stop, list, get, events,
  screenshots; /api/me reports the live session
- the hourly log job closes expired sessions and drops them with the logs
- logsink.serialize and routes._common.capped_body shared by their readers
This commit is contained in:
2026-09-19 21:38:53 +02:00
parent 44a792c34f
commit a7d7456279
18 changed files with 1133 additions and 54 deletions
+2 -2
View File
@@ -21,8 +21,8 @@ Three layers, by *who* runs the code — not by feature:
3. **Flat `app/*.py` core modules — cross-cutting primitives.** `auth`, `sysconfig`, `quota`, `audit`,
`notifications`, `userscope`, `realtime`, `ratelimit`, `progress`, `logs` (the one logging
configuration), `logsink` (its `log_events` table) and `diagnostics` (a user's consented log
level), … are single-file primitives many layers import. They depend on nothing above them (a core
configuration), `logsink` (its `log_events` table), `diagnostics` (a user's consented log
level) and `debugsession` (a recording that tags its user's lines, E16), … are single-file primitives many layers import. They depend on nothing above them (a core
module must never import a `routes/…` module).
**Extraction trigger: the SECOND consumer.** Keep new logic inline in its first caller. The moment a