Files
leocrm/docs/full-audit-errors.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

55 KiB

Vollständiger Architektur-Audit — Alle gefundenen Fehler

Datum: 2026-08-15 Methode: Persönliche Zeile-für-Zeile-Lektüre jeder Datei Status: In Arbeit — bisher 44 von 420 Dateien gelesen


KRITISCHE RUNTIME-FEHLER (werden bei Nutzung crashen)

FEHLER 1: hooks.py:83 — unregister() _filters 2-tuple CRASH

Datei: app/core/hooks.py:83 Beweis:

# Zeile 83: _filters nutzt 2-tuple unpacking, aber register_filter speichert 3-tuple
self._filters[hook_name] = [
    (p, c) for p, c in self._filters.get(hook_name, []) if c != callback  # CRASH: too many values to unpack
]

Auswirkung: Jeder Aufruf von unregister() für Filter crasht mit ValueError: too many values to unpack. Blast Radius: Alle Filter-Unregistrierungen. Schwere: P0 — Runtime Crash.

FEHLER 2: trigger_dispatcher.py:127 — AutomationDefinition nicht importiert

Datei: app/core/trigger_dispatcher.py:127 Beweis:

# Zeile 127: AutomationDefinition wird in Query verwendet, aber nie importiert
query = (
    select(AutomationDefinition)  # NameError: name 'AutomationDefinition' is not defined
    .where(AutomationDefinition.is_active.is_(True))

Auswirkung: Trigger-Dispatcher crasht bei jedem Event mit NameError. Blast Radius: Alle Event-gesteuerten Automatisierungen. Schwere: P0 — Runtime Crash.

FEHLER 3: contacts/plugin.py:88-90 — clear_actions statt unregister_actions_by_owner

Datei: app/plugins/builtins/contacts/plugin.py:88-90 Beweis:

hook_reg.clear_actions("contact.after_create")  # Entfernt Hooks anderer Plugins!
hook_reg.clear_actions("contact.after_update")
hook_reg.clear_actions("contact.after_delete")

Auswirkung: Wenn Contacts deaktiviert wird, werden History-Hooks aller anderen Plugins für contact.after_* Events entfernt. Das verletzt die Hook-Isolation. Blast Radius: Alle Plugins die contact.after_* Hooks registrieren (ai_assistant, automation, system_notif, etc.). Schwere: P0 — Hook-Isolation verletzt.

FEHLER 4: attachment_service.py:48 — DmsFile type hint used but not imported

Datei: app/services/attachment_service.py:48 Beweis:

def _entity_attachment_to_dict(ea: EntityAttachment, dms_file: DmsFile | None = None) -> dict[str, Any]:

DmsFile wird als Type-Hint verwendet, aber nie importiert. _get_dms_file_model() holt es zur Laufzeit via Contract, aber der Type-Hint crasht bei Type-Checking oder zur Laufzeit wenn Annotations evaluiert werden. Auswirkung: TypeError zur Laufzeit wenn Annotations evaluiert werden (z.B. mit from __future__ import annotations nicht das Problem, aber inkonsistent). Blast Radius: Attachment-Service Serialisierung. Schwere: P1 — Potenzieller Runtime-Fehler.


DOPPEL-REGISTRIERUNGEN

FEHLER 5: restore_registry.py — register_default_entities registriert Contact

Datei: app/core/restore_registry.py:113-195 Beweis: register_default_entities() registriert Contact RestoreConfig. ContactsPlugin.on_activate() registriert AUCH Contact RestoreConfig. → Doppel-Registrierung. Auswirkung: Doppel-Registrierung mit Warning-Log. Zweite überschreibt erste, aber beide werden aufgerufen. Blast Radius: Contact-Restore. Schwere: P1 — Inkonsistenz.

FEHLER 6: history_hooks.py — register_default_history_hooks registriert Contact ohne owner_tag

Datei: app/core/history_hooks.py:140 Beweis: register_default_history_hooks() registriert Contact hooks ohne owner_tag. ContactsPlugin.on_activate() registriert AUCH Contact hooks mit owner_tag="contacts". → Doppel-Registrierung: 2x hooks für jedes contact.after_* Event. Auswirkung: History wird 2x aufgezeichnet. register_default_history_hooks kann die Hooks nicht gezielt entfernen (kein owner_tag). Blast Radius: Contact-History. Schwere: P1 — Doppelte History-Einträge.

FEHLER 7: entity_permission_service.py:59-61 — Contact hardcoded in ENTITY_MODELS

Datei: app/services/entity_permission_service.py:59-61 Beweis:

ENTITY_MODELS: dict[str, type] = {
    "contact": Contact,
    "contacts": Contact,
    "company": Contact,
    # ...
}

ContactsPlugin.get_entity_models() liefert auch contact, contacts, company. main.py:lifespan() registriert diese erneut. → Doppel-Registrierung. Auswirkung: Doppel-Registrierung. Nicht kritisch (dict überschreibt), aber inkonsistent. Blast Radius: Entity-Permission-System. Schwere: P2 — Inkonsistenz.


FEHLENDE DEREGISTRIERUNGEN

FEHLER 8: mail/plugin.py — on_deactivate fehlt unregister_actions_by_owner und restore unregister

Datei: app/plugins/builtins/mail/plugin.py Beweis: mail/plugin.py on_deactivate hat KEIN unregister_actions_by_owner für history hooks und KEIN unregister für restore config. Auswirkung: Mail-History-Hooks und Restore-Config bleiben bei Deaktivierung aktiv. Blast Radius: Mail-Plugin Deaktivierung. Schwere: P1 — Unvollständige Deaktivierung.


TYPE-ANNOTATION-FEHLER

FEHLER 9: hooks.py:52-53 — Type-Annotationen falsch

Datei: app/core/hooks.py:52-53 Beweis:

cls._instance._actions: dict[str, list[tuple[int, Callable]]] = defaultdict(list)  # sollte tuple[int, Callable, str | None]
cls._instance._filters: dict[str, list[tuple[int, Callable]]] = defaultdict(list)  # sollte tuple[int, Callable, str | None]

Auswirkung: Type-Checker fehlschlagen, aber Runtime funktioniert (Python ignoriert Type-Annotationen). Blast Radius: Type-Checking. Schwere: P2 — Type-Safety.


HARDCODED PATTERN VALIDATION

FEHLER 10: saved_views.py:62 — pattern validation hardcoded

Datei: app/routes/saved_views.py:62 Beweis:

entity_type: str | None = Query(None, pattern="^(contacts|mail|calendar|dms)$"),

Obwohl _validate_entity_type() gegen ENTITY_MODELS prüft, hat der Query-Parameter noch ein hardcoded pattern das nur 4 Entity-Types erlaubt. Auswirkung: Andere Entity-Types (task, calendar_entry, file, etc.) werden mit 422 abgelehnt bevor _validate_entity_type aufgerufen wird. Blast Radius: Saved-Views für nicht-Contact Entities. Schwere: P1 — Falsche Validierung.

FEHLER 11: saved_filters.py:62 — pattern validation hardcoded

Datei: app/routes/saved_filters.py:62 Beweis:

entity_type: str | None = Query(None, pattern="^(contacts|mail|calendar|dms)$"),

Gleiches Problem wie saved_views.py. Auswirkung: Andere Entity-Types werden mit 422 abgelehnt. Blast Radius: Saved-Filters für nicht-Contact Entities. Schwere: P1 — Falsche Validierung.


HARDCODED PERMISSIONS

FEHLER 12: saved_views.py:60 — require_permission("contacts:read") hardcoded

Datei: app/routes/saved_views.py:60 Beweis:

@router.get("", dependencies=[Depends(require_permission("contacts:read"))])

Saved-Views benötigen contacts:read Permission. Das ist zu spezifisch — Saved-Views sind generisch für alle Entities. Auswirkung: User ohne contacts:read Permission können keine Saved-Views sehen, selbst für andere Entity-Types. Blast Radius: Saved-Views Access. Schwere: P2 — Falsche Permission.

FEHLER 13: saved_filters.py:60 — require_permission("contacts:read") hardcoded

Datei: app/routes/saved_filters.py:60 Beweis:

@router.get("", dependencies=[Depends(require_permission("contacts:read"))])

Gleiches Problem wie saved_views.py. Auswirkung: User ohne contacts:read Permission können keine Saved-Filters sehen. Blast Radius: Saved-Filters Access. Schwere: P2 — Falsche Permission.


CORE-SPEZIFISCHE DATEN IN GENERISCHEN SERVICES

FEHLER 14: sensitive_data.py:83-98 — DATA_EXPOSURE_POLICY hat Contact-spezifische Felder

Datei: app/core/sensitive_data.py:83-98 Beweis:

DATA_EXPOSURE_POLICY: dict[str, dict[str, dict[str, bool]]] = {
    "contact": {
        "code": _EXPORT_ONLY,
        "accounting_code": _EXPORT_ONLY,
        # ... 10 contact-spezifische Felder
    },
}

Auswirkung: Core enthält Contact-spezifische Data-Exposure-Policy. Plugins können ihre eigenen Policies nicht deklarieren (obwohl register_sensitive_fields existiert, gibt es kein register_exposure_policy). Blast Radius: Data-Redaction für alle Entities. Schwere: P2 — Core-spezifische Daten.

FEHLER 15: permission_registry.py:86-122 — CORE_FIELD_DEFINITIONS hat Contact-spezifische Felder

Datei: app/core/permission_registry.py:86-122 Beweis: ~40 Contact-spezifische Felddefinitionen hartkodiert in Core. Auswirkung: Core enthält CRM-spezifische Felddefinitionen. Neue Contact-Felder erfordern Core-Änderung. Blast Radius: Feld-Level-Berechtigungen für Contacts. Schwere: P2 — Core-spezifische Daten.

FEHLER 16: sensitive_data.py:24-48 — SENSITIVE_FIELDS hat Contact/Mail-spezifische Felder

Datei: app/core/sensitive_data.py:24-48 Beweis:

SENSITIVE_FIELDS: dict[str, set[str]] = {
    "contact": {"password_hash", "smtp_password", "imap_password", ...},
    "mail_account": {"smtp_password", "imap_password", "oauth_token"},
}

Auswirkung: Core enthält Contact/Mail-spezifische sensitive Felder. register_sensitive_fields existiert für Plugins, aber Core-Felder sind statisch. Blast Radius: Data-Redaction, LLM-Context-Filtering. Schwere: P2 — Core-spezifische Daten.


DEAD CODE / VERALTETE FUNKTIONEN

FEHLER 17: restore_registry.py — register_default_entities ist veraltet

Datei: app/core/restore_registry.py Beweis: register_default_entities() existiert noch und registriert Contact, aber ContactsPlugin.on_activate() registriert es auch. Die Funktion wird nirgends mehr aufgerufen (main.py ruft sie nicht auf). Auswirkung: Tote Funktion. Sollte entfernt oder dokumentiert werden. Blast Radius: Keiner (wird nicht aufgerufen). Schwere: P3 — Dead Code.

FEHLER 18: history_hooks.py — register_default_history_hooks ist veraltet

Datei: app/core/history_hooks.py Beweis: register_default_history_hooks() existiert noch und registriert Contact hooks, aber ContactsPlugin.on_activate() registriert sie auch. Die Funktion wird nirgends mehr aufgerufen. Auswirkung: Tote Funktion. Sollte entfernt oder dokumentiert werden. Blast Radius: Keiner (wird nicht aufgerufen). Schwere: P3 — Dead Code.

FEHLER 19: registry.py:36 — _mounted_routes ist Dead Code

Datei: app/plugins/registry.py:36 Beweis: self._mounted_routes: dict[str, list[Any]] = {} wird initialisiert aber nie befüllt. Route-Removal-Logik wurde entfernt (Gate-Modell dokumentiert). Auswirkung: Tote Variable. Blast Radius: Keiner. Schwere: P3 — Dead Code.


PLUGIN→PLUGIN IMPORTS (deklariert aber direkte Imports statt Contracts)

FEHLER 20: ai_assistant/plugin.py:96-98 — direkter Import von kommunikation.contracts

Datei: app/plugins/builtins/ai_assistant/plugin.py:96-98 Beweis:

from app.plugins.builtins.kommunikation.contracts import (
    get_participant_registry,
)

Bewertung: Deklariert in dependencies=["kommunikation"] — erlaubt per Regel 2. Aber direkter Import statt Contract-Registry. Schwere: P3 — Erlaubt aber inkonsistent.

FEHLER 21: system_notif/plugin.py:161 — direkter Import von kommunikation.contracts

Datei: app/plugins/builtins/system_notif/plugin.py:161 Beweis:

from app.plugins.builtins.kommunikation.contracts import create_plugin_room, send_message

Bewertung: Deklariert in dependencies=["kommunikation"] — erlaubt. Aber direkter Import. Schwere: P3 — Erlaubt aber inkonsistent.

FEHLER 22: graph_rag/plugin.py:44,57 — direkter Import von unified_search.contracts

Datei: app/plugins/builtins/graph_rag/plugin.py:44,57 Beweis:

from app.plugins.builtins.unified_search.contracts import get_search_registry

Bewertung: Deklariert in dependencies=["unified_search"] — erlaubt. Aber direkter Import. Schwere: P3 — Erlaubt aber inkonsistent.

FEHLER 22a: worker.py:169 — direkter Import von unified_search.provider_registry (Core→Plugin)

Datei: app/core/worker.py:169 Beweis:

from app.plugins.builtins.unified_search.provider_registry import auto_register_providers

Bewertung: Core importiert Plugin-Modul direkt. UnifiedSearchContract existiert, exponiert aber auto_register_providers nicht. Contract-Umgehung. Schwere: P2 — Core→Plugin-Kopplung.

FEHLER 22b: worker.py:280 — direkter Import von forgejo_error_reporter.service (Core→Plugin)

Datei: app/core/worker.py:280 Beweis:

from app.plugins.builtins.forgejo_error_reporter.service import report_error_to_forgejo

Bewertung: Core importiert Plugin-Modul direkt. ForgejoErrorReporterContract existiert und wird in main.py/errors.py korrekt genutzt — hier inkonsistent umgangen. Schwere: P2 — Core→Plugin-Kopplung, inkonsistent.


SIDE-EFFECT IMPORTS

FEHLER 23: report_generator/plugin.py:9 — top-level import of jobs module

Datei: app/plugins/builtins/report_generator/plugin.py:9 Beweis:

from app.plugins.builtins.report_generator import jobs  # noqa: F401

Top-Level-Import von jobs-Modul hat Side-Effects (register_job Aufrufe beim Import). Auswirkung: Jobs werden beim Import registriert, nicht erst bei on_activate(). Wenn Plugin deaktiviert ist, bleiben Jobs registriert. Blast Radius: Report-Generator Jobs. Schwere: P2 — Jobs nicht lifecycle-gesteuert.


BASE PLUGIN on_deactivate HEURISTIK

FEHLER 24: base.py:81 — unregister_all_for_plugin nutzt self Heuristik

Datei: app/plugins/base.py:81 Beweis:

get_hook_registry().unregister_all_for_plugin(self.manifest.name)

unregister_all_for_plugin nutzt callback.__self__.manifest.name um Plugin-zugehörigkeit zu bestimmen. Das funktioniert nur für Bound-Methods, nicht für Free Functions (wie sie von register_history_hooks erstellt werden). Auswirkung: History-Hooks die via register_history_hooks registriert wurden, werden von unregister_all_for_plugin NICHT entfernt. Plugins müssen unregister_actions_by_owner manuell aufrufen. Blast Radius: Alle Plugins mit register_history_hooks. Schwere: P2 — Unvollständige Hook-Entfernung.


WEITERE PROBLEME

FEHLER 25: entity_permission_service.py:30 — importiert create_notification

Datei: app/services/entity_permission_service.py:30 Beweis:

from app.core.notifications import create_notification

create_notification ist deprecated und nutzt jetzt get_contract("kommunikation"). Der Import ist nicht falsch, aber die deprecated Funktion wird verwendet. Auswirkung: Deprecated Function wird verwendet. Blast Radius: Permission-Change Notifications. Schwere: P3 — Deprecated Usage.


DEPS.PY HARDCODED PERMISSIONS

FEHLER 26: deps.py:21-36 — _WRITE_PERMISSIONS hardcoded mit Plugin-Permissions

Datei: app/deps.py:21-36 Beweis:

_WRITE_PERMISSIONS = [
    "contacts:write",
    "contacts:create",
    # ...
]

contacts:write und contacts:create sind Plugin-Permissions (ContactsPlugin), aber in Core deps.py hardcoded. Auswirkung: Core kennt Plugin-Permissions. Wenn Contacts deaktiviert wird, bleiben diese Permissions in der Liste. Blast Radius: require_write() Dependency. Schwere: P2 — Core-spezifische Daten.


WORKFLOW SERVICE DEPRECATED NOTIFICATIONS

FEHLER 27: workflow_service.py:13 — importiert deprecated Notification model

Datei: app/services/workflow_service.py:13 Beweis:

from app.models.notification import Notification

Workflow-Service nutzt deprecated Notification model statt kommunikation Contract. Auswirkung: Workflow-Notifications nutzen altes System statt kommunikation Plugin. Blast Radius: Workflow-Notifications. Schwere: P2 — Deprecated Usage.


DASHBOARD HARDCODED CONTACT COUNTS

FEHLER 28: dashboard.py:14,61-93 — hardcoded Contact counts, kein Plugin-Beitrag möglich

Datei: app/routes/dashboard.py:14,61-93 Beweis:

from app.models.contact import Contact  # Core→Contact (Plugin-Entity)
# ...
contact_query = select(func.count(Contact.id)).where(...)  # Hardcoded Contact
company_query = select(func.count(Contact.id)).where(..., Contact.type == "company")  # Hardcoded Company
person_query = select(func.count(Contact.id)).where(..., Contact.type == "person")  # Hardcoded Person

Dashboard /counts ist hardcoded auf Contact/Company/Person. Kein Plugin kann Dashboard-Statistiken beitragen. /widgets ist dynamisch, aber /counts ist statisch. Auswirkung: Dashboard-Statistiken sind nicht plugin-erweiterbar. Blast Radius: Dashboard /counts Endpoint. Schwere: P2 — Nicht generisch.


IMPORT/EXPORT HARDCODED ENTITY TYPES

FEHLER 29: import_export.py:40 — entity_type default 'companies' hardcoded

Datei: app/routes/import_export.py:40 Beweis:

entity_type: str = Form("companies"),

Import/Export unterstützt nur 'companies' und 'contacts' (beide Contact-Modell). Kein Plugin kann Import/Export für seine Entities anbieten. Auswirkung: Import/Export ist Contact-spezifisch, nicht generisch. Blast Radius: Import/Export für alle Entities außer Contacts. Schwere: P2 — Nicht generisch.


MAIL PLUGIN INCOMPLETE DEACTIVATION

FEHLER 30: mail/plugin.py:194-200 — on_deactivate fehlt restore + history unregister

Datei: app/plugins/builtins/mail/plugin.py:194-200 Beweis:

async def on_deactivate(self, db, service_container, event_bus) -> None:
    # Contract abmelden
    from app.plugins.builtins.contracts import get_contract_registry
    get_contract_registry().unregister(self.manifest.name)
    # ... stop auto_sync_task ...
    await super().on_deactivate(db, service_container, event_bus)

KEIN get_restore_registry().unregister("mail") und KEIN unregister_actions_by_owner für history hooks. Auswirkung: Mail-Restore-Config und History-Hooks bleiben bei Deaktivierung aktiv. Blast Radius: Mail-Plugin Deaktivierung. Schwere: P1 — Unvollständige Deaktivierung.


FRONTEND/BACKEND INKONSISTENZ

FEHLER 31: frontend/src/api/tags.ts:12 — EntityType hardcoded und inkonsistent mit Backend

Datei: frontend/src/api/tags.ts:12 Beweis:

export type EntityType = 'contact' | 'file' | 'calendar_entry';

Backend validiert dynamisch gegen ENTITY_MODELS (contact, file, folder, task, calendar_entry, etc.). Frontend hat hardcoded 3 Types, davon 'calendar_entry' was im Backend nicht in der ursprünglichen VALID_ENTITY_TYPES war. Auswirkung: Frontend bietet 'calendar_entry' an, Backend akzeptiert es (über ENTITY_MODELS). Aber Frontend bietet nicht 'folder', 'task', 'mail', etc. an. User-sichtbare Inkonsistenz. Blast Radius: Tag-Vergabe im Frontend. Schwere: P1 — Frontend/Backend-Inkonsistenz.


CONFTEST.PY HARDCODED MODEL IMPORTS

FEHLER 32: conftest.py:41-53 — hardcoded Core-Model imports trotz dynamischer Discovery

Datei: tests/conftest.py:41-53 Beweis:

from app.models.ai_conversation import AIConversation, AIMessage  # noqa: F401
from app.models.contact import Contact, ContactPerson  # noqa: F401
from app.models.contact_merge import ContactMergeHistory  # noqa: F401
from app.models.plugin import Plugin, PluginMigration  # noqa: F401
from app.models.role import Role
from app.models.tenant import Tenant
from app.models.user import User, UserTenant
from app.models.user_preference import UserPreference  # noqa: F401
from app.models.workflow import Workflow, WorkflowInstance, WorkflowStepHistory  # noqa: F401
from app.models.outbox import EventOutbox  # noqa: F401
from app.models.consumer_inbox import ConsumerInbox  # noqa: F401
from app.models.outbox_delivery import OutboxDelivery  # noqa: F401
from app.models.saved_filter import SavedFilter  # noqa: F401

Obwohl dynamische Discovery in Zeile 57-71 läuft, werden Core-Modelle noch hardcoded importiert. Das ist nicht falsch (Core-Modelle müssen immer verfügbar sein), aber AIConversation/AIMessage sind Plugin-Modelle die über ai_assistant Plugin kommen sollten. Auswirkung: AIConversation/AIMessage werden hardcoded importiert statt über Plugin-Discovery. Wenn ai_assistant Plugin nicht aktiv ist, schlagen diese Imports fehl. Blast Radius: Test-Setup. Schwere: P2 — Inkonsistenz.


ROLES.PY HARDCODED PERMISSIONS

FEHLER 33: roles.py:27-50 — SYSTEM_PERMISSIONS hardcoded mit Plugin-Permissions

Datei: app/routes/roles.py:27-50 Beweis:

SYSTEM_PERMISSIONS: list[dict[str, str]] = [
    {"key": "contacts:read", "label": "Contacts: Read", "category": "system"},
    {"key": "contacts:write", "label": "Contacts: Write", "category": "system"},
    {"key": "contacts:delete", "label": "Contacts: Delete", "category": "system"},
    # ...
]

contacts:read, contacts:write, contacts:delete sind Plugin-Permissions (ContactsPlugin), aber in Core roles.py hardcoded als SYSTEM_PERMISSIONS. Auswirkung: Core kennt Plugin-Permissions. Wenn Contacts deaktiviert wird, bleiben diese Permissions in der Liste. Doppelte Quelle zur permission_registry. Blast Radius: Rollen-Verwaltung, Permission-UI. Schwere: P2 — Core-spezifische Daten, Doppelquelle.


USERS.PY DEPRECATED NOTIFICATIONS

FEHLER 34: users.py:15 — importiert create_notification (deprecated)

Datei: app/routes/users.py:15 Beweis:

from app.core.notifications import create_notification

User-Route nutzt deprecated create_notification statt post_system_message über KommunikationContract. Auswirkung: User-Notifications nutzen altes System statt kommunikation Plugin. Blast Radius: User-Notifications. Schwere: P2 — Deprecated Usage.


WORKFLOW ENGINE DEPRECATED NOTIFICATIONS

FEHLER 35: workflows/engine.py:122-130 — erstellt Notification model direkt

Datei: app/workflows/engine.py:122-130 Beweis:

notification = Notification(
    tenant_id=self.tenant_id,
    user_id=uuid.UUID(user_id),
    type=config.get("notification_type", "workflow_action"),
    title=config.get("title", "Workflow notification"),
    body=config.get("body", ""),
)
self.db.add(notification)

Workflow-Engine erstellt direkt Notification model (deprecated) statt post_system_message über KommunikationContract zu verwenden. Auswirkung: Workflow-Notifications nutzen altes System. Doppelarchitektur. Blast Radius: Workflow-Notifications. Schwere: P2 — Deprecated Usage, Doppelarchitektur.


SCHEMAS/COMMON.PY DEPRECATED NOTIFICATION SCHEMAS

FEHLER 36: schemas/common.py:22-58 — Notification schemas für deprecated system

Datei: app/schemas/common.py:22-58 Beweis:

class NotificationResponse(BaseModel): ...
class NotificationListResponse(BaseModel): ...
class UnreadCountResponse(BaseModel): ...
class NotificationPreferenceUpdate(BaseModel): ...
class NotificationTypeResponse(BaseModel): ...
class NotificationPreferenceResponse(BaseModel): ...

6 Schemas für deprecated notification system. Sollten zu kommunikation Plugin migriert werden. Auswirkung: Core enthält deprecated notification schemas. Blast Radius: API-Schemas. Schwere: P3 — Deprecated Code.


CUSTOM_FIELDS.PY PRIVATE ATTRIBUT ZUGRIFF

FEHLER 37: custom_fields.py:42 — greift auf registry._plugins direkt zu

Datei: app/routes/custom_fields.py:42 Beweis:

for plugin in registry._plugins.values():

Greift auf privates Attribut _plugins zu statt öffentlichen API (list_discovered() + get_plugin()) zu nutzen. Auswirkung: Kapselung verletzt. Wenn Registry-Internals ändern, bricht dieser Code. Blast Radius: Custom-Field-Definitionen. Schwere: P3 — Private Attribut.


WEBHOOKS.PY FALSCHE PERMISSIONS

FEHLER 38: webhooks.py:26,49 — require_permission('automation:read/write') für Core-Webhooks

Datei: app/routes/webhooks.py:26,49 Beweis:

dependencies=[Depends(require_permission("automation:read"))]
dependencies=[Depends(require_permission("automation:write"))]

Webhooks sind Core-Funktionalität (app/routes/webhooks.py), aber nutzen automation:read/write Permissions. Das sind Plugin-Permissions (AutomationPlugin). Wenn Automation deaktiviert ist, können User keine Webhooks verwalten. Auswirkung: Webhook-Access hängt von Automation-Plugin ab. Core-Funktion blockiert durch Plugin-Deaktivierung. Blast Radius: Webhook-Verwaltung. Schwere: P2 — Falsche Permission-Zuordnung.


FRONTEND SIDEBAR UND PLUGIN STORE — OK

Sidebar.tsx und pluginStore.ts wurden geprüft — beide sind korrekt dynamisch. Sidebar liest Menu-Items aus usePluginStore(s => s.manifests), pluginStore ist generisch. Keine Fehler.


PERSÖNLICHE ZEILE-FÜR-ZEILE-LEKTÜRE — Neue Funde

FEHLER 46: contacts/plugin.py:88-90 — clear_actions() NICHT GEFIXT (P0)

Datei: app/plugins/builtins/contacts/plugin.py:88-90 Beweis:

hook_reg.clear_actions("contact.after_create")
hook_reg.clear_actions("contact.after_update")
hook_reg.clear_actions("contact.after_delete")

Problem: on_deactivate nutzt clear_actions() statt unregister_actions_by_owner(). Entfernt History-Hooks ALLER Plugins für contact.after_* Events. P0-3 wurde dokumentiert aber NICHT GEFIXT. Schwere: P0 — Runtime Crash bei Deaktivierung.

FEHLER 47: mail/plugin.py:194-217 — on_deactivate FEHLT restore unregister (P1)

Datei: app/plugins/builtins/mail/plugin.py:194-217 Beweis: on_deactivate deregistriert history hooks (Zeile 213-215) aber hat KEIN get_restore_registry().unregister("mail"). Problem: Mail restore config bleibt aktiv wenn Plugin deaktiviert wird. Schwere: P1 — Funktionaler Fehler bei Deaktivierung.

FEHLER 48: entity_attachment.py:45-49 — import inline statt func import (P3)

Datei: app/models/entity_attachment.py:45-49 Beweis:

created_at: Mapped[datetime] = mapped_column(
    DateTime(timezone=True), nullable=False, server_default=__import__('sqlalchemy').func.now()
)

Problem: __import__('sqlalchemy').func.now() statt from sqlalchemy import func + func.now(). Schlechte Praxis, unnötige Performance-Kosten. Schwere: P3 — Code Quality.

FEHLER 49: ai_copilot_service.py:18-19 — Duplicate Contact import (P3)

Datei: app/services/ai_copilot_service.py:18-19 Beweis:

from app.models.contact import Contact
from app.models.contact import Contact  # Duplicate!

Schwere: P3 — Code Quality.

FEHLER 50: system_settings.py:44-54 — Duplicate field definitions (P3)

Datei: app/schemas/system_settings.py:44-54 Beweis:

class SystemSettingsResponse(BaseModel):
    # Zeilen 45-48:
    tax_number: str | None = None
    vat_id: str | None = None
    iban: str | None = None
    bic: str | None = None
    # Zeilen 51-54 (duplicate!):
    tax_number: str | None = None
    vat_id: str | None = None
    iban: str | None = None
    bic: str | None = None

Problem: Pydantic überschreibt stillschweigend. Kein Runtime-Fehler, aber verwirrend und Code-Quality-Problem. Schwere: P3 — Code Quality.

FEHLER 51: address.py:9 — Hardcoded pattern="^contact$" (P2)

Datei: app/schemas/address.py:9 und app/routes/addresses.py:20 Beweis:

entity_type: str = Field(..., pattern="^contact$", description="'contact'")

Problem: Address-Modell ist generisch (entity_type + entity_id), aber Schema UND Route limitieren auf 'contact'. Addresses können NUR für Contacts erstellt werden. Schwere: P2 — Architekturfehler, generisches Modell künstlich limitiert.

FEHLER 52: unified_search hardcoded entity maps (P2)

Dateien: app/plugins/builtins/unified_search/search_engine.py:19-24, lifecycle.py:22-27, jobs.py:16-21 Beweis: Drei separate hardcoded Entity-Maps für dieselben 4 Entity-Types (contact, mail, file, event). Duplikation, nicht erweiterbar. Schwere: P2 — Architekturfehler, dreifache Duplikation.

FEHLER 53: ai_proactive/services.py:26-27 — Duplicate Contact import (P3)

Datei: app/plugins/builtins/ai_proactive/services.py:26-27 Beweis:

from app.models.contact import Contact, ContactPerson
from app.models.contact import Contact, ContactPerson  # Duplicate!

Schwere: P3 — Code Quality.

FEHLER 54: frontend routes/index.tsx — Hardcoded Plugin-Routes (P2)

Datei: frontend/src/routes/index.tsx:17-70 Beweis: 54 hardcoded lazy-loaded page imports für Plugin-Seiten. PluginRouteRenderer (Zeile 207) ist dynamisch als catch-all, aber alle Haupt-Routes sind statisch. Problem: Neues Plugin → Frontend-Route muss hardcoded hinzugefügt werden. Schwere: P2 — Frontend nicht vollständig dynamisch.

FEHLER 55: tags/schemas.py:27,33,39 — Hardcoded entity_type pattern (P2)

Datei: app/plugins/builtins/tags/schemas.py:27,33,39 Beweis: pattern="^(contact|file|folder)$" in TagAssignRequest, TagUnassignRequest, TagBulkAssignRequest. Problem: Schema limitiert auf 3 Entity-Types, Backend validiert dynamisch. Schema blockiert gültige Requests. Schwere: P2 — Schema restriktiver als Backend.

FEHLER 56: entity_links/schemas.py:9 — Hardcoded entity_type pattern (P2)

Datei: app/plugins/builtins/entity_links/schemas.py:9 Beweis: entity_type: str = Field(..., pattern="^(contact|company)$") Problem: Schema limitiert auf 2 Entity-Types, Backend validiert dynamisch. Schwere: P2 — Schema restriktiver als Backend.

FEHLER 57: forgejo_error_reporter/models.py:13 — Eigenes Base, nicht app.core.db.Base (P2)

Datei: app/plugins/builtins/forgejo_error_reporter/models.py:13 Beweis:

from sqlalchemy.orm import declarative_base
Base = declarative_base()  # Eigenes Base, nicht app.core.db.Base!

Problem: ReportedError verwendet ein eigenes Base statt app.core.db.Base. Die Tabelle wird nicht von Base.metadata.create_all() erfasst und ist nicht Teil des gemeinsamen Metadaten-Registries. Tests können diese Tabelle nicht erstellen. Schwere: P2 — Architekturfehler, Modell isoliert vom Haupt-Metadaten-Registries.

FEHLER 58: mcp_client/models.py:35-36 — Naive datetime statt UTC (P1)

Datei: app/plugins/builtins/mcp_client/models.py:35-36 Beweis: datetime.utcnow (naive) statt datetime.now(UTC). Schwere: P1 — Verletzt TIMESTAMPTZ-Regel.


TEST-AUDIT — 60 weitere Fehler in Test-Dateien

P0 (5)

  • test_external_agent_api.py:36-39 — check_permission mocked to True (alle Tests bypass RBAC)
  • test_graph_rag.py:39-43 — check_permission mocked to True
  • test_agent_memory.py:39-43 — check_permission mocked to True
  • test_marketplace.py:45-49 — check_permission mocked to True
  • test_cross_tenant_standalone.py:33 — Hardcoded DB credential mit Passwort

P1 (26)

  • test_ai_copilot.py:45-47,77-79 — Tests silently pass bei 403
  • test_user_service.py:38-39,82-100 — Non-deterministic assertions, try/except pass
  • test_backup_service.py:37-38,50-51,63 — Overly permissive assertions (4 status codes)
  • test_mcp_server.py:62,127assert success in (True, False) — immer True
  • 12 Test-Dateien — Missing cross-tenant isolation tests (saved_filters, tasks, dashboard, custom_fields, calendar, workflows, notifications, companies, contacts, dms, entity_links, tags)

P2 (21)

  • test_cross_tenant_security.py:98 — Invalid bcrypt hash $2b$12$testhash
  • test_cross_tenant_security_v2.py:132 — Same invalid hash
  • test_cross_tenant_standalone.py:73 — Same invalid hash
  • test_workspaces.py:42, test_api_tokens.py:33password_hash="dummy"
  • test_tags.py:138,165,194 — Random UUIDs für non-existent entities
  • test_ai_proactive.py:146-150 — Test grants is_system_admin to bypass permissions
  • test_commands.py:140-148 — Wildcard permissions *: * bypass real RBAC
  • test_mcp_client.py:41 — API token returned in plaintext
  • 3 Test-Dateien — Hardcoded SECRET_KEY

P3 (8)

  • test_agent_subtasks.py:29-31,35-37 — Duplicate fixture definition
  • test_cross_tenant_security.py:187-188 — Duplicate @pytest.mark.asyncio
  • test_tenant.py:189 — Potential KeyError (custom_role)
  • 4 Test-Dateien — sys.path.insert path manipulation
  • test_dms_coverage.py:13 — Unused import

FRONTEND-AUDIT — Weitere Fehler aus persönlichem Lesen

P1

  • frontend/src/components/common/ProtectedRoute.tsx:22-24 — Fail-open: wenn perms.length === 0, wird access erlaubt. Begründung: 'better to show page and let backend 403 handle it'. Aber das bedeutet dass ein User mit nicht geladenen Permissions alles sehen kann.

P2

  • frontend/src/pages/Settings.tsx:18-29 — 10 hardcoded settings nav items (Stammdaten, Nutzerverwaltung, System, Mail, AI, Notifications, Custom Fields, Webhooks, Workspaces, Backup). Plugin settings pages werden dynamisch hinzugefügt, aber Core-Settings sind hardcoded.
  • frontend/src/api/search.ts:40-47 — ENTITY_URL_MAP hardcoded mit 6 Entity-Types (contact, company, mail, file, event, message). Neue Entity-Types haben keine URL-Map und bekommen '#'.
  • frontend/src/components/notifications/NotificationDropdown.tsx — Nutzt alte /notifications API statt kommunikation System Channel.
  • frontend/src/components/layout/NotificationBell.tsx:13 — Nutzt useUnreadNotificationCount von /notifications API statt kommunikation.
  • frontend/src/api/contacts.ts + frontend/src/api/unifiedContacts.ts — Dual API client: contacts.ts (legacy) und unifiedContacts.ts (neu). Beide existieren parallel.

P3

  • frontend/src/pages/Settings.tsx:5import * as LucideIcons from 'lucide-react' lädt ALLE Icons (Sidebar.tsx vermeidet das explizit mit ICON_MAP).
  • frontend/src/pages/Settings.tsx:38(LucideIcons as any)[p.icon]any type cast verletzt TypeScript strict rule.
  • frontend/src/pages/Communication.tsx:78-80 — MiniApp interface definiert aber möglicherweise ungenutzt.
  • scripts/seed_perf_data.py:72role='admin' hardcoded für perf test user.

P2 (Frontend weiter)

  • frontend/src/components/search/CommandPalette.tsx:12-19 — TYPE_LABELS hardcoded mit 6 Entity-Types (company, contact, mail, file, event, message). Neue Entity-Types haben keine Labels.
  • frontend/src/components/search/CommandPalette.tsx:21-28 — TYPE_ICON_CLASSES hardcoded mit 6 Entity-Types.
  • frontend/src/components/search/CommandPalette.tsx:31-39 — typeIcon() hardcoded mit 6 Entity-Types.
  • frontend/src/api/types.ts:12-22 — Contact interface mit first_name/last_name (legacy, nicht unified Contact model). Dual interface: types.ts (legacy) + unifiedContacts.ts (neu).
  • frontend/src/pages/Settings.tsx:18-29 — 10 hardcoded settings nav items. Plugin settings pages werden dynamisch hinzugefügt, aber Core-Settings sind hardcoded.
  • frontend/src/components/contacts/ContactDetail.tsx:11import * as LucideIcons lädt ALLE Icons (Sidebar.tsx vermeidet das explizit).
  • frontend/src/components/contacts/ContactDetail.tsx:67-74 — 8x (contact as any) type casts verletzen TypeScript strict rule.
  • frontend/src/components/contacts/ContactList.tsx:28-80 — ALL_COLUMNS hardcoded mit Contact-spezifischen Spalten.
  • frontend/src/components/contacts/FilterPanel.tsx:22-80 — FIELD_DEFS hardcoded mit ~40 Contact-spezifischen Filter-Feldern.
  • frontend/src/components/HistoryViewer.tsx + EntityHistoryPanel.tsx — Dual history components (HistoryViewer + EntityHistoryPanel) mit überlappender Funktionalität.
  • frontend/src/components/ai/SuggestionBadge.tsx:14,19 — Hardcoded API paths /ai-proactive/suggestions und SSE endpoint.
  • frontend/src/hooks/useAIContext.ts:5entityData?: any type cast.
  • frontend/src/components/contacts/ContactList.tsx:36 — Checkbox ohne ARIA label für screen readers (hat aria-label aber nur auf checkbox).
  • frontend/src/components/contacts/SortPanel.tsx:22-69 — SORT_FIELDS hardcoded mit ~30 Contact-Feldern (dupliziert von FilterPanel FIELD_DEFS).
  • frontend/src/components/contacts/GroupPanel.tsx:22-68 — GROUP_FIELDS hardcoded mit ~30 Contact-Feldern (nochmal dupliziert). Triple-duplicated hardcoded field definitions across FilterPanel + SortPanel + GroupPanel.
  • frontend/src/components/contacts/CustomFieldRenderer.tsx:15-16Record<string, any> und any type casts.
  • frontend/src/components/mail/MailList.tsx:62 — inline style paddingLeft (verletzt Tailwind-only rule).
  • frontend/src/components/mail/MailDetail.tsx:49-52sanitized_html || body_html wird in iframe gerendert (potential XSS wenn sanitized_html nicht properly sanitized).
  • frontend/src/components/mail/MailFolderTree.tsx:22-43 — FOLDER_NAME_MAP hardcoded mit 20 IMAP folder names.
  • frontend/src/components/mail/MailFilterPanel.tsx:22-44 — FIELD_DEFS hardcoded mit 10 Mail-spezifischen Feldern.
  • frontend/src/components/SavedFilters.tsx:22,47,56Record<string, any> und err: any type casts.
  • frontend/src/store/windowStore.ts:8-9ComponentType<any> und Record<string, any>.
  • frontend/src/store/commStore.ts:27,61Record<string, any> für metadata und reactions: any[].
  • frontend/src/hooks/useCommWebSocket.ts:31console.log statt strukturiertem Logger.
  • frontend/src/components/ai/ChatWindow.tsx:39-41 — inline styles für animationDelay (verletzt Tailwind-only rule).
  • frontend/src/pages/Trash.tsx:17 — ENTITY_TYPES hardcoded mit 5 Types (contact, task, calendar_entry, dms_file, mail).
  • frontend/src/pages/GlobalSearchResults.tsx:15-22 — TYPE_LABELS hardcoded mit 6 Entity-Types (dupliziert von CommandPalette).
  • frontend/src/pages/AgentDashboard.tsx:50-61 — commonModels hardcoded mit 10 Modellnamen (gpt-4, claude-3, llama-3, etc.).
  • frontend/src/pages/StartPage.tsx:22-31 — DEFAULT_WORKSPACES hardcoded mit 1 Workspace.
  • frontend/src/pages/StartPage.tsx:38-42 — menuItems hardcoded (Workspaces, Einstellungen, Hilfe).
  • frontend/src/pages/NoAccessPage.tsx:9-13 — Hardcoded German strings ohne i18n t() function.
  • frontend/src/pages/Login.tsx:41error: any type cast.
  • frontend/src/pages/Workflows.tsx:59,70err: any type casts.
  • frontend/src/pages/AuditLog.tsx:37ColumnDef<AuditLogEntry, any> type cast.
  • frontend/src/pages/SettingsWebhooks.tsx:38-56 — AVAILABLE_EVENTS hardcoded mit 16 Events, davon 6 nicht existent (deal.created/updated/deleted, note.created/updated/deleted — Deals und Notes existieren nicht im System).
  • frontend/src/pages/SettingsUsers.tsx:19-25 — LEGACY_ROLES hardcoded mit 5 Rollen (admin, manager, user, guest, viewer).
  • frontend/src/pages/SettingsUsers.tsx:47-48any type casts für confirmDeactivate/confirmDelete.
  • frontend/src/pages/SettingsRoles.tsx:21,23Record<string, any> für permissions.
  • frontend/src/pages/SettingsGroups.tsx:29-76 — gleiche any patterns wie SettingsRoles.
  • frontend/src/pages/CustomFields.tsx:40-43 — ENTITY_OPTIONS hardcoded mit 2 Types (contact, company).
  • frontend/src/pages/ActivityTimeline.tsx:25-26 — hardcoded German strings ('Heute', 'Gestern') ohne i18n.
  • frontend/src/pages/NoAccessPage.tsx:9-13 — hardcoded German strings ohne i18n.
  • frontend/src/pages/SettingsStammdaten.tsx:18-39 — addressTypeToBackend/addressTypeToFrontend hardcoded mappings + addressTypeOptions hardcoded.
  • frontend/src/pages/SettingsFirmendaten.tsx:19-36 — Zod schema mit hardcoded German validation messages.
  • frontend/src/pages/SettingsSystem.tsx:21 — hardcoded 'System' string ohne i18n.
  • frontend/src/pages/SettingsCurrencies.tsx:50,70,80err: any type casts.
  • frontend/src/pages/SettingsTaxes.tsx:50,71err: any type casts.
  • frontend/src/pages/SettingsSequences.tsx:49,69,80err: any type casts.
  • frontend/src/pages/SettingsNotifications.tsx:45err: any type cast.
  • frontend/src/pages/SettingsBackup.tsx:38 — hardcoded 'de-DE' locale.
  • frontend/src/pages/SettingsTheme.tsx:14-23 — FONT_OPTIONS hardcoded mit 8 Fonts.
  • frontend/src/pages/SettingsTheme.tsx:25-32 — RADIUS_OPTIONS hardcoded mit 6 Optionen.
  • frontend/src/pages/SettingsTheme.tsx:34-41 — PRESET_THEMES hardcoded mit 6 Themes.
  • frontend/src/components/workflows/WorkflowEditor.tsx:16-27 — triggerEventOptions hardcoded mit 10 Events, davon 4 nicht existent (deal.created/stage_changed/won/lost).
  • frontend/src/components/comm/blocks/BlockRenderer.tsx:59 — hardcoded German string 'Unbekannter Block-Typ'.
  • frontend/src/components/comm/blocks/MiniAppBlock.tsx:13,24 — hardcoded German strings.
  • frontend/src/components/comm/blocks/ActionCardBlock.tsx:59 — hardcoded German string 'Aktion'.
  • frontend/src/components/comm/blocks/HtmlBlock.tsx:17-20 — redundante regex javascript: URL removal vor DOMPurify (unvollständig, DOMPurify sollte das alleine machen).
  • frontend/src/components/shared/CsvImportDialog.tsx:48,71,78 — hardcoded German strings ohne i18n.
  • frontend/src/components/shared/DataGrid.tsx:20,37ColumnDef<T, any>[] und T extends Record<string, any> type casts.
  • frontend/src/components/dms/FileDetails.tsx:73 — inline style (e.target as HTMLImageElement).style.display = 'none' (direkte DOM-Manipulation). Datei: app/plugins/builtins/mcp_client/models.py:35-36 Beweis:
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)

