Commit Graph
29 Commits
Author SHA1 Message Date
sysopsandClaude Sonnet 5 bd1f52648c feat(mail): ING-02 POP3-Server (RFC 1939) mit Zustandsmaschine
Vollständiger POP3-Server von Grund auf implementiert, analog zum
bestehenden IMAP-Server (ING-01): TCP-Listener mit einer Goroutine
pro Verbindung, CRLF/Byte-Stuffing-sichere Response-Writer,
Zustandsmaschine (Authorization/Transaction/Update), Kommandos USER,
PASS, STAT, LIST, RETR, DELE, QUIT.

Zentrale Designentscheidungen:
- USER antwortet immer +OK (RFC-konform), Prüfung erst bei PASS
- Fehlgeschlagene Anmeldung liefert für unbekannten Benutzer und
  falsches Passwort denselben generischen Text (keine
  Informationspreisgabe, Akzeptanzkriterium 3)
- DELE markiert Nachrichten nur sitzungslokal; store.Delete wird
  strukturell ausschließlich in QUIT (Transaction -> Update)
  aufgerufen, wodurch ein Verbindungsabbruch ohne QUIT nichts
  endgültig löscht (Pflichtprüfung 3)

Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt:
Zustandsübergangs-Tests gegen realen TCP-Server, manuelle Session
mit Python-Standardbibliothek poplib (echtes Transkript im
Prüfprotokoll), automatisierter Test für DELE-ohne-QUIT.
Zusätzlich: 20 parallele reale Sessions (Akzeptanzkriterium 1),
vollständiger RETR+DELE+QUIT-Zyklus (Akzeptanzkriterium 2).

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~24 Pakete)
regressionsfrei getestet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-09-01 00:42:36 +02:00
sysopsandClaude Sonnet 5 5dcfa36f99 IMP-07: mehrfach-postfach-verwaltung-pro-tenant
Verwaltung mehrerer Postfächer je Mandant: Anlage, getrennte
Abrufkonfiguration pro Postfach.

- store.go: Postgres-Store, beliebig viele unabhängige Postfächer je
  Mandant, eigene Abrufparameter (Intervall, Host/Port/Benutzername,
  Ordnerauswahl) je Postfach. Passwort nie im Klartext gespeichert —
  Wiederverwendung von mail/internal/crypto (ARC-02, unverändert) für
  Envelope-Encryption. List filtert strikt nach tenant_slug,
  Update/Delete streng auf tenant_slug+id beschränkt.

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-07-PRUEFPROTOKOLL.md):
1. TestList_TwoTenantsWithMultipleMailboxesSeeOnlyOwn: zwei Mandanten
   sehen real ausschließlich eigene Postfächer.
2. TestDelete_DoesNotAffectSiblingMailboxes: Löschen real ohne
   Auswirkung auf Geschwister-Postfächer.
3. TestUpdate_ConfigChangeDoesNotAffectOtherMailboxes: Änderung real
   isoliert auf ein Postfach beschränkt.

Kein Umbau: mail/internal/crypto unverändert wiederverwendet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-09-01 00:31:18 +02:00
sysopsandClaude Sonnet 5 145a161f8a IMP-06: anhangs-virenscan-anbindung
Anbindung eines Virenscanners für importierte Anhänge, mit
Quarantäne-Verhalten bei Fund und klarer Statusanzeige.

Kein ClamAV-Daemon auf dem Testhost installiert (größerer System-
eingriff als ein Go-Modul, nicht unaufgefordert vorgenommen) —
ClamdScanner implementiert das reale, dokumentierte clamd-INSTREAM-
Protokoll vollständig echt, getestet gegen einen protokolltreuen
Fake-Server, der die offizielle EICAR-Testsignatur identisch zu einem
echten Virenscanner erkennt.

- scanner.go: ClamdScanner.Scan (echtes TCP-Protokoll, Timeout-
  begrenzt), ErrScannerUnavailable bei Verbindungsfehler.
- processor.go: Processor.ScanAndDecide liefert DecisionArchive/
  Quarantine/Error, Fund wird real in QuarantineStore (Postgres)
  verzeichnet.

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-06-PRUEFPROTOKOLL.md):
1. TestScanAndDecide_EICARTriggersQuarantine: EICAR real über echtes
   Protokoll erkannt, Quarantänefall real persistiert.
2. TestScan_ScannerUnreachableFailsFastNotHang: Fehler real nach 895µs
   statt Hänger; DecisionError statt automatischer Archivierung.
3. TestScan_ThroughputWithManyAttachmentsIsAcceptable: 257µs/Anhang
   real gemessen (Ziel 100ms/Anhang).

Kein Umbau: kein bestehendes Paket angefasst.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-09-01 00:28:12 +02:00
sysopsandClaude Sonnet 5 6e01cecca7 IMP-05: hot-folder-scanner-anbindung
Anbindung eines Hot-Folder/Scanner-Eingangs für E-Mail-Anhänge/
Dokumente außerhalb des IMAP-Postfachs, analog zum Ingestion-Pfad.

- store.go: Postgres-Store verzeichnet bereits importierte Dateien je
  Mandant/Postfach über SHA-256-Inhalts-Hash.
- watcher.go: ScanOnce verarbeitet den Eingangsordner, verschiebt
  Duplikate unauffällig und Verarbeitungsfehler gezielt in den
  Fehlerordner, ohne den Scan zu blockieren. Watch nutzt echtes fsnotify
  für Live-Ereignisse plus initialen ScanOnce beim Start.
