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:
+77
-8
@@ -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).
|
**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.
|
**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)
|
- Outbox: 158 failed Events wochenlang unbemerkt (#380)
|
||||||
- Suite-Isolation und alembic-check-Blockade verhindern verlässliche Regressionsschutz-Gates
|
- Suite-Isolation und alembic-check-Blockade verhindern verlässliche Regressionsschutz-Gates
|
||||||
|
|
||||||
**95%-Definition (messbar, Betriebssicht) — die 5 Abnahmekriterien:**
|
**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.
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
### 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
|
- 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
@@ -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.
|
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):**
|
**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 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 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)
|
- **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)
|
||||||
|
|||||||
@@ -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 9–14 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 | F01–F05, F10–F11, F15, F20–F21, F23, F30 | Keine unberechtigten Aktionen oder globalen Identitätsänderungen; garantiert isolierte Tests |
|
||||||
|
| 2: Ausführung verbinden | F06–F09, F12–F19, F31, F37, F40–F41 | API und separater Worker funktionieren mit echten Rollen, Kontexten und Pluginzuständen |
|
||||||
|
| 3: Fachliche Integrität | F25–F28, F32–F35, F38–F39 | 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.
|
||||||
Reference in New Issue
Block a user