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!
193 lines
5.3 KiB
JavaScript
193 lines
5.3 KiB
JavaScript
#!/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 };
|