- 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
27 KiB
Konsolidierte Fehlerliste — LeoCRM Architektur-Audit
Datum: 2026-08-15 Dateien geprüft: 750 von 1052 (siehe docs/audit-tracker.md) Verbleibend: 302 Dateien (hauptsächlich Alembic-Migrationen + Test-Dateien)
Zusammenfassung
| Schwere | Backend | Frontend | Tests | Total |
|---|---|---|---|---|
| P0 | 3 | 0 | 5 | 8 |
| P1 | 10 | 1 | 26 | 37 |
| P2 | 25 | 25 | 21 | 71 |
| P3 | 20 | 30 | 8 | 58 |
| Total | 58 | 56 | 60 | 174 |
P0 — Runtime Crashes / Security (8)
P0-1: hooks.py:83 — unregister() _filters 2-tuple CRASH
Datei: app/core/hooks.py:83
Beweis: Beweis:
# Zeile 83: _filters nutzt 2-tuple unpacking, aber register_filter speichert 3-tuple
self._filters[hook_name] = [
(p, c) for
### P0-2: trigger_dispatcher.py:127 — AutomationDefinition nicht importiert
**Datei:** `app/core/trigger_dispatcher.py:127`
**Beweis:** **Beweis:**
```python
# Zeile 127: AutomationDefinition wird in Query verwendet, aber nie importiert
query = (
select(AutomationDefinition) # Nam
### P0-3: contacts/plugin.py:88-90 — clear_actions statt unregister_actions_by_owner
**Datei:** `app/plugins/builtins/contacts/plugin.py:88-90`
**Beweis:** **Beweis:**
```python
hook_reg.clear_actions("contact.after_create") # Entfernt Hooks anderer Plugins!
hook_reg.clear_actions("contact.after_update")
### P0-T1: test_external_agent_api.py:36-39 — check_permission mocked to True
**Datei:** `tests/test_external_agent_api.py:36-39`
**Beweis:** `@pytest.fixture(autouse=True)` → `patch("app.core.permissions.check_permission", return_value=True)` — alle Tests bypass RBAC
### P0-T2: test_graph_rag.py:39-43 — check_permission mocked to True
**Datei:** `tests/test_graph_rag.py:39-43`
**Beweis:** Same autouse fixture — alle Tests bypass RBAC
### P0-T3: test_agent_memory.py:39-43 — check_permission mocked to True
**Datei:** `tests/test_agent_memory.py:39-43`
**Beweis:** Same autouse fixture — alle Tests bypass RBAC
### P0-T4: test_marketplace.py:45-49 — check_permission mocked to True
**Datei:** `tests/test_marketplace.py:45-49`
**Beweis:** Same autouse fixture — alle Tests bypass RBAC
### P0-T5: test_cross_tenant_standalone.py:33 — Hardcoded DB credential
**Datei:** `tests/test_cross_tenant_standalone.py:33`
**Beweis:** `DB_URL = "postgresql+asyncpg://crm_user:4B6X2wlfbIx-PyaG8kGutsatdLbjdBUI@crm-postgres:5432/crm_db"` — Passwort im Source Code
---
## P1 — Funktionale Fehler (37)
### P1-4: attachment_service.py:48 — DmsFile type hint used but not imported
**Datei:** `app/services/attachment_service.py:48`
**Beweis:** **Beweis:**
```python
def _entity_attachment_to_dict(ea: EntityAttachment, dms_file: DmsFile | None = None) -> dict[str, Any]:
DmsFile wird als
P1-5: restore_registry.py — register_default_entities registriert Contact
Datei: app/core/restore_registry.py:113-195
Beweis: Beweis:
register_default_entities() registriert Contact RestoreConfig. ContactsPlugin.on_activate() registriert AUCH Contact RestoreConfig. →
P1-6: history_hooks.py — register_default_history_hooks registriert Contact ohne owner_tag
Datei: app/core/history_hooks.py:140
Beweis: Beweis:
register_default_history_hooks() registriert Contact hooks ohne owner_tag. ContactsPlugin.on_activate() registriert AUCH Contact hooks
P1-8: mail/plugin.py — on_deactivate fehlt unregister_actions_by_owner und restore unregister
Datei: app/plugins/builtins/mail/plugin.py
Beweis: Beweis:
mail/plugin.py on_deactivate hat KEIN unregister_actions_by_owner für history hooks und KEIN unregister für restore config.
*Auswirkung:
P1-10: saved_views.py:62 — pattern validation hardcoded
Datei: app/routes/saved_views.py:62
Beweis: Beweis:
entity_type: str | None = Query(None, pattern="^(contacts|mail|calendar|dms)$"),
Obwohl _validate_entity_type() gegen ENTI
P1-11: saved_filters.py:62 — pattern validation hardcoded
Datei: app/routes/saved_filters.py:62
Beweis: Beweis:
entity_type: str | None = Query(None, pattern="^(contacts|mail|calendar|dms)$"),
Gleiches Problem wie saved_views.py. **Ausw
P1-30: mail/plugin.py:194-200 — on_deactivate fehlt restore + history unregister
Datei: app/plugins/builtins/mail/plugin.py:194-200
Beweis: Beweis:
async def on_deactivate(self, db, service_container, event_bus) -> None:
# Contract abmelden
from app.plugins.builtins.c
### P1-31: frontend/src/api/tags.ts:12 — EntityType hardcoded und inkonsistent mit Backend
**Datei:** `frontend/src/api/tags.ts:12`
**Beweis:** **Beweis:**
```typescript
export type EntityType = 'contact' | 'file' | 'calendar_entry';
Backend validiert dynamisch gegen ENTITY_MODELS (contact
P1-47: mail/plugin.py:194-217 — on_deactivate FEHLT restore unregister (P1)
Datei: app/plugins/builtins/mail/plugin.py:194-217
Beweis: Beweis: on_deactivate deregistriert history hooks (Zeile 213-215) aber hat KEIN get_restore_registry().unregister("mail").
Problem: Mail res
P1-58: mcp_client/models.py:35-36 — Naive datetime statt UTC (P1)
Datei: app/plugins/builtins/mcp_client/models.py:35-36
Beweis: Beweis: datetime.utcnow (naive) statt datetime.now(UTC).
P1-F1: ProtectedRoute.tsx:22-24 — Fail-open bei leeren Permissions
Datei: frontend/src/components/common/ProtectedRoute.tsx:22-24
Beweis: if (perms.length === 0) { return <>{children}</>; } — access erlaubt wenn permissions nicht geladen
P1-T1: test_ai_copilot.py:45-47,77-79
Beweis: Tests silently pass bei 403 — assert status_code in (200, 403) then return
P1-T2: test_user_service.py:38-39,82-100
Beweis: Non-deterministic assertions, try/except pass
P1-T3: test_backup_service.py:37-38,50-51,63
Beweis: Overly permissive assertions (4 status codes)
P1-T4: test_mcp_server.py:62,127
Beweis: assert success in (True, False) — immer True
P1-T5: test_saved_filters.py (entire file)
Beweis: Missing cross-tenant isolation + RBAC tests
P1-T6: test_tasks.py (entire file)
Beweis: Missing cross-tenant isolation + RBAC tests
P1-T7: test_dashboard.py (entire file)
Beweis: Missing cross-tenant isolation + RBAC tests
P1-T8: test_custom_fields.py (entire file)
Beweis: Missing cross-tenant isolation + RBAC tests
P1-T9: test_calendar.py (first 200 lines)
Beweis: Missing cross-tenant isolation test
P1-T10: test_workflows.py (entire file)
Beweis: Missing RBAC + tenant isolation tests
P1-T11: test_notifications.py (entire file)
Beweis: Missing tenant isolation + RBAC tests
P1-T12: test_companies.py (entire file)
Beweis: Missing visibility filter test
P1-T13: test_contacts.py (entire file)
Beweis: Missing visibility filter test
P1-T14: test_dms.py + test_dms_coverage.py + test_dms_errors.py
Beweis: Missing cross-tenant isolation tests
P1-T15: test_entity_links.py (entire file)
Beweis: Missing cross-tenant test
P1-T16: test_tags.py (entire file)
Beweis: Missing cross-tenant + RBAC test
P2 — Architekturfehler (71)
P2-7: entity_permission_service.py:59-61 — Contact hardcoded in ENTITY_MODELS
Datei: app/services/entity_permission_service.py:59-61
Beweis: Beweis:
ENTITY_MODELS: dict[str, type] = {
"contact": Contact,
"contacts": Contact,
"company": Contact,
# ...
}
Cont
P2-9: hooks.py:52-53 — Type-Annotationen falsch
Datei: app/core/hooks.py:52-53
Beweis: Beweis:
cls._instance._actions: dict[str, list[tuple[int, Callable]]] = defaultdict(list) # sollte tuple[int, Callable, str | None]
cls
### P2-12: saved_views.py:60 — require_permission("contacts:read") hardcoded
**Datei:** `app/routes/saved_views.py:60`
**Beweis:** **Beweis:**
```python
@router.get("", dependencies=[Depends(require_permission("contacts:read"))])
Saved-Views benötigen contacts:read Permissio
P2-13: saved_filters.py:60 — require_permission("contacts:read") hardcoded
Datei: app/routes/saved_filters.py:60
Beweis: Beweis:
@router.get("", dependencies=[Depends(require_permission("contacts:read"))])
Gleiches Problem wie saved_views.py. **Auswirku
P2-14: sensitive_data.py:83-98 — DATA_EXPOSURE_POLICY hat Contact-spezifische Felder
Datei: app/core/sensitive_data.py:83-98
Beweis: Beweis:
DATA_EXPOSURE_POLICY: dict[str, dict[str, dict[str, bool]]] = {
"contact": {
"code": _EXPORT_ONLY,
"accounti
### P2-15: permission_registry.py:86-122 — CORE_FIELD_DEFINITIONS hat Contact-spezifische Felder
**Datei:** `app/core/permission_registry.py:86-122`
**Beweis:** **Beweis:**
~40 Contact-spezifische Felddefinitionen hartkodiert in Core.
**Auswirkung:** Core enthält CRM-spezifische Felddefinitionen. Neue Contact-
### P2-16: sensitive_data.py:24-48 — SENSITIVE_FIELDS hat Contact/Mail-spezifische Felder
**Datei:** `app/core/sensitive_data.py:24-48`
**Beweis:** **Beweis:**
```python
SENSITIVE_FIELDS: dict[str, set[str]] = {
"contact": {"password_hash", "smtp_password", "imap_password", ...},
"mail_acc
### P2-23: report_generator/plugin.py:9 — top-level import of jobs module
**Datei:** `app/plugins/builtins/report_generator/plugin.py:9`
**Beweis:** **Beweis:**
```python
from app.plugins.builtins.report_generator import jobs # noqa: F401
Top-Level-Import von jobs-Modul hat Side-Effects (regis
P2-24: base.py:81 — unregister_all_for_plugin nutzt self Heuristik
Datei: app/plugins/base.py:81
Beweis: Beweis:
get_hook_registry().unregister_all_for_plugin(self.manifest.name)
unregister_all_for_plugin nutzt `callback.self.manif
P2-26: deps.py:21-36 — _WRITE_PERMISSIONS hardcoded mit Plugin-Permissions
Datei: app/deps.py:21-36
Beweis: Beweis:
_WRITE_PERMISSIONS = [
"contacts:write",
"contacts:create",
# ...
]
contacts:write und contacts:create sind
P2-27: workflow_service.py:13 — importiert deprecated Notification model
Datei: app/services/workflow_service.py:13
Beweis: Beweis:
from app.models.notification import Notification
Workflow-Service nutzt deprecated Notification model statt kommunikation Co
P2-28: dashboard.py:14,61-93 — hardcoded Contact counts, kein Plugin-Beitrag möglich
Datei: app/routes/dashboard.py:14,61-93
Beweis: Beweis:
from app.models.contact import Contact # Core→Contact (Plugin-Entity)
# ...
contact_query = select(func.count(Contact.id)).wher
### P2-29: import_export.py:40 — entity_type default 'companies' hardcoded
**Datei:** `app/routes/import_export.py:40`
**Beweis:** **Beweis:**
```python
entity_type: str = Form("companies"),
Import/Export unterstützt nur 'companies' und 'contacts' (beide Contact-Modell). Kein
P2-32: conftest.py:41-53 — hardcoded Core-Model imports trotz dynamischer Discovery
Datei: tests/conftest.py:41-53
Beweis: Beweis:
from app.models.ai_conversation import AIConversation, AIMessage # noqa: F401
from app.models.contact import Contact, ContactPe
### P2-33: roles.py:27-50 — SYSTEM_PERMISSIONS hardcoded mit Plugin-Permissions
**Datei:** `app/routes/roles.py:27-50`
**Beweis:** **Beweis:**
```python
SYSTEM_PERMISSIONS: list[dict[str, str]] = [
{"key": "contacts:read", "label": "Contacts: Read", "category": "system"},
### P2-34: users.py:15 — importiert create_notification (deprecated)
**Datei:** `app/routes/users.py:15`
**Beweis:** **Beweis:**
```python
from app.core.notifications import create_notification
User-Route nutzt deprecated create_notification statt `post_system_
P2-35: workflows/engine.py:122-130 — erstellt Notification model direkt
Datei: app/workflows/engine.py:122-130
Beweis: Beweis:
notification = Notification(
tenant_id=self.tenant_id,
user_id=uuid.UUID(user_id),
type=config.get("notification_typ
### P2-38: webhooks.py:26,49 — require_permission('automation:read/write') für Core-Webhooks
**Datei:** `app/routes/webhooks.py:26,49`
**Beweis:** **Beweis:**
```python
dependencies=[Depends(require_permission("automation:read"))]
dependencies=[Depends(require_permission("automation:write"))]
P2-51: address.py:9 — Hardcoded pattern="^contact$" (P2)
Datei: app/schemas/address.py:9 und app/routes/addresses.py:20
Beweis: Beweis:
entity_type: str = Field(..., pattern="^contact$", description="'contact'")
Problem: Address-Modell ist generisch (entit
P2-54: frontend routes/index.tsx — Hardcoded Plugin-Routes (P2)
Datei: frontend/src/routes/index.tsx:17-70
Beweis: Beweis: 54 hardcoded lazy-loaded page imports für Plugin-Seiten. PluginRouteRenderer (Zeile 207) ist dynamisch als catch-all, aber alle Haupt-Rout
P2-55: tags/schemas.py:27,33,39 — Hardcoded entity_type pattern (P2)
Datei: app/plugins/builtins/tags/schemas.py:27,33,39
Beweis: Beweis: pattern="^(contact|file|folder)$" in TagAssignRequest, TagUnassignRequest, TagBulkAssignRequest.
Problem: Schema limitiert auf 3 Ent
P2-56: entity_links/schemas.py:9 — Hardcoded entity_type pattern (P2)
Datei: app/plugins/builtins/entity_links/schemas.py:9
Beweis: Beweis: entity_type: str = Field(..., pattern="^(contact|company)$")
Problem: Schema limitiert auf 2 Entity-Types, Backend validiert dynamis
P2-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: Beweis:
from sqlalchemy.orm import declarative_base
Base = declarative_base() # Eigenes Base, nicht app.core.db.Base!
Problem:
P2-F1: frontend/src/routes/index.tsx:17-70
Beweis: 54 hardcoded lazy-loaded Plugin-Routes
P2-F2: frontend/src/pages/Settings.tsx:18-29
Beweis: 10 hardcoded settings nav items
P2-F3: frontend/src/api/search.ts:40-47
Beweis: ENTITY_URL_MAP hardcoded mit 6 Entity-Types
P2-F4: frontend/src/components/search/CommandPalette.tsx:12-39
Beweis: TYPE_LABELS + TYPE_ICON_CLASSES + typeIcon() hardcoded
P2-F5: frontend/src/pages/GlobalSearchResults.tsx:15-22
Beweis: TYPE_LABELS hardcoded (dupliziert)
P2-F6: frontend/src/api/tags.ts:12
Beweis: EntityType hardcoded, inkonsistent mit Backend
P2-F7: frontend/src/components/notifications/NotificationDropdown.tsx
Beweis: Nutzt alte /notifications API statt kommunikation
P2-F8: frontend/src/components/layout/NotificationBell.tsx:13
Beweis: Nutzt useUnreadNotificationCount von /notifications
P2-F9: frontend/src/api/contacts.ts + unifiedContacts.ts
Beweis: Dual API client (legacy + neu)
P2-F10: frontend/src/components/contacts/FilterPanel.tsx:22-80
Beweis: FIELD_DEFS hardcoded ~40 Contact-Felder
P2-F11: frontend/src/components/contacts/SortPanel.tsx:22-69
Beweis: SORT_FIELDS hardcoded (dupliziert)
P2-F12: frontend/src/components/contacts/GroupPanel.tsx:22-68
Beweis: GROUP_FIELDS hardcoded (dupliziert, triple-dup)
P2-F13: frontend/src/components/mail/MailFolderTree.tsx:22-43
Beweis: FOLDER_NAME_MAP hardcoded 20 IMAP names
P2-F14: frontend/src/components/mail/MailFilterPanel.tsx:22-44
Beweis: FIELD_DEFS hardcoded 10 Mail-Felder
P2-F15: frontend/src/components/mail/MailSortPanel.tsx:19-27
Beweis: SORT_FIELDS hardcoded (dupliziert)
P2-F16: frontend/src/components/mail/MailGroupPanel.tsx:19-29
Beweis: GROUP_FIELDS hardcoded (dupliziert, triple-dup)
P2-F17: frontend/src/components/dashboard/DashboardWidgetLoader.tsx:11-21
Beweis: widgetRegistry hardcoded 3 Widgets
P2-F18: frontend/src/components/contacts/ContactList.tsx:28-80
Beweis: ALL_COLUMNS hardcoded
P2-F19: frontend/src/api/types.ts:12-22
Beweis: Contact interface legacy (first_name/last_name)
P2-F20: frontend/src/pages/SettingsWebhooks.tsx:38-56
Beweis: AVAILABLE_EVENTS hardcoded, 6 Events existieren nicht (deal., note.)
P2-F21: frontend/src/components/workflows/WorkflowEditor.tsx:16-27
Beweis: triggerEventOptions hardcoded, 4 deal.* Events existieren nicht
P2-F22: frontend/src/pages/SettingsUsers.tsx:19-25
Beweis: LEGACY_ROLES hardcoded 5 Rollen
P2-F23: frontend/src/pages/SettingsMenuOrder.tsx:34-46
Beweis: DEFAULT_ORDER hardcoded 11 Items
P2-F24: frontend/src/pages/Trash.tsx:17
Beweis: ENTITY_TYPES hardcoded 5 Types
P2-F25: frontend/src/pages/AgentDashboard.tsx:50-61
Beweis: commonModels hardcoded 10 Modellnamen
P2-T1: test_cross_tenant_security.py:98
Beweis: Invalid bcrypt hash $2b$12$testhash
P2-T2: test_cross_tenant_security_v2.py:132
Beweis: Same invalid hash
P2-T3: test_cross_tenant_standalone.py:73
Beweis: Same invalid hash
P2-T4: test_workspaces.py:42, test_api_tokens.py:33
Beweis: password_hash="dummy" — not valid bcrypt
P2-T5: test_tags.py:138,165,194
Beweis: Random UUIDs für non-existent entities
P2-T6: test_ai_proactive.py:146-150
Beweis: Test grants is_system_admin to bypass permissions
P2-T7: test_commands.py:140-148
Beweis: Wildcard permissions *: * bypass real RBAC
P2-T8: test_mcp_client.py:41
Beweis: API token returned in plaintext in response
P2-T9: test_cross_tenant_security.py:40
Beweis: Hardcoded DB URL with default password
P2-T10: test_cross_tenant_security_v2.py:47-56
Beweis: Hardcoded DB URLs with credentials
P2-T11: test_cross_tenant_security_v2.py:34-37
Beweis: Hardcoded SECRET_KEY
P2-T12: test_cross_tenant_standalone.py:18-21
Beweis: Hardcoded SECRET_KEY
P2-T13: test_no_legacy_tenant_var.py:17-20
Beweis: Hardcoded SECRET_KEY
P2-T14: test_entity_links.py:105
Beweis: Variable named contact_id but used as company entity
P2-T15: test_external_agent_api.py:92-101
Beweis: Manually constructed user with is_system_admin: True
P2-T16: test_graph_rag.py (similar)
Beweis: Same manually constructed user
P2-T17: test_agent_memory.py (similar)
Beweis: Same manually constructed user
P2-T18: test_marketplace.py (similar)
Beweis: Same manually constructed user
P2-T19: frontend/src/pages/SettingsRechte.tsx:37-75
Beweis: PermissionLevelBadge + PrincipalTypeBadge hardcoded
P2-T20: frontend/src/pages/ProactiveAISettings.tsx:3-17
Beweis: categoryLabels + modelOptions hardcoded
P2-T21: frontend/src/pages/CustomFields.tsx:40-43
Beweis: ENTITY_OPTIONS hardcoded (contact, company)
P3 — Dead Code / Code Quality / Deprecated (58)
P3-19: registry.py:36 — _mounted_routes ist Dead Code
Datei: app/plugins/registry.py:36
Beweis: Beweis:
self._mounted_routes: dict[str, list[Any]] = {} wird initialisiert aber nie befüllt. Route-Removal-Logik wurde entfernt (Gate-Modell dok
P3-20: ai_assistant/plugin.py:96-98 — direkter Import von kommunikation.contracts
Datei: app/plugins/builtins/ai_assistant/plugin.py:96-98
Beweis: Beweis:
from app.plugins.builtins.kommunikation.contracts import (
get_participant_registry,
)
Bewertung: Deklariert in `dep
P3-21: system_notif/plugin.py:161 — direkter Import von kommunikation.contracts
Datei: app/plugins/builtins/system_notif/plugin.py:161
Beweis: Beweis:
from app.plugins.builtins.kommunikation.contracts import create_plugin_room, send_message
Bewertung: Deklariert in `depe
P3-22: graph_rag/plugin.py:44,57 — direkter Import von unified_search.contracts
Datei: app/plugins/builtins/graph_rag/plugin.py:44,57
Beweis: Beweis:
from app.plugins.builtins.unified_search.contracts import get_search_registry
Bewertung: Deklariert in `dependencies=["u
P3-25: entity_permission_service.py:30 — importiert create_notification
Datei: app/services/entity_permission_service.py:30
Beweis: Beweis:
from app.core.notifications import create_notification
create_notification ist deprecated und nutzt jetzt `get_contract("k
P3-36: schemas/common.py:22-58 — Notification schemas für deprecated system
Datei: app/schemas/common.py:22-58
Beweis: Beweis:
class NotificationResponse(BaseModel): ...
class NotificationListResponse(BaseModel): ...
class UnreadCountResponse(BaseModel):
### P3-37: custom_fields.py:42 — greift auf registry._plugins direkt zu
**Datei:** `app/routes/custom_fields.py:42`
**Beweis:** **Beweis:**
```python
for plugin in registry._plugins.values():
Greift auf privates Attribut _plugins zu statt öffentlichen API (`list_discovere
P3-48: entity_attachment.py:45-49 — import inline statt func import (P3)
Datei: app/models/entity_attachment.py:45-49
Beweis: Beweis:
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=__import__('sqlalchemy
### P3-49: ai_copilot_service.py:18-19 — Duplicate Contact import (P3)
**Datei:** `app/services/ai_copilot_service.py:18-19`
**Beweis:** **Beweis:**
```python
from app.models.contact import Contact
from app.models.contact import Contact # Duplicate!
P3-50: system_settings.py:44-54 — Duplicate field definitions (P3)
Datei: app/schemas/system_settings.py:44-54
Beweis: Beweis:
class SystemSettingsResponse(BaseModel):
# Zeilen 45-48:
tax_number: str | None = None
vat_id: str | None = None
### P3-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:** **Beweis:**
```python
from app.models.contact import Contact, ContactPerson
from app.models.contact import Contact, ContactPerson # Duplicate!
P3-F1: frontend/src/pages/Settings.tsx:5
Beweis: import * as LucideIcons lädt ALLE Icons
P3-F2: frontend/src/pages/Settings.tsx:38
Beweis: (LucideIcons as any)[p.icon] — any type cast
P3-F3: frontend/src/components/contacts/ContactDetail.tsx:11
Beweis: import * as LucideIcons lädt ALLE Icons
P3-F4: frontend/src/components/contacts/ContactDetail.tsx:67-74
Beweis: 8x (contact as any) type casts
P3-F5: frontend/src/components/ai/ChatWindow.tsx:39-41
Beweis: inline styles für animationDelay
P3-F6: frontend/src/components/mail/MailList.tsx:62
Beweis: inline style paddingLeft
P3-F7: frontend/src/components/mail/MailDetail.tsx:49-52
Beweis: iframe HTML rendering (potential XSS)
P3-F8: frontend/src/components/contacts/CustomFieldRenderer.tsx:15-16
Beweis: any types
P3-F9: frontend/src/components/SavedFilters.tsx:22,47,56
Beweis: any types
P3-F10: frontend/src/store/windowStore.ts:8-9
Beweis: ComponentType<any> und Record<string, any>
P3-F11: frontend/src/store/commStore.ts:27,61
Beweis: Record<string, any> und reactions: any[]
P3-F12: frontend/src/hooks/useCommWebSocket.ts:31
Beweis: console.log statt strukturiertem Logger
P3-F13: frontend/src/hooks/useAIContext.ts:5
Beweis: entityData?: any
P3-F14: frontend/src/api/auth.ts:28,36,60
Beweis: any type casts
P3-F15: frontend/src/pages/Login.tsx:41
Beweis: error: any
P3-F16: frontend/src/pages/Workflows.tsx:59,70
Beweis: err: any
P3-F17: frontend/src/pages/AuditLog.tsx:37
Beweis: ColumnDef<AuditLogEntry, any>
P3-F18: frontend/src/components/comm/blocks/BlockRenderer.tsx:59
Beweis: hardcoded German string
P3-F19: frontend/src/components/comm/blocks/MiniAppBlock.tsx:13,24
Beweis: hardcoded German strings
P3-F20: frontend/src/components/comm/blocks/ActionCardBlock.tsx:59
Beweis: hardcoded German string
P3-F21: frontend/src/components/comm/blocks/HtmlBlock.tsx:17-20
Beweis: redundante regex vor DOMPurify
P3-F22: frontend/src/components/shared/CsvImportDialog.tsx:48,71,78
Beweis: hardcoded German strings
P3-F23: frontend/src/components/shared/DataGrid.tsx:20,37
Beweis: any types
P3-F24: frontend/src/components/dms/FileDetails.tsx:73
Beweis: inline style DOM-Manipulation
P3-F25: frontend/src/pages/NoAccessPage.tsx:9-13
Beweis: hardcoded German strings ohne i18n
P3-F26: frontend/src/pages/ActivityTimeline.tsx:25-26
Beweis: hardcoded German strings
P3-F27: frontend/src/pages/SettingsBackup.tsx:38
Beweis: hardcoded de-DE locale
P3-F28: frontend/src/pages/PasswordResetConfirm.tsx:15
Beweis: hardcoded English validation message
P3-F29: frontend/src/pages/DmsTrash.tsx:27-34
Beweis: leere Implementation (Trash-Endpoint fehlt)
P3-F30: frontend/src/pages/GuestLogin.tsx:16 + GuestContacts.tsx:16
Beweis: hardcoded German strings
P3-T1: test_agent_subtasks.py:29-31,35-37
Beweis: Duplicate fixture definition
P3-T2: test_cross_tenant_security.py:187-188
Beweis: Duplicate @pytest.mark.asyncio
P3-T3: test_tenant.py:189
Beweis: Potential KeyError (custom_role)
P3-T4: test_api_documentation.py:10
Beweis: sys.path.insert path manipulation
P3-T5: test_backup_restore.py:15
Beweis: sys.path.insert path manipulation
P3-T6: test_ai_deploy.py:13
Beweis: sys.path.insert path manipulation
P3-T7: test_ai_health_check.py:13
Beweis: sys.path.insert path manipulation
P3-T8: test_dms_coverage.py:13
Beweis: Unused import
Root Causes (4)
- Keine definierte Core/Plugin-Grenze — Core importiert Plugin-Modelle, -Permissions, -Felder direkt
- Discovery existiert, wird aber umgangen — Schema-Patterns, roles.py SYSTEM_PERMISSIONS, frontend routes
- Generische Services haben kein Registrierungs-Interface — ENTITY_MODELS, restore, history, saved_views, tags, dashboard, dedup, import/export
- Contract-System wird umgangen — 14 deprecated notifications, direkte Plugin→Plugin Imports, frontend nutzt alte API
Verbleibende 302 Dateien
- ~100 Alembic-Migrationen (23 stichprobenartig geprüft, alle sauber)
- ~80 Frontend Test-Dateien (Vitest tests/)
- ~55 Backend Test-Dateien (vom Test-Audit-Subordinate abgedeckt — 60 Fehler gefunden)
- ~30 Frontend API-Clients und Hooks (bereits geprüft)
- ~20 Frontend Stores und Utils (bereits geprüft)
- ~10 Scripts (8 geprüft)
- ~7 Frontend E2E Tests
Siehe docs/audit-tracker.md für die vollständige Liste der geprüften und verbleibenden Dateien.