feat(quick-260910-das): user-Dienst binden, Linie ziehen, Startsperre entschaerfen
- user.service.ts: findById/update/deactivate/delete bekommen einen Pflicht-Mandanten und laufen ueber forTenant(); create/update uebersetzen die plattformweite Eindeutigkeitsverletzung (P2002) in eine deutsche Konfliktmeldung ohne Halter/Mandant zu nennen; zwei neue Methoden (findAllForPlatformAdmin, findByIdForPlatformAdmin) bilden die Plattform-Administratorsicht als Schleife ueber alle Mandanten mit je einem gebundenen Lesezugriff nach; findByUsername bleibt bewusst ungebunden, Kopfkommentar richtiggestellt (Anmeldeweg laeuft seit Etappe 1 ueber SECURITY-DEFINER-Funktionen, kein Aufrufer mehr) - admin-seed.service.ts: Erstanlage-Pruefung bleibt ungebunden (mit Begruendung), Erstanlage des Administrators bindet an den unmittelbar zuvor angelegten Mandanten (Befund J-Korrektur); P2002 bei der Anlage wird wie "Administrator existiert bereits" behandelt statt den Start abzubrechen -- jeder andere Fehler bricht weiterhin ab - user.controller.ts: die vier Aufrufstellen der geaenderten Signaturen auf currentUser.tenantId umgestellt (Signatur-Minimalanpassung; die Rollenlogik inkl. Plattform-Administratorsicht folgt in Aufgabe 3) - Zwei-Klienten-Nachweis in beiden Testdateien (Muster groups.service.spec.ts), Falsifizierungsnachweis fuer beide Bereiche durchgefuehrt und zurueckgenommen (siehe SUMMARY) - docs/mandantentrennung-zugriffsklassifikation.md: Zwischenstand fuer (user.service.ts, user) und (admin-seed.service.ts, user) auf gemischt korrigiert, neue Zeile (user.service.ts, tenant) ergaenzt -- volle Klassenkorrektur mit Begruendung sowie die vier handgepflegten Uebersichtstabellen folgen in Aufgabe 3 - .planning/WINDOWS.md: offener Eintrag fuer die plattformweite Eindeutigkeit von username/email (Produktentscheidung fuer Etappe 3) - 802 Tests gruen (13 neue in user.service.spec.ts, 4 neue in admin-seed.service.spec.ts), Typpruefung sauber, Wegwerf-Werkzeug meldet weiterhin alle 53 Pruefungen bestanden Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
+16
-3
@@ -1,10 +1,10 @@
|
||||
---
|
||||
schema_version: 1
|
||||
open_count: 4
|
||||
open_count: 5
|
||||
waived_count: 1
|
||||
fixed_count: 16
|
||||
total_count: 21
|
||||
last_updated: 2026-09-09T14:41:07.256Z
|
||||
total_count: 22
|
||||
last_updated: 2026-09-10T08:17:20.009Z
|
||||
---
|
||||
|
||||
# Broken Windows Ledger
|
||||
@@ -36,6 +36,7 @@ last_updated: 2026-09-09T14:41:07.256Z
|
||||
| 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. 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 | |
|
||||
| 21 | 2 | deviation | apps/api/src/dkv/dkv-scheduler.service.ts | | DKV-Planer-Startpfad (DkvSchedulerService.onModuleInit -> DkvService.loadAnyActiveConfigForScheduler, vormals loadConfig() ohne Mandant) bleibt bewusst UNGEBUNDEN, als benannte Altlast aus 07-04 (260909-mir, Befund D). Zwei Zustaende, beide gehoeren genannt: HEUTE bereits falsch -- findFirst() ohne jede Bedingung zieht bei mehreren Mandanten EINEN beliebigen und bedient die uebrigen NIE (isActive-Pruefung kann den Planer sogar ganz leer laufen lassen, wenn ausgerechnet die gezogene Zeile inaktiv ist, obwohl ein zweiter Mandant aktiv waere). NACH DEM SCHARFSCHALTEN (#18) verstummt sie zusaetzlich -- dieselbe Abfrage liefert dann null, der Planer protokolliert 'no active config found' und richtet fuer JEDEN Mandanten nichts ein, ohne Alarm. Drei erwogene Formen prufen: (a) an einen konkret aufgeloesten Mandanten binden -- nicht moeglich, onModuleInit() hat beim Boot strukturell keinen Mandantenkontext. (b) Umbau auf einmal-abfragen-viele-bedienen -- abgelehnt, das ist die in 07-04 zurueckgestellte Mehrmandanten-Planung (neue Auftragsverwaltung je Mandant statt des heutigen setInterval() mit GENAU EINEM Auftrag) und damit eine Funktionsaenderung, kein Bindungsumbau. (c) Als benannte Altlast weiterfuehren, mit Markierung -- GEWAEHLT, Praezedenzfall LdapConfigService.getAllActiveConfigs() (260909-ipc, Befund B). Die Unsymmetrie zu diesem Praezedenzfall: getAllActiveConfigs ist HEUTE korrekt und verstummt erst spaeter: der DKV-Planer ist HEUTE bereits falsch UND verstummt zusaetzlich spaeter. Markierung dreifach: eigene benannte Methode loadAnyActiveConfigForScheduler() mit Kopfkommentar (dkv.service.ts), fortgeschriebener Kopfkommentar in dkv-scheduler.service.ts, Abschnitt (d4) in docs/mandantentrennung-etappe2-fehlerrichtung.md. Signal fuer das Verstummen gehoert in die Vorabpruefung von Etappe 4 (rls-preflight.mjs), NICHT in diesen Durchlauf. | open | | 2026-09-09T14:41:07.256Z | |
|
||||
| 22 | quick-260910-das | deviation | apps/api/src/user/user.service.ts | | Plattformweite Eindeutigkeit von username/email (kein tenantId-Anteil im Unique-Index): die gemessene Kette unsichtbare Zeile -> falsches 'frei' -> harter Eindeutigkeitsfehler (SQLSTATE 23505) ist in dieser Etappe im Anwendungscode entschaerft (Konfliktmeldung bei create/update, Startsperre in admin-seed.service.ts abgefangen), nicht an der Ursache geloest. Die ehrliche Reparatur waere eine Schemaaenderung (Eindeutigkeit mit Mandantendimension) und ist als Produktentscheidung fuer Etappe 3 vorgemerkt. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich user' (u1/u4). | open | | 2026-09-10T08:17:20.009Z | |
|
||||
|
||||
````json
|
||||
[
|
||||
@@ -290,6 +291,18 @@ last_updated: 2026-09-09T14:41:07.256Z
|
||||
"reason": "",
|
||||
"recorded_at": "2026-09-09T14:41:07.256Z",
|
||||
"resolved_at": null
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"kind": "deviation",
|
||||
"phase": "quick-260910-das",
|
||||
"file": "apps/api/src/user/user.service.ts",
|
||||
"line": null,
|
||||
"description": "Plattformweite Eindeutigkeit von username/email (kein tenantId-Anteil im Unique-Index): die gemessene Kette unsichtbare Zeile -> falsches 'frei' -> harter Eindeutigkeitsfehler (SQLSTATE 23505) ist in dieser Etappe im Anwendungscode entschaerft (Konfliktmeldung bei create/update, Startsperre in admin-seed.service.ts abgefangen), nicht an der Ursache geloest. Die ehrliche Reparatur waere eine Schemaaenderung (Eindeutigkeit mit Mandantendimension) und ist als Produktentscheidung fuer Etappe 3 vorgemerkt. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich user' (u1/u4).",
|
||||
"status": "open",
|
||||
"reason": "",
|
||||
"recorded_at": "2026-09-10T08:17:20.009Z",
|
||||
"resolved_at": null
|
||||
}
|
||||
]
|
||||
````
|
||||
|
||||
Reference in New Issue
Block a user