Files
leocrm/docs/architecture-cleanup-plan.md
T
Agent Zero abbe7a18fc fix(audit): P0-P3 audit fixes — 838 ruff errors → 0, 30 F821 bugs fixed, 118 files changed
- P0: hooks.py 3-tuple fix, trigger_dispatcher Contract, contacts/plugin unregister_actions_by_owner
- P0: 5 test files — check_permission mocks removed, hardcoded DB credential → env var
- P1: attachment_service DmsFile via Contract helper, restore_registry/history_hooks dedup
- P1: mail/plugin restore unregister, mcp_client datetime.now(UTC), saved_views/filters patterns
- P1: ProtectedRoute fail-closed, 13 test assertion fixes (bcrypt, DB-URLs, SECRET_KEYs)
- P2: deprecated notifications → post_system_message (3 files), forgejo Base, report_generator lazy import
- P2: webhooks permissions, deps.py/roles.py plugin perms removed, import_export default
- P2: address/tags/entity_links patterns removed, worker.py Contract-Umgehungen fixed
- P2: 28 frontend TODOs (hardcoded constants, deprecated notification API)
- P3: dead code, duplicates, deprecated imports, private attr, __import__ inline
- P3: 8 frontend TODOs (LucideIcons, inline styles, XSS, i18n)
- ruff: 838 → 0 (612 auto-fix + 246 manual + 27 F821 regression fix)
- F821: 30 → 0 (AutomationDefinition, DmsFile, user_id, Path, Any, String)
- Contract-Umgehungen: 2 neue gefunden (worker.py:169, worker.py:280) und gefixt
2026-08-16 01:17:18 +02:00

23 KiB

Architecture Cleanup Plan — LeoCRM

Erstellt: 2026-08-14 Basis: Adversarial Architecture Audit (P0-1 bis P0-10, P1-9 bis P1-24) Ziel: Plugin-System funktionsfähig machen, Hartcodierungen entfernen, Core/Plugin-Grenze etablieren


Prinzipien

  1. Jeder Fix nutzt existierende Interfaces — kein Neubau, nur Verdrahtung
  2. Jeder Fix ist testbar — Plugin aktivieren/deaktivieren ohne Neustart muss funktionieren
  3. Keine neuen Hartcodierungen — jede neue Entität/Permission/Job kommt aus Plugin-Manifesten
  4. Minimal-invasiv — Core-Routes bleiben statisch (nicht migrieren), nur Plugin-Teile dynamisieren
  5. Phase für Phase verifizierbar — jede Phase hat klare Acceptance Criteria

Phase 1: Plugin-Lifecycle zur Laufzeit funktionsfähig machen

Priorität: P0-Kritisch | Aufwand: ~4h | Abhängigkeiten: keine

Problem

Plugin-Aktivierung/Deaktivierung zur Laufzeit funktioniert nicht (P0-10, P0-9, P0-1).

Tasks

1.1 Permission-Registry bei Runtime-Aktivierung aktualisieren (P0-10)

Datei: app/services/plugin_service.py Änderung:

  • In activate_plugin(): Nach registry.activate()register_plugin_permissions(name, plugin.manifest.permissions) aufrufen
  • In deactivate_plugin(): Nach registry.deactivate()unregister_plugin_permissions(name) aufrufen
  • from app.core.permission_registry import register_plugin_permissions, unregister_plugin_permissions

Verifikation:

  1. Plugin via API aktivieren → Permission-Check für Plugin-Route gibt kein 403 mehr
  2. Plugin via API deaktivieren → Permission-Registry enthält Plugin nicht mehr
  3. Test: test_plugin_lifecycle.py — aktivieren, Route callen, deaktivieren, Route gibt 403

1.2 Route-Registrierung aus Discovery-Ergebnis (P0-1)

Datei: app/main.py:583-605 Änderung:

  • Ersetze hartcodierte plugin_modules-Liste mit registry.list_discovered()
  • Für jeden discovered Plugin: Lese Manifest-Routes, registriere mit require_active_plugin()
  • Behalte try/except für robustness
