munin/internal/mcpserver/curated.go
2026-10-19 10:13:56 +02:00

72 lines
3.1 KiB
Go

package mcpserver
// Kuratierte Einträge vor unbemerktem Überschreiben schützen.
//
// „Kuratiert" heißt: Ein Mensch hat den Inhalt geprüft. Schreibt ein Agent
// einen solchen Eintrag um, ist der neue Text nicht mehr geprüft — die
// Markierung fällt deshalb weg. Das ist richtig. Früher geschah es aber
// still: Der Agent erfuhr nicht, dass er geprüftes Wissen ersetzt hatte, und
// kein Mensch bekam es mit.
//
// Jetzt läuft es in zwei Schritten, wie bei den Dubletten (duplicates.go):
//
// 1. remember auf einen kuratierten Eintrag schreibt NICHT, sondern meldet
// judgment_required mit einer Erklärung.
// 2. Hält der Agent die Änderung für nötig, ruft er erneut auf, mit
// override_curated: true und einer Begründung (reason). Dann wird
// geschrieben, die Markierung fällt weg, und das Protokoll vermerkt den
// Vorgang samt Begründung. Die Nutzungsseite listet solche Einträge, bis
// ein Mensch sie wieder als kuratiert markiert.
//
// # Warum nicht ganz sperren?
//
// Weil kuratiertes Wissen auch veralten kann. Ein Agent, der nachweislich
// sieht, dass ein Server umgezogen ist, soll das festhalten dürfen. Die
// Hürde soll Fehlgriffe aufhalten, nicht berechtigte Korrekturen.
//
// Zwischen Prüfung und Schreiben könnte ein Mensch den Eintrag gerade
// kuratieren. Dieses Zeitfenster ist Millisekunden breit; der Verlauf hält
// den geprüften Stand in jedem Fall fest.
import (
"context"
"errors"
"strings"
"munin/internal/store"
"munin/internal/workspace"
)
// isCurated prüft, ob unter slug ein kuratierter Eintrag liegt.
// Gibt es den Eintrag nicht, ist er auch nicht kuratiert.
func isCurated(ctx context.Context, ws *workspace.Workspace, project store.Project, slug string) (bool, error) {
e, err := ws.Store.EntryBySlug(ctx, project.ID, slug)
if errors.Is(err, store.ErrNotFound) {
return false, nil
}
if err != nil {
return false, err
}
return e.Curated, nil
}
// curatedHint ist die Antwort an den Agenten, wenn er einen kuratierten
// Eintrag ohne override_curated ändern will. Sie sagt ausdrücklich, was er
// tun soll — wie alle Meldungen an Modelle.
const curatedHint = "Nichts geschrieben: Dieser Eintrag ist kuratiert — ein Mensch hat ihn geprüft. " +
"Geprüftes Wissen hat Vorrang vor eigenen Annahmen. Ändere ihn nur, wenn er nachweislich nicht " +
"mehr stimmt (etwa weil du gerade das Gegenteil beobachtet hast). Dann denselben Aufruf mit " +
"\"override_curated\": true und unter \"reason\" einer kurzen Begründung wiederholen. " +
"Die Markierung \"kuratiert\" fällt dabei weg, und ein Mensch prüft die Änderung. " +
"Sonst: nichts ändern, oder die Ergänzung in einem eigenen Eintrag festhalten und mit [[slug]] verweisen."
// checkJustification stellt sicher, dass ein override_curated eine
// Begründung trägt. Ohne sie wüsste der prüfende Mensch nicht, warum der
// geprüfte Stand ersetzt wurde.
func checkJustification(in rememberInput) error {
if in.OverrideCurated && strings.TrimSpace(in.Reason) == "" {
return errors.New("override_curated braucht eine Begründung unter \"reason\": " +
"Warum stimmt der kuratierte Stand nicht mehr?")
}
return nil
}