#!/usr/bin/env node /** * Schema Sync Validation - Enforces inst_058 * Validates field mappings before synchronizing JSON config to MongoDB * * Usage: node scripts/verify-schema-sync.js */ const fs = require('fs'); const path = require('path'); const mongoose = require('mongoose'); // Known schema mappings that require transformation const KNOWN_MAPPINGS = { 'instruction-history': { // inst_075 has "rules" in JSON but must be "SYSTEM"/"STRATEGIC"/"OPERATIONAL"/"TACTICAL" in DB quadrant: { sourceField: 'quadrant', destField: 'quadrant', transform: (value) => { // Map "rules" to a valid quadrant if (value === 'rules') { console.warn('โš ๏ธ Found "rules" quadrant - must map to SYSTEM/STRATEGIC/OPERATIONAL/TACTICAL'); return null; // Indicates mapping required } return value; }, enumValues: ['SYSTEM', 'STRATEGIC', 'OPERATIONAL', 'TACTICAL'] }, persistence: { sourceField: 'persistence', destField: 'persistence', transform: (value) => value, enumValues: ['HIGH', 'MEDIUM', 'LOW'] } } }; function validateMapping(jsonData, collectionName) { const issues = []; const mapping = KNOWN_MAPPINGS[collectionName]; if (!mapping) { console.log(`\nโš ๏ธ No schema mapping defined for collection: ${collectionName}`); console.log(` Consider adding to KNOWN_MAPPINGS if enum constraints exist.\n`); return { valid: true, issues: [] }; } // For instruction-history, validate each instruction if (collectionName === 'instruction-history') { if (!jsonData.instructions || !Array.isArray(jsonData.instructions)) { issues.push('JSON must have "instructions" array'); return { valid: false, issues }; } jsonData.instructions.forEach((inst, idx) => { // Check quadrant mapping if (inst.quadrant) { const quadrantMapping = mapping.quadrant; const transformed = quadrantMapping.transform(inst.quadrant); if (transformed === null) { issues.push({ instruction: inst.id || `index ${idx}`, field: 'quadrant', value: inst.quadrant, issue: 'Value requires manual mapping', validValues: quadrantMapping.enumValues }); } else if (!quadrantMapping.enumValues.includes(transformed)) { issues.push({ instruction: inst.id || `index ${idx}`, field: 'quadrant', value: transformed, issue: 'Invalid enum value', validValues: quadrantMapping.enumValues }); } } // Check persistence mapping if (inst.persistence) { const persistenceMapping = mapping.persistence; const transformed = persistenceMapping.transform(inst.persistence); if (!persistenceMapping.enumValues.includes(transformed)) { issues.push({ instruction: inst.id || `index ${idx}`, field: 'persistence', value: transformed, issue: 'Invalid enum value', validValues: persistenceMapping.enumValues }); } } }); } return { valid: issues.length === 0, issues }; } function testMappingWithSingleRecord(jsonData, collectionName) { console.log('\n๐Ÿงช Testing mapping with single record (inst_058 requirement)\n'); if (collectionName === 'instruction-history') { const testRecord = jsonData.instructions[0]; if (!testRecord) { console.log('โš ๏ธ No records to test\n'); return true; } console.log(`Testing: ${testRecord.id || 'first record'}`); console.log(` Quadrant: ${testRecord.quadrant}`); console.log(` Persistence: ${testRecord.persistence}`); const mapping = KNOWN_MAPPINGS[collectionName]; if (mapping.quadrant) { const transformed = mapping.quadrant.transform(testRecord.quadrant); console.log(` โ†’ Transformed quadrant: ${transformed}`); if (transformed === null || !mapping.quadrant.enumValues.includes(transformed)) { console.log(` โŒ Mapping would fail for this record\n`); return false; } } console.log(` โœ… Mapping successful\n`); } return true; } async function main() { const jsonFile = process.argv[2]; const collectionName = process.argv[3]; if (!jsonFile || !collectionName) { console.log('Usage: verify-schema-sync.js '); console.log(''); console.log('Example:'); console.log(' verify-schema-sync.js .claude/instruction-history.json instruction-history'); console.log(''); console.log('Enforces inst_058: Validates field mappings before sync operations'); process.exit(0); } console.log('\n๐Ÿ“Š Schema Sync Validation (inst_058)\n'); console.log(`Source: ${jsonFile}`); console.log(`Target Collection: ${collectionName}\n`); // Load JSON file if (!fs.existsSync(jsonFile)) { console.log(`โŒ File not found: ${jsonFile}\n`); process.exit(1); } const jsonData = JSON.parse(fs.readFileSync(jsonFile, 'utf8')); // Validate mappings const validation = validateMapping(jsonData, collectionName); if (!validation.valid) { console.log(`โŒ Found ${validation.issues.length} mapping issue(s):\n`); validation.issues.forEach((issue, idx) => { console.log(`${idx + 1}. ${issue.instruction}`); console.log(` Field: ${issue.field}`); console.log(` Value: "${issue.value}"`); console.log(` Issue: ${issue.issue}`); console.log(` Valid values: ${issue.validValues.join(', ')}\n`); }); console.log('Fix mapping issues before executing sync operation.\n'); process.exit(1); } // Test with single record (inst_058 requirement) const testPassed = testMappingWithSingleRecord(jsonData, collectionName); if (!testPassed) { console.log('โŒ Single record mapping test failed\n'); console.log('Fix mapping functions before batch sync.\n'); process.exit(1); } console.log('โœ… All field mappings validated'); console.log('โœ… Single record test passed'); console.log('\nSafe to proceed with batch sync operation.\n'); process.exit(0); } main();