# ALT:
plugin_modules = ["app.plugins.builtins.tags", ...]

# NEU:
registry = get_registry()
for plugin_name in registry.list_discovered():
    plugin = registry.get_plugin(plugin_name)
    if plugin and plugin.manifest.routes:
        for route_def in plugin.manifest.routes:
            # ... gleiche Logik wie bisher, aber dynamisch

Verifikation:

  1. Neues Plugin-Verzeichnis in app/plugins/builtins/ erstellen → Routes erscheinen ohne main.py-Änderung
  2. Alle existierenden Plugin-Routes noch vorhanden (OpenAPI check)
  3. builtins/__init__.py-Imports entfernen → Discovery findet Plugins trotzdem

1.3 _mounted_routes befüllen oder Route-Removal dokumentieren (P0-9)

Datei: app/plugins/registry.py:36,690-692 Änderung (Option A — empfohlen):

  • In activate(): Nach app.include_router()_mounted_routes[name].append(router)
  • Das erfordert, dass activate() Zugriff auf app hat (bereits via self._app)
  • Route-Removal in deactivate() funktioniert dann tatsächlich

ODER Option B — einfacher:

  • Entferne Route-Removal-Logik aus deactivate()
  • Dokumentiere: Routes bleiben registriert, require_active_plugin() ist die einzige Verteidigung
  • Das ist die aktuelle Realität — nur ehrlich dokumentiert

Verifikation:

  • Option A: Plugin deaktivieren → Route gibt 404 (nicht 403)
  • Option B: Plugin deaktivieren → Route gibt 403 (dokumentiert)

1.4 builtins/__init__.py-Imports entfernen (P0-6)

Datei: app/plugins/builtins/__init__.py Änderung:

  • Entferne alle 10 hartcodierten Plugin-Imports
  • discover_builtins() findet Plugins via pkgutil — die __init__.py-Imports sind redundant
  • Behalte nur den Docstring

Verifikation:

  1. App startet ohne Fehler
  2. registry.list_discovered() enthält alle 21 Plugins
  3. Alle Plugin-Routes registriert

Acceptance Criteria Phase 1

  • Plugin via API aktivieren → Routes funktionieren ohne Neustart
  • Plugin via API deaktivieren → Routes geben 403/404
  • Neues Plugin in builtins/ ablegen → Routes erscheinen ohne Core-Änderung
  • builtins/__init__.py hat keine Plugin-Imports mehr
  • Test: test_plugin_lifecycle.py existiert und ist grün

Phase 2: Plugin-Selbstregistrierung statt Core-Hartcodierung

Priorität: P0-Hoch | Aufwand: ~6h | Abhängigkeiten: Phase 1

Problem

Core registriert Plugin-Entities, Hooks, Restore-Configs, Worker-Jobs hartkodiert (P0-7, P0-8, P0-5).

Tasks

2.1 Restore-Registry: Plugins registrieren selbst (P0-7)

Dateien:

  • app/core/restore_registry.py:113-195 — entferne register_default_entities() für Plugin-Entities
  • app/plugins/builtins/tasks/plugin.py — in on_activate(): reg.register(RestoreConfig(entity_type="task", ...))
  • app/plugins/builtins/calendar/plugin.py — in on_activate(): reg.register(RestoreConfig(entity_type="calendar_entry", ...))
  • app/plugins/builtins/dms/plugin.py — in on_activate(): reg.register(RestoreConfig(entity_type="dms_file", ...))
  • app/plugins/builtins/mail/plugin.py — in on_activate(): reg.register(RestoreConfig(entity_type="mail", ..., special_handler=_mail_restore_handler))
  • app/plugins/builtins/mail/plugin.py_mail_restore_handler nach Mail-Plugin verschieben

Core behält nur: Contact-Registrierung (Contact ist Core)

Verifikation:

  1. Plugin aktivieren → Restore für Plugin-Entity funktioniert
  2. Plugin deaktivieren → Restore-Config für Plugin-Entity entfernt
  3. register_default_entities() registriert nur noch Contact

