Commit Graph
48 Commits
Author SHA1 Message Date
sysops 1825387603 feat(mail): ARC-09 Postfach-Quota (unabhängig von Core LIC-05)
mailboxconfig (IMP-07) bekommt eine quota_bytes-Spalte statt einer
eigenen Tabelle — ein Postfach ist bereits eindeutig über
(tenant_slug, name) identifiziert. SetQuotaBytes/LimitBytes, 0 =
unbegrenzt (Standardwert, keine Migration bestehender Postfächer
nötig). LimitBytes erfüllt strukturell quota.LimitProvider.

storage.ArchiveMailboxPrefix (ARC-04-Ergänzung, Präfix ALLER Jahre
eines Postfachs) und storage.UsageCounter: realer Speicherverbrauch
durch echtes S3-Listing im physisch getrennten Mandanten-Bucket
(ARC-06) — kein separat gepflegter Zählerstand.

Neues Paket mail/internal/quota: Checker verbindet LimitProvider und
UsageProvider. Kein konfiguriertes Limit = immer erlaubt (Core-LIC-05-
Quota läuft unabhängig weiter — beide Ebenen bewusst unabhängig
durchgesetzt, bekannter Fehler vermieden).

smtp.QuotaChecker (schmale Schnittstelle, keine Paketkopplung an
quota) wird in handleRcptTo geprüft, VOR der Datenübertragung: 552
(RFC 5321 "exceeded storage allocation") bei Überschreitung, Session
bleibt nutzbar. nil-Checker erhält bisheriges Verhalten unverändert.

Alle drei Pflichtprüfungen mit echten Nachweisen: Quota-Überschreitung
liefert 552, Session bleibt funktionsfähig; ein anderes Postfach
desselben Tenants läuft währenddessen vollständig normal durch;
vollständiger Ende-zu-Ende-Integrationstest gegen reale Postgres- und
MinIO-Instanzen — 5000 echte Bytes abgelegt, real gemessen, Limit
knapp darunter/darüber gesetzt, SMTP reagiert jeweils korrekt auf den
tatsächlichen gemessenen Wert. Dabei einen echten Cleanup-Fehler
gefunden und behoben (defer schloss den Pool vor dem zugehörigen
t.Cleanup, verwaiste Testdaten blieben zurück).

go build/go vet/golangci-lint clean, gesamtes Mail-Modul
regressionsfrei getestet.
2026-09-02 23:47:32 +02:00
sysops c344fa938b feat(mail): ARC-04 Aufbewahrungsstruktur (Mandant/Postfach/Jahr)
Neue Datei storage/archivekey.go: ArchiveKey(mailbox, sentAt,
messageID, partIndex) liefert den Objektschlüssel nach dem Schema
Postfach/Jahr innerhalb des bereits mandantenspezifischen Buckets
(ARC-06) — additiv neben dem bestehenden, flachen ObjectKey (ARC-01),
das für bestehende Aufrufer (mailapi/INT-01) unverändert bleibt.
ArchiveYearPrefix(mailbox, year) ist der eigenständig berechenbare
Präfix eines Postfach-Jahrs — Grundlage für spätere Retention-Regeln
ohne Migration. Fehlendes Postfach bzw. Null-Sendedatum führen zu
dokumentierten Fallback-Segmenten statt einem Ablagefehler; ArchiveKey
liefert bewusst keinen error, da es strukturell keinen Fehlerfall gibt.

Alle drei Pflichtprüfungen: mehrjähriger Import erzeugt nachweislich
getrennte Jahresordner, fehlendes Postfach/Sendedatum nutzt den
dokumentierten Fallback (inkl. Test gegen mehrere ungewöhnliche
Eingaben), sowie ergänzend ein Nachweis für Akzeptanzkriterium 2
(Retention-Präfix trifft exakt die zuvor abgelegten Schlüssel
desselben Postfach-Jahrs). Pflichtprüfung 3 (Stichprobenreview durch
zweite Person) bleibt strukturell offen, im Prüfprotokoll dokumentiert
(analog zu ING-10/QA-04/QA-02).

go build/go vet/golangci-lint clean, gesamtes Mail-Modul
regressionsfrei getestet.
2026-09-01 20:17:11 +02:00
sysops b3c8d36b58 feat(mail): INT-06 E-Mail-Regel-Engine über API steuerbar
mailrules.Store (IMP-03) bekommt Update (bislang nur Create/List/
Delete) — gleiches Muster wie Create: Musterprüfung vor dem Schreiben,
streng auf tenant_slug+id beschränkt, ErrNotFound bei fremder/nicht
existierender ID.

