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.
106 lines
4.8 KiB
Markdown
106 lines
4.8 KiB
Markdown
# 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.json` → `bewusst_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).
|