Files
boc/SIL/replay-mode.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

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');
}