fix(d5): Marathon-Scanner-Triage — trace_api_contracts 859→218 (-75%, Router-Präfixe/Multi-Router/leere Pfade/Template-Literals gefixt), trace_plugins 27→0 (-100%, Inline-Manifest-Konvention erkannt); 371 HIGH-Fehlalarme eliminiert (OpenAPI-verifiziert); ~12 echte API-Bugs als Follow-up dokumentiert (ai/sessions ×5, policies ×4, mail ×4)
This commit is contained in:
+4
-1
@@ -80,7 +80,10 @@
|
||||
| D3-g | ARCH-051: 14 dict-body-Routes auf Pydantic-Schemas umgestellt (entity_permissions bulk ×2, guests invite, users menu-order, system_settings backup-config+dsar, knowledge ×3, self_improvement ×5); dabei DSAR-Export F821-Bug behoben (datetime/timezone undefined → NameError zur Laufzeit beim GDPR-Export) und Zeitstempel auf datetime.now(UTC)-Konvention umgestellt | ✅ ruff exit=0 auf allen 6 Dateien; create_app OK (559 routes); 0 verbleibende body: dict in gepatchten Dateien; Validierung jetzt im Schema statt in Routen (AGENTS.md-Konvention) | c32e4bb |
|
||||
| D4-a | ARCH-027 SECRET_KEY Production-Fail: Verifiziert bereits implementiert UND strenger als gefordert — get_settings() lehnt Default-Key UND <32-Zeichen-Keys Import-zeitig in ALLEN Umgebungen ab (RuntimeError) | ✅ Direkter Verifikationstest: Default-Key → RuntimeError 'SECRET_KEY must be changed from default value' beim Modul-Import (Traceback-Beweis); Tests setzen gültigen Key im conftest | — |
|
||||
| D4-b | BUG-019 453 hardcoded Secrets: Präziser Entropie-Wert-Scan (≥16-Zeichen-Literals an secret-ish Namen, Placeholder gefiltert) | ✅ 0 echte hardcoded Secret-Werte — alle Treffer sind Nutzungs-Muster (hash_password, Token-Generierung, Schema-Felder); Triage-Tabelle in test-bugs.md | — |
|
||||
| D4-c | BUG-020 288 SQLi-Risiken: Cluster-Analyse → 10 f-string-SQL + 2 String-Konkatenationen; alle Interpolationen aus Whitelists (_TABLE_MAP, tables-Dicts mit Guard) oder int-Config (hnsw_ef_search) — kein User-Input-Fluss | ✅ Kein fixbares Finding; agent_memory type_filter statisch+parameterisiert; Triage in test-bugs.md dokumentiert | — |
|
||||
| D4-c | BUG-020 288 SQLi-Risiken: Cluster-Analyse → 10 f-string-SQL + 2 String-Konkatenationen; alle Interpolationen aus Whitelists (_TABLE_MAP, tables-Dicts mit Guard) oder int-Config (hnsw_ef_search) — kein User-Input-Fluss | ✅ Kein fixbares Finding; agent_memory type_filter statisch+parameterisiert; Triage in test-bugs.md dokumentiert | c0e8e4e |
|
||||
| D5-a | BUG-074 trace_api_contracts 859 issues: Scanner-Bugs identifiziert (Router-Präfixe fehlten, Multi-Router-Module, leere Pfad-Strings, Template-Literals) | ✅ Scanner gefixt: 859→218 (-75%); 371 HIGH-Fehlalarme eliminiert (OpenAPI-verifiziert); verbleibende 22 = ~10 Artefakte + ~12 echte Bugs als Follow-up dokumentiert (ai/sessions ×5, policies ×4, mail ×4, notifications ×1, agents/skills ×1) | — |
|
||||
| D5-b | BUG-077 trace_plugins 27 issues: Scanner erwartete manifest.py, Projekt-Konvention ist Inline-Manifest in plugin.py; migrations/tests fälschlich als Plugins; menu_items-Findings konzeptionell falsch (dynamische Konsumtion) | ✅ Scanner gefixt: 27→0 (-100%) | — |
|
||||
| D5-c | BUG-073 broken imports: Neu-Lauf bestätigt 0 broken imports (2568 Imports geprüft); BUG-075 stores/BUG-076 hooks: Findings sind überwiegend False Positives des naiven Scanners (z.B. 'const'/'null' als Store-Member) | ✅ Dokumentiert; Scanner-Qualität als bekanntes Limit vermerkt | — |
|
||||
|
||||
**Block A ABGESCHLOSSEN** — Gate A bestanden (32f63ad).
|
||||
**Block B ABGESCHLOSSEN** — Gate B bestanden (alle 5 Checks bewiesen).
|
||||
|
||||
+6
-2
@@ -540,7 +540,11 @@ Playwright E2E: 10 passed, 24 failed (BUG-011/012/013 — Mock-Daten und data-te
|
||||
- **Erwartet:** 0 issues
|
||||
- **Tatsächlich:** 859 issues in Frontend↔Backend API Contracts
|
||||
- **Schweregrad:** Medium
|
||||
- **Status:** ⏳ Nicht gefixt — muss genauer untersuchen
|
||||
- **Status:** ✅ Triage+Scanner-Fixes 2026-08-24 (Block D/D5): 859→218 (-75%). Scanner-Bugs behoben: (1) Router-Präfixe fehlten im Endpoint-Pfad (371 Fehlalarme), (2) Multi-Router-Module (calendar_router/resource_router) nutzten falschen Präfix, (3) leere Pfad-Strings ('' für Collection-Routen) nicht erfasst, (4) Template-Literals (${qs}) nicht normalisiert. Verbleibende 196 backend_endpoint_no_frontend = legitime Admin-/Interne-Endpoints ohne Frontend-Nutzung. Verbleibende 22 frontend_call_no_backend = ~10 Parsing-Artefakte + ~12 echte Bugs als Follow-up dokumentiert:
|
||||
- /ai/sessions-Familie ×5 (Frontend nutzt alte API; Backend hat /ai/conversations)
|
||||
- /policies/{type}/{id}-Nested ×4 (Backend nur PUT/DELETE /policies/{policy_id})
|
||||
- mail signatures PATCH/DELETE ×2, mail drafts PATCH-vs-PUT ×1, mail labels DELETE ×1
|
||||
- DELETE /notifications/{id} ×1 (Backend nur PATCH .../read), GET /agents/skills ×1 (Backend /agents/tools)
|
||||
|
||||
### BUG-075: Marathon trace_stores — 323 issues
|
||||
- **Kategorie:** Frontend / Stores
|
||||
@@ -561,7 +565,7 @@ Playwright E2E: 10 passed, 24 failed (BUG-011/012/013 — Mock-Daten und data-te
|
||||
- **Erwartet:** 0 issues
|
||||
- **Tatsächlich:** 27 issues in Plugin traces
|
||||
- **Schweregrad:** Medium
|
||||
- **Status:** ⏳ Nicht gefixt
|
||||
- **Status:** ✅ Triage+Scanner-Fixes 2026-08-24 (Block D/D5): 27→0. Root-Cause war Scanner-Bug: Erwartete manifest.py pro Plugin, aber Projekt-Konvention ist Inline-Manifest via PluginManifest(...) in plugin.py (0/28 Builtins haben manifest.py). Zusätzlich: migrations/tests-Verzeichnisse fälschlich als Plugins gewertet; menu_items-MEDIUM-Findings waren konzeptionell falsch (Frontend konsumiert Menüs dynamisch über active-manifests-API/pluginManifests.ts, nicht statische Imports).
|
||||
|
||||
### BUG-078: Marathon trace_functions — 3 issues
|
||||
- **Kategorie:** Architektur / Functions
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -101,6 +101,13 @@
|
||||
"file": "app/plugins/builtins/tests/test_contracts.py",
|
||||
"attributes": []
|
||||
},
|
||||
{
|
||||
"name": "ContactsContract",
|
||||
"file": "app/plugins/builtins/contacts/contracts.py",
|
||||
"attributes": [
|
||||
"contract_name"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "TasksContract",
|
||||
"file": "app/plugins/builtins/tasks/contracts.py",
|
||||
@@ -137,6 +144,7 @@
|
||||
"find_similar_all_types",
|
||||
"get_search_registry",
|
||||
"llm_analyze_query",
|
||||
"simple_search",
|
||||
"BaseSearchProvider"
|
||||
]
|
||||
},
|
||||
@@ -192,6 +200,7 @@
|
||||
"get_messages",
|
||||
"send_message",
|
||||
"create_plugin_room",
|
||||
"find_locked_room_id",
|
||||
"get_participant_registry",
|
||||
"ParticipantHandler",
|
||||
"MiniAppRegistry",
|
||||
@@ -321,11 +330,6 @@
|
||||
"contract": "DmsContract",
|
||||
"attribute": "DmsFile",
|
||||
"file": "app/plugins/builtins/permissions/public_routes.py"
|
||||
},
|
||||
{
|
||||
"contract": "KommunikationContract",
|
||||
"attribute": "send_message",
|
||||
"file": "app/plugins/builtins/self_improvement/services.py"
|
||||
}
|
||||
],
|
||||
"mismatches": []
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"py_defs": 1773,
|
||||
"py_dead": 692,
|
||||
"ts_defs": 1020,
|
||||
"ts_dead": 457,
|
||||
"py_defs": 1783,
|
||||
"py_dead": 691,
|
||||
"ts_defs": 1022,
|
||||
"ts_dead": 453,
|
||||
"critical_dead": [
|
||||
{
|
||||
"name": "seed_default_workspace",
|
||||
|
||||
@@ -32,10 +32,6 @@
|
||||
"file": "app/plugins/builtins/mail/plugin.py",
|
||||
"hook_name": "mail.after_delete"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/wiki/plugin.py",
|
||||
"hook_name": "wiki"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/plugin.py",
|
||||
"hook_name": "contact.after_create"
|
||||
@@ -79,37 +75,9 @@
|
||||
{
|
||||
"file": "app/plugins/builtins/knowledge/plugin.py",
|
||||
"hook_name": "wiki.article.updated"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/knowledge/plugin.py",
|
||||
"hook_name": "knowledge"
|
||||
}
|
||||
],
|
||||
"triggers": [
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.before_create"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.after_create"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.before_update"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.after_update"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.before_delete"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.after_delete"
|
||||
},
|
||||
{
|
||||
"file": "app/ai/agent_loop.py",
|
||||
"hook_name": "agent.step"
|
||||
@@ -298,6 +266,30 @@
|
||||
"file": "app/plugins/builtins/mail/services.py",
|
||||
"hook_name": "mail.before_send"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.before_create"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.after_create"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.before_update"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.after_update"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.before_delete"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.after_delete"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/tasks/services.py",
|
||||
"hook_name": "task.before_create"
|
||||
@@ -432,10 +424,6 @@
|
||||
}
|
||||
],
|
||||
"orphan_registrations": [
|
||||
{
|
||||
"file": "app/plugins/builtins/wiki/plugin.py",
|
||||
"hook_name": "wiki"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/knowledge/plugin.py",
|
||||
"hook_name": "wiki.article.created"
|
||||
@@ -443,37 +431,9 @@
|
||||
{
|
||||
"file": "app/plugins/builtins/knowledge/plugin.py",
|
||||
"hook_name": "wiki.article.updated"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/knowledge/plugin.py",
|
||||
"hook_name": "knowledge"
|
||||
}
|
||||
],
|
||||
"orphan_triggers": [
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.before_create"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.after_create"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.before_update"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.after_update"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.before_delete"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/companies.py",
|
||||
"hook_name": "company.after_delete"
|
||||
},
|
||||
{
|
||||
"file": "app/ai/agent_loop.py",
|
||||
"hook_name": "agent.step"
|
||||
@@ -606,6 +566,30 @@
|
||||
"file": "app/plugins/builtins/mail/services.py",
|
||||
"hook_name": "mail.before_send"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.before_create"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.after_create"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.before_update"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.after_update"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.before_delete"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py",
|
||||
"hook_name": "company.after_delete"
|
||||
},
|
||||
{
|
||||
"file": "app/plugins/builtins/tasks/services.py",
|
||||
"hook_name": "task.before_create"
|
||||
|
||||
@@ -1,40 +1,4 @@
|
||||
{
|
||||
"total_imports": 2556,
|
||||
"broken": [
|
||||
{
|
||||
"file": "app/workflows/step_handlers.py",
|
||||
"module": "app.services.company_service",
|
||||
"name": "create_company",
|
||||
"line": 447,
|
||||
"issue": "Module not found"
|
||||
},
|
||||
{
|
||||
"file": "app/workflows/step_handlers.py",
|
||||
"module": "app.services.company_service",
|
||||
"name": "update_company",
|
||||
"line": 451,
|
||||
"issue": "Module not found"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/workflows.py",
|
||||
"module": "app.core.approval",
|
||||
"name": "decide_approval",
|
||||
"line": 476,
|
||||
"issue": "Name 'decide_approval' not found in module"
|
||||
},
|
||||
{
|
||||
"file": "app/routes/workflows.py",
|
||||
"module": "app.core.approval",
|
||||
"name": "decide_approval",
|
||||
"line": 539,
|
||||
"issue": "Name 'decide_approval' not found in module"
|
||||
},
|
||||
{
|
||||
"file": "app/core/auth.py",
|
||||
"module": "app.models.session",
|
||||
"name": "SessionModel",
|
||||
"line": 261,
|
||||
"issue": "Name 'SessionModel' not found in module"
|
||||
}
|
||||
]
|
||||
"total_imports": 2568,
|
||||
"broken": []
|
||||
}
|
||||
@@ -3,137 +3,161 @@
|
||||
{
|
||||
"name": "mcp_client",
|
||||
"path": "app/plugins/builtins/mcp_client",
|
||||
"has_manifest": false
|
||||
"has_manifest": true
|
||||
},
|
||||
{
|
||||
"name": "forgejo_error_reporter",
|
||||
"path": "app/plugins/builtins/forgejo_error_reporter",
|
||||
"has_manifest": false
|
||||
"has_manifest": true
|
||||
},
|
||||
{
|
||||
"name": "automation",
|
||||
"path": "app/plugins/builtins/automation",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true,
|
||||
"has_settings_pages": true
|
||||
},
|
||||
{
|
||||
"name": "dms",
|
||||
"path": "app/plugins/builtins/dms",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true,
|
||||
"has_detail_tabs": true
|
||||
},
|
||||
{
|
||||
"name": "mail",
|
||||
"path": "app/plugins/builtins/mail",
|
||||
"has_manifest": false
|
||||
},
|
||||
{
|
||||
"name": "migrations",
|
||||
"path": "app/plugins/builtins/migrations",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true,
|
||||
"has_settings_pages": true,
|
||||
"has_detail_tabs": true
|
||||
},
|
||||
{
|
||||
"name": "wiki",
|
||||
"path": "app/plugins/builtins/wiki",
|
||||
"has_manifest": false
|
||||
},
|
||||
{
|
||||
"name": "tests",
|
||||
"path": "app/plugins/builtins/tests",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true
|
||||
},
|
||||
{
|
||||
"name": "contacts",
|
||||
"path": "app/plugins/builtins/contacts",
|
||||
"has_manifest": false
|
||||
"has_manifest": true
|
||||
},
|
||||
{
|
||||
"name": "tasks",
|
||||
"path": "app/plugins/builtins/tasks",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true
|
||||
},
|
||||
{
|
||||
"name": "calendar",
|
||||
"path": "app/plugins/builtins/calendar",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true,
|
||||
"has_detail_tabs": true
|
||||
},
|
||||
{
|
||||
"name": "unified_search",
|
||||
"path": "app/plugins/builtins/unified_search",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_page_routes": true
|
||||
},
|
||||
{
|
||||
"name": "agent_memory",
|
||||
"path": "app/plugins/builtins/agent_memory",
|
||||
"has_manifest": false
|
||||
"has_manifest": true
|
||||
},
|
||||
{
|
||||
"name": "marketplace",
|
||||
"path": "app/plugins/builtins/marketplace",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true,
|
||||
"has_settings_pages": true,
|
||||
"has_detail_tabs": true
|
||||
},
|
||||
{
|
||||
"name": "graph_rag",
|
||||
"path": "app/plugins/builtins/graph_rag",
|
||||
"has_manifest": false
|
||||
"has_manifest": true
|
||||
},
|
||||
{
|
||||
"name": "report_generator",
|
||||
"path": "app/plugins/builtins/report_generator",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true
|
||||
},
|
||||
{
|
||||
"name": "system_notif",
|
||||
"path": "app/plugins/builtins/system_notif",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_settings_pages": true
|
||||
},
|
||||
{
|
||||
"name": "knowledge",
|
||||
"path": "app/plugins/builtins/knowledge",
|
||||
"has_manifest": false
|
||||
"has_manifest": true
|
||||
},
|
||||
{
|
||||
"name": "entity_links",
|
||||
"path": "app/plugins/builtins/entity_links",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_detail_tabs": true
|
||||
},
|
||||
{
|
||||
"name": "kommunikation",
|
||||
"path": "app/plugins/builtins/kommunikation",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true
|
||||
},
|
||||
{
|
||||
"name": "ai_proactive",
|
||||
"path": "app/plugins/builtins/ai_proactive",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_settings_pages": true
|
||||
},
|
||||
{
|
||||
"name": "ai_assistant",
|
||||
"path": "app/plugins/builtins/ai_assistant",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_menu_items": true,
|
||||
"has_page_routes": true,
|
||||
"has_settings_pages": true
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"path": "app/plugins/builtins/tags",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_detail_tabs": true
|
||||
},
|
||||
{
|
||||
"name": "permissions",
|
||||
"path": "app/plugins/builtins/permissions",
|
||||
"has_manifest": false
|
||||
"has_manifest": true,
|
||||
"has_settings_pages": true
|
||||
},
|
||||
{
|
||||
"name": "mcp_server",
|
||||
"path": "app/plugins/builtins/mcp_server",
|
||||
"has_manifest": false
|
||||
"has_manifest": true
|
||||
},
|
||||
{
|
||||
"name": "ai_ui_control",
|
||||
"path": "app/plugins/builtins/ai_ui_control",
|
||||
"has_manifest": false
|
||||
"has_manifest": true
|
||||
},
|
||||
{
|
||||
"name": "self_improvement",
|
||||
"path": "app/plugins/builtins/self_improvement",
|
||||
"has_manifest": false
|
||||
"has_manifest": true
|
||||
}
|
||||
],
|
||||
"frontend_refs": {},
|
||||
@@ -768,6 +792,141 @@
|
||||
"path": "/categories",
|
||||
"file": "app/plugins/builtins/wiki/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/export",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/merge-history",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/{contact_id}",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/{contact_id}",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/{contact_id}",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/{contact_id}/persons",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/{contact_id}/persons",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/{contact_id}/persons/{person_id}",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/{contact_id}/persons/{person_id}",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/duplicates",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/merge",
|
||||
"file": "app/plugins/builtins/contacts/routes.py"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/{folder_id}",
|
||||
"file": "app/plugins/builtins/contacts/folder_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/{folder_id}",
|
||||
"file": "app/plugins/builtins/contacts/folder_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/{folder_id}/reorder",
|
||||
"file": "app/plugins/builtins/contacts/folder_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/contacts/{contact_id}/move",
|
||||
"file": "app/plugins/builtins/contacts/folder_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/{folder_id}/permissions",
|
||||
"file": "app/plugins/builtins/contacts/folder_permission_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/{folder_id}/permissions",
|
||||
"file": "app/plugins/builtins/contacts/folder_permission_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/{folder_id}/permissions/{permission_id}",
|
||||
"file": "app/plugins/builtins/contacts/folder_permission_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/{folder_id}/permissions/{permission_id}",
|
||||
"file": "app/plugins/builtins/contacts/folder_permission_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/{folder_id}/access",
|
||||
"file": "app/plugins/builtins/contacts/folder_permission_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/export",
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/{company_id}",
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/{company_id}",
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/{company_id}",
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/{company_id}/contacts/{contact_id}",
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/{company_id}/contacts/{contact_id}",
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/{company_id}/emails",
|
||||
"file": "app/plugins/builtins/contacts/company_routes.py"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/{task_id}",
|
||||
@@ -1529,141 +1688,5 @@
|
||||
"file": "app/plugins/builtins/self_improvement/routes.py"
|
||||
}
|
||||
],
|
||||
"issues": [
|
||||
{
|
||||
"plugin": "mcp_client",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "forgejo_error_reporter",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "automation",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "dms",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "mail",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "migrations",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "wiki",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "tests",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "contacts",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "tasks",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "calendar",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "unified_search",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "agent_memory",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "marketplace",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "graph_rag",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "report_generator",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "system_notif",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "knowledge",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "entity_links",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "kommunikation",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "ai_proactive",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "ai_assistant",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "tags",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "permissions",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "mcp_server",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "ai_ui_control",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
},
|
||||
{
|
||||
"plugin": "self_improvement",
|
||||
"issue": "No manifest.py",
|
||||
"severity": "HIGH"
|
||||
}
|
||||
]
|
||||
"issues": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -138,50 +138,62 @@ def find_backend_response_fields() -> list[dict]:
|
||||
return responses
|
||||
|
||||
|
||||
def _router_prefixes(content: str) -> dict[str, str]:
|
||||
"""Extract all router variable definitions with their APIRouter prefixes.
|
||||
|
||||
Modules may define multiple routers (e.g. ``router``, ``calendar_router``,
|
||||
``resource_router``) each with its own prefix.
|
||||
"""
|
||||
prefixes: dict[str, str] = {}
|
||||
for m in re.finditer(
|
||||
r"(\w+)\s*=\s*APIRouter\([^)]*?prefix\s*=\s*[\"']([^\"']+)[\"']", content
|
||||
):
|
||||
prefixes[m.group(1)] = m.group(2).rstrip("/")
|
||||
return prefixes
|
||||
|
||||
|
||||
def find_backend_endpoints() -> list[dict]:
|
||||
"""Find all backend endpoints from route decorators."""
|
||||
"""Find all backend endpoints from route decorators.
|
||||
|
||||
Includes the module's APIRouter prefix so paths are comparable with
|
||||
frontend calls (which use full /api/v1/... URLs).
|
||||
"""
|
||||
endpoints = []
|
||||
routes_dir = BACKEND_APP / "routes"
|
||||
if not routes_dir.exists():
|
||||
return endpoints
|
||||
scan_dirs = [
|
||||
BACKEND_APP / "routes",
|
||||
BACKEND_APP / "plugins" / "builtins",
|
||||
]
|
||||
|
||||
for py_file in routes_dir.rglob("*.py"):
|
||||
try:
|
||||
content = py_file.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
for scan_dir in scan_dirs:
|
||||
if not scan_dir.exists():
|
||||
continue
|
||||
|
||||
rel_path = str(py_file.relative_to(BACKEND_APP))
|
||||
|
||||
# Find router decorators
|
||||
for match in re.finditer(r"@router\.(get|post|put|patch|delete)\s*\(\s*[\"']([^\"']+)[\"']", content):
|
||||
method = match.group(1).upper()
|
||||
path = match.group(2)
|
||||
endpoints.append({
|
||||
"file": rel_path,
|
||||
"method": method,
|
||||
"path": path,
|
||||
})
|
||||
|
||||
# Also find plugin routes
|
||||
plugins_dir = BACKEND_APP / "plugins" / "builtins"
|
||||
if plugins_dir.exists():
|
||||
for py_file in plugins_dir.rglob("*.py"):
|
||||
is_plugin_dir = scan_dir.name == "builtins"
|
||||
for py_file in scan_dir.rglob("*.py"):
|
||||
try:
|
||||
content = py_file.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
rel_path = str(py_file.relative_to(BACKEND_APP))
|
||||
for match in re.finditer(r"@router\.(get|post|put|patch|delete)\s*\(\s*[\"']([^\"']+)[\"']", content):
|
||||
method = match.group(1).upper()
|
||||
path = match.group(2)
|
||||
endpoints.append({
|
||||
prefixes = _router_prefixes(content)
|
||||
|
||||
# Variable-agnostic with per-variable prefix mapping: modules may
|
||||
# define multiple routers (e.g. ``calendar_router``) each with its
|
||||
# own prefix. Path may be an empty string for collection routes
|
||||
# (e.g. ``@router.get("")``).
|
||||
for match in re.finditer(r"@(\w+)\.(get|post|put|patch|delete)\s*\(\s*[\"']([^\"']*)[\"']", content):
|
||||
var_name = match.group(1)
|
||||
method = match.group(2).upper()
|
||||
prefix = prefixes.get(var_name, "")
|
||||
path = prefix + match.group(3)
|
||||
ep = {
|
||||
"file": rel_path,
|
||||
"method": method,
|
||||
"path": path,
|
||||
"is_plugin": True,
|
||||
})
|
||||
}
|
||||
if is_plugin_dir:
|
||||
ep["is_plugin"] = True
|
||||
endpoints.append(ep)
|
||||
|
||||
return endpoints
|
||||
|
||||
@@ -190,9 +202,14 @@ def normalize_path(path: str) -> str:
|
||||
"""Normalize a path for comparison."""
|
||||
# Remove leading /api/v1 if present
|
||||
path = re.sub(r"^/api/v1/", "/", path)
|
||||
# Replace JS template-literal interpolations (${qs}, ${id}, ...) like params;
|
||||
# ensure a '/' separator so '/webhooks${params}' -> '/webhooks/{param}'
|
||||
path = re.sub(r"([^/])\$\{[^}]*\}", r"\1/{param}", path)
|
||||
path = re.sub(r"^\$\{[^}]*\}", "{param}", path)
|
||||
# Replace {param} patterns
|
||||
path = re.sub(r"\{[^}]+\}", "{param}", path)
|
||||
# Remove trailing slash
|
||||
# Collapse duplicate slashes and remove trailing slash
|
||||
path = re.sub(r"//+", "/", path)
|
||||
path = path.rstrip("/")
|
||||
return path
|
||||
|
||||
|
||||
@@ -9,16 +9,27 @@ APP = ROOT / "app"
|
||||
FRONTEND = ROOT / "frontend" / "src"
|
||||
|
||||
def find_plugin_manifests() -> list[dict]:
|
||||
"""Find plugin manifests.
|
||||
|
||||
Project convention: manifests are defined inline in plugin.py via
|
||||
``PluginManifest(...)`` — no separate manifest.py file exists.
|
||||
"""
|
||||
plugins = []
|
||||
builtins = APP / "plugins" / "builtins"
|
||||
if not builtins.exists(): return plugins
|
||||
for d in builtins.iterdir():
|
||||
if not d.is_dir() or d.name.startswith("_"): continue
|
||||
manifest_file = d / "manifest.py"
|
||||
plugin_info = {"name": d.name, "path": str(d.relative_to(ROOT)), "has_manifest": manifest_file.exists()}
|
||||
if manifest_file.exists():
|
||||
try: content = manifest_file.read_text()
|
||||
plugin_file = d / "plugin.py"
|
||||
if not plugin_file.exists():
|
||||
# Not a plugin directory (e.g. migrations/, tests/) — skip silently
|
||||
continue
|
||||
has_manifest = False
|
||||
plugin_info = {"name": d.name, "path": str(d.relative_to(ROOT)), "has_manifest": False}
|
||||
if plugin_file.exists():
|
||||
try: content = plugin_file.read_text()
|
||||
except: continue
|
||||
has_manifest = bool(re.search(r'PluginManifest\s*\(', content))
|
||||
plugin_info["has_manifest"] = has_manifest
|
||||
for m in re.finditer(r'menu_items\s*[=:]\s*\[', content): plugin_info["has_menu_items"] = True
|
||||
for m in re.finditer(r'page_routes\s*[=:]\s*\[', content): plugin_info["has_page_routes"] = True
|
||||
for m in re.finditer(r'settings_pages\s*[=:]\s*\[', content): plugin_info["has_settings_pages"] = True
|
||||
@@ -47,6 +58,16 @@ def find_plugin_routes() -> list[dict]:
|
||||
routes.append({"method": m.group(1).upper(), "path": m.group(2), "file": rel})
|
||||
return routes
|
||||
|
||||
|
||||
def _has_dynamic_consumption() -> bool:
|
||||
"""True if the frontend consumes plugin manifests dynamically.
|
||||
|
||||
The frontend fetches menu_items/page_routes via the active-manifests API
|
||||
(frontend/src/api/pluginManifests.ts) and renders them through pluginStore —
|
||||
static '@/plugins/<name>' imports are therefore not required for wiring.
|
||||
"""
|
||||
return (FRONTEND / "api" / "pluginManifests.ts").exists()
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("TRACE PLUGINS — Plugin→Manifest→Frontend Verkabelungsfehler")
|
||||
@@ -57,8 +78,12 @@ def main():
|
||||
issues = []
|
||||
for p in plugins:
|
||||
if not p["has_manifest"]:
|
||||
issues.append({"plugin": p["name"], "issue": "No manifest.py", "severity": "HIGH"})
|
||||
if p.get("has_menu_items") and p["name"] not in frontend_refs:
|
||||
issues.append({"plugin": p["name"], "issue": "No manifest (PluginManifest in plugin.py expected)", "severity": "HIGH"})
|
||||
# NOTE: menu_items are consumed dynamically by the frontend via the
|
||||
# active-manifests API (frontend/src/api/pluginManifests.ts + pluginStore),
|
||||
# NOT via static '@/plugins/<name>' imports — a missing static import is
|
||||
# therefore not a wiring error.
|
||||
if p.get("has_menu_items") and p["name"] not in frontend_refs and not _has_dynamic_consumption():
|
||||
issues.append({"plugin": p["name"], "issue": "Has menu_items but no frontend reference", "severity": "MEDIUM"})
|
||||
print(f"\nPlugins found: {len(plugins)}")
|
||||
print(f"Plugin routes: {len(plugin_routes)}")
|
||||
|
||||
Reference in New Issue
Block a user