import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const repoRoot = __dirname; const schemaDir = path.join(repoRoot, 'schema'); const schemaFiles = fs .readdirSync(schemaDir) .filter((name) => name.endsWith('.sql')) .sort(); const combinedSql = schemaFiles .map((name) => fs.readFileSync(path.join(schemaDir, name), 'utf8').toLowerCase()) .join('\n'); function assertSql(pattern: RegExp, message: string) { assert(pattern.test(combinedSql), message); } // Core tables used by runtime. assertSql(/\bcreate\s+table\s+if\s+not\s+exists\s+trade_profiles\b/, 'Missing trade_profiles table definition'); assertSql(/\bcreate\s+table\s+if\s+not\s+exists\s+bot_config\b/, 'Missing bot_config table definition'); assertSql(/\bcreate\s+table\s+if\s+not\s+exists\s+dynamic_config\b/, 'Missing dynamic_config table definition'); // Orders columns controlled by local migrations. assertSql(/\balter\s+table\s+orders\b/, 'orders migration statements missing'); assertSql(/\borders\b[\s\S]*\bprofile_id\b/, 'orders.profile_id not found in migration contract'); assertSql(/\borders\b[\s\S]*\bstop_loss\b/, 'orders.stop_loss not found in migration contract'); assertSql(/\borders\b[\s\S]*\btake_profit\b/, 'orders.take_profit not found in migration contract'); assertSql(/\borders\b[\s\S]*\baction\b/, 'orders.action not found in migration contract'); assertSql(/\borders\b[\s\S]*\btrade_id\b/, 'orders.trade_id not found in migration contract'); assertSql(/\borders\b[\s\S]*\bsub_tag\b/, 'orders.sub_tag not found in migration contract'); // Capital ledger reserve formula must include realized_pnl to match runtime available-capital math. assertSql( /\bcreate\s+or\s+replace\s+function\s+fn_reserve_for_order\b[\s\S]*allocated_capital\s*\+\s*realized_pnl[\s\S]*reserved_for_orders[\s\S]*reserved_for_positions[\s\S]*>=\s*p_amount/, 'fn_reserve_for_order must gate reservation using allocated_capital + realized_pnl - reserved balances' ); // Trade history columns used by dashboard/bot traceability. assertSql(/\btrade_history\b[\s\S]*\bprofile_id\b/, 'trade_history.profile_id not found in migration contract'); assertSql(/\btrade_history\b[\s\S]*\brules_metadata\b/, 'trade_history.rules_metadata not found in migration contract'); assertSql(/\btrade_history\b[\s\S]*\btrade_id\b/, 'trade_history.trade_id not found in migration contract'); assertSql(/\btrade_history\b[\s\S]*\bsource\b/, 'trade_history.source not found in migration contract'); // RLS presence for critical user-scoped tables. assertSql(/\balter\s+table\s+trade_profiles\s+enable\s+row\s+level\s+security\b/, 'trade_profiles RLS enable statement missing'); assertSql(/\bcreate\s+policy\b[\s\S]*trade_profiles\b/, 'trade_profiles policy definition missing'); console.log(`[schema-contract] OK: validated ${schemaFiles.length} schema files`);