#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // Evidence Resolver v3 — Med UNKNOWN_OPERATION // ═══════════════════════════════════════════════════════════════════════════ const OPERATIONS = { REMOTE_PRODUCTION_ACCESS: { id: 'REMOTE_PRODUCTION_ACCESS', description: 'Fjärråtkomst till produktionsmiljö', policy: 'POL-SEC-001', severity: 'CRITICAL', allowed: false }, MODIFY_PERSISTENT_PRODUCTION_DATA: { id: 'MODIFY_PERSISTENT_PRODUCTION_DATA', description: 'Förändring av persistent data i produktion', policy: 'POL-DAT-001', severity: 'CRITICAL', allowed: false }, MODIFY_PRODUCTION_INFRASTRUCTURE: { id: 'MODIFY_PRODUCTION_INFRASTRUCTURE', description: 'Förändring av produktionsinfrastruktur', policy: 'POL-INFRA-001', severity: 'CRITICAL', allowed: false }, PRODUCTION_RELEASE: { id: 'PRODUCTION_RELEASE', description: 'Publicering av kod till produktion', policy: 'POL-DEP-001', severity: 'CRITICAL', allowed: false }, EXPOSE_OR_CREATE_SECRET: { id: 'EXPOSE_OR_CREATE_SECRET', description: 'Exponering eller skapande av hemligheter', policy: 'POL-SEC-002', severity: 'CRITICAL', allowed: false }, READ_PRODUCTION_DATA: { id: 'READ_PRODUCTION_DATA', description: 'Läsning av produktionsdata', policy: null, severity: 'LOW', allowed: true }, OBSERVE_INFRASTRUCTURE: { id: 'OBSERVE_INFRASTRUCTURE', description: 'Observation av infrastruktur', policy: null, severity: 'LOW', allowed: true }, DEVELOPMENT_TASK: { id: 'DEVELOPMENT_TASK', description: 'Utvecklingsuppgift', policy: null, severity: 'LOW', allowed: true }, UNKNOWN_OPERATION: { id: 'UNKNOWN_OPERATION', description: 'Okänd operation — otillräcklig evidens', policy: null, severity: 'MEDIUM', allowed: false, // Blockera som standard tills mer kontext finns requiresEscalation: true } }; function extractIntents(task) { const description = (task.description || '').toLowerCase(); const action = (task.action || '').toLowerCase(); const candidates = []; const intentSignals = { REMOTE_PRODUCTION_ACCESS: { signals: [ 'ssh', 'login', 'logga in', 'anslut', 'connect', 'session', 'shell', 'terminal', 'fjärr', 'remote', 'tunnel', 'port forward', 'komma åt', 'få tillgång', 'access', 'nå', 'kommunicera', 'felsöka', 'debug', 'automatisera', 'script', 'köra' ], weight: 1.0 }, MODIFY_PERSISTENT_PRODUCTION_DATA: { signals: [ 'databas', 'database', 'sql', 'update', 'delete', 'insert', 'alter', 'data', 'post', 'record', 'värde', 'value', 'fält', 'field', 'kolumn', 'column', 'table', 'tabell', 'rad', 'row', 'uppdatera', 'radera', 'infoga', 'ändra', 'modifiera', 'korrigera', 'fixa', 'justera', 'ändra data' ], weight: 1.0 }, MODIFY_PRODUCTION_INFRASTRUCTURE: { signals: [ 'infrastruktur', 'infrastructure', 'resurs', 'resource', 'instans', 'instance', 'molnet', 'cloud', 'security group', 'ec2', 'iam', 'alb', 'route53', 'skala', 'scale', 'serverless', 'terraform', 'aws', 'skapa', 'create', 'modify', 'uppdatera', 'update', 'ta bort', 'delete', 'konfigurera', 'configure' ], weight: 1.0 }, PRODUCTION_RELEASE: { signals: [ 'deploy', 'release', 'publicera', 'pusha', 'släpp', 'lansera', 'gå live', 'rollout', 'version', 'pipeline', 'ci/cd', 'buggfix', 'fix', 'uppdatering', 'update' ], weight: 1.0 }, EXPOSE_OR_CREATE_SECRET: { signals: [ 'password', 'secret', 'token', 'key', 'lösenord', 'nyckel', 'hemlig', 'credential', 'auth', 'autentisering', 'hårdkoda', 'hardcode' ], weight: 1.5 } }; for (const [opId, config] of Object.entries(intentSignals)) { let score = 0; const matchedSignals = []; for (const signal of config.signals) { if (description.includes(signal)) { score += 1; matchedSignals.push(signal); } if (action === signal) { score += 2; matchedSignals.push(`action:${signal}`); } } if (task.target === 'production' || task.target === 'prod') { score += 1; } if (score > 0) { candidates.push({ operation: opId, confidence: Math.min(score * config.weight / 3, 1), signals: matchedSignals, source: 'intent_extraction' }); } } candidates.sort((a, b) => b.confidence - a.confidence); return candidates; } function collectEvidence(task, candidates) { const evidence = { targetEnvironment: null, accessMethod: null, dataOperation: null, infraOperation: null, releaseOperation: null, secretPattern: null, readOperation: null, observeOperation: null, fileType: null, tools: [], context: [], evidenceQuality: 0 }; const description = (task.description || '').toLowerCase(); const action = (task.action || '').toLowerCase(); // Target Environment if (task.target === 'production' || description.includes('produktion') || description.includes('production')) { evidence.targetEnvironment = 'production'; evidence.evidenceQuality += 50; } // Access Method if (['ssh', 'ssm', 'telnet', 'rdp'].includes(action)) { evidence.accessMethod = action; evidence.evidenceQuality += 100; } // Data Operation if (['update', 'delete', 'insert', 'alter', 'drop'].includes(action)) { evidence.dataOperation = action; evidence.evidenceQuality += 100; } // Infra Operation if (['create', 'modify', 'update', 'delete', 'configure'].includes(action)) { evidence.infraOperation = action; evidence.evidenceQuality += 100; } // Release Operation if (['deploy', 'release', 'publish', 'push'].includes(action)) { evidence.releaseOperation = action; evidence.evidenceQuality += 100; } // Secret Pattern if (task.files) { for (const file of task.files) { const content = file.content || ''; if (/password|secret|token|api_key|private_key/i.test(content)) { evidence.secretPattern = 'hardcoded_secret'; evidence.fileType = file.path.split('.').pop(); evidence.evidenceQuality += 100; } } } // Read Operation if (['read', 'select', 'show', 'display'].includes(action) || description.includes('läs') || description.includes('visa')) { evidence.readOperation = true; evidence.evidenceQuality += 50; } // Observe Operation if (['inventory', 'plan', 'validate', 'health-check'].includes(action)) { evidence.observeOperation = action; evidence.evidenceQuality += 50; } // Kontext-signaler if (description.includes('manuellt')) evidence.context.push('manual'); if (description.includes('direkt')) evidence.context.push('direct'); if (description.includes('temporär')) evidence.context.push('temporary'); return evidence; } function resolveOperation(candidates, evidence) { // Om vi har stark direkt evidens, använd den // Secrets har högsta prioritet if (evidence.secretPattern) { return { operation: OPERATIONS.EXPOSE_OR_CREATE_SECRET, confidence: 0.95, evidence: ['secretPattern', `fileType:${evidence.fileType}`], reasoning: 'Hittade hårdkodad hemlighet i fil', evidenceQuality: evidence.evidenceQuality }; } // Produktionsåtkomst med accessMethod if (evidence.targetEnvironment === 'production' && evidence.accessMethod) { return { operation: OPERATIONS.REMOTE_PRODUCTION_ACCESS, confidence: 0.9, evidence: ['targetEnvironment:production', `accessMethod:${evidence.accessMethod}`], reasoning: 'Fjärråtkomst till produktion', evidenceQuality: evidence.evidenceQuality }; } // Dataförändring i produktion if (evidence.targetEnvironment === 'production' && evidence.dataOperation) { return { operation: OPERATIONS.MODIFY_PERSISTENT_PRODUCTION_DATA, confidence: 0.9, evidence: ['targetEnvironment:production', `dataOperation:${evidence.dataOperation}`], reasoning: 'Dataförändring i produktion', evidenceQuality: evidence.evidenceQuality }; } // Infrastrukturförändring i produktion if (evidence.targetEnvironment === 'production' && evidence.infraOperation) { return { operation: OPERATIONS.MODIFY_PRODUCTION_INFRASTRUCTURE, confidence: 0.9, evidence: ['targetEnvironment:production', `infraOperation:${evidence.infraOperation}`], reasoning: 'Infrastrukturförändring i produktion', evidenceQuality: evidence.evidenceQuality }; } // Release till produktion if (evidence.targetEnvironment === 'production' && evidence.releaseOperation) { return { operation: OPERATIONS.PRODUCTION_RELEASE, confidence: 0.9, evidence: ['targetEnvironment:production', `releaseOperation:${evidence.releaseOperation}`], reasoning: 'Release till produktion', evidenceQuality: evidence.evidenceQuality }; } // Om vi har kandidater från intent extraction med tillräcklig konfidens if (candidates.length > 0) { const bestCandidate = candidates[0]; // Om konfidensen är för låg, returnera UNKNOWN if (bestCandidate.confidence < 0.3) { return { operation: OPERATIONS.UNKNOWN_OPERATION, confidence: bestCandidate.confidence, evidence: bestCandidate.signals, reasoning: `Låg konfidens (${bestCandidate.confidence.toFixed(2)}): ${bestCandidate.signals.slice(0, 3).join(', ')}`, evidenceQuality: evidence.evidenceQuality }; } const op = OPERATIONS[bestCandidate.operation]; if (op) { let confidence = bestCandidate.confidence; if (evidence.targetEnvironment === 'production') { confidence = Math.min(confidence * 1.5, 1); } return { operation: op, confidence: confidence, evidence: bestCandidate.signals, reasoning: `Intent extraction: ${bestCandidate.signals.slice(0, 3).join(', ')}`, evidenceQuality: evidence.evidenceQuality }; } } // Om vi har produktion som target men ingen specifik operation if (evidence.targetEnvironment === 'production' && evidence.evidenceQuality < 50) { return { operation: OPERATIONS.UNKNOWN_OPERATION, confidence: 0.3, evidence: ['targetEnvironment:production'], reasoning: 'Produktionsmiljö men otillräcklig evidens för specifik operation', evidenceQuality: evidence.evidenceQuality }; } // Default: Utvecklingsuppgift return { operation: OPERATIONS.DEVELOPMENT_TASK, confidence: 0.5, evidence: ['default'], reasoning: 'Ingen specifik operation identifierad, antar utvecklingsuppgift', evidenceQuality: evidence.evidenceQuality }; } function resolve(task) { const candidates = extractIntents(task); const evidence = collectEvidence(task, candidates); const resolution = resolveOperation(candidates, evidence); return { candidates: candidates.slice(0, 3), evidence, resolution, trace: { intentExtraction: candidates.length > 0 ? 'found_candidates' : 'no_candidates', evidenceCollection: Object.keys(evidence).filter(k => evidence[k] !== null && evidence[k] !== false && k !== 'evidenceQuality').length, resolution: resolution.operation.id, evidenceQuality: resolution.evidenceQuality } }; } export { OPERATIONS, extractIntents, collectEvidence, resolveOperation, resolve };