> ## Documentation Index
> Fetch the complete documentation index at: https://docs-terra.withunify.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Audit Logging

> SOC 2 compliant audit logging for tracking all user actions

# Audit Logging

> Every significant action is logged with who, what, when, and why. Designed for SOC 2 compliance.

## What's Logged

| Category            | Actions                                  | Details Captured                         |
| ------------------- | ---------------------------------------- | ---------------------------------------- |
| **Forms**           | Create, edit, publish, delete, duplicate | Schema diff, field changes, version info |
| **Submissions**     | Status changes, bulk operations          | Before/after status, affected IDs        |
| **Settings**        | System settings, branding, integrations  | Changed fields, before/after values      |
| **User Management** | Role changes, invites, removals          | Target user, old/new role                |
| **Data Access**     | Exports, PDF downloads                   | Format, record count                     |
| **Authentication**  | Login/logout                             | User, IP (anonymized), user agent        |

## Log Structure

```typescript theme={null}
interface AuditLog {
  id: string;
  action_type: string;    // "create", "update", "delete", "export", etc.
  entity_type: string;    // "form", "submission", "user", "settings"
  entity_id: string;
  user_id: string;
  user_email: string;
  user_role: string;
  workspace_id?: string;
  changes?: {
    [field: string]: { before: unknown; after: unknown };
  };
  metadata?: Record<string, unknown>;
  ip_address?: string;    // Anonymized (last octet zeroed)
  user_agent?: string;
  request_id?: string;    // For distributed tracing
  success: boolean;
  error_message?: string;
  created_at: string;
}
```

## Using the Audit Functions

### Basic Audit Log

```typescript theme={null}
import { auditLog } from "@/lib/audit";

await auditLog({
  actionType: "update",
  entityType: "form",
  entityId: formId,
  changes: {
    title: { before: "Old Title", after: "New Title" },
  },
  metadata: { formTitle: "New Title" },
});
```

### With Request Context (IP/User Agent)

```typescript theme={null}
import { auditLog, getRequestContext } from "@/lib/audit";
import { headers } from "next/headers";

const requestContext = getRequestContext(await headers());

await auditLog({
  actionType: "export",
  entityType: "submission",
  entityId: "bulk:50",
  metadata: { format: "csv", count: 50 },
  requestContext,
});
```

### Convenience Functions

```typescript theme={null}
import {
  auditFormCreate,
  auditFormPublish,
  auditSettingsChange,
  auditSubmissionStatusChange,
} from "@/lib/audit";

// Form operations
await auditFormCreate(formId, { title, createdBy });
await auditFormPublish(formId, { version: 1 });

// Settings changes
await auditSettingsChange("settings", "system", "system_settings", {
  before: oldSettings,
  after: newSettings,
});

// Submission status
await auditSubmissionStatusChange(
  submissionId,
  { oldStatus: "pending", newStatus: "approved" },
  { formId }
);
```

## Querying Logs

### Via API

```typescript theme={null}
// GET /api/audit?page=1&limit=50&actionType=update&entityType=form
const response = await fetch("/api/audit?" + params);
const { logs, total } = await response.json();
```

### Direct SQL

```sql theme={null}
-- Find all changes to a form
SELECT * FROM audit_logs
WHERE entity_type = 'form'
  AND entity_id = $1
ORDER BY created_at DESC;

-- Find all actions by a user
SELECT * FROM audit_logs
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT 100;

-- Find all exports (for compliance review)
SELECT * FROM audit_logs
WHERE action_type = 'export'
ORDER BY created_at DESC;
```

## Access Review Report

For SOC 2 access reviews, export a report of all users with system access:

```bash theme={null}
# Download CSV
curl -H "Authorization: Bearer $TOKEN" \
  "https://your-domain.com/api/audit/access-report?format=csv" \
  -o access-review.csv

# Get JSON
curl -H "Authorization: Bearer $TOKEN" \
  "https://your-domain.com/api/audit/access-report"
```

The report includes:

* All users with access
* Their roles and permissions
* Account status (Active/Pending)
* Last activity timestamp

## Retention Policy

Audit logs are retained for **7 years** (2555 days) by default for SOC 2 compliance.

### Configuration

The retention period is stored in `system_settings.audit_retention_days`.

### Automated Cleanup

A daily cron job cleans up expired logs:

```typescript theme={null}
// /api/cron/audit-cleanup
// Called daily by Vercel cron

// Runs: SELECT * FROM cleanup_expired_audit_logs();
// Returns: { deleted_count, retention_days }
```

To set up the cron job, add to `vercel.json`:

```json theme={null}
{
  "crons": [
    {
      "path": "/api/cron/audit-cleanup",
      "schedule": "0 3 * * *"
    }
  ]
}
```

## IP Anonymization

IP addresses are anonymized for privacy while preserving geographic analysis capability:

* **IPv4**: Last octet zeroed (e.g., `192.168.1.100` → `192.168.1.0`)
* **IPv6**: Last 80 bits zeroed

```typescript theme={null}
function anonymizeIP(ip: string): string {
  // IPv4: 192.168.1.100 → 192.168.1.0
  // IPv6: 2001:db8:85a3::8a2e:370:7334 → 2001:db8:85a3::
}
```

## Immutability

Audit logs are append-only. The database enforces this via RLS:

```sql theme={null}
-- Only service role can insert
CREATE POLICY "Service role can manage audit logs"
ON audit_logs
FOR ALL
USING (true)
WITH CHECK (true);
```

Application code never updates or deletes audit logs.

## UI Dashboard

Access the audit log dashboard at **Settings → Audit Logs**:

* View all actions with filtering by type, entity, date
* Search by user email or entity ID
* Export access review reports
* View detailed change diffs

***

<Card title="Database Schema" icon="database" href="/core-systems/data/database-schema">
  Full table reference
</Card>

<Card title="Security" icon="shield" href="/operations/security">
  Security best practices
</Card>
