feat: add explicit feature flag contract

This commit is contained in:
Saravana Achu Mac 2026-04-04 17:18:20 -07:00
parent 77c7b32ac0
commit 0baf32bfcf
4 changed files with 26 additions and 6 deletions

View File

@ -1644,6 +1644,17 @@ export class ApiServer {
});
});
this.app.get('/api/feature-flags', this.requireAuth, (_req, res) => {
res.json({
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),
}
});
});
this.app.get('/api/me/profile', this.requireAuth, async (req, res) => {
const authReq = req as AuthenticatedRequest;
const authUserId = authReq.authUserId;

View File

@ -109,6 +109,12 @@ pnpm lint
- 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
## Feature Flag Ownership
- backend `GET /api/feature-flags` is the authoritative runtime contract for user-facing feature access
- web feature gates must read explicit feature-flag contracts instead of scraping generic config payloads
- dynamic config may still store the underlying values, but the product surfaces should consume the typed feature-flag API
## Staged Cutover
### Order

View File

@ -40,6 +40,7 @@ It assumes:
- [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] Backtest feature access now reads from an explicit backend feature-flags contract instead of scraping generic runtime config
- [x] Root verification and lint flows now run successfully without sandbox-hostile script harness behavior
- [-] DRY cleanup completed for runtime/config/bootstrap concerns, shared websocket auth helpers, platform-session handling, and request tracing, but not yet for all persistence and feature-flag concerns
- [!] Full common-platform data-plane replacement remains a follow-up where selected trading-record repositories still depend on legacy Supabase storage because Cosmos-native equivalents are not finished yet
@ -178,7 +179,7 @@ Ensure all surfaces adopt one consistent platform model for auth, kill switch, t
- [x] Define ownership split between product accessibility controls and trading-behavior controls
- [x] Define telemetry envelope fields
- [x] Define correlation ID and request propagation strategy
- [ ] Define feature flag ownership and evaluation model
- [x] Define feature flag ownership and evaluation model
- [x] Define system-of-record ownership by concern
- [x] Define degraded-platform fallback behavior
- [x] Define transitional adapters needed for legacy auth flows

View File

@ -1,5 +1,6 @@
import { getPlatformAccessToken } from '../lib/authSession';
import { tradingRuntime } from '../lib/runtime';
import { createRequestId } from '../../../shared/request-id.js';
export interface BacktestRuntimeFlags {
enableBacktest: boolean;
@ -53,19 +54,20 @@ export const loadBacktestRuntimeFlags = async (): Promise<BacktestRuntimeFlags>
}
const apiUrl = tradingRuntime.tradingApiUrl;
const response = await fetch(`${apiUrl}/api/config`, {
const response = await fetch(`${apiUrl}/api/feature-flags`, {
headers: {
Authorization: `Bearer ${accessToken}`
Authorization: `Bearer ${accessToken}`,
'x-request-id': createRequestId('web-flags')
}
});
if (!response.ok) {
throw new Error(`Failed to fetch runtime config (${response.status})`);
throw new Error(`Failed to fetch feature flags (${response.status})`);
}
const body = await response.json().catch(() => ({} as any));
const loaded: BacktestRuntimeFlags = {
enableBacktest: toBoolean(body?.ENABLE_BACKTEST, false),
customerEnabled: toBoolean(body?.BACKTEST_CUSTOMER_ENABLED, false)
enableBacktest: toBoolean(body?.backtest?.enableBacktest, false),
customerEnabled: toBoolean(body?.backtest?.customerEnabled, false)
};
runtimeFlagsCache = loaded;