#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // Decision Provenance — Komplett beslutsunderlag för varje EOS-beslut // ═══════════════════════════════════════════════════════════════════════════ import { createHash } from 'crypto'; class DecisionProvenance { constructor() { this.decisions = []; } /** * Skapa ett komplett beslutsunderlag */ record(task, runtimeResult, trace) { const decision = { // Identitet id: this.generateId(task, runtimeResult), timestamp: new Date().toISOString(), // Task task: { description: task.description, type: task.type, action: task.action, target: task.target }, // Runtime-version runtime: { version: 'v10', contract: '1.0', evidence_resolver: 'v5', policy_engine: 'v4' }, // Kontext context: { sources: trace.nodes .filter(n => n.phase === 'CONTEXT') .map(n => n.result), git: trace.nodes .find(n => n.phase === 'CONTEXT')?.result?.git || null }, // Minne memory: { entries: trace.nodes .filter(n => n.phase === 'MEMORY') .map(n => n.result), relevant_decisions: trace.nodes .find(n => n.phase === 'MEMORY')?.result?.results?.relevantDecisions || [] }, // Knowledge Graph knowledge: { graph_version: '1.0', capabilities: trace.nodes .find(n => n.phase === 'KNOWLEDGE')?.result?.results?.relatedCapabilities || [] }, // Intent Resolution intent: { candidates: trace.nodes .find(n => n.phase === 'INTENT')?.result?.candidates || [], resolution: trace.nodes .find(n => n.phase === 'INTENT')?.result?.resolution, confidence: trace.nodes .find(n => n.phase === 'INTENT')?.result?.confidence, evidence_quality: trace.nodes .find(n => n.phase === 'INTENT')?.result?.evidenceQuality }, // Policy policies: { checked: trace.nodes .filter(n => n.phase === 'EOS') .map(n => ({ policy: n.result?.blockedBy, passed: n.result?.passed, reason: n.result?.reason })), registry_version: '1.0' }, // Beslut decision: { operation: runtimeResult.status === 'blocked' ? 'BLOCKED' : 'ALLOWED', reason: runtimeResult.reason || null, policy: runtimeResult.policy || null, evidence: runtimeResult.evidence || null }, // Confidence confidence: trace.nodes .find(n => n.phase === 'INTENT')?.result?.confidence || 0, // Review (placeholder — fylls i av Reviewer) review: { reviewer: null, approved: null, comments: [] }, // Commit (placeholder — fylls i vid commit) commit: { hash: null, branch: null, message: null }, // Runtime Contract runtime_contract: { version: '1.0', invariants_verified: true, architecture_drift: 0 }, // Replay-hash replay_hash: this.generateReplayHash(task, runtimeResult) }; this.decisions.push(decision); return decision; } generateId(task, result) { const data = `${task.description}-${Date.now()}`; return createHash('sha256').update(data).digest('hex').substring(0, 16); } generateReplayHash(task, result) { const data = JSON.stringify({ task: task.description, status: result.status, policy: result.policy, timestamp: new Date().toISOString() }); return createHash('sha256').update(data).digest('hex'); } /** * Exportera beslut till fil */ export(decisionId, path) { const decision = this.decisions.find(d => d.id === decisionId); if (!decision) return null; const fs = require('fs'); const filePath = path || `./provenance/decision-${decisionId}.json`; fs.mkdirSync('./provenance', { recursive: true }); fs.writeFileSync(filePath, JSON.stringify(decision, null, 2)); return filePath; } /** * Hämta statistik */ getStats() { const total = this.decisions.length; const blocked = this.decisions.filter(d => d.decision.operation === 'BLOCKED').length; const allowed = total - blocked; const avgConfidence = total > 0 ? this.decisions.reduce((sum, d) => sum + d.confidence, 0) / total : 0; return { total, blocked, allowed, blockedPercentage: total > 0 ? Math.round((blocked / total) * 100) : 0, avgConfidence: Math.round(avgConfidence * 100) / 100 }; } /** * Lista alla beslut */ list() { return this.decisions.map(d => ({ id: d.id, timestamp: d.timestamp, operation: d.decision.operation, confidence: d.confidence })); } } export { DecisionProvenance };