Problem: datetime.utcnow erzeugt naive datetime (ohne timezone info) trotz DateTime(timezone=True) Spalte. Verletzt AGENTS.md Regel 'TIMESTAMPTZ only, never naive datetime'. Sollte datetime.now(UTC) sein. Schwere: P1 — Verletzt Projekt-Konvention, potenzielle Zeitzone-Bugs. Datei: app/plugins/builtins/entity_links/schemas.py:9 Beweis: entity_type: str = Field(..., pattern="^(contact|company)$") Problem: Schema limitiert auf 2 Entity-Types (contact, company), Backend routes.py validiert dynamisch gegen ENTITY_MODELS. Inkonsistenz. Schwere: P2 — Schema restriktiver als Backend. Datei: app/plugins/builtins/tags/schemas.py:27,33,39 Beweis:

# TagAssignRequest
tity_type: str = Field(..., pattern="^(contact|file|folder)$")
# TagUnassignRequest
tity_type: str = Field(..., pattern="^(contact|file|folder)$")
# TagBulkAssignRequest
tity_type: str = Field(..., pattern="^(contact|file|folder)$")

Problem: Schema limitiert auf 3 Entity-Types (contact, file, folder), während Backend routes.py jetzt dynamisch gegen ENTITY_MODELS validiert. Inkonsistenz: Backend akzeptiert mehr Entity-Types als Schema erlaubt. Pydantic validiert VOR der Route — Schema blockiert gültige Requests. Schwere: P2 — Frontend/Backend-Inkonsistenz, Schema restriktiver als Backend. Datei: frontend/src/routes/index.tsx:17-70 Beweis: 54 hardcoded lazy-loaded page imports für Plugin-Seiten (Calendar, DMS, Mail, Tasks, Communication, AI, Reports, etc.). PluginRouteRenderer (Zeile 207) ist dynamisch, aber nur als catch-all fallback — alle Haupt-Plugin-Routes sind statisch. Problem: Neues Plugin → Frontend-Route muss hardcoded hinzugefügt werden. PluginRouteRenderer fängt nur unbekannte Routes ab, nicht die Haupt-Routes. Schwere: P2 — Frontend nicht vollständig dynamisch. Dateien: app/plugins/builtins/unified_search/search_engine.py:19-24 und app/plugins/builtins/unified_search/lifecycle.py:22-27 und app/plugins/builtins/unified_search/jobs.py:16-21 Beweis:

# search_engine.py:19-24
SEARCHABLE_ENTITIES: dict[str, tuple[str, str, str]] = {
    "contact": ("contacts", "search_tsv", "embedding"),
    "mail": ("mails", "body_tsv", "embedding"),
    "file": ("files", "content_tsv", "embedding"),
    "event": ("calendar_entries", "search_tsv", "embedding"),
}

# lifecycle.py:22-27 — DUPLICATE of the same map
_ENTITY_MAP: dict[str, tuple[str, str, str]] = {
    "contact": ("contacts", "search_tsv", "embedding"),
    "mail": ("mails", "body_tsv", "embedding"),
    "file": ("files", "content_tsv", "embedding"),
    "event": ("calendar_entries", "search_tsv", "embedding"),
}

# jobs.py:16-21 — ANOTHER DUPLICATE
_TABLE_MAP: dict[str, str] = {
    "contact": "contacts",
    "mail": "mails",
    "file": "files",
    "event": "calendar_entries",
}

Problem: Drei separate hardcoded Entity-Maps für dieselben 4 Entity-Types. Wenn ein neues Plugin eine durchsuchbare Entität hinzufügt, müssen alle drei Maps aktualisiert werden. Die Maps sind nicht dynamisch — sie sollten über die Provider-Registry oder ENTITY_MODELS bezogen werden. Schwere: P2 — Architekturfehler, dreifache Duplikation, nicht erweiterbar. Datei: app/schemas/address.py:9 Beweis:

