Files
nexarch/mail/docs/SRC-11-PRUEFPROTOKOLL.md
sysops 4fdb424b23 feat(mail): SRC-11 geschlossener FacetField-Typ statt Whitelist-Liste
fields.go: neuer Typ FacetField mit vier geschlossenen Konstanten
(FacetFieldSender/Mailbox/AttachmentType/Tag). IsValid() entscheidet
über ein erschöpfendes switch/case statt eine []string-Liste zu
durchsuchen — genau der aus known-issues-archivmail.md #12 und
known-issues-archivdms.md #10 bekannte Fehler (dynamische Tabellen-/
Feldnamen nur durch eine fragile Whitelist-Funktion abgesichert) wird
damit strukturell vermieden: ein vergessener Listeneintrag kann nichts
mehr durchlassen, weil es keine durchsuchte Liste mehr gibt.
ParseFacetField ist die einzige vorgesehene Konstruktionsstelle für
FacetField aus einer externen Zeichenkette.

facets.go: FacetFilter.Field ist jetzt FacetField statt string,
buildFilteredMust prüft f.Field.IsValid() statt Listenmitgliedschaft
(isFacetField entfernt, es gibt keine Liste mehr, die die Entscheidung
trifft).

Alle Pflichtprüfungen mit echten Nachweisen: unbekannte/erfundene
Facettenfelder werden abgelehnt, alle vier realen Facettenfelder
funktionieren weiterhin, ein FacetField-Wert per direkter
Typkonvertierung (nicht über ParseFacetField) wird trotzdem zuverlässig
abgelehnt (Akzeptanzkriterium 2: Whitelist ist nicht die einzige
Absicherung), automatisiertes Code-Review bestätigt kein fmt.Sprintf in
facets.go/fields.go. Entscheidung dokumentiert: Mail-eigene
Implementierung, keine geteilte Utility mit dem DMS-Board (Prüfprotokoll).

Keine Regression, insbesondere mail/internal/savedsearch (Konsument von
FacetFilter) unverändert grün — go build/go vet/golangci-lint clean,
gesamtes Mail-Modul regressionsfrei getestet.
2026-09-01 14:01:34 +02:00

4.8 KiB

SRC-11 — Feld-Whitelist-Query-Builder für Suchindex-Zugriff: Prüfprotokoll

Datum: 2026-09-01 Host: 192.168.1.131 (Build/Test/Lint), rsync + ssh Paket: mail/internal/search (fields.go, facets.go)

Umsetzung

Grundlage war bereits vorhanden (SRC-01/SRC-05): statische FieldXxx- Konstanten in fields.go, Suchanfragen ausschließlich über Manticores strukturierte HTTP-JSON-API (kein SQL-String-Zusammenbau). Was fehlte, war Akzeptanzkriterium 2: die Facetten-Whitelist war eine []string (FacetFields), gegen die isFacetField per Schleife prüfte — eine klassische "Whitelist-Funktion", genau das Muster, das known-issues-archivmail.md #12 und known-issues-archivdms.md #10 als unzureichend benennen (ein vergessener/fehlerhafter Eintrag in der Liste lässt unbemerkt alles durch).

Neu: FacetField ist ein eigener, geschlossener Typ (fields.go). FacetField.IsValid() entscheidet über ein erschöpfendes switch/case auf den vier Konstanten (FacetFieldSender, FacetFieldMailbox, FacetFieldAttachmentType, FacetFieldTag) — keine Liste mehr, die durchsucht wird und die man vergessen könnte zu pflegen. ParseFacetField ist die einzige vorgesehene Stelle, um aus einer externen Zeichenkette (z. B. künftig ein HTTP-Query-Parameter) ein FacetField zu machen. FacetFilter.Field ist jetzt FacetField statt string. buildFilteredMust (einzige Stelle, die Filter-Feldnamen in eine Suchanfrage einbaut) prüft f.Field.IsValid() statt Listenmitgliedschaft.

