- shared/realtime.ts: add SOCKET_NAMESPACES constants (/trading, /admin, root) - shared/feature-flags.ts: add tabs.marketplace and tabs.membership to TradingFeatureFlagsResponse; add FEATURE_FLAG_KEYS constants - .env.example: remove /api suffix from VITE/NEXT_PUBLIC trading URL vars (web appends /api itself); add tab visibility flag vars with comments - web: add useTabFeatureFlags hook + DOM test; wire tab visibility into App.tsx - web/vite.config.ts: finalize build config - mobile/providers/TradingDataProvider.tsx: deriveSocketParams for proxy-safe socket origin/path resolution (already landed upstream, conflict resolved) - docs: add CUTOVER_WEB.md, CUTOVER_MOBILE.md checklists; update OPERATIONS.md with Docker commands and resolved gap log; update ROADMAP.md to Done; add BACKEND_AUDIT_SCHEMA.md, BACKEND_API_DEPRECATION.md, CONVENTIONS.md; add audit-events container entry to AZURE_INFRASTRUCTURE.md - README.md: full rewrite with workspace table, arch summary, env var reference Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
143 lines
5.2 KiB
Markdown
143 lines
5.2 KiB
Markdown
# 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 }`
|