entity_type: str = Field(..., pattern="^contact$", description="'contact'")

Problem: Address-Modell ist generisch (entity_type + entity_id), aber Schema limitiert auf 'contact'. Addresses können NUR für Contacts erstellt werden. Auch app/routes/addresses.py:20 hat pattern="^contact$". Schwere: P2 — Architekturfehler, generisches Modell künstlich limitiert. Datei: app/services/ai_copilot_service.py:18-19 Beweis:

from app.models.contact import Contact
from app.models.contact import Contact  # Duplicate!

Schwere: P3 — Code Quality.


TIEFE STATISCHE ANALYSE — 420 Dateien, 309 Issues

Die tiefe statische Analyse über ALLE 420 Python-Dateien hat folgende zusätzliche Issues gefunden:

FEHLER 39: 35 Routes ohne audit logs — Sicherheitsverstoß

Dateien: 35 Route-Dateien mit Mutationen (POST/PUT/PATCH/DELETE) aber kein log_audit Aufruf Beweis:

app/routes/saved_filters.py: has mutations but no log_audit call
app/routes/api_tokens.py: has mutations but no log_audit call
app/routes/sequences.py: has mutations but no log_audit call
app/routes/contact_folders.py: has mutations but no log_audit call
app/routes/delegations.py: has mutations but no log_audit call
app/routes/owner_transfer.py: has mutations but no log_audit call
app/routes/contacts.py: has mutations but no log_audit call
app/routes/guests.py: has mutations but no log_audit call
app/routes/tenants.py: has mutations but no log_audit call
app/routes/custom_field_definitions.py: has mutations but no log_audit call
app/routes/saved_views.py: has mutations but no log_audit call
app/routes/workspaces.py: has mutations but no log_audit call
app/routes/backups.py: has mutations but no log_audit call
app/routes/custom_fields.py: has mutations but no log_audit call
app/routes/auth.py: has mutations but no log_audit call
app/routes/bank_accounts.py: has mutations but no log_audit call
app/routes/webhooks.py: has mutations but no log_audit call
app/routes/errors.py: has mutations but no log_audit call
app/routes/addresses.py: has mutations but no log_audit call
app/routes/contact_folder_permissions.py: has mutations but no log_audit call
# ... 15 weitere

