1076 lines
55 KiB
Markdown
1076 lines
55 KiB
Markdown
|
|
# 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:**
|
||
|
|
```python
|
||
|
|
# 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:**
|
||
|
|
```python
|
||
|
|
# 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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
@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:**
|
||
|
|
```python
|
||
|
|
@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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
_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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```typescript
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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 `/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: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]` — `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: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 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:5** — `entityData?: 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-16** — `Record<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-52** — `sanitized_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,56** — `Record<string, any>` und `err: any` type casts.
|
||
|
|
- **frontend/src/store/windowStore.ts:8-9** — `ComponentType<any>` und `Record<string, any>`.
|
||
|
|
- **frontend/src/store/commStore.ts:27,61** — `Record<string, any>` für metadata und `reactions: any[]`.
|
||
|
|
- **frontend/src/hooks/useCommWebSocket.ts:31** — `console.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:41** — `error: any` type cast.
|
||
|
|
- **frontend/src/pages/Workflows.tsx:59,70** — `err: any` type 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** — `any` type 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 `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,80** — `err: any` type casts.
|
||
|
|
- **frontend/src/pages/SettingsTaxes.tsx:50,71** — `err: any` type casts.
|
||
|
|
- **frontend/src/pages/SettingsSequences.tsx:49,69,80** — `err: any` type casts.
|
||
|
|
- **frontend/src/pages/SettingsNotifications.tsx:45** — `err: 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,37** — `ColumnDef<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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
# 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:**
|
||
|
|
```python
|
||
|
|
# 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:**
|
||
|
|
```python
|
||
|
|
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:**
|
||
|
|
```python
|
||
|
|
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
|