Replaces the build-time VITE_BACKTEST_ENABLED gate with a fully runtime
flow: a global Cosmos-backed default (already shipped in the existing
dynamicConfig system) plus a new per-user override layer. An admin can
now enable backtest for specific users without flipping the global
switch — useful for staged rollout and beta testers.
Resolution order: per-user override > global config > env fallback.
Both /api/feature-flags (FE display) and /api/backtest/run (server
guard) consult the same merge logic.
Backend (backend/src/...):
~ services/profileRepository.ts
+ TradingUserFeatureFlags interface
+ featureFlags?: TradingUserFeatureFlags on TradingUserProfile
+ setUserFeatureFlags(userId, { backtestEnabled, ... })
~ saveCurrentUserProfile() — strip role + featureFlags from input
so non-admins can't elevate via PATCH /api/me/profile
~ mergeTradingUserProfiles() — preserves explicit flag values only
~ services/apiServer.ts
~ /api/feature-flags merges per-user override into the response
+ /api/admin/users/:userId/feature-flags (GET — overrides + effective)
+ /api/admin/users/:userId/feature-flags (PATCH — admin-only writer)
~ /api/backtest/run resolves effective flags before guarding
~ backtest/index.ts
+ RunBacktestOptions.skipGlobalFeatureFlagCheck
~ runBacktest() honors the override (route already gated stricter)
Frontend (web/src/...):
~ backtest/flags.ts — isBacktestBuildEnabled() now returns true.
Kept as a no-op function so existing callers don't break.
+ lib/userFeatureFlagsApi.ts — typed admin client
+ components/admin/UserFeatureFlagsPanel.tsx
Tri-state picker per flag (Default / On / Off), Look up by user id,
Save/Reset, shows the merged "effective" value.
~ tabs/ConfigTab.tsx — mounts <UserFeatureFlagsPanel /> below the
existing global Backtest Access Control section.
~ layout-fixes.css §27 — styles for the per-user panel.
Tests:
+ testBacktestEngine: skipGlobalFeatureFlagCheck enables per-user
override semantics. 12/12 regression checks pass.
Security note: featureFlags + role are explicitly stripped from
saveCurrentUserProfile input. Only the admin-only PATCH endpoint can
set per-user overrides.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Backend:
- /api/devops/info now requires admin role (was: any authenticated user).
Exposes env keys, dep checks, and socket counts — admin-only by design.
- New /api/devops/version (public, no auth) returns build SHA/branch/image
for ops/CI rollback verification.
- Dep checks: live ping for Cosmos (trading_users) and platform-service.
- Service version read dynamically via readServiceVersion(import.meta.url)
— no more hardcoded '0.1.0'.
- extra: socketIoConnections + tradingApiUrl for runtime debugging.
- saveCurrentUserProfile no longer accepts client-supplied role —
prevents drift with platform JWT (which is authoritative).
Web:
- DevOps tab is now admin-only (gated behind isAdmin like Bot Config and
Admin Panel). Both the section list and content render are guarded.
- Service version baked into bundle via Vite `define` (__WEB_SERVICE_VERSION__)
read from web/package.json — no more hardcoded VERSION constant.
- Bumps @bytelyst/devops dep to ^0.1.2.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>