Aufgabe 3 der Etappe 2: elf Abfragen in sechs Methoden (listGroups,
upsertMappedUser, searchUsers, importUsersByDn, importGroupsByDn,
syncUsersForTenant) laufen jetzt ueber forTenant(), teils mit einem neu
erzeugten, teils mit dem in derselben Methode bereits vorhandenen gebundenen
Client. resolveEmailForWrite bleibt ausdruecklich ungebunden (Befund A,
T-IPC-04): email/username sind plattformweit eindeutig, eine Bindung wuerde
einen fremden Halter uebersehen und eine saubere Kollisionsmeldung in einen
P2002-Abbruch verwandeln.
Ein neuer Testblock biegt forTenant() auf ein zweites, unterscheidbares
Client-Objekt um (der bisherige Identitaets-Mock haette die Umstellung nicht
bemerkt, Befund F) und belegt damit, dass die Adressabfrage weiterhin am
ungebundenen und der Rest am gebundenen Client landet. Alle 67 Bestandstests
bleiben unveraendert gruen.
docs/mandantentrennung-zugriffsklassifikation.md ist fuer den Bereich ldap
geschlossen: gemessener Stand je Fundstelle, neu gerechnete Bereichsuebersicht
(gebunden getrennt von ungebunden gezaehlt) und die Uebergabe des
Standardgruppen-Punkts an den Bereich groups vor Etappe 4 dokumentiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
Aufgabe 2 der Etappe 2: getConfig/createConfig/updateConfig sowie
addFieldMapping/removeFieldMapping laufen jetzt ueber forTenant(), gebunden
an den aus der Anfrage bekannten Mandanten. removeFieldMapping nimmt den
Mandanten neu als Pflichtparameter entgegen und der Controller holt ihn aus
dem Sitzungsnachweis statt nur die URL-Kennung weiterzureichen (T-IPC-01) --
ein Administrator konnte bisher die Feldzuordnung eines fremden Mandanten
loeschen, wenn er ihre Kennung kannte. getAllActiveConfigs() und die
Start-Nachverschluesselung bleiben bewusst uebergreifend, mit ausgeschriebener
Begruendung im Code (Befund B).
rls-access-inventory.spec.ts erkennt jetzt neben `this.prisma.<Modell>` auch
gebundene `<Name>.<Modell>`-Zugriffe (Befund F/G) und prueft eine neue
Stand-Spalte (gebunden/ungebunden/gemischt) im Klassifikationsdokument gegen
den Quelltext. Das macht zwei bisher unsichtbare, weil schon laenger
gebundene Fundstellen sichtbar (auth.service.ts/passwordResetToken,
ldap.service.ts/groupMembership) und deckt auf, dass
(ldap-config.service.ts, ldapConfig) tatsaechlich "beides" ist, nicht
"muss-mandantengebunden" (Befund B).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
Aufgabe 1 der Etappe 2: erweitert das Wegwerf-Werkzeug rls-scratch-check.mjs
um fuenf Messungen des forTenant()-Musters gegen die echte, aus der
ausgelieferten Migration geschnittene LdapConfig/LdapFieldMapping-Policy
unter einer Rolle ohne BYPASSRLS. Belegt insbesondere, dass ein ungebundener
Zugriff nach dem Scharfschalten 0 Zeilen liefert, nicht alle -- die
Fehlerrichtung dreht sich um. Die neue Kritikschrift
docs/mandantentrennung-etappe2-fehlerrichtung.md haelt das schriftlich fest,
mit Signaltabelle je Pfad und den vier Stellen, die Leere als Abwesenheit
deuten. Kein Dienstcode angefasst.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
WINDOWS #18/#20, Aufgabe 3: docs/mandantentrennung-zugriffsklassifikation.md
haelt fuer jede der 227 this.prisma.*-Fundstellen (32 Dateien, zusammengefasst
zu 59 Datei-Modell-Paaren) eine Klasse fest — muss-mandantengebunden (31),
keine-mandantengebundene-tabelle (16), bewusst-uebergreifend (3, mit
ausgeschriebenem Grund) oder beides (9, der Hintergrunddienst-Sonderfall:
uebergreifend lesen, je Zeile mandantengebunden schreiben — betrifft
ldap.service.ts, tender-digest.scheduler.ts, tender-matching.service.ts).
rls-access-inventory.spec.ts ermittelt die Fundstellen bei jedem Testlauf neu
aus dem Quelltext und vergleicht sie gegen die Tabelle im Dokument — Datei und
Modellname als Schluessel, keine Zeilennummer. Scheitert nachweislich, sobald
eine Fundstelle fehlt oder ein Eintrag verwaist (per Testlauf geprueft, danach
zurueckgesetzt).
Zwei belegte Befunde im Dokument festgehalten: req.tenantPrisma wird gesetzt,
aber nirgends gelesen; WINDOWS #19 (nullbares tenantId bei SearchProvider/
TenderRssFeedSource) bleibt benannter Blocker fuer Etappe 3.
docs/mandantentrennung-datenbankrolle.md verweist jetzt auf das neue
Dokument und korrigiert die ueberholte Zahl 182 auf den nachgemessenen Stand
(227/59). WINDOWS.md #18/#19 um Nachtrag auf diesen Plan ergaenzt; #20 (der
in Aufgabe 1 gemessene und behobene forTenant()-Verbindungsdefekt) als
"fixed" markiert.
Deviation (Rule 3, blockierend fuer die Bestandsaufnahme-Pruefung):
auth.service.ts-Kommentar umformuliert, der zuvor woertlich
"this.prisma.user.findUnique" als erklaerenden Text enthielt und dadurch
einen Eigentreffer der grep-basierten Inventur-Pruefung erzeugte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
- apps/api/scripts/rls-preflight.mjs misst fuenf benannte Eigenschaften
(rollenrechte, kontext-setzbar, ohne-kontext-leer, mit-kontext-sichtbar,
schreibrechte) jeweils in einer eigenen Transaktion gegen eine per
TESSERA_PREFLIGHT_DATABASE_URL angegebene Verbindung; --print-plan
verbindet nicht, das Werkzeug schreibt in keiner Betriebsart
- 5 Tests in rls-preflight.spec.ts (rot vor dem Werkzeug, jetzt gruen)
- docs/mandantentrennung-datenbankrolle.md: Befund, Sperrgrund (182
unskalierte Zugriffe, Anmeldeweg), Handgriffe des Betreibers samt
Kennwortsetzung, Freigabebedingung und Rueckweg; docs/README.md verweist
darauf
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
- docker-compose.yml und docker-compose.prod.yml mounten /app/user-files
im Dienst api auf ein neues benanntes Volume user-files (Eigentuemerschaft
uid 1001 folgt aus dem Image, kein Bind-Mount)
- docker-compose.dev.yml bleibt unveraendert (Compose fuehrt Mount-Listen
ueber das Ziel zusammen)
- Betriebshandbuch Kapitel 6: Speicher als dauerhaft beschrieben, Mount-Zeile
woertlich zum Kopieren, Hinweis dass /opt/tessera/docker-compose.yml auf
dem Server separat gepflegt werden muss (Deploy erreicht sie nicht)
- Betriebshandbuch Kapitel 7: Fehlerzeile zu verschwundenen Avataren/Exporten
an den reparierten Repository-Stand angepasst
WINDOWS #17 bleibt offen, bis die Serverdatei manuell ergaenzt ist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
Bisher gab es fuer Kollegen keine Dokumentation: im Projekt lagen nur das
CI/CD-Runbook und die Arbeitsanweisungen fuer die Entwicklung. Diese Luecke
schliessen vier Anleitungen plus eine Einstiegsseite unter docs/.
Alle vier wurden gegen den Quelltext geschrieben, nicht aus der Planung
abgeleitet, und anschliessend unabhaengig gegengeprueft: jede zitierte
Beschriftung ist woertlich aus de.json belegt, jede beschriebene Funktion im
Code nachgewiesen, alle Befehle und Pfade gegen die echten Compose-Dateien,
Dockerfiles und package.json-Skripte verifiziert. Die Gegenpruefung fand keine
falsche Aussage.
Die Einstiegsseite hebt die drei Punkte hervor, die in der Praxis am meisten
Zeit gekostet haben: Anmeldung ueber den Benutzernamen statt der E-Mail,
der Unterschied zwischen aktiviert und freigegeben, und dass ein blosses
'up -d' die laufenden Container nicht ersetzt.
Nebenbefund beim Schreiben des Betriebshandbuchs, als #17 im Ledger erfasst:
user-files/ ist in keiner Compose-Datei als Volume eingebunden — hochgeladene
Profilbilder und DKV-Exporte ueberleben kein --force-recreate. Noch ohne
Schaden, da bisher kein Nutzer ein Profilbild hinterlegt hat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU