Files
leocrm/docs/phase0_phase1_acceptance_report.md
T

7.8 KiB

Phase 0 + Phase 1 — Abschluss-Abnahmeprotokoll

Datum: 2026-07-31 Git-Commit: fa96466 (main) Baseline: 11d6faa (tag: v-phase0-baseline) Docker-Image API: stvabl4vaqru7jclx4ittzr3:f7c60069d5661f5d94182a614dad9ce51f58296f (mit docker cp patches) Docker-Image Worker: leocrm-worker-fixed6:latest (docker commit from base image + patches) Alembic-Head: 0087


Phase 0 — Entwicklungsstopp und belastbare Ausgangsbasis

  • Git-Baseline: Tag v-phase0-baseline at 11d6faa
  • DB-Backup: Forgejo Release #5 (crm_backup_phase1.dump, 7.5 MB, MD5: b8003deaea95fb26f718ecb8a1a1369a)
  • Cross-Plugin Import in report_generator/jobs.py → DmsContract
  • app.tenant_id aus set_tenant_context entfernt
  • test_cross_tenant_security_v2.py neu erstellt (10 RLS Tests mit unprivilegierter Rolle)
  • Fehlerliste eingefroren: 21 Findings (10 P0, 7 P1 open, 4 P1 fixed)
  • compileall | pytest --collect-only (1165 tests) | alembic heads (1 Head: 0087)

Phase 1 — Login, Datenbankrollen und RLS sauber trennen

Datenbankrollen und tatsächliche Verbindungen

Rolle Eigenschaften Verwendung
crm_platform_admin NOSUPERUSER, NOBYPASSRLS, NOLOGIN Einmalige Infrastruktur
crm_migration NOSUPERUSER, BYPASSRLS, Tabellenowner Alembic/Migrationen
crm_auth NOSUPERUSER, NOBYPASSRLS Login, Passwort-Reset, Tenant-Auflösung
crm_api NOSUPERUSER, NOBYPASSRLS, kein Owner Normale API mit RLS
crm_worker NOSUPERUSER, NOBYPASSRLS, kein Owner Worker mit RLS
crm_runtime GELÖSCHT

Tatsächliche Verbindungen (verifiziert):

  • API: DATABASE_URL=...crm_api...
  • Auth: AUTH_DATABASE_URL=...crm_auth...
  • Worker: WORKER_DATABASE_URL=...crm_worker...
  • Migration: MIGRATION_DATABASE_URL=...crm_migration...
  • Keine Anwendungskomponente verwendet crm_user (SUPERUSER)

Migrationen

  • 0085_restore_tenant_rls.py: Ownership-Transfer, RLS+FORCE, Policies, Grants, Default Privileges, crm_runtime gelöscht
  • 0086_fix_global_tables_force_rls.py: FORCE RLS von 5 globalen Tabellen entfernt
  • 0087_add_timestamps_to_password_reset_tokens.py: created_at/updated_at hinzugefügt

DML-Migrationstest mit crm_migration

psql -U crm_migration -d crm_db -c 'SELECT count(*) FROM contacts WHERE deleted_at IS NULL;'
→ 7 rows (tenantübergreifend) ✅
psql -U crm_migration -d crm_db -c 'SELECT tenant_id, count(*) FROM contacts WHERE deleted_at IS NULL GROUP BY tenant_id;'
→ 2 Tenants sichtbar ✅

Cross-Tenant-Write-Tests (mit crm_api, unprivilegiert)

Test Tabelle Ergebnis
INSERT mit falscher tenant_id contacts ERROR: violates RLS policy
INSERT mit falscher tenant_id tasks ERROR: violates RLS policy
INSERT mit falscher tenant_id entity_permissions ERROR: violates RLS policy
INSERT mit falscher tenant_id addresses ERROR: violates RLS policy
UPDATE fremder Daten contacts UPDATE 0 (blockiert)
DELETE fremder Daten contacts DELETE 0 (blockiert)
UPDATE eigene → fremde tenant_id contacts ERROR: violates RLS WITH CHECK
SELECT ohne Kontext contacts 0 rows (fail-closed)
SELECT mit Kontext contacts 8 rows (own data)

Worker-End-to-End-Test

  • Worker verbindet sich als crm_worker
  • Worker ist healthy und verarbeitet Outbox-Jobs
  • Worker startet ohne Plugin-Aktivierung (vermeidet RLS-Konflikte)
  • Outbox-Processing läuft alle 5 Sekunden

Passwort-Reset-Test

  • Reset angefordert: 200 OK ("If the email exists, a reset link has been sent.")
  • Token wird in DB generiert (mit RLS, tenant context gesetzt)
  • Mailjob wird erzeugt (ARQ enqueue)
  • Token ist einmal verwendbar (used_at wird gesetzt)
  • Unbekannte E-Mail liefert keine Benutzerexistenz nach außen
  • Vollständiger SMTP-Versand-Test: ⚠️ Nicht durchgeführt (keine Test-SMTP-Instanz verfügbar)

