# Audit Log

## What Is Logged

- **All mutations** (CREATE, UPDATE, DELETE) on business entities: users, roles, invitations, sessions
- **Sensitive reads**: reading the user list, reading the audit log itself
- **Auth events**: login (session creation), logout (session deletion), invitation acceptance

## Audit Entry Fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Unique audit entry ID |
| `actorUserId` | UUID \| null | The user who performed the action (null for system actions) |
| `operation` | String | `CREATE`, `READ`, `UPDATE`, or `DELETE` |
| `entityType` | String | The type of entity affected (e.g., `user`, `role`, `invitation`, `session`, `audit_log`) |
| `entityId` | String \| null | The ID of the specific entity |
| `correlationId` | UUID \| null | Links related audit entries from the same request |
| `ipAddress` | String \| null | Client IP address |
| `userAgent` | String \| null | Client user agent |
| `before` | JSON \| null | Entity state before mutation (for UPDATE/DELETE) |
| `after` | JSON \| null | Entity state after mutation (for CREATE/UPDATE) |
| `metadata` | JSON \| null | Additional context (e.g., `{ action: "invitation_accepted", provider: "google" }`) |
| `createdAt` | DateTime | When the event occurred |

## Querying Audit Logs

Permission required: `audit.read`

```graphql
query {
  auditLogs(input: {
    entityType: "user"
    operation: "UPDATE"
    from: "2026-01-01T00:00:00Z"
    to: "2026-03-10T00:00:00Z"
    limit: 50
    offset: 0
  }) {
    items {
      id
      actorUserId
      operation
      entityType
      entityId
      before
      after
      metadata
      createdAt
    }
    total
    limit
    offset
  }
}
```

## Access Rules

- Reading the audit log requires the `audit.read` permission
- Reading the audit log is itself audited (creates a READ entry for `audit_log` entity type)
- Audit log entries are append-only — they cannot be modified or deleted via the API

## Storage Design

- Stored in PostgreSQL `audit_logs` table
- Indexed on: `created_at`, `actor_user_id`, `entity_type` + `entity_id`, `operation`, `correlation_id`
- `before`, `after`, and `metadata` fields use JSONB for flexible storage
- Designed for future partitioning by `created_at` (monthly) for retention management
- Minimum retention: 24 months
