# Data Model: Audit Log Foundation

**Feature**: 134-audit-log-foundation | **Date**: 2026-03-11

## Overview

This feature extends an existing persisted entity rather than introducing a second audit-history store. The design adds a first-class event model, normalized actor and target snapshots, and a canonical read model for the Monitoring audit page.

The main model layers are:

1. `AuditLog` as the durable event record,
2. normalized event taxonomy and outcome semantics,
3. actor and target snapshots captured at write time,
4. structured context payloads with redaction,
5. a workspace-scoped audit list and detail read model.

## Existing Persistent Entity to Evolve

### AuditLog

Current persisted shape already includes:

| Attribute | Type | Notes |
|-----------|------|-------|
| `id` | int | Existing primary key |
| `tenant_id` | int nullable | Existing tenant scope field |
| `workspace_id` | int nullable | Added later for workspace-scoped events |
| `actor_id` | int nullable | Existing actor identifier |
| `actor_email` | string nullable | Existing actor display fallback |
| `actor_name` | string nullable | Existing actor display fallback |
| `action` | string | Existing event key |
| `resource_type` | string nullable | Existing target-type precursor |
| `resource_id` | string nullable | Existing target-id precursor |
| `status` | string | Existing outcome precursor |
| `metadata` | json nullable | Existing structured payload precursor |
| `recorded_at` | timestamp | Existing event time |

Planned first-class audit semantics to add or formalize:

| Target Semantic | Likely Storage Shape | Purpose |
|-----------------|----------------------|---------|
| `event_type` or normalized action | existing `action` reused or renamed semantically | Stable taxonomy key |
| `summary` | new string/text column | Human-readable list/detail summary |
| `outcome` | new column or normalized use of `status` | Explicit success/failure/partial/info/blocked state |
| `actor_type` | new string column | Human/system/scheduled/integration distinction |
| `actor_label` | derived from existing name/email or new snapshot field | Stable readable actor label |
| `target_type` | existing `resource_type` reused or normalized | Stable target taxonomy key |
| `target_id` | existing `resource_id` reused or normalized | Structured target identity |
| `target_label` | new string column | Stable readable target label |
| `operation_run_id` | optional relation shortcut | Operational cross-linking |
| `context` | existing `metadata` reused or renamed semantically | Structured supporting metadata |
| `occurred_at` | existing `recorded_at` reused or normalized | Canonical event timestamp |

**Core rules**:
- Every row is append-only in normal product flows.
- Every row must remain understandable even when the source object is deleted or renamed later.
- Every row must be scoped at least to a workspace; tenant scope remains nullable for workspace-only events.

## Supporting Domain Concepts

### AuditEventType

| Field | Type | Description |
|------|------|-------------|
| `key` | string | Stable registry key such as `baseline_capture.completed` |
| `family` | string | Domain grouping such as `baseline`, `finding`, `backup`, `restore`, `workspace_membership` |
| `verb` | string | Normalized operator-facing action verb |
| `supports_target_link` | bool | Whether target drill-down is meaningful |

**Rules**:
- Must come from a shared canonical registry.
- Must cover both legacy migrated keys and new first-wave keys.

### AuditOutcome

| Value | Meaning |
|-------|---------|
| `success` | Action completed successfully |
| `failed` | Action failed |
| `partial` | Action completed partially |
| `info` | Informational/non-mutating event |
| `blocked` | Action was intentionally prevented |

**Rules**:
- Badge rendering is centralized.
- Existing historical values like `success` and `failure` need compatibility mapping where required.

### AuditActorSnapshot

| Field | Type | Description |
|------|------|-------------|
| `actor_type` | string enum | `human`, `system`, `scheduled`, `integration`, `platform` if compatibility requires |
| `actor_id` | string or int nullable | Stable identifier when available |
| `actor_label` | string nullable | Display label captured at event time |
| `actor_email` | string nullable | Secondary identity where useful |

**Rules**:
- Human and non-human actors must be distinguishable in storage and UI.
- Historical readability must not depend on the actor record continuing to exist.

### AuditTargetSnapshot

| Field | Type | Description |
|------|------|-------------|
| `target_type` | string nullable | Canonical target kind such as `finding`, `baseline_profile`, `backup_set` |
| `target_id` | string nullable | Stable identity |
| `target_label` | string nullable | Display label captured at write time |
| `workspace_id` | int | Scope guard |
| `tenant_id` | int nullable | Tenant guard for tenant-owned targets |

**Rules**:
- Target label should be snapped at write time whenever practical.
- Related links remain optional and permission-aware.

### AuditContext

| Field | Type | Description |
|------|------|-------------|
| `context` | JSON-like object | Structured metadata for interpretation |

Expected content patterns:

| Pattern | Example |
|---------|---------|
| before/after lifecycle change | `before_status`, `after_status` |
| related run identity | `operation_run_id` |
| workflow rationale | `reason`, `reason_code` |
| ownership reassignment | `from_assignee_user_id`, `to_assignee_user_id` |
| summary counts | `items_succeeded`, `items_failed` |

**Rules**:
- Context must remain intentionally shaped.
- `AuditContextSanitizer` or equivalent must redact secrets, tokens, and sensitive fields.
- Raw policy payloads and oversized blobs are not first-class audit context.

## Read Models for the Monitoring Page

### AuditLogListRow

| Field | Type | Description |
|------|------|-------------|
| `id` | int | Event identity |
| `occurred_at` | datetime | Default sort field |
| `summary` | string | Primary row text |
| `event_type` | string | Secondary technical key |
| `outcome` | string | Badge-rendered state |
| `actor_label` | string | Who or what caused the event |
| `actor_type` | string | Actor-kind indicator |
| `target_label` | string nullable | Affected object |
| `target_type` | string nullable | Secondary target hint |
| `tenant_label` | string nullable | Only when safe to show |
| `workspace_id` | int | Scope |
| `has_related_link` | bool | Whether drill-down is available to this viewer |

**Rules**:
- Summary-first ordering is mandatory.
- Rows remain understandable without opening the detail view.

### AuditLogFilterState

| Field | Type | Description |
|------|------|-------------|
| `search` | string nullable | High-signal summary search |
| `tenant_id` | string nullable | Defaulted from active tenant when entitled |
| `event_type` | string nullable | Taxonomy filter |
| `outcome` | string nullable | Outcome filter |
| `actor` | string nullable | Actor-label or actor-kind filter |
| `target_type` | string nullable | Target kind filter |
| `date_from` | date nullable | Range lower bound |
| `date_until` | date nullable | Range upper bound |

**Rules**:
- Filter options must come only from the active workspace and authorized tenant subset.
- Empty results are valid and intentional.

### AuditLogDetailViewModel

| Field | Type | Description |
|------|------|-------------|
| `summary` | string | Primary heading |
| `occurred_at` | datetime | Event timestamp |
| `actor` | AuditActorSnapshot | Who/what caused the event |
| `target` | AuditTargetSnapshot nullable | Affected object |
| `outcome` | string | Result state |
| `context_items` | list | Readable context facts |
| `technical_metadata` | list | Secondary raw identifiers or event key |
| `related_link` | object nullable | Canonical target destination when permitted |

**Rules**:
- Readable context facts come before raw JSON.
- The event must stay intelligible if `related_link` is absent.

## Existing Domain Relations Important for Coverage

### Finding

Relevant persisted fields already include status, severity, assignee and owner users, due dates, and closed or resolved timestamps.

**Audit significance**:
- status changes
- assignee or owner changes
- risk acceptance via `STATUS_RISK_ACCEPTED`
- reopen, resolve, close transitions

### BaselineProfile

Relevant persisted fields already include workspace ownership, status, capture mode, active snapshot, and creator.

**Audit significance**:
- create or update or archive actions
- capture and compare lifecycle events through related jobs and operation context

### BackupSet

Relevant persisted fields already include tenant ownership, metadata, completion time, and restore-run relation.

**Audit significance**:
- create or update or archive events
- restore initiation linkages and backup-scope summaries

### RestoreRun

Relevant persisted fields already include status, preview, results, group mapping, completion timestamps, and operation-run relation.

**Audit significance**:
- restore started, completed, failed, partial outcomes
- assignment outcome summaries and preview/execute distinctions when meaningful

### OperationRun

Remains the canonical operational record for long-running workflows.

**Audit significance**:
- high-value completion/failure/retry evidence
- optional `operation_run_id` shortcut from `AuditLog`

## State and Transition Notes

### AuditLog lifecycle

```text
Created
  -> persisted as historical fact
  -> never user-edited or user-deleted in normal flows
  -> may be superseded or corrected only by later audit events
```

### Workflow-to-audit relationship

```text
User/system action occurs
  -> domain service or job performs authorization and mutation
  -> shared audit recorder writes one meaningful event with actor/target/context
  -> canonical Monitoring page surfaces the event in reverse chronological order
```

## Validation Rules

| Rule | Result |
|------|--------|
| Every audit row has workspace scope | Required |
| Tenant-scoped events must belong to the same workspace | Required |
| Actor kind is explicit for new rows | Required |
| Summary is human-readable and non-empty for new rows | Required |
| Context is sanitized and shaped | Required |
| No user-facing edit/delete path exists | Required |
| Related links are permission-aware and optional | Required |

## Schema Impact

Schema changes are expected. The preferred migration strategy is additive evolution of `audit_logs` with compatibility-safe backfills and indexes for:

- occurred-at or recorded-at descending review,
- workspace + occurred-at,
- tenant + occurred-at,
- event type,
- outcome,
- actor lookup,
- target lookup,
- optional operation-run shortcuts.
