Files
leocrm/docs/fix-plan-v3.md
T

39 KiB
Raw Blame History

LeoCRM — Reparaturplan v3 (komplett überarbeitet)

Erstellt: 2026-08-23 · v3 nach Plattform-Klarstellung Basis: v1 (leocrm-fix-plan.zip gesichert) + ChatGPT-Review (16 Punkte) + eigene Code-Verifizierung Grundsatz: Erst Plattformbasis reparieren, dann Plugin-Grenze, dann Frontend, DANACH erst Einzelbugs. Alle ARCH-IDs + neue Findings sind explizit einem Block zugeordnet. Nichts hängt mehr in der Luft.

LEITBILD (bindend für alle Blöcke)

Dieses Projekt ist KEINE CRM-Anwendung, sondern eine Business- & Agenten-Plattform:

  • Der Core ist ein Plattform-Kernel: Multi-Tenant, Auth/Permissions, Plugin-System, Events, Workflows, AI-Agent-Laufzeit.
  • Fachmodule (CRM heute, ERP-Module später) sind ausschließlich Plugins — nachrüstbar, deaktivierbar, ohne Core-Änderung.
  • Agenten sollen die Software VOLL bedienen können — jede Plugin-Funktion muss automatisch agenten-bedienbar werden, ohne dass der Core angefasst wird.
  • Konsequenz als Prüfregel für JEDEN Task: "Kann ein neues Fachmodul diese Funktion nutzen, ohne eine Core-Datei zu ändern?" — Wenn nein, ist der Task falsch geschnitten.

0. Korrigierte Statistik (v1 war falsch)

Angabe v1 (falsch) v2 (korrekt)
Bugs gesamt 148 139 eindeutige IDs
Offen 113 108 eindeutig (31 High / 56 Medium / 21 Low nach Duplikat-Entfernung)
Gefixt 20 19 (BUG-073 war doppelt)
Kein Bug 12 12

Entfernte Duplikate: BUG-073 (Gefixt-Liste), BUG-074/075/076/077 (Phase 2 doppelt), BUG-078 (Phase 3 doppelt).

1. Was gegenüber v1 geändert wurde (ChatGPT-Review, alle 16 Punkte)

  1. Statistik korrigiert (siehe oben), alle Duplikate entfernt.
  2. Phase 0 (Agent Zero Config) gestrichen — deepseek-v4-pro/ministral/Vision gehören nicht in diesen Plan.
  3. Reihenfolge gedreht: Plattformbasis ist jetzt Block A (zuerst), Einzelbugs folgen in D.
  4. Contacts-Ansatz korrigiert: NICHT "NUR Plugin" (v1-Fehler), sondern Core-Plugin (is_core=True, nicht deaktivierbar) OHNE Core-Hardcoding.
  5. Workspace ARCH-004 hat jetzt eine konkrete Fixphase (C2) mit verifizierter Ursache.
  6. Checker-Fix erweitert: nicht nur Default, sondern ZWEI Bugs (Default + ValueError-Crash bei relativen Pfaden).
  7. Regel aufgenommen: KEINE globalen commit→flush-Transformationen; nur nachgewiesene Einzelfälle.
  8. Regel aufgenommen: Integration/Install-Tests ausschließlich über Alembic-Migrationen, kein pauschales Base.metadata.create_all.
  9. Frontend-Permission-Reihenfolge fixiert: ERST Backend-Felder FrontendPageRoute.permission + FrontendMenuItem.permission (C1), DANN statische Routes abbauen (C4) — sonst Permission-Verlust.
  10. Settings + Dashboard als eigene Fixpunkte ergänzt (C6/C7) — fehlten in v1 komplett.
  11. Lifecycle-Findings konkret eingeplant: ARCH-012, 013, 015, 033037, 042 jetzt einzeln in Block A aufgeführt.
  12. God Objects raus aus dem Pflichtumbau → separater Track S1.
  13. i18n raus aus dem Basisumbau → separater Track S2.
  14. Secrets/SQL-Injection = TRIAGE statt Pauschalfix (D4): jede Stelle klassifizieren, nur echte Findings fixen.
  15. Marathon-Zahlen = Triage-Cluster (D5), keine Bug-Anzahl, Root-Cause-Clustering.
  16. Verifikations-Gates in JEDEN Block integriert (nicht erst am Ende) + 10 verpflichtende Integrationstests vor dem Löschen alter Wege.

2. Neue Findings (nicht in v1, heute code-verifiziert)

ID Finding Ort Block
SYNTAX-001 SyntaxError Zeile 413 (expected 'except' or 'finally') in UNCOMMITTED Änderung — App startet mit aktuellem Working-Tree nicht. Einrückung von logger.info + except wurde beim if-Guard-Verschub nicht mitgenommen app/plugins/builtins/automation/plugin.py 0
CHECK-002 Checker crasht mit ValueError: 'app/ai/action_mapper.py' is not in the subpath bei relativen Pfaden — check_file() ruft relative_to(PROJECT_ROOT) ohne Resolution scripts/check_cross_plugin_imports.py:113 0
DT-001 Naive datetime.utcnow() in mcp_client routes (fehlte in allen v1-Listen) app/plugins/builtins/mcp_client/routes.py:173 D2
SQLITE-001 SQLite in-memory Fixture — Forbidden Pattern (PostgreSQL only) app/plugins/builtins/automation/tests/test_automation.py:35 D2

Vollscan-Ergebnis (nach CHECK-002-Fix): 14 Cross-Plugin-Verstöße, davon Core→Plugin: ai/integration_tools.py (3×), core/worker.py (2×), routes/compliance.py, workflows/engine.py, routes/compliance.py; Plugin→Plugin: 5× (Details in Block B).


BLOCK 0 — Sofortmaßnahmen (Blocker, vor allem anderen)