2.2 History-Hooks: Plugins registrieren selbst (P0-8)

Dateien:

  • app/core/history_hooks.py:135-173 — entferne Plugin-Entity-Hooks aus register_default_history_hooks()
  • app/plugins/builtins/tasks/plugin.py — in on_activate(): register_history_hooks(reg, "task", "task.after_create", ...)
  • app/plugins/builtins/calendar/plugin.py — gleiche für calendar_entry
  • app/plugins/builtins/dms/plugin.py — gleiche für dms_file
  • app/plugins/builtins/mail/plugin.py — gleiche für mail

Core behält nur: Contact-Hooks

Verifikation:

  1. Plugin aktivieren → History wird für Plugin-Entities aufgezeichnet
  2. Plugin deaktivieren → Hooks werden entfernt (on_deactivate muss reg.unregister_action() aufrufen)
  3. register_default_history_hooks() registriert nur noch Contact

2.3 Worker-Jobs: Plugins registrieren selbst (P0-5)

Dateien:

  • app/core/worker.py:221-227 — entferne hartcodierte plugin_job_modules-Liste
  • app/plugins/base.py — füge get_job_modules() -> list[str] hinzu (default: [])
  • Jedes Plugin mit Jobs: überschreibe get_job_modules() → return ["app.plugins.builtins.<name>.jobs"]
  • worker.py — iteriere registry.list_discovered(), rufe plugin.get_job_modules() auf, importiere dynamisch

Verifikation:

  1. Plugin mit Jobs aktivieren → Jobs laufen
  2. Plugin deaktivieren → Jobs werden nicht mehr geladen
  3. Neues Plugin mit Jobs → funktioniert ohne worker.py-Änderung

2.4 OpenAPI-Tags dynamisch aus Manifesten (P1-18)

Datei: app/main.py:300-370 Änderung:

  • Entferne Plugin-spezifische OpenAPI-Tags (dms, mail, calendar, search, etc.)
  • Behalte nur Core-Tags (health, auth, users, contacts, etc.)
  • Nach Plugin-Route-Registrierung: füge Tags aus Plugin-Manifest hinzu

Verifikation:

  1. OpenAPI-Schema enthält alle Plugin-Tags mit Beschreibungen
  2. Plugin deaktivieren → Tag verschwindet aus OpenAPI

Acceptance Criteria Phase 2

  • register_default_entities() registriert nur Contact
  • register_default_history_hooks() registriert nur Contact
  • worker.py hat keine hartkodierte Plugin-Modulliste
  • main.py OpenAPI-Tags enthalten keine Plugin-Tags mehr
  • Plugin deaktivieren entfernt Restore-Config, History-Hooks, Worker-Jobs
  • Plugin aktivieren registriert alles neu

Phase 3: Generische Services erweiterbar machen

Priorität: P1-Hoch | Aufwand: ~8h | Abhängigkeiten: Phase 2

Problem

ENTITY_MODELS, CORE_PERMISSIONS, saved_views, tags haben hartcodierte Entity-Types (P0-3, P0-4, P1-12, P1-13).

Tasks

3.1 ENTITY_MODELS: Plugin-Registrierungs-Interface (P0-3)

Dateien:

  • app/services/entity_permission_service.py:70-190 — entferne alle try/except Plugin-Import-Blöcke
  • app/plugins/base.py — füge get_entity_models() -> dict[str, type] hinzu (default: {})
  • Jedes Plugin: überschreibe get_entity_models() → return {"file": DmsFile, "folder": DmsFolder}
  • entity_permission_service.py — neue Funktion register_entity_model(entity_type, model_class)
  • main.py:lifespan() — nach Plugin-Aktivierung: iteriere Plugins, rufe get_entity_models(), registriere
  • registry.activate() — rufe register_entity_model() für aktive Plugins
  • registry.deactivate() — entferne Entity-Models für deaktivierte Plugins

Core behält: Contact, Address, Attachment, BankAccount, Workflow, Sequence, SavedFilter, SavedView, Webhook, CustomFieldDefinition, ContactFolder, EntityAttachment, EntityHistory

