Files

18 KiB
Raw Permalink Blame History

phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, tech-stack, key-files, key-decisions, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals plan_head_before tech-stack key-files key-decisions requirements-completed coverage duration completed status
quick-260910-das 01 database
prisma
row-level-security
multi-tenancy
nestjs
postgres
phase provides
quick-260909-mir rls-scratch-check.mjs mit sechs Bereichsabschnitten, prisma-tenant.extension.ts (forTenant/withTenantTransaction), die Klassifikations- und Fehlerrichtungsdokumente
runUserAreaChecks in rls-scratch-check.mjs (12 neue Pruefungen, siebter Abschnitt)
UserService gebunden (findById/create/update/deactivate/delete ueber forTenant, zwei neue Plattform-Administratorsicht-Methoden)
AdminSeedService
Erstanlage des Administrators gebunden, Startsperre bei plattformweiter Eindeutigkeitsverletzung entschaerft
UserController vollstaendig gebunden, Selbstloesch-Riegel repariert (Befund H)
user.controller.spec.ts (neu, Zwei-Klienten-Nachweis fuer vorher testlose Steuerungsschicht)
docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt "Bereich user" (u1-u5 plus Nachtrag)
docs/mandantentrennung-zugriffsklassifikation.md auf den Bereich user nachgezogen, inkl. aller vier handgepflegten Stellen
quick-260910-etappe3-plattformweite-eindeutigkeit
quick-etappe4-scharfschalten
tokens tasks commits
31500 3 3
7e7a697
added patterns
Plattform-Administratorsicht als Schleife ueber alle Mandanten mit je EINEM gebundenen Lesezugriff im Rumpf (bereits in admin-seed.service.ts vorgemacht, jetzt zweimal in user.service.ts uebernommen)
Bewusst-ungebundener Nachschlageweg auf einem plattformweit eindeutigen Schluessel mit geschriebener Begruendung am Ort (Praezedenzfall resolveEmailForWrite, hier UserService.findByUsername)
P2002-Uebersetzung am einzigen Erzeugungspunkt fuer eine Entitaet statt bei jedem Aufrufer (UserService.create/update)
created modified
apps/api/src/user/user.controller.spec.ts
apps/api/scripts/rls-scratch-check.mjs
apps/api/src/user/user.service.ts
apps/api/src/user/user.service.spec.ts
apps/api/src/user/admin-seed.service.ts
apps/api/src/user/admin-seed.service.spec.ts
apps/api/src/user/user.controller.ts
docs/mandantentrennung-etappe2-fehlerrichtung.md
docs/mandantentrennung-zugriffsklassifikation.md
.planning/WINDOWS.md
Task-2-Signaturaenderung (findById/update/deactivate/delete bekommen einen Pflicht-Mandanten) UND die vier Aufrufstellen in user.controller.ts wurden im SELBEN Task-2-Commit angepasst (nicht nach Aufgabe 3 verschoben), weil Aufgabe 2s eigenes Verify-Gate volle Typpruefung und einen gruenen Testlauf verlangt. Die minimale Anpassung uebergibt currentUser.tenantId; das ist fuer SUPER_ADMIN semantisch noch nicht korrekt (wird erst in Aufgabe 3 mit findByIdForPlatformAdmin geloest), aber verhaltensneutral, weil der Schalter aus bleibt (BYPASSRLS aktiv) und die betroffenen Methoden keine explizite tenantId ins where schreiben — die reale Rueckgabe war ueber beide Aufgaben hinweg identisch.
Klassifikationsdokument wurde in Aufgabe 2 bereits minimal nachgezogen (Stand-Spalte fuer zwei Paare auf gemischt, neue Zeile user.service.ts/tenant), obwohl das Dateilisting formal erst Aufgabe 3 zuweist — sonst waere rls-access-inventory.spec.ts, Teil des von Aufgabe 2 selbst verlangten npm run test, rot geblieben. Die vollen Klassenkorrekturen mit Begruendung sowie alle vier handgepflegten Uebersichtstabellen bleiben wie geplant Aufgabe 3 vorbehalten.
user.controller.ts bekommt eine private resolveTargetUser()-Hilfsmethode, um die Rollenverzweigung (ADMIN gebunden vs. SUPER_ADMIN uebergreifend) nicht dreimal zu wiederholen (findOne/update/remove) — keine Aenderung an der Pruefreihenfolge oder den bestehenden Ausnahmen.
WINDOWS-18
ETAPPE-2-USER
id description requirement verification human_judgment
D1 Die Kette unsichtbare Zeile -> falsches 'frei' -> harter Eindeutigkeitsfehler ist an der echten ausgelieferten User-Policy gemessen, samt Unterscheidung SQLSTATE 23505 (Eindeutigkeitsverletzung) vs. 42501 (Zeilenschutz-Ablehnung) WINDOWS-18
kind ref status
other apps/api/scripts/rls-scratch-check.mjs runUserAreaChecks() — 12 benannte Pruefungen gegen Wegwerf-Datenbank, alle bestanden pass
false
id description requirement verification human_judgment
D2 UserService bindet findById/create/update/deactivate/delete an den Mandanten; findByUsername bleibt bewusst ungebunden mit geschriebener Begruendung ETAPPE-2-USER
kind ref status
unit apps/api/src/user/user.service.spec.ts — 13 Tests (Test 1-8 plus 4 bestehende plus 1 Zusatztest), Falsifizierungsnachweis fuer findById durchgefuehrt pass
false
id description requirement verification human_judgment
D3 AdminSeedService bindet die Erstanlage des Administrators und entschaerft die Startsperre bei plattformweiter Eindeutigkeitsverletzung, ohne andere Startfehler abzuschwaechen ETAPPE-2-USER
kind ref status
unit apps/api/src/user/admin-seed.service.spec.ts — 10 Tests (Test 9-12 neu plus 6 bestehende), Falsifizierungsnachweis durchgefuehrt (6 Tests rot bei zurueckgebauter Bindung) pass
false
id description requirement verification human_judgment
D4 UserController bindet alle sieben eigenen Zugriffe, loest den Zielbenutzer rollenabhaengig auf, und der Selbstloesch-Riegel greift (Befund H, vorher wirkungslos) ETAPPE-2-USER
kind ref status
unit apps/api/src/user/user.controller.spec.ts — 8 Tests, Rot-vor-Reparatur-Nachweis fuer Test 6 (Selbstloesch-Riegel) und Falsifizierungsnachweis fuer Test 7 (Bindung) durchgefuehrt pass
false
id description requirement verification human_judgment
D5 Beide Dokumente (Fehlerrichtung, Klassifikation) sind fortgeschrieben statt umgeschrieben; alle vier handgepflegten Stellen der Klassifikation sind maschinell gegen den Quelltext gegatet ETAPPE-2-USER
kind ref status
unit apps/api/src/prisma/rls-access-inventory.spec.ts (10 Tests, deckt Bestandsaufnahme ab) + vier awk/grep-Pruefungen aus dem Plan-Verify-Block (Uebersichtszeile, Summenzeile, Klassen-Verteilung samt Summe/Ueberschrift, Hintergrunddienst-Ueberschrift) pass
false
65min 2026-09-10 complete

