Files
MindSift/Doku/flaws.md
T
2026-10-26 19:54:51 +01:00

13 KiB
Raw Blame History

Bekannte Schwachstellen, Risiken & Technische Schulden (Flaws)

Diese Datei dokumentiert eine ehrliche und schonungslose technische Analyse aller bekannten Limitierungen, Architektur-Kompromisse, Skalierungsrisiken und UX-Schwachstellen in MindSift. Sie dient als verbindlicher Backlog für zukünftige Refactorings und Architektur-Meilensteine.


📌 Übersicht & Risikomatrix

ID Bereich Problem / Schwachstelle Schweregrad Risiko im Produktivbetrieb
FLAW-01 Workspace Sync Kein echtes CRDT/OT – Lock-Heartbeat-Modell mit Last-Write-Wins Gelöst (v2.1) Behoben durch Yjs CRDT-Engine & blockbasierte kollaborative Bearbeitung
FLAW-02 Storage Dual-Storage Drift (SQLite vs. Dateisystem) Obsolet / Gelöst Kein Risiko: MindSift nutzt Pure-DB (keine .md-Dateien auf Platte)
FLAW-03 Netzwerk / WS Proxy-Inkompatibilität bei WebSocket-Upgrades & Polling-Fallback Gelöst (v2.2) Behoben durch Dual-Transport SSE-Fallback & globale Singleton-Verbindung
FLAW-04 Datenbank / Suche Volltextsuche via LIKE '%query%' statt SQLite FTS5 Mittel Performance-Einbrüche bei wachsenden Notizbeständen (>10.000 Blöcke)
FLAW-05 Frontend DOM Fehlende Virtualisierung im Outliner-Baum Mittel Frame-Drops und Lags bei Dokumenten mit >500 Blöcken auf Mobilgeräten
FLAW-06 SQLite Concurrency Single-Writer-Bottleneck bei hohem parallelem Schreibaufkommen Mittel SQLITE_BUSY Fehler bei vielen gleichzeitigen Team-Nutzern
FLAW-07 Benachrichtigungen Fehlende Dead-Letter-Queue für Webhook- & E-Mail-Alarme Niedrig Verlorene Benachrichtigungen bei temporärem Ausfall von Drittsystemen
FLAW-08 Sicherheit / Auth Statische JWT-Tokens ohne rotierende Refresh-Tokens & Blacklisting Niedrig Keine sofortige Sitzungsinvalidierung bei Rechteentzug
FLAW-09 Outliner UX Kein hierarchischer Undo/Redo-Stack für Strukturmutationen Mittel Versehentliches Löschen/Einrücken von Blöcken nicht per Ctrl+Z rückgängig machbar

🔍 Detaillierte Analyse & Maßnahmen

FLAW-01: [GELÖST / IMPLEMENTIERT] Yjs Outliner CRDT & Multi-User Kollaboration

  • Status: Gelöst via Yjs CRDT Engine (Lösung i)
  • Betroffene Komponenten: api/websocket.md, modules/workspace.md, internal/ws/hub.go, internal/storage/sqlite.go, frontend/src/lib/crdt.ts, frontend/src/features/workspace/WorkspaceView.tsx
  • Ursache (Historisch): MindSift nutzte ein kooperatives Dokumenten-Lease-Locking mit Last-Write-Wins. Bei parallelem Zugriff oder Netztrennung konnten Änderungen überschrieben werden.
  • Implementierte Lösung (Yjs CRDT):
    1. Block-Level Yjs CRDT: Implementierung eines vollwertigen CRDT-Modells mit Y.Doc. Die Blockreihenfolge wird über ein Y.Array<string> (Block-IDs) konfliktfrei mit dem YATA-Algorithmus aufgelöst. Inhalte und Metadaten sind in Y.Map gekapselt.
    2. Vollständige Eliminierung des Lock-Managers: Alle pessimistischen Dokumentensperren (isLockedByOther, Lease-Heartbeats, 423 Locked) wurden restlos entfernt. Mehrere Nutzer können zeitgleich an denselben Absätzen und Blöcken arbeiten.
    3. Proxy-Resiliente Dual-Transport-Architektur:
      • Primary: Gorilla WebSocket (/api/v1/ws/docs/:id) mit binärer Übertragung.
      • Fallback: 4-Sekunden-Watchdog schaltet bei blockiertem WebSocket-Upgrade automatisch auf HTTP-Transport um: Downstream via Server-Sent Events (GET /api/v1/sync/docs/:id/stream) und Upstream via HTTP POST (POST /api/v1/sync/docs/:id/update).
    4. Offline-First via IndexedDB: Lokale Eingaben landen sofort und unterbrechungsfrei in y-indexeddb. Daten gehen auch bei Netzwerkabbrüchen nicht verloren.
    5. Asynchroner DocRoom-Hub & Debounced SQLite-Persistenz: In-Memory DocRoom pro aktivem Dokument; SQLite-Schreibvorgänge laufen debounced (2.5 Sekunden Inaktivität oder Raum-Austritt) rein asynchron im Hintergrund, ohne API-Threads oder Tastenschläge zu blockieren.
    6. UI Status-Indikator: Dezent im Header visualisiert: Grün ("Live (WebSocket)"), Gelb ("Eingeschränkt (HTTP-Proxy-Modus)") und Grau ("Offline (Lokal gesichert)").

FLAW-02: [OBSOLET / GELÖST] Dual-Storage Drift (SQLite vs. Dateisystem)

  • Status: Entkräftet & Behoben (Pure Database Architecture)
  • Klarstellung: In früheren Entwürfen wurde angedacht, Notizen zusätzlich als Markdown-Dateien auf dem Dateisystem zu spiegeln. MindSift speichert Notizen und Blöcke jedoch zu 100% relational in SQLite (documents und document_blocks).
  • Verifikation:
    • Es existieren keine Markdown-Dateien auf der Festplatte.
    • Das Go-Backend schreibt keine Dateien in ein Notizverzeichnis.
    • Unnötige Verzeichniserstellungen (docDir/workspace) wurden aus internal/storage/sqlite.go vollständig entfernt.
  • Ergebnis: Es gibt keinen Dual-Storage und somit keinen File-Drift oder externe Dateikonflikte. Notizen verhalten sich vollständig transaktional und konsistent innerhalb der SQLite-Datenbank.

FLAW-03: [GELÖST / IMPLEMENTIERT] Proxy-Inkompatibilität bei WebSocket-Upgrades & Polling-Fallback

  • Status: Gelöst & Vollständig Implementiert (Dual-Transport SSE Fallback & Proxy-Resilienz)
  • Betroffene Komponenten: api/websocket.md, reverse-proxy.md, internal/ws/hub.go, internal/api/server.go, frontend/src/lib/api.ts
  • Problemursache: Corporate Proxies (z.B. Zscaler, BlueCoat, strikte Firewalls) oder unvollständig konfigurierte Reverse-Proxies verwerfen den HTTP-Upgrade-Header Upgrade: websocket (NS_ERROR_CONNECTION_REFUSED, Status 1006 / 1002). Früher fiel der Client dabei auf 2-Sekunden-Polling zurück, was zu Verzögerungen und Last führte.
  • Implementierte Lösung:
    1. Unidirektionaler Server-Sent Events (SSE) Fallback:
      • Neuer Streaming-Endpunkt GET /api/v1/events im Go-Backend mit Content-Type: text/event-stream, X-Accel-Buffering: no und 15s Heartbeat-Pings (: ping\n\n).
      • Standard HTTPS Ports (443/80), wodurch kein Proxy den Datenstrom blockieren kann.
    2. Hub-Multiplexing:
      • ws.Hub verwaltet WebSocket- und SSE-Clients parallel in gemeinsamen Räumen (rooms, userRooms, allClients).
      • Sämtliche Events (BroadcastToAll, BroadcastToUser, BroadcastRoom) werden transparent an beide Transportarten verteilt.
    3. REST-Parität für Outbound CRDT & Awareness:
      • PUT /api/v1/documents/:id/crdt: Akzeptiert Delta-Updates (update) und broadcastet sie an alle Peers.
      • POST /api/v1/documents/:id/awareness und DELETE /api/v1/documents/:id/awareness: Übertragen Blockfokus und Cursor ohne WebSocket.
      • POST /api/v1/events/rooms: Erlaubt SSE-Clients dynamisches Room-Joining.
    4. Frontend Realtime Transport Manager (frontend/src/lib/api.ts):
      • Automatischer 3,5s-Watchdog: Schlägt das WebSocket-Upgrade fehl oder wird Code 1006/1002 gemeldet, wechselt MindSift nahtlos auf EventSource('/api/v1/events').
      • Grüner/türkiser Statusindikator im Header zeigt Live (WebSocket) bzw. Live (Server-Sent Events / Proxy-Stream).
    5. Reverse-Proxy-Handbuch:
      • Vollständige Produktions-Konfigurationen für Nginx, Traefik, Caddy, Cloudflare und Apache unter reverse-proxy.md.

FLAW-04: Volltextsuche via LIKE '%query%'

  • Betroffene Komponenten: database.md, internal/api/search.go, internal/api/thoughts.go
  • Ursache: Suchabfragen über Gedanken, Notizblöcke und Aufgaben nutzen aktuell einfaches SQL:
    WHERE content LIKE '%' || ? || '%'
    
  • Folgen: LIKE '%...' kann B-Tree-Indizes prinzipbedingt nicht nutzen und erzwingt einen sequentiellen Full Table Scan. Bei Datenbanken mit Zehntausenden Blöcken führt dies zu CPU-Spitzen und trägen Suchreaktionen.
  • Empfohlene Lösung:
    1. Einrichten einer virtuellen SQLite FTS5-Tabelle (document_blocks_fts) mit Tokenizer (unicode61 oder trigram).
    2. Automatische Synchronisierung über SQLite Trigger (AFTER INSERT, AFTER UPDATE, AFTER DELETE).