Verifikation:

  1. Plugin aktivieren → ENTITY_MODELS enthält Plugin-Entities
  2. Plugin deaktivieren → ENTITY_MODELS enthält Plugin-Entities nicht mehr
  3. Permission-Resolution für Plugin-Entity funktioniert
  4. Neues Plugin mit neuer Entität → funktioniert ohne entity_permission_service.py-Änderung

3.2 CORE_PERMISSIONS: Plugin-Permissions entfernen (P0-4)

Datei: app/core/permission_registry.py:21-128 Änderung:

  • Entferne alle Plugin-Permissions aus CORE_PERMISSIONS (calendar, dms, mail, tasks, comm, automation, ai, tags, entity_links, reports, search, mcp, permissions, agents, dashboard)
  • Behalte nur echte Core-Permissions: contacts, users, roles, groups, audit, settings, plugins, tenants, notifications, attachments, workflows, user_preferences, sequences, addresses, taxes, currencies, import_export, workspaces, system
  • Plugin-Permissions kommen bereits via register_plugin_permissions() aus Manifesten — das ist die dynamische Quelle
  • Entferne Kommentar "Plugin permissions (registered at startup, but also listed here for completeness)"

Verifikation:

  1. Plugin aktivieren → Plugin-Permissions in Registry
  2. Plugin deaktivieren → Plugin-Permissions nicht in Registry
  3. Permission-UI zeigt nur aktive Plugin-Permissions
  4. Core-Permissions weiterhin verfügbar

3.3 CORE_FIELD_DEFINITIONS: Contact-Felder als Plugin oder Core-Deklaration (P1-16)

Datei: app/core/permission_registry.py:135-176 Änderung:

  • Contact-Felddefinitionen bleiben in Core (Contact ist Core)
  • User-Felddefinitionen bleiben in Core (User ist Core)
  • Das ist akzeptabel — Core darf Core-Felder deklarieren
  • Kein Fix nötig — nur Dokumentation dass dies Core-spezifisch ist

3.4 saved_views/saved_filters: Entity-Types dynamisch (P1-12)

Dateien: app/routes/saved_views.py:19, app/routes/saved_filters.py:19 Änderung:

  • Entferne VALID_ENTITY_TYPES = {"contacts", "mail", "calendar", "dms"}
  • Entferne Pydantic pattern="^(contacts|mail|calendar|dms)$"
  • Stattdessen: Validiere gegen ENTITY_MODELS.keys() oder eine neue get_valid_entity_types() Funktion
  • Akzeptiere jeden String, validiere zur Laufzeit gegen registrierte Entity-Types

Verifikation:

  1. Saved View für task erstellen → funktioniert
  2. Saved View für nonexistent erstellen → 422
  3. Plugin deaktivieren → Saved Views für Plugin-Entity noch abrufbar aber nicht neu erstellbar

Dateien: app/plugins/builtins/tags/routes.py:25, app/plugins/builtins/entity_links/routes.py:22 Änderung:

  • Entferne hartcodierte Sets
  • Tags: Validiere gegen ENTITY_MODELS.keys() (jede registrierte Entität kann getaggt werden)
  • Entity-Links: Validiere gegen ENTITY_MODELS.keys() (jede registrierte Entität kann verlinkt werden)
  • Frontend tags.ts:12EntityType dynamisch aus API laden oder als string deklarieren

Verifikation:

  1. Tag für task erstellen → funktioniert
  2. Tag für nonexistent → 422
  3. Frontend zeigt alle verfügbaren Entity-Types an

3.6 Dashboard-Counts dynamisch (P1-21)

Datei: app/routes/dashboard.py:57-110 Änderung:

  • Behalte Contact/Company/Person als Core-Counts
  • Füge Plugin-Counts-Interface hinzu: BasePlugin.get_dashboard_counts(db, tenant_id, user_id) -> list[dict]
  • /counts-Endpoint iteriert aktive Plugins, sammelt Counts
  • Plugins können eigene Counts beitragen (z.B. Tasks: offene Tasks, Mail: ungelesene Mails)