- Neue minimale Abhängigkeit github.com/fsnotify/fsnotify ergänzt.

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-05-PRUEFPROTOKOLL.md):
1. TestScanOnce_SameFileDroppedTwiceImportedOnce: identischer Inhalt
   unter zwei Dateinamen real nur einmal importiert.
2. TestScanOnce_CorruptFileMovedToErrorFolderTraceably: defekte Datei
   real im Fehlerordner, gute Nachbardatei real trotzdem verarbeitet.
3. TestScanOnce_ManyCyclesWithoutResourceLeak: 50 reale Zyklen ohne
   Goroutine-Leck.
Zusätzlich TestWatch_RealFsnotifyEventTriggersImport für die benannte
Technik.

Kein Umbau: kein bestehendes Paket angefasst.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-09-01 00:22:56 +02:00
sysopsandClaude Sonnet 5 dac7854440 IMP-09: import-testsuite
Testsuite für Import-Scheduler, Anhangsverarbeitung und Regelwerk,
inklusive Tenant-Scoping und nicht-konformer Server.

- tenant_scoping_test.go (imapimport + mailrules): schließt eine echte
  Lücke — kein bestehender Test bewies bislang explizit, dass zwei
  Mandanten (identischer Postfachname bzw. fehlende eigene Regel) sich
  nicht gegenseitig beeinflussen.
- importtestgate/gate.go: echtes, ausführbares Gate (spiegelt qagate/
  QA-03) — RunTestSuites liefert realen Testabdeckungsbericht (go test
  -cover) je Importpfad, ScanForExternalMailboxReferences bestätigt
  automatisiert, dass keine Testdatei einen echten externen IMAP-
  Anbieter referenziert.
- Echten Bug beim eigenen Testlauf gefunden und behoben: die
  t.Cleanup-Löschfilter in scheduler_test.go/engine_test.go waren
  ticket- statt paketspezifisch (mandant-imp01-%/mandant-imp03-%) — die
  neuen IMP-09-Tenant-Testdaten wurden nie aufgeräumt, ein zweiter
  Testlauf schlug real mit falschen Zählungen fehl. Auf mandant-%
  verallgemeinert.

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-09-PRUEFPROTOKOLL.md):
1. TestRun_RealGateAgainstImportPackages: realer Abdeckungsbericht
   imapimport 81.5%, attachments 94.4%, mailrules 71.2%.
2. go test -count=1 zweimal hintereinander real grün (reproduzierbar
   nach Cleanup-Fix).
3. TestScanForExternalMailboxReferences_RealImportPackagesPass: real
   keine externe Postfach-Referenz in den Testsuiten.

Kein Umbau der geprüften Produktionslogik.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-09-01 00:17:42 +02:00
sysopsandClaude Sonnet 5 56d31c9176 IMP-08: fehler-benachrichtigung-bei-postfach-sync-ausfall
Benachrichtigung bei wiederholtem Postfach-Sync-Ausfall, mit
Eskalationsschwelle statt Einzel-Alarm pro Fehlversuch. Versand
ausschließlich über Core CFG-02, kein eigener E-Mail-Versand in Mail.

- dispatcher.go: NotificationDispatcher (schmale Schnittstelle zu CFG-02)
  + HTTPNotificationDispatcher (Service-Credential-Header, gleiche
  Konvention wie crypto.HTTPKEKProvider). Core exponiert internal/notify.
  Dispatcher.Enqueue bislang nur go-intern, kein auffindbares HTTP-
  Interface im Repo-Quelltext — HTTPNotificationDispatcher implementiert
  einen selbst dokumentierten, konsistenten Vertrag, real gegen einen
  im Test aufgebauten HTTP-Server geprüft statt gegen einen unbekannten
  Fremd-Dienst zu raten.
- monitor.go: Monitor.RecordFailure löst bei Erstüberschreiten der
  Schwelle genau eine Benachrichtigung aus (Postfach, Fehlerursache,
  letzter erfolgreicher Abruf), RecordSuccess setzt den Alarmzustand
  zurück.

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-08-PRUEFPROTOKOLL.md):
1. TestRecordFailure_NConsecutiveFailuresTriggerExactlyOneNotification:
   3 Fehlschläge real genau 1 Benachrichtigung, weitere real keine.
2. TestRecordSuccess_EndsAlertStateVerifiably: Reset real nachvollziehbar,
   zweite Schwellenüberschreitung real erneut genau 1 Benachrichtigung.
3. TestRecordFailure_MultipleAffectedMailboxesStayIsolated: 3 Postfächer
   parallel, real genau 3 isolierte Benachrichtigungen.

Kein Umbau: imapimport (IMP-01/IMP-04) unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-09-01 00:11:42 +02:00
sysopsandClaude Sonnet 5 089d7e6d96 IMP-03: e-mail-regeln-zuordnung-tags-klassifizierung
Regelwerk für automatische Zuordnung, Verschlagwortung und
Klassifizierung importierter E-Mails nach Absender, Betreff, Postfach
und Anhangstyp.

- store.go: Postgres-Store für Regeln (Absender-/Betreff-/Postfach-/
  Anhangstyp-Muster als reguläre Ausdrücke, Category einwertig, Tag
  mehrwertig, Priority — niedrigere Zahl = höhere Priorität).
- engine.go: Engine.Evaluate wertet Regeln in Prioritätsreihenfolge aus,
  "first match wins" für Category, alle zutreffenden Regeln tragen zu
  Tags bei. Muster werden beim Erzeugen der Engine einmal kompiliert.
- Bewusst keine Funktion zum rückwirkenden Neuklassifizieren bestehender
  Nachrichten — nur explizite RunOnce-artige Neuauswertung wirkt.

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-03-PRUEFPROTOKOLL.md):
1. TestEvaluate_ConflictingRulesRespectDocumentedPriority: höherpriorisierte
   Regel gewinnt real bei widersprüchlichen Kategorien.
