refactor: share feature flag contract across backend and web

This commit is contained in:
Saravana Achu Mac 2026-04-04 18:10:34 -07:00
parent d99cb94d19
commit 5bba149a7b
7 changed files with 33 additions and 16 deletions

View File

@ -48,6 +48,7 @@ import {
canonicalLifecycleService,
type CanonicalLifecycleProfileMeta
} from './canonicalLifecycleService.js';
import type { TradingFeatureFlagsResponse } from '../../../shared/feature-flags.js';
interface AuthenticatedRequest extends Request {
authUserId?: string;
@ -1645,14 +1646,15 @@ export class ApiServer {
});
this.app.get('/api/feature-flags', this.requireAuth, (_req, res) => {
res.json({
const flags: TradingFeatureFlagsResponse = {
backtest: {
enableBacktest: Boolean(config.ENABLE_BACKTEST),
customerEnabled: Boolean(config.BACKTEST_CUSTOMER_ENABLED),
maxCsvBytes: Number(config.BACKTEST_MAX_CSV_BYTES),
maxRows: Number(config.BACKTEST_MAX_ROWS),
}
});
};
res.json(flags);
});
this.app.get('/api/me/profile', this.requireAuth, async (req, res) => {

View File

@ -4,16 +4,17 @@
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"rootDir": "..",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*"
],
"include": [
"src/**/*",
"../shared/**/*.ts"
],
"exclude": [
"src/test_*"
]
}
}

View File

@ -228,6 +228,7 @@ 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 exchange/order-level correlation-ID propagation are not fully standardized yet
- broader feature-flag ownership beyond the current shared backtest contract is not fully standardized yet
- exchange/order-level correlation-ID propagation is not fully standardized yet
These are follow-up items, not hidden defects. They should remain tracked in `docs/ROADMAP.md`.

View File

@ -42,6 +42,7 @@ It assumes:
- [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, 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] Feature-flag key ownership and response shape for backtest are now shared across backend and web through a common product contract
- [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
- [x] Runtime sub-tag repair now operates through the Cosmos-backed order repository

15
shared/feature-flags.ts Normal file
View File

@ -0,0 +1,15 @@
export const BACKTEST_FLAG_KEYS = {
ENABLE_BACKTEST: 'ENABLE_BACKTEST',
BACKTEST_CUSTOMER_ENABLED: 'BACKTEST_CUSTOMER_ENABLED',
} as const;
export interface BacktestFeatureFlags {
enableBacktest: boolean;
customerEnabled: boolean;
maxCsvBytes?: number;
maxRows?: number;
}
export interface TradingFeatureFlagsResponse {
backtest: BacktestFeatureFlags;
}

View File

@ -1,10 +1,11 @@
import { getPlatformAccessToken } from '../lib/authSession';
import { tradingRuntime } from '../lib/runtime';
import { createRequestId } from '../../../shared/request-id.js';
import type { TradingFeatureFlagsResponse } from '../../../shared/feature-flags.js';
export interface BacktestRuntimeFlags {
enableBacktest: boolean;
customerEnabled: boolean;
enableBacktest: boolean;
customerEnabled: boolean;
}
const CACHE_TTL_MS = 15_000;
@ -64,7 +65,7 @@ export const loadBacktestRuntimeFlags = async (): Promise<BacktestRuntimeFlags>
throw new Error(`Failed to fetch feature flags (${response.status})`);
}
const body = await response.json().catch(() => ({} as any));
const body = await response.json().catch(() => ({} as TradingFeatureFlagsResponse));
const loaded: BacktestRuntimeFlags = {
enableBacktest: toBoolean(body?.backtest?.enableBacktest, false),
customerEnabled: toBoolean(body?.backtest?.customerEnabled, false)

View File

@ -2,6 +2,7 @@ import React from 'react';
import { AlertCircle, CheckCircle2, LayoutDashboard, Info, Zap, Key, ShieldAlert } from 'lucide-react';
import { clearBacktestRuntimeFlagCache } from '../backtest/flags';
import { fetchDynamicConfigItems, upsertDynamicConfigItems } from '../lib/dynamicConfigApi';
import { BACKTEST_FLAG_KEYS } from '../../../shared/feature-flags.js';
interface ConfigItem {
key: string;
@ -14,11 +15,6 @@ interface BacktestFlagState {
customerEnabled: boolean;
}
const BACKTEST_FLAG_KEYS = {
ENABLE_BACKTEST: 'ENABLE_BACKTEST',
BACKTEST_CUSTOMER_ENABLED: 'BACKTEST_CUSTOMER_ENABLED'
} as const;
const readBooleanConfig = (configs: ConfigItem[], key: string, fallback: boolean = false): boolean => {
const row = configs.find((item) => item.key === key);
if (!row) return fallback;