Files
boc/SIL/validation/provenance-audit.mjs
T

241 lines
8.1 KiB
JavaScript
Raw Normal View History

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