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.`);