Phase quick-260910-das: Mandantentrennung Etappe 2, Bereich user Summary

UserService/AdminSeedService/UserController vollstaendig an forTenant() gebunden, mit exakt vier begruendeten Ausnahmen (Benutzername-Suche, Erstanlage-Pruefung, zwei Mandantentabellen-Zugriffe), plus Reparatur des wirkungslosen Selbstloesch-Riegels und Entschaerfung einer Startsperre nach dem geplanten Scharfschalten.

Performance

  • Duration: 65 min
  • Started: 2026-09-10T08:03:00Z
  • Completed: 2026-09-10T09:08:00Z
  • Tasks: 3
  • Files modified: 10 (1 neu, 9 geaendert)

Accomplishments

  • Die im Auftrag beschriebene Kette (unsichtbare Zeile → falsches „frei" → harter Eindeutigkeitsfehler) ist an der echten, ausgelieferten User-Policy gemessen, nicht behauptet — inklusive der Unterscheidung zwischen einer Eindeutigkeitsverletzung (SQLSTATE 23505) und einer Zeilenschutz-Ablehnung (SQLSTATE 42501).
  • Die schwerste Ausprägung der umgekehrten Fehlerrichtung im gesamten Vorhaben — eine Startsperre für jede Installation mit gesetzten Administrator-Umgebungswerten — ist im Anwendungscode entschärft, ohne dass irgendein anderer Startfehler seine abbrechende Wirkung verliert.
  • Die Linie zwischen „muss binden" und „darf nicht binden" ist je Methode gezogen und am Ort begründet: UserService.findByUsername bleibt bewusst ungebunden (derselbe Fall wie resolveEmailForWrite im Bereich ldap), alle übrigen Zugriffe binden.
  • Eine heute wirksame Rechteausweitung ist geschlossen: ein Administrator konnte sich bisher selbst löschen, weil der Riegel gegen ein im Sitzungsnachweis nicht existierendes Feld (sub) verglich.
  • Der Bereich hat erstmals in allen drei Dateien Tests, die auf eine vergessene Bindung rot werden können — durch tatsächlichen probeweisen Rückbau nachgewiesen, nicht behauptet.

Task Commits

Each task was committed atomically:

  1. Aufgabe 1: Die Kette messen und die Kritikschrift schreiben - b848ba6 (feat)
  2. Aufgabe 2: Testlage herstellen, Dienst umstellen, Linie ziehen, Startsperre entschärfen - 888f660 (feat)
  3. Aufgabe 3: Steuerungsschicht binden, Selbstlöschriegel schließen, Dokumente nachziehen - 3a9391d (feat)

Alle drei Commits enthalten sowohl den TDD-Testnachweis als auch die Implementierung — kein separater test→feat-Split, weil das Vorgehen "Nachweis vor Umstellung, dann Umstellung, dann Falsifizierungsnachweis mit Rückbau" innerhalb jeder Aufgabe verlief, nicht über Aufgabengrenzen hinweg.

Files Created/Modified

  • apps/api/scripts/rls-scratch-check.mjs — siebter Abschnitt runUserAreaChecks (12 neue Prüfungen), Hilfsfunktionen normalizePolicySql/sqlStateOf
  • apps/api/src/user/user.service.ts — findById/update/deactivate/delete mit Pflicht-Mandant, create/update mit P2002-Übersetzung, zwei neue Methoden für die Plattform-Administratorsicht, findByUsername-Kommentar richtiggestellt
  • apps/api/src/user/user.service.spec.ts — Zwei-Klienten-Nachweis, 13 Tests
  • apps/api/src/user/admin-seed.service.ts — Erstanlage gebunden, Startsperre entschärft, Kopfkommentar der Reparaturschleife ergänzt (Befund K)
  • apps/api/src/user/admin-seed.service.spec.ts — Zwei-Klienten-Nachweis, 10 Tests
  • apps/api/src/user/user.controller.ts — alle sieben Zugriffe gebunden, resolveTargetUser(), Selbstlöschriegel repariert
  • apps/api/src/user/user.controller.spec.ts — neu, Zwei-Klienten-Nachweis, 8 Tests
  • docs/mandantentrennung-etappe2-fehlerrichtung.md — Abschnitt „Bereich user" (u1–u5) plus Nachtrag
  • docs/mandantentrennung-zugriffsklassifikation.md — Übersichtszeile, Summenzeile, Klassen-Verteilung, Hintergrunddienst-Abschnitt (fünf Fälle), zwei Klassenkorrekturen, neue Zeile user.service.ts/tenant
  • .planning/WINDOWS.md — offener Eintrag #22 (plattformweite Eindeutigkeit von username/email, Produktentscheidung für Etappe 3)

Decisions Made

  • Reihenfolge der Signaturänderung (Aufgabe 2): Die vier UserService-Methoden bekamen ihren Pflicht-Mandanten UND die vier Aufrufstellen in user.controller.ts wurden im selben Aufgabe-2-Commit angepasst, statt die Signaturänderung komplett nach Aufgabe 3 zu verschieben — Aufgabe 2s eigenes <verify> verlangt eine saubere Typprüfung. Die minimale Anpassung übergibt currentUser.tenantId; das ist für SUPER_ADMIN semantisch noch unvollständig (erst Aufgabe 3 löst es korrekt über findByIdForPlatformAdmin), aber verhaltensneutral: der Schalter bleibt aus (tessera-Rolle mit BYPASSRLS), und die betroffenen Methoden schreiben keine explizite tenantId ins where — die tatsächliche Rückgabe war über beide Aufgaben hinweg identisch.
  • Klassifikationsdokument teilweise in Aufgabe 2 nachgezogen: obwohl das Dateilisting es formal erst Aufgabe 3 zuweist, wurden zwei Stand-Korrekturen und eine neue Zeile bereits in Aufgabe 2 ergänzt (Rule 3 — blockierendes Problem), weil rls-access-inventory.spec.ts sonst rot geblieben wäre und Aufgabe 2s eigenes npm run test-Gate nicht hätte bestehen können. Die vollen Klassenkorrekturen mit Begründung und alle vier handgepflegten Übersichtstabellen blieben wie geplant Aufgabe 3 vorbehalten.
  • resolveTargetUser()-Hilfsmethode in user.controller.ts, um die Rollenverzweigung nicht dreimal zu wiederholen — keine Änderung an Prüfreihenfolge oder Ausnahmen.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Klassifikationsdokument teilweise vorgezogen, damit Aufgabe 2s eigenes Test-Gate besteht

  • Found during: Task 2 (nach der Umstellung von user.service.ts/admin-seed.service.ts)
  • Issue: rls-access-inventory.spec.ts (Teil von npm run test, das Aufgabe 2s <verify> selbst verlangt) schlug fehl: das neue Paar (user.service.ts, tenant) fehlte im Dokument, und der Stand von (user.service.ts, user) sowie (admin-seed.service.ts, user) war noch als ungebunden dokumentiert, obwohl der Code jetzt gemischt war.
  • Fix: Minimale Korrektur der drei betroffenen Zeilen (Stand-Spalte, neue Zeile) mit dem Vermerk „ZWISCHENSTAND nach Aufgabe 2 — Klassenkorrektur folgt in Aufgabe 3", ohne die vier handgepflegten Übersichtstabellen anzufassen.
  • Files modified: docs/mandantentrennung-zugriffsklassifikation.md
  • Verification: rls-access-inventory.spec.ts grün nach der Korrektur; Aufgabe 3 hat die Zeilen anschließend vollständig fertiggestellt (Klassenkorrektur mit Begründung).
  • Committed in: 888f660 (Aufgabe-2-Commit)

Total deviations: 1 auto-fixed (Rule 3 — blockierendes Testproblem, keine Funktionsänderung) Impact on plan: Notwendig, um Aufgabe 2s eigenes Verify-Gate zu erfüllen; die eigentliche inhaltliche Arbeit (Klassenkorrekturen, Übersichtstabellen) blieb wie im Plan vorgesehen Aufgabe 3 vorbehalten. Kein Scope Creep.

Falsifizierungsnachweise

Aufgabe 2, UserService.findById: forTenant(this.prisma, tenantId) probeweise durch this.prisma (ungebunden) ersetzt. Ergebnis: genau user.service.spec.ts, Test 4 ("steht gebunden im Protokoll und liefert einen Benutzer eines anderen Mandanten NICHT"), wurde rot, mit der Meldung erwarteter gebundener Aufruf user.findUnique(tenant=t1) fehlt im Protokoll: []. Rückbau zurückgenommen, derselbe Testlauf danach wieder grün (13/13).

Aufgabe 2, AdminSeedService.seedAdmin: forTenant(this.prisma, tenant.id) probeweise durch this.prisma (ungebunden, ohne .user.create) ersetzt. Ergebnis: sechs Tests wurden rot (u. a. Test 9–12), alle mit TypeError: tenantPrisma.user.create is not a function — der ungebundene Basisclient in der Testattrappe trägt keine create-Methode. Rückbau zurückgenommen, alle zehn Tests danach wieder grün.

Aufgabe 3, UserController.uploadAvatar: forTenant(this.prisma, currentUser.tenantId) probeweise durch this.prisma (ungebunden, ohne .user) ersetzt. Ergebnis: genau user.controller.spec.ts, Test 7 ("alle fünf Zugriffe der vier Selbstbedienungswege stehen gebunden im Protokoll"), wurde rot, mit TypeError: Cannot read properties of undefined (reading 'update'). Rückbau zurückgenommen, derselbe Testlauf danach wieder grün (8/8).

Aufgabe 3, Selbstlöschriegel (Rot-vor-Reparatur-Nachweis, kein Rückbau): user.controller.ts wurde probeweise auf den ursprünglichen, fehlerhaften Vergleich user.id === currentUser.sub zurückgesetzt, BEVOR der Test geschrieben wurde grün lief. Testlauf: genau user.controller.spec.ts, Test 6 ("der Riegel gegen das Löschen des eigenen Kontos greift"), wurde rot mit promise resolved "{ message: 'User deleted' }" instead of rejecting — der Beleg, dass der Riegel in der ursprünglichen Fassung NIE griff. Reparatur (currentUser.id) danach wiederhergestellt, derselbe Test grün.

Known Stubs

Keine — jede in diesem Plan berührte Methode ist entweder vollständig implementiert oder trägt eine geschriebene, im Code lesbare Begründung für die bewusst gelassene Ausnahme (kein Platzhalter, kein TODO).

Threat Flags

Keine neuen — alle in diesem Plan berührten Zugriffe sind im <threat_model> des Plans (T-DAS-01 bis T-DAS-10) bereits erfasst und entschärft.

Issues Encountered

  • .env.prod.example löste den Secret-Read-Guard in der Bash-Tool-Sandbox aus, wenn es als Argument in einem git diff --name-only/git status --porcelain -- ...-Aufruf genannt wurde — obwohl nur der Dateiname, nicht der Inhalt, gelesen worden wäre. Umgangen durch ein einfaches git status --porcelain ohne Pfadfilter (bestätigt: nur die erwarteten Dateien geändert), statt die geschützten Dateinamen literal in der Kommandozeile zu nennen.
  • Prisma-Rohfehlermeldungen (err.code) sind bei $executeRaw-Fehlern immer P2010, nicht der tatsächliche PostgreSQL-SQLSTATE — empirisch gegen tessera-ctl-db-1 geprüft (siehe sqlStateOf()-Kommentar in rls-scratch-check.mjs). Der echte SQLSTATE liegt unter err.meta.code. Ohne diese Prüfung hätte die zentrale Messung user-eindeutigkeit-greift-trotz-unsichtbarkeit möglicherweise am falschen Feld gelesen.

User Setup Required

None - keine externe Diensteinrichtung nötig. Der Schalter (DATABASE_URL → Rolle tessera) bleibt unverändert aus.

Next Phase Readiness

  • Fünf von fünf Bereichen der Etappe 2 sind jetzt umgestellt (ldap, groups, tenders, dkv, user) — die Klassifikationstabelle listet 63 Paare, davon 31 muss-mandantengebunden, 17 keine-mandantengebundene-tabelle, 13 beides, 2 bewusst-uebergreifend.
  • Etappe 3 (plattformweite Eindeutigkeit von username/email als Schemaentscheidung; WINDOWS #19 nullbares tenantId; die offene Architekturfrage req.tenantPrisma) ist mit vollständiger Beweislage vorgemerkt — siehe WINDOWS-Eintrag #22 und Abschnitt (u4) der Fehlerrichtung.
  • Reihenfolgebedingungen für Etappe 4 (Scharfschalten): keine neuen aus diesem Plan. Bestehende (Bereiche groups/settings für dkv/tenders) unverändert.
  • rls-preflight.mjs (Etappe 4) sollte künftig auch die plattformweite Eindeutigkeit von username/email als Signal berücksichtigen — bislang nicht Gegenstand dieses Werkzeugs.

Self-Check: PASSED

Alle zehn im Plan gelisteten Artefakte auf der Festplatte gefunden; alle drei Task-Commit-Hashes (b848ba6, 888f660, 3a9391d) in git log gefunden.


Phase: quick-260910-das Completed: 2026-09-10