#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // SIL Validation — Fråga 3: Hur robust är SIL mot förändring? // Simulerar refactoring och mäter degradation // ═══════════════════════════════════════════════════════════════════════════ import { execSync } from 'child_process'; import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; import { createHash } from 'crypto'; const TEST_DIR = '/home/bernt/.openclaw/workspace/SIL/validation/robustness-tests'; const RESULTS_LOG = '/home/bernt/.openclaw/workspace/SIL/validation/robustness-results.jsonl'; class RobustnessTester { constructor(repoPath) { this.repoPath = repoPath; if (!existsSync(TEST_DIR)) { mkdirSync(TEST_DIR, { recursive: true }); } } /** * Kör alla robusthetstester */ runAllTests() { console.log('🧪 Kör robusthetstester...\n'); const tests = [ { name: 'rename_service', fn: this.testRenameService.bind(this) }, { name: 'move_code', fn: this.testMoveCode.bind(this) }, { name: 'split_module', fn: this.testSplitModule.bind(this) }, { name: 'add_dependency', fn: this.testAddDependency.bind(this) }, { name: 'change_structure', fn: this.testChangeStructure.bind(this) }, { name: 'introduce_violation', fn: this.testIntroduceViolation.bind(this) } ]; const results = []; for (const test of tests) { console.log(`🔍 ${test.name}...`); try { const result = test.fn(); results.push({ name: test.name, ...result }); console.log(` ${result.passed ? '✅' : '❌'} Degradation: ${result.degradation}`); } catch (e) { results.push({ name: test.name, error: e.message, passed: false }); console.log(` ❌ Fel: ${e.message}`); } } const report = this.generateReport(results); this.saveResults(results); this.printReport(report); return report; } /** * Test 1: Byt namn på en tjänst */ testRenameService() { // Simulera: byt namn på "wallet" till "payment" const before = this.runAnalysis(); // Simulerad förändring: ändra i grafen const modifiedGraph = this.simulateRename('wallet', 'payment'); const after = this.runAnalysisWithGraph(modifiedGraph); return this.compareResults(before, after, 'rename_service'); } /** * Test 2: Flytta kod mellan paket */ testMoveCode() { const before = this.runAnalysis(); // Simulera: flytta auth-kod till nytt paket const modifiedGraph = this.simulateMove('auth', 'auth-v2'); const after = this.runAnalysisWithGraph(modifiedGraph); return this.compareResults(before, after, 'move_code'); } /** * Test 3: Dela upp modul */ testSplitModule() { const before = this.runAnalysis(); // Simulera: dela upp mission i mission-core och mission-api const modifiedGraph = this.simulateSplit('mission', ['mission-core', 'mission-api']); const after = this.runAnalysisWithGraph(modifiedGraph); return this.compareResults(before, after, 'split_module'); } /** * Test 4: Lägg till nytt beroende */ testAddDependency() { const before = this.runAnalysis(); // Simulera: lägg till Redis-beroende const modifiedGraph = this.simulateAddDependency('wallet', 'redis'); const after = this.runAnalysisWithGraph(modifiedGraph); return this.compareResults(before, after, 'add_dependency'); } /** * Test 5: Ändra katalogstruktur */ testChangeStructure() { const before = this.runAnalysis(); // Simulera: flytta alla services till src/services/ const modifiedGraph = this.simulateRestructure(); const after = this.runAnalysisWithGraph(modifiedGraph); return this.compareResults(before, after, 'change_structure'); } /** * Test 6: Introducera avsiktlig arkitekturavvikelse */ testIntroduceViolation() { const before = this.runAnalysis(); // Simulera: lägg till direkt DB-anrop i UI const modifiedGraph = this.simulateViolation(); const after = this.runAnalysisWithGraph(modifiedGraph); return this.compareResults(before, after, 'introduce_violation'); } // ── Simuleringar ──────────────────────────────────────────────────────── simulateRename(oldName, newName) { const graph = this.loadGraph(); // Byt namn på noder for (const node of graph.nodes) { if (node.id === oldName) { node.id = newName; node.label = newName; } } // Uppdatera kanter for (const edge of graph.edges) { if (edge.from === oldName) edge.from = newName; if (edge.to === oldName) edge.to = newName; } return graph; } simulateMove(oldLocation, newLocation) { const graph = this.loadGraph(); // Lägg till ny nod graph.nodes.push({ id: newLocation, label: newLocation, type: 'service' }); // Flytta kanter for (const edge of graph.edges) { if (edge.from === oldLocation) { edge.from = newLocation; } } return graph; } simulateSplit(moduleName, newModules) { const graph = this.loadGraph(); // Ta bort gammal nod graph.nodes = graph.nodes.filter(n => n.id !== moduleName); // Lägg till nya noder for (const mod of newModules) { graph.nodes.push({ id: mod, label: mod, type: 'service' }); } // Uppdatera kanter for (const edge of graph.edges) { if (edge.from === moduleName) edge.from = newModules[0]; if (edge.to === moduleName) edge.to = newModules[0]; } return graph; } simulateAddDependency(from, to) { const graph = this.loadGraph(); graph.edges.push({ from, to, relation: 'depends_on', confidence: 0.8 }); return graph; } simulateRestructure() { const graph = this.loadGraph(); // Simulera att alla services får nytt prefix for (const node of graph.nodes) { if (node.type === 'service') { node.id = `src/services/${node.id}`; } } return graph; } simulateViolation() { const graph = this.loadGraph(); // Lägg till en förbjuden kant: UI → DB graph.edges.push({ from: 'app', to: 'database', relation: 'direct_access', confidence: 0.99 }); return graph; } // ── Analys ────────────────────────────────────────────────────────────── runAnalysis() { try { const output = execSync( `node /home/bernt/.openclaw/workspace/SIL/pr-analyzer.mjs ${this.repoPath} HEAD~1 HEAD 2>&1`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 } ); return this.parseAnalysis(output); } catch (e) { return { error: e.message }; } } runAnalysisWithGraph(graph) { // Spara temporär graf const tempPath = `${TEST_DIR}/temp-graph.json`; writeFileSync(tempPath, JSON.stringify(graph)); try { const output = execSync( `node /home/bernt/.openclaw/workspace/SIL/pr-analyzer.mjs ${this.repoPath} HEAD~1 HEAD 2>&1`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 } ); return this.parseAnalysis(output); } catch (e) { return { error: e.message }; } } parseAnalysis(output) { // Extrahera nyckeldata från output const components = []; const lines = output.split('\n'); for (const line of lines) { const match = line.match(/✅\s+(.+)/); if (match && !line.includes('Körning')) { components.push(match[1].trim()); } } return { components }; } loadGraph() { try { return JSON.parse(readFileSync('/home/bernt/.openclaw/workspace/SYSTEM_GRAPH.json', 'utf8')); } catch { return { nodes: [], edges: [] }; } } // ── Jämförelse ────────────────────────────────────────────────────────── compareResults(before, after, testName) { if (before.error || after.error) { return { testName, passed: false, degradation: '100%', reason: before.error || after.error }; } const beforeComponents = new Set(before.components || []); const afterComponents = new Set(after.components || []); const intersection = [...beforeComponents].filter(c => afterComponents.has(c)); const union = new Set([...beforeComponents, ...afterComponents]); const similarity = union.size > 0 ? intersection.length / union.size : 1; const degradation = Math.round((1 - similarity) * 100) + '%'; return { testName, passed: similarity >= 0.9, // Max 10% degradation degradation, similarity: Math.round(similarity * 100) + '%', before: before.components?.length || 0, after: after.components?.length || 0 }; } // ── Rapport ───────────────────────────────────────────────────────────── generateReport(results) { const passed = results.filter(r => r.passed).length; const total = results.length; const avgDegradation = results .filter(r => !r.error) .reduce((a, r) => a + parseFloat(r.degradation), 0) / total; return { timestamp: new Date().toISOString(), totalTests: total, passed, failed: total - passed, avgDegradation: Math.round(avgDegradation) + '%', meetsCriteria: avgDegradation < 10, results }; } printReport(report) { console.log('\n╔═══════════════════════════════════════════════════════════════╗'); console.log('║ VALIDATION: FRÅGA 3 ║'); console.log('║ Hur robust är SIL mot förändring? ║'); console.log('╚═══════════════════════════════════════════════════════════════╝'); console.log(); console.log('📊 RESULTAT\n'); console.log(` Tester: ${report.totalTests}`); console.log(` Godkända: ${report.passed} ✅`); console.log(` Misslyckade: ${report.failed} ❌`); console.log(` Genomsnittlig degradation: ${report.avgDegradation}`); console.log(); console.log('📋 PER TEST\n'); for (const result of report.results) { const icon = result.passed ? '✅' : '❌'; console.log(` ${icon} ${result.testName}`); console.log(` Degradation: ${result.degradation}`); console.log(` Similarity: ${result.similarity || 'N/A'}`); if (result.reason) { console.log(` Fel: ${result.reason}`); } } console.log(); console.log('🎯 SUCCESS-KRITERIUM\n'); console.log(` Krav: <10% degradation vid refactoring`); console.log(` Resultat: ${report.avgDegradation}`); console.log(` ${report.meetsCriteria ? '✅ UPPFYLLT' : '❌ EJ UPPFYLLT'}`); console.log(); console.log('═══════════════════════════════════════════════════════════════\n'); } saveResults(results) { for (const result of results) { writeFileSync(RESULTS_LOG, JSON.stringify(result) + '\n', { flag: 'a' }); } } } // ── Main ────────────────────────────────────────────────────────────────── const repoPath = process.argv[2] || '/home/bernt/repos/quixzoom.com'; const tester = new RobustnessTester(repoPath); tester.runAllTests();