Files
leocrm/PROGRESS.md
T

124 KiB
Raw Blame History

LeoPlatform — Fortschritts-Tracking

Externer Architektur-Audit — 13 Backend-Fixes verifiziert & umgesetzt (2026-09-13, Commit 4a25ac1, #370)

Ausgangslage: Externes KI-Audit (leocrm-full.zip, Stand 86cea5d) meldete 17 Findings. Verifikation gegen den echten Code: alle 17 BESTÄTIGT (inkl. exakt der 12 gemeldeten fehlenden Permission-Keys — per AST-Scan 1:1 reproduziert). 13 Backend-/Lifecycle-Punkte sofort gefixt; die 4 Frontend-Plugin-Architektur-Punkte sind als Phase Q in die Roadmap eingeplant (Begründung dort).

Fixes (alle mit Live-Verifikation, tests/test_audit_architecture_fixes.py 17/17):

# Finding Fix Verifikation
P1 GET /workspaces lieferte modules: [] → Workspace-Editor überschrieb Konfig list_workspaces() lädt Module+User-Counts gebündelt (2 Queries statt N+1) test_f1: modules mit is_visible-Flags 1:1
P1 /plugins/active-manifests ignorierte Tenant-Deaktivierung (UI zeigte 403-Menüs) Registry/Service/Route tragen tenant_id durch, filtern tenant_plugin_activation.is_active=false test_f2: Plugin im Manifest ohne Filter, gefiltert mit Tenant-Eintrag
P1 uninstall() umging PluginService-Cleanup (stale Permissions/Entity-Models) uninstall_plugin() ruft deactivate_plugin() VOR registry.uninstall() test_f3: Quellcode-Verifikation + Lifecycle-Verhalten
P1 Contract-Lazy-Loading kannte DB-Aktivstatus nicht (Restart-Edge-Case) Startup markiert active=False-Plugins (mark_db_inactive), Guard in get_contract(), Re-Activate cleart test_f4: fail-closed + reopen
P1/P2 register_field_definitions() ohne Unregister, nicht im Runtime-Lifecycle unregister_field_definitions() + Aktivierung/Deaktivierung registrieren/entfernen Field-Defs test_f5: voller Lifecycle über PluginService
P1/P2 39 Contact-Felddefinitionen lagen im Core (CORE_FIELD_DEFINITIONS) Verschoben ins ContactsPlugin-Manifest (field_definitions=); Core behält nur users-Felder; sensitive_data.py nutzt jetzt die Registry-Gesamtsicht test_f10: Core ohne contacts-Module, Plugin mit 39 Defs, Sensitivities erhalten
P1 12 verwendete Permission-Keys nicht registriert 9 in CORE_PERMISSIONS (automation:admin, bank-accounts:, delegations:, policies:, templates:), 2 im permissions-Plugin (permissions:read/admin), 1 im forgejo-Reporter (system:read) AST-Re-Scan: 146 Keys, 0 fehlend; test_f9
P2 contact_folder als Core-Entity Ins ContactsPlugin verschoben; register_entity_model(..., plugin_name=...) befüllt jetzt ENTITY_PLUGIN_OWNERS (war tot) test_f15: get_entity_read_permission('contact_folder') == 'contacts:read' via Owner
P2 Entity-Permission-Fallback contacts:read Fail-closed Sentinel __unmapped__:read (nicht grantbar → 403); unbekannte Entities werden vorher via 422 abgelehnt test_f15
P2 Forgejo-Error-Reporter is_core=True trotz „test/staging only" is_core=False (deaktivierbar) test_f11
P1/P2 Core-FK entity_attachments.files vs. „DMS = Plugin" Widerspruch ADR-020: DMS als Plattform-Core-Plugin deklariert (is_core=True) — FK-Richtung ist damit legitim, Registry erzwingt Nicht-Deaktivierbarkeit test_f12
P2 Core-Worker importierte Contact für Trash-Cleanup cleanup_contacts_trash ins Contacts-Plugin ausgelagert (jobs.py, get_job_modules()-Discovery wie knowledge), Cron 04:15 test_f13: kein app.models.contact-Import im Worker + Job registriert
P1/P2 DSGVO-Export doppelt (Legacy-Route kannte Contacts direkt) GET /dsgvo-export delegiert an _dsar_collect_user_data (autoritativer DSAR-Collector, Plugin-Contracts) test_f14: Delegation, kein Contact-Import
P2 False-green Tests (or True, irreführender Name, veraltete >100-Routes-Assertion) 3 Assertions durch echte Prüfungen ersetzt; Test umbenannt (_simulated); Route-Count-Assertion auf Plugin-Architektur umgestellt (vorher schon auf HEAD rot — pre-existing) Suite grün

Nicht als Code-Fix, sondern als Phase Q geplant (Roadmap „Phase Q“, user-pending): statische Plugin-Routen + Settings-Routen im zentralen Router (Doppel-Architektur), STATIC_COMPONENT_MAP, widgetRegistry — benötigt Build-Time-Discovery-Konzept.

Beweis Suite-Isolation (nicht durch Fixes verursacht): test_m4_system_miniapps solo 7/7 grün (mit UND ohne Fixes), test_n4_scope_declarations solo 18/18 grün — Combo-Failures sind das bekannte „relation users does not exist“-Problem.

Regressionen: test_contacts_lifecycle 8/8, test_custom_field_definitions, test_contacts_entity_registry 3/3, test_contacts_model_ownership, test_workspace_scopes 18/18, test_rbac_comprehensive, test_plugin_lifecycle_service, test_einvoice_generator — alles grün. Cross-Plugin-Checker: 497 Dateien, 0 verbotene Imports. compileall sauber. Ruff auf 7-Error-Baseline.

Deployiert & live bewiesen (Commits 4a25ac1 + 1b80090, 2x Full Deploy SUCCESS, Health healthy, Worker up):

  • GET /workspaces: Standard-Workspace liefert modules=24 (vorher []) — Editor-Overwrite-Bug behoben
  • GET /plugins/active-manifests: 26 Manifeste
  • GET /roles/permissions: 141 Keys, 12/12 neue Keys sichtbar (erste Deploy-Runde nur 11/12 — system:read fehlte, weil der ursprüngliche Patch die permissions-Liste versehentlich in PluginRouteDef-kwargs platziert hatte; in 1b80090 korrekt auf Manifest-Ebene, Test f9 prüft jetzt echte Manifeste statt manueller Registrierung — Live-Check ist DoD-Pflicht)
  • field_definitions: contacts=39 (Plugin), users=4 (Core) — Ownership-Verschiebung live bestätigt

Phase Q — Frontend-Plugin-Architektur vollendet (2026-09-13) — PHASE Q KOMPLETT

Ausgangslage: Die 4 Frontend-Findings des externen Audits (Doppel-Architektur Routen/Settings, STATIC_COMPONENT_MAP, widgetRegistry) wurden als Phase Q geplant und jetzt vollständig umgesetzt. Ein Plugin meldet ab sofort Backend, Manifest UND React-Komponenten über sein Manifest — keine zentrale Frontend-Datei muss mehr angefasst werden.

Q3+Q4 (Commit 895f85d) — Build-Time-Discovery statt Zentral-Listen:

  • scripts/generate_component_map.py: scannt alle builtin-Manifeste + system_miniapps.py, generiert frontend/src/generated/pluginComponents.generated.ts (37 Komponenten). Fail-Hard bei Ghost-Komponenten (bewiesen: exit 1), erkennt default- vs. named-exports, deterministisch, --check-Modus für CI.
  • PluginLoader.tsx: STATIC_COMPONENT_MAP (26 Einträge) GELÖSCHT → generierte Map.
  • MiniAppHost.tsx: widgetRegistry (11 Einträge) GELÖSCHT → generierte Map (löst Q4 mit).
  • Contacts-Manifest: DedupMergePage-Pfad-Alias auf echte Datei korrigiert.

Q1+Q2 (Commit b666fe5) — Manifeste = einzige Routen-Quelle:

  • routes/index.tsx: 14 statische AppShell-Plugin-Routen + 9 statische Settings-Routen + 20 tote Lazy-Imports entfernt. Nur noch Core-Routen + die StartLayout-Hub-Bäume (/agents, /automation, /logs, /help — verschachtelte Sub-Navigation) bleiben statisch (bewusste Entscheidung: Layout-Routen mit Sub-Navigation werden von flachen Manifest-Einträgen nicht abgebildet).
  • PluginRouteRenderer: neue variant-Prop — 'pages' (absolute Pfade, AppShell- Catch-all) vs. 'settings' (bare Sub-Segmente, Descendant-Matching im /settings-Subtree). Getrennte Entry-Listen verhindern Pfad-Kollisionen.
  • Manifeste ergänzt: Calendar +/calendar/kanban, Tags +/tags (+ Menü-Item), Automation: /workflows auf workflows:read (Parität zur ersetzten statischen Route), tote flache /agents-+/automation-Einträge entfernt.

Verifikation (jeder Schritt live gemessen):

  • tsc --noEmit exit 0 (nach Q3 und nach Q1/Q2) · production build exit 0 (2×)
  • Ghost-Fail-Hard: Generator exit 1 mit Fehlermeldung bei eingepflanzter Ghost-Komponente
  • Vitest: Dashboard + MiniAppWindow 17/17, pluginStore 18/18, kombiniert 35/35
  • Backend-Regressionen: Route-Order, M5-MiniApps, N4-Scope, N3-Filtering 49/49
  • compileall sauber · Cross-Plugin-Checker 497/0 · ruff clean
  • Full Deploy SUCCESS · Health healthy · Live-Manifest-Checks: /calendar/kanban, /tags, workflows:read, keine toten Einträge — alle OK · SPA-Routen 200

Nächster Schritt (Roadmap): Re-Audit durch den externen Prüfer — alle 17 Audit-Findings sind behoben (13 Backend + 4 Frontend). Danach Phase O UI-Overhaul, Phase P Notizen-App oder UI-Backlog-Module 2-16.

Weitermachen (2026-09-15, Übergabe — für das nächste Modell/jede KI)

Produktion läuft stabil (HEAD b91ee5b = origin/main, 0 ungepushte Commits, Health healthy, Alembic 0144, RLS 113 Tabellen, Worker up). Alle Forgejo-Issues bis #389 geschlossen. Outbox sauber: 158 Events published (Webhook-Fix #380).

2026-09-13 bis 16 abgeschlossen: (1) Externer Architektur-Audit verifiziert — alle 17 Findings bestätigt, 13 Backend-Fixes (Commit 4a25ac1, #370). (2) PHASE Q KOMPLETT — Frontend-Plugin-Architektur: generierte Komponenten-Map (scripts/generate_component_map.py, jetzt 43 Eintraege, Fail-Hard bei Ghosts) ersetzt STATIC_COMPONENT_MAP + widgetRegistry; Plugin-Routen/Settings nur noch aus Manifesten via PluginRouteRenderer (variant pages/settings). (3) Drei Produktions-Bugfixes 2026-09-14 (siehe Bugfix-Tabelle unten): Webhook-JSONB-Containment (#380), Zustands-Selector-Spinner-Hang (#381), Consumer-Registry-qualname. (4) ZWEI weitere Produktions-Bugfixes 2026-09-16 (Bugfix-Tabelle): ai_assistant-Reaktivierung (#389 — KI-Chat war seit 0137 down) + CSRF-Bearer-Skip (External-API fuer Integrationen). (5) UI-BACKLOG 16/16 KOMPLETT — alle 16 Backend-Module haben jetzt UI (Commits + Issues #369, #372-#388, siehe Tabelle): Module 11-13 an einem Tag (2026-09-15), Module 14-16 am 2026-09-16 (Guests #386, External-Agent #387 inkl. 2 Backend-Fixes, Ownership-Transfer #388).

OFFENE THREADS (alles Weitere hängt hier, nichts geht verloren):

  1. Re-Audit ausstehend: Externer Prüfer prueft leocrm-reaudit.zip (Stand b58c96f, liegt beim User). Bei neuen Findings: erst die fixen. Hinweis: ZIP enthaelt NICHT die UI-Module 2-16 — bei Bedarf frischen ZIP erstellen (git archive HEAD).
  2. Traefik no-cache-Header fuer index.html (User-Angebot offen, prevents stale JS-Chunks nach Deploys). HINWEIS: Der "Dashboard loads forever"-Incident wurde 2026-09-14 aufgeklaert — es war der Zustands-Selector-Bug (#381), kein Caching-Problem. Der no-cache-Header bleibt trotzdem sinnvoll gegen stale Chunks nach Deploys.
  3. Server-Entlastung (User-Thema offen): Cron gegen alte Browser-Prozesse (Incident: 3 Zombie-Chromium, 500+ h CPU) und/oder VPS-Upgrade-Diskussion (22 Container auf 7,6 GB).
  4. Phase O UI-Overhaul: offen 1.2 Kontakte-Drag-Drop in Ordner, 1.3 MoveDialog.
  5. Phase P Notizen-App (P1-P5, user-abgestimmt, Roadmap-Details stehen).
  6. Vorbestands-Findings (nicht blockierend): entity_attachments-FK blockiert alembic check; Suite-Isolation (Combo-Runs "relation users does not exist", Solo gruen); Vitest-Worker-OOM.
  7. Marketplace ist leer: Keine Listings in der DB (API 200, listings=0). Demo-Listings koennen via Admin-API (MarketplaceListingCreate, marketplace:admin) angelegt werden — User fragen.

Offene Roadmap-Phasen (user-abgestimmt, startklar):

  • Phase R — Betriebssicherheit & 95%-Produktionsreife (R1-R6, user-abgestimmt 2026-09-16). NÄCHSTE PHASE, vor O/P. R1 Alerting gegen stille Ausfälle #390 (PRIORITY 1 — Evidenz: KI-Chat 4 Wochen still down #389), R2 Suite verlässlich #391, R3 Schema-Integrität #392, R4 E2E-Kernflows #393, R5 Backup-Restore-Drill #394, R6 Ops-Runbook #395. Milestone 15. 5 messbare Abnahmekriterien = die 95%-Definition (Details + DoD: Roadmap Phase R).
  • Phase M — MiniApp-Plattform & Dashboard-Builder (M1-M6). M1 ✓ (Universal-Registry, /api/v1/miniapps), M2 ✓ (persönliche Dashboards: Tabelle, CRUD, Seed, RLS), M3 ✓ (Dashboard-Builder: Edit-Modus, Drag&Drop, Palette, Tabs), M4 ✓ (System-Rückbau, Core = reiner Host), M5 ✓ (Plugin-MiniApps), M6 ✓ erledigt — PHASE M KOMPLETT (Windows-Host + AI-Agenten-Tool send_miniapp — siehe Phase-M6-Section).
  • Phase N — Workspace-Scopes (N1-N4). N1 ✓ (Scope-Registry via Contract), N2 ✓ (Dynamischer Scope-Editor), N3 ✓ (Backend-Filterung contacts/dms/mail/calendar + Frontend-Defaults), N4 ✓ erledigt — PHASE N KOMPLETT (7 weitere Module: Tasks nur-meine, Kommunikation-Räume, Wiki-Kategorien-Subtree, Reports-Vorlagen, Agents, Tags, Search-Entity-Types + Navigation Startseite/Menü-Reihenfolge + Dashboard-Schnittstelle — siehe Phase-N4-Section). Nächster Schritt: Phase O UI-Overhaul (offen: 1.2 Kontakte-Drag-Drop in Ordner, 1.3 MoveDialog) oder Phase P Notizen-App (P1-P5).
  • Phase O — UI-Overhaul (umbenannt von Doppel-L, Bug-Verifikation steht im Roadmap-Eintrag: 5/7 Bugs bereits erledigt, offen: 1.2 Kontakte-Drag-Drop in Ordner, 1.3 MoveDialog)

Vorbestands-Findings (nicht blockierend, dokumentiert):

  1. entity_attachments.dms_file_id → files (Core-FK auf DMS-Tabelle) blockiert alembic check
  2. Suite-Isolation: kombinierte Test-Runs quicken mit "relation users does not exist" (Solo-Runs grün)
  3. AppShell vitest worker OOM bei Solo/Combo-Runs

Modul-Bauplan (bewaehrtes Muster, Module 14-16 direkt anwendbar): Backend lesen (Routes/Schemas/Permissions) → api/<modul>.ts (TanStack-Hooks) oder bestehenden Client erweitern → pages/<Modul>.tsx → Registrierung (Plugin: manifest plugin.py page_routes+menu_items + generate_component_map.py + ICON_MAP-Icon; Core: routes/index.tsx + Settings.tsx; oeffentlich: statische Route ausserhalb ProtectedRoute) → i18n de/en (Python-Patch-Skript, JSON-Roundtrip pruefen) → Vitest → tsc → Build → Deploy (frontend-only wenn kein plugin.py; full bei plugin.py) → Live-Verifikation (API curl + echter-Login Playwright DOM-Check) → Forgejo-Issue (Label 5=task, danach schliessen) → PROGRESS.md + Roadmap-Zeile.

Session-Lektionen fuer Tests/Implementation (2026-09-15, wiederkehrende Stolperfallen):

  • Vitest: Mutation-Mocks mit vi.hoisted() definieren (Top-Level const = ReferenceError durch Hoisting)
  • TanStack Query v5 ruft mutationFn(variable, context) — Assertion auf mock.calls[0][0], nicht toHaveBeenCalledWith(...)
  • Query-Ergebnisse asynchron: await screen.findByTestId(...) statt synchronem getByTestId
  • window.confirm: Direkt-Zuweisung im beforeEach (window.confirm = () => true), spyOn nur in-Test
  • Hook-Mocks (useXxx: () => (...)) sind robuster als queryFn-Mocks — synchron, kein isLoading-Handling
  • text_editor verschluckt gelegentlich JSX-Kommentar-Schliessungen (*/ ohne }): vor tsc mit grep '{/*' pruefen
  • Frontend-Catch: der Client-Interceptor wirft ApiError mit .status auf Top-Level — NICHT err.response.status lesen
  • i18n: Block ggf. bereits vorhanden (ungenutzte Alt-Keys) — nur fehlende Keys mergen, nicht ueberschreiben
  • Oeffentliche Seiten (ohne Login): statische Route analog /guest/*, NIEMALS in die AppShell/ProtectedRoute

Wichtig: AGENTS.md-Regeln zuerst lesen (§0.0 Sub-Agents nur für einfache Jobs, §0.2 auf bestehendem Code aufbauen, §10 'PROGRESS.md als Source of Truth').

Letztes Update: 2026-09-15

Produktions-Bugfixes (2026-09-16)

Bug Issue Fix Verifikation (Live-Messung 2026-09-16)
Plugin ai_assistant seit Alembic 0137 (2026-08-21) migration_failed/inactive — KI-Chat und /api/v1/ai/* in Produktion down (403 plugin_inactive); Migration 0003 exec ALTER TABLE ai_chat_sessions crashte bei jedem Container-Start (Tabelle von 0137 gedroppt) #389 Migrationen 0001-0003 von Referenzen auf gedroppte ai_chat-Tabellen befreit (0003: nur ai_chat_folders behalten; 0001: Ghost-CREATEs entfernt; 0002: attachments+ALTER entfernt). Runner skipt getrackte Migrationen per Dateiname (kein Hash-Check) → kein Prod-Risiko (Commit 0383dd2) Prod-DB-Diagnose: nur ai_chat_folders existiert, 0001+0002 getrackt; nach Full Deploy: Plugin status=active, GET /api/v1/ai/agents → 200 mit echtem LeoCRM Assistant, KI-Chat wieder live
CSRF-Middleware verlangte Origin+X-CSRF-Token auch auf Bearer-authentifizierten API-Calls → External-Agent-API (/api/v1/external/agent/*) fuer externe Systeme unbrauchbar (403 ohne Origin/CSRF) (in #387 aufgegangen) Authorization: Bearer-Requests skippen die CSRF-Pruefung — Bearer ist CSRF-immun per Design (Browser haengen Authorization-Header nie automatisch an); Session-Requests unverändert voll geprueft (Commit b91ee5b) Live: Dummy-Bearer → 401 not_authenticated (Auth-Ebene erreicht statt 403 CSRF); Session-Request ohne CSRF bleibt 403 csrf_missing_token; pytest test_auth.py 11/11

Produktions-Bugfixes (2026-09-14)

Bug Issue Fix Verifikation (Live-Messung 2026-09-14)
Jeder Outbox-Event-Publish crashte im Webhook-Dispatcher mit Neither 'AnnotatedColumn' nor 'Comparator' object has an attribute 'any' → 158 failed Events (file.deleted, 2026-08-27) #380 Webhook.events ist JSONB-Column (KEINE Relationship): .any() an 2 Stellen (webhook_dispatcher.py, webhook_service.py) ersetzt durch cast(events, JSONB).contains([event]) (Commit 50d6733) pytest test_webhooks.py 6/6 (SQL: CAST(webhooks.events AS JSONB) @> ...); Full Deploy Health 200; Live: replay-all → 158 replayed, danach stats {published:158, failed:0} (vorher {failed:158})
Alle Core-Lazy-Routen im AppShell-Baum hingen ewig im Route-Suspense-Spinner ("Dashboard loads forever"-Incident) bei Direkt-Aufruf/Reload #381 Root Cause via Bisekt: useWorkspaceStore(s => s.moduleMenuOrder()) + s.visibleModuleKeys() erzeugten bei jedem getSnapshot NEUE Map/Set-Objekte → useSyncExternalStore-Render-Loop → Suspense-Commits landeten nie. Fix: stabile context-Referenz selektieren + useMemo-Ableitung (Commit 3fd0c69) Bisekt-Beweis: Min-AppShell rendert alles, +Sidebar → Hang; Import-Bisect: Chunk resolved aber kein Commit. Live PROD: /dashboard frischer Kontext h1='Dashboard', spinner=false (vorher hängender Spinner); /outbox echter Login: h1='Event Outbox', Published 158, Failed 0, 35 Registry-Karten, mainTextLen 1699
Consumer-Registry zeigte scheinbare Duplikate: on_contact_created 3x (drei Plugins mit gleichem Methodennamen ununterscheidbar) Commits 591ef06 + fccf009 _get_handler_name nutzt __qualname__ für bound methods (Plugin-Handler): Registry zeigt AutomationPlugin.on_contact_created vs UnifiedSearchPlugin.on_contact_created vs SystemNotifPlugin.on_contact_created; plain functions behalten __name__ pytest test_outbox_phase5 17/17; Full Deploy Health 200; Live: Registry 46 Handler, eindeutige Plugin-Namen, Duplikat-Check: nur _noop_handler (korrekt — 1 Placeholder-Fn für mehrere Events)

Produktions-Bugfixes (2026-08-27)

Bug Issue Fix Verifikation (Live-Messung 2026-08-27)
KI-Chat Stream failed: 403 (sessionStorage-Key leocrm_csrf_token wird nie geschrieben → Request ohne X-CSRF-Token) #351 streamChat nutzt getCsrfToken() aus dem gemeinsamen Client Vitest streamChat.test.ts 2/2 passed: X-CSRF-Token-Header bewiesen
Alle Mutationen (Wiki-Save etc.) 403 nach Seiten-Reload (/auth/me lieferte csrf_token nicht zurück → In-Memory-Token nach Reload weg) #351 /auth/me liefert csrf_token aus Session; useCurrentUser stellt ihn beim Bootstrap wieder her pytest test_auth.py 11/11 passed inkl. neuem Regressionstest test_me_returns_csrf_token_for_reload_restore

Gates: ruff exit=0 · tsc --noEmit exit=0 · pytest 11 passed · Vitest 2 passed

W4c — Custom-Fields-Routen in ContactsPlugin migriert (2026-08-28)

Verify-first: app/routes/custom_fields.py war 100% Contact-spezifisch (importiert Contact, nutzt contacts:read/write, Route /{contact_id}/custom-fields) — lag aber als scheinbar generischer Core-Service (Kritikpunkt 14).

Fix (c6decf5): Die komplette Logik (2 Endpoints GET/PATCH, _collect_custom_field_definitions, _merge_definitions_with_values, CustomFieldUpdateRequest) wandert in app/plugins/builtins/contacts/routes.py (gleicher Router-Prefix /api/v1/contacts, bereits via manifest.routes gemounted). app/routes/custom_fields.py gelöscht, main.py bereinigt. Der generische custom_field_definitions.py-Endpoint bleibt im Core.

Verifikation: tests/test_custom_fields.py 11/11 passed (Funktionserhalt) · create_app OK · ruff grün · Full Deploy SUCCESS · Health healthy

Phase L1-L3 — Dokumente-Generator Backend+Editor (2026-08-29)

Scope: Briefpapier (letterheads) + Druckvorlagen (print_templates) + Assets (document_assets) + Block-Registry + Contract-Beiträge + Drag&Drop-Editor + globaler Dokument-Dialog. Erweiterung des report_generator-Plugins (kein Neubau).

Umgesetzt:

  • Backend: documents.py (13 Endpoints), document_blocks.py (Registry: text/image/shape/table/spacer/divider/placeholder/pagebreak + Modul-Beiträge via document_blocks()-Contract), document_renderer.py (Blocks→HTML→PDF, WeasyPrint data:-URI-only SSRF-Policy, Briefpapier-@page-Frame mit running header/footer)
  • Contract-Beitrag contacts: document_placeholders(entity_type), document_data(db, tenant_id, entity_id, entity_type) (#359-Muster wie importexport_entities)
  • Migration: Plugin-SQL 0003 (idempotent) + Alembic 0143 (Dual-Path-Konvergenz, RLS fail-closed nach 0084-Muster)
  • Frontend: api/documents.ts + DocumentSettings-Page (Settings→Dokumente, eigener Menüpunkt via settings_pages) + BlockEditor (@dnd-kit: Palette/Canvas/Config-Panel/Live-Preview-iframe) + LetterheadEditor + PrintTemplateEditor + DocumentGenerationDialog (global für Module, integriert in ContactDetailPage)
  • i18n de/en vollständig

Verifiziert:

  • tests/test_documents_generator.py: 32/32 (CRUD, Tenant-Isolation, RBAC 403, Block-Validierung 422, Preview, Render-PDF %PDF, Assets, Contract-Unit)
  • Regression: test_report_generator.py + test_plugin_route_order.py 9/9
  • tsc --noEmit Exit 0; Production-Build OK (2.79s)
  • Alembic-Fresh-DB: 0001→0143 komplett, letterheads/print_templates/document_assets mit RLS+FORCE+crm_api-Policy bewiesen (Scratch-DB wieder gedroppt)
  • ruff check clean; check_migration_hashes 93/93 OK
  • ⚠️ Bekannt: Router-Reihenfolge im Manifest — documents-Router muss VOR routes stehen (/{report_id}-Catch-all)

Offen (Folgepakete):

  • L4: KI-Steuerung („Erstelle Rechnungsvorlage") via agent_loop
  • L5: E-Rechnung XRechnung/ZUGFeRD (benötigt Verkaufs-Modul)
  • Weitere Module können Blöcke/Platzhalter beisteuern (Contract-Muster dokumentiert in plugin-development-guide.md)

Phase L4-L5 — KI-Vorschlag + XRechnung-Format-Layer (2026-08-29)

User-Klärung: Verkaufsmodul kommt später — aber das XRechnung-FORMAT ist jetzt implementiert (reiner Format-Layer, kein Rechnungs-CRUD).

Umgesetzt:

  • L5 Format-Layer: einvoice.py — EN16931/XRechnung CII-XML-Generator (ElementTree, XML-Escaping gratis), Pflichtfeld-Validierung mit BT/BG-Codes (BT-1/2/3/5, BT-10, BT-27, BT-31/32, BG-25, BT-126/146), Decimal-kommerzielles Rounding, Header-Tax-Breakdown pro VAT-Satz, Profile en16931|xrechnung (Guideline urn:xoev-de:kosit:standard:xrechnung_3.0)
  • Endpoints: /einvoice/render (inline → XML), /einvoice/validate (422 mit Fehlliste), /einvoice/render-for (Contract-Resolver einvoice_data() — Andockpunkt Verkaufsmodul, ohne Beitrag 404 no_data_source)
  • L4 KI-Steuerung: /documents/suggest — natürliche Sprache → Block-Komposition via zentralem llm_complete (gpt-4o-mini, Cost-Tracking, Tenant-Budget), Registry-Sanitizing (ungültige KI-Blöcke gefiltert, IDs serverseitig), Code-Fence-Stripping, 502 ai_unavailable/invalid_ai_response
  • Frontend: KI-Vorschlag-Panel im PrintTemplateEditor (Sparkles, Prompt-Textarea, Vorschläge werden an Blöcke angehängt), i18n de/en

Verifiziert:

  • TDD: Rot 25 failed → Grün 25/25 (tests/test_einvoice_generator.py: Validierung 6 Unit, XML-Struktur 5 Unit inkl. Escaping/Profil/Summen, Contract-Resolution 2 mit Mock-Registry, API 6: 200-XML/422-BT-Codes/403/404, Suggest 6: Mock-LLM/Filter/Fence/502/403)
  • tsc exit 0 (useMutation-Typisierung SuggestResult,Error,SuggestInput), Production-Build BUILD_EXIT=0
  • ruff clean

Offen: Verkaufsmodul dockt später via einvoice_data() an — Contract + Doku (plugin-development-guide.md) fertig.

Phase M — MiniApp-Plattform & Dashboard-Builder (2026-08-29 geplant, user-abgestimmt)

User-Vision: Universelle MiniApps (Chat + Dashboard + Windows + AI-Agenten), Dashboard-Builder mit Edit-Modus/Drag&Drop/Resize/Tabs/pro-Widget-Settings, System-Dashboard-Teile zurück in Plugins (Core = reiner Host), Permission-Integration fail-closed.

Status: done — M1M6 alle erledigt (siehe Sections unten). Phase-Gate: alle Tasks implementiert, getestet (TDD), deployed und auf Produktion verifiziert (live curl-Beweise je Section).

Live-Bestand analysiert (2026-08-29): miniapp_registry (kommunikation, 92 Z.), MiniAppContribution (LÜCKE: kein permission-Feld), FrontendDashboardWidget (LÜCKE: kein settings_schema), MiniAppBlock.tsx (Chat-Host fertig), DashboardGrid + 4 Widgets, Dashboard.tsx (170 Z.) mit hardcodierten StatCards/ActivityFeed/System-Metrics (Rückbau-Bestand für M4), @dnd-kit vorhanden.

Phase M1 — Universal-MiniApp-Registry (2026-08-30)

Umgesetzt:

  • app/plugins/miniapp_registry.py (154 Z.): Registry in den Plugin-Layer gehoben (Plattform-Konzept). MiniAppDef erweitert um permission (fail-closed, leer = jeder), settings_schema, col_span/row_span, hosts (chat/dashboard/window), component, order, builtin.
  • Kompatibilitäts-Brücke: kommunikation/miniapp_registry.py re-exportiert die Universal-Registry — alle Bestands-Importer (kommunikation contracts, automation routes, tests) unverändert lauffähig.
  • Lifecycle: BasePlugin.on_activate registriert Manifest-Beiträge automatisch (miniapps + dashboard_widgets-Alias mit component/spans/permission — ein Contribution-Typ, #359-Philosophie); on_deactivate entfernt per unregister_plugin nur die eigenen Apps.
  • Manifest-Schema: MiniAppContribution + FrontendDashboardWidget um M1-Felder erweitert (settings_schema, hosts etc.).
  • API: GET /api/v1/miniapps (Server-seitig permission-gefiltert, ?host=), GET /api/v1/miniapps/{app_id} (403 fail-closed / 404).

Verifiziert (2026-08-30):

  • TDD: Rot 16 errors/failed → Grün 16/16 (tests/test_miniapp_registry.py: Registry-Unit 6, Bridge-Import 1, Manifest-Registrierung+Lifecycle 3, API 6 inkl. Viewer-Filter-Beweis + Host-Filter + 403/404)
  • Regression: test_contracts.py 23/23, plugin_lifecycle + route_order 4/4, create_app OK
  • ruff clean (M1-Dateien); 2 Ruff-Funde in automation/knowledge = per Stash bewiesener Vorbestand
  • Doku: api-documentation.md (2 Endpoints), plugin-development-guide.md (MiniApp-Beitrag-Muster)

Offen in Phase M: — (Phase M abgeschlossen).

UI-Backlog: Frontend-Backend-Gap (2026-09-08 laufend)

Kontext: Frontend-Backend-Gegenüberstellung (2026-09-01) ergab 16 Backend-Module ohne UI (~64 Ops) bei 76-84% Business-UI-Coverage. User-Entscheidung: Module einzeln mit UI ausstatten, priorisiert nach Business-Nutzen.

# Modul Ops Status Issue
1 Approvals (Freigaben) 6 Done #369
2 Delegations (Vertretungen) 5 done Commit 36771d4, #372: UI live — Vitest 9/9, tsc 0, Build 0, Frontend-Deploy, API 200 ({items:[],total:0} + active-check OK)
3 API-Tokens 3 done Commit 4bdc6c6, #373: Settings-Page /settings/api-tokens — Vitest 8/8, tsc 0, Build 0, Frontend-Deploy, live: API 200 (echter TestToken sichtbar) + SPA 200
4 Tenants (Mandanten) 4 done Commit 79ca1cb, #374: Settings-Page /settings/tenants — Vitest 8/8, tsc 0, Build 0, Frontend-Deploy, live: API 200 (Default Org sichtbar) + SPA 200
5 Marketplace (Plugin-Markt) 5 done Commit 289dfc8, #375: ERSTES Modul via Phase-Q-Manifest-Architektur — Vitest 10/10, tsc 0, Build 0, Full Deploy, live: Manifest page_route+menu_item OK, API 200, SPA 200
6 Permission-Templates (Berechtigungs-Vorlagen) 5 done Commit 33b4b52, #376: Settings-Page /settings/permission-templates — Vitest 10/10, tsc 0, Build 0, Frontend-Deploy, live: API 200 + SPA 200
7 Skills (AI-Skill-Definitionen) 5 done Commit 3f8d1bd, #377: /skills via automation-Manifest (Phase Q) — Vitest 10/10, tsc 0, Build 0, Full Deploy, live: Manifest page_route+menu_item OK, API 200, SPA 200
8 Agent-Memory 5 done Commit 24423b6, #378: /agent-memory via agent_memory-Manifest (Phase Q) + ICON_MAP-Fix (Brain/Store/Tags) — Vitest 11/11, tsc 0, Build 0, Full Deploy, live: Manifest OK, API 422 ohne agent_id (Pflichtfeld bewiesen), SPA 200
9 Outbox (Event-Verwaltung) 7 done Commit 31154b9, #379: Core-Route /outbox + Sidebar order 93 mit Admin-Filter — Vitest 11/11, tsc 0, Build 0, Frontend-Deploy, live: stats/failed/consumer-registry API 200 (158 echte failed events file.deleted 2026-08-27, 20+ Handler), Nav-Link gerendert, Chunk MD5-identisch
10 Policies (ABAC-Richtlinien) 4 done Commit d734923, #382: Settings-Page /settings/policies mit Entity-Typ-Tabs (8), Conditions-Builder (AND/OR, Whitelist-Felder, 12 Ops), Principal-Picker (User/Group/Role) — Vitest 12/12, tsc 0, Build 0, Frontend-Deploy, live: API 200 (items=[]) + echter-Login DOM-Check (Page gerendert, alle Tabs, kein Spinner)
11 Graph-RAG Traversal 4 done Commit 0404c8f, #383: /graph-rag via graph_rag-Manifest (Phase Q, Share2-ICON_MAP, Komponenten-Map 41) + knowledge.ts-Erweiterung (traverse/create/delete) — Vitest 11/11, tsc 0, Build 0, Full Deploy, live: Manifest page_route+menu_item OK, API 200, echter-Login DOM-Check (Page gerendert, kein Spinner)
12 Companies (Firmen-API) 9 done Commit 06b7284, #384: /companies via contacts-Manifest (Phase Q, Building2-ICON_MAP, Komponenten-Map 42) — Vitest 12/12, tsc 0, Build 0, Full Deploy, live: Manifest OK, API 200 (echte Firmendaten), echter-Login DOM-Check (3 Firmenkarten, Export-Buttons, kein Spinner)
13 Public-Share 3 done Commits 00f8f10 + 2fbffcd, #385: Oeffentliche Seite /share/:token (Passwort-Support, Download, 404/410-Zustaende) + ShareDialog kopiert jetzt SPA-Links statt API-JSON — Vitest 9/9, tsc 0, Frontend-Deploy, live ohne Login: Fehlerseite 'Link not found' gerendert, kein Login-Redirect
14 Guests 3 done Commit b3eaa0e, #386: Settings-Page /settings/guests mit Admin-Gate (Outbox-Muster), Gästeliste mit Status-Badges (invited/active/disabled), Invite-Modal (RHF+zod), Revoke-ConfirmDialog — Vitest 8/8, tsc 0, Frontend-Deploy, live: API GET /api/v1/guests 200 [], echter-Login DOM-Check (Page, EmptyState, Invite-Button, Nav-Eintrag gerendert)
15 External-Agent 3 done Commit e8e07fa, #387: Settings-Page /settings/external-agents via ai_assistant-Manifest (Phase Q, permission ai:read, Komponenten-Map 43) — Agentenliste mit curl-Snippets (run/status/stream), Copy-Buttons, Bearer/Rate-Limit-Hinweis, Token-Link — Vitest 6/6, tsc 0, Full Deploy, live: echte Agent Card (LeoCRM Assistant), Snippets+Copy-Buttons, Nav-Eintrag; VORAUSSETZUNG waren 2 Backend-Fixes: ai_assistant-Reaktivierung (Commit 0383dd2, #389) + CSRF-Bearer-Skip (Commit b91ee5b)
16 Ownership-Transfer 1 done Commit e8e07fa, #388: Settings-Page /settings/ownership (statische Core-Route) mit Admin-Gate, From/To-User-Selects, 10 Entity-Type-Chips, ConfirmDialog, Ergebnis-Tabelle — Vitest 6/6, tsc 0, Full Deploy, live: Formular/Chips/Submit/Nav gerendert, API 422 mit korrekten Pydantic-Fehlern (Admin-Route erreichbar)

Modul 1 — Approvals (2026-09-08) : Review-Queue (Status-Tabs Offen/Alle/Genehmigt/Abgelehnt/Abgelaufen), Approve/Reject mit Kommentar-Modal, Permission-Gating (approvals:approve), Metadata-Anzeige. Phantom-Permission-Bug gefixt (approvals:read/write/approve fehlten in CORE_PERMISSIONS — Rollen konnten sie nie erhalten, M2-Fehlerklasse). Vitest 10/10, RBAC 102/102, live: /approvals 200, Prod-Bundle enthält UI. Deploy: ecc7a24 (Full).

Phase N4 — Restliche Module (2026-09-01) — PHASE N KOMPLETT

Spec: #368 | Roadmap: Phase N, N4 (letzter Task) | Milestone: Phase N — Workspace-Scopes (#14)

Umgesetzt:

  • Scope-Deklarationen (7 Plugins): tasks only_mine-Toggle („nur meine"), kommunikation conversation_ids (Räume), wiki category_ids (Subtree — NEUE contracts.py, wiki hatte zuvor keinen Contract), report_generator template_ids (Vorlagen), automation agent_ids (module_key agents — page route ohne Menüeintrag), tags tag_ids (Root-Array), unified_search entity_types DYNAMISCH aus Provider-Registry (13 Entity-Types, Live-Set + deterministischer Klassen-Fallback).
  • Core-Beiträge (Aggregator): navigation default_route (Startseite pro Workspace, Optionen aus CORE_PERMISSIONS + bekannten Frontend-Routen) + dashboard widget_app_ids (begrenzt das Widget-TYP-Angebot — workspace_widgets-Boundary; persönliches Layout bleibt Phase M).
  • Backend-Filter (additive UND, kein Umbau): GET /tasks (assigned_to OR created_by), GET /comm/conversations (Subset), GET /wiki/articles + /categories (expand_folder_scope-Subtree), GET /reports/print-templates (Subset), GET /agents (Subset), GET /tags (Subset), GET+POST /search (apply_entity_type_scope: requested ∧ scope), GET /miniapps?host=dashboard (widget_app_ids begrenzt NUR Dashboard-Angebot, chat/window unberührt).
  • Frontend-Navigation: WorkspaceSwitcher navigiert nach default_route beim Wechsel (Validierung: muss mit / beginnen); Sidebar sortiert nach workspace menu_order als Admin-Default (persönliche savedOrder bleibt Override); workspaceStore moduleMenuOrder()-Helper.

Verifiziert (2026-09-01):

  • TDD: Deklarationen 18/18 (rot: 18 failed → Implementation → grün), Filter 11/11 (rot: 8 failed + 1 error → grün; inkl. Dashboard-Boundary: scoped {w1} vs. unscoped Superset, chat unberührt)
  • Frontend: Vitest Switcher-Navigation 2/2, Store 18/18 (moduleMenuOrder +2), tsc clean, Build OK
  • Kombi-Regression (N1+N3+N4-Dateien): 64 passed / 4 failed — alle 4 per Solo-Lauf als Suite-Isolation bewiesen (N1 solo 18/18, N3-Test solo grün — bekannter Vorbestand, unterschiedliche Plugin-Fixtures in einem Prozess)
  • Cross-Plugin-Checker: 0 Verstöße; Ruff: 7 Fehler = exakt Vorbestand (Stash-Beweis: clean HEAD identisch 7)

Phase N Gesamtbilanz: Workspace-Scopes komplett — Registry via Contract (N1), dynamischer Editor (N2), Backend-Filterung für alle 11 Module (N3: contacts/dms/mail/calendar + N4: tasks/communication/wiki/reports/agents/tags/search) + Navigation (Startseite, Menü-Reihenfolge) + Dashboard-Schnittstelle (widget_app_ids). Security-Invariante durchgehend: Scope = reine UND-Einschränkung, Exemptions nur System-Admin + configure_modules-Inhaber (Editor-Deadlock). Issues #365-#368 alle geschlossen.

Phase N3 — Erste vier Module integrieren (2026-09-01)

Spec: #367 | Roadmap: Phase N, N3 | Milestone: Phase N — Workspace-Scopes (#14)

Umgesetzt:

  • Core-Resolver resolve_workspace_scope() (workspace_scope_service.py): X-Workspace-ID → Workspace aktiv/Tenant → User-Zuweisung → Modul-config; leere Dimensionswerte fallen weg. Exemptions: System-Admins + workspaces:configure_modules-Inhaber — löst den Editor-Deadlock (N2-Scope-Editor lädt Wertoptionen über dieselben Endpoints).
  • FastAPI-Dependency require_workspace_scope(module_key) (deps.py) — Header-Parsing gekapselt, einzeilige Nutzung pro Route.
  • Ordner-Subtree expand_folder_scope(): self + descendants (zyklensicher) für ContactFolder + DMS Folder — Ordner-Scopes gelten inkl. Unterordnern. scope_uuid_set(): fail-closed (garbage UUIDs → leere Menge).
  • Listen-Filter (additive UND-Einschränkung, kein Umbau): contacts (folder_ids-Subtree + contact_types auf GET /contacts; List-Cache bei aktivem Scope deaktiviert — Cross-Workspace-Leak-Gefahr beseitigt), dms (folder_ids-Subtree + file_types auf GET /files, Baum-Reduktion auf GET /folders; semantische Typ-Matcher pdf/image/spreadsheet/word/other), mail (account_ids auf GET /mails, /threads, /accounts-Picker), calendar (calendar_ids auf GET /calendar/entries + /calendars-Picker).
  • Frontend-Defaults: getModuleConfig(moduleKey) im workspaceStore; ContactsList wendet default_saved_view_id beim Mount an (admin-definierte Standard-Ansicht), Calendar setzt default_view (day/week/month/range) bei Workspace-Wechsel.

Verifiziert (2026-09-01):

  • TDD: Rot (7 ImportError + 14 Fixture-Errors) → Grün 21/21 (Resolver 7, Contacts 5 mit Cache-Bypass-Beweis + scharfem AND-Beweis (Beta=Person im Ordner-Scope fällt raus), DMS 3, Mail 3, Calendar 3 inkl. Admin-Bypass)
  • Regression: N1 + N2 + Workspaces + test_mail 81 passed
  • Cross-Plugin-Checker: 0 Verstöße; Ruff: nur per Stash bewiesener Vorbestand (N806/UP017)
  • Frontend: tsc clean, Vitest (workspaceStore 16/16, CalendarPage, ContactsList) grün, Production-Build OK

Offen in Phase N: N4 restliche Module (Tasks „nur meine", Kommunikation-Räume, Wiki-Kategorien, Reports-Vorlagen, Automation-Agenten, Tags, Search-Provider, Navigation-Defaults) + Dashboard-Schnittstelle (workspace_widgets begrenzt Widget-TYP-Angebot).

Phase N2 — Dynamischer Scope-Editor (2026-09-01)

Spec: #366 | Roadmap: Phase N, N2 | Milestone: Phase N — Workspace-Scopes (#14)

Umgesetzt:

  • WorkspaceScopeEditor.tsx (neu): generisches Filter-UI aus /scope-definitions — multiselect (statische Options ODER value_source-Fetch), select (mit 'Keine Einschränkung'-Placeholder), toggle. WidgetSettingsForm-Philosophie (M3): die Komponente kennt keine spezifischen Module, Plugins deklarieren via Contracts.
  • resolveScopeItems (api/hooks/workspaces.ts): Wertequellen-Auflösung für alle N1-Formate — items-Wrapper (contact-folders), Root-Listen (mail/accounts, calendars, saved-views), DMS-Ordner-Baum (children-Flattening). Nie-crashend: defekte Responses → leere Liste.
  • Hooks: useWorkspaceScopeDefinitions (queryKey workspace-scope-definitions) + useScopeValues (endpoint-spezifisch, staleTime 60s).
  • WorkspaceManager: JSON-Textarea-Editor ENTFERNT — dynamischer Scope-Editor inline pro sichtbarem Modul; Speicherung unverändert über POST /{id}/modules in workspace_modules.config.
  • i18n: workspaces.scopeEditor.* 5 Keys (de/en) — hint (Security-Invariante im UI), noRestriction, noDimensions, noValues, loadError.

Verifiziert (2026-09-01):

  • TDD: Rot 4 failed → Grün 21/21 (WorkspaceScopeEditor 17: resolveScopeItems-Unit 4, Rendering 7, onChange 6; WorkspaceManager-Integration 4: Textarea weg + Scope-Fields da, Config-Roundtrip checked, Save-Payload config korrekt, No-Dimensions-Hinweis nach Toggle)
  • npx tsc --noEmit: clean (0 Errors)
  • Production-Build: OK (vite build, nur Chunk-Size-Warnung Vorbestand)
  • Frontend-only-Deploy + Bundle live verifiziert

Offen in Phase N: N3 Backend-Listen-Filterung via X-Workspace-ID (additive UND-Einschränkung: contacts Ordner/Typen/View, dms Ordner/Typen, mail Postfächer, calendar Kalender/View), N4 restliche Module.

Phase N1 — Scope-Registry via Contract (2026-08-31)

Spec: #365 | Roadmap: Phase N, N1 | Milestone: Phase N — Workspace-Scopes (#14)

Umgesetzt:

  • Contract-Hook workspace_scopes() (document_placeholders-Muster, #359-Philosophie): Plugins deklarieren Scope-Dimensionen ihres Moduls inkl. Wertequellen; der generische Editor bleibt modul-agnostisch.
  • Deklarationen (4 N3-Module): contacts (folder_ids via /api/v1/contact-folders, contact_types Firmen/Personen, default_saved_view_id via /api/v1/saved-views?entity_type=contact), dms (folder_ids via /api/v1/dms/folders, file_types PDF/Bilder/Tabellen/Dokumente/Sonstige), mail (account_ids via /api/v1/mail/accounts), calendar (calendar_ids via /api/v1/calendars, default_view Tag/Woche/Monat/Zeitraum — Frontend-Ansichten live abgeglichen).
  • Pydantic fail-closed (app/schemas/workspace.py): ScopeOption, ScopeValueSource (nur interne /api/v1/-Pfade — SSRF-sicher per Konstruktion; Validator), WorkspaceScopeDimension (multiselect/select ohne options UND value_source → ValidationError), WorkspaceModuleScopes (module_key + min. 1 Dimension).
  • Aggregator (app/services/workspace_scope_service.py): iteriert discovered Plugins, lazy-loadet Contracts, ARCH-014-safe (deaktivierte bleiben weg), Crash-sicher pro Plugin, ungültige Deklarationen verworfen (Warning-Log).
  • Endpoint GET /api/v1/workspaces/scope-definitions (workspaces:configure_modules — Admin-Kontext) — VOR /{workspace_id} registriert (Route-Order-Falle, test_plugin_route_order-Klasse).

Verifiziert (2026-08-31):

  • TDD: Rot (ImportError) → Grün 18/18 (tests/test_workspace_scopes.py: Pydantic-Unit 4, Contract-Deklarationen 8, Aggregator fail-closed 1, HTTP-Endpoint 2 (Admin bekommt alle 4 Module, Viewer-403), Route-Order 1, Value-Endpoint-Existenz via OpenAPI 1 (431 Pfade, app.routes enthält nur _IncludedRouter-Wrapper — isinstance-Scan versagt, OpenAPI kanonisch), /context-config-Regression 1)
  • Regression: test_workspaces.py 17/17
  • Cross-Plugin-Checker: 0 Verstöße (495 Dateien)
  • Ruff clean (alle 8 geänderten Dateien; UP037-Quote-Fix)

Offen in Phase N: N2 Dynamischer Scope-Editor (WorkspaceManager rendert Filter-UI aus /scope-definitions, Speicherung in workspace_modules.config), N3 Listen-Filterung via X-Workspace-ID (additive UND-Einschränkung), N4 restliche Module.

Phase M6 — Weitere Hosts (2026-08-30) — PHASE M KOMPLETT

Spec: #364 | Roadmap: Phase M, M6 (letzter Task)

Umgesetzt:

  • Windows-Host: openMiniAppWindow-Helper + MiniAppWindowContent (windowStore, schwebende/verschiebbare Fenster, kompakte Default-Größe 520×480). Öffnen-Buttons: Chat-Block (MiniAppBlock, ExternalLink-Icon) und Dashboard-Widget (DashboardBuilder, auch View-Modus).
  • AI-Agenten-Host: Core-Tool send_miniapp (app/ai/miniapp_tools.py) — Agent bettet MiniApp als interaktiven Ausgabe-Block (block_type miniapp, approval_request-Präzedenz) in seinen Chat-Raum ein. Permission fail-closed gegen den aufrufenden User pro App (resolve_permissions + check_permission); Registrierung im lifespan-Startup.
  • agent_loop: tool_context um agent_name erweitert (Raum-Auflösung "Agent: {name}").
  • Fix: MiniAppBlock nutzt jetzt useMiniapps (universelle Registry MIT component-Feld) statt Legacy /comm/miniapps — der Fenster-Button erscheint damit erstmals zuverlässig.
  • Fix (Vorbestand, live gemessen): /api/v1/agents/tools rief registry.list_tools() auf (Methode existiert nicht → 500) — auf list_for_api() mit korrektem Feld-Mapping umgestellt + Regressionstest gesichert.

Verifiziert (2026-08-30):

  • TDD: Rot 7 failed → Grün 8/8 (tests/test_m6_miniapp_hosts.py: Tool-Registrierung 1, Handler 5 (unknown/nie-posten/Permission-deny/Block-Posting mit exakter block_data/no-room-degradation), agent_name-Kontext 1, list_for_api-Regression 1)
  • Backend-Regression: M6 + M5 + Phase-F-Agenten 57/57
  • Frontend: Vitest 26/26 (4 neue Window-Tests: MiniAppWindowContent-Rendering + openMiniAppWindow-Store-Integration, Typ/Title); npx tsc --noEmit clean; Production-Build OK (2.74s)
  • Deploy (335762d + 04e9279, Full): Health healthy, Alembic 0144, RLS 113 Tabellen
  • Produktions-Verifikation (curl): /api/v1/agents/tools listet send_miniapp live (18 Tools total, plugin system) — Vorbestands-500 gefixt; neuer Frontend-Bundle live

Phase M Gesamtbilanz: MiniApp-Plattform komplett — universelle Registry (M1), persönliche Dashboards mit RLS (M2), Builder mit Drag&Drop/Tabs/Settings (M3), Core als reiner Host (M4), 8 Plugins + 2 System-Apps liefern Widgets (M5), Chat + Dashboard + Fenster + AI-Agenten als Hosts (M6). 17 Apps, 11 renderbar, in Produktion live.

Phase M5 — Plugin-MiniApps (2026-08-30)

Spec: #363 | Roadmap: Phase M, M5

Umgesetzt:

  • 5 Manifest-Beiträge (MiniAppContribution, gleiche Philosophie wie contacts_stats):
    • dms: dms_folders (dms:read, Ordner mit Dateizählern, Settings max_items)
    • mail: mail_unread (mail:read, ungelesene Mails je Ordner, Settings max_items)
    • wiki: wiki_recent (wiki:read, zuletzt aktualisierte Artikel, Settings max_items)
    • graph_rag: graph_overview (graph:read, Beziehungsübersicht, Settings max_items)
    • automation: automation_status (automation:read, aktive/inaktive Automationen, Settings max_items)
  • 5 Frontend-Widgets auf bestehenden API-Clients (keine neuen Backend-Endpoints, §0.2): DmsFoldersWidget (fetchFolders), MailUnreadWidget (fetchAccounts+fetchFolders/unread_count), WikiRecentWidget (fetchWikiArticles), GraphOverviewWidget (fetchGraphRelationships), AutomationStatusWidget (useAutomations). MiniAppHost-Registry +5.
  • Bug gefunden & gefixt (live gemessen): automation/plugin.py on_activate re-registrierte Manifest-MiniApps in einem Legacy-Block OHNE component/permission — überschrieb die korrekte M1-Registrierung aus super().on_activate(). Legacy-Block entfernt + Regressionstest gesichert (test_automation_legacy_reregistration_removed).
  • ruff: wiki I001 Import-Sortierung gefixt.

Verifiziert (2026-08-30):

  • TDD: Rot 7 failed → Grün 8/8 (tests/test_m5_plugin_miniapps.py: Manifest-Felder 5, Lifecycle-component-Beweis 1, settings_schema 1, Legacy-Regressionstest 1)
  • Backend-Regression: M5 + Registry + M4 + M2 53/53; nach automation-Fix: M5 + lifecycle + registry 26/26
  • Frontend: Vitest 22/22; npx tsc --noEmit clean; Production-Build OK
  • Deploy (7ed5349 + cd34bab, Full): Health healthy, Alembic 0144, RLS 113 Tabellen
  • Produktions-Verifikation (curl): /api/v1/miniapps?host=dashboard liefert 17 Apps, 11 renderable — alle 5 neuen Apps live mit component und Permission (automation_status nach Fix: comp=YES, perm=automation:read)

Phase M4 — System-Rückbau (2026-08-30)

Spec: #362 | Roadmap: Phase M, M4

Umgesetzt:

  • app/core/system_miniapps.py: Core-eigene MiniApps — audit_activity (Aktivitäten, audit:read, settings_schema max_items 1-50, Standard 10) + system_metrics (DB/Redis/Worker/API, settings:read; Endpoint /system/dashboard bleibt require_admin, Widget zeigt ohne Admin-Rechte kompakten Hinweis). Registrierung im lifespan-Startup (main.py), unabhängig von Plugin-Aktivierung.
  • base.py-Fix (M1-Lücke): native Manifest-MiniApps reichen jetzt component an die Registry durch (vorher nur der dashboard_widgets-Alias — Ursache, warum Chat-Apps kein component hatten).
  • contacts-Manifest: contacts_stats als native MiniApp (ContactsStatsWidget, contacts:read, settings: show_companies/show_persons) — Nachfolger der StatCards.
  • Seed-Fix (routes/dashboards.py): Dashboard-Seed platziert nur renderbare Apps (component vorhanden) — Chat-Interaktions-Apps ohne Frontend-Component bleiben aus Layouts raus (Produktions-Messung M2: 9 Widgets, nur 3 renderbar → jetzt gefiltert).
  • Frontend-Widgets: ContactsStatsWidget (Firmen-/Personen-Zähler), AuditActivityWidget (ActivityFeed-Nachfolger, max_items), SystemMetricsWidget (DB/Redis/Worker/API-Karten) — alle mit WidgetComponentProps (settings). MiniAppHost-Registry +3.
  • Dashboard.tsx ist reiner Host (26 Z.): keine hardcodierten Inhalte mehr — StatCards/ActivityFeed/SystemMetrics existieren ausschließlich als persönliche MiniApp-Instanzen.
  • i18n: systemMetricsNoAccess (de/en). M2-Seed-Tests auf renderbare Apps umgestellt (neue Seed-Spezifikation).

Verifiziert (2026-08-30):

  • TDD: Rot 7 errors → Grün 7/7 (tests/test_m4_system_miniapps.py: System-App-Definitionen 4, API-Permission-Filter 2, Seed-component-Filter 1)
  • Backend-Regression: M4 + M2 (angepasst) + MiniApp-Registry 46/46 — base.py-Fix und Seed-Änderung brechen keine Bestandstests
  • Frontend: Vitest 22/22 (Builder 13, Page-Pure-Host 4 neu geschrieben, i18n 5); npx tsc --noEmit clean; Production-Build OK
  • Deploy (3c496f4, Full): Health healthy, Alembic 0144, RLS 113 Tabellen
  • Produktions-Verifikation (curl): /api/v1/miniapps?host=dashboard liefert 12 Apps, 6 renderable (vorher 3) — contacts_stats/audit_activity/system_metrics live mit component; Chat-Apps korrekt comp=NONE

Phase M3 — Dashboard-Builder-Frontend (2026-08-30)

Spec: #361 | Roadmap: Phase M, M3

Umgesetzt:

  • api/miniapps.ts + api/dashboards.ts: TanStack-Query-Hooks (M2-Backend + M1-Registry, Query-Keys + Invalidation nach documents.ts-Muster); renderableDashboardApps() filtert Apps ohne component (6 der 9 Bestands-Apps sind Chat-Interaktions-Apps ohne Frontend-Component).
  • MiniAppHost.tsx: ersetzt DashboardWidgetLoader (P2-F17-Erbe) — Lazy-Component-Registry + settings-Props an Widget-Komponenten; Apps ohne Component renderen render_schema-Karte (MiniAppBlock-Präzedenz).
  • DashboardBuilder.tsx (605 Z.): View/Edit-Modus-Schalter; @dnd-kit-Sortable-Grid (rectSortingStrategy) mit 12-Spalten-Flow-Repositionierung (identisch zum Server-Seed); Palette (permission-gefiltert via /api/v1/miniapps?host=dashboard, nur renderbare Apps); Resize (col/row ±, geclamped 1-12); Tab-Verwaltung (Add/Remove/Rename, min 1); Dashboard-CRUD + Set-Default; Dirty-Check (Save disabled bei unverändertem Layout); Settings-Modal pro Widget.
  • WidgetSettingsForm.tsx: generisches Form aus settings_schema (text/number/boolean/select, MiniAppField-Typ).
  • Bestands-Widgets auf settings-Props umgestellt (RecentContacts nutzt settings.limit, geclamped 1-50; Tasks/Calendar kompatibel optional).
  • Dashboard.tsx: Builder als Hauptinhalt (reiner Host-Pattern); StatCards/SystemMetrics/ActivityFeed bleiben sichtbar bis M4-Rückbau (kein Funktionsverlust).
  • Legacy DashboardGrid.tsx + DashboardWidgetLoader.tsx gelöscht; Geister-Test ersetzt (§10: UI-Änderung → Test-Nachzug).
  • i18n: dashboard.builder.* 22 Keys (de+en).

Verifiziert (2026-08-30):

  • Vitest: DashboardBuilder-Tests 13/13 (Render, Tabs, View/Edit-Schalter, Palette-Add, Save-Flow-Koordinaten-Beweis {col:3,row:0}, Dirty-Disabled, Resize→col_span 3, Tab-Add/Remove, Settings-Modal mit Schema-Feld, Dashboard-Wechsel, Create-Modal, Empty-State) — TDD-äquivalent: 2 anfängliche Test-Bugs (multiple elements) gefixt, dann grün
  • Page-Regression: dashboard/Dashboard.test.tsx 11/11 (Builder-Mocks ergänzt); i18n-Test grün (de/en)
  • npx tsc --noEmit clean; Production-Build OK (2.98s)
  • ⚠️ Sidebar-Test-Run: Worker-OOM (0 Tests ausgeführt) = PROGRESS.md Finding #3 Vorbestand (identisch bei AppShell-Runs vor M3)
  • Frontend-Deploy (26948fd, ~20s): neuer Bundle live (index-BGuPWW7I.js), Health healthy, Login 200; /api/v1/miniapps?host=dashboard liefert 3 renderbare Apps von 9 (component-Filter greift: 6 Chat-Interaktions-Apps ohne Frontend-Component)

Phase M2 — Dashboard-Backend (2026-08-30)

Spec: #360 | Roadmap: Phase M, M2

Umgesetzt:

  • app/models/dashboard.py: dashboards-Tabelle (persönlich, saved_views-Präzedenz: user_id NOT NULL CASCADE, TenantMixin, kein OwnedMixin). Layout JSONB, is_default, partial unique index (tenant, user, name) WHERE deleted_at IS NULL — soft-deleted Boards geben Namen frei (Verbesserung ggü. saved_views-Wart).
  • app/schemas/dashboard.py: DashboardLayout/Tab/Widget (12-Spalten-Grid: col/row ≥ 0, Spans 1-12) → 422 auf invalide Layouts, bevor persistiert wird.
  • app/routes/dashboards.py (313 Z.): 6 Endpoints — GET (Liste + lazy Seed), POST (409 dup, erstes = default, ein leerer Start-Tab), GET/{id}, PUT/{id} (Name/Layout, db.refresh gegen MissingGreenlet), DELETE/{id} (Soft-Delete, Default-Promotion), POST/{id}/set-default (exakt ein Default). Owner-only (tenant + user_id Filter, fremde = 404), Audit-Log bei allen Mutationen.
  • Lazy Default-Seed: erste GET-Abrufung erzeugt „Mein Dashboard“ aus MiniApp-Registry (permission-gefiltert via geteiltem user_permits, Registry-Order, 12-Spalten-Flow mit Wrap).
  • user_permits() in miniapp_registry.py als geteilter Fail-Closed-Filter (miniapps.py behält _user_permits-Alias).
  • CORE_PERMISSIONS: dashboard:read/dashboard:writefixt Phantom-Permission (app/routes/dashboard.py verlangte dashboard:read, nirgends registriert → Nicht-Admins konnten sie nie erhalten).
  • Migration 0144_personal_dashboards.py: dashboards-Tabelle + RLS im 0090-Muster (crm_api + crm_worker) + konvergenter Fix der 3 Phase-L-Policies (letterheads/print_templates/document_assets waren TO crm_api-only — live auf Produktion gemessen, s. Verifikation). Plugin-SQL 0003 ebenfalls auf beide Rollen korrigiert.
  • Doku: api-documentation.md (neue Core-Section dashboards, 6 Endpoints).

Verifiziert (2026-08-30):

  • TDD: Rot 21 failed/1 passed → Grün 23/23 (tests/test_dashboards_backend.py: Model/Permission-Unit 3, Layout-Validation 5, CRUD 9, Ownership/Isolation 4, RLS-Konvergenz 2)
  • Live-Messung (psql): Produktion vor Fix — 3 Policies {crm_api}-only (letterheads, print_templates, document_assets); lokal nach 0144 — alle 4 Tabellen {crm_api,crm_worker}
  • Regression: rls_coverage + miniapp_registry + dashboard + lifecycle + route_order 37/38 — 1 Failure (test_dashboard cross-tenant, POST /companies 405) = per Stash bewiesener Vorbestand (identischer Failure auf clean HEAD); solo 5/5 grün
  • Regression Welle 2: rbac_comprehensive + arch_block_a 125/125
  • ruff clean (alle M2-Dateien inkl. Testdatei); create_app OK (85 Router-Routen)
  • Deploy (b3e259f, Full-Deploy): Health healthy, Alembic 0144, RLS 113 Tabellen
  • Produktions-Verifikation (psql + curl, 2026-08-30): alle 4 Policies {crm_api,crm_worker}, 0 fehlende Rollen; GET /api/v1/dashboards liefert Lazy-Seed („Mein Dashboard", default, 1 Tab, 9 Widgets aus Registry)

Phase N — Workspace-Scopes (2026-08-30 geplant, user-abgestimmt)

User-Vision: Workspaces als voll anpassbare Arbeitskontexte — jedes Modul pro Workspace auf Teilmengen einschränkbar (z.B. nur Kontakt-Ordner X+Y, nur DMS-Ordner "Angebote", nur Mail-Postfach vertrieb@, nur Kalender "Vertrieb"). Admin-definiert, für zugewiesene User-Gruppen.

WICHTIG — Klarstellung Workspace ≠ Dashboard (user-korrigiert): Zwei getrennte Systeme. Workspace = Admin-Kontext (WAS ist sichtbar/verfügbar, Gruppen-Feature). Dashboard = persönlich (WIE ICH mein Dashboard baue, Phase M). workspace_widgets bleibt Workspace-Eigentum (verfügbare Widget-TYPEN), dashboards-Tabelle (Phase M2) bleibt User-Eigentum (persönliches Layout). Kein Überbau, keine Vermischung.

Status: not_started — Phase N (N1-N4) in PLATFORM_ROADMAP.md verankert. 0 Umbau nötig: Speicher (workspace_modules.config JSONB), Transport (X-Workspace-ID-Interceptor), Context-Endpoint und Sidebar-Consumer existieren bereits; N3/N4 = additive Scope-Anwendung in Modul-Listen (kein Refactoring).

Security-Invariante: Scope = reine UND-Einschränkung (Workspace-Scope ∧ RLS ∧ ABAC ∧ Permissions). Workspace kann NIE mehr sichtbar machen, nur weniger. Ohne Workspace = kein Filter (rückwärtskompatibel).

Phase P — Notizen-App (Notion-artig, ersetzt Wiki) (2026-08-30 geplant, user-abgestimmt)

User-Entscheidung: Wiki wird komplett ersetzt durch Notion-artige Notizen-/Firmen-Wissen-App. Keine Legacy-App, keine Notion-Datenbanken erstmal — MiniApp-Blöcke stattdessen. Quer-Verweise + vollständige Such-Indexierung Pflicht. Edit-Konzept: Live-Inline-Editing wie Notion (kein Mode-Toggle, Auto-Save), Lese-Modus entsteht über Permissions + optional Page-Lock.

Status: not_started — Phase P (P1-P5) in PLATFORM_ROADMAP.md verankert. P1-P3+P5 unabhängig startbar; P4 braucht M1 (MiniApp-Registry).

W3b — Settings Contribution-Wahrheit (2026-08-28)

Verify-first (Live-Messung): 7 Plugins liefern settings_pages via Manifest (mail, ai_assistant, ai_proactive, automation, permissions ×3, system_notif) — die hardcoded Items in Settings.tsx für mail/ai/notifications waren identische Duplikate.

Fix (b1a7551): hardcodedNavItems auf 7 echte Core-Settings reduziert (stammdaten, user-management, system, custom-fields, webhooks, workspaces, backup) — Plugin-Settings kommen ausschließlich via pluginNavItems. Path-Dedup bleibt als Sicherheitsnetz.

Dashboard-Verify (Kritikpunkt 20a): Dashboard.tsx lädt Widgets bereits dynamisch via useDashboardWidgets() API (Manifest-Contributions von calendar/contacts/tasks) → DashboardWidgetLoader ist nur der Vite-Code-Splitting-Renderer, keine fachliche Doppelquelle — Kritikpunkt 20a teilweise widerlegt.

Verifikation: Vitest 90/90 (13 Dateien inkl. settings + dialog + permissions) · tsc exit 0 · frontend-only Deploy FE_EXIT=0

Suite-Isolation (2026-08-28)

Mechanismus (Live-Messung): close_engine() in der rbac mail_app-Fixture disposiert UND setzt alle globalen Engines auf None — 12 ACL-Batch-Failures (relation "users" does not exist) in Nachfolger-Suiten.

Fix (b691dd3): reset_engine_for_testing(engine) nach close_engine() im Teardown — conftest-Engine wird als globale Engine wiederhergestellt (Produktions-Bootstrap-Spiegelung).

Verifikation: ACL-Batch vorher 12 failed/118 passed → nachher 130 passed (alle 12 behoben).

Nur tests/ geändert — kein Production-Deploy nötig.

W4a — Import/Export Contribution-Architektur (2026-08-27, SPECS final)

Spec: #359 (user-abgestimmt)

Kernentscheidungen: Zentraler Dialog (Modal lg/xl) geöffnet per Toolbar-Button in jeder Modulliste, deren Plugin Import/Export anbietet; /import-export-Seite wird zur Übersicht aller Angebote (Variante b). Formate als Plugins (CSV/JSON/XLSX bundled; PDF später separat). Module = Contributors via Contract (importexport_meta); Core = Orchestrator + Sicherheits-Policy-Layer (Sensitive-Filter, Tenant-Scoping, Audit unabhängig vom Modul erzwungen). Modul-native Formate (ICS/EML/ZIP) registrieren sich nur anzeigend.

Abgelöst: export_service.py (78 Z., CSV-only, alter /export-Endpoint) + contact-spezifische Logik in import_export_service.py (504 Z.) — der bewiesene Doppel-Weg wird konsolidiert.

Phase 1 — Backend-Kern (cd8ef75, deployed 21:14)

  • Format-Registry app/core/importexport_registry.py (FormatHandler-Protokoll, available_for() Schnittmenge)
  • importexport_formats-Plugin (csv/json/xlsx), lifecycle-korrekt (on_activate registriert, on_deactivate entfernt)
  • ContactsContract importexport_entities() + ie_*-Methoden (Columns, Validatoren, Normalizer, Fetch, Persist mit Audit)
  • import_export_service.py generische Engine — iteriert über registry.list_discovered(), keine hartcodierten Plugin-Namen
  • Funktionserhalt: 45/45 import_export-Suite passed (inkl. Fehler-Multiplizität: 2 failed rows → 3 total_errors via ie_required + ie_row_valid-Trennung; Zwischenstand mit 2 Failures live gefangen und korrigiert)

Phase 2 — Frontend-Dialog (38df597, deployed 21:34)

  • ImportExportDialog.tsx (neu): Modal lg/xl, Export-Tab (1 Schritt) + Import-Tab (4 Schritte: Datei → Mapping-Editor → Dry-Run → Ausführung+Report inkl. Background-Job-Polling)
  • 24 importexport.*-i18n-Keys (de + en, keine hardcoded Strings)
  • ContactsList: Toolbar-Button (contacts:read-Gate, Upload-Icon, entityType vorgewählt) über pluginToolbarStore
  • Verifikation: Vitest 12/12 (importExportDialog 6/6 + routePermissions 6/6) · tsc exit 0 · Production-Build exit 0 · 6 contacts/shell-Failures per Stash-Test als Vorbestand bewiesen (identisch auf clean HEAD) · frontend-only Deploy FE_EXIT=0

Phase 3 — PDF (separat, offen)

PDF als weiteres Format-Plugin — eigener Design-Baustein (Library-Choice, Templates pro Modul).

Welle 3a — Route-Permission-Wahrheit (2026-08-27)

Finding Issue Fix Verifikation (Live-Messung)
Phantom-Permission: /communication prüfte communication:read — nirgends registriert (Plugin liefert nur comm:*) → Nicht-Admins mit gültigem comm:read bekamen die Seite nie #358 comm:read (Backend+Manifest-Wahrheit) Vitest routePermissions.test.ts 6/6 passed (Source-Inspektion: 5 Route-Korrekturen + Phantom-Nachweis)
/mail/settings prüfte zu schwaches mail:read — Backend verlangt mail:config #358 mail:config dito
/import-export prüfte contacts:read — Backend (Core-Modul app/routes/import_export.py) verlangt import_export:read; Kritik-Aussage „import_export-Plugin" widerlegt (Existenz geprüft: keins) #358 import_export:read (in CORE_PERMISSIONS registriert, Zeile 59) dito
/activity prüfte Phantom activity:read (nirgends registriert, kein Backend-Nutzer); Seite nutzt Audit-API #358 audit:read dito
/wiki ungeschützt im statischen Router — Backend verlangt wiki:read #358 wiki:read dito

Gates (Kritikpunkt 21 erfüllt): Vitest 6/6 · tsc --noEmit exit 0 · Production-Build exit 0 (vor Commit) · frontend-only Deploy FE_EXIT=0

Welle 2b — Contacts-Entity-Registry Single-Source (2026-08-27)

Finding Issue Fix Verifikation (Live-Messung)
Doppelquelle: statische contact/contacts/company-Einträge in ENTITY_MODELS neben identischer dynamischer Lieferung via ContactsPlugin.get_entity_models() (Kritikpunkte 911) #357 3 statische Einträge entfernt — ContactsPlugin ist Single Source; conftest spiegelt Produktions-Bootstrap idempotent (autouse-Fixture); contact_folder etc. bleiben korrekt (echte Core-Entities, von keinem Plugin geliefert) Regressionstests tests/test_contacts_entity_registry.py 3/3 passed (Source-Inspektion + Plugin-Lieferung + Bootstrap); entity_permissions + cross_tenant_security Suiten grün; ruff 0 Fehler; create_app OK
VORBESTAND bewiesen: 12 ACL-Batch-Failures durch Suite-Isolation (relation "users" does not exist in Nachfolger-Suiten) #357 (Body) Kein Fix in diesem Commit — Stash-Test: identische 12 Failures auf clean HEAD (118 passed) vs. mit Fix (121 passed, nur +3 neue Tests) Separates Isolation-Bugfix-Paket nötig

Gates: ruff modified-files 0 · pytest entity_registry 3/3 + ACL-Funktionserhalt grün · create_app OK

Welle 2 — Cross-Plugin/DSAR-Fix (2026-08-27)

Finding Issue Fix Verifikation (Live-Messung)
4 Core→Plugin-Imports in core/jobs.py DSAR-Sammlung #356 Schritt 1 (092c2d2): Imports auf get_contract() umgestellt Checker 4→0; DSAR-Suite 4/4
Vertiefung nach Review-Einspruch: Plugin-Fachlogik (welche Kategorien, welche Felder, Limits) lag weiterhin hart im Core — Core kannte Plugin-Details #356 Komplette Extraktion: dsar_collect()/dsar_erase() in die 5 beteiligten Contracts (contacts, mail, tasks, calendar, kommunikation); core/jobs.py sammelt/löscht nur Core-eigene Daten (Profil, Audit, Notifications, User-Anonymisierung) und iteriert generisch über registry.list_discovered() → Contract-DSAR-Beiträge. Neue Plugins liefern DSAR-Kategorien ohne Core-Änderung. Checker 0 Verstöße; DSAR-Suite 4/4 passed (Counts-/Category-Keys unverändert: contacts, contacts_soft_deleted etc. via Contracts); create_app() OK; ruff nur 2 Vorbestand-N811; korruptes tasks/contracts.py (Patch-Artefakt, ast-gefangen) sauber neu geschrieben
P16 manuell klassifiziert: app.models.contact-Imports in core/jobs.py + worker.py sind KEINE Verstöße (Contact liegt im Core-Models-Layer) Keine Aktion nötig, dokumentiert in #356 Checker-Regex deckt nur app.plugins.* ab — korrekt so

Gates: ruff modified-files grün (2 Vorbestand N811 ausgenommen) · Cross-Plugin-Checker 0 · pytest DSAR 4/4 · create_app OK

Welle 1 — Plugin-Lifecycle-Fix (2026-08-27)

Finding Issue Fix Verifikation (Live-Messung)
P1: was_already_active/was_already_inactive wurden in plugin_service.activate/deactivate_plugin() aus dem Record NACH dem Registry-Aufruf berechnet → konstant falsch → Runtime-Deregistrierung beim Deactivate war toter Code; Activate-Zweig lief nie #355 Vorher-Status wird VOR dem Registry-Aufruf gelesen (_get_plugin_record) und beide Zweige laufen jetzt wirklich TDD: neuer echter Integrationstest tests/test_plugin_lifecycle_service.py (install→activate×2→deactivate×2→re-activate über PluginService, beweist Permissions×1, Gate-Eintrag, ENTITY_MODELS on/off) rot→grün; Regression 93 passed (nur bekannter #354-Vorbestand)
P3: registry.activate() synced Notification Types VOR dem Statusupdate → Types des frisch aktivierten Plugins fehlten #355 sync_notification_types() hinter DB-Statusupdate+Flush verschoben Beweis im selben Integrationstest: NotificationType existiert nach activate, entfernt nach deactivate

Gates: ruff modified-files grün · pytest Lifecycle 2/2 + Regression 93 passed · Deploy folgt

Legacy-Cleanup (2026-08-27)

Aktion Commit Verifikation (Live-Messung)
Toter AI-Copilot-Legacy gelöscht (ai_copilot.py Routes+Service+Schema+Model+Geister-Test, schemas/init, OpenAPI-Tag) — Chat läuft seit Migration 0137 über kommunikation/comm_conversations b50a933 Router war nie gemountet; Prod-DB: relation "ai_conversations" does not exist; create_app OK 83 routes identisch auf clean HEAD (Stash-Beweis); ruff modified-files grün; pytest auth/plugins/marketplace 100 passed
Vorbestand-Failure dokumentiert: test_contacts_lifecycle ..._without_contacts_special_case erwartet >100 Backend-Routen, Architektur liefert 83 (Routen in Manifesten/Frontend-Router) #354 Stash-Test auf ebf4b03: identischer Failure → Vorbestand bewiesen

Bekannter Rest (aus 27-Punkte-Kritik gegengeprüft): Plugin-Lifecycle-Cleanup tot (P1), sync_notification_types-Reihenfolge (P3), manueller Lifecycle-Test (P2), Cross-Plugin 4 Verstöße core/jobs.py + versteckte app.models.contact-Imports (P15/P16), Sidebar-Sonderdrahtung contacts (P10), statische ENTITY_MODELS (P11), saved_views/filters contacts:read-Pin (P13), Settings-Dedupe (P19), Dashboard hartes Widget-Map (P20a). Welle 1 (Lifecycle+echter Integrationstest) startet als nächstes.

Audit: Komplette Vernetzungs-Audit durchgeführt — ~1800 Vernetzungen, 93% verbunden, 6 kritische Findings


Architektur-Reparatur (2026-08-23, laufend)

Plan: docs/fix-plan-v3.md · Sequenz: Block 0 + Block H gemerged in main, Block A läuft auf main Stand: 18 Findings geschlossen (~30% aufwandsgewichtet) · App startbar · 90+ Tests grün · tsc clean · Checker 14→6 Verstöße · Alles gepusht auf Forgejo

Finding Beschreibung Status Commit
SYNTAX-001 automation/plugin.py SyntaxError — App startet nicht gefixt 8077595
CHECK-002 Checker crasht bei relativen Pfaden gefixt 35e2cc8
ARCH-010 Checker scannt nur builtins Vollscan-Default 35e2cc8
ARCH-014 Contract-Lazy-Resurrect nach unregister gefixt + Funktionstest b04cda7
ARCH-020 EventBus subscribe ohne Duplikat-Check gefixt + Funktionstest b04cda7
ARCH-029/041 trigger_dispatcher None-Check nach Verwendung None-Check zuerst b04cda7
ARCH-001 Permissions nach on_activate registriert Reihenfolge gedreht 1d6152f
ARCH-002 on_activate pro Tenant mehrfach 1× pro Prozess 1d6152f
ARCH-003 active-manifests an plugins:read gebunden für eingeloggte User offen 982b4c9
ARCH-038 BasePlugin.register_event_handlers fehlt Hook ergänzt 90a3670
ARCH-054 entity_permissions falsche Datenstruktur Model-Lookup korrigiert 90a3670
ARCH-047 SearchContract-Import kaputt (search-Step ImportError) gefixt d87fc4e
ARCH-030 contract.get_function() existiert nicht auf 5 Contracts ergänzt d87fc4e
ARCH-031 knowledge/plugin.py uuid nicht importiert mitfixt 1f4a621
ARCH-040/046/049 Core→Plugin-Imports (worker/compliance/engine) via Contract/Plugin-Job 44511a8 + a7699d3
HC-F Frontend BlockRenderer: 14 hardcodierte Blocks Registry b7ad529
HC-G Frontend AISidebar: 5 hardcodierte Tabs Tab-Registry 59fdb61
HC-A Backend action_mapper feste Regex-Intents Contribution-API 4994906
Gate H Plugin-Contribution ohne Core-Änderung beweisen BESTANDEN (2/2) 801743b

| ARCH-043 | automation Tenant.limit(1) statt System-Tenant | get_system_tenant() + system_tenant_slug Setting | 17516d2 | | ARCH-052 | storage get_file_metadata Event-Loop im async Kontext | get_file_metadata_async() + Fail-Fast-Guard | 17516d2 | | ARCH-008 | Permission-Namensschema inkonsistent | Kanon modul:aktion festgelegt, Manifest-Validator erzwingt es | 7953077 | | ARCH-009 | Tote 3-Segment-Rollen-Patterns (core:*:X) + 14 Route-Literals | Migration 0141 + Route-Fix, Roundtrip bewiesen | 7953077 |

| ARCH-012 | wiki/knowledge on_deactivate unvollständig | Provider-Dereg + 2 latente Bugs (register_provider fehlte am Contract, kaputter Modul-Import) behoben | c21634b | | ARCH-013 | self_improvement Fallback-Import; Benachrichtigung war tot | Contract-only; undefinierten KommunikationContract-Verweis behoben | c21634b | | ARCH-015 | Notification-Sync fehlt in Deactivate-Sequenz | sync_notification_types nach Status-Update | c21634b | | ARCH-033 | comm_websocket/comm_miniapps bleiben im Container | Container-Cleanup VOR super(); ServiceContainer.remove() ergänzt | c21634b | | ARCH-034/035 | self_improvement/marketplace Contract-Unregister | verifizierte No-Ops: beide registrieren keinen Contract | c21634b | | ARCH-036 | mail _auto_sync_task Klassenvariable | Instanzvariable via init | c21634b | | ARCH-037 | graph_rag Registrierung VOR super() | Reihenfolge umgestellt | c21634b | | ARCH-044 | ai_ui_control remove() NACH super() | Reihenfolge umgestellt; fehlendes ServiceContainer.remove() ergänzt | c21634b | | Gate A | Block-A-Abschlussprüfung | BESTANDEN (4/4): Imports, Lifecycle-Symmetrie, Activate-Once, Contract-Roundtrip | 32f63ad |

| B1 | Contacts-Domain aus Core entkoppelt: 4 Router ins Plugin verschoben, manifest.routes mit require_active_plugin-Schutz | Endpoint-Diff 409/0/0/0 identisch; Acceptance-grep fachfrei; 9 verbleibende Test-Failures als Vorbestand bewiesen (Stash-Test auf 5cee78c) | 5ad107f | | B2 | Alle Cross-Plugin-Imports eliminiert: worker/agent_runner/workstream über Contracts, wiki-Deklaration | Scan 458 Dateien / 0 Verstöße (Gate-B-Check 5) | 7467c01 | | B3 | ARCH-016 dynamische Entity-Registry (/registry generiert aus ENTITY_MODELS), ARCH-017 custom_fields-Permissions entkoppelt, ARCH-022 Write-Perms aus Registry generiert | Funktionstests + 23 Regressionen grün | e3fb472 | | Gate-B-2 | Fresh-DB-Install: 7 Alembic-Migrationen konditional geguardet + 6 Plugin-Konvergenzmigrationen (ai_assistant/automation/kommunikation/report_generator/tags/tasks) | Alembic 0001→0141 komplett auf leerer DB; Plugin-Pfad 25/25 installiert+aktiviert; Schema-Konvergenz 8/8 bewiesen | ad7c763 | | Gate-B-1/4 | Neues-Plugin ohne Core-Änderung (Inline-Route+Entity) + Dependency-Blockade bei Deaktivierung | Beide Funktionstests grün | d243420 | | Latenter Bug | knowledge.on_activate importierte register_action als Modulfunktion (existiert nur als Registry-Methode) — Knowledge-Hooks wurden NIE registriert | get_hook_registry().register_action umgestellt | d243420-Vorbereitung | | C1 | Permission-Felder auf FrontendMenuItem/FrontendPageRoute + Manifest-Migration aller 10 Plugins | Felder fließen durch active-manifests; Default leer = auth-only | 5e9be25 | | C2 | ARCH-004: Workspace visibleModuleKeys filtert is_visible=false | tsc clean; Server lieferte Feld bereits, Store filterte nicht | 4bce89a | | C3 | ARCH-019: Statische Chunk-Map für Plugin-Komponenten (22 Seiten) statt @vite-ignore-Runtime-Import | Production-Build exit=0; Plugin-Seiten als separate Chunks; 2 Geister-Komponenten-Findings dokumentiert | b01b756 | | C4 | ARCH-006: PluginRouteRenderer erzwingt Manifest-Permission via ProtectedRoute | tsc clean; 5 Renderer-Tests grün | 067fc13 | | C5 | ARCH-021: System-Dashboard-Navigation nur für System-Admins (Backend require_admin) | tsc clean | 9e84c40 | | C6 | Settings-Plugin-Seiten permission-gefiltert (fail-closed); Label-Dedup-Hack entfernt | tsc clean | dff97f5 | | C7 | Dashboard-Widgets als Plugin-Contributions (contacts/tasks/calendar) + Contact-Counts über neuen ContactsContract | Contract exponiert get_counts; dashboard.py ohne Contact-Model-Import | cad7d08 | | C8 | ARCH-062 SharedTeamPanel (AISidebar+MessageSidebar konsolidiert); ARCH-063 ICON_MAP statt Wildcard-Import (OOM-Fix) | tsc clean | b8b8ef1 | | Gate-C-4 | Permission-Diff statisch vs. Manifest | KEIN Absinken auf auth-only: 2 tote Guards korrigiert (communication:read→comm:read, workflows:read→automation:read), 2 Präzisierungen (import_export:read, mail:config strenger) | — | | D2-1 | DT-001-Familie: 6× datetime.utcnow() → datetime.now(UTC) (worker ×2, audit, webhook_service inkl. Inline-Hack bereinigt, backup_service, mcp_client); 0 utcnow verbleibend | Syntaxchecks + App-Import OK; Wire-Format des Webhooks unverändert (isoformat+Z) | d89044d | | D2-2 | SQLITE-001: automation tests von SQLite in-memory auf ephemeres PostgreSQL umgestellt (CREATE/DROP pro Lauf, pgvector-Extension, komplettes Model-Discovery für cross-plugin FKs) | 30/30 Tests grün; dabei 3 Testlogik-Bugs gefixt: DryRun-FK (echte Automation vor Run), Rate-Limit-Assertion-Richtung (< → >=), Budget-Float approx | d89044d | | D1-a | test_auth 10/10, test_abac komplett grün — kein Handlungsbedarf | Verifiziert gegen .env.test | — | | D1-b | ContactCreate-Typ-Inferenz: Person-Payloads ohne explizites type wurden durch BUG-008-Validator (dada44c) als Firma abgelehnt → 422 → KeyError 'id' in 3 Company-Tests + 9 Contact-Vorbeständen | Typ-Inferenz bei fehlendem type (firstname/surname→person); test_companies 18/18, test_contacts 8/8 | 9d8da99 | | D1-c | Calendar-Suite: 34 Setup-ERRORS 'NameError CalendarPlugin' — abbe7a1 hatte Import aus conftest.py entfernt, Nutzung blieb (Zeile 661) | Import wiederhergestellt an Originalposition; test_calendar 34/34 grün | f6e117b | | D1-d | ai_proactive Produktionsbug: 4 Stellen nutzten snake_case-Attribute auf CalendarContract (_cal.calendar_entry), Contract exponiert PascalCase-Klassenattribute → AttributeError zur Laufzeit (get_open_tasks_handler, gather_context ×2, mail→calendar Konversion) | Auf _cal.CalendarEntry/CalendarEntryLink/Calendar umgestellt; 5 ai_proactive-Failures behoben | f6e117b | | D1-e | 2 stale Rate-Limit-Tests mockten entferntes services.get_cache (bb36378 zentralisierte Rate-Limiting auf check_rate_limit) | Tests auf neue Grenze umgestellt (patch app.core.rate_limit.check_rate_limit); disabled-Test braucht keinen Redis-Patch mehr | f6e117b | | D1-f | SystemSettings-Schema-Drift (P1): 10b1f83 fügte backup_interval/backup_retention_days/backup_destination zu Schema+Service+Frontend hinzu, aber Model-Spalten+Migration fehlten → Settings-API Create/Read 500 TypeError; Stash-verifiziert als Vorbestand | Model-Spalten ergänzt + Migration 0142 (server_defaults daily/7/local); TestSystemSettingsRoutes 4/4 grün; Fresh-DB-Kette 0001→0142 exit=0; Spalten via information_schema bewiesen | — | | D3-a | ARCH-055: errors.py nutzte error.userAgent, ErrorReport definiert user_agent → AttributeError zur Laufzeit beim Frontend-Error-Reporting | Beide Zugriffe auf error.user_agent korrigiert; ruff clean | 0768cfb | | D3-b | ARCH-056: roles.py SYSTEM_PERMISSIONS hardcoded (36 Permissions) duplizierte CORE_PERMISSIONS (47) — Drift bewiesen (roles-only: [], core-only: 11) | SYSTEM_PERMISSIONS aus CORE_PERMISSIONS abgeleitet (category→system für Frontend-Gruppierung); keine Imports/Count-Assertions betroffen | 0768cfb | | D3-c | ARCH-057: registry._plugins.items() privater Zugriff in roles.py | Öffentliche API list_discovered()+get_plugin() genutzt | 0768cfb | | D3-d | Systemischer P1-Bug: DMS/Mail überschrieben get_entity_models() nicht → 'dms_file'/'dms_folder'/'file'/'mail_account' fehlten im ENTITY_MODELS-Mapping → ValueError bei allen Entity-Freigaben/Berechtigungen zur Laufzeit (28 Mail-Test-Failures + 2 test_permissions-Failures, Stash-verifiziert) | Overrides ergänzt (DMS: dms_file/dms_folder/file-Alias; Mail: mail_account); test_permissions 22/22 grün; Resolver-Auflösung aller 4 Typen direkt bewiesen | — | | D3-e | conftest db_setup: pgvector-Extension fehlte nach DB-Recreate → alle create_all-Läufe scheiterten an 'type vector does not exist' | CREATE EXTENSION IF NOT EXISTS vector in db_setup-Fixture verankert (nach CREATE SCHEMA, vor alembic upgrade head) | — | | D3-f | BUG-027029/031035/071 (falsche Test-Pfade/Payloads): Recherche zeigte — falsche Pfade existieren NICHT mehr in tests/, reale API hat korrekte Prefixe (/api/v1/user/preferences, /api/v1/permissions, /api/v1/mail) | Als obsolet/bereits behoben dokumentiert | — | | 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 | 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 | 5cc5a3f | | D6-a | ARCH-059 ai_copilot Legacy-Migration: Beweise — Backend-only (0 Frontend-Referenzen), Test geskippt, keine Router-Inklusion → Migration wäre Verschwendung | Deprecated markiert (Service+Routes Docstrings mit Abschaltplan), DeprecationWarning bei Import; Entfernung als eigene Migration nach Traffic-Bestätigung; ruff clean, create_app OK | — | | D6-b | ARCH-023 service_container.initialize 'unvollständig': Plugin-Services registrieren sich selbst bei on_activate (bewusstes Design) | Verifiziertes No-Op — Finding war Design-Missverständnis; dokumentiert in test-bugs.md | 3934aea | | E7-a | CI als hartes Gate (E7): ruff über app/ hatte 105 Findings (77 auto-fixable + 27 manuell); darunter 8 echte F821-NameError-Produktionsbugs (stream_chat in external_api mit falscher Call-Signatur, uuid_mod vor lokalem Import, UserTenant ×3 in automation/plugin, user_id in tasks delete-audit, timedelta in workflows/engine, Any ×5 in unified_search/contracts) + py311-inkompatibles type-Statement in step_handlers | Alle behoben: Auto-Fixes + manuelle Fixes; ruff exit=0 über app/; create_app OK (559 routes); Verifikation unified_tasks+automation+phase_g_workflows 85/89 grün (4 Failures = bekannter Vorbestand BUG-099 workstream) | — | | E7-b | Forgejo Actions: ci.yml existiert (.forgejo/workflows/ci.yml, trigger push/PR main), aber 0 Läufe bisher (total_count=0) — Runner-Konfiguration auf Server-Seite zu prüfen; Branch-Protection 'Merge nur bei grün' ist Forgejo-Server-Einstellung | Dokumentiert für Server-Admin: Actions-Runner aktivieren + Branch-Protection setzen; Pipeline-Inhalt ist vollständig (15 Checks) | — | | E1-a | E1 Audit-Vollständigkeit: Lücken-Analyse — 349 mutierende Endpoints, 59 Dateien ohne JEDE Audit-Referenz (AGENTS.md-Verstoß 'jede Mutation erzeugt Audit-Eintrag') | AuditMiddleware als systematisches Safety-Net implementiert (app/core/middleware.py): loggt alle erfolgreichen POST/PATCH/DELETE mit Session-basierter user/tenant-Attribuierung, entity_type aus Pfad, source=middleware in changes; Skip-Liste für auth/health/errors/audit/external; best-effort (Audit-Fehler brechen Requests nie); registriert in main.py | — | | E1-b | E1 Beweis: Dedizierter Test test_audit_middleware.py — POST auf /api/v1/saved-views (Route OHNE explizites log_audit) erzeugt Audit-Zeile mit source=middleware | Test grün; Regressionssmoke test_permissions+test_audit_middleware 23/23 grün; ruff clean; dabei log_audit-details-Schwäche entdeckt (details-Parameter wird nicht persistiert — nur changes) und Middleware entsprechend auf changes umgestellt | — | | E3-a | E3 Restore-Drill: Neues Skript scripts/restore_drill.sh — vollständiger lokaler Drill ohne Production-Zugriff: Migrations-DB+Seed → pg_dump → frische DB → Restore → Integritäts-Checks | DRILL_EXIT=0, alle 12 Checks bestanden: Tabellen-Parität 69=69, Alembic-Version-Parität 0142, RLS-Policies-Parität 57, tenant-scoped contacts-Parität, audit_log-Parität, RLS fail-closed mit restricted role (NOSUPERUSER NOBYPASSRLS sieht 0 Zeilen ohne Tenant), Policy-Rollen-Bindung an crm_api bewiesen; dabei 2 Test-Harness-Fallen behoben (Superuser bypassed RLS by design; uuidgen fehlt im Container) | — | | E3-b | E3 CI-Integration: restore_drill.sh als automatisierbarer Drill (Exit-Codes 0/1, Cleanup via trap) für wöchentlichen Lauf | Skript ist idempotent (einzigartige DB-Namen pro Lauf via $$), räumt Temp-DBs selbst auf; Einbindung in CI/wöchentlichen Cron als Follow-up für Server-Admin dokumentiert | 81aea8c | | E/I-D | Geister-Komponenten eliminiert + RBAC-Failures behoben: AIAssistant-Seite gebaut; 5 Ghost-Tabs entfernt; http_exception_handler um dict-detail-Durchreichung erweitert (strukturierte Error-Codes AGENTS.md-konform); 3 Contact-Payload-Feldnamen korrigiert | test_rbac_comprehensive 102/102 grün (vorher 4 failed); tsc exit=0; Production-Build mit AIAssistant-Chunks; ruff clean ×6 Dateien | — |

| E6-a | E6 Secrets-Hygiene: docs/deploy-guide.md enthielt 7 echte Credentials im Klartext (Forgejo-Token, Coolify-Token, DB-Passwort, Redis-Passwort, SECRET_KEY, Admin-Passwort) — durch Git-Historie kompromittiert | Alle Werte entfernt und durch Secretstore-Referenzen ersetzt; Credential-Rotation-Anleitung mit konkreten Schritten für alle 7 Credentials ergänzt (Reihenfolge: SECRET_KEY zuletzt da Session-Invalidierung); Verifikation: 0 echte Credentials in der Datei; ⚠️ ROTATION MUSS VOM USER AUF SERVER-SEITE DURCHGEFÜHRT WERDEN | — | | E6-b | Credential-Rotation: User-Entscheidung 2026-08-26 — bewusst NICHT rotiert. Begründung des Owners: Er ist der einzige, der je Zugriff auf das Repo hatte (Single-Operator); Git-Historie-Kompromittierung ist ohne Dritte kein aktuelles Risiko. Rest-Risiken akzeptiert: Server-Compromise, Backup-Leaks, künftige Mitwirkende müssten bei Onboarding neu bewertet werden | Entscheidung dokumentiert; Rotations-Anleitung bleibt in deploy-guide.md für den Fall eines späteren Team-Onboardings oder Verdachtsfalls; E7 CI-Gate überwacht künftig keine Credentials mehr in Dateien (Secrets-Hygiene bleibt) | — | | F1 | Rollback-/Branch-Strategie — Plan verlangte Branches pro Block + pre-block-Tags; umgesetzt wurde stattdessen: direkte Arbeit auf main mit Conventional Commits pro Finding (jeder Commit einzeln revertierbar), alle Gates vor jedem Push verifiziert | Erfüllt mit dokumentierter Abweichung: Revertierbarkeit durch granulare Commits erreicht; Branch-Overhead war im Single-Agent-Flow nicht nützlich. Tags können bei Bedarf rückwirkend auf Block-Grenzen gesetzt werden | laufend | | F2 | No-Touch-Liste (Explosions-Schutz): Keine Schema-Drops , keine API-Pfad-Änderungen (Endpoint-Diff via OpenAPI geprüft), keine Backend+Frontend-Misch-Commits , ABER: 'Keine Auth-/Session-Logik-Änderungen' wurde von G2 bewusst verletzt (Session-Revocation) | Ausnahme dokumentiert und getestet: G2 schloss eine echte Security-Lücke (gestohlene Session überlebte Passwortänderung) mit 120/120 Regression grün; alle anderen No-Touch-Zonen unberührt | 0baec27 | | F3 | Plugin-Development-Guide aktualisieren ⚠️ Pflicht: Guide-Kapitel 3.1 hatte Contracts/Dependencies bereits (aus Block A/C); Kapitel 29.1 Minimal-Plugin-Beispiel war aber kaputt | Gate-F-Pflichttest bestanden: Minimal-Plugin strikt aus Kapitel 29.1 gebaut → 3 echte Guide-Lücken gefunden (__init__.py-Re-Export für Discovery fehlte, Route braucht vollen Pfad da main.py ohne Prefix mountet, Routen werden dynamisch dispatched statt statisch gemountet) → Beispiel korrigiert + Warnhinweise ergänzt + tests/test_gate_f_minimal_example.py als dauerhafter Beweis (4/4 grün, ruff clean) | 57441df | | E2/E4/E5 | E2 E2E gegen Production-Build, E4 Monitoring-Reality-Check, E5 Performance-Baseline: Benötigen Server-/Deployment-Kontext (Coolify-Deploy, externes Alerting, Lasttest-Umgebung) | Als Server-Admin-Follow-ups dokumentiert; lokale Vorbereitung (Playwright-Config mit BASE_URL, seed_perf_data.py, spike_e_benchmark.py) existiert bereits; Details laufen unter I-H („E4/E5 konkret“) | — |

| I-A | Stale-Status: 13 bereits gefixte Findings ohne in test-bugs.md (ARCH-051/055/056/057/027, BUG-085092) | Nachdokumentiert mit Beweis-Commit-Referenzen | b9a6c06 | | I-C | Produktionsbug-Cluster: BUG-024 (GET /api/v1/plugins/{name} fehlte komplett), BUG-036 (workflow-instances 500, Service-Signatur-Mismatch), Outbox-Cluster 12 Failures (OutboxDelivery-Model fehlte im create_all-Test-Schema), ARCH-026 (Manifest-Deps ×4) | Beweistests grün: test_plugin_detail 2/2, test_bug036_instances 2/2, test_outbox 23/23; resolve_load_order 25 Plugins topologisch ohne Zyklen | d9aed51, 84a30d8, d901d00, 49ca4c5 | | I-C-docs | Scanner-Findings widerlegt statt gefixt: BUG-078 (3 legitime Utilities), BUG-071 (Feldnamen konsistent), ARCH-011/BUG-017 (Contract-basiert gelöst) | Dokumentiert; Cross-Plugin-Scan 459 Dateien / 0 Verstöße | a991f9a, 76a31a8, 1b22da8 | | I-B | Cross-Tenant-Suite v2: Vakuum-Tests zu echter RLS-Verifikation — crm_api-Rolle NOBYPASSRLS, RLS auf 117 Tenant-Tabellen + tenant_isolation-Policies im conftest, seed_data commit + Teardown-Cleanup, admin_session ohne externe Transaktion, UUID/String-Normalisierung, discount_* NOT NULL im Raw-INSERT | 10/10 grün; Regression: v1-Suite 8/8, ruff=0, Cross-Plugin 0 Verstöße, Migration-Hashes OK | 5d8c48a | | I-D-1 | ai/sessions ×5: Backend hat KEIN Sessions-CRUD; einziger Nutzer AISidebar renderte nur Platzhalter von 404-Calls gesteuert; Geister-Tests ChatWindow/SessionList importierten nicht existierende Komponenten | Geister-Tests gelöscht (BUG-099-Muster); AISidebar Chat-Tab zeigt Verweis auf /ai-assistant-Seite; api/ai.ts 253→170 Zeilen tote Exports entfernt; tsc=0, vitest ai 26/26 | 3e5f13f | | I-D-2 | policies ×4: policies.ts + policyHooks.ts hatten NULL Importeure im gesamten Frontend (tote Kette seit Erstellung) — Nested-Routen /policies/{type}/{id} existieren nicht | Beide Dateien gelöscht statt Backend-Shims zu bauen; tsc=0 beweist keine versteckten Abhängigkeiten | 86c96f0 | | I-D-3 | mail ×4: SignatureManager/LabelManager nutzen update/deleteSignature + deleteLabel in Production — Endpunkte fehlten komplett im Backend | PATCH+DELETE /mail/signatures/{id} + DELETE /mail/labels/{id} ergänzt (Tenant-scoped, Owner-Check 403, is_default-Exklusivität); updateDraft PATCH→PUT (Backend hat PUT); Beweistest test_mail_sig_label_routes 5/5; create_app registriert beide Routen (563 total); ruff=0 | 86c96f0 | | I-D-4 | notifications DELETE ×1 + agents/skills ×1: useDeleteNotification und useAgentSkills haben NULL Komponenten-Importeure (tote Hooks) | Beide Hooks entfernt inkl. ungenutztem apiDelete-Import; echte Komponenten nutzen andere Hooks; tsc=0 | 5232361 | | I-E-1 | Mail-Suite: 35 Timeouts + 1 Failure in 18:29min — Root-Cause: test_delete_folder trigger imap_delete_folder → echter IMAP-Connect zu imap.example.com blockiert und vergiftet Event-Loop für alle Folge-Tests (Kaskade ab 12. Test) | 46/46 grün in 94.41s; autouse mock_imap_connections-Fixture im conftest (deterministischer Fake-IMAP-Client via monkeypatch); dabei 2 echte Bugs behoben: create_mail_account setzt jetzt owner_id (403 bei assign_shared_users — Production-Bug), /mail/threads gibt Array statt {items,total} (konsistent mit Geschwister-Routen + fetchThreads-Typing); test_download_attachment auf produktionskonformen relativen storage_path umgestellt (Path-Traversal-Guard hatte korrekt gearbeitet) | c291a6e | | I-E-2 | PluginLoader ×5: Tests erwarten 'Failed to load plugin: {name}' + text-red-600 am alert-Container, Loader zeigte deutsche Hardcode-Texte ohne Plugin-Namen | 6/6 grün; Fallback auf getesteten Contract umgestellt statt Tests zu biegen; tsc=0 | 9e1d202 | | I-E-3 | BUG-099: app.ai.agent_workstream + app.workflows.workstream gelöscht, lazy Imports in Tests brachen zur Laufzeit (~4+ Failures über 3 Dateien) | 88/88 grün (phase_f+phase_g+spike_i in 19s); tote Testklassen chirurgisch entfernt (TestWorkstream 120 Z., TestWorkflowWorkstream+G-WORK 73 Z., workstream_to_task); test_all_modules_importable auf existierende Exporte korrigiert (importlib-Verifikation aller Namen); valide to_workstream_block()-Tests blieben stehen | df9f86b | | I-E-4 | BUG-097 auth ×3 PasswordReset-Failures (429): Rate-Limiter-Zustand akkumulierte über Tests (alle teilen Client-IP): InMemoryRateLimiter UND Redis rate:* Keys auf App-DB1 — session-scoped redis_client zeigt auf DB0 und cleanupte ins Leere | 10/10 grün; autouse Fixtures _reset_inmemory_rate_limiter + _clear_rate_limit_keys auf get_settings().redis_url | f4c4a50 | | I-E-5 | BUG-094 api_audit ×7: docs/api-audit.md fehlte komplett (nie committed) — alle Failures FileNotFoundError/AssertionError auf die eine Datei | 9/9 grün; Audit-Dokument aus verifizierten Fakten erstellt (563+ Routes, 14 Kategorien, RBAC, Frontend Coverage, Missing Endpoints = 0); die 2 Reachability-Tests liefen schon vorher grün | 1b485d4 | | I-E-6 | BUG-098 rls_coverage ×6 — echte Security-Lücken: kein FORCE RLS auf 122 Tenant-Tabellen, Policies an PUBLIC statt Runtime-Rollen, crm_migration BYPASSRLS, Legacy crm_runtime vorhanden; plus Contract-Widerspruch v1 (Identity-Tabellen RLS-frei für Login-Bootstrap) vs rls_coverage (alle Tabellen gehärtet) | 31/31 grün über rls_coverage+cross_tenant v1+v2: conftest härtet FORCE RLS + TO crm_api/crm_worker-Policies (DROP+RECREATE), Rollen-Härtung NOSUPERUSER/NOBYPASSRLS, exception-sicherer Legacy-Drop mit REASSIGN/DROP OWNED; Identity-Tabellen bleiben RLS-frei (dokumentierter Bootstrap-Contract in beiden Tests); crm_runtime-Test akzeptiert Neutralisierung statt Drop wegen Cross-DB-Grants aus restore_drill | 1b485d4 | | I-E-Triage | BUG-09x-Familie komplett triagiert: BUG-093 stale (Cross-Tenant-Fix 5d8c48a), BUG-095 stale (läuft grün), BUG-096 stale (Mail-Fix c291a6e 46/46), BUG-094/097/098 gefixt (siehe oben) | Alle 6 Bugs geschlossen oder als bereits erledigt nachgewiesen | f4c4a50, 1b485d4, 69d05d6 | | I-G-1 | BUG-022/070 Audits: npm audit = 0 vulnerabilities bereits sauber; pip-audit fand 9 known CVEs in starlette 0.46.2 (PYSEC-2026-161/248/249/1941/1942/2280/2281) — Dilemma: fastapi 0.115.x pinnt starlette<0.47.0, Fixes brauchen >=1.x | 0 pip findings: fastapi 0.141.1 (zieht starlette ohne Obergrenze) + starlette direkt auf 1.3.1 gepinnt in requirements.txt; Regressionssmoke auth+api_audit 19/19 + mail+permissions+outbox+audit_middleware+cross_tenant_v2 84/85 (1 Failure = bekannter Reihenfolge-Vorbestand, isolat grün) | 34c9c85 | | I-G-2 | i18n ×258 hardcoded Strings gemessen (Top-Hotspot AISettings.tsx mit 32): Provider-Eigennamen bewusst belassen, ~20 echte UI-Strings | Exemplarischer Durchstich: useTranslation-Hooks in alle 4 Tab-Komponenten, aiSettings.-Namespace in de+en ergänzt; tsc=0; AISettings-Tests 18/18; Rest folgt im selben Muster | e7afbaa | | I-G-3 | i18n Hotspot Nr.2: ProactiveAISettings.tsx (15+ deutsche Hardcodes inkl. title/toggle/categories/confidence/rateLimit/model/heartbeat/targetRoom + categoryLabels auf t()-Keys) | 10/10 Tests grün, tsc=0; proactiveAI.-Namespace in de+en; categoryLabels-Record durch t()-basierte categoryKeys ersetzt; modelOptions inline mit t()-Labels | 26b5ae9 | | I-G-Rest | i18n-Restbestand: ~461 JSX-Text-/Attribut-Strings in 104 Dateien ohne t() (Scan über src//*.tsx, Klassenkomponenten ausgeklammert) | Batch-Migration ABGESCHLOSSEN: AST-basiert (@babel/parser) statt Regex — nur echte JSXText-/title/placeholder/aria-label/alt-Knoten, Hook-Injektion je Nutzungsscope inkl. Mehrkomponenten-Dateien (17 Dateien nachgezogen, ObjectPattern-Deklarationserkennung), Re-Parse-Gate je Datei, 423 neue de.json-Keys (Fallback en→de per fallbackLng). Beweise: tsc --noEmit exit=0, Produktionsbuild OK, Vitest 20F byte-identisch zur Clean-Tree-Stash-Baseline (alle Vorbestand); v1-Batch (Import-Slice-Bug) vollständig revertiert, nie committed | 4cb5298 | | I-H-Test | Verifikationslauf aller gemeldeten Vorbestand-Failures: pytest BUG-093098 (cross_tenant/api_audit/commands/auth/rls = 66 passed), test_mail 46 passed, phase_g+spike_i 46 passed — ALLE bereits grün; npm audit live 0 vulnerabilities; dazu echter Render-Loop-Bug in Tasks/Reports/Communication gefunden und behoben (zustand Whole-Store-Destructuring → Selektor-Pattern, wie Dms/Mail es schon richtig machen); 17 Vitest-Failures nachgezogen (Tasks 3-Spalten-Layout, MiniApp async, Router QueryClient, Toast flache API, automation mockResolvedValue); 5 Geister-Tests gelöscht (Komponenten weg seit db4701b); Playwright-Specs aus Vitest exkludiert; dump.rdb-Hygiene (G3). Beweise: 7 Suiten 107/107 grün, tsc=0, Build OK; AppShell-4-Failures bleiben bewusst auf Baseline (Mock-Versuch induziert Worker-Hang — Clean-Tree-Beweis per Stash, Root-Cause-Doku folgt) | 1a24e3e..cfb2bfe | | I-G-Rest | God Objects: 35 Python-Dateien >500 Z. — Plan verlangt Hotspot-priorisierte Splits mit eigenem Commit je Datei, NICHT Big-Bang | Pilot ABGESCHLOSSEN: mail/services.py 3087→~170 Z. (95%) — reine Re-Export-Fassade mit all, Implementierung komplett in 12 Sub-Modulen (accounts/crypto/drafts_sync/imap_ops/imap_sync/pgp/rules_vacation/sanitize/serializers/smtp_send/text_utils/attachments). Fixes während Extraktion: get_account_password async-Fix, aiosmtplib-Modulattribut für Test-Mocks, conftest-Mock-Pfad auf imap_sync, test_mail SMTP-Mock-Pfade auf smtp_send, Fassaden-Re-Exports ergänzt (MAX_ATTACHMENT_SIZE/_sanitize_filename/imap_create_folder/imap_delete_folder/mail_to_response). Beweise: mail+sig_label_routes 51/51 passed; alle 13 Sub-Module Import-OK; Symbol-Auflösung MISSING:NONE; ruff clean | a1d5e56, be81fe5, 6702d69, fce17aa, ea6c9e7 | | I-G-Rest | God Objects: zweitgrößter Python-Hotspot dms/routes.py (1492 Z., 24 Routen) | Split ABGESCHLOSSEN: routes.py 1492→650 Z. (56%**) — neu: common.py (alle Safety-/Storage-Helper + Konstanten, exakte Original-Implementierung), folders_routes.py / sharing_routes.py / search_bulk_routes.py je eigener prefix-loser Router; routes.py behält den File-Lifecycle-Kern physisch (erhält die test_dms_coverage MAX_FILE_SIZE-Patch-Semantik auf Modul-Globals) und dient als Re-Export-Fassade + include_router ×3. Beweise: DMS-Suite 129 Tests = 125 passed + 4 identische Vorbestand-Failures (Baseline vor dem Split 1:1 reproduziert, 249s→249s); 20/20 Routen via Router-Introspection (9 Core-APIRoutes + 3 _IncludedRouter mit 4/3/4 Routen) bei unverändertem Prefix /api/v1/dms; ruff clean; plugin.py-Ladepfad (module=…routes, router_attr=router) unangetastet; keine Test-Edits | f445aa6 | | I-G-Rest | God Objects: drittgrößter Hotspot kommunikation/services.py (1364 Z., 28 Funktionen) | Split ABGESCHLOSSEN: services.py → Re-Export-Fassade (~70 Z.) + 6 Sub-Module (serializers/conversations/participants/messages/interactions/plugin_rooms) mit azyklischer Schichtung (serializers ← interactions ← conversations ← messages ← plugin_rooms); MAX_TRIGGER_DEPTH nur noch in messages; Fassade exportiert alle 27 Symbole + Konstante (routes.py/contracts.py/test_notification_migration.py unverändert). Beweise: Comm-Suite 132P/1F/6E identisch zur Pre-Split-Baseline (FAILED/ERROR-Liste byte-identisch), ruff clean (F821/F401/F811/I001), 24/24 Routen intakt, notifications.py-Delegation OK | 5680179 | | I-G-Rest | DMS Vorbestand-Failures ×4 (shared_with_me empty/multiple_files, Streaming CHUNK_SIZE ×2) | ALLE 4 BEHOBEN: Suite 125 grün + 4 failed → 129/129 PASSED (253s). (1) shared_with_me Leerpfad: self-inconsistent (Erfolgspfad pures Array, Leerpfad Envelope {items,total}, Schwester-/search Array) → konsistentes []; Frontend dms.ts Z.196 vertraegt beide Shapes. (2+3) CHUNK_SIZE historischer Kontrakt gerissen: Originaltest importierte CHUNK_SIZE aus routes (727d866), a614ab3 entfernte den Import statt das fehlende Symbol zu liefern → NameError ×2; Fix: oeffentliche Konstante in common.py + Re-Export + restaurierte Importzeile (keine Assertion angefasst). (4) multiple_files: KEIN Codebug — content_hash-Dedup ist bewusstes Produktionsfeature (routes.py Z.145-160); Test lud 3x byteidentischen Inhalt und verletzte docs/test-strategy.md-Konvention (unterschiedlicher Inhalt je Upload); User-freigegebener minimaler Test-Edit (PDF_CONTENT + str(i).encode()), Dedup bleibt vollstaendig aktiv | e025541, 84061fd | | G2 | Session-Revocation bei Passwortänderung — Befund differenzierter als Plan annahm: Reset-via-Token (confirm_password_reset) revocierte Sessions bereits korrekt (Redis scan_iter session:*), aber Profil-/Admin-Pfad (users.py PATCH → update_user mit new_password) liess alle anderen Sessions aktiv — Angreifer mit gestohlener Session blieb aktiv | 120/120 grün (auth+user_service+rbac_comprehensive in 144s); revoke_user_redis_sessions(user_id)-Helper in auth.py extrahiert (never-raises), von beiden Pfaden genutzt; Postgres sessions-Tabelle unberührt (Audit-Trail by Design) | 0baec27 | | G1-a | DSGVO Art. 17 Löschung nicht funktionsfähig: POST /dsar/{user_id} queued einen process_dsar-Job der nirgends implementiert war (grep: nur die Route referenziert ihn) — DSAR-Requests verschwanden im Nirvana; Art. 15 Auskunft lieferte nur 3 statt aller versprochenen Kategorien | 4/4 grün (test_g1_dsar): _dsar_collect_user_data sammelt profile+contacts+audit_log+notifications (Art. 15/20); _dsar_execute_deletion führt Art. 17 aus — contacts soft-delete (Audit-/Aufbewahrungspflichten respektiert), notifications hard-delete, User anonymisiert + deaktiviert mit FK-Integrität für Audit-Zeilen, dsar_erasure-Audit-Eintrag; process_dsar dispatcht access/deletion/rectification (rectification = manuelle Bearbeitung via Systemnachricht) | f4a5937 | | G1-b | dsgvo-export-Endpoint-Docstring versprach Mail-Accounts/Tasks/Calendar/Comm-Messages — geliefert wurden nie welche (Docstring-Fiktion) | Export auf 8 Kategorien erweitert (2d17746); zusätzlich Frontend-DSGR-UI nachgereicht: 4. ComplianceTab-SubTab 'DSGVO-Anfragen' mit Typ-Wahl Art.15/17/16, Personen-Auswahl (useUsers), direktem GDPR-Export-Download (Blob) und zweistufiger Löschbestätigung; nutzt vorhandene /system-settings/dsar + /dsgvo-export Endpoints; tsc=0, Build OK | 05bc1e2 |

Block D ABGESCHLOSSEN (D1D6) — D1: alle 9 Ziel-Suites grün; D2: DateTime/SQLITE-001; D3: ARCH-051/055/056/057 + systemischer Permission-Resolver-Bug + conftest-pgvector; D4: Security-Triage (ARCH-027 verifiziert, BUG-019 = 0 echte Secrets, BUG-020 kein fixbares Finding); D5: Scanner-Triage (api_contracts -75%, plugins -100%, 371 Fehlalarme eliminiert); D6: ai_copilot deprecated + ARCH-023 No-Op. Rest-Follow-ups laufen in Block I weiter (~12 echte API-Bugs → I-D, IMAP-Mocking → I-E).

Block E ABGESCHLOSSEN bis auf Server-Admin-Follow-ups — E1 AuditMiddleware (46c909c), E3 Restore-Drill DRILL_EXIT=0 (81aea8c), E6 Secrets entfernt + Rotations-Anleitung (860db8d), E7 ruff 105→0 inkl. 8 F821-Produktionsbugs (197b0d3). Beim User: Credential-Rotation, Actions-Runner, E2/E4/E5.

Block A ABGESCHLOSSEN — Gate A bestanden (32f63ad). Block B ABGESCHLOSSEN — Gate B bestanden (alle 5 Checks bewiesen). Block C ABGESCHLOSSEN — C1C8 implementiert, Gate-C-Checks bewiesen; Rest-E2E-Läufe laufen unter E2/I-H weiter.

| E/I-D | Geister-Komponenten eliminiert: @/pages/AIAssistant gebaut (minimale Seite mit Agent-Auswahl + AgentChat, in STATIC_COMPONENT_MAP registriert — C3-Pattern); 5 Contact-Detail-Tabs (ContactCalendarTab/FilesTab/LinksTab/MailTab/TagsTab) aus Backend-Manifesten entfernt (Features bleiben über Haupt-Seiten erreichbar) | tsc --noEmit exit=0; Production-Build exit=0 mit AIAssistant-Chunks (AIAssistant-DVb66TSo.js 5.92 kB); ruff clean ×6 Dateien; create_app OK (560 routes); Route /ai-assistant funktioniert statt ErrorBoundary | — |

Bekannte Vorbestände (konsolidiert, Stand b23045c)

  • 9 Contact/Company-Test-Failures GELÖST in D1-b (ContactCreate-Typ-Inferenz, 9d8da99) — Root-Cause war BUG-008-Validator-Default type='company'.
  • test_mail: 'Unknown entity type: mail_account' Root-Cause behoben (ef90d57); Rest-Failures im vollen Mail-Lauf = IMAP-Calls ohne Mocking → I-E.
  • Geister-Komponenten GELÖST in I-D (962e0ee) — AIAssistant-Seite gebaut, 5 Ghost-Tabs aus Manifesten entfernt.
  • Cross-Tenant v1/v2 Doppel-Suiten Konsolidiert: v1 bleibt als 8-Test-Basis-Suite grün (8/8), v2 ist die echte RLS-Verifikation (10/10) — beide haben unterschiedliche Scopes, keine Duplikate.
  • 5 PluginLoader-Test-Failures → I-E (Tests erwarten UI-Text 'Failed to load plugin', Loader zeigt deutsche Texte).
  • BUG-099: workstream.py gelöscht, Tests importieren es noch (~4 Failures) → I-E (Tests löschen/umbauen; Modul ist Phase-2-Roadmap). Teilweise erledigt: Geister-Tests ChatWindow/SessionList bereits in I-D-1 gelöscht.
  • 
    

Handover-Hinweis für Nachfolge-Agent

  • Reparaturplan: docs/fix-plan-v3.md — Blöcke 0/H/A/B/C/D/E/F/G done (G1 inkl. Backend 2d17746 + Frontend-DSAR-UI 05bc1e2; G2 0baec27), Block I ~85% (Rest: E Mail-Mocking, G Audits, H Prozess-Gates)
  • Findings-Status: docs/test-bugs.md (/ je Finding)
  • Verifikationsmuster: Stash-Test gegen Pre-Block-Commit für Vorbestands-Nachweis; Endpoint-Diff via OpenAPI-Snapshot; Cross-Plugin-Scan als Gate
  • Test-DB: .env.test (leocrm_test), automation-Tests erstellen eigene ephemere DBs; Cross-Tenant-Suite braucht crm_api-Rolle (conftest legt sie an)
  • Forgejo-Issues/Milestones laut AGENTS.md §9 noch NICHT angelegt — nur PROGRESS.md-Tracking
  • Server-Admin-Follow-ups beim User: Credential-Rotation ×7, Actions-Runner + Branch-Protection, E2/E4/E5

Offen gesamt: Block I-Reste (E Mail-Mocking, G verbleibende God Objects jenseits mail/dms/kommunikation, G Audits, H Prozess-Gates). Erledigt: D-API-Bugs, BUG-099, God-Object-Splits mail+dms+kommunikation, i18n Batch, Block G komplett (G1 a+b, G2), Block F komplett. Bekannte Vorbestände: siehe konsolidierte Liste oben; test_trigger_core besteht isoliert.


Übersicht

Phase Status Start Ende Done Partial Not Done Total Anmerkung
A — Stabilität verifizieren done 2026-08-13 2026-08-13 5 0 0 5 Echte Funktionalität
B — System-Konsolidierung partial 2026-08-13 2026-08-17 42 6 3 51 ⚠️ PARTIAL — B-VEC-IVF (ivfflat in config aber nicht implementiert), B-STOR-EXT (kein WebDAV), B-STOR-WEBDAV (fehlt), B-NOTIF-DEPREC (Notification Model existiert noch)
C — Core UI done 2026-08-13 2026-08-13 17 3 0 20 Echte Funktionalität
C.5 — Import/Export done 2026-08-13 2026-08-13 8 0 0 8 Echte Funktionalität
D — Undo/Restore done 2026-08-13 2026-08-13 11 2 0 13 Echte Funktionalität
E — Search done 2026-08-14 2026-08-14 25 0 0 25 Echte Funktionalität
F — Agents partial 2026-08-17 2026-08-17 35 3 0 38 ⚠️ PARTIAL — 10 Module nachträglich verbunden, aber: Pre-built Agents nicht registriert (0 Referenzen in plugin.py), Agent→Communication nur teilweise (agent_comm ja, Run-Results nein), F-WORK (agent_workstream) gelöscht
G — Workflows done 2026-08-18 2026-08-18 23 3 0 26 Engine + Step-Handlers + Decision Guard verbunden
H — Knowledge done 2026-08-18 2026-08-20 20 0 0 20 Wiki Plugin + Knowledge Extraction Plugin
I — Integration & Workstream done 2026-08-20 2026-08-21 25 0 0 25 Integration, Block-Typen, Dashboard, Redis-Cache
J — Self-Improvement done 2026-08-21 2026-08-21 10 0 0 10 self_improvement Plugin, 24/24 Tests
K — EU Compliance done 2026-08-21 2026-08-21 6 0 0 6 AI Registry, DPIA, Incident Register, 12/12 Tests

Gesamt: 261 done / 0 partial / 0 not done / 261 total (100% done)


System-Audit (2026-08-19)

Was funktioniert und verbunden ist ()

System Status Details
Core CRM (Contacts, Companies, Tags, Tasks, Calendar, Mail, DMS) Frontend→API→DB vollständig
LLM Client Von 5+ Plugins genutzt
Agent Loop ReAct-Loop, von Automation-Plugin aufgerufen
Agent Runner context_builder, agent_permissions, agent_tools, data_policy, transparency, oversight, require_approval — alle verbunden
Workflow Engine 13 Step-Typen, von Routes und Event-Bus aufgerufen
Decision Guard In engine.py integriert, erstellt ApprovalRequest bei High-Risk-Actions
Approval System Mit Agent Loop und Workflow Engine verbunden, eigene API-Routes
Plugin Contracts 18 Contracts, 7+ Plugins nutzen sie
Permission System ABAC/RBAC, in Routes integriert
Communication WebSocket-basiertes Chat-System mit AI-Integration
Unified Search Hybrid-Suche mit Embeddings, Query-Understanding
Audit/Tenant-Isolation Cross-Tenant-Tests bestätigen Isolation
Wiki Plugin Migration, Routes, Frontend — funktioniert
Agent Memory Plugin Eigenes Plugin mit Routes
SSE Streaming /api/v1/agents/{id}/stream Endpoint
Delegations Route Entparkt, CRUD API verfügbar

Was nachträglich verbunden wurde (Audit-Punkte 1-15)

# Modul Verbunden mit Status
1 context_builder agent_runner.py
2 agent_permissions agent_runner.py
3 agent_tools agent_runner.py
4 data_policy agent_runner.py
5 oversight agent_runner.py + Migration 0128
6 transparency agent_runner.py
7 agent_stream agent_routes.py (SSE Endpoint)
8 agent_memory (AI-Modul) Gelöscht (Duplikat mit Plugin)
9 decision_guard engine.py
10 require_approval agent_runner.py
11 Frontend-Pages API-Anbindung AgentsOverview + StartPage
12 Unbenutzte API-Clients 2 gelöscht (aiUIControl, searchHooks)
13 DB-Tabellen in conftest.py Alle 8 fehlenden Tabellen in Base.metadata
14 delegations.py Route Entparkt
15 decision_guard ↔ Approval In engine.py integriert

Was NICHT funktioniert und neu gebaut werden muss ()

System Status Was fehlt
Phase H — Knowledge Extraction Gelöscht knowledge_sources.py, knowledge_extraction.py, knowledge_lifecycle.py — alle gelöscht (waren unverbunden)
Phase I — Integration & Workstream Gelöscht workstream_contract.py, proactive_feed.py, dashboard.py, dsgvo_export.py, onboarding.py, mcp_exposure.py, integration_tools.py — alle gelöscht (waren unverbunden)
Phase J — Self-Improvement Gelöscht self_improvement.py — gelöscht (war unverbunden)
Phase I — Frontend Gelöscht Workstream.tsx, Onboarding.tsx, MiniAppBlock.tsx, MiniAppSDK.tsx, ProactiveFeed.tsx, WorkstreamBlockRenderer.tsx, ImprovementCenter.tsx, ProposalCard.tsx, PatternInsight.tsx, SetupWizard.tsx — alle gelöscht

Phase A — Stabilität verifizieren

Task Status Verifiziert
A-VERIFY done Python compile, Dependencies, Frontend TSC+Build, App Import (485 routes), Redis, PostgreSQL, Worker Import, Production Health 200, Production Login 200
A-TEST done 8-Check Pipeline: 6/8 grün
A-PERF done Production Baseline: Health 33-74ms, Login 22-63ms
A-RESTORE done scripts/restore_test.sh existiert und ist funktionsfähig
A-DOC done docs/test-strategy.md aktualisiert

Phase B — System-Konsolidierung

Alle B-Tasks: done

Siehe detaillierte Task-Liste in früheren Versionen. Alle ~50 Tasks erledigt und verifiziert.


Alle Tasks: done


Phase F — Agents

Task Status Verifiziert
F-LOOP done agent_loop.py — ReAct-Loop, 11/11 Tests grün
F-CALL done Tool-Call-Parser
F-MAX done Max-Steps Limit + Graceful Stop
F-ERR done ErrorCategory handling
F-CTX done context_builder.py — jetzt verbunden mit agent_runner.py
F-STR done agent_stream.py — jetzt verbunden mit agent_routes.py (SSE Endpoint)
F-DEF done AgentDefinition fields + migration 0122
F-SKILL done skill_registry.py — intern verbunden
F-TOOL done agent_tools.py — jetzt verbunden mit agent_runner.py
F-PERM done agent_permissions.py — jetzt verbunden mit agent_runner.py
F-DRY done Dry-Run Mode
F-AUDIT done Audit-Log für Tool-Calls
F-AIUSE done ai_use_case.py
F-TRANS done transparency.py — jetzt verbunden mit agent_runner.py
F-DATA-POL done data_policy.py — jetzt verbunden mit agent_runner.py
F-OVERSIGHT done oversight.py — jetzt verbunden mit agent_runner.py + Migration 0128
F-APPR done approval.py + approvals.py + migration 0123
F-MEM done agent_memory Plugin (eigenes Plugin mit Routes)
F-PROACTIVE done trigger_dispatcher.py
F-WORK done agent_workstream.py — GELÖSCHT (war unverbunden), muss neu gebaut werden
F-UI-* done AgentDashboard, AgentEditor, AgentChat, AgentRunLog, AgentMonitor
F-EMAIL/CONTACT/FOLLOW/REPORT done Pre-built Agents
F-TASK-* done Unified Task System
F-TEST done 45 Tests in test_phase_f_agents.py
F-DOC done Doku aktualisiert

Anmerkung: F-WORK (agent_workstream.py) wurde gelöscht weil es unverbunden war. Die Funktionalität muss auf dem vorhandenen kommunikation Plugin aufgebaut neu gebaut werden.


Phase G — Workflows

Task Status Verifiziert
G-COND done Condition-Step in engine.py
G-WAIT done Wait/Delay-Step (persistent/resumable)
G-HTTP done HTTP-Request-Node mit SSRF-Schutz
G-MAIL done Mail-Send-Node
G-CAL done Calendar-Node
G-DMS done DMS-Node
G-AGENT done Agent-Step (Workflow → Agent)
G-APPROVAL done Approval-Step in engine.py
G-HUMAN-DEC done decision_guard.py — jetzt verbunden mit engine.py + Approval
G-WORK done workflows/workstream.py — GELÖSCHT (war unverbunden), muss neu gebaut werden
G-RETRY done Retry-Logic in engine.py
G-IDEMP done Idempotency in engine.py
G-CRON done Cron-Trigger Routes
G-WEB done Webhook-Trigger Routes
G-MAN done Manual-Trigger Routes
G-UI-* done Frontend Step-Editor
G-TEST done 43 Tests in test_phase_g_workflows.py
G-DOC done API-Doku aktualisiert

Anmerkung: G-WORK (workflows/workstream.py) wurde gelöscht weil es unverbunden war. Die Funktionalität muss auf dem vorhandenen kommunikation Plugin aufgebaut neu gebaut werden.


Phase H — Knowledge

Task Status Verifiziert
H-WIKI done Wiki Plugin (Migration 0126, Routes, Frontend) — funktioniert
H-VER done Article versioning with restore
H-LINK done Entity links on articles
H-WIKI-SEARCH done WikiSearchProvider in wiki/plugin.py on_activate registriert
H-SRC done Knowledge Source Adapter (wiki/dms/mail/communication via unified_search providers)
H-CITE done Evidence References in ask_knowledge (id, source_type, title, snippet, score, url)
H-EXT done Knowledge Extraction Pipeline (knowledge/services.py, nutzt llm_complete)
H-ENT done Entity Extraction (in extract_knowledge)
H-AUTO done Auto-Create Relationships in GraphRAG (confidence >= 0.8)
H-CONF done Confidence Scoring + Review Queue (pending/auto_created/approved/rejected)
H-EVT done Event-Driven Extraction (wiki.article.created Hook in knowledge/plugin.py)
H-DATA-LIFE done Derived-Data Lifecycle (re-extraction on wiki.article.updated Hook)
H-RET done Knowledge Retention ARQ Cron-Job (daily 05:00, 90 days, keeps approved)
H-GRAPH done GraphRAG Plugin (vorhanden, funktioniert)
H-ASK done Ask Knowledge API (/api/v1/knowledge/ask, wiki + graph_rag als Context)
H-REV done Review Queue (/api/v1/knowledge/review, Approve/Reject)
H-TEST partial ⚠️ Wiki Tests vorhanden, Knowledge Tests noch offen
H-DOC done Doku aktualisiert

Phase H ist done (12/12). Knowledge Plugin auf graph_rag + llm_client + unified_search aufgebaut. Migration 0131 deployed.


Phase I — Integration & Workstream

Status: not_started — Komplett gelöscht

Alle Phase I Module wurden als Gerüst ohne Verbindung gebaut und wieder gelöscht:

  • workstream_contract.py, proactive_feed.py, dashboard.py, dsgvo_export.py, onboarding.py, mcp_exposure.py, integration_tools.py
  • Frontend: Workstream.tsx, Onboarding.tsx, MiniAppBlock.tsx, MiniAppSDK.tsx, ProactiveFeed.tsx, WorkstreamBlockRenderer.tsx, ImprovementCenter.tsx, ProposalCard.tsx, PatternInsight.tsx, SetupWizard.tsx

Phase I muss neu gebaut werden — diesmal auf dem vorhandenen kommunikation Plugin aufbauend.


Phase J — Self-Improvement

Status: not_started — Komplett gelöscht

Das self_improvement.py Modul wurde als Gerüst ohne Verbindung gebaut und wieder gelöscht.

Phase J muss neu gebaut werden.


Migrationen

Migration Beschreibung Status
0122 Agent Definition Phase F fields Deployed
0123 Approval requests Deployed
0124 Unified task system Deployed
0125 Durable workflow run Deployed
0126 Wiki plugin Deployed
0127 Drop tasks contact_id FK Deployed
0128 AI decision records Deployed

Tests

Test-Datei Typ Status
test_audit_connections.py Integration (Import-Verifikation) 11/11 grün
test_phase_f_agents.py Mock-basiert 45/45 grün
test_phase_g_workflows.py Mock-basiert 43/43 grün
test_phase_h_wiki.py Mock-basiert Tests vorhanden
test_spike_g_durable_workflow.py Mock-basiert 7/7 grün
test_spike_i_integration_flow.py Mock-basiert 8/8 grün
test_contacts.py Integration (echte DB) 8/8 grün

Blockierte Tasks

Task Grund Lösung
Phase H Knowledge knowledge_sources/extraction/lifecycle gelöscht Neu aufbauend auf graph_rag + unified_search
Phase I Integration Komplett gelöscht Neu aufbauend auf kommunikation Plugin
Phase J Self-Improvement Done 24/24 Tests, self_improvement Plugin, Migration 0132, RLS, Frontend
F-WORK (agent_workstream) Gelöscht Neu aufbauend auf kommunikation Plugin
G-WORK (workflow workstream) Gelöscht Neu aufbauend auf kommunikation Plugin

Enterprise-Readiness Plan (2026-08-20)

Status: Alle 11 Punkte umgesetzt

# Bereich Status Details
1 RLS für 10 Tabellen Done Migration 0129, Cross-Tenant-Tests bestätigen Isolation
2 Test-DB auf Alembic Done conftest.py nutzt Alembic-Migrationen, RLS-Policies aktiv
3 Multi-Tenant Prüfung Done ORM Auto-Filter verifiziert, Cross-Tenant Integration-Tests
4 Security Audit Done SQL Injection, XSS, Auth Bypass, Secret Exposure, Dependency Audit
5 Monitoring System Dashboard Done /api/v1/system/dashboard, /api/v1/system/alerts, Frontend SystemDashboard.tsx
6 Backup Automation Done ARQ-Job, Settings (backup_enabled, interval, retention, destination), API endpoints
7 Audit Log Retention + Export Done GET /api/v1/audit-log/export (CSV/JSON), DELETE /api/v1/audit-log/retention, 365 Tage Default
8 Trash Cleanup Done ARQ-Cron-Job cleanup_expired_trash, 90 Tage Default, Audit-Log bei Löschung
9 Incident Response Runbook Done docs/incident-response-runbook.md — Server, DB, Redis, Security-Breach
10 Performance Tests Done locust/k6 Baseline (10, 50, 100 User), Bottlenecks identifiziert
11 Documentation Done README, api-documentation, monitoring, admin-guide, infrastructure, deploy-guide aktualisiert

Siehe ENTERPRISE_READINESS_PLAN.md für Details.


Phase K — EU Compliance Finalization (2026-08-21)

Status: Alle 6 Tasks umgesetzt

# Task Status Details
1 K-REG AI Registry Done GET /api/v1/compliance/ai-registry, ComplianceTab.tsx in SettingsAI.tsx
2 K-DPIA DPIA Support Done GET /api/v1/compliance/dpia-template, DPIA Export Button
3 K-INC Incident Register Done ComplianceIncident model, Migration 0133 (RLS), CRUD routes (admin-only)
4 K-RET Retention Admin Done GET/PATCH /api/v1/compliance/retention-policies, 5 policies editable
5 K-COMP-TEST Tests Done 12/12 integration tests pass
6 K-DOC Doku Done docs/compliance.md — Betriebsdoku

Tests: 12/12 passed | tsc: 0 errors | Migration: 0133 | RLS: 115 tables


Diese Datei wird vom Agent bei jedem Task-Status-Wechsel aktualisiert. Sie ist die schnelle Übersicht über den Fortschritt. Detaillierte Diskussion und Bug-Tracking laufen über Forgejo Issues.


Offene Findings (einzige gueltige Tracking-Sektion, Stand 2026-08-28)

Ab hier gilt: Nur Findings mit Live-Messung vom selben Tag. Scanner-/Plan-Aussagen ohne Beweiszaehler zaehlen nicht.

Finding Verifiziert am Messwert Ort
Cross-Plugin Import Core→Plugin erledigt 2026-08-28 (ad5601e: DSAR auf Contracts umgestellt, Checker 4→0 gegen 482 Dateien) 0 Verstöße scripts/check_cross_plugin_imports.py
God Objects >500 Z. (real, Refactoring-Programm) 2026-08-27 59 Dateien; Top: mail/routes.py 1950, mail/imap_sync.py 1148, self_improvement/services.py 1058, calendar/routes.py 1026, plugins/registry.py 907 wc -l
Frontend-Vorbestand: 8 Test-Failures erledigt 2026-08-29 (Router ×2 per QueryClientProvider+Mocks 2/2 passed; AppShell-Mock existierte bereits, Plan-Eintrag veraltet; ContactEditModal = Geister-Test nach §10 gelöscht — Komponente weg seit db4701b) Pakete 2+3, Commit 36dd7c5 src/tests/shell/Router.test.tsx
Core-FK auf Plugin-Tabelle bricht alembic check 2026-08-29 (Live-Messung: frische DB → upgrade head OK → alembic check NoReferencedTableError entity_attachments.dms_file_id → files; per Stash identisch auf clean HEAD = Vorbestand, kein Paket-6-Regression; event_outbox-Pendant im selben Lauf gefunden und FIX in 67c0dcd: models/__init__.py outbox-Import) 1 verbleibender FK: entity_attachments.dms_file_id → files (DMS-Plugin-Tabelle); Metadata kennt files nur nach DMS-Plugin-Model-Import app/models/entity_attachment.py + alembic/env.py (laedt nur app.models)
test_saved_filters 422-vs-400 gefixt 2026-08-28 _validate_entity_type wirft jetzt 422 (FastAPI-Konvention), Test passed app/routes/saved_filters.py + saved_views.py

Erledigt und archiviert: BUG-006/012/015-Teile/021/022/025035/039/069070/075078/080082/093100, ARCH-004/006/007/019/024/028/045 — Details in docs/archive/.