Neues Paket mail/internal/mailrulesapi: vier Endpunkte (GET/POST
/api/v1/mail/rules, PUT/DELETE /api/v1/mail/rules/{id}), tenant-Query-
Parameter Pflicht, gleiche Konvention wie mailapi (INT-01).
Akzeptanzkriterium 3 ist strukturell garantiert: mailrulesapi ruft
ausschließlich mailrules.Store auf, denselben Store, den IMP-03s
Import-Pfad ohnehin verwendet — kein zweiter, paralleler Schreibpfad.

Alle drei Pflichtprüfungen mit echten Nachweisen: vollständiger
Anlegen/Priorisieren/Einsehen/Löschen-Zyklus über echte HTTP-Requests;
eine über die API angelegte Regel wird über genau den Weg gelesen und
ausgewertet, den IMP-03s Import-Pfad geht (Store.List ->
mailrules.NewEngine -> Evaluate) und liefert das korrekte
Klassifizierungsergebnis; Mandant Bs Update-Versuch mit der echten,
bekannten ID von Mandant As Regel liefert 404, Mandant As Regel bleibt
unverändert.

go build/go vet/golangci-lint clean, gesamtes Mail-Modul
regressionsfrei getestet — bestehende mailrules-Tests (IMP-03/IMP-09)
bleiben nach der Update-Erweiterung unverändert grün.
2026-09-01 19:55:30 +02:00
sysops 8ee0e6c771 feat(mail): INT-07 Health-Check-Endpunkt für Mail-Modul
Neues Paket mail/internal/healthcheck: Checker sammelt benannte
CheckFunc-Prüfungen, liefert Gesamt- und Einzelstatus je Komponente
(ok/degraded, nie ein generischer Fehler). Fehlertexte einzelner
Prüfungen fließen nie in die HTTP-Antwort — nur Name+Status je
Komponente.

Vier konkrete Prüfungen (checks.go) gegen die real vorhandenen
Ticket-Abhängigkeiten: DatabaseCheck (pgxpool.Ping), ObjectStorageCheck
(HeadBucket gegen den ARC-06-Bucket), SearchIndexCheck (reale
Manticore-Anfrage), JobQueueCheck (COUNT gegen mail_index_jobs,
SRC-02/indexworker — COUNT statt Zeilenzugriff, damit eine leere aber
erreichbare Queue nicht fälschlich als Ausfall gilt).

RegisterRoutes registriert GET /api/v1/mail/health ohne
Authentifizierung auf einem vom Aufrufer bereitgestellten
*http.ServeMux, gleiches Pfadschema wie mailapi (INT-01) — Core API-01
hat weiterhin keinen abrufbaren Router.

Alle drei Pflichtprüfungen mit echten Nachweisen: simulierter Ausfall
einer von vier Abhängigkeiten korrekt als degraded abgebildet, eine
Prüfung mit absichtlich eingebetteter Verbindungszeichenfolge inkl.
Passwort im Fehlertext taucht nirgends in der HTTP-Antwort auf, echter
httptest-HTTP-Server-Integrationstest gegen den Endpunkt. Alle vier
konkreten Prüfungen zusätzlich real gegen laufende Postgres-, MinIO-
und Manticore-Instanzen verifiziert (inkl. echter ARC-06-Provisionierung).

go build/go vet/golangci-lint clean, gesamtes Mail-Modul
regressionsfrei getestet.
2026-09-01 19:49:47 +02:00
sysops d26a341fa8 feat(mail): INT-05 Benachrichtigungs-Service "neue Mail"
Neues Paket mail/internal/notifyclient: Mail-seitige Kopplung an Core
CFG-02/CFG-05 (POST /notify, service-token-authentifiziert). Core
CFG-02/CFG-05 stehen auf core-kanban zwar auf "Fertig", haben im
aktuellen Repository-Stand aber keinen abrufbaren Endpunkt — dieselbe
Situation wie ARC-06/Core TEN-01 und INT-01/Core API-01, im
Prüfprotokoll begründet. Client richtet sich nach dem in CFG-05s eigener
Beschreibung dokumentierten Vertrag. 204 wird bewusst nicht als Fehler
behandelt (CFG-05 wrappt laut Beschreibung bereits
notifyprefs.EnqueueIfAllowed — die Zustellentscheidung nach
Benutzerpräferenz liegt vollständig bei Core, Mail dupliziert diese
Logik nicht).

