# Audit System

# Audit System

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/db/build-entity-snapshot.ts](src/db/build-entity-snapshot.ts)
- [src/db/database-patch-to-sql.ts](src/db/database-patch-to-sql.ts)
- [src/db/repository/audit-join-helper.ts](src/db/repository/audit-join-helper.ts)
- [src/db/schema-types.ts](src/db/schema-types.ts)
- [src/lib/audit/audit-service.ts](src/lib/audit/audit-service.ts)
- [src/lib/audit/audit-types.ts](src/lib/audit/audit-types.ts)

</details>



The Primebrick backend implements a robust, cross-cutting audit infrastructure designed to track every lifecycle event of domain entities. This system automatically records who changed what and when, storing state transitions as JSON deltas. The architecture ensures that audit trails are partitioned for performance and easily retrievable via standard API patterns.

### Audit Architecture Overview

The audit system is triggered during repository write operations. When an entity decorated with `@Auditable()` is modified, the system calculates the difference between the old and new states and persists this "delta" into a dedicated audit table.

#### System Flow: Write to Retrieval
The following diagram illustrates the flow from a data modification to the retrieval of its audit history.

**Audit Lifecycle Flow**
```mermaid
graph TD
    subgraph "Natural Language Space"
        UserAction["User updates a Customer"]
        AuditTrail["View Audit History"]
    end

    subgraph "Code Entity Space"
        Repository["Repository.update() [src/db/repository/repository.ts]"]
        DeltaCalc["Delta Calculator [src/db/repository/repository.ts]"]
        AuditService["AuditService.writeAudit() [src/lib/audit/audit-service.ts]"]
        AuditTable["{table_name}_audit [PostgreSQL]"]
        JoinHelper["audit-join-helper.ts [src/db/repository/audit-join-helper.ts]"]
    end

    UserAction --> Repository
    Repository --> DeltaCalc
    DeltaCalc --> AuditService
    AuditService --> AuditTable
    AuditTrail --> JoinHelper
    JoinHelper --> AuditTable
```
**Sources:** [src/lib/audit/audit-service.ts:6-37](), [src/db/repository/audit-join-helper.ts:1-58]()

---

### Core Components

#### 1. AuditService and Delta Calculation
The `AuditService` is the primary interface for persisting audit records. It targets tables following the `{table_name}_audit` naming convention [src/lib/audit/audit-service.ts:19-19](). 

- **AuditAction**: Captures the type of change: `INSERT`, `UPDATE`, `SOFT_DELETE`, `HARD_DELETE`, or `RESTORE` [src/lib/audit/audit-types.ts:1-7]().
- **Delta Format**: Changes are stored as a `jsonb` object where each key represents a field, containing an object with `old` and `new` values [src/lib/audit/audit-service.ts:16-16]().
- **Database Partitioning**: Audit tables are automatically partitioned by month using `pg_partman` to ensure long-term scalability [src/db/database-patch-to-sql.ts:120-122]().

For details on implementation and the delta logic, see **[AuditService and Delta Calculator](#5.1)**.

#### 2. Auditable Joins and Display Names
Because audit records store the `changed_by` field as a string (often a UUID or 'system'), the system provides helpers to resolve these identifiers into human-readable display names during API retrieval.

- **Regex Guardrails**: To prevent join errors when `changed_by` contains non-UUID strings (like "system"), the system uses a regex pattern `^[0-9a-fA-F-]{36}$` in the SQL join clause [src/db/repository/audit-join-helper.ts:13-19]().
- **Standard Projections**: `getAuditSelectWithDisplayName` provides a consistent set of fields for audit logs, including the actor's display name and IDP code [src/db/repository/audit-join-helper.ts:27-39]().

For details on SQL construction and type safety, see **[Auditable Joins and Display Name Resolution](#5.2)**.

---

### Database Schema for Auditing

When the `buildSqlPatchFromMetaDiff` function detects an auditable entity, it generates a secondary audit table with a fixed schema.

| Column | Type | Description |
| :--- | :--- | :--- |
| `id` | `bigint` | Primary key (identity) |
| `entity_id` | `bigint` | The internal ID of the audited record |
| `entity_uuid` | `uuid` | The public UUID of the audited record |
| `action` | `text` | The `AuditAction` performed |
| `changed_at` | `timestamptz` | Timestamp of the change |
| `changed_by` | `text` | UUID of the user or 'system' |
| `version` | `integer` | The version of the record after the change |
| `delta` | `jsonb` | The specific field changes |

**Audit Table Generation Logic**
```mermaid
graph LR
    subgraph "Entity Definition"
        Entity["@Entity() class"]
        IsAuditable["isAuditable: true"]
    end

    subgraph "DDL Generation [src/db/database-patch-to-sql.ts]"
        CreateTable["CREATE TABLE {name}"]
        CreateAudit["CREATE TABLE {name}_audit"]
        Partman["pg_partman.create_parent()"]
    end

    Entity --> IsAuditable
    IsAuditable --> CreateTable
    IsAuditable --> CreateAudit
    CreateAudit --> Partman
```
**Sources:** [src/db/database-patch-to-sql.ts:103-128](), [src/db/schema-types.ts:34-42](), [src/db/build-entity-snapshot.ts:28-35]()

---

### API Exposure

Audit trails are typically exposed via a `GET /audit` sub-resource on entity routers (e.g., `GET /customers/:uuid/audit`). The Data Access Layer (DAL) utilizes the `audit-join-helper.ts` utilities to return a hydrated list of changes, allowing the frontend to display a chronological history of the record.

**Sources:** [src/db/repository/audit-join-helper.ts:21-39]()

---
