Die Arbeit liefert Rolle, Verbindungstrennung, Pruefwerkzeug und Policies fuer alle 16 offenen Tabellen, schaltet die Trennung aber bewusst NICHT scharf. Grund, gemessen statt vermutet: im Code stehen 182 Datenbankzugriffe ohne Mandantenkontext gegen 19 mit. Der Anmeldeweg ist zwingend darunter — er liest die Benutzerzeile, bevor der Mandant bekannt ist, weil der Mandant erst aus dieser Zeile kommt. Unter einer Rolle ohne Umgehungsrecht liefert diese Abfrage nichts, und niemand koennte sich mehr anmelden. Das Scharfschalten ist damit ein eigener Vorgang, kein Nebeneffekt dieser Arbeit. Neu im Ledger als #19: SearchProvider und TenderRssFeedSource haben ein nullable tenantId. Die einfache Policy vergleicht NULL nie gleich, wodurch die plattformweiten Zeilen nach dem Scharfschalten fuer JEDEN Mandanten verschwinden wuerden — nicht nur fuer fremde. Heute wirkungslos, beim Scharfschalten zwingend mitzuloesen. Die richtige Semantik ist eine Produktentscheidung, deshalb bewusst nicht eigenmaechtig anders geloest. 673/673 Tests gruen, Typpruefung sauber. #18 bleibt offen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
13 KiB
phase, plan, subsystem, tags, requires, provides, affects, actuals, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | actuals | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260909-dgj | 01 | database |
|
|
|
|
|
|
|
|
|
|
|
15min | 2026-09-09 | complete |
Quick Task 260909-dgj: Mandantentrennung auf Datenbankebene — Rolle, Nachweis, Abdeckung (Umstellung selbst bleibt aus) Summary
Legt die Datenbankrolle tessera_app ohne RLS-Umgehungsrecht an, trennt Migrations- von Laufzeitverbindung, liefert ein Pruefwerkzeug fuer den Nachweis und deckt alle 20 Tabellen mit tenantId per Policy ab — schaltet die Verbindung selbst aber bewusst NICHT um, weil 182 gemessene unskalierte Prisma-Zugriffe (darunter der Anmeldeweg) das sofort verhindern wuerden.
Performance
- Duration: ~15 min
- Started: 2026-09-09T07:56:00Z
- Completed: 2026-09-09T08:06:00Z
- Tasks: 4/4
- Files modified: 14
Accomplishments
tessera_app-Rolle (Migration20260909130000_rls_app_role) wird wiederholbar angelegt bzw. aufNOSUPERUSER/NOBYPASSRLSkonvergiert, ohne Kennwort im SQL, mit lautem Abbruch samt Handlungsanweisung, fallscurrent_userdie Rechte dafuer fehlenapps/api/scripts/migrate-and-start.shtrennt den Migrationsschritt (TESSERA_MIGRATE_DATABASE_URL) vom Laufzeitschritt (DATABASE_URL); ohne gesetzte Variable bleibt der Containerstart identisch zum bisherigenCMD— verifiziert per Test, nicht behauptetapps/api/scripts/rls-preflight.mjsfuehrt fuenf transaktionssichere Nachweise (Rollenrechte, Kontext-Setzbarkeit, keine Zeilen ohne Kontext, Zeilen mit Kontext, vollstaendige Schreibrechte) gegen eine beliebige Verbindung aus, ohne etwas zu veraendern- Migration
20260909140000_rls_remaining_tenant_tablesergaenzt Policies fuer die 16 zuvor offenen Tabellen; ein Abdeckungstest liest Schema und Migrationen zur Laufzeit und faellt kuenftig bei jedem neuen Modell ohne bewusste Entscheidung docs/mandantentrennung-datenbankrolle.mdbenennt den Sperrgrund (182 unskalierte Zugriffe, Anmeldeweg als struktureller Grund) unmissverstaendlich, dazu die Handgriffe des Betreibers und den Rueckweg bei einer nicht mehr verbindenden API
Task Commits
Each task was committed atomically:
- Task 1: Anwendungsrolle ohne RLS-Umgehungsrecht anlegen (Migration) -
b3375ae(feat) - Task 2: Migrationsverbindung von der Laufzeitverbindung trennen -
a5f99e5(feat) - Task 3: Pruefwerkzeug fuer den Nachweis plus Betriebsanleitung mit Rueckweg -
44a90e5(feat) - Task 4: Policies fuer die 16 fehlenden Tabellen -
efaabc9(feat)
Alle vier Aufgaben folgten TDD: Testdatei zuerst geschrieben, rot bestaetigt, dann die Implementierung, dann gruen bestaetigt — jeweils im selben Commit (Test + Implementierung gehoerten inhaltlich zusammen, keine separate RED-Phase committet).
Files Created/Modified
apps/api/prisma/migrations/20260909130000_rls_app_role/migration.sql- legttessera_appan, konvergiert wiederholbarapps/api/prisma/migrations/20260909140000_rls_remaining_tenant_tables/migration.sql- 16 fehlende Policies, D-03-Korrektur im Kopfapps/api/scripts/migrate-and-start.sh- trennt Migrations-/Laufzeitverbindung, POSIX sh,--print-plan-Betriebsartapps/api/scripts/rls-preflight.mjs- fuenf transaktionssichere Nachweise,--print-plan-Betriebsartapps/api/src/prisma/rls-app-role.spec.ts/start-script.spec.ts/rls-preflight.spec.ts/rls-coverage.spec.ts- 22 Tests insgesamt, alle rot vor der jeweiligen Implementierungapps/api/Dockerfile- kopiertapps/api/scripts, CMD ruft das neue Skript auf statt der alten&&-Kettedocker-compose.yml/docker-compose.prod.yml- reichenTESSERA_MIGRATE_DATABASE_URLmit leerem Vorgabewert durch.env.example- erklaert beide Variablen, Umstellungszeile bleibt auskommentiertdocs/mandantentrennung-datenbankrolle.md- Befund, Vorbereitetes, Sperrgrund, Handgriffe, Vorher-Pruefung, Rueckwegdocs/README.md- verlinkt die neue Anleitung
Decisions Made
- Umstellung bleibt standardmaessig aus (siehe key-decisions oben) — WINDOWS #18 bleibt bewusst offen, nicht
fixed. - Die D-03-Korrektur zur pauschalen Tender-RLS-Aussage aus
20260804130918steht ausschliesslich im Kopf der neuen Migration20260909140000; die angewendete Datei blieb unangetastet (Prisma-Pruefsumme). SearchProviderundTenderRssFeedSource(beide mit nullabletenantId) bekamen dieselbe einfache Policy wie die uebrigen 14 Tabellen — siehe „Threat Flags" unten fuer die damit verbundene, noch offene Nebenwirkung.
Deviations from Plan
Keine Abweichung von Rule 1-4 noetig — der Plan war bereits sehr praezise vorgemessen (28/20/7/16-Zaehlung, 182-vs-19-Zugriffszaehlung, exakte Zeilennummern in docker-compose.yml) und stimmte beim Ausfuehren mit dem Codebestand ueberein.
Eine Test-Iteration war noetig: Der erste Entwurf der Rollen-Migration verwendete in Kopfkommentaren zweimal die Formulierung „BYPASSRLS-Rollen" bzw. „SUPERUSER/BYPASSRLS", was den eigenen Test 2 (kein bares BYPASSRLS ohne vorangestelltes NO) verletzte. Umformuliert auf „Rollen ohne NOBYPASSRLS" bzw. „SUPERUSER/NOBYPASSRLS" — inhaltlich identisch, textuell konform. Kein Rule-Fall, da innerhalb desselben ungetesteten ersten Entwurfs vor dem ersten gruenen Lauf behoben.
Total deviations: 0 (Rule 1-4) Impact on plan: Keine — Plan exakt wie geschrieben umgesetzt.
Issues Encountered
Keine blockierenden Probleme. Alle vier TDD-Zyklen liefen rot -> gruen ohne Iterationsbedarf jenseits der oben genannten Testfeinschliff-Korrektur.
User Setup Required
Keine sofortige Handlung noetig — die Umstellung ist bewusst nicht Teil dieses Vorgangs. Wenn die 182 unskalierten Zugriffe (siehe Sperrgrund in docs/mandantentrennung-datenbankrolle.md, Abschnitt 3) in einem eigenen, spaeteren Vorgang behandelt sind, folgen die vier Handgriffe aus Abschnitt 4 derselben Anleitung: Kennwort setzen, .env auf dem Server umstellen, Vorher-Pruefung ausfuehren, Container neu erstellen — ausdruecklich vom Betreiber, nicht automatisiert.
Known Stubs
Keine — alle vier Bausteine sind vollstaendig implementiert und getestet (keine leeren Rueckgabewerte, kein "coming soon").
Next Phase Readiness
Bereit fuer einen Folge-Vorgang, der die 182 unskalierten Prisma-Zugriffe behandelt (Anmeldeweg, AD-Abgleich, Ausschreibungs-Digest, Modulzugriffspruefung, Treffersuche, Admin-Erstanlage) — erst danach ist die Umstellung selbst (Kennwort setzen, .env umbiegen) sinnvoll und sicher. docs/mandantentrennung-datenbankrolle.md beschreibt den vollstaendigen Weg.
Blocker: keiner fuer diesen Vorgang selbst. Die Umstellung bleibt fuer den Produktivbetrieb blockiert, bis der Folge-Vorgang abgeschlossen ist — genau wie in Abschnitt 3 der neuen Anleitung dokumentiert.
WINDOWS #18 bleibt open (nicht fixed, nicht waived) — dieser Vorgang bereitet die Behebung vor, vollzieht sie aber nicht.
Threat Flags
| Flag | File | Description |
|---|---|---|
| threat_flag: nullable-tenantId-policy-gap | apps/api/prisma/migrations/20260909140000_rls_remaining_tenant_tables/migration.sql (SearchProvider, TenderRssFeedSource) | Beide Modelle haben tenantId String? (nullable) fuer plattformweite Zeilen (Vorgabe-Suchanbieter bzw. plattformweite RSS-Feeds mit userId = null). Die neue Policy "tenantId" = current_tenant_id() liefert fuer NULL-Zeilen laut PostgreSQL-Dreiwertlogik nie true — sobald die Rolle aktiv verbindet, wuerden diese plattformweiten Zeilen fuer JEDEN Mandanten unsichtbar, nicht nur fuer den falschen. Derzeit ohne Wirkung, da die Umstellung nicht aktiv ist (siehe Sperrgrund). Zu klaeren im Folge-Vorgang, der die Umstellung tatsaechlich vollzieht: entweder eine OR "tenantId" IS NULL-Klausel ergaenzen oder die plattformweiten Zeilen ueber einen anderen Mechanismus ausliefern. |
Self-Check: PASSED
- FOUND: apps/api/prisma/migrations/20260909130000_rls_app_role/migration.sql
- FOUND: apps/api/prisma/migrations/20260909140000_rls_remaining_tenant_tables/migration.sql
- FOUND: apps/api/scripts/migrate-and-start.sh
- FOUND: apps/api/scripts/rls-preflight.mjs
- FOUND: apps/api/src/prisma/rls-app-role.spec.ts
- FOUND: apps/api/src/prisma/start-script.spec.ts
- FOUND: apps/api/src/prisma/rls-preflight.spec.ts
- FOUND: apps/api/src/prisma/rls-coverage.spec.ts
- FOUND: docs/mandantentrennung-datenbankrolle.md
- FOUND commit
b3375ae,a5f99e5,44a90e5,efaabc9
Quick Task: 260909-dgj Completed: 2026-09-09