FLAW-05: Fehlende Virtualisierung im Outliner-Baum

  • Betroffene Komponenten: modules/workspace.md, frontend/src/features/workspace/WorkspaceView.tsx
  • Ursache: Jeder Block im Notizbaum wird als eigenes DOM-Element gerendert. Enthält ein Dokument 500 bis 2000 Blöcke (z. B. lange Projektjournale oder Besprechungsprotokolle), befinden sich tausende verschachtelte div- und contenteditable-Elemente gleichzeitig im DOM.
  • Folgen: Auf älteren Smartphones oder bei schnellem Tippen kommt es zu spürbaren Input-Latenzen und Ruckeln beim Scrollen.
  • Empfohlene Lösung:
    1. Implementierung einer virtuellen Fensterung (Virtual Windowing) für Blöcke außerhalb des Sichtbereichs.
    2. Automatisches Einklappen (Collapsing) tiefer Hierarchieebenen beim initialen Laden langer Dokumente.

FLAW-06: SQLite Concurrency & Single-Writer-Bottleneck

  • Betroffene Komponenten: architecture.md, database.md, internal/storage/sqlite.go
  • Ursache: Obwohl SQLite mit PRAGMA journal_mode = WAL; parallele Leser erlaubt, kann zu jedem Zeitpunkt immer nur ein einziger Thread schreiben. Bei intensiver gleichzeitiger Nutzung (z. B. 20 Teammitglieder tippen gleichzeitig in Listen und Notizen, während im Hintergrund Webhook-Events einlaufen) können Schreib-Transaktionen aufeinander warten.
  • Folgen: Risiko von database is locked (SQLITE_BUSY)-Fehlern, wenn der Busy-Timeout überschritten wird.
  • Empfohlene Lösung:
    1. Setzen von PRAGMA busy_timeout = 5000; (5 Sekunden Wartezeit vor Fehlerwurf).
    2. Kapselung aller Schreibzugriffe über einen dedizierten Go-Channel / Single-Worker-Pool oder Migration auf PostgreSQL bei größeren Installationen.

FLAW-07: Fehlende Dead-Letter-Queue für Benachrichtigungen

  • Betroffene Komponenten: modules/tasks.md, internal/scheduler/alarm_ticker.go
  • Ursache: Der Alarm-Ticker überprüft minütlich fällige Aufgaben und sendet Webhooks bzw. E-Mails. Schlägt der HTTP-Post an den externen Webhook fehl (z. B. Zielserver vorübergehend offline), wird der Fehler lediglich ins Log geschrieben.
  • Folgen: Der Alarm wird nicht erneut versucht und die Benachrichtigung geht dauerhaft verloren.
  • Empfohlene Lösung:
    1. Einführung einer Tabelle notification_outbox mit Spalten retry_count, next_retry_at, status.
    2. Exponentieller Backoff (1m, 5m, 15m, 1h) und Status-Markierung (failed, delivered).

FLAW-08: Statische JWT-Tokens ohne Blacklisting

  • Betroffene Komponenten: architecture.md, internal/api/auth.go
  • Ursache: Nach dem Login wird ein JWT ausgestellt, das clientseitig im localStorage gespeichert wird. Der Server prüft lediglich die kryptografische Signatur des Tokens, führt aber keine serverseitige Sitzungsliste.
  • Folgen: Wird einem Benutzer die Berechtigung entzogen oder ändert er sein Passwort, bleibt ein bereits ausgestellter Token bis zu seinem Ablaufdatum (z. B. 7 Tage) gültig.
  • Empfohlene Lösung:
    1. Einführung einer revoked_tokens-Tabelle oder einer token_version-Spalte in der users-Tabelle.
    2. Implementierung von kurzlebigen Access-Tokens (15 Min.) in Verbindung mit rotierenden Refresh-Tokens in httpOnly-Cookies.

FLAW-09: Kein hierarchischer Undo/Redo-Stack im Outliner

  • Betroffene Komponenten: modules/workspace.md, frontend/src/features/workspace/WorkspaceView.tsx
  • Ursache: Standardmäßiges Browser-Ctrl+Z funktioniert nur innerhalb eines einzelnen aktiven Textfelds (contenteditable / Textarea). Strukturelle Aktionen – wie das Verschieben von Blöcken via Drag & Drop, Einrücken (Tab), Ausrücken (Shift+Tab) oder das Löschen ganzer Unterbäume – sind im Standard-Browser-Undo-Stack nicht enthalten.
  • Folgen: Verschiebt ein Nutzer versehentlich einen Block samt 10 Unterpunkten, kann dies nicht mit Ctrl+Z rückgängig gemacht werden.
  • Empfohlene Lösung:
    1. Implementierung eines expliziten In-Memory Command-Stacks im Frontend (Command-Pattern: execute() / undo()).
    2. Aufzeichnung aller Block-Mutationen (Insert, Indent, Move, Delete) mit Revert-Payloads.

🧭 Verwandte Dokumente