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:
@@ -96,7 +96,7 @@ autoritative Quelle.
|
||||
|
||||
| Bereich | Ungebunden | Gebunden | Hinweis |
|
||||
|---|---|---|---|
|
||||
| tenders | 62 | 0 | unverändert |
|
||||
| tenders | 36 | 26 | **war 62/0** — Aufgabe 2/3 (260909-laa) haben die fünf Nutzer-CRUD-Dienste (vier vollständig, `tender-rss-feed.service.ts` teilweise mit im Code begründeter WINDOWS-#19-Grenze) und die Je-Treffer-Hälften der beiden Hintergrunddienste (`tender-digest.scheduler.ts`, `tender-matching.service.ts`) auf `forTenant()` umgestellt. Die 36 verbleibenden ungebundenen Treffer sind die zwölf bewusst nicht angefassten Paare (D-03-Katalog, zwei Fan-out-Adapter) plus die drei bewusst ungebundenen RSS-Pfade plus die übergreifenden Hälften der beiden Hintergrunddienste (Etappe-3-Übergabe) |
|
||||
| groups | 0 | 31 | **war 37/0** — Aufgabe 2/3 (260909-jts) haben `groups.service.ts` (12 Methoden) und `module-grants.service.ts` (5 Methoden) vollständig auf `forTenant()`/`withTenantTransaction()` umgestellt. Die neun zusätzlichen, über `tx` gebundenen Zugriffe innerhalb der drei Transaktionen zählt dieses einfache Muster nicht mit (siehe Methodenhinweis oben) |
|
||||
| ldap | 4 | 26 | **war 21/0** — Aufgabe 2/3 (260909-ipc) haben `ldap-config.service.ts` (5 Methoden) und `ldap.service.ts` (6 Methoden, 11 Abfragen) auf `forTenant()` umgestellt. Die 4 verbleibenden ungebundenen Treffer sind bewusst: `getAllActiveConfigs`/`onApplicationBootstrap` (Befund B) und `resolveEmailForWrite` (Befund A, T-IPC-04) |
|
||||
| dkv | 21 | 0 | unverändert |
|
||||
@@ -108,7 +108,7 @@ autoritative Quelle.
|
||||
| tenant | 8 | 0 | unverändert |
|
||||
| favorites | 7 | 0 | unverändert |
|
||||
| settings | 4 | 0 | unverändert |
|
||||
| **Summe** | **173** | **62** | Ungebunden: war 210 vor dieser Etappe (260909-ipc-Stand), Delta = die 37 in Aufgabe 2/3 (260909-jts) umgestellten `groups`-Rohtreffer. Gebunden: war 31, jetzt zusätzlich 31 in `groups` (Bodensatz — siehe Methodenhinweis oben) |
|
||||
| **Summe** | **147** | **88** | Ungebunden: war 173 vor dieser Etappe (260909-jts-Stand), Delta = die 26 in Aufgabe 2/3 (260909-laa) umgestellten `tenders`-Rohtreffer. Gebunden: war 62, jetzt zusätzlich 26 in `tenders` |
|
||||
|
||||
## Klassen-Verteilung (nach (Datei, Modell)-Fundstellen, 62 Paare)
|
||||
|
||||
@@ -124,11 +124,18 @@ Die Zahl ist der Ausgabe der Pruefung in
|
||||
`apps/api/src/prisma/rls-access-inventory.spec.ts` entnommen, nicht
|
||||
geschaetzt.
|
||||
|
||||
**Stand 260909-laa (Aufgabe 2):** dieselben 62 Paare, keine neue Fundstelle
|
||||
hinzugekommen oder verschwunden — nur EINE Klasse hat sich verschoben:
|
||||
`tender-rss-feed.service.ts`/`tenderRssFeedSource` wechselt von
|
||||
`muss-mandantengebunden` auf `beides` (WINDOWS #19 — `createForUser` bindet,
|
||||
`listForUser`/`createPlatform`/`remove` bleiben bewusst uebergreifend), der
|
||||
exakte Praezedenzfall aus `ldapConfig` in 260909-ipc.
|
||||
|
||||
| Klasse | Anzahl Paare |
|
||||
|---|---|
|
||||
| muss-mandantengebunden | 33 |
|
||||
| muss-mandantengebunden | 32 |
|
||||
| keine-mandantengebundene-tabelle | 16 |
|
||||
| beides | 10 |
|
||||
| beides | 11 |
|
||||
| bewusst-uebergreifend | 3 |
|
||||
| **Summe** | **62** |
|
||||
|
||||
@@ -160,16 +167,31 @@ betroffen:
|
||||
Zustand zum Zeitpunkt der ldap-Umstellung korrekt beschreibt und für
|
||||
spätere Etappen als Beleg dient, siehe auch
|
||||
`docs/mandantentrennung-etappe2-fehlerrichtung.md`, Abschnitt (e), Befund D.
|
||||
- **`tender-digest.scheduler.ts`** (Ausschreibungs-Digest): liest
|
||||
`tenderMatch`/`tenderNotificationPref`/`user` bewusst über ALLE Mandanten
|
||||
in einem `findMany` (ein einziger globaler Cron-Job, kein Mandant im
|
||||
Job selbst — so von Anfang an entworfen, Pitfall 1 in den Kommentaren der
|
||||
Datei). Innerhalb der Verteilung je Treffer ist der Mandant aus der Zeile
|
||||
bekannt und der Versand muss darauf gebunden laufen.
|
||||
- **`tender-matching.service.ts`** (Ausschreibungs-Sofortmeldung): dieselbe
|
||||
Form — `tenderMatch`/`tenderSavedSearch`/`user` werden für die
|
||||
Sofort-Benachrichtigung über alle betroffenen Mandanten hinweg gelesen,
|
||||
der Versand je Treffer ist mandantengebunden.
|
||||
- **`tender-digest.scheduler.ts`** (Ausschreibungs-Digest) — **Stand
|
||||
260909-laa, Aufgabe 3: Je-Treffer-Hälfte geschlossen, übergreifende
|
||||
Hälfte an Etappe 3 übergeben.** Liest `tenderMatch` (Kandidatenabfrage,
|
||||
`findMany` mit `distinct: ['userId']`) bewusst über ALLE Mandanten in
|
||||
einem einzigen `findMany` (ein einziger globaler Cron-Job, kein Mandant
|
||||
im Job selbst — so von Anfang an entworfen, Pitfall 1 in den Kommentaren
|
||||
der Datei) — diese Kandidatenabfrage bleibt UNGEBUNDEN und ist im Code
|
||||
als Etappe-3-Übergabe kommentiert. Sie wählt zusätzlich das
|
||||
denormalisierte `tenantId` der Treffer-Zeile mit aus, damit die Schleife
|
||||
binden kann; der Sonderfall eines Nutzers mit Treffern unter zwei
|
||||
verschiedenen Mandanten (Mandantenwechsel) ist NICHT gelöst, siehe
|
||||
`docs/mandantentrennung-etappe2-fehlerrichtung.md`. Innerhalb der
|
||||
Schleife laufen `tenderNotificationPref.findUnique`,
|
||||
`tenderMatch.findMany`/`updateMany` und `user.findUnique` je
|
||||
Kandidatenzeile über `forTenant()`, gebunden an deren Mandanten.
|
||||
- **`tender-matching.service.ts`** (Ausschreibungs-Sofortmeldung) —
|
||||
**Stand 260909-laa, Aufgabe 3: Je-Treffer-Hälfte geschlossen, übergreifende
|
||||
Hälfte an Etappe 3 übergeben.** `tenderSavedSearch.findMany` (Profile ALLER
|
||||
Mandanten) und der Lesezugriff auf den plattformweiten `tender`-Katalog
|
||||
(D-03) bleiben UNGEBUNDEN, beide im Code als Etappe-3-Übergabe bzw. D-03
|
||||
kommentiert. Innerhalb der Profilschleife laufen die Treffer-Anlage
|
||||
(`tenderMatch.upsert`) und — im nachgelagerten Instant-Dispatch —
|
||||
`tenderMatch.findMany`/`updateMany` sowie `user.findUnique` über
|
||||
`forTenant()`, EIN gebundener Client je Profil, gebunden an dessen
|
||||
Mandanten.
|
||||
|
||||
## Bestandsaufnahme
|
||||
|
||||
@@ -220,17 +242,17 @@ verwendet), Klasse, Stand (`gebunden`/`ungebunden`/`gemischt`, seit
|
||||
| apps/api/src/tenders/adapters/rss.adapter.ts | tenderRssFeedSource | bewusst-uebergreifend | ungebunden | Fan-out über jeden aktiven Feed, plattformweit UND persönlich, in einer Abfrage (Zeilen 55–83 im Dateikopf begründet) — dieselbe Scheduler-Ebene wie beim E-Mail-Adapter. |
|
||||
| apps/api/src/tenders/tender-dedup.service.ts | tender | keine-mandantengebundene-tabelle | ungebunden | Explizit im Dateikopf: "platform-global, RLS-exempt tables. Never wrap these queries in forTenant()." (D-03) |
|
||||
| apps/api/src/tenders/tender-dedup.service.ts | tenderSource | keine-mandantengebundene-tabelle | ungebunden | Dieselbe Begründung. |
|
||||
| apps/api/src/tenders/tender-digest.scheduler.ts | tenderMatch | beides | ungebunden | Ein einziger globaler Cron-Job liest über ALLE Mandanten (bewusst übergreifend, Pitfall-1-Kommentar im Dateikopf), der Versand je Treffer ist an dessen Mandanten gebunden. |
|
||||
| apps/api/src/tenders/tender-digest.scheduler.ts | tenderNotificationPref | beides | ungebunden | Dieselbe Begründung — Präferenzen werden über alle Mandanten gelesen, aber je Zeile mandantenbezogen ausgewertet. |
|
||||
| apps/api/src/tenders/tender-digest.scheduler.ts | user | beides | ungebunden | E-Mail-Adressen für den Versand werden über alle Mandanten gelesen, der eigentliche Versand ist je Treffer mandantengebunden. |
|
||||
| apps/api/src/tenders/tender-digest.scheduler.ts | tenderMatch | beides | gemischt | Ein einziger globaler Cron-Job liest über ALLE Mandanten (bewusst übergreifend, Pitfall-1-Kommentar im Dateikopf) — seit 260909-laa (Aufgabe 3) bleibt die Kandidatenabfrage (`findMany` mit `distinct`) bewusst ungebunden, die Je-Treffer-Abfrage (`findMany` nach Mandant) und die Stempelung (`updateMany`) je Kandidatenzeile laufen über `forTenant()`. |
|
||||
| apps/api/src/tenders/tender-digest.scheduler.ts | tenderNotificationPref | beides | gebunden | Präferenzen werden je Kandidatenzeile gelesen. Seit 260909-laa (Aufgabe 3) läuft der einzige Zugriff über `forTenant()`, gebunden an den Mandanten der jeweiligen Zeile. |
|
||||
| apps/api/src/tenders/tender-digest.scheduler.ts | user | beides | gebunden | E-Mail-Adressen für den Versand werden je Kandidatenzeile gelesen. Seit 260909-laa (Aufgabe 3) läuft der einzige Zugriff über `forTenant()`. |
|
||||
| apps/api/src/tenders/tender-email-config.service.ts | tenderEmailConfig | muss-mandantengebunden | gebunden | Nutzer-CRUD für die eigene Postfachanbindung (Phase 17, D-01) — anders als der Fan-out-Adapter oben, hier ist der Mandant aus der Anfrage bekannt. Seit 260909-laa (Aufgabe 2) laufen `getConfigForApi` (beide Lesezugriffe), `saveConfig` und `testConnection` vollständig über `forTenant()`. |
|
||||
| apps/api/src/tenders/tender-fingerprint-backfill.service.ts | tender | keine-mandantengebundene-tabelle | ungebunden | Einmaliges Backfill-Skript über den plattformweiten `Tender`-Katalog (D-03). |
|
||||
| apps/api/src/tenders/tender-ingestion.service.ts | tender | keine-mandantengebundene-tabelle | ungebunden | Explizit im Dateikopf: "Multi-tenant safety (D-03, T-10-09): uses the plain, non-tenant-scoped ... queries ... these are platform-global". |
|
||||
| apps/api/src/tenders/tender-ingestion.service.ts | tenderSourcePollConfig | keine-mandantengebundene-tabelle | ungebunden | Plattformweiter Poll-Status, kein `tenantId` (Migration 20260909140000, Gruppe b). |
|
||||
| apps/api/src/tenders/tender-matching.service.ts | tender | keine-mandantengebundene-tabelle | ungebunden | Liest den plattformweiten Katalog (D-03), um Treffer zu berechnen — kein `tenantId`. |
|
||||
| apps/api/src/tenders/tender-matching.service.ts | tenderMatch | beides | ungebunden | Sofortmeldung: Treffer über alle betroffenen Mandanten gelesen, Versand je Treffer mandantengebunden — siehe Abschnitt "Der Hintergrunddienst als Falle". |
|
||||
| apps/api/src/tenders/tender-matching.service.ts | tenderSavedSearch | beides | ungebunden | Dieselbe Begründung — gespeicherte Suchprofile aller Mandanten werden gegen neue Treffer geprüft, der Versand ist je Profil mandantengebunden. |
|
||||
| apps/api/src/tenders/tender-matching.service.ts | user | beides | ungebunden | Dieselbe Begründung — E-Mail-Adressen für die Sofortmeldung. |
|
||||
| apps/api/src/tenders/tender-matching.service.ts | tenderMatch | beides | gebunden | Sofortmeldung — siehe Abschnitt "Der Hintergrunddienst als Falle". Seit 260909-laa (Aufgabe 3) laufen sowohl die Treffer-Anlage (`upsert`) als auch der Instant-Dispatch (`findMany`/`updateMany`) vollständig über `forTenant()`, EIN gebundener Client je Profil. |
|
||||
| apps/api/src/tenders/tender-matching.service.ts | tenderSavedSearch | beides | ungebunden | Gespeicherte Suchprofile ALLER Mandanten werden gegen neue Treffer geprüft — bewusst übergreifend, Etappe-3-Übergabe (260909-laa, Aufgabe 3, unverändert). |
|
||||
| apps/api/src/tenders/tender-matching.service.ts | user | beides | gebunden | E-Mail-Adressen für die Sofortmeldung. Seit 260909-laa (Aufgabe 3) läuft der einzige Zugriff über `forTenant()`, gebunden an den Mandanten des jeweiligen Profils. |
|
||||
| apps/api/src/tenders/tender-notification-pref.service.ts | tenderNotificationPref | muss-mandantengebunden | gebunden | Nutzer-CRUD für die eigenen Benachrichtigungseinstellungen — Mandant aus der Anfrage bekannt. Seit 260909-laa (Aufgabe 2) laufen `getForUser`/`setForUser` vollständig über `forTenant()`; `setForUser` übersetzt eine P2002-Verletzung (Befund F) in eine verständliche deutsche Meldung. |
|
||||
| apps/api/src/tenders/tender-rss-feed.service.ts | tenderRssFeedSource | beides | gemischt | Nutzer-CRUD für die eigenen RSS-Quellen (anders als der Fan-out in `adapters/rss.adapter.ts`). Seit 260909-laa (Aufgabe 2) bindet `createForUser` (Zähler + Anlage, beide ausschließlich auf persönlichen Zeilen mit gesetztem Mandanten) über `forTenant()`; `listForUser`/`createPlatform`/`remove` bleiben bewusst ungebunden, weil sie (auch) die nullbare, plattformweite Zeile berühren (WINDOWS #19, Aufgabe 1 gemessen: eine gebundene Zeile wäre unter jedem Mandanten unsichtbar bzw. ein gebundenes Einfügen ohne Mandant würde abgewiesen). Korrektur der Klasse von `muss-mandantengebunden` auf `beides`, keine Verhaltensänderung — derselbe Präzedenzfall wie `ldapConfig` in 260909-ipc. |
|
||||
| apps/api/src/tenders/tender-saved-search.service.ts | tenderSavedSearch | muss-mandantengebunden | gebunden | Nutzer-CRUD für die eigenen gespeicherten Suchprofile — Mandant aus der Anfrage bekannt. Seit 260909-laa (Aufgabe 2) laufen `list`/`create`/`update`/`remove` vollständig über `forTenant()`. |
|
||||
|
||||
Reference in New Issue
Block a user