Neues Paket mail/internal/importnotify: NotifyBatch löst am Ende EINES
imapimport.RunOnce-Laufs höchstens EINEN Notify-Aufruf aus — es gibt
strukturell keinen Codepfad für mehr als einen Aufruf je Lauf
(Bündelung statt Flut bei Massenimport).

Alle drei Pflichtprüfungen mit echten Nachweisen: eine neue Nachricht
löst genau eine Benachrichtigung aus, 50 neue Nachrichten weiterhin
genau eine gebündelte Benachrichtigung (Count: 50), ein echter
HTTP-Server bildet den CFG-05-204-Unterdrückungsvertrag nach und
bestätigt keine Zustellung ohne Fehler. Ergänzt um echte Fehlerpfade
(5xx, nicht erreichbarer Endpunkt mit Timeout statt unbegrenztem
Warten).

go build/go vet/golangci-lint clean, gesamtes Mail-Modul
regressionsfrei getestet.
2026-09-01 17:55:44 +02:00
sysops c9b062062b feat(mail): INT-01 REST-API v1 für Mail-Zugriff & OpenAPI-Beschreibung
Neues Paket mail/internal/mailapi: drei v1-Endpunkte (Mail-Liste,
Mail-Detail, Anhang-Download). Core API-01 (REST-Grundgerüst) und
API-04 (OpenAPI-Beschreibung) haben im aktuellen Repository-Stand
keinen abrufbaren Router — RegisterRoutes registriert die Endpunkte
deshalb auf einem vom Aufrufer bereitgestellten *http.ServeMux mit dem
dokumentierten Pfadschema /api/v1/mail/..., Core kann sich später dort
einhängen, im Prüfprotokoll begründet (gleiche Situation wie
ARC-06/Core TEN-01).

tenant-Query-Parameter ist auf allen drei Endpunkten Pflicht (fehlender
Kontext -> 400), keine eigene Login-/Session-Logik (IAM bleibt
Core-Board-Sache). Anhang-Download nutzt storage.ObjectKey gegen den
physisch getrennten Bucket des Mandanten (ARC-06) — ein Anhang mit
identischer messageID in einem fremden Mandantenkontext ist strukturell
nicht erreichbar. Neue Methode search.Client.GetByMessageID liefert das
vollständige Suchdokument für Mail-Detail.

openapi.yaml: vollständiger OpenAPI-3-Beitrag für alle drei Endpunkte
inklusive Fehlerantworten. Als neue, gepinnte Abhängigkeit
github.com/getkin/kin-openapi v0.135.0 (bewusst nicht @latest — hätte
das Modul von go 1.24 auf go 1.25 gezwungen) für einen echten
Standard-Validierungslauf gegen das Dokument sowie einen
OpenAPI-Router, der jede implementierte Route real gegen das Dokument
auflöst statt nur Pfad-Strings zu vergleichen.

Alle vier Pflichtprüfungen mit echten Nachweisen: Zugriff ohne
Tenant-Kontext auf allen drei Endpunkten abgelehnt, Vertragstests inkl.
physischer Bucket-Trennung beim Anhang-Download, automatisiertes
Code-Review bestätigt Abwesenheit IAM-naher Bezeichner,
OpenAPI-Dokument validiert fehlerfrei gegen kin-openapi.

go build/go vet/golangci-lint clean, go mod verify clean, gesamtes
Mail-Modul regressionsfrei getestet.
2026-09-01 17:49:40 +02:00
sysops 2d32157de4 feat(mail): QA-02 Prüfgate Ingestion & Import
Neues Paket mail/internal/ingestiontestgate, spiegelt das etablierte
Muster aus mail/internal/qagate (QA-03) und mail/internal/importtestgate
(IMP-09): ein echtes, ausführbares Gate statt einer nur behaupteten
Prüfung. IngestionAndImportPackages listet alle 14 aus QA-02s eigener
dependsOn-Liste folgenden Pakete (Protokoll-Server, Fehlerbehandlung/
Logging, Import-Pfade). RunTestSuites führt go test -count=1 -p 1 über
alle 14 Pakete aus — -p 1 bewusst, da parallele Testbinaries sich bei
den echten QA-07-Lasttests gegenseitig CPU-Kontingent wegnehmen und
Latenz-Zielwerte durch reine Testhost-Überlastung verfehlen lassen
(real beobachtet, mit -p 1 behoben). ScanForKnownErrorPointTests prüft
automatisiert, dass die drei in Akzeptanzkriterium 2 namentlich
geforderten Fehlerpunkte (Header-Injection, Anhang-Limit, UIDVALIDITY)
je einen passenden Regressionstest im zuständigen Paket haben.

