Audit Log
Data Entity
Description
Tamper-evident, append-only record of all significant system events and user actions across the Meander platform. Supports compliance, security investigations, and regulatory reporting requirements.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key, generated server-side at write time | PKrequiredunique |
user_id |
uuid |
FK to users — the authenticated user who triggered the event. Nullable for system-generated events (cron jobs, background tasks). | - |
organization_id |
uuid |
FK to organizations — the tenant context in which the event occurred. Nullable for global-admin or cross-org system events. | - |
action |
string |
Verb describing the operation performed. Uses dot-notation namespacing: entity_type.verb (e.g., user.created, expense.approved, session.terminated, auth.login_failed). | required |
entity_type |
string |
The domain entity type affected by the action (e.g., user, activity, expense, assignment, session, organization, feature_flag). Used for filtering and cross-referencing. | required |
entity_id |
string |
Identifier of the affected entity instance. Stored as string to accommodate UUID, numeric, and slug-based IDs across entity types. | - |
actor_role |
enum |
The role the user held at time of action, denormalized for query performance without join to user_roles. | - |
severity |
enum |
Severity classification of the event for alerting and dashboard prioritization. | required |
outcome |
enum |
Whether the action succeeded or failed. Failures are always logged, even for security-sensitive operations like authentication. | required |
ip_address |
string |
IPv4 or IPv6 address of the request origin. Captured from X-Forwarded-For header (Vercel / CDN aware). | - |
user_agent |
string |
HTTP User-Agent string. Enables device/client type analysis (Flutter app vs browser admin portal). | - |
session_id |
string |
FK-by-value to sessions table. Stored as string rather than hard FK to preserve audit records after session deletion. | - |
before_state |
json |
Snapshot of the entity's field values before the operation. Null for create events. Enables diff views in the audit log UI. | - |
after_state |
json |
Snapshot of the entity's field values after the operation. Null for delete events. Sensitive fields (passwords, tokens) are redacted before storage. | - |
metadata |
json |
Arbitrary event-specific context not captured in structured fields (e.g., bulk operation count, export file format, assigned role name, rejection reason). | - |
checksum |
string |
SHA-256 HMAC of all other fields in the record using a server-side secret key. Used by the tamper-detection scan to verify log integrity. | required |
created_at |
datetime |
UTC timestamp of when the event was recorded. Set server-side; never accepted from client. | required |
Database Indexes
idx_audit_logs_org_created
Columns: organization_id, created_at
idx_audit_logs_user_created
Columns: user_id, created_at
idx_audit_logs_entity
Columns: entity_type, entity_id
idx_audit_logs_action
Columns: action
idx_audit_logs_severity_created
Columns: severity, created_at
idx_audit_logs_created_at
Columns: created_at
Validation Rules
action_format_validation
error
Validation failed
created_at_server_only
error
Validation failed
entity_id_required_for_entity_actions
error
Validation failed
ip_address_format
warning
Validation failed
before_after_state_size_limit
warning
Validation failed
checksum_integrity_on_read
error
Validation failed
Business Rules
append_only_immutability
Audit log records are strictly append-only. No UPDATE operations are permitted after creation. Any attempt to modify an existing record must be rejected at the application and database layer (achieved via row-level security policy in PostgreSQL denying UPDATE on audit_logs).
deletion_by_retention_policy_only
Records may only be deleted by the scheduled audit-retention-job enforcing the organization's configured retention window (minimum 12 months for GDPR-compliant archival). Manual deletion via the UI or API is not permitted.
checksum_on_create
A SHA-256 HMAC checksum is computed over all field values at write time using a server-managed secret. The checksum is stored in the record and used by periodic integrity scans to detect tampering.
sensitive_field_redaction
Before/after state snapshots must have sensitive fields redacted (passwords, tokens, API keys, personal health data in assignments) before storage. The redaction list is defined in a server-side config, not client-controlled.
org_tenant_isolation
Audit log reads are scoped to organization_id matching the requesting user's active org context. Global Admins may query across organizations for system-level events only. Coordinators and Org Admins see only their own organization's logs.
system_events_logged_without_user
Background jobs (cron schedulers, retention jobs, bulk sync tasks) may create audit records with user_id = NULL and actor_role = 'system'. These are valid records and must not be filtered out by queries.
critical_events_trigger_security_alert
Any audit log record created with severity = 'critical' must synchronously notify the security-monitoring-service so it can surface the event on the Security Dashboard and evaluate anomaly thresholds.
auth_failures_always_logged
Failed authentication attempts (wrong password, BankID error, token rejection) must always be logged regardless of whether the user_id can be resolved. IP address and user_agent are required for these records to support brute-force detection.