Audit Log
Data Entity
Description
Tamper-evident, append-only record of significant actions performed across the Meander platform, scoped per organization. Captures actor identity, action type, affected resource, outcome, and contextual metadata to support compliance auditing, security investigations, and Global Admin support-access accountability.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Immutable primary key generated at write time | PKrequiredunique |
organization_id |
uuid |
FK to organizations. Every audit entry is scoped to exactly one tenant; cross-tenant reads are prohibited | required |
actor_user_id |
uuid |
FK to users. The human who triggered the action. NULL for automated system actions (e.g. expiry-check-service auto-pause, background-sync-scheduler) | - |
actor_role |
enum |
Role the actor held at the moment of the action. Stored denormalized so role changes do not rewrite history | - |
actor_session_id |
uuid |
FK to sessions. Links the audit entry to the specific authenticated session, enabling session-level investigation | - |
actor_ip_address |
string |
IP address of the request origin. IPv4 or IPv6. NULL for background system jobs | - |
source_product |
enum |
Which product surface generated this entry | required |
action |
string |
Dot-notation action identifier, e.g. user.created, expense.approved, session.revoked, role.assigned, data_export.bufdir, support_access.granted | required |
action_category |
enum |
High-level grouping for filtering and dashboard aggregation | required |
resource_type |
string |
The entity type affected by the action, e.g. user, expense, session, module_configuration, bufdir_export | required |
resource_id |
uuid |
Primary key of the affected resource record. NULL when action targets a collection or no specific record (e.g. bulk export) | - |
resource_display_name |
string |
Human-readable label for the resource at the time of the action (e.g. user email, expense amount+date). Denormalized so renames or deletions do not break audit readability | - |
outcome |
enum |
Whether the action succeeded, was rejected by authorization, or failed due to an error | required |
severity |
enum |
Operational severity for alerting and dashboard triage | required |
metadata |
json |
Structured context specific to the action type: before/after values for config changes, export row counts, failed field names for validation errors, support-access grant expiry date, etc. | - |
user_agent |
string |
HTTP User-Agent header from the request. Distinguishes mobile vs web portal vs API client | - |
checksum |
string |
HMAC-SHA256 of the record's canonical fields (id, organization_id, actor_user_id, action, resource_id, outcome, created_at) signed with a server-side key. Enables tamper detection during audit exports | required |
created_at |
datetime |
UTC timestamp set by the database at insert time. Immutable — no application-layer override permitted | required |
Database Indexes
idx_audit_logs_org_created
Columns: organization_id, created_at
idx_audit_logs_actor_user
Columns: actor_user_id, created_at
idx_audit_logs_action_category
Columns: organization_id, action_category, created_at
idx_audit_logs_resource
Columns: resource_type, resource_id
idx_audit_logs_session
Columns: actor_session_id
idx_audit_logs_severity
Columns: organization_id, severity, created_at
idx_audit_logs_outcome
Columns: organization_id, outcome, created_at
Validation Rules
action_dot_notation_format
error
Validation failed
actor_presence_required_for_human_actions
error
Validation failed
organization_id_not_null
error
Validation failed
valid_enum_values
error
Validation failed
metadata_size_limit
error
Validation failed
export_checksum_verification
critical
Validation failed
Business Rules
append_only_immutability
Audit log records are permanently immutable. No UPDATE or DELETE is permitted at the application layer. The database role used by the API has INSERT and SELECT grants only on this table — no UPDATE or DELETE grants exist
organization_scoped_reads
Every query against audit_logs MUST include organization_id as a filter predicate. The audit-log-service enforces this before executing any SELECT. Global Admins with active support access may read an org's logs only within the granted time window
support_access_always_logged
Every action taken by a Global Admin under a time-bounded support-access grant must produce an audit entry with action_category=support_access and severity=warning, regardless of outcome. This is non-negotiable per the platform spec
mandatory_for_sensitive_operations
The following operations must produce an audit entry or be rejected: expense approval/rejection, role assignment/revocation, user deactivation, module configuration change, Bufdir export, session revocation, support-access grant/revoke. Services performing these operations call audit-log-service synchronously inside the same transaction
checksum_generation_required
The audit-log-service computes the HMAC-SHA256 checksum before INSERT. Records without a valid checksum are rejected. The signing key is server-side only and rotated quarterly
severity_escalation_for_denied_outcomes
Any entry with outcome=denied is automatically escalated to severity=warning minimum. If action_category is authentication or support_access the severity is escalated to critical
created_at_server_only
The created_at timestamp is set exclusively by the database DEFAULT NOW(). No application code may supply a created_at value. This prevents backdating of audit entries