Verifikation:

  1. Dashboard zeigt Plugin-Counts an
  2. Plugin deaktivieren → Plugin-Counts verschwinden

Acceptance Criteria Phase 3

  • ENTITY_MODELS enthält keine try/except Plugin-Import-Blöcke mehr
  • CORE_PERMISSIONS enthält keine Plugin-Permissions mehr
  • saved_views/saved_filters akzeptieren alle registrierten Entity-Types
  • tags/entity_links akzeptieren alle registrierten Entity-Types
  • Dashboard-Counts enthalten Plugin-Beiträge
  • Plugin deaktivieren entfernt Permissions, Entity-Models aus Registries

Phase 4: Core/Plugin-Abhängigkeiten reduzieren

Priorität: P1-Mittel | Aufwand: ~6h | Abhängigkeiten: Phase 3

Problem

Core-Dateien importieren direkt Plugin-Modelle (P1-9, P1-10, P1-19, P1-20).

Tasks

4.1 Core→Plugin-Imports durch Contracts ersetzen (P1-9, P1-10)

Dateien (41 Core→Plugin-Imports):

  • app/core/notifications.py:43 → nutze get_contract("kommunikation") statt direktem Import
  • app/core/restore_registry.py:132-237 → nach Phase 2.1 erledigt (Plugins registrieren selbst)
  • app/core/trigger_dispatcher.py:116,174 → nutze get_contract("automation")
  • app/core/worker.py:169,221-227,273 → nach Phase 2.3 erledigt (dynamische Job-Discovery)
  • app/services/entity_permission_service.py:86-187 → nach Phase 3.1 erledigt (dynamische ENTITY_MODELS)
  • app/services/attachment_service.py:25 → nutze get_contract("dms") für File-Modell
  • app/commands/mail_commands.py:16,36,109,147 → nutze get_contract("mail")
  • app/commands/calendar_commands.py:38,104,153 → nutze get_contract("calendar")
  • app/commands/dms_commands.py:50,130 → nutze get_contract("dms")
  • app/ai/llm_client.py:292,319 → nutze get_contract("ai_assistant")
  • app/routes/errors.py:124 → nutze get_contract("forgejo_error_reporter") oder mache Error-Reporting generisch
  • app/main.py:151,171 → gleiche wie errors.py

Verifikation:

  1. grep -rn 'from app.plugins.builtins' app/core/ app/services/ app/routes/ app/commands/ app/ai/ → 0 Treffer (außer contracts)
  2. Plugin deaktivieren → Core funktioniert ohne Fehler (graceful degradation)
  3. check_cross_plugin_imports.py erweitert auf Core-Verzeichnisse

4.2 Cross-Plugin-Import-Checker auf Core erweitern (P1-20)

Datei: scripts/check_cross_plugin_imports.py Änderung:

  • find_python_files() default search_path: auch app/core/, app/services/, app/routes/, app/commands/, app/ai/ scannen
  • Neue EXEMPT_PATHS für legitime Core-Imports (z.B. main.py für Route-Registrierung)
  • CI-Pipeline prüft nun Core→Plugin-Imports auch

Verifikation:

  1. python scripts/check_cross_plugin_imports.py findet 0 Verstöße
  2. CI-Pipeline grün

4.3 Cross-Plugin-Imports in Plugins auf Contracts umstellen (P1-19)

Aufwand: Hoch (226 Imports), aber mechanisch Priorisierung:

  • Start mit Plugins, die am häufigsten importiert werden (kommunikation, ai_assistant, unified_search)
  • Jeder from app.plugins.builtins.<plugin>.<module> import Xget_contract("<plugin>") mit None-Check
  • Contracts müssen alle aktuell direkt importierten Symbole exponieren

Verifikation:

  1. grep -rn 'from app.plugins.builtins' app/plugins/builtins/ | grep -v contracts | grep -v __init__ → 0
  2. Alle Plugin-Tests grün
  3. Plugin deaktivieren → abhängige Plugins degradieren gracefully

