#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // Architecture Drift Detector — Verifierar att implementation följer kontrakt // ═══════════════════════════════════════════════════════════════════════════ import { readFileSync } from 'fs'; /** * Kontraktets invariants */ const CONTRACT = { executionOrder: [ 'PLANNER', 'CONTEXT', 'MEMORY', 'KNOWLEDGE', 'INTENT', 'EOS', 'DEVELOPER', 'REVIEWER', 'COMMIT', 'OPERATOR' ], requiredPhases: ['PLANNER', 'EOS', 'DEVELOPER'], forbiddenTransitions: [ { from: 'DEVELOPER', to: 'EOS', reason: 'EOS måste alltid köras före Developer' } ], requiredChecks: { 'EOS': ['passed', 'blockedBy', 'reason'] } }; /** * Extrahera exekveringsordning från Runtime-kod */ function extractExecutionOrder(runtimeCode) { const order = []; const methodPattern = /async run(\w+)\(/g; let match; while ((match = methodPattern.exec(runtimeCode)) !== null) { const phase = match[1].toUpperCase(); if (!order.includes(phase)) { order.push(phase); } } return order; } /** * Verifiera exekveringsordning */ function verifyExecutionOrder(actualOrder) { const errors = []; // Kontrollera att alla obligatoriska faser finns for (const required of CONTRACT.requiredPhases) { // EOS kan heta EOS eller EOSCHECK const found = actualOrder.some(phase => phase === required || (required === 'EOS' && phase.includes('EOS')) ); if (!found) { errors.push(`Missing required phase: ${required}`); } } // Kontrollera att ordningen stämmer med kontraktet let contractIndex = 0; for (const phase of actualOrder) { const expectedIndex = CONTRACT.executionOrder.indexOf(phase); if (expectedIndex === -1) continue; // Okänd fas, hoppa över if (expectedIndex < contractIndex) { errors.push(`Phase ${phase} is out of order. Expected after ${CONTRACT.executionOrder[contractIndex]}`); } else { contractIndex = expectedIndex; } } // Kontrollera förbjudna övergångar for (let i = 0; i < actualOrder.length - 1; i++) { const from = actualOrder[i]; const to = actualOrder[i + 1]; for (const forbidden of CONTRACT.forbiddenTransitions) { if (from === forbidden.from && to === forbidden.to) { errors.push(`Forbidden transition: ${from} → ${to}. ${forbidden.reason}`); } } } return errors; } /** * Verifiera att EOS-check alltid körs före Developer */ function verifyEOSBeforeDeveloper(runtimeCode) { const errors = []; // Hitta runEOSCheck och runDeveloper const eosIndex = runtimeCode.indexOf('runEOSCheck'); const devIndex = runtimeCode.indexOf('runDeveloper'); if (eosIndex === -1) { errors.push('runEOSCheck not found in Runtime'); } if (devIndex === -1) { errors.push('runDeveloper not found in Runtime'); } if (eosIndex !== -1 && devIndex !== -1 && eosIndex > devIndex) { errors.push('runEOSCheck must be called before runDeveloper'); } return errors; } /** * Verifiera att Runtime inte har direktåtkomst till Policy Registry */ function verifyMemoryIsolation(runtimeCode) { const errors = []; // Kontrollera att Runtime inte modifierar Policy Registry direkt if (runtimeCode.includes('policyRegistry.') && runtimeCode.includes('=')) { errors.push('Runtime may not modify Policy Registry directly'); } return errors; } /** * Huvudfunktion för drift-detektering */ export function detectArchitectureDrift(runtimePath) { const runtimeCode = readFileSync(runtimePath, 'utf8'); const executionOrder = extractExecutionOrder(runtimeCode); const orderErrors = verifyExecutionOrder(executionOrder); const eosErrors = verifyEOSBeforeDeveloper(runtimeCode); const isolationErrors = verifyMemoryIsolation(runtimeCode); const allErrors = [...orderErrors, ...eosErrors, ...isolationErrors]; return { drift: allErrors.length, errors: allErrors, executionOrder, passed: allErrors.length === 0 }; } /** * CLI-användning */ if (process.argv[1] === new URL(import.meta.url).pathname) { const runtimePath = process.argv[2] || './agent-runtime-v10.mjs'; console.log('═══════════════════════════════════════════════════════════════'); console.log(' ARCHITECTURE DRIFT DETECTOR'); console.log('═══════════════════════════════════════════════════════════════\n'); const result = detectArchitectureDrift(runtimePath); console.log(`Runtime: ${runtimePath}`); console.log(`Execution Order: ${result.executionOrder.join(' → ')}`); console.log(`Drift: ${result.drift}`); if (result.errors.length > 0) { console.log('\n❌ ERRORS:'); for (const error of result.errors) { console.log(` - ${error}`); } process.exit(1); } else { console.log('\n✅ No architecture drift detected'); process.exit(0); } }