05ed037fe8
- 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!
308 lines
9.5 KiB
JavaScript
308 lines
9.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// EOS Capability Registry — Resonera i verksamhetsförmågor, inte tekniska komponenter
|
|
// Erik-krav: "Då börjar ni resonera i verksamhetsförmågor snarare än tekniska komponenter"
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
|
|
const REGISTRY_PATH = '/home/bernt/.openclaw/workspace/EOS/capability-registry.json';
|
|
|
|
/**
|
|
* Capabilities för quiXzoom/LandveX
|
|
*/
|
|
const DEFAULT_CAPABILITIES = [
|
|
{
|
|
id: 'cap-mission-mgmt',
|
|
name: 'Mission Management',
|
|
description: 'Hantera uppdrag (missions) för Zoomers',
|
|
owner: 'Mission Team',
|
|
services: ['Mission Service'],
|
|
apis: ['/mission/create', '/mission/assign', '/mission/complete'],
|
|
database: 'missions',
|
|
tests: ['mission.integration.ts'],
|
|
dashboards: ['Mission KPI', 'Completion Rate'],
|
|
dependencies: ['cap-user-mgmt', 'cap-payment'],
|
|
budget: {
|
|
maxDirectDeps: 8,
|
|
maxExternalApis: 2,
|
|
maxDatabases: 1
|
|
}
|
|
},
|
|
{
|
|
id: 'cap-user-mgmt',
|
|
name: 'User Management',
|
|
description: 'Hantera Zoomers, kunder och identiteter',
|
|
owner: 'Identity Team',
|
|
services: ['Identity Service', 'Wallet Service'],
|
|
apis: ['/user/register', '/user/login', '/user/verify'],
|
|
database: 'users',
|
|
tests: ['user.integration.ts', 'identity.unit.ts'],
|
|
dashboards: ['User Growth', 'Verification Rate'],
|
|
dependencies: ['cap-payment'],
|
|
budget: {
|
|
maxDirectDeps: 6,
|
|
maxExternalApis: 2,
|
|
maxDatabases: 1
|
|
}
|
|
},
|
|
{
|
|
id: 'cap-payment',
|
|
name: 'Payment Processing',
|
|
description: 'Hantera betalningar via Stripe Connect',
|
|
owner: 'Finance Team',
|
|
services: ['Payment Service', 'Stripe Adapter'],
|
|
apis: ['/payment/create', '/payment/webhook', '/payout/schedule'],
|
|
database: 'transactions',
|
|
tests: ['payment.integration.ts'],
|
|
dashboards: ['Revenue', 'Payout Status'],
|
|
dependencies: [],
|
|
budget: {
|
|
maxDirectDeps: 4,
|
|
maxExternalApis: 1,
|
|
maxDatabases: 1
|
|
}
|
|
},
|
|
{
|
|
id: 'cap-content',
|
|
name: 'Content Management',
|
|
description: 'Hantera bilder, video och dokument',
|
|
owner: 'Content Team',
|
|
services: ['Media Service', 'Storage Adapter'],
|
|
apis: ['/media/upload', '/media/retrieve', '/media/delete'],
|
|
database: 'media_metadata',
|
|
tests: ['media.integration.ts'],
|
|
dashboards: ['Storage Usage', 'Upload Success Rate'],
|
|
dependencies: [],
|
|
budget: {
|
|
maxDirectDeps: 5,
|
|
maxExternalApis: 1,
|
|
maxDatabases: 1
|
|
}
|
|
},
|
|
{
|
|
id: 'cap-analytics',
|
|
name: 'Analytics & Reporting',
|
|
description: 'Samla och analysera data',
|
|
owner: 'Data Team',
|
|
services: ['Analytics Service', 'Report Engine'],
|
|
apis: ['/analytics/event', '/report/generate'],
|
|
database: 'analytics',
|
|
tests: ['analytics.unit.ts'],
|
|
dashboards: ['System Health', 'Business Metrics'],
|
|
dependencies: ['cap-mission-mgmt', 'cap-user-mgmt'],
|
|
budget: {
|
|
maxDirectDeps: 6,
|
|
maxExternalApis: 2,
|
|
maxDatabases: 1
|
|
}
|
|
}
|
|
];
|
|
|
|
class CapabilityRegistry {
|
|
constructor() {
|
|
this.capabilities = this.loadCapabilities();
|
|
}
|
|
|
|
loadCapabilities() {
|
|
if (existsSync(REGISTRY_PATH)) {
|
|
return JSON.parse(readFileSync(REGISTRY_PATH, 'utf8'));
|
|
}
|
|
return DEFAULT_CAPABILITIES;
|
|
}
|
|
|
|
saveCapabilities() {
|
|
writeFileSync(REGISTRY_PATH, JSON.stringify(this.capabilities, null, 2));
|
|
}
|
|
|
|
/**
|
|
* Hitta capability från vilken som helst referens
|
|
*/
|
|
resolve(reference) {
|
|
// Sök på ID
|
|
let cap = this.capabilities.find(c => c.id === reference);
|
|
if (cap) return cap;
|
|
|
|
// Sök på service
|
|
cap = this.capabilities.find(c => c.services.includes(reference));
|
|
if (cap) return cap;
|
|
|
|
// Sök på API
|
|
cap = this.capabilities.find(c => c.apis.includes(reference));
|
|
if (cap) return cap;
|
|
|
|
// Sök på databas
|
|
cap = this.capabilities.find(c => c.database === reference);
|
|
if (cap) return cap;
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Kontrollera om en ändring bryter mot capability-struktur
|
|
*/
|
|
validateChange(change) {
|
|
const cap = this.resolve(change.capability);
|
|
if (!cap) {
|
|
return {
|
|
valid: false,
|
|
reason: `Ingen capability hittad för: ${change.capability}`
|
|
};
|
|
}
|
|
|
|
const issues = [];
|
|
|
|
// Kontrollera beroenden
|
|
if (change.newDependencies) {
|
|
const currentDeps = cap.dependencies.length;
|
|
const newDeps = change.newDependencies.length;
|
|
if (currentDeps + newDeps > cap.budget.maxDirectDeps) {
|
|
issues.push(`För många beroenden: ${currentDeps + newDeps}/${cap.budget.maxDirectDeps}`);
|
|
}
|
|
}
|
|
|
|
// Kontrollera externa API:er
|
|
if (change.newExternalApis) {
|
|
const currentApis = cap.apis.filter(a => a.includes('stripe') || a.includes('external')).length;
|
|
const newApis = change.newExternalApis.length;
|
|
if (currentApis + newApis > cap.budget.maxExternalApis) {
|
|
issues.push(`För många externa API:er: ${currentApis + newApis}/${cap.budget.maxExternalApis}`);
|
|
}
|
|
}
|
|
|
|
// Kontrollera databaser
|
|
if (change.newDatabase) {
|
|
if (cap.database && change.newDatabase !== cap.database) {
|
|
issues.push(`Flera databaser: ${cap.database}, ${change.newDatabase} (max: ${cap.budget.maxDatabases})`);
|
|
}
|
|
}
|
|
|
|
return {
|
|
valid: issues.length === 0,
|
|
capability: cap,
|
|
issues,
|
|
budget: cap.budget
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Visa capability-karta
|
|
*/
|
|
showMap() {
|
|
console.log('🗺️ CAPABILITY MAP\n');
|
|
|
|
for (const cap of this.capabilities) {
|
|
console.log(`📦 ${cap.name} (${cap.id})`);
|
|
console.log(` ${cap.description}`);
|
|
console.log(` Ägare: ${cap.owner}`);
|
|
console.log();
|
|
|
|
console.log(' 🔗 Services:');
|
|
for (const service of cap.services) {
|
|
console.log(` • ${service}`);
|
|
}
|
|
|
|
console.log(' 📡 APIs:');
|
|
for (const api of cap.apis) {
|
|
console.log(` • ${api}`);
|
|
}
|
|
|
|
console.log(` 🗄️ Database: ${cap.database}`);
|
|
|
|
console.log(' 🧪 Tests:');
|
|
for (const test of cap.tests) {
|
|
console.log(` • ${test}`);
|
|
}
|
|
|
|
console.log(' 📊 Dashboards:');
|
|
for (const dash of cap.dashboards) {
|
|
console.log(` • ${dash}`);
|
|
}
|
|
|
|
if (cap.dependencies.length > 0) {
|
|
console.log(' ⬇️ Dependencies:');
|
|
for (const dep of cap.dependencies) {
|
|
const depCap = this.resolve(dep);
|
|
console.log(` • ${depCap?.name || dep}`);
|
|
}
|
|
}
|
|
|
|
console.log(' 💰 Budget:');
|
|
console.log(` Max direkta beroenden: ${cap.budget.maxDirectDeps}`);
|
|
console.log(` Max externa API:er: ${cap.budget.maxExternalApis}`);
|
|
console.log(` Max databaser: ${cap.budget.maxDatabases}`);
|
|
|
|
console.log();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Visa beroendegraf
|
|
*/
|
|
showDependencyGraph() {
|
|
console.log('📊 DEPENDENCY GRAPH\n');
|
|
|
|
for (const cap of this.capabilities) {
|
|
const deps = cap.dependencies.map(d => {
|
|
const depCap = this.resolve(d);
|
|
return depCap?.name || d;
|
|
});
|
|
|
|
const depCount = deps.length;
|
|
const budgetStatus = depCount > cap.budget.maxDirectDeps ? '🔴' :
|
|
depCount > cap.budget.maxDirectDeps * 0.8 ? '⚠️' : '✅';
|
|
|
|
console.log(`${depCount}/${cap.budget.maxDirectDeps} ${budgetStatus} ${cap.name}`);
|
|
|
|
if (deps.length > 0) {
|
|
for (const dep of deps) {
|
|
console.log(` → ${dep}`);
|
|
}
|
|
}
|
|
|
|
console.log();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Main ──────────────────────────────────────────────────────────────────
|
|
|
|
const registry = new CapabilityRegistry();
|
|
const command = process.argv[2] || '--map';
|
|
|
|
if (command === '--map') {
|
|
registry.showMap();
|
|
} else if (command === '--deps') {
|
|
registry.showDependencyGraph();
|
|
} else if (command === '--validate') {
|
|
const change = {
|
|
capability: process.argv[3] || 'cap-mission-mgmt',
|
|
newDependencies: process.argv[4]?.split(',') || [],
|
|
newExternalApis: process.argv[5]?.split(',') || [],
|
|
newDatabase: process.argv[6] || null
|
|
};
|
|
|
|
const result = registry.validateChange(change);
|
|
|
|
console.log(`🔍 Validerar ändring för: ${change.capability}\n`);
|
|
|
|
if (result.valid) {
|
|
console.log('✅ Ändringen bryter inte mot capability-strukturen\n');
|
|
} else {
|
|
console.log('❌ Ändringen bryter mot capability-strukturen\n');
|
|
for (const issue of result.issues) {
|
|
console.log(` • ${issue}`);
|
|
}
|
|
console.log();
|
|
console.log('💰 Budget:');
|
|
console.log(` Max direkta beroenden: ${result.budget.maxDirectDeps}`);
|
|
console.log(` Max externa API:er: ${result.budget.maxExternalApis}`);
|
|
console.log(` Max databaser: ${result.budget.maxDatabases}`);
|
|
}
|
|
} else {
|
|
console.log('Användning:');
|
|
console.log(' node capability-registry.mjs --map # Visa capability-karta');
|
|
console.log(' node capability-registry.mjs --deps # Visa beroendegraf');
|
|
console.log(' node capability-registry.mjs --validate <cap> [deps] [apis] [db] # Validera ändring');
|
|
}
|