Acceptance Criteria Phase 4

  • 0 Core→Plugin-Imports (außer contracts)
  • Cross-Plugin-Checker prüft Core-Verzeichnisse
  • Cross-Plugin-Imports in Plugins reduziert um >80%
  • Plugin deaktivieren → keine Import-Fehler in Core oder anderen Plugins

Phase 5: Test-Infrastruktur und Qualität

Priorität: P1-Mittel | Aufwand: ~4h | Abhängigkeiten: Phase 1-4

Problem

Tests mocken Permissions weg, conftest importiert alle Plugins hartkodiert, keine E2E-Tests für Plugin-Lifecycle (P1-14, P1-15).

Tasks

5.1 conftest.py: Plugin-Modelle dynamisch laden (P1-14)

Datei: tests/conftest.py:50-80 Änderung:

  • Entferne alle hartcodierten Plugin-Model-Imports
  • Stattdessen: iteriere registry.list_discovered(), rufe plugin.get_entity_models() auf, importiere Modelle dynamisch
  • Base.metadata.create_all() findet alle Tabellen weil Modelle importiert wurden
# ALT: 15 hartcodierte Imports
from app.plugins.builtins.calendar.models import Calendar, CalendarEntry, ...

# NEU:
registry = get_registry()
registry.discover_builtins()
for name in registry.list_discovered():
    plugin = registry.get_plugin(name)
    if plugin:
        models = plugin.get_entity_models()
        # Import module to register models with Base.metadata
        for entity_type, model_class in models.items():
            # Model class is already imported via get_entity_models()
            pass

Verifikation:

  1. Tests laufen ohne hartcodierte Plugin-Imports
  2. Plugin entfernen → Tests für Plugin laufen nicht, aber andere Tests grün
  3. Neues Plugin → Tests finden Modelle automatisch

5.2 Plugin-Lifecycle E2E-Test (neu)

Datei: tests/test_plugin_lifecycle.py (neu) Inhalt:

  1. Test-Plugin erstellen (minimal, mit Route, Permission, Entity-Model, Job)
  2. Plugin aktivieren via API → Route erreichbar, Permission verfügbar, Job registriert
  3. Plugin deaktivieren via API → Route gibt 403/404, Permission entfernt, Job entfernt
  4. Plugin wieder aktivieren → alles wieder da
  5. Plugin mit Dependency aktivieren → funktioniert nur wenn Dependency aktiv
  6. Plugin mit Dependency deaktivieren → wird blockiert wenn Dependency aktiv

Verifikation:

  1. Test ist grün
  2. Test läuft ohne Mocks für Permission/Visibility/Tenant

5.3 Permission-Mock-Tests umstellen (P1-15)

Dateien: tests/test_graph_rag.py:42, test_agent_memory.py:42, test_marketplace.py:48, test_external_agent_api.py:38 Änderung:

  • Entferne patch("app.core.permissions.check_permission", return_value=True)
  • Stattdessen: Test-User mit echten Permissions erstellen
  • conftest.py hat bereits create_test_user mit Role → nutze echte Permissions
  • Für set_tenant_context Mocks: nutze echte DB-Session mit Tenant-Kontext

Verifikation:

  1. Tests laufen ohne Permission-Mocks
  2. Tests testen echte Permission-Enforcement
  3. Test mit unzureichenden Permissions → 403 (nicht 200)

Acceptance Criteria Phase 5

  • conftest.py hat keine hartcodierten Plugin-Imports
  • test_plugin_lifecycle.py existiert und ist grün
  • Keine patch("app.core.permissions.check_permission") mehr in Tests
  • Plugin-Lifecycle E2E-Test testet echte Permission/Visibility/Tenant-Isolation

Phase 6: Doppelarchitekturen auflösen

Priorität: P1-Niedrig | Aufwand: ~4h | Abhängigkeiten: Phase 4

Problem

Notification-Doppelarchitektur, Dedup/Import-Export Contact-spezifisch (P1-11, P1-22, P1-23).

Tasks

