feat(laa-03): binde die Je-Treffer-Haelften der Hintergrunddienste und schliesse die Dokumente

tender-digest.scheduler.ts: die uebergreifende Kandidatenabfrage
(tenderMatch.findMany mit distinct:['userId']) bleibt bewusst ungebunden
und waehlt zusaetzlich das denormalisierte tenantId der Treffer-Zeile mit
aus; innerhalb der Schleife binden tenderNotificationPref.findUnique,
tenderMatch.findMany/updateMany und user.findUnique an den Mandanten
DIESER Kandidatenzeile.

tender-matching.service.ts: die Profilabfrage (tenderSavedSearch.findMany)
und der Lesezugriff auf den plattformweiten Tender-Katalog (D-03) bleiben
ungebunden; innerhalb der Profilschleife bindet die Treffer-Anlage
(tenderMatch.upsert), im nachgelagerten Instant-Dispatch binden
tenderMatch.findMany/updateMany und user.findUnique — je EIN gebundener
Client pro Profil, nicht neu je Treffer.

Beide Dateien tragen Codekommentare, die die uebergreifenden Abfragen
ausdruecklich als Etappe-3-Uebergabe benennen — die Trennlinie zwischen
Etappe 2 und Etappe 3 wird hier gezogen, nicht verwischt.

Alle drei betroffenen Testdateien (inkl. der gemeinsamen
Integrationsdatei) bekommen den Zwei-Client-Nachweis, Tests fuer
Zwei-Mandanten-Laeufe und die lautlose Fehlerform (kein Versand,
notifiedAt bleibt NULL). Falsifiziert: ein probeweiser Rueckbau der
user.findUnique-Bindung im Instant-Dispatch von tender-matching.service.ts
machte genau den erwarteten Test rot, danach zurueckgenommen.

rls-access-inventory.spec.ts gemessen und docs/mandantentrennung-zugriffsklassifikation.md
nachgezogen: Stand der fuenf betroffenen Paare (tender-digest.scheduler.ts/
tenderMatch,tenderNotificationPref,user; tender-matching.service.ts/
tenderMatch,user) auf gemischt bzw. gebunden; Bereichsuebersicht und
Klassen-Verteilung neu gemessen (tenders jetzt 36 ungebunden/26 gebunden);
"Der Hintergrunddienst als Falle" um den Abschluss beider Dateien ergaenzt.

docs/mandantentrennung-etappe2-fehlerrichtung.md: (t4) um den Nachtrag
ergaenzt, dass die Je-Treffer-Haelften geschlossen sind und die
uebergreifenden Haelften an Etappe 3 uebergeben bleiben.

770 Tests gruen, Typpruefung sauber, Wegwerf-Werkzeug 32/32, kein
Schema-/Migrations-/Compose-/Umgebungsdatei-Diff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
2026-09-09 16:02:40 +02:00
parent 3336a6e419
commit df5c5b728b
7 changed files with 461 additions and 108 deletions
@@ -548,6 +548,31 @@ Warnung wäre Dauerlärm und verlöre ihr Signal.
über alle Mandanten hinweg ungebunden und ist als Etappe-3-Übergabe
kommentiert — siehe `docs/mandantentrennung-zugriffsklassifikation.md`,
Abschnitt "Der Hintergrunddienst als Falle".
**Nachtrag (260909-laa, Aufgabe 3): Je-Treffer-Hälften GESCHLOSSEN,
übergreifende Hälften ausdrücklich an Etappe 3 übergeben.** Innerhalb der
Kandidatenschleife von `tender-digest.scheduler.ts`
(`tenderNotificationPref.findUnique`, `tenderMatch.findMany`/`updateMany`,
`user.findUnique`) und der Profilschleife von `tender-matching.service.ts`
(`tenderMatch.upsert` in der Treffer-Anlage, sowie im nachgelagerten
Instant-Dispatch `tenderMatch.findMany`/`updateMany` und
`user.findUnique`) laufen jetzt alle Zugriffe über `forTenant()`,
gebunden an den Mandanten der jeweiligen Kandidaten-/Profilzeile — je EIN
gebundener Client pro Zeile, nicht neu je Modellzugriff. Belegt durch
Bindungstests je Methode UND einen Falsifizierungsnachweis (ein
probeweiser Rückbau der `user.findUnique`-Bindung im Instant-Dispatch von
`tender-matching.service.ts` machte genau den erwarteten Test rot, danach
zurückgenommen — siehe 260909-laa-SUMMARY.md). Die übergreifenden
Kandidaten-/Profilabfragen selbst
(`tenderMatch.findMany({distinct:['userId']})` bzw.
`tenderSavedSearch.findMany()`) bleiben UNVERÄNDERT ungebunden und tragen
im Code einen Kommentar, der sie als Etappe-3-Übergabe benennt — die
Trennlinie zwischen Etappe 2 und Etappe 3 wird hier gezogen, nicht
verwischt. Der Sonderfall, dass ein Nutzer Treffer unter zwei
verschiedenen Mandanten haben könnte (der Digest wählt das
denormalisierte `tenantId` der Kandidatenzeile über `distinct`, was bei
einem Mandantenwechsel veraltet sein kann), ist NICHT gelöst, sondern im
Code und hier benannt — Gegenstand von Etappe 3.
- **Befund K — die Abhängigkeit vom noch nicht umgestellten Bereich
`settings`.** `tender-mail.service.ts` holt die SMTP-Angaben über
`SettingsService.getDecryptedSmtpConfig(tenantId)`; `settings.service.ts`