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.
Spracherkennung für OCR-Texte und Qualitätsbewertung (Konfidenzwert), um
schlechte OCR-Ergebnisse kenntlich zu machen. Letztes Ticket vor QA-03.
- ocr/language.go: RecognizeWithLanguageAndConfidence erkennt ein Bild
einzeln je Kandidatensprache (deu/eng) im Tesseract-TSV-Modus — die
Sprache mit höherem Konfidenzwert gewinnt, derselbe Lauf liefert den
Konfidenzwert direkt mit.
- search: neue Felder ocr_language/ocr_confidence (Migrationen 0006/0007,
gleiches ALTER-Muster wie SRC-05), in Document/Result gespiegelt.
Client.AttachmentsBelowConfidence filtert gezielt auf niedrige
Konfidenz, schließt Dokumente ohne OCR-Anhang aus.
- Regressionsbug gefunden und behoben: reindex.go (SRC-09) kannte die
neuen OCR-Spalten nicht, Reindex wäre mit "unknown column"
fehlgeschlagen.
Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-10-PRUEFPROTOKOLL.md):
1. TestRecognizeWithLanguageAndConfidence_MultilingualCorpus: deutsches
und englisches Testbild real korrekt als deu/eng erkannt.
2. TestRecognizeWithLanguageAndConfidence_DegradedImageLowersConfidence:
künstliche Verschlechterung senkt Konfidenz real von 91,76 auf 28,21.
3. TestAttachmentsBelowConfidence_QueryReturnsExpectedResults: Abfrage
unterhalb Schwelle liefert real genau die erwarteten 2 von 4 Treffern.
Kein Umbau: Search/Facets/SearchWithFilters/Index/Delete-Verhalten sonst
unverändert, dedup/indexworker/storage/crypto/encstorage/savedsearch
unverändert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
Facetten- und Filter-API für Suche (Absender, Zeitraum, Postfach,
Anhangstyp, Tag), mandantengetrennt, mit UND-Verknüpfung mehrerer Filter.
- migrations/0002..0005: vier nummerierte ALTER-Migrationen für die neuen
Facettenfelder (Manticore erlaubt nur eine Spalte je ALTER-Anweisung),
EnsureSchema wendet sie idempotent nach.
- fields.go: FacetFields-Whitelist, einzige zulässige Facettendimensionen.
- facets.go: Client.Facets nutzt Manticores strukturierte aggs.terms/
aggs.range-API, kein dynamischer SQL-Klauselbau. Filter kombinieren als
zusätzliche equals-Klauseln in derselben bool.must-Liste wie der
Tenant-Filter. Zeitraum-Facette über feste Buckets via aggs.range.
Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-05-PRUEFPROTOKOLL.md):
1. TestFacets_CountsMatchActualHits: Facettenzahlen stimmen real mit der
tatsächlichen Treffermenge überein.
2. TestFacets_ThreeFiltersCombineWithAND: 3 kombinierte Filter schränken
4 Dokumente real auf genau 1 verbleibenden Treffer ein.
3. TestFacets_TenantSeparation: Facetten eines Mandanten enthalten real
keine Werte eines anderen.
Kein Umbau: Search/Delete/Index-Verhalten aus SRC-01/SRC-03 unverändert,
dedup/indexworker/storage/crypto/encstorage unverändert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
Indexierungs-Worker, der neu archivierte Mails asynchron in den
Manticore-Index (SRC-01) einpflegt und Löschungen nachzieht.
- indexworker/queue.go: Postgres-Jobqueue (mail_index_jobs), FOR UPDATE
SKIP LOCKED, Stale-Lock-Wiedervorlage bei Worker-Absturz, arithmetischer
Backoff bei Fail (kein String-Concat für Intervalle) — Konvention aus
dms/internal/jobqueue (FDN-04), hier bewusst ohne DLQ (nicht Bestandteil
der Akzeptanzkriterien dieser Kachel).
- indexworker/worker.go: RunOnce verarbeitet index-/delete-Jobs über
search.Client.
- search: minimale Erweiterung um Client.Delete und deterministisches
DocumentID(tenantSlug, messageID), damit Index/Delete für dieselbe Mail
immer dasselbe Dokument treffen.
Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-02-PRUEFPROTOKOLL.md):
1. TestDequeue_WorkerCrashMidRunLosesNoJob: simulierter Worker-Absturz,
Job wird nach Ablauf der Stale-Lock-Frist real erneut zugestellt.
2. TestDeleteJob_RemovesMailFromSearchResults: Lösch-Job entfernt Mail
nachweislich aus Suchtreffern.
3. TestConsistency_DatabaseAndIndexMatchOnSample: DB-Job-Status und
Index-Inhalt stichprobenartig real abgeglichen.
Zusätzlich TestIndexJob_MakesMailSearchable für Akzeptanzkriterium 1.
Kein Umbau: storage/crypto/encstorage/dedup unverändert, bestehendes
SRC-01-Verhalten unverändert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