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!
272 lines
10 KiB
JavaScript
272 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// SIL Replay Mode — Spela upp historisk analys exakt som den såg ut då
|
|
// Erik-krav: "Samma grafversion, samma regler, samma observer-version, samma reasoner-version"
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
|
|
|
const SHADOW_LOG = '/home/bernt/.openclaw/workspace/SIL/shadow-log.jsonl';
|
|
const VERSION_DIR = '/home/bernt/.openclaw/workspace/SIL/graph-versions';
|
|
const PROVENANCE_LOG = '/home/bernt/.openclaw/workspace/SIL/provenance-log.jsonl';
|
|
|
|
class ReplayMode {
|
|
constructor() {
|
|
this.analyses = this.loadAnalyses();
|
|
}
|
|
|
|
loadAnalyses() {
|
|
if (!existsSync(SHADOW_LOG)) return [];
|
|
return readFileSync(SHADOW_LOG, 'utf8')
|
|
.split('\n')
|
|
.filter(line => line.trim())
|
|
.map(line => {
|
|
try { return JSON.parse(line); } catch { return null; }
|
|
})
|
|
.filter(Boolean);
|
|
}
|
|
|
|
/**
|
|
* Hitta en analys att spela upp
|
|
*/
|
|
findAnalysis(analysisId) {
|
|
return this.analyses.find(a => a.analysisId === analysisId);
|
|
}
|
|
|
|
/**
|
|
* Spela upp en historisk analys
|
|
*/
|
|
replay(analysisId) {
|
|
const analysis = this.findAnalysis(analysisId);
|
|
|
|
if (!analysis) {
|
|
console.log(`❌ Analys ${analysisId} hittades inte\n`);
|
|
return null;
|
|
}
|
|
|
|
console.log(`🔄 REPLAY: ${analysisId}\n`);
|
|
|
|
// 1. Återskapa grafversion
|
|
const graphVersion = analysis.provenance?.traceId;
|
|
const graph = this.reconstructGraph(graphVersion);
|
|
|
|
// 2. Återskapa kontext
|
|
const context = this.reconstructContext(analysis);
|
|
|
|
// 3. Återskapa beslutsunderlag
|
|
const decision = this.reconstructDecision(analysis);
|
|
|
|
// 4. Visa exakt hur analysen såg ut
|
|
this.printReplay({
|
|
analysisId,
|
|
timestamp: analysis.timestamp,
|
|
graphVersion,
|
|
context,
|
|
decision,
|
|
provenance: analysis.provenance
|
|
});
|
|
|
|
return {
|
|
analysisId,
|
|
timestamp: analysis.timestamp,
|
|
graphVersion,
|
|
context,
|
|
decision,
|
|
provenance: analysis.provenance
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Återskapa graf från version
|
|
*/
|
|
reconstructGraph(versionId) {
|
|
if (!versionId) {
|
|
return { error: 'Ingen grafversion specificerad' };
|
|
}
|
|
|
|
const path = `${VERSION_DIR}/graph-${versionId}.json`;
|
|
if (!existsSync(path)) {
|
|
return { error: `Grafversion ${versionId} hittades inte` };
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(readFileSync(path, 'utf8'));
|
|
} catch {
|
|
return { error: 'Kunde inte läsa grafversion' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Återskapa kontext
|
|
*/
|
|
reconstructContext(analysis) {
|
|
return {
|
|
repo: analysis.repo || 'unknown',
|
|
baseBranch: analysis.baseBranch || 'main',
|
|
headBranch: analysis.headBranch || 'HEAD',
|
|
commit: analysis.commit || 'unknown',
|
|
changedFiles: analysis.changedFiles || 0,
|
|
// Versioner
|
|
versions: {
|
|
graph: analysis.provenance?.traceId,
|
|
observer: analysis.observerVersion || 'unknown',
|
|
reasoner: analysis.reasonerVersion || 'unknown',
|
|
policy: analysis.policyVersion || 'unknown'
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Återskapa beslutsunderlag
|
|
*/
|
|
reconstructDecision(analysis) {
|
|
return {
|
|
riskScore: analysis.decision?.summary?.riskScore,
|
|
blastRadius: analysis.decision?.summary?.blastRadius,
|
|
confidence: analysis.decision?.summary?.confidence,
|
|
recommendation: analysis.decision?.summary?.recommendation,
|
|
// Osäkerhet
|
|
uncertainty: analysis.uncertaintySummary,
|
|
// Tester
|
|
requiredTests: analysis.requiredTests?.length || 0,
|
|
// Risker
|
|
risks: analysis.risks?.length || 0
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Visa replay
|
|
*/
|
|
printReplay(replay) {
|
|
console.log('╔═══════════════════════════════════════════════════════════════╗');
|
|
console.log('║ SIL REPLAY MODE ║');
|
|
console.log('║ Exakt återskapning av historisk analys ║');
|
|
console.log('╚═══════════════════════════════════════════════════════════════╝');
|
|
console.log();
|
|
|
|
console.log('📋 ANALYS\n');
|
|
console.log(` ID: ${replay.analysisId}`);
|
|
console.log(` Tidpunkt: ${replay.timestamp}`);
|
|
console.log(` Grafversion: ${replay.graphVersion || 'unknown'}`);
|
|
console.log();
|
|
|
|
console.log('🌐 KONTEXT\n');
|
|
console.log(` Repo: ${replay.context.repo}`);
|
|
console.log(` Branch: ${replay.context.headBranch} → ${replay.context.baseBranch}`);
|
|
console.log(` Commit: ${replay.context.commit}`);
|
|
console.log(` Ändrade filer: ${replay.context.changedFiles}`);
|
|
console.log();
|
|
|
|
console.log('⚙️ VERSIONER\n');
|
|
console.log(` Graf: ${replay.context.versions.graph || 'unknown'}`);
|
|
console.log(` Observer: ${replay.context.versions.observer}`);
|
|
console.log(` Reasoner: ${replay.context.versions.reasoner}`);
|
|
console.log(` Policy: ${replay.context.versions.policy}`);
|
|
console.log();
|
|
|
|
if (replay.decision) {
|
|
console.log('📊 BESLUTSUNDERLAG\n');
|
|
console.log(` Risk Score: ${replay.decision.riskScore || 'N/A'}`);
|
|
console.log(` Blast Radius: ${replay.decision.blastRadius || 'N/A'}`);
|
|
console.log(` Confidence: ${replay.decision.confidence || 'N/A'}`);
|
|
console.log(` Rekommendation: ${replay.decision.recommendation || 'N/A'}`);
|
|
console.log(` Tester: ${replay.decision.requiredTests}`);
|
|
console.log(` Risker: ${replay.decision.risks}`);
|
|
console.log();
|
|
}
|
|
|
|
if (replay.provenance) {
|
|
console.log('📋 PROVENANCE\n');
|
|
console.log(` Trace ID: ${replay.provenance.traceId}`);
|
|
console.log(` Steg: ${replay.provenance.steps}`);
|
|
console.log(` Slutsatser: ${replay.provenance.conclusions}`);
|
|
console.log();
|
|
}
|
|
|
|
console.log('═══════════════════════════════════════════════════════════════\n');
|
|
}
|
|
|
|
/**
|
|
* Lista alla tillgängliga analyser att spela upp
|
|
*/
|
|
listAvailableReplays() {
|
|
console.log('📋 TILLGÄNGLIGA REPLAYS\n');
|
|
|
|
for (const analysis of this.analyses.slice(-20)) {
|
|
const id = analysis.analysisId || analysis.timestamp;
|
|
const date = analysis.timestamp?.split('T')[0] || 'unknown';
|
|
const components = analysis.summary?.changedComponents || 0;
|
|
|
|
console.log(` ${id}`);
|
|
console.log(` Datum: ${date}`);
|
|
console.log(` Komponenter: ${components}`);
|
|
console.log(` Grafversion: ${analysis.provenance?.traceId || 'unknown'}`);
|
|
console.log();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Jämför två replay:er
|
|
*/
|
|
compareReplays(analysisId1, analysisId2) {
|
|
const replay1 = this.replay(analysisId1);
|
|
const replay2 = this.replay(analysisId2);
|
|
|
|
if (!replay1 || !replay2) return;
|
|
|
|
console.log('╔═══════════════════════════════════════════════════════════════╗');
|
|
console.log('║ REPLAY COMPARISON ║');
|
|
console.log('╚═══════════════════════════════════════════════════════════════╝');
|
|
console.log();
|
|
|
|
console.log(`📊 ${analysisId1} vs ${analysisId2}\n`);
|
|
|
|
// Jämför beslutsunderlag
|
|
if (replay1.decision && replay2.decision) {
|
|
console.log('BESLUTSUNDERLAG\n');
|
|
console.log(` Risk Score: ${replay1.decision.riskScore || 'N/A'} → ${replay2.decision.riskScore || 'N/A'}`);
|
|
console.log(` Confidence: ${replay1.decision.confidence || 'N/A'} → ${replay2.decision.confidence || 'N/A'}`);
|
|
console.log(` Rekommendation: ${replay1.decision.recommendation || 'N/A'} → ${replay2.decision.recommendation || 'N/A'}`);
|
|
console.log();
|
|
}
|
|
|
|
// Jämför versioner
|
|
console.log('VERSIONER\n');
|
|
console.log(` Graf: ${replay1.context.versions.graph || 'N/A'} → ${replay2.context.versions.graph || 'N/A'}`);
|
|
console.log(` Observer: ${replay1.context.versions.observer || 'N/A'} → ${replay2.context.versions.observer || 'N/A'}`);
|
|
console.log(` Reasoner: ${replay1.context.versions.reasoner || 'N/A'} → ${replay2.context.versions.reasoner || 'N/A'}`);
|
|
console.log();
|
|
|
|
console.log('═══════════════════════════════════════════════════════════════\n');
|
|
}
|
|
}
|
|
|
|
// ── Main ──────────────────────────────────────────────────────────────────
|
|
|
|
const replay = new ReplayMode();
|
|
const command = process.argv[2] || '--list';
|
|
|
|
if (command === '--list') {
|
|
replay.listAvailableReplays();
|
|
} else if (command === '--replay') {
|
|
const analysisId = process.argv[3];
|
|
if (!analysisId) {
|
|
console.log('Användning: node replay-mode.mjs --replay <analysis-id>');
|
|
process.exit(1);
|
|
}
|
|
replay.replay(analysisId);
|
|
} else if (command === '--compare') {
|
|
const id1 = process.argv[3];
|
|
const id2 = process.argv[4];
|
|
if (!id1 || !id2) {
|
|
console.log('Användning: node replay-mode.mjs --compare <id1> <id2>');
|
|
process.exit(1);
|
|
}
|
|
replay.compareReplays(id1, id2);
|
|
} else {
|
|
console.log('Användning:');
|
|
console.log(' node replay-mode.mjs --list # Lista tillgängliga replays');
|
|
console.log(' node replay-mode.mjs --replay <analysis-id> # Spela upp analys');
|
|
console.log(' node replay-mode.mjs --compare <id1> <id2> # Jämför två analyser');
|
|
}
|