Gate-Lauf gegen aktuellen Stand real ausgeführt und mit Zeitstempel
dokumentiert: BESTANDEN, 14 Pakete grün, alle drei bekannten
Fehlerpunkte abgedeckt. Scanner durch Negativtest bewiesen, dass er
eine tatsächlich fehlende Abdeckung real erkennt statt immer
"bestanden" zu melden.

Pflichtprüfung 3 (Zweitperson bestätigt Gate-Ergebnis unabhängig)
bleibt strukturell offen — eine einzelne Sitzung kann keine unabhängige
zweite Person sein, im Prüfprotokoll dokumentiert (analog zu ING-10 und
QA-04).

go build/go vet/golangci-lint clean, gesamtes Mail-Modul
regressionsfrei getestet.
2026-09-01 17:36:48 +02:00
sysops 0505351e8f feat(mail): QA-04 Sicherheits-/Berechtigungsprüfung — fehlenden SMTP-Idle-Timeout gefunden und behoben
Gezielter Testangriff auf den SMTP-Pfad deckte einen realen
Härtungsfehler auf: ING-07 (Idle-Timeout via protoguard) wurde
versehentlich nur in mail/internal/imap und mail/internal/pop3
verdrahtet, SMTP bekam nie einen Timeout. Eine Gegenstelle, die eine
Kommandozeile ohne abschließendes CRLF öffnet und nie beendet, konnte
die Session unbegrenzt blockieren — real reproduziert und danach
behoben.

session.go/server.go (smtp): guard *protoguard.Guard neu, Timeout wird
in readLine() selbst gesetzt (ein Ort für Haupt-Serve-Schleife,
handleData, drainUntilDot). Neuer Konstruktor
NewServerWithMaxMessageBytesTLSLoggerRateLimitAndGuardConfig für
abweichende Timeout-Werte. Bestehende Konstruktoren bekommen automatisch
protoguard.DefaultConfig() (5 Minuten) statt wie zuvor gar keinen
Timeout — reine Härtung, keine Verhaltensänderung für funktionierende
Clients, QA-07-Lasttest bleibt unverändert grün.

Neue Tests: qa04_security_test.go (Header-Injection-Angriffe auf
Envelope-Adressen, Ressourcenerschöpfung durch nie abgeschlossene Zeile
— deckte den Fehler auf und bestätigt die Korrektur).
mailboxconfig/tenant_scoping_test.go: Stichprobe eines dritten
Speicherpfads (verschlüsselte IMAP-Zugangsdaten) — Zugriff mit echter,
bekannter fremder ID wird über alle vier Operationen zuverlässig
abgelehnt.

Rate-Limiting-Teil von Akzeptanzkriterium 3 real bestätigt (ING-09,
erneut mitgeprüft). API-Token-Teil bleibt offen: das Mail-Board besitzt
keine eigene Token-Authentifizierung, bewusst an Core-Board IAM
delegiert (QA-04s eigene Ausgangslage) — im Prüfprotokoll dokumentiert.

go build/go vet/golangci-lint clean, gesamtes Mail-Modul
regressionsfrei getestet.
2026-09-01 14:24:56 +02:00
sysops e19003b5d9 feat(mail): ARC-06 automatisierte Bucket-Provisionierung je Mandant
S3Driver (ARC-01) war strukturell bereits physisch getrennt: eine
Instanz kennt beim Konstruieren genau einen Bucketnamen, kein
Pfad-Präfix-Parameter, über den je ein anderes Bucket adressierbar
wäre. Was fehlte, war die automatisierte Provisionierung dieser
Trennung und der Nachweis dafür.

