diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index 94131c2..351d0f1 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -98,7 +98,7 @@ pnpm lint ## Request Tracing -- the main web and mobile API paths now attach `x-request-id` +- the main web and mobile API paths, operator actions, and lifecycle fetches now attach `x-request-id` - backend HTTP responses echo `x-request-id` so browser/app logs can be correlated with backend logs - during incident review, treat `x-request-id` as the primary request correlation key across client and backend traces @@ -228,6 +228,6 @@ Manual mobile release smoke is still required before broad rollout: - web now uses platform-session handling end to end; the remaining auth cleanup is removing dormant compatibility stubs and aligning profile bootstrap contracts fully with backend-owned product APIs - root `pnpm verify` is green again after aligning the web Vitest harness with platform-session storage and current API contracts - mobile does not yet include push notification infrastructure -- feature-flag ownership and correlation-ID propagation are not fully standardized yet +- feature-flag ownership and exchange/order-level correlation-ID propagation are not fully standardized yet These are follow-up items, not hidden defects. They should remain tracked in `docs/ROADMAP.md`. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 3ab86ae..9bef15c 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -40,7 +40,7 @@ It assumes: - [x] Backend risk and PnL aggregate reads now flow through repository abstractions instead of direct legacy service calls - [x] Web history, profile, marketplace, config, and manual-entry flows now run through backend APIs instead of browser-side table access - [x] Release smoke coverage now exists for web auth and product accessibility flows, with a tracked mobile release smoke checklist in operations -- [x] Request ID propagation is now standardized across the main web/mobile API paths and echoed by backend HTTP responses +- [x] Request ID propagation is now standardized across the main web/mobile API paths, operator actions, lifecycle fetches, and backend HTTP responses - [x] Backtest feature access now reads from an explicit backend feature-flags contract instead of scraping generic runtime config - [x] Trading user profiles and marketplace presets now have Cosmos-backed authority paths - [x] Runtime order, trade-history, manual-entry, order-activity, and reconciliation-audit repositories now use Cosmos-backed trading-record storage instead of the legacy service layer diff --git a/mobile/providers/TradingDataProvider.tsx b/mobile/providers/TradingDataProvider.tsx index 7b77cb4..08d9931 100644 --- a/mobile/providers/TradingDataProvider.tsx +++ b/mobile/providers/TradingDataProvider.tsx @@ -5,6 +5,7 @@ import { mobileRuntime } from '@/lib/runtime'; import { mobileTelemetry, trackMobileError } from '@/lib/telemetry'; import { useMobileAuth } from '@/providers/MobileAuthProvider'; import { buildTradingSocketOptions, isUnauthorizedSocketError } from '../../shared/realtime.js'; +import { createRequestId } from '../../shared/request-id.js'; type HealthSnapshot = { tradingControl?: { @@ -165,6 +166,7 @@ export function TradingDataProvider({ children }: { children: ReactNode }) { const response = await fetch(`${mobileRuntime.tradingApiUrl}/state`, { headers: { Authorization: `Bearer ${accessToken}`, + 'x-request-id': createRequestId('mobile-state'), }, }); @@ -335,6 +337,7 @@ export function TradingDataProvider({ children }: { children: ReactNode }) { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, + 'x-request-id': createRequestId('mobile-control'), }, body: JSON.stringify({ reason: reason || 'Requested from mobile trading surface', diff --git a/web/src/components/ChatControl.tsx b/web/src/components/ChatControl.tsx index 133dbe7..f124ef3 100644 --- a/web/src/components/ChatControl.tsx +++ b/web/src/components/ChatControl.tsx @@ -2,6 +2,7 @@ import { useState, useRef, useEffect, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { tradingRuntime } from '../lib/runtime'; import { getPlatformAccessToken } from '../lib/authSession'; +import { createRequestId } from '../../../shared/request-id.js'; import { Send, X, Bot, User, Check, Loader2, @@ -204,7 +205,8 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}` + 'Authorization': `Bearer ${accessToken}`, + 'x-request-id': createRequestId('web-chat') }, body: JSON.stringify({ message: msg, diff --git a/web/src/components/EntryForm.tsx b/web/src/components/EntryForm.tsx index 8010714..1a4a404 100644 --- a/web/src/components/EntryForm.tsx +++ b/web/src/components/EntryForm.tsx @@ -4,6 +4,7 @@ import { useAuth } from '../components/AuthContext'; import { tradingRuntime } from '../lib/runtime'; import { createManualEntry, updateManualEntry } from '../lib/manualEntriesApi'; import { getPlatformAccessToken } from '../lib/authSession'; +import { createRequestId } from '../../../shared/request-id.js'; interface EntryFormProps { onSuccess: () => void; @@ -129,7 +130,8 @@ export function EntryForm({ onSuccess, initialData }: EntryFormProps) { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}` + 'Authorization': `Bearer ${accessToken}`, + 'x-request-id': createRequestId('web-entry') }, body: JSON.stringify(apiPayload) }); diff --git a/web/src/lib/canonicalLifecycleApi.ts b/web/src/lib/canonicalLifecycleApi.ts index c2d91e4..d135fd6 100644 --- a/web/src/lib/canonicalLifecycleApi.ts +++ b/web/src/lib/canonicalLifecycleApi.ts @@ -1,4 +1,5 @@ import { tradingRuntime } from './runtime'; +import { createRequestId } from '../../../shared/request-id.js'; export type CanonicalLifecycleState = 'OPEN' | 'PARTIAL_EXIT' | 'CLOSED' | 'ORPHAN_EXIT'; export type CanonicalSide = 'BUY' | 'SELL'; @@ -145,7 +146,8 @@ export const fetchCanonicalLifecycleSnapshot = async (args: { const response = await fetch(endpoint, { headers: { - Authorization: `Bearer ${token}` + Authorization: `Bearer ${token}`, + 'x-request-id': createRequestId('web-lifecycle') } }); diff --git a/web/src/tabs/AdminTab.tsx b/web/src/tabs/AdminTab.tsx index d289652..f8c1a41 100644 --- a/web/src/tabs/AdminTab.tsx +++ b/web/src/tabs/AdminTab.tsx @@ -16,6 +16,7 @@ import { useAuth } from '../components/AuthContext'; import { tradingRuntime } from '../lib/runtime'; import { fetchDynamicConfigItems, upsertDynamicConfigItems } from '../lib/dynamicConfigApi'; import { getPlatformAccessToken } from '../lib/authSession'; +import { createRequestId } from '../../../shared/request-id.js'; interface AdminTabProps { botState: BotState; @@ -158,7 +159,8 @@ export const AdminTab = ({ botState }: AdminTabProps) => { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'x-request-id': createRequestId('web-admin') }, body: JSON.stringify({ reason: 'Admin pause from dashboard' }) }); @@ -183,7 +185,8 @@ export const AdminTab = ({ botState }: AdminTabProps) => { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'x-request-id': createRequestId('web-admin') }, body: JSON.stringify({ reason: 'Admin resume from dashboard' }) }); @@ -208,7 +211,8 @@ export const AdminTab = ({ botState }: AdminTabProps) => { method: 'DELETE', headers: { 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'x-request-id': createRequestId('web-admin') } }); if (!res.ok) { @@ -230,7 +234,8 @@ export const AdminTab = ({ botState }: AdminTabProps) => { if (!accessToken) return; const res = await fetch(`${apiUrl}/api/config`, { headers: { - 'Authorization': `Bearer ${accessToken}` + 'Authorization': `Bearer ${accessToken}`, + 'x-request-id': createRequestId('web-admin') } }); if (res.ok) { diff --git a/web/src/tabs/PositionsTab.tsx b/web/src/tabs/PositionsTab.tsx index bcf7a03..8e4b5b8 100644 --- a/web/src/tabs/PositionsTab.tsx +++ b/web/src/tabs/PositionsTab.tsx @@ -3,6 +3,7 @@ import type { BotState } from '../hooks/useWebSocket'; import { getPlatformAccessToken } from '../lib/authSession'; import { tradingRuntime } from '../lib/runtime'; import { useAuth } from '../components/AuthContext'; +import { createRequestId } from '../../../shared/request-id.js'; import { Layers, ListFilter, Link2, GitBranch, AlertTriangle, Lock, RefreshCw, CheckCircle, XCircle } from 'lucide-react'; import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle'; import { fetchPositionsBootstrap } from '../lib/positionsApi'; @@ -1370,9 +1371,10 @@ export const PositionsTab = ({ botState }: PositionsTabProps) => { const response = await fetch(`${tradingRuntime.tradingApiUrl}/api/close`, { method: 'POST', headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}` - }, + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${accessToken}`, + 'x-request-id': createRequestId('web-close') + }, body: JSON.stringify({ profile_id: pos.profileId, symbol: pos.symbol diff --git a/web/src/tabs/ReconciliationAuditPanel.tsx b/web/src/tabs/ReconciliationAuditPanel.tsx index 577bce2..4d151fc 100644 --- a/web/src/tabs/ReconciliationAuditPanel.tsx +++ b/web/src/tabs/ReconciliationAuditPanel.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { AlertTriangle, Clock3, RefreshCcw, Search, ShieldCheck, Undo2 } from 'lucide-react'; import { tradingRuntime } from '../lib/runtime'; import { getPlatformAccessToken } from '../lib/authSession'; +import { createRequestId } from '../../../shared/request-id.js'; interface ReconciliationBackfillAuditRow { id: number; @@ -167,13 +168,22 @@ export const ReconciliationAuditPanel = () => { const [auditResponse, batchResponse, manualReviewResponse] = await Promise.all([ fetch(`${apiUrl}/api/reconciliation/backfill/audit?${auditParams.toString()}`, { - headers: { Authorization: `Bearer ${accessToken}` } + headers: { + Authorization: `Bearer ${accessToken}`, + 'x-request-id': createRequestId('web-recon') + } }), fetch(`${apiUrl}/api/reconciliation/backfill/batches?${batchParams.toString()}`, { - headers: { Authorization: `Bearer ${accessToken}` } + headers: { + Authorization: `Bearer ${accessToken}`, + 'x-request-id': createRequestId('web-recon') + } }), fetch(`${apiUrl}/api/reconciliation/backfill/audit?${manualReviewParams.toString()}`, { - headers: { Authorization: `Bearer ${accessToken}` } + headers: { + Authorization: `Bearer ${accessToken}`, + 'x-request-id': createRequestId('web-recon') + } }) ]); @@ -266,7 +276,8 @@ export const ReconciliationAuditPanel = () => { method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + 'x-request-id': createRequestId('web-recon') }, body: JSON.stringify({ batchId }) });