Files
leocrm/docs/ABSCHLUSSBERICHT_PHASE0_PHASE1.md
T

477 lines
19 KiB
Markdown
Raw Normal View History

# LeoCRM — Abschlussbericht Phase 0 + Phase 1 und vollständiger Sanierungsplan
**Datum:** 2026-08-01
**Git-Commit:** 733fa1c (main)
**Alembic-Head:** 0090
**Produktion:** https://crm.media-on.de — healthy
---
## 1. Aktueller Stand
### 1.1 Abgenommene Gates
| Gate | Beschreibung | Status |
|------|-------------|--------|
| Gate 1 | Reproduzierbares Coolify-Deployment | ✅ Bestanden |
| Gate 2 | Neuinstallation auf leerer Datenbank | ✅ Bestanden |
| Gate 3 | Vollständiger Restore-Test | ✅ Bestanden |
| Gate 4 | Passwort-Reset end-to-end | ✅ Bestanden |
| Gate 5 | Worker und Eventhandler | ✅ Bestanden |
### 1.2 Produktionsstand
| Komponente | Wert |
|-----------|------|
| Git-Commit | 733fa1c |
| Docker-Image | stvabl4vaqru7jclx4ittzr3:733fa1c |
| API-Container | stvabl4vaqru7jclx4ittzr3-201530032526 — healthy |
| Worker-Container | leocrm-worker — healthy |
| Alembic-Head | 0090 |
| Tabellen | 124 |
| RLS-Tabellen | 108 (alle Tenant-Tabellen) |
| RLS-Policies | 112 |
| Legacy app.tenant_id Policies | 0 |
| DB-Rollen | 5 (crm_platform_admin, crm_migration, crm_auth, crm_api, crm_worker) |
| crm_api | NOSUPERUSER, NOBYPASSRLS — API-Laufzeit |
| crm_auth | NOSUPERUSER, NOBYPASSRLS — Login/Authentifizierung |
| crm_worker | NOSUPERUSER, NOBYPASSRLS — Worker-Laufzeit |
| crm_migration | NOSUPERUSER, BYPASSRLS — Migrationen und DDL |
| ~~crm_runtime~~ | Gelöscht |
### 1.3 Datenbankrollen-Architektur
```
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL (crm_db) │
├─────────────────────────────────────────────────────────────┤
│ crm_user (POSTGRES_USER, SUPERUSER) │
│ └── Nur für Bootstrap und DB-Initialisierung │
│ │
│ crm_migration (NOSUPERUSER, BYPASSRLS, Tabellenowner) │
│ ├── Alembic-Migrationen (00010090) │
│ ├── Plugin-Migrationen (DDL) │
│ └── Datenmigrationen (tenantübergreifend) │
│ │
│ crm_auth (NOSUPERUSER, NOBYPASSRLS) │
│ ├── Login/Logout │
│ ├── Tenant-Auflösung │
│ ├── User/Tenant-Membership │
│ └── Password-Reset-Token │
│ │
│ crm_api (NOSUPERUSER, NOBYPASSRLS, kein Owner) │
│ ├── Normale API-Abfragen (SELECT, INSERT, UPDATE, DELETE) │
│ ├── Audit-Log (über separate Session mit Tenant-Kontext) │
│ └── Keine DDL-Rechte │
│ │
│ crm_worker (NOSUPERUSER, NOBYPASSRLS, kein Owner) │
│ ├── ARQ-Background-Jobs │
│ ├── Outbox-Processing (per-Tenant mit RLS-Kontext) │
│ ├── Cron-Jobs (scheduler_tick, tasks_due_reminder) │
│ └── Event-Handler für aktive Plugins │
└─────────────────────────────────────────────────────────────┘
```
### 1.4 RLS-Architektur
- **Fail-closed:** Kein Tenant-Kontext = kein Zugriff auf Tenant-Daten
- **Policy:** `USING/WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid)`
- **FORCE ROW LEVEL SECURITY** auf allen 108 Tenant-Tabellen
- **Scoped to:** `crm_api, crm_worker` (nicht PUBLIC)
- **21 globale Tabellen** ohne RLS: users, tenants, sessions, plugins, etc.
- **0 legacy Policies** mit `app.tenant_id` (alle durch `app.current_tenant_id` ersetzt)
### 1.5 Verifizierte Sicherheitsnachweise
| Test | Ergebnis |
|------|----------|
| RLS ohne Tenant-Kontext | 0 rows (fail-closed) ✅ |
| RLS mit Tenant A | Nur Tenant-A-Daten ✅ |
| RLS mit Tenant B | Nur Tenant-B-Daten ✅ |
| Cross-Tenant INSERT | Blockiert (RLS violation) ✅ |
| Cross-Tenant UPDATE | 0 rows affected ✅ |
| Cross-Tenant DELETE | 0 rows affected ✅ |
| WITH CHECK (tenant_id ändern) | Blockiert ✅ |
| DDL durch crm_api | Blockiert (permission denied) ✅ |
| Login über crm_auth | 200 OK ✅ |
| Passwort-Reset end-to-end | Email zugestellt, Token einmalig, Session widerrufen ✅ |
| Leere DB-Installation | 124 Tabellen, 0090, keine manuellen Eingriffe ✅ |
| Restore + Upgrade | 0086 → 0090, Datenintegrität erhalten ✅ |
### 1.6 Durchgeführte Code-Änderungen (Phase 0 + Phase 1)
| Commit | Beschreibung |
|--------|-------------|
| v-phase0-baseline | Git-Baseline bei 11d6faa |
| 4a5c905 | P0-Fix: Plugin-Migrationen über Migrations-Engine |
| 1029613 | Migration 0085: crm_runtime DROP ROLE Fix |
| 48ddd78 | Mail Plugin Migration 0009 Fix |
| 569476b | prestart.sh: DB-Rollen-Passwörter setzen |
| b5191f0 | Migration 0089: sessions.updated_at |
| 010ef44 | 40 Migrationen idempotent gemacht (IF NOT EXISTS) |
| 89b775b | Migration 0090: Legacy policies fix + seed_admin.py rewrite |
| cea21ff | Gate 5: Worker event handlers + per-tenant outbox |
| 94847ea | PluginModel.active Fix (worker crash) |
| 733fa1c | Gate 3: Restore-Test Doku |
### 1.7 Migrationen
| Migration | Beschreibung |
|-----------|-------------|
| 0085 | RLS-Restore: Rollen, Policies, Grants, FORCE RLS auf 108 Tabellen |
| 0086 | Globaltabellen-Korrektur: FORCE RLS entfernt von 5 globalen Tabellen |
| 0087 | password_reset_tokens: created_at, updated_at |
| 0088 | Auth RLS policies: password_reset_tokens, audit_log für crm_auth |
| 0089 | sessions: updated_at Spalte |
| 0090 | Legacy app.tenant_id policies auf _old Tabellen fixen |
### 1.8 Offene Risiken
| # | Risiko | Bewertung |
|---|--------|-----------|
| 1 | Coolify-API-Token im Chat verwendet | Mittel — Token widerrufen und neu erstellen |
| 2 | Test-DB-Passwort (TestDbPass2026) | Niedrig — nur in Testumgebung verwendet |
| 3 | Worker-Env-Variablen manuell gesetzt | Mittel — bei Coolify-Rebuild verloren, muss in Coolify .env dokumentiert werden |
| 4 | pg_restore --no-acl überspringt Grants | Niedrig — Restore-Prozedur muss Grants neu anwenden |
| 5 | DMS-Dateien nicht im Restore-Test | Niedrig — Storage-Volume separat sichern |
| 6 | Bootstrap über crm_user (SUPERUSER) | Niedrig — akzeptiert für Gate 2, später auf crm_migration umstellen |
---
## 2. Vollständiger Sanierungsplan — Verbleibende Phasen
### Phase 2 — Datenintegrität
**Ziel:** Konsistente Fremdschlüssel, keine verwaisten Datensätze, saubere Sequenzen.
**Aufgaben:**
1. Fremdschlüssel-Constraints prüfen und fehlende ergänzen
2. Verwaiste Datensätze identifizieren und bereinigen
3. Sequenzen synchronisieren (sync mit MAX(id))
4. ON DELETE CASCADE prüfen und dokumentieren
5. Datenbank-Integritäts-Test-Suite erstellen
6. Migration für fehlende FK-Constraints erstellen
**Abnahmekriterien:**
- Alle FK-Constraints vorhanden und gültig
- Keine verwaisten Datensätze
- Alle Sequenzen synchron
- Integritäts-Tests grün
**Aufwand:** 816 Stunden
---
### Phase 3 — Plugin-Lifecycle
**Ziel:** Saubere Plugin-Aktivierung, Deaktivierung und Migration ohne Race-Conditions.
**Aufgaben:**
1. Plugin-Aktivierung: Prüfen ob bereits aktiv, idempotent machen
2. Plugin-Deaktivierung: Event-Handler deregistrieren, Cron-Jobs entfernen
3. Plugin-Migration: Versionierung und Rollback
4. Tenant-Plugin-Aktivierung: Per-Tenant mit Tenant-Kontext
5. Plugin-Abhängigkeiten: Load-Order respektieren
6. Plugin-Router: Nur in API registrieren, nicht im Worker
7. Plugin-Event-Handler: Nur für aktive Plugins registrieren
8. Test: Plugin aktivieren → deaktivieren → reaktivieren
**Abnahmekriterien:**
- Plugin-Aktivierung ist idempotent
- Plugin-Deaktivierung deregistriert Event-Handler
- Plugin-Migrationen haben Versionierung
- Tenant-Plugin-Aktivierung funktioniert mit RLS
- Keine Race-Conditions bei paralleler Aktivierung
**Aufwand:** 610 Stunden
---
### Phase 4 — Sichere KI-Delegation
**Ziel:** KI-Agenten können sicher und kontrolliert Aufgaben ausführen.
**Aufgaben:**
1. Delegation-Contract definieren (Input, Output, Permissions)
2. KI-Agent-Permissions: Tenant-scoped, keine Cross-Tenant
3. KI-Agent-Session: Separate Session mit Tenant-Kontext
4. KI-Agent-Limits: Max executions, timeout, rate-limit
5. KI-Agent-Audit: Alle Aktionen protokollieren
6. KI-Agent-Rollback: Fehlerhafte Aktionen zurückrollen
7. KI-Agent-Approval: Menschliche Freigabe für kritische Aktionen
8. Test: KI-Agent erstellt Kontakt → aktualisiert → löscht (nur im eigenen Tenant)
**Abnahmekriterien:**
- KI-Agent kann nur im zugewiesenen Tenant arbeiten
- KI-Agent-Aktionen sind auditiert
- KI-Agent-Timeout und Rate-Limit funktionieren
- KI-Agent kann keine Cross-Tenant-Daten lesen/schreiben
- Kritische Aktionen erfordern Freigabe
**Aufwand:** 1224 Stunden
---
### Phase 5 — Transactional Outbox
**Ziel:** Zuverlässige Event-Zustellung ohne Events zu verlieren.
**Aufgaben:**
1. Outbox-Claim: Per-Tenant mit Tenant-Kontext (bereits implementiert in Gate 5)
2. Outbox-Event-Consumer: Erwartete Consumer pro Event registrieren
3. Outbox-Dead-Letter: Events nach max_attempts in DLQ
4. Outbox-Monitoring: Backlog-Metriken, Failed-Jobs-Alert
5. Outbox-Retry: Exponentieller Backoff (bereits implementiert)
6. Outbox-Idempotency: consumer_inbox Check (bereits implementiert)
7. Outbox-Delivery-Guarantee: At-least-once, consumer must be idempotent
8. Test: Event erzeugen → Worker verarbeitet → Consumer ausführen → Idempotency prüfen
**Abnahmekriterien:**
- Events gehen nicht verloren (auch bei Worker-Crash)
- Events werden mindestens einmal zugestellt
- Consumer sind idempotent
- Dead-Letter-Queue funktioniert
- Backlog-Monitoring funktioniert
**Aufwand:** 1424 Stunden
---
### Phase 6 — Workspaces
**Ziel:** Mehrere unabhängige Workspaces pro Benutzer, pro Browser-Tab.
**Aufgaben:**
1. Workspace-Model: UUID, Name, Owner, Tenant, Config
2. Workspace-Widget-Config: Eigene UUID, Position, Größe, Konfiguration
3. Workspace-Store: Zentraler React/Zustand-Store
4. Workspace-Switcher: Sofortiger Wechsel ohne Page-Reload
5. sessionStorage als Persistenz (nicht mehrere unabhängige Hook-Zustände)
6. Sidebar reagiert sofort auf Workspace-Wechsel
7. Leerer Workspace zeigt keine Module
8. Direkte Links auf berechtigte Fachobjekte funktionieren
9. Mehrfach-Widgets: Gleicher widget_key kann mehrfach vorkommen
10. Workspace-Manager: Kann nur eigenen Workspace konfigurieren
11. Cross-Tenant-Zuweisungen unmöglich
12. Ausgeblendetes Modul erscheint nicht in Navigation
**Abnahmekriterien:**
1. Einkauf und Verkauf stellen dasselbe Kontakte-Modul unterschiedlich dar
2. Kalender unterscheiden sich pro Workspace
3. Workspacekonfiguration macht keine unberechtigten Daten sichtbar
4. Zwei Browser-Tabs können unterschiedliche Workspaces verwenden
5. Derselbe Widget-Typ kann mehrfach vorkommen
6. Workspace-Manager kann nur seinen Workspace konfigurieren
7. Workspace-Manager kann keine Rechte ändern
8. Cross-Tenant-Zuweisungen sind unmöglich
9. Ein ausgeblendetes Modul erscheint nicht in der Navigation
10. Direkte berechtigte Objektlinks bleiben erreichbar
**Aufwand:** 3050 Stunden
---
### Phase 7 — DMS und Attachments
**Ziel:** Konsistenter Storage- und Berechtigungspfad für alle Dateiabläufe.
**Aufgaben:**
1. Attachment-Upload streamend implementieren (kein vollständiges await file.read())
2. Download über Storage-Streaming
3. Alte Attachments nach files + entity_attachments migrieren
4. Deduplikation nur tenantlokal
5. Physische Datei nur löschen wenn keine Referenzen existieren
6. Technische Felder (storage_path, Hashwerte) nicht an Clients ausgeben
7. Entity-Typen konsistent registrieren
8. Größenlimit, MIME-Prüfung und Hashing zentralisieren
9. Lokales Storage und S3 identisch behandeln
10. Keine Cross-Tenant-Dateireferenzen
11. Optional: Malware-Scan
**Abnahmekriterien:**
- Große Dateien verursachen keine mehrfache RAM-Belegung
- Lokaler und S3-Storage funktionieren
- Bestehende Attachments bleiben erhalten
- Tenantfremde Dateien können nicht referenziert werden
- Aktive Dateien werden nicht versehentlich physisch gelöscht
**Aufwand:** 1220 Stunden
---
### Phase 8 — Verbleibende Sicherheits- und Betriebsfehler
**HTML:**
1. Alle Mail-, Signatur- und HTML-Pfade serverseitig mit derselben Sanitization behandeln
**Gäste:**
2. Tenant-Slug verpflichtend oder eindeutige Tenant-Auswahl
3. Gleiche E-Mail in mehreren Tenants darf Login nicht zum Absturz bringen
4. Sofortiger Session-Widerruf
5. Einladungstoken nur gehasht, einmalig, mit Ablaufzeit und Widerruf
**Webhooks:**
6. SSRF-Schutz beibehalten
7. DNS-Ziel beim tatsächlichen Connect erneut prüfen
8. Redirects begrenzen oder deaktivieren
9. Secrets verschlüsselt speichern, nur einmal bei Erstellung anzeigen
10. Interne und private Netze blockieren
11. Retry und Fehlerstatus implementieren
**Healthchecks:**
12. Trennen: /health/live, /health/ready, /metrics
13. Readiness muss bei nicht verfügbaren Abhängigkeiten HTTP 503 liefern
**Build:**
14. Entfernen: `npm ci || npm install` → Verwenden: `RUN npm ci`
15. Python-Abhängigkeiten exakt pinnen oder über Lockdatei verwalten
**Report-Worker:**
16. Keine direkten Cross-Plugin-Imports
17. DMS nur über Contract oder Core-Service
18. PDF-Erstellung nur im Worker
19. Synchronen API-Reportpfad entfernen oder stark begrenzen
20. Read-only-Dateisystem, CPU- und RAM-Limits, kein allgemeiner Netzwerkzugriff
**Aufwand:** 1018 Stunden
---
### Phase 9 — CI und verbindliche Quality Gates
**Ziel:** Jeder Merge muss folgende Gates bestehen:
| # | Gate |
|---|------|
| 1 | Python Compile |
| 2 | Ruff |
| 3 | Python Typecheck |
| 4 | Vollständige Testcollection |
| 5 | Pytest |
| 6 | Frontend Typecheck |
| 7 | Vitest |
| 8 | Frontend Production Build |
| 9 | Cross-Plugin-Importprüfung |
| 10 | SQL-Injection-Prüfung |
| 11 | Jinja-Sandbox-Test |
| 12 | RLS-Variablenprüfung |
| 13 | RLS-Abdeckungsprüfung |
| 14 | Cross-Tenant-Integrationstest |
| 15 | Test mit echter crm_api-Rolle |
| 16 | Login-Test mit crm_auth |
| 17 | Alembic auf leerer Datenbank |
| 18 | Upgrade von vorherigem Release |
| 19 | Container Smoke Test |
| 20 | API- und Worker-Healthcheck |
| 21 | Dependency Scan |
| 22 | Prüfung auf unerlaubte Bootstrap-RLS-Policies |
| 23 | Prüfung der Tabellenowner |
| 24 | Prüfung auf genau einen Alembic-Head |
Kein Gate darf über `|| true`, `allow_failure` oder `continue-on-error` ignoriert werden.
**Aufwand:** 1628 Stunden
---
### Phase 10 — Backup, Restore, Monitoring und Pilotfreigabe
**Backup:**
1. PostgreSQL, DMS/Object Storage, Secrets, Verschlüsselungsschlüssel, Anwendungsversion, Alembic-Stand
**Restore:**
2. PostgreSQL wiederherstellen → DMS wiederherstellen → Secrets → alembic current → alembic upgrade head → App/Worker starten → Login testen → Datensatzanzahlen vergleichen → RLS testen → Dateien stichprobenartig öffnen → Outbox/Worker testen → Workspace prüfen
**Monitoring:**
3. Externes Monitoring für: API Liveness, API Readiness, Worker Heartbeat, Redis, PostgreSQL, Outbox-Rückstau, Failed Jobs, Fehlerrate, Antwortzeit, DB-Pool-Auslastung, Storage-Erreichbarkeit
**Pilotfreigabe:**
4. Erst freigeben wenn:
- alle P0- und P1-Tests grün
- Cross-Tenant-Tests mit echter Runtime-Rolle grün
- Backup und Restore praktisch getestet
- KI-Delegation auditiert funktioniert
- mindestens ein kompletter Geschäftsablauf getestet
- keine offenen kritischen Findings
- App, Worker und Migrationen getrennte Rollen verwenden
- RLS auf allen Fachtabellen aktiv und erzwungen
**Aufwand:** 1220 Stunden
---
## 3. Gesamtschätzung
### Reine Codeänderungen
| Phase | Beschreibung | Aufwand |
|------|-------------|---------|
| 0+1 | Ausgangsbasis, Login, DB-Rollen, RLS | ✅ Abgeschlossen |
| 2 | Datenintegrität | 816 h |
| 3 | Plugin-Lifecycle | 610 h |
| 4 | Sichere KI-Delegation | 1224 h |
| 5 | Transactional Outbox | 1424 h |
| 6 | Workspaces | 3050 h |
| 7 | DMS und Attachments | 1220 h |
| 8 | Sicherheitsreste und Build | 1018 h |
| 9 | CI und Quality Gates | 1628 h |
| 10 | Backup, Restore, Monitoring | 1220 h |
| **Gesamt** | **Verbleibend** | **120210 h** |
### Einschließlich Migrationen, Tests und Deployment
| Bereich | Aufwand |
|----------|---------|
| Verbleibende Codeänderungen | 120210 h |
| Tests, Fehlerkorrekturen, Deployment | +3050 h |
| **Gesamt verbleibend** | **150260 h** |
### Pilotfähiger technischer Kern (ohne vollständige Workspaces)
| Bereich | Aufwand |
|----------|---------|
| Datenintegrität | 816 h |
| Plugin-Lifecycle | 610 h |
| Sichere KI-Delegation | 1224 h |
| Outbox | 1424 h |
| DMS und Attachments | 1220 h |
| Sicherheitsreste und Build | 1018 h |
| CI | 1628 h |
| Backup, Restore, Monitoring | 1220 h |
| **Gesamt (ohne Workspaces)** | **90160 h** |
### Vollständige Workspaces zusätzlich
| Bereich | Aufwand |
|----------|---------|
| Workspaces | 3050 h |
| **Gesamt einschließlich Workspaces** | **120210 h** |
---
## 4. Empfohlene Reihenfolge
1. **Phase 2** (Datenintegrität) — Fundament für alle weiteren Phasen
2. **Phase 3** (Plugin-Lifecycle) — Saubere Basis für Plugin-Funktionen
3. **Phase 5** (Outbox) — Bereits teilweise implementiert, fertigstellen
4. **Phase 4** (KI-Delegation) — Baut auf Outbox auf
5. **Phase 7** (DMS) — Unabhängig, parallel möglich
6. **Phase 8** (Sicherheitsreste) — Unabhängig, parallel möglich
7. **Phase 9** (CI) — Nach allen Code-Phasen, vor Pilot
8. **Phase 6** (Workspaces) — Größter Aufwand, nach Kern-Stabilität
9. **Phase 10** (Backup, Monitoring, Pilot) — Als Abschluss
---
## 5. Nächste Schritte
1. **Freigabe Phase 2** — Nach Abnahme dieses Berichts
2. **Coolify-API-Token widerrufen** — Token wurde im Chat verwendet
3. **Produktions-Passwörter rotieren** — Falls noch nicht geschehen
4. **Coolify .env dokumentieren** — WORKER_DATABASE_URL und MIGRATION_DATABASE_URL für Worker-Container
5. **Restore-Prozedur dokumentieren** — Grants müssen nach pg_restore neu angewendet werden
---
*Dieser Bericht wurde am 2026-08-01 erstellt und entspricht dem Stand Commit 733fa1c auf main.*