fix(quick-260909-eor): WINDOWS #20 offen halten bis Wirkung nach Scharfschalten belegt ist

Aufgabe 4: die Browser-Gegenprobe der Anmeldung ist bestanden (siehe SUMMARY).
Auf ausdruecklichen Wunsch bleibt WINDOWS #20 (forTenant()-Verbindungsdefekt)
dennoch OPEN statt fixed — die technische Reparatur ist nachgewiesen
(rls-scratch-check.mjs, 8/8 gegen eine Wegwerf-Datenbank mit Rolle ohne
BYPASSRLS), aber die Wirkung unter der echten Anwendungsrolle tessera_app
ist erst nach dem Scharfschalten (#18) beobachtbar. #20 bleibt damit an
dieselbe Bedingung gebunden wie #18 und #19.

Nebenbefund beim Zuruecksetzen: die vorherige Markierung als "fixed" hatte
nur die Markdown-Tabelle veraendert, nicht den massgeblichen JSON-Block am
Dateiende (die eigentliche Quelle der Wahrheit fuer gsd-tools windows status)
— `gsd-tools windows status` scheiterte seitdem mit
"Ledger counts disagree with entries". Behoben durch Neuaufbau aus der
Vorversion via der broken-windows.cjs-Bibliothek (parseLedger/renderLedger),
damit Tabelle und JSON-Block wieder uebereinstimmen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
This commit is contained in:
2026-09-09 11:11:28 +02:00
parent 5f3a39c2c3
commit da0ac04e73
+10 -10
View File
@@ -1,10 +1,10 @@
---
schema_version: 1
open_count: 2
open_count: 3
waived_count: 1
fixed_count: 17
fixed_count: 16
total_count: 20
last_updated: 2026-09-09T09:03:22.739Z
last_updated: 2026-09-09T09:10:51.419Z
---
# Broken Windows Ledger
@@ -32,9 +32,9 @@ last_updated: 2026-09-09T09:03:22.739Z
| 15 | 16 | unmet-truth | apps/api/src/ldap/ldap.service.ts | | Der Sync reicht rohe Techniktexte an den Administrator durch und laesst echte AD-Konten still liegen. Am 2026-09-07 auf alpha gegen das echte AD gemessen (Lauf 14:42): zehn Fehlerzeilen unter den drei Zahlenzeilen, davon zwei Sorten. (1) Vier Konten scheitern mit der woertlichen Prisma-Meldung 'Invalid prisma.user.create() invocation: Unique constraint failed on the fields: (email)' — CN=uvertrieb_ro, uvertrieb_rw, uvertrieb_ro_ss, usoftware_rw aus OU=CTL_PWS_Gruppen teilen sich offenbar eine E-Mail-Adresse. Sie werden dadurch NIE importiert, ohne dass der Administrator erfaehrt warum oder was er tun soll. (2) Sechs Eintraege melden englisch 'no username mapped (check sAMAccountName mapping)' — korrekt uebersprungene Kontakte/Ressourcen ohne sAMAccountName, aber die Meldung liest sich wie ein Fehler und ist nicht uebersetzt. Beides braucht eine verstaendliche deutsche Meldung; die E-Mail-Kollision zusaetzlich eine Entscheidung, ob solche Konten ohne E-Mail angelegt oder bewusst uebersprungen werden. Beleg: .planning/phases/16-ad-gruppen-synchronisation/uat-2026-09-07/windows6a-sync-zahlenzeilen.png | fixed | | 2026-09-07T12:47:32.058Z | 2026-09-09T06:25:18.145Z |
| 16 | 14 | unmet-truth | apps/api/src/tenders/tenders.controller.ts | | Das Postfach im Ausschreibungs-Radar hat keinen Verbindungstest, obwohl die Faehigkeit fertig vorliegt. Beide Inbox-Provider bringen testConnection() mit (imap.provider.ts:343, exchange-inbox.provider.ts:294), und das DKV-Modul nutzt sie ueber POST /dkv/test-connection samt Knopf 'Verbindung testen' im InboxConfigForm. Beim Ausschreibungs-Radar fehlt beides: der Controller kennt zu email-config nur GET (:343) und PUT (:357), kein Test-Endpunkt, und das Formular unter Meine Quellen hat keinen Knopf. Historie geprueft: der Knopf war nie vorhanden (git log -S testConnection im tender-radar-Frontend ist leer), es ist also eine Luecke, keine Regression. Folge fuer den Betrieb: ein Tippfehler in der EWS-Endpunkt-URL oder falsche Zugangsdaten fallen erst auf, wenn dauerhaft nichts ankommt — und dann ist nicht unterscheidbar, ob die Verbindung scheitert oder schlicht keine Alarm-Mail da war. Das trifft besonders WINDOWS #12, dessen ganzer Zweck der Beleg des handgeschriebenen NTLM/SOAP-Wegs ist. Aufgefallen am 2026-09-07 beim Einrichten des Postfachs. | fixed | | 2026-09-07T13:22:33.916Z | 2026-09-09T05:20:09.851Z |
| 17 | 6 | unmet-truth | docker-compose.yml | | Hochgeladene Dateien ueberleben kein Neuerstellen der Container. Der Code legt sie unter user-files/ ab (user.controller.ts:40 und :266 fuer Profilbilder, dazu die DKV-Exporte), aber KEINE der Compose-Dateien mountet dieses Verzeichnis — weder im Repository (docker-compose.yml, .prod.yml, .dev.yml haben nur das Volume pgdata) noch in der abweichenden Datei auf dem Server /opt/tessera/docker-compose.yml. Am 2026-09-09 gemessen: 'docker inspect' auf tessera-api-1 meldet ueberhaupt keinen Mount, /app/user-files liegt damit nur in der beschreibbaren Container-Schicht und ist bei jedem 'up -d --force-recreate' weg. Aufgefallen beim Schreiben des Betriebshandbuchs. KEIN Schaden entstanden: aktuell hat kein Nutzer ein Profilbild hinterlegt (avatarPath ueberall NULL), und die bisherigen Neuerstellungen trafen einen leeren Ordner. Die Luecke schlaegt zu, sobald der erste Nutzer ein Bild hochlaedt oder ein DKV-Export aufgehoben werden soll. Behebung: ein benanntes Volume oder Bind-Mount fuer user-files in beiden Compose-Dateien; die Server-Datei muss zusaetzlich von Hand ergaenzt werden, weil sie vom Repository abweicht. | fixed | | 2026-09-09T06:42:22.801Z | 2026-09-09T08:22:51.235Z |
| 18 | 2 | unmet-truth | docker-compose.yml | | Die Mandantentrennung auf Datenbankebene ist wirkungslos, weil die Anwendungsrolle sie umgeht. Die API verbindet laut docker-compose.yml:33 als Rolle 'tessera'; diese Rolle hat auf alpha rolsuper=t UND rolbypassrls=t. PostgreSQL wendet Row-Level-Security auf solche Rollen grundsaetzlich nicht an — auch FORCE ROW LEVEL SECURITY aendert daran nichts, das erzwingt nur die Anwendung auf den Tabelleneigentuemer, nicht auf BYPASSRLS-Rollen. Am 2026-09-09 praktisch gemessen: ohne gesetztes app.current_tenant liefert 'SELECT count(*) FROM "Group"' zwei Zeilen, waehrend die Policy USING ("tenantId" = current_tenant_id()) bei NULL-Kontext null Zeilen liefern muesste. Damit sind alle sieben bisher mit RLS ausgestatteten Tabellen (User, Group, GroupMembership, LdapConfig, LdapFieldMapping, ModuleGrant, PasswordResetToken) faktisch ungeschuetzt; die Trennung haengt allein am manuellen 'where tenantId' im Anwendungscode. Die Migration 20260804130918 nennt RLS ausdruecklich 'ein zweites Sicherheitsnetz' — dieses Netz existiert derzeit nicht. Reihenfolge der Behebung: ZUERST eine eigene Anwendungsrolle ohne Superuser- und BYPASSRLS-Recht einrichten und die Anwendung darauf umstellen, DANN greifen die vorhandenen Policies, und ERST DANN lohnt es, fehlende Tabellen zu ergaenzen. Vorher gebaute Policies waeren wirkungslos und wuerden eine Sicherheit vortaeuschen. Kein akutes Risiko, solange Tessera nur intern und einmandantig laeuft (ein einziger Mandant 'default'), aber vor jedem Kundeneinsatz zwingend. NACHTRAG (260909-eor, Aufgabe 3): dieser Plan hat den fuer die Umstellung noetigen Anwendungscode klassifiziert (docs/mandantentrennung-zugriffsklassifikation.md, 227 Fundstellen / 59 Datei-Modell-Paare) und zusaetzlich einen weiteren, beim Anlegen dieses Eintrags noch nicht bekannten Defekt gefunden und behoben: forTenant() setzte den Mandantenkontext auf einer anderen Datenbankverbindung als die eigentliche Abfrage lief (siehe #20, jetzt behoben). Ohne diese Reparatur waere die Umstellung auf die Rolle tessera_app selbst nach Behebung dieses Eintrags wirkungslos gewesen. | open | | 2026-09-09T07:42:13.878Z | |
| 18 | 2 | unmet-truth | docker-compose.yml | | Die Mandantentrennung auf Datenbankebene ist wirkungslos, weil die Anwendungsrolle sie umgeht. Die API verbindet laut docker-compose.yml:33 als Rolle 'tessera'; diese Rolle hat auf alpha rolsuper=t UND rolbypassrls=t. PostgreSQL wendet Row-Level-Security auf solche Rollen grundsaetzlich nicht an — auch FORCE ROW LEVEL SECURITY aendert daran nichts, das erzwingt nur die Anwendung auf den Tabelleneigentuemer, nicht auf BYPASSRLS-Rollen. Am 2026-09-09 praktisch gemessen: ohne gesetztes app.current_tenant liefert 'SELECT count(*) FROM "Group"' zwei Zeilen, waehrend die Policy USING ("tenantId" = current_tenant_id()) bei NULL-Kontext null Zeilen liefern muesste. Damit sind alle sieben bisher mit RLS ausgestatteten Tabellen (User, Group, GroupMembership, LdapConfig, LdapFieldMapping, ModuleGrant, PasswordResetToken) faktisch ungeschuetzt; die Trennung haengt allein am manuellen 'where tenantId' im Anwendungscode. Die Migration 20260804130918 nennt RLS ausdruecklich 'ein zweites Sicherheitsnetz' — dieses Netz existiert derzeit nicht. Reihenfolge der Behebung: ZUERST eine eigene Anwendungsrolle ohne Superuser- und BYPASSRLS-Recht einrichten und die Anwendung darauf umstellen, DANN greifen die vorhandenen Policies, und ERST DANN lohnt es, fehlende Tabellen zu ergaenzen. Vorher gebaute Policies waeren wirkungslos und wuerden eine Sicherheit vortaeuschen. Kein akutes Risiko, solange Tessera nur intern und einmandantig laeuft (ein einziger Mandant 'default'), aber vor jedem Kundeneinsatz zwingend. NACHTRAG (260909-eor, Aufgabe 3): dieser Plan hat den fuer die Umstellung noetigen Anwendungscode klassifiziert (docs/mandantentrennung-zugriffsklassifikation.md, 227 Fundstellen / 59 Datei-Modell-Paare) und zusaetzlich einen weiteren, beim Anlegen dieses Eintrags noch nicht bekannten Defekt gefunden und behoben: forTenant() setzte den Mandantenkontext auf einer anderen Datenbankverbindung als die eigentliche Abfrage lief (siehe #20). #20 bleibt trotz nachgewiesener Reparatur bewusst OPEN, an dieselbe Bedingung gebunden wie dieser Eintrag — die Wirkung unter der echten Rolle ist erst nach dem Scharfschalten beobachtbar. | open | | 2026-09-09T07:42:13.878Z | |
| 19 | 2 | unmet-truth | apps/api/prisma/migrations/20260909140000_rls_remaining_tenant_tables/migration.sql | | Zwei der neuen Policies wuerden plattformweite Zeilen unsichtbar machen, sobald die Mandantentrennung scharf geschaltet wird. SearchProvider und TenderRssFeedSource haben ein nullable tenantId: Zeilen mit tenantId = NULL gelten fuer alle Mandanten (die von der Administration gepflegten Feeds und Suchanbieter). Die einfache Policy 'tenantId = current_tenant_id()' vergleicht NULL niemals gleich, diese Zeilen waeren nach der Aktivierung fuer JEDEN Mandanten weg — nicht nur fuer fremde. Heute ohne Wirkung, weil die Anwendung weiter als BYPASSRLS-Rolle verbindet (#18, Schalter bewusst aus). Beim Scharfschalten zwingend mitzuloesen, zusammen mit den 182 unskalierten Zugriffen: die Policy muss die plattformweiten Zeilen ausdruecklich einschliessen, etwa ueber 'tenantId IS NULL OR tenantId = current_tenant_id()' fuer den Lesezugriff, waehrend Schreibzugriffe weiterhin einen Mandanten verlangen. Beim Schreiben der Migration am 2026-09-09 aufgefallen und bewusst nicht eigenmaechtig anders geloest, weil die richtige Semantik eine Produktentscheidung ist. NACHTRAG (260909-eor, Aufgabe 3): docs/mandantentrennung-zugriffsklassifikation.md haelt diesen Befund im Abschnitt 'Zwei belegte Befunde' fest und benennt ihn als Blocker fuer Etappe 3. Die '182 unskalierten Zugriffe' sind ueberholt — die aktuelle, maschinell geprüfte Zahl ist 227 Fundstellen (59 Datei-Modell-Paare, siehe Klassifikationsdokument). | open | | 2026-09-09T08:08:19.293Z | |
| 20 | 2 | unmet-truth | apps/api/src/prisma/prisma-tenant.extension.ts | | forTenant() setzt den Mandantenkontext auf einer anderen Verbindung als die Abfrage laeuft — die Mandantentrennung hat damit nie funktioniert, auch nicht dort, wo sie scheinbar benutzt wird. Die Erweiterung oeffnet prisma.$transaction, setzt app.current_tenant per set_config(..., true) auf tx, ruft dann aber query(args) auf, das ueber den AEUSSEREN Client dispatcht. set_config mit local=true gilt nur in der Transaktion und nur auf deren Verbindung. Am 2026-09-09 gegen die lokale Datenbank reproduziert: set_config landete auf Backend-PID 254999, die eigentliche Abfrage auf 255000, und dort war current_setting('app.current_tenant') NULL. Heute ohne sichtbare Folge, weil die Anwendungsrolle BYPASSRLS hat (#18) und deshalb ohnehin alles sieht. NACH dem Scharfschalten kehrt sich das um: die betroffenen Abfragen liefern dann NULL ZEILEN statt zu vieler. Besonders gefaehrlich in ldap.service.ts (Loeschzweig um Zeile 1559): der Sync deutet die Leere als 'Gruppe im Verzeichnis verschwunden' und loescht sie samt Mitgliedschaften und Modulfreigaben — aus einem stillen Trennungsfehler wuerde stiller Datenverlust. Zusatzbefund: von den 36 vermeintlichen forTenant-Vorkommen sind die meisten Kommentare, die erklaeren, warum forTenant FEHLT; echte Aufrufstellen sind 6, echte mandantengebundene Abfragen 9, alle in ldap.service.ts. Ausserdem setzen tenant.middleware.ts:44 und tenant.guard.ts:41 ein req.tenantPrisma, das in apps/api/src von NIEMANDEM gelesen wird. Muss vor jedem weiteren Umbau repariert werden, sonst baut alles Weitere auf einem Helfer auf, der nicht traegt. | fixed | | 2026-09-09T08:44:18.496Z | 2026-09-09T09:03:22.739Z |
| 20 | 2 | unmet-truth | apps/api/src/prisma/prisma-tenant.extension.ts | | forTenant() setzt den Mandantenkontext auf einer anderen Verbindung als die Abfrage laeuft — die Mandantentrennung hat damit nie funktioniert, auch nicht dort, wo sie scheinbar benutzt wird. Die Erweiterung oeffnet prisma.$transaction, setzt app.current_tenant per set_config(..., true) auf tx, ruft dann aber query(args) auf, das ueber den AEUSSEREN Client dispatcht. set_config mit local=true gilt nur in der Transaktion und nur auf deren Verbindung. Am 2026-09-09 gegen die lokale Datenbank reproduziert: set_config landete auf Backend-PID 254999, die eigentliche Abfrage auf 255000, und dort war current_setting('app.current_tenant') NULL. Heute ohne sichtbare Folge, weil die Anwendungsrolle BYPASSRLS hat (#18) und deshalb ohnehin alles sieht. NACH dem Scharfschalten kehrt sich das um: die betroffenen Abfragen liefern dann NULL ZEILEN statt zu vieler. Besonders gefaehrlich in ldap.service.ts (Loeschzweig um Zeile 1559): der Sync deutet die Leere als 'Gruppe im Verzeichnis verschwunden' und loescht sie samt Mitgliedschaften und Modulfreigaben — aus einem stillen Trennungsfehler wuerde stiller Datenverlust. Zusatzbefund: von den 36 vermeintlichen forTenant-Vorkommen sind die meisten Kommentare, die erklaeren, warum forTenant FEHLT; echte Aufrufstellen sind 6, echte mandantengebundene Abfragen 9, alle in ldap.service.ts. Ausserdem setzen tenant.middleware.ts:44 und tenant.guard.ts:41 ein req.tenantPrisma, das in apps/api/src von NIEMANDEM gelesen wird. Muss vor jedem weiteren Umbau repariert werden, sonst baut alles Weitere auf einem Helfer auf, der nicht traegt. NACHTRAG (260909-eor, Aufgabe 1/4): der beschriebene Verbindungsfehler ist behoben (Array-Form von $transaction, prisma-tenant.extension.ts) und gegen eine Wegwerf-Datenbank mit einer Rolle ohne BYPASSRLS live nachgewiesen (rls-scratch-check.mjs, 8/8 Pruefungen bestanden). Bleibt dennoch bewusst OPEN, nicht fixed: die Wirkung unter der echten Anwendungsrolle tessera_app ist erst nach dem Scharfschalten (#18) beobachtbar — bis dahin bleibt #20 an dieselbe Bedingung gebunden wie #18 und #19. | open | | 2026-09-09T08:44:18.496Z | |
````json
[
@@ -248,7 +248,7 @@ last_updated: 2026-09-09T09:03:22.739Z
"phase": "2",
"file": "docker-compose.yml",
"line": null,
"description": "Die Mandantentrennung auf Datenbankebene ist wirkungslos, weil die Anwendungsrolle sie umgeht. Die API verbindet laut docker-compose.yml:33 als Rolle 'tessera'; diese Rolle hat auf alpha rolsuper=t UND rolbypassrls=t. PostgreSQL wendet Row-Level-Security auf solche Rollen grundsaetzlich nicht an — auch FORCE ROW LEVEL SECURITY aendert daran nichts, das erzwingt nur die Anwendung auf den Tabelleneigentuemer, nicht auf BYPASSRLS-Rollen. Am 2026-09-09 praktisch gemessen: ohne gesetztes app.current_tenant liefert 'SELECT count(*) FROM \"Group\"' zwei Zeilen, waehrend die Policy USING (\"tenantId\" = current_tenant_id()) bei NULL-Kontext null Zeilen liefern muesste. Damit sind alle sieben bisher mit RLS ausgestatteten Tabellen (User, Group, GroupMembership, LdapConfig, LdapFieldMapping, ModuleGrant, PasswordResetToken) faktisch ungeschuetzt; die Trennung haengt allein am manuellen 'where tenantId' im Anwendungscode. Die Migration 20260804130918 nennt RLS ausdruecklich 'ein zweites Sicherheitsnetz' — dieses Netz existiert derzeit nicht. Reihenfolge der Behebung: ZUERST eine eigene Anwendungsrolle ohne Superuser- und BYPASSRLS-Recht einrichten und die Anwendung darauf umstellen, DANN greifen die vorhandenen Policies, und ERST DANN lohnt es, fehlende Tabellen zu ergaenzen. Vorher gebaute Policies waeren wirkungslos und wuerden eine Sicherheit vortaeuschen. Kein akutes Risiko, solange Tessera nur intern und einmandantig laeuft (ein einziger Mandant 'default'), aber vor jedem Kundeneinsatz zwingend.",
"description": "Die Mandantentrennung auf Datenbankebene ist wirkungslos, weil die Anwendungsrolle sie umgeht. Die API verbindet laut docker-compose.yml:33 als Rolle 'tessera'; diese Rolle hat auf alpha rolsuper=t UND rolbypassrls=t. PostgreSQL wendet Row-Level-Security auf solche Rollen grundsaetzlich nicht an — auch FORCE ROW LEVEL SECURITY aendert daran nichts, das erzwingt nur die Anwendung auf den Tabelleneigentuemer, nicht auf BYPASSRLS-Rollen. Am 2026-09-09 praktisch gemessen: ohne gesetztes app.current_tenant liefert 'SELECT count(*) FROM \"Group\"' zwei Zeilen, waehrend die Policy USING (\"tenantId\" = current_tenant_id()) bei NULL-Kontext null Zeilen liefern muesste. Damit sind alle sieben bisher mit RLS ausgestatteten Tabellen (User, Group, GroupMembership, LdapConfig, LdapFieldMapping, ModuleGrant, PasswordResetToken) faktisch ungeschuetzt; die Trennung haengt allein am manuellen 'where tenantId' im Anwendungscode. Die Migration 20260804130918 nennt RLS ausdruecklich 'ein zweites Sicherheitsnetz' — dieses Netz existiert derzeit nicht. Reihenfolge der Behebung: ZUERST eine eigene Anwendungsrolle ohne Superuser- und BYPASSRLS-Recht einrichten und die Anwendung darauf umstellen, DANN greifen die vorhandenen Policies, und ERST DANN lohnt es, fehlende Tabellen zu ergaenzen. Vorher gebaute Policies waeren wirkungslos und wuerden eine Sicherheit vortaeuschen. Kein akutes Risiko, solange Tessera nur intern und einmandantig laeuft (ein einziger Mandant 'default'), aber vor jedem Kundeneinsatz zwingend. NACHTRAG (260909-eor, Aufgabe 3): dieser Plan hat den fuer die Umstellung noetigen Anwendungscode klassifiziert (docs/mandantentrennung-zugriffsklassifikation.md, 227 Fundstellen / 59 Datei-Modell-Paare) und zusaetzlich einen weiteren, beim Anlegen dieses Eintrags noch nicht bekannten Defekt gefunden und behoben: forTenant() setzte den Mandantenkontext auf einer anderen Datenbankverbindung als die eigentliche Abfrage lief (siehe #20). #20 bleibt trotz nachgewiesener Reparatur bewusst OPEN, an dieselbe Bedingung gebunden wie dieser Eintrag — die Wirkung unter der echten Rolle ist erst nach dem Scharfschalten beobachtbar.",
"status": "open",
"reason": "",
"recorded_at": "2026-09-09T07:42:13.878Z",
@@ -260,7 +260,7 @@ last_updated: 2026-09-09T09:03:22.739Z
"phase": "2",
"file": "apps/api/prisma/migrations/20260909140000_rls_remaining_tenant_tables/migration.sql",
"line": null,
"description": "Zwei der neuen Policies wuerden plattformweite Zeilen unsichtbar machen, sobald die Mandantentrennung scharf geschaltet wird. SearchProvider und TenderRssFeedSource haben ein nullable tenantId: Zeilen mit tenantId = NULL gelten fuer alle Mandanten (die von der Administration gepflegten Feeds und Suchanbieter). Die einfache Policy 'tenantId = current_tenant_id()' vergleicht NULL niemals gleich, diese Zeilen waeren nach der Aktivierung fuer JEDEN Mandanten weg — nicht nur fuer fremde. Heute ohne Wirkung, weil die Anwendung weiter als BYPASSRLS-Rolle verbindet (#18, Schalter bewusst aus). Beim Scharfschalten zwingend mitzuloesen, zusammen mit den 182 unskalierten Zugriffen: die Policy muss die plattformweiten Zeilen ausdruecklich einschliessen, etwa ueber 'tenantId IS NULL OR tenantId = current_tenant_id()' fuer den Lesezugriff, waehrend Schreibzugriffe weiterhin einen Mandanten verlangen. Beim Schreiben der Migration am 2026-09-09 aufgefallen und bewusst nicht eigenmaechtig anders geloest, weil die richtige Semantik eine Produktentscheidung ist.",
"description": "Zwei der neuen Policies wuerden plattformweite Zeilen unsichtbar machen, sobald die Mandantentrennung scharf geschaltet wird. SearchProvider und TenderRssFeedSource haben ein nullable tenantId: Zeilen mit tenantId = NULL gelten fuer alle Mandanten (die von der Administration gepflegten Feeds und Suchanbieter). Die einfache Policy 'tenantId = current_tenant_id()' vergleicht NULL niemals gleich, diese Zeilen waeren nach der Aktivierung fuer JEDEN Mandanten weg — nicht nur fuer fremde. Heute ohne Wirkung, weil die Anwendung weiter als BYPASSRLS-Rolle verbindet (#18, Schalter bewusst aus). Beim Scharfschalten zwingend mitzuloesen, zusammen mit den 182 unskalierten Zugriffen: die Policy muss die plattformweiten Zeilen ausdruecklich einschliessen, etwa ueber 'tenantId IS NULL OR tenantId = current_tenant_id()' fuer den Lesezugriff, waehrend Schreibzugriffe weiterhin einen Mandanten verlangen. Beim Schreiben der Migration am 2026-09-09 aufgefallen und bewusst nicht eigenmaechtig anders geloest, weil die richtige Semantik eine Produktentscheidung ist. NACHTRAG (260909-eor, Aufgabe 3): docs/mandantentrennung-zugriffsklassifikation.md haelt diesen Befund im Abschnitt 'Zwei belegte Befunde' fest und benennt ihn als Blocker fuer Etappe 3. Die '182 unskalierten Zugriffe' sind ueberholt — die aktuelle, maschinell geprüfte Zahl ist 227 Fundstellen (59 Datei-Modell-Paare, siehe Klassifikationsdokument).",
"status": "open",
"reason": "",
"recorded_at": "2026-09-09T08:08:19.293Z",
@@ -272,11 +272,11 @@ last_updated: 2026-09-09T09:03:22.739Z
"phase": "2",
"file": "apps/api/src/prisma/prisma-tenant.extension.ts",
"line": null,
"description": "forTenant() setzt den Mandantenkontext auf einer anderen Verbindung als die Abfrage laeuft — die Mandantentrennung hat damit nie funktioniert, auch nicht dort, wo sie scheinbar benutzt wird. Die Erweiterung oeffnet prisma.$transaction, setzt app.current_tenant per set_config(..., true) auf tx, ruft dann aber query(args) auf, das ueber den AEUSSEREN Client dispatcht. set_config mit local=true gilt nur in der Transaktion und nur auf deren Verbindung. Am 2026-09-09 gegen die lokale Datenbank reproduziert: set_config landete auf Backend-PID 254999, die eigentliche Abfrage auf 255000, und dort war current_setting('app.current_tenant') NULL. Heute ohne sichtbare Folge, weil die Anwendungsrolle BYPASSRLS hat (#18) und deshalb ohnehin alles sieht. NACH dem Scharfschalten kehrt sich das um: die betroffenen Abfragen liefern dann NULL ZEILEN statt zu vieler. Besonders gefaehrlich in ldap.service.ts (Loeschzweig um Zeile 1559): der Sync deutet die Leere als 'Gruppe im Verzeichnis verschwunden' und loescht sie samt Mitgliedschaften und Modulfreigaben — aus einem stillen Trennungsfehler wuerde stiller Datenverlust. Zusatzbefund: von den 36 vermeintlichen forTenant-Vorkommen sind die meisten Kommentare, die erklaeren, warum forTenant FEHLT; echte Aufrufstellen sind 6, echte mandantengebundene Abfragen 9, alle in ldap.service.ts. Ausserdem setzen tenant.middleware.ts:44 und tenant.guard.ts:41 ein req.tenantPrisma, das in apps/api/src von NIEMANDEM gelesen wird. Muss vor jedem weiteren Umbau repariert werden, sonst baut alles Weitere auf einem Helfer auf, der nicht traegt.",
"status": "fixed",
"description": "forTenant() setzt den Mandantenkontext auf einer anderen Verbindung als die Abfrage laeuft — die Mandantentrennung hat damit nie funktioniert, auch nicht dort, wo sie scheinbar benutzt wird. Die Erweiterung oeffnet prisma.$transaction, setzt app.current_tenant per set_config(..., true) auf tx, ruft dann aber query(args) auf, das ueber den AEUSSEREN Client dispatcht. set_config mit local=true gilt nur in der Transaktion und nur auf deren Verbindung. Am 2026-09-09 gegen die lokale Datenbank reproduziert: set_config landete auf Backend-PID 254999, die eigentliche Abfrage auf 255000, und dort war current_setting('app.current_tenant') NULL. Heute ohne sichtbare Folge, weil die Anwendungsrolle BYPASSRLS hat (#18) und deshalb ohnehin alles sieht. NACH dem Scharfschalten kehrt sich das um: die betroffenen Abfragen liefern dann NULL ZEILEN statt zu vieler. Besonders gefaehrlich in ldap.service.ts (Loeschzweig um Zeile 1559): der Sync deutet die Leere als 'Gruppe im Verzeichnis verschwunden' und loescht sie samt Mitgliedschaften und Modulfreigaben — aus einem stillen Trennungsfehler wuerde stiller Datenverlust. Zusatzbefund: von den 36 vermeintlichen forTenant-Vorkommen sind die meisten Kommentare, die erklaeren, warum forTenant FEHLT; echte Aufrufstellen sind 6, echte mandantengebundene Abfragen 9, alle in ldap.service.ts. Ausserdem setzen tenant.middleware.ts:44 und tenant.guard.ts:41 ein req.tenantPrisma, das in apps/api/src von NIEMANDEM gelesen wird. Muss vor jedem weiteren Umbau repariert werden, sonst baut alles Weitere auf einem Helfer auf, der nicht traegt. NACHTRAG (260909-eor, Aufgabe 1/4): der beschriebene Verbindungsfehler ist behoben (Array-Form von $transaction, prisma-tenant.extension.ts) und gegen eine Wegwerf-Datenbank mit einer Rolle ohne BYPASSRLS live nachgewiesen (rls-scratch-check.mjs, 8/8 Pruefungen bestanden). Bleibt dennoch bewusst OPEN, nicht fixed: die Wirkung unter der echten Anwendungsrolle tessera_app ist erst nach dem Scharfschalten (#18) beobachtbar — bis dahin bleibt #20 an dieselbe Bedingung gebunden wie #18 und #19.",
"status": "open",
"reason": "",
"recorded_at": "2026-09-09T08:44:18.496Z",
"resolved_at": "2026-09-09T09:03:22.739Z"
"resolved_at": null
}
]
````