0.1 SYNTAX-001: automation/plugin.py reparieren

  • logger.info(...) und except Exception: im Cron-Job-Block auf die neue Einrücktiefe heben (innerhalb if default_tenant_id is not None:fortry).
  • Verify: /opt/venv/bin/python -m py_compile app/plugins/builtins/automation/plugin.py → OK, dann App-Start smoke testen.
  • Danach entscheiden: committen (nach Review) oder reverten. Zustand nicht so lassen.

0.2 ARCH-010 + CHECK-002: Cross-Plugin-Checker doppelt fixen

parser.add_argument("--path", default=None)
...
search_path = Path(args.path).resolve() if args.path else None
files = find_python_files(search_path)   # None -> Vollscan app/
  • In check_file(): filepath = Path(filepath).resolve() vor relative_to(PROJECT_ROOT).
  • Verify: ohne Argument → ~450+ Dateien, 14 Verstöße; --path app (relativ) → kein Crash, identisches Ergebnis.

0.3 Gate

  • python -c "import app.main" lädt sauber (mit framework runtime).
  • Checker-Ausgabe (14 Findings) wird Arbeitsliste für Block A/B.

BLOCK A — Plattformbasis: Lifecycle, Contracts, Events, Runtime

Ziel: Plugin activate/deactivate deterministisch, Contracts stabil, keine Laufzeit-Namen Fehler.

A1 Activation-Reihenfolge & Tenant-Logik

  • ARCH-001: plugin_service.py:94 — Permissions registrieren VOR on_activate(); active=True erst nach erfolgreichem on_activate setzen.
  • ARCH-002: main.py:292-302 — on_activate() einmal pro Prozess (global scope), nicht pro Tenant. Tenant-Seed bleibt separat.
  • ARCH-043: automation register_plugin_contributions — Tenant.limit(1) ersetzen durch definierten Default-Tenant-Mechanismus (konfigurierter System-Tenant), dokumentieren.

A2 Deactivation & Cleanup (vollständige Liste, war in v1 ungeplant)

  • ARCH-012: knowledge/wiki on_deactivate vervollständigen (Search-Provider, Event-Handler, Entities deregistrieren).
  • ARCH-013: self_improvement services.py:586-588 — Fallback auf direkten kommunikation-Import entfernen, Contract-only.
  • ARCH-015: registry.py:181-244,622-623 — Notification-Type Sync in deactivate-Sequence einordnen.
  • ARCH-033: kommunikation on_deactivate → service_container.remove() für alle registrierten Services.
  • ARCH-034: self_improvement on_deactivate → contract unregister.
  • ARCH-035: marketplace on_deactivate → contract unregister.
  • ARCH-036: mail _auto_sync_task Klassenvariable → Instanzvariable (sonst teilen sich Instanzen den Task-State).
  • ARCH-037: graph_rag on_activate — super().on_activate() VOR eigenem registry.register().
  • ARCH-044: ai_ui_control on_deactivate — service_container.remove() VOR super().on_deactivate().
  • BasePlugin erzwingt Symmetrie: activate was du registered, deactivate must remove (Checkliste in base.py dokumentieren).

A3 Contracts

  • ARCH-014: contracts.py:88-89 — Lazy-Load nach unregister() deaktivieren (Flag unregistered=True prüfen).
  • ARCH-042: calendar/dms contracts — get_contract() darf neue Instanz erzeugen; registrierte Instanz aus Registry zurückgeben.
  • ARCH-030: step_handlers.py:221,261,306,351,394 — get_function() existiert nicht; tatsächliche Contract-API verwenden (oder Methode sauber ergänzen).
  • ARCH-047: SearchContract → UnifiedSearchContract (Import + Usage).

A4 Events & Worker

  • ARCH-020: event_bus.py:38 — Duplikat-Check vor append.
  • ARCH-038: BasePlugin.register_event_handlers() definieren (Hook-Punkt), worker.py:168 nutzt ihn.
  • ARCH-048: engine.py:663 — payload key "event" → "event_name" (Outbox-Envelope-Kontrakt).
  • ARCH-050: engine.py acquire_lock — await get_redis() auf nicht-async Funktion; konsistent machen (async oder await entfernen).

A5 Runtime-Namen & None-Fehler

  • ARCH-029/041: trigger_dispatcher.py:125 — None-Check VOR Verwendung.
  • ARCH-031: knowledge/plugin.py — import uuid ergänzen.
  • ARCH-032: knowledge:66 + wiki:41 — unregister_actions_by_owner(hook_name, owner_tag) mit 2 Argumenten.
  • ARCH-054: entity_permissions.py:114/259 — ENTITY_MODELS.get(entity_type) direkt, nicht model_info["model"].
  • ARCH-052: storage.py get_file_metadata — kein asyncio.new_event_loop() im async Kontext; korrekt awaiten.

A6 Default-Permissions-Basis

  • ARCH-009: alembic/0019 + permissions.py:46-50 — Wildcard-Pattern an 2-Segment-Schema angleichen (contacts:read statt core:contacts:read) + Datenmigration für Bestandsrollen.
  • ARCH-008: Permission-Namen vereinheitlichen (kommunikation plugin.py:44 vs dashboard.py:23) — Namensschema festlegen und durchsetzen (Konstanten aus registry).

Gate A (muss grün sein vor Block B)

  1. Import-Test: jedes Modul in app/ lädt (kein NameError/ImportError).
  2. Activate → Permissions/Entities sofort registriert; Deactivate → sauber deregistriert (Runtime-Test).
  3. Multi-Tenant-Startup: on_activate genau 1× im Log.
  4. Contract roundtrip: register → get_contract liefert registrierte Instanz → unregister → get_contract liefert None (kein Lazy-Resurrect).
  5. pytest-Smoke der Plugin-Tests (ohne die in D geschobenen Suiten).

BLOCK B — Plugin-Grenze vollständig ziehen

Ziel: Core kennt Plugins nicht als Sonderfall. Plugins deklarieren alles.

B1 Contacts: Core-Plugin statt Core-Hardcoding ⚠️ Kernstück

