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:
Agent Zero
2026-08-24 12:43:41 +02:00
parent c0e8e4ecfd
commit 5cc5a3fa6a
11 changed files with 2702 additions and 7360 deletions
+4 -1
View File
@@ -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 | | 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-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-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 A ABGESCHLOSSEN** — Gate A bestanden (32f63ad).
**Block B ABGESCHLOSSEN** — Gate B bestanden (alle 5 Checks bewiesen). **Block B ABGESCHLOSSEN** — Gate B bestanden (alle 5 Checks bewiesen).
+6 -2
View File
@@ -540,7 +540,11 @@ Playwright E2E: 10 passed, 24 failed (BUG-011/012/013 — Mock-Daten und data-te
- **Erwartet:** 0 issues - **Erwartet:** 0 issues
- **Tatsächlich:** 859 issues in Frontend↔Backend API Contracts - **Tatsächlich:** 859 issues in Frontend↔Backend API Contracts
- **Schweregrad:** Medium - **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 ### BUG-075: Marathon trace_stores — 323 issues
- **Kategorie:** Frontend / Stores - **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 - **Erwartet:** 0 issues
- **Tatsächlich:** 27 issues in Plugin traces - **Tatsächlich:** 27 issues in Plugin traces
- **Schweregrad:** Medium - **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 ### BUG-078: Marathon trace_functions — 3 issues
- **Kategorie:** Architektur / Functions - **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", "file": "app/plugins/builtins/tests/test_contracts.py",
"attributes": [] "attributes": []
}, },
{
"name": "ContactsContract",
"file": "app/plugins/builtins/contacts/contracts.py",
"attributes": [
"contract_name"
]
},
{ {
"name": "TasksContract", "name": "TasksContract",
"file": "app/plugins/builtins/tasks/contracts.py", "file": "app/plugins/builtins/tasks/contracts.py",
@@ -137,6 +144,7 @@
"find_similar_all_types", "find_similar_all_types",
"get_search_registry", "get_search_registry",
"llm_analyze_query", "llm_analyze_query",
"simple_search",
"BaseSearchProvider" "BaseSearchProvider"
] ]
}, },
@@ -192,6 +200,7 @@
"get_messages", "get_messages",
"send_message", "send_message",
"create_plugin_room", "create_plugin_room",
"find_locked_room_id",
"get_participant_registry", "get_participant_registry",
"ParticipantHandler", "ParticipantHandler",
"MiniAppRegistry", "MiniAppRegistry",
@@ -321,11 +330,6 @@
"contract": "DmsContract", "contract": "DmsContract",
"attribute": "DmsFile", "attribute": "DmsFile",
"file": "app/plugins/builtins/permissions/public_routes.py" "file": "app/plugins/builtins/permissions/public_routes.py"
},
{
"contract": "KommunikationContract",
"attribute": "send_message",
"file": "app/plugins/builtins/self_improvement/services.py"
} }
], ],
"mismatches": [] "mismatches": []
@@ -1,8 +1,8 @@
{ {
"py_defs": 1773, "py_defs": 1783,
"py_dead": 692, "py_dead": 691,
"ts_defs": 1020, "ts_defs": 1022,
"ts_dead": 457, "ts_dead": 453,
"critical_dead": [ "critical_dead": [
{ {
"name": "seed_default_workspace", "name": "seed_default_workspace",
+48 -64
View File
@@ -32,10 +32,6 @@
"file": "app/plugins/builtins/mail/plugin.py", "file": "app/plugins/builtins/mail/plugin.py",
"hook_name": "mail.after_delete" "hook_name": "mail.after_delete"
}, },
{
"file": "app/plugins/builtins/wiki/plugin.py",
"hook_name": "wiki"
},
{ {
"file": "app/plugins/builtins/contacts/plugin.py", "file": "app/plugins/builtins/contacts/plugin.py",
"hook_name": "contact.after_create" "hook_name": "contact.after_create"
@@ -79,37 +75,9 @@
{ {
"file": "app/plugins/builtins/knowledge/plugin.py", "file": "app/plugins/builtins/knowledge/plugin.py",
"hook_name": "wiki.article.updated" "hook_name": "wiki.article.updated"
},
{
"file": "app/plugins/builtins/knowledge/plugin.py",
"hook_name": "knowledge"
} }
], ],
"triggers": [ "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", "file": "app/ai/agent_loop.py",
"hook_name": "agent.step" "hook_name": "agent.step"
@@ -298,6 +266,30 @@
"file": "app/plugins/builtins/mail/services.py", "file": "app/plugins/builtins/mail/services.py",
"hook_name": "mail.before_send" "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", "file": "app/plugins/builtins/tasks/services.py",
"hook_name": "task.before_create" "hook_name": "task.before_create"
@@ -432,10 +424,6 @@
} }
], ],
"orphan_registrations": [ "orphan_registrations": [
{
"file": "app/plugins/builtins/wiki/plugin.py",
"hook_name": "wiki"
},
{ {
"file": "app/plugins/builtins/knowledge/plugin.py", "file": "app/plugins/builtins/knowledge/plugin.py",
"hook_name": "wiki.article.created" "hook_name": "wiki.article.created"
@@ -443,37 +431,9 @@
{ {
"file": "app/plugins/builtins/knowledge/plugin.py", "file": "app/plugins/builtins/knowledge/plugin.py",
"hook_name": "wiki.article.updated" "hook_name": "wiki.article.updated"
},
{
"file": "app/plugins/builtins/knowledge/plugin.py",
"hook_name": "knowledge"
} }
], ],
"orphan_triggers": [ "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", "file": "app/ai/agent_loop.py",
"hook_name": "agent.step" "hook_name": "agent.step"
@@ -606,6 +566,30 @@
"file": "app/plugins/builtins/mail/services.py", "file": "app/plugins/builtins/mail/services.py",
"hook_name": "mail.before_send" "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", "file": "app/plugins/builtins/tasks/services.py",
"hook_name": "task.before_create" "hook_name": "task.before_create"
+2 -38
View File
@@ -1,40 +1,4 @@
{ {
"total_imports": 2556, "total_imports": 2568,
"broken": [ "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"
}
]
} }
+195 -172
View File
@@ -3,137 +3,161 @@
{ {
"name": "mcp_client", "name": "mcp_client",
"path": "app/plugins/builtins/mcp_client", "path": "app/plugins/builtins/mcp_client",
"has_manifest": false "has_manifest": true
}, },
{ {
"name": "forgejo_error_reporter", "name": "forgejo_error_reporter",
"path": "app/plugins/builtins/forgejo_error_reporter", "path": "app/plugins/builtins/forgejo_error_reporter",
"has_manifest": false "has_manifest": true
}, },
{ {
"name": "automation", "name": "automation",
"path": "app/plugins/builtins/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", "name": "dms",
"path": "app/plugins/builtins/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", "name": "mail",
"path": "app/plugins/builtins/mail", "path": "app/plugins/builtins/mail",
"has_manifest": false "has_manifest": true,
}, "has_menu_items": true,
{ "has_page_routes": true,
"name": "migrations", "has_settings_pages": true,
"path": "app/plugins/builtins/migrations", "has_detail_tabs": true
"has_manifest": false
}, },
{ {
"name": "wiki", "name": "wiki",
"path": "app/plugins/builtins/wiki", "path": "app/plugins/builtins/wiki",
"has_manifest": false "has_manifest": true,
}, "has_menu_items": true,
{ "has_page_routes": true
"name": "tests",
"path": "app/plugins/builtins/tests",
"has_manifest": false
}, },
{ {
"name": "contacts", "name": "contacts",
"path": "app/plugins/builtins/contacts", "path": "app/plugins/builtins/contacts",
"has_manifest": false "has_manifest": true
}, },
{ {
"name": "tasks", "name": "tasks",
"path": "app/plugins/builtins/tasks", "path": "app/plugins/builtins/tasks",
"has_manifest": false "has_manifest": true,
"has_menu_items": true,
"has_page_routes": true
}, },
{ {
"name": "calendar", "name": "calendar",
"path": "app/plugins/builtins/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", "name": "unified_search",
"path": "app/plugins/builtins/unified_search", "path": "app/plugins/builtins/unified_search",
"has_manifest": false "has_manifest": true,
"has_page_routes": true
}, },
{ {
"name": "agent_memory", "name": "agent_memory",
"path": "app/plugins/builtins/agent_memory", "path": "app/plugins/builtins/agent_memory",
"has_manifest": false "has_manifest": true
}, },
{ {
"name": "marketplace", "name": "marketplace",
"path": "app/plugins/builtins/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", "name": "graph_rag",
"path": "app/plugins/builtins/graph_rag", "path": "app/plugins/builtins/graph_rag",
"has_manifest": false "has_manifest": true
}, },
{ {
"name": "report_generator", "name": "report_generator",
"path": "app/plugins/builtins/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", "name": "system_notif",
"path": "app/plugins/builtins/system_notif", "path": "app/plugins/builtins/system_notif",
"has_manifest": false "has_manifest": true,
"has_settings_pages": true
}, },
{ {
"name": "knowledge", "name": "knowledge",
"path": "app/plugins/builtins/knowledge", "path": "app/plugins/builtins/knowledge",
"has_manifest": false "has_manifest": true
}, },
{ {
"name": "entity_links", "name": "entity_links",
"path": "app/plugins/builtins/entity_links", "path": "app/plugins/builtins/entity_links",
"has_manifest": false "has_manifest": true,
"has_detail_tabs": true
}, },
{ {
"name": "kommunikation", "name": "kommunikation",
"path": "app/plugins/builtins/kommunikation", "path": "app/plugins/builtins/kommunikation",
"has_manifest": false "has_manifest": true,
"has_menu_items": true,
"has_page_routes": true
}, },
{ {
"name": "ai_proactive", "name": "ai_proactive",
"path": "app/plugins/builtins/ai_proactive", "path": "app/plugins/builtins/ai_proactive",
"has_manifest": false "has_manifest": true,
"has_settings_pages": true
}, },
{ {
"name": "ai_assistant", "name": "ai_assistant",
"path": "app/plugins/builtins/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", "name": "tags",
"path": "app/plugins/builtins/tags", "path": "app/plugins/builtins/tags",
"has_manifest": false "has_manifest": true,
"has_detail_tabs": true
}, },
{ {
"name": "permissions", "name": "permissions",
"path": "app/plugins/builtins/permissions", "path": "app/plugins/builtins/permissions",
"has_manifest": false "has_manifest": true,
"has_settings_pages": true
}, },
{ {
"name": "mcp_server", "name": "mcp_server",
"path": "app/plugins/builtins/mcp_server", "path": "app/plugins/builtins/mcp_server",
"has_manifest": false "has_manifest": true
}, },
{ {
"name": "ai_ui_control", "name": "ai_ui_control",
"path": "app/plugins/builtins/ai_ui_control", "path": "app/plugins/builtins/ai_ui_control",
"has_manifest": false "has_manifest": true
}, },
{ {
"name": "self_improvement", "name": "self_improvement",
"path": "app/plugins/builtins/self_improvement", "path": "app/plugins/builtins/self_improvement",
"has_manifest": false "has_manifest": true
} }
], ],
"frontend_refs": {}, "frontend_refs": {},
@@ -768,6 +792,141 @@
"path": "/categories", "path": "/categories",
"file": "app/plugins/builtins/wiki/routes.py" "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", "method": "GET",
"path": "/{task_id}", "path": "/{task_id}",
@@ -1529,141 +1688,5 @@
"file": "app/plugins/builtins/self_improvement/routes.py" "file": "app/plugins/builtins/self_improvement/routes.py"
} }
], ],
"issues": [ "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"
}
]
} }
File diff suppressed because it is too large Load Diff
+49 -32
View File
@@ -138,50 +138,62 @@ def find_backend_response_fields() -> list[dict]:
return responses 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]: 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 = [] endpoints = []
routes_dir = BACKEND_APP / "routes" scan_dirs = [
if not routes_dir.exists(): BACKEND_APP / "routes",
return endpoints BACKEND_APP / "plugins" / "builtins",
]
for py_file in routes_dir.rglob("*.py"): for scan_dir in scan_dirs:
if not scan_dir.exists():
continue
is_plugin_dir = scan_dir.name == "builtins"
for py_file in scan_dir.rglob("*.py"):
try: try:
content = py_file.read_text(encoding="utf-8") content = py_file.read_text(encoding="utf-8")
except Exception: except Exception:
continue continue
rel_path = str(py_file.relative_to(BACKEND_APP)) rel_path = str(py_file.relative_to(BACKEND_APP))
prefixes = _router_prefixes(content)
# Find router decorators # Variable-agnostic with per-variable prefix mapping: modules may
for match in re.finditer(r"@router\.(get|post|put|patch|delete)\s*\(\s*[\"']([^\"']+)[\"']", content): # define multiple routers (e.g. ``calendar_router``) each with its
method = match.group(1).upper() # own prefix. Path may be an empty string for collection routes
path = match.group(2) # (e.g. ``@router.get("")``).
endpoints.append({ 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, "file": rel_path,
"method": method, "method": method,
"path": path, "path": path,
}) }
if is_plugin_dir:
# Also find plugin routes ep["is_plugin"] = True
plugins_dir = BACKEND_APP / "plugins" / "builtins" endpoints.append(ep)
if plugins_dir.exists():
for py_file in plugins_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({
"file": rel_path,
"method": method,
"path": path,
"is_plugin": True,
})
return endpoints return endpoints
@@ -190,9 +202,14 @@ def normalize_path(path: str) -> str:
"""Normalize a path for comparison.""" """Normalize a path for comparison."""
# Remove leading /api/v1 if present # Remove leading /api/v1 if present
path = re.sub(r"^/api/v1/", "/", path) 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 # Replace {param} patterns
path = re.sub(r"\{[^}]+\}", "{param}", path) path = re.sub(r"\{[^}]+\}", "{param}", path)
# Remove trailing slash # Collapse duplicate slashes and remove trailing slash
path = re.sub(r"//+", "/", path)
path = path.rstrip("/") path = path.rstrip("/")
return path return path
+31 -6
View File
@@ -9,16 +9,27 @@ APP = ROOT / "app"
FRONTEND = ROOT / "frontend" / "src" FRONTEND = ROOT / "frontend" / "src"
def find_plugin_manifests() -> list[dict]: 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 = [] plugins = []
builtins = APP / "plugins" / "builtins" builtins = APP / "plugins" / "builtins"
if not builtins.exists(): return plugins if not builtins.exists(): return plugins
for d in builtins.iterdir(): for d in builtins.iterdir():
if not d.is_dir() or d.name.startswith("_"): continue if not d.is_dir() or d.name.startswith("_"): continue
manifest_file = d / "manifest.py" plugin_file = d / "plugin.py"
plugin_info = {"name": d.name, "path": str(d.relative_to(ROOT)), "has_manifest": manifest_file.exists()} if not plugin_file.exists():
if manifest_file.exists(): # Not a plugin directory (e.g. migrations/, tests/) — skip silently
try: content = manifest_file.read_text() 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 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'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'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 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}) routes.append({"method": m.group(1).upper(), "path": m.group(2), "file": rel})
return routes 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(): def main():
print("=" * 70) print("=" * 70)
print("TRACE PLUGINS — Plugin→Manifest→Frontend Verkabelungsfehler") print("TRACE PLUGINS — Plugin→Manifest→Frontend Verkabelungsfehler")
@@ -57,8 +78,12 @@ def main():
issues = [] issues = []
for p in plugins: for p in plugins:
if not p["has_manifest"]: if not p["has_manifest"]:
issues.append({"plugin": p["name"], "issue": "No manifest.py", "severity": "HIGH"}) issues.append({"plugin": p["name"], "issue": "No manifest (PluginManifest in plugin.py expected)", "severity": "HIGH"})
if p.get("has_menu_items") and p["name"] not in frontend_refs: # 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"}) issues.append({"plugin": p["name"], "issue": "Has menu_items but no frontend reference", "severity": "MEDIUM"})
print(f"\nPlugins found: {len(plugins)}") print(f"\nPlugins found: {len(plugins)}")
print(f"Plugin routes: {len(plugin_routes)}") print(f"Plugin routes: {len(plugin_routes)}")