From 0baf32bfcff8fe8262a4cbabfd9c26197e0560be Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Sat, 4 Apr 2026 17:18:20 -0700 Subject: [PATCH] feat: add explicit feature flag contract --- backend/src/services/apiServer.ts | 11 +++++++++++ docs/OPERATIONS.md | 6 ++++++ docs/ROADMAP.md | 3 ++- web/src/backtest/flags.ts | 12 +++++++----- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/backend/src/services/apiServer.ts b/backend/src/services/apiServer.ts index e8a67e5..21f2600 100644 --- a/backend/src/services/apiServer.ts +++ b/backend/src/services/apiServer.ts @@ -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; diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index fec01b2..7cce14d 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -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 diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 3078895..ce9d1a0 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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 diff --git a/web/src/backtest/flags.ts b/web/src/backtest/flags.ts index 17d9229..5c6b915 100644 --- a/web/src/backtest/flags.ts +++ b/web/src/backtest/flags.ts @@ -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 } 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;