Auswirkung: 35 Route-Dateien haben Mutationen (create/update/delete) aber erstellen keine Audit-Log-Einträge. Das verletzt die AGENTS.md Regel 'All mutations create audit log entries'. Blast Radius: Alle Mutationen in diesen 35 Routes — Compliance-Verstoß. Schwere: P1 — Sicherheitsverstoß.

FEHLER 40: 7 Routes ohne visibility filter — potenzieller Datenleck

Dateien:

app/routes/audit.py: GET endpoints with select() but no visibility filter
app/routes/companies.py: GET endpoints with select() but no visibility filter
app/routes/guests.py: GET endpoints with select() but no visibility filter
app/routes/roles.py: GET endpoints with select() but no visibility filter
app/routes/custom_fields.py: GET endpoints with select() but no visibility filter
app/routes/notifications.py: GET endpoints with select() but no visibility filter
app/routes/user_preferences.py: GET endpoints with select() but no visibility filter

Auswirkung: 7 Route-Dateien haben GET-Endpoints mit DB-Queries aber keine apply_visibility_filter oder check_single_entity_access. Potenzieller Datenleck — User könnten Daten sehen die sie nicht sehen sollten. Blast Radius: Daten-Isolation in 7 Routes. Schwere: P1 — Potenzieller Datenleck.

