Appearance
Architecture Diagrams
The canonical technical diagrams. Rendered as monospace blocks so they stay correct in every theme and copy cleanly.
System architecture
text
┌───────────────┐
│ Dashboard │ React/Vite bundle (dashboard + Training Center)
└───────┬───────┘
│ credentialed fetch (HttpOnly session cookie)
┌───────▼───────┐
│ Flask API │ dps-code-api (Render) — routes, gates, validation
└───────┬───────┘
│ service-role Supabase client
┌───────▼───────┐
│ Services │ domain rules, shared operations, serialization
└───────┬───────┘
│ named RPC calls / gated table access
┌───────▼───────┐
│ RPC / DB Auth │ transactional functions, row locks, RLS, grants
└───────┬───────┘
│
┌───────▼───────┐
│ PostgreSQL │ Supabase — single source of truth
└───────┬───────┘
│
┌──────────┴──────────┐
│ │
Application Data Audit / Events
(reports, agents, (timeline, events,
queues, training) admin_audit_log)Side channels: the Discord bot (BotGhost) talks to the API with the shared API key; Discord OAuth flows browser → API → Discord; the ER:LC relay bridges the API to the Roblox game-server API; Supabase Realtime broadcasts training events to browsers (best-effort synchronization only).
Request lifecycle
text
Request
→ Authentication browser session cookie (live agents row) or API key
→ Authorization clearance / rank / allowlist / assignment
→ Validation fields, enums, URLs, bounds, reasons
→ Service domain rules, shared operation
→ Authoritative DB Operation named RPC (one transaction) or gated table write
→ Audit/Event timeline + canonical event in the same transaction
→ Response serialized, identifier-scrubbed payloadInvestigation lifecycle
text
┌─────────────────────────────────────────────┐
│ Open / Pending (not started) │
└──────────────────┬──────────────────────────┘
│ action=investigate
│ (reopen of Closed/Completed/
│ Validated/Invalidated needs
│ clearance 4+)
▼
┌─────────────────────────────────────────────┐
│ Under Investigation │
│ · notes / evidence / timeline / contact │
└───────┬──────────────────┬──────────────────┘
│ │
action=validate action=invalidate
│ │
▼ ▼
┌──────────────┐ ┌───────────────┐
│ Validated │ │ Invalidated │
└──────────────┘ └───────────────┘
│ │
└───────┬──────────┘
│ both: close open contact thread,
│ queue bot action, emit
│ report.investigation_concluded
│
Under Investigation ──(conclude)──► Pending
(no bot work; timeline + event only)Report action side effects
text
POST /reports/<id>/action → perform_report_action → rpc_report_action
│
┌─────────────────────────────────────┼────────────────────┐
▼ ▼ ▼
status / assignment update timeline row(s) canonical event
(under FOR UPDATE lock, ("Report validated", (report.* / contact.*,
guarded against races) "Investigation begun", same transaction)
"Case claimed by agent")
│
├──► contact_messages row (contact_reporter only)
└──► pending_actions row (bot work; not for conclude)Training session lifecycle
text
pending ──(start)──► active ⇄ (pause/resume) ──(end)──► completed
│ │ ▲
│ ├──(scenario activate)──► child: waiting → active → completed
│ │
└──(abort)──────────► aborted (trainee sees the no-active state)Realtime synchronization invariant
text
API/database mutation → persistent state/event → best-effort Realtime broadcast
→ recipient receives event → authoritative GET/reconciliationA missed broadcast never corrupts UI state: pages reconcile with an authoritative GET after connect and after event bursts.