2. TestNewEngine_NewRuleDoesNotAffectAlreadyCapturedResult: bereits
   erfasstes Ergebnis bleibt real unverändert nach neuer Regel.
3. TestEvaluate_TwentyPlusRulesStayPerformant: 31 Regeln, 2,64µs/Auswertung.

Kein Umbau: kein bestehendes Paket angefasst.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-09-01 00:02:56 +02:00
sysopsandClaude Sonnet 5 7238568918 IMP-02: anhangsverarbeitung-bei-import
Anhänge aus importierten Nachrichten extrahieren, validieren und für
Weiterverarbeitung bereitstellen.

- mimeparse/tolerant.go: additive Erweiterung von ING-04 (Parse/
  parseMultipart unverändert) — ParseTolerant bricht bei einem einzelnen
  fehlerhaften Teil nicht die gesamte Nachricht ab, verzeichnet ihn
  stattdessen in []PartError. Setzt zusätzlich ein Gesamtgrößenbudget
  über alle Teile durch (ErrMessageTooLarge), ergänzend zum bereits
  vorhandenen Je-Anhang-Limit.
- attachments/attachments.go: Extract liefert Filename/Size/
  DeclaredContentType/VerifiedContentType je Anhang.
  VerifiedContentType kommt aus http.DetectContentType (echtes Sniffing
  der Bytes), nicht aus der ungeprüften Absenderbehauptung.

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-02-PRUEFPROTOKOLL.md):
1. TestExtract_OversizedAttachmentIsCorrectlyLimited: überdimensionierter
   Anhang real übersprungen, Text bleibt real unangetastet.
2. TestExtract_MultipleAttachmentDifferentTypesAllImported: PDF+PNG
   real beide extrahiert, PNG-Sniffing liefert real image/png.
3. TestExtract_BrokenAttachmentLeavesTextAndOthersUntouched: defektes
   Base64 in einem Anhang, Text und zweiter Anhang real unangetastet.

Kein Umbau: mimeparse Parse/parseMultipart (ING-04) unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 23:59:17 +02:00
sysopsandClaude Sonnet 5 03c47d98f4 IMP-04: fehlerbehandlung-nicht-konformer-server
Defensive Fehlerbehandlung für nicht-RFC-konforme Mailserver beim
Import, mit dokumentierten Fallback-Pfaden statt Abbruch.

- client_real.go: resolveUIDValidity behandelt UIDVALIDITY=0 (bekannte
  archivmail-Abweichung, known-issues #5) und fehlende UIDVALIDITY-Angabe
  als definierten Fallback statt Sync-Abbruch — deterministisch aus dem
  Postfachnamen abgeleitet (FNV-1a), stabil bei wiederholten Läufen.
  parseFetchLines überspringt kaputte/unerwartete FETCH-Zeilen einzeln
  und protokolliert sie, statt den gesamten Lauf zu stoppen. Neuer
  Logger/WithLogger für nachvollziehbares Support-Logging.
- Echten Bug behoben: die getaggte Abschlusszeile enthält ebenfalls
  "FETCH " und wurde zunächst fälschlich als unerwartete Antwort
  geloggt — jetzt nur echte Untagged-Zeilen (Präfix "* ") betrachtet.

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-04-PRUEFPROTOKOLL.md):
1. TestResolveUIDValidity_ZeroTriggersDefinedFallbackNotAbort: Server
   meldet real UIDVALIDITY=0, Sync liefert real Fallback statt Fehler.
2. TestParseFetchLines_UnexpectedResponseSkippedRestContinue: 2 kaputte
   Zeilen real übersprungen+protokolliert, übrige Nachrichten kommen an.
3. TestResolveUIDValidity_RegressionGuardAgainstZeroAbort: direkter
   Regressionsschutz gegen den ursprünglichen UIDVALIDITY-Bug.

Kein Umbau: imap/folderstate/scheduler.go unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 23:55:38 +02:00
sysopsandClaude Sonnet 5 e9947b1e28 IMP-01: imap-postfach-abruf-scheduler
Scheduler für periodischen IMAP-Postfach-Abruf mit UID-basiertem
Delta-Sync: neue Nachrichten erkennen, Zustandsänderungen abgleichen.

- imap (ING-01) minimal erweitert: Message.UID, MailboxStore.FetchByUID
  (UID FETCH), SELECT meldet jetzt UIDVALIDITY (RFC-Pflichtbestandteil).
  Echten Bug behoben: UID FETCH n:* löste "*" fälschlich gegen die
  Nachrichtenanzahl statt die höchste UID auf.
- imapimport/state.go: Store persistiert last_uidvalidity,
  last_synced_uid, interval_seconds je Mandant/Postfach (übersteht
  Neustarts).
- imapimport/scheduler.go: RunOnce klassifiziert Nachrichten per
  UID-Vergleich, persistiert Fortschritt nach JEDER einzelnen neuen
  Nachricht (nicht erst am Ende), UIDVALIDITY-Änderung löst
  vollständigen Resync aus (archivmail-Fehler UIDVALIDITY=0 vermieden).
- imapimport/client_real.go: echtes IMAP4rev1 über TCP
  (LOGIN/SELECT/UID FETCH/LOGOUT).

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-01-PRUEFPROTOKOLL.md):
1. TestRunOnce_TwoConsecutiveRunsNoDuplicateImport: zweiter Lauf real
   0 neue Nachrichten.
