Files
boc/graph-engine.mjs
T
Bernt 05ed037fe8 pilot.landvex.com: HTTPS + Full Stack Verified
- 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!
2026-07-02 17:34:19 +00:00

378 lines
12 KiB
JavaScript
Executable File

#!/usr/bin/env node
// ═══════════════════════════════════════════════════════════════════════════
// System Graph Reasoning Engine
// Traverserar kunskapsgrafen för att besvara frågor om systemet
// ═══════════════════════════════════════════════════════════════════════════
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const graphPath = join(__dirname, 'SYSTEM_GRAPH.json');
let graph;
try {
graph = JSON.parse(readFileSync(graphPath, 'utf8'));
} catch (e) {
console.error('❌ Kunde inte ladda SYSTEM_GRAPH.json:', e.message);
process.exit(1);
}
// ── Hjälpfunktioner ───────────────────────────────────────────────────────
function getNode(id) {
return graph.nodes.find(n => n.id === id);
}
function getEdgesFrom(nodeId) {
return graph.edges.filter(e => e.from === nodeId);
}
function getEdgesTo(nodeId) {
return graph.edges.filter(e => e.to === nodeId);
}
function getRelatedNodes(nodeId, direction = 'both') {
const related = new Set();
if (direction === 'from' || direction === 'both') {
getEdgesFrom(nodeId).forEach(e => related.add(e.to));
}
if (direction === 'to' || direction === 'both') {
getEdgesTo(nodeId).forEach(e => related.add(e.from));
}
return Array.from(related).map(id => getNode(id)).filter(Boolean);
}
// ── Traversering ──────────────────────────────────────────────────────────
function traverse(nodeId, visited = new Set(), depth = 0, maxDepth = 10) {
if (depth > maxDepth || visited.has(nodeId)) return [];
visited.add(nodeId);
const node = getNode(nodeId);
if (!node) return [];
const edges = getEdgesFrom(nodeId);
const results = [{ node, depth, edges }];
for (const edge of edges) {
results.push(...traverse(edge.to, visited, depth + 1, maxDepth));
}
return results;
}
function reverseTraverse(nodeId, visited = new Set(), depth = 0, maxDepth = 10) {
if (depth > maxDepth || visited.has(nodeId)) return [];
visited.add(nodeId);
const node = getNode(nodeId);
if (!node) return [];
const edges = getEdgesTo(nodeId);
const results = [{ node, depth, edges }];
for (const edge of edges) {
results.push(...reverseTraverse(edge.from, visited, depth + 1, maxDepth));
}
return results;
}
// ── Query-handlers ────────────────────────────────────────────────────────
function impactAnalysis(componentId) {
console.log(`\n🔍 IMPACT ANALYSIS: ${componentId}\n`);
const node = getNode(componentId);
if (!node) {
console.log(`❌ Komponent "${componentId}" hittades inte i grafen`);
return;
}
console.log(`📍 Komponent: ${node.label} (${node.type})`);
if (node.critical) console.log(`⚠️ KRITISK KOMPONENT`);
console.log('');
const impacted = traverse(componentId);
const byDepth = {};
impacted.forEach(({ node, depth }) => {
if (depth === 0) return; // Skip self
if (!byDepth[depth]) byDepth[depth] = [];
byDepth[depth].push(node);
});
console.log('📊 Påverkade komponenter:\n');
for (const [depth, nodes] of Object.entries(byDepth)) {
console.log(` Nivå ${depth} (direkt${depth > 1 ? ' + indirekt' : ''}):`);
nodes.forEach(n => {
const icon = n.critical ? '🔴' : n.type === 'actor' ? '👤' : n.type === 'service' ? '⚙️' : n.type === 'database' ? '🗄️' : '📦';
console.log(` ${icon} ${n.label} (${n.type})${n.critical ? ' [KRITISK]' : ''}`);
});
console.log('');
}
// Affärsprocesser som påverkas
const businessProcesses = impacted.filter(({ node }) => node.type === 'business_process');
if (businessProcesses.length > 0) {
console.log('🏭 Affärsprocesser som påverkas:');
businessProcesses.forEach(({ node }) => {
console.log(`${node.label}`);
});
console.log('');
}
// Tester som behövs
const services = impacted.filter(({ node }) => node.type === 'service');
if (services.length > 0) {
console.log('🧪 Tester som måste köras:');
services.forEach(({ node }) => {
if (node.endpoints) {
console.log(`${node.label}:`);
node.endpoints.forEach(ep => console.log(` - ${ep}`));
}
});
console.log('');
}
}
function failureAnalysis(componentId) {
console.log(`\n💥 FAILURE ANALYSIS: ${componentId}\n`);
const node = getNode(componentId);
if (!node) {
console.log(`❌ Komponent "${componentId}" hittades inte`);
return;
}
console.log(`📍 Komponent som fallerar: ${node.label}\n`);
// Hitta alla noder som ENDAST kan nås via denna komponent
const allNodes = graph.nodes.filter(n => n.id !== componentId);
const cutOff = [];
for (const target of allNodes) {
const paths = findAllPaths(target.id, componentId);
if (paths.length === 0) continue;
// Kolla om det finns vägar som INTE går via componentId
const alternativePaths = paths.filter(p => !p.includes(componentId));
if (alternativePaths.length === 0) {
cutOff.push(target);
}
}
if (cutOff.length === 0) {
console.log('✅ Ingen komponent är helt beroende av denna. Systemet har redundans.');
return;
}
console.log('❌ Komponenter som BLIR OTILLGÄNGLIGA:\n');
const byType = {};
cutOff.forEach(n => {
if (!byType[n.type]) byType[n.type] = [];
byType[n.type].push(n);
});
for (const [type, nodes] of Object.entries(byType)) {
const icon = type === 'actor' ? '👤' : type === 'service' ? '⚙️' : type === 'business_process' ? '🏭' : type === 'database' ? '🗄️' : '📦';
console.log(` ${icon} ${type.toUpperCase()}:`);
nodes.forEach(n => {
console.log(`${n.label}${n.critical ? ' [KRITISK]' : ''}`);
});
console.log('');
}
// Affärspåverkan
const businessImpact = cutOff.filter(n => n.type === 'business_process');
if (businessImpact.length > 0) {
console.log('💰 AFFÄRSPÅVERKAN:\n');
businessImpact.forEach(n => {
console.log(` 🏭 ${n.label}`);
console.log(` Steg: ${n.steps?.join(' → ') || 'okänt'}`);
});
console.log('');
}
}
function findAllPaths(targetId, excludeId = null, currentPath = [], visited = new Set()) {
if (currentPath.length > 10) return [];
const paths = [];
const edges = getEdgesTo(targetId);
for (const edge of edges) {
if (edge.from === excludeId) continue;
if (visited.has(edge.from)) continue;
const newPath = [edge.from, ...currentPath];
if (newPath.length > 1) {
paths.push(newPath);
}
visited.add(edge.from);
paths.push(...findAllPaths(edge.from, excludeId, newPath, visited));
}
return paths;
}
function blastRadius(tableName) {
console.log(`\n💣 BLAST RADIUS: ${tableName}\n`);
const tableNode = graph.nodes.find(n => n.label === tableName && n.type === 'domain');
if (!tableNode) {
console.log(`❌ Tabell "${tableName}" hittades inte`);
return;
}
console.log(`📍 Tabell: ${tableNode.label}\n`);
const impacted = reverseTraverse(tableNode.id);
console.log('🔍 Komponenter som använder denna tabell:\n');
const services = [];
const processes = [];
impacted.forEach(({ node, depth }) => {
if (depth === 0) return;
if (node.type === 'service') services.push({ node, depth });
if (node.type === 'business_process') processes.push({ node, depth });
});
if (services.length > 0) {
console.log(' ⚙️ TJÄNSTER:');
services.forEach(({ node, depth }) => {
console.log(` ${' '.repeat(depth - 1)}${node.label} ${node.endpoints ? `(${node.endpoints.length} endpoints)` : ''}`);
});
console.log('');
}
if (processes.length > 0) {
console.log(' 🏭 AFFÄRSPROCESSER:');
processes.forEach(({ node }) => {
console.log(`${node.label}`);
});
console.log('');
}
// Förslag på tester
console.log('🧪 FÖRESLAGNA TESTER:\n');
services.forEach(({ node }) => {
if (node.endpoints) {
console.log(` ${node.label}:`);
node.endpoints.forEach(ep => {
console.log(` - GET/POST ${ep} (med ${tableName} data)`);
});
}
});
}
// ── Huvudmeny ─────────────────────────────────────────────────────────────
function printHelp() {
console.log(`
🧠 System Graph Reasoning Engine
Användning:
node graph-engine.mjs <kommando> [argument]
Kommandon:
impact <komponent> Visa vilka komponenter påverkas av en ändring
failure <komponent> Visa vad som går sönder om komponenten fallerar
blast <tabell> Visa vilka som använder en databastabell
list Lista alla komponenter i grafen
stats Visa statistik om grafen
Exempel:
node graph-engine.mjs impact wallet
node graph-engine.mjs failure stripe
node graph-engine.mjs blast missions
`);
}
function listComponents() {
console.log('\n📋 KOMPONENTER I SYSTEMET\n');
const byType = {};
graph.nodes.forEach(n => {
if (!byType[n.type]) byType[n.type] = [];
byType[n.type].push(n);
});
for (const [type, nodes] of Object.entries(byType)) {
const icon = type === 'actor' ? '👤' : type === 'service' ? '⚙️' : type === 'database' ? '🗄️' : type === 'frontend' ? '🖥️' : type === 'business_process' ? '🏭' : type === 'domain' ? '📊' : type === 'risk' ? '⚠️' : type === 'agent' ? '🤖' : '📦';
console.log(`${icon} ${type.toUpperCase()} (${nodes.length}):`);
nodes.forEach(n => {
const critical = n.critical ? ' 🔴' : '';
console.log(`${n.label}${critical}`);
});
console.log('');
}
}
function stats() {
console.log('\n📊 GRAF-STATISTIK\n');
console.log(` Noder: ${graph.nodes.length}`);
console.log(` Kanter: ${graph.edges.length}`);
console.log(` Affärsprocesser: ${graph.nodes.filter(n => n.type === 'business_process').length}`);
console.log(` Risker: ${graph.nodes.filter(n => n.type === 'risk').length}`);
console.log(` Kritiska komponenter: ${graph.nodes.filter(n => n.critical).length}`);
console.log(` Senast uppdaterad: ${graph.last_updated}`);
console.log(` Uppdateringskälla: ${graph.update_source}`);
console.log('');
}
// ── Main ──────────────────────────────────────────────────────────────────
const [,, command, ...args] = process.argv;
if (!command || command === 'help') {
printHelp();
process.exit(0);
}
switch (command) {
case 'impact':
if (!args[0]) {
console.log('❌ Ange komponent: node graph-engine.mjs impact <komponent>');
process.exit(1);
}
impactAnalysis(args[0]);
break;
case 'failure':
if (!args[0]) {
console.log('❌ Ange komponent: node graph-engine.mjs failure <komponent>');
process.exit(1);
}
failureAnalysis(args[0]);
break;
case 'blast':
if (!args[0]) {
console.log('❌ Ange tabell: node graph-engine.mjs blast <tabellnamn>');
process.exit(1);
}
blastRadius(args[0]);
break;
case 'list':
listComponents();
break;
case 'stats':
stats();
break;
default:
console.log(`❌ Okänt kommando: ${command}`);
printHelp();
process.exit(1);
}