Files
boc/EOS/readiness-gate.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

164 lines
5.4 KiB
JavaScript

#!/usr/bin/env node
// ═══════════════════════════════════════════════════════════════════════════
// EOS Readiness Gate — Ingen PR får mergeas om den sänker Engineering Readiness
// Erik-krav: "Readiness före: Git 82, Efter: Git 76 → Merge blockeras"
// ═══════════════════════════════════════════════════════════════════════════
import { readFileSync, existsSync } from 'fs';
const READINESS_PATH = '/home/bernt/.openclaw/workspace/EOS/readiness-report-v3.json';
const HISTORY_PATH = '/home/bernt/.openclaw/workspace/EOS/readiness-history.json';
class ReadinessGate {
constructor() {
this.currentReadiness = this.loadCurrentReadiness();
this.history = this.loadHistory();
}
loadCurrentReadiness() {
if (existsSync(READINESS_PATH)) {
return JSON.parse(readFileSync(READINESS_PATH, 'utf8'));
}
return null;
}
loadHistory() {
if (existsSync(HISTORY_PATH)) {
return JSON.parse(readFileSync(HISTORY_PATH, 'utf8'));
}
return [];
}
/**
* Kontrollera om en PR skulle sänka Readiness
*/
checkPR(pr) {
console.log(`🔍 Readiness Gate: Kontrollerar PR "${pr.title}"\n`);
if (!this.currentReadiness) {
console.log('❌ Ingen Readiness-rapport hittad');
return { canMerge: false, reason: 'Ingen Readiness-rapport' };
}
// Simulera hur PR påverkar Readiness
const impact = this.simulateImpact(pr);
console.log('📊 PÅVERKAN PÅ READINESS\n');
let blocked = false;
const changes = [];
for (const area of this.currentReadiness.areas) {
const areaImpact = impact.areas.find(a => a.id === area.id);
if (!areaImpact) continue;
const before = area.score;
const after = areaImpact.score;
const delta = after - before;
changes.push({
area: area.name,
before,
after,
delta
});
const icon = delta > 0 ? '📈' : delta < 0 ? '📉' : '➡️';
console.log(` ${icon} ${area.name}: ${before}% → ${after}% (${delta > 0 ? '+' : ''}${delta}%)`);
// Blockera om Readiness sjunker under tröskel
if (after < area.target && before >= area.target) {
console.log(` 🔴 Sjunker under mål (${area.target}%)`);
blocked = true;
}
// Blockera om Readiness sjunker överhuvudtaget för kritiska områden
if (delta < 0 && ['git', 'cicd', 'backup', 'infrastructure'].includes(area.id)) {
console.log(` 🔴 Kritiskt område försämras`);
blocked = true;
}
}
console.log();
const result = {
canMerge: !blocked,
changes,
overallBefore: this.currentReadiness.overall,
overallAfter: impact.overall,
overallDelta: impact.overall - this.currentReadiness.overall
};
if (blocked) {
console.log('🔴 MERGE BLOCKERAD\n');
console.log(' PR:en sänker Engineering Readiness.');
console.log(' Åtgärda försämringarna innan merge.\n');
} else if (result.overallDelta < 0) {
console.log('⚠️ MERGE VARNING\n');
console.log(' PR:en sänker Readiness men inte under tröskel.');
console.log(' Överväg att åtgärda ändå.\n');
} else {
console.log('✅ MERGE GODKÄND\n');
console.log(' PR:en förbättrar eller behåller Readiness.\n');
}
return result;
}
simulateImpact(pr) {
// Simulera påverkan baserat på PR:ens filer
const areas = this.currentReadiness.areas.map(area => ({
...area,
score: area.score
}));
let overallImpact = 0;
for (const file of pr.files || []) {
// Exempel: Om PR lägger till tester → öka Testing
if (file.includes('test') || file.includes('spec')) {
const testingArea = areas.find(a => a.id === 'testing');
if (testingArea) testingArea.score = Math.min(100, testingArea.score + 5);
}
// Om PR lägger till CI/CD → öka CI/CD
if (file.includes('.github') || file.includes('.gitlab')) {
const cicdArea = areas.find(a => a.id === 'cicd');
if (cicdArea) cicdArea.score = Math.min(100, cicdArea.score + 10);
}
// Om PR ändrar utan .gitignore → sänk Git
if (file.includes('node_modules') || file.includes('dist')) {
const gitArea = areas.find(a => a.id === 'git');
if (gitArea) gitArea.score = Math.max(0, gitArea.score - 10);
}
}
// Beräkna ny overall
let totalScore = 0;
let totalWeight = 0;
for (const area of areas) {
totalScore += area.score * area.weight;
totalWeight += area.weight;
}
return {
overall: Math.round(totalScore / totalWeight),
areas
};
}
}
// ── Main ──────────────────────────────────────────────────────────────────
const gate = new ReadinessGate();
const pr = {
title: process.argv[2] || 'Lägg till tester för Mission Service',
files: process.argv.slice(3).length > 0 ? process.argv.slice(3) : [
'src/services/mission.test.ts',
'.github/workflows/test.yml'
]
};
gate.checkPR(pr);