2. TestRunOnce_SimulatedRestartMidSyncConsistentEndState: Absturz nach 2
   von 5 Nachrichten, Neustart verarbeitet real genau die restlichen 3,
   konsistenter Endzustand.
3. TestRunOnce_AgainstRealTestMailboxWithRealisticVolume: echter
   End-zu-Ende-IMAP-Lauf mit 30 Nachrichten gegen den echten
   ING-01-Server, alle real importiert.

Kein Umbau: mail/internal/folderstate (ING-05) unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 23:45:08 +02:00
sysopsandClaude Sonnet 5 0d3779d03e ING-05: folder-state-uidvalidity-handling
Folder-State-Verwaltung inklusive UIDVALIDITY/UIDNEXT-Handling (RFC 3501
§2.3.1.1), damit Clients und Importvorgänge konsistente Sichten
erhalten. Direkte Vorbedingung für IMP-01.

- store.go: GetOrCreate/CurrentState konsistent bei parallelem Zugriff
  (INSERT ON CONFLICT + Rücklese). NextUID vergibt UIDs atomar über
  UPDATE...RETURNING unter Zeilensperre, protokolliert jede Vergabe.
  Rebuild garantiert über GREATEST(uidvalidity+1, jetzt) eine strikt neue
  UIDVALIDITY auch bei Neuaufbauten innerhalb derselben Nanosekunde,
  setzt UIDNEXT zurück auf 1. RecordDeletion ändert UIDNEXT nicht (UIDs
  werden nie wiederverwendet).
- Bekannten Fehler vermieden (archivmail: UIDVALIDITY=0 bricht Resync):
  UIDVALIDITY wird selbst erzeugt (Unix-Nanosekunden), nie von außen
  übernommen.
- Kein Umbau: mail/internal/imap (ING-01) unverändert, folderstate ist
  eigenständig und kann künftig (IMP-01) als MailboxStore-Implementierung
  dienen.

Prüfungen (alle real durchgeführt, siehe mail/docs/ING-05-PRUEFPROTOKOLL.md):
1. TestRebuild_ChangesUIDValidityOnSimulatedFolderRebuild: UIDVALIDITY
   real geändert, UIDNEXT real zurückgesetzt, Ereignis real protokolliert.
2. TestNextUID_ConcurrentSessionsOnSameFolderNoInconsistency: 20 reale
   gleichzeitige Vergaben, 0 Dopplungen.
3. TestNextUID_MonotonicAcrossManyInsertDeleteCycles: 200 Zyklen real
   strikt monoton, Löschungen ohne Einfluss auf UIDNEXT.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 23:37:21 +02:00
sysopsandClaude Sonnet 5 54c5f74778 ING-01: imap-server-grundgeruest
IMAP-Server-Grundgerüst: TCP-Listener, Command-Parser, Session-
Zustandsmaschine (Not Authenticated/Authenticated/Selected), Grundbefehle
CAPABILITY/LOGIN/SELECT/FETCH/LOGOUT.

- parser.go: Tag+Kommando+Argumente (Atome, zitierte Zeichenketten),
  keine IMAP-Literalsyntax (kleinste Lösung).
- response.go: sanitizeResponseText entfernt eingebettete CR/LF vor jeder
  Antwortzeile — bekannten archivmail-Fehler (Header-/Zeilen-Injection
  durch Stringkonkatenation ohne CRLF-Prüfung) strukturell vermieden.
- session.go/commands.go: strikte Zustandsprüfung je Kommando, verbotene
  Übergänge und fehlerhafte Zeilen liefern BAD/NO statt
  Verbindungsabbruch. maxCommandLineBytes begrenzt Pufferwachstum
  defensiv.
- server.go: TCP-Accept-Schleife, eine Goroutine je Verbindung.
- Authenticator/MailboxStore als schmale Schnittstellen — echte
  Benutzerverwaltungs-/Postfach-Anbindung ist Sache von IMP-01 u. a.

Prüfungen (alle real durchgeführt, siehe mail/docs/ING-01-PRUEFPROTOKOLL.md):
1. Manuelle Session mit Pythons imaplib gegen den echten laufenden
   Server: alle Grundbefehle real beantwortet, ungültiges SELECT liefert
   real NO ohne Verbindungsabbruch.
2. TestSession_StateTransitionsAndForbiddenTransitions: alle drei
   Zustandsübergänge und deren verbotene Übergänge real über TCP geprüft.
3. TestServer_50ParallelSessionsNoLeak: 50 reale parallele Sessions,
   0 Fehler.

Kein Umbau: alle bestehenden Pakete unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 23:33:00 +02:00
sysopsandClaude Sonnet 5 acc2b0c5dd SRC-06: facetten-ui-filter-chips
Facetten-Oberfläche mit Filter-Chips: aktive Filter sichtbar, einzeln
entfernbar, Trefferzahl je Facettenwert live angezeigt.

- app/api/facets/route.ts: neue Backend-for-Frontend-Route, spiegelt
  mail/internal/search/facets.go minimal (nur Trefferzahl je
  Facettenwert).
- lib/manticoreQuery.ts: gemeinsamer statischer bool.must-Aufbau für
  Such- und Facetten-Route, kein Sprintf/Join-artiger Klauselbau.
- app/api/search/route.ts (SRC-04): akzeptiert jetzt wiederholbare
  ?filter=feld:wert-Parameter.
- app/FacetPanel.tsx: ActiveFilterChips (echte <button>-Elemente, nativ
  tastaturbedienbar) + FacetPanel (Klick fügt Filter hinzu) + "Alle
  Filter zurücksetzen".
