MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m11s
144 lines
13 KiB
Markdown
144 lines
13 KiB
Markdown
# 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`](./api/websocket.md), [`modules/workspace.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`](./api/websocket.md), [`reverse-proxy.md`](./reverse-proxy.md), [`internal/ws/hub.go`](../internal/ws/hub.go), [`internal/api/server.go`](../internal/api/server.go), [`frontend/src/lib/api.ts`](../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`](./reverse-proxy.md).
|
||
|
||
---
|
||
|
||
### FLAW-04: Volltextsuche via `LIKE '%query%'`
|
||
- **Betroffene Komponenten:** [`database.md`](./database.md), `internal/api/search.go`, `internal/api/thoughts.go`
|
||
- **Ursache:** Suchabfragen über Gedanken, Notizblöcke und Aufgaben nutzen aktuell einfaches SQL:
|
||
```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`](./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`](./architecture.md), [`database.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`](./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`](./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`](./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
|
||
- 🏗️ [Gesamtarchitektur](architecture.md)
|
||
- 💾 [Datenbank-Design & Tabellenschemata](database.md)
|
||
- 🔌 [WebSocket & Live-Protokoll](api/websocket.md)
|
||
- 🧠 [Workspace-Modul (Outliner)](modules/workspace.md)
|
||
- 🛡️ [Rechte & Freigabesystem](sharing-and-permissions.md)
|