Files
boc/EOS/policy-enforcement-v2.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

253 lines
9.6 KiB
JavaScript

#!/usr/bin/env node
// ═══════════════════════════════════════════════════════════════════════════
// EOS Policy Enforcement v2 — Förklara VARFÖR och HUR man häver blockering
// Erik-krav: "Ett bra enforcement-system säger inte bara NEJ. Det säger: vilken regel, varför, evidens, och exakt vad som krävs."
// ═══════════════════════════════════════════════════════════════════════════
import { execSync } from 'child_process';
import { existsSync, appendFileSync } from 'fs';
const ENFORCEMENT_LOG = '/home/bernt/.openclaw/workspace/EOS/enforcement-log-v2.jsonl';
/**
* Policyer med förklaringar och vägar tillbaka
*/
const POLICIES = [
{
id: 'block-direct-prod',
name: 'Blockera direkt skrivning till produktion',
rule: 'Ingen agent får skriva direkt till produktion',
why: 'Direkta ändringar i produktion kan inte spåras, testas eller återställas. De skapar "drift" som skiljer sig från kodbasen.',
evidence: (action) => `Kör på server: ${existsSync('/etc/ec2-release') ? 'Ja' : 'Nej'}`,
requiredToUnblock: [
'Gör ändringen i en branch',
'Skapa PR för code review',
'Låt CI köra tester',
'Mergea till main',
'Låt pipeline deploya'
],
check: (action) => {
const isProd = process.env.NODE_ENV === 'production' || existsSync('/etc/ec2-release');
const isWrite = action.type === 'write' || action.type === 'deploy';
return {
blocked: isProd && isWrite,
reason: isProd && isWrite ? 'Direkt skrivning till produktion' : null
};
}
},
{
id: 'block-no-pipeline-deploy',
name: 'Blockera deployment utan pipeline',
rule: 'Ingen deployment utan godkänd CI/CD pipeline',
why: 'Manuell deployment är felbenägen och kan inte reproduceras. Pipeline säkerställer att tester körs och att deployment är spårbart.',
evidence: (action) => `CI/CD hittad: ${existsSync('/home/bernt/repos/quixzoom.com/.github/workflows') ? 'Ja' : 'Nej'}`,
requiredToUnblock: [
'Skapa .github/workflows/deploy.yml',
'Konfigurera deployment-steg',
'Verifiera att pipeline kan köras',
'Dokumentera pipeline i README'
],
check: (action) => {
if (action.type !== 'deploy') return { blocked: false };
const hasPipeline = existsSync('/home/bernt/repos/quixzoom.com/.github/workflows') ||
existsSync('/home/bernt/repos/quixzoom.com/.gitlab-ci.yml');
return {
blocked: !hasPipeline,
reason: !hasPipeline ? 'Ingen CI/CD pipeline konfigurerad' : null
};
}
},
{
id: 'block-server-edit',
name: 'Blockera redigering på server',
rule: 'Ingen redigering direkt på server',
why: 'Ändringar på server är inte versionshanterade och kan förloras vid omstart eller deployment. All kod ska gå via Git.',
evidence: (action) => `Kör på server: ${existsSync('/etc/ec2-release') ? 'Ja' : 'Nej'}`,
requiredToUnblock: [
'Gör ändringen lokalt eller i en dev-miljö',
'Commita ändringen',
'Pusha till remote',
'Låt CI/CD deploya till server'
],
check: (action) => {
const isServer = existsSync('/etc/ec2-release') || existsSync('/var/lib/cloud');
const isEdit = action.type === 'edit' || action.type === 'write';
return {
blocked: isServer && isEdit,
reason: isServer && isEdit ? 'Redigering på server' : null
};
}
},
{
id: 'block-uncommitted',
name: 'Blockera ändring utan commit',
rule: 'Ingen ny ändring när ocommitade filer finns',
why: 'Ocommitade ändringar riskerar att gå förlorade eller konflikta med nya ändringar. Varje ändring ska vara spårbart.',
evidence: (action) => {
try {
const status = execSync('git status --short', {
cwd: '/home/bernt/repos/quixzoom.com',
encoding: 'utf8'
});
const count = status.trim().split('\n').filter(l => l.trim()).length;
return `${count} ocommitade filer`;
} catch {
return 'Kunde inte kontrollera git-status';
}
},
requiredToUnblock: [
'Kör "git status" för att se ocommitade filer',
'Bestäm: commita eller återställ varje fil',
'Kör "git add" för filer som ska commitas',
'Kör "git commit -m \"beskrivande meddelande\""',
'Kör "git push" för att synka med remote'
],
check: (action) => {
if (action.type === 'commit') return { blocked: false };
try {
const status = execSync('git status --short', {
cwd: '/home/bernt/repos/quixzoom.com',
encoding: 'utf8'
});
const hasUncommitted = status.trim().length > 0;
return {
blocked: hasUncommitted && (action.type === 'write' || action.type === 'edit'),
reason: hasUncommitted ? 'Ocommitade ändringar finns' : null
};
} catch {
return { blocked: true, reason: 'Kunde inte verifiera git-status' };
}
}
},
{
id: 'block-no-migration',
name: 'Blockera databasändring utan migration',
rule: 'Ingen databasändring utan versionshanterad migration',
why: 'Databasändringar utan migration kan inte reproduceras eller återställas. De riskerar att bryta andra miljöer.',
evidence: (action) => `Migrationer hittade: ${existsSync('/home/bernt/repos/quixzoom.com/migrations') ? 'Ja' : 'Nej'}`,
requiredToUnblock: [
'Skapa migrations-katalog om den saknas',
'Använd Prisma/TypeORM/Sequelize för att generera migration',
'Verifiera migration lokalt',
'Commita migration-filen',
'Dokumentera migrationsprocessen'
],
check: (action) => {
if (!action.target?.match(/database|db|sql/i)) return { blocked: false };
const hasMigration = existsSync('/home/bernt/repos/quixzoom.com/migrations') ||
existsSync('/home/bernt/repos/quixzoom.com/prisma');
return {
blocked: !hasMigration,
reason: !hasMigration ? 'Inga databasmigrationer konfigurerade' : null
};
}
},
{
id: 'block-no-api-contract',
name: 'Blockera API-ändring utan kontrakt',
rule: 'Ingen API-ändring utan uppdaterat kontrakt',
why: 'API-ändringar utan kontrakt bryter konsumenter. OpenAPI är den officiella sanningen om API:ets gränssnitt.',
evidence: (action) => `OpenAPI hittad: ${existsSync('/home/bernt/repos/quixzoom.com/openapi.yaml') ? 'Ja' : 'Nej'}`,
requiredToUnblock: [
'Skapa openapi.yaml om den saknas',
'Uppdatera OpenAPI med nya endpoints/ändringar',
'Verifiera att kontraktet är validerbart',
'Commita ändringar i både kod och kontrakt',
'Uppdatera API-dokumentation'
],
check: (action) => {
if (!action.target?.match(/api|endpoint|route/i)) return { blocked: false };
const hasContract = existsSync('/home/bernt/repos/quixzoom.com/openapi.yaml') ||
existsSync('/home/bernt/repos/quixzoom.com/openapi.json');
return {
blocked: !hasContract,
reason: !hasContract ? 'Inget API-kontrakt (OpenAPI) hittat' : null
};
}
}
];
class PolicyEnforcementV2 {
constructor() {
this.policies = POLICIES;
}
validateAction(action) {
console.log(`🔍 Validerar åtgärd: ${action.type} ${action.target || ''}\n`);
const violations = [];
for (const policy of this.policies) {
const result = policy.check(action);
if (result.blocked) {
const violation = {
policy: policy.id,
name: policy.name,
rule: policy.rule,
why: policy.why,
evidence: typeof policy.evidence === 'function' ? policy.evidence(action) : policy.evidence,
requiredToUnblock: policy.requiredToUnblock,
reason: result.reason
};
violations.push(violation);
this.logEnforcement({
action,
policy: policy.id,
blocked: true,
reason: result.reason,
timestamp: new Date().toISOString()
});
}
}
const result = {
action,
allowed: violations.length === 0,
violations,
timestamp: new Date().toISOString()
};
this.printResult(result);
return result;
}
printResult(result) {
if (result.allowed) {
console.log('✅ ÅTGÄRD GODKÄND\n');
console.log(` ${result.action.type} ${result.action.target || ''} kan utföras`);
} else {
console.log('🔴 ÅTGÄRD BLOCKERAD\n');
console.log(` ${result.action.type} ${result.action.target || ''} kan INTE utföras\n`);
for (const v of result.violations) {
console.log(` 📋 REGEL: ${v.rule}`);
console.log(` ❓ VARFÖR: ${v.why}`);
console.log(` 📊 EVIDENS: ${v.evidence}`);
console.log();
console.log(' 🔓 FÖR ATT HÄVA BLOCKERINGEN:\n');
for (const step of v.requiredToUnblock) {
console.log(` 1. ${step}`);
}
console.log();
}
}
}
logEnforcement(entry) {
appendFileSync(ENFORCEMENT_LOG, JSON.stringify(entry) + '\n', 'utf8');
}
}
// ── Main ──────────────────────────────────────────────────────────────────
const enforcement = new PolicyEnforcementV2();
const action = {
type: process.argv[2] || 'deploy',
target: process.argv[3] || 'production'
};
enforcement.validateAction(action);