- 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>
54 lines
3.0 KiB
Markdown
54 lines
3.0 KiB
Markdown
# ARC-03 – Prüfprotokoll: Dublettenerkennung E-Mail
|
||
|
||
Voraussetzung ARC-01 (Mail, Fertig).
|
||
|
||
## Umsetzung
|
||
|
||
- `mail/internal/dedup/hash.go` — `HashAndBuffer(plaintext io.Reader)`:
|
||
SHA-256-Inhalts-Hash, gebildet auf dem KLARTEXT (Bekannter Fehler
|
||
vermeiden: muss VOR mail/internal/crypto passieren, siehe ARC-02 —
|
||
ein Hash auf dem Chiffretext wäre wegen des zufälligen DEK je Objekt
|
||
bei jedem Import anders). Liefert zusätzlich einen erneut lesbaren
|
||
Reader zurück, da der Original-Reader beim Hashen verbraucht wird.
|
||
- `mail/internal/dedup/store.go` — `Store.Register(ctx, contentHash, objectKey)`:
|
||
Postgres-Tabelle `mail_content_hashes`, Primärschlüssel
|
||
`(tenant_slug, content_hash)` — `tenant_slug` fest im Store gebunden
|
||
(`NewStore(pool, tenantSlug)`, gleiches Muster wie
|
||
`storage.Service`/`encstorage.Service`), nicht nur Konvention.
|
||
`ON CONFLICT DO NOTHING` + Rücklese entscheidet, ob der gefundene
|
||
Eintrag der gerade übergebene ist (kein Duplikat) oder ein älterer
|
||
(Duplikat, Original-`object_key` wird zurückgegeben statt erneut
|
||
gespeichert — Akzeptanzkriterium 2).
|
||
- Kein Umbau: `mail/internal/storage`/`mail/internal/crypto`/
|
||
`mail/internal/encstorage` unverändert (`git diff --stat` bleibt für
|
||
alle drei leer). `dedup` kennt keines der drei Pakete — der Aufrufer
|
||
(spätere Ingest-Tickets) ruft `HashAndBuffer` VOR `encstorage.Put`
|
||
auf.
|
||
|
||
## Prüfungen
|
||
|
||
| # | Prüfung | Ergebnis |
|
||
|---|---|---|
|
||
| 1 | Test: dieselbe Nachricht aus zwei Quellen wird als Duplikat erkannt | **bestanden** – `TestRegister_SameMessageFromTwoSourcesIsDuplicate`: gleicher Hash, zwei verschiedene `object_key` ("quelle-1/objekt", "quelle-2/objekt") — zweite Registrierung liefert real `isDuplicate=true` und referenziert das Original `quelle-1/objekt` |
|
||
| 2 | Test: zwei Mandanten mit identischem Mailinhalt werden nicht fälschlich verknüpft | **bestanden** – `TestRegister_SameContentTwoTenantsNotLinked`: zwei `Store`-Instanzen mit unterschiedlichem `tenantSlug`, IDENTISCHER Hash — beide Registrierungen liefern real `isDuplicate=false`, keine Verknüpfung über die Mandantengrenze |
|
||
| 3 | Test mit knapp unterschiedlichen Nachrichten bestätigt korrekte Nicht-Erkennung | **bestanden** – `TestHashAndBuffer_SlightlyDifferentContentDifferentHash`: zwei Nachrichten, die sich nur im letzten Zeichen unterscheiden (`.` vs `,`) — real unterschiedlicher SHA-256-Hash |
|
||
|
||
## Build/Test-Ergebnis (192.168.1.131)
|
||
|
||
```
|
||
go build ./... -> clean
|
||
go vet ./... -> clean
|
||
golangci-lint run ./... -> 0 issues
|
||
TEST_TENANT_DSN=postgresql://nexarch_test:***@localhost:5432/tenant_acme?sslmode=disable \
|
||
go test ./... -v -p 1 -> alle Pakete bestanden, inkl. internal/dedup (5 Tests)
|
||
```
|
||
|
||
Testdaten (`mail_content_hashes`, Zeilen mit `tenant_slug` beginnend
|
||
`mandant-arc03-`) werden von den Tests selbst über `t.Cleanup`
|
||
entfernt.
|
||
|
||
## Gesamtergebnis
|
||
|
||
**Bestanden.** Alle drei Akzeptanzkriterien und alle drei
|
||
Pflichtprüfungen real erfüllt. Entsperrt SRC-01, SRC-02, SRC-07.
|