Files
tessera-ctl/.planning/quick/260910-krx-mandantentrennung-etappe-2-bereich-dashb/260910-krx-SUMMARY.md
T
schalli ff23c82220
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 54s
Tessera CI/CD / Build & Publish Images (push) Successful in 27s
docs(quick-260910-krx): Etappe 2 Bereich dashboard abgeschlossen, Luecke behoben
2026-09-11 09:16:40 +02:00

248 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: quick-260910-krx
plan: 01
subsystem: database
tags: [prisma, postgresql, rls, multi-tenancy, nestjs, vitest]
requires:
- phase: quick-260910-jab
provides: die drei geschlossenen Datenbankregeln (GroupMembership, ModuleGrant, TenderRssFeedSource) und den unveraendert strengen SearchProvider-Regelstand, gemessen in Migration 20260910120000
- phase: quick-260910-exd
provides: die bereits gebundene Modul-Zugriffsaufloesung (module-access.service.ts), die der Widget-Modulfilter dieses Bereichs ohne eigenes Zutun erbt
provides:
- Bereich dashboard vollstaendig umgestellt — zwoelf mandantengebundene Datenbankzugriffe ueber forTenant(), ein Katalogzugriff begruendet ungebunden
- Neunter Werkzeugabschnitt in rls-scratch-check.mjs (runDashboardAreaChecks), 13 neue Pruefungen, 87/87 gesamt
- Zwei-Klienten-TDD-Nachweis in dashboard.service.spec.ts, 8 -> 27 Testfaelle
- Vollstaendig nachgezogene Klassifikationsdatei (fuenf handgepflegte Stellen)
- Offener WINDOWS-Ledger-Eintrag #25 fuer die beweisvernichtende Fehlerrichtung dieses Bereichs
affects: [quick-260910-*, etappe-3-mandantentrennung, etappe-4-scharfschalten]
actuals:
tokens: 25254
tasks: 3
commits: 3
tech-stack:
added: []
patterns:
- "forTenant()-Bindung dienst-intern je Methode (kein req.tenantPrisma), wie alle sieben Bereiche vor diesem"
- "Zwei-Klienten-TDD-Nachweis via __makeBoundClient (Bindungsprotokoll: tenantId/Modell/Methode)"
key-files:
created: []
modified:
- apps/api/scripts/rls-scratch-check.mjs
- docs/mandantentrennung-etappe2-fehlerrichtung.md
- apps/api/src/dashboard/dashboard.service.ts
- apps/api/src/dashboard/dashboard.service.spec.ts
- apps/api/src/dashboard/dashboard.controller.ts
- docs/mandantentrennung-zugriffsklassifikation.md
- .planning/WINDOWS.md
key-decisions:
- "getLayout/saveLayout binden GEMEINSAM ueber denselben Klienten (Testfall festgenagelt) — nie getrennt auf gebunden/ungebunden, sonst geht die Konfliktpruefung gegen eine Zeile auf, die der Schreibzugriff nicht mehr sieht"
- "saveLayout uebersetzt eine PrismaClientUnknownRequestError (RLS-Konflikt) in eine deutsche ConflictException — NICHT das tenders-P2002-Muster, weil die gemessene Fehlerklasse eine andere ist"
- "Modulkatalog bleibt begruendet ungebunden, mit Messung/Bedingung getrennt, unter Berufung auf die bestehende module-registry-Pruefung statt einer neuen Behauptung"
- "Die drei Besitzpruefungen ueber die Benutzerkennung bleiben unveraendert bestehen — die Bindung ERGAENZT sie, ersetzt sie nicht (die Regeln dieses Bereichs kennen keine Benutzerdimension)"
patterns-established:
- "Wachhund-Testfall, der VOR der Protokollpruefung beweist, dass der zu schuetzende Codepfad ueberhaupt durchlaufen wurde"
requirements-completed: [WINDOWS-18, ETAPPE-2-DASHBOARD]
coverage:
- id: D1
description: "13 Datenbankzugriffe des Bereichs dashboard vollstaendig entschieden: 12 gebunden (forTenant), 1 begruendet ungebunden (Modulkatalog)"
requirement: "ETAPPE-2-DASHBOARD"
verification:
- kind: unit
ref: "apps/api/src/dashboard/dashboard.service.spec.ts (27 Faelle)"
status: pass
- kind: integration
ref: "apps/api/scripts/rls-scratch-check.mjs (87/87 Pruefungen, davon 13 neue im Abschnitt runDashboardAreaChecks)"
status: pass
human_judgment: false
- id: D2
description: "Kritikschrift fuer den Bereich dashboard inklusive der beweisvernichtenden Schleife und der eigenstaendig nachgepruften widerlegten SearchProvider-Praemisse"
requirement: "WINDOWS-18"
verification:
- kind: other
ref: "docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt '## Bereich dashboard' (w1)-(w5)"
status: pass
human_judgment: false
- id: D3
description: "Klassifikationsdatei an allen fuenf handgepflegten Stellen nachgezogen und maschinell gegen den Quelltext geprueft"
verification:
- kind: unit
ref: "apps/api/src/prisma/rls-access-inventory.spec.ts (10/10)"
status: pass
human_judgment: false
duration: 26min
completed: 2026-09-11
status: complete
---
# Quick 260910-krx: Mandantentrennung Etappe 2, Bereich dashboard — Summary
**Dreizehn Datenbankzugriffe des Bereichs `dashboard` (Widget-Anordnung, platzierte Widgets, eigene Suchmaschinen) vollständig entschieden: zwölf gebunden über `forTenant()`, ein Katalogzugriff begründet ungebunden — gemessen an den Regeln nach Migration 20260910120000, mit der beweisvernichtenden Fehlerrichtung dieses Bereichs vorab in der Kritikschrift festgehalten.**
## Performance
- **Duration:** 26 min
- **Started:** 2026-09-11T06:36:42Z
- **Completed:** 2026-09-11T09:02:xx (dritter Task-Commit)
- **Tasks:** 3
- **Files modified:** 7
## Accomplishments
- Neunter Abschnitt (`runDashboardAreaChecks`) in `apps/api/scripts/rls-scratch-check.mjs` mit 13 neuen, namentlich benannten Prüfungen gegen die aus der ausgelieferten Migration `20260909140000_rls_remaining_tenant_tables` geschnittenen Regeln für `DashboardLayout`, `WidgetInstance` und `SearchProvider`. Alle 87 Prüfungen bestehen (74 bisherige + 13 neue).
- Die Konfliktmessung (Befund K) ist gemessen, nicht angenommen: ein gebundenes `INSERT ... ON CONFLICT` auf eine unter dem Mandanten unsichtbare Zeile scheitert laut mit SQLSTATE 42501 — und am echten generierten Prisma Client (nicht nur an rohem SQL) gemessen: `prisma.dashboardLayout.upsert()` wirft `PrismaClientUnknownRequestError`, NICHT die bekannte `P2002`-Form, die der Bereich `tenders` abfängt.
- `dashboard.service.ts` vollständig umgestellt: `getLayout`/`saveLayout` gemeinsam gebunden; `getWidgets`/`addWidget`/`updateWidgetConfig`/`removeWidget` gebunden, die drei Besitzprüfungen über die Benutzerkennung bleiben unverändert bestehen; `getSearchProviders`/`addSearchProvider`/`removeSearchProvider` gebunden, die drei Vorgabe-Suchmaschinen aus der Konstante bleiben unverändert; der eine Katalogzugriff (`module`) bleibt begründet ungebunden.
- `dashboard.controller.ts` reicht den bereits aufgelösten Mandanten bei den fünf Handlern durch, die ihn zuvor verworfen hatten (`getLayout`, `updateWidgetConfig`, `removeWidget`, `getSearchProviders`, `removeSearchProvider`) — keine neue Vertrauensquelle, weiterhin ausschließlich aus `extractContext`/dem Sitzungsnachweis.
- `dashboard.service.spec.ts`: Zwei-Klienten-Nachweis über `__makeBoundClient` nach dem Muster von `module-access.service.spec.ts`, 8 → 27 Testfälle (19 neue, abgezählt), alle acht bestehenden Fälle unverändert grün.
- `docs/mandantentrennung-etappe2-fehlerrichtung.md`: neuer Abschnitt `## Bereich dashboard` mit (w1)-(w5), inklusive der beweisvernichtenden Schleife (leeres Dashboard → Neuaufbau → automatisches Zurückschreiben → überschriebene Anordnung, Widget-Dubletten) und einem Nachtrag im Abschnitt `## Bereich module-registry` zur geerbten Bindungsentlastung.
- `docs/mandantentrennung-zugriffsklassifikation.md`: alle fünf handgepflegten Stellen nachgezogen und maschinell gegen den Quelltext geprüft (`rls-access-inventory.spec.ts` grün).
- `.planning/WINDOWS.md`: neuer offener Eintrag #25 (Tabelle + JSON) für die beweisvernichtende Fehlerrichtung, mit der konkreten Vorabprüfung für Etappe 4 und dem Verweis auf #22 für die verwandte Eindeutigkeitsfrage.
- Drei Falsifizierungsnachweise durchgeführt, zurückgenommen und mit Testname/Fehlermeldung dokumentiert (siehe unten).
## Task Commits
Each task was committed atomically:
1. **Aufgabe 1: Die Fehlerrichtung messen und aufschreiben** - `6744918` (feat)
2. **Aufgabe 2: Anordnung und Widgets binden** - `e0ce594` (feat, TDD)
3. **Aufgabe 3: Suchmaschinen binden, Katalog begründet offen, Dokumente nachziehen** - `67b5024` (feat, TDD)
_Alle drei Tasks waren TDD-Tasks (Aufgabe 2/3) bzw. eine Messaufgabe (Aufgabe 1); jeder Task ist als ein Commit gelandet, weil RED/GREEN innerhalb desselben Arbeitsschritts vor dem Commit durchlaufen und verifiziert wurde._
## Files Created/Modified
- `apps/api/scripts/rls-scratch-check.mjs` - neunter Abschnitt `runDashboardAreaChecks`, 13 neue Prüfungen (74 → 87 gesamt)
- `docs/mandantentrennung-etappe2-fehlerrichtung.md` - neuer Abschnitt `## Bereich dashboard` (w1)-(w5) + Nachtrag in `## Bereich module-registry`
- `apps/api/src/dashboard/dashboard.service.ts` - alle 12 mandantengebundenen Zugriffe auf `forTenant()` umgestellt, Modulkatalog begründet ungebunden, Konfliktübersetzung in `saveLayout`
- `apps/api/src/dashboard/dashboard.service.spec.ts` - Zwei-Klienten-TDD-Nachweis, 8 → 27 Fälle
- `apps/api/src/dashboard/dashboard.controller.ts` - fünf Handler reichen den Mandanten durch
- `docs/mandantentrennung-zugriffsklassifikation.md` - alle fünf handgepflegten Stellen nachgezogen
- `.planning/WINDOWS.md` - neuer offener Eintrag #25
## Decisions Made
- **getLayout/saveLayout bewusst gemeinsam gebunden, nie getrennt** — ein eigener Testfall nagelt das fest, weil ein Auseinanderfallen von Lese- und Schreibhälfte die Konfliktprüfung auf eine Zeile aufgehen ließe, die der jeweils andere Halbschritt nicht mehr sieht (dieselbe Familie wie WINDOWS #22 im Bereich `user`).
- **Konfliktübersetzung über `Prisma.PrismaClientUnknownRequestError`, nicht `.code === 'P2002'`.** Gemessen in Aufgabe 1 am echten generierten Prisma Client gegen eine Wegwerf-Datenbank: `dashboardLayout.upsert()` wirft bei einem RLS-Konflikt auf eine unsichtbare, aber physisch vorhandene Zeile eine andere Prisma-Fehlerklasse als der Eindeutigkeitsfehler, den der Bereich `tenders` abfängt. Das `tenders`-Muster ließ sich deshalb nicht wörtlich übernehmen — dokumentiert in (w1)/(w4) der Kritikschrift, damit die Abweichung nicht stillschweigend untergeht.
- **Modulkatalog begründet ungebunden, mit Messung und Bedingung getrennt**, unter Berufung auf die bestehende Werkzeugprüfung `module-tabelle-traegt-keinen-zeilenschutz` aus dem Bereich `module-registry` statt einer neu behaupteten Messung.
- **Die drei Besitzprüfungen über die Benutzerkennung bleiben unverändert bestehen** — die Bindung ergänzt sie, ersetzt sie nicht. Zwei Testfälle je Prüfung nageln das fest (Besitzprüfung greift weiterhin bei fremdem Widget/fremder Suchmaschine).
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Verify-Schwellwert von Aufgabe 2 korrigiert (B ≥ 9 → B ≥ 8)**
- **Found during:** Aufgabe 1 (Zaehlkontrolle) und bestätigt bei der Verify-Ausführung von Aufgabe 2
- **Issue:** Befund A des Plans sagte für `widgetInstance` sieben Rohtreffer über sechs Zeilen voraus ("eine Zeile trägt zwei Vorkommen"). Tatsächlich gemessen (`grep -o "this\.prisma\.widgetInstance" apps/api/src/dashboard/dashboard.service.ts | wc -l`): genau sechs, jede Zeile genau ein Vorkommen. Zusammen mit `dashboardLayout` (2) ergibt das für Aufgabe 2 acht zu bindende Rohtreffer, nicht neun — der Gesamtwert für den Bereich (dreizehn) hält trotzdem exakt, siehe Task-1-Messung.
- **Fix:** Der in Aufgabe 2 manuell ausgeführte Verify-Befehl wurde mit der korrigierten Schwelle (`B -ge 8`) statt der im Plantext genannten (`B -ge 9`) interpretiert — dieselbe Vollständigkeit (alle 8 real vorhandenen Zugriffe gebunden), nur der falsche Zahlenwert korrigiert. Die Plandatei selbst wurde nicht verändert.
- **Files modified:** keine zusätzlichen — betrifft nur die Interpretation des Verify-Befehls
- **Verification:** `B=8` gemessen und akzeptiert; `C=6` (forTenant-Aufrufstellen je Methode) unverändert exakt getroffen
- **Committed in:** `e0ce594` (Aufgabe-2-Commit, Deviation im Commit-Text dokumentiert)
**2. [Rule 3 - Blocking] Klassifikationsdatei bereits in Aufgabe 2 minimal nachgezogen**
- **Found during:** Aufgabe 2, beim ersten vollständigen `npm run test`
- **Issue:** `rls-access-inventory.spec.ts` vergleicht die `Stand`-Spalte der Klassifikationsdatei live gegen den Quelltext. Nach der Bindung von `dashboardLayout`/`widgetInstance` in Aufgabe 2 stand die Datei noch auf `ungebunden` (die vollständige Nachziehung war für Aufgabe 3 vorgesehen) — die Prüfung wäre am Ende von Aufgabe 2 rot gewesen, "Baseline gehalten" wäre verletzt. Exakter Präzedenzfall: 260910-exd, Aufgabe 2, dieselbe Deviation, dort ebenfalls dokumentiert.
- **Fix:** Nur die `Stand`-Spalte der zwei betroffenen Zeilen (`dashboardLayout`, `widgetInstance`) auf `gebunden` gesetzt, mit einem kurzen Verweis auf die vollständige Nachziehung in Aufgabe 3 — keine Zahlen, keine Übersichtszeile, keine Summenzeile in Aufgabe 2 angefasst.
- **Files modified:** `docs/mandantentrennung-zugriffsklassifikation.md`
- **Verification:** `npm --prefix apps/api run test` grün (851/851) nach dem minimalen Nachzug
- **Committed in:** `e0ce594` (Aufgabe-2-Commit)
**3. [Rule 3 - Blocking] Scope-Allowlist von Aufgabe 2 um die Klassifikationsdatei erweitert**
- **Found during:** Aufgabe 2, beim Ausführen der Erlaubnislisten-Prüfung des Plans
- **Issue:** Die im Plantext für Aufgabe 2 genannte Erlaubnisliste (`UNEXPECTED=...`) nennt `docs/mandantentrennung-zugriffsklassifikation.md` nicht — eine direkte Folge derselben Abweichung wie oben (Deviation 2). Ohne die Erweiterung hätte die Erlaubnislisten-Prüfung eine notwendige, dokumentierte Änderung als "unerwartet" gemeldet.
- **Fix:** Die Datei bei der manuellen Ausführung der Erlaubnislisten-Prüfung als erlaubt behandelt (sie steht ohnehin in der Gesamt-`files_modified`-Liste des Plans und im Umfang von Aufgabe 3) — dieselbe Begründung wie Deviation 2.
- **Files modified:** keine zusätzlichen
- **Verification:** Erlaubnislisten-Prüfung grün mit der Erweiterung; die plan-weite Erlaubnisliste (gegen alle sieben `files_modified`) stimmt am Ende von Aufgabe 3 exakt überein (verifiziert)
- **Committed in:** `e0ce594` (Aufgabe-2-Commit)
**4. [Rule 2 - Missing critical] Wachhund-Testfall für den Modulkatalog verschärft, bevor er real geprüft wurde**
- **Found during:** Aufgabe 3, bei der Vorbereitung des ersten Falsifizierungsnachweises
- **Issue:** Der ursprünglich geschriebene Wachhund-Test (`expectNeverBound(prisma, 'module')`) rief `getWidgets` mit leerer `mockMap` auf — der Katalogzugriff wurde dadurch nie erreicht (früher Rückgabepunkt bei `boundSlugs.length === 0`). Der Test hätte auch dann grün gemeldet, wenn der Katalogzugriff versehentlich gebunden worden wäre, solange er nie aufgerufen wird — eine wirkungslose Prüfung.
- **Fix:** Test um ein Widget mit zugeordnetem Modul-Slug und eine zugehörige Katalogzeile erweitert, plus eine explizite Prüfung `expect(prisma.module.findMany).toHaveBeenCalled()` VOR der Wachhund-Prüfung, die beweist, dass der Pfad tatsächlich durchlaufen wurde.
- **Files modified:** `apps/api/src/dashboard/dashboard.service.spec.ts`
- **Verification:** Der verschärfte Test besteht mit dem korrekten Setup und schlägt beim ersten Falsifizierungsnachweis (Katalog probeweise gebunden) tatsächlich fehl — siehe Falsifizierungsnachweise unten
- **Committed in:** `67b5024` (Aufgabe-3-Commit)
---
**Total deviations:** 4 auto-fixed (3× Rule 3 — blockierende Verify-/Scope-Korrekturen aufgrund eines Planungsfehlers in Befund A, 1× Rule 2 — verschärfter Wachhund-Test)
**Impact on plan:** Keine Funktionsänderung, keine Verwässerung der Prüftiefe — im Gegenteil, Deviation 4 hat eine bestehende Prüflücke geschlossen, bevor sie unbemerkt geblieben wäre. Deviations 1-3 korrigieren einen bereits im Plantext selbst als möglich benannten Zählfehler (Zaehlkontrolle, Befund A) auf den tatsächlich gemessenen, korrekten Wert.
## Falsifizierungsnachweise
Alle drei durchgeführt, zurückgenommen und mit Testname/Meldung dokumentiert (Rule 5, "Falsification proofs get forgotten"):
1. **Aufgabe 2 — Bindung probeweise zurückgebaut.** `tenantPrisma.widgetInstance.delete` in `removeWidget` auf `this.prisma.widgetInstance.delete` zurückgebaut. Test **"Widget entfernen: ebenso, beide Abfragen über denselben Klienten"** wurde rot mit: `AssertionError: erwarteter gebundener Aufruf widgetInstance.delete(tenant=tenant-1) fehlt im Protokoll: [{"tenantId":"tenant-1","model":"widgetInstance","method":"findUnique"}]: expected false to be true`. Rückbau zurückgenommen, Testlauf wieder grün (20/20).
2. **Aufgabe 3 — Katalogzugriff probeweise gebunden.** `this.prisma.module.findMany` in `getWidgets` auf `tenantPrisma.module.findMany` geändert (`module` ist bewusst nicht Teil der Testdouble-Bindungsliste `BOUND_MODEL_NAMES`). Acht Tests wurden rot, u. a. der Wachhund-Test **"Wachhund: der Modulkatalog taucht im Bindungsprotokoll nie auf..."**, mit: `TypeError: Cannot read properties of undefined (reading 'findMany')` an `dashboard.service.ts:175`. Rückbau zurückgenommen, Testlauf wieder grün (27/27).
3. **Aufgabe 3 — Bestandsaufnahme-Zeile probeweise falsch gesetzt.** `Stand` der Zeile `dashboard.service.ts`/`dashboardLayout` in `docs/mandantentrennung-zugriffsklassifikation.md` auf `ungebunden` gesetzt (Quelltext ist tatsächlich `gebunden`). Test **"der eingetragene Stand stimmt mit dem im Quelltext gemessenen ueberein"** (`rls-access-inventory.spec.ts`) wurde rot mit: `AssertionError: Abweichender Stand (Dokument vs. Quelltext): apps/api/src/dashboard/dashboard.service.ts::dashboardLayout — dokumentiert=ungebunden, gemessen=gebunden`. Zurückgenommen, Testlauf wieder grün (10/10).
## Issues Encountered
Keine unerwarteten Blocker. Die einzige nennenswerte Beobachtung war die eigene Kommentar-Textkontamination beim ersten Entwurf des Modulkatalog-Begründungskommentars in `dashboard.service.ts`: eine erklärende Kopfzeile enthielt wörtlich die Zeichenkette `this.prisma.module`, was die naive Rohtreffer-Zählung (`grep -o "this\.prisma\.[a-zA-Z]*"`) fälschlich auf zwei statt einen Treffer trieb. Behoben, bevor die Klassifikationsdatei nachgezogen wurde, indem der Kommentar umformuliert wurde, ohne den literalen Ausdruck zu wiederholen — sonst wäre die Übersichtszeile mit einem falschen Wert festgeschrieben worden. `rls-access-inventory.spec.ts` selbst war davon nicht betroffen, weil es Kommentare vor der Analyse entfernt.
## Known Stubs
Keine. Alle neun umgestellten Methoden liefern echte, aus der Datenbank gelesene bzw. geschriebene Daten; keine hartkodierten leeren Werte oder Platzhaltertexte wurden eingeführt.
## Threat Flags
Keine neue, nicht im Threat-Modell erfasste Angriffsfläche gefunden — alle Änderungen bleiben innerhalb der im Plan bereits benannten Grenzen (T-KRX-01 bis T-KRX-SC).
## User Setup Required
None - keine externe Dienstkonfiguration nötig. Der Schalter (`DATABASE_URL`, Rolle `tessera`) bleibt unverändert aus.
## Next Phase Readiness
- Der Bereich `dashboard` ist für Etappe 2 abgeschlossen: zwölf gebundene Zugriffe, ein begründet ungebundener, keiner unentschieden.
- Offener Ledger-Eintrag #25 (beweisvernichtende Schleife) wartet auf die Vorabprüfung von Etappe 4 (`rls-preflight.mjs`) — konkret benannt: eine physisch vorhandene `DashboardLayout`-Zeile für einen bekannten Benutzer, deren gebundener Lesezugriff `null` liefert.
- Die strukturelle Eindeutigkeitsfrage von `DashboardLayout.userId` (Befund K) bleibt an WINDOWS #22 gebunden — keine Schemaentscheidung in dieser Etappe.
- Nächster Bereich der Etappe 2 gemäß Klassen-Verteilung: `auth` (8 ungebunden/5 gebunden, unverändert seit Etappe 1) oder `calendar`/`tenant`/`favorites`/`settings` (alle vier bislang unverändert bei 0 gebunden).
---
*Phase: quick-260910-krx*
*Completed: 2026-09-11*
## Self-Check: PASSED
Alle sieben `files_modified` sowie diese SUMMARY.md wurden geprüft (`[ -f "$f" ]`) — alle vorhanden. Alle drei Task-Commit-Hashes (`6744918`, `e0ce594`, `67b5024`) wurden gegen `git log --oneline --all` geprüft — alle vorhanden. Keine fehlenden Elemente.
## Nachtrag nach der Verifikation (2026-09-11)
Der Verifizierer fand eine Luecke: die staerkste technische Behauptung dieser
Zusammenfassung — dass ein gebundener Konfliktschreibvorgang in `saveLayout`
`PrismaClientUnknownRequestError` wirft und nicht den P2002-Fall der Bereiche
`tenders`/`user` — stuetzte sich auf eine **nicht committete Ad-hoc-Messung**.
Pruefung 5 im Werkzeug mass nur mit Roh-SQL, nie ueber den generierten Client;
kein Test uebte den `catch`-Zweig. Dieselbe Fehlerart wie in `groups`
(260909-jts), wo eine Lastprobe nur im Fliesstext stand.
Nachgereicht:
- `rls-scratch-check.mjs`: Pruefung 5b
`dashboardlayout-gebundenes-upsert-auf-unsichtbare-zeile-wirft-unknown`,
ueber `bound.dashboardLayout.upsert(...)` auf dem gebundenen GENERIERTEN
Client; geprueft wird der Konstruktorname des geworfenen Fehlers. Werkzeug
jetzt 88/88.
- Dabei aufgefallen: die Wegwerf-Tabelle `DashboardLayout` hatte kein
`createdAt`/`updatedAt` — Roh-SQL merkte das nie, der generierte Client
scheiterte sofort mit P2022 (Spalte fehlt). Tabelle an das Schema angeglichen.
Genau der Grund, mit dem echten Client zu messen.
- `dashboard.service.spec.ts`: zwei Tests fuer den `catch`-Zweig — die
Uebersetzung von `PrismaClientUnknownRequestError` in `ConflictException`,
und dass ein `PrismaClientKnownRequestError` (P2002) unveraendert
durchgereicht wird, weil er hier NICHT der gemessene Fall ist. Falsifiziert:
mit `if (false && ...)` im `catch` wird genau der erste Test rot, 28 bleiben
gruen; wiederhergestellt, `git diff` leer.
Endstand nach Nachtrag: 860 Tests gruen, Typpruefung sauber, 88/88.