docs(quick-260910-jab): Aktenstand kohaerent machen — Ledger, Klassifikation, Kritikschrift, Betriebsanleitung
- WINDOWS.md: #19 auf fixed gesetzt (Tabelle + JSON-Block), mit Beleg (Migrationsname + benannte Pruefungen) und ausdruecklicher Feststellung, dass die SearchProvider-Haelfte als widerlegte Praemisse schliesst, nicht als geloestes Problem. #18/#20/#21/#22/#23 bleiben unveraendert offen. Ein neuer Eintrag #24 haelt den fehlenden Verwaltungsweg fuer plattformweite Zeilen unter der Anwendungsrolle offen (verschwindet nicht mit #19). Die vier Kopfzahlen sind aus dem JSON-Block abgeleitet (6 offen, 17 behoben, 1 zurueckgestellt, 24 gesamt). - Klassifikation: #19-Block von offener Frage zu beantwortet, die Uebersichtszeile tenders (35/27) und Summenzeile (107/135) aus dem Quelltext neu abgeleitet, vier Bestandsaufnahme-Zeilen nachgezogen (searchProvider, groups.service.ts/user, module-grants.service.ts/ moduleGrant, tender-rss-feed.service.ts/tenderRssFeedSource), der Punkt in "Was diese Etappe NICHT entscheidet" aufgeloest. - Kritikschrift: neuer Abschnitt "Regelschluss T-JTS-02, T-JTS-03 und WINDOWS #19" mit tatsaechlich beobachteter Ausgabe, Regelliste der lebenden Datenbank, Signaltabelle mit beiden Fehlerrichtungen je Regel, der neuen Stelle aus Befund F, der selbst ausgefuehrten Messung zur fehlenden Benutzerdimension (keine zweite Sitzungsvariable gefunden) und dem, was dieser Durchlauf nicht loest/nicht anfasst. Fuenf ueberholte Bestandsstellen mit Nachtraegen versehen (g4, t4, die Signaltabellenzeile zu den RSS-Pfaden, drei aufgezeichnete Werkzeugausgaben, m4), die alten Messprotokolle bleiben woertlich stehen. - Betriebsanleitung: die eine Stelle, die #19 als offen fuehrte, nennt jetzt den Aufloesungsstand und WINDOWS #24. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
@@ -87,8 +87,13 @@ Arbeitsvorrat fuer die Umstellung. **16** Paare betreffen keine
|
||||
mandantengebundene Tabelle (plattformweite Daten wie der Ausschreibungs- und
|
||||
Modulkatalog, D-03) und **3** sind bewusst uebergreifend mit ausgeschriebenem
|
||||
Grund. Zusaetzlich zu beachten: das entdeckte, aber in dieser Etappe nicht
|
||||
behobene `forTenant()`-Verbindungsproblem (WINDOWS #20, siehe unten) und
|
||||
WINDOWS #19 (nullbares `tenantId` bei `SearchProvider`/`TenderRssFeedSource`).
|
||||
behobene `forTenant()`-Verbindungsproblem (WINDOWS #20, siehe unten). WINDOWS
|
||||
#19 (nullbares `tenantId` bei `SearchProvider`/`TenderRssFeedSource`) ist
|
||||
GESCHLOSSEN (260910-jab, Migration
|
||||
`20260910120000_rls_widen_membership_grant_and_platform_read`) — siehe
|
||||
`docs/mandantentrennung-zugriffsklassifikation.md`, Abschnitt "Zwei belegte
|
||||
Befunde", und `.planning/WINDOWS.md`. Offen geblieben ist der Verwaltungsweg
|
||||
fuer plattformweite Zeilen unter der Anwendungsrolle (WINDOWS #24).
|
||||
|
||||
Darunter war bis Aufgabe 2 dieser Etappe auch der Anmeldeweg selbst, der
|
||||
strukturell nicht anders funktionieren konnte: `apps/api/src/auth/auth.service.ts`
|
||||
|
||||
@@ -198,6 +198,18 @@ modulegrant-fremde-gruppe-trotz-eigener-mandantenkennung-erlaubt: bestanden —
|
||||
tenantmoduleactivation-gebunden-nur-eigene-zeile: bestanden — forTenant(TENANT-A) liefert 1 Zeile(n): ["TENANT-A"]
|
||||
```
|
||||
|
||||
**NACHTRAG (260910-jab):** die beiden fett hervorgehobenen Zeilen oben
|
||||
(`groupmembership-schreiben-fremder-benutzer-nicht-verhindert` und
|
||||
`modulegrant-fremde-gruppe-trotz-eigener-mandantenkennung-erlaubt`) sind ein
|
||||
Messprotokoll vom 2026-09-09 und bleiben UNVERÄNDERT stehen — sie belegen,
|
||||
dass die beiden Löcher existierten. Seit Migration
|
||||
`20260910120000_rls_widen_membership_grant_and_platform_read` sind beide
|
||||
Aussagen ÜBERHOLT: die Prüfungen sind umgekehrt (`groupmembership-schreiben-
|
||||
fremder-benutzer-abgelehnt`, `modulegrant-fremde-gruppe-abgelehnt`), das
|
||||
INSERT wird jetzt jeweils ABGEWIESEN statt zu gelingen. Siehe den neuen
|
||||
Abschnitt "Regelschluss T-JTS-02, T-JTS-03 und WINDOWS #19" unten für die
|
||||
aktuell beobachtete Ausgabe.
|
||||
|
||||
Die Belegzeile, die diesen Abschnitt trägt, ist `group-ungebunden-null-zeilen`:
|
||||
der IDENTISCHE `SELECT "tenantId" FROM "Group"` ohne vorheriges `set_config`
|
||||
liefert **0 Zeilen**, nicht etwa die 2 tatsächlich vorhandenen — an der
|
||||
@@ -366,6 +378,14 @@ zum `deleteMany`-mit-`notIn` des ldap-Bereichs.
|
||||
`addUserToDefaultGroup`, `assertTargetBelongsToTenant`) bleiben deshalb
|
||||
der primäre Schutz gegen diese beiden Formen der Rechteausweitung und
|
||||
werden durch diesen Durchlauf NICHT durch die Datenbank ersetzt.
|
||||
**NACHTRAG (260910-jab):** ÜBERHOLT — seit Migration
|
||||
`20260910120000_rls_widen_membership_grant_and_platform_read` prüft
|
||||
`GroupMembership` BEIDE Seiten (T-JTS-02 geschlossen) und `ModuleGrant`
|
||||
zusätzlich beide möglichen Ziele (T-JTS-03 geschlossen, beide Zweige des
|
||||
Entweder-oder D-04). Die Anwendungsprüfungen bleiben trotzdem bestehen —
|
||||
sie sind bis zum Scharfschalten (#18) der einzige tatsächlich wirksame
|
||||
Schutz, siehe den neuen Abschnitt "Regelschluss T-JTS-02, T-JTS-03 und
|
||||
WINDOWS #19" unten.
|
||||
|
||||
### (g5) Fortschreibung des ldap-Abschnitts
|
||||
|
||||
@@ -411,6 +431,17 @@ tendertriage-einfuegen-auf-unsichtbare-zeile-verletzt-eindeutigkeit: bestanden
|
||||
Alle 32 Pruefungen bestanden.
|
||||
```
|
||||
|
||||
**NACHTRAG (260910-jab):** die fett hervorgehobene Zeile oben
|
||||
(`tenderrssfeed-plattformzeile-unter-jedem-mandanten-unsichtbar`) ist ein
|
||||
Messprotokoll vom 2026-09-09 und bleibt UNVERÄNDERT stehen — sie belegt,
|
||||
dass WINDOWS #19 existierte. Seit Migration
|
||||
`20260910120000_rls_widen_membership_grant_and_platform_read` ist diese
|
||||
Aussage ÜBERHOLT: die Prüfung ist umgekehrt
|
||||
(`tenderrssfeed-plattformzeile-gebunden-sichtbar`), die plattformweite Zeile
|
||||
ist jetzt unter BEIDEN Mandantenkontexten SICHTBAR. Siehe den neuen
|
||||
Abschnitt "Regelschluss T-JTS-02, T-JTS-03 und WINDOWS #19" unten für die
|
||||
aktuell beobachtete Ausgabe.
|
||||
|
||||
Die Belegzeile, die diesen Abschnitt trägt, ist
|
||||
`tendersavedsearch-ungebunden-null-zeilen`: der IDENTISCHE
|
||||
`SELECT "tenantId" FROM "TenderSavedSearch"` ohne vorheriges `set_config`
|
||||
@@ -441,6 +472,12 @@ Bereich brauchte:
|
||||
Kehrseite: ein gebundenes `INSERT` mit `tenantId = NULL` wird von der
|
||||
ausgelieferten Policy abgewiesen — `createPlatform` würde in genau diese
|
||||
Abweisung laufen, würde man es binden.
|
||||
**NACHTRAG (260910-jab):** ÜBERHOLT für `listForUser` — seit Migration
|
||||
`20260910120000_rls_widen_membership_grant_and_platform_read` bindet
|
||||
dieser Pfad (die neue Leseregel schließt Zeilen ohne Mandant ausdrücklich
|
||||
ein). `createPlatform`/`remove` bleiben unverändert ungebunden, aus dem
|
||||
jetzt ausdrücklichen Grund einer eigenen Schreibregel je Befehl (WINDOWS
|
||||
#24).
|
||||
|
||||
Eine dritte Messung trägt die Fehlerbehandlung von Aufgabe 2:
|
||||
`tendertriage-einfuegen-auf-unsichtbare-zeile-verletzt-eindeutigkeit` zeigt,
|
||||
@@ -474,7 +511,7 @@ nicht eingeführt.
|
||||
| `TenderNotificationPrefService.getForUser` | **Sonderfall**: kein Treffer bedeutet hier nicht "leer", sondern der Vorgabewert `daily` (D-01) | Ein Nutzer, der `off` gewählt hat, sieht in der Oberfläche wieder `daily` — ein zu kleines Leseergebnis setzt die Einstellung stillschweigend auf täglich zurück, statt sie leer zu lassen |
|
||||
| `TenderEmailConfigService.getConfigForApi` | Der gebundene `findUnique` liefert 0 Zeilen statt der eigenen Konfiguration | `GET /email-config` liefert `null`; die Oberfläche zeigt "kein Postfach verbunden" für einen Nutzer, der tatsächlich eines hat |
|
||||
| `TenderEmailConfigService.testConnection` | Der gebundene Rückgriff auf gespeicherte Zugangsdaten liefert 0 Zeilen statt der eigenen | Ein Verbindungstest mit leer gelassenem Formular (Rückgriff auf gespeicherte Zugangsdaten) schlägt fehl, obwohl gespeicherte Zugangsdaten existieren |
|
||||
| `TenderRssFeedSourceService.listForUser`/`createPlatform`/`remove` (bewusst UNGEBUNDEN, WINDOWS #19) | Betrifft nicht diese drei Pfade selbst — sie binden nicht und liefern deshalb weiterhin die plattformweite Zeile korrekt. Das Risiko liegt in einer KÜNFTIGEN Bindung (Etappe 3), nicht in dieser Umstellung | Würde man sie binden: leere Feed-Liste bzw. 404 beim Entfernen einer plattformweiten Quelle — hier ausdrücklich als Grenze festgehalten, nicht als heute beobachtbares Verhalten |
|
||||
| `TenderRssFeedSourceService.listForUser`/`createPlatform`/`remove` (bewusst UNGEBUNDEN, WINDOWS #19) | Betrifft nicht diese drei Pfade selbst — sie binden nicht und liefern deshalb weiterhin die plattformweite Zeile korrekt. Das Risiko liegt in einer KÜNFTIGEN Bindung (Etappe 3), nicht in dieser Umstellung | Würde man sie binden: leere Feed-Liste bzw. 404 beim Entfernen einer plattformweiten Quelle — hier ausdrücklich als Grenze festgehalten, nicht als heute beobachtbares Verhalten. **NACHTRAG (260910-jab):** ÜBERHOLT für `listForUser` — dieser Pfad ist seit Migration `20260910120000_rls_widen_membership_grant_and_platform_read` GEBUNDEN (WINDOWS #19 geschlossen, Befund F: ungebunden hätte die Reparatur ihn sonst still auf nur die plattformweiten Zeilen reduziert). `createPlatform`/`remove` bleiben unverändert ungebunden (WINDOWS #24). |
|
||||
| `TenderRssFeedSourceService.createForUser` (gebunden) | Der Zähler (`count`, gebunden) liefert 0 statt der tatsächlichen Anzahl eigener Feeds | Ein Nutzer, der bereits am Limit von 20 eigenen Feeds ist, könnte scheinbar unbegrenzt neue anlegen (harmlose Richtung: die Kappung aus T-17-10 wirkt nicht mehr, kein Datenverlust) |
|
||||
|
||||
### (t3) Welcher Code Leere als Abwesenheit deutet
|
||||
@@ -541,6 +578,18 @@ Warnung wäre Dauerlärm und verlöre ihr Signal.
|
||||
Policy-Semantik selbst (eine `tenantId IS NULL OR tenantId =
|
||||
current_tenant_id()`-Lesevariante) gehört zu Etappe 3 und wird hier nicht
|
||||
angefasst.
|
||||
**NACHTRAG (260910-jab): ÜBERHOLT — WINDOWS #19 ist geschlossen.** Migration
|
||||
`20260910120000_rls_widen_membership_grant_and_platform_read` ersetzt die
|
||||
einfache Regel durch vier nach Befehl getrennte Regeln
|
||||
(`tenant_platform_read_policy` schließt Zeilen ohne Mandant beim Lesen
|
||||
ausdrücklich ein, die drei Schreibregeln verlangen weiterhin einen
|
||||
Mandanten). `listForUser` ist seither GEBUNDEN (Befund F: ungebunden hätte
|
||||
die Reparatur ihn sonst still auf nur die plattformweiten Zeilen
|
||||
reduziert); `createPlatform`/`remove` bleiben bewusst ungebunden — beide
|
||||
Pfade lassen sich unter der Anwendungsrolle grundsätzlich nicht
|
||||
anlegen/entfernen (WINDOWS #24, eigener offener Punkt, verschwindet nicht
|
||||
mit #19). Siehe den neuen Abschnitt "Regelschluss T-JTS-02, T-JTS-03 und
|
||||
WINDOWS #19" unten.
|
||||
- **Die übergreifenden Hälften der beiden Hintergrunddienste.** Aufgabe 3
|
||||
bindet nur die Je-Treffer-Hälften von `tender-digest.scheduler.ts` und
|
||||
`tender-matching.service.ts`; die Kandidatenabfrage
|
||||
@@ -1293,6 +1342,18 @@ freigabe-eindeutigkeitsindizes-fuehren-mit-der-mandantenkennung: bestanden — a
|
||||
Alle 66 Pruefungen bestanden.
|
||||
```
|
||||
|
||||
**NACHTRAG (260910-jab):** die fett hervorgehobene Zeile oben
|
||||
(`gruppenpfad-gebunden-schliesst-die-fremde-gruppe-aus`) ist ein
|
||||
Messprotokoll vom 2026-09-10 und bleibt UNVERÄNDERT stehen. Ihre Behauptung
|
||||
"die Regel auf ModuleGrant lässt diese Zeile durch (T-JTS-03)" ist seit
|
||||
Migration `20260910120000_rls_widen_membership_grant_and_platform_read`
|
||||
ÜBERHOLT: die Regel weist ein gebundenes Einfügen von `grant-foreign-group`
|
||||
jetzt nachweislich ab (`modulegrant-fremde-gruppe-abgelehnt`); die Zeile
|
||||
existiert in aktuellen Läufen nur noch, weil
|
||||
`modulegrant-fremde-gruppe-ueber-die-wartungsrolle-weiterhin-moeglich` sie
|
||||
über die Wartungsrolle (BYPASSRLS) anlegt. Der Meldetext dieser Prüfung ist
|
||||
im Quelltext des Werkzeugs entsprechend richtiggestellt.
|
||||
|
||||
Die Belegzeile, die diesen Abschnitt trägt, ist
|
||||
`modulegrant-ungebunden-null-zeilen`: der IDENTISCHE `SELECT "tenantId" FROM
|
||||
"ModuleGrant"` ohne vorheriges `set_config` liefert **0 Zeilen**, nicht etwa
|
||||
@@ -1426,7 +1487,7 @@ und verlöre ihr Signal.
|
||||
|
||||
### (m4) Was dieser Durchlauf bewusst nicht löst
|
||||
|
||||
- **T-JTS-02/T-JTS-03 bleiben aufgezeichnet und ungefixt.** Gemessen in
|
||||
- ~~**T-JTS-02/T-JTS-03 bleiben aufgezeichnet und ungefixt.** Gemessen in
|
||||
Aufgabe 1 (Prüfungen 6/7): die Regel auf `ModuleGrant` prüft nur die
|
||||
Mandantenkennung der Zeile selbst, nicht die referenzierte Gruppe; die
|
||||
Regel auf `GroupMembership` prüft nur die Gruppenseite. Die Bindung fügt
|
||||
@@ -1434,7 +1495,17 @@ und verlöre ihr Signal.
|
||||
schließt die fremde Gruppe gebunden aus), aber erst nach Etappe 4 — die
|
||||
Schreibseiten-Gegenprüfung in `module-grants.service.ts`
|
||||
(`assertTargetBelongsToTenant`) bleibt deshalb der heutige Schutz und wird
|
||||
durch diesen Durchlauf NICHT ersetzt.
|
||||
durch diesen Durchlauf NICHT ersetzt.~~
|
||||
**NACHTRAG (260910-jab): ÜBERHOLT — BEIDE Befunde sind geschlossen.**
|
||||
Migration `20260910120000_rls_widen_membership_grant_and_platform_read`
|
||||
lässt die Regel auf `GroupMembership` jetzt beide Seiten prüfen
|
||||
(T-JTS-02) und die Regel auf `ModuleGrant` zusätzlich beide möglichen
|
||||
Ziele (T-JTS-03, beide Zweige des Entweder-oder D-04). Die
|
||||
Schreibseiten-Gegenprüfung in `module-grants.service.ts`
|
||||
(`assertTargetBelongsToTenant`) bleibt TROTZDEM bestehen — sie ist bis
|
||||
zum Scharfschalten (#18, Schalter weiterhin aus) der einzige tatsächlich
|
||||
wirksame Schutz und wird NICHT ersetzt. Siehe den neuen Abschnitt
|
||||
"Regelschluss T-JTS-02, T-JTS-03 und WINDOWS #19" unten.
|
||||
- **Die Regel für den Modulkatalog gehört zu Etappe 3.** Gemessen in
|
||||
Aufgabe 1 (Prüfung 8/9, Befund E): `"Module"` trägt heute keinen
|
||||
Zeilenschutz, eine Bindung wäre heute wirkungslos, nicht katastrophal.
|
||||
@@ -1540,6 +1611,122 @@ nichts gefunden" unterscheidet, mit der Vorabprüfung für Etappe 4 und der
|
||||
begründeten Verwerfung einer Laufzeitwarnung — siehe (m3) oben und
|
||||
`.planning/WINDOWS.md`.
|
||||
|
||||
## Regelschluss T-JTS-02, T-JTS-03 und WINDOWS #19
|
||||
|
||||
Dieser Abschnitt weicht bewusst von der geplanten Reihenfolge ab (260910-jab,
|
||||
auf ausdrückliche Anweisung des Nutzers): die drei Regelfixe, ursprünglich
|
||||
nach Etappe 2 vorgesehen, wurden vorgezogen. Folge: die fünf noch offenen
|
||||
Bereiche der Etappe 2 messen ab jetzt gegen die NEUEN Regeln, mehrere
|
||||
bereits abgeschlossene Bereiche (`groups`, `tenders`, `module-registry` oben)
|
||||
haben gegen die ALTEN gemessen — diese Messungen stehen aufgezeichnet und
|
||||
sind mit Nachträgen an den betroffenen Stellen versehen, nicht umgeschrieben.
|
||||
|
||||
### (r1) Tatsächlich beobachtete Ausgabe und Regelliste der lebenden Datenbank
|
||||
|
||||
Lauf vom 2026-09-10 gegen `tessera-ctl-db-1`, Adresse `172.19.0.2` (nur die
|
||||
Zeilen der drei betroffenen Bereiche sowie der beiden aufsetzenden
|
||||
`module-registry`-Prüfungen; die vollständige Ausgabe umfasst 74
|
||||
Prüfungen):
|
||||
|
||||
```
|
||||
groupmembership-folgt-join-auf-group: bestanden — forTenant(TENANT-A) liefert 1 Mitgliedschaft(en): ["group-a"]
|
||||
groupmembership-schreiben-fremde-gruppe-abgelehnt: bestanden — INSERT mit fremder groupId abgewiesen: ERROR: new row violates row-level security policy for table "GroupMembership"
|
||||
groupmembership-schreiben-fremder-benutzer-abgelehnt: bestanden — INSERT mit A-eigener Gruppe, aber fremder Benutzerkennung (user-b, TENANT-B) abgewiesen: ERROR: new row violates row-level security policy for table "GroupMembership"
|
||||
groupmembership-fremder-benutzer-ueber-die-wartungsrolle-weiterhin-moeglich: bestanden — INSERT mit A-eigener Gruppe, aber fremder Benutzerkennung (user-b, TENANT-B) ist ueber die Wartungsrolle (BYPASSRLS) weiterhin GELUNGEN — der Ausschluss aus der vorigen Pruefung kommt damit nachweislich von der Regel, nicht vom Aufbau
|
||||
modulegrant-gebunden-nur-eigene-zeile: bestanden — forTenant(TENANT-A) liefert 1 Zeile(n): ["TENANT-A"]
|
||||
modulegrant-fremde-gruppe-abgelehnt: bestanden — INSERT mit korrekter eigener tenantId, aber fremder groupId (group-b, TENANT-B) abgewiesen: ERROR: new row violates row-level security policy for table "ModuleGrant"
|
||||
modulegrant-fremder-benutzer-abgelehnt: bestanden — INSERT mit korrekter eigener tenantId, aber fremder userId (user-b, TENANT-B) abgewiesen: ERROR: new row violates row-level security policy for table "ModuleGrant"
|
||||
modulegrant-fremde-gruppe-ueber-die-wartungsrolle-weiterhin-moeglich: bestanden — INSERT mit korrekter eigener tenantId, aber fremder groupId ist ueber die Wartungsrolle (BYPASSRLS) weiterhin GELUNGEN — legt zugleich die Zeile 'grant-foreign-group' an, auf der zwei Pruefungen des Bereichs module-registry aufsetzen (Befund C)
|
||||
tenderrssfeed-plattformzeile-gebunden-sichtbar: bestanden — forTenant(TENANT-A) sieht die Platform-Zeile: true, forTenant(TENANT-B) sieht sie: true
|
||||
tenderrssfeed-eigene-zeile-gebunden-weiterhin-sichtbar: bestanden — forTenant(TENANT-A) liefert ["rss-a","rss-platform"] — eigene Zeile (rss-a) sichtbar: true, fremde Zeile (rss-b, TENANT-B) sichtbar: false
|
||||
tenderrssfeed-ungebunden-nur-die-plattformzeile: bestanden — ungebundener SELECT auf "TenderRssFeedSource" liefert 1 Zeile(n): ["rss-platform"]
|
||||
tenderrssfeed-gebundenes-einfuegen-ohne-mandant-abgelehnt: bestanden — gebundenes INSERT mit tenantId=NULL abgewiesen (tenant_insert_policy)
|
||||
tenderrssfeed-gebundenes-aendern-der-plattformzeile-abgelehnt: bestanden — gebundenes UPDATE auf die plattformweite Zeile traf keine Zeile (tenant_update_policy filtert sie heraus) — url unveraendert
|
||||
tenderrssfeed-gebundenes-loeschen-der-plattformzeile-abgelehnt: bestanden — gebundenes DELETE auf die plattformweite Zeile traf 0 Zeilen (tenant_delete_policy filtert sie heraus)
|
||||
searchprovider-mandantenlose-zeile-bleibt-unter-jedem-kontext-unsichtbar: bestanden — forTenant(TENANT-A) sieht die mandantenlose Zeile: false, forTenant(TENANT-B) sieht sie: false — bewusst UNVERAENDERT (widerlegte Praemisse)
|
||||
gruppenpfad-gebunden-schliesst-die-fremde-gruppe-aus: bestanden — forTenant(TENANT-A) liefert 'grant-foreign-group' ueber denselben Drei-Tabellen-Weg NICHT — die Zeile wurde ueber die Wartungsrolle angelegt
|
||||
gruppenpfad-ueber-die-wartungsrolle-liefert-die-fremde-gruppe-mit: bestanden — dieselbe Abfrage ueber die Verwaltungsrolle (BYPASSRLS) liefert ["grant-a","grant-foreign-group"]
|
||||
Alle 74 Pruefungen bestanden.
|
||||
```
|
||||
|
||||
Regelliste aus `pg_policies` der lebenden Datenbank (Systemkatalog, nicht die
|
||||
Migrationsdatei):
|
||||
|
||||
```
|
||||
GroupMembership#tenant_isolation_policy#ALL#(("groupId" IN (SELECT "Group".id FROM "Group" WHERE ("Group"."tenantId" = current_tenant_id()))) AND ("userId" IN (SELECT "User".id FROM "User" WHERE ("User"."tenantId" = current_tenant_id()))))
|
||||
ModuleGrant#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND (("groupId" IS NULL) OR ("groupId" IN (SELECT "Group".id FROM "Group" WHERE ("Group"."tenantId" = current_tenant_id())))) AND (("userId" IS NULL) OR ("userId" IN (SELECT "User".id FROM "User" WHERE ("User"."tenantId" = current_tenant_id())))))
|
||||
SearchProvider#tenant_isolation_policy#ALL#("tenantId" = current_tenant_id())
|
||||
TenderRssFeedSource#tenant_delete_policy#DELETE#("tenantId" = current_tenant_id())
|
||||
TenderRssFeedSource#tenant_insert_policy#INSERT##WITH CHECK ("tenantId" = current_tenant_id())
|
||||
TenderRssFeedSource#tenant_platform_read_policy#SELECT#(("tenantId" = current_tenant_id()) OR ("tenantId" IS NULL))
|
||||
TenderRssFeedSource#tenant_update_policy#UPDATE#USING ("tenantId" = current_tenant_id())#WITH CHECK ("tenantId" = current_tenant_id())
|
||||
```
|
||||
|
||||
### (r2) Signaltabelle — beide Fehlerrichtungen je Regel
|
||||
|
||||
| Regel | Zu streng (Falsch-Negativ, DoS) | Zu locker (Falsch-Positiv, Rechteausweitung) |
|
||||
|---|---|---|
|
||||
| `GroupMembership` | Wäre die neue Regel zu streng, würde eine tatsächlich mandanteneigene Mitgliedschaft unsichtbar — Signal: `groupmembership-folgt-join-auf-group` würde fehlschlagen (liefert heute korrekt 1 Zeile) | `groupmembership-schreiben-fremder-benutzer-abgelehnt` — Signal wäre ein GELUNGENES Einfügen einer fremden Benutzerkennung; heute abgewiesen |
|
||||
| `ModuleGrant` | Eine eigene, korrekt referenzierende Freigabe würde unsichtbar — Signal: `modulegrant-gebunden-nur-eigene-zeile` würde fehlschlagen (heute 1 Zeile) | `modulegrant-fremde-gruppe-abgelehnt`/`modulegrant-fremder-benutzer-abgelehnt` — Signal wäre ein GELUNGENES Einfügen mit fremder Gruppen- bzw. Benutzerkennung; heute beide abgewiesen |
|
||||
| `TenderRssFeedSource` (Lese-/Schreibsplit) | Zu streng bedeutet: die plattformweite Zeile bleibt trotz Bindung unsichtbar — Signal: `tenderrssfeed-plattformzeile-gebunden-sichtbar` würde fehlschlagen (heute sichtbar unter beiden Mandanten) ODER die eigene Zeile verschwindet — Signal: `tenderrssfeed-eigene-zeile-gebunden-weiterhin-sichtbar` würde fehlschlagen | Zu locker bedeutet: ein Mandant kann die plattformweite Zeile ändern/entfernen — Signal: `tenderrssfeed-gebundenes-aendern-der-plattformzeile-abgelehnt`/`tenderrssfeed-gebundenes-loeschen-der-plattformzeile-abgelehnt` würden fehlschlagen (heute beide: 0 betroffene Zeilen) |
|
||||
|
||||
### (r3) Stellen, an denen Leere weiterhin als Abwesenheit gedeutet wird
|
||||
|
||||
Namentliche Liste, wie in den Abschnitten (d)/(g3)/(t3)/(u3)/(m3) oben je
|
||||
Bereich geführt — hier bereichsübergreifend für die drei betroffenen
|
||||
Tabellen, inklusive der NEUEN Stelle aus Befund F:
|
||||
|
||||
- `TenderRssFeedSourceService.listForUser` (**NEU seit 260910-jab, Aufgabe
|
||||
2**) — vor dieser Reparatur lieferte der ungebundene Pfad nach dem
|
||||
Scharfschalten NICHTS (eine schreiende Leere, die auffällt). Nach der
|
||||
Reparatur würde er UNGEBUNDEN nur die plattformweiten Zeilen liefern — eine
|
||||
kurze, glaubhafte Teilantwort, die NICHT auffällt (der Nutzer sieht
|
||||
plattformweite Feeds und hat keinen Anlass zu melden, dass seine eigenen
|
||||
fehlen). Deshalb gebunden — die einzige Stelle, die diese Aufgabe still
|
||||
falsch gemacht hätte, wenn sie nicht mitgebunden worden wäre.
|
||||
- Alle bereits in (t3) genannten fünf lautlosen Stellen in den beiden
|
||||
Tender-Hintergrunddiensten bleiben unverändert lautlos — von dieser
|
||||
Regeländerung nicht betroffen.
|
||||
- `GroupMembership`/`ModuleGrant` selbst tragen keine "Leere als Abwesenheit
|
||||
gedeutet"-Stelle im hier verstandenen Sinn (eine ABGEWIESENE Schreibung ist
|
||||
ein harter Fehler, keine stille Leere) — die relevante Fehlerrichtung ist
|
||||
hier die Rechteausweitung (r2), nicht die stille Leere.
|
||||
|
||||
### (r4) Was dieser Durchlauf bewusst nicht löst
|
||||
|
||||
- **Der Verwaltungsweg für plattformweite Zeilen fehlt.** Unter der
|
||||
Anwendungsrolle lässt sich eine plattformweite `TenderRssFeedSource`-Zeile
|
||||
weder anlegen noch entfernen — in der alten wie in der neuen Regel, weil
|
||||
jede Schreibregel einen Mandanten verlangt. `createPlatform`/`remove`
|
||||
bleiben deshalb bewusst ungebunden. Als eigener offener Ledger-Eintrag
|
||||
festgehalten (WINDOWS #24), damit dieser Rest nicht mit #19 verschwindet —
|
||||
ein Verwaltungsweg (z. B. eine eigene Systemrolle oder ein expliziter
|
||||
Admin-Bypass-Pfad) ist Gegenstand von Etappe 4.
|
||||
- **Die fehlende Benutzerdimension der Ausschreibungsregeln.** Selbst
|
||||
gemessen statt übernommen (`grep -rn "current_setting\|set_config"
|
||||
apps/api/src apps/api/prisma/migrations`, 260910-jab): es existiert
|
||||
GENAU EINE Sitzungsvariable, `app.current_tenant`
|
||||
(`prisma-tenant.extension.ts`, `20260618112133_rls_policies`). Es gibt
|
||||
KEINE zweite Sitzungsvariable für den Benutzer — Tabellen wie
|
||||
`TenderSavedSearch` (siehe `tendersavedsearch-fremder-nutzer-desselben-
|
||||
mandanten-sichtbar` oben) haben deshalb strukturell keine Möglichkeit,
|
||||
eine Benutzerdimension auf Datenbankebene durchzusetzen, ohne eine solche
|
||||
Variable erst einzuführen. Nicht gebaut in diesem Durchlauf — die
|
||||
anwendungsseitige `userId`-Filterung bleibt der einzige Schutz.
|
||||
- **Die plattformweite Eindeutigkeit von Anmeldename und Adresse (WINDOWS
|
||||
#22)** — unverändert, nicht Gegenstand dieses Plans.
|
||||
|
||||
### (r5) Was dieser Durchlauf bewusst nicht anfasst
|
||||
|
||||
- `SearchProvider` — die Regel bleibt wörtlich unverändert (`"tenantId" =
|
||||
current_tenant_id()`), mit gemessener Begründung (widerlegte Prämisse,
|
||||
siehe `docs/mandantentrennung-zugriffsklassifikation.md`).
|
||||
- Die Regeln auf `Group` und `TenantModuleActivation` — beide unverändert,
|
||||
nicht Teil der drei benannten Löcher.
|
||||
- Der Schalter (`DATABASE_URL`, Rolle `tessera`) — bleibt aus. Die drei
|
||||
Regeln sind heute wirkungslos; das Wegwerf-Werkzeug und die Regelliste der
|
||||
lebenden Datenbank sind die einzigen Zeugen dafür, dass sie greifen.
|
||||
|
||||
## Verweis
|
||||
|
||||
Die Bestandsaufnahme, welche Fundstelle den hier beschriebenen Übergang
|
||||
|
||||
@@ -48,20 +48,40 @@ zweiten `forTenant()`-Aufruf je Service-Methode) oder ob der Weg ersatzlos
|
||||
entfällt. Dieser Plan entscheidet das nicht, hält den Befund nur fest.
|
||||
|
||||
**WINDOWS #19 — nullbares `tenantId` bei `SearchProvider` und
|
||||
`TenderRssFeedSource`.** Beide Modelle tragen ein nullbares `tenantId`
|
||||
(`SearchProvider` für admin-gepflegte Vorgabe-Suchmaschinen, wobei laut
|
||||
05-02-Entscheidung die tatsächlichen Vorgaben als Konstanten und nicht als
|
||||
DB-Zeilen mit `tenantId = NULL` geführt werden — die Spalte ist nullbar,
|
||||
ob es heute tatsächlich `NULL`-Zeilen gibt, ist damit eine offene Frage für
|
||||
Etappe 3, nicht eine hier beantwortete; `TenderRssFeedSource` für
|
||||
plattformweite RSS-Quellen wie den geseedeten `service.bund.de`-Feed, D-06).
|
||||
Die aktuelle Policy `"tenantId" = current_tenant_id()` vergleicht `NULL`
|
||||
nie gleich — nach dem Scharfschalten wären plattformweite Zeilen für JEDEN
|
||||
Mandanten unsichtbar, nicht nur für fremde. Das ist heute ohne Wirkung
|
||||
(Schalter aus, #18), muss aber in Etappe 3 zusammen mit den restlichen
|
||||
Fundstellen gelöst werden: die Policy braucht für den Lesezugriff
|
||||
`tenantId IS NULL OR tenantId = current_tenant_id()`, während Schreibzugriffe
|
||||
weiterhin einen Mandanten verlangen.
|
||||
`TenderRssFeedSource` — GESCHLOSSEN (260910-jab, Aufgabe 1/2).** Beide Modelle
|
||||
tragen ein nullbares `tenantId` (`SearchProvider` für admin-gepflegte
|
||||
Vorgabe-Suchmaschinen; `TenderRssFeedSource` für plattformweite RSS-Quellen
|
||||
wie den geseedeten `service.bund.de`-Feed, D-06). Die ausgelieferte Policy
|
||||
`"tenantId" = current_tenant_id()` verglich `NULL` nie gleich — nach dem
|
||||
Scharfschalten wären plattformweite Zeilen für JEDEN Mandanten unsichtbar
|
||||
gewesen, nicht nur für fremde.
|
||||
|
||||
Geschlossen durch Migration
|
||||
`20260910120000_rls_widen_membership_grant_and_platform_read`, lokal
|
||||
angewandt und gegen den Systemkatalog der lebenden Datenbank gemessen:
|
||||
`TenderRssFeedSource` bekommt vier nach Befehl getrennte Regeln —
|
||||
`tenant_platform_read_policy` (SELECT) schließt Zeilen ohne Mandant
|
||||
ausdrücklich ein, `tenant_insert_policy`/`tenant_update_policy`/
|
||||
`tenant_delete_policy` verlangen weiterhin ausnahmslos einen Mandanten. Die
|
||||
Trennung nach Befehl ist notwendig, weil ein einzelner `USING`-Ausdruck auch
|
||||
bestimmt, welche Zeilen `UPDATE`/`DELETE` erreichen — eine Leseregel, die
|
||||
plattformweite Zeilen einschließt, hätte ohne Trennung jedem Mandanten auch
|
||||
das Ändern/Entfernen dieser Zeilen erlaubt.
|
||||
|
||||
Die Hälfte zur Suchanbietertabelle (`SearchProvider`) schließt NICHT als
|
||||
gelöstes Problem, sondern als **widerlegte Prämisse**: lokal gemessen gibt es
|
||||
keinen Codeweg, der eine mandantenlose `SearchProvider`-Zeile erzeugt — der
|
||||
einzige Schreibweg (`dashboard.service.ts`) verlangt die Mandantenkennung als
|
||||
Pflichtparameter, und die Vorgabe-Suchmaschinen kommen laut 05-02-Entscheidung
|
||||
aus Konstanten, nicht aus der Datenbank. Die Regel bleibt deshalb bewusst
|
||||
unverändert streng; eine Lockerung wäre hier die falsche Richtung, weil sie
|
||||
eine künftige mandantenlose Zeile jedem Mandanten zeigen würde.
|
||||
|
||||
Was diese Reparatur NICHT löst: unter der Anwendungsrolle lässt sich eine
|
||||
plattformweite `TenderRssFeedSource`-Zeile weder anlegen noch entfernen, in
|
||||
der alten wie in der neuen Regel, weil jede Schreibregel einen Mandanten
|
||||
verlangt. Als eigener offener Ledger-Eintrag festgehalten (WINDOWS #24),
|
||||
damit dieser Rest nicht mit #19 verschwindet.
|
||||
|
||||
## Übersicht je Bereich (Zeilentreffer je Bereich, ungebunden vs. gebunden)
|
||||
|
||||
@@ -96,7 +116,7 @@ autoritative Quelle.
|
||||
|
||||
| Bereich | Ungebunden | Gebunden | Hinweis |
|
||||
|---|---|---|---|
|
||||
| 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) |
|
||||
| tenders | 35 | 27 | **war 62/0**, dann 36/26 nach 260909-laa — 260910-jab (Aufgabe 2) hat `tender-rss-feed.service.ts`/`listForUser` zusätzlich auf `forTenant()` umgestellt (WINDOWS #19 geschlossen, Befund F: ungebunden hätte die Reparatur den Pfad sonst still auf nur die plattformweiten Zeilen reduziert): ein Rohtreffer wandert von ungebunden nach gebunden (36→35, 26→27). Die 35 verbleibenden ungebundenen Treffer sind die zwölf bewusst nicht angefassten Paare (D-03-Katalog, zwei Fan-out-Adapter) plus die zwei bewusst ungebundenen RSS-Pfade (`createPlatform`/`remove`, WINDOWS #24) 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 | 1 | 22 | **war 21/0** — Aufgabe 2/3 (260909-mir) haben `dkv.service.ts` vollständig auf `forTenant()` umgestellt: Konfigurationspfade (`loadConfig`, `getConfigForApi`, `saveConfig`, `testConnection`), Historie, Fahrzeugstammdaten und der neue Besitzriegel vor dem Ausfuhrdatei-Download. Gebunden sind es 22 statt 21, weil der Riegel einen zusätzlichen Lesezugriff auf `dkvInvoiceHistory` einführt (T-MIR-03). Der eine verbleibende ungebundene Treffer ist der benannte Planer-Startpfad `loadAnyActiveConfigForScheduler()` (Befund D, WINDOWS #21) — bewusst, mit dreifacher Markierung |
|
||||
@@ -108,7 +128,7 @@ autoritative Quelle.
|
||||
| tenant | 8 | 0 | unverändert |
|
||||
| favorites | 7 | 0 | unverändert |
|
||||
| settings | 4 | 0 | unverändert |
|
||||
| **Summe** | **108** | **134** | Ungebunden: war 118 nach 260910-das, Delta = die 10 in Aufgabe 2/3 (260910-exd) gesunkenen `module-registry`-Rohtreffer (17→7). Gebunden: war 124, jetzt zusätzlich 10 in `module-registry`. 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 |
|
||||
| **Summe** | **107** | **135** | Ungebunden: war 118 nach 260910-das, dann 108 nach 260910-exd (module-registry 17→7), jetzt 107 nach 260910-jab (`tenders` 36→35, `listForUser` gebunden). Gebunden: war 124, dann 134 nach 260910-exd (zusätzlich 10 in `module-registry`), jetzt 135 nach 260910-jab (zusätzlich 1 in `tenders`). 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, 63 Paare)
|
||||
|
||||
@@ -276,7 +296,7 @@ verwendet), Klasse, Stand (`gebunden`/`ungebunden`/`gemischt`, seit
|
||||
| apps/api/src/calendar/calendar.service.ts | calendarSource | muss-mandantengebunden | ungebunden | Kalenderquellen eines Nutzers, `tenantId`-Spalte vorhanden. |
|
||||
| apps/api/src/dashboard/dashboard.service.ts | dashboardLayout | muss-mandantengebunden | ungebunden | Widget-Anordnung eines Nutzers, `tenantId`-Spalte vorhanden. |
|
||||
| apps/api/src/dashboard/dashboard.service.ts | module | keine-mandantengebundene-tabelle | ungebunden | Modulkatalog ist plattformweit, kein `tenantId` (Migration 20260909140000, Gruppe b). |
|
||||
| apps/api/src/dashboard/dashboard.service.ts | searchProvider | muss-mandantengebunden | ungebunden | `tenantId` nullbar (WINDOWS #19) — heutige, tatsächlich gespeicherte Zeilen sind nutzerangelegt und tragen einen Mandanten; Vorgabe-Anbieter kommen laut 05-02 aus Konstanten, nicht aus der DB. |
|
||||
| apps/api/src/dashboard/dashboard.service.ts | searchProvider | muss-mandantengebunden | ungebunden | `tenantId` nullbar. WINDOWS #19 geschlossen (260910-jab) als **widerlegte Prämisse** für dieses Modell: lokal gemessen null Zeilen mit `tenantId = NULL` insgesamt, dieser Schreibweg verlangt die Mandantenkennung als Pflichtparameter; Vorgabe-Anbieter kommen laut 05-02 aus Konstanten, nicht aus der DB. Die Regel auf `SearchProvider` bleibt deshalb unverändert streng. |
|
||||
| apps/api/src/dashboard/dashboard.service.ts | widgetInstance | muss-mandantengebunden | ungebunden | Platzierte Dashboard-Widgets eines Nutzers, `tenantId`-Spalte vorhanden. |
|
||||
| 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. |
|
||||
@@ -286,10 +306,10 @@ verwendet), Klasse, Stand (`gebunden`/`ungebunden`/`gemischt`, seit
|
||||
| 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). |
|
||||
| apps/api/src/groups/groups.service.ts | tenantModuleActivation | muss-mandantengebunden | gebunden | Welche Module ein Mandant aktiviert hat. Bis 260909-jts vollstaendig unsichtbar (Befund B, 260909-jts-PLAN.md): der einzige Zugriff dieser Datei lief innerhalb der interaktiven Transaktion von `ensureDefaultGroup` ueber den Rueckgabeparameter (`tx.tenantModuleActivation.findMany`) — weder `this.prisma.<Modell>` noch `<gebundener Client>.<Modell>` sahen das, weil `tx` weder `this.prisma` noch aus einer `forTenant(`-Zuweisung stammte. Die um Transaktionsparameter erweiterte Erkennung aus Aufgabe 2 macht dieses Paar erstmals sichtbar; die Stelle ist seit derselben Aufgabe gebunden (ueber `withTenantTransaction()`). |
|
||||
| apps/api/src/groups/groups.service.ts | user | muss-mandantengebunden | gebunden | Nutzerverwaltung innerhalb eines Mandanten. Seit 260909-jts gebunden; `addUserToDefaultGroup` prueft seither zusaetzlich, dass der Zielbenutzer zum Mandanten gehoert (Befund E, T-JTS-02) — die Regel auf `GroupMembership` prueft nachweislich nur die Gruppenseite. |
|
||||
| apps/api/src/groups/groups.service.ts | user | muss-mandantengebunden | gebunden | Nutzerverwaltung innerhalb eines Mandanten. Seit 260909-jts gebunden; `addUserToDefaultGroup` prueft seither zusaetzlich, dass der Zielbenutzer zum Mandanten gehoert (Befund E, T-JTS-02). Bis 20260910120000_rls_widen_membership_grant_and_platform_read pruefte die Regel auf `GroupMembership` nachweislich nur die Gruppenseite — seit 260910-jab prueft sie beide Seiten, die Anwendungspruefung bleibt trotzdem bestehen (Schalter weiterhin aus, #18). |
|
||||
| apps/api/src/groups/module-grants.service.ts | group | muss-mandantengebunden | gebunden | Wie groups.service.ts. Seit 260909-jts (Aufgabe 3) gebunden — `assertTargetBelongsToTenant` erzeugt seinen eigenen Kontext, `getMatrix` teilt sich einen Kontext mit den beiden anderen parallelen Teilabfragen. |
|
||||
| apps/api/src/groups/module-grants.service.ts | groupMembership | muss-mandantengebunden | gebunden | Kein eigenes `tenantId`, RLS über Join auf `Group`. Seit 260909-jts gebunden; der `where`-Filter über die Beziehung zur Gruppe bleibt zusätzlich stehen, weil die Regel auf dieser Tabelle nachweislich nur die Gruppenseite prüft (Aufgabe 1). |
|
||||
| apps/api/src/groups/module-grants.service.ts | moduleGrant | muss-mandantengebunden | gebunden | Modulfreigaben je Mandant. Seit 260909-jts gebunden; die Mandanten-Gegenprüfung vor jedem Erteilen (`assertTargetBelongsToTenant`) bleibt zusätzlich bestehen, weil die Regel auf dieser Tabelle nur die Mandantenkennung der Zeile prüft, nicht die referenzierte Gruppe (Befund F, T-JTS-03, Aufgabe 1). |
|
||||
| apps/api/src/groups/module-grants.service.ts | moduleGrant | muss-mandantengebunden | gebunden | Modulfreigaben je Mandant. Seit 260909-jts gebunden; die Mandanten-Gegenprüfung vor jedem Erteilen (`assertTargetBelongsToTenant`) bleibt zusätzlich bestehen. Bis 20260910120000_rls_widen_membership_grant_and_platform_read prüfte die Regel auf dieser Tabelle nur die Mandantenkennung der Zeile, nicht die referenzierte Gruppe/den referenzierten Benutzer (Befund F, T-JTS-03, Aufgabe 1) — seit 260910-jab prüft sie beide Ziele zusätzlich, die Anwendungsprüfung bleibt trotzdem der erste Schutz (Schalter weiterhin aus, #18). |
|
||||
| apps/api/src/groups/module-grants.service.ts | tenantModuleActivation | muss-mandantengebunden | gebunden | Welche Module ein Mandant aktiviert hat, `tenantId`-Spalte vorhanden. Seit 260909-jts gebunden. |
|
||||
| apps/api/src/groups/module-grants.service.ts | user | muss-mandantengebunden | gebunden | Zielbenutzer eines Grants innerhalb des Mandanten. Seit 260909-jts gebunden; die Mandanten-Gegenprüfung bleibt bestehen. |
|
||||
| apps/api/src/ldap/ldap-config.service.ts | ldapConfig | beides | gemischt | `getConfig`, `createConfig` und `updateConfig` laufen ueber `forTenant()`, gebunden an den aus der Anfrage bereits bekannten Mandanten. `getAllActiveConfigs()` (Planer-Lesezugriff ueber ALLE Mandanten) und die Start-Nachverschluesselung in `onApplicationBootstrap` bleiben bewusst uebergreifend: beide laufen, bevor bzw. unabhaengig davon, ob ein einzelner Mandantenkontext feststeht (Befund B, 260909-ipc-PLAN.md). Korrektur der Klasse von `muss-mandantengebunden`: das Paar ist tatsaechlich `beides`, keine Verhaltensaenderung. |
|
||||
@@ -322,7 +342,7 @@ verwendet), Klasse, Stand (`gebunden`/`ungebunden`/`gemischt`, seit
|
||||
| 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-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()`. Seit 260910-jab (Aufgabe 2, WINDOWS #19 geschlossen) bindet zusätzlich `listForUser` — die neue Leseregel (`tenant_platform_read_policy`, 20260910120000_rls_widen_membership_grant_and_platform_read) schließt die plattformweite Zeile ausdrücklich ein, ungebunden hätte die Reparatur den Pfad sonst still auf nur die plattformweiten Zeilen reduziert (Befund F). `createPlatform`/`remove` bleiben bewusst ungebunden — beide Pfade lassen sich unter der Anwendungsrolle grundsätzlich nicht anlegen/entfernen, weil jede Schreibregel einen Mandanten verlangt (WINDOWS #24, eigener offener Punkt). Klasse `beides` bleibt korrekt: zwei gebundene, zwei bewusst ungebundene Zugriffe in derselben Datei. |
|
||||
| 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()`. |
|
||||
| apps/api/src/tenders/tender-scheduler.service.ts | tenderSourcePollConfig | keine-mandantengebundene-tabelle | ungebunden | Plattformweiter DÖE-Poll-Status, kein `tenantId` — im Dateikopf explizit als "genuine platform-wide singleton" begründet. |
|
||||
| apps/api/src/tenders/tender-triage.service.ts | tenderTriage | muss-mandantengebunden | gebunden | Favorisierungs-/Ablehnungsstatus eines Nutzers, `tenantId`-Spalte vorhanden. Seit 260909-laa (Aufgabe 2) laufen `setTriage`/`listForUser`/`favoriteIds` vollständig über `forTenant()`. |
|
||||
@@ -348,9 +368,13 @@ verwendet), Klasse, Stand (`gebunden`/`ungebunden`/`gemischt`, seit
|
||||
`forTenant()`- bzw. `withTenantTransaction()`-Aufruf, gebundene Clients
|
||||
werden nicht zwischen Methoden weitergereicht. Die Frage bleibt für alle
|
||||
übrigen Bereiche der Etappe 2 offen.
|
||||
- Wie die WINDOWS-#19-Policy für `SearchProvider`/`TenderRssFeedSource`
|
||||
- ~~Wie die WINDOWS-#19-Policy für `SearchProvider`/`TenderRssFeedSource`
|
||||
am Ende genau lautet — nur, dass sie vor dem Scharfschalten gelöst sein
|
||||
muss.
|
||||
muss.~~ Aufgelöst (260910-jab): `TenderRssFeedSource` bekommt vier nach
|
||||
Befehl getrennte Regeln, `SearchProvider` bleibt unverändert streng
|
||||
(widerlegte Prämisse). Siehe Abschnitt "Zwei belegte Befunde" oben. Was
|
||||
weiterhin offen ist: der Verwaltungsweg für plattformweite Zeilen unter
|
||||
der Anwendungsrolle (WINDOWS #24).
|
||||
- Die Reihenfolge und Zuschnitt der Etappe-2-Pläne — dafür ist die
|
||||
Klassen-Verteilung oben der Arbeitsvorrat, siehe `<next_stages>` im
|
||||
Plan `260909-eor-PLAN.md`.
|
||||
|
||||
Reference in New Issue
Block a user