docs(audit): Astra-Externaudit aufgenommen — 41 Findings verifiziert, PHASE S (4 Wellen) in Roadmap, Milestone 16, Issues #396-399

- docs/audits/astra-audit-2026-09-17.md: vollstaendiger Pruefbericht (2 P0, 29 P1, 10 P2), 10 Findings intern stichprobenartig verifiziert (alle korrekt)
- PLATFORM_ROADMAP.md: PHASE S (S1 Sicherheitsgrenzen, S2 Ausfuehrung verbinden, S3 Fachliche Integritaet, S4 Betriebsfreigabe) mit je Finding Korrektur+Abnahme; Abnahmeszenarien quer (Kontakt->Outbox->Worker->Suchindex->KI; Mail->Freigabe->Versand)
- Phase R: 8 Astra-Kritikpunkte eingearbeitet (externe Ueberwachung, Sollzustand-Vergleich, Heartbeat statt Queue, echte Prozesse, Modelldiscovery, E2E-Szenarien, Restore-Nachweis, 95%-Formulierung als Freigabekriterien)
- PROGRESS.md: Phase S als NÄCHSTE PHASE, Wellen-Issues verlinkt
This commit is contained in:
Agent Zero
2026-09-17 22:27:19 +02:00
parent ee5545d58f
commit 8a26737680
3 changed files with 878 additions and 9 deletions
+77 -8
View File
@@ -1814,7 +1814,71 @@ Der AI Assistent ist ein paralleles System das die Kommunikation-Plattform dupli
**Reihenfolge (wie umgesetzt):** Q3 → Q1/Q2 → Q4 (Q4 fiel mit Q3 mit, da MiniAppHost dieselbe generierte Map nutzt). Jeder Schritt mit Vitest-Sicherung der betroffenen Seiten und Production-Build-Verifikation (Chunk-Existenz prüfen).
## Phase R — Betriebssicherheit & 95%-Produktionsreife (geplant, user-abgestimmt 2026-09-16 — NÄCHSTE PHASE, vor O/P)
## Externaudit Astra 2026-09-17 (41 Findings) — Sanierung PHASE S (bestätigt, NÄCHSTE PHASE, vor/neben R)
**Auditergebnis:** 2 P0 (KI führt nicht freigegebene Tools aus; Mandantenverwaltung kann globale Anmeldeidentitäten ändern), 29 P1, 10 P2. Geprüft am vollständigen Stand ee5545d (ZIP). Interne Verifikation am 2026-09-17: 10 Findings stichprobenartig am Code nachgelesen (F01, F02, F05, F08, F10, F12, F17, F24, F37, F41) — **alle 10 korrekt**. Übrige Findings: detailliert mit Zeilennummern belegt, Detail-Verifikation erfolgt jeweils bei Umsetzung. Volltext des Audits: [docs/audits/astra-audit-2026-09-17.md](docs/audits/astra-audit-2026-09-17.md); Kernpunkte je Finding in den Wellen-Issues.
**Strukturdiagnose (Astra):** Mehrere Stellen verwalten denselben Zustand (Plugin-Aktivität, Schema); Contracts garantieren zu wenig Verhalten; API- und Worker-Ausführung nicht gleichwertig; Berechtigungsprüfungen liegen zu weit vom Seiteneffekt entfernt; Statusanzeigen teils von tatsächlicher Funktion entkoppelt. — Bestätigt und deckt sich mit den realen Incidents (#389 Plugin down 4 Wochen, #380 158 Events failed).
**Sanierungswellen (Reihenfolge nach Risiko, an Astra-Empfehlung angelehnt):**
### Welle S1 — Sicherheitsgrenzen (P0 + Auth/Permission-Kette) — ZUERST
- **F01 (P0)** agent_loop._execute_tool: Tool-Ausführung ohne Allowlist- und Permission-Check — unmittelbar vor Handleraufruf prüfen: Tool in der dem LLM angebotenen Liste, required_permission gegen aktuelle User-Rechte, Verbote, Mandant, Plugin aktiv, ggf. Approval. Abnahme: nicht angebotenes Tool → Ablehnung, Handler bleibt null.
- **F02 (P0)** users.py update_user: globale User.email durch Mandanten-Admin (users:write) änderbar → globale Identitätsänderungen (email, is_system_admin global, Passwort) von Mandantenverwaltung trennen; nur Selbstservice oder echte globale Admin. Abnahme: Tenant-Admin kann globale E-Mail/Aktivstatus fremder Mandanten-Mitglieder nicht ändern.
- **F10** require_permission: Token-Scopes ersetzen User-Rechte (early-return) → effektive Rechte = Schnittmenge(User, Token-Scopes, Delegation), Verbote vorrangig. Abnahme: Token mail:write + User ohne mail:write → 403.
- **F05** require_active_plugin läuft vor Auth/ohne Mandantenkontext → Plugin-Gate an authentifizierten Kontext binden, fehlender Kontext = ablehnen. Abnahme: mandantendeaktiviertes Plugin → 403 auch bei gültiger Session.
- **F03** Session-Widerruf: Deaktivierung/Austritt/Löschen/Passwortwechsel müssen in Redis- UND DB-Fallback-Sessionpfaden wirken; Widerruf dauerhaft speichern. Abnahme: Widerruf wirkt auch bei Redis-Ausfall.
- **F11** Approval-Resolution: approver_id/Ablauf/Gruppe/Atomarität prüfen, Entscheider getrennt speichern, Approval an Aktion+Argumente+Revision binden.
- **F15** Workflow-HTTP: aufgelöste IPv4/6-Ziele gegen Privatnetz prüfen, Verbindung an geprüfte Auflösung binden, Redirects prüfen.
- **F20** prestart überschreibt gezielte Rechte-Entzüge (0100) mit pauschalem GRANT DELETE → Tabellenschutz nur migrieren; keine Rechteanhebung beim Start.
- **F21** test_migrations.sh: MIGRATION_DATABASE_URL überschreiben + Zielidentität vor DDL prüfen (sonst Gefahr für echte DB).
- **F23** Tenant-Backup-API triggert datenbankweiten Restore → Gesamtrestore als globale Betriebsoperation mit separater Berechtigung.
- **F30** Admin-Standardpasswort bei unkonfiguriertem Start → verpflichtendes Secret oder sicherer Einmal-Generierung.
### Welle S2 — Ausführung verbinden (Worker, Jobs, Contracts, Migrationen)
- **F06** Worker registriert keine der 44 Plugin-Event-Handler (BasePlugin.register_event_handlers ist leer) → API und Worker dieselbe idempotente Registrierung; Abnahme über echten Outbox-Durchgriff (Kontakt anlegen → Worker → Suchindex).
- **F07** Hintergrundjobs verlieren Mandantenkontext/Transaktionen → Mandant+Auftraggeber im Job-Payload Pflicht; Kontext vor erstem SQL; fachliche Änderung+Audit+Outbox gemeinsam committen.
- **F08** External-Agent-API: require_permission an Cookie-Auth gebunden (Bearer nie erreicht) + get_db() ist kein Contextmanager (TypeError) → gemeinsamer geprüfter Auth-Kontext für Cookie+Token; Session-Factory statt get_db.
- **F09** CRM-/MCP-Tools senden nicht anerkannte interne Header → Delegationsmechanismus (delegation_token.py) einbinden; UI und Agent gleiche Rechte-Antwort.
- **F12** Workflow approve/reject: approval["id"] auf ORM-Objekt (TypeError) + falsche resolve-Signatur → an zentralen Vertrag anpassen, wartende Freigabe auflösen statt Selbst-Genehmigung.
- **F13** Workflow-Engine: acquire_lock ohne Aufrufer, Idempotenz unvollständig, Resume ungesperrt → Engine als verbindlichen Zustandsübergang; Abnahme: Worker-Neustart + parallele Resume → keine Doppel-Mails.
- **F14** enforce_data_policy lässt Strings ungefiltert + läuft nur vor der Schleife mit db=None → strukturierte Filterung vor Serialisierung; JEDE LLM-Anfrage (inkl. Tool-Antworten) durch Policy; nicht ladbare Policy = Versand-Stop.
- **F16** Plugin-Lifecycle: prestart reaktiviert absichtlich deaktivierte Plugins; Aktivierungsfehler lassen DB-Zustand aktiv → gewünschten Zustand von Installation/Mandantenfreigabe/Laufzeitgesundheit trennen; Abnahme: Deaktivierung überlebt Neustart.
- **F17** 6 Produktionsstellen rufen ContractRegistry.get() auf (existiert nicht; nur get_contract) → Aufrufer fixen; Abnahme über reale Einstiegspunkte (Miniapp-Tools, proaktive Hinweise, Report-Jobs).
- **F18** Drei Schema-Verfahren (Alembic/Plugin-SQL/sync_plugin_schema) mit Sync-Verlust bei Unique/Partial-Indizes → einen Migrationsbesitzer pro Objekt; Startup-Sync als lesender Driftbericht.
- **F19** alembic/env.py lädt nur app.models (46/129 Tabellen; Sortierung scheitert) → deterministische vollständige Modelldiscovery.
- **F31** Provider-Registry vs. Reindex-Listen divergieren → Plugin-Beiträge als gemeinsame Quelle; Abnahme: neuer Provider wird vollständig indiziert.
- **F37** SMTP-Env-Namen (SMTP_USER vs smtp_username u.a.) → Compose/Config/Doku angleichen; Abnahme: Reset-/Alarm-Mail authentifiziert.
- **F40** Plugin-Migrationen nur Dateiname-Tracking → Hashes speichern und prüfen; Sollzustand vorhandener Tabellen (Spalten/FKs/Policies) vergleichen.
- **F41** Agenten-Stundenlimit zählt ab jetzt() statt letzte Stunde → timedelta(hours=1); Kontingent atomar reservieren.
### Welle S3 — Fachliche Integrität (Daten- und UI-Korrektheit)
- **F25** CSV-Import: Rollback vernichtet frühere Zeilen, Zähler behalten Erfolge, RLS-Kontext weg → Savepoints pro Zeile, Original-Zeilennummern; Zähler = Persistenz.
- **F26** DMS-Dedup vermischft Identität (fremder Datensatz statt eigener Upload) → Content-Storage vs. Fachobjekt trennen; jeder Upload eigene Identität/Rechte.
- **F27** Kalender: SQL-Filter wirft Serien weg bevor Wiederholungen berechnet werden; end_at-Dauer; Mehrtagesüberlappung → Serie nach Laufzeit selektieren, Wiederholungen im Fenster erzeugen.
- **F28** Import/Export ohne Fachrechte (import_export:write ≠ contacts:write; Export ohne Feldrechte) → Fachrechte UND Importrecht; Feldfilter vor Dateierzeugung.
- **F32** Suche: entity_types=[] = alle (soll 0), Filter nach Top-N, Offset unwirksam, before_search zu spät → None/[] unterscheiden; Filter vor Limit; Hook vor Parametern.
- **F33** Workspace-Wechsel invalidiert fachliche Querykeys nicht → Workspace in Query-Identität oder kontrolliert verwerfen.
- **F34** Mandantenwechsel: alte Daten bis Refetch sichtbar → kontrollierter Kontextwechsel (Abbrechen, Caches leeren, Header synchronisieren).
- **F38** pluginStore-Fehler → Dauerspinner (loaded bleibt false) → Fehler/Leer/Erfolg getrennt rendern, Retry anbieten.
- **F39** Office-Edit-Session verweist auf /preview (PDF-only) + Callback-Route existiert nicht → funktionsfähigen Ablauf anbinden oder Feature als nicht-betriebsbereit kennzeichnen.
### Welle S4 — Betriebsfreigabe (inkl. korrigierter Phase R)
- **F22** Backup im Container nicht betriebsfähig (pg_dump fehlt, Pfade nicht persistiert, Kontext-/User-Bugs) → dokumentierter Ablauf mit Programmen, Rechten, persistiertem Ziel.
- **F24** /health/ready liefert 200 bei not_ready; Worker-Check meldet up ohne Worker → korrekte HTTP-Codes (503), Heartbeat-Alter statt Queue-Länge.
- **F29** CI ohne Lockfiles/Testdienste/tatsächliches Artefakt → reproduzierbare Pipeline gegen eigenes Image.
- **F36** Komponenten-Map-Generator nicht verbindlich im Build → Check an npm-Build/Dockerfile/CI hängen.
- **F04** Suche: autocomplete/similar ohne Objekt-/Feldrechte; Snippet/Titel unfiltert zur LLM → ein Schutzpfad für ALLE Suchvarianten vor Snippet- und LLM-Übergabe.
- **F35** PWA abgeschaltet, aber Offline-Banner verspricht Schreibspeicherung → PWA wiederherstellen ODER Banner an Realität anpassen.
- Phase-R-Korrektur (siehe unten, bereits eingearbeitet).
**Abnahmeszenarien quer über alle Wellen (Astra-Vorschlag, verbindlich):**
1. Kontaktanlage → Audit/Outbox → separater Worker → Suchindex → erlaubte KI-Abfrage (F06, F07, F17, F04)
2. Mailentwurf → Freigabe → einmaliger Versand → nachvollziehbares Ergebnis (F11, F12, F13)
**Reihenfolge-Logik:** S1 zuerst (jede nicht autorisierte Aktion verboten), S2 parallel startbar nach S1-P0s, S3/S4 danach. Nach S1+S2 verifizierter Welle: Aufwand neu schätzen (Astra-Hinweis: die 9-14 Tage aus Phase R sind keine Schätzung für 41 Findings).
## Phase R — Betriebssicherheit & 95%-Produktionsreife (geplant, user-abgestimmt 2026-09-16 — läuft in S4 auf; korrigiert 2026-09-17 nach Astra-Kritik)
**Ziel:** Von „Produktion läuft stabil" zu „Produktion verlässlich": stille Ausfälle werden automatisch erkannt und alarmiert (Minuten statt Wochen), die Test-Suite wird zum vertrauenswürdigen Regressionsschutz, Schema-Drift wird automatisch erkannt, Kernprozesse werden nach jedem Deploy regressionsgetestet, Backups sind nachweislich wiederherstellbar.
@@ -1824,14 +1888,19 @@ Der AI Assistent ist ein paralleles System das die Kommunikation-Plattform dupli
- Outbox: 158 failed Events wochenlang unbemerkt (#380)
- Suite-Isolation und alembic-check-Blockade verhindern verlässliche Regressionsschutz-Gates
**95%-Definition (messbar, Betriebssicht) — die 5 Abnahmekriterien:**
1. Kein kritisches Subsystem kann länger als 30 Minuten unbemerkt ausfallen (Auto-Alarm)
2. Komplette pytest-Suite UND komplette Vitest-Suite laufen in je einem Durchlauf grün (verbindliches Regressionsschutz-Gate)
3. Schema-Drift (Alembic vs. Models vs. Plugin-Migrationen) wird automatisch erkannt (alembic check + Hash-Checks in CI)
4. Kern-Flows sind per E2E nach jedem Full-Deploy verifiziert
5. Backup-Restore ist per Drill nachweislich funktioniert und dokumentiert (RTO/RPO)
**95%-Definition (korrigiert 2026-09-17 nach Astra-Kritik):** Die fünf Kriterien sind kein mathematischer Reifegrad, sondern **konkrete Freigabekriterien**. Dokumentiert wird: erfüllte Kriterien, verbleibende Risiken und bekannte Grenzen (Battle-Testing im Echtbetrieb). „95 %" = Zustand, in dem jeder Ausfall laut statt still wird; die restlichen ~5 % sind Echtbetriebs-Edge-Cases, die nur echte Nutzung findet.
Verbleibende ~5% = Battle-Testing im Echtbetrieb: Edge-Cases, die nur echte Nutzung findet, plus neue Features. 100% existiert bei lebenden Systemen nicht — der Unterschied ist, dass bei 95% jeder Ausfall laut wird, statt still.
**Astra-Kritik an Phase R (8 Punkte, 2026-09-17) — eingearbeitet:**
1. ARQ-Heartbeat überwacht sich nicht selbst → zusätzlich externe Überwachung außerhalb der ARQ/Redis-Ausfallkette (z.B. Cron auf Host oder externer Uptime-Check gegen /health/ready).
2. „Installiert aber inaktiv"-Alarm trifft absichtliche Deaktivierung → **Sollzustand** (DB desired state) mit tatsächlicher Betriebsbereitschaft vergleichen; nur Abweichung alarmiert.
3. Leere Queue ≠ laufender Worker → Worker-Heartbeat-ALTER und Verarbeitungsnachweis messen, nicht Queue-Länge.
4. Komplette Suite grün reicht nicht (Mocks/Admin-Tests können Rechtefehler verdecken) → zusätzlich echte API-/Worker-Prozesse mit tatsächlichen Laufzeitrollen prüfen.
5. Ein FK-Fix + Migrationshash genügt nicht → vollständige Modelldiscovery (F19) und eindeutige Schema-Verantwortung (F18) sind Voraussetzung; R3 hängt an S2.
6. E2E-Normalfälle prüfen Rechteentzug/Neustart nicht → Mehrmandanten-, Rollen-, Fehler- und Wiederaufnahme-Szenarien ergänzen.
7. Monatlicher Restorejob beweist keine sichere Zielwahl → isoliertes Ziel und tatsächliche DB-+Datei-Wiederherstellung nach Containerersatz nachweisen.
8. „95 % Produktionsreife" ist keine messbare Zahl → konkrete Freigabekriterien + verbleibende Risiken dokumentieren (siehe oben).
**Aufwandskorrektur (Astra):** Die 9-14 Tage gelten NICHT für die Behebung aller 41 Audit-Findings (Phase S). Neue Schätzung nach Abschluss von S1+S2.
### R1 — Stille-Ausfälle-Wächter + Alerting (2-3 Tage) — PRIORITY 1, größter Risikoreduktor
- ARQ-Heartbeat-Job (alle 5 Min) prüft: (a) /api/v1/plugins — installiert aber nicht active → ALARM (exakt der #389-Fall), (b) /health/ready — DB/Redis/Storage/Worker, (c) Outbox-DLQ — failed > 0 (#380-Klasse), (d) Worker-Queue-Länge
+2 -1
View File
@@ -95,7 +95,8 @@ Phase P Notizen-App oder UI-Backlog-Module 2-16.
7. **Marketplace ist leer:** Keine Listings in der DB (API 200, listings=0). Demo-Listings koennen via Admin-API (MarketplaceListingCreate, marketplace:admin) angelegt werden — User fragen.
**Offene Roadmap-Phasen (user-abgestimmt, startklar):**
- **Phase R** — Betriebssicherheit & 95%-Produktionsreife (R1-R6, user-abgestimmt 2026-09-16). **NÄCHSTE PHASE, vor O/P.** R1 Alerting gegen stille Ausfälle [#390](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/390) (PRIORITY 1 — Evidenz: KI-Chat 4 Wochen still down #389), R2 Suite verlässlich [#391](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/391), R3 Schema-Integrität [#392](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/392), R4 E2E-Kernflows [#393](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/393), R5 Backup-Restore-Drill [#394](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/394), R6 Ops-Runbook [#395](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/395). Milestone 15. 5 messbare Abnahmekriterien = die 95%-Definition (Details + DoD: Roadmap Phase R).
- **Phase S** — Astra-Sanierung (S1-S4, bestätigt 2026-09-17). **NÄCHSTE PHASE.** Externaudit Astra: 41 Findings (2 P0, 29 P1, 10 P2), 10 stichprobenartig intern verifiziert — alle korrekt. Volltext: [docs/audits/astra-audit-2026-09-17.md](docs/audits/astra-audit-2026-09-17.md). Wellen: S1 Sicherheitsgrenzen [#396](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/396) (F01 KI-Tool-Freigabe P0, F02 globale E-Mail P0, F03, F05, F10, F11, F15, F20, F21, F23, F30), S2 Ausführung verbinden [#397](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/397) (F06-F09, F12-F19, F31, F37, F40, F41), S3 Fachliche Integrität [#398](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/398) (F25-F28, F32-F35, F38, F39), S4 Betriebsfreigabe [#399](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/399) (F04, F22, F24, F29, F36 + korrigierte Phase R). Milestone 16. Abnahmen quer: Kontaktanlage→Outbox→Worker→Suchindex→KI-Abfrage; Mailentwurf→Freigabe→einmaliger Versand. Neue Aufwandsschätzung nach S1+S2.
- **Phase R** — Betriebssicherheit & 95%-Produktionsreife (R1-R6, user-abgestimmt 2026-09-16). Läuft in Phase S Welle 4 auf; korrigiert 2026-09-17 nach Astra-Kritik (8 Punkte in Roadmap eingearbeitet). R1 Alerting gegen stille Ausfälle [#390](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/390) (PRIORITY 1 — Evidenz: KI-Chat 4 Wochen still down #389), R2 Suite verlässlich [#391](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/391), R3 Schema-Integrität [#392](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/392), R4 E2E-Kernflows [#393](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/393), R5 Backup-Restore-Drill [#394](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/394), R6 Ops-Runbook [#395](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/395). Milestone 15. 5 messbare Abnahmekriterien = die 95%-Definition (Details + DoD: Roadmap Phase R).
- **Phase M** — MiniApp-Plattform & Dashboard-Builder (M1-M6). **M1 ✓** (Universal-Registry, `/api/v1/miniapps`), **M2 ✓** (persönliche Dashboards: Tabelle, CRUD, Seed, RLS), **M3 ✓** (Dashboard-Builder: Edit-Modus, Drag&Drop, Palette, Tabs), **M4 ✓** (System-Rückbau, Core = reiner Host), **M5 ✓** (Plugin-MiniApps), **M6 ✓ erledigt — PHASE M KOMPLETT** (Windows-Host + AI-Agenten-Tool send_miniapp — siehe Phase-M6-Section).
- **Phase N** — Workspace-Scopes (N1-N4). **N1 ✓** (Scope-Registry via Contract), **N2 ✓** (Dynamischer Scope-Editor), **N3 ✓** (Backend-Filterung contacts/dms/mail/calendar + Frontend-Defaults), **N4 ✓ erledigt — PHASE N KOMPLETT** (7 weitere Module: Tasks nur-meine, Kommunikation-Räume, Wiki-Kategorien-Subtree, Reports-Vorlagen, Agents, Tags, Search-Entity-Types + Navigation Startseite/Menü-Reihenfolge + Dashboard-Schnittstelle — siehe Phase-N4-Section). **Nächster Schritt:** Phase O UI-Overhaul (offen: 1.2 Kontakte-Drag-Drop in Ordner, 1.3 MoveDialog) oder Phase P Notizen-App (P1-P5).
- **Phase O** — UI-Overhaul (umbenannt von Doppel-L, Bug-Verifikation steht im Roadmap-Eintrag: 5/7 Bugs bereits erledigt, offen: 1.2 Kontakte-Drag-Drop in Ordner, 1.3 MoveDialog)
+799
View File
@@ -0,0 +1,799 @@
# Externaudit Astra — Vollständiger Prüfbericht (17.09.2026)
> **Geprüft:** leocrm-full-2026-09-16_ee5545d.zip (Stand ee5545d, Alembic-Head 0144)
> **Prüfmodell:** Astra (neues Modell, 2026-09-17)
> **Bearbeitung:** Interne Verifikation 2026-09-17 — 10 Findings stichprobenartig am Code bestätigt (F01, F02, F05, F08, F10, F12, F17, F24, F37, F41, alle korrekt). Sanierung: PHASE S in PLATFORM_ROADMAP.md, Milestone + Wellen-Issues in Forgejo.
> **Volltext unverändert übernommen aus der Modellausgabe.**
---
## Gesamturteil
LeoCRM hat eine brauchbare modulare Grundarchitektur. Der geprüfte Stand rechtfertigt aber noch keine belastbare Produktionsfreigabe für mandantenfähige, durch KI gesteuerte Geschäftsprozesse.
Das Hauptproblem sind widersprüchliche oder unterbrochene Verbindungen zwischen vorhandenen Komponenten:
- Berechtigungen werden je nach Zugangsweg unterschiedlich geprüft.
- API und Worker registrieren unterschiedliche Funktionen.
- Mandantenkontext geht an Session- und Transaktionsgrenzen verloren.
- Migrationen und Startup-Skripte verändern dieselben Zustände unterschiedlich.
- Erfolgsanzeigen entsprechen teilweise nicht dem tatsächlichen Ergebnis.
Die Sanierung kann auf dem bestehenden System aufbauen. Vorrang haben gemeinsame Ausführungspfade, verlässliche Transaktionen und durchgängige Autorisierung.
Die Befunde verteilen sich auf:
| Priorität | Bedeutung | Anzahl |
|---|---|---|
| P0 | Grundlegende Sicherheitsgrenze verletzt; zuerst absichern | 2 |
| P1 | Erheblicher Sicherheits-, Funktions-, Integritäts- oder Betriebsfehler | 29 |
| P2 | Konkreter Integrations-, Konsistenz- oder Wartbarkeitsfehler | 10 |
Die Einstufungen sind Auditprioritäten, keine CVSS-Bewertungen.
## Was tatsächlich geprüft wurde
Das Archiv wurde inventarisiert; relevante Aufrufketten wurden gelesen und mit Build-, Test- und Laufzeitprüfungen ergänzt. 23 isolierte Proben untersuchten konkrete Fehlerbedingungen und Bestände mit echtem Anwendungscode und gezielt ersetztem I/O.
| Prüfung | Ergebnis |
|---|---|
| Python-Compile für Anwendung und Migrationen | Erfolgreich |
| Backend-Testcollection | 2.362 Tests gefunden |
| test_phase_f_agents.py | 42 Testkörper bestanden; 42 Teardown-Fehler wegen fehlendem Redis |
| Ruff | 7 Meldungen |
| TypeScript und Frontend-Produktionsbuild | Erfolgreich |
| Vollständiger Vitest-Versuch | Kein abgeschlossener Ergebnisdatensatz |
| BlockRenderer.test.tsx gezielt | 76 Tests: 68 bestanden, 8 fehlgeschlagen |
| Generierte Plugin-Komponentenmap | Aktuell, 43 Komponenten |
| Cross-Plugin-Checker | 497 Dateien geprüft, 0 gemeldete Verstöße |
| Alembic-Head | Genau ein Head: 0144 |
| Alembic-Metadatensortierung | Fehler reproduziert |
| Geladener ORM-Bestand | 129 Tabellen, 120 mit tenant_id, 231 FK-Constraints |
Die acht Fehler der gezielten UI-Testdatei betreffen sieben fehlende QueryClient-Fixtures und eine überholte Erwartung an bereinigtes HTML. Daraus folgt keine nachgewiesene XSS-Lücke.
Prüfgrenze: Kein vollständiger PostgreSQL-/Redis-Integrationstest, kein separater produktiver Worker, kein vollständiger Browser-E2E-Lauf und kein tatsächlicher Restore. Die Produktionsinstanz wurde nicht verändert. Der Projektcode blieb unverändert.
## Findings
### F01 · P0 — KI führt nicht freigegebene Werkzeuge aus
Die ReAct-Schleife übernimmt Werkzeugnamen aus der Modellantwort und sucht diese im gesamten ToolRegistry. _execute_tool prüft weder die tatsächlich angebotene Werkzeugliste noch required_permission oder aktuelle Benutzerrechte.
Reproduziert: Angeboten wurde nur audit_allowed. Das Modell lieferte audit_restricted, registriert mit system:admin. Dessen Handler wurde trotzdem ausgeführt.
Die konkrete Schadenswirkung hängt davon ab, ob der jeweilige Handler zusätzliche eigene Rechteprüfungen besitzt.
Korrektur: Unmittelbar vor jedem Handleraufruf Werkzeugfreigabe, Argumente, Benutzerrechte, Verbote, Mandant, Plugin-Aktivierung und gegebenenfalls Genehmigung prüfen.
Abnahme: Ein registriertes, aber nicht freigegebenes Werkzeug wird abgewiesen; Handleraufrufe bleiben null. Rechteentzug während eines Agentenlaufs wirkt auf die nächste Aktion.
Fundstelle: app/ai/agent_loop.py, Zeilen 116 und 344.
### F02 · P0 — Mandantenverwaltung kann globale Anmeldeidentitäten verändern
Mit users:write kann die globale User.email eines Mitglieds des aktuellen Mandanten verändert werden.
User ist jedoch mandantenübergreifend. Derselbe Datensatz trägt Passwort und Systemadmin-Flag. Der Passwort-Reset verwendet die veränderbare E-Mail-Adresse.
Ein Benutzerverwalter aus Mandant A kann dadurch bei einem gemeinsamen Benutzer dessen globale Reset-Adresse verändern. Der vollständige Übernahmeablauf wurde nicht gegen ein reales Konto ausgeführt. Die Änderung des globalen Feldes wurde isoliert reproduziert.
Korrektur: Globale Identitätsänderungen von der Verwaltung einer Mandantenmitgliedschaft trennen. E-Mail-Wechsel nur über verifizierten Selbstservice oder ausdrücklich berechtigte globale Administration.
Abnahme: Ein Tenant-Verwalter kann weder die globale E-Mail-Adresse noch den globalen Aktivstatus eines gemeinsamen Benutzers verändern.
Fundstellen: app/routes/users.py, Zeile 176; app/services/user_service.py, Zeile 193; app/services/auth_service.py, Zeile 218.
### F03 · P1 — Sitzungswiderruf ist unvollständig
Mehrere Fälle sind inkonsistent:
- Der Cookie-Pfad prüft is_active aus der Redis-Session. Eine Deaktivierung invalidiert aber hauptsächlich den Berechtigungscache.
- Eine fehlende UserTenant-Mitgliedschaft wird nicht zwingend abgewiesen.
- Die Benutzerlöschung invalidiert weder Sessions noch Berechtigungscache zuverlässig.
- Passwortwechsel löschen Redis-Sessions, lassen PostgreSQL-Fallback-Sessions bestehen.
Bei einem späteren Redis-Ausfall kann deshalb eine noch nicht abgelaufene alte DB-Session wieder akzeptiert werden. Bei gesundem Redis wird ein fehlender Session-Schlüssel dagegen korrekt abgewiesen.
Korrektur: Widerruf dauerhaft speichern und in beiden Sessionpfaden prüfen. Aktive Mitgliedschaft und Kontostatus zwingend durchsetzen.
Abnahme: Deaktivierung, Austritt und Passwortwechsel wirken unmittelbar auch bei Redis-Ausfall.
Fundstellen: app/deps.py, Zeilen 101 und 131; app/core/auth.py, Zeilen 88 und 221; app/routes/users.py, Zeile 319.
### F04 · P1 — Suche umgeht Teile der Objekt- und Feldberechtigungen
Hauptsuche, Vorschläge, Facetten und Ähnlichkeitssuche verwenden unterschiedliche Schutzmechanismen.
autocomplete und find_similar_all_types filtern nach Mandant, berücksichtigen aber keine vollständige Benutzer-/Objektsichtbarkeit. Innerhalb desselben Mandanten reicht das nicht zum Schutz privater Daten.
Im Hauptpfad wird nur result.data feldweise gefiltert. title und snippet bleiben unverändert. Die KI-Zusammenfassung bekommt die Ergebnisse bereits vor dieser Feldfilterung.
Korrektur: Modulrechte, Plugin-Aktivierung, Objektsichtbarkeit und Feldfreigaben vor Snippet-Erzeugung und LLM-Übergabe anwenden. Alle Suchvarianten müssen denselben Schutzpfad nutzen.
Abnahme: Private Dateien oder Mails erscheinen weder in Vorschlägen noch in Ähnlichkeiten, Facetten oder KI-Zusammenfassungen.
Fundstellen: app/plugins/builtins/unified_search/search_engine.py, Zeilen 186 und 258; routes.py, Zeilen 197 und 209.
### F05 · P1 — Plugin-Sperre läuft vor der Authentisierung
require_active_plugin wird als Router-Dependency vor der Authentisierung der jeweiligen Route ausgeführt.
Sie liest den Mandanten aus dem DB-Kontext. Fehlt dieser, kehrt sie ohne Sperre zurück.
Reproduziert: Die Plugin-Prüfung lief mit leerem Mandantenkontext; erst danach setzte die Auth-Dependency den Mandanten. Der Endpunkt antwortete mit HTTP 200.
Korrektur: Die Plugin-Prüfung ausdrücklich vom bereits geprüften Benutzer-/Mandantenkontext abhängig machen. Fehlender Kontext muss zur Ablehnung führen.
Abnahme: Global aktives, im aktuellen Mandanten deaktiviertes Plugin liefert auch bei gültiger Sitzung und passenden Modulrechten 403.
Fundstellen: app/deps.py, Zeile 409; app/main.py, Zeile 656.
### F06 · P1 — Worker registriert keine der 44 deklarierten Plugin-Ereignisbehandlungen
Der Worker überspringt on_activate und ruft stattdessen register_event_handlers auf.
Diese Basismethode ist leer. Keines der 27 gefundenen Plugins überschreibt sie. Die eigentliche Registrierung findet in BasePlugin.on_activate statt.
Reproduziert: 27 Plugins, 44 Ereignisdeklarationen, 0 Registrierungen über den Worker-Aufruf.
API und Worker sind getrennte Prozesse; ihre Registries werden nicht geteilt. Gesonderte Core- oder Wildcard-Handler ändern diesen Befund nicht.
Korrektur: Prozessregistrierung und mandantenbezogenes Seeding trennen. API und Worker müssen dieselbe idempotente Registrierung ausführen.
Abnahme: Kontakt oder Datei anlegen → Outbox → separater Worker → zuständiger Plugin-Handler → persistierter Suchindex.
Fundstellen: app/core/worker.py, Zeile 150; app/plugins/base.py, Zeilen 51 und 155.
### F07 · P1 — Hintergrundjobs verlieren Mandantenkontext und Transaktionen
Mehrere Suchjobs und run_agent öffnen neue Sessions und lesen zunächst einen mandantenbezogenen Datensatz, um dessen Mandanten zu bestimmen. Unter der vorgesehenen RLS fehlt bereits für diese erste Abfrage der erforderliche Kontext.
Der Workflow-Timeoutjob setzt ebenfalls keinen Kontext und beendet Änderungen teilweise mit flush, ohne commit.
Zusätzlich verspricht TenantMixin automatische ORM-Filterung, implementiert im untersuchten DB-Modul aber lediglich die Spalten.
set_config(..., true) gilt nur für die aktuelle Transaktion. Nach Commit oder Rollback muss eine neue Transaktion ihren Kontext erneut erhalten.
Korrektur: Mandant und Auftraggeber im Job-Payload verpflichtend übergeben; Kontext vor dem ersten fachlichen SQL setzen; Änderungen, Audit und Outbox gemeinsam committen.
Abnahme: Indexierung, Agentenstart und Timeout-Verarbeitung funktionieren mit crm_worker, erzwungener RLS und zwei Mandanten.
Fundstellen: app/core/db/__init__.py, Zeilen 52 und 280; unified_search/jobs.py, Zeile 31; automation/agent_runner.py, Zeile 26; automation/workflow_timeout.py, Zeile 18.
### F08 · P1 — Externe Agenten-API funktioniert für reine Bearer-Clients nicht
Die externen Agentenrouten verlangten zunächst require_permission, das an die Cookie-Authentisierung gebunden ist.
Die Bearer-Verifikation im Handler wird deshalb bei einem normalen API-Client ohne Cookie nicht erreicht.
Reproduziert: Statusabfrage mit ausschließlich Bearer-Header liefert 401.
Im /run-Pfad steckt ein weiterer Fehler: `async with get_db() as stream_db:` — get_db() ist ein FastAPI-Asyncgenerator und kein solcher Contextmanager. Das erzeugt einen TypeError.
Korrektur: Cookie und API-Token über einen gemeinsamen geprüften Auth-Kontext auflösen. Für selbst verwaltete Sessions die vorhandene Sessionfactory beziehungsweise den richtigen Contextmanager verwenden.
Abnahme: Gültiger Bearer ohne Cookie funktioniert für Status, Run und Stream; ungültige Tokens werden abgelehnt.
Fundstellen: app/plugins/builtins/ai_assistant/external_api.py, Zeilen 44 und 126; app/deps.py, Zeile 299.
### F09 · P1 — Generische CRM-/MCP-Werkzeuge haben keinen funktionierenden Delegationspfad
Zwei Implementierungen senden X-Internal-Call, X-Tenant-Id, X-User-Id. Diese Header werden von der geschützten API nicht als gültige Authentisierung akzeptiert.
Reproduziert: Der generische CRM-Aufruf erhält „Not authenticated“.
Im separaten Worker zeigt der lokale Standardhost außerdem auf den Worker selbst. Der vorhandene Delegationstoken-Code ist nicht in diese Aufrufe eingebunden.
Korrektur: Fachservices und vorhandenen Delegationsmechanismus verbinden. Einen gemeinsamen geprüften Ausführungskontext verwenden und die beiden generischen Aufrufimplementierungen konsolidieren.
Abnahme: Dieselbe Fachaktion ist für denselben Benutzer über UI und Agent gleichermaßen erlaubt oder gesperrt; das Audit nennt Benutzer, Agent und Mandant.
Fundstellen: ai_assistant/crm_api_tool.py, Zeile 114; mcp_server/tool_definitions.py, Zeile 104; app/core/delegation_token.py.
### F10 · P1 — Token-Scopes vergeben Rechte, statt sie einzuschränken
require_permission akzeptiert einen passenden _token_scopes-Eintrag durch ein frühzeitiges return, auch wenn die aktuellen Benutzerberechtigungen leer sind.
Isoliert reproduziert: mail:write wird trotz fehlender Benutzerberechtigung erlaubt.
Der Cookie-Zwang aus F08 verdeckt diesen Fehler auf vielen HTTP-Pfaden. Eine alleinige Reparatur der Bearer-Unterstützung kann ihn erst erreichbar machen.
Korrektur: Effektive Rechte als Schnittmenge aus Benutzerrechten, Token-Scopes und Delegation berechnen. Explizite Verbote müssen Vorrang haben.
Abnahme: Token mit mail:write, Benutzer ohne mail:write → 403. Späterer Rechteentzug wirkt auf bestehende Tokens.
Fundstelle: app/deps.py, Zeile 314.
### F11 · P1 — Freigaben sind nicht sicher an Entscheider und Aktion gebunden
resolve_approval_request prüft Mandant und Status, aber nicht ausreichend: Ablaufdatum, vorgesehenen Genehmiger, Genehmigergruppe, konkurrierende Entscheidungen.
Die ursprüngliche approver_id wird mit dem tatsächlichen Entscheider überschrieben.
Reproduziert: Eine abgelaufene Anfrage konnte von einem anderen Entscheider genehmigt werden.
Für Agenten fehlt außerdem eine durchgängige, dauerhaft gespeicherte Fortsetzung genau des genehmigten Werkzeugaufrufs.
Korrektur: Zuordnung und Entscheider getrennt speichern; Ablauf und Berechtigung prüfen; Zustand atomar ändern. Genehmigung an Aktion, Argumente, Revision und Kontext binden.
Abnahme: Falscher Entscheider, abgelaufene Anfrage und doppelte Entscheidung werden abgewiesen. Genehmigung führt exakt die geprüfte Aktion einmal aus.
Fundstellen: app/core/approval.py, Zeile 106; app/ai/agent_loop.py, Zeile 384; app/routes/approvals.py, Zeile 236.
### F12 · P1 — Workflow-Genehmigung und -Ablehnung scheitern an falschen Aufrufen
Die Workflow-Routen behandeln die Rückgabe von create_approval_request als Dictionary: approval["id"]. Tatsächlich wird ein ORM-Objekt zurückgegeben.
Reproduziert: TypeError: 'ApprovalRequest' object is not subscriptable.
Danach wäre auch der Aufruf von resolve_approval_request falsch: tenant_id fehlt, und decided_by wird statt approver_id verwendet.
Korrektur: Beide Routen an den tatsächlichen zentralen Vertrag anpassen und die bestehende wartende Freigabe auflösen. Nicht bei jeder Entscheidung eine neue, sofort selbst genehmigte Anfrage erzeugen.
Abnahme: Beide tatsächlichen URLs funktionieren; Zustandswechsel, Audit und Freigabe stimmen. Wiederholung erzeugt keinen zweiten Fortschritt.
Fundstellen: app/routes/workflows.py, Zeilen 507 und 525; app/core/approval.py, Zeile 106.
### F13 · P1 — Dauerhafte Workflow-Ausführung ist nur teilweise verbunden
Die Engine dokumentiert Locks, Wiederaufnahme und Idempotenz. Im Anwendungscode hat acquire_lock jedoch keinen Aufrufer.
idempotency_key existiert als Modellfeld, wird aber von den Seiteneffekt-Handlern nicht durchgängig verwendet. resume verarbeitet Schritte ohne wirksame Sperre.
Der manuelle Trigger legt eine Instanz an; eine durchgängige automatische Ausführung und Wiederaufnahme fälliger Wait-Schritte ist nicht vollständig angeschlossen.
Korrektur: Die vorhandene Engine zum verbindlichen Zustandsübergang machen. Start und Wiederaufnahme über registrierte Jobs verbinden; Instanzen atomar übernehmen und stabile Schritt-/Idempotenzzustände persistieren.
Abnahme: Ein Ablauf übersteht Worker-Neustart. Gleichzeitige Resume-Aufrufe versenden eine Mail nicht doppelt. Unklarer Ausgang eines externen Aufrufs wird nachvollziehbar abgeglichen.
Fundstellen: app/workflows/engine.py, Zeilen 526 und 567; app/models/workflow.py, Zeile 72; app/routes/workflows.py, Zeile 338.
### F14 · P1 — KI-Datenrichtlinie schützt nicht den gesamten Nachrichtenstrom
enforce_data_policy filtert Dictionary-Inhalte, lässt Strings aber unverändert.
Reproduziert: Ein JSON-String mit einem synthetischen smtp_password passiert unverändert.
Der Agentenrunner ruft die Policy nur vor der ReAct-Schleife und mit db=None auf. Dadurch wird keine Providerkonfiguration geladen. Werkzeugantworten entstehen erst später innerhalb der Schleife.
Korrektur: Strukturierte Daten vor der Serialisierung filtern. Jede LLM-Anfrage einschließlich Toolantworten und RAG-Kontext muss denselben geprüften Pfad passieren. Verpflichtende, nicht ladbare Richtlinien müssen den Versand verhindern.
Abnahme: Gesperrte Felder fehlen am tatsächlichen Provider-Eingang sowohl im Startkontext als auch nach Werkzeugaufrufen.
Fundstellen: app/ai/data_policy.py, Zeile 35; automation/agent_runner.py, Zeile 242; app/ai/agent_loop.py, Zeile 346.
### F15 · P1 — Workflow-HTTP-Schutz lässt interne Hostnamen durch
_is_url_safe blockiert IP-Literale und einige fest eingetragene Hostnamen, lässt andere Hostnamen ungeprüft passieren.
Reproduziert: http://postgres:5432/ wird vom Validator akzeptiert.
Auch ein externer Domainname mit privater DNS-Auflösung kann passieren. Welche internen Dienste tatsächlich erreichbar wären, hängt vom Deployment ab. Die Probe sendete keine Netzwerkanfrage.
Korrektur: Aufgelöste IPv4-/IPv6-Ziele prüfen, die Verbindung an die geprüfte Auflösung binden und erlaubte Ziele beziehungsweise Ports begrenzen. Weiterleitungen ebenfalls prüfen oder sperren.
Abnahme: Interne Servicenamen, private DNS-Ziele und DNS-Wechsel werden abgefangen.
Fundstelle: app/workflows/step_handlers.py, Zeilen 169 und 197.
### F16 · P1 — Plugin-Lifecycle hat widersprüchliche Aktivierungswege
Mehrere Mechanismen verwalten denselben Zustand:
- prestart aktiviert absichtlich deaktivierte Plugins erneut.
- main.py führt einen eigenen Aktivierungsablauf aus.
- Seeding erfolgt im Kontext des ersten gefundenen Mandanten.
- Aktivierungsfehler werden protokolliert, ohne einen bereits aktiven DB-Zustand zuverlässig zurückzunehmen.
- Der Worker lädt Jobmodule vor seinem DB-Aktivierungsabgleich.
Global installiert, mandantenbezogen freigegeben und im Prozess betriebsbereit werden dadurch vermischt.
Korrektur: Registry-/PluginService-Lifecycle konsolidieren. Gewünschten Zustand, Installation, Mandantenfreigabe und Laufzeitgesundheit getrennt behandeln.
Abnahme: Absichtliche Deaktivierung bleibt nach Neustart erhalten. Ein Aktivierungsfehler erscheint nicht als gesund. Mandanten erhalten ihre Seeds unabhängig von der Ladefolge.
Fundstellen: prestart.sh, Zeile 148; app/main.py, Zeilen 309 und 315; app/core/worker.py, Zeile 221.
### F17 · P1 — Integrationen rufen eine nicht vorhandene Registry-Methode auf
ContractRegistry besitzt get_contract, aber kein get.
Mindestens sechs Produktionsstellen verwenden trotzdem get_contract_registry().get(...).
Betroffen sind unter anderem Agenten-/Workflow-Kommunikation, Miniapp-Werkzeuge, proaktive Hinweise und Berichtjobs. Teilweise wird nur geloggt, sodass erwartete Nachrichten oder Ergebnisse still fehlen.
Korrektur: Aufrufer auf den tatsächlichen Contract-Zugriff umstellen. Öffentliche Funktionen typisieren und ihre Verträge prüfen.
Abnahme: Betroffene Funktionen über ihre realen Einstiegspunkte ausführen; Nachrichten, Miniapps und Berichtablage müssen tatsächlich persistiert werden.
Fundstellen: app/plugins/builtins/contracts.py, Zeile 49; app/ai/miniapp_tools.py, Zeile 26; report_generator/jobs.py, Zeile 80; ai_proactive/services.py, Zeile 78.
### F18 · P1 — Drei Schema-Verfahren erzeugen Drift
Alembic, Plugin-SQL und sync_plugin_schema.py verändern dasselbe Datenbankschema.
Der Startup-Sync verliert bei Indizes wichtige Eigenschaften: UNIQUE, partielle WHERE-Bedingungen, PostgreSQL-Indexmethoden und Optionen.
Reproduziert: Aus dem partiellen eindeutigen Workspace-Default-Index erzeugt der Sync lediglich einen normalen Index auf tenant_id.
Abgefangene DDL-Fehler können außerdem die gemeinsame Transaktion unbrauchbar machen, während der Ablauf weiterläuft.
Korrektur: Pro Tabelle und Änderung einen verbindlichen Migrationsbesitzer bestimmen. Startup-Sync zunächst als lesenden Driftbericht verwenden; Reparaturen ausdrücklich migrieren.
Abnahme: Frischinstallation und Upgrade ergeben dieselben Spalten, Constraints, Indizes und Policies.
Fundstellen: prestart.sh, Zeile 135; scripts/sync_plugin_schema.py, Zeilen 177 und 188.
### F19 · P1 — Alembic kennt nicht alle benötigten Modelle
alembic/env.py importiert nur app.models.
In einem frischen Prozess umfasst das 46 Tabellen. Die Metadatensortierung scheitert bereits an einer Referenz von contact_merge_history auf contacts.
Reproduziert: NoReferencedTableError.
Nach Laden der Pluginmodelle sind 129 Tabellen bekannt. Nur den in der Roadmap genannten einzelnen Attachment-FK zu reparieren reicht daher nicht.
Korrektur: Deterministische, nebenwirkungsfreie Modelldiscovery für Alembic und Schema-Prüfungen einführen.
Abnahme: Metadatensortierung funktioniert im frischen Prozess; alembic check läuft nach Frischinstallation und Upgrade gegen PostgreSQL durch.
Fundstellen: alembic/env.py, Zeile 15; app/models/contact_merge.py, Zeile 28.
### F20 · P1 — Startup überschreibt beabsichtigte Datenbankrechte
Migration 0100 entzieht API und Worker Löschrechte für besonders geschützte Tabellen, darunter audit_log.
Danach erteilt prestart erneut: GRANT DELETE ON ALL TABLES IN SCHEMA public ...
Damit wird der beabsichtigte Löschschutz wieder aufgehoben.
Zusätzlich setzt der Start mehrere DB-Rollen auf dasselbe aus der privilegierten Verbindung entnommene Passwort. API und Worker erhalten die Migrationsverbindung im Environment.
Korrektur: Tabellenspezifische Rechte ausschließlich nachvollziehbar migrieren. Keine pauschale Rechteanhebung beim Start. Separate Zugangsdaten und ein abgegrenzter Migrationsschritt.
Abnahme: API und Worker können Auditdaten schreiben, aber nicht löschen. Laufzeitcontainer benötigen keinen frei verwendbaren Migrationszugang.
Fundstellen: prestart.sh, Zeilen 58 und 90; alembic/versions/0100_restrict_delete_grants.py, Zeile 39; docker-compose.yaml, Zeile 78.
### F21 · P1 — Migrationstest kann die falsche Datenbank verändern
test_migrations.sh überschreibt nur DATABASE_URL. Alembic bevorzugt jedoch MIGRATION_DATABASE_URL.
Wenn diese Variable auf eine echte Instanz zeigt und die vorherigen Skriptschritte erfolgreich laufen, können Upgrade und Downgrade dort statt in der Testdatenbank ausgeführt werden.
Zusätzlich wird eine SQLAlchemy-URL mit postgresql+psycopg2:// an psql übergeben. Das passt nicht zum erwarteten Verbindungsformat.
Das Skript wurde wegen dieses Befunds nicht ausgeführt.
Korrektur: Beide Alembic-Variablen auf das explizite temporäre Ziel setzen. Vor jeder DDL-Aktion Zielidentität prüfen und Aufräumen per trap sicherstellen.
Abnahme: Selbst bei anders gesetzter MIGRATION_DATABASE_URL verändert der Test ausschließlich die erzeugte Testdatenbank.
Fundstellen: scripts/test_migrations.sh, Zeilen 24 und 70; alembic/env.py, Zeile 27.
### F22 · P1 — Backup ist im ausgelieferten Container nicht vollständig betriebsfähig
Mehrere Probleme greifen zusammen:
- Das Runtime-Image installiert libpq5, aber nicht die benötigten Programme pg_dump und pg_restore.
- API-Backups landen in /data/backups, CLI-Backups standardmäßig in /backups.
- Compose persistiert diese Backupverzeichnisse nicht.
- Backup-Konfiguration wird ohne erforderlichen Mandantenkontext gelesen.
- Fehlerbenachrichtigung verwendet User.tenant_id, obwohl User global ist.
- Die vorgesehene eingeschränkte Laufzeitrolle kann keinen vollständigen RLS-Dump garantieren.
Korrektur: Einen dokumentierten Backupablauf mit passenden Programmen, gesonderten Rechten, dauerhaftem Ziel, Ergebnisregistrierung und gemeinsamer DB-/Dateisicherung herstellen.
Abnahme: Sicherung im gebauten Image erstellen, Container ersetzen und anschließend Datenbank und Dateien auf isoliertem Ziel wiederherstellen.
Fundstellen: Dockerfile, Zeile 56; app/services/backup_service.py, Zeile 19; app/core/backup_job.py, Zeilen 26 und 208.
### F23 · P1 — Mandanten-Backup-API führt einen datenbankweiten Restore aus
Die Restore-Route ist über automation:admin eines Mandanten erreichbar. Sie wählt einen Backupdatensatz dieses Mandanten aus.
Der eigentliche Restore bearbeitet jedoch die gemeinsame Datenbank ohne Mandantenfilter.
Die Fehler aus F22 verhindern derzeit viele Aufrufe. Das ist aber keine Sicherheitsgrenze: Eine bloße Reparatur der Programme und Rechte kann den gefährlichen Wirkungsbereich erst freilegen.
Der Kommentar, pg_restore --clean sei atomar, ist ebenfalls falsch. --clean löscht zu restaurierende Objekte; Atomarität benötigt ausdrücklich passende Transaktionsoptionen.
Korrektur: Gesamtrestore als globale Betriebsoperation mit separater Berechtigung, Wartungszustand und isoliertem Ziel behandeln.
Abnahme: Ein Tenant-Admin kann keinen Gesamtrestore auslösen.
Fundstellen: app/routes/backups.py, Zeile 58; app/services/backup_service.py, Zeilen 175 und 224.
### F24 · P1 — Healthchecks melden Ausfälle als Erfolg
Reproduziert:
- /health/ready liefert bei not_ready weiterhin HTTP 200.
- check_worker meldet up, wenn Redis erreichbar und die Queue leer ist selbst ohne Worker.
Ein curl -f erkennt den ersten Fehler nicht. Der Compose-Workercheck prüft ebenfalls Redis statt die tatsächliche Verarbeitung.
Korrektur: Fehlerzustände mit passenden HTTP-Codes zurückgeben. Worker über zeitlich begrenzten Heartbeat und Verarbeitungsnachweis prüfen.
Abnahme: DB aus → Readiness 503. Worker aus bei gesundem Redis → Worker down. Festhängende Verarbeitung wird innerhalb einer definierten Zeit erkannt.
Fundstellen: app/routes/health.py, Zeile 39; app/core/monitoring.py, Zeile 219; docker-compose.yaml.
### F25 · P1 — CSV-Import meldet zurückgerollte Zeilen als erfolgreich
Mehrere Zeilen werden in derselben Transaktion geschrieben. Scheitert eine spätere Zeile, ruft der Import db.rollback() auf.
Dabei verschwinden auch frühere unbestätigte Zeilen. Die Zähler succeeded und created behalten diese aber bei.
Mit Transaktionsmodell reproduziert: partial_success mit einer erfolgreichen Zeile, obwohl anschließend keine Zeile bestätigt wird.
Der Rollback entfernt zudem den lokalen RLS-Kontext. Fehlerzeilen werden nach Vorvalidierung teilweise neu nummeriert.
Korrektur: Atomaren Import oder Teilimport ausdrücklich festlegen. Für Teilimport Savepoints pro Zeile verwenden und Originalzeilennummern erhalten.
Abnahme: Bei einem Fehler in der mittleren von drei Zeilen stimmen gespeicherte Daten, Erfolgszähler und Fehlernummern überein.
Fundstelle: app/services/import_export_service.py, Zeilen 131 und 138.
### F26 · P1 — DMS-Deduplizierung vermischt Dateiinhalt und Dateiidentität
Beim Upload wird nach gleichem Inhalt im ganzen Mandanten gesucht. Bei Treffer wird der bestehende DmsFile-Datensatz zurückgegeben.
Dabei werden Besitzer, Ordner, Dateiname und Sichtbarkeit nicht passend zum neuen Upload behandelt.
Reproduziert: Der Upload eines zweiten Benutzers erhält ID, Name und uploaded_by des vorhandenen fremden Datensatzes; es entsteht kein eigener Dateieintrag.
Korrektur: Binären Inhalt und fachliches Dateiobjekt trennen. Gleiche Bytes dürfen intern wiederverwendet werden; jeder fachliche Upload braucht aber seine eigene Identität und Rechtezuordnung.
Abnahme: Zwei Benutzer können dieselbe Vorlage in unterschiedliche Ordner hochladen, ohne fremde Metadaten zu erhalten. Löschen einer Datei beschädigt die andere nicht.
Fundstelle: app/plugins/builtins/dms/routes.py, Zeilen 146 und 159.
### F27 · P1 — Kalenderfilter entfernt Serientermine vor ihrer Berechnung
list_entries filtert zunächst CalendarEntry.start_at >= start_dt.
Eine wöchentliche Serie, die im Januar beginnt, wird dadurch für eine Februaransicht bereits aus der SQL-Ergebnismenge entfernt. Ihre Februar-Wiederholungen werden anschließend gar nicht berechnet.
Bei erzeugten Wiederholungen wird außerdem start_at verändert, während end_at auf dem Ausgangstermin bleiben kann. Auch mehrtägige Termine mit Beginn vor dem Sichtfenster fehlen.
Korrektur: Einzeltermine nach Überschneidung und Serien nach relevanter Laufzeit auswählen. Danach Wiederholungen im Sichtfenster erzeugen und Endzeit um dieselbe Dauer verschieben.
Abnahme: Januarserie erscheint im Februar; Dauer, mehrtägige Überschneidung und Sommerzeitwechsel stimmen. Ungültige Datumsparameter liefern einen Validierungsfehler.
Fundstelle: app/plugins/builtins/calendar/routes.py, Zeilen 369, 407 und 433.
### F28 · P1 — Import/Export besitzt einen abweichenden Berechtigungspfad
Die generischen Routen prüfen import_export:read/write.
Der Kontaktvertrag schreibt importierte Kontakte direkt als Modelle. Eine zusätzliche vollständige Prüfung von contacts:write und Feldschreibrechten ist dort nicht enthalten.
Beim Export werden Objektsichtbarkeit und eine feste Sensitivitätsliste berücksichtigt, aber individuelle Feldberechtigungen nicht vollständig übergeben.
Korrektur: Import-/Exportrechte als zusätzliche Rechte zu den Fach- und Feldrechten behandeln. Vorhandene Fachservices nutzen und Feldfilter vor Dateierzeugung anwenden.
Abnahme: Importrecht allein erlaubt keine Kontaktanlage. Ein verborgenes Feld fehlt gleichermaßen in API, CSV, XLSX und JSON.
Fundstellen: app/routes/import_export.py, Zeile 294; app/plugins/builtins/contacts/contracts.py, Zeilen 194 und 257.
### F29 · P1 — CI liefert keinen reproduzierbaren vollständigen Qualitätsnachweis
Der gezeigte Forgejo-Workflow:
- installiert requirements.txt, aber nicht ausdrücklich die Testabhängigkeiten,
- definiert keine vollständige PostgreSQL-/Redis-Testumgebung,
- verwendet Versionsbereiche, während das Containerimage den Lockfile verwendet.
Der Docker-Frontendbuild ruft Vite direkt auf und überspringt die im normalen npm-Build enthaltene TypeScript-Prüfung.
Ein optionaler Smokecheck fragt eine bestehende externe Instanz statt das gerade gebaute Image ab.
Korrektur: Eine reproduzierbare Pipeline mit identischen Locks, Testdiensten, echten DB-Rollen und Prüfungen des erzeugten Images herstellen.
Abnahme: Ein sauberer Runner besteht Backend-/Frontend-Suite, Typcheck, Migrationen und E2E gegen sein eigenes Artefakt.
Fundstellen: .forgejo/workflows/ci.yml, Zeile 21; Dockerfile, Zeile 19; scripts/ci_pipeline.sh, Zeile 113.
### F30 · P1 — Frischinstallation besitzt ein bekanntes Administrator-Standardpasswort
Compose und seed_admin.py enthalten einen festen Passwort-Fallback für das initiale Administratorkonto.
Wenn die Konfiguration fehlt, wird ein Konto mit bekanntem Zugang angelegt.
Der Befund betrifft den Installationspfad. Er beweist nicht, dass auf dem laufenden Server noch dieses Passwort gilt.
Korrektur: Initiales Geheimnis verpflichtend machen oder sicher einmalig erzeugen. Unkonfigurierter Produktivstart muss vor Benutzeranlage abbrechen.
Abnahme: Ein frisches Volume ohne gesetztes Geheimnis erzeugt kein nutzbares Konto mit festem Standardpasswort.
Fundstellen: docker-compose.yaml, Zeile 95; scripts/seed_admin.py, Zeile 79.
### F31 · P2 — Provider-Discovery und Reindexierung passen nicht zusammen
auto_register_providers erhält eine DB-Session, verwendet sie aber nicht zum Aktivierungsabgleich. Stattdessen wird eine feste Providerliste registriert.
Die Discovery registriert 13 effektive Suchtypen. Reindex- und Ähnlichkeitspfade verwenden daneben eine feste Liste aus vier Tabellen.
Ein neuer Provider bedeutet deshalb nicht automatisch vollständige Indexierung, Reindexierung und Ähnlichkeitssuche.
Korrektur: Beiträge aktiver Plugins zur gemeinsamen Quelle für Such- und Indexfähigkeiten machen. Jeder Provider beschreibt seine tatsächlich implementierten Fähigkeiten und Indexjobs.
Abnahme: Ein neuer Testprovider wird allein über seinen Pluginbeitrag gefunden und vollständig indiziert. Deaktivierung wirkt in API und Worker.
Fundstellen: unified_search/provider_registry.py, Zeile 118; search_engine.py, Zeile 19; jobs.py, Zeile 245.
### F32 · P2 — Suchscopes, Filter und Pagination liefern falsche Ergebnisse
Vier konkrete Probleme:
- entity_types=[] wird zu „alle Typen“.
- Datum-/Tagfilter laufen erst nach der Begrenzung auf die besten Treffer.
- Der Request-Offset wird nicht wirksam in die Ergebnisabfrage umgesetzt.
- search.before_search läuft erst nach Erzeugung von Suchausdruck und Embedding.
Reproduziert: Trotz leerer Typenmenge wird ein Provider aufgerufen.
Korrektur: None und leere Menge unterscheiden. Fachfilter vor Limit und Offset anwenden; Hook vor Ableitung der Suchparameter ausführen.
Abnahme: Leere Workspace-Schnittmenge ergibt null Treffer. Seite zwei enthält die richtigen Folgetreffer; Filter finden auch Datensätze außerhalb der ursprünglichen Top-N.
Fundstellen: unified_search/search_engine.py, Zeilen 94 und 116; routes.py, Zeile 182.
### F33 · P2 — Workspace-Wechsel aktualisiert den Datenkontext nicht zuverlässig
Der Wechsel verändert Store und globalen HTTP-Header. Fachliche Querykeys wie für Aufgaben enthalten aber keinen Workspace und werden dabei nicht passend invalidiert.
Dadurch können Daten aus dem vorherigen Arbeitsbereich sichtbar bleiben.
Beim Wiederherstellen aus sessionStorage wird der API-Header nicht entsprechend synchronisiert; auch Reset behandelt beide Zustände nicht vollständig gemeinsam.
Korrektur: Workspace in die Queryidentität aufnehmen oder betroffene Queries kontrolliert verwerfen. Wiederherstellung und Reset mit dem API-Kontext synchronisieren.
Abnahme: Wechsel und Neuladen zwischen zwei unterschiedlich gefilterten Workspaces zeigen sofort passende Daten.
Einordnung: Das ist ein Arbeitskontextfehler; Workspaces sollen weiterhin keine Rechte vergeben.
Fundstellen: frontend/src/hooks/useWorkspace.ts, Zeile 56; frontend/src/store/workspaceStore.ts; frontend/src/api/tasks.ts, Zeile 118.
### F34 · P2 — Mandantenwechsel lässt alten Clientzustand bestehen
useSwitchTenant setzt den Mandanten und invalidiert Queries. Alte Daten, laufende Anfragen sowie Workspace-/Pluginzustand werden aber nicht vollständig entfernt beziehungsweise zurückgesetzt.
Viele fachliche Querykeys enthalten keinen Mandanten.
Dadurch können alte Mandantendaten bis zum Refetch sichtbar bleiben oder verspätete Antworten den neuen Zustand überlagern.
Korrektur: Mandantenwechsel als kontrollierten Kontextwechsel ausführen: Anfragen abbrechen, betroffene Caches und Stores leeren, Auth-/CSRF-Kontext aktualisieren und neue Plugin-/Workspacezustände laden.
Abnahme: Eine langsame Anfrage aus A liefert nach Wechsel zu B keine sichtbaren A-Daten.
Einordnung: Eine serverseitige Umgehung der Mandantentrennung wurde damit nicht nachgewiesen.
Fundstellen: frontend/src/api/auth.ts, Zeile 100; frontend/src/api/tasks.ts, Zeile 118; frontend/src/api/client.ts, Zeile 34.
### F35 · P2 — PWA-Anforderung wurde durch Abschaltung ersetzt
main.tsx deregistriert die Service Worker. Die Vite-Konfiguration aktiviert die PWA nicht.
Gleichzeitig verspricht der Offlinebanner: „Änderungen werden gespeichert, wenn die Verbindung wiederhergestellt ist.“
Ein entsprechender Offline-Schreibpfad ist dort nicht vorhanden.
Korrektur: Die vereinbarte PWA für Installation und kontrolliertes Caching statischer Assets wiederherstellen. API-/ERP-Daten und Schreiboperationen nicht pauschal offline cachen. Den Banner an die tatsächliche Funktion anpassen.
Abnahme: Installierbarkeit und Assetupdates funktionieren; Offline-Schreibaktionen werden nicht fälschlich als später gespeichert dargestellt.
Fundstellen: frontend/src/main.tsx, Zeile 7; frontend/src/App.tsx, Zeile 49; frontend/vite.config.ts.
### F36 · P2 — Generierte Plugin-Komponentenmap ist nicht verpflichtend geprüft
Die neue Map ist vorhanden und aktuell: 43 Komponenten, Check grün.
Der Generator beziehungsweise Aktualitätscheck wird aber nicht verbindlich von npm-Build, Dockerfile und der gezeigten CI aufgerufen.
Ein künftiger Manifestbeitrag kann deshalb fehlen, während die alte Map erfolgreich gebaut wird.
Korrektur: Generatorcheck an den normalen Build-/CI-Pfad hängen.
Abnahme: Ein neuer Manifestpfad ohne aktualisierte Map macht CI rot. Nach Generierung ist die Seite im Produktionsbundle enthalten.
Fundstellen: scripts/generate_component_map.py, Zeile 71; frontend/package.json, Zeile 8; Dockerfile, Zeile 19.
### F37 · P1 — SMTP-Variablen heißen in Compose anders als in Settings
Settings erwartet: SMTP_USERNAME, SMTP_FROM_EMAIL, SMTP_USE_TLS.
Compose setzt dagegen: SMTP_USER, SMTP_FROM, SMTP_TLS.
Reproduziert: Trotz gesetzter Compose-Namen bleibt der Benutzername leer; Standardabsender und Standard-TLS-Wert bleiben bestehen.
Betroffen sind insbesondere Systemmails wie Reset, Einladungen und geplante Alarmierung.
Korrektur: Environmentnamen zwischen Settings, Compose und Dokumentation angleichen.
Abnahme: Aus dem tatsächlichen Compose-Environment entstehen die erwarteten Settings; Reset- und Alarmmail funktionieren mit korrekter Anmeldung und Absender.
Fundstellen: app/config.py, Zeile 57; docker-compose.yaml, Zeile 89.
### F38 · P2 — Manifestfehler können einen endlosen Spinner erzeugen
pluginStore.setError setzt loading=false, lässt aber loaded=false.
PluginRouteRenderer betrachtet nur loaded und zeigt weiterhin den Spinner. Der Fehlerzustand wird dort nicht ausgewertet.
Korrektur: Laden, erfolgreich leeres Ergebnis und Fehler getrennt darstellen. Fehleranzeige und erneuten Versuch anbieten.
Abnahme: Manifest-API liefert 500 → verständliche Fehlermeldung statt Dauerspinner. Ein erneuter Versuch kann die Oberfläche wiederherstellen.
Fundstellen: frontend/src/store/pluginStore.ts, Zeile 118; PluginRouteRenderer.tsx, Zeile 74.
### F39 · P2 — DMS-Office-Editiersitzung verweist auf unpassende Endpunkte
create_edit_session akzeptiert Office-Dateien, gibt aber /preview als Dokument-URL zurück. Diese Route akzeptiert ausschließlich PDFs.
Die erzeugte Callback-URL /files/{id}/callback besitzt im DMS-Router keine Implementierung.
Damit ist die zurückgegebene Konfiguration noch kein funktionsfähiger Office-Bearbeitungsablauf.
Korrektur: Geeignete Download-/Speicher-Routen und einen begrenzten, geprüften Editorzugang anschließen. Bis dahin die Funktion nicht als betriebsbereit darstellen.
Abnahme: DOCX öffnen, ändern, speichern und erneut laden ergibt den aktualisierten Inhalt; fremde oder abgelaufene Editiersitzungen können nicht schreiben.
Fundstellen: app/plugins/builtins/dms/sharing_routes.py, Zeile 36; routes.py, Zeile 592.
### F40 · P2 — Plugin-Migrationen werden nur anhand des Dateinamens erkannt
Der Runner überspringt angewandte Dateien anhand von migration_file.
Wird das SQL nachträglich verändert, bleibt dies unbemerkt.
Die RLS-Einrichtung betrifft außerdem vor allem neu angelegte Tabellen. Der vollständige Sollzustand bereits vorhandener Plugin-Tabellen wird damit nicht garantiert.
Korrektur: Angewandte Migrationen unveränderlich machen und Hashes speichern. Zusätzlich tatsächliche Spalten, FKs, Indizes, Policies und Grants vergleichen.
Abnahme: Sowohl eine veränderte angewandte Migration als auch eine bestehende Tabelle mit falscher Policy werden erkannt.
Fundstelle: app/plugins/migration_runner.py, Zeilen 95 und 153.
### F41 · P2 — Stündliches Agentenlimit verwendet das falsche Zeitfenster
Der Code setzt: one_hour_ago = datetime.now(UTC).
Die Stunde wird nicht abgezogen.
Die anschließende Abfrage zählt deshalb nicht die Läufe der vergangenen Stunde, sondern nur Einträge ab dem aktuellen Zeitpunkt. Das konfigurierte Stundenlimit schützt so nicht zuverlässig vor wiederholten Starts.
F07 kann den Job bereits früher blockieren. Nach dessen Reparatur bleibt dieser weitere Fehler bestehen.
Korrektur: Zeitfenster korrekt berechnen und Kontingente atomar reservieren, damit parallele Starts die Begrenzung nicht umgehen.
Abnahme: Ausschöpfung des Stundenlimits blockiert weitere Starts; nach Ablauf des Fensters sind sie wieder erlaubt.
Fundstelle: app/plugins/builtins/automation/agent_runner.py, Zeile 68.
## Architektur- und Strukturbewertung
Die Zahl der Dateien oder ihre Länge ist nicht das Hauptproblem. Große Mail-/Kalender-Routen, PluginRegistry und Agenten-/Workflowmodule sind zwar Wartungsschwerpunkte. Ihr bloßes Aufteilen würde die beschriebenen Fehler jedoch nicht beheben.
Die grundlegenden Strukturprobleme sind:
1. Mehrere Stellen verwalten denselben Zustand. Pluginaktivität wird in Startup, Registry, API und Worker unterschiedlich interpretiert. Drei Mechanismen verändern das Schema.
2. Contracts garantieren zu wenig Verhalten. Exportierte ORM-Modelle, Any und dynamische Funktionszugriffe erlauben, Fachlogik außerhalb ihres Moduls neu zusammenzusetzen.
3. API- und Worker-Ausführung sind nicht gleichwertig. Kontext und Registrierungen werden stillschweigend als vorhanden angenommen, obwohl ein neuer Prozess oder eine neue Session beginnt.
4. Berechtigungsprüfungen liegen zu weit vom Seiteneffekt entfernt. UI-Sperren und angebotene Toollisten schützen nicht automatisch die ausgeführte Fachoperation.
5. Statusanzeigen sind teilweise von der tatsächlichen Funktion entkoppelt. „Aktiv“, „fertig“ oder „gesund“ kann trotz fehlender Registrierung, Persistenz oder Verarbeitung erscheinen.
Die bestehende Aufteilung sollte verbindlicher werden:
| Bereich | Verantwortung |
|---|---|
| Core | Identität, Mandant, Rechte, Transaktionen, Audit, Outbox, Jobs, gemeinsame Registries |
| Fachplugin | Fachmodell, Regeln, Services, API, UI und Pluginbeiträge |
| Pluginvertrag | Öffentliche, typisierte Fachoperationen und klar beschriebene Fähigkeiten |
| Ausführungskontext | Mandant, Benutzer, Herkunft, Delegation, aktuelle Rechte und Nachverfolgbarkeit |
| Workspace | Navigation, Ansichten, Defaults und einschränkende Abfragefilter |
| Suche | Gemeinsamer Schutzpfad für Hauptsuche, Vorschläge, Facetten, Ähnlichkeit und RAG |
| Betrieb | Migrationen, Sicherungen, Wiederherstellung und unabhängige Ausfallüberwachung |
UI, externe API, Agent und Worker sollten dieselben fachlichen Operationen mit demselben geprüften Kontext aufrufen. Fachänderung, Audit und Outbox gehören in eine gemeinsame Transaktion. Wiederholte Ereignisverarbeitung muss beherrscht werden.
## Datenmodell
Die gemessenen 129 Tabellen und 231 FK-Constraints sind der geladene ORM-Bestand. Daraus lässt sich nicht ableiten, welche Constraints auf dem Produktionsserver tatsächlich bestehen.
Für die Schemaabnahme müssen insbesondere nachgewiesen werden:
- Mandantenbezogene Referenzen können nicht auf fremde Mandanten zeigen.
- Globale Benutzeridentität und Mandantenmitgliedschaft haben getrennte Änderungsrechte.
- Softdelete und Eindeutigkeitsregeln passen zusammen.
- Default-Objekte sind im vorgesehenen Umfang eindeutig.
- Suchindex und fachliche Daten besitzen einen konsistenten Lebenszyklus.
Ein einfacher Fremdschlüssel auf eine UUID erzwingt nicht automatisch die Mandantengleichheit. Ob sämtliche notwendigen zusammengesetzten Fremdschlüssel bereits wirksam migriert sind, muss an einer vollständig aufgebauten PostgreSQL-Datenbank gemessen werden.
## Konzept: Business-KI-Plattform
Das Ziel bleibt sinnvoll: Fachmodule bringen ihre Domänenlogik selbst mit; die Plattform stellt einen verbindlichen technischen und sicherheitsbezogenen Rahmen bereit.
Was aktuell fehlt, ist vor allem der Nachweis vollständiger Abläufe über diese Grenzen hinweg.
Die ersten beiden Abnahmen sollten deshalb sein:
1. Kontaktanlage → Audit/Outbox → Worker → Suche → erlaubte KI-Abfrage.
2. Mailentwurf → Freigabe → einmaliger Versand → nachvollziehbares Ergebnis.
Damit werden Fachservice, Rechte, Transaktionen, Pluginbeiträge, Worker und KI gemeinsam geprüft.
## Kalender und spätere LeoRental-Integration
Der Kalendervertrag exportiert derzeit vor allem Modelle und Workspacebeiträge. Ein verbindlicher CalendarPort-/CalendarProjection-Vertrag ist im gelieferten Stand noch nicht zu finden.
Vor der späteren Rental-Anbindung müssen feststehen:
- Welches Fachmodul besitzt den Termin?
- Welche stabile Referenz verbindet Projekt und Kalendereintrag?
- Wie werden Änderungen, Storno und Löschung übertragen?
- Welches System ist führend?
- Wie werden Zeitzonen und Konflikte behandelt?
LeoCRM Calendar soll dabei die führende Plattformfähigkeit bleiben. Zuerst muss jedoch der vorhandene Serien-/Zeitraumfehler F27 behoben werden. Das ist eine offene Integrationsaufgabe; ein bereits geliefertes Rental-Modul wurde hier nicht geprüft.
## Import/Export
Die bestehende modulare Contract-Anbindung ist die richtige Richtung. Sie benötigt vollständige Autorisierung, Transaktionsregeln und dieselben Fachvalidierungen wie die normale API.
Die gemeinsame Basis kann Parsing, Mapping, Vorschau und Fehlerberichte übernehmen. Fachliche Regeln und Persistenzverhalten bleiben beim zuständigen Modul.
## Fehler und Lücken im bestehenden Bauplan
Die geplante Phase R mit Betriebssicherheit, Teststabilität, Schemaabgleich, E2E und Restore ist richtig priorisiert. Ihr Entwurf muss aber korrigiert werden:
| Planung | Problem | Erforderliche Änderung |
|---|---|---|
| ARQ überwacht Worker und Redis | Wenn ARQ oder Redis ausfällt, läuft auch der Wächter nicht mehr | Ergänzende Überwachung außerhalb dieser Ausfallkette |
| Alarm bei „installiert, aber inaktiv“ | Absichtliche Deaktivierung ist erlaubt | Gewünschten Zustand mit tatsächlicher Betriebsbereitschaft vergleichen |
| Queue-Länge als Workerbeweis | Leere Queue kann auch fehlenden Worker bedeuten | Heartbeat-Alter und tatsächliche Verarbeitung messen |
| Komplette Suite grün | Richtiger Ansatz, aber Mock-/Admin-Tests können reale Rechtefehler verdecken | Echte API-/Workerprozesse und tatsächliche Laufzeitrollen prüfen |
| Ein FK-Fix plus Migrationshash | Behebt unvollständige Modelldiscovery und konkurrierende Schemawege nicht | Vollständige Metadaten und eindeutige Schema-Verantwortung herstellen |
| E2E pro Kernfunktion | Erfolgreicher Normalfall prüft weder Rechteentzug noch Neustart | Mehrmandanten-, Rollen-, Fehler- und Wiederaufnahmeszenarien ergänzen |
| Monatlicher Restorejob | Ein Jobname beweist keine sichere Zielwahl oder vollständige Sicherung | Isoliertes Ziel und tatsächliche DB-/Dateiwiederherstellung nachweisen |
| „95 % Produktionsreife“ | Die fünf Kriterien ergeben keinen mathematischen Reifegrad | Konkrete Freigabekriterien und verbleibende Risiken dokumentieren |
Die genannten 914 Arbeitstage der Roadmap sind keine belastbare Aufwandsschätzung zur Behebung aller hier dokumentierten Fehler. Nach der ersten verifizierten Reparaturwelle sollte neu geschätzt werden.
Die Umsetzung gehört in die vorhandene PROGRESS.md-/Roadmap-Struktur. Es sollte kein zusätzlicher konkurrierender Status-Tracker entstehen.
## Empfohlene Reparaturreihenfolge
| Welle | Aufgaben | Erforderlicher Nachweis |
|---|---|---|
| 1: Sicherheitsgrenzen | F01F05, F10F11, F15, F20F21, F23, F30 | Keine unberechtigten Aktionen oder globalen Identitätsänderungen; garantiert isolierte Tests |
| 2: Ausführung verbinden | F06F09, F12F19, F31, F37, F40F41 | API und separater Worker funktionieren mit echten Rollen, Kontexten und Pluginzuständen |
| 3: Fachliche Integrität | F25F28, F32F35, F38F39 | Importzählung, Dateiidentität, Termine, Feldrechte und Kontextwechsel stimmen |
| 4: Betriebsfreigabe | F22, F24, F29, F36 und korrigierte Phase R | Frischinstallation, Upgrade, Vollsuite, E2E, unabhängiger Ausfallalarm und Restore bestehen |
Alarmierung und Testinfrastruktur können bereits während der ersten Welle vorbereitet werden.
## Was bereits sinnvoll gelöst ist
- Die Pluginrouten verwenden echte Routermechanismen für Parameter und Routenpriorität.
- Die generierte Komponentenmap ist aktuell.
- dms_file wird als Entityalias registriert; der Name ist für sich kein Fehler.
- Workspaces sind ausdrücklich als Arbeitskontext ohne Rechtevergabe modelliert.
- Einige Core-Workerjobs setzen den Mandantenkontext bereits korrekt.
- Logout leert den QueryClient bereits.
- Der Frontend-Produktionsbuild und TypeScript laufen erfolgreich.
Diese Verbesserungen sollten bei der Sanierung erhalten bleiben.
## Was für eine belastbare Freigabe noch nachgewiesen werden muss
1. PostgreSQL mit echten Rollen: Frischinstallation, Upgrade, RLS, Grants, Fremdschlüssel sowie Verhalten bei fehlendem Kontext.
2. Separater Worker: Pluginregistrierung, Outbox, Retry, Fehlerablage, Deaktivierung und Wiederaufnahme nach Neustart.
3. Browser mit mehreren Mandanten und Rollen: Suche, Kontextwechsel, Pluginfehler, Upload, Serientermine und KI-Freigaben.
4. Reale Testintegrationen: SMTP/IMAP, Storage, Editor und LLM-Anbieter einschließlich Fehlerfällen.
5. Wiederherstellung: Datenbank und Dateien auf isoliertem Ziel nach tatsächlichem Containerersatz.
6. Weitere getrennte Prüfungen: Aktueller Abhängigkeits-/CVE- und Lizenzscan, Lasttests und vollständige Prüfung der produktiven Konfiguration. Diese wurden durch das vorliegende Codeaudit nicht ersetzt.