Files
nexarch/mail/internal/search/fields.go
T
sysopsandClaude Sonnet 5 c5bdde0cc8 SRC-02: indexierungs-worker-synchronisierung
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
2026-08-31 09:47:50 +02:00

46 lines
2.0 KiB
Go

// Package search implementiert SRC-01: den Manticore-RT-Suchindex für
// Mail-Inhalte. Bekannter Fehler vermeiden (siehe known-issues-archivmail.md
// #11/#12): archivmail baute WHERE-Klauseln und teils Spalten-/Tabellennamen
// dynamisch über fmt.Sprintf/strings.Join zusammen. Für dieses Paket gilt
// verbindlich: Spalten- und Tabellennamen kommen AUSSCHLIESSLICH aus den
// Konstanten dieser Datei, nirgendwo sonst im Paket wird ein Feld- oder
// Tabellenname zur Laufzeit zusammengesetzt. Suchanfragen laufen über die
// strukturierte Manticore-HTTP-JSON-API (Query/Insert-Sub, kein
// String-Zusammenbau von SQL), nicht über die SQL-Schnittstelle.
package search
import "hash/fnv"
// IndexName ist der einzige Ort, an dem der Manticore-Indexname als
// Literal steht.
const IndexName = "mail_documents"
// Statische Feld-Whitelist des mail_documents-Index (muss deckungsgleich
// mit migrations/0001_mail_documents.sql bleiben).
const (
FieldTenantSlug = "tenant_slug"
FieldMessageID = "message_id"
FieldSubject = "subject"
FieldBody = "body"
FieldAttachmentText = "attachment_text"
FieldSentAt = "sent_at"
)
// searchableTextFields sind die Volltextfelder, über die eine Suchanfrage
// läuft (Akzeptanzprüfung 3: Volltextsuche liefert erwartete Treffer).
var searchableTextFields = []string{FieldSubject, FieldBody, FieldAttachmentText}
// DocumentID berechnet deterministisch die Manticore-Dokument-ID aus
// Mandant und Message-ID (FNV-1a, 64 Bit). Deterministisch statt einer
// separat vergebenen ID, damit Re-Indexierung (Index) und Löschung
// (Delete) für dieselbe Mail immer dieselbe Dokument-ID referenzieren,
// ohne dass der Aufrufer sie zwischenspeichern muss (SRC-02: Löschungen
// müssen ohne zusätzlichen Zustand nachgezogen werden können).
func DocumentID(tenantSlug, messageID string) uint64 {
h := fnv.New64a()
_, _ = h.Write([]byte(tenantSlug))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(messageID))
return h.Sum64()
}