- 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>
5.2 KiB
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
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):
{
"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:
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/pausePOST /internal/trading/resumePOST /internal/trading/revert-backfillGET /api/reconciliation/backfill/auditPOST /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:
- Structured log — always, via
logger.info('[AUDIT] ...'). Zero runtime dependency. - Cosmos
audit-eventscontainer — best-effort, viapersistAuditEvent()inauditRepository.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 }