docs(quick-260911-gwh): Etappe 2 auf Endstand bringen -- sechster Fall, Befund K erfuellt, Ledger, Anleitung
- .planning/WINDOWS.md: drei neue offene Eintraege (#30 Startpfad des Mailmoduls, #31 verschluckte Leere favorites, #32 verschluckte Leere settings); Platzhalter WINDOWS #TBD-GWH in settings.service.ts und mail.module.ts durch #30 ersetzt - docs/mandantentrennung-zugriffsklassifikation.md: Uebersichtszeilen favorites (0/8, war 7/0) und settings (1/3, war 4/0) neu gemessen; Summenzeile 68/178 (Endstand Etappe 2); Bestandsaufnahme (favoriteLink gebunden, smtpConfig gemischt, neue Zeile widgetInstance/gebunden); Klassen-Verteilung 65 Paare (33/17/13/2); Hintergrunddienst-Abschnitt mit sechstem Fall (mail.module.ts, WINDOWS #30) und erfuellter Befund-K-Bedingung; neuer Punkt in "Was diese Etappe NICHT entscheidet" - docs/mandantentrennung-etappe2-fehlerrichtung.md: Nachtraege unter Befund K in (t4) und im Uebergaben-Absatz von (d4) -- Reihenfolgebedingung erfuellt; ## Etappe 2 -- Abschluss mit den derivierten Endzahlen - docs/anleitung-entwicklung.md: 23 RLS-Tabellen statt sieben, FavoriteLink nicht mehr als Tabelle ohne Regel, tenantPrisma statt manuellem tenantId-Filter als gelebter Stil - rls-access-inventory.spec.ts wieder gruen (11/11), volle Suite 994/994, Werkzeug 137/137; zwei Dokument-Falsifizierungen durchgefuehrt und zurueckgenommen (siehe SUMMARY) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
@@ -308,21 +308,25 @@ Prisma-Client mehr und veröffentlicht keinen auf dem Anfrageobjekt.
|
||||
> Express-Middleware mit identischer Logik — beides wurde mit 260911-e2s entfernt, nachdem eine
|
||||
> Volltextsuche keinen Leser dieser Eigenschaft außerhalb der beiden Dateien fand.
|
||||
|
||||
`app.current_tenant` wird von **Postgres Row-Level-Security** ausgewertet. RLS-Policies sind aber
|
||||
**nicht** auf allen Tabellen aktiv — aktuell nur auf `User`, `PasswordResetToken`, `LdapConfig`,
|
||||
`LdapFieldMapping`, `Group`, `GroupMembership` und `ModuleGrant` (siehe die Migrationen
|
||||
`20260618112133_rls_policies` und `20260804130918_groups_rls_policies`). Alle übrigen
|
||||
mandantenbezogenen Tabellen — u. a. `DkvVehicleMaster`, `DkvInvoiceHistory`, `CalendarSource`,
|
||||
`Tender`, `TenderSavedSearch`, `FavoriteLink` — tragen zwar eine `tenantId`-Spalte, aber **keine**
|
||||
RLS-Policy.
|
||||
`app.current_tenant` wird von **Postgres Row-Level-Security** ausgewertet. RLS-Policies liegen
|
||||
seit `20260909140000_rls_remaining_tenant_tables` auf 23 Tabellen (4 aus
|
||||
`20260618112133_rls_policies`, 3 aus `20260804130918_groups_rls_policies`, 16 aus der
|
||||
`_rls_remaining_tenant_tables`-Migration selbst — `grep -c "ENABLE ROW LEVEL SECURITY"` über die
|
||||
drei Migrationen, zur Ausführungszeit nachzählen), darunter `FavoriteLink` und `SmtpConfig`. Ohne
|
||||
eigene `tenantId`-Spalte bzw. bewusst plattformweit bleiben `Module`, `Tenant`, `Tender`,
|
||||
`TenderSource` und `TenderSourcePollConfig` (siehe die Bestandsaufnahme in
|
||||
`docs/mandantentrennung-zugriffsklassifikation.md`, Klasse `keine-mandantengebundene-tabelle`, für
|
||||
die vollständige, maschinell geprüfte Liste — von dort ableiten, nicht raten).
|
||||
|
||||
**Was ein Entwickler nie vergessen darf:** Bei jeder Query gegen eine Tabelle ohne RLS-Policy muss
|
||||
`tenantId` **manuell** in die `where`-Klausel — die Datenbank filtert hier nichts von selbst. Das
|
||||
ist im Code auch der gelebte Stil: `DkvService.loadConfig()`
|
||||
(`apps/api/src/dkv/dkv.service.ts`) etwa nutzt den plain, UNGEBUNDENEN `PrismaService` und
|
||||
filtert explizit mit `where: { tenantId }`. Wer bei einer solchen Tabelle das `tenantId`-Filter
|
||||
vergisst, liest oder schreibt mandantenübergreifend — ohne dass RLS das auffängt. Bei den sieben
|
||||
RLS-geschützten Tabellen greift die DB-seitige Absicherung zusätzlich, vorausgesetzt die Query
|
||||
**Was ein Entwickler nie vergessen darf:** jeder Zugriff auf eine mandantengebundene Tabelle läuft
|
||||
dienst-intern über einen mit `forTenant()` gebundenen Klienten `tenantPrisma`
|
||||
(`apps/api/src/prisma/prisma-tenant.extension.ts`) — die zusätzlichen `where`-Filter über
|
||||
`userId` bleiben bestehen, wo die Regel selbst keine Benutzerdimension kennt (siehe
|
||||
`docs/mandantentrennung-etappe2-fehlerrichtung.md`). Bei den Tabellen ohne eigene `tenantId`
|
||||
(oben) filtert die Anwendung stattdessen — wo relevant — über den zutreffenden Bezug (z. B.
|
||||
plattformweiter Katalog, kein Mandantenfilter nötig); siehe
|
||||
`docs/mandantentrennung-zugriffsklassifikation.md` für den vollständigen Stand je Datei/Modell.
|
||||
Bei den RLS-geschützten Tabellen greift die DB-seitige Absicherung zusätzlich, vorausgesetzt die Query
|
||||
läuft tatsächlich über einen dienst-intern per `forTenant()` gebundenen Client und nicht über
|
||||
den globalen, ungebundenen `PrismaService`.
|
||||
|
||||
|
||||
@@ -3025,14 +3025,22 @@ Regelschluss-Plan `tenders`/`SearchProvider` (260910-jab), `tenant`
|
||||
|
||||
**Summenzeile der Übersichtstabelle, vorher/nachher.** Zum Kopf des
|
||||
Klassifikationsdokuments (Stand 260909-eor): 227 Rohtreffer über 59
|
||||
Datei-Modell-Paare. Nach Aufgabe 3 dieses Laufs (siehe Summenzeile in
|
||||
`docs/mandantentrennung-zugriffsklassifikation.md`, DERIVIERT aus den
|
||||
Bereichszeilen, nicht abgeschrieben): siehe dortige Summenzeile für die
|
||||
Endzahl.
|
||||
Datei-Modell-Paare. Nach Aufgabe 3 dieses Laufs — DERIVIERT aus der
|
||||
Summenzeile in `docs/mandantentrennung-zugriffsklassifikation.md`, nicht
|
||||
abgeschrieben: **68 ungebundene, 178 gebundene Rohtreffer** (Summe 246 —
|
||||
mehr als 227, weil Aufgabe 2 dieses Laufs mit dem Widget-Besitzriegel einen
|
||||
zusätzlichen gebundenen Rohtreffer einführt, der zur Planungszeit noch nicht
|
||||
feststand). Jeder der 68 verbleibenden ungebundenen Rohtreffer ist einer der
|
||||
in diesem Dokument (Abschnitte "## Bereich ...") oder im Klassifikationsdokument
|
||||
namentlich benannten, bewusst ungebundenen Fälle — siehe die Liste unten.
|
||||
|
||||
**Klassen-Verteilung.** Siehe `docs/mandantentrennung-zugriffsklassifikation.md`,
|
||||
Abschnitt "Klassen-Verteilung", Stand 260911-gwh (Aufgabe 3) für die
|
||||
Paarzahl und die Verteilung nach den vier Klassen.
|
||||
Abschnitt "Klassen-Verteilung", Stand 260911-gwh (Aufgabe 3): **65 Paare**
|
||||
(33 `muss-mandantengebunden`, 17 `keine-mandantengebundene-tabelle`, 13
|
||||
`beides`, 2 `bewusst-uebergreifend`) — ein neues Paar
|
||||
(`favorites.service.ts`/`widgetInstance`) gegenüber den 64 Paaren vor
|
||||
diesem Lauf, plus zwei Paare, die nur ihre `Stand`-Spalte ändern
|
||||
(`favorites.service.ts`/`favoriteLink`, `settings.service.ts`/`smtpConfig`).
|
||||
|
||||
**Bewusst ungebundene Reste je Bereich, mit Grund:**
|
||||
|
||||
|
||||
@@ -143,11 +143,11 @@ autoritative Quelle.
|
||||
| auth | 3 | 10 | **war 8/5** — 260911-fh9 (Aufgabe 2) hat `getMe`, `changePassword`, `adminResetPassword` (fünf Rohtreffer auf `user`, drei Methoden) auf `forTenant()` umgestellt. Die 3 verbleibenden ungebundenen Rohtreffer sind die `$queryRaw`-Aufrufe der drei Anmeldefunktionen (`validateUser`, `requestPasswordReset`, `resetPassword`) — KEINE Modellzugriffe (`$` liegt nicht in `[a-zA-Z]`, die Bestandsaufnahme führt sie deshalb nicht als (Datei, Modell)-Paar), bewusst und dauerhaft ungebunden, siehe `20260909160000_auth_lookup_functions` und `docs/mandantentrennung-etappe2-fehlerrichtung.md`, Abschnitt "## Bereich auth", (h1) |
|
||||
| calendar | 0 | 12 | **war 12/0** — Aufgabe 2 (260911-cwh) hat `calendar.service.ts` vollständig auf `forTenant()` umgestellt: `getSources`, `addSource`, beide Abfragen von `updateSource`/`deleteSource`, alle drei Abfragen von `testConnection`, Laden plus beide Synchronstatus-Rückschreibungen von `fetchAndCacheEvents` — je Methode ein Klient. Anders als bei den sieben Bereichen davor bleibt KEIN ungebundener Rest übrig: `CalendarSource` trägt eine Pflicht-Mandantenkennung, und kein Pfad dieses Bereichs liest über Mandanten hinweg |
|
||||
| tenant | 8 | 3 | **war 8/0** — 260911-e2s (Aufgabe 3) hat drei gebundene Benutzerzähler in `tenant.controller.ts` eingeführt (Fan-out je Mandant nach dem Muster von `UserService.findAllForPlatformAdmin`, ersetzt die drei vorherigen Relationszähler); die acht `tenant`-Zugriffe selbst BLEIBEN ungebunden — `Tenant` trägt keine Regel in irgendeiner ausgelieferten Migration (260911-e2s Aufgabe 1, Prüfung 1/2), hier ist Ungebundenheit richtig, nicht geduldet |
|
||||
| favorites | 7 | 0 | unverändert |
|
||||
| settings | 4 | 0 | unverändert |
|
||||
| **Summe** | **78** | **167** | Ungebunden: war 118 nach 260910-das, dann 108 nach 260910-exd (module-registry 17→7), dann 107 nach 260910-jab (`tenders` 36→35, `listForUser` gebunden), dann 95 nach 260910-krx (`dashboard` 13→1), dann 83 nach 260911-cwh (`calendar` 12→0), unverändert nach 260911-e2s (`tenant` bleibt bei 8 ungebundenen Rohtreffern), jetzt 78 nach 260911-fh9 (`auth` 8→3). Gebunden: war 124, dann 134 nach 260910-exd (zusätzlich 10 in `module-registry`), dann 135 nach 260910-jab (zusätzlich 1 in `tenders`), dann 147 nach 260910-krx (zusätzlich 12 in `dashboard`), dann 159 nach 260911-cwh (zusätzlich 12 in `calendar`), dann 162 nach 260911-e2s (zusätzlich 3 in `tenant`), jetzt 167 nach 260911-fh9 (zusätzlich 5 in `auth`). Diese Übersicht ist eine Buchführungshilfe; **autoritativ ist die Fundstellentabelle unten**, die `rls-access-inventory.spec.ts` bei jedem Lauf gegen den Quelltext prüft |
|
||||
| favorites | 0 | 8 | **war 7/0** — 260911-gwh (Aufgabe 2) hat `favorites.service.ts` vollständig auf `forTenant()` umgestellt: `list`, `create`, `update`, `remove`, `getIconBytes` laufen je über EINEN Klienten `tenantPrisma` (7 gebundene `favoriteLink`-Rohtreffer); `create` prüft zusätzlich über einen gebundenen `widgetInstance.findUnique`, dass das Ziel-Widget dem Aufrufer gehört (T-GWH-05, Befund F aus Aufgabe 1: der Fremdschlüssel prüft am Zeilenschutz vorbei) — der achte gebundene Rohtreffer dieser Zeile |
|
||||
| settings | 1 | 3 | **war 4/0** — 260911-gwh (Aufgabe 2) hat `getSmtpConfig`, `saveSmtpConfig`, `getDecryptedSmtpConfig` auf `forTenant()` umgestellt (3 gebundene `smtpConfig`-Rohtreffer). Der eine verbleibende ungebundene Rohtreffer ist der umbenannte Planer-Startpfad `loadAnySmtpConfigForStartupTransport()` (Befund D, WINDOWS #30) — bewusst, mit dreifacher Markierung; Befund K (`tenders`/`dkv` hängen an `getDecryptedSmtpConfig`) ist damit erfüllt |
|
||||
| **Summe** | **68** | **178** | Ungebunden: war 118 nach 260910-das, dann 108 nach 260910-exd (module-registry 17→7), dann 107 nach 260910-jab (`tenders` 36→35, `listForUser` gebunden), dann 95 nach 260910-krx (`dashboard` 13→1), dann 83 nach 260911-cwh (`calendar` 12→0), unverändert nach 260911-e2s (`tenant` bleibt bei 8 ungebundenen Rohtreffern), dann 78 nach 260911-fh9 (`auth` 8→3), jetzt 68 nach 260911-gwh (`favorites` 7→0, `settings` 4→1). Gebunden: war 124, dann 134 nach 260910-exd (zusätzlich 10 in `module-registry`), dann 135 nach 260910-jab (zusätzlich 1 in `tenders`), dann 147 nach 260910-krx (zusätzlich 12 in `dashboard`), dann 159 nach 260911-cwh (zusätzlich 12 in `calendar`), dann 162 nach 260911-e2s (zusätzlich 3 in `tenant`), dann 167 nach 260911-fh9 (zusätzlich 5 in `auth`), jetzt 178 nach 260911-gwh (zusätzlich 8 in `favorites`, 3 in `settings`). Dies ist der ENDSTAND der Etappe 2: jeder verbleibende ungebundene Rohtreffer ist einer der in diesem Dokument benannten, bewusst ungebundenen Fälle. Diese Übersicht ist eine Buchführungshilfe; **autoritativ ist die Fundstellentabelle unten**, die `rls-access-inventory.spec.ts` bei jedem Lauf gegen den Quelltext prüft |
|
||||
|
||||
## Klassen-Verteilung (nach (Datei, Modell)-Fundstellen, 64 Paare)
|
||||
## Klassen-Verteilung (nach (Datei, Modell)-Fundstellen, 65 Paare)
|
||||
|
||||
Stand 260909-jts (Aufgabe 3): 61 Paare aus dem vorherigen Durchlauf
|
||||
(260909-ipc) plus ein bisher vollstaendig unsichtbares Paar
|
||||
@@ -226,22 +226,35 @@ einer vergessenen Nachziehung nicht zu unterscheiden — deshalb steht die
|
||||
Abwesenheit einer Aenderung hier ausdruecklich, statt stillschweigend
|
||||
uebersprungen zu werden.
|
||||
|
||||
**Stand 260911-gwh (Aufgabe 3):** 65 Paare — 64 aus dem vorherigen Durchlauf
|
||||
plus EIN neues Paar (`favorites.service.ts`/`widgetInstance`, Klasse
|
||||
`muss-mandantengebunden`, Stand `gebunden`): der Besitzriegel in `create()`
|
||||
(T-GWH-05, Aufgabe 1 Pruefung 7 hat den Fremdschluessel-Durchgriff
|
||||
bestaetigt). Zwei Paare aendern nur ihre `Stand`-Spalte, keine ihrer Klasse:
|
||||
`favorites.service.ts`/`favoriteLink` (`ungebunden` auf `gebunden`) und
|
||||
`settings.service.ts`/`smtpConfig` (`ungebunden` auf `gemischt`). Das ist
|
||||
der ENDSTAND der Etappe 2 — die Zahl ist der Ausgabe von
|
||||
`rls-access-inventory.spec.ts` entnommen, nicht geschaetzt.
|
||||
|
||||
| Klasse | Anzahl Paare |
|
||||
|---|---|
|
||||
| muss-mandantengebunden | 32 |
|
||||
| muss-mandantengebunden | 33 |
|
||||
| keine-mandantengebundene-tabelle | 17 |
|
||||
| beides | 13 |
|
||||
| bewusst-uebergreifend | 2 |
|
||||
| **Summe** | **64** |
|
||||
| **Summe** | **65** |
|
||||
|
||||
## Der Hintergrunddienst als Falle — fünf Fälle
|
||||
## Der Hintergrunddienst als Falle — sechs Fälle
|
||||
|
||||
Ein Planer, der über alle Mandanten iteriert, liest zu Recht übergreifend —
|
||||
muss aber *innerhalb* der Schleife je Mandant binden. Vier Dateien sind in
|
||||
diesem Sinne `beides`-Fälle, davon einer (260910-das) der bislang EINZIGE,
|
||||
der auf BEIDEN Hälften bereits richtig ist; der fünfte, seit 260909-mir
|
||||
bekannte Fall ist von anderer Art und deshalb unten getrennt aufgeführt — er
|
||||
iteriert gar nicht, sondern greift sich eine beliebige Zeile heraus:
|
||||
iteriert gar nicht, sondern greift sich eine beliebige Zeile heraus. Der
|
||||
sechste Fall (seit 260911-gwh) ist von DERSELBEN Bauart wie der fünfte —
|
||||
kein Iterieren, eine beliebige-aber-vorhandene Zeile, kein Mandantenkontext
|
||||
beim Start — mit einer zusätzlichen Verdeckungsschicht, siehe unten:
|
||||
|
||||
- **`ldap.service.ts`** (AD-Abgleich) — **Stand 260909-ipc, Aufgaben 2/3:
|
||||
geschlossen.** Iteriert nicht selbst über alle Mandanten (der Sync läuft
|
||||
@@ -331,6 +344,61 @@ Verzweigung hinter einem optionalen Parameter, die jemand später
|
||||
`dkv-scheduler.service.ts`, Ledger-Eintrag WINDOWS #21. Das Signal für das
|
||||
Verstummen gehört in die Vorabprüfung von Etappe 4 (`rls-preflight.mjs`).
|
||||
|
||||
**Der sechste Fall, gleicher Bauart wie der fünfte — `mail.module.ts` /
|
||||
`SettingsService.loadAnySmtpConfigForStartupTransport()`** (260911-gwh,
|
||||
Befund D, WINDOWS #30): offen, als benannte Altlast weitergeführt. Dieselbe
|
||||
Form wie der fünfte Fall — `findFirst()` ohne jede Bedingung zieht bei
|
||||
mehreren Mandanten EINEN beliebigen und bedient die übrigen NIE; **heute
|
||||
bereits falsch**, weil der SMTP-Server und die Absenderadresse EINES
|
||||
beliebigen Mandanten die Kennwort-Zurücksetzungs- und Willkommensmails
|
||||
ALLER Mandanten tragen (T-GWH-03, Nutzung fremder Zugangsdaten). Binden ist
|
||||
auch hier keine Lösung: `useFactory` hat beim Start strukturell keinen
|
||||
Mandantenkontext. Der Umbau auf Transport je Versand aus
|
||||
`getDecryptedSmtpConfig(tenantId)` — die Form, die `DkvMailService`/
|
||||
`TenderMailService` bereits haben — ist eine Funktionsänderung
|
||||
(Umbau des Mailmoduls), kein Bindungsumbau, deshalb NICHT in Etappe 2
|
||||
vorgenommen.
|
||||
|
||||
Die UNSYMMETRIE zu BEIDEN Präzedenzfällen: `ldap.service.ts`/
|
||||
`getAllActiveConfigs` ist heute korrekt und verstummt erst später; der
|
||||
DKV-Planer (fünfter Fall) ist heute bereits falsch und verstummt zusätzlich
|
||||
später, ABER MIT einer Protokollzeile ("no active config found"). Der
|
||||
Mail-Startpfad ist heute bereits falsch UND verstummt später OHNE
|
||||
Protokollzeile, weil `mail.module.ts`s Rückfallkette (Priorität 2 `MAIL_*`,
|
||||
3 `TESSERA_SMTP_*`, 4 `localhost:1025`) einen FALSCHEN, aber vorhandenen
|
||||
Transport an die Stelle der Leere setzt — `MailService` fängt den
|
||||
Transportfehler (T-02-12), der Controller antwortet `200`. Das Verstummen
|
||||
ist damit DOPPELT verdeckt, eine dritte Ausprägung, die keiner der beiden
|
||||
Vorlagen (`ldap`, `dkv`) vollständig entspricht.
|
||||
|
||||
Dreifache Markierung: eigene benannte Methode mit Kopfkommentar (dkv-
|
||||
Präzedenzfall, ein Name, den niemand für einen Anfrageweg hält), Modul-
|
||||
kommentar in `mail.module.ts`, Ledger-Eintrag WINDOWS #30 (EIGENER Eintrag
|
||||
statt Anschluss an #21: andere Datei, andere Reparatur, andere
|
||||
Verdeckungsform). Das Signal für das Verstummen gehört ebenfalls in die
|
||||
Vorabprüfung von Etappe 4 (`rls-preflight.mjs`).
|
||||
|
||||
Befund K ist mit dieser Bindung ERFÜLLT: `getDecryptedSmtpConfig(tenantId)`
|
||||
— der einzige Versandpfad von `tender-mail.service.ts` und
|
||||
`dkv-mail.service.ts` — läuft seit 260911-gwh über `forTenant()`
|
||||
(siehe Bestandsaufnahme-Zeile `settings.service.ts`/`smtpConfig` oben und
|
||||
`docs/mandantentrennung-etappe2-fehlerrichtung.md`, Abschnitte "## Bereich
|
||||
tenders" (t4) und "## Bereich dkv" (d4), jeweils Nachtrag 260911-gwh); die
|
||||
Etappe-4-Vorabprüfung muss diese Reihenfolgebedingung ab jetzt NICHT mehr
|
||||
führen.
|
||||
|
||||
**Stand 260911-gwh — der Bereich `favorites` fügt diesem Abschnitt keinen
|
||||
weiteren Fall hinzu, gemessen statt angenommen (Befund K).** Anweisung:
|
||||
`grep -rn "@Cron\|onModuleInit\|onApplicationBootstrap\|setInterval\|setTimeout" apps/api/src/favorites apps/api/src/settings --include=*.ts | grep -v spec`
|
||||
liefert außerhalb von Testdateien genau EINEN Treffer,
|
||||
`icon-discovery.service.ts:255` — ein `setTimeout` für den Abbruch eines
|
||||
HTTP-Abrufs, kein Planer (dieselbe Form wie `ics.provider.ts:100` in
|
||||
260911-cwh). Die Bauform dieses Abschnitts (übergreifend LESEN über alle
|
||||
Mandanten, dann je Mandant BINDEN) kommt in `favorites` an keiner Stelle
|
||||
vor; der einzige Hintergrund-Zugriff des Bereichspaares ist der oben
|
||||
beschriebene sechste Fall, und der lebt nicht in `favorites`, sondern in
|
||||
`mail.module.ts`/`settings.service.ts`.
|
||||
|
||||
**Stand 260910-exd — kein sechster Fall, gemessen statt angenommen.** Der
|
||||
Bereich `module-registry` fügt diesem Abschnitt KEINEN sechsten Fall hinzu.
|
||||
`ModuleRegistryService.seedModule()` (die Katalogpflege beim Start,
|
||||
@@ -428,7 +496,8 @@ Aufgabe 3 behoben (Fan-out ersetzt den Relationszähler).
|
||||
| apps/api/src/dkv/dkv.service.ts | dkvInvoiceHistory | muss-mandantengebunden | gebunden | DKV-Rechnungshistorie je Mandant, `tenantId`-Spalte vorhanden. Seit 260909-mir (Aufgabe 3) laufen beide Historien-Schreibzugriffe der Verarbeitungsstrecke, beide parallelen Lesezugriffe von `getHistory` und der neue Riegel vor dem Ausfuhrdatei-Download vollstaendig ueber `forTenant()`. |
|
||||
| apps/api/src/dkv/dkv.service.ts | dkvModuleConfig | muss-mandantengebunden | gemischt | Postfach-/Zugangsdaten des DKV-Moduls je Mandant. Seit 260909-mir (Aufgabe 2) laufen `loadConfig`, `getConfigForApi`, `saveConfig`, `testConnection` und der Konfigurations-Lesezugriff der Verarbeitungsstrecke ueber `forTenant()`. Die Mischung stammt ausschliesslich vom einen benannten, bewusst ungebundenen Planer-Startpfad `loadAnyActiveConfigForScheduler()` (WINDOWS #21) — keine uebersehene Fundstelle. |
|
||||
| apps/api/src/dkv/dkv.service.ts | dkvVehicleMaster | muss-mandantengebunden | gebunden | Fahrzeugstammdaten des DKV-Moduls je Mandant. Seit 260909-mir (Aufgabe 3) laufen Fahrzeugliste, Anlegen, beide Paare aus Besitzpruefung und Schreibzugriff (Aendern/Loeschen), beide Zweige des CSV-Imports und der gebuendelte Lesezugriff beim Aufbau der Ausfuhrzeilen vollstaendig ueber `forTenant()`; die vorgeschalteten Besitzpruefungen bei Aendern/Loeschen bleiben zusaetzlich bestehen (Befund G — ein gebundenes UPDATE ueber die Kennung allein trifft eine fremde Zeile still, nicht laut). |
|
||||
| apps/api/src/favorites/favorites.service.ts | favoriteLink | muss-mandantengebunden | ungebunden | Favoriten-Links eines Nutzers, `tenantId`-Spalte vorhanden. |
|
||||
| apps/api/src/favorites/favorites.service.ts | favoriteLink | muss-mandantengebunden | gebunden | Favoriten-Links eines Nutzers, `tenantId`-Spalte vorhanden. Seit 260911-gwh (Aufgabe 2) laufen `list`, `create`, `update`, `remove`, `getIconBytes` vollstaendig ueber `forTenant()`, je Methode EIN Klient `tenantPrisma`; die Besitzpruefungen (`findUnique`, Vergleich `link.userId !== userId`, dann Schreibzugriff auf DEMSELBEN Klienten) bleiben zusaetzlich bestehen — die Regel auf `FavoriteLink` kennt keine Benutzerdimension (Aufgabe 1, Pruefung 4), die `userId`-Filter sind bis zur Etappe-3-Entscheidung (2) der einzige Schutz gegen Quer-Lesen zwischen Nutzern DESSELBEN Mandanten. Die Mandantenquelle ist dieselbe wie bei `dashboard` (`extractContext` im Controller), nicht das Claim wie bei `auth`. |
|
||||
| apps/api/src/favorites/favorites.service.ts | widgetInstance | muss-mandantengebunden | gebunden | NEUE Fundstelle (260911-gwh, Aufgabe 2): `create()` prueft ueber einen gebundenen `widgetInstance.findUnique` (`select: { userId: true }`), dass das Ziel-Widget (`dto.widgetId`) dem Aufrufer gehoert, BEVOR die Zeile angelegt wird — der Fremdschluessel `FavoriteLink.widgetId` prueft an der Zeilenschutz-Regel von `WidgetInstance` VORBEI (dokumentiertes PostgreSQL-Verhalten, Aufgabe 1 Pruefung 7 hat das GELINGEN eines gebundenen `create` mit einer fremdmandantigen `widgetId` bestaetigt); ohne den Riegel waere der Unterschied zwischen "Widget existiert nicht" (FK-Verletzung) und "gehoert einem fremden Mandanten" (gelingt) ein Existenzorakel ueber Mandantengrenzen (T-GWH-05). |
|
||||
| apps/api/src/groups/groups.service.ts | group | muss-mandantengebunden | gebunden | Gruppen sind je Mandant, `tenantId`-Spalte vorhanden. Alle 12 Methoden laufen seit 260909-jts (Aufgabe 2) ueber `forTenant()` bzw. `withTenantTransaction()`. |
|
||||
| apps/api/src/groups/groups.service.ts | groupMembership | muss-mandantengebunden | gebunden | Kein eigenes `tenantId`, RLS über Join auf `Group` (Migration 20260618112133-Nachfolger) — braucht trotzdem `forTenant()`, damit der Join-Kontext gesetzt ist. Seit 260909-jts gebunden, einschliesslich der drei Zugriffe innerhalb des Standardgruppen-Aufbaus (`ensureDefaultGroup`), die zuvor ueber den Transaktionsparameter liefen und fuer keine Pruefung dieses Projekts sichtbar waren (Befund B). |
|
||||
| apps/api/src/groups/groups.service.ts | moduleGrant | muss-mandantengebunden | gebunden | Modulfreigaben je Mandant, `tenantId`-Spalte vorhanden. Seit 260909-jts gebunden, einschliesslich des Zugriffs innerhalb des Standardgruppen-Aufbaus (Befund B). |
|
||||
@@ -450,7 +519,7 @@ Aufgabe 3 behoben (Fan-out ersetzt den Relationszähler).
|
||||
| apps/api/src/module-registry/module-access.service.ts | tenantModuleActivation | muss-mandantengebunden | gebunden | Aktivierung je Mandant, `tenantId`-Spalte vorhanden. Seit 260910-exd (Aufgabe 2) laufen Kurzschlusszweig, Schnittmengenabfrage und der eigene Lesezugriff von `getCatalogFlags` ueber `forTenant()`. |
|
||||
| apps/api/src/module-registry/module-registry.service.ts | module | keine-mandantengebundene-tabelle | ungebunden | Modulkatalog ist plattformweit. Bleibt bewusst ungebunden (260910-exd, Aufgabe 1, Befund E): keine Regel auf der Tabelle, eine Bindung waere heute wirkungslos, nicht katastrophal — katastrophal erst, WENN Etappe 3 dieser Tabelle eine Regel gibt. `findBySlug` ist zusaetzlich die Stelle, die `ModuleGuard` bei JEDER Modulanfrage aufruft. |
|
||||
| apps/api/src/module-registry/module-registry.service.ts | tenantModuleActivation | muss-mandantengebunden | gebunden | Aktivierung je Mandant. Seit 260910-exd (Aufgabe 3) laufen `findActiveForTenant`, `activateForTenant`, beide Zugriffe von `deactivateForTenant` (ueber EINEN Klienten) und `isModuleActive` ueber `forTenant()`, je Methode EIN Klient. |
|
||||
| apps/api/src/settings/settings.service.ts | smtpConfig | muss-mandantengebunden | ungebunden | SMTP-Zugangsdaten je Mandant, `tenantId`-Spalte vorhanden. |
|
||||
| apps/api/src/settings/settings.service.ts | smtpConfig | muss-mandantengebunden | gemischt | SMTP-Zugangsdaten je Mandant, `tenantId`-Spalte vorhanden. Seit 260911-gwh (Aufgabe 2) laufen `getSmtpConfig`, `saveSmtpConfig`, `getDecryptedSmtpConfig` ueber `forTenant()`. Die Mischung stammt ausschliesslich vom einen benannten, bewusst ungebundenen Planer-Startpfad `loadAnySmtpConfigForStartupTransport()` (WINDOWS #30, sechster Fall der Hintergrunddienst-Falle) — keine uebersehene Fundstelle, dieselbe Form wie `dkv.service.ts`/`dkvModuleConfig`. Befund K (`tender-mail.service.ts`/`dkv-mail.service.ts` haengen an `getDecryptedSmtpConfig`) ist damit erfuellt. |
|
||||
| apps/api/src/tenant/tenant.controller.ts | tenant | keine-mandantengebundene-tabelle | ungebunden | `Tenant` ist die Mandantentabelle selbst — hat keine eigene `tenantId`-Spalte, kann sie per Definition nicht haben (Migration 20260909140000, Gruppe b). Gemessen 260911-e2s (Aufgabe 1, Prüfung 1/2): keine Regel auf `Tenant` in irgendeiner der 34 ausgelieferten Migrationen einschließlich `20260910120000_rls_widen_membership_grant_and_platform_read`; gebunden und ungebunden liefern über Roh-SQL UND generierten Client dieselben Zeilen. |
|
||||
| apps/api/src/tenant/tenant.controller.ts | user | muss-mandantengebunden | gebunden | Seit 260911-e2s (Aufgabe 3): `findAll`/`findOne`/`remove` zählen Benutzer je Mandant über drei gebundene Aufrufstellen (`tenantPrisma.user.count`, Fan-out-Muster aus `UserService.findAllForPlatformAdmin`) statt über den früheren Relationszähler (`include: { _count: { select: { users } } }`), der nach dem Scharfschalten unter der Regel von `User` unbemerkt null geliefert hätte (260911-e2s Aufgabe 1, Prüfungen 5-7). `where: { tenantId }` bleibt heute (Rolle mit BYPASSRLS, WINDOWS #18) der einzige wirksame Filter. |
|
||||
| apps/api/src/tenant/tenant.service.ts | tenant | keine-mandantengebundene-tabelle | ungebunden | Dieselbe Begründung. Gemessen 260911-e2s (Aufgabe 1, Prüfung 1/2): keine Regel auf `Tenant` in irgendeiner der 34 ausgelieferten Migrationen einschließlich `20260910120000_rls_widen_membership_grant_and_platform_read`. |
|
||||
@@ -533,3 +602,13 @@ Aufgabe 3 behoben (Fan-out ersetzt den Relationszähler).
|
||||
an `username`/`email`. Siehe
|
||||
`docs/mandantentrennung-etappe2-fehlerrichtung.md`, "## Bereich auth",
|
||||
(h4)(a).
|
||||
- **Wie das Mailmodul künftig je Mandant versendet (260911-gwh).** Der
|
||||
Startpfad `loadAnySmtpConfigForStartupTransport()` bleibt bewusst
|
||||
ungebunden (sechster Fall der Hintergrunddienst-Falle, WINDOWS #30, siehe
|
||||
oben) — ein Umbau auf Transport je Versand aus
|
||||
`getDecryptedSmtpConfig(tenantId)`, die Form, die `DkvMailService`/
|
||||
`TenderMailService` bereits haben, ist eine Funktionsänderung
|
||||
(Umbau des Mailmoduls), kein Bindungsumbau, und deshalb NICHT Gegenstand
|
||||
dieser Etappe. Siehe
|
||||
`docs/mandantentrennung-etappe2-fehlerrichtung.md`, "## Bereich settings",
|
||||
(s4)(a).
|
||||
|
||||
Reference in New Issue
Block a user