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

535 lines
17 KiB
JavaScript

#!/usr/bin/env node
// ═══════════════════════════════════════════════════════════════════════════
// EOS Engineering Readiness v3 — Viktade områden
// Erik-krav: "En hög poäng i ett mindre kritiskt område ska inte kompensera för saknad deployprocess"
// ═══════════════════════════════════════════════════════════════════════════
import { execSync } from 'child_process';
import { existsSync, writeFileSync } from 'fs';
const READINESS_PATH = '/home/bernt/.openclaw/workspace/EOS/readiness-report-v3.json';
/**
* Viktade områden — kritiska områden väger tyngre
*/
const READINESS_AREAS = [
{
id: 'git',
name: 'Git Discipline',
weight: 0.20, // 20%
target: 90,
rationale: 'Allt börjar med Git. Utan versionshantering finns ingen spårbarhet.',
measures: [
{
name: 'Ocommitade filer',
check: () => {
try {
const status = execSync('git status --short', {
cwd: '/home/bernt/repos/quixzoom.com',
encoding: 'utf8'
});
const uncommitted = status.trim().split('\n').filter(l => l.trim()).length;
const score = Math.max(0, 100 - uncommitted * 2);
return {
score,
evidence: `${uncommitted} ocommitade filer`,
blocker: uncommitted > 10,
action: uncommitted > 0 ? `Commita eller .gitignore ${uncommitted} filer` : null
};
} catch {
return { score: 0, evidence: 'Kunde inte köra git status', blocker: true, action: 'Kontrollera git-konfiguration' };
}
}
},
{
name: 'Git-konfiguration',
check: () => {
const hasGit = existsSync('/home/bernt/repos/quixzoom.com/.git');
return {
score: hasGit ? 100 : 0,
evidence: hasGit ? 'Git-repo hittat' : 'Inget Git-repo',
blocker: !hasGit,
action: !hasGit ? 'Initiera git-repo' : null
};
}
},
{
name: 'Branch-skydd',
check: () => {
return {
score: 0,
evidence: 'Branch-skydd ej verifierat',
blocker: true,
action: 'Konfigurera branch-skydd på GitHub/GitLab'
};
}
}
]
},
{
id: 'cicd',
name: 'CI/CD',
weight: 0.20, // 20%
target: 90,
rationale: 'Utan automatiserad pipeline är deployment felbenägen och ospårbar.',
measures: [
{
name: 'CI/CD pipeline',
check: () => {
const hasCI = existsSync('/home/bernt/repos/quixzoom.com/.github/workflows') ||
existsSync('/home/bernt/repos/quixzoom.com/.gitlab-ci.yml');
return {
score: hasCI ? 100 : 0,
evidence: hasCI ? 'CI/CD hittad' : 'Ingen CI/CD',
blocker: !hasCI,
action: !hasCI ? 'Sätt upp GitHub Actions eller GitLab CI' : null
};
}
},
{
name: 'Automatiserade tester i pipeline',
check: () => {
return {
score: 0,
evidence: 'Ej verifierat',
blocker: true,
action: 'Lägg till test-steg i CI-pipeline'
};
}
}
]
},
{
id: 'backup',
name: 'Backup & Rollback',
weight: 0.15, // 15%
target: 80,
rationale: 'Förmågan att återställa vid fel är avgörande för produktionssäkerhet.',
measures: [
{
name: 'Backup-procedur',
check: () => {
const hasBackup = existsSync('/home/bernt/repos/quixzoom.com/scripts/backup.sh');
return {
score: hasBackup ? 100 : 0,
evidence: hasBackup ? 'Backup-script hittat' : 'Inget backup-script',
blocker: !hasBackup,
action: !hasBackup ? 'Skapa backup-script för databas' : null
};
}
},
{
name: 'Rollback-procedur',
check: () => {
const hasRollback = existsSync('/home/bernt/repos/quixzoom.com/scripts/rollback.sh');
return {
score: hasRollback ? 100 : 0,
evidence: hasRollback ? 'Rollback-script hittat' : 'Inget rollback-script',
blocker: !hasRollback,
action: !hasRollback ? 'Skapa rollback-script' : null
};
}
}
]
},
{
id: 'infrastructure',
name: 'Infrastructure as Code',
weight: 0.15, // 15%
target: 80,
rationale: 'Manuell infrastruktur är ospårbar och kan inte reproduceras.',
measures: [
{
name: 'IaC-verktyg',
check: () => {
const hasIaC = existsSync('/home/bernt/repos/quixzoom.com/terraform') ||
existsSync('/home/bernt/repos/quixzoom.com/cloudformation') ||
existsSync('/home/bernt/repos/quixzoom.com/cdk');
return {
score: hasIaC ? 100 : 0,
evidence: hasIaC ? 'IaC hittad' : 'Ingen IaC',
blocker: !hasIaC,
action: !hasIaC ? 'Skapa Terraform/CloudFormation för AWS-resurser' : null
};
}
},
{
name: 'Miljökonfiguration',
check: () => {
const hasEnv = existsSync('/home/bernt/repos/quixzoom.com/.env.example');
return {
score: hasEnv ? 100 : 0,
evidence: hasEnv ? '.env.example hittad' : 'Ingen .env.example',
blocker: false,
action: !hasEnv ? 'Skapa .env.example' : null
};
}
}
]
},
{
id: 'testing',
name: 'Testing',
weight: 0.10, // 10%
target: 80,
rationale: 'Tester ger förtroende för att ändringar inte bryter befintlig funktionalitet.',
measures: [
{
name: 'Tester',
check: () => {
const hasTests = existsSync('/home/bernt/repos/quixzoom.com/tests') ||
existsSync('/home/bernt/repos/quixzoom.com/__tests__');
return {
score: hasTests ? 100 : 0,
evidence: hasTests ? 'Tester hittade' : 'Inga tester',
blocker: !hasTests,
action: !hasTests ? 'Skapa första testerna' : null
};
}
},
{
name: 'Test-konfiguration',
check: () => {
const hasConfig = existsSync('/home/bernt/repos/quixzoom.com/jest.config.js') ||
existsSync('/home/bernt/repos/quixzoom.com/vitest.config.ts');
return {
score: hasConfig ? 100 : 0,
evidence: hasConfig ? 'Test-config hittad' : 'Ingen test-config',
blocker: !hasConfig,
action: !hasConfig ? 'Konfigurera Jest/Vitest' : null
};
}
}
]
},
{
id: 'security',
name: 'Security',
weight: 0.10, // 10%
target: 70,
rationale: 'Säkerhet är inte en feature utan en förutsättning.',
measures: [
{
name: 'Dependency scanning',
check: () => {
return {
score: 0,
evidence: 'Ej verifierat',
blocker: false,
action: 'Lägg till npm audit i CI'
};
}
},
{
name: 'Secret-hantering',
check: () => {
const hasEnv = existsSync('/home/bernt/repos/quixzoom.com/.env.example');
return {
score: hasEnv ? 50 : 0,
evidence: hasEnv ? '.env.example finns' : 'Ingen secret-hantering',
blocker: false,
action: 'Använd AWS Secrets Manager eller liknande'
};
}
}
]
},
{
id: 'api-contracts',
name: 'API Contracts',
weight: 0.05, // 5%
target: 80,
rationale: 'API-kontrakt dokumenterar gränssnitt och förhindrar brytande ändringar.',
measures: [
{
name: 'OpenAPI-spec',
check: () => {
const hasOpenAPI = existsSync('/home/bernt/repos/quixzoom.com/openapi.yaml') ||
existsSync('/home/bernt/repos/quixzoom.com/openapi.json');
return {
score: hasOpenAPI ? 100 : 0,
evidence: hasOpenAPI ? 'OpenAPI hittad' : 'Ingen OpenAPI',
blocker: !hasOpenAPI,
action: !hasOpenAPI ? 'Skapa OpenAPI-spec för alla endpoints' : null
};
}
},
{
name: 'API-dokumentation',
check: () => {
const hasDocs = existsSync('/home/bernt/repos/quixzoom.com/API.md') ||
existsSync('/home/bernt/repos/quixzoom.com/docs/api.md');
return {
score: hasDocs ? 100 : 0,
evidence: hasDocs ? 'API-docs hittad' : 'Ingen API-dokumentation',
blocker: false,
action: !hasDocs ? 'Skapa API-dokumentation' : null
};
}
}
]
},
{
id: 'database',
name: 'DB Migrations',
weight: 0.03, // 3%
target: 90,
rationale: 'Migrationer säkerställer att databasändringar är reproducerbara.',
measures: [
{
name: 'Migrationer',
check: () => {
const hasMigrations = existsSync('/home/bernt/repos/quixzoom.com/migrations') ||
existsSync('/home/bernt/repos/quixzoom.com/prisma');
return {
score: hasMigrations ? 100 : 0,
evidence: hasMigrations ? 'Migrationer hittade' : 'Inga migrationer',
blocker: !hasMigrations,
action: !hasMigrations ? 'Sätt upp Prisma eller liknande migrationsverktyg' : null
};
}
},
{
name: 'Migration i CI',
check: () => {
return {
score: 0,
evidence: 'Ej verifierat',
blocker: true,
action: 'Lägg till migration-steg i CI-pipeline'
};
}
}
]
},
{
id: 'observability',
name: 'Observability',
weight: 0.01, // 1%
target: 70,
rationale: 'Observerbarhet hjälper till att diagnostisera problem i produktion.',
measures: [
{
name: 'Loggning',
check: () => {
const hasLogging = existsSync('/home/bernt/repos/quixzoom.com/src/lib/logger.ts') ||
existsSync('/home/bernt/repos/quixzoom.com/src/utils/logger.js');
return {
score: hasLogging ? 100 : 0,
evidence: hasLogging ? 'Logger hittad' : 'Ingen logger',
blocker: false,
action: !hasLogging ? 'Skapa central logger' : null
};
}
},
{
name: 'Health checks',
check: () => {
const hasHealth = existsSync('/home/bernt/repos/quixzoom.com/src/routes/health.ts') ||
existsSync('/home/bernt/repos/quixzoom.com/src/api/health.js');
return {
score: hasHealth ? 100 : 0,
evidence: hasHealth ? 'Health endpoint hittad' : 'Ingen health endpoint',
blocker: false,
action: !hasHealth ? 'Skapa /health endpoint' : null
};
}
}
]
},
{
id: 'agent-safety',
name: 'Agent Safety',
weight: 0.01, // 1%
target: 100,
rationale: 'EOS måste skydda sig själv. Agent Safety är metasäkerhet.',
measures: [
{
name: 'EOS-kontrakt',
check: () => {
const hasContract = existsSync('/home/bernt/.openclaw/workspace/EOS/engineering-contract.mjs');
return {
score: hasContract ? 100 : 0,
evidence: hasContract ? 'EOS-kontrakt hittat' : 'Inget EOS-kontrakt',
blocker: !hasContract,
action: !hasContract ? 'Skapa EOS-kontrakt' : null
};
}
},
{
name: 'Immutable Truth',
check: () => {
const hasTruth = existsSync('/home/bernt/.openclaw/workspace/EOS/immutable-truth.mjs');
return {
score: hasTruth ? 100 : 0,
evidence: hasTruth ? 'Immutable Truth hittad' : 'Ingen Immutable Truth',
blocker: !hasTruth,
action: !hasTruth ? 'Skapa Immutable Truth-validering' : null
};
}
}
]
}
];
class EngineeringReadinessV3 {
constructor() {
this.areas = READINESS_AREAS;
}
calculate() {
console.log('📊 Beräknar Engineering Readiness v3 (viktad)...\n');
const results = [];
let totalScore = 0;
let totalWeight = 0;
let totalBlockers = 0;
for (const area of this.areas) {
const areaResult = this.calculateArea(area);
results.push(areaResult);
totalScore += areaResult.score * area.weight;
totalWeight += area.weight;
totalBlockers += areaResult.blockers.length;
}
const overallScore = Math.round(totalScore / totalWeight);
const report = {
timestamp: new Date().toISOString(),
overall: overallScore,
totalBlockers,
areas: results,
thresholds: {
critical: 60,
warning: 80,
ready: 90
},
status: overallScore >= 90 ? 'READY' :
overallScore >= 60 ? 'WARNING' : 'CRITICAL'
};
this.saveReport(report);
this.printReport(report);
return report;
}
calculateArea(area) {
let areaScore = 0;
const measures = [];
const blockers = [];
const actions = [];
for (const measure of area.measures) {
const result = measure.check();
measures.push({
name: measure.name,
score: result.score,
evidence: result.evidence,
blocker: result.blocker,
action: result.action
});
areaScore += result.score;
if (result.blocker) {
blockers.push({
measure: measure.name,
evidence: result.evidence,
action: result.action
});
}
if (result.action) {
actions.push(result.action);
}
}
const avgScore = Math.round(areaScore / area.measures.length);
return {
id: area.id,
name: area.name,
weight: area.weight,
target: area.target,
score: avgScore,
gap: area.target - avgScore,
rationale: area.rationale,
measures,
blockers,
actions,
status: avgScore >= area.target ? 'READY' :
avgScore >= 60 ? 'WARNING' : 'CRITICAL'
};
}
saveReport(report) {
writeFileSync(READINESS_PATH, JSON.stringify(report, null, 2));
}
printReport(report) {
console.log('╔═══════════════════════════════════════════════════════════════╗');
console.log('║ ENGINEERING READINESS v3 ║');
console.log('║ Viktad — kritiska områden väger tyngre ║');
console.log('╚═══════════════════════════════════════════════════════════════╝');
console.log();
const overallIcon = report.status === 'READY' ? '✅' :
report.status === 'WARNING' ? '⚠️' : '🔴';
console.log(`${overallIcon} ÖVERGRIPANDE: ${report.overall}%`);
console.log(` Status: ${report.status}`);
console.log(` Blockerare: ${report.totalBlockers}`);
console.log();
console.log('📋 PER OMRÅDE (sorterat efter vikt)\n');
// Sortera efter vikt (högst först)
const sortedAreas = [...report.areas].sort((a, b) => b.weight - a.weight);
for (const area of sortedAreas) {
const icon = area.status === 'READY' ? '✅' :
area.status === 'WARNING' ? '⚠️' : '🔴';
const bar = '█'.repeat(area.score / 5) + '░'.repeat(20 - area.score / 5);
const weightPct = Math.round(area.weight * 100);
console.log(`${icon} ${area.name} (${weightPct}%)`);
console.log(` [${bar}] ${area.score}% (mål: ${area.target}%)`);
console.log(` ${area.rationale}`);
if (area.blockers.length > 0) {
console.log(` 🔴 Blockerare:`);
for (const blocker of area.blockers) {
console.log(`${blocker.measure}: ${blocker.evidence}`);
console.log(` Åtgärd: ${blocker.action}`);
}
}
console.log();
}
console.log('🎯 TRÖSKLAR\n');
console.log(` Critical: <${report.thresholds.critical}%`);
console.log(` Warning: <${report.thresholds.warning}%`);
console.log(` Ready: ≥${report.thresholds.ready}%`);
console.log();
console.log('💡 REKOMMENDATION\n');
if (report.status === 'CRITICAL') {
console.log(' 🔴 STOP — Agenter får INTE utföra ändringar');
console.log(` Åtgärda ${report.totalBlockers} blockerare innan fortsatt arbete`);
} else if (report.status === 'WARNING') {
console.log(' ⚠️ WARNING — Begränsad agent-aktivitet');
} else {
console.log(' ✅ READY — Agenter kan arbeta normalt');
}
console.log();
console.log('═══════════════════════════════════════════════════════════════\n');
}
}
// ── Main ──────────────────────────────────────────────────────────────────
const readiness = new EngineeringReadinessV3();
readiness.calculate();