Audit Log
Data Entity
Description
An immutable, append-only record of significant system events — authentication events, data access, approval decisions, role changes, and sensitive data operations. Supports compliance requirements for GDPR and Bufdir grant auditing. No update or delete operations are permitted on this table.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Immutable primary key generated server-side at insert time. Never client-provided. | PKrequiredunique |
actor_id |
uuid |
Foreign key to users.id — the authenticated user who triggered the event. May be null only for system-initiated events (cron jobs, scheduled tasks). | - |
organization_id |
uuid |
Foreign key to organizations.id — the top-level organization context for tenant scoping. Required for all non-global events. | - |
association_id |
uuid |
Foreign key to local_associations.id — the local association context when the event is scoped below the organization level. Null for org-level and global events. | - |
event_type |
enum |
Classification of the event that occurred. Drives querying, alerting, and compliance report generation. | required |
resource_type |
string |
The type of entity affected by this event. Combined with resource_id to identify the specific record. Examples: 'user', 'activity', 'reimbursement', 'assignment', 'certification', 'role', 'organization'. | required |
resource_id |
string |
The identifier of the affected resource. Stored as string to accommodate UUIDs and composite keys across all entity types without FK constraint (avoids cross-table coupling). | - |
action |
string |
Human-readable description of the specific action taken, e.g. 'approved reimbursement', 'changed role from peer_mentor to coordinator', 'exported bufdir report Q1-2025'. Supplements event_type for audit display. | required |
previous_state |
json |
Snapshot of the relevant resource state before the action. Null for create and access events. Must not include raw encryption keys or raw biometric data. Sensitive fields (personnummer, medical data) must be masked before storage. | - |
new_state |
json |
Snapshot of the relevant resource state after the action. Null for delete and access events. Same masking rules as previous_state apply. | - |
metadata |
json |
Flexible bag of contextual data specific to the event_type. For auth events: ip_address, user_agent, provider. For approval events: notes, threshold_evaluated, rule_name. For bulk operations: affected_ids array, count. For export events: format, period, record_count. | - |
ip_address |
string |
IPv4 or IPv6 address of the client at the time of the event. Populated for authentication and sensitive data access events. May be null for system-initiated background events. | - |
session_id |
uuid |
Reference to the sessions.id value active at the time of the event. Allows grouping all events within a single authenticated session. Null for system events. | - |
severity |
enum |
Risk classification of the event for monitoring and alerting thresholds. Used by compliance dashboards to surface high-severity events. | required |
created_at |
datetime |
Server-side UTC timestamp set automatically at insert. Never provided by the client. Indexed for time-range queries and retention policy enforcement. | required |
Database Indexes
idx_audit_log_actor_id
Columns: actor_id
idx_audit_log_organization_id
Columns: organization_id
idx_audit_log_event_type
Columns: event_type
idx_audit_log_resource
Columns: resource_type, resource_id
idx_audit_log_created_at
Columns: created_at
idx_audit_log_org_created_at
Columns: organization_id, created_at
idx_audit_log_actor_created_at
Columns: actor_id, created_at
idx_audit_log_association_id
Columns: association_id
idx_audit_log_severity
Columns: severity, created_at
Validation Rules
event_type_valid_enum
error
Validation failed
resource_type_non_empty
error
Validation failed
action_description_non_empty
error
Validation failed
metadata_valid_json
error
Validation failed
previous_and_new_state_valid_json
error
Validation failed
ip_address_format
error
Validation failed
severity_default_info
warning
Validation failed
no_duplicate_rapid_fire_inserts
warning
Validation failed
Business Rules
append_only_immutability
No UPDATE or DELETE statements may be executed against the audit_logs table under any circumstances. The table must be enforced as append-only at both the application layer (no update/delete methods in AuditLogRepository) and the database layer (a PostgreSQL row-level security policy or trigger that rejects all UPDATE and DELETE operations).
server_side_timestamp
created_at must always be set by the server using NOW() at insert time. Client-provided timestamps must never be accepted. This ensures the audit trail cannot be forged or backdated.
sensitive_field_masking
Before writing previous_state or new_state snapshots, components must mask sensitive personal data fields — personnummer, full medical data (epikrise contents), encryption keys, and raw biometric payloads. Masked values are stored as '[REDACTED]' strings. This satisfies GDPR Article 25 data minimisation in audit storage.
system_event_actor_null_allowed
For scheduled background jobs (certification expiry, badge evaluation, annual summary aggregation) the actor_id may be null and a system_event event_type must be used. All user-initiated events must include a non-null actor_id.
mandatory_approval_logging
Every reimbursement approval, rejection, or clarification action — whether automated or manual — must produce an audit log entry before the status change is committed. The audit write and the status update must be within the same database transaction.
mandatory_role_change_logging
Every user role assignment, removal, or modification must produce an audit log entry capturing the previous role and new role. This is required for GDPR accountability and Bufdir grant compliance.
org_scoping_required_for_non_global
All events that are not of event_type 'system_event' or global admin actions must include a non-null organization_id. Events below the org level (coordinator actions, local association operations) must also include association_id.
no_cross_tenant_reads
Read queries against audit_logs must always include an organization_id filter matching the requesting user's organization. Coordinators are further restricted to entries where association_id matches their association membership. Global Administrators may query across all organizations.
critical_severity_for_auth_failures
Authentication failure events (auth_login_failed, auth_biometric_failed) must be recorded with severity='critical'. Repeated failures for the same actor within a 15-minute window should be flagged in metadata for monitoring systems.