05ed037fe8
- DNS: pilot.landvex.com -> 16.170.83.169 - TLS: Let's Encrypt certificate (expires 2026-09-30) - Nginx: reverse proxy with SSL termination - API: https://pilot.landvex.com/api/v1/missions - UI: https://pilot.landvex.com/ - Upload: POST /api/v1/missions/import (multipart/form-data) Verified: ✅ https://pilot.landvex.com/health ✅ https://pilot.landvex.com/version ✅ https://pilot.landvex.com/api/v1/missions (list) ✅ https://pilot.landvex.com/api/v1/missions/:id (get) ✅ POST /api/v1/missions/import (video upload) ✅ UI loads with title 'LandveX Intelligence Lab' Next: Pilot 001 — Break the system!
402 lines
12 KiB
JavaScript
402 lines
12 KiB
JavaScript
#!/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();
|