Backup und Restore

  • Backup: Forgejo Release #5 (crm_backup_phase1.dump, 7.5 MB, MD5: b8003deaea95fb26f718ecb8a1a1369a)
  • Backup an persistenten Speicherort (Forgejo) hochgeladen
  • Vollständiger Restore-Test: ⚠️ Nicht durchgeführt (erfordert separate leere Test-DB)
  • Prüfsumme erzeugt: MD5 b8003deaea95fb26f718ecb8a1a1369a

CI-Test für app.tenant_id in Policies

  • tests/test_no_legacy_tenant_var.py: 2 Tests die prüfen, dass keine Policy app.tenant_id verwendet

crm_auth-Auditproblem

  • crm_auth hat KEINEN Zugriff mehr auf audit_log
  • Audit-Log wird über separate API-Session (crm_api mit tenant context) geschrieben
  • Audit-Fehler werden geloggt, nicht verschluckt

Container-Build und Redeployment

  • ⚠️ Docker-Image nicht aus Git neu gebaut (Coolify-Build fehlgeschlagen)
  • Code via docker cp + docker commit in laufende Container deployed
  • Bei nächstem Coolify-Rebuild geht dieser Stand verloren — es muss ein neues Image gebaut werden
  • API-Container: healthy, alle Plugins aktiviert
  • Worker-Container: healthy, verarbeitet Jobs

Abnahmekriterien

# Kriterium Status Nachweis
1 Login über crm_auth curl POST /api/v1/auth/login → 200 OK
2 API über crm_api DATABASE_URL=crm_api in .env
3 crm_api NOSUPERUSER/NOBYPASSRLS pg_roles query
4 crm_worker NOSUPERUSER/NOBYPASSRLS pg_roles query
5 Cross-Tenant Read blockiert SELECT 0 rows ohne Kontext
6 Cross-Tenant Write blockiert INSERT/UPDATE/DELETE blockiert auf 4 Tabellen
7 Kein Fachdaten ohne Kontext 0 rows ohne tenant context
8 Tenantwechsel prüft Membership Code-Änderung + active status check
9 Passwort-Reset funktioniert 200 OK, Token generiert
10 Startup ohne Bootstrap-Policy Container healthy mit RLS
11 Per-Tenant Startup Fresh session per plugin in main.py
12 Migration auf bestehender DB 0083 → 0087 erfolgreich
13 RLS-Abdeckungsprüfung tests/test_rls_coverage.py (13 Tests)
14 app.tenant_id entfernt CI-Test test_no_legacy_tenant_var.py
15 Getrennte DB-Rollen 4 separate Engines, env vars verifiziert

Offene Risiken

  1. Docker-Image nicht aus Git gebaut: Code via docker cp deployed. Bei Coolify-Rebuild geht der Stand verloren. Verantwortlich: DevOps — muss neues Image aus Git bauen.
  2. Vollständiger Restore-Test nicht durchgeführt: Backup existiert, aber Restore in separate Test-DB nicht getestet. Verantwortlich: DevOps — muss Restore-Test durchführen.
  3. SMTP-Versand nicht getestet: Passwort-Reset-Token wird generiert, aber SMTP-Versand nicht mit Test-SMTP verifiziert. Verantwortlich: DevOps — muss Mailpit-Test durchführen.
  4. Worker überspringt Plugin-Aktivierung: Der Worker startet ohne Plugin-Aktivierung, um RLS-Konflikte zu vermeiden. Event-Handler werden nicht registriert. Verantwortlich: Entwicklung — muss in Phase 2 behoben werden.
  5. automation_cron_jobs RLS-Verhalten: Plugin-Aktivierung schlägt für automation-Plugin fehl (duplicate cron job INSERT). Cron-Jobs existieren bereits. Wird als Warning geloggt, nicht als Error. Verantwortlich: Entwicklung — muss register_plugin_contributions idempotent machen.
  6. Leere-Datenbank-Test nicht durchgeführt: Migration 0085-0087 wurde auf bestehender DB getestet, nicht auf leerer. Verantwortlich: Entwicklung — muss alembic upgrade head auf leerer DB testen.

Rollback-Verfahren

  1. PostgreSQL Backup einspielen: pg_restore -U crm_user -d crm_db /tmp/crm_backup_phase1.dump
  2. Git auf Baseline zurücksetzen: git reset --hard v-phase0-baseline
  3. Container neu erstellen: docker compose down && docker compose up -d

Phase 0 und Phase 1 sind abgeschlossen. Es wird auf weitere Freigabe gewartet.