# Backend Admin Audit Schema

## Purpose

This document defines the `TradeAuditEvent` schema used by the trading backend to log all
admin-facing, operator-facing, and safety-critical state changes. Every audit event is
written to the structured logger via `logger.info('[AUDIT] {...}')` with a UTC timestamp
prepended.

Audit events are not yet persisted to Cosmos — they are log-only. Downstream log aggregation
(e.g., Azure Monitor, Log Analytics) is the durable store for audit history.

---

## TradeAuditEvent Schema

```typescript
interface TradeAuditEvent {
  event: string;            // Required. Identifies the audit event type (see catalogue below).
  userId?: string;          // Auth user ID performing or triggering the action.
  profileId?: string;       // Trading profile ID relevant to the action (if applicable).
  symbol?: string;          // Asset symbol relevant to the action (if applicable).
  outcome?: 'accepted'      // Action was applied.
           | 'rejected'     // Action was blocked by a rule or safety guard.
           | 'error';       // Action failed due to a runtime error.
  reason?: string;          // Human-readable explanation for the outcome.
  details?: Record<string, unknown>; // Structured metadata specific to the event type.
}
```

Log line format (written via `logger.info`):

```json
{
  "ts": "2026-04-07T10:00:00.000Z",
  "event": "manual_order_created",
  "userId": "user-abc",
  "profileId": "profile-xyz",
  "symbol": "BTC/USDT",
  "outcome": "accepted",
  "reason": "within risk limits",
  "details": { "side": "BUY", "qty": 0.01, "allocatedCapital": 1000 }
}
```

---

## Event Catalogue

All events are emitted via `auditTradeEvent()` in `backend/src/services/apiServer.ts`.

| Event | Trigger | Key details fields |
|---|---|---|
| `manual_order_created` | POST `/api/orders/manual` — order submitted | side, qty, symbol, profileId |
| `manual_order_rejected` | Manual order blocked by risk/capital guard | reason, guard name |
| `manual_order_error` | Manual order failed at execution | error message |
| `profile_control_create` | Chat AI creates a new trading profile | profileName, allocatedCapital |
| `profile_control_update` | Chat AI updates an existing profile | profileId, updatedFields |
| `profile_control_error` | Chat AI profile action fails | error message |
| `chat_profile_control` | Generic chat-initiated profile action | action type, profileId |
| `trading_paused` | POST `/internal/trading/pause` — admin pauses trading | pausedBy, reason |
| `trading_resumed` | POST `/internal/trading/resume` — admin resumes trading | resumedBy |
| `backfill_reverted` | POST `/internal/trading/revert-backfill` — admin reverts exit backfill | symbol, profileId |
| `reconciliation_audit` | GET `/api/reconciliation/backfill/audit` — admin reads reconciliation audit | — |
| `position_closed_manual` | Admin/operator manually closes a position | symbol, profileId, tradeId |
| `order_failure` | Order execution failure recorded | side, qty, reason, tradeId |

> Not all events use every field. Absent fields are omitted from the log payload.

---

## Audit Middleware

The `auditTradeEvent()` private method in `ApiServer` writes directly to the logger:

```typescript
private auditTradeEvent(evt: TradeAuditEvent): void {
    const payload = { ts: new Date().toISOString(), ...evt };
    logger.info(`[AUDIT] ${JSON.stringify(payload)}`);
}
```

All audit calls are synchronous and never throw — audit failure must not block the primary
operation.

---

## Admin-Scoped Endpoints

Routes that require `requireAdmin` middleware are the primary sources of audit events:

- `POST /internal/trading/pause`
- `POST /internal/trading/resume`
- `POST /internal/trading/revert-backfill`
- `GET /api/reconciliation/backfill/audit`
- `POST /api/admin/dynamic-config`

All admin actions must produce an audit event with `userId` set from `authUserId` on the
authenticated request.

---

## Persistence

Audit events are written to two sinks simultaneously:

1. **Structured log** — always, via `logger.info('[AUDIT] ...')`. Zero runtime dependency.
2. **Cosmos `audit-events` container** — best-effort, via `persistAuditEvent()` in
   `auditRepository.ts`. Silently skipped if Cosmos is not configured or the write fails.

### Activating Cosmos Persistence

Create the container in your Cosmos database:

| Setting | Value |
|---|---|
| Container name | `audit-events` |
| Partition key | `/productId` |
| TTL (Time To Live) | `7776000` seconds (90 days) |
| Throughput | Shared or dedicated — start with 400 RU/s |

Once the container exists and `COSMOS_ENDPOINT` / `COSMOS_KEY` are configured, all
`auditTradeEvent()` calls will persist records automatically.

## Admin Audit Endpoint

`GET /api/admin/audit` (admin-only) queries the `audit-events` container:

```
GET /api/admin/audit?userId=user-123&event=manual_order_created&since=1712500000000&limit=50
```

Query parameters (all optional):

| Parameter | Type | Description |
|---|---|---|
| `userId` | string | Filter by user ID |
| `event` | string | Filter by event name (exact match) |
| `since` | number | Unix epoch ms — return events newer than this timestamp |
| `limit` | number | Max records to return (default 100, max 500) |

Response: `{ records: AuditEventDocument[], count: number }`
