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!
241 lines
8.1 KiB
JavaScript
241 lines
8.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// SIL Validation — Fråga 2: Kan SIL motivera sina slutsatser?
|
|
// Granskar 50 slumpmässiga analyser för provenance completeness
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
|
|
|
const SHADOW_LOG = '/home/bernt/.openclaw/workspace/SIL/shadow-log.jsonl';
|
|
const AUDIT_RESULTS = '/home/bernt/.openclaw/workspace/SIL/validation/provenance-audit-results.jsonl';
|
|
|
|
/**
|
|
* Checklista för provenance completeness
|
|
*/
|
|
const PROVENANCE_CHECKLIST = [
|
|
{
|
|
id: 'rule',
|
|
question: 'Vilken regel användes?',
|
|
check: (analysis) => analysis.provenance?.trace?.steps?.some(s => s.type === 'rule')
|
|
},
|
|
{
|
|
id: 'observation',
|
|
question: 'Vilka observationer användes?',
|
|
check: (analysis) => analysis.provenance?.trace?.steps?.some(s => s.type === 'observation')
|
|
},
|
|
{
|
|
id: 'artifacts',
|
|
question: 'Vilka artefakter användes?',
|
|
check: (analysis) => analysis.evidence?.static?.length > 0
|
|
},
|
|
{
|
|
id: 'nodes',
|
|
question: 'Vilka noder traverserades?',
|
|
check: (analysis) => analysis.provenance?.trace?.steps?.some(s => s.type === 'traversal')
|
|
},
|
|
{
|
|
id: 'verified',
|
|
question: 'Hur stor del var verifierad?',
|
|
check: (analysis) => {
|
|
const conclusions = analysis.provenance?.trace?.conclusions || [];
|
|
if (conclusions.length === 0) return false;
|
|
const verified = conclusions.filter(c => c.verified).length;
|
|
return verified / conclusions.length >= 0.5; // Minst hälften verifierad
|
|
}
|
|
},
|
|
{
|
|
id: 'inferred',
|
|
question: 'Hur stor del var inferens?',
|
|
check: (analysis) => {
|
|
const conclusions = analysis.provenance?.trace?.conclusions || [];
|
|
if (conclusions.length === 0) return false;
|
|
const inferred = conclusions.filter(c => c.inferred).length;
|
|
return inferred / conclusions.length <= 0.5; // Max hälften inferens
|
|
}
|
|
}
|
|
];
|
|
|
|
class ProvenanceAuditor {
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Granska N slumpmässiga analyser
|
|
*/
|
|
audit(sampleSize = 50) {
|
|
// Välj slumpmässiga analyser
|
|
const shuffled = [...this.analyses].sort(() => Math.random() - 0.5);
|
|
const sample = shuffled.slice(0, Math.min(sampleSize, shuffled.length));
|
|
|
|
console.log(`🔍 Granskar provenance för ${sample.length} analyser...\n`);
|
|
|
|
const results = [];
|
|
|
|
for (const analysis of sample) {
|
|
const audit = this.auditAnalysis(analysis);
|
|
results.push(audit);
|
|
}
|
|
|
|
const report = this.generateReport(results);
|
|
this.saveResults(results);
|
|
this.printReport(report);
|
|
|
|
return report;
|
|
}
|
|
|
|
/**
|
|
* Granska en enskild analys
|
|
*/
|
|
auditAnalysis(analysis) {
|
|
const checks = [];
|
|
let passed = 0;
|
|
let total = PROVENANCE_CHECKLIST.length;
|
|
|
|
for (const check of PROVENANCE_CHECKLIST) {
|
|
const result = check.check(analysis);
|
|
checks.push({
|
|
id: check.id,
|
|
question: check.question,
|
|
passed: result
|
|
});
|
|
if (result) passed++;
|
|
}
|
|
|
|
const completeness = Math.round(passed / total * 100);
|
|
|
|
return {
|
|
analysisId: analysis.analysisId || analysis.timestamp,
|
|
timestamp: analysis.timestamp,
|
|
checks,
|
|
completeness,
|
|
passed,
|
|
total,
|
|
// Kategorisera
|
|
quality: completeness >= 90 ? 'EXCELLENT' :
|
|
completeness >= 70 ? 'GOOD' :
|
|
completeness >= 50 ? 'FAIR' : 'POOR'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generera rapport
|
|
*/
|
|
generateReport(results) {
|
|
const total = results.length;
|
|
if (total === 0) {
|
|
return { error: 'Inga analyser att granska' };
|
|
}
|
|
|
|
const avgCompleteness = results.reduce((a, r) => a + r.completeness, 0) / total;
|
|
|
|
const byQuality = {
|
|
EXCELLENT: results.filter(r => r.quality === 'EXCELLENT').length,
|
|
GOOD: results.filter(r => r.quality === 'GOOD').length,
|
|
FAIR: results.filter(r => r.quality === 'FAIR').length,
|
|
POOR: results.filter(r => r.quality === 'POOR').length
|
|
};
|
|
|
|
// Per-check resultat
|
|
const byCheck = {};
|
|
for (const check of PROVENANCE_CHECKLIST) {
|
|
const passed = results.filter(r =>
|
|
r.checks.find(c => c.id === check.id)?.passed
|
|
).length;
|
|
byCheck[check.id] = {
|
|
question: check.question,
|
|
passed,
|
|
total,
|
|
rate: Math.round(passed / total * 100) + '%'
|
|
};
|
|
}
|
|
|
|
return {
|
|
timestamp: new Date().toISOString(),
|
|
sampleSize: total,
|
|
avgCompleteness: Math.round(avgCompleteness) + '%',
|
|
byQuality,
|
|
byCheck,
|
|
// Success-kriterium
|
|
meetsCriteria: avgCompleteness >= 90
|
|
};
|
|
}
|
|
|
|
printReport(report) {
|
|
console.log('╔═══════════════════════════════════════════════════════════════╗');
|
|
console.log('║ VALIDATION: FRÅGA 2 ║');
|
|
console.log('║ Kan SIL motivera sina slutsatser? ║');
|
|
console.log('╚═══════════════════════════════════════════════════════════════╝');
|
|
console.log();
|
|
|
|
if (report.error) {
|
|
console.log(`❌ ${report.error}\n`);
|
|
return;
|
|
}
|
|
|
|
console.log('📊 PROVENANCE COMPLETENESS\n');
|
|
console.log(` Genomsnitt: ${report.avgCompleteness}`);
|
|
console.log(` Analyser granskade: ${report.sampleSize}`);
|
|
console.log();
|
|
|
|
console.log('📈 KVALITETSFÖRDELNING\n');
|
|
console.log(` EXCELLENT (≥90%): ${report.byQuality.EXCELLENT}`);
|
|
console.log(` GOOD (70-89%): ${report.byQuality.GOOD}`);
|
|
console.log(` FAIR (50-69%): ${report.byQuality.FAIR}`);
|
|
console.log(` POOR (<50%): ${report.byQuality.POOR}`);
|
|
console.log();
|
|
|
|
console.log('✅ PER CHECK\n');
|
|
for (const [id, data] of Object.entries(report.byCheck)) {
|
|
const icon = parseFloat(data.rate) >= 90 ? '✅' : '⚠️';
|
|
console.log(` ${icon} ${data.question}`);
|
|
console.log(` ${data.passed}/${data.total} (${data.rate})`);
|
|
}
|
|
console.log();
|
|
|
|
console.log('🎯 SUCCESS-KRITERIUM\n');
|
|
console.log(` Krav: ≥90% completeness`);
|
|
console.log(` Resultat: ${report.avgCompleteness}`);
|
|
console.log(` ${report.meetsCriteria ? '✅ UPPFYLLT' : '❌ EJ UPPFYLLT'}`);
|
|
console.log();
|
|
|
|
console.log('═══════════════════════════════════════════════════════════════\n');
|
|
}
|
|
|
|
saveResults(results) {
|
|
for (const result of results) {
|
|
writeFileSync(AUDIT_RESULTS, JSON.stringify(result) + '\n', { flag: 'a' });
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Main ──────────────────────────────────────────────────────────────────
|
|
|
|
const auditor = new ProvenanceAuditor();
|
|
const command = process.argv[2] || '--audit';
|
|
|
|
if (command === '--audit') {
|
|
const sampleSize = parseInt(process.argv[3]) || 50;
|
|
auditor.audit(sampleSize);
|
|
} else if (command === '--checklist') {
|
|
console.log('📋 PROVENANCE CHECKLIST\n');
|
|
for (const check of PROVENANCE_CHECKLIST) {
|
|
console.log(` • ${check.question}`);
|
|
}
|
|
} else {
|
|
console.log('Användning:');
|
|
console.log(' node provenance-audit.mjs --audit [n] # Granska N analyser');
|
|
console.log(' node provenance-audit.mjs --checklist # Visa checklista');
|
|
}
|