#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // EOS Engineering Contract — Hårda regler som agenter måste följa // Erik-krav: "Det ska inte vara rekommendationer utan hårda regler" // ═══════════════════════════════════════════════════════════════════════════ import { execSync } from 'child_process'; import { existsSync, appendFileSync } from 'fs'; const VIOLATION_LOG = '/home/bernt/.openclaw/workspace/EOS/contract-violations.jsonl'; /** * Hårda regler — ingen agent får bryta dessa */ // ── Princip: Verifierbar användning ─────────────────────────────────────── // "Ingen komponent anses existera förrän den producerar verifierbara // artefakter från verklig användning." // ────────────────────────────────────────────────────────────────────────── const CONTRACT_RULES = [ { id: 'no-ssh-prod', rule: 'Ingen direkt SSH till produktion', check: () => { // Kontrollera att ingen SSH-nyckel är aktiv mot produktion try { const sshConfig = execSync('cat ~/.ssh/config 2>/dev/null || echo "NO_CONFIG"', { encoding: 'utf8' }); const hasProdSSH = sshConfig.match(/Host.*prod|Host.*production/i); return { valid: !hasProdSSH, evidence: hasProdSSH ? 'SSH-config innehåller produktionshost' : 'Ingen produktions-SSH hittad' }; } catch { return { valid: true, evidence: 'Ingen SSH-config hittad' }; } }, consequence: 'STOP' }, { id: 'no-server-edit', rule: 'Ingen redigering på server', check: () => { // Kontrollera att vi inte är på en server const isServer = existsSync('/etc/ec2-release') || existsSync('/var/lib/cloud'); return { valid: !isServer, evidence: isServer ? 'Kör på server (EC2/cloud-init hittat)' : 'Kör lokalt' }; }, consequence: 'STOP' }, { id: 'no-uncommitted-changes', rule: 'Ingen ändring utan commit', check: () => { try { const status = execSync('git status --short', { cwd: '/home/bernt/repos/quixzoom.com', encoding: 'utf8' }); return { valid: status.trim().length === 0, evidence: status.trim().length > 0 ? `Ocommitade ändringar: ${status.trim().split("\n").length} filer` : 'Allt commitat' }; } catch { return { valid: false, evidence: 'Kunde inte köra git status' }; } }, consequence: 'ESCALATE' }, { id: 'no-deploy-without-pipeline', rule: 'Ingen deployment utan pipeline', check: () => { const hasCI = existsSync('/home/bernt/repos/quixzoom.com/.github/workflows') || existsSync('/home/bernt/repos/quixzoom.com/.gitlab-ci.yml'); return { valid: hasCI, evidence: hasCI ? 'CI/CD pipeline hittad' : 'Ingen CI/CD pipeline hittad' }; }, consequence: 'STOP' }, { id: 'no-migration-without-version', rule: 'Ingen migration utan versionshantering', check: () => { const hasMigrations = existsSync('/home/bernt/repos/quixzoom.com/migrations'); return { valid: hasMigrations, evidence: hasMigrations ? 'Migrationer versionshanterade' : 'Inga migrationer hittade' }; }, consequence: 'STOP' }, { id: 'no-rollback-without-verification', rule: 'Ingen rollback utan verifierad återställningspunkt', check: () => { // Kontrollera att det finns backup/rollback-mekanism const hasBackup = existsSync('/home/bernt/repos/quixzoom.com/scripts/backup.sh') || existsSync('/home/bernt/repos/quixzoom.com/scripts/rollback.sh'); return { valid: hasBackup, evidence: hasBackup ? 'Backup/rollback scripts hittade' : 'Inga backup/rollback scripts' }; }, consequence: 'ESCALATE' }, { id: 'no-code-without-tests', rule: 'Ingen kod utan tester', check: () => { const hasTests = existsSync('/home/bernt/repos/quixzoom.com/tests') || existsSync('/home/bernt/repos/quixzoom.com/__tests__'); return { valid: hasTests, evidence: hasTests ? 'Tester hittade' : 'Inga tester hittade' }; }, consequence: 'ESCALATE' }, { id: 'no-merge-without-analysis', rule: 'Ingen merge utan analys', check: () => { const hasSIL = existsSync('/home/bernt/.openclaw/workspace/SIL/pr-analyzer.mjs'); return { valid: hasSIL, evidence: hasSIL ? 'SIL finns för PR-analys' : 'SIL saknas' }; }, consequence: 'ESCALATE' } ]; class EngineeringContract { constructor() { this.rules = CONTRACT_RULES; } /** * Validera alla kontraktsregler */ validate() { console.log('🔍 Validerar Engineering Contract...\n'); const results = []; let stopViolations = 0; let escalateViolations = 0; for (const rule of this.rules) { const result = rule.check(); results.push({ id: rule.id, rule: rule.rule, valid: result.valid, evidence: result.evidence, consequence: rule.consequence }); if (!result.valid) { if (rule.consequence === 'STOP') stopViolations++; if (rule.consequence === 'ESCALATE') escalateViolations++; this.logViolation({ rule: rule.id, description: rule.rule, consequence: rule.consequence, evidence: result.evidence }); } const icon = result.valid ? '✅' : rule.consequence === 'STOP' ? '🔴' : '🟡'; console.log(`${icon} ${rule.rule}`); console.log(` ${result.evidence}`); console.log(` Konsekvens: ${rule.consequence}`); console.log(); } const report = { timestamp: new Date().toISOString(), total: this.rules.length, valid: this.rules.length - stopViolations - escalateViolations, stopViolations, escalateViolations, results }; this.printReport(report); return report; } logViolation(violation) { appendFileSync(VIOLATION_LOG, JSON.stringify({ ...violation, timestamp: new Date().toISOString() }) + '\n', 'utf8'); } printReport(report) { console.log('╔═══════════════════════════════════════════════════════════════╗'); console.log('║ ENGINEERING CONTRACT ║'); console.log('║ Hårda regler för agenter ║'); console.log('╚═══════════════════════════════════════════════════════════════╝'); console.log(); console.log(`📊 RESULTAT\n`); console.log(` Regler: ${report.total}`); console.log(` Godkända: ${report.valid} ✅`); console.log(` STOP-violations: ${report.stopViolations} 🔴`); console.log(` ESCALATE-violations: ${report.escalateViolations} 🟡`); console.log(); if (report.stopViolations > 0) { console.log('🔴 STOP — Agenter får INTE fortsätta\n'); for (const result of report.results.filter(r => !r.valid && r.consequence === 'STOP')) { console.log(` • ${result.rule}`); console.log(` ${result.evidence}`); } console.log(); } if (report.escalateViolations > 0) { console.log('🟡 ESCALATE — Kräver mänsklig granskning\n'); for (const result of report.results.filter(r => !r.valid && r.consequence === 'ESCALATE')) { console.log(` • ${result.rule}`); console.log(` ${result.evidence}`); } console.log(); } console.log('═══════════════════════════════════════════════════════════════\n'); } } // ── Main ────────────────────────────────────────────────────────────────── const contract = new EngineeringContract(); contract.validate();