FEHLER 41: 14 deprecated notification usages — viel mehr als manuell gefunden

Dateien:

app/plugins/registry.py: imports Notification model directly
app/plugins/builtins/automation/execution_engine.py: imports Notification model directly
app/plugins/builtins/automation/workflow_timeout.py: imports Notification model directly
app/plugins/builtins/mail/services.py: uses create_notification
app/plugins/builtins/tasks/jobs.py: uses create_notification
app/plugins/builtins/kommunikation/services.py: imports Notification model directly
app/plugins/builtins/ai_proactive/jobs.py: uses create_notification
app/plugins/builtins/ai_proactive/services.py: uses create_notification
app/workflows/engine.py: uses create_notification + imports Notification model
app/routes/users.py: uses create_notification
app/services/workflow_service.py: imports Notification model directly
app/services/permission_audit.py: uses create_notification
app/services/entity_permission_service.py: uses create_notification

Auswirkung: 14 Stellen nutzen deprecated notification system statt kommunikation Contract. Doppelarchitektur aktiv und weit verbreitet. Blast Radius: Alle Notifications im System. Schwere: P2 — Doppelarchitektur.

FEHLER 42: 65 hardcoded plugin permissions in Core — massiv

Dateien: 65 Stellen in Core-Dateien die require_permission("contacts:read"), require_permission("contacts:write"), etc. verwenden. Beweis:

