--- phase: quick-260911-cwh plan: 01 subsystem: database tags: [prisma, postgresql, row-level-security, multi-tenancy, nestjs, vitest, calendar] requires: - phase: quick-260910-krx provides: Bereich dashboard vollstaendig gebunden, Muster fuer generierten-Client-Messung (Pruefung 5b) provides: - "Bereich calendar (calendar.service.ts, Modell calendarSource) vollstaendig an forTenant() gebunden, alle zwoelf Datenbankzugriffe" - "Elfter Abschnitt runCalendarAreaChecks in rls-scratch-check.mjs mit 13 neuen Pruefungen (davon vier ueber den generierten Client)" - "calendar.service.spec.ts NEU angelegt — erste Testlage dieses Bereichs, 23 Testfaelle mit Zwei-Klienten-Nachweis" - "Kritikschrift Abschnitt '## Bereich calendar' mit gemessenem Cache-Schluessel-Urteil und Zugangsdaten-Erhaltungsbefund" - "WINDOWS #26: offener Ledger-Eintrag zur lautlosen Fehlerrichtung im Bereich calendar" affects: [etappe-2-mandantentrennung, calendar-modul, rls-scratch-check-tooling] actuals: tokens: 25378 tasks: 3 commits: 3 plan_head_before: 508d9e43015df192f5b86c39ac1e81c983f100b8 tech-stack: added: [] patterns: - "forTenant(this.prisma, tenantId) als lokale tenantPrisma-Konstante je Methode mit Datenbankzugriff (dienst-interner Weg, wie alle acht Bereiche vor calendar)" - "Zwei-Klienten-Testnachweis via __makeBoundClient(tenantId) — ungebundener Fake protokolliert nicht, gebundener schon" - "Generierter-Client-Messung an einer schemagleichen Wegwerf-Tabelle (Spaltenmenge zur Laufzeit gegen schema.prisma geprueft) statt Roh-SQL, fuer Fehlerklassen die die Anwendung tatsaechlich sieht" key-files: created: - apps/api/src/calendar/calendar.service.spec.ts modified: - apps/api/scripts/rls-scratch-check.mjs - docs/mandantentrennung-etappe2-fehlerrichtung.md - apps/api/src/calendar/calendar.service.ts - apps/api/src/calendar/calendar.controller.ts - docs/mandantentrennung-zugriffsklassifikation.md - .planning/WINDOWS.md key-decisions: - "Cache-Schluessel (userId:from:to) bleibt OHNE Mandantenanteil — die vierteilige Kette (schema.prisma User.id @default(uuid()) -> JwtStrategy.validate -> auth.service.ts sub:user.id -> extractContext) zeigt eine plattformweit eindeutige UUID, die Etappe-3-Entscheidung (1) betrifft User.username/User.email, nicht User.id" - "Keine neue Fehleruebersetzung fuer die drei Besitzpruefungen noetig: der gemessene Wettlauf-Fall wirft PrismaClientKnownRequestError/P2025 (nicht PrismaClientUnknownRequestError wie im Bereich dashboard), und dieser Pfad ist durch die vorgeschaltete findUnique-Besitzpruefung strukturell unerreichbar" - "refreshCacheInBackground wird NICHT als sechster Hintergrunddienst-Fall gefuehrt — sie iteriert nicht ueber Mandanten, sondern traegt den Mandanten der Anfrage, die sie angestossen hat (Befund B)" - "Antwortsemantik 403 (Forbidden bei fremdem Besitz) vs. 404 (NotFound bei unbekannter Kennung) bleibt unveraendert — waere eine API-Aenderung ausserhalb des Auftrags" requirements-completed: [WINDOWS-18, ETAPPE-2-CALENDAR] coverage: - id: D1 description: "Alle zwoelf Datenbankzugriffe von calendar.service.ts laufen gebunden ueber forTenant(), ein tenantPrisma-Klient je Methode mit Datenbankzugriff" requirement: ETAPPE-2-CALENDAR verification: - kind: unit ref: "apps/api/src/calendar/calendar.service.spec.ts — 23 Testfaelle" status: pass - kind: unit ref: "apps/api/src/prisma/rls-access-inventory.spec.ts — Bestandsaufnahme stimmt mit Quelltext ueberein" status: pass - kind: other ref: "node apps/api/scripts/rls-scratch-check.mjs — runCalendarAreaChecks, 13 neue Pruefungen" status: pass human_judgment: false - id: D2 description: "Kritikschrift misst die umgekehrte Fehlerrichtung an der ausgelieferten Regel (Migration 20260909140000, bestaetigt unveraendert durch 20260910120000) inklusive vier Pruefungen ueber den generierten Prisma-Client an einer schemagleichen Wegwerf-Tabelle" requirement: WINDOWS-18 verification: - kind: other ref: "node apps/api/scripts/rls-scratch-check.mjs — Pruefungen calendarsource-wegwerftabelle-deckt-alle-spalten-des-generierten-clients, calendarsource-generierter-client-*" status: pass human_judgment: false - id: D3 description: "Fuenf handgepflegte Dokumentstellen der Klassifikation nachgezogen und maschinell gegen den Quelltext gegatet; WINDOWS-Ledger-Eintrag #26 in Tabelle, JSON-Block und Kopfzaehlern konsistent" verification: - kind: unit ref: "apps/api/src/prisma/rls-access-inventory.spec.ts" status: pass - kind: other ref: "Plan-Verify-Gates Aufgabe 3 (Uebersichtszeile, Summenzeile, Klassen-Verteilung, Hintergrunddienst-Abschnitt, WINDOWS.md-Konsistenz) — manuell in der Ausfuehrung nachvollzogen" status: pass human_judgment: false duration: 21min completed: 2026-09-11 status: complete --- # Quick Task 260911-cwh: Etappe 2 Mandantentrennung, Bereich calendar — Summary **Alle zwoelf `calendarSource`-Datenbankzugriffe in `calendar.service.ts` auf `forTenant()` umgestellt (ein `tenantPrisma`-Klient je Methode), mit einer aus dem Nichts angelegten Testlage (23 Faelle), 13 neuen Wegwerf-Pruefungen — vier davon ueber den generierten Prisma-Client — und einem gemessenen, schriftlich festgehaltenen Urteil zum Cache-Schluessel sowie zur Zugangsdaten-Erhaltung.** ## Performance - **Duration:** 21 min - **Started:** 2026-09-11T07:39:07Z - **Completed:** 2026-09-11T08:00:00Z (ca.) - **Tasks:** 3 - **Files modified:** 7 (6 modified, 1 neu angelegt) ## Accomplishments - Neunter Bereich der Etappe 2 (Mandantentrennung) abgeschlossen: `calendar` — der einzige bisher umgestellte Bereich, der verschluesselte Zugangsdaten zu FREMDEN Servern (Exchange/CalDAV/ICS) haelt - `apps/api/scripts/rls-scratch-check.mjs`: elfter Abschnitt `runCalendarAreaChecks` mit 13 namentlich benannten Pruefungen, davon vier ueber den generierten Client an einer Wegwerf-Tabelle, deren 17-Spalten-Deckung zur Laufzeit gegen `schema.prisma` geprueft wird — alle 101 Pruefungen des Werkzeugs bestehen - `calendar.service.spec.ts`: NEU angelegt (dieser Bereich hatte zuvor KEINE Testdatei), 23 Testfaelle mit Zwei-Klienten-Nachweis, decken jeden Datenbankpfad, alle drei Besitzpruefungen je Ausnahmeart, die drei Zugangsdaten-Erhaltungsfaelle, beide Rueckschreibungen der Aggregationsschleife und den Wachhund fuer "genau ein Klient je Aufruf" ab - Kritikschrift (`docs/mandantentrennung-etappe2-fehlerrichtung.md`, Abschnitt `## Bereich calendar`) mit (k1)-(k5): die tatsaechlich gemessene Fehlerklasse des Wettlauf-Falls (`PrismaClientKnownRequestError`/P2025, ANDERS als im Bereich `dashboard`), das vierteilige Cache-Schluessel-Urteil, die Backend- UND Frontend-Leere-als-Abwesenheit-Kette samt Fehlerverschluckung - Fuenf handgepflegte Stellen der Klassifikation nachgezogen (Bestandsaufnahme-Zeile, Uebersichtszeile 0/12, Summenzeile 83/159, Klassen-Verteilung mit Stand-Vermerk, Hintergrunddienst-Abschnitt mit dem Sonderfall `refreshCacheInBackground`), WINDOWS-Ledger-Eintrag #26 angelegt ## Task Commits Each task was committed atomically: 1. **Aufgabe 1: Fehlerrichtung messen** - `bf5fc4d` (feat) 2. **Aufgabe 2: Testlage anlegen, alle zwoelf Zugriffe binden** - `77cb124` (feat) 3. **Aufgabe 3: Restliche Dokumentstellen, WINDOWS-Ledger, Falsifizierungsnachweise** - `e0e163e` (docs) _Kein TDD-Modus fuer diesen Plan — die Testlage wurde in Aufgabe 2 als Voraussetzung neu angelegt, nicht als RED/GREEN-Zyklus einzeln entwickelt._ ## Files Created/Modified - `apps/api/scripts/rls-scratch-check.mjs` - elfter Abschnitt `runCalendarAreaChecks` (13 neue Pruefungen), Helfer `readSchemaModelFieldNames()` - `docs/mandantentrennung-etappe2-fehlerrichtung.md` - Abschnitt `## Bereich calendar` mit (k1)-(k5) - `apps/api/src/calendar/calendar.service.spec.ts` - NEU, 23 Testfaelle, Zwei-Klienten-Nachweis - `apps/api/src/calendar/calendar.service.ts` - alle zwoelf Zugriffe gebunden, Klassenkommentar praezisiert, Cache-Schluessel-Urteil und Hintergrunddienst-Begruendung als Kommentare - `apps/api/src/calendar/calendar.controller.ts` - sechs kontextnutzende Handler reichen Benutzer- UND Mandantenkennung durch - `docs/mandantentrennung-zugriffsklassifikation.md` - fuenf handgepflegte Stellen nachgezogen - `.planning/WINDOWS.md` - neuer offener Eintrag #26 ## Decisions Made - Cache-Schluessel bleibt ohne Mandantenanteil — vierteilige Kette gemessen, Urteil in Code UND Kritikschrift verankert (siehe `key-decisions` oben) - Keine neue Fehleruebersetzung fuer die Besitzpruefungen: gemessener Wettlauf-Fall (P2025) ist ueber die vorgeschaltete Pruefung strukturell unerreichbar - `refreshCacheInBackground` bleibt ausserhalb des Hintergrunddienst-Abschnitts (kein sechster Fall) — sie traegt den Mandanten der Anfrage, iteriert nicht ueber mehrere Mandanten - Antwortsemantik 403/404 bewusst unveraendert gelassen ## Deviations from Plan Eine, dokumentarischer Art: Aufgabe 2 war im Plan als `tdd="true"` markiert, wurde aber nicht als RED/GREEN-Zyklus je Testfall entwickelt — die Testdatei entstand als Voraussetzung im Ganzen (siehe Hinweis oben unter der Testtabelle). Inhaltlich ohne Folge: die Falsifizierung durch Rueckbau ersetzt den RED-Nachweis. Dieser Abschnitt sagte zunaechst 'None' und widersprach damit dem eigenen Hinweis weiter oben; vom Verifizierer bemerkt, hier berichtigt. Alle Planungsbefunde (A-L) wurden zur Ausfuehrungszeit nachgeprueft und bestaetigt. ## Falsifizierungsnachweise (drei, alle durchgefuehrt und zurueckgenommen) 1. **Aufgabe 2 — halb gebundene Aggregationsschleife:** Die Bindung der Synchronstatus-Rueckschreibung im Erfolgspfad von `fetchAndCacheEvents` wurde probeweise zurueckgenommen (`tenantPrisma.calendarSource.update` → `this.prisma.calendarSource.update`). Ergebnis: der benannte Test `aggregateEvents, Erfolgspfad: das Laden der Quellen UND die Synchronstatus-Rueckschreibung bei Erfolg stehen gebunden unter derselben Mandantenkennung im Protokoll` ging rot mit der Meldung `erwarteter gebundener Aufruf calendarSource.update(tenant=t1) fehlt im Protokoll: [{"tenantId":"t1","model":"calendarSource","method":"findMany"}]: expected false to be true`. Bindung wiederhergestellt, Testlage danach wieder gruen (23/23). 2. **Aufgabe 3a — falscher Stand in der Bestandsaufnahme:** Die Zeile `calendar.service.ts`/`calendarSource` wurde probeweise auf `Stand: ungebunden` zurueckgesetzt. Ergebnis: `rls-access-inventory.spec.ts` ging rot mit `Abweichender Stand (Dokument vs. Quelltext): apps/api/src/calendar/calendar.service.ts::calendarSource — dokumentiert=ungebunden, gemessen=gebunden`. Zurueckgesetzt, Test danach wieder gruen (10/10). 3. **Aufgabe 3b — falsche Uebersichtszahl:** Die Uebersichtszeile `calendar` wurde probeweise auf `5 | 12` (statt der gemessenen `0 | 12`) gesetzt. Ergebnis: das herleitende Gate (`grep -qE "^\| calendar \| ${DU} \| ${DB} \| \*\*war 12/0\*\*"`) meldete `UEBERSICHTSZEILE calendar nennt nicht die neu gemessenen Zahlen 0/12 im etablierten Stil`. Zurueckgesetzt, Gate danach wieder erfuellt. ## Issues Encountered None. ## User Setup Required None - keine externe Diensteinrichtung erforderlich. ## Next Phase Readiness - Neunter von zwoelf Bereichen der Etappe 2 abgeschlossen — Klassen-Verteilung (63 Paare) unveraendert, nur die `Stand`-Spalte des `calendar`-Paares gewechselt - Der Schalter (`DATABASE_URL` → Rolle `tessera`, `BYPASSRLS`) bleibt AUS — keine Compose-/Umgebungsdatei angefasst, keine Schema-/Migrationsaenderung, nichts in Active Directory - Baseline am Ende gehalten: 883 Tests gruen (57 Dateien, davon 23 neu), Typpruefung sauber, `rls-scratch-check.mjs` meldet 101/101 Pruefungen bestanden - WINDOWS #26 bleibt bis nach dem Scharfschalten (Etappe 4) offen — an dieselbe Bedingung gebunden wie #18, Familie mit #23/#25 - Naechster Bereich der Etappe 2 (zehnter von zwoelf) ist aus der Klassen-Verteilung in `docs/mandantentrennung-zugriffsklassifikation.md` zu waehlen ## Self-Check: PASSED All created/modified files verified present on disk; all three task commit hashes (`bf5fc4d`, `77cb124`, `e0e163e`) verified present in git history. --- *Phase: quick-260911-cwh* *Completed: 2026-09-11*