refactor: share feature flag contract across backend and web
This commit is contained in:
parent
d99cb94d19
commit
5bba149a7b
@ -48,6 +48,7 @@ import {
|
|||||||
canonicalLifecycleService,
|
canonicalLifecycleService,
|
||||||
type CanonicalLifecycleProfileMeta
|
type CanonicalLifecycleProfileMeta
|
||||||
} from './canonicalLifecycleService.js';
|
} from './canonicalLifecycleService.js';
|
||||||
|
import type { TradingFeatureFlagsResponse } from '../../../shared/feature-flags.js';
|
||||||
|
|
||||||
interface AuthenticatedRequest extends Request {
|
interface AuthenticatedRequest extends Request {
|
||||||
authUserId?: string;
|
authUserId?: string;
|
||||||
@ -1645,14 +1646,15 @@ export class ApiServer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.app.get('/api/feature-flags', this.requireAuth, (_req, res) => {
|
this.app.get('/api/feature-flags', this.requireAuth, (_req, res) => {
|
||||||
res.json({
|
const flags: TradingFeatureFlagsResponse = {
|
||||||
backtest: {
|
backtest: {
|
||||||
enableBacktest: Boolean(config.ENABLE_BACKTEST),
|
enableBacktest: Boolean(config.ENABLE_BACKTEST),
|
||||||
customerEnabled: Boolean(config.BACKTEST_CUSTOMER_ENABLED),
|
customerEnabled: Boolean(config.BACKTEST_CUSTOMER_ENABLED),
|
||||||
maxCsvBytes: Number(config.BACKTEST_MAX_CSV_BYTES),
|
maxCsvBytes: Number(config.BACKTEST_MAX_CSV_BYTES),
|
||||||
maxRows: Number(config.BACKTEST_MAX_ROWS),
|
maxRows: Number(config.BACKTEST_MAX_ROWS),
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
res.json(flags);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.get('/api/me/profile', this.requireAuth, async (req, res) => {
|
this.app.get('/api/me/profile', this.requireAuth, async (req, res) => {
|
||||||
|
|||||||
@ -4,16 +4,17 @@
|
|||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"rootDir": "./src",
|
"rootDir": "..",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*",
|
||||||
],
|
"../shared/**/*.ts"
|
||||||
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"src/test_*"
|
"src/test_*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
- 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
|
- 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
|
- 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`.
|
These are follow-up items, not hidden defects. They should remain tracked in `docs/ROADMAP.md`.
|
||||||
|
|||||||
@ -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] 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] 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] 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] 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 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
|
- [x] Runtime sub-tag repair now operates through the Cosmos-backed order repository
|
||||||
|
|||||||
15
shared/feature-flags.ts
Normal file
15
shared/feature-flags.ts
Normal 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;
|
||||||
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import { getPlatformAccessToken } from '../lib/authSession';
|
import { getPlatformAccessToken } from '../lib/authSession';
|
||||||
import { tradingRuntime } from '../lib/runtime';
|
import { tradingRuntime } from '../lib/runtime';
|
||||||
import { createRequestId } from '../../../shared/request-id.js';
|
import { createRequestId } from '../../../shared/request-id.js';
|
||||||
|
import type { TradingFeatureFlagsResponse } from '../../../shared/feature-flags.js';
|
||||||
|
|
||||||
export interface BacktestRuntimeFlags {
|
export interface BacktestRuntimeFlags {
|
||||||
enableBacktest: boolean;
|
enableBacktest: boolean;
|
||||||
customerEnabled: boolean;
|
customerEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CACHE_TTL_MS = 15_000;
|
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})`);
|
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 = {
|
const loaded: BacktestRuntimeFlags = {
|
||||||
enableBacktest: toBoolean(body?.backtest?.enableBacktest, false),
|
enableBacktest: toBoolean(body?.backtest?.enableBacktest, false),
|
||||||
customerEnabled: toBoolean(body?.backtest?.customerEnabled, false)
|
customerEnabled: toBoolean(body?.backtest?.customerEnabled, false)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { AlertCircle, CheckCircle2, LayoutDashboard, Info, Zap, Key, ShieldAlert } from 'lucide-react';
|
import { AlertCircle, CheckCircle2, LayoutDashboard, Info, Zap, Key, ShieldAlert } from 'lucide-react';
|
||||||
import { clearBacktestRuntimeFlagCache } from '../backtest/flags';
|
import { clearBacktestRuntimeFlagCache } from '../backtest/flags';
|
||||||
import { fetchDynamicConfigItems, upsertDynamicConfigItems } from '../lib/dynamicConfigApi';
|
import { fetchDynamicConfigItems, upsertDynamicConfigItems } from '../lib/dynamicConfigApi';
|
||||||
|
import { BACKTEST_FLAG_KEYS } from '../../../shared/feature-flags.js';
|
||||||
|
|
||||||
interface ConfigItem {
|
interface ConfigItem {
|
||||||
key: string;
|
key: string;
|
||||||
@ -14,11 +15,6 @@ interface BacktestFlagState {
|
|||||||
customerEnabled: boolean;
|
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 readBooleanConfig = (configs: ConfigItem[], key: string, fallback: boolean = false): boolean => {
|
||||||
const row = configs.find((item) => item.key === key);
|
const row = configs.find((item) => item.key === key);
|
||||||
if (!row) return fallback;
|
if (!row) return fallback;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user