Compare commits

..
68 changed files with 182 additions and 2353 deletions
+3 -3
View File
@@ -1,4 +1,4 @@
# Vendoo 1.35.0 Beispielkonfiguration für Windows, Docker und NAS
# Vendoo 1.34.3 Beispielkonfiguration für Windows, Docker und NAS
# Diese Datei enthält ausschließlich Platzhalter. Für den Betrieb als .env kopieren.
# Niemals eine produktive .env in Release-ZIPs, Git oder Support-Logs aufnehmen.
@@ -44,7 +44,7 @@ VENDOO_DOMAIN=
# Optional: öffentliche JSON-Manifest-URL für die Updateprüfung
VENDOO_UPDATE_MANIFEST_URL=
# Server-, Mehrbenutzer- und Deployment-Sicherheit (1.35.0)
# Server-, Mehrbenutzer- und Deployment-Sicherheit (1.34.3)
# Standardmäßig nur auf diesem PC erreichbar. Für LAN bewusst auf 0.0.0.0 setzen.
VENDOO_BIND_HOST=127.0.0.1
# Kommagetrennte zusätzliche Hostnamen/IPs, z. B. vendoo.intern,192.168.178.50
@@ -59,7 +59,7 @@ VENDOO_READ_RATE_LIMIT=360
VENDOO_MUTATION_RATE_LIMIT=120
# Produktionsbereitstellung 1.35.0
# Produktionsbereitstellung 1.34.3
# Mindestens 32 zufällige Zeichen. Wird beim ersten Admin-Setup abgefragt.
VENDOO_SETUP_TOKEN=
VENDOO_REQUIRE_SETUP_TOKEN=true
-1
View File
@@ -14,7 +14,6 @@
- [ ] `npm ci`
- [ ] `npm run verify:git`
- [ ] `npm run check`
- [ ] `npm run verify:architecture`
- [ ] `npm run verify:deployment`
- [ ] `npm run verify:db`
- [ ] Windows-/Docker-/Browser-Abnahme dokumentiert oder ausdrücklich als offen markiert
-3
View File
@@ -45,9 +45,6 @@ jobs:
- name: Syntaxprüfung
run: npm run check
- name: Plattformarchitektur und Design Tokens prüfen
run: npm run verify:architecture
- name: Deployment-Gate
run: npm run verify:deployment
-1
View File
@@ -48,7 +48,6 @@ jobs:
npm run verify:git-regression
npm run verify:git-staging
npm run check
npm run verify:architecture
npm run verify:deployment
npm run verify:config
npm run verify:installer-shell
-23
View File
@@ -1,28 +1,5 @@
# Changelog
## 1.35.0 Modular Platform Kernel, Design-System Contracts & Security Architecture Foundation
### Neu
- Modularer Plattformkernel mit Lifecycle, Event Bus, Feature Flags, Modul- und Policy-Registry.
- Zwölf bestehende Fachbereiche als validierte `legacy-bridge`-Module mit expliziten Abhängigkeiten, Capabilities und Ownership.
- Deny-by-default Route Registry: neue Kernel-Routen benötigen Besitzer und explizite Policy.
- Administrativ geschützte Plattform-, Modul- und Theme-Vertragsendpunkte.
- Kernel-Healthcheck in `/readyz` und geordneter Kernel-Shutdown.
- Versionierte Design-Token-Quelle mit 33 Tokens für Light, Dark und High Contrast.
- Deterministischer Token-Generator und browserseitige Theme Runtime.
- Sichere Theme-Definitionen: nur freigegebene Farb- und Dimensionswerte, kein freies CSS/JS.
- Modul-JSON-Schema, Architekturgrenzen, Migrationsroadmap und Security-Architekturdokumentation.
- Neues `npm run verify:architecture` mit Zyklus-, Policy-, Event-, Theme- und Token-Regressionstests.
- GitHub Draft Pull Request #7 für `release/1.35.0-platform-kernel`.
### Kompatibilität
- Kein Reset, keine Framework-Migration, keine Datenbankablösung.
- Windows-Lokalbetrieb, Docker, NAS, VPS, Zero-Edit-Installer und Git-Sicherheitsgate bleiben erhalten.
- Bestehende CSS-Variablen bleiben über eine Token-Kompatibilitätsbrücke funktionsfähig.
- Bestehende Routen werden in 1.35.0 noch nicht massenhaft verschoben; die Migration erfolgt fachmodulweise.
## 1.34.3 Git-Staging Update-Archiv-Ausschluss Hotfix
### Behoben
-11
View File
@@ -979,14 +979,3 @@ Vendoo bündelt direkte API-Veröffentlichungen und Extension-Workflows in einer
- neutraler FLUX-Studio-Start und Arbeitsbereich-Reset
- stabilisierte Benutzerbearbeitung und sichere Update-Antwort
---
## Plattformarchitektur ab 1.35.0
Vendoo wird als modularer Monolith weiterentwickelt. Version 1.35.0 ergänzt einen Plattformkernel, validierte Modulmanifeste, Lifecycle, Event Bus, Feature Flags, Policy Engine und Routenverträge. Die bestehenden Fachbereiche sind als `legacy-bridge` registriert und werden in späteren Releases einzeln migriert.
Das Design-System verwendet eine versionierte Tokenquelle unter `public/design-system/tokens/source.json`. Light, Dark und High Contrast werden deterministisch in CSS erzeugt. Freies CSS ist für den geplanten Theme Manager ausgeschlossen.
Neue Kernel-Routen benötigen zwingend einen Modulbesitzer und eine explizite Policy. Unbekannte Policies verweigern den Zugriff. Externe Erweiterungen dürfen nicht im Hauptprozess ausgeführt werden; der isolierte Extension-Host bleibt als deaktivierter Feature-Flag vorbereitet.
+23 -66
View File
@@ -1,16 +1,16 @@
f344f75df655bc133e6c4abb2d215b2234fb22a4bb155c217317c36ee4f0f8fb .dockerignore
1b43579196c0b46adf1cd291612878bc6d4ea83d999cc019d421cd94332a4497 .env.example
bd750f6b33c65babda1e9e87416b8db206a8a82109373d31238286794a6fd486 .env.example
58e3f4b76cc9398f32dcd5481b2080e7e5ba322aca04b8b8374446299c5d7dd6 .github/CODEOWNERS
5bd335eb750d084ca12b6a3b3f35d0cd7bc454ba24ed280187c66acf75eaf396 .github/dependabot.yml
f65684e98b01d0e0b6bfe1284b5de3354559e4cd51e634fe9e9e2e5473b890e5 .github/pull_request_template.md
caf8166f14dbd7c354c70c482548ea4e7ceca473c64c9c13a1297b30e0b3855f .github/workflows/ci.yml
5d7c1365f5319df6a756b8cc48d42d91ae954e463062a1c5e9df0b7f9d2da7f4 .github/workflows/release.yml
3653210a322b7b4a04d31a69bfab75190c9d4ee51fc9d6b55de45e6a1145b99b .github/pull_request_template.md
55732bab53a88c24d9755bd3ec7e0b5a0f96f9589876d721dd45aef0d47fadf0 .github/workflows/ci.yml
b65a764fe196c55ad041570b67b68abaa8d6eefe2f43be841273c5fd3f0a662a .github/workflows/release.yml
9e7decb6c419fc032644ad361043e69770104ea5904bfcce4942f9416787d975 .gitignore
9bfa732952ad571d06527b32d96d79133fc23fa607df00de440b2802f543608c .npmrc
1e4d8f485bc932ec1bb1cbf5100fb7897788e165d46af9247afb11ecbc69158c CHANGELOG.md
5ffff7948e72e933f5e4b18af3e33b65fc20dce11bbfdf76efbd58c5966ec4e3 CHANGELOG.md
61823fc9fdc036170cce6edeecbb612b442d1c65fc9f7a7eaba47e5016316ee5 CLAUDE.md
2e0077cd6dd83d3a07fcef25b7e37ab5fc056dbe1ca034b33d55135234045882 Dockerfile
712347f03bb08feed4538a9c4f397504a6801f50d03acfe962b5ba7eb534fe08 FEATURES.md
b730d810c7c9c4a6fb201494c43978c2e1ba8c092e3543a9b3e424694c9f577f FEATURES.md
7f78a0d512de717ba76c9a5e3e1bd2e52db9edf146e8eeb7297458fde5d92afa GitHub-Deploy-Assistent.bat
302ab1609d08b3059ba6563b676c64f0709651f2e3c531857bfca3840c535a79 MockupDesign/AdminDashboard.png
49f7625a1299a16576feabfd7824ec3511391bd9b7c53a1fba9232da7e8bd6c6 MockupDesign/ChatGPT Image 7. Juli 2026, 10_05_19 (1).png
@@ -24,38 +24,13 @@ b1c62e118d9be316274e966c558cdcccdd41afb3a9f05a60ec1e4a3433d2ca0c MockupDesign/L
a52983b9ea2bf920d115485d10bc30226da88bc5cba8b00bebf801b21c096346 MockupDesign/Publish.png
8c8bf278d0c6725e1dbb97b3c3d0c36a9bfc0753c3c86433fdc294bec70dbc0e MockupDesign/Startseite.png
62f14221b1d71c9e631efdadd221397ff657adae4b737df52c8bff1610b90999 MockupDesign/Vorlagen.png
d0ef5e20267debda98199c158974ccca0bdc3b614bfa29263c0863297a3c6fd3 README.md
fafda88c1e46cbe9f2085ec140220b9f04182ac0ce32df854954e39bb025f51e SECURITY.md
83834fdbc2a44866b20d6cb9fa7adfd6ba345fccd519299ba658e61d28e48819 README.md
c25d3afa3adbde80250c698a984af41c9db0702b2584aada9b6064d7843da70e SECURITY.md
2851c914a1b50a036cb265ddd32e5545cc7e8e34ba9d682445926acc23eb8f28 THIRD_PARTY_NOTICES.md
4c424ef64a09639953388bd2e749d45e9a0d3d533c8b6fce547292237a0a48f9 app/architecture-boundaries.json
a6bdb9b6fdc97a0b2bbc052bd242a9885a75bcc46ee4f0414d8b7e215b6940de app/contracts/module.schema.json
8e213cf43eec8f7965adc9529e44346c1689005c00bbd37dcd48c6774a391fbe app/core/themes/theme-contract.mjs
d84a573699e413e0a0803459e0d05c5c4e6e35d3499eb634f95302d151e75c4b app/kernel/errors.mjs
a34333cbc784d39b39a8071a437972c6175c551104dc2d82fc4c0e0506b18259 app/kernel/event-bus.mjs
0e8f691f96e1ef0a7a86d32cae7b7e1db04325fe94a32b8c1f484a5b6950f3b4 app/kernel/feature-flags.mjs
a0223a00691908862aaf1fd9383b6d86d67f26325c509280b8c2daa5cfcee28e app/kernel/lifecycle.mjs
ba1f4f799a508ff113738cce7ac9f5af27739cb60d7f04d7a6c5878de3c5b7f4 app/kernel/module-contract.mjs
1baa99ffd80fffcfed69f21a5db8911500f35c0bd9df23cd02a7fb3b563266df app/kernel/module-registry.mjs
e89075bc42b6352edbf35c343f0fcb1250b23fa8850b5ccef3d20710ecf3bec5 app/kernel/platform-kernel.mjs
7944f68d995a5d70273f8a985973f668fc62a414d85636d92f3774e34d39a37f app/kernel/policy-engine.mjs
8fd120558114a0357d55b070c3a150464f57d6d87a84594c10562774ed994213 app/kernel/route-registry.mjs
0bb099111f7cf49780389d0b078083756a448b663661ee22475d84d38a5f9719 app/modules/ai/module.json
83ad66e0eba88246d292c96fb1ec02b9c59ed15614bbbf4a4a989787fe51e2d3 app/modules/auth/module.json
7646d3b546cb34ed9c973f73bbc27d43d5c35478e5eea92f468fa6924cbfdc16 app/modules/catalog.mjs
083db2c78089db389e51e743078c847f1067f3d1a39081080c601d31556f15fd app/modules/flux-studio/module.json
82b31b548c603cdc4e21b50707fd3d21114b85ef1f61db607c881f9ac1809132 app/modules/listings/module.json
6453b41f4074b37c7f7a2c30aa8a0eba8c4233f8d474251919094ed0c15fe38d app/modules/media/module.json
9723a9b12a9b472b2ed8c4635cdce663b2f0f65643d8ae0b6219715d853f740d app/modules/operations/module.json
b71c0f607bbba70b4485fcce883a7d2ab4a533a6dc203eb7de616aa4d8bae435 app/modules/publishing/module.json
61aa66129d18e2c5e6c2e7319de836a4713fde1ddcc31c6a5ca56c22d22d9b48 app/modules/quality/module.json
1f02d199bac34cdfe2e41c32dfd450f2160493d9589b11eb1ca595fba74d5daf app/modules/security/module.json
452a5262c418b393bdd8754fe90f9ea14edf61338f8dcfd6438f0852920f608c app/modules/system/module.json
1fb7c2eaffa03cca8f91f14d20d10bad06841e34a5927376fe2ccd502670fa1b app/modules/themes/module.json
11b4a1bc94b68998a3a2a8bec766bc56c7ff829939d914b0ce896f963feb10ed app/modules/users/module.json
69ce3be6a8a4a28725c5c7f9dc8803415ab0d1e503047b66dfcc9a44c9e90109 bootstrap.mjs
4a3273ec603224a70ceb81c0d5ed6617c3ba2d3111342b4e646a79752f4b3f87 compose.bind-mounts.example.yaml
afe1021b726e5bf3a93d5ed64aee836975c7063a4ffd623d815e4749768fc2a7 compose.vps.yaml
4be1b7e10a5923ccd314dbaa83d6c6fbf201cb100e878226feb36689d5b3ae2a compose.yaml
2259c85b731bb029cf5fd82e78a2a4b7f4bc70a5c2c085e76daa2025ae72e138 compose.vps.yaml
06565b1235d84d435740a8dcc54e08b23194922f16724200d535567bc5d08267 compose.yaml
c62149ccb007896c2ac2c238b593bb5233aca97719eaaeed067348eb43a078bd db/schema.sql
49690c4e4c8e84de8eb40ffe79823493b7be20fe46fa2ad9e6544f7f0afbfd2c deploy/Caddyfile
cc794bca282d79a27d860d0bfef4641769f4e9f31172778a1d9d8205558d948e docs/ABNAHMEBERICHT_1_32_0.md
@@ -64,13 +39,12 @@ de0d86f806a70195050613f5b56f6c4da1ee2eff02a9a991b6aacee229c46ca5 docs/ABNAHMEBE
26c87fa38f804749a9363812127dc33ef69e201e4ef9b59206f6fe6e15c845b0 docs/ABNAHMEBERICHT_1_34_1.md
f792d366c13c5a4a9b4e8d4054e26190d9c327a7a770005d74bbe2b31bb84ab3 docs/ABNAHMEBERICHT_1_34_2.md
bc638fd21222ada7b15e40adff80a318aebec58139978109cbfd30040815a8d4 docs/ABNAHMEBERICHT_1_34_3.md
c0ae5c4176ba6f7510be047d4feaf383cd646152131003ebccc6bfbf8c8056f5 docs/ABNAHMEBERICHT_1_35_0.md
67a1be91b0c2d2797716f785f2f770c881cf88d7fd617722468df8d949d0638a docs/AUDIT_1_31_0_IST_UND_LUECKENANALYSE.md
2acba8fcc5da42e5a808925a86628bc0c6fc817ddb082b0ead3857c6a43460f1 docs/BRANDING.md
f23c643236c84b0e3715620598f358065748b6d090229003e23df8ad9320b54e docs/FULL_LISTING_EDITOR_2026.md
ba9a9b93622fadc399cd9067d80b4ea339193eb19825106264a3b86b7128d107 docs/GENERATOR_STUDIO_2026.md
54f45ae5c2f12b6314b3f1bc3e59e753c64ca7cfe39644725d765e9f02f637ef docs/GITHUB_DEPLOYMENT_1_34_0.md
2f91666756e40d38cbf6bce35be6664e7b7ab81494e26900d7c6c43ab017d313 docs/GITHUB_IMPORT_STATUS.md
83448fb3e3fae37ca4d172867c57ce1c4a2a2d1581d72bfb8d3003aad8b7e723 docs/GITHUB_IMPORT_STATUS.md
65d19c1576f4a6011008a7e447a3530222517752fb59ebc5bbb506b791e32605 docs/HOTFIX_1_23_1_EDITOR_CACHE_VINTED_FIELDS.md
c9c8f934a0c4192dae29df913588188aab1321364d1c46b9c66da5c754e9df41 docs/HOTFIX_1_23_2_FLUX_EDITOR_JOB_COLLAPSE.md
2306ff399000c07a9d7e260a58d92c748fa3219922a4dda25f6b2121a1ad2dbc docs/HOTFIX_STUDIO_FLOW_DETAIL.md
@@ -104,8 +78,6 @@ ac834ee57ab6bd8fe3dd0238837193e66e01d7e78590a289347b002591da3fb4 docs/RELEASE_1
4c2c141ffc85b1d7517672abf2b28bb19fe4769505c0eb4d37e22bae06826cb3 docs/RELEASE_1_34_2_VERIFICATION.json
d55eaec79e10800a930fa030367ffa34037755e5757bc532e72d25711c5ca09a docs/RELEASE_1_34_3_UPDATE_ARCHIVE_EXCLUSION.md
4bb731af51e786a4827ec48bc7c4d4843c287456aedc09d8b74aa0eed3c0ec16 docs/RELEASE_1_34_3_VERIFICATION.json
e7bb3bb950e3c5303746f43139ca8f7c985d2d73896b44f5b707dd46a9152529 docs/RELEASE_1_35_0_PLATFORM_KERNEL.md
33eb9f0059a2ddc4b4debe5bf733720f2a5bb22f2710bab0a0cc9ac1b898ff11 docs/RELEASE_1_35_0_VERIFICATION.json
fe5756f788b2ab131f0832504d90d46d1ed744bfe470fd040803985234afa3cf docs/SLICE_02_ACCEPTANCE.md
05614ecf41becc399acfa6a3b2f681f37a8c80f5ce0130b98c578b2c8c60713c docs/SLICE_05_ACCEPTANCE.md
0838bcc6464a6beff39f0b4b056e513c1a390193521d9a8fc5d440d38034e9d0 docs/SLICE_06_ACCEPTANCE.md
@@ -138,10 +110,6 @@ d425fc1724ac55853bf3433348486de8f8aab111f53fefee3c532beb1deb82af docs/SLICE_32_
aa7ebc90470071566749c61b0dece4169f87666dcca4b88922f7fbafcfdd5a84 docs/SLICE_34_VERIFICATION.json
4b66a14b4515e38477f10325ab9d03d9782ad0d426845aefe2d508986ed2cd81 docs/UI_FOUNDATION_2026.md
5e655fcf3bb1182f8850b4104b56697ea952b01d2698af6d3d40b05e338b3506 docs/UI_UX_FIXES_SLICE_03.md
e5f065ba8d516a5ebd47e7e6859d0aab40ef69797413e358d969ef27c454fcdd docs/architecture/DESIGN_SYSTEM_1_35_0.md
cc0edf92756a51414563b4407dedb21d1e3b36682f4bfbbc334aeb33a22ae1b4 docs/architecture/MIGRATION_ROADMAP_1_35_0.md
eb47b055dd336e8360ec7c4712dee4ae37f109f3820e3f185969e2327d790cf8 docs/architecture/MODULE_SYSTEM_1_35_0.md
d7773faac0e93510a5a51c91de997df03547b51f3124267dcf137d4e99299562 docs/architecture/SECURITY_ARCHITECTURE_1_35_0.md
cc77a92ca268d3c444c6113a9cfaa38d3c805a579f6b1b21b7dd43a6ada58551 docs/superpowers/specs/2026-07-06-autolister-features-design.md
c7db96df5fed3c8336062204322d62b4870f5c8ab1d30ea48cc6e7e38ed3513b extension/README.txt
3873602c1529daad6647a7585c858d817821de0afbec93a26d1b3f5a7b9cb01e extension/background.js
@@ -261,15 +229,15 @@ ddb9c7677600795a4adf8a19a7525b7551455459d16eed4cdfbe751ef9ddf0eb lib/zip.mjs
2924f4e8abe5ebbd099fe1ceb2bc6ceb4bdda55fa3e57dd299cfc05d310a9246 local-ai/start-local-flux.ps1
7c6176d8311b1ba9d511beb40edd41b8248e61c298528bbec3f9ef1171856c05 local-ai/stop-local-flux.ps1
5f220a2dca02cc0764f6d0157677611b26c88860b48367eb661b6e47a1b92265 local-ai/workflows/vendoo_flux_schnell_api.json
b192a542334cf3ba96bcdb7f6f1d3139d03ab50ea39e7d3746ccf40774491bb2 package-lock.json
51a24a9f7f970b75ccab6b19e847a2f4ec7ed8eb8df4c7b9a54fa87d20d6ebb3 package.json
d076846dd47faf06b9783dcff3407da3ead1a02a0583c2dd900566dd2a73486b package-lock.json
bbcef72048cac0ee13d58fd8b4b24cfb4e8a42fe4efeb8da0f386d996a36f564 package.json
3da71dd28cb25c7c7317b9061855d40c6a3ab8bc39391963b49ee1e707f838e5 platforms/ebay-de.mjs
cc1ca2b4384c8c548bf755dc33101d248541bf893c3d5f35369961f904afcc00 platforms/ebay-ka.mjs
fa52b247dd758aeee06120499bd1ae6c53e9f460f8bd7762b8f06141b201e32b platforms/etsy.mjs
5c925e3934a4c9e2f26dc15b3449a249f5ce20ecebbd459b15365a34804824d6 platforms/vinted.mjs
a2ea3f5f56dba8bd4d23fa54a26fd941567b4febdd1ed1b4e9f5bcc1c9786e53 public/android-chrome-192x192.png
46ea6ba4ca6ca2352f37353b8bc4ca258935d29ecb8d3c9b133a48ea75c0c58f public/android-chrome-512x512.png
adc20f43652cd20cc8951e76da8e2739e55a141398385ff48dd3c5a666e94a2d public/app.js
706c4e4349f0ac9ac1af89b87ae81c4ee3f08f4191248098ab4dd28835a12932 public/app.js
e0e7b8ef01c36f0625b6157aa46bd69de05c17be0b054208fb635d6507b52154 public/apple-touch-icon.png
a2ea3f5f56dba8bd4d23fa54a26fd941567b4febdd1ed1b4e9f5bcc1c9786e53 public/brand/favicons/android-chrome-192x192.png
46ea6ba4ca6ca2352f37353b8bc4ca258935d29ecb8d3c9b133a48ea75c0c58f public/brand/favicons/android-chrome-512x512.png
@@ -303,40 +271,31 @@ ed3dfa59e989b154fac88a3c72ac840290ee3e1565d44aa75596bf0055c74020 public/brand/i
3f39371abd1efa6cc8366aac9a6d067f3339b8f2a2da77ab8a26c6bdcf8c4fce public/brand/logo/vendoo-logo-original.png
b56d07515f6220de7fd0cb149fed598a7361c985ae6ca7cb14329a99e8947aee public/brand/logo/vendoo-logo-transparent.png
e83921d2af9930ab27df1abe64374cb9311189bfaaa951068109177443571a07 public/brand/logo/vendoo-wordmark-transparent.png
1711a9387f2d6e75797b77c8daa92b94d7a15e128476e4b8aa9d54ccb0692a0e public/core/api-client.js
f30395399a5c6d7e0a884b22f28f7648e6343695cee9960d6ff13b34de7b48f1 public/core/frontend-module-registry.js
fb3dbcd03f7ed15b773b582c2a2b785c3040d3319aba5b7c682a7e74e1cf3b4e public/core/safe-dom.js
9c73eb996e6f1aa4dee07aed367ca6ba2dffbeac0f3d8dcd8deab8cd123da4e3 public/design-system/theme-contract.json
d4715f78f47be6c9be9b03c67de051d824bc2abfd239d691438d14ba09dea0eb public/design-system/theme-runtime.js
5163f6ef56fea0afb1254dac370f65e7c18463902a7970ed2cbeca84571fd60b public/design-system/tokens/source.json
3f9062017494212a7eadc1daaf2e0396e79e861cb464e76d3a36ca08d8729955 public/design-system/tokens.css
15bf8dd655b34a8015447e834d387af12685238ede598f5f0e7927eea2933483 public/favicon.ico
c5c46fdbafd46a0a26c88126ddc1bc8a82e751f2edf49b414b6e6a72d7923afd public/index.html
83dca0cd4307c0c4e7ab784346ca651c2469e30d6c6254b6fc669eeca89e1a1f public/index.html
141c261b2d0ea713af140d2e89f98b9c9046584bef60e97d8bf3d6b6f504efa2 public/login.html
59291f75eca405e3856bafea76cc601d681060eb6b36bf82a4be3c3ab7eac290 public/mobile-upload-expired.html
f046f166d89fc32774b87deb02e6d144fd2f40c0925aac67487e5b7c489a0ec1 public/mobile-upload.css
6152e4fe715efbf8fd6a7c4286f23172bc3cf15dde085e5e4f7389ad5e65ad73 public/mobile-upload.html
f13846801a8493aa43df6ec3d78b826b21b59e8f21d47735f68fd0b278e440c7 public/mobile-upload.js
3352513208e08e9ba2c56509734de788c83bdf93bc83ded6a1bb237822f84ec2 public/modules/README.md
d2c88e1199eb060211be40b9c83b1382b829cc5b32af22a161ad83c6c0784765 public/style.css
a6f90e7e6ba15519619000e628c024feb3667ef013f44d62a3d6645cceb2ddf2 public/style.css
ff525e0af963e6f54636cfdc1bd2d045aaf82e7b0199755eb5cdf4c52110be2e public/vendoo-icons.js
deb1a45f6414a2357046ff1d66fc58114549ba5ad695ed40fcceef2aef9f64a8 scripts/docker-backup.sh
20e7590a7633b6c61086a5596d0bec1d343f0ff0eecc444ac932130ff363e258 scripts/docker-entrypoint.sh
be72c39f00415cd57b69835346f00043841ce9f853b10436eb1799ca42980cb5 scripts/docker-restore.sh
e0d184804eec84d84859b544c8173f38dfd9e9363e8d68238e409bb0175887e4 scripts/github-deploy.ps1
cca73fc41c82d9689c0e109352a62e904d45f8883d77f89974bbd20679a48615 scripts/github-deploy.sh
6563b5f92e0bfba32b6659bb3c9232ea1b59a3746f33cae0bf942a16799c946e scripts/github-deploy.ps1
2e9c33fb6d955d41d4a717056aab639bd9cf924454e1ffca6a7380d097c533b8 scripts/github-deploy.sh
0a9bafc79c1c5d59fb9b51ebc34c00eec4a4e9a5e873ce1818f50f3ab983da70 scripts/install-local-flux.ps1
2924f4e8abe5ebbd099fe1ceb2bc6ceb4bdda55fa3e57dd299cfc05d310a9246 scripts/runtime-start-local-flux.ps1
7c6176d8311b1ba9d511beb40edd41b8248e61c298528bbec3f9ef1171856c05 scripts/runtime-stop-local-flux.ps1
1a5736cf5fccc991df122163977c2eac80281bd99a54a5680b3d7eae99e4b79d scripts/start-local-flux.bat
3bf3aa74f5cc1f2946bb2da637eb30fe8daec783a32f9721ebb0468e20df90bd scripts/uninstall-local-flux.ps1
a2b4868505cd169ee3c5a692eedb90241675401ddbe5cb9e026fb4091384af1d server.mjs
ac7c937b32b74a898c5eeb85aeaece05fe4207d977ed388129271f8e991a30a9 setup-gui.ps1
0951af11647d4b408248b76daa455916a6e89a05c0a5b9693ae728c697d01ede server.mjs
ffee63d39a4aa0c57631a66522c1f2ec422b40a804a0e081962efdc37eeeaeb5 setup-gui.ps1
cf4519ce5adc01f05458e3286e86f3a419cceaff5fcdd869a1d07f1c1d1a0d8c setup.bat
285403934cdf25226f085c9bb2bf174ea7fb12a59e60afd326fa379b38340608 setup.ps1
f7f2eccf8e1f1af608e8076774f0fbb35616ae65962bf68119b732cd79e2c9bc setup.sh
0af0ccd1ce1ae914c5e120b5ca06d3d65e83a47035ed74f5a1804d802288a099 setup.ps1
3256acb4e2fa0877e34edf2404b53484316332fa29b159ee6dbbf94f0c4dfc61 setup.sh
90485a16e3468cb9a6750646700fbb036cb9ac410e9b8bd5c582f7b923bf3c31 tools/build-release.mjs
68ab3cada7062bb2a226e5d55d0063788674e9724ea504630261f604c2841e71 tools/generate-design-tokens.mjs
294659f2beb607540abbc3d6b4a1658525448a42f8880f2b282081a259c4e35b tools/prepare-git-staging.mjs
8f9ede5f112ee03323db3a6c73aee00b7037f5139dad241b11baf5f0e9cee4e0 tools/verify-config-store.mjs
1bf7158230c79b64149f3632ef0e1474f284f2e6b98069aeb6fc7360645e971d tools/verify-db-migrations.py
@@ -345,13 +304,11 @@ f7f2eccf8e1f1af608e8076774f0fbb35616ae65962bf68119b732cd79e2c9bc setup.sh
cbd91b45d97e11bd4f5ca2f9d2edf2b5931cb2aa336cb71afc30aad9cffbe1e0 tools/verify-deployment-1.34.0.mjs
01a43040a95cdac40703f49031acd9267cdba99d9027a51718f35ffb643237fc tools/verify-deployment-1.34.2.mjs
906767e574c06aedebb2d7797150bda2f1e4c4536e8f82f4c7de2e3cc8398c8e tools/verify-deployment-1.34.3.mjs
71d00227a59592cc0ad8ba321c63350ec25748ef965d8cd0eff1317738c7fba8 tools/verify-deployment-1.35.0.mjs
1f052446f6c9627420afeb48aa09771b61f9e08ba573f6a0651b2843ef0b79f8 tools/verify-git-safety-regression.mjs
5bba3d7ae042b0bcf4eb56bacb3f6e41e1b0e00cadd7666505792ba6748cc987 tools/verify-git-safety.mjs
2f8d687319619240efe5849c276f7723e69755c66aa3b9609e41fdf0cda01e55 tools/verify-git-staging.mjs
1d4351e4a827e718766a1677ddfbc3771a91942d0d5a93753e39f1b99c68a4dd tools/verify-hotfix-1.23.2.mjs
44c95844422be3deaad6054ee3f0cced9c25986815d976e0c64b042d0c2c5e1e tools/verify-installer-shell.sh
cab03e98687af87f359f6424d440fc0b644da97b3c614509b8c7d008d4b18a8d tools/verify-platform-architecture-1.35.0.mjs
36e35fedb08923c22fd8551389d33432b66dbd4215d95c594ef29bfc790c98e0 tools/verify-release-1.25.0.mjs
89621694b7bad18b6f3f1ac1956032f64112c40d3edc069ca581dc813d57d628 tools/verify-release-1.25.1-static.mjs
c908f5186cf2d71ef83d5e649327ae19965568933acfb32057ca00d29cf890bc tools/verify-release-1.25.1.mjs
@@ -368,4 +325,4 @@ a372258b97aa68d59a1fe0fd3f380d019c59412d5c408a6064f437d5cfc6e709 tools/verify-s
21061ed5dc88feef48e80efffd9a1054d38598dce5a5d3f9f6bde39b539b83f0 tools/verify-slice32-batch.mjs
0f05b1ac3b686cf535bcfb3e6f6df24a29c3429ade1b3c666c1b99abb1a7311b tools/verify-slice32-image-render.mjs
0f3f684019763a14f5fbaec42e42c680cf748d930d46cd607fc61eb413ca4553 tools/verify-ui-contracts-1.26.1.mjs
c98b9e6e1c09022b2d4416b5bdc74354cdb9151012c00b5622f65f3620018c47 update-manifest.json
8b9180ea0587d13f75ff63bc56e0c5c1c7b1c55ca4cca7d8814394ee9ce0785d update-manifest.json
+5 -19
View File
@@ -1,25 +1,11 @@
# Vendoo 1.35.0
# Vendoo 1.34.3
Vendoo 1.35.0 führt die produktionsorientierte **Modular Platform Foundation** ein. Ein neuer Plattformkernel registriert bestehende Bereiche als klar versionierte `legacy-bridge`-Module, prüft Abhängigkeiten, startet und stoppt Module geordnet und verlangt für neue Kernel-Routen explizite Deny-by-default-Policies. Der bestehende Funktionsumfang bleibt unverändert; es gibt keinen Big-Bang-Umbau, keinen Reset und keine Framework-Migration.
Vendoo 1.34.3 führt eine **Zero-Edit-Installation** ein: Auf Windows startet `setup.bat` eine grafische Installationsmaske; auf Linux, NAS und VPS führt `setup.sh` dialogbasiert durch alle Pflichtangaben. Niemand muss `.env`, Compose- oder Proxy-Dateien manuell bearbeiten. Zugangsdaten für AI und Marktplätze werden nach dem ersten Login in den Vendoo-Einstellungen gepflegt.
Das Design-System besitzt nun eine versionierte Tokenquelle mit Light-, Dark- und High-Contrast-Vertrag. Die bestehende Oberfläche nutzt weiterhin ihre bisherigen Variablennamen über eine Kompatibilitätsbrücke. Ein späterer Theme Manager darf nur typisierte, freigegebene Tokens verändern freies CSS oder JavaScript ist ausgeschlossen.
Die **Zero-Edit-Installation** bleibt erhalten: Auf Windows startet `setup.bat` eine grafische Installationsmaske; auf Linux, NAS und VPS führt `setup.sh` durch alle Pflichtangaben. Der GitHub-Deploy-Assistent filtert lokale Laufzeit-, Update-, Backup- und Secret-Dateien weiterhin vor dem Push.
Der GitHub-Deploy-Assistent verwendet ab 1.34.3 auf allen Plattformen denselben geprüften Staging-Builder. Lokale `.env.*`, Operations-Daten, Uploads, Backups, Logs, Datenbanken und Modelle werden automatisch ausgeschlossen. Bei einem echten Sicherheitsfund zeigt Windows jede Detailzeile an und öffnet zusätzlich den Bericht `logs/github-deploy-safety-report.txt`.
Der lokale Ordner `updates/` mit Update-, Rollback- und Slice-Sicherungen wird ebenfalls vollständig ausgeschlossen. Aktive Installationsdateien im Projektroot und unter `scripts/` bleiben Teil des GitHub-Imports.
## Plattformarchitektur 1.35.0
- Kernel: `app/kernel/`
- Modulmanifeste: `app/modules/*/module.json`
- Architekturgrenzen: `app/architecture-boundaries.json`
- Design-Token-Quelle: `public/design-system/tokens/source.json`
- Theme-Vertrag: `public/design-system/theme-contract.json`
- Architektur-Gate: `npm run verify:architecture`
- vollständige technische Dokumentation: `docs/architecture/`
Die zwölf registrierten Bestandsmodule stehen bewusst auf `legacy-bridge`. Das zeigt transparent, welche Bereiche bereits einen Vertrag besitzen, aber noch schrittweise aus `server.mjs`, `public/app.js` und `public/style.css` herausgelöst werden müssen.
## Einfachster Start
- **Windows lokal oder Docker Desktop:** `setup.bat` doppelklicken, Ziel und Netzwerkzugriff auswählen, **Installieren** anklicken.
@@ -159,7 +145,7 @@ Lokaler Multi-Plattform Listing-Generator mit AI, Lagerverwaltung und Publishing
## Aktueller Entwicklungsstand
- Version: `1.35.0`
- Version: `1.34.3`
- UI-Slice: `UI 2026 / Slice 07 Cross-Browser Extensions, Responsive UI & Mobile Upload`
- Startseite: `Studio Flow`
- Tech Stack: Node.js, Express (ESM), Vanilla JS, SQLite (`better-sqlite3`)
@@ -296,7 +282,7 @@ Die Fortschrittsanzeige verwendet im FLUX-Studio-Pro-Pfad ausschließlich reale
Vendoo 1.28.0 prüft Artikel vor dem Publishing auf Blocker, Vollständigkeit, Bildqualität, mögliche Duplikate und Plattformtauglichkeit. AI-Verbesserungen werden nur als Vorschlag angezeigt und erst nach ausdrücklicher Bestätigung gespeichert.
## Installationsziel wählen (1.35.0)
## Installationsziel wählen (1.34.3)
`setup.bat` führt auf Windows durch lokale Node-Installation oder Docker Desktop. Für Linux, NAS, Portainer und VPS steht `setup.sh` bereit. Beide Wege verwenden dieselbe `.env`, dieselben Compose-Dateien und dieselben persistenten Datenbereiche.
-11
View File
@@ -56,14 +56,3 @@ Sicherheitsprobleme nicht mit Zugangsdaten oder personenbezogenen Daten in öffe
- Bearbeitungssperren verhindern parallele Änderungen, ersetzen aber keine Backups.
- Audit-Logs können personenbezogene Betriebsdaten wie Benutzer, IP und Aktionen enthalten; Aufbewahrung und Zugriff müssen organisatorisch geregelt werden.
## Architektur-Sicherheitsregeln ab 1.35.0
- Neue Kernel-Routen sind Deny-by-default und benötigen eine registrierte Policy.
- Modulmanifeste, Abhängigkeiten, Capabilities und Ownership werden vor dem Start validiert.
- Zyklische Abhängigkeiten, fehlende Capabilities und doppelte Ressourcen-Ownership blockieren den Kernelstart.
- Drittanbieter-Erweiterungen dürfen nicht im Vendoo-Hauptprozess ausgeführt werden.
- Themes dürfen nur freigegebene, typisierte Design Tokens verändern; freies CSS, JavaScript und externe URLs sind verboten.
- Der Event Bus ordnet Listener einem Owner zu, damit Module beim Stoppen ihre Listener vollständig entfernen können.
- Das Architektur-Gate `npm run verify:architecture` ist verpflichtender Bestandteil von CI und Releases.
-19
View File
@@ -1,19 +0,0 @@
{
"schemaVersion": 1,
"architecture": "modular-monolith",
"rules": {
"modulePrefix": "vendoo.",
"denyImplicitRoutes": true,
"denyCrossModuleDatabaseAccess": true,
"denyCrossModuleInternalImports": true,
"thirdPartyExtensionsInMainProcess": false,
"themeCustomCss": false,
"themeTokensOnly": true
},
"migration": {
"mode": "strangler",
"legacyBridgeStatus": "legacy-bridge",
"frameworkMigration": false,
"databaseReplacement": false
}
}
-34
View File
@@ -1,34 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://vendoo.local/contracts/module.schema.json",
"title": "Vendoo Module Manifest",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "id", "name", "version", "type", "status", "requires", "permissions", "provides", "consumes", "owns"],
"properties": {
"schemaVersion": { "const": 1 },
"id": { "type": "string", "pattern": "^vendoo\\.[a-z][a-z0-9-]*$" },
"name": { "type": "string", "minLength": 1, "maxLength": 80 },
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$" },
"type": { "enum": ["core", "feature", "adapter", "extension-host"] },
"status": { "enum": ["native", "legacy-bridge", "disabled"] },
"description": { "type": "string", "maxLength": 500 },
"requires": { "$ref": "#/$defs/moduleIds" },
"permissions": { "$ref": "#/$defs/capabilities" },
"provides": { "$ref": "#/$defs/capabilities" },
"consumes": { "$ref": "#/$defs/capabilities" },
"owns": { "$ref": "#/$defs/capabilities" }
},
"$defs": {
"moduleIds": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "pattern": "^vendoo\\.[a-z][a-z0-9-]*$" }
},
"capabilities": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(?:\\.[a-z][a-z0-9-]*)+$" }
}
}
}
-63
View File
@@ -1,63 +0,0 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { APP_ROOT } from '../../../lib/runtime-paths.mjs';
import { PlatformError } from '../../kernel/errors.mjs';
const source = JSON.parse(readFileSync(join(APP_ROOT, 'public', 'design-system', 'tokens', 'source.json'), 'utf8'));
const HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
const RGB_COLOR = /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(0|1|0?\.\d+))?\s*\)$/i;
function validColor(value) {
if (HEX_COLOR.test(value)) return true;
const match = value.match(RGB_COLOR);
if (!match) return false;
const channels = match.slice(1, 4).map(Number);
const alpha = match[4] === undefined ? 1 : Number(match[4]);
return channels.every(channel => Number.isFinite(channel) && channel >= 0 && channel <= 255) && Number.isFinite(alpha) && alpha >= 0 && alpha <= 1;
}
const DIMENSION = /^-?\d+(?:\.\d+)?px$/i;
function validateValue(token, value) {
const raw = String(value || '').trim();
if (token.type === 'color' && !validColor(raw)) return false;
if (token.type === 'dimension') {
if (!DIMENSION.test(raw)) return false;
const numeric = Number.parseFloat(raw);
if (token.min !== undefined && numeric < Number(token.min)) return false;
if (token.max !== undefined && numeric > Number(token.max)) return false;
}
return ['color', 'dimension'].includes(token.type);
}
export function getThemeContract() {
return {
schemaVersion: source.schemaVersion,
contractVersion: source.contractVersion,
themes: [...source.themes],
tokens: Object.fromEntries(Object.entries(source.tokens).map(([name, token]) => [name, {
css: token.css,
type: token.type,
customizable: Boolean(token.customizable),
...(token.min !== undefined ? { min: token.min } : {}),
...(token.max !== undefined ? { max: token.max } : {}),
}])),
};
}
export function validateThemeDefinition(theme) {
if (!theme || typeof theme !== 'object' || Array.isArray(theme)) {
throw new PlatformError('Theme-Definition ist ungültig.', { code: 'THEME_INVALID', status: 400, expose: true });
}
const id = String(theme.id || '').trim();
const name = String(theme.name || '').trim();
if (!/^[a-z][a-z0-9-]{2,39}$/.test(id) || ['light', 'dark', 'contrast', 'system'].includes(id)) throw new PlatformError('Theme-ID ist ungültig.', { code: 'THEME_ID_INVALID', status: 400, expose: true });
if (!name || name.length > 80) throw new PlatformError('Theme-Name ist ungültig.', { code: 'THEME_NAME_INVALID', status: 400, expose: true });
const values = {};
for (const [tokenName, value] of Object.entries(theme.values || {})) {
const token = source.tokens[tokenName];
if (!token || !token.customizable) throw new PlatformError(`Token ist nicht anpassbar: ${tokenName}`, { code: 'THEME_TOKEN_FORBIDDEN', status: 400, expose: true });
if (!validateValue(token, value)) throw new PlatformError(`Ungültiger Wert für ${tokenName}.`, { code: 'THEME_VALUE_INVALID', status: 400, expose: true });
values[tokenName] = String(value).trim();
}
return Object.freeze({ schemaVersion: 1, id, name, values });
}
-31
View File
@@ -1,31 +0,0 @@
export class PlatformError extends Error {
constructor(message, { code = 'PLATFORM_ERROR', status = 500, details = null, expose = false, cause = undefined } = {}) {
super(String(message || 'Unbekannter Plattformfehler'), { cause });
this.name = 'PlatformError';
this.code = String(code || 'PLATFORM_ERROR');
this.status = Number.isInteger(status) ? status : 500;
this.details = details;
this.expose = Boolean(expose || this.status < 500);
}
}
export function normalizePlatformError(error) {
if (error instanceof PlatformError) return error;
return new PlatformError(error?.message || 'Interner Serverfehler', {
code: error?.code || 'INTERNAL_ERROR',
status: Number.isInteger(error?.status) ? error.status : 500,
details: error?.details || null,
expose: Boolean(error?.expose),
cause: error,
});
}
export function platformErrorPayload(error, requestId = null) {
const normalized = normalizePlatformError(error);
return {
error: normalized.expose ? normalized.message : 'Interner Serverfehler',
code: normalized.code,
request_id: requestId || null,
...(normalized.expose && normalized.details ? { details: normalized.details } : {}),
};
}
-77
View File
@@ -1,77 +0,0 @@
import { PlatformError } from './errors.mjs';
const EVENT_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
export class EventBus {
#listeners = new Map();
#maxListeners;
constructor({ maxListenersPerEvent = 50 } = {}) {
this.#maxListeners = Math.max(1, Number(maxListenersPerEvent) || 50);
}
on(eventName, handler, { owner = 'core', once = false } = {}) {
if (!EVENT_PATTERN.test(String(eventName))) {
throw new PlatformError(`Ungültiger Eventname: ${eventName}`, { code: 'EVENT_NAME_INVALID', status: 500 });
}
if (typeof handler !== 'function') {
throw new PlatformError('Event-Handler muss eine Funktion sein.', { code: 'EVENT_HANDLER_INVALID', status: 500 });
}
const listeners = this.#listeners.get(eventName) || [];
if (listeners.length >= this.#maxListeners) {
throw new PlatformError(`Zu viele Listener für ${eventName}.`, { code: 'EVENT_LISTENER_LIMIT', status: 500 });
}
const entry = Object.freeze({ handler, owner: String(owner || 'core'), once: Boolean(once) });
listeners.push(entry);
this.#listeners.set(eventName, listeners);
return () => this.off(eventName, handler);
}
once(eventName, handler, options = {}) {
return this.on(eventName, handler, { ...options, once: true });
}
off(eventName, handler) {
const listeners = this.#listeners.get(eventName) || [];
const remaining = listeners.filter(entry => entry.handler !== handler);
if (remaining.length) this.#listeners.set(eventName, remaining);
else this.#listeners.delete(eventName);
return remaining.length !== listeners.length;
}
removeOwner(owner) {
let removed = 0;
for (const [eventName, listeners] of this.#listeners) {
const remaining = listeners.filter(entry => {
const match = entry.owner === owner;
if (match) removed += 1;
return !match;
});
if (remaining.length) this.#listeners.set(eventName, remaining);
else this.#listeners.delete(eventName);
}
return removed;
}
async emit(eventName, payload = null, context = {}) {
const listeners = [...(this.#listeners.get(eventName) || [])];
const results = [];
for (const entry of listeners) {
try {
results.push({ owner: entry.owner, ok: true, value: await entry.handler(payload, Object.freeze({ ...context, eventName })) });
} catch (error) {
results.push({ owner: entry.owner, ok: false, error });
} finally {
if (entry.once) this.off(eventName, entry.handler);
}
}
return results;
}
snapshot() {
return [...this.#listeners.entries()].map(([event, listeners]) => ({
event,
listeners: listeners.map(entry => ({ owner: entry.owner, once: entry.once })),
}));
}
}
-59
View File
@@ -1,59 +0,0 @@
import { PlatformError } from './errors.mjs';
const FLAG_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
function envKey(flag) {
return `VENDOO_FEATURE_${flag.replace(/[^a-z0-9]/gi, '_').toUpperCase()}`;
}
function parseBoolean(value, fallback) {
if (value === undefined || value === null || value === '') return fallback;
if (/^(1|true|yes|on)$/i.test(String(value))) return true;
if (/^(0|false|no|off)$/i.test(String(value))) return false;
throw new PlatformError(`Ungültiger Feature-Flag-Wert: ${value}`, { code: 'FEATURE_FLAG_VALUE_INVALID', status: 500 });
}
export class FeatureFlagRegistry {
#flags = new Map();
define(name, { defaultValue = false, owner = 'vendoo.system', description = '', mutable = false } = {}) {
const normalized = String(name || '');
if (!FLAG_PATTERN.test(normalized)) {
throw new PlatformError(`Ungültiger Feature-Flag: ${normalized}`, { code: 'FEATURE_FLAG_NAME_INVALID', status: 500 });
}
if (this.#flags.has(normalized)) {
throw new PlatformError(`Feature-Flag bereits definiert: ${normalized}`, { code: 'FEATURE_FLAG_DUPLICATE', status: 500 });
}
const configured = parseBoolean(process.env[envKey(normalized)], Boolean(defaultValue));
this.#flags.set(normalized, {
name: normalized,
value: configured,
defaultValue: Boolean(defaultValue),
owner: String(owner),
description: String(description),
mutable: Boolean(mutable),
source: process.env[envKey(normalized)] === undefined ? 'default' : 'environment',
});
return configured;
}
isEnabled(name) {
if (!this.#flags.has(name)) {
throw new PlatformError(`Unbekannter Feature-Flag: ${name}`, { code: 'FEATURE_FLAG_UNKNOWN', status: 500 });
}
return this.#flags.get(name).value;
}
set(name, value) {
const flag = this.#flags.get(name);
if (!flag) throw new PlatformError(`Unbekannter Feature-Flag: ${name}`, { code: 'FEATURE_FLAG_UNKNOWN', status: 404, expose: true });
if (!flag.mutable) throw new PlatformError(`Feature-Flag ist nicht zur Laufzeit änderbar: ${name}`, { code: 'FEATURE_FLAG_IMMUTABLE', status: 409, expose: true });
flag.value = Boolean(value);
flag.source = 'runtime';
return flag.value;
}
list() {
return [...this.#flags.values()].map(flag => ({ ...flag }));
}
}
-49
View File
@@ -1,49 +0,0 @@
import { PlatformError } from './errors.mjs';
export class LifecycleManager {
#state = 'created';
#hooks = [];
register({ id, order = 100, start = async () => {}, stop = async () => {} }) {
if (!id || this.#hooks.some(hook => hook.id === id)) {
throw new PlatformError(`Ungültiger oder doppelter Lifecycle-Hook: ${id}`, { code: 'LIFECYCLE_HOOK_INVALID' });
}
this.#hooks.push({ id: String(id), order: Number(order) || 100, start, stop, state: 'registered' });
}
async start(context) {
if (!['created', 'stopped'].includes(this.#state)) throw new PlatformError(`Kernel kann aus Zustand ${this.#state} nicht starten.`, { code: 'LIFECYCLE_STATE_INVALID' });
this.#state = 'starting';
const started = [];
try {
for (const hook of [...this.#hooks].sort((a, b) => a.order - b.order)) {
hook.state = 'starting';
await hook.start(context);
hook.state = 'running';
started.push(hook);
}
this.#state = 'running';
} catch (error) {
for (const hook of started.reverse()) {
try { await hook.stop({ ...context, rollback: true }); } catch {}
hook.state = 'stopped';
}
this.#state = 'failed';
throw error;
}
}
async stop(context) {
if (!['running', 'starting'].includes(this.#state)) return;
this.#state = 'stopping';
for (const hook of [...this.#hooks].sort((a, b) => b.order - a.order)) {
if (hook.state !== 'running') continue;
try { await hook.stop(context); } finally { hook.state = 'stopped'; }
}
this.#state = 'stopped';
}
snapshot() {
return { state: this.#state, hooks: this.#hooks.map(({ start: _s, stop: _t, ...hook }) => ({ ...hook })) };
}
}
-47
View File
@@ -1,47 +0,0 @@
import { PlatformError } from './errors.mjs';
const MODULE_ID = /^vendoo\.[a-z][a-z0-9-]*$/;
const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
const CAPABILITY = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
const TYPES = new Set(['core', 'feature', 'adapter', 'extension-host']);
const STATUSES = new Set(['native', 'legacy-bridge', 'disabled']);
function uniqueStrings(values, label, pattern = CAPABILITY) {
if (!Array.isArray(values)) throw new PlatformError(`${label} muss ein Array sein.`, { code: 'MODULE_MANIFEST_INVALID' });
const normalized = values.map(value => String(value || '').trim());
if (normalized.some(value => !pattern.test(value))) {
throw new PlatformError(`${label} enthält einen ungültigen Eintrag.`, { code: 'MODULE_MANIFEST_INVALID' });
}
if (new Set(normalized).size !== normalized.length) {
throw new PlatformError(`${label} enthält Duplikate.`, { code: 'MODULE_MANIFEST_INVALID' });
}
return normalized;
}
export function validateModuleManifest(manifest) {
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
throw new PlatformError('Modulmanifest fehlt oder ist ungültig.', { code: 'MODULE_MANIFEST_INVALID' });
}
const normalized = {
schemaVersion: Number(manifest.schemaVersion || 1),
id: String(manifest.id || '').trim(),
name: String(manifest.name || '').trim(),
version: String(manifest.version || '').trim(),
type: String(manifest.type || '').trim(),
status: String(manifest.status || 'native').trim(),
description: String(manifest.description || '').trim(),
requires: Object.freeze(uniqueStrings(manifest.requires || [], 'requires', MODULE_ID)),
permissions: Object.freeze(uniqueStrings(manifest.permissions || [], 'permissions')),
provides: Object.freeze(uniqueStrings(manifest.provides || [], 'provides')),
consumes: Object.freeze(uniqueStrings(manifest.consumes || [], 'consumes')),
owns: Object.freeze(uniqueStrings(manifest.owns || [], 'owns')),
};
if (normalized.schemaVersion !== 1) throw new PlatformError('Nicht unterstützte Manifestversion.', { code: 'MODULE_MANIFEST_VERSION' });
if (!MODULE_ID.test(normalized.id)) throw new PlatformError(`Ungültige Modul-ID: ${normalized.id}`, { code: 'MODULE_ID_INVALID' });
if (!normalized.name) throw new PlatformError(`Modulname fehlt: ${normalized.id}`, { code: 'MODULE_NAME_REQUIRED' });
if (!SEMVER.test(normalized.version)) throw new PlatformError(`Ungültige Modulversion: ${normalized.id}`, { code: 'MODULE_VERSION_INVALID' });
if (!TYPES.has(normalized.type)) throw new PlatformError(`Ungültiger Modultyp: ${normalized.id}`, { code: 'MODULE_TYPE_INVALID' });
if (!STATUSES.has(normalized.status)) throw new PlatformError(`Ungültiger Modulstatus: ${normalized.id}`, { code: 'MODULE_STATUS_INVALID' });
if (normalized.requires.includes(normalized.id)) throw new PlatformError(`Modul darf sich nicht selbst benötigen: ${normalized.id}`, { code: 'MODULE_SELF_DEPENDENCY' });
return Object.freeze(normalized);
}
-128
View File
@@ -1,128 +0,0 @@
import { PlatformError } from './errors.mjs';
import { validateModuleManifest } from './module-contract.mjs';
export class ModuleRegistry {
#modules = new Map();
#startOrder = [];
#ownership = new Map();
register({ manifest, lifecycle = {} }) {
const validated = validateModuleManifest(manifest);
if (this.#modules.has(validated.id)) {
throw new PlatformError(`Modul bereits registriert: ${validated.id}`, { code: 'MODULE_DUPLICATE' });
}
for (const resource of validated.owns) {
const owner = this.#ownership.get(resource);
if (owner) throw new PlatformError(`Ressource ${resource} wird bereits von ${owner} besessen.`, { code: 'MODULE_OWNERSHIP_CONFLICT' });
}
const record = {
manifest: validated,
lifecycle: {
start: typeof lifecycle.start === 'function' ? lifecycle.start : async () => {},
stop: typeof lifecycle.stop === 'function' ? lifecycle.stop : async () => {},
health: typeof lifecycle.health === 'function' ? lifecycle.health : async () => ({ ok: true }),
},
state: validated.status === 'disabled' ? 'disabled' : 'registered',
startedAt: null,
error: null,
};
this.#modules.set(validated.id, record);
for (const resource of validated.owns) this.#ownership.set(resource, validated.id);
return validated;
}
get(id) {
return this.#modules.get(id) || null;
}
#resolveStartOrder() {
const visiting = new Set();
const visited = new Set();
const order = [];
const visit = id => {
if (visited.has(id)) return;
if (visiting.has(id)) throw new PlatformError(`Zyklische Modulabhängigkeit bei ${id}.`, { code: 'MODULE_DEPENDENCY_CYCLE' });
const record = this.#modules.get(id);
if (!record) throw new PlatformError(`Fehlende Modulabhängigkeit: ${id}`, { code: 'MODULE_DEPENDENCY_MISSING' });
visiting.add(id);
for (const dependency of record.manifest.requires) visit(dependency);
visiting.delete(id);
visited.add(id);
order.push(id);
};
for (const id of this.#modules.keys()) visit(id);
const capabilities = new Set([...this.#modules.values()].flatMap(record => record.manifest.provides));
for (const record of this.#modules.values()) {
for (const capability of record.manifest.consumes) {
if (!capabilities.has(capability)) throw new PlatformError(`Fehlende Capability ${capability} für ${record.manifest.id}.`, { code: 'MODULE_CAPABILITY_MISSING' });
}
}
return order;
}
async startAll(context) {
this.#startOrder = this.#resolveStartOrder();
const started = [];
for (const id of this.#startOrder) {
const record = this.#modules.get(id);
if (record.state === 'disabled') continue;
try {
record.state = 'starting';
await record.lifecycle.start(Object.freeze({ ...context, module: record.manifest }));
record.state = 'running';
record.startedAt = new Date().toISOString();
started.push(id);
} catch (error) {
record.state = 'failed';
record.error = error?.message || String(error);
for (const startedId of started.reverse()) {
const startedRecord = this.#modules.get(startedId);
try { await startedRecord.lifecycle.stop(Object.freeze({ ...context, module: startedRecord.manifest, rollback: true })); } catch {}
startedRecord.state = 'stopped';
}
throw new PlatformError(`Modulstart fehlgeschlagen: ${id}`, { code: 'MODULE_START_FAILED', cause: error, details: { module: id } });
}
}
}
async stopAll(context) {
for (const id of [...this.#startOrder].reverse()) {
const record = this.#modules.get(id);
if (!record || record.state !== 'running') continue;
try {
record.state = 'stopping';
await record.lifecycle.stop(Object.freeze({ ...context, module: record.manifest }));
record.state = 'stopped';
} catch (error) {
record.state = 'failed';
record.error = error?.message || String(error);
}
}
}
async health() {
const checks = [];
for (const [id, record] of this.#modules) {
if (record.state === 'disabled') {
checks.push({ id, ok: true, state: 'disabled' });
continue;
}
try {
const result = await record.lifecycle.health({ module: record.manifest });
checks.push({ id, state: record.state, ok: record.state === 'running' && result?.ok !== false, ...(result || {}) });
} catch (error) {
checks.push({ id, state: record.state, ok: false, error: error?.message || String(error) });
}
}
return checks;
}
snapshot() {
return [...this.#modules.values()].map(record => ({
...record.manifest,
state: record.state,
startedAt: record.startedAt,
error: record.error,
}));
}
}
-74
View File
@@ -1,74 +0,0 @@
import { EventBus } from './event-bus.mjs';
import { FeatureFlagRegistry } from './feature-flags.mjs';
import { LifecycleManager } from './lifecycle.mjs';
import { ModuleRegistry } from './module-registry.mjs';
import { PolicyEngine } from './policy-engine.mjs';
import { RouteRegistry } from './route-registry.mjs';
import { loadBuiltInModules } from '../modules/catalog.mjs';
export function createPlatformKernel({ version, hasPermission }) {
const events = new EventBus();
const features = new FeatureFlagRegistry();
const modules = new ModuleRegistry();
const policies = new PolicyEngine();
const routes = new RouteRegistry({ policyEngine: policies });
const lifecycle = new LifecycleManager();
policies.register('platform.authenticated', ({ user }) => Boolean(user), {
description: 'Erfordert eine gültige Vendoo-Sitzung.',
});
policies.register('platform.security-admin', ({ user }) => Boolean(user && hasPermission(user.role, 'security.manage')), {
description: 'Erfordert die Berechtigung security.manage.',
});
features.define('platform.module-kernel', { defaultValue: true, description: 'Aktiviert den modularen Plattformkernel.' });
features.define('platform.route-contracts', { defaultValue: true, description: 'Aktiviert registrierte Routenverträge.' });
features.define('themes.token-runtime', { defaultValue: true, owner: 'vendoo.themes', description: 'Aktiviert den versionierten Design-Token-Vertrag.' });
features.define('extensions.isolated-host', { defaultValue: false, owner: 'vendoo.security', description: 'Reserviert für einen späteren isolierten Extension-Host.' });
for (const module of loadBuiltInModules()) modules.register(module);
lifecycle.register({
id: 'modules',
order: 20,
start: context => modules.startAll(context),
stop: context => modules.stopAll(context),
});
const context = Object.freeze({ version, events, features, policies });
return Object.freeze({
version,
events,
features,
lifecycle,
modules,
policies,
routes,
async start() {
await lifecycle.start(context);
await events.emit('platform.started', { version }, { owner: 'vendoo.system' });
},
async stop() {
await events.emit('platform.stopping', { version }, { owner: 'vendoo.system' });
await lifecycle.stop(context);
},
async health() {
const moduleChecks = await modules.health();
return {
ok: lifecycle.snapshot().state === 'running' && moduleChecks.every(check => check.ok),
lifecycle: lifecycle.snapshot(),
modules: moduleChecks,
};
},
snapshot() {
return {
version,
architecture: 'modular-monolith',
lifecycle: lifecycle.snapshot(),
modules: modules.snapshot(),
features: features.list(),
policies: policies.list(),
routes: routes.list(),
};
},
});
}
-30
View File
@@ -1,30 +0,0 @@
import { PlatformError } from './errors.mjs';
const POLICY_ID = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
export class PolicyEngine {
#policies = new Map();
register(id, evaluator, { owner = 'vendoo.security', description = '' } = {}) {
const normalized = String(id || '');
if (!POLICY_ID.test(normalized)) throw new PlatformError(`Ungültige Policy-ID: ${normalized}`, { code: 'POLICY_ID_INVALID' });
if (this.#policies.has(normalized)) throw new PlatformError(`Policy bereits registriert: ${normalized}`, { code: 'POLICY_DUPLICATE' });
if (typeof evaluator !== 'function') throw new PlatformError(`Policy benötigt eine Prüffunktion: ${normalized}`, { code: 'POLICY_EVALUATOR_INVALID' });
this.#policies.set(normalized, { id: normalized, evaluator, owner, description });
}
async evaluate(id, context) {
const policy = this.#policies.get(id);
if (!policy) {
return { allowed: false, reason: 'policy_not_registered', policy: id };
}
const result = await policy.evaluator(Object.freeze({ ...context }));
if (result === true) return { allowed: true, policy: id };
if (result === false || result == null) return { allowed: false, reason: 'denied', policy: id };
return { allowed: Boolean(result.allowed), reason: result.reason || null, policy: id, details: result.details || null };
}
list() {
return [...this.#policies.values()].map(({ evaluator: _evaluator, ...policy }) => ({ ...policy }));
}
}
-62
View File
@@ -1,62 +0,0 @@
import { PlatformError, platformErrorPayload } from './errors.mjs';
const METHODS = new Set(['get', 'post', 'put', 'patch', 'delete']);
const ROUTE_ID = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
export class RouteRegistry {
#routes = new Map();
#policyEngine;
constructor({ policyEngine }) {
this.#policyEngine = policyEngine;
}
register(app, definition) {
const method = String(definition.method || '').toLowerCase();
const id = String(definition.id || '');
const path = String(definition.path || '');
if (!ROUTE_ID.test(id)) throw new PlatformError(`Ungültige Route-ID: ${id}`, { code: 'ROUTE_ID_INVALID' });
if (!METHODS.has(method)) throw new PlatformError(`Ungültige HTTP-Methode für ${id}.`, { code: 'ROUTE_METHOD_INVALID' });
if (!path.startsWith('/')) throw new PlatformError(`Ungültiger Routenpfad für ${id}.`, { code: 'ROUTE_PATH_INVALID' });
if (!definition.policy) throw new PlatformError(`Route ${id} benötigt eine explizite Policy.`, { code: 'ROUTE_POLICY_REQUIRED' });
if (!definition.owner) throw new PlatformError(`Route ${id} benötigt einen Modulbesitzer.`, { code: 'ROUTE_OWNER_REQUIRED' });
if (typeof definition.handler !== 'function') throw new PlatformError(`Route ${id} benötigt einen Handler.`, { code: 'ROUTE_HANDLER_REQUIRED' });
const key = `${method.toUpperCase()} ${path}`;
if (this.#routes.has(key)) throw new PlatformError(`Route bereits registriert: ${key}`, { code: 'ROUTE_DUPLICATE' });
const contract = Object.freeze({
id,
method: method.toUpperCase(),
path,
owner: String(definition.owner),
policy: String(definition.policy),
audit: String(definition.audit || ''),
rateLimit: String(definition.rateLimit || 'default'),
csrf: definition.csrf !== false,
stability: String(definition.stability || 'internal'),
});
this.#routes.set(key, contract);
app[method](path, ...(definition.middleware || []), async (req, res, next) => {
try {
const decision = await this.#policyEngine.evaluate(contract.policy, { req, user: req.user, route: contract });
if (!decision.allowed) {
throw new PlatformError('Keine Berechtigung für diese Aktion.', {
code: 'POLICY_DENIED', status: 403, expose: true,
details: { policy: contract.policy, reason: decision.reason },
});
}
await definition.handler(req, res, { route: contract, decision });
} catch (error) {
if (res.headersSent) return next(error);
const status = Number.isInteger(error?.status) ? error.status : 500;
res.status(status).json(platformErrorPayload(error, req.requestId));
}
});
return contract;
}
list() {
return [...this.#routes.values()].map(route => ({ ...route }));
}
}
-28
View File
@@ -1,28 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.ai",
"name": "AI Services",
"version": "1.0.0",
"type": "feature",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich AI Services; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.security",
"vendoo.media"
],
"permissions": [
"generation.execute"
],
"provides": [
"ai.text",
"ai.images"
],
"consumes": [
"media.assets",
"security.policies"
],
"owns": [
"ai.providers",
"ai.jobs"
]
}
-26
View File
@@ -1,26 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.auth",
"name": "Authentication",
"version": "1.0.0",
"type": "core",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich Authentication; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.security"
],
"permissions": [
"sessions.manage"
],
"provides": [
"auth.sessions",
"auth.identity"
],
"consumes": [
"security.policies"
],
"owns": [
"auth.sessions",
"auth.tokens"
]
}
-17
View File
@@ -1,17 +0,0 @@
import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const here = dirname(fileURLToPath(import.meta.url));
const moduleNames = ['system', 'security', 'auth', 'users', 'themes', 'media', 'listings', 'ai', 'flux-studio', 'quality', 'publishing', 'operations'];
export function loadBuiltInModules() {
return moduleNames.map(name => ({
manifest: JSON.parse(readFileSync(join(here, name, 'module.json'), 'utf8')),
lifecycle: {
async start() {},
async stop() {},
async health() { return { ok: true, bridge: 'legacy' }; },
},
}));
}
-25
View File
@@ -1,25 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.flux-studio",
"name": "FLUX Studio",
"version": "1.0.0",
"type": "feature",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich FLUX Studio; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.ai"
],
"permissions": [
"generation.execute"
],
"provides": [
"flux.prompt-jobs"
],
"consumes": [
"ai.images"
],
"owns": [
"flux.generations",
"flux.history"
]
}
-31
View File
@@ -1,31 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.listings",
"name": "Listings & Inventory",
"version": "1.0.0",
"type": "feature",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich Listings & Inventory; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.auth",
"vendoo.media"
],
"permissions": [
"listings.view",
"listings.edit",
"listings.delete",
"inventory.edit"
],
"provides": [
"listings.catalog",
"inventory.stock"
],
"consumes": [
"media.assets",
"auth.identity"
],
"owns": [
"listings.records",
"inventory.locations"
]
}
-28
View File
@@ -1,28 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.media",
"name": "Media Library",
"version": "1.0.0",
"type": "feature",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich Media Library; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.auth"
],
"permissions": [
"media.view",
"media.edit",
"media.delete"
],
"provides": [
"media.assets",
"media.derivatives"
],
"consumes": [
"auth.identity"
],
"owns": [
"media.library",
"media.uploads"
]
}
-26
View File
@@ -1,26 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.operations",
"name": "Operations Center",
"version": "1.0.0",
"type": "feature",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich Operations Center; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.security"
],
"permissions": [
"operations.manage"
],
"provides": [
"operations.backup",
"operations.update"
],
"consumes": [
"security.audit"
],
"owns": [
"operations.backups",
"operations.updates"
]
}
-30
View File
@@ -1,30 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.publishing",
"name": "Publishing",
"version": "1.0.0",
"type": "feature",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich Publishing; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.listings",
"vendoo.security"
],
"permissions": [
"publishing.view",
"publishing.execute",
"publishing.manage"
],
"provides": [
"publishing.jobs",
"publishing.adapters"
],
"consumes": [
"listings.catalog",
"security.policies"
],
"owns": [
"publishing.queue",
"publishing.events"
]
}
-26
View File
@@ -1,26 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.quality",
"name": "Quality Center",
"version": "1.0.0",
"type": "feature",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich Quality Center; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.listings",
"vendoo.ai"
],
"permissions": [
"quality.execute"
],
"provides": [
"quality.reports"
],
"consumes": [
"listings.catalog",
"ai.text"
],
"owns": [
"quality.history"
]
}
-26
View File
@@ -1,26 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.security",
"name": "Security Core",
"version": "1.0.0",
"type": "core",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich Security Core; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.system"
],
"permissions": [
"security.manage"
],
"provides": [
"security.policies",
"security.audit"
],
"consumes": [
"platform.events"
],
"owns": [
"security.permissions",
"security.rate-limits"
]
}
-23
View File
@@ -1,23 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.system",
"name": "System Kernel",
"version": "1.0.0",
"type": "core",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich System Kernel; schrittweise Migration ohne Funktionsbruch.",
"requires": [],
"permissions": [
"dashboard.view"
],
"provides": [
"platform.lifecycle",
"platform.events",
"platform.features"
],
"consumes": [],
"owns": [
"system.runtime",
"system.health"
]
}
-26
View File
@@ -1,26 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.themes",
"name": "Themes & Design System",
"version": "1.0.0",
"type": "feature",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich Themes & Design System; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.system"
],
"permissions": [
"settings.manage"
],
"provides": [
"themes.tokens",
"themes.runtime"
],
"consumes": [
"platform.events"
],
"owns": [
"themes.definitions",
"themes.user-preferences"
]
}
-26
View File
@@ -1,26 +0,0 @@
{
"schemaVersion": 1,
"id": "vendoo.users",
"name": "Users & Roles",
"version": "1.0.0",
"type": "feature",
"status": "legacy-bridge",
"description": "Vertrag für den bestehenden Vendoo-Bereich Users & Roles; schrittweise Migration ohne Funktionsbruch.",
"requires": [
"vendoo.auth"
],
"permissions": [
"users.manage"
],
"provides": [
"users.directory",
"users.roles"
],
"consumes": [
"auth.identity"
],
"owns": [
"users.accounts",
"users.preferences"
]
}
+1 -1
View File
@@ -4,7 +4,7 @@ services:
build:
context: .
dockerfile: Dockerfile
image: vendoo:1.35.0
image: vendoo:1.34.3
container_name: vendoo
restart: unless-stopped
init: true
+1 -1
View File
@@ -4,7 +4,7 @@ services:
build:
context: .
dockerfile: Dockerfile
image: vendoo:1.35.0
image: vendoo:1.34.3
container_name: vendoo
restart: unless-stopped
init: true
-53
View File
@@ -1,53 +0,0 @@
# Vendoo 1.35.0 Abnahmebericht
## Bestanden
- JavaScript-/MJS-Syntax aller bestehenden und neuen Kernbestandteile
- Plattform-Architecture-Gate
- zwölf Modulmanifeste und Abhängigkeiten
- Zykluserkennung
- fehlende Capability wird blockiert
- doppelte Ressourcen-Ownership wird blockiert
- Deny-by-default-Route ohne Policy wird blockiert
- nicht berechtigter Zugriff auf registrierte Route ergibt HTTP 403
- Event-Auslieferung und Owner-Cleanup
- Lifecycle- und Modulshutdown
- sichere Theme-Werte und verbotene Token
- RGB-Bereichsprüfung
- deterministische Token-Generierung
- Light, Dark und High Contrast vorhanden
- Safe-DOM-Vertrag ohne `innerHTML`
- API-Client-Vertrag mit CSRF und Same-Origin
- Deployment-Gate 1.35.0
- Git-Sicherheitsgate: 365 Dateien ohne Secrets/Laufzeitdaten
- Git-Sicherheits-Regressionstest
- Git-Staging-Regressionstest
- persistenter Konfigurationsspeicher
- Shell-Installer-Simulation für Docker, NAS und VPS
- 68 Migrationseinheiten gegen frische Datenbank
- 68 Migrationseinheiten gegen bestehende Datenbank
- vorhandener Testdatensatz blieb erhalten
## Dependency-Test
`npm ci` wurde versucht. Die Lockdatei wurde aufgelöst, aber `better-sqlite3` konnte in der Sandbox nicht fertig gebaut werden:
1. Prebuild-Download scheiterte wegen fehlender DNS-/Netzverbindung zu GitHub.
2. Der Fallback mit lokal vorhandenen Node-Headern begann erfolgreich zu kompilieren, wurde aber beim SQLite-C-Build durch das Ausführungslimit beendet.
Dieser Punkt wird nicht als bestanden ausgegeben.
## Nicht praktisch getestet
- Windows PowerShell 5.1 und WinForms
- echter Windows-Lokalstart
- echter Docker-/NAS-/Portainer-Start
- VPS/Caddy/TLS
- Browser-End-to-End-Test
- visuelle Regression aller Seiten
- vollständiger Node-Serverstart mit nativ kompiliertem `better-sqlite3`
- GitHub Actions und GHCR-Liveausführung
## Daten- und Kompatibilitätsaussage
Keine Datenbank, Uploads, Backups, Operations-Daten, Benutzer, Artikel, API-Schlüssel, FLUX-Modelle oder ComfyUI-Daten wurden in das Paket aufgenommen oder gelöscht. Es gibt keine Framework- oder Datenbankmigration.
+6 -6
View File
@@ -1,10 +1,10 @@
# Vendoo 1.35.0 GitHub-Importstatus
# Vendoo 1.34.3 GitHub-Importstatus
- Importbranch: `release/1.35.0-platform-kernel`
- Repository: `Masterluke77/vendoo` (privat)
- Importbranch: `release/1.34.3-initial-import`
- Zielbranch: `main`
- Draft Pull Request: `#7`
- Repository: privat
- Draft Pull Request: `#6`
Der vollständige Quellstand wird über den Vendoo GitHub-Deploy-Assistenten übertragen. Vor dem Merge müssen Git-Sicherheitsgate, Architektur-Gate, Datenbankmigrationen, vollständiger Diff und GitHub Actions erfolgreich sein.
Version 1.34.3 schließt den lokalen Ordner `updates/` einschließlich `updates/backups/`, Slice-Sicherungen und Rollback-Artefakten vollständig aus dem temporären Git-Staging aus. Aktive Dateien aus dem Projektroot und aus `scripts/` werden weiterhin importiert.
Lokale `.env.*`, Datenbanken, Uploads, Backups, Logs, Operations-, Update- und Modellordner bleiben ausgeschlossen.
Der Pull Request darf erst nach vollständigem Quellimport, geprüftem Diff, grünem CI-Lauf und bestätigter Secret-/Laufzeitdatenprüfung zusammengeführt werden.
-64
View File
@@ -1,64 +0,0 @@
# Vendoo 1.35.0 Modular Platform Kernel, Design-System Contracts & Security Architecture Foundation
## Ziel
Version 1.35.0 stoppt den unkontrollierten weiteren Ausbau der großen Bestandsdateien und schafft eine verbindliche Plattformarchitektur. Die bestehende Anwendung bleibt funktionsfähig und wird nicht per Big-Bang ersetzt.
## Implementiert
### Plattformkernel
- `app/kernel/platform-kernel.mjs`
- geordneter Start und Stop
- Lifecycle-Rollback bei fehlgeschlagenem Start
- Kernelzustand im Readiness-Check
- geordneter Kernelshutdown vor dem SQLite-Checkpoint
### Modulsystem
- zwölf validierte Bestandsmodule
- Manifestvertrag und JSON-Schema
- Abhängigkeitsauflösung mit Zykluserkennung
- Capability-Prüfung
- eindeutige Ressourcen-Ownership
- Start-Rollback bereits gestarteter Module
- Status `legacy-bridge` für noch nicht vollständig migrierte Bereiche
### Sicherheit
- Policy Engine mit Deny-by-default
- Routenregistrierung nur mit Modulbesitzer und Policy
- standardisierte Plattformfehler mit Request-ID
- geschützte Admin-Endpunkte für Kernel-, Modul- und Theme-Vertragsstatus
- Owner-basierter Event-Bus
- feste Feature-Flag-Registrierung
- isolierter Extension-Host bleibt standardmäßig deaktiviert
### Design-System
- 33 versionierte Design Tokens
- Light, Dark und High Contrast
- deterministische CSS- und Vertragsgenerierung
- Kompatibilitätsbrücke zu bestehenden CSS-Variablen
- Browser-Theme-Runtime mit Systempräferenz
- sichere Backend-Validierung für künftige Theme-Definitionen
- kein freies CSS, JavaScript oder externe URL-Werte
### Frontend-Verträge
- Frontend Module Registry mit `mount`/`unmount`
- Listener-Cleanup über `AbortController`
- Safe-DOM-Helfer auf Basis von `textContent`
- zentraler zukünftiger API-Client mit Same-Origin-Credentials, CSRF und standardisierten Fehlern
## Neue interne Endpunkte
Nur Benutzer mit `security.manage`:
- `GET /api/security/platform`
- `GET /api/security/platform/modules`
- `GET /api/security/platform/theme-contract`
## Nicht behauptet
Version 1.35.0 ist die Foundation, nicht die vollständige Migration. `server.mjs`, `public/app.js`, `public/style.css` und `lib/db.mjs` bleiben weiterhin groß. Die Fachmodule werden ab 1.38.0 einzeln migriert. Der sichtbare Theme Manager folgt in einem eigenen Release.
-27
View File
@@ -1,27 +0,0 @@
{
"release": "1.35.0",
"architecture": "modular-monolith",
"modules": 12,
"design_tokens": 33,
"themes": ["light", "dark", "contrast"],
"feature_flags": 4,
"policies": 2,
"new_registered_routes": 3,
"git_branch": "release/1.35.0-platform-kernel",
"draft_pull_request": 7,
"checks": {
"syntax": true,
"architecture_gate": true,
"deployment_gate": true,
"git_safety": true,
"git_safety_regression": true,
"git_staging_regression": true,
"config_store": true,
"installer_shell_simulation": true,
"fresh_db_migration_blocks": 68,
"existing_db_migration_blocks": 68,
"existing_row_preserved": true,
"npm_ci": false,
"npm_ci_reason": "Native better-sqlite3 build could not finish in the network-restricted, time-limited sandbox."
}
}
-39
View File
@@ -1,39 +0,0 @@
# Vendoo 1.35.0 Design-System- und Theme-Vertrag
## Quelle der Wahrheit
Die editierbare Tokenquelle liegt unter:
`public/design-system/tokens/source.json`
Aus ihr erzeugt `tools/generate-design-tokens.mjs` deterministisch:
- `public/design-system/tokens.css`
- `public/design-system/theme-contract.json`
Direkte Änderungen an `tokens.css` sind nicht zulässig.
## Ebenen
- Primitive/semantische Farb-, Radius-, Schatten-, Dichte-, Layout- und Bewegungswerte
- CSS-Variablen mit Präfix `--vd-*`
- Kompatibilitätsbrücke für bestehende Variablen wie `--bg`, `--accent` und `--radius`
## Themes
1. `light`
2. `dark`
3. `contrast`
4. Browserpräferenz `system` über die Theme Runtime
## Sicherheit
Der zukünftige Theme Manager darf ausschließlich Tokens mit `customizable: true` verändern. Freies CSS, HTML, URLs, JavaScript, Schattenausdrücke und beliebige Variablennamen sind nicht erlaubt. Farben und Dimensionen werden typ- und bereichsgeprüft. Ein Theme wird nie ungeprüft als Stylesheet gespeichert.
## Nächster Ausbau
- persistente Themes pro Benutzer und systemweit
- Live-Vorschau in isoliertem Vorschaucontainer
- automatische Kontrastprüfung
- Import/Export eines versionierten Theme-JSON
- Versionsverlauf und Zurücksetzen
@@ -1,25 +0,0 @@
# Vendoo Modularisierungs-Roadmap ab 1.35.0
## 1.35.0 Foundation
Kernel, Manifeste, Lifecycle, Event Bus, Feature Flags, Policy-/Routenverträge, Design Tokens und Architekturtests.
## 1.36.0 Theme Manager
Persistente sichere Themeverwaltung, Vorschau, Import/Export, Kontrast- und Accessibility-Gates.
## 1.37.0 Security & Secrets
Secret Provider, Schema Registry, strukturierte Logs, sichere Fehlergrenzen und CSP-Migrationsgrundlage.
## 1.38.0 Auth, Users & Settings
Erster vollständig migrierter Backend-/Frontend-Modulverbund einschließlich Vertragstests.
## 1.39.0 Listings, Inventory & Media
Fachmodule, Repository-Schicht, UI-Komponenten und Datenzugriffsgrenzen.
## 1.40.0 Publishing, AI & FLUX
Adapterverträge, Job-Schnittstellen, FLUX-Modul und Vorbereitung des isolierten Extension-Hosts.
-31
View File
@@ -1,31 +0,0 @@
# Vendoo 1.35.0 Modulsystem
## Ziel
Vendoo wird schrittweise als modularer Monolith organisiert. Der bestehende Code bleibt in 1.35.0 funktional unverändert und wird über `legacy-bridge`-Manifeste erfasst. Neue Funktionen müssen die Kernelverträge verwenden; bestehende Bereiche werden in späteren Releases einzeln migriert.
## Verbindliche Regeln
1. Jedes Modul besitzt eine eindeutige ID `vendoo.<name>` und ein validiertes `module.json`.
2. Abhängigkeiten müssen explizit und frei von Zyklen sein.
3. Neue Routen benötigen Besitzer, Policy, Auditklasse, Rate-Limit-Klasse und Stabilitätsstatus.
4. Unbekannte Policies verweigern den Zugriff. Es gibt keinen Allow-by-default-Fallback.
5. Module kommunizieren künftig über öffentliche Services, Capabilities und Events, nicht über interne Querimporte.
6. Ein Modul darf langfristig nur seine eigenen Tabellen beziehungsweise Repository-Schnittstellen verändern.
7. Externe Erweiterungen werden nicht im Vendoo-Hauptprozess ausgeführt. Ein isolierter Extension-Host bleibt bis zur sicheren Umsetzung deaktiviert.
8. Modulaktivierung durch bloßes Kopieren einer Datei ist unzulässig.
## Status 1.35.0
Die Bereiche System, Security, Auth, Benutzer, Themes, Medien, Artikel, AI, FLUX Studio, Quality Center, Publishing und Operations sind registriert. Ihr Status `legacy-bridge` bedeutet: Der Vertrag existiert, die bestehende Implementierung liegt aber noch überwiegend in `server.mjs`, `lib/`, `public/app.js` und `public/style.css`.
## Migrationsmethode
Vendoo verwendet das Strangler-Verfahren:
- neue Architektur neben dem Bestand aufbauen,
- einen fachlichen Bereich vollständig migrieren,
- Verhalten und Datenbestand prüfen,
- erst danach den alten Pfad entfernen.
Kein Big-Bang-Refactoring, keine Framework-Migration und kein Datenbankreset.
@@ -1,29 +0,0 @@
# Vendoo 1.35.0 Sicherheitsarchitektur
## Neue Grundlage
- Deny-by-default Policy Engine für neue Kernel-Routen
- explizite Routenverträge
- standardisierte interne Fehler mit Request-ID
- validierte Modulmanifeste und Abhängigkeiten
- Owner-basierter Event-Bus mit sauberem Listener-Abbau
- Feature Flags mit fester Registrierung und sicherem Environment-Override
- sichere Theme-Werte statt frei eingebbarem CSS
- geordneter Kernelstart und Kernelshutdown
## Bestehender Schutz bleibt aktiv
Sitzungen, CSRF, Rollen, Berechtigungen, Rate Limits, Origin-/Hostprüfung, Uploadschutz, Audit und Ressourcenlocks werden nicht ersetzt oder umgangen.
## Offene Produktiv-Gates
Diese Punkte werden bewusst nicht als bereits gelöst ausgegeben:
- vollständige CSP ohne `unsafe-inline`
- zentral validierte Request-/Response-Schemas für alle Bestandsrouten
- verschlüsselter Secret Provider je Plattform
- isolierter Extension-Host
- vollständige Repository-Trennung aller Datenbankzugriffe
- automatisierte Browser-, Accessibility- und Penetrationstests
Bis zu ihrer Umsetzung bleiben die entsprechenden Feature Flags deaktiviert oder die Altpfade unter bestehenden Schutzmechanismen aktiv.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vendoo",
"version": "1.35.0",
"version": "1.34.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vendoo",
"version": "1.35.0",
"version": "1.34.3",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"better-sqlite3": "^12.11.1",
+5 -8
View File
@@ -1,28 +1,25 @@
{
"name": "vendoo",
"version": "1.35.0",
"version": "1.34.3",
"type": "module",
"private": true,
"scripts": {
"start": "node bootstrap.mjs",
"dev": "node --watch bootstrap.mjs",
"check": "node --check bootstrap.mjs && node --check server.mjs && node --check lib/config-store.mjs && node --check lib/runtime-paths.mjs && node --check lib/db.mjs && node --check lib/auth.mjs && node --check lib/security.mjs && node --check lib/operations-center.mjs && node --check lib/images.mjs && node --check lib/ai-local-images.mjs && node --check lib/ai-model-photos.mjs && node --check lib/ai-flux-prompt-jobs.mjs && node --check lib/image-editor.mjs && node --check lib/image-batch-jobs.mjs && node --check lib/quality-center.mjs && node --check public/app.js && node --check public/design-system/theme-runtime.js && node --check public/core/safe-dom.js && node --check public/core/frontend-module-registry.js && node --check public/core/api-client.js && node --check app/kernel/errors.mjs && node --check app/kernel/event-bus.mjs && node --check app/kernel/feature-flags.mjs && node --check app/kernel/module-contract.mjs && node --check app/kernel/module-registry.mjs && node --check app/kernel/policy-engine.mjs && node --check app/kernel/route-registry.mjs && node --check app/kernel/lifecycle.mjs && node --check app/kernel/platform-kernel.mjs && node --check app/modules/catalog.mjs && node --check app/core/themes/theme-contract.mjs && node --check tools/generate-design-tokens.mjs && node --check tools/verify-platform-architecture-1.35.0.mjs && node --check tools/verify-config-store.mjs && node --check tools/verify-git-safety.mjs && node --check tools/verify-deployment-1.35.0.mjs && node --check tools/verify-git-safety-regression.mjs && node --check tools/prepare-git-staging.mjs && node --check tools/verify-git-staging.mjs",
"check": "node --check bootstrap.mjs && node --check lib/config-store.mjs && node --check server.mjs && node --check lib/runtime-paths.mjs && node --check lib/db.mjs && node --check lib/auth.mjs && node --check lib/security.mjs && node --check lib/operations-center.mjs && node --check lib/images.mjs && node --check lib/ai-local-images.mjs && node --check lib/ai-model-photos.mjs && node --check lib/ai-flux-prompt-jobs.mjs && node --check lib/image-editor.mjs && node --check lib/image-batch-jobs.mjs && node --check lib/quality-center.mjs && node --check public/app.js && node --check tools/verify-config-store.mjs && node --check tools/verify-git-safety.mjs && node --check tools/verify-deployment-1.34.3.mjs && node --check tools/verify-git-safety-regression.mjs && node --check tools/prepare-git-staging.mjs && node --check tools/verify-git-staging.mjs",
"verify:slice30": "node tools/verify-slice30-mock.mjs",
"verify:db": "python tools/verify-db-migrations.py",
"verify:slice31": "node tools/verify-slice31-image-editor.mjs",
"verify:hotfix": "node tools/verify-release-1.25.1.mjs",
"verify:gallery": "node tools/verify-release-1.25.1-static.mjs",
"verify:release": "node tools/verify-deployment-1.35.0.mjs",
"verify:release": "node tools/verify-deployment-1.34.3.mjs",
"verify:ui-contracts": "node tools/verify-ui-contracts-1.26.1.mjs",
"verify:deployment": "node tools/verify-deployment-1.35.0.mjs",
"verify:deployment": "node tools/verify-deployment-1.34.3.mjs",
"verify:git": "node tools/verify-git-safety.mjs",
"verify:config": "node tools/verify-config-store.mjs",
"verify:installer-shell": "sh tools/verify-installer-shell.sh",
"verify:git-regression": "node tools/verify-git-safety-regression.mjs",
"verify:git-staging": "node tools/verify-git-staging.mjs",
"generate:tokens": "node tools/generate-design-tokens.mjs",
"verify:architecture": "node tools/verify-platform-architecture-1.35.0.mjs",
"verify:all": "npm run verify:git && npm run verify:git-regression && npm run verify:git-staging && npm run check && npm run verify:architecture && npm run verify:deployment && npm run verify:config && npm run verify:installer-shell && npm run verify:db"
"verify:git-staging": "node tools/verify-git-staging.mjs"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
+4 -10
View File
@@ -1,4 +1,4 @@
const VENDOO_UI_BUILD = '1.35.0';
const VENDOO_UI_BUILD = '1.34.3';
const state = {
photos: [],
@@ -6479,21 +6479,15 @@ function escapeHtml(str) {
// --- Dark Mode ---
function setupDarkMode() {
const saved = localStorage.getItem('vendoo-theme');
if (window.VendooTheme) {
window.VendooTheme.setTheme(saved === 'light' || saved === 'dark' || saved === 'contrast' ? saved : 'system', { persist: false });
} else if (saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
if (saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.setAttribute('data-theme', 'dark');
}
updateThemeIcon();
document.getElementById('theme-toggle').addEventListener('click', () => {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const nextTheme = isDark ? 'light' : 'dark';
if (window.VendooTheme) window.VendooTheme.setTheme(nextTheme);
else {
document.documentElement.setAttribute('data-theme', nextTheme);
localStorage.setItem('vendoo-theme', nextTheme);
}
document.documentElement.setAttribute('data-theme', isDark ? 'light' : 'dark');
localStorage.setItem('vendoo-theme', isDark ? 'light' : 'dark');
updateThemeIcon();
});
}
-40
View File
@@ -1,40 +0,0 @@
export class VendooApiError extends Error {
constructor(message, { status = 0, code = 'API_ERROR', requestId = null, details = null } = {}) {
super(message);
this.name = 'VendooApiError';
this.status = status;
this.code = code;
this.requestId = requestId;
this.details = details;
}
}
function readCookie(name) {
const prefix = `${encodeURIComponent(name)}=`;
return document.cookie.split(';').map(value => value.trim()).find(value => value.startsWith(prefix))?.slice(prefix.length) || '';
}
export async function apiRequest(path, { method = 'GET', body, headers = {}, signal } = {}) {
const verb = String(method).toUpperCase();
const requestHeaders = new Headers(headers);
requestHeaders.set('Accept', 'application/json');
if (body !== undefined) requestHeaders.set('Content-Type', 'application/json');
if (!['GET', 'HEAD', 'OPTIONS'].includes(verb)) requestHeaders.set('X-CSRF-Token', decodeURIComponent(readCookie('csrf_token')));
const response = await fetch(path, {
method: verb,
credentials: 'same-origin',
headers: requestHeaders,
body: body === undefined ? undefined : JSON.stringify(body),
signal,
redirect: 'error',
});
const requestId = response.headers.get('x-vendoo-request-id');
const payload = await response.json().catch(() => ({}));
if (!response.ok) throw new VendooApiError(payload.error || `HTTP ${response.status}`, {
status: response.status,
code: payload.code || 'API_ERROR',
requestId: payload.request_id || requestId,
details: payload.details || null,
});
return payload;
}
-51
View File
@@ -1,51 +0,0 @@
const MODULE_ID = /^vendoo\.[a-z][a-z0-9-]*$/;
export class FrontendModuleRegistry {
#modules = new Map();
#active = null;
register(definition) {
const id = String(definition?.id || '');
if (!MODULE_ID.test(id)) throw new Error(`Ungültige Frontend-Modul-ID: ${id}`);
if (this.#modules.has(id)) throw new Error(`Frontend-Modul bereits registriert: ${id}`);
if (typeof definition.mount !== 'function') throw new Error(`Frontend-Modul benötigt mount(): ${id}`);
this.#modules.set(id, Object.freeze({
id,
version: String(definition.version || '1.0.0'),
status: String(definition.status || 'native'),
mount: definition.mount,
unmount: typeof definition.unmount === 'function' ? definition.unmount : async () => {},
}));
}
async mount(id, root, services = {}) {
if (!(root instanceof Element)) throw new Error('Frontend-Modul benötigt ein gültiges Root-Element.');
const module = this.#modules.get(id);
if (!module) throw new Error(`Frontend-Modul ist nicht registriert: ${id}`);
await this.unmount();
const controller = new AbortController();
const context = Object.freeze({
root,
services: Object.freeze({ ...services }),
signal: controller.signal,
listen(target, event, handler, options = {}) {
target.addEventListener(event, handler, { ...options, signal: controller.signal });
},
});
await module.mount(context);
this.#active = { module, context, controller };
return module;
}
async unmount() {
if (!this.#active) return;
const active = this.#active;
this.#active = null;
active.controller.abort();
await active.module.unmount(active.context);
}
list() {
return [...this.#modules.values()].map(({ mount: _mount, unmount: _unmount, ...module }) => ({ ...module }));
}
}
-26
View File
@@ -1,26 +0,0 @@
export function setText(node, value) {
if (!(node instanceof Node)) throw new TypeError('DOM-Ziel fehlt.');
node.textContent = value == null ? '' : String(value);
return node;
}
export function createElement(tagName, { className = '', text = '', attributes = {}, children = [] } = {}) {
if (!/^[a-z][a-z0-9-]*$/i.test(String(tagName))) throw new TypeError('Ungültiger Elementname.');
const element = document.createElement(tagName);
if (className) element.className = String(className);
if (text !== '') setText(element, text);
for (const [name, value] of Object.entries(attributes)) {
if (!/^(aria-[a-z-]+|data-[a-z-]+|id|role|title|type|name|value|href|src|alt|tabindex)$/i.test(name)) {
throw new TypeError(`Attribut ist nicht freigegeben: ${name}`);
}
if (['href', 'src'].includes(name.toLowerCase())) {
const url = new URL(String(value), window.location.origin);
if (!['http:', 'https:', 'blob:'].includes(url.protocol) || (['http:', 'https:'].includes(url.protocol) && url.origin !== window.location.origin)) {
throw new TypeError(`Externe oder unsichere URL ist nicht erlaubt: ${name}`);
}
}
element.setAttribute(name, String(value));
}
for (const child of children) element.append(child);
return element;
}
-186
View File
@@ -1,186 +0,0 @@
{
"schemaVersion": 1,
"contractVersion": "1.0.0",
"themes": [
"light",
"dark",
"contrast"
],
"tokens": {
"color.canvas": {
"css": "--vd-canvas",
"type": "color",
"customizable": true
},
"color.surface": {
"css": "--vd-surface",
"type": "color",
"customizable": true
},
"color.surfaceRaised": {
"css": "--vd-surface-raised",
"type": "color",
"customizable": true
},
"color.surfaceMuted": {
"css": "--vd-surface-muted",
"type": "color",
"customizable": true
},
"color.surfaceQuiet": {
"css": "--vd-surface-quiet",
"type": "color",
"customizable": true
},
"color.line": {
"css": "--vd-line",
"type": "color",
"customizable": true
},
"color.lineStrong": {
"css": "--vd-line-strong",
"type": "color",
"customizable": true
},
"color.ink": {
"css": "--vd-ink",
"type": "color",
"customizable": true
},
"color.inkSecondary": {
"css": "--vd-ink-secondary",
"type": "color",
"customizable": true
},
"color.inkMuted": {
"css": "--vd-ink-muted",
"type": "color",
"customizable": true
},
"color.brand": {
"css": "--vd-brand",
"type": "color",
"customizable": true
},
"color.brandStrong": {
"css": "--vd-brand-strong",
"type": "color",
"customizable": true
},
"color.brandSoft": {
"css": "--vd-brand-soft",
"type": "color",
"customizable": false
},
"color.success": {
"css": "--vd-success",
"type": "color",
"customizable": true
},
"color.successSoft": {
"css": "--vd-success-soft",
"type": "color",
"customizable": false
},
"color.warning": {
"css": "--vd-warning",
"type": "color",
"customizable": true
},
"color.warningSoft": {
"css": "--vd-warning-soft",
"type": "color",
"customizable": false
},
"color.danger": {
"css": "--vd-danger",
"type": "color",
"customizable": true
},
"color.dangerSoft": {
"css": "--vd-danger-soft",
"type": "color",
"customizable": false
},
"color.info": {
"css": "--vd-info",
"type": "color",
"customizable": true
},
"color.infoSoft": {
"css": "--vd-info-soft",
"type": "color",
"customizable": false
},
"color.focus": {
"css": "--vd-focus",
"type": "color",
"customizable": false
},
"shadow.float": {
"css": "--vd-shadow-float",
"type": "shadow",
"customizable": false
},
"shadow.soft": {
"css": "--vd-shadow-soft",
"type": "shadow",
"customizable": false
},
"radius.xs": {
"css": "--vd-radius-xs",
"type": "dimension",
"customizable": true,
"min": 0,
"max": 16
},
"radius.sm": {
"css": "--vd-radius-sm",
"type": "dimension",
"customizable": true,
"min": 0,
"max": 20
},
"radius.md": {
"css": "--vd-radius-md",
"type": "dimension",
"customizable": true,
"min": 0,
"max": 28
},
"layout.sidebarWidth": {
"css": "--vd-sidebar-width",
"type": "dimension",
"customizable": false
},
"layout.topbarHeight": {
"css": "--vd-topbar-height",
"type": "dimension",
"customizable": false
},
"density.controlHeight": {
"css": "--vd-control-height",
"type": "dimension",
"customizable": true,
"min": 32,
"max": 52
},
"density.contentGap": {
"css": "--vd-content-gap",
"type": "dimension",
"customizable": true,
"min": 8,
"max": 32
},
"motion.fast": {
"css": "--vd-motion-fast",
"type": "duration",
"customizable": false
},
"motion.normal": {
"css": "--vd-motion-normal",
"type": "duration",
"customizable": false
}
}
}
-36
View File
@@ -1,36 +0,0 @@
const STORAGE_KEY = 'vendoo-theme';
const SAFE_THEMES = new Set(['light', 'dark', 'contrast', 'system']);
function effectiveTheme(preference) {
if (preference !== 'system') return preference;
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
function applyTheme(preference) {
const normalized = SAFE_THEMES.has(preference) ? preference : 'system';
const resolved = effectiveTheme(normalized);
document.documentElement.dataset.theme = resolved;
document.documentElement.dataset.themePreference = normalized;
return resolved;
}
function setTheme(preference, { persist = true } = {}) {
if (!SAFE_THEMES.has(preference)) throw new Error('Unbekanntes Vendoo-Theme.');
if (persist) localStorage.setItem(STORAGE_KEY, preference);
const resolved = applyTheme(preference);
window.dispatchEvent(new CustomEvent('vendoo:theme-changed', { detail: { preference, resolved } }));
return resolved;
}
const preference = SAFE_THEMES.has(localStorage.getItem(STORAGE_KEY)) ? localStorage.getItem(STORAGE_KEY) : 'system';
applyTheme(preference);
window.matchMedia?.('(prefers-color-scheme: dark)').addEventListener?.('change', () => {
if (document.documentElement.dataset.themePreference === 'system') applyTheme('system');
});
window.VendooTheme = Object.freeze({
get preference() { return document.documentElement.dataset.themePreference || 'system'; },
get resolved() { return document.documentElement.dataset.theme || 'light'; },
setTheme,
supported: Object.freeze([...SAFE_THEMES]),
});
-113
View File
@@ -1,113 +0,0 @@
/* Automatisch generiert aus design-system/tokens/source.json. Nicht direkt bearbeiten. */
/* Contract 1.0.0 */
:root {
--vd-canvas: #f7f5f1;
--vd-surface: #fffdf9;
--vd-surface-raised: #ffffff;
--vd-surface-muted: #f1efea;
--vd-surface-quiet: #ebe8e1;
--vd-line: #e2ded6;
--vd-line-strong: #cbc5ba;
--vd-ink: #171714;
--vd-ink-secondary: #5f5b54;
--vd-ink-muted: #8c877e;
--vd-brand: #e33a16;
--vd-brand-strong: #c92f10;
--vd-brand-soft: rgba(227, 58, 22, 0.08);
--vd-success: #2f7d50;
--vd-success-soft: #e7f2ea;
--vd-warning: #b96a23;
--vd-warning-soft: #fbefe3;
--vd-danger: #c44234;
--vd-danger-soft: #f9e8e5;
--vd-info: #3975ba;
--vd-info-soft: #e8f0f9;
--vd-focus: rgba(227, 58, 22, 0.18);
--vd-shadow-float: 0 18px 48px rgba(35, 30, 24, 0.12);
--vd-shadow-soft: 0 8px 24px rgba(35, 30, 24, 0.06);
--vd-radius-xs: 5px;
--vd-radius-sm: 8px;
--vd-radius-md: 12px;
--vd-sidebar-width: 148px;
--vd-topbar-height: 60px;
--vd-control-height: 40px;
--vd-content-gap: 16px;
--vd-motion-fast: 120ms;
--vd-motion-normal: 180ms;
}
[data-theme="dark"] {
--vd-canvas: #171714;
--vd-surface: #1d1d19;
--vd-surface-raised: #22221e;
--vd-surface-muted: #292923;
--vd-surface-quiet: #303029;
--vd-line: #37372f;
--vd-line-strong: #4a4940;
--vd-ink: #f2efe8;
--vd-ink-secondary: #c3beb4;
--vd-ink-muted: #8f8a80;
--vd-brand: #ff5a32;
--vd-brand-strong: #ff744f;
--vd-brand-soft: rgba(255, 90, 50, 0.12);
--vd-success: #75bd8c;
--vd-success-soft: #203629;
--vd-warning: #e2a35f;
--vd-warning-soft: #3b2e20;
--vd-danger: #ed7568;
--vd-danger-soft: #3b2523;
--vd-info: #78a9df;
--vd-info-soft: #202f40;
--vd-focus: rgba(255, 90, 50, 0.26);
--vd-shadow-float: 0 20px 56px rgba(0, 0, 0, 0.38);
--vd-shadow-soft: 0 8px 26px rgba(0, 0, 0, 0.20);
--vd-radius-xs: 5px;
--vd-radius-sm: 8px;
--vd-radius-md: 12px;
--vd-sidebar-width: 148px;
--vd-topbar-height: 60px;
--vd-control-height: 40px;
--vd-content-gap: 16px;
--vd-motion-fast: 120ms;
--vd-motion-normal: 180ms;
}
[data-theme="contrast"] {
--vd-canvas: #000000;
--vd-surface: #090909;
--vd-surface-raised: #111111;
--vd-surface-muted: #1b1b1b;
--vd-surface-quiet: #242424;
--vd-line: #ffffff;
--vd-line-strong: #ffffff;
--vd-ink: #ffffff;
--vd-ink-secondary: #ffffff;
--vd-ink-muted: #f2f2f2;
--vd-brand: #ffe600;
--vd-brand-strong: #ffffff;
--vd-brand-soft: rgba(255, 230, 0, 0.20);
--vd-success: #66ff99;
--vd-success-soft: #002d12;
--vd-warning: #ffd000;
--vd-warning-soft: #332a00;
--vd-danger: #ff6b6b;
--vd-danger-soft: #3a0000;
--vd-info: #72c7ff;
--vd-info-soft: #00253d;
--vd-focus: rgba(255, 230, 0, 0.55);
--vd-shadow-float: 0 0 0 2px #ffffff;
--vd-shadow-soft: 0 0 0 1px #ffffff;
--vd-radius-xs: 0px;
--vd-radius-sm: 2px;
--vd-radius-md: 4px;
--vd-sidebar-width: 168px;
--vd-topbar-height: 64px;
--vd-control-height: 44px;
--vd-content-gap: 18px;
--vd-motion-fast: 0ms;
--vd-motion-normal: 0ms;
}
@media (prefers-reduced-motion: reduce) {
:root { --vd-motion-fast: 0ms; --vd-motion-normal: 0ms; }
}
-40
View File
@@ -1,40 +0,0 @@
{
"schemaVersion": 1,
"contractVersion": "1.0.0",
"themes": ["light", "dark", "contrast"],
"tokens": {
"color.canvas": { "css": "--vd-canvas", "type": "color", "customizable": true, "light": "#f7f5f1", "dark": "#171714", "contrast": "#000000" },
"color.surface": { "css": "--vd-surface", "type": "color", "customizable": true, "light": "#fffdf9", "dark": "#1d1d19", "contrast": "#090909" },
"color.surfaceRaised": { "css": "--vd-surface-raised", "type": "color", "customizable": true, "light": "#ffffff", "dark": "#22221e", "contrast": "#111111" },
"color.surfaceMuted": { "css": "--vd-surface-muted", "type": "color", "customizable": true, "light": "#f1efea", "dark": "#292923", "contrast": "#1b1b1b" },
"color.surfaceQuiet": { "css": "--vd-surface-quiet", "type": "color", "customizable": true, "light": "#ebe8e1", "dark": "#303029", "contrast": "#242424" },
"color.line": { "css": "--vd-line", "type": "color", "customizable": true, "light": "#e2ded6", "dark": "#37372f", "contrast": "#ffffff" },
"color.lineStrong": { "css": "--vd-line-strong", "type": "color", "customizable": true, "light": "#cbc5ba", "dark": "#4a4940", "contrast": "#ffffff" },
"color.ink": { "css": "--vd-ink", "type": "color", "customizable": true, "light": "#171714", "dark": "#f2efe8", "contrast": "#ffffff" },
"color.inkSecondary": { "css": "--vd-ink-secondary", "type": "color", "customizable": true, "light": "#5f5b54", "dark": "#c3beb4", "contrast": "#ffffff" },
"color.inkMuted": { "css": "--vd-ink-muted", "type": "color", "customizable": true, "light": "#8c877e", "dark": "#8f8a80", "contrast": "#f2f2f2" },
"color.brand": { "css": "--vd-brand", "type": "color", "customizable": true, "light": "#e33a16", "dark": "#ff5a32", "contrast": "#ffe600" },
"color.brandStrong": { "css": "--vd-brand-strong", "type": "color", "customizable": true, "light": "#c92f10", "dark": "#ff744f", "contrast": "#ffffff" },
"color.brandSoft": { "css": "--vd-brand-soft", "type": "color", "customizable": false, "light": "rgba(227, 58, 22, 0.08)", "dark": "rgba(255, 90, 50, 0.12)", "contrast": "rgba(255, 230, 0, 0.20)" },
"color.success": { "css": "--vd-success", "type": "color", "customizable": true, "light": "#2f7d50", "dark": "#75bd8c", "contrast": "#66ff99" },
"color.successSoft": { "css": "--vd-success-soft", "type": "color", "customizable": false, "light": "#e7f2ea", "dark": "#203629", "contrast": "#002d12" },
"color.warning": { "css": "--vd-warning", "type": "color", "customizable": true, "light": "#b96a23", "dark": "#e2a35f", "contrast": "#ffd000" },
"color.warningSoft": { "css": "--vd-warning-soft", "type": "color", "customizable": false, "light": "#fbefe3", "dark": "#3b2e20", "contrast": "#332a00" },
"color.danger": { "css": "--vd-danger", "type": "color", "customizable": true, "light": "#c44234", "dark": "#ed7568", "contrast": "#ff6b6b" },
"color.dangerSoft": { "css": "--vd-danger-soft", "type": "color", "customizable": false, "light": "#f9e8e5", "dark": "#3b2523", "contrast": "#3a0000" },
"color.info": { "css": "--vd-info", "type": "color", "customizable": true, "light": "#3975ba", "dark": "#78a9df", "contrast": "#72c7ff" },
"color.infoSoft": { "css": "--vd-info-soft", "type": "color", "customizable": false, "light": "#e8f0f9", "dark": "#202f40", "contrast": "#00253d" },
"color.focus": { "css": "--vd-focus", "type": "color", "customizable": false, "light": "rgba(227, 58, 22, 0.18)", "dark": "rgba(255, 90, 50, 0.26)", "contrast": "rgba(255, 230, 0, 0.55)" },
"shadow.float": { "css": "--vd-shadow-float", "type": "shadow", "customizable": false, "light": "0 18px 48px rgba(35, 30, 24, 0.12)", "dark": "0 20px 56px rgba(0, 0, 0, 0.38)", "contrast": "0 0 0 2px #ffffff" },
"shadow.soft": { "css": "--vd-shadow-soft", "type": "shadow", "customizable": false, "light": "0 8px 24px rgba(35, 30, 24, 0.06)", "dark": "0 8px 26px rgba(0, 0, 0, 0.20)", "contrast": "0 0 0 1px #ffffff" },
"radius.xs": { "css": "--vd-radius-xs", "type": "dimension", "customizable": true, "min": 0, "max": 16, "light": "5px", "dark": "5px", "contrast": "0px" },
"radius.sm": { "css": "--vd-radius-sm", "type": "dimension", "customizable": true, "min": 0, "max": 20, "light": "8px", "dark": "8px", "contrast": "2px" },
"radius.md": { "css": "--vd-radius-md", "type": "dimension", "customizable": true, "min": 0, "max": 28, "light": "12px", "dark": "12px", "contrast": "4px" },
"layout.sidebarWidth": { "css": "--vd-sidebar-width", "type": "dimension", "customizable": false, "light": "148px", "dark": "148px", "contrast": "168px" },
"layout.topbarHeight": { "css": "--vd-topbar-height", "type": "dimension", "customizable": false, "light": "60px", "dark": "60px", "contrast": "64px" },
"density.controlHeight": { "css": "--vd-control-height", "type": "dimension", "customizable": true, "min": 32, "max": 52, "light": "40px", "dark": "40px", "contrast": "44px" },
"density.contentGap": { "css": "--vd-content-gap", "type": "dimension", "customizable": true, "min": 8, "max": 32, "light": "16px", "dark": "16px", "contrast": "18px" },
"motion.fast": { "css": "--vd-motion-fast", "type": "duration", "customizable": false, "light": "120ms", "dark": "120ms", "contrast": "0ms" },
"motion.normal": { "css": "--vd-motion-normal", "type": "duration", "customizable": false, "light": "180ms", "dark": "180ms", "contrast": "0ms" }
}
}
+4 -5
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="vendoo-build" content="1.35.0">
<meta name="vendoo-build" content="1.34.3">
<title>Vendoo</title>
<link rel="icon" href="/brand/favicons/favicon.ico" sizes="any">
<link rel="icon" type="image/png" sizes="16x16" href="/brand/favicons/favicon-16x16.png">
@@ -13,7 +13,7 @@
<link rel="manifest" href="/brand/favicons/site.webmanifest">
<meta name="msapplication-config" content="/brand/favicons/browserconfig.xml">
<meta name="theme-color" content="#FAF6F2">
<link rel="stylesheet" href="style.css?v=1.35.0">
<link rel="stylesheet" href="style.css?v=1.34.3">
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">
</head>
<body>
@@ -2262,9 +2262,8 @@ PUBLIC_BASE_URL=https://vendoo.example.de</pre>
<div id="toast" class="toast"></div>
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js"></script>
<script src="design-system/theme-runtime.js?v=1.35.0"></script>
<script src="vendoo-icons.js?v=1.35.0"></script>
<script src="app.js?v=1.35.0"></script>
<script src="vendoo-icons.js?v=1.34.3"></script>
<script src="app.js?v=1.34.3"></script>
-5
View File
@@ -1,5 +0,0 @@
# Frontend-Module
Neue Frontend-Module werden künftig als ES-Module unter diesem Verzeichnis angelegt und über `FrontendModuleRegistry` mit einem klaren `mount`-/`unmount`-Lebenszyklus betrieben.
Die Bestandsoberfläche aus `public/app.js` bleibt in 1.35.0 unverändert aktiv. Sie wird in späteren Releases tabweise migriert. Direkte neue globale Event-Listener und ungeprüfte `innerHTML`-Zuweisungen sind für migrierte Module nicht zulässig.
+109 -34
View File
@@ -1,36 +1,29 @@
@import url('./design-system/tokens.css?v=1.35.0');
:root {
color-scheme: light;
/* Kompatibilitätsbrücke: bestehende Oberfläche bleibt unverändert, neue Module verwenden --vd-* direkt. */
--bg: var(--vd-canvas);
--bg-card: var(--vd-surface-raised);
--bg-input: var(--vd-surface-raised);
--bg-hover: var(--vd-surface-muted);
--bg-page: var(--vd-surface-quiet);
--border: var(--vd-line);
--border-focus: var(--vd-brand);
--text: var(--vd-ink);
--text-muted: var(--vd-ink-muted);
--text-secondary: var(--vd-ink-secondary);
--accent: var(--vd-brand);
--accent-hover: var(--vd-brand-strong);
--accent-light: var(--vd-brand-soft);
--orange: var(--vd-warning);
--orange-hover: var(--vd-warning);
--orange-light: var(--vd-warning-soft);
--success: var(--vd-success);
--danger: var(--vd-danger);
--warning: var(--vd-warning);
--radius: var(--vd-radius-md);
--radius-sm: var(--vd-radius-sm);
--shadow-sm: none;
--shadow: var(--vd-shadow-soft);
--bg: #f6f5f0;
--bg-card: #ffffff;
--bg-input: #ffffff;
--bg-hover: #eeedea;
--bg-page: #eae8e1;
--border: #e0ddd4;
--border-focus: #2c6e49;
--text: #2c2c2a;
--text-muted: #888880;
--text-secondary: #6b6b64;
--accent: #2c6e49;
--accent-hover: #245a3b;
--accent-light: rgba(44,110,73,0.08);
--orange: #c77b2a;
--orange-hover: #b06a1e;
--orange-light: rgba(199,123,42,0.1);
--success: #2c6e49;
--danger: #c0392b;
--warning: #c77b2a;
--radius: 12px;
--radius-sm: 8px;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
--shadow: 0 2px 8px rgba(0,0,0,0.08);
}
[data-theme="dark"] { color-scheme: dark; }
[data-theme="contrast"] { color-scheme: dark; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
@@ -1270,11 +1263,93 @@ main { max-width: 960px; margin: 0 auto; padding: 24px 28px; width: 100%; }
.topbar-btn span { display: none; }
}
/* ==========================================================================
Vendoo Design System Contract 1.0
Tokenquelle: public/design-system/tokens/source.json
Generiert: public/design-system/tokens.css
/* ==========================================================================
Vendoo UI Foundation 2026
Freigegebene Designrichtung: Editorial Commerce + Studio Flow
========================================================================== */
:root {
--vd-canvas: #f7f5f1;
--vd-surface: #fffdf9;
--vd-surface-raised: #ffffff;
--vd-surface-muted: #f1efea;
--vd-surface-quiet: #ebe8e1;
--vd-line: #e2ded6;
--vd-line-strong: #cbc5ba;
--vd-ink: #171714;
--vd-ink-secondary: #5f5b54;
--vd-ink-muted: #8c877e;
--vd-brand: #e33a16;
--vd-brand-strong: #c92f10;
--vd-brand-soft: rgba(227, 58, 22, 0.08);
--vd-success: #2f7d50;
--vd-success-soft: #e7f2ea;
--vd-warning: #b96a23;
--vd-warning-soft: #fbefe3;
--vd-danger: #c44234;
--vd-danger-soft: #f9e8e5;
--vd-info: #3975ba;
--vd-info-soft: #e8f0f9;
--vd-focus: rgba(227, 58, 22, 0.18);
--vd-shadow-float: 0 18px 48px rgba(35, 30, 24, 0.12);
--vd-shadow-soft: 0 8px 24px rgba(35, 30, 24, 0.06);
--vd-radius-xs: 5px;
--vd-radius-sm: 8px;
--vd-radius-md: 12px;
--vd-sidebar-width: 148px;
--vd-topbar-height: 60px;
/* Legacy token bridge */
--bg: var(--vd-canvas);
--bg-card: var(--vd-surface-raised);
--bg-input: var(--vd-surface-raised);
--bg-hover: var(--vd-surface-muted);
--bg-page: var(--vd-surface-quiet);
--border: var(--vd-line);
--border-focus: var(--vd-brand);
--text: var(--vd-ink);
--text-muted: var(--vd-ink-muted);
--text-secondary: var(--vd-ink-secondary);
--accent: var(--vd-brand);
--accent-hover: var(--vd-brand-strong);
--accent-light: var(--vd-brand-soft);
--orange: var(--vd-warning);
--orange-hover: var(--vd-warning);
--orange-light: var(--vd-warning-soft);
--success: var(--vd-success);
--danger: var(--vd-danger);
--warning: var(--vd-warning);
--radius: var(--vd-radius-md);
--radius-sm: var(--vd-radius-sm);
--shadow-sm: none;
--shadow: var(--vd-shadow-soft);
}
[data-theme="dark"] {
--vd-canvas: #171714;
--vd-surface: #1d1d19;
--vd-surface-raised: #22221e;
--vd-surface-muted: #292923;
--vd-surface-quiet: #303029;
--vd-line: #37372f;
--vd-line-strong: #4a4940;
--vd-ink: #f2efe8;
--vd-ink-secondary: #c3beb4;
--vd-ink-muted: #8f8a80;
--vd-brand: #ff5a32;
--vd-brand-strong: #ff744f;
--vd-brand-soft: rgba(255, 90, 50, 0.12);
--vd-success: #75bd8c;
--vd-success-soft: #203629;
--vd-warning: #e2a35f;
--vd-warning-soft: #3b2e20;
--vd-danger: #ed7568;
--vd-danger-soft: #3b2523;
--vd-info: #78a9df;
--vd-info-soft: #202f40;
--vd-focus: rgba(255, 90, 50, 0.26);
--vd-shadow-float: 0 20px 56px rgba(0, 0, 0, 0.38);
--vd-shadow-soft: 0 8px 26px rgba(0, 0, 0, 0.2);
}
html { background: var(--vd-canvas); }
body {
+5 -5
View File
@@ -1,9 +1,9 @@
# Vendoo 1.35.0 kontrollierter GitHub-Erstimport
# Vendoo 1.34.3 kontrollierter GitHub-Erstimport
$ErrorActionPreference = 'Stop'
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
$RepoUrl = 'https://github.com/Masterluke77/vendoo.git'
$PullRequestUrl = 'https://github.com/Masterluke77/vendoo/pull/7'
$Branch = 'release/1.35.0-platform-kernel'
$PullRequestUrl = 'https://github.com/Masterluke77/vendoo/pull/6'
$Branch = 'release/1.34.3-initial-import'
$WorkRoot = Join-Path $env:TEMP ('vendoo-github-deploy-' + [Guid]::NewGuid().ToString('N'))
$CloneDir = Join-Path $WorkRoot 'repo'
@@ -123,7 +123,7 @@ function Run-SafetyGate {
try {
Write-Host ''
Write-Host 'Vendoo 1.35.0 GitHub Deploy Assistent' -ForegroundColor Green
Write-Host 'Vendoo 1.34.3 GitHub Deploy Assistent' -ForegroundColor Green
Write-Host 'Repository: Masterluke77/vendoo (privat)' -ForegroundColor DarkGray
Write-Host ''
@@ -165,7 +165,7 @@ try {
$confirm = Read-Host 'Geprüften Vendoo-Stand als Release-Branch zu GitHub übertragen? Zum Fortfahren PUSH eingeben'
if ($confirm -cne 'PUSH') { Warn 'Deployment abgebrochen. Auf GitHub wurde nichts verändert.'; exit 0 }
Invoke-Git @('commit','-m','fix: import Vendoo 1.35.0 update archive exclusion hotfix')
Invoke-Git @('commit','-m','fix: import Vendoo 1.34.3 update archive exclusion hotfix')
Invoke-Git @('push','--set-upstream','origin',$Branch)
Ok "Branch $Branch wurde erfolgreich übertragen."
+3 -3
View File
@@ -2,8 +2,8 @@
set -eu
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
REPO_URL="https://github.com/Masterluke77/vendoo.git"
BRANCH="release/1.35.0-platform-kernel"
PR_URL="https://github.com/Masterluke77/vendoo/pull/7"
BRANCH="release/1.34.3-initial-import"
PR_URL="https://github.com/Masterluke77/vendoo/pull/6"
WORK_DIR=$(mktemp -d)
cleanup() { rm -rf "$WORK_DIR"; }
trap cleanup EXIT INT TERM
@@ -45,6 +45,6 @@ git status --short
printf 'Zum Pushen exakt PUSH eingeben: '
read -r answer
[ "$answer" = "PUSH" ] || { echo "Abgebrochen."; exit 0; }
git commit -m "feat: import Vendoo 1.35.0 modular platform kernel"
git commit -m "fix: import Vendoo 1.34.3 update archive exclusion hotfix"
git push --set-upstream origin "$BRANCH"
echo "Branch übertragen. Draft Pull Request #5 öffnen: $PR_URL"
+3 -55
View File
@@ -10,8 +10,6 @@ import { networkInterfaces } from 'os';
import { spawn } from 'child_process';
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync, unlinkSync, readdirSync, copyFileSync } from 'fs';
import { CONFIG_PATH, readRuntimeConfig, setConfigValue, writeRuntimeConfig, updateRuntimeConfig } from './lib/config-store.mjs';
import { createPlatformKernel } from './app/kernel/platform-kernel.mjs';
import { getThemeContract } from './app/core/themes/theme-contract.mjs';
import {
createUser, getUser, getUserByEmail, getUsers, updateUser, deleteUser,
createSession, validateSession, destroySession, destroySessionById, destroyUserSessions,
@@ -98,9 +96,6 @@ const PORT = Number(process.env.PORT || 8124);
ensureRuntimeDirectories();
let shuttingDown = false;
const platformKernel = createPlatformKernel({ version: APP_VERSION, hasPermission });
await platformKernel.start();
startAiModelJobLoop();
startFluxPromptJobLoop();
startImageBatchJobLoop();
@@ -360,7 +355,7 @@ app.get('/healthz', (_req, res) => {
res.json({ ok: true, version: APP_VERSION, uptime_seconds: Math.floor(process.uptime()), time: new Date().toISOString() });
});
app.get('/readyz', async (_req, res) => {
app.get('/readyz', (_req, res) => {
res.setHeader('Cache-Control', 'no-store');
const checks = [];
try {
@@ -370,12 +365,6 @@ app.get('/readyz', async (_req, res) => {
checks.push({ name: 'database', ok: false, path: DB_PATH, error: error.message });
}
for (const item of verifyRuntimeWritable()) checks.push({ name: 'filesystem', ok: item.writable, ...item });
try {
const kernel = await platformKernel.health();
checks.push({ name: 'platform-kernel', ok: kernel.ok, lifecycle: kernel.lifecycle.state, modules: kernel.modules.length });
} catch (error) {
checks.push({ name: 'platform-kernel', ok: false, error: error.message });
}
const ok = !shuttingDown && checks.every(item => item.ok !== false);
res.status(ok ? 200 : 503).json({ ok, shutting_down: shuttingDown, version: APP_VERSION, checks, paths: runtimePathSummary(), time: new Date().toISOString() });
});
@@ -673,46 +662,6 @@ app.use('/auth/etsy', requireAuth, requirePermission('settings.manage'));
// CSRF for state-changing API calls
app.use('/api', csrfProtect);
// Platform kernel contracts (new routes must be deny-by-default and explicitly owned)
platformKernel.routes.register(app, {
id: 'platform.status.read',
method: 'GET',
path: '/api/security/platform',
owner: 'vendoo.system',
policy: 'platform.security-admin',
audit: 'platform.status.read',
rateLimit: 'administration',
csrf: false,
stability: 'internal',
handler: async (_req, res) => res.json(platformKernel.snapshot()),
});
platformKernel.routes.register(app, {
id: 'platform.modules.read',
method: 'GET',
path: '/api/security/platform/modules',
owner: 'vendoo.system',
policy: 'platform.security-admin',
audit: 'platform.modules.read',
rateLimit: 'administration',
csrf: false,
stability: 'internal',
handler: async (_req, res) => res.json({ modules: platformKernel.modules.snapshot() }),
});
platformKernel.routes.register(app, {
id: 'themes.contract.read',
method: 'GET',
path: '/api/security/platform/theme-contract',
owner: 'vendoo.themes',
policy: 'platform.security-admin',
audit: 'themes.contract.read',
rateLimit: 'administration',
csrf: false,
stability: 'internal',
handler: async (_req, res) => res.json(getThemeContract()),
});
// Current user info
app.get('/api/me', (req, res) => {
if (isSetupMode()) return res.json({ setupMode: true, role: 'admin' });
@@ -3424,11 +3373,10 @@ function gracefulShutdown(signal) {
console.error('Shutdown-Zeitlimit erreicht. Prozess wird beendet.');
process.exit(1);
}, 10_000);
server.close(async () => {
try { await platformKernel.stop(); } catch (error) { console.error('Platform-Kernel-Shutdown fehlgeschlagen:', error.message || error); }
server.close(() => {
clearTimeout(forceTimer);
try { db.pragma('wal_checkpoint(TRUNCATE)'); } catch {}
try { db.close(); } catch {}
clearTimeout(forceTimer);
console.log('Vendoo wurde sauber beendet.');
process.exit(0);
});
+2 -2
View File
@@ -1,4 +1,4 @@
# Vendoo 1.35.0 grafischer Zero-Edit-Installationsassistent
# Vendoo 1.34.3 grafischer Zero-Edit-Installationsassistent
$ErrorActionPreference = 'Stop'
$VendooDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$SetupScript = Join-Path $VendooDir 'setup.ps1'
@@ -90,7 +90,7 @@ function Set-Configuration([string]$Target, [string]$Access, [int]$Port, [string
}
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Vendoo 1.35.0 Installation'
$form.Text = 'Vendoo 1.34.3 Installation'
$form.StartPosition = 'CenterScreen'
$form.Size = New-Object System.Drawing.Size(720, 620)
$form.MinimumSize = New-Object System.Drawing.Size(720, 620)
+2 -2
View File
@@ -1,6 +1,6 @@
$ErrorActionPreference = 'Stop'
$VendooDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$Version = '1.35.0'
$Version = '1.34.3'
$Port = 8124
$InstallModeFile = Join-Path $VendooDir '.vendoo-install-mode'
@@ -831,7 +831,7 @@ function Start-VendooLocal {
Write-Host ''
Write-Step '...' 'Starte Vendoo-Server...'
Push-Location $VendooDir
Start-Process "http://localhost:$Port/?build=1.35.0"
Start-Process "http://localhost:$Port/?build=1.34.3"
& node bootstrap.mjs
Pop-Location
}
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
set -eu
APP_VERSION="1.35.0"
APP_VERSION="1.34.3"
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
MODE_FILE="$ROOT_DIR/.vendoo-install-mode"
ENV_FILE="$ROOT_DIR/.env"
-54
View File
@@ -1,54 +0,0 @@
import { readFileSync, writeFileSync } from 'fs';
import { dirname, join, resolve } from 'path';
import { fileURLToPath } from 'url';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const sourcePath = join(root, 'public', 'design-system', 'tokens', 'source.json');
const cssPath = join(root, 'public', 'design-system', 'tokens.css');
const contractPath = join(root, 'public', 'design-system', 'theme-contract.json');
const source = JSON.parse(readFileSync(sourcePath, 'utf8'));
const failures = [];
if (source.schemaVersion !== 1) failures.push('schemaVersion muss 1 sein');
if (!/^\d+\.\d+\.\d+$/.test(String(source.contractVersion || ''))) failures.push('contractVersion ist ungültig');
if (!Array.isArray(source.themes) || !source.themes.includes('light') || !source.themes.includes('dark')) failures.push('light und dark müssen definiert sein');
const cssVars = new Set();
for (const [name, token] of Object.entries(source.tokens || {})) {
if (!/^[a-z][A-Za-z0-9]*(?:\.[a-z][A-Za-z0-9]*)+$/.test(name)) failures.push(`Ungültiger Tokenname: ${name}`);
if (!/^--vd-[a-z0-9-]+$/.test(String(token.css || ''))) failures.push(`Ungültige CSS-Variable: ${name}`);
if (cssVars.has(token.css)) failures.push(`Doppelte CSS-Variable: ${token.css}`);
cssVars.add(token.css);
for (const theme of source.themes) if (typeof token[theme] !== 'string' || !token[theme].trim()) failures.push(`${name} fehlt in Theme ${theme}`);
}
if (failures.length) {
console.error(`Design-Token-Generierung fehlgeschlagen (${failures.length}):`);
failures.forEach(item => console.error(`- ${item}`));
process.exit(1);
}
function block(selector, theme) {
const lines = Object.values(source.tokens).map(token => ` ${token.css}: ${token[theme]};`);
return `${selector} {\n${lines.join('\n')}\n}`;
}
const css = `/* Automatisch generiert aus design-system/tokens/source.json. Nicht direkt bearbeiten. */\n` +
`/* Contract ${source.contractVersion} */\n` +
`${block(':root', 'light')}\n\n${block('[data-theme="dark"]', 'dark')}\n\n${block('[data-theme="contrast"]', 'contrast')}\n\n` +
`@media (prefers-reduced-motion: reduce) {\n :root { --vd-motion-fast: 0ms; --vd-motion-normal: 0ms; }\n}\n`;
writeFileSync(cssPath, css, 'utf8');
const contract = {
schemaVersion: source.schemaVersion,
contractVersion: source.contractVersion,
themes: source.themes,
tokens: Object.fromEntries(Object.entries(source.tokens).map(([name, token]) => [name, {
css: token.css,
type: token.type,
customizable: Boolean(token.customizable),
...(token.min !== undefined ? { min: token.min } : {}),
...(token.max !== undefined ? { max: token.max } : {}),
}])),
};
writeFileSync(contractPath, `${JSON.stringify(contract, null, 2)}\n`, 'utf8');
console.log(`Design Tokens erzeugt: ${Object.keys(source.tokens).length} Tokens, ${source.themes.length} Themes.`);
-129
View File
@@ -1,129 +0,0 @@
import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
import { dirname, join, relative, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = dirname(dirname(fileURLToPath(import.meta.url)));
const read = rel => readFileSync(join(root, rel), 'utf8');
const failures = [];
const expect = (text, marker, label = marker) => { if (!text.includes(marker)) failures.push(`Fehlt: ${label}`); };
const forbid = (text, marker, label = marker) => { if (text.includes(marker)) failures.push(`Darf nicht enthalten sein: ${label}`); };
const pkg = JSON.parse(read('package.json'));
const lock = JSON.parse(read('package-lock.json'));
const manifest = JSON.parse(read('update-manifest.json'));
const html = read('public/index.html');
const app = read('public/app.js');
const bootstrap = read('bootstrap.mjs');
const configStore = read('lib/config-store.mjs');
const runtimePaths = read('lib/runtime-paths.mjs');
const setupPs = read('setup.ps1');
const setupGui = read('setup-gui.ps1');
const setupBat = read('setup.bat');
const setupSh = read('setup.sh');
const compose = read('compose.yaml');
const composeVps = read('compose.vps.yaml');
const caddy = read('deploy/Caddyfile');
const env = read('.env.example');
const gitignore = read('.gitignore');
const ci = read('.github/workflows/ci.yml');
const release = read('.github/workflows/release.yml');
const gitDeploy = read('scripts/github-deploy.ps1');
const gitSafety = read('tools/verify-git-safety.mjs');
const gitSafetyRegression = read('tools/verify-git-safety-regression.mjs');
const releaseBuilder = read('tools/build-release.mjs');
const installerShellGate = read('tools/verify-installer-shell.sh');
const gitStaging = read('tools/prepare-git-staging.mjs');
const gitStagingRegression = read('tools/verify-git-staging.mjs');
const architectureGate = read('tools/verify-platform-architecture-1.35.0.mjs');
const tokenSource = read('public/design-system/tokens/source.json');
const tokenCss = read('public/design-system/tokens.css');
const moduleSchema = read('app/contracts/module.schema.json');
const boundaries = read('app/architecture-boundaries.json');
for (const [label, value] of [
['package.json', pkg.version], ['package-lock.json', lock.version],
['package-lock root', lock.packages?.['']?.version], ['update-manifest', manifest.version],
]) if (value !== '1.35.0') failures.push(`${label}-Version ist ${value}`);
for (const marker of ['content="1.35.0"', 'style.css?v=1.35.0', 'app.js?v=1.35.0']) expect(html, marker, `UI-Build ${marker}`);
if (!app.startsWith("const VENDOO_UI_BUILD = '1.35.0';")) failures.push('UI-Build ist nicht 1.35.0');
for (const marker of ['VENDOO_CONFIG_PATH', "await import('./server.mjs')", 'override: true']) expect(bootstrap, marker, `Bootstrap ${marker}`);
for (const marker of ['CONFIG_PATH', 'writeRuntimeConfig', 'updateRuntimeConfig', 'mode: 0o600']) expect(configStore, marker, `Konfigurationsspeicher ${marker}`);
for (const marker of ['VENDOO_CONFIG_PATH', 'runtime_config: CONFIG_PATH']) expect(runtimePaths, marker, `Runtime-Pfade ${marker}`);
for (const marker of ['readRuntimeConfig()', 'writeRuntimeConfig(envContent)', 'updateRuntimeConfig({ LOCAL_IMAGE_TOKEN: token })', 'runtime_config_path: CONFIG_PATH']) expect(read('server.mjs'), marker, `Server-Konfiguration ${marker}`);
forbid(read('server.mjs'), "join(__dirname, '.env')", 'direktes Schreiben in schreibgeschütztes App-.env');
for (const marker of ["$Version = '1.35.0'", '?build=1.35.0', "'install-local'", "'install-docker'", 'Configure-VendooConsole', 'Initialize-VendooEnvironment']) expect(setupPs, marker, `Windows-Setup ${marker}`);
for (const marker of [
'Vendoo 1.35.0 Installation', 'Lokal auf diesem Windows-PC', 'Docker Desktop auf diesem Windows-PC',
'Set-Configuration', 'VENDOO_SETUP_TOKEN', 'Clipboard', 'AI- und Marktplatz-Schlüssel werden später',
]) expect(setupGui, marker, `Windows-GUI ${marker}`);
forbid(setupGui, 'PlaceholderText', 'nicht mit Windows PowerShell 5.1 kompatible PlaceholderText-Eigenschaft');
for (const marker of ['setup-gui.ps1', '-STA', 'Parser]::ParseFile']) expect(setupBat, marker, `setup.bat ${marker}`);
for (const marker of [
'Zero-Edit Deployment Assistant', 'configure_wizard', 'VPS / öffentlicher Server mit automatischem HTTPS',
'VENDOO_DOMAIN', 'compose.vps.yaml', 'Caddy übernimmt HTTPS automatisch', 'keine Datei musste manuell bearbeitet werden',
'Aktualisieren (mit Sicherheitsbackup)', 'docker-backup.sh',
]) expect(setupSh, marker, `Linux-Assistent ${marker}`);
for (const marker of ['VENDOO_CONFIG_PATH: /app/data/config/vendoo.env', 'vendoo_config:/app/data/config']) expect(compose, marker, `Basis-Compose-Konfiguration ${marker}`);
for (const marker of ['caddy:2-alpine', '80:80', '443:443', './deploy/Caddyfile', 'condition: service_healthy', 'read_only: true', 'vendoo_config:/app/data/config']) expect(composeVps, marker, `VPS-Compose ${marker}`);
for (const marker of ['{$VENDOO_DOMAIN}', 'reverse_proxy vendoo:8124', 'Strict-Transport-Security']) expect(caddy, marker, `Caddy ${marker}`);
for (const marker of ['VENDOO_DOMAIN=', 'VENDOO_SETUP_TOKEN=', 'VENDOO_COOKIE_SECURE=', 'LOCAL_IMAGE_ENABLED=false']) expect(env, marker, `.env.example ${marker}`);
for (const marker of ['name: Vendoo CI', 'npm ci', 'npm run verify:git', 'npm run verify:git-regression', 'npm run verify:git-staging', 'npm run verify:deployment', 'npm run verify:config', 'npm run verify:installer-shell', 'docker build --pull', 'compose.vps.yaml']) expect(ci, marker, `CI ${marker}`);
for (const marker of ['name: Vendoo Release', 'Release-Tag gegen Paketversion prüfen', 'npm run verify:git-staging', 'npm run verify:config', 'npm run verify:installer-shell', 'release-output', 'gh release upload', 'ghcr.io', 'docker/build-push-action@v6', 'actions/attest@v4', 'attestations: write', 'id-token: write', 'steps.push.outputs.digest', 'linux/amd64,linux/arm64']) expect(release, marker, `Release-Workflow ${marker}`);
for (const marker of ['release/1.35.0-platform-kernel', 'verify-git-safety.mjs', 'prepare-git-staging.mjs', 'RedirectStandardOutput', 'github-deploy-safety-report.txt', 'Zum Fortfahren PUSH eingeben', 'vendoo/pull/7']) expect(gitDeploy, marker, `GitHub-Deploy ${marker}`);
for (const marker of ['fileURLToPath', 'kompletten Arbeitsbaum', 'forbiddenPathPatterns', 'secretPatterns', 'Git-Sicherheitsgate erfolgreich']) expect(gitSafety, marker, `Git-Sicherheitsgate ${marker}`);
for (const marker of ['unversioniert', '.env.local', 'untracked-secret.txt', 'Git-Sicherheitsgate-Regressionstest erfolgreich']) expect(gitSafetyRegression, marker, `Git-Sicherheitsgate-Regression ${marker}`);
for (const marker of ['prepareGitStaging', 'shouldSkipStagingEntry', 'allowedEnvTemplates', 'skippedTopLevelDirs', 'operations', 'updates', 'Ziel ist kein geklontes Git-Repository']) expect(gitStaging, marker, `Git-Staging ${marker}`);
for (const marker of ['.env.local', 'operations/staging/job.json', 'updates/backups/slice22_1_20260707-194953/setup.ps1', '.git/marker', 'Git-Staging-Regressionstest erfolgreich']) expect(gitStagingRegression, marker, `Git-Staging-Regression ${marker}`);
for (const marker of ['release-output', 'FILE_CHECKSUMS_SHA256.txt', 'createHash', 'shouldSkipStagingEntry']) expect(releaseBuilder, marker, `Release-Builder ${marker}`);
for (const marker of ['run_mode docker', 'run_mode nas', 'run_mode vps', 'VENDOO_SETUP_TOKEN', 'stat -c']) expect(installerShellGate, marker, `Shell-Installer-Gate ${marker}`);
for (const marker of ['.env', 'runtime/', 'updates/', 'local-ai/comfyui/', '.vendoo-install-mode', 'release-output/']) expect(gitignore, marker, `.gitignore ${marker}`);
for (const marker of ['createPlatformKernel', "id: 'platform.status.read'", "policy: 'platform.security-admin'", 'await platformKernel.stop()']) expect(read('server.mjs'), marker, `Platform-Kernel ${marker}`);
for (const marker of ['Platform-Architecture-Gate 1.35.0 erfolgreich', 'MODULE_DEPENDENCY_CYCLE', 'ROUTE_POLICY_REQUIRED', 'THEME_VALUE_INVALID']) expect(architectureGate, marker, `Architektur-Gate ${marker}`);
for (const marker of ['contractVersion', 'color.brand', 'density.controlHeight', 'motion.normal']) expect(tokenSource, marker, `Tokenquelle ${marker}`);
for (const marker of [':root {', '[data-theme="dark"]', '[data-theme="contrast"]', '--vd-brand:']) expect(tokenCss, marker, `Token-CSS ${marker}`);
for (const marker of ['Vendoo Module Manifest', 'legacy-bridge', 'extension-host']) expect(moduleSchema, marker, `Modulschema ${marker}`);
for (const marker of ['modular-monolith', 'denyImplicitRoutes', 'thirdPartyExtensionsInMainProcess', 'strangler']) expect(boundaries, marker, `Architekturgrenzen ${marker}`);
for (const required of ['app/kernel/platform-kernel.mjs', 'app/kernel/module-registry.mjs', 'app/kernel/route-registry.mjs', 'app/kernel/policy-engine.mjs', 'app/core/themes/theme-contract.mjs', 'public/design-system/theme-runtime.js', 'public/design-system/theme-contract.json', 'public/core/frontend-module-registry.js', 'public/core/safe-dom.js', 'public/core/api-client.js', 'docs/architecture/MODULE_SYSTEM_1_35_0.md', 'docs/architecture/DESIGN_SYSTEM_1_35_0.md', 'docs/architecture/SECURITY_ARCHITECTURE_1_35_0.md']) if (!existsSync(join(root, required))) failures.push(`Architektur-Pflichtdatei fehlt: ${required}`);
for (const marker of ['read_only: true', 'no-new-privileges:true', 'cap_drop:', '/readyz', 'host.docker.internal:host-gateway']) expect(compose, marker, `Basis-Compose ${marker}`);
for (const required of [
'.github/workflows/ci.yml', '.github/workflows/release.yml', '.github/dependabot.yml', '.github/CODEOWNERS',
'GitHub-Deploy-Assistent.bat', 'scripts/github-deploy.ps1', 'scripts/github-deploy.sh',
'setup-gui.ps1', 'bootstrap.mjs', 'lib/config-store.mjs', 'compose.vps.yaml', 'deploy/Caddyfile', 'tools/verify-git-safety.mjs', 'tools/verify-git-safety-regression.mjs', 'tools/prepare-git-staging.mjs', 'tools/verify-git-staging.mjs', 'tools/verify-installer-shell.sh', 'tools/build-release.mjs',
]) if (!existsSync(join(root, required))) failures.push(`Pflichtdatei fehlt: ${required}`);
function walk(dir, out = []) {
for (const name of readdirSync(dir)) {
if (name === 'node_modules' || name === 'release-output' || name === '.git') continue;
const abs = join(dir, name);
const rel = relative(root, abs).split(sep).join('/');
const stat = lstatSync(abs);
if (stat.isDirectory()) walk(abs, out); else out.push(rel);
}
return out;
}
const files = walk(root);
const forbiddenRuntime = files.filter(rel =>
rel === '.env' || rel === '.vendoo-install-mode' ||
/(^|\/)(runtime|node_modules|uploads|backups|logs|updates)(\/|$)/i.test(rel) ||
/^db\/.*\.(db|sqlite|sqlite3)(-wal|-shm)?$/i.test(rel) ||
/^local-ai\/(comfyui|downloads|extract-temp)\//i.test(rel)
);
if (forbiddenRuntime.length) failures.push(`Verbotene Laufzeitdaten im Paket: ${forbiddenRuntime.slice(0, 10).join(', ')}`);
forbid(read('README.md'), '.env.example` nach `.env` kopieren', 'alte manuelle .env-Anweisung im README');
if (failures.length) {
console.error(`Deployment-Gate 1.35.0 fehlgeschlagen (${failures.length}):`);
failures.forEach(item => console.error(`- ${item}`));
process.exit(1);
}
console.log('Deployment-Gate 1.35.0 erfolgreich: Plattformkernel, Modul-/Policy-/Routenverträge, Design Tokens, Legacy-Bridges, Update-Archiv-Ausschluss, Zero-Edit-Installer, CI und Release-Workflow geprüft.');
@@ -1,118 +0,0 @@
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.`);
+3 -3
View File
@@ -1,10 +1,10 @@
{
"schema": "vendoo-update-manifest-v1",
"version": "1.35.0",
"name": "Vendoo Modular Platform Kernel & Design-System Foundation",
"version": "1.34.3",
"name": "Vendoo Git-Staging Update-Archiv-Ausschluss Hotfix",
"channel": "stable",
"minimum_node": "18",
"notes": "Modularer Plattformkernel, validierte Modulmanifeste, Deny-by-default-Routenverträge, Feature Flags, Lifecycle/Event Bus sowie versionierte Design Tokens mit sicherem Theme-Vertrag. Bestehendes Verhalten bleibt über Legacy-Bridges erhalten.",
"notes": "Lokale updates/backups-, Slice- und Rollback-Artefakte werden vor dem Git-Sicherheitsgate automatisch aus dem temporären Staging ausgeschlossen; aktive Root- und scripts-Dateien bleiben enthalten.",
"download_url": "",
"sha256": ""
}