Prinzip: is_core=True bedeutet nicht deaktivierbar — NICHT im Core hardcoded. Contacts bleibt immer aktiv, aber 100% über den Plugin-Mechanismus.

  1. main.py:549 app.include_router(contacts.router) entfernen — NACHDEM der Plugin-Route-Mechanismus identische Endpoints bereitstellt (Endpoint-Diff-Test davor!).
  2. Plugin manifest: routes=[]-Kommentar beseitigen; Contacts-Routes über Plugin registrieren mit require_active_plugin("contacts")-Dependency (schützt bei versehentlicher Deaktivierung + klarer Fehler).
  3. CORE_FIELD_DEFINITIONS (Contact-Feldmassen) → Plugin-Field-Contribution migrieren.
  4. Sidebar-Sonderfall ("Only non-plugin items: dashboard, contacts, settings") → contacts über pluginNavItems.
  5. Restore/History/Entity-Links: Contacts-Entity über EntityRegistry (B3), nicht über Core-Annahmen.
  6. Acceptance: grep -rn "contacts" app/main.py → keine fachliche Referenz mehr; alle Contact-E2E-Tests grün.

B2 Core→Plugin Imports via Contracts (alle 6 Core-Stellen des Vollscans)

  • app/ai/integration_tools.py: tool_registry/knowledge/provider_registry → get_contract("ai_assistant"/"knowledge"/"unified_search").
  • app/ai/agent_loop.py: analog prüfen und umstellen.
  • app/core/worker.py:175,460: provider_registry + KnowledgeExtraction (deckt ARCH-040 ab) → Contract/Service-Interface.
  • app/routes/compliance.py:22 (ARCH-046): AgentDefinition → Contract oder gemeinsames Core-Schema.
  • app/workflows/engine.py:94-95 (ARCH-049): CommConversation → kommunikation-Contract.
  • Regel: Wenn ein Contract fehlt, Contract im Plugin definieren und exportieren — NIEMALS Model-Import behalten.

B3 Entity-Registry & Custom Fields dynamisch

  • ARCH-016: ENTITY_PERMISSIONS-Liste aus registrierten Plugin-Entities generieren (entity_permission_service.py:54, entity_permissions.py:252).
  • ARCH-017: custom_field_definitions.py:25,42 — von contacts-Permission entkoppeln (plugin-eigene Permission je entity_type).
  • ARCH-022: deps.py _WRITE_PERMISSIONS aus permission_registry generieren (nicht hardcoded).

B4 Dependencies deklarieren

  • ARCH-026: Manifest depends_on; Aktivierungsreihenfolge topologisch; Abhängiges nicht deaktivierbar solange Abhängiges aktiv (Fehlermeldung statt stiller Bruch).
  • ARCH-021-Vorbereitung: Menü/Routen-Daten kommen vollständig aus active-manifests (siehe C).

B5 ARCH-003 (früh, weil Frontend es braucht)

  • plugins.py:97 — /plugins/active-manifests für eingeloggte User freigeben (eigene Permission plugins:view_manifests, Default-Rollen erhalten sie). Frontend braucht das für C.

Gate B (vor Block C)

  1. Neues-Plugin-Test: Minimal-Plugin mit Route+Entity+Menü — OHNE Änderung einer Core-Datei funktionsfähig.
  2. Fresh-DB-Install: leere DB → ausschließlich Alembic → Admin-Seed → Contacts/Companies voll funktional.
  3. Contacts-Vertrag: Endpoint-Vergleich alt (core route) vs. neu (plugin route) — identische Pfade/Statuscodes.
  4. Dependency-Test: A hängt an B → B deaktivieren wird blockiert.
  5. Cross-Plugin-Vollscan: 0 Core→Plugin-Verstöße.

BLOCK C — Frontend + Workspace (erst nach A+B)

⚠️ Reihenfolge innerhalb C ist verbindlich: Backend-Felder (C1) → Store-Filter (C2) → Loader (C3) → erst DANN Route-/Menu-Umbau (C4C7) → Production-Verify (C8) → alte Wege löschen (C9).

C1 Backend: Permission-Felder liefern (VORAUSSETZUNG für alles Weitere)

  • FrontendPageRoute.permission: str | None + FrontendMenuItem.permission: str | None in app/plugins/manifest.py ergänzen (aktuell nur protected: bool — verifiziert).
  • active-manifests liefert die Felder aus; TypeScript-Typen (PluginMenuItem erwartet bereits permission? — Backend zieht nach).
  • Migration der vorhandenen Plugin-Manifeste: jede Route/Menü bekommt ihre Permission.

C2 Workspace is_visible (ARCH-004, verifizierte Ursache)

  • workspaceStore.ts:103-105: visibleModuleKeys() filtert aktuell gar nicht: new Set((ctx?.modules ?? []).filter(m => m.is_visible !== false).map(m => m.module_key))
  • Server-Seite prüfen: liefert WorkspaceContext modules ohne is_visible? Falls ja, Feld ergänzen.
  • Test: is_visible=false → Modul unsichtbar in Sidebar + Route guard; Toggle im WorkspaceManager wirkt sofort.

C3 PluginLoader Production-Build (ARCH-019)

  • @vite-ignore Dynamic-Import-Problem lösen: Plugin-Frontends als bekannte Chunk-Map (build-time generiertes Manifest) oder importmap. Ziel: Production-Build lädt Plugin-Module wirklich.

C4 Dynamische Routes mit Permission (ARCH-006 + ARCH-007)

  • PluginRouteRenderer: PermissionGate mit route.permission aus C1 wrappen (gleiches Verhalten wie heutige statische <PermissionRoute permission="calendar:read">).
  • Statische Plugin-Routes in routes/index.tsx markieren (deprecated), parallel betreiben bis C8 grün.
  • ARCH-061 gleich mit: leeres Route-Objekt (index.tsx:58-59) entfernen.

