Appearance
Events & Audit
DPS records activity in several layers, each with a distinct job:
| Stream | Table | Read via | Job |
|---|---|---|---|
| Canonical events | events | (internal; future consumers) | Machine-readable, append-only activity stream — the single answer to "what has been happening in DPS" |
| Timeline / audit log | timeline | GET /audit (rpc_audit_log_v2) | Human-readable per-case history shown on dockets and the Audit Log page |
| Admin audit | admin_audit_log | GET /admin/audit | Forensic System Administration trail: reasons, before/after state, correlation ids |
| Training events | training_events | session detail payloads | Session-scoped simulation telemetry (idempotent, broadcast) |
The canonical stream (events, migration 033) is the newest layer and the reference model; the others are retained for backward compatibility and their specialized jobs.
Event records
Each events row (written only via rpc_write_event):
| Field | Meaning |
|---|---|
event_type | Stable machine-readable type, e.g. report.status_changed |
category | First segment of the type; CHECK-constrained to report/contact/punishment/delivery/queue/agent/admin/legacy |
actor_id | Canonical actor identity — the agent's Discord ID, api-key for the bot system actor, or NULL only when no identity exists |
actor_name | Display-name snapshot at write time — never the identity carrier |
actor_type | agent | system | reporter |
target_type / target_id | The resource affected (no FK — audit survives deletion) |
report_id | Denormalized report link (NULL for non-report events) |
metadata | Structured detail (reasons, to/from values, refs) |
before_state / after_state | Pre/post images — admin mutations only, mirroring admin_audit_log |
correlation_id | Request id tying UI → event → admin audit row |
created_at | Server timestamp |
Actor identity
The identity rules are strict (migration 034):
actor_idis the canonical Discord ID, passed as a dedicated parameter (p_actor_id) by every dashboard-driven mutation. The API derives it from the session — never from the client.actor_nameis a pure display snapshot. Display names are never the identity; identity is never smuggled into display text.- The bot system actor is
api-key/BotGhost (API key); reporter actors are the reporter's stored name withactor_type = reporter. - Historical backfills assign
actor_idonly where the stored value is the identity (an exactagents.discord_id/agent_idmatch); no name-to-ID guessing.
Event taxonomy
The full taxonomy is maintained in event_service.EVENT_TYPES (and mirrored in the reference). Categories: report.*, contact.*, punishment.*, delivery.*, queue.*, agent.*, admin.*, and legacy.timeline_event (historical rows with no machine-readable mapping).
Append-only behavior
events, admin_audit_log, and admin_health_history are append-only:
- service_role receives SELECT+INSERT only (no UPDATE/DELETE grants).
- RLS is enabled with zero policies.
- A
BEFORE UPDATE OR DELETE OR TRUNCATEtrigger raisesEVENTS_APPEND_ONLY(or the audit-log equivalent). The only exception is the guarded 034 actor-identity backfill, which disables the trigger transactionally and re-enables it in all paths — every row written after the migration is fully protected.
Administrative events
Admin mutations (queue maintenance, access management, report correction, agent updates, schema reload) write two records in one transaction:
- The forensic
admin_audit_logrow —agent_id,agent_name,action,target, requiredreason,before_state,after_state,correlation_id. - The canonical event via
admin_write_audit→admin_audit_event_map, linked back withmetadata.admin_audit_id.
This means the append-only admin trail and the unified stream can never diverge, and a mutation can never exist without both records.
Mutation events
Report/queue/delivery mutations write their canonical events inside the mutation's transaction (migrations 033/034):
rpc_report_action→report.investigation_started/report.status_changed/report.assigned/contact.reporter_contact_requested/ …rpc_add_note/rpc_add_evidence→report.note_added/report.evidence_added- queue RPCs →
queue.action_claimed/action_completed/action_failed/action_requeued - discipline RPCs →
delivery.queued/claimed/completed/failed/requeued rpc_delete_report→report.deletedvia the forensic audit dual-write
Route-level event recording (e.g. the report PATCH path) goes through event_service.record_event, which is best-effort by design: a failed event write is logged and never fails the request that produced the activity. The strict path is the transactional one — events required for a mutation's integrity participate in the mutation's transaction.
Audit guarantees
- Destructive operations never succeed without their audit record.
rpc_delete_reportwrites the forensic audit (andreport.deletedevent) in the deletion's transaction; if the audit write fails, the deletion rolls back. - Actor identity comes from an authoritative source. The session's
discord_id(verified against the liveagentsrow) or the verified API key; never client-supplied text. - Historical events are not silently rewritten.
eventsis trigger-protected; the only writes are the guarded backfills, which are non-destructive and idempotent. - Audit writes required for a mutation participate in the same transaction. See the RPC list above.
- Audit records survive data deletion.
timelineandeventshave no foreign key toreports; a Director's report deletion leaves the audit trail intact (migration 017 retention model).
Why audit records must remain trustworthy
Audit records are the platform's memory: they answer who did what, when, and why, including destructive and administrative actions. If they could be rewritten or silently skipped, an unauthorized action could be erased, a corrupted actor attribution could stand, and the admin audit trail would lose its evidentiary value. The layered protections (grants, RLS, append-only triggers, same-transaction writes, canonical identity) exist so that trust does not depend on any single component behaving correctly.
The timeline (legacy) layer
timeline rows are human-readable ("Report validated — reason…", "Note added …") and remain the backing for the /audit page and docket timelines. Report writers dual-write timeline + canonical event so both stay in sync. rpc_audit_log_v2 resolves by_name from agents (discord_id or agent_id) and scrubs identifiers from all text.
Known limitations
training_eventsis deliberately not mirrored intoevents: training is a parallel simulation environment with its own event model (client idempotency, realtime broadcast, high volume) and no unified-stream consumer (migration 033 documents this decision).timelineis append-only by convention, not by trigger (noted inAUDIT_LOG_FINDINGS.md); the API never updates/deletes it, and the canonical stream is trigger-protected.eventshas no public read endpoint yet — it is written for future unified consumers (e.g. a global activity feed or export).- Session detail payloads bound training events at 200 rows; long sessions will need pagination/retention eventually.