isFacetField (die alte Listenfunktion) ist entfernt — es gibt keine Liste mehr, die die Zulässigkeitsentscheidung trifft, nur noch das switch/case in IsValid().

Pflichtprüfung 1: Versuch, ein nicht in der Whitelist enthaltenes Feld anzufragen, wird abgewiesen statt stillschweigend ignoriert

TestBuildFilteredMust_RejectsUnknownField (search/src11_test.go): zwei Fälle — ein reales Suchfeld, das aber KEIN Facettenfeld ist (tenant_slug), und ein frei erfundenes Feld (inkl. eines absichtlich SQL-injection-artigen Strings, um zu zeigen, dass er nicht einmal in die Fehlermeldung unverarbeitet "verschwindet", sondern sauber als Fehler zurückkommt) — beide werden mit Fehler abgelehnt, kein stillschweigendes Ignorieren. TestBuildFilteredMust_AcceptsAllWhitelistedFields stellt sicher, dass die Prüfung nicht zu streng ist (alle vier realen Facettenfelder funktionieren).

Ergebnis: BESTANDEN.

Pflichtprüfung 2: Code-Review bestätigt: kein dynamischer Spalten-/Tabellenname wird per String-Zusammenbau erzeugt

TestNoDynamicFieldNameConstruction (search/src11_test.go): automatisiertes Code-Review — facets.go und fields.go enthalten in keiner Codezeile (Kommentarzeilen ausgenommen, dort nur erklärender Text über den zu vermeidenden Fehler) ein fmt.Sprintf. Ergänzt um TestFacetField_ClosedSetEvenViaDirectTypeConversion (Akzeptanzkriterium 2 wörtlich: die Whitelist ist NICHT die einzige Absicherung — selbst ein FacetField-Wert, der nicht über ParseFacetField entstanden ist, sondern durch direkte Typkonvertierung, wird von IsValid() zuverlässig abgelehnt) und TestParseFacetField_OnlyAcceptsKnownStrings.

Ergebnis: BESTANDEN.

Akzeptanzkriterien

  1. Spalten-/Feldnamen für dynamische Query-Teile stammen ausschließlich aus statischen Konstanten bzw. einem geschlossenen Enum/Switch-Typ: FacetField + die vier FacetFieldXxx-Konstanten, durch Pflichtprüfung 2 belegt.
  2. Whitelist ist nicht die einzige Absicherung: IsValid() ist ein erschöpfendes switch/case, keine Listen-Iteration mehr — durch Pflichtprüfung 1+2 belegt.
  3. Entscheidung dokumentiert: Mail-eigene Implementierung, keine geteilte Utility mit dem DMS-Board: siehe unten.

Zu Akzeptanzkriterium 3

Diese Kachel implementiert den Query-Builder ausschließlich innerhalb von mail/internal/search — keine neue geteilte Utility mit dem DMS-Board angelegt. Konsistent mit der bereits im Ticket-Prompt genannten, vorab getroffenen Entscheidung (nexarch-state.jsonbewusst_nicht_zentralisiert), Suche/OCR zwischen Mail und DMS nicht zu zentralisieren.

Build/Vet/Lint/Test — Gesamtmodul

go build ./...    → OK
go vet ./...      → OK
golangci-lint run ./... → 0 issues
go test ./... -p 1 (TEST_TENANT_DSN, TEST_MANTICORE_URL gesetzt) → alle Pakete ok

Keine Regression — insbesondere mail/internal/savedsearch (Konsument von search.FacetFilter) unverändert grün: die Typänderung von Field string zu Field FacetField ist für bestehende Aufrufer, die den untypisierten String-Konstanten FieldSender usw. übergeben, verhalten sich unverändert (Go erlaubt die implizite Umwandlung untypisierter Konstanten).

Ergebnis

SRC-11 erfüllt alle Akzeptanzkriterien mit echten, ausgeführten Nachweisen. Freigeschaltet: QA-04 (zusammen mit ARC-06).