Neue Datei provision.go: BucketNameForTenant liefert den
deterministischen Bucketnamen je Mandant. ProvisionTenant legt in EINEM
Aufruf sowohl die Registry-Zeile in derselben tenants-Tabelle wie Core
TEN-01 (migrations/0001_tenant_registry.sql) als auch den Bucket an —
schlägt die Bucket-Anlage fehl, wird die Registry-Zeile automatisch
zurückgenommen, kein halb provisionierter Mandant. Core TEN-01 ist im
aktuellen Stand ein Grundgerüst ohne eigene aufrufbare
Provisionierungsfunktion — ProvisionTenant schreibt deshalb direkt über
den Registry-DSN in dieselbe Tabelle, dokumentiert im Prüfprotokoll.

Alle drei Pflichtprüfungen mit echten Nachweisen gegen eine reale
lokale MinIO-Instanz und Postgres durchgeführt: physische
Bucket-Trennung zweier Mandanten (ein in Mandant As Bucket
geschriebenes Objekt ist über Mandant Bs Driver nicht erreichbar, weil
es dort kein Objekt dieses Namens gibt, nicht weil ein Pfadfilter
greift); ein nie provisionierter Pseudo-Mandant scheitert auf
Bucket-Ebene (NoSuchBucket), bevor überhaupt eine Schlüsselsuche
stattfinden könnte; ein Provisionierungsaufruf legt Datenbank-Registry-
Zeile und Bucket nachweislich in einem Schritt an, inklusive
Rollback-Test bei fehlschlagender Bucket-Anlage.

go build/go vet/golangci-lint clean, gesamtes Mail-Modul
regressionsfrei getestet. Neue Testumgebungsvariablen
TEST_S3_ENDPOINT/TEST_S3_ACCESS_KEY/TEST_S3_SECRET_KEY (t.Skip ohne
sie, gleiche Konvention wie TEST_TENANT_DSN/TEST_MANTICORE_URL).
2026-09-01 14:06:23 +02:00
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
sysops af1709a2bb feat(mail): ING-09 Rate-Limiting auf Protokollebene für IMAP/POP3/SMTP
Neues Paket mail/internal/ratelimit: Token-Bucket je (Mandant,
Quelle)-Schlüssel. ConfigProvider/StaticConfig liefern Burst/Nachfüllrate
je Mandant mit Fallback auf eine Default-Konfiguration
(Akzeptanzkriterium 2/3: begrenzt, konfigurierbar, je Mandant getrennt).
Limiter.Allow liefert bei Ablehnung eine konkrete positive Wartezeit
statt nur false — Grundlage für die geforderte definierte Ablehnung MIT
Wartezeit-Hinweis statt Verbindungsabbruch (Akzeptanzkriterium 3).

IMAP LOGIN und POP3 PASS begrenzen Anmeldeversuche pro (Mandant,
Quell-IP). SMTP MAIL FROM begrenzt die Annahmerate pro (Mandant,
Absenderadresse+Quell-IP). Bei Überschreitung: POP3 -ERR, IMAP NO, SMTP
451 (temporärer Fehlercode) mit Wartezeit in Sekunden, Verbindung
bleibt in allen drei Fällen offen und weiter nutzbar. loginLimiter/
acceptLimiter sind optional (nil = deaktiviert, Rückwärtskompatibilität
zu ING-01..ING-08) — neue Konstruktoren
NewServerWithGuardTLSLoggerAndRateLimit (IMAP/POP3) und
NewServerWithMaxMessageBytesTLSLoggerAndRateLimit (SMTP). Jeder Server
bekommt eine tenantID, konsistent mit dem in ING-10 etablierten Muster
"ein Server je Mandant".

Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt, jeweils
in IMAP, POP3 und SMTP einzeln: 20 reale Versuche bei Burst=5 -> exakt
5 akzeptiert, 15 mit Wartezeit-Hinweis abgelehnt, keine Verbindung
getrennt; legitime Nutzung unterhalb der Schwelle unbeeinträchtigt;
zwei Mandanten mit unterschiedlichem Burst über denselben Limiter
unabhängig und exakt wirksam (2 vs. 8 akzeptierte Versuche).

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~31 Pakete)
regressionsfrei getestet — insbesondere QA-07-Lasttests bleiben grün,
da Rate-Limiting standardmäßig deaktiviert ist.
2026-09-01 12:22:21 +02:00
sysops 060b73566b feat(mail): QA-07 Last- & Leistungstest für IMAP/POP3/SMTP
Neues Paket mail/internal/loadtest: parallele Sessions ausführen,
Latenz-Perzentile (p50/p95/max) und Durchsatz messen, Ressourcen-
Stichprobe (Heap über runtime.MemStats, kumulierte CPU-Zeit über
/proc/self/stat). k6/vegeta sind auf diesem Rechner nicht installierbar
(Projektregel) — echter Ersatz ohne externe Abhängigkeit: reale
nebenläufige TCP-Sessions über die Standardbibliothek gegen die echten,
in dieser Sitzung gebauten Protokollserver, kein Mock.

