# 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 ```