Files
boc/SIL/uncertainty.mjs
T
Bernt 05ed037fe8 pilot.landvex.com: HTTPS + Full Stack Verified
- 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!
2026-07-02 17:34:19 +00:00

344 lines
12 KiB
JavaScript

#!/usr/bin/env node
// ═══════════════════════════════════════════════════════════════════════════
// SIL Uncertainty — Osäkerhet som förstklassig signal
// Erik-krav: "Jag skulle också vilja att agenten ibland kan säga: 'Jag vet inte.'"
// ═══════════════════════════════════════════════════════════════════════════
/**
* Uncertainty-aware analysis — när SIL är osäker säger den det tydligt
*/
export class UncertaintyEngine {
constructor() {
this.thresholds = {
// När ska vi säga "Jag vet inte"?
lowConfidence: 0.5, // Under 50% — osäker
mediumConfidence: 0.7, // 50-70% — kvalificerad gissning
highConfidence: 0.9 // Över 90% — säker
};
}
/**
* Utvärdera osäkerhet för en slutsats
*/
evaluateUncertainty(conclusion) {
const confidence = conclusion.confidence || 0;
const evidence = conclusion.evidence || [];
const verified = conclusion.verified || false;
// Beräkna olika osäkerhetsdimensioner
const dimensions = {
// 1. Confidence-osäkerhet
confidenceUncertainty: this.calculateConfidenceUncertainty(confidence),
// 2. Evidence-osäkerhet (hur mycket evidence har vi?)
evidenceUncertainty: this.calculateEvidenceUncertainty(evidence),
// 3. Verifierings-osäkerhet (är det verifierat eller infererat?)
verificationUncertainty: verified ? 0 : 0.3,
// 4. Kontext-osäkerhet (saknas kontext?)
contextUncertainty: this.calculateContextUncertainty(conclusion)
};
// Total osäkerhet (max av dimensionerna — vi är osäkra om NÅGON dimension är osäker)
const totalUncertainty = Math.max(...Object.values(dimensions));
// Bestäm signal
let signal;
let explanation;
if (totalUncertainty > 0.7) {
signal = 'UNKNOWN';
explanation = this.generateUnknownExplanation(dimensions, conclusion);
} else if (totalUncertainty > 0.4) {
signal = 'UNCERTAIN';
explanation = this.generateUncertainExplanation(dimensions, conclusion);
} else {
signal = 'CERTAIN';
explanation = null;
}
return {
conclusion: conclusion.text || conclusion.conclusion,
confidence,
signal, // UNKNOWN, UNCERTAIN, CERTAIN
totalUncertainty: Math.round(totalUncertainty * 100) + '%',
dimensions: {
confidence: Math.round(dimensions.confidenceUncertainty * 100) + '%',
evidence: Math.round(dimensions.evidenceUncertainty * 100) + '%',
verification: Math.round(dimensions.verificationUncertainty * 100) + '%',
context: Math.round(dimensions.contextUncertainty * 100) + '%'
},
explanation,
// Rekommendation baserat på osäkerhet
recommendation: this.generateRecommendation(signal, conclusion)
};
}
calculateConfidenceUncertainty(confidence) {
// Lägre confidence = högre osäkerhet
if (confidence >= this.thresholds.highConfidence) return 0.1;
if (confidence >= this.thresholds.mediumConfidence) return 0.3;
if (confidence >= this.thresholds.lowConfidence) return 0.6;
return 0.9;
}
calculateEvidenceUncertainty(evidence) {
if (!evidence || evidence.length === 0) return 0.9;
if (evidence.length === 1) return 0.5;
if (evidence.length >= 3) return 0.1;
return 0.3;
}
calculateContextUncertainty(conclusion) {
// Om vi saknar kontext (t.ex. runtime-data) är vi osäkra
const needsRuntime = conclusion.text?.match(/runtime|production|live|actual/i);
const hasRuntime = conclusion.evidence?.some(e =>
e.source?.match(/runtime|trace|metric|log/i)
);
if (needsRuntime && !hasRuntime) return 0.7;
return 0.1;
}
generateUnknownExplanation(dimensions, conclusion) {
const reasons = [];
if (parseFloat(dimensions.confidenceUncertainty) > 0.5) {
reasons.push(`Confidence är för låg (${Math.round((conclusion.confidence || 0) * 100)}%)`);
}
if (parseFloat(dimensions.evidenceUncertainty) > 0.5) {
reasons.push('För lite evidence — behöver fler källor');
}
if (parseFloat(dimensions.verificationUncertainty) > 0.2) {
reasons.push('Slutsatsen är inte verifierad — endast inferens');
}
if (parseFloat(dimensions.contextUncertainty) > 0.5) {
reasons.push('Saknar runtime-kontext — kan inte verifiera i produktion');
}
return {
status: 'JAG VET INTE',
reasons,
whatWeKnow: this.extractWhatWeKnow(conclusion),
whatWeNeed: this.extractWhatWeNeed(dimensions, conclusion)
};
}
generateUncertainExplanation(dimensions, conclusion) {
const reasons = [];
if (parseFloat(dimensions.confidenceUncertainty) > 0.3) {
reasons.push(`Confidence är medel (${Math.round((conclusion.confidence || 0) * 100)}%)`);
}
if (parseFloat(dimensions.evidenceUncertainty) > 0.3) {
reasons.push('Begränsat evidence');
}
return {
status: 'OSÄKER',
reasons,
caveat: 'Slutsatsen kan vara korrekt men bör verifieras manuellt'
};
}
extractWhatWeKnow(conclusion) {
const known = [];
if (conclusion.evidence) {
for (const ev of conclusion.evidence) {
if (ev.verified) {
known.push(ev.description || ev.type);
}
}
}
return known.length > 0 ? known : ['Inget är säkert känt'];
}
extractWhatWeNeed(dimensions, conclusion) {
const needed = [];
if (parseFloat(dimensions.confidenceUncertainty) > 0.5) {
needed.push('Mer data för att höja confidence');
}
if (parseFloat(dimensions.verificationUncertainty) > 0.2) {
needed.push('Runtime-verifiering (traces, metrics, logs)');
}
if (parseFloat(dimensions.contextUncertainty) > 0.5) {
needed.push('Produktionsdata för att validera');
}
return needed;
}
generateRecommendation(signal, conclusion) {
switch (signal) {
case 'UNKNOWN':
return {
action: 'REQUIRE_MANUAL_REVIEW',
message: 'SIL kan inte avgöra — kräver mänsklig granskning',
autoApprove: false,
blockMerge: true
};
case 'UNCERTAIN':
return {
action: 'SUGGEST_VERIFICATION',
message: 'SIL är osäker — föreslå verifiering innan merge',
autoApprove: false,
blockMerge: false
};
case 'CERTAIN':
return {
action: 'AUTO_APPROVE',
message: 'SIL är säker — kan godkännas automatiskt',
autoApprove: true,
blockMerge: false
};
}
}
/**
* Formatera osäkerhet för mänsklig läsning
*/
formatUncertainty(uncertaintyResult) {
const lines = [];
lines.push(`\n${'='.repeat(60)}`);
lines.push('OSÄKERHETSANALYS');
lines.push(`${'='.repeat(60)}\n`);
lines.push(`Slutsats: ${uncertaintyResult.conclusion}`);
lines.push(`Confidence: ${Math.round((uncertaintyResult.confidence || 0) * 100)}%`);
lines.push(`Signal: ${uncertaintyResult.signal}`);
lines.push(`Total osäkerhet: ${uncertaintyResult.totalUncertainty}\n`);
lines.push('Dimensioner:');
for (const [dim, value] of Object.entries(uncertaintyResult.dimensions)) {
lines.push(` ${dim}: ${value}`);
}
if (uncertaintyResult.explanation) {
lines.push(`\n${uncertaintyResult.explanation.status}`);
if (uncertaintyResult.explanation.reasons) {
lines.push('Anledningar:');
for (const reason of uncertaintyResult.explanation.reasons) {
lines.push(`${reason}`);
}
}
if (uncertaintyResult.explanation.whatWeKnow) {
lines.push('\nVad vi vet:');
for (const known of uncertaintyResult.explanation.whatWeKnow) {
lines.push(`${known}`);
}
}
if (uncertaintyResult.explanation.whatWeNeed) {
lines.push('\nVad vi behöver:');
for (const need of uncertaintyResult.explanation.whatWeNeed) {
lines.push(` ? ${need}`);
}
}
}
lines.push(`\nRekommendation: ${uncertaintyResult.recommendation.message}`);
lines.push(`Åtgärd: ${uncertaintyResult.recommendation.action}`);
lines.push(`Blockera merge: ${uncertaintyResult.recommendation.blockMerge ? 'Ja' : 'Nej'}`);
lines.push(`${'='.repeat(60)}\n`);
return lines.join('\n');
}
}
// ── Integration med PRAnalyzer ────────────────────────────────────────────
export function withUncertainty(AnalyzerClass) {
return class extends AnalyzerClass {
constructor(...args) {
super(...args);
this.uncertainty = new UncertaintyEngine();
}
analyzePR(repoPath, baseBranch = 'main', headBranch = 'HEAD') {
const report = super.analyzePR(repoPath, baseBranch, headBranch);
// Utvärdera osäkerhet för varje slutsats
if (report.provenance && report.provenance.trace) {
const trace = JSON.parse(report.provenance.trace);
for (const conclusion of trace.conclusions || []) {
const uncertainty = this.uncertainty.evaluateUncertainty(conclusion);
// Lägg till osäkerhet i rapporten
if (!report.uncertainty) report.uncertainty = [];
report.uncertainty.push(uncertainty);
}
}
// Lägg till osäkerhetssammanfattning
if (report.uncertainty) {
const unknownCount = report.uncertainty.filter(u => u.signal === 'UNKNOWN').length;
const uncertainCount = report.uncertainty.filter(u => u.signal === 'UNCERTAIN').length;
const certainCount = report.uncertainty.filter(u => u.signal === 'CERTAIN').length;
report.uncertaintySummary = {
total: report.uncertainty.length,
unknown: unknownCount,
uncertain: uncertainCount,
certain: certainCount,
// Om NÅGON slutsats är UNKNOWN, blockera merge
shouldBlockMerge: unknownCount > 0,
// Om fler än 30% är osäkra, föreslå manuell review
suggestManualReview: (unknownCount + uncertainCount) / report.uncertainty.length > 0.3
};
}
return report;
}
};
}
// ── CLI ───────────────────────────────────────────────────────────────────
if (import.meta.url === `file://${process.argv[1]}`) {
const engine = new UncertaintyEngine();
// Demo: olika typer av osäkerhet
const examples = [
{
text: 'Wallet Service påverkar Stripe',
confidence: 0.95,
verified: true,
evidence: [
{ type: 'git_diff', verified: true, source: 'git' },
{ type: 'ast_analysis', verified: true, source: 'AST' }
]
},
{
text: 'Auth Service påverkar KYC-flödet',
confidence: 0.42,
verified: false,
evidence: [
{ type: 'readme_reference', verified: false, source: 'README' }
]
},
{
text: 'Mission Service påverkar produktionsdatabasen',
confidence: 0.65,
verified: false,
evidence: [
{ type: 'code_reference', verified: false, source: 'AST' }
]
}
];
console.log('🎯 OSÄKERHETS-DEMO\n');
for (const example of examples) {
const result = engine.evaluateUncertainty(example);
console.log(engine.formatUncertainty(result));
}
}