- 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
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
- Jeder Fix nutzt existierende Interfaces — kein Neubau, nur Verdrahtung
- Jeder Fix ist testbar — Plugin aktivieren/deaktivieren ohne Neustart muss funktionieren
- Keine neuen Hartcodierungen — jede neue Entität/Permission/Job kommt aus Plugin-Manifesten
- Minimal-invasiv — Core-Routes bleiben statisch (nicht migrieren), nur Plugin-Teile dynamisieren
- 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(): Nachregistry.activate()→register_plugin_permissions(name, plugin.manifest.permissions)aufrufen - In
deactivate_plugin(): Nachregistry.deactivate()→unregister_plugin_permissions(name)aufrufen from app.core.permission_registry import register_plugin_permissions, unregister_plugin_permissions
Verifikation:
- Plugin via API aktivieren → Permission-Check für Plugin-Route gibt kein 403 mehr
- Plugin via API deaktivieren → Permission-Registry enthält Plugin nicht mehr
- 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 mitregistry.list_discovered() - Für jeden discovered Plugin: Lese Manifest-Routes, registriere mit
require_active_plugin() - Behalte
try/exceptfü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:
- Neues Plugin-Verzeichnis in
app/plugins/builtins/erstellen → Routes erscheinen ohne main.py-Änderung - Alle existierenden Plugin-Routes noch vorhanden (OpenAPI check)
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(): Nachapp.include_router()→_mounted_routes[name].append(router) - Das erfordert, dass
activate()Zugriff aufapphat (bereits viaself._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 viapkgutil— die__init__.py-Imports sind redundant- Behalte nur den Docstring
Verifikation:
- App startet ohne Fehler
registry.list_discovered()enthält alle 21 Plugins- 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__.pyhat keine Plugin-Imports mehr- Test:
test_plugin_lifecycle.pyexistiert 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— entferneregister_default_entities()für Plugin-Entitiesapp/plugins/builtins/tasks/plugin.py— inon_activate():reg.register(RestoreConfig(entity_type="task", ...))app/plugins/builtins/calendar/plugin.py— inon_activate():reg.register(RestoreConfig(entity_type="calendar_entry", ...))app/plugins/builtins/dms/plugin.py— inon_activate():reg.register(RestoreConfig(entity_type="dms_file", ...))app/plugins/builtins/mail/plugin.py— inon_activate():reg.register(RestoreConfig(entity_type="mail", ..., special_handler=_mail_restore_handler))app/plugins/builtins/mail/plugin.py—_mail_restore_handlernach Mail-Plugin verschieben
Core behält nur: Contact-Registrierung (Contact ist Core)
Verifikation:
- Plugin aktivieren → Restore für Plugin-Entity funktioniert
- Plugin deaktivieren → Restore-Config für Plugin-Entity entfernt
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 ausregister_default_history_hooks()app/plugins/builtins/tasks/plugin.py— inon_activate():register_history_hooks(reg, "task", "task.after_create", ...)app/plugins/builtins/calendar/plugin.py— gleiche fürcalendar_entryapp/plugins/builtins/dms/plugin.py— gleiche fürdms_fileapp/plugins/builtins/mail/plugin.py— gleiche fürmail
Core behält nur: Contact-Hooks
Verifikation:
- Plugin aktivieren → History wird für Plugin-Entities aufgezeichnet
- Plugin deaktivieren → Hooks werden entfernt (
on_deactivatemussreg.unregister_action()aufrufen) 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 hartcodierteplugin_job_modules-Listeapp/plugins/base.py— fügeget_job_modules() -> list[str]hinzu (default:[])- Jedes Plugin mit Jobs: überschreibe
get_job_modules()→ return["app.plugins.builtins.<name>.jobs"] worker.py— iteriereregistry.list_discovered(), rufeplugin.get_job_modules()auf, importiere dynamisch
Verifikation:
- Plugin mit Jobs aktivieren → Jobs laufen
- Plugin deaktivieren → Jobs werden nicht mehr geladen
- 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:
- OpenAPI-Schema enthält alle Plugin-Tags mit Beschreibungen
- Plugin deaktivieren → Tag verschwindet aus OpenAPI
Acceptance Criteria Phase 2
register_default_entities()registriert nur Contactregister_default_history_hooks()registriert nur Contactworker.pyhat keine hartkodierte Plugin-Modullistemain.pyOpenAPI-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 alletry/exceptPlugin-Import-Blöckeapp/plugins/base.py— fügeget_entity_models() -> dict[str, type]hinzu (default:{})- Jedes Plugin: überschreibe
get_entity_models()→ return{"file": DmsFile, "folder": DmsFolder} entity_permission_service.py— neue Funktionregister_entity_model(entity_type, model_class)main.py:lifespan()— nach Plugin-Aktivierung: iteriere Plugins, rufeget_entity_models(), registriereregistry.activate()— ruferegister_entity_model()für aktive Pluginsregistry.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:
- Plugin aktivieren → ENTITY_MODELS enthält Plugin-Entities
- Plugin deaktivieren → ENTITY_MODELS enthält Plugin-Entities nicht mehr
- Permission-Resolution für Plugin-Entity funktioniert
- 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:
- Plugin aktivieren → Plugin-Permissions in Registry
- Plugin deaktivieren → Plugin-Permissions nicht in Registry
- Permission-UI zeigt nur aktive Plugin-Permissions
- 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 neueget_valid_entity_types()Funktion - Akzeptiere jeden String, validiere zur Laufzeit gegen registrierte Entity-Types
Verifikation:
- Saved View für
taskerstellen → funktioniert - Saved View für
nonexistenterstellen → 422 - Plugin deaktivieren → Saved Views für Plugin-Entity noch abrufbar aber nicht neu erstellbar
3.5 tags/entity_links: VALID_ENTITY_TYPES dynamisch (P1-13)
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:12—EntityTypedynamisch aus API laden oder alsstringdeklarieren
Verifikation:
- Tag für
taskerstellen → funktioniert - Tag für
nonexistent→ 422 - 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:
- Dashboard zeigt Plugin-Counts an
- Plugin deaktivieren → Plugin-Counts verschwinden
Acceptance Criteria Phase 3
ENTITY_MODELSenthält keinetry/exceptPlugin-Import-Blöcke mehrCORE_PERMISSIONSenthä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→ nutzeget_contract("kommunikation")statt direktem Importapp/core/restore_registry.py:132-237→ nach Phase 2.1 erledigt (Plugins registrieren selbst)app/core/trigger_dispatcher.py:116,174→ nutzeget_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→ nutzeget_contract("dms")für File-Modellapp/commands/mail_commands.py:16,36,109,147→ nutzeget_contract("mail")app/commands/calendar_commands.py:38,104,153→ nutzeget_contract("calendar")app/commands/dms_commands.py:50,130→ nutzeget_contract("dms")app/ai/llm_client.py:292,319→ nutzeget_contract("ai_assistant")app/routes/errors.py:124→ nutzeget_contract("forgejo_error_reporter")oder mache Error-Reporting generischapp/main.py:151,171→ gleiche wie errors.py
Verifikation:
grep -rn 'from app.plugins.builtins' app/core/ app/services/ app/routes/ app/commands/ app/ai/→ 0 Treffer (außer contracts)- Plugin deaktivieren → Core funktioniert ohne Fehler (graceful degradation)
check_cross_plugin_imports.pyerweitert 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: auchapp/core/,app/services/,app/routes/,app/commands/,app/ai/scannen- Neue EXEMPT_PATHS für legitime Core-Imports (z.B.
main.pyfür Route-Registrierung) - CI-Pipeline prüft nun Core→Plugin-Imports auch
Verifikation:
python scripts/check_cross_plugin_imports.pyfindet 0 Verstöße- 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 X→get_contract("<plugin>")mit None-Check - Contracts müssen alle aktuell direkt importierten Symbole exponieren
Verifikation:
grep -rn 'from app.plugins.builtins' app/plugins/builtins/ | grep -v contracts | grep -v __init__→ 0- Alle Plugin-Tests grün
- 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(), rufeplugin.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:
- Tests laufen ohne hartcodierte Plugin-Imports
- Plugin entfernen → Tests für Plugin laufen nicht, aber andere Tests grün
- Neues Plugin → Tests finden Modelle automatisch
5.2 Plugin-Lifecycle E2E-Test (neu)
Datei: tests/test_plugin_lifecycle.py (neu)
Inhalt:
- Test-Plugin erstellen (minimal, mit Route, Permission, Entity-Model, Job)
- Plugin aktivieren via API → Route erreichbar, Permission verfügbar, Job registriert
- Plugin deaktivieren via API → Route gibt 403/404, Permission entfernt, Job entfernt
- Plugin wieder aktivieren → alles wieder da
- Plugin mit Dependency aktivieren → funktioniert nur wenn Dependency aktiv
- Plugin mit Dependency deaktivieren → wird blockiert wenn Dependency aktiv
Verifikation:
- Test ist grün
- 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.pyhat bereitscreate_test_usermit Role → nutze echte Permissions- Für
set_tenant_contextMocks: nutze echte DB-Session mit Tenant-Kontext
Verifikation:
- Tests laufen ohne Permission-Mocks
- Tests testen echte Permission-Enforcement
- Test mit unzureichenden Permissions → 403 (nicht 200)
Acceptance Criteria Phase 5
conftest.pyhat keine hartcodierten Plugin-Importstest_plugin_lifecycle.pyexistiert 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
NotificationDropdownauf Communication-API umstellen app/routes/notifications.pyals deprecated markieren oder auf Communication redirect- Langfristig:
notifications-Tabelle entfernen, alles über Communication-Plugin
Verifikation:
- Frontend nutzt Communication-API für Notifications
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 | Nonehinzufü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.py→contact_dedup_service.pyumbenennen- 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.pydeklarieren
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
- Phase 1 kann versteckte Abhängigkeiten aufdecken — wenn Plugin-Aktivierung zur Laufzeit zum ersten Mal richtig getestet wird, können neue Bugs sichtbar werden
- Phase 3.1 (ENTITY_MODELS) ist der komplexeste Fix — das Permission-System hängt davon ab
- Phase 4.3 (226 Cross-Imports) ist mechanisch aber fehleranfällig — jeder Contract muss alle Symbole exponieren
- 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:
- Plugin hinzufügen: 0 Core-Dateien ändern → Plugin in
builtins/ablegen, aktivieren - Plugin deaktivieren: Alle Routes, Permissions, Jobs, Hooks, Entity-Models entfernt
- Plugin entfernen:
uninstall→ alle Spuren gelöscht - Neue Entität: Plugin deklariert Entität in Manifest → Permissions, Tags, Links, Saved Views funktionieren
- Cross-Plugin-Checker: 0 Verstöße in Core und Plugins
- E2E-Test: Plugin-Lifecycle-Test grün ohne Mocks
Das ist das Ziel: Ein Plugin-System, das wirklich modular ist.