622 lines
47 KiB
Markdown
622 lines
47 KiB
Markdown
# Repomap — Munin
|
|
|
|
Übersicht aller Dateien und Ordner mit ihrer Aufgabe.
|
|
|
|
**Sprache im Code:** Bezeichner (Pakete, Dateien, Typen, Funktionen,
|
|
Variablen, Felder, Tests, JS) sind englisch, ebenso Tabellen, Spalten,
|
|
Indizes und die Dateinamen der Migrationen. Kommentare, Doku, Hilfeseiten
|
|
und alle Texte der Weboberfläche (auch URLs wie `/hilfe`) bleiben deutsch.
|
|
Eine einmal ausgelieferte Migration wird nicht mehr umbenannt: Ihr
|
|
Dateiname steht als „angewendet“ in jeder bestehenden Datenbank.
|
|
|
|
```
|
|
munin/
|
|
├── main.go ← Einstiegspunkt. Liest die Konfiguration, prüft die
|
|
│ Pflichtangaben (SESSION_SECRET, ADMIN_TOKEN inkl.
|
|
│ Mindestlänge), öffnet Kontroll-Datenbank und
|
|
│ Workspace-Verwaltung (legt beim ersten Start den
|
|
│ Workspace "Intern" an), setzt die
|
|
│ Bestandteile zusammen (buildHandler, auch vom
|
|
│ Lasttest genutzt) und startet den Server.
|
|
│ Enthält außerdem die Server-Timeouts (gegen
|
|
│ Slowloris), die Body-Größenlimits und die
|
|
│ täglichen Aufgaben (Protokoll aufräumen,
|
|
│ Export), die über forEachWorkspace alle
|
|
│ Workspaces erreichen. Tests: main_test.go,
|
|
│ Lasttest: last_test.go (nur mit -tags last).
|
|
│
|
|
├── 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 und wohin er gehört
|
|
│ (AGENTS.md für OpenCode/Cline, CLAUDE.md mit
|
|
│ @AGENTS.md für Claude Code), slug-Regeln,
|
|
│ Muster für einzelne Aufgaben.
|
|
├── BETRIEB.md ← Betriebshandbuch für den Ernstfall: Server startet
|
|
│ nicht, Platte voll, Key oder Geheimnis geleakt,
|
|
│ Wiederherstellen aus der Sicherung, Updates.
|
|
├── AUSBAUSTUFEN.md ← Was bewusst nicht gebaut wurde: Messwerte,
|
|
│ Begründung und die Auslöser, an denen man
|
|
│ merkt, dass es Zeit dafür wird.
|
|
├── LASTTEST.md ← Lasttest: Ausführung, Schalter, Ergebnisse mit
|
|
│ Datum und ihre Grenzen.
|
|
├── REPOMAP.md ← Diese Datei.
|
|
│
|
|
├── internal/
|
|
│ │
|
|
│ ├── store/ ── Die einzige Stelle, die SQL kennt ──────────────
|
|
│ │ ├── store.go ← Öffnet eine Workspace-Datei (Open), 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.
|
|
│ │ │
|
|
│ │ ├── backend.go ← Wo liegen die Daten? Interface Backend, von
|
|
│ │ │ main.go anhand von DB_DRIVER gewählt
|
|
│ │ │ (AUSBAUSTUFEN.md 4.10).
|
|
│ │ ├── backend_sqlite.go ← Eine Datei je Workspace plus control.db (Standard).
|
|
│ │ ├── backend_postgres.go ← Ein Schema je Workspace (munin_ws_<id>) plus
|
|
│ │ │ munin_control. Feste Poolgrößen, Prüfung gegen
|
|
│ │ │ max_connections, Wiederholung beim Start,
|
|
│ │ │ Workspace-Name als Schema-Kommentar.
|
|
│ │ ├── dialect.go ← Was sich zwischen SQLite und PostgreSQL
|
|
│ │ │ unterscheidet: Platzhalter (? → $1) und
|
|
│ │ │ Migrationsordner. Bewusst klein.
|
|
│ │ ├── pool.go ← pool/transaction: Jede Abfrage läuft hier durch
|
|
│ │ │ den Dialekt — vorbei kommt keine.
|
|
│ │ │
|
|
│ │ ├── control.go ← Die zentrale Kontroll-Datenbank (OpenControl):
|
|
│ │ │ Workspaces anlegen, umbenennen, löschen, dazu die
|
|
│ │ │ Zuordnung Key-Prüfsumme → Workspace
|
|
│ │ │ (ResolveKeyWorkspace). Das ist der erste Schritt
|
|
│ │ │ jeder Anmeldung: Erst danach ist klar, welche
|
|
│ │ │ Workspace-Datei den Key kennt.
|
|
│ │ │
|
|
│ │ ├── 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 ← 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 und abgelaufene Keys
|
|
│ │ │ (expires_at, migrations/007) 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. Außerdem:
|
|
│ │ │ · snapshotEntryVersion sichert vor jeder Änderung
|
|
│ │ │ die bisherige Fassung, in derselben Transaktion;
|
|
│ │ │ EntryVersions/EntryVersionByNumber lesen sie
|
|
│ │ │ · BulkUpdateTags ändert Schlagworte vieler
|
|
│ │ │ Einträge auf einmal und überspringt IDs aus
|
|
│ │ │ fremden Projekten
|
|
│ │ │ · LinkGraph liefert Knoten und Kanten für die
|
|
│ │ │ Graph-Ansicht
|
|
│ │ │ Jede lesende Abfrage trägt notInTrash
|
|
│ │ │ (deleted_at IS NULL) — außer PutEntry, das
|
|
│ │ │ einen Eintrag im Papierkorb bewusst sieht und
|
|
│ │ │ ihn beim Überschreiben zurückholt.
|
|
│ │ │
|
|
│ │ ├── usage.go ← Kennzahlen der Nutzungsübersicht: UsageSummaries
|
|
│ │ │ (Schreibvorgänge der letzten 30 Tage aus dem
|
|
│ │ │ Protokoll, Bestand aus entries — für ein oder
|
|
│ │ │ alle Projekte in zwei Abfragen), dazu
|
|
│ │ │ MissingLinks, UntaggedEntries, RecentlyUsedKeys.
|
|
│ │ │ Keine eigene Zähltabelle.
|
|
│ │ │
|
|
│ │ ├── sysinfo.go ← Kennzahlen eines ganzen Workspace für die
|
|
│ │ │ Systemseite: Bestand, Belegung (Dialekt-
|
|
│ │ │ Methode belegung), Aktivität je Tag,
|
|
│ │ │ Schemastand.
|
|
│ │ ├── searchlog.go ← Was Agenten mit recall suchen (search_log):
|
|
│ │ │ LogSearch, SearchStatsFor, EmptySearches,
|
|
│ │ │ PruneSearchLog. Eigene Tabelle statt Protokoll,
|
|
│ │ │ 30 Tage, verschwindet mit dem Projekt.
|
|
│ │ │
|
|
│ │ ├── trash.go ← Papierkorb: MoveEntryToTrash (statt DELETE),
|
|
│ │ │ ListTrash, RestoreFromTrash, PurgeFromTrash
|
|
│ │ │ (nur was schon im Papierkorb liegt) und
|
|
│ │ │ PurgeTrash für die tägliche Aufgabe. Die Frist
|
|
│ │ │ TrashRetention (30 Tage) steht hier, damit
|
|
│ │ │ Oberfläche und Aufräumen dieselbe Zahl nutzen.
|
|
│ │ │
|
|
│ │ ├── related.go ← RelatedEntries: Nachbarn im Verweisgraphen, in
|
|
│ │ │ beide Richtungen, bis zu zwei Schritte weit
|
|
│ │ │ (Breitensuche, eine Abfrage je Richtung und Ebene).
|
|
│ │ │
|
|
│ │ ├── 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. EntryWriteDetail vermerkt bei
|
|
│ │ │ Schreibvorgängen "angelegt" oder "aktualisiert".
|
|
│ │ │ AuditFilter filtert nach Vorgang, Zugang und
|
|
│ │ │ Zeitraum; AuditActions benennt die Vorgänge.
|
|
│ │ │
|
|
│ │ ├── migrations_control/ ← je Datenbank ein Unterordner: sqlite/, postgres/
|
|
│ │ │ └── 001_init.sql ← Schema der Kontroll-Datenbank: workspaces und
|
|
│ │ │ key_index (Key-Prüfsumme → Workspace). Bewusst
|
|
│ │ │ dünn — Inhalte liegen nie hier.
|
|
│ │ │
|
|
│ │ └── migrations/ ← Schema jedes Workspace. Je Datenbank ein
|
|
│ │ Unterordner (sqlite/, postgres/) mit denselben
|
|
│ │ Dateinamen; eine neue Migration gehört in beide.
|
|
│ │ Abweichungen stehen in der Datei (z.B. 006:
|
|
│ │ Identitätsspalte in PostgreSQL).
|
|
│ │ ├── 001_init.sql ← Das Schema. Der Kopfkommentar nennt die Regeln
|
|
│ │ │ für portables SQL, an die sich alle weiteren
|
|
│ │ │ Migrationen halten sollen.
|
|
│ │ ├── 002_project_in_child_tables.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_write_budget_per_project.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.
|
|
│ │ ├── 004_change_history.sql
|
|
│ │ ← Tabelle entry_versions für abgelöste Fassungen.
|
|
│ │ Wird mit dem Eintrag gelöscht (ON DELETE CASCADE).
|
|
│ │ ├── 005_trash.sql
|
|
│ │ ← deleted_at/deleted_by an entries und ein
|
|
│ │ Teilindex nur über den Papierkorb. Löschen setzt
|
|
│ │ nur noch die Markierung; CASCADE greift erst
|
|
│ │ beim endgültigen Löschen.
|
|
│ │ ├── 006_search_log.sql
|
|
│ │ ← Tabelle search_log (recall-Anfragen, 30 Tage).
|
|
│ │ └── 007_key_expiry.sql
|
|
│ │ ← expires_at an api_keys. Leer = läuft nie ab;
|
|
│ │ abgelaufen gilt wie zurückgezogen.
|
|
│ │
|
|
│ ├── statistik/ ── Kennzahlen aller Workspaces ───────────────────
|
|
│ │ └── stats.go ← Sammler für die Systemseite: Bestand, Platz,
|
|
│ │ Schema-Stand und Aktivität der letzten 30 Tage
|
|
│ │ über alle Workspaces. Rechnet beim Aufruf der
|
|
│ │ Seite, höchstens alle 5 Minuten neu.
|
|
│ │
|
|
│ ├── workspace/ ── Welche Datei gehört zu welcher Abteilung? ──────
|
|
│ │ └── workspace.go ← Manager öffnet Workspaces über den
|
|
│ │ store.Backend bei Bedarf und hält sie offen,
|
|
│ │ jeweils mit eigenem Suchindex. Datenbank und
|
|
│ │ Index gehören zusammen, damit kein Index
|
|
│ │ versehentlich an der falschen Datei hängt.
|
|
│ │ Delete schließt einen Workspace und entfernt
|
|
│ │ seine Datei bzw. sein Schema.
|
|
│ │
|
|
│ ├── 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. Die Sitzung des Plattform-
|
|
│ │ │ Admins trägt einen Fingerabdruck des ADMIN_TOKEN,
|
|
│ │ │ damit ein neues Token sie sofort beendet.
|
|
│ │ │
|
|
│ │ ├── 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 (über internal/markdown),
|
|
│ │ │ Auflösen der [[…]]-Verweise, Textausschnitte für
|
|
│ │ │ Trefferlisten samt sicherer Hervorhebung (am
|
|
│ │ │ längsten Wort der Anfrage, das im Text steht),
|
|
│ │ │ die Zeile „Auch gesucht als: …" auslesen.
|
|
│ │ │
|
|
│ │ └── diff.go ← Zeilenvergleich zweier Fassungen (DiffLines) als
|
|
│ │ LCS ohne externe Abhängigkeit. Bei sehr langen
|
|
│ │ Einträgen weicht er auf einen groben Vergleich
|
|
│ │ aus (coarseDiff), damit Speicher und Rechenzeit
|
|
│ │ begrenzt bleiben.
|
|
│ │
|
|
│ ├── markdown/ ── Markdown nach HTML, eine Regel für alles ───────
|
|
│ │ └── markdown.go ← Der gemeinsame goldmark-Renderer für Einträge und
|
|
│ │ Hilfe: GFM (Tabellen, Checklisten,
|
|
│ │ Durchstreichen, automatische Links), Roh-HTML
|
|
│ │ gesperrt. Nur die Hilfe bekommt Überschrift-
|
|
│ │ Anker (RenderWithAnchors) — mit gefalteten
|
|
│ │ Umlauten über store.Slugify, damit die
|
|
│ │ "?"-Links der Oberfläche darauf zeigen können.
|
|
│ │
|
|
│ ├── export/ ── Sicherung als Markdown ─────────────────────────
|
|
│ │ ├── export.go ← Schreibt jeden Workspace als Ordner
|
|
│ │ │ (<workspace-id>/, mit _workspace.md) und darin
|
|
│ │ │ jedes Projekt als Ordner voller
|
|
│ │ │ Markdown-Dateien, mit allen Feldern im Dateikopf.
|
|
│ │ │ Kennt selbst keine Datenbank: Der Aufrufer gibt
|
|
│ │ │ die Workspace-Datei je Lauf mit.
|
|
│ │ │ 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.
|
|
│ │ └── zip.go ← Dieselben Dateien als ZIP für den Download.
|
|
│ │ Erst alles aus der Datenbank sammeln, dann
|
|
│ │ packen — sonst bekäme der Browser bei einem
|
|
│ │ Fehler ein halbes Archiv statt einer Fehlerseite.
|
|
│ │
|
|
│ ├── importer/ ── Gegenstück zum Export ──────────────────────────
|
|
│ │ ├── archive.go ← Liest Markdown-Dateien aus einem ZIP-Archiv, mit
|
|
│ │ │ Grenzen für Anzahl und ausgepackte Größe.
|
|
│ │ │ Übergeht _index.md, versteckte Ordner, __MACOSX.
|
|
│ │ ├── parse.go ← Kleiner Frontmatter-Leser (kein YAML-Paket):
|
|
│ │ │ Munin-Export und fremde Köpfe (Hugo, Obsidian).
|
|
│ │ │ Ohne Kopf: Bezeichner aus dem Dateinamen, Titel
|
|
│ │ │ aus der ersten Überschrift.
|
|
│ │ └── importer.go ← Importiere: Vorschau oder Schreiben, bei
|
|
│ │ belegtem Bezeichner überspringen oder
|
|
│ │ überschreiben (alter Stand in den Verlauf).
|
|
│ │ Erkennt Unverändertes und den Papierkorb.
|
|
│ │
|
|
│ ├── search/ ── Volltextsuche ──────────────────────────────────
|
|
│ │ ├── index.go ← Invertierter Index mit BM25-Bewertung. Titel zählen
|
|
│ │ │ dreifach, Schlagworte zweifach. Search für
|
|
│ │ │ Agenten, SearchFuzzy (mit Tippfehlern) für die
|
|
│ │ │ Weboberfläche.
|
|
│ │ │
|
|
│ │ ├── variants.go ← Wortvarianten je Suchwort, schwächer gewichtet
|
|
│ │ │ als ein exakter Treffer: Wortanfang ("deploy" →
|
|
│ │ │ Deployment), Endung ("deployen"), Teilwort
|
|
│ │ │ ("prozess" → Releaseprozess), Tippfehler.
|
|
│ │ │ Dazu Stoppwörter, nur auf der Anfrageseite.
|
|
│ │ │
|
|
│ │ └── 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.
|
|
│ │ Refresh liest nach jedem Schreiben aus der
|
|
│ │ Datenbank nach, damit sich überholende Schreiber
|
|
│ │ keinen alten Stand im Index hinterlassen. Jedes
|
|
│ │ Projekt hat eine eigene Sperre, damit ein langer
|
|
│ │ Aufbau nicht den ganzen Workspace aufhält.
|
|
│ │
|
|
│ ├── hilfe/ ── Eingebaute Onboarding-Hilfe ───────────────────
|
|
│ │ ├── help.go ← Liste der Hilfeseiten (Reihenfolge, Titel,
|
|
│ │ │ Zielgruppe: alle / Admins / Plattform-Admin) und
|
|
│ │ │ das Rendern mit Überschrift-Ankern. Erklärt,
|
|
│ │ │ warum die Hilfe im Binary steckt statt als
|
|
│ │ │ Einträge in der Datenbank: Sie muss zur
|
|
│ │ │ laufenden Fassung des Programms passen.
|
|
│ │ └── seiten/*.md ← Die Inhalte, je Seite eine Datei. {{SERVER}} und
|
|
│ │ {{PROJEKT}} werden beim Anzeigen ersetzt.
|
|
│ │ Tests: jede Seite verlinkt nur, was ihr Leser
|
|
│ │ sehen darf, und jeder #Abschnitt-Link trifft.
|
|
│ │
|
|
│ ├── 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. Die
|
|
│ │ │ Can…-Methoden sind die einzige Stelle, an der
|
|
│ │ │ der Workspace-Admin Rechte bekommt.
|
|
│ │ │
|
|
│ │ ├── middleware.go ← Sitzungsprüfung (inkl. "gilt der Key noch?"),
|
|
│ │ │ CSRF, Projektauflösung aus der URL, Rollenprüfung.
|
|
│ │ │ Die Sitzung trägt die Workspace-ID signiert mit
|
|
│ │ │ (sessionFromSubject); requireWorkspace hält einen
|
|
│ │ │ Plattform-Admin ohne gewählten Workspace von den
|
|
│ │ │ Projektseiten fern.
|
|
│ │ │ 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. Der
|
|
│ │ │ Plattform-Admin ohne gewählten Workspace landet
|
|
│ │ │ auf /admin/workspaces.
|
|
│ │ ├── handlers_entries.go ← Einträge auflisten, suchen, ansehen, bearbeiten.
|
|
│ │ │ Dazu Vorschau (preview), Vorschläge für [[…]]
|
|
│ │ │ (suggest), Fassungen und Vergleich, Graph-Daten
|
|
│ │ │ Schlagworte für viele Einträge (bulk-tags) und
|
|
│ │ │ viele Einträge in den Papierkorb (bulk-delete).
|
|
│ │ ├── handlers_tags.go ← Seite "Alle Schlagworte" (/p/{projekt}/tags),
|
|
│ │ │ nach Häufigkeit oder alphabetisch gruppiert. Die
|
|
│ │ │ Seitenleiste zeigt nur die häufigsten (navTagLimit
|
|
│ │ │ in server.go).
|
|
│ │ ├── handlers_usage.go ← Nutzungsseite je Projekt (/p/{projekt}/usage),
|
|
│ │ │ für jede Rolle. Die Admin-Projektliste nutzt
|
|
│ │ │ dieselben Zahlen (projectListItems).
|
|
│ │ ├── handlers_system.go ← /admin/system (Plattform-Admin): Kasten
|
|
│ │ │ "Zustand" (systemHints: Warnungen und
|
|
│ │ │ Hinweise), Aktivität, Bestand, Speicher,
|
|
│ │ │ Bereitschaft, offene Verbindungen je Workspace
|
|
│ │ │ gegen das Budget, Fassung aus den Build-Infos.
|
|
│ │ ├── handlers_overview.go
|
|
│ │ │ ← /admin/overview: Überblick des Workspace für
|
|
│ │ │ Workspace-Admins — Befunde je Projekt mit Link
|
|
│ │ │ (projectHints), Projekttabelle, Aktivität.
|
|
│ │ ├── chart.go ← Balkendiagramme der Systemseite als SVG, vom
|
|
│ │ │ Server ausgerechnet (kein JavaScript, CSP-fest).
|
|
│ │ ├── handlers_search.go ← /admin/search: Suche in allen Projekten des
|
|
│ │ │ Workspace, nach Projekt gruppiert (Plattform-
|
|
│ │ │ und Workspace-Admin; Keys bleiben bei einem
|
|
│ │ │ Projekt).
|
|
│ │ ├── handlers_ready.go
|
|
│ │ │ ← /live (Prozess läuft, für den Container-Neustart)
|
|
│ │ │ und /ready (Datenbank antwortet, sonst 503; für
|
|
│ │ │ Load-Balancer). /healthz ist Alias für /live.
|
|
│ │ │ Ins Log kommt nur der Wechsel der Bereitschaft.
|
|
│ │ ├── handlers_download.go
|
|
│ │ │ ← Download als ZIP: ein Projekt (/p/{projekt}/
|
|
│ │ │ export.zip, wer darf: Session.CanDownloadProject)
|
|
│ │ │ oder der ganze Workspace (/admin/export.zip).
|
|
│ │ │ Unabhängig von EXPORT_DIR, nichts bleibt liegen.
|
|
│ │ ├── handlers_import.go ← ZIP-Upload für den Importer (/p/{projekt}/import),
|
|
│ │ │ nur mit Rolle admin. Läuft im Server, damit der
|
|
│ │ │ Suchindex im selben Zug nachgezogen wird.
|
|
│ │ ├── handlers_trash.go
|
|
│ │ │ ← Papierkorb je Projekt: ansehen, wiederherstellen,
|
|
│ │ │ endgültig löschen, Filter nach löschendem Zugang.
|
|
│ │ │ Alles nur mit Schreibrecht, und bewusst nur hier
|
|
│ │ │ — über MCP gibt es kein Wiederherstellen.
|
|
│ │ ├── handlers_help.go ← Zeigt die eingebauten Hilfeseiten unter /hilfe
|
|
│ │ │ für jede Sitzung. Setzt vorher Server-Adresse
|
|
│ │ │ und Projekt-Bezeichner in die Beispiele ein
|
|
│ │ │ (serverURL, mit geprüftem Host-Header).
|
|
│ │ ├── handlers_workspaces.go
|
|
│ │ │ ← Workspaces auflisten, anlegen, wählen, umbenennen,
|
|
│ │ │ löschen (nur Plattform-Admin). Löscht jemand den
|
|
│ │ │ Workspace der eigenen Sitzung, wird die Auswahl
|
|
│ │ │ zurückgesetzt, statt ihn auszusperren.
|
|
│ │ ├── handlers_workspace_admins.go
|
|
│ │ │ ← Workspace-Admins (wa_…, AUSBAUSTUFEN 4.8)
|
|
│ │ │ anlegen und zurückziehen, nur durch den
|
|
│ │ │ Plattform-Admin, auf der Bearbeitungsseite des
|
|
│ │ │ Workspace.
|
|
│ │ └── handlers_admin.go ← Projekte, Zugänge, Protokollansicht, Export. 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.
|
|
│ │
|
|
│ ├── dubletten/ ── Gibt es schon? ──────────────────────────────────
|
|
│ │ └── duplicates.go ← Vor dem Anlegen: Gibt es einen Eintrag mit
|
|
│ │ ähnlichem Titel? (Pruefe) Gemeinsames Präfix
|
|
│ │ zählt nicht, Suchbegriffe nur wörtlich, die
|
|
│ │ gemeinsamen Wörter kommen mit. Genutzt von
|
|
│ │ remember (MCP: candidates statt schreiben,
|
|
│ │ force legt doch an) und vom Formular "Neuer
|
|
│ │ Eintrag" (nur Hinweis).
|
|
│ │
|
|
│ └── 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.
|
|
│ │
|
|
│ ├── curated.go ← Kuratierte Einträge ändert remember nur mit
|
|
│ │ override_curated und Begründung; die Änderung
|
|
│ │ landet auf der Nutzungsseite zur Prüfung.
|
|
│ │
|
|
│ └── tools.go ← Die sechs Werkzeuge: recall, get, list, related,
|
|
│ remember, forget. Alte Einträge tragen in den
|
|
│ Antworten "stale": true. 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.
|
|
│ ├── preview.js ← Reiter "Schreiben"/"Vorschau" im Editor. Holt das
|
|
│ │ HTML vom Server, statt Markdown im Browser zu
|
|
│ │ rendern. Ohne JavaScript bleibt das Formular
|
|
│ │ unverändert nutzbar.
|
|
│ ├── autocomplete.js ← Vorschläge nach "[[" im Textfeld, per Tastatur
|
|
│ │ und Maus bedienbar.
|
|
│ ├── graph.js ← Selbst geschriebene Kräftesimulation und
|
|
│ │ SVG-Darstellung für die Graph-Ansicht. Zoom zieht
|
|
│ │ Knoten auseinander, Namen nur, wo Platz ist.
|
|
│ │ Texte nur über textContent, nie innerHTML.
|
|
│ ├── tagfilter.js ← Filterfeld auf der Seite "Alle Schlagworte".
|
|
│ ├── similar.js ← "Gibt es schon?" unter dem Titel eines neuen
|
|
│ │ Eintrags, fragt nach einer Tipp-Pause.
|
|
│ ├── unsaved.js ← Rückfrage beim Verlassen eines Formulars mit
|
|
│ │ ungespeicherten Änderungen (data-unsaved).
|
|
│ ├── shortcuts.js ← "/" Suche, "n" neu, "e" bearbeiten, Strg+Enter
|
|
│ │ speichern. Ziel steht als data-shortcut im
|
|
│ │ Template, die Rechte also dort.
|
|
│ └── img/ ← Logo in mehreren Fassungen (hell, dunkel, ohne
|
|
│ Hintergrund). Die Anmeldeseite wählt je nach
|
|
│ Farbschema die passende.
|
|
│
|
|
├── scripts/
|
|
│ ├── backup.sh ← Sichert Kontroll-Datenbank und jeden Workspace
|
|
│ │ einzeln in einen zeitgestempelten Ordner:
|
|
│ │ SQLite über sqlite3 .backup, PostgreSQL
|
|
│ │ (DB_DRIVER=postgres) über pg_dump je Schema,
|
|
│ │ mit Lesbarkeitsprüfung. RETENTION_DAYS räumt
|
|
│ │ alte Sicherungen auf.
|
|
│ ├── sicherung-pruefen.sh ← Probe einer Sicherung (SQLite oder PostgreSQL):
|
|
│ │ zurückspielen, Munin darauf starten, /ready
|
|
│ │ und Zahl der Workspaces prüfen, aufräumen.
|
|
│ │ Lehnt eine nicht leere Probe-Datenbank ab.
|
|
│ └── postgres-dev-init.sql ← Legt im Entwicklungs-Container die Datenbank
|
|
│ munin_test für die Tests an.
|
|
│
|
|
├── docker-compose.dev.yml ← PostgreSQL für die Entwicklung (Sortierung C,
|
|
│ max_connections 200, nur localhost).
|
|
│
|
|
└── data/ ← Daten (nicht im Repository, als Volume gemountet)
|
|
├── control.db ← Kontroll-Datenbank: Workspaces, Key-Zuordnung
|
|
└── workspaces/
|
|
└── <workspace-id>.db ← Eine Datei je Workspace
|
|
```
|
|
|
|
## 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.
|
|
|
|
Ausnahme ist der Lasttest `last_test.go`: Er braucht Zeit und lastet die
|
|
Maschine aus und läuft deshalb nur auf Wunsch, mit
|
|
`go test -tags last -run TestLast -v .` (Schalter für Agentenzahl, Dauer usw.
|
|
oben in der Datei). Er misst über echtes HTTP und MCP drei Aufstellungen (ein
|
|
Projekt, mehrere Projekte in einem Workspace, mehrere Workspaces) und prüft am
|
|
Ende, dass kein Schreibvorgang verloren ging und der Suchindex aktuell ist.
|
|
Ausführung, Schalter und Ergebnisse: `LASTTEST.md`.
|
|
|
|
| Datei | Schwerpunkt |
|
|
|---|---|
|
|
| `internal/auth/auth_test.go` | Signatur, Manipulation, Ablauf, CSRF, Key-Form, Rollen, Cookie-Flags, Rate-Limiting, X-Forwarded-For |
|
|
| `internal/store/sysinfo_test.go` | Bestand samt Key-Regeln, Belegung wächst mit dem Inhalt, Aktivität je Tag und Weg mit Zeitgrenze, Schemastand (auch gegen PostgreSQL) |
|
|
| `internal/stats/stats_test.go` | Summen über zwei Workspaces, alle 30 Tage gefüllt, aktive und ruhige Projekte, Zwischenspeicher für 5 Minuten |
|
|
| `internal/web/chart_test.go` | Balken gestapelt von der Nulllinie, Skala 1-2-5, kleine Werte sichtbar, Tooltip, Bytes lesbar |
|
|
| `internal/store/usage_test.go` | Zählung nach Art und Weg samt Zeitfenster, Bestand ohne Papierkorb, Verweise ins Leere, aktive Zugänge |
|
|
| `internal/store/trash_test.go` | **Jeder lesende Weg blendet den Papierkorb aus** (Einzelabruf, Liste mit Filtern, Suchindex/Export, Zählen, Schlagworte, Rückverweise, Graph, Sammelbearbeitung); Wiederherstellen, Zurückholen durch Überschreiben, Konflikt, endgültiges Löschen, Frist |
|
|
| `internal/store/store_test.go` | Migrationen, Slug-Prüfung, Abschottung, Dublettenvermeidung, optimistisches Sperren, Verweise, Protokoll, Änderungshistorie |
|
|
| `internal/store/control_test.go` | Kontroll-Datenbank: Standard-Workspace bleibt stabil, Auflistung, Zuordnung Key → Workspace |
|
|
| `internal/store/dialect_test.go` | Platzhalter-Umwandlung (Text, Kommentare, Bezeichner), **beide Migrationsordner gleich**, Schemanamen, Textkonstanten |
|
|
| `internal/store/postgres_test.go` | Nur mit `MUNIN_TEST_PG`: **Schema-Vergleich SQLite ↔ PostgreSQL**, Schema je Workspace samt Kommentar, Abschottung, Verbindungsbudget. Schaltet außerdem `testStore`/`testControl` auf PostgreSQL um |
|
|
| `internal/importer/postgres_test.go` | Nur mit `MUNIN_TEST_PG`: Export als ZIP aus einem PostgreSQL-Workspace und Import in einen zweiten, Einträge danach gleich |
|
|
| `internal/workspace/workspace_test.go` | Manager hält Dateien offen, trennt Workspaces, schließt sauber |
|
|
| `internal/entry/entry_test.go` | HTML-Escaping, Verweisauflösung, Textausschnitte, sichere Hervorhebung |
|
|
| `internal/markdown/markdown_test.go` | GFM-Erweiterungen, kein Roh-HTML und keine `javascript:`-Links, Anker nur in der Hilfe |
|
|
| `internal/entry/diff_test.go` | Zeilenvergleich, Grenzfälle, Ausweichen bei sehr langen Einträgen |
|
|
| `internal/search/search_test.go` | Bewertung, Wortanfang, Aktualisieren und Entfernen |
|
|
| `internal/search/variants_test.go` | Wortanfang neben exaktem Treffer, Komposita, Endungen, Stoppwörter, Tippfehler nur in SearchFuzzy |
|
|
| `internal/search/bench_test.go` | Laufzeit einer Suche bei 10.000 Einträgen und 20.000 Wörtern |
|
|
| `internal/search/registry_test.go` | Index folgt der Datenbank, auch wenn sich Schreiber überholen, ein Eintrag gleich wieder gelöscht wird oder während des Aufbaus geschrieben wird; ein langsamer Aufbau bremst andere Projekte nicht |
|
|
| `internal/mcpserver/concurrency_test.go` | Viele Agenten schreiben über MCP gleichzeitig denselben Eintrag: keine verlorene Fassung, Index aktuell |
|
|
| `internal/store/related_test.go` | Verwandte Einträge über zwei Ebenen, Papierkorb als Sackgasse, Grenze für veraltete Einträge, übernommene Zeitstempel beim Anlegen |
|
|
| `internal/importer/importer_test.go` | **Rundlauf Export → ZIP → Import ohne Verlust**, auch mit dem ZIP aus dem Download, Vorschau, Überspringen/Überschreiben, Papierkorb, doppelte Bezeichner, fremde Köpfe, Grenzen des Archivs |
|
|
| `internal/export/export_test.go` | Dateikopf und YAML-Maskierung, Aufräumen verwaister Dateien, Stabilität über mehrere Läufe |
|
|
| `internal/web/download_test.go` | ZIP-Download: Inhalt und Protokolleintrag, nur Admins, kein fremdes Projekt, Workspace-ZIP für Plattform- und Workspace-Admin, sichere Dateinamen |
|
|
| `internal/web/import_test.go` | Import mit Vorschau und Übernehmen, Suchindex nachgezogen, ein Protokolleintrag, nur für admin, Body-Grenze je Route |
|
|
| `internal/web/web_test.go` | Anmeldung, zurückgezogene Keys, CSRF, **Projektabschottung**, **Workspace-Trennung**, Rollen, Header, Hilfeseiten samt eingesetzter Adresse, `/live` und `/ready` (503 ohne Datenbank, ohne Einzelheiten) |
|
|
| `internal/web/ui_test.go` | Veraltete Einträge (Marke, Filter, Nutzungsseite), „Auch verwandt“, „Gibt es schon?“ (auch: nicht mit Leserecht, nicht bei vorhandenem Bezeichner), Formular-Attribute und Kürzel je Rolle, Protokollfilter, Systemseite (nur Plattform-Admin), **Ablaufdatum für Keys** (Ende der Sitzung), Suche über alle Projekte (nicht für Projektzugänge) |
|
|
| `internal/duplicates/duplicates_test.go` | Wann ein gemeinsames Titel-Präfix aus dem Vergleich fällt |
|
|
| `internal/mcpserver/mcpserver_test.go` | Werkzeugbestand, `instructions`, Dublettenvermeidung (gleicher Bezeichner und ähnlicher Titel), Synonyme, Suchprotokoll, Konflikte, Rollen, **Projektabschottung** |
|
|
| `internal/store/searchlog_test.go` | Kennzahlen und Anfragen ohne Treffer, Zeitfenster, Aufräumen, verschwindet mit dem Projekt |
|
|
| `internal/help/help_test.go` | Seitenliste und Dateien passen zusammen, interne Links führen zu vorhandenen Seiten, kein Roh-HTML |
|
|
|
|
## 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
|
|
→ store/control.go (ResolveKeyWorkspace) → Workspace
|
|
→ workspace/workspace.go (Get) → dessen Datei und Suchindex
|
|
→ store/keys.go (ResolveKeyHash) → 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 über Wortvarianten (variants.go)
|
|
→ store/entries.go (EntryBySlug) Treffer nachladen
|
|
→ entry/entry.go (Snippet) Textausschnitt je Treffer
|
|
→ store/searchlog.go (LogSearch) Anfrage und Trefferzahl
|
|
→ 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
|
|
→ dubletten (Pruefe) neuer Bezeichner, ähnlicher Titel?
|
|
→ candidates zurück, nichts schreiben
|
|
→ curated.go (isCurated) kuratiert und kein override_curated?
|
|
→ judgment_required, nichts schreiben
|
|
→ 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 (Refresh) 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 (Refresh)
|
|
→ store/audit.go (LogAudit)
|
|
→ Weiterleitung zur Detailansicht
|
|
```
|