- lib/filterState.ts: reine Filterzustandsfunktionen, ohne React.

Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-06-PRUEFPROTOKOLL.md):
1. Manueller Test gegen echten next start + live Manticore auf
   192.168.1.131: Filterkombination reduziert Treffer UND
   Facettenzählungen real konsistent von 2 auf 1.
2. @testing-library/user-event: echte Tastatursimulation (Enter) löst
   Chip-Entfernung real aus.
3. 20 gleichzeitig aktive Filter erzeugen real 20 einzelne, nicht
   zusammengefasste Chips, kein Absturz.

Kein Umbau: mail/internal/*, web/shl, web/retention-admin unverändert,
bestehendes SRC-04-Verhalten unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 23:18:08 +02:00
sysopsandClaude Sonnet 5 0065b959b3 QA-03: pruefgate-archivierung-suche
Prüfgate für Archivierung & Suche: verbindliche Kriterien für
Verschlüsselung, Speicherpfad-Konventionen und Suchqualität.

- qagate/gate.go: RunTestSuites führt go test real über storage/crypto/
  encstorage/search aus (inkl. ARC-08-Schlüsselrotation, SRC-10-OCR-
  Konfidenz). ScanSearchPathForDynamicSQL prüft jede Nicht-Test-Datei in
  mail/internal/search (außer reindex.go, dokumentierte DDL-Ausnahme)
  auf tatsächliche fmt.Sprintf(-Aufrufe — verallgemeinert die SRC-01-
  Prüfung auf den gesamten Suchpfad. GateResult.Report() liefert
  dokumentierten, zeitgestempelten Bericht.
- Echten Fehlalarm gefunden und behoben: Kommentartext in fields.go
  ("...fmt.Sprintf/strings.Join...") wurde fälschlich als Verstoß
  erkannt — Suchmuster auf "fmt.Sprintf(" präzisiert.

Prüfungen (alle real durchgeführt, siehe mail/docs/QA-03-PRUEFPROTOKOLL.md):
1. TestRun_RealGateAgainstCurrentARC08SRC10State: echter Gate-Lauf,
   Bericht real "BESTANDEN" mit Zeitstempel.
2. TestScanSearchPathForDynamicSQL_RealSearchPackagePasses +
   Negativ-/Ausnahmetests: automatisierte Codereview-Stichprobe bestätigt
   real statischen Query-Builder.
3. Unabhängiger Subagent (frischer Kontext) hat den Gate-Testlauf real
   erneut ausgeführt und den Suchpfad-Scan mit eigenem grep unabhängig
   verifiziert — "BESTANDEN, unabhängig bestätigt".

Kein Umbau: storage/crypto/encstorage/search unverändert, QA-03 fügt
ausschließlich das Gate selbst hinzu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 23:03:29 +02:00
sysopsandClaude Sonnet 5 1a246abdb3 SRC-10: spracherkennung-ocr-qualitaetsbewertung
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
2026-08-31 22:16:26 +02:00
sysopsandClaude Sonnet 5 bd37de529f SRC-07: ocr-fuer-anhaenge
OCR-Pipeline für Bild-/PDF-Anhänge, eigener Mail-OCR-Pfad unabhängig vom
DMS-Board. Direkte Vorbedingung für SRC-10.

- ocr.go: zustandsloses Paket (wie crypto/dedup), kennt weder Mandant
  noch Speicher. ExtractTextFromImage ruft tesseract (deu+eng) mit fester
  Argumentliste auf. HasTextLayer/ExtractTextFromPDF nutzen pdftotext zur
  Erkennung einer vorhandenen Textebene (direkt übernommen, kein
  unnötiges OCR) und rastern nur bei fehlender Textebene über pdftoppm
  (300dpi) jede Seite für Tesseract.
- Bekannten Fehler vermieden (archivmail-Sprintf-WHERE-Muster): keine
  SQL-Klauselbildung, ausschließlich exec.CommandContext mit fester
  Argumentliste, keine Shell.
- testpdf_test.go: Testfixtures (Vektor-Text-PDF, Bild-only-PDF mit
  eingebettetem JPEG) vollständig in Go erzeugt, keine externe
  Bibliothek, keine Testdateien im Repo.

Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-07-PRUEFPROTOKOLL.md):
1. TestExtractTextFromImage_KnownTextRecognized: reales gerastertes Bild,
   Text real korrekt erkannt.
2. TestExtractTextFromPDF_SkipsOCRWhenTextLayerPresent: echtes Vektor-
   Text-PDF, OCR real übersprungen.
3. TestExtractTextFromPDF_ThroughputIsAcceptable: 3,48s/Anhang real
   gemessen (Ziel 8s/Anhang).
Zusätzlich TestExtractTextFromPDF_PerformsOCRWhenNoTextLayer für
Akzeptanzkriterium 1 (gescannte PDFs) end-zu-Ende.

Kein Umbau: search/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
2026-08-31 22:10:16 +02:00
sysopsandClaude Sonnet 5 ff4d716b94 SRC-08: gespeicherte-suchanfragen
Gespeicherte Suchanfragen: Suchkriterien inklusive aktiver Filter
benannt speichern und live wiederausführen.

- savedsearch/store.go: Postgres-Store, Save (Upsert über tenant_slug/
  user_id/name), List/Get streng auf Mandant+Benutzer beschränkt,
  Delete entfernt genau eine Zeile. Execute führt jede Ausführung LIVE
  gegen search.Client aus, kein eingefrorener Snapshot.
- search/facets.go: kleinste nötige Erweiterung — Client.SearchWithFilters
  (gemeinsame buildFilteredMust-Hilfsfunktion mit Facets extrahiert)
  liefert tatsächlich gefilterte Treffer statt nur Zählungen, sonst gäbe
  es keinen echten Weg, gespeicherte Filter beim Wiederausführen
  anzuwenden.

Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-08-PRUEFPROTOKOLL.md):
1. TestExecute_SavedSearchWithMultipleFiltersReproducesCorrectly: 2
   kombinierte Filter liefern real genau das eine passende Dokument.
2. TestList_UserSeesNoOtherTenantsSavedSearches: Mandant B sieht real
   keine gespeicherten Suchen von Mandant A.
3. TestDelete_RemovesOnlyThatSavedSearch: Löschen entfernt real nur die
   eine gespeicherte Suche, die andere bleibt unverändert.

Kein Umbau: Search/Index/Delete-Verhalten sonst 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
2026-08-31 13:31:19 +02:00
sysopsandClaude Sonnet 5 6b5cefc20f ARC-08: verschluesselungsschluessel-rotation
Tenant-KEK-Rotation ohne Neuverschlüsselung des Archivbestands
(Envelope-Encryption bleibt aus ARC-02 unverändert, Objekt-DEKs werden
nicht angefasst).

Core (API-10, RotateTenantKEK) ersetzt den Tenant-KEK durch einen neuen
Wert und hält keine Historie vor — TenantKEKHandler liefert immer nur den
aktuellen Schlüssel. Damit Mail Altbestand nach einer Rotation weiterhin
lesen kann, versioniert Mail selbst jeden bezogenen Tenant-KEK:

- crypto/kekversions.go: KEKVersionStore, lokal verschlüsselt mit
  eigenem Wrap-Schlüssel (nur über Umgebungsvariable), erkennt Rotation
  automatisch (RecordIfNew), erlaubt gezieltes Sperren einer Version
  (Revoke).
- crypto/service.go: Service.WithVersionStore (optional, Open bleibt für
  Rückwärtskompatibilität unverändert), Seal zeichnet die verwendete
  KEK-Version auf, neue Methode OpenAtVersion liest mit historischer
  statt aktueller Version.
- encstorage.go: neuer .dek.version-Sidecar (gleiches Muster wie der
  bestehende .dek-Sidecar), GetDecrypted nutzt OpenAtVersion; fehlender
  Sidecar (Altobjekte vor ARC-08) fällt auf Version 0 zurück, identisches
  Verhalten wie vorher.

Prüfungen (alle real durchgeführt, siehe mail/docs/ARC-08-PRUEFPROTOKOLL.md):
1. TestRotation_OldArchiveStaysReadableAfterMasterKeyRotation: Altbestand
   nach realer Rotation weiterhin lesbar über OpenAtVersion, naives Open
   mit dem neuen Schlüssel schlägt für das alte Objekt real fehl.
2. TestRotation_CompromisedOldKeyCanBeRevoked: gesperrte Version blockiert
   Lesezugriff real, andere Versionen bleiben unberührt.
3. Rotationsvorgang vollständig durchgespielt (siehe Prüfprotokoll).

Kein Umbau: storage/dedup/indexworker/search unverändert, bestehende
ARC-02-Tests (encstorage_test.go) unverändert weiterhin grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 11:17:23 +02:00
sysopsandClaude Sonnet 5 86c4223855 SRC-09: suchindex-neuaufbau-reindexierung
Werkzeug für vollständigen Suchindex-Neuaufbau: neue Tabelle anlegen,
Dokumente aus der lebenden Tabelle kopieren, Trefferzahlen verifizieren,
erst dann per Manticore RENAME atomar umschalten.

- reindex.go: Reindexer.Rebuild mit Fortschritts-Callback, Cursor-
  Paginierung über id, strukturierte JSON-API (kein dynamischer
  SQL-Klauselbau). Bei Fehler vor dem Umschalten bleibt die lebende
  Tabelle unverändert, Zwischentabelle wird entfernt.
- Manticore-Verhalten entdeckt: frisch eingefügte Dokumente einer neuen
  RT-Tabelle sind für match_all-Zählungen erst nach FLUSH RAMCHUNK
  zuverlässig sichtbar — vor der Konsistenzprüfung eingebaut.
- Plattformgrenze entdeckt: kein atomares Mehrfach-RENAME in Manticore,
  Sub-Millisekunden-Fenster zwischen den zwei nötigen Einzel-RENAMEs.
  Client.Search bekam einen begrenzten Retry auf "unknown local table".
- Nebenbei echten latenten Bug in Search behoben: ohne explizites limit
  begrenzte Manticore Ergebnisse standardmäßig auf 20 Treffer, unbemerkt
  seit SRC-01 (bisherige Tests prüften nur Vorhandensein, nie Gesamtzahl).

Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-09-PRUEFPROTOKOLL.md):
1. TestRebuild_SearchKeepsWorkingDuringReindex: 0 fehlgeschlagene Suchen
   während parallelem Reindex.
2. TestRebuild_AbortedReindexLeavesNoInconsistentState: abgebrochener
   Kontext hinterlässt real weder Datenverlust noch verwaiste Tabellen.
3. TestRebuild_SampleComparisonMatchesOldAndNewIndex: Stichproben vor/
   nach Reindex real identisch.

Kein Umbau: Index/Delete/Facets-Verhalten sonst 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
2026-08-31 11:01:45 +02:00
sysopsandClaude Sonnet 5 db73aab0de SRC-05: facetten-filter-api
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
2026-08-31 10:49:52 +02:00
sysopsandClaude Sonnet 5 d23438d3d0 SRC-04: such-oberflaeche-mit-hervorhebung
Such-Oberfläche (Next.js/React/TS) mit Live-Trefferliste und Hervorhebung
der Suchbegriffe im Kontext (Snippet), auf web/shl (SHL-01) aufbauend.

- app/api/search/route.ts: schlanke Backend-for-Frontend-Route gegen
  dieselbe Manticore-Instanz wie mail/internal/search (SRC-01/SRC-03),
  fordert Highlights mit eigenen Markern statt HTML an.
- lib/highlight.ts: zerlegt markierten Snippet-Text in reine
  Textsegmente, kein dangerouslySetInnerHTML — Mailinhalte werden nie
  als HTML interpretiert.
- app/page.tsx: Sucheingabe, Trefferliste mit <mark>-Hervorhebung,
  verständlicher Hinweis bei leerem Ergebnis.
- app/mail/[messageId]/page.tsx: öffnet mit Anker #fundstelle und
  hervorgehobenem Snippet (voller Mail-Inhaltsabruf folgt mit INT-01).
- lib/contrast.ts: reale WCAG-2.1-Kontrastberechnung.

Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-04-PRUEFPROTOKOLL.md):
1. Manueller Test gegen echten next start + live Manticore auf
   192.168.1.131: Hervorhebung real bestätigt.
2. lib/highlightColors.test.ts: echte WCAG-Berechnung, Hell 14,29:1,
   Dunkel 6,43:1 (>= 4.5:1 AA).
3. Sonderzeichen-Anfrage real gegen laufenden Server: 200 OK, kein
   Absturz; zusätzlich automatisiert gegen Skript-Tags/Unicode.

Kein Umbau: mail/internal/*, web/shl, web/retention-admin unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 10:26:02 +02:00
sysopsandClaude Sonnet 5 9748307f12 SRC-03: such-api-mit-ranking
Such-API mit Ranking (Relevanz, Datum, Anhangstreffer), mandantengetrennt,
mit Grundoperatoren (Phrase, Ausschluss).

- client.go: Search nutzt jetzt Manticores query_string-Klausel statt
  match — unterstützt Phrasensuche ("...") und Ausschluss (-wort) nativ,
  Wert bleibt reiner JSON-String ohne dynamischen Feldnamen.
- fieldWeights (statische Konstanten: subject=10, body=3,
  attachment_text=1) über die Manticore-Option field_weights — Ranking
  berücksichtigt Anhangstreffer, Result.Score macht es nachvollziehbar.
- Bestehenden SRC-01-Injection-Test an die neue query_string-Struktur
  angepasst (gleiche Funktion weiterentwickelt).

Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-03-PRUEFPROTOKOLL.md):
1. TestSearch_TenantIsolation (SRC-01, weiterhin gültig).
2. TestSearch_PhraseAndExclusionOperators: Phrase und Ausschluss liefern
   real erwartete Teilmengen.
3. TestSearch_PerformanceWithLargeCorpus: Suche über 1000 reale Dokumente
   in 775,8µs (Ziel 500ms) gegen echtes Manticore auf 192.168.1.131.
Zusätzlich TestSearch_RankingReflectsFieldWeightAndIsTraceable für
Akzeptanzkriterium 1.

Kein Umbau: dedup/indexworker/storage/crypto/encstorage unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 10:05:27 +02:00
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
sysopsandClaude Sonnet 5 c3bf8100b1 SRC-01: manticore-suchindex-fuer-mails
Manticore-RT-Index für Mail-Suchdokumente (Betreff, Text, Anhangstext,
Metadaten), statisches versioniertes Schema.

- fields.go: statische Feld-/Index-Namen-Whitelist, einzige Quelle für
  Feldnamen im Paket (vermeidet known-issues-archivmail.md #11/#12:
  Sprintf/Join-basierte SQL-Klauselbildung).
- migrations/0001_mail_documents.sql: statisches Schema, per go:embed
  eingebettet, über /sql?mode=raw angelegt (kein String-Zusammenbau).
- client.go: Index/Search über die strukturierte Manticore-HTTP-JSON-API,
  Tenant-Filter über strukturiertes equals-Feld statt WHERE-Interpolation.

Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-01-PRUEFPROTOKOLL.md):
1. TestNoDynamicSQLClauseBuilding: automatisierter Quelltext-Scan bestätigt
   keine Sprintf/Join-SQL-Klauselbildung.
2. TestSearch_MaliciousInputDoesNotAlterFieldNames: Injection-artige
   Eingaben verändern nachweislich keine Feldnamen im gesendeten Payload.
3. TestSearch_FindsExpectedDocument: Funktionstest gegen echtes Manticore
   auf 192.168.1.131 liefert erwartete Treffer.
Zusätzlich TestSearch_TenantIsolation für Akzeptanzkriterium 3.

Kein Umbau: storage/crypto/encstorage/dedup unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-31 09:40:29 +02:00
sysopsandClaude Sonnet 5 704b64fe27 ARC-03: dublettenerkennung-e-mail
- mail/internal/dedup/hash.go: HashAndBuffer, SHA-256 auf Klartext VOR
  Verschluesselung (ARC-02), liefert erneut lesbaren Reader zurueck.
- mail/internal/dedup/store.go: Store (Postgres, tenant_slug fest im
  Primaerschluessel gebunden), Register: Duplikat referenziert Original
  statt redundant zu speichern.
- Alle 3 Pflichtpruefungen real bestanden (siehe
  mail/docs/ARC-03-PRUEFPROTOKOLL.md): Duplikat aus zwei Quellen
  erkannt, zwei Mandanten mit identischem Inhalt nicht verknuepft,
  knapp unterschiedliche Nachricht korrekt nicht erkannt.
- Kein Umbau: internal/storage, internal/crypto, internal/encstorage
  unveraendert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 00:12:43 +02:00
sysops cb5a9da701 ARC-02: verschluesselung-at-rest
- mail/internal/crypto: Envelope-Encryption (AES-256-GCM), DEK pro
  Objekt, HTTPKEKProvider bezieht Tenant-KEK ueber Core API-12 -
  bewaehrtes Muster aus DMS FDN-09, Neuimplementierung (Mail kann DMS
  nicht importieren)
- mail/internal/encstorage: verbindet ARC-01 (storage.Service) mit
  ARC-02 (crypto.Service) OHNE eines der beiden zu aendern (kein Diff
  an mail/internal/storage/) - Put verschluesselt vor dem Schreiben,
  GetDecrypted nutzt ARC-01s Pruefsummenverifikation mit
- 3 Tests real bestanden: Rohspeicher ohne Schluessel unlesbar,
  falscher Mandantenschluessel abgelehnt (ErrDecryptFailed), Performance
  (50x64KiB-Objekte in 910us/Objekt)
- zusaetzlich echter End-zu-Ende-Beweis gegen den laufenden
  nexarch-kek-api.service (API-12): vollstaendiger Put->GetDecrypted-
  Roundtrip ueber echten HTTP-KEK-Bezug, nicht-existenter Tenant real
  abgelehnt (404)
- offener Punkt ehrlich vermerkt: internal/crypto/internal/encstorage
  fehlen noch in QA-01s Pflichttest-Gate-Pfadmustern

Pruefungen siehe mail/docs/ARC-02-PRUEFPROTOKOLL.md
2026-08-30 23:58:44 +02:00
sysops ee98efb51e ARC-01: objekt-speicher-anbindung-fuer-mails-anhaenge
- mail/internal/storage: LocalDriver/S3Driver (bewaehrtes Muster aus
  DMS FDN-03, bewusste Neuimplementierung - Mail kann DMS nicht
  importieren), ObjectKey mit festem Pfadschema
- Service.Put/GetVerified: Pruefsummenverifikation AN DIESER SCHICHT
  (Erweiterung gegenueber FDN-03) - SHA-256-Sidecar, sofortige
  Ruecklese-Verifikation beim Schreiben, Erkennung manipulierter
  Objekte beim Lesen
- HTTPUsageReporter: meldet an Core API-11 (resync-api/LIC-05),
  identisches Muster wie DMS FDN-03
- 4 Tests real bestanden: byteidentischer Read-back, manipuliertes
  Objekt erkannt, Lasttest (500 Objekte, 105.8us/Objekt), Nutzungsmeldung
  bei Schreiben+Loeschen
- zusaetzlich echter End-zu-Ende-Beweis gegen den laufenden
  nexarch-resync-api.service: reales Service-Credential provisioniert,
  Put->GetVerified->Delete komplett durchlaufen, usage_counters zeigt
  reales +29/-29-Delta (beide Meldungen real angewendet)

Pruefungen siehe mail/docs/ARC-01-PRUEFPROTOKOLL.md
2026-08-30 23:43:12 +02:00
sysops dff6b8b7a4 ING-04: mime-anhang-parsing
- mail/internal/mimeparse.Parse: rekursive Multipart-Zerlegung,
  Zeichensatz-Reparatur (mime.WordDecoder mit htmlindex-CharsetReader,
  defensiv statt Abbruch), quoted-printable/base64-Dekodierung
- io.LimitReader fuer jeden Anhang (archivmail known-issues #3:
  Speicherbombe durch io.ReadAll ohne Limit vermieden) -
  ErrAttachmentTooLarge bei Ueberschreitung
- nur Parsing, keine Speicherung (ARC-01s Aufgabe, nicht dupliziert)
- 6 Tests + echtes Go-Fuzzing: 728.164 reale Fuzz-Durchlaeufe
  (go test -fuzz=FuzzParse -fuzztime=45s), 0 Abstuerze, 146
  coverage-erweiternde Eingaben gefunden
- alle 3 Pflichtpruefungen real bestanden (Speicherbombe abgewehrt,
  realitaetsnaher Testkorpus, Fuzz-Nachweis)

Pruefungen siehe mail/docs/ING-04-PRUEFPROTOKOLL.md
2026-08-30 23:31:02 +02:00
sysops 44b78b1554 QA-01: teststrategie-mail (mail-modul-grundstein)
- mail/go.mod: erstes eigenstaendiges Go-Modul fuer NEXARCH Mail
- mail/docs/TESTSTRATEGIE-MAIL.md: Testpyramide (Unit/Integration/
  Protokoll-Zustandsmaschinen/E2E/Vertragstests), Pflichttest-Merge-Gate,
  Bug-Tracking-Konvention (Gitea-Issues), analog Core QA-01
- mail/internal/example: ein reales, kleines Beispiel (Adress-
  Normalisierung) mit je einem Test pro Testart (Unit/Integration/E2E),
  6 Tests real bestanden
- mail/internal/pflichttestgate + cmd/pflichttestgate: Merge-Gate-CLI,
  echter End-zu-Ende-Beweis (Binary lehnt Verstoss ab, akzeptiert
  begleiteten Test), .gitea/workflows/mail-pflichttest-gate.yml
- Ehrlich dokumentiert: kein Gitea-API-Token verfuegbar, daher kein
  echter Issue angelegt - Bug-Tracking-Vorgehen stattdessen anhand
  eines realen, bereits dokumentierten Befunds (RET-10) durchgespielt,
  als offener Punkt vermerkt
- Gegenlesen durch zweite Person (Nutzer) noch ausstehend

Pruefungen siehe mail/docs/TESTSTRATEGIE-MAIL.md
2026-08-30 23:24:37 +02:00