'use strict'; /** * DRIFT CHECK — compare the committed schema contract against a LIVE database. * * The unit guard proves the retention writer stays inside the contract. This * proves the CONTRACT still describes reality. Both are needed: a correct * writer against a stale contract is the same failure wearing a different hat. * * SUPABASE_URL=... SUPABASE_SERVICE_KEY=... node scripts/verify-schema-contract.js * * Exit 0 when the live table is a superset of the contract (safe: every column * the writer may emit exists). Exit 1 when a contract column is MISSING live — * that is the condition that breaks retention. */ const fs = require('fs'); const path = require('path'); const { createClient } = require('@supabase/supabase-js'); const TABLE = process.env.SCHEMA_TABLE || 'model_snapshots'; async function main() { const contract = JSON.parse(fs.readFileSync( path.join(__dirname, '..', 'supabase', 'schema', `${TABLE}.columns.json`), 'utf8')); const url = process.env.SUPABASE_URL; const key = process.env.SUPABASE_SERVICE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY; if (!url || !key) throw new Error('SUPABASE_URL + service key required'); const sb = createClient(url, key, { auth: { persistSession: false } }); // One row is enough to learn the live column set from the response shape. const { data, error } = await sb.from(TABLE).select('*').limit(1); if (error) throw new Error(`live read failed: ${error.message}`); if (!data || data.length === 0) throw new Error(`${TABLE} is empty — cannot infer live columns`); const live = new Set(Object.keys(data[0])); const missing = contract.columns.filter((c) => !live.has(c)); const extra = [...live].filter((c) => !contract.columns.includes(c)).sort(); console.log(`contract columns : ${contract.columns.length}`); console.log(`live columns : ${live.size}`); console.log(`live-only (informational, safe): ${extra.length ? extra.join(', ') : 'none'}`); console.log(`MISSING LIVE (breaks retention): ${missing.length ? missing.join(', ') : 'none'}`); process.exit(missing.length ? 1 : 0); } if (require.main === module) { main().catch((e) => { console.error('FAILED:', e.message); process.exit(1); }); }