6.1 Notification-Doppelarchitektur dokumentieren oder auflösen (P1-11)

Datei: app/core/notifications.py Änderung:

  • create_notification() als deprecated markieren (bereits getan)
  • Frontend NotificationDropdown auf Communication-API umstellen
  • app/routes/notifications.py als deprecated markieren oder auf Communication redirect
  • Langfristig: notifications-Tabelle entfernen, alles über Communication-Plugin

Verifikation:

  1. Frontend nutzt Communication-API für Notifications
  2. notifications-Route gibt Deprecation-Warning

6.2 Dedup-Service: Plugin-Interface oder als Contact-Service deklarieren (P1-22)

Datei: app/services/dedup_service.py Änderung (Option A — Plugin-Interface):

  • BasePlugin.get_dedup_config() -> DedupConfig | None hinzufügen
  • Plugins deklarieren Dedup-Felder und Match-Logik
  • Dedup-Service iteriert aktive Plugins
  • Aufwand: Hoch — generische Dedup-Engine

ODER Option B — ehrlich deklarieren:

  • dedup_service.pycontact_dedup_service.py umbenennen
  • Dokumentieren: Dedup ist Contact-spezifisch, nicht generisch
  • Aufwand: Klein — nur Umbenennung und Doku

Empfehlung: Option B — Dedup ist CRM-spezifisch, muss nicht generisch sein.

6.3 Import/Export: Plugin-Interface oder als Contact-Service deklarieren (P1-23)

Datei: app/services/import_export_service.py Gleiche Entscheidung wie 6.2:

  • Option A: Generisches Import/Export-Interface für Plugins
  • Option B: Als contact_import_export_service.py deklarieren

Empfehlung: Option B für jetzt, Option A wenn ein Plugin Import/Export braucht.

Acceptance Criteria Phase 6

  • Notification-Doppelarchitektur aufgelöst oder dokumentiert
  • Dedup/Import-Export als Contact-spezifisch deklariert oder generisch gemacht

Gesamtaufwand

Phase Aufwand Priorität Abhängigkeit
1: Plugin-Lifecycle ~4h P0-Kritisch keine
2: Selbstregistrierung ~6h P0-Hoch Phase 1
3: Generische Services ~8h P1-Hoch Phase 2
4: Core/Plugin-Abhängigkeiten ~6h P1-Mittel Phase 3
5: Test-Infrastruktur ~4h P1-Mittel Phase 1-4
6: Doppelarchitekturen ~4h P1-Niedrig Phase 4
Total ~32h

Bei 8h/Tag: 4 Arbeitstage für alle Phasen. Phase 1 allein: einen halben Tag.


Risiken

  1. Phase 1 kann versteckte Abhängigkeiten aufdecken — wenn Plugin-Aktivierung zur Laufzeit zum ersten Mal richtig getestet wird, können neue Bugs sichtbar werden
  2. Phase 3.1 (ENTITY_MODELS) ist der komplexeste Fix — das Permission-System hängt davon ab
  3. Phase 4.3 (226 Cross-Imports) ist mechanisch aber fehleranfällig — jeder Contract muss alle Symbole exponieren
  4. Tests können brechen — wenn Permission-Mocks entfernt werden, können Tests failen die vorher grün waren (was gut ist, aber Aufwand bedeutet)

Erfolgsmessung

Nach Abschluss aller Phasen:

  1. Plugin hinzufügen: 0 Core-Dateien ändern → Plugin in builtins/ ablegen, aktivieren
  2. Plugin deaktivieren: Alle Routes, Permissions, Jobs, Hooks, Entity-Models entfernt
  3. Plugin entfernen: uninstall → alle Spuren gelöscht
  4. Neue Entität: Plugin deklariert Entität in Manifest → Permissions, Tags, Links, Saved Views funktionieren
  5. Cross-Plugin-Checker: 0 Verstöße in Core und Plugins
  6. E2E-Test: Plugin-Lifecycle-Test grün ohne Mocks

Das ist das Ziel: Ein Plugin-System, das wirklich modular ist.