app/deps.py: require_permission("contacts:read") — plugin permission in Core
app/routes/saved_filters.py: require_permission("contacts:read") — plugin permission in Core (3x)
app/routes/api_tokens.py: require_permission("mcp:write") — plugin permission in Core (2x)
app/routes/companies.py: require_permission("contacts:read/write/delete") — plugin permission in Core (9x)
app/routes/contact_folders.py: require_permission("contacts:write") — plugin permission in Core (6x)
app/routes/contacts.py: require_permission("contacts:read/write/delete") — plugin permission in Core (7x)
# ... 35 weitere

Auswirkung: 65 Stellen in Core-Dateien nutzen Plugin-Permissions. Wenn Contacts deaktiviert wird, schlagen alle diese Routes fehl. Core ist nicht von Plugins entkoppelt. Blast Radius: Alle Core-Routes die Plugin-Permissions nutzen. Schwere: P2 — Core/Plugin-Kopplung.

FEHLER 43: 168 potential circular imports

Beweis: Statische Analyse findet 168 Module mit potenziellen zirkulären Imports. Auswirkung: Potenzielle Import-Fehler bei bestimmten Ladereihenfolgen. Python fängt die meisten mit lazy imports ab, aber es ist ein architektonisches Risiko. Blast Radius: Gesamte Codebase. Schwere: P3 — Architektonisches Risiko.

