refactor: remove dynamic config legacy fallback

This commit is contained in:
Saravana Achu Mac 2026-04-04 18:13:41 -07:00
parent 5bba149a7b
commit b4d312ce74
6 changed files with 33 additions and 105 deletions

View File

@ -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);

View File

@ -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 {

View File

@ -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}` });

View File

@ -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<DynamicConfigEntry[]> {
.filter((entry): entry is DynamicConfigEntry => Boolean(entry));
}
async function listFromSupabase(legacyService?: LegacySupabaseService): Promise<DynamicConfigEntry[]> {
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<DynamicConfigEntry[]> {
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<DynamicConfigDocument>({
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<void> {
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<DynamicConfigEntry[]> {
export async function listDynamicConfigEntries(): Promise<DynamicConfigEntry[]> {
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<void> {
export async function upsertDynamicConfigEntries(entries: DynamicConfigEntry[]): Promise<void> {
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<DynamicConfigDocument>({
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<DynamicConfigDocument>({
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'}`);
}
}

View File

@ -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

View File

@ -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