Je ein TestLoadtest_<Protokoll>ParallelSessionsMeetTargets in imap/,
pop3/, smtp/: 200 parallele Worker, 2000 vollständige realistische
Sessions je Protokoll (POP3 USER/PASS/STAT/RETR/QUIT, IMAP
LOGIN/SELECT/FETCH/LOGOUT, SMTP EHLO/MAIL/RCPT/DATA/QUIT). Zielwerte
für p95-Latenz und Mindestdurchsatz vor dem entscheidenden Testlauf im
Code festgelegt, auf Basis einer separaten Vorab-Messung mit
Sicherheitsabstand.

Reale Messwerte auf 192.168.1.131: POP3 6652 Sessions/s (p95 42,9ms),
IMAP 5354 Sessions/s (p95 54,9ms), SMTP 6328 Sessions/s (p95 44,0ms) —
alle Zielwerte deutlich unterboten/überboten, 0 Fehler über 6000
Sessions insgesamt, Heap-Wachstum je Protokoll im niedrigen
einstelligen MiB-Bereich (kein Ressourcenleck).

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~30 Pakete)
regressionsfrei getestet.
2026-09-01 10:29:05 +02:00
sysops 12c9037121 feat(mail): ING-10 Ingestion-Testsuite — Tenant-Scoping-Tests, mimeparse-Lücke geschlossen
Kein neues Produktionspaket, Audit- und Test-Kachel über die fünf
Ingestion-Module (IMAP, POP3, SMTP, MIME, Folder-State). Zwei konkrete
Lücken geschlossen:

Neuer tenant_scoping_test.go in allen fünf Paketen: je zwei simulierte
Mandanten mit ABSICHTLICH identischen Schlüsseln (Benutzername,
Postfachname) — der Realfall, in dem ein fehlendes Scoping-Prädikat am
ehesten eine echte Vermischung zeigen würde, statt trivial durch
unterschiedliche Schlüssel zu bestehen. IMAP/POP3: zwei unabhängige
Serverinstanzen mit je eigenem Store. SMTP: zwei Serverinstanzen,
gleichzeitig mit vielen Nachrichten bedient. mimeparse: paralleles
Parsen vieler "Mandanten"-Nachrichten (das Paket hat keinen
Datenbankzugriff — Tenant-Scoping bedeutet hier: kein geteilter
veränderlicher Zustand). folderstate: echte Postgres-Instanz,
NextUID/Rebuild für Mandant A dürfen Mandant Bs Zustand nachweislich
nicht verändern.

mimeparse.ParseTolerant (IMP-02) war zu 0% Zeilenabdeckung vollständig
ungetestet — genau der aus known-issues-archivmail.md #4 bekannte
Fehler (kritische Ingestion-Logik ohne Tests). Neue tolerant_test.go:
ein fehlerhafter Teil reißt die übrigen nicht mit, Gesamtgrößenlimit
über alle Teile hinweg, strukturell kaputte Multipart-Hülle liefert
weiterhin einen echten Fehler, Nicht-Multipart-Pfad. Abdeckung
mimeparse 44,0% -> 76,7%.

