47 KiB
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 Kontroll-Datenbank und
│ Workspace-Verwaltung (legt beim ersten Start den
│ Workspace "Intern" an), setzt die
│ Bestandteile zusammen (baueHandler, 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 fuerJedenWorkspace 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.
│ │ │
│ │ ├── speicher.go ← Wo liegen die Daten? Interface Speicher, von
│ │ │ main.go anhand von DB_DRIVER gewählt
│ │ │ (AUSBAUSTUFEN.md 4.10).
│ │ ├── speicher_sqlite.go ← Eine Datei je Workspace plus control.db (Standard).
│ │ ├── speicher_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.
│ │ ├── dialekt.go ← Was sich zwischen SQLite und PostgreSQL
│ │ │ unterscheidet: Platzhalter (? → $1) und
│ │ │ Migrationsordner. Bewusst klein.
│ │ ├── verbindung.go ← pool/transaktion: 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 nichtImPapierkorb
│ │ │ (deleted_at IS NULL) — außer PutEntry, das
│ │ │ einen Eintrag im Papierkorb bewusst sieht und
│ │ │ ihn beim Überschreiben zurückholt.
│ │ │
│ │ ├── nutzung.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.
│ │ │
│ │ ├── systemstand.go ← Kennzahlen eines ganzen Workspace für die
│ │ │ Systemseite: Bestand, Belegung (Dialekt-
│ │ │ Methode belegung), Aktivität je Tag,
│ │ │ Schemastand.
│ │ ├── suchprotokoll.go ← Was Agenten mit recall suchen (search_log):
│ │ │ LogSearch, SearchStatsFor, EmptySearches,
│ │ │ PruneSearchLog. Eigene Tabelle statt Protokoll,
│ │ │ 30 Tage, verschwindet mit dem Projekt.
│ │ │
│ │ ├── papierkorb.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_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.
│ │ ├── 004_aenderungshistorie.sql
│ │ ← Tabelle entry_versions für abgelöste Fassungen.
│ │ Wird mit dem Eintrag gelöscht (ON DELETE CASCADE).
│ │ ├── 005_papierkorb.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_suchprotokoll.sql
│ │ ← Tabelle search_log (recall-Anfragen, 30 Tage).
│ │ └── 007_key_ablauf.sql
│ │ ← expires_at an api_keys. Leer = läuft nie ab;
│ │ abgelaufen gilt wie zurückgezogen.
│ │
│ ├── statistik/ ── Kennzahlen aller Workspaces ───────────────────
│ │ └── statistik.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.Speicher 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 (grobDiff), 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 (RenderMitAnkern) — 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 ──────────────────────────
│ │ ├── archiv.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.
│ │ │
│ │ ├── varianten.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 ───────────────────
│ │ ├── hilfe.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_nutzung.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" (systemHinweise: Warnungen und
│ │ │ Hinweise), Aktivität, Bestand, Speicher,
│ │ │ Bereitschaft, offene Verbindungen je Workspace
│ │ │ gegen das Budget, Fassung aus den Build-Infos.
│ │ ├── handlers_ueberblick.go
│ │ │ ← /admin/overview: Überblick des Workspace für
│ │ │ Workspace-Admins — Befunde je Projekt mit Link
│ │ │ (projektHinweise), Projekttabelle, Aktivität.
│ │ ├── diagramm.go ← Balkendiagramme der Systemseite als SVG, vom
│ │ │ Server ausgerechnet (kein JavaScript, CSP-fest).
│ │ ├── handlers_suche.go ← /admin/search: Suche in allen Projekten des
│ │ │ Workspace, nach Projekt gruppiert (Plattform-
│ │ │ und Workspace-Admin; Keys bleiben bei einem
│ │ │ Projekt).
│ │ ├── handlers_bereitschaft.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_papierkorb.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_hilfe.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? ──────────────────────────────────
│ │ └── dubletten.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.
│ │
│ ├── kuratiert.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".
│ ├── aehnlich.js ← "Gibt es schon?" unter dem Titel eines neuen
│ │ Eintrags, fragt nach einer Tipp-Pause.
│ ├── ungespeichert.js ← Rückfrage beim Verlassen eines Formulars mit
│ │ ungespeicherten Änderungen (data-ungespeichert).
│ ├── tastenkuerzel.js ← "/" Suche, "n" neu, "e" bearbeiten, Strg+Enter
│ │ speichern. Ziel steht als data-kuerzel 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/systemstand_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/statistik/statistik_test.go |
Summen über zwei Workspaces, alle 30 Tage gefüllt, aktive und ruhige Projekte, Zwischenspeicher für 5 Minuten |
internal/web/diagramm_test.go |
Balken gestapelt von der Nulllinie, Skala 1-2-5, kleine Werte sichtbar, Tooltip, Bytes lesbar |
internal/store/nutzung_test.go |
Zählung nach Art und Weg samt Zeitfenster, Bestand ohne Papierkorb, Verweise ins Leere, aktive Zugänge |
internal/store/papierkorb_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/dialekt_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/varianten_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/gleichzeitig_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/oberflaeche_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/dubletten/dubletten_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/suchprotokoll_test.go |
Kennzahlen und Anfragen ohne Treffer, Zeitfenster, Aufräumen, verschwindet mit dem Projekt |
internal/hilfe/hilfe_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 (varianten.go)
→ store/entries.go (EntryBySlug) Treffer nachladen
→ entry/entry.go (Snippet) Textausschnitt je Treffer
→ store/suchprotokoll.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
→ kuratiert.go (istKuratiert) 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