refactor: remove dynamic config legacy fallback
This commit is contained in:
parent
5bba149a7b
commit
b4d312ce74
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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}` });
|
||||
|
||||
@ -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'}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user