Testabdeckungsbericht für alle fünf Module dokumentiert, CI-Lauf auf
frischem Checkout ohne externe Live-Postfächer verifiziert grün.
Pflichtprüfung 3 (Stichprobenreview durch zweite Person) ist durch
eine einzelne Sitzung strukturell nicht erfüllbar und bleibt offen —
im Prüfprotokoll dokumentiert, Nutzer-Review ausstehend.

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~29 Pakete)
regressionsfrei getestet.
2026-09-01 09:35:45 +02:00
sysops 7c892ed10a feat(mail): ING-08 strukturiertes Protokoll-Logging & Diagnose für IMAP/POP3/SMTP
Neues Paket mail/internal/protolog (log/slog): SessionLogger loggt
strukturierte Ereignisse einer Verbindung mit fester correlation_id und
protocol über die gesamte Verbindungsdauer (Akzeptanzkriterium 1) — ein
Logger mit logger==nil ist sicher benutzbar und loggt nichts
(Rückwärtskompatibilität zu ING-01..ING-07, Logging ist opt-in wie TLS
und Guard-Konfiguration). RedactCommandLine ersetzt bei sensiblen
Kommandos (PASS, LOGIN, AUTH) alle Argumente vollständig durch
[REDACTED] statt einzeln zu parsen (Akzeptanzkriterium 2).
Reconstruct liest zeilenweise JSON-Logs und liefert ausschließlich die
Einträge einer Korrelations-ID in Reihenfolge — das geforderte
Diagnosewerkzeug (Akzeptanzkriterium 3).

Alle drei Sessions loggen jetzt session_start/command (je empfangener
Zeile, redigiert)/session_end. Nachrichteninhalte werden strukturell
nie geloggt: SMTP-DATA-Body-Zeilen laufen durch eine eigene
Leseschleife, die nicht durch den Kommando-Logpfad der Hauptschleife
kommt: nur das Kommando DATA selbst erscheint im Log.

Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt, jeweils
in IMAP, POP3 und SMTP einzeln: Redaktion gegen den echten laufenden
Server bestätigt (Klartextpasswort bzw. absichtlich eingebettetes
Geheimnis im SMTP-Body erscheint nie im Log), zwei gemischte reale
Sessions über dieselbe Korrelations-ID lückenlos rekonstruiert,
Lasttest mit 100 Sessions mit/ohne Logging ohne relevante
Durchsatzeinbuße.

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~29 Pakete)
regressionsfrei getestet.
2026-09-01 09:07:35 +02:00
sysops b22ab67bb2 feat(mail): ING-06 TLS/STARTTLS-Absicherung für IMAP/POP3/SMTP
Neues Paket mail/internal/tlscert: hot-reloadbarer Zertifikat-Store
(Store.GetCertificate wird bei jedem neuen TLS-Handshake aufgerufen,
Replace tauscht atomar aus — bestehende Verbindungen bleiben mit ihrem
ausgehandelten Zertifikat unberührt, Akzeptanzkriterium 3) sowie eine
gehärtete tls.Config (MinVersion TLS 1.2, ausschließlich AEAD-Suiten
für TLS 1.2, Akzeptanzkriterium 2). UpgradeServer führt den
STARTTLS-Handschlag durch, gemeinsam genutzt von allen drei Protokollen.

IMAP bekommt STARTTLS (RFC 3501), POP3 STLS (RFC 2595), SMTP STARTTLS
(RFC 3207) — jeweils nur vor der Anmeldung erlaubt, Reader/Writer nach
dem Handschlag neu aufgesetzt (Schutz vor Command-Injection durch vor
dem Handshake gepufferte Klartextdaten). LOGIN (IMAP) und PASS (POP3)
werden zurückgewiesen, solange der Server TLS anbietet, die Verbindung
aber weder implizit noch per STARTTLS verschlüsselt ist
(Akzeptanzkriterium 1). Implizites TLS (Port 993/995/465) braucht keine
Codeänderung — Server.Serve nimmt jeden net.Listener entgegen, ein
tls.NewListener-gewrapptes Listener liefert bereits *tls.Conn, von der
Session per Typ-Assertion erkannt. Ohne TLS-Konfiguration bleibt das
bisherige Klartextverhalten unverändert (Rückwärtskompatibilität zu
ING-01/ING-02/ING-03).

Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt: echter
openssl-s_client-Scan gegen den laufenden SMTP-Server (TLS 1.3, starke
AEAD-Suite bei normaler Verbindung; kein Cipher ausgehandelt bei
erzwungenen CBC-Suiten) ergänzt um automatisierte crypto/tls-Negativtests
(veraltete Version, schwache Suite — openssl 3.5.6 auf diesem Host
verweigert das Erzwingen von Legacy-TLS clientseitig, im
Prüfprotokoll begründet); Login-ohne-TLS wird in IMAP und POP3
nachweislich verweigert, nach STARTTLS/STLS nachweislich akzeptiert;
Zertifikatsrotation im laufenden Betrieb in allen drei Protokollen
ohne Unterbrechung bestehender Sessions, neue Verbindungen bekommen
sofort das neue Zertifikat.

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~28 Pakete)
regressionsfrei getestet.
2026-09-01 01:17:40 +02:00
sysops 6631bcbbdd feat(mail): ING-03 SMTP-Server & Mailer (RFC 5321)
Neues Paket mail/internal/smtp: SMTP-Server für eingehende Mails, von
Grund auf implementiert, analog zu mail/internal/imap und
mail/internal/pop3 — TCP-Listener mit einer Goroutine pro Verbindung,
Session-Zustandsmaschine (Greeting/Ready/MailFromSet/RcptToSet),
Kommandos HELO/EHLO, MAIL FROM, RCPT TO, DATA, RSET, NOOP, QUIT.
Envelope wird schrittweise aufgebaut und validiert (503 bei
übersprungenen Schritten, 553 bei ungültiger Absender-/Empfängeradresse),
Nachrichtengröße wird während DATA laufend gegen eine konfigurierbare
Höchstgröße geprüft (552 bei Überschreitung, Sink bekommt die Nachricht
nicht). Dot-Stuffing beim Empfang korrekt rückgängig gemacht.

