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!
288 lines
9.7 KiB
JavaScript
288 lines
9.7 KiB
JavaScript
#!/usr/bin/env node
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// SIL Failure Review — Klassificera varje fel
|
|
// Erik-krav: "Varje gång SIL har fel ska ni inte bara logga det. Ni ska klassificera felet."
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs';
|
|
|
|
const FAILURE_LOG = '/home/bernt/.openclaw/workspace/SIL/validation/failure-log.jsonl';
|
|
|
|
/**
|
|
* Feltyper som SIL kan göra
|
|
*/
|
|
const FAILURE_TYPES = {
|
|
KNOWLEDGE_ERROR: {
|
|
description: 'Grafen saknade en relation',
|
|
example: 'SIL visste inte att Wallet påverkar Payout',
|
|
action: 'Uppdatera kunskapsgrafen'
|
|
},
|
|
PARSING_ERROR: {
|
|
description: 'AST missade en import',
|
|
example: 'SIL såg inte att filen importerade Stripe',
|
|
action: 'Förbättra AST-parser'
|
|
},
|
|
RUNTIME_DRIFT: {
|
|
description: 'Driftmiljön skiljde sig från modellen',
|
|
example: 'Produktion hade en feature flag som grafen inte kände till',
|
|
action: 'Uppdatera observer'
|
|
},
|
|
POLICY_ERROR: {
|
|
description: 'Arkitekturregel felaktigt formulerad',
|
|
example: 'Regeln var för strikt och flaggade giltig kod',
|
|
action: 'Justera arkitekturpolicy'
|
|
},
|
|
LLM_ERROR: {
|
|
description: 'Felaktig inferens',
|
|
example: 'SIL drog fel slutsats från korrekt data',
|
|
action: 'Förbättra resonemangsmodell'
|
|
},
|
|
CONFIDENCE_ERROR: {
|
|
description: 'För hög confidence trots svag evidens',
|
|
example: 'SIL sa 95% confidence men hade fel',
|
|
action: 'Kalibrera om confidence-modell'
|
|
},
|
|
OBSERVATION_ERROR: {
|
|
description: 'Felaktig eller ofullständig observation',
|
|
example: 'Git diff missade en fil',
|
|
action: 'Förbättra observer'
|
|
},
|
|
REASONING_ERROR: {
|
|
description: 'Felaktigt resonemang från korrekt data',
|
|
example: 'Korrekt data men fel slutsats',
|
|
action: 'Granska resonemangsregler'
|
|
}
|
|
};
|
|
|
|
class FailureReview {
|
|
constructor() {
|
|
this.failures = this.loadFailures();
|
|
}
|
|
|
|
loadFailures() {
|
|
if (!existsSync(FAILURE_LOG)) return [];
|
|
return readFileSync(FAILURE_LOG, 'utf8')
|
|
.split('\n')
|
|
.filter(line => line.trim())
|
|
.map(line => {
|
|
try { return JSON.parse(line); } catch { return null; }
|
|
})
|
|
.filter(Boolean);
|
|
}
|
|
|
|
/**
|
|
* Logga ett nytt fel med klassificering
|
|
*/
|
|
logFailure(failure) {
|
|
const entry = {
|
|
timestamp: new Date().toISOString(),
|
|
analysisId: failure.analysisId,
|
|
pr: failure.pr,
|
|
// Vad SIL sa
|
|
silPrediction: failure.silPrediction,
|
|
silConfidence: failure.silConfidence,
|
|
// Vad som var sant
|
|
actualOutcome: failure.actualOutcome,
|
|
// Klassificering
|
|
failureType: failure.failureType,
|
|
failureTypeDescription: FAILURE_TYPES[failure.failureType]?.description,
|
|
// Kontext
|
|
component: failure.component,
|
|
evidence: failure.evidence,
|
|
// Root cause
|
|
rootCause: failure.rootCause,
|
|
// Åtgärd
|
|
action: failure.action || FAILURE_TYPES[failure.failureType]?.action,
|
|
// Allvarlighet
|
|
severity: failure.severity || 'MEDIUM',
|
|
// Lärande
|
|
lesson: failure.lesson
|
|
};
|
|
|
|
appendFileSync(FAILURE_LOG, JSON.stringify(entry) + '\n', 'utf8');
|
|
console.log(`✅ Fel loggat: ${failure.failureType}`);
|
|
console.log(` ${FAILURE_TYPES[failure.failureType]?.description}`);
|
|
console.log(` Åtgärd: ${entry.action}\n`);
|
|
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* Analysera felmönster
|
|
*/
|
|
analyzePatterns() {
|
|
const byType = {};
|
|
const byComponent = {};
|
|
const bySeverity = {};
|
|
|
|
for (const failure of this.failures) {
|
|
// Per typ
|
|
byType[failure.failureType] = (byType[failure.failureType] || 0) + 1;
|
|
|
|
// Per komponent
|
|
if (failure.component) {
|
|
byComponent[failure.component] = (byComponent[failure.component] || 0) + 1;
|
|
}
|
|
|
|
// Per allvarlighet
|
|
bySeverity[failure.severity] = (bySeverity[failure.severity] || 0) + 1;
|
|
}
|
|
|
|
return {
|
|
total: this.failures.length,
|
|
byType: Object.entries(byType)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.map(([type, count]) => ({
|
|
type,
|
|
count,
|
|
percentage: Math.round(count / this.failures.length * 100) + '%',
|
|
description: FAILURE_TYPES[type]?.description,
|
|
action: FAILURE_TYPES[type]?.action
|
|
})),
|
|
byComponent: Object.entries(byComponent)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 10)
|
|
.map(([component, count]) => ({ component, count })),
|
|
bySeverity
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generera rapport
|
|
*/
|
|
generateReport() {
|
|
const patterns = this.analyzePatterns();
|
|
|
|
const report = {
|
|
timestamp: new Date().toISOString(),
|
|
totalFailures: patterns.total,
|
|
patterns,
|
|
// Dominant feltyp
|
|
dominantFailure: patterns.byType[0],
|
|
// Rekommendationer
|
|
recommendations: this.generateRecommendations(patterns)
|
|
};
|
|
|
|
this.printReport(report);
|
|
return report;
|
|
}
|
|
|
|
generateRecommendations(patterns) {
|
|
const recs = [];
|
|
|
|
// Om en feltyp dominerar → prioritera den
|
|
if (patterns.byType.length > 0) {
|
|
const dominant = patterns.byType[0];
|
|
if (parseFloat(dominant.percentage) > 30) {
|
|
recs.push({
|
|
priority: 'HIGH',
|
|
issue: `${dominant.type} dominerar (${dominant.percentage})`,
|
|
action: dominant.action
|
|
});
|
|
}
|
|
}
|
|
|
|
// Om vissa komponenter har många fel → granska dem
|
|
for (const comp of patterns.byComponent.slice(0, 3)) {
|
|
if (comp.count > 3) {
|
|
recs.push({
|
|
priority: 'MEDIUM',
|
|
issue: `Komponent ${comp.component} har ${comp.count} fel`,
|
|
action: `Granska och uppdatera graf för ${comp.component}`
|
|
});
|
|
}
|
|
}
|
|
|
|
return recs;
|
|
}
|
|
|
|
printReport(report) {
|
|
console.log('╔═══════════════════════════════════════════════════════════════╗');
|
|
console.log('║ FAILURE REVIEW ║');
|
|
console.log('║ Klassificering av SIL:s fel ║');
|
|
console.log('╚═══════════════════════════════════════════════════════════════╝');
|
|
console.log();
|
|
|
|
console.log(`📊 TOTALT: ${report.totalFailures} fel\n`);
|
|
|
|
console.log('📋 PER TYP\n');
|
|
for (const type of report.patterns.byType) {
|
|
console.log(` ${type.count}x ${type.type}`);
|
|
console.log(` ${type.description}`);
|
|
console.log(` ${type.percentage} av alla fel`);
|
|
console.log(` Åtgärd: ${type.action}`);
|
|
console.log();
|
|
}
|
|
|
|
console.log('📋 PER KOMPONENT (top 10)\n');
|
|
for (const comp of report.patterns.byComponent) {
|
|
console.log(` ${comp.component}: ${comp.count} fel`);
|
|
}
|
|
console.log();
|
|
|
|
console.log('📋 PER ALLVARLIGHET\n');
|
|
for (const [sev, count] of Object.entries(report.patterns.bySeverity)) {
|
|
console.log(` ${sev}: ${count}`);
|
|
}
|
|
console.log();
|
|
|
|
if (report.recommendations.length > 0) {
|
|
console.log('💡 REKOMMENDATIONER\n');
|
|
for (const rec of report.recommendations) {
|
|
const icon = rec.priority === 'HIGH' ? '🔴' : '🟡';
|
|
console.log(` ${icon} [${rec.priority}] ${rec.issue}`);
|
|
console.log(` → ${rec.action}`);
|
|
}
|
|
console.log();
|
|
}
|
|
|
|
console.log('═══════════════════════════════════════════════════════════════\n');
|
|
}
|
|
|
|
/**
|
|
* Lista alla feltyper
|
|
*/
|
|
listTypes() {
|
|
console.log('📋 FELTYPER\n');
|
|
for (const [type, info] of Object.entries(FAILURE_TYPES)) {
|
|
console.log(` ${type}:`);
|
|
console.log(` ${info.description}`);
|
|
console.log(` Exempel: ${info.example}`);
|
|
console.log(` Åtgärd: ${info.action}`);
|
|
console.log();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Main ──────────────────────────────────────────────────────────────────
|
|
|
|
const review = new FailureReview();
|
|
const command = process.argv[2] || '--report';
|
|
|
|
if (command === '--report') {
|
|
review.generateReport();
|
|
} else if (command === '--log') {
|
|
// Exempel på att logga ett fel
|
|
const failure = {
|
|
analysisId: process.argv[3] || 'unknown',
|
|
pr: process.argv[4] || 'PR-123',
|
|
silPrediction: process.argv[5] || 'Wallet påverkar inte Payout',
|
|
silConfidence: 0.85,
|
|
actualOutcome: 'Wallet påverkar Payout',
|
|
failureType: process.argv[6] || 'KNOWLEDGE_ERROR',
|
|
component: process.argv[7] || 'wallet',
|
|
evidence: 'Git diff visade ändring i payout.ts',
|
|
rootCause: 'Grafen saknade kant Wallet → Payout',
|
|
severity: 'HIGH',
|
|
lesson: 'Måste uppdatera graf när nya beroenden läggs till'
|
|
};
|
|
review.logFailure(failure);
|
|
} else if (command === '--types') {
|
|
review.listTypes();
|
|
} else {
|
|
console.log('Användning:');
|
|
console.log(' node failure-review.mjs --report # Generera rapport');
|
|
console.log(' node failure-review.mjs --log <id> <pr> <pred> <type> <comp> # Logga fel');
|
|
console.log(' node failure-review.mjs --types # Lista feltyper');
|
|
}
|