Files
tessera-ctl/.planning/quick/260910-krx-mandantentrennung-etappe-2-bereich-dashb/260910-krx-SUMMARY.md
T

19 KiB
Raw Blame History

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-260910-krx 01 database
prisma
postgresql
rls
multi-tenancy
nestjs
vitest
phase provides
quick-260910-jab die drei geschlossenen Datenbankregeln (GroupMembership, ModuleGrant, TenderRssFeedSource) und den unveraendert strengen SearchProvider-Regelstand, gemessen in Migration 20260910120000
phase provides
quick-260910-exd die bereits gebundene Modul-Zugriffsaufloesung (module-access.service.ts), die der Widget-Modulfilter dieses Bereichs ohne eigenes Zutun erbt
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
quick-260910-*
etappe-3-mandantentrennung
etappe-4-scharfschalten
tokens tasks commits
25254 3 3
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)
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
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)
Wachhund-Testfall, der VOR der Protokollpruefung beweist, dass der zu schuetzende Codepfad ueberhaupt durchlaufen wurde
WINDOWS-18
ETAPPE-2-DASHBOARD
id description requirement verification human_judgment
D1 13 Datenbankzugriffe des Bereichs dashboard vollstaendig entschieden: 12 gebunden (forTenant), 1 begruendet ungebunden (Modulkatalog) ETAPPE-2-DASHBOARD
kind ref status
unit apps/api/src/dashboard/dashboard.service.spec.ts (27 Faelle) pass
kind ref status
integration apps/api/scripts/rls-scratch-check.mjs (87/87 Pruefungen, davon 13 neue im Abschnitt runDashboardAreaChecks) pass
false
id description requirement verification human_judgment
D2 Kritikschrift fuer den Bereich dashboard inklusive der beweisvernichtenden Schleife und der eigenstaendig nachgepruften widerlegten SearchProvider-Praemisse WINDOWS-18
kind ref status
other docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt '## Bereich dashboard' (w1)-(w5) pass
false
id description verification human_judgment
D3 Klassifikationsdatei an allen fuenf handgepflegten Stellen nachgezogen und maschinell gegen den Quelltext geprueft
kind ref status
unit apps/api/src/prisma/rls-access-inventory.spec.ts (10/10) pass
false
26min 2026-09-11 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.