C5 Sidebar (ARCH-021)

  • singleItems reduzieren auf dashboard/login/settings(+profil). Alles andere aus pluginStore (Manifest-Menü inkl. permission, order, icon).
  • Sortierreihenfolge aus Manifest order.

C6 Settings-Doppelarchitektur auflösen (NEU, fehlte in v1)

  • Settings.tsx:33-61: hardcodedNavItems + pluginNavItems + Dedup → EINE Quelle: Settings-Seiten als Manifest-Contribution (parent: "/settings" existiert bereits in FrontendPageRoute). Core behält nur Profil/Security-Basis.

C7 Dashboard entkoppeln (NEU, fehlte in v1)

  • DashboardWidgetLoader.tsx:13-20: statische Widget-Map → Widget-Registry aus Plugin-Manifest (component + permission + dashboard-slot).
  • dashboard.py:14,60-66: Contact-Count nicht hardcoded — Count-Endpoint je Entity über Contract/Registry (Contacts-Plugin liefert seinen Count; Core aggregiert).
  • RecentContacts/TasksSummary/CalendarUpcoming → Contributions ihrer Plugins.

C8 Kleinere Frontend-Dedupes (funktional begründet, KEIN Refactor-Programm)

  • ARCH-062: TeamPanel → components/shared/TeamPanel.tsx (echte Duplikation, 50 Zeilen).
  • ARCH-063: SortableMenuItem.tsx:4 import * as LucideIcons → ICON_MAP-Pattern (behebt OOM in Tests — funktional, nicht kosmetisch).

C9 Alte Wege löschen (NUR nach C8-Verify)

  • Statische Plugin-Routes + Sidebar-singleItems + Settings-hardcodes entfernen.

Gate C

  1. Production Build (npm run build) + Playwright GEGEN DEN BUILD (nicht dev-server).
  2. Workspace: is_visible=false wirklich unsichtbar (Sidebar + Direktaufruf geblockt).
  3. Normaler User (kein Admin): sieht Plugin-Menüs/Routes gemäß Permission; 403 wo keine.
  4. Permission-Diff-Test: jede ehemals statische Route hatte Permission X → dynamische Route fordert dieselbe X (KEIN Absinken auf auth-only).
  5. tsc --noEmit clean.

BLOCK D — Produktbugs & Triage (JETZT erst, auf stabiler Basis)

