munin/REPOMAP.md
2026-09-15 20:36:26 +02:00

278 lines
18 KiB
Markdown

# Repomap — Munin
Übersicht aller Dateien und Ordner mit ihrer Aufgabe.
```
munin/
├── main.go ← Einstiegspunkt. Liest die Konfiguration, prüft die
│ Pflichtangaben (SESSION_SECRET, ADMIN_TOKEN inkl.
│ Mindestlänge), öffnet die Datenbank, setzt die
│ Bestandteile zusammen und startet den Server.
│ Enthält außerdem die Server-Timeouts (gegen
│ Slowloris), die Body-Größenlimits und die
│ tägliche Aufräumaufgabe fürs Protokoll.
│
├── go.mod / go.sum ← Moduldefinition und Prüfsummen der Abhängigkeiten.
│
├── Dockerfile ← Zweistufiger Bau: Stufe 1 kompiliert mit dem
│ Go-Compiler, Stufe 2 packt nur das fertige Binary
│ in ein minimales Alpine-Abbild. Läuft als
│ gewöhnlicher Benutzer, mit Healthcheck und Volume
│ für die Datenbank.
│
├── .env.example ← Vorlage für .env, mit Erklärung jeder Variable.
├── .gitignore / .dockerignore ← Schließen Datenbank, Binary und Geheimnisse aus.
│
├── README.md ← Anleitung: Schnellstart, MCP einrichten, Werkzeuge,
│ Konfiguration, Sicherheit, Betrieb.
├── AGENTEN-GUIDE.md ← Wie man KI-Werkzeuge anbindet und anspricht,
│ damit sie den Speicher tatsächlich nutzen:
│ Projektprompt zum Übernehmen, slug-Regeln,
│ Muster für einzelne Aufgaben.
├── AUSBAUSTUFEN.md ← Was bewusst nicht gebaut wurde: Messwerte,
│ Begründung und die Auslöser, an denen man
│ merkt, dass es Zeit dafür wird.
├── REPOMAP.md ← Diese Datei.
│
├── internal/
│ │
│ ├── store/ ── Die einzige Stelle, die SQL kennt ──────────────
│ │ ├── store.go ← Öffnet die Datenbank, setzt die nötigen PRAGMAs
│ │ │ (WAL, busy_timeout, foreign_keys) und wendet die
│ │ │ Migrationen an. Erklärt auch, warum der Treiber
│ │ │ modernc.org/sqlite ist: Er ist reines Go und
│ │ │ erlaubt damit ein statisches Binary.
│ │ │
│ │ ├── models.go ← Die Datentypen (Project, APIKey, Entry, …), die
│ │ │ Fehlerwerte (ErrNotFound, ErrConflict,
│ │ │ ErrDuplicate) sowie ID- und Zeitbehandlung.
│ │ │ NewID erzeugt sortierbare IDs in Go statt per
│ │ │ AUTOINCREMENT — das hält den Umstieg auf eine
│ │ │ andere Datenbank offen.
│ │ │
│ │ ├── projects.go ← Workspace und Projekte: anlegen, lesen, ändern,
│ │ │ löschen. Enthält ValidSlug und Slugify sowie
│ │ │ SetProjectWriteLimit für die eigene Obergrenze
│ │ │ eines Projekts.
│ │ │
│ │ ├── keys.go ← API-Keys. ResolveKeyHash ist der heißeste Pfad im
│ │ │ Programm (jeder MCP-Request) und deshalb ein
│ │ │ indizierter Lookup über die Prüfsumme, keine
│ │ │ Schleife. Zurückgezogene Keys gelten als nicht
│ │ │ vorhanden, der Datensatz bleibt fürs Protokoll.
│ │ │
│ │ ├── entries.go ← Einträge. PutEntry legt an ODER aktualisiert —
│ │ │ der Bezeichner entscheidet, und genau das ist der
│ │ │ Mechanismus gegen Dubletten. Enthält das
│ │ │ optimistische Sperren (ExpectedVersion) und das
│ │ │ Nachladen der Schlagworte in einer Abfrage statt
│ │ │ einer pro Eintrag.
│ │ │
│ │ ├── links.go ← Erkennt [[bezeichner]] im Inhalt und ersetzt sie
│ │ │ beim Rendern. Das ersetzt die frühere
│ │ │ Ordnerhierarchie als Struktur zwischen Einträgen.
│ │ │
│ │ ├── audit.go ← Protokoll: schreiben, lesen, aufräumen. Die
│ │ │ Aufbewahrungsfrist ist nicht nur eine Frage des
│ │ │ Plattenplatzes — das Protokoll enthält Adressen
│ │ │ und Zugangsnamen.
│ │ │
│ │ └── migrations/
│ │ ├── 001_init.sql ← Das Schema. Der Kopfkommentar nennt die Regeln
│ │ │ für portables SQL, an die sich alle weiteren
│ │ │ Migrationen halten sollen.
│ │ ├── 002_projekt_in_nebentabellen.sql
│ │ ← Führt project_id auch in entry_tags und
│ │ entry_links. Eine bewusste Redundanz gegen zwei
│ │ gemessene Bremsen: das Schlagwortmenü lief über
│ │ alle Einträge (6,7 ms bei jedem Seitenaufruf),
│ │ und bei den Rückverweisen kippte durch ein
│ │ ORDER BY die Verknüpfungsreihenfolge
│ │ (3,1 ms statt 9 µs).
│ │ └── 003_schreibbudget_je_projekt.sql
│ │ ← Spalte write_limit_hourly an projects. Vorgabe 0
│ │ heißt "keine eigene Angabe" — dann gilt der
│ │ Standard aus WRITE_LIMIT_HOURLY. So muss die
│ │ Migration keine Daten anfassen.
│ │
│ ├── auth/ ── Wer ist das? ────────────────────────────────────
│ │ ├── doc.go ← Erklärt die beiden Wege hinein (API-Key im Header,
│ │ │ Sitzungs-Cookie im Browser) und was hier bewusst
│ │ │ NICHT liegt: die Frage, was jemand darf.
│ │ │
│ │ ├── keys.go ← Erzeugen, Prüfsumme bilden, Form prüfen. Enthält
│ │ │ die Rollen (read/write/admin) und die Begründung,
│ │ │ warum SHA-256 statt bcrypt richtig ist.
│ │ │
│ │ ├── session.go ← Signierte Sitzungs-Cookies (HMAC-SHA256). Der
│ │ │ Cookie trägt nur eine Behauptung ("ich bin Key X"),
│ │ │ nie den Key selbst.
│ │ │
│ │ ├── csrf.go ← CSRF-Tokens aus Sitzung und Geheimnis, ohne Zustand
│ │ │ auf dem Server. Der Kopfkommentar erklärt den
│ │ │ Angriff, gegen den das schützt.
│ │ │
│ │ ├── cookies.go ← Setzt HttpOnly, SameSite und Secure an einer
│ │ │ Stelle. Secure ist ohne Konfiguration an, außer
│ │ │ bei localhost — damit die lokale Entwicklung über
│ │ │ http weiter funktioniert.
│ │ │
│ │ ├── ratelimit.go ← Zwei Begrenzungen auf einem gemeinsamen
│ │ │ Fixed-Window-Zähler: RateLimiter sperrt eine
│ │ │ Adresse nach zu vielen Fehlversuchen,
│ │ │ WriteBudget deckelt die schreibenden
│ │ │ MCP-Aufrufe je Projekt und Stunde. Letzteres
│ │ │ hängt am Projekt und nicht am Key — ein
│ │ │ Key-Limit ließe sich mit einem zweiten Key
│ │ │ umgehen.
│ │ │
│ │ └── trustedproxy.go ← Entscheidet, wann X-Forwarded-For geglaubt wird:
│ │ nur bei einer über TRUSTED_PROXY hinterlegten
│ │ Absenderadresse. Ohne Konfiguration wird der
│ │ Header ignoriert — er ist frei fälschbar.
│ │
│ ├── entry/ ── Wie sieht ein Eintrag aus? ─────────────────────
│ │ └── entry.go ← Markdown nach HTML (goldmark, ohne Roh-HTML),
│ │ Auflösen der [[…]]-Verweise, Textausschnitte für
│ │ Trefferlisten samt sicherer Hervorhebung.
│ │
│ ├── export/ ── Sicherung als Markdown ─────────────────────────
│ │ └── export.go ← Schreibt jedes Projekt als Ordner voller
│ │ Markdown-Dateien, mit allen Feldern im Dateikopf.
│ │ Gedacht als Git-fähige Sicherung: Ein Lauf ohne
│ │ inhaltliche Änderungen fasst keine Datei an und
│ │ erzeugt deshalb auch keinen Diff. Verwaiste
│ │ Dateien gelöschter Einträge werden entfernt,
│ │ fremde Dateien im Zielordner bleiben unberührt.
│ │
│ ├── search/ ── Volltextsuche ──────────────────────────────────
│ │ ├── index.go ← Invertierter Index mit BM25-Bewertung. Titel zählen
│ │ │ dreifach, Schlagworte zweifach. Enthält die
│ │ │ Präfix-Rückfallebene: Ohne sie findet "deploy"
│ │ │ den "Deployment-Prozess" nicht — bei einer
│ │ │ KI-Anfrage ein stiller Fehltreffer.
│ │ │
│ │ └── registry.go ← Ein Index je Projekt, verzögert aufgebaut. Getrennte
│ │ Indizes statt eines gemeinsamen mit Filter: So wird
│ │ die Abschottung strukturell, und die BM25-Werte
│ │ werden nicht über Projektgrenzen hinweg verzerrt.
│ │
│ ├── web/ ── HTTP: Browser und API ──────────────────────────
│ │ ├── context.go ← Session-Typ (wer greift zu, was darf er) und die
│ │ │ Ablage im Request-Context. CanAccessProject ist
│ │ │ die Mandantentrennung der Weboberfläche.
│ │ │
│ │ ├── middleware.go ← Sitzungsprüfung (inkl. "gilt der Key noch?"),
│ │ │ CSRF, Projektauflösung aus der URL, Rollenprüfung.
│ │ │ Dazu die allgemeinen Schutzschichten
│ │ │ SecurityHeaders und LimitBody sowie APIAuth für
│ │ │ den MCP-Endpunkt.
│ │ │
│ │ ├── server.go ← Der Server-Typ, alle Routen und das Rendern der
│ │ │ Templates (erst in einen Puffer, dann senden —
│ │ │ sonst gäbe es bei einem Fehler eine halbe Seite
│ │ │ mit Status 200).
│ │ │
│ │ ├── handlers_auth.go ← Anmelden, Abmelden, Startseite.
│ │ ├── handlers_entries.go ← Einträge auflisten, suchen, ansehen, bearbeiten.
│ │ └── handlers_admin.go ← Projekte, Zugänge, Protokollansicht. Die
│ │ Bearbeitungsseite je Projekt (Name,
│ │ Beschreibung, Schreibbudget, Löschen) liegt
│ │ hier — bewusst eine Seite je Projekt statt
│ │ eines Auswahlfelds über alle: Nur so stehen die
│ │ tatsächlichen Werte in den Feldern, und ein
│ │ Speichern ohne Änderung überschreibt nichts.
│ │
│ └── mcpserver/ ── Die Hauptschnittstelle ────────────────────────
│ ├── server.go ← Handler und Transport (Streamable HTTP), ein
│ │ MCP-Server je Projekt. Enthält den Text der
│ │ `instructions` — das wirksamste Stellrad für die
│ │ Qualität der Antworten, weil er bei jedem Client
│ │ im Kontext steht.
│ │
│ └── tools.go ← Die fünf Werkzeuge: recall, get, list, remember,
│ forget. Jedes ist eine Closure über genau ein
│ Projekt und kann strukturell nicht an ein anderes.
│
├── templates/ ← HTML der Weboberfläche. partials.html enthält die
│ gemeinsamen Blöcke "kopf" und "fuss", die jede
│ Seite einbindet — kein verschachteltes Layout.
│
├── static/
│ ├── style.css ← Das gesamte Aussehen, ohne Framework. Farben als
│ │ Variablen ganz oben, helle und dunkle Darstellung.
│ └── confirm.js ← Bestätigungsabfrage vor dem Löschen. Liest das
│ Attribut data-confirm, statt onclick="" im HTML zu
│ verwenden — Inline-JavaScript verbietet die
│ Content-Security-Policy.
│
└── data/ ← Datenbank (nicht im Repository, als Volume gemountet)
└── munin.db
```
## Tests
Jedes Package hat seine Tests direkt daneben. Sie laufen mit `go test ./...` ohne
weitere Vorbereitung — die Datenbanktests legen jeweils eine echte SQLite-Datei
in einem Testverzeichnis an, damit auch Migrationen und Fremdschlüssel geprüft
werden.
| Datei | Schwerpunkt |
|---|---|
| `internal/auth/auth_test.go` | Signatur, Manipulation, Ablauf, CSRF, Key-Form, Rollen, Cookie-Flags, Rate-Limiting, X-Forwarded-For |
| `internal/store/store_test.go` | Migrationen, Slug-Prüfung, Abschottung, Dublettenvermeidung, optimistisches Sperren, Verweise, Protokoll |
| `internal/entry/entry_test.go` | HTML-Escaping, Verweisauflösung, Textausschnitte, sichere Hervorhebung |
| `internal/search/search_test.go` | Bewertung, Präfix-Rückfallebene, Aktualisieren und Entfernen |
| `internal/export/export_test.go` | Dateikopf und YAML-Maskierung, Aufräumen verwaister Dateien, Stabilität über mehrere Läufe |
| `internal/web/web_test.go` | Anmeldung, zurückgezogene Keys, CSRF, **Projektabschottung**, Rollen, Header |
| `internal/mcpserver/mcpserver_test.go` | Werkzeugbestand, `instructions`, Dublettenvermeidung, Konflikte, Rollen, **Projektabschottung** |
## Datenfluss: Agent ruft Wissen ab
```
KI-Werkzeug POST /mcp { "method": "tools/call", "params": { "name": "recall", … } }
→ web/middleware.go (SecurityHeaders, LimitBody)
→ web/middleware.go (APIAuth) Key → Prüfsumme → Lookup → Projekt + Rolle
in den Context; Fehlversuche in die Sperre
→ mcpserver/server.go (ServeHTTP) Projekt aus dem Context → dessen MCP-Server
→ mcpserver/tools.go (recall)
→ search/registry.go Index dieses Projekts (beim ersten Mal
aus der Datenbank aufgebaut)
→ search/index.go (Search) BM25, bei Bedarf Präfix-Rückfallebene
→ store/entries.go (EntryBySlug) Treffer nachladen
→ entry/entry.go (Snippet) Textausschnitt je Treffer
→ JSON-Antwort mit Trefferliste und Hinweis zum weiteren Vorgehen
```
## Datenfluss: Agent legt Wissen ab
```
KI-Werkzeug POST /mcp { … "name": "remember", "arguments": { "slug": …, "content": … } }
→ web/middleware.go (APIAuth) wie oben
→ mcpserver/tools.go (remember)
→ requireWrite() Rolle read wird hier abgewiesen
→ store/entries.go (PutEntry) eine Transaktion:
· Bezeichner vorhanden? → aktualisieren
· ExpectedVersion prüfen → ggf. Konflikt
· Schlagworte und Verweise ersetzen
· Herkunft (agent) und Zugangsname setzen
→ search/registry.go (Update) Index nachziehen
→ store/audit.go (LogAudit) wer, was, wann
→ Antwort: Bezeichner, neue Fassung, angelegt oder aktualisiert
```
## Datenfluss: Mensch bearbeitet im Browser
```
Browser POST /p/plattform/e/deployment/save
→ web/middleware.go (SecurityHeaders, LimitBody)
→ web/middleware.go (requireSession) Cookie prüfen → Key aus der Datenbank
laden → gilt er noch? → CSRF-Token prüfen
→ web/middleware.go (requireProject) Projekt aus der URL; fremdes Projekt → 404
→ web/middleware.go (requireWrite) Rollenprüfung
→ web/handlers_entries.go (handleEntrySave)
→ store/entries.go (PutEntry) Herkunft: human, Autor: Key-Name
→ search/registry.go (Update)
→ store/audit.go (LogAudit)
→ Weiterleitung zur Detailansicht
```