Neues Paket mail/internal/mailer: Mailer-Komponente für ausgehende
Nachrichten. headerWriter ist die einzige Stelle, an der Header
geschrieben werden — jeder Feldwert wird hart gegen CR/LF/Steuerzeichen
geprüft, bevor er in die Nachricht geschrieben wird. Behebt den
bekannten archivmail-Fehler (Header-Injection durch Stringkonkatenation
ohne CRLF-Prüfung, siehe known-issues-archivmail.md #1). Sender.Send
überträgt per echtem net/smtp-Client (Standardbibliothek) — keine
Zugangsdaten im Code, Zieladresse kommt vom Aufrufer.

Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt:
CRLF-/Steuerzeichen-Injection in Betreff und Anzeigenamen schlägt fehl
(vier Testfälle), Ende-zu-Ende-Header-Integritätstest über echten
SMTP-Dialog (Mailpit/MailHog nicht installierbar auf diesem Rechner —
Ersatz durch den in dieser Kachel gebauten echten SMTP-Server, kein
Mock, im Prüfprotokoll begründet), Lasttest mit 50 gleichzeitigen
Verbindungen ohne Goroutine-/Verbindungsleck.

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~26 Pakete)
regressionsfrei getestet.
2026-09-01 00:59:56 +02:00
sysops 16c4ad0075 feat(mail): ING-07 einheitliche Fehlerbehandlung & Wiederverbindung IMAP/POP3
Neues Paket mail/internal/protoguard kapselt die für IMAP- und
POP3-Sessions gemeinsam benötigte Timeout- und Backoff-Logik einer
einzelnen Verbindung:

- Pro Protokollphase konfigurierbarer Idle-Read-Timeout (POP3:
  Authorization/Transaction, IMAP: NotAuthenticated/Selected), vor
  jedem Lesevorgang neu gesetzt.
- Sich verdoppelnder Backoff bei wiederholten Anmeldefehlversuchen
  einer Verbindung (BackoffBase bis BackoffMax), Verbindungstrennung
  nach konfigurierbarer Höchstzahl statt Dauerschleife.

Server.NewServer bleibt unverändert (Standardkonfiguration);
NewServerWithGuardConfig erlaubt abweichende Werte. Ressourcenaufräumung
bei Verbindungsabbruch war bereits durch defer conn.Close() strukturell
gegeben — der Timeout sorgt dafür, dass dieser Pfad auch bei hängenden
oder böswilligen Gegenstellen zuverlässig erreicht wird.

Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt:
Chaos-Test mit 30 hart gekappten Verbindungen während aktiver
Übertragung (kein Goroutine-Leck), Timeout-Auslösung in jeder
Protokollphase beider Server, steigender Backoff mit definierter
Verbindungstrennung nach Höchstzahl an Fehlversuchen.

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~24 Pakete)
regressionsfrei getestet.
2026-09-01 00:51:52 +02:00
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
sysops c895a67c4b core: initial Go module skeleton (config, db pool, tenant registry migration) 2026-08-27 17:27:59 +02:00
sysops 72261cc69f first commit 2026-08-27 17:20:02 +02:00