D1 pytest-Suiten reparieren (die v1-„Phase 1"-Massen)

  • Reihenfolge nach Basisabhängigkeit: conftest/fixtures → test_auth → test_abac → test_companies → test_calendar → test_ai_proactive → test_api_tokens → test_backend_coverage_gaps → test_phase_h_wiki (BUG-097, 091, 087, 088, 089, 090, 086, 085, 092096, 098, 099, 067).
  • Pro Suite: Root-Cause zuerst (meist Fixtures/RLS/Permission-Setup), keine Test-Anpassung um grün zu werden (AGENTS.md §2).

D2 DateTime & Forbidden Patterns (abgeschlossen, konkrete Liste)

  • datetime.utcnow()datetime.now(UTC) an: core/worker.py:365,409,468 · routes/audit.py:170 · services/webhook_service.py:221 · services/backup_service.py:159 · plugins/mcp_client/routes.py:173 (DT-001, neu). (Deckt ARCH-039, 053, 058, 060.)
  • SQLITE-001 (neu): automation test fixture auf ephemeres PostgreSQL (projekt-Konvention) umstellen.

D3 API-/Testpfad-Korrekturen (Low, mechanisch)

  • BUG-027, 028, 029, 031, 032, 033, 034, 035, 071 (Test-Pfade/Payloads an reale API anpassen — Docs prüfen, nicht Tests verbiegen falls API falsch: dann API fixen).
  • ARCH-051: dict-body-Routes auf Pydantic-Schemas (Liste aus test-bugs abarbeiten).
  • ARCH-055: errors.py:124 user_agent. ARCH-056/057: roles.py SYSTEM_PERMISSIONS aus registry importieren; _plugins → öffentliche Methode. ARCH-061 falls nicht schon in C4.

D4 Security-TRIAGE (nicht pauschal fixen)

  • BUG-019 „453 hardcoded secrets“: Klassifizieren (echtes Secret / Testdaten / Konstante / false positive) → nur echte Findings: Secret in env/Secretstore, Referenz einfügen. Ergebnis als Triage-Tabelle dokumentieren.
  • BUG-020 „288 SQL-Injection-Risiken“: Nur Stellen mit User-Input-Fluss in String-SQL prüfen; parameterisieren. Scan-Finding ≠ Bug.
  • ARCH-027 hier einsortieren: config.py Default SECRET_KEY in Production hart failen lassen.

D5 Marathon-Scanner-Triage (BUG-074078)

  • trace_api_contracts (859), stores (323), hooks (70), plugins (27), functions (3): Cluster-Analyse → wahrscheinliche Root-Causes zählen (Erfahrungswert: deutlich weniger als Issue-Zahl) → Cluster fixen, Scanner erneut laufen lassen, Delta dokumentieren.

D6 Legacy-Migration (umsichtig)

  • ARCH-059 (+ ARCH-018-Familie): AIConversation/AIMessage → kommunikation-Conversations migrieren ODER Modul als deprecated markieren + Abschaltplan. Kein Big-Bang: erst Parität herstellen, dann Quellen umstellen, dann Tabellen droppen (eigene Migration).
  • ARCH-023: service_container.initialize() vervollständigen.

SEPARATE TRACKS (bewusst NICHT Teil dieses Umbaus)

Track Inhalt Warum geschoben
S1 Code-Hygiene God Objects (36 Py + 9 FE, BUG-018/081), weitere >500-Zeilen-Splits Kein nachgewiesener Basisdefekt; Risiko von Big-Refactors bekannt
S2 i18n BUG-021, ARCH-024, 025, 028, 045 UI-Qualitätsarbeit, kein Architekturproblem
S3 Dependency-Audits BUG-022/070 npm, pip-audit-Routine Routineaufgabe, automatisierbar (CI), kein Umbau

VERIFIKATIONS-GATES (gültig in ALLEN Blöcken)

Nach jedem Block, vor dem nächsten:

  1. Import-Test aller app/-Module (framework runtime).
  2. Cross-Plugin-Vollscan: 0 Verstöße der jeweiligen Kategorie.
  3. npx tsc --noEmit + ruff + mypy (Ziel-Regelwerk) bei jedem Touch.
  4. pytest-Smoke der betroffenen Suites.

10 verpflichtende Integrationstests VOR dem Löschen alter Doppelwege (C9/B1-final)

# Test Muss bestehen
1 Runtime Plugin Activation Permissions + Entities sofort registriert
2 Runtime Deactivation sauber deregistriert (Contracts/Services/Events)
3 Multi-Tenant Startup on_activate genau 1×
4 Normaler User Plugin-Manifeste/Menüs ohne Adminrecht (ARCH-003-Fix wirksam)
5 Workspace is_visible=false wirklich unsichtbar
6 Dependency A→B geladen; B nicht deaktivierbar solange A aktiv
7 Neues Plugin keine Core-Dateiänderung nötig
8 Frontend Production Build dynamische Plugin-Routes laden wirklich (nicht dev)
9 Fresh DB Editor/Viewer-Permissions funktionieren nach reiner Migration+Seed
10 Contacts Core-Plugin funktioniert ohne Contacts-Hardcodings im Core

BLOCK F — Sicherheit des Umbaus selbst + Wissenssicherung

Ziel: Der Umbau darf selbst nichts kaputt machen, und die neue Architektur muss so dokumentiert sein, dass die Fehlerklasse NIE wieder entsteht.

F1 Rollback- & Branch-Strategie (schützt vor 'Plan macht was kaputt')

  • Pro Block ein eigener Git-Branch (fix/block-a, fix/block-b, ...). Merge in main NUR nach bestandenem Block-Gate.
  • Pro Task ein Commit (Conventional Commits mit Finding-ID) — jeder Commit ist einzeln revertierbar.
  • DB-Migrationen additiv-first: neue Spalten/Tabellen hinzufügen, alte NICHT droppen, solange der neue Weg nicht durch Gate verifiziert ist. Drop-Migrationen erst in einem eigenen 'Cleanup-Release' nach C9.
  • Feature-Flags wo machbar: dynamische Routes/Sidebar hinter einem Toggle (DYNAMIC_PLUGIN_UI=true/false), Default=false bis Gate C grün. Sofortiger Rollback = Flag off, kein Code-Revert.
  • Deploy-Reihenfolge: Staging-Container (docker compose profile test) VOR Production. Production-Deploy nur mit grünem Gate.
  • Vor jedem Block-Start: git tag pre-block-<X> als Restore-Punkt.

F2 Was der Plan bewusst NICHT anfasst (Explosions-Schutz)

  • Keine Schema-Änderungen an bestehenden 130 Tabellen (nur additive neue Felder: permission in Manifests ist Code, kein DB-Schema).
  • Keine API-Path-Änderungen für bestehende Endpoints (Contacts-Entkopplung muss identische Pfade liefern — Endpoint-Diff-Test in B1 erzwingt das).
  • Keine Auth-/Session-Logik-Änderungen (Security-Basics sind verifiziert gut — nicht anfassen).
  • Keine gleichzeitige Änderung von Backend + Frontend im selben Commit (Backend erst, dann Frontend — C-Reihenfolge).

F3 Plugin-Development-Guide aktualisieren (VERHINDERT RÜCKFALL) ⚠️ Pflicht

Befund: docs/plugin-development-guide.md existiert (2549 Zeilen, gut strukturiert: Manifest, Lifecycle, UI-Registration, Events, Permissions, Migrations). ABER verifizierte Lücken:

  • Contracts: nur 3 Erwähnungen — der zentrale Mechanismus der neuen Architektur fehlt fast komplett
  • depends_on: 0 Erwähnungen — ARCH-026 führt es ein, der Guide kennt es nicht
  • Kein 'Plugin-Checkliste'-Abschnitt, der die Fehlerklasse (Cross-Plugin-Imports, Lifecycle-Asymmetrie, fehlende Permissions) strukturell verhindert

Pflicht: Der Guide wird MIT jedem Block aktualisiert — ein Block gilt erst als done, wenn der Guide den neuen Zustand beschreibt.

Nach Block Guide-Update
A Kapitel 'Contracts' vollständig: get_contract, register/unregister, Symmetrie-Pflicht activate/deactivate, register_event_handlers-Hook, EventBus-Duplikat-Regel
B Kapitel 'Dependencies': depends_on im Manifest, topologische Aktivierung, Deaktivier-Schutz. Kapitel 'Entity-Registrierung': dynamische ENTITY_PERMISSIONS, Field-Contributions (statt CORE_FIELD_DEFINITIONS)
C Kapitel 'Frontend-Contributions' aktualisieren: permission-Feld in FrontendPageRoute/FrontendMenuItem (PFLICHT, nicht optional), Widget-Registry statt statischem Loader, Workspace is_visible
D Kapitel 'Konventionen': datetime.now(UTC) statt utcnow, Pydantic-Schemas statt dict-bodies, Audit-Log-Pflicht bei Mutationen

Neuer Guide-Abschnitt 'Plugin-Checkliste' (Muss-Bestehen vor Merge jedes neuen Plugins):

  1. Kein direkter Import aus anderem Plugin — nur get_contract (Checker läuft in CI)
  2. on_activate/on_deactivate symmetrisch: alles was registriert wird, wird auch deregistriert
  3. Jede Route hat Permission (kein auth-only), jede Menü-Route hat permission-Feld im Manifest
  4. depends_on deklariert, wenn Plugin andere nutzt
  5. Entities über Plugin-Registrierung, keine Core-Annahmen
  6. Frontend-Components über Manifest-Contribution, keine hardcoded Loader-Einträge
  7. Migrationen folgen Namenskonvention, RLS für eigene Tabellen
  8. Audit-Log bei allen Mutationen
  9. datetime nur mit UTC
  10. E2E-Smoke gegen Production-Build

Enforcement: Die Checkliste wird als CI-Template-Check + Review-Checkliste referenziert. AGENTS.md §0 (Auf bestehendem Code aufbauen) bleibt die übergeordnete Regel.

Gate F

  • Guide-Review: Jeder neue Mechanismus (Contracts, depends_on, permission-Felder, Widget-Registry) hat ein Kapitel mit Code-Beispiel aus einem REAL existierenden Plugin (nicht fiktiv).
  • Checkliste als Datei (docs/plugin-checklist.md) existiert und ist in PR-Template verlinkt.
  • Test: Ein Entwickler (oder Agent) baut ein Minimal-Plugin NUR mit dem Guide — ohne Fragen an den Architekten. Gelingt das nicht, ist der Guide nicht fertig.

BLOCK G — Compliance & Account-Security (NEU aus Final-Check 2026-08-23)

Ziel: Rechtliche Pflichten (DSGVO) und Account-Sicherheit, die im Final-Check ans Licht kamen. Diese Punkte waren in v1, v2 UND dem ChatGPT-Review nicht enthalten.

G1 DSGVO-Compliance ⚠️ KRITISCH (verifiziert: grep 'gdpr' in app/routes + app/services = 0 Treffer)

Ein CRM mit Personendaten ohne DSGVO-Funktionen darf in DE nicht produktiv gehen:

  • Art. 15 Auskunft: Endpunkt GET /api/v1/me/data-export — alle Daten einer natürlichen Person, maschinenlesbar (JSON).
  • Art. 17 Löschung: Der von AGENTS.md geforderte Hard-Delete (?gdpr=true) existiert als Regel, aber KEIN Endpunkt implementiert ihn. Implementieren: anonymisiere/lösche Personendaten inkl. aller verknüpften Entities (Contacts, Addresses, Notes, Files, Mail-Verknüpfungen), respektiere Aufbewahrungspflichten (Buchhaltung), protokolliere die Löschung selbst im Audit.
  • Art. 20 Portabilität: Strukturierter Export (JSON/CSV) der Kontaktdaten eines Tenants/Users.
  • Verarbeitungsübersicht: doc (welche Daten, wo, wie lange) als Grundlage für den AVV.
  • Aufwand: mittelgroßes Feature-Paket, eigener Track mit Tests; NICHT in die Architekturblöcke mischen.

G2 Session-Revocation bei Passwortänderung (verifiziert: nur Logout invalidiert)

  • Befund: app/routes/auth.py:105 invalidiert NUR die eigene Session beim Logout. Eine Passwortänderung löscht NICHT andere aktive Sessions des Accounts.
  • Risiko: kompromittierter Account bleibt nach PW-Wechsel kompromittiert (Angreifer-Session lebt weiter).
  • Fix: Nach erfolgreichem Passwortwechsel/-reset alle Sessions des Users löschen (außer der aktuellen); optional E-Mail-Benachrichtigung über den Vorgang.
  • Klein, wichtig, sofort machbar — gehört in Block 0 oder A.

G3 Hygiene-Funde (klein)

  • dump.rdb im Repo-Root: git-ignored (kein Leak, verifiziert), aber aufräumen und Redis-Workdir sauber konfigurieren.
  • scripts/test_migrations.sh:70 testet alembic downgrade base, ignoriert aber Fehler bewusst ('not always lossless'). Entscheidung dokumentieren: Downgrade-Fähigkeit ist KEIN Release-Kriterium — dann Kommentar so lassen und in docs/test-strategy.md festhalten.

Verifiziert GUT (nichts zu tun — explizit geprüft im Final-Check)

  • Webhook-Signaturen: HMAC-SHA256 vorhanden (webhook_service.py:200-231)
  • API-Token-Expiry-Prüfung vorhanden (api_token.py:91-95)
  • Content-based MIME-Detection via python-magic (storage.py:32-34)
  • CORS konfigurierbar über Settings (main.py:468-469)
  • Redis: Passwort-Pflicht, Named Volume, Healthcheck (docker-compose.yaml:50-59)

BLOCK H — Agent-Plattform-Kern (NEU v3: Kernprodukt, war unsichtbar)

⚠️ Höchste Priorität nach Block 0 — VOR bzw. zusammen mit Block B.

Kontext: Die Plattform soll ERP-Fachmodule per Plugin nachrüsten und Agenten müssen die Software VOLL bedienen. Code-Verifizierung zeigt: Dieser Kernanspruch ist aktuell architektonisch NICHT erfüllt.

H1 Befunde (verifiziert 2026-08-23)

  1. Agent-Tools sind hardcoded: app/ai/integration_tools.py registriert Workflow/Knowledge-Tools fest und importiert direkt aus Plugins (Core→Plugin, ARCH-011-Familie). Ein neues Fachmodul könnte seine Funktionen NICHT agenten-bedienbar machen, ohne diese Core-Datei zu ändern — direkter Verstoß gegen das Plattform-Prinzip.
  2. Agent-Laufzeit hängt an einem Plugin: ToolRegistry liegt unter app/plugins/builtins/ai_assistant/tool_registry.py. Ist ai_assistant deaktiviert, existiert keine Registry mehr — Agent-Betrieb ohne dieses Plugin unmöglich.
  3. Manifest hat Contribution-Ansätze (agent_capabilities manifest.py:200, AgentDefinitionContribution.tool_ids :88-94), aber keinen vollständigen Weg: BasePlugin kennt keinen register_tools-Hook; die Hardcodes umgehen das Manifest komplett.

H2 Architektur-Entscheidung (zuerst klären, dann bauen)

  • Agent-Laufzeit = Core: tool_registry, skill_registry, llm_client, agent_loop gehören in app/core/ bzw. app/ai/ (Core-Layer), NICHT ins ai_assistant-Plugin. Das ai_assistant-Plugin wird zum normalen Fachplugin, das NUR seine eigenen Tools beiträgt.
  • Tool-Contribution-API: Manifest-Feld tools: list[ToolContribution] (name, description, input_schema, handler_ref, required_permission) + BasePlugin-Lifecycle: on_activate registriert Tools beim Core-Registry, on_deactivate meldet sie ab (Symmetrie-Pflicht wie alle Contributions).
  • Permission-Gebundenheit: Jedes Agent-Tool trägt die Permission des darunterliegenden Endpoints; der Agent kann nur handeln, was der ausführende User darf (bestehendes agent_permissions.py nutzen/konsolidieren).

H3 Umbau

  1. Core-Registry nach app/core/ ziehen (Move + Alias-Import für Übergangszeit), ai_assistant nutzt sie wie jedes andere Plugin.
  2. integration_tools.py auflösen: Die 4 Hardcoded-Tools werden zu regulären Contributions ihrer Quell-Plugins (workflows, knowledge, unified_search) via Manifest/Hook — Core-Datei verschwindet.
  3. Plugin-Guide: neues Kapitel 'Agent-Tools beitragen' mit Schema + Beispiel + Test-Pflicht (Tool ohne Permission verboten).
  4. ARCH-011-Core-Imports (ai/integration_tools, ai/agent_loop) lösen sich dadurch STRUKTURELL auf — nicht durch Contract-Surrogate an dieser Stelle.

Gate H

  1. Neues Fachmodul-Testplugin bringt ein Agent-Tool MIT OHNE Änderung einer Core-Datei; Agent führt es aus (Tenant-scoped, permission-geprüft).
  2. ai_assistant deaktivieren → Agent-Laufzeit läuft weiter; dessen eigene Tools sind weg, alles andere bleibt.
  3. Deactivate/Activate-Zyklus: Tools sauber ab-/angemeldet, keine Duplikate (EventBus-Duplikatfix greift hier analog).
  4. Jeder Tool-Call landet im Audit-Log mit actor=agent, acting_user, permission.

H4 Weitere Hardcoding-Systeme (gründlicher Nachscan 2026-08-23, alle code-verifiziert)

Gleiche Fehlerklasse wie H1 — Systeme, die Fachmodule hardcodieren statt Contribution anzubieten:

ID System Ort Befund Fix-Richtung
HC-A NL→Aktions-Mapping app/ai/action_mapper.py:52-128 map_query_to_actions() hat FESTE API-Pfade per Regex — neue Fachmodul-Endpunkte sind für Agenten-Vorschläge UNSICHTBAR Aktions-Katalog aus Plugin-Manifest generieren (Endpoint-Registry existiert schon via OpenAPI)
HC-B Workflow-Schritte app/workflows/step_handlers.py:221,261,306,351,394 Importiert 5 Contract-KLASSEN direkt (MailContract, CalendarContract, DmsContract, SearchContract, AutomationContract) statt get_contract() — neue Module können KEINE eigenen Step-Types beitragen Step-Handler-Registry: Plugins registrieren Step-Types per Manifest; Core kennt nur Interface
HC-C AI-Chat & Workflow-Notifications an kommunikation app/ai/agent_loop.py:397 + app/workflows/engine.py:95 CommConversation-Model direkt importiert — AI-Konversationspersistenz und Workflow-Benachrichtigung hängen am kommunikation-Plugin Über kommunikation-Contract (besteht laut Checker-Output teilweise) oder Notification-Abstraktion
HC-D Knowledge-Worker app/core/worker.py:460 KnowledgeExtraction-Model direkt importiert (deckt ARCH-040) Contract oder generischer Extraction-Job über Plugin-Hook
HC-E Compliance-Route app/routes/compliance.py:22 AgentDefinition-Model direkt importiert Contract auf automation oder Model in Core-Schema überführen

POSITIV-VORBILD im eigenen Code: unified_search/provider_registry.py macht es RICHTIG — dynamisches register()/unregister()/get(), graph_rag contributet seinen Provider per Contract (graph_rag/plugin.py:44-58). Dieses Muster ist die Blaupause für ALLE H-Fixes.

Inkonsistenz gleich mitfixen: knowledge/services.py:110 importiert provider_registry DIREKT statt den eigenen unified_search-Contract zu nutzen (Plugin→Plugin-Verstoß aus dem Vollscan).

H5 Frontend-Hardcodings (Nachscan Frontend 2026-08-23, code-verifiziert)

ID System Ort Befund Fix-Richtung
HC-F Message-Block-Typen frontend/src/components/comm/blocks/BlockRenderer.tsx:3-15,34+ 14 Block-Typen hart im switch, darunter FACHMODUL-Blocks (TaskCardBlock, WorkflowCardBlock, KnowledgeCardBlock, ContactCardBlock, AgentResultBlock, ApprovalRequestBlock). Keine dynamische Registry (grep registerBlock/BLOCK_REGISTRY = leer). Ein ERP-Modul kann keine eigenen Message-Blocks beitragen Block-Registry: Plugins registrieren Block-Component per Manifest-Contribution (component + block_type + permission); Core behält nur text/markdown/html/file/image/audio/video als Basis
HC-G AISidebar-Tabs frontend/src/components/layout/AISidebar.tsx:137-141 5 Tabs hardcodiert (chat/proactive/notifications/team/chatroom), Fachmodule können keine eigenen Sidebar-Tabs beitragen Tab-Contribution über Manifest (wie Menü/Routes); Core behält Basis-Tabs, Plugin-Tabs kommen aus pluginStore

Geprüft und sauber (Frontend): Core-Layout-Komponenten (App.tsx, routes/, components/layout/, components/common/) importieren KEINE Plugin-API-Clients direkt. Die Doppelarchitektur-Probleme (Routes, Sidebar-Menü, Settings, Dashboard) sind bereits als C4C7 geplant.

Gate H erweitert: 5. Testplugin registriert einen eigenen Workflow-Step-Type OHNE Core-Änderung; Workflow führt ihn aus. 6. Agent-Query auf einen Fachmodul-Begriff schlägt dessen Aktionen vor (HC-A wirksam). 7. grep-Beweis: Keine der Dateien integration_tools/step_handlers/agent_loop/engine enthält noch Plugin-Model-Imports. 8. Testplugin contributet einen eigenen Message-Block-Typ; BlockRenderer rendert ihn dynamisch (HC-F wirksam). 9. Testplugin contributet einen AISidebar-Tab; Tab erscheint dynamisch (HC-G wirksam).


BLOCK E — Production-Härtung (NEU aus Deep-Dive 2026-08-23)

Ziel: Der Unterschied zwischen 'Architektur repariert' und 'produktiv betreibbar'. Diese Punkte fehlten in v1 UND v2 komplett.

E1 Audit-Logging-Vollständigkeit

  • Befund: Nur 1 Service-Datei referenziert AuditLog (grep-verified). AGENTS.md verbietet Mutationen OHNE Audit-Eintrag.
  • Fix: Systematische Prüfung ALLER schreibenden Routes/Services auf Audit-Aufruf; Lücke schließen (Middleware-/Repository-Pattern statt handgestreut); Test: jede POST/PATCH/DELETE-Route erzeugt Audit-Zeile.

E2 E2E-Test-Realität

  • Befund: BUG-012 — Playwright helpers.ts nutzt Mock-Daten. Die E2E-'Abdeckung' ist teilweise Schein.
  • Fix: helpers.ts auf echte API umstellen; E2E gegen Production-Build + echte Backend-Instanz (docker compose profile test). Erst danach zählt E2E als Verifikations-Gate.

E3 Backup/Restore-Drill

  • Vorhanden: backup.py, restore.py, restore_test.sh — aber ungeprüft, ob Restore auf FRISCHER DB wirklich reproduzierbar funktioniert.
  • Fix: Automatisierten Restore-Drill in CI aufnehmen (Dump → leere DB → Restore → Smoke-Checks). Monatlicher manuell getriggerte Drill zusätzlich.

E4 Monitoring-Reality-Check

  • Docs vorhanden (monitoring.md, incident-response-runbook.md) — tatsächliche Aktivierung ungeprüft.
  • Fix: Health-Endpunkte extern erreichbar + Alerting konfiguriert? Metrics-Endpoint gefüllt? Einmal durchspielen und dokumentieren, was WIRKLICH alarmiert.

E5 Performance-Baseline

  • Keine Lasttests erkennbar; bereits ein Search-Perf-Bug (6.34s) aufgetreten.
  • Fix: Baseline-Messung der Top-10-Endpoints (p95), seed_perf_data.py nutzen, Schwellwerte dokumentieren. Kein Voll-Lasttest-Programm — nur Baseline + Regressionsschwelle.

E6 Secrets & Credential-Hygiene

  • Befund: Sensible Credentials liegen lt. Projektanweisungen in docs/deploy-guide.md (privates Repo, aber Repo-Datei ≠ Secretstore).
  • Fix: Triage wie D4; Ziel: Deploy-Guide referenziert Secretstore statt Werte zu enthalten. pip-audit/npm-audit-Routine in CI (aus S3 vorziehen: nur die AUTOMATISIERUNG, nicht die Altlasten).

E7 CI als hartes Gate

  • Vorhanden aber ungeprüft: ci_pipeline.sh, .forgejo/workflows, .pre-commit-cross-plugin.yaml.
  • Fix: Pipeline zum Pflicht-Gate machen: Import-Check + Cross-Plugin-Vollscan (nach 0.2-Fix) + ruff (85 Auto-Fixes zuerst bereinigen, dann Regel scharf) + tsc + pytest-Smoke. Merge ohne grün = unmöglich.

Reihenfolge von E

  • E7 sofort nach Block 0 (CI braucht den funktionierenden Checker).
  • E1 nach Block B (Audit braucht stabile Service-Schicht).
  • E2E6 parallel zu Block C/D, spätestens VOR Go-Live-Kandidat.

ARBEITSREGELN (aus Review abgeleitete Verbote/Gebote)

  1. KEINE globalen commit()→flush()-Transformationen. Nur nachgewiesene Einzelfälle mit Transaktionsbegründung (BUG-003-Muster).
  2. KEIN pauschales Base.metadata.create_all in conftest. Unit-Fixtures ok; Integration/Install ausschließlich Alembic.
  3. Erst Backend-Feld (permission), dann Frontend-Consumption. Nie umgekehrt.
  4. Alte Wege (Routes/Sidebar/Settings/Dashboard-Hardcodes) erst nach Production-Build-Verify löschen.
  5. Scanner-Zahlen sind Triage-Queues, keine Bugcounts.
  6. Pro Task: Forgejo-Issue + PROGRESS.md-Update (AGENTS.md §9). Conventional Commits mit Finding-ID (fix(arch-004): ...).
  7. Jede Änderung: minimal focused, bestehender Stil, Tests beweisen Wirkung.

AUSFÜHRUNGSREIHENFOLGE (kurz)

Block 0 (Sofort: SYNTAX-001, CHECKER-FIX)          ~ halber Tag
Block A (Lifecycle/Contracts/Runtime)              → Gate A
Block B (Plugin-Grenze, Contacts-Entkopplung)      → Gate B (inkl. Fresh-DB)
Block C (Backend permission-Felder → Workspace →
        Routes/Sidebar/Settings/Dashboard → Verify → Löschen) → Gate C
Block D (pytest-Massen, DateTime, API-Pfade,
        Security-Triage, Marathon-Triage, Legacy)  → Full Regression
Separate Tracks S1S3: danach, unabhängig