From b4d312ce74adcd68e0519082c0838328db5f8d02 Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Sat, 4 Apr 2026 18:13:41 -0700 Subject: [PATCH] refactor: remove dynamic config legacy fallback --- backend/src/config/index.ts | 7 +- backend/src/index.ts | 4 +- backend/src/services/apiServer.ts | 6 +- .../src/services/dynamicConfigRepository.ts | 119 ++++-------------- docs/OPERATIONS.md | 1 + docs/ROADMAP.md | 1 + 6 files changed, 33 insertions(+), 105 deletions(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index b9858c4..da7e8a3 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -424,13 +424,12 @@ export function applyDynamicConfigEntries(data: Array<{ key: string; value: unkn } /** - * Loads global configuration from Cosmos-first dynamic config storage to override .env defaults. - * Falls back to the legacy Supabase table during migration. + * Loads global configuration from Cosmos-backed dynamic config storage to override .env defaults. */ -export const loadDynamicConfig = async (supabase?: any) => { +export const loadDynamicConfig = async () => { try { logger.info('--- Loading Dynamic Global Config from control-plane storage ---'); - const data = await listDynamicConfigEntries(supabase); + const data = await listDynamicConfigEntries(); if (data && data.length > 0) { const loadedKeys = applyDynamicConfigEntries(data); diff --git a/backend/src/index.ts b/backend/src/index.ts index 7c89847..0eaaf53 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -27,13 +27,13 @@ async function main() { validateConfig(); // --- 0. Primary Account Setup (for Market Data) --- - await loadDynamicConfig(supabaseService); + await loadDynamicConfig(); let dynamicConfigRefreshInFlight = false; const refreshDynamicConfig = async () => { if (dynamicConfigRefreshInFlight) return; dynamicConfigRefreshInFlight = true; try { - await loadDynamicConfig(supabaseService); + await loadDynamicConfig(); } catch (error: any) { logger.error(`[Config] Dynamic refresh failed: ${error.message || error}`); } finally { diff --git a/backend/src/services/apiServer.ts b/backend/src/services/apiServer.ts index 1d266cb..caecb95 100644 --- a/backend/src/services/apiServer.ts +++ b/backend/src/services/apiServer.ts @@ -1969,7 +1969,7 @@ export class ApiServer { this.app.get('/api/admin/config/dynamic', this.requireAuth, this.requireAdmin, async (_req, res) => { try { - const items = await listDynamicConfigEntries(supabaseService); + const items = await listDynamicConfigEntries(); res.json({ items }); } catch (error: any) { res.status(500).json({ error: `Failed to load dynamic config: ${error.message}` }); @@ -1979,9 +1979,9 @@ export class ApiServer { this.app.put('/api/admin/config/dynamic', this.requireAuth, this.requireAdmin, async (req, res) => { try { const items = Array.isArray(req.body?.items) ? req.body.items : []; - await upsertDynamicConfigEntries(items, supabaseService); + await upsertDynamicConfigEntries(items); applyDynamicConfigEntries(items); - await loadDynamicConfig(supabaseService); + await loadDynamicConfig(); res.json({ success: true }); } catch (error: any) { res.status(500).json({ error: `Failed to update dynamic config: ${error.message}` }); diff --git a/backend/src/services/dynamicConfigRepository.ts b/backend/src/services/dynamicConfigRepository.ts index 6301c94..8eb80ac 100644 --- a/backend/src/services/dynamicConfigRepository.ts +++ b/backend/src/services/dynamicConfigRepository.ts @@ -1,7 +1,6 @@ import { getContainer } from '@bytelyst/cosmos'; import { config } from '../config/index.js'; import logger from '../utils/logger.js'; -import type { supabaseService } from './SupabaseService.js'; export interface DynamicConfigEntry { key: string; @@ -16,8 +15,6 @@ interface DynamicConfigDocument extends DynamicConfigEntry { updatedAt: string; } -type LegacySupabaseService = typeof supabaseService; - const CONTAINER_NAME = 'dynamic_config'; function isCosmosConfigured(): boolean { @@ -55,92 +52,21 @@ async function listFromCosmos(): Promise { .filter((entry): entry is DynamicConfigEntry => Boolean(entry)); } -async function listFromSupabase(legacyService?: LegacySupabaseService): Promise { - const client = legacyService?.getClient?.(); - if (!client) return []; - - try { - const { data, error } = await client - .from('bot_config') - .select('*') - .order('key'); - if (error || !Array.isArray(data)) { - return []; - } - return data - .map((row) => normalizeEntry(row as DynamicConfigEntry)) - .filter((entry): entry is DynamicConfigEntry => Boolean(entry)); - } catch (error) { - logger.warn(`[DynamicConfig] Legacy Supabase read failed: ${error instanceof Error ? error.message : 'unknown error'}`); - return []; - } -} - -async function seedCosmosFromLegacy(legacyService?: LegacySupabaseService): Promise { - const legacyEntries = await listFromSupabase(legacyService); - if (!legacyEntries.length) { - return []; - } - - const now = new Date().toISOString(); - const container = getContainer(CONTAINER_NAME); - await Promise.all(legacyEntries.map((entry) => container.items.upsert({ - id: entry.key, - productId: config.PRODUCT_ID, - key: entry.key, - value: entry.value, - description: entry.description || '', - updated_at: entry.updated_at || now, - updatedAt: entry.updated_at || now, - }))); - - logger.info(`[DynamicConfig] Seeded ${legacyEntries.length} config entries from legacy store into Cosmos.`); - return legacyEntries; -} - -async function mirrorToSupabase(entries: DynamicConfigEntry[], legacyService?: LegacySupabaseService): Promise { - const client = legacyService?.getClient?.(); - if (!client || entries.length === 0) return; - - try { - const payload = entries.map((entry) => ({ - key: entry.key, - value: entry.value, - description: entry.description || '', - updated_at: entry.updated_at || new Date().toISOString(), - })); - const { error } = await client - .from('bot_config') - .upsert(payload, { onConflict: 'key' }); - if (error) { - logger.warn(`[DynamicConfig] Legacy Supabase mirror failed: ${error.message}`); - } - } catch (error) { - logger.warn(`[DynamicConfig] Legacy Supabase mirror failed: ${error instanceof Error ? error.message : 'unknown error'}`); - } -} - -export async function listDynamicConfigEntries(legacyService?: LegacySupabaseService): Promise { +export async function listDynamicConfigEntries(): Promise { if (!isCosmosConfigured()) { - return listFromSupabase(legacyService); + logger.warn('[DynamicConfig] Cosmos is not configured; dynamic config overrides are unavailable in this environment.'); + return []; } try { - const cosmosEntries = await listFromCosmos(); - if (cosmosEntries.length > 0) { - return cosmosEntries; - } - return await seedCosmosFromLegacy(legacyService); + return await listFromCosmos(); } catch (error) { - logger.warn(`[DynamicConfig] Cosmos read/seed failed: ${error instanceof Error ? error.message : 'unknown error'}`); + logger.warn(`[DynamicConfig] Cosmos read failed: ${error instanceof Error ? error.message : 'unknown error'}`); return []; } } -export async function upsertDynamicConfigEntries( - entries: DynamicConfigEntry[], - legacyService?: LegacySupabaseService -): Promise { +export async function upsertDynamicConfigEntries(entries: DynamicConfigEntry[]): Promise { const normalized = entries .map((entry) => normalizeEntry(entry)) .filter((entry): entry is DynamicConfigEntry => Boolean(entry)); @@ -150,22 +76,23 @@ export async function upsertDynamicConfigEntries( } const now = new Date().toISOString(); - if (isCosmosConfigured()) { - try { - const container = getContainer(CONTAINER_NAME); - await Promise.all(normalized.map((entry) => container.items.upsert({ - id: entry.key, - productId: config.PRODUCT_ID, - key: entry.key, - value: entry.value, - description: entry.description || '', - updated_at: entry.updated_at || now, - updatedAt: entry.updated_at || now, - }))); - } catch (error) { - logger.warn(`[DynamicConfig] Cosmos upsert failed: ${error instanceof Error ? error.message : 'unknown error'}`); - } + if (!isCosmosConfigured()) { + logger.warn('[DynamicConfig] Cosmos is not configured; skipping dynamic config upsert.'); + return; } - await mirrorToSupabase(normalized, legacyService); + try { + const container = getContainer(CONTAINER_NAME); + await Promise.all(normalized.map((entry) => container.items.upsert({ + id: entry.key, + productId: config.PRODUCT_ID, + key: entry.key, + value: entry.value, + description: entry.description || '', + updated_at: entry.updated_at || now, + updatedAt: entry.updated_at || now, + }))); + } catch (error) { + logger.warn(`[DynamicConfig] Cosmos upsert failed: ${error instanceof Error ? error.message : 'unknown error'}`); + } } diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index ee74717..a1e3fe2 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -74,6 +74,7 @@ Rule: - platform-service and Cosmos are the only supported production systems for this repo - legacy repos may still be consulted as code references, but they are not runtime dependencies - trading user profiles, dynamic config, trading controls, snapshots, capital ledgers, and strategy presets already use Cosmos-backed authority paths +- dynamic config runtime refresh and admin updates no longer seed from or mirror to legacy storage in the active backend runtime path ## Verification Standard diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index e6f19e5..435e526 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -33,6 +33,7 @@ It assumes: - [x] Mobile migrated into `mobile/` with product identity, shared runtime bootstrap, launch-time kill-switch gate, platform-service auth, live backend polling plus websocket-backed updates, startup/error telemetry capture, secure session storage with invalidation handling, and explicit degraded/offline status surfacing - [x] Backend now accepts common-platform JWTs and persists global trading-control state through Cosmos-backed control storage - [x] Dynamic config now flows through backend control-plane APIs with Cosmos-backed storage +- [x] Dynamic config runtime loading and admin writes are now Cosmos-only in the new backend runtime path - [x] Backend snapshots now use a Cosmos-backed repository - [x] Distributed entry and reconciliation locks now use a Cosmos-backed repository - [x] Capital ledger persistence now uses a Cosmos-backed repository