Files
boc/EOS/engineering-contract.mjs
T

242 lines
8.7 KiB
JavaScript
Raw Normal View History

#!/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();