feat: standardize request ids across operator flows

This commit is contained in:
Saravana Achu Mac 2026-04-04 18:07:43 -07:00
parent ffa60fcfb7
commit d99cb94d19
9 changed files with 44 additions and 17 deletions

View File

@ -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`.

View File

@ -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

View File

@ -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',

View File

@ -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,

View File

@ -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)
});

View File

@ -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')
}
});

View File

@ -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) {

View File

@ -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';
@ -1371,7 +1372,8 @@ export const PositionsTab = ({ botState }: PositionsTabProps) => {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
'Authorization': `Bearer ${accessToken}`,
'x-request-id': createRequestId('web-close')
},
body: JSON.stringify({
profile_id: pos.profileId,

View File

@ -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 })
});