Files
vendoo/tools/verify-platform-architecture-1.35.0.mjs
Masterluke77andGitHub 6f48827f4d Vendoo 1.41.2 – Git Ignore Boundary & Module Tracking Hotfix (#18)
* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix

* fix: track native source modules with root-anchored runtime ignores
2026-07-09 17:09:00 +02:00

119 lines
8.7 KiB
JavaScript

import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { dirname, join, resolve } from 'path';
import { fileURLToPath } from 'url';
import { spawnSync } from 'child_process';
import { createPlatformKernel } from '../app/kernel/platform-kernel.mjs';
import { ModuleRegistry } from '../app/kernel/module-registry.mjs';
import { PolicyEngine } from '../app/kernel/policy-engine.mjs';
import { RouteRegistry } from '../app/kernel/route-registry.mjs';
import { EventBus } from '../app/kernel/event-bus.mjs';
import { validateThemeDefinition } from '../app/core/themes/theme-contract.mjs';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const failures = [];
const assert = (condition, message) => { if (!condition) failures.push(message); };
const expectThrow = async (fn, code, message) => {
try { await fn(); failures.push(message); }
catch (error) { if (code && error.code !== code) failures.push(`${message} (erhalten: ${error.code || error.message})`); }
};
const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
assert(packageJson.version === '1.35.0', `package.json ist ${packageJson.version}`);
const kernel = createPlatformKernel({
version: packageJson.version,
hasPermission: (role, permission) => role === 'admin' && permission === 'security.manage',
});
await kernel.start();
const snapshot = kernel.snapshot();
assert(snapshot.architecture === 'modular-monolith', 'Architekturtyp fehlt');
assert(snapshot.modules.length === 12, `Erwartet 12 Module, erhalten ${snapshot.modules.length}`);
assert(snapshot.modules.every(module => module.state === 'running'), 'Nicht alle Module laufen');
assert(snapshot.modules.every(module => module.status === 'legacy-bridge'), 'Bestandsmodule müssen als legacy-bridge markiert sein');
assert(snapshot.features.some(flag => flag.name === 'platform.module-kernel' && flag.value), 'Kernel-Feature-Flag fehlt');
assert(snapshot.policies.some(policy => policy.id === 'platform.security-admin'), 'Security-Admin-Policy fehlt');
const health = await kernel.health();
assert(health.ok, 'Kernel-Healthcheck ist nicht erfolgreich');
await kernel.stop();
assert(kernel.snapshot().lifecycle.state === 'stopped', 'Kernel stoppt nicht sauber');
const registry = new ModuleRegistry();
registry.register({ manifest: {
schemaVersion: 1, id: 'vendoo.test-a', name: 'A', version: '1.0.0', type: 'feature', status: 'native',
requires: ['vendoo.test-b'], permissions: [], provides: ['test.a'], consumes: [], owns: ['test.a'],
} });
registry.register({ manifest: {
schemaVersion: 1, id: 'vendoo.test-b', name: 'B', version: '1.0.0', type: 'feature', status: 'native',
requires: ['vendoo.test-a'], permissions: [], provides: ['test.b'], consumes: [], owns: ['test.b'],
} });
await expectThrow(() => registry.startAll({}), 'MODULE_DEPENDENCY_CYCLE', 'Zyklische Modulabhängigkeit wurde nicht blockiert');
const ownershipRegistry = new ModuleRegistry();
ownershipRegistry.register({ manifest: { schemaVersion: 1, id: 'vendoo.owner-a', name: 'Owner A', version: '1.0.0', type: 'feature', status: 'native', requires: [], permissions: [], provides: ['owner.a'], consumes: [], owns: ['shared.resource'] } });
await expectThrow(() => ownershipRegistry.register({ manifest: { schemaVersion: 1, id: 'vendoo.owner-b', name: 'Owner B', version: '1.0.0', type: 'feature', status: 'native', requires: [], permissions: [], provides: ['owner.b'], consumes: [], owns: ['shared.resource'] } }), 'MODULE_OWNERSHIP_CONFLICT', 'Doppelte Modul-Ownership wurde nicht blockiert');
const capabilityRegistry = new ModuleRegistry();
capabilityRegistry.register({ manifest: { schemaVersion: 1, id: 'vendoo.consumer', name: 'Consumer', version: '1.0.0', type: 'feature', status: 'native', requires: [], permissions: [], provides: ['consumer.ready'], consumes: ['missing.capability'], owns: ['consumer.state'] } });
await expectThrow(() => capabilityRegistry.startAll({}), 'MODULE_CAPABILITY_MISSING', 'Fehlende Capability wurde nicht blockiert');
const policies = new PolicyEngine();
policies.register('test.admin', ({ user }) => user?.role === 'admin');
const routes = new RouteRegistry({ policyEngine: policies });
const fakeApp = { get(path, handler) { this.path = path; this.handler = handler; } };
await expectThrow(() => routes.register(fakeApp, { id: 'test.no-policy', method: 'GET', path: '/x', owner: 'vendoo.test', handler() {} }), 'ROUTE_POLICY_REQUIRED', 'Route ohne Policy wurde akzeptiert');
routes.register(fakeApp, { id: 'test.route.read', method: 'GET', path: '/x', owner: 'vendoo.test', policy: 'test.admin', csrf: false, handler(_req, res) { res.json({ ok: true }); } });
let deniedStatus = null;
await fakeApp.handler({ user: { role: 'viewer' }, requestId: 'test' }, { headersSent: false, status(code) { deniedStatus = code; return this; }, json() {} }, () => {});
assert(deniedStatus === 403, `Deny-by-default-Route gab ${deniedStatus} statt 403 zurück`);
const events = new EventBus();
let eventCalls = 0;
events.on('test.created', () => { eventCalls += 1; }, { owner: 'vendoo.test' });
await events.emit('test.created');
assert(eventCalls === 1, 'Event Bus liefert Event nicht aus');
assert(events.removeOwner('vendoo.test') === 1, 'Event-Owner-Cleanup funktioniert nicht');
const validTheme = validateThemeDefinition({ id: 'markus-theme', name: 'Markus Theme', values: { 'color.brand': '#123456', 'radius.md': '14px' } });
assert(validTheme.values['color.brand'] === '#123456', 'Gültiges Theme wurde nicht akzeptiert');
await expectThrow(() => validateThemeDefinition({ id: 'unsafe', name: 'Unsafe', values: { 'shadow.float': 'url(javascript:alert(1))' } }), 'THEME_TOKEN_FORBIDDEN', 'Nicht anpassbarer Token wurde akzeptiert');
await expectThrow(() => validateThemeDefinition({ id: 'unsafe', name: 'Unsafe', values: { 'color.brand': 'url(javascript:alert(1))' } }), 'THEME_VALUE_INVALID', 'Unsicherer Farbwert wurde akzeptiert');
await expectThrow(() => validateThemeDefinition({ id: 'unsafe-rgb', name: 'Unsafe RGB', values: { 'color.brand': 'rgb(999,0,0)' } }), 'THEME_VALUE_INVALID', 'Farbkanal außerhalb 0-255 wurde akzeptiert');
const tokenCssBefore = readFileSync(join(root, 'public', 'design-system', 'tokens.css'), 'utf8');
const generated = spawnSync(process.execPath, [join(root, 'tools', 'generate-design-tokens.mjs')], { cwd: root, encoding: 'utf8' });
assert(generated.status === 0, `Token-Generator fehlgeschlagen: ${generated.stderr || generated.stdout}`);
const tokenCssAfter = readFileSync(join(root, 'public', 'design-system', 'tokens.css'), 'utf8');
assert(tokenCssBefore === tokenCssAfter, 'Token-Generator ist nicht deterministisch');
assert(tokenCssAfter.includes('[data-theme="contrast"]'), 'Kontrast-Theme fehlt');
const style = readFileSync(join(root, 'public', 'style.css'), 'utf8');
assert(style.startsWith("@import url('./design-system/tokens.css?v=1.35.0');"), 'style.css bindet Tokenvertrag nicht zuerst ein');
assert(!style.includes('Vendoo UI Foundation 2026'), 'Alte eingebettete Tokenquelle ist noch vorhanden');
const frontendRegistry = readFileSync(join(root, 'public', 'core', 'frontend-module-registry.js'), 'utf8');
const safeDom = readFileSync(join(root, 'public', 'core', 'safe-dom.js'), 'utf8');
const apiClient = readFileSync(join(root, 'public', 'core', 'api-client.js'), 'utf8');
assert(frontendRegistry.includes('AbortController') && frontendRegistry.includes('async unmount()'), 'Frontend-Modul-Lifecycle fehlt');
assert(safeDom.includes('textContent') && !safeDom.includes('innerHTML'), 'Safe-DOM-Vertrag ist unsicher');
assert(apiClient.includes("credentials: 'same-origin'") && apiClient.includes('X-CSRF-Token'), 'API-Client-Sicherheitsvertrag fehlt');
const html = readFileSync(join(root, 'public', 'index.html'), 'utf8');
assert(html.includes('design-system/theme-runtime.js?v=1.35.0'), 'Theme Runtime ist nicht eingebunden');
const server = readFileSync(join(root, 'server.mjs'), 'utf8');
for (const marker of ['createPlatformKernel', "id: 'platform.status.read'", "policy: 'platform.security-admin'", 'await platformKernel.stop()']) {
assert(server.includes(marker), `Serverintegration fehlt: ${marker}`);
}
const temp = mkdtempSync(join(tmpdir(), 'vendoo-architecture-'));
try {
writeFileSync(join(temp, 'proof.txt'), 'ok');
assert(readFileSync(join(temp, 'proof.txt'), 'utf8') === 'ok', 'Temporäre Testumgebung ist nicht schreibbar');
} finally { rmSync(temp, { recursive: true, force: true }); }
if (failures.length) {
console.error(`Platform-Architecture-Gate 1.35.0 fehlgeschlagen (${failures.length}):`);
failures.forEach(failure => console.error(`- ${failure}`));
process.exit(1);
}
console.log(`Platform-Architecture-Gate 1.35.0 erfolgreich: ${snapshot.modules.length} Module, ${snapshot.features.length} Feature Flags, ${snapshot.policies.length} Policies, Tokenvertrag und Deny-by-default-Routen geprüft.`);