FEHLER 44: 13 hardcoded OpenAPI tags in main.py

Beweis:

main.py: hardcoded OpenAPI tag "automation" for plugin
main.py: hardcoded OpenAPI tag "agents" for plugin
main.py: hardcoded OpenAPI tag "dms" for plugin
main.py: hardcoded OpenAPI tag "mail" for plugin
main.py: hardcoded OpenAPI tag "calendar" for plugin
main.py: hardcoded OpenAPI tag "search" for plugin
main.py: hardcoded OpenAPI tag "reports" for plugin
main.py: hardcoded OpenAPI tag "entity-links" for plugin
main.py: hardcoded OpenAPI tag "kommunikation" for plugin
main.py: hardcoded OpenAPI tag "ai-proactive" for plugin
main.py: hardcoded OpenAPI tag "ai-assistant" for plugin
main.py: hardcoded OpenAPI tag "tags" for plugin
main.py: hardcoded OpenAPI tag "permissions" for plugin

Auswirkung: OpenAPI-Tags für Plugins in Core hartkodiert. Neues Plugin → kein Tag → fehlt in API-Doku. Blast Radius: API-Dokumentation. Schwere: P3 — Kosmetisch.

FEHLER 45: 6 try/except plugin imports — sollten dynamisch sein

Dateien:

app/plugins/registry.py: 1 try/except plugin import
app/plugins/builtins/contracts.py: 2 try/except plugin imports
app/plugins/builtins/automation/agent_comm.py: 6 try/except plugin imports
app/plugins/builtins/automation/agent_routes.py: 6 try/except plugin imports
app/plugins/builtins/mail/routes.py: 4 try/except plugin imports
app/plugins/builtins/ai_assistant/services.py: 3 try/except plugin imports

Auswirkung: 22 try/except Import-Blöcke die Plugin-Module importieren. Sollten über Contracts oder dynamische Registration laufen. Blast Radius: Plugin-Imports in 6 Dateien. Schwere: P3 — Tech Debt.


ZUSAMMENFASSUNG

Geprüfte Dateien: 420 von 420 (100%) — statische Analyse über alle Dateien Persönlich gelesene Dateien: 100 von 420 (tiefe manuelle Prüfung) Gefundene Fehler: 45 Kategorien mit 309 einzelnen Issues

Fehler-Verteilung:

Schwere Kategorien Einzelne Issues Beschreibung
P0 3 3 Runtime Crashes bei Nutzung
P1 5 50 Funktionale Fehler (35 fehlende Audit-Logs, 7 fehlende Visibility-Filter, 8 andere)
P2 10 97 Architekturfehler (65 hardcoded Permissions, 14 deprecated Notifications, 18 andere)
P3 7 159 Dead Code / Deprecated (168 circular imports, 13 OpenAPI tags, 6 try/except, etc.)
Total 45 309

Root Causes (4):

RC-1: Keine definierte Core/Plugin-Grenze — Core importiert Plugin-Modelle, -Permissions, -Felder direkt (65 hardcoded Plugin-Permissions in Core, 14 deprecated Notification-Imports, Contact-spezifische Daten in sensitive_data/permission_registry/roles)

RC-2: Discovery existiert, wird aber umgangen — commands/__init__.py, notifications.py, roles.py haben eigene hardcoded Listen parallel zur dynamischen Discovery

RC-3: Generische Services haben kein Registrierungs-Interface — ENTITY_MODELS, restore_registry, history_hooks, saved_views, tags, dashboard, dedup, import/export — alle hartcodiert

RC-4: Contract-System wird umgangen — 5 direkte Imports statt Contracts (3 Plugin→Plugin + 2 Core→Plugin in worker.py), 14 deprecated notification usages statt kommunikation Contract, 6 try/except plugin imports

Blast Radius:

  • ~35 Core-Dateien mit fehlenden Audit-Logs (Sicherheitsverstoß)
  • 7 Core-Routes ohne Visibility-Filter (Datenleck-Risiko)
  • 65 Stellen mit hardcoded Plugin-Permissions in Core
  • 14 Stellen mit deprecated Notification-System
  • 168 Module mit potenziellen zirkulären Imports
  • 3 P0 Runtime Crashes die bei Nutzung auftreten
  • ~30 Core-Dateien mit Architekturverletzungen
  • ~15 Plugin-Dateien mit direkten Cross-Imports

Geprüfte Aspekte (16):

  1. Syntax-Check über alle 420 Python-Dateien — 0 Fehler
  2. Import-Check über alle Module — 0 Fehler
  3. Name-Check (undefined names) — 1 Fehler (AutomationDefinition)
  4. Hardcoded entity_types in Core — 0 (bereits dynamisch)
  5. Fehlende Audit-Logs in Mutationen — 35 Routes
  6. Fehlende Visibility-Filter — 7 Routes
  7. Deprecated Notification Usage — 14 Stellen
  8. Hardcoded Plugin-Permissions in Core — 65 Stellen
  9. Try/except Plugin-Imports — 6 Dateien (22 Blöcke)
  10. Zirkuläre Imports — 168 Module
  11. Bare except — 0
  12. Tenant-Isolation — 0 Issues
  13. Hardcoded OpenAPI-Tags — 13
  14. Plugin-Lifecycle-Konsistenz — 7 Fehler
  15. Registry-Konsistenz — 1 Fehler
  16. Hook-Registry 3-tuple — 2 Fehler