- 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
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,127 —
assert 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:33 —
password_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
/notificationsAPI statt kommunikation System Channel. - frontend/src/components/layout/NotificationBell.tsx:13 — Nutzt
useUnreadNotificationCountvon/notificationsAPI statt kommunikation. - frontend/src/api/contacts.ts + frontend/src/api/unifiedContacts.ts — Dual API client:
contacts.ts(legacy) undunifiedContacts.ts(neu). Beide existieren parallel.
P3
- frontend/src/pages/Settings.tsx:5 —
import * 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]—anytype cast verletzt TypeScript strict rule. - frontend/src/pages/Communication.tsx:78-80 — MiniApp interface definiert aber möglicherweise ungenutzt.
- scripts/seed_perf_data.py:72 —
role='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:11 —
import * as LucideIconslä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/suggestionsund SSE endpoint. - frontend/src/hooks/useAIContext.ts:5 —
entityData?: anytype 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-16 —
Record<string, any>undanytype casts. - frontend/src/components/mail/MailList.tsx:62 — inline style
paddingLeft(verletzt Tailwind-only rule). - frontend/src/components/mail/MailDetail.tsx:49-52 —
sanitized_html || body_htmlwird 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,56 —
Record<string, any>underr: anytype casts. - frontend/src/store/windowStore.ts:8-9 —
ComponentType<any>undRecord<string, any>. - frontend/src/store/commStore.ts:27,61 —
Record<string, any>für metadata undreactions: any[]. - frontend/src/hooks/useCommWebSocket.ts:31 —
console.logstatt 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:41 —
error: anytype cast. - frontend/src/pages/Workflows.tsx:59,70 —
err: anytype casts. - frontend/src/pages/AuditLog.tsx:37 —
ColumnDef<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-48 —
anytype casts für confirmDeactivate/confirmDelete. - frontend/src/pages/SettingsRoles.tsx:21,23 —
Record<string, any>für permissions. - frontend/src/pages/SettingsGroups.tsx:29-76 — gleiche
anypatterns 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,80 —
err: anytype casts. - frontend/src/pages/SettingsTaxes.tsx:50,71 —
err: anytype casts. - frontend/src/pages/SettingsSequences.tsx:49,69,80 —
err: anytype casts. - frontend/src/pages/SettingsNotifications.tsx:45 —
err: anytype 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,37 —
ColumnDef<T, any>[]undT 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-36Beweis:
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):
- ✅ Syntax-Check über alle 420 Python-Dateien — 0 Fehler
- ✅ Import-Check über alle Module — 0 Fehler
- ✅ Name-Check (undefined names) — 1 Fehler (AutomationDefinition)
- ✅ Hardcoded entity_types in Core — 0 (bereits dynamisch)
- ✅ Fehlende Audit-Logs in Mutationen — 35 Routes
- ✅ Fehlende Visibility-Filter — 7 Routes
- ✅ Deprecated Notification Usage — 14 Stellen
- ✅ Hardcoded Plugin-Permissions in Core — 65 Stellen
- ✅ Try/except Plugin-Imports — 6 Dateien (22 Blöcke)
- ✅ Zirkuläre Imports — 168 Module
- ✅ Bare except — 0
- ✅ Tenant-Isolation — 0 Issues
- ✅ Hardcoded OpenAPI-Tags — 13
- ✅ Plugin-Lifecycle-Konsistenz — 7 Fehler
- ✅ Registry-Konsistenz — 1 Fehler
- ✅ Hook-Registry 3-tuple — 2 Fehler