14 KiB
phase, plan, subsystem, tags, requires, provides, affects, actuals, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | actuals | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | status | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260909-ipc | 01 | database |
|
|
|
|
|
|
|
|
|
|
~55min | 2026-09-09 | complete |
Quick Task 260909-ipc: Mandantentrennung Etappe 2, Bereich ldap — Summary
21 klassifizierte Datenbankzugriffe in ldap-config.service.ts und ldap.service.ts auf forTenant() umgestellt, eine Fremdzugriffsluecke beim Loeschen von Feldzuordnungen geschlossen, und die Fehlerrichtung nach dem geplanten Scharfschalten ("sieht zu viel" wird zu "sieht nichts") schriftlich und an der echten RLS-Policy gemessen festgehalten.
Performance
- Duration: ~55 min
- Tasks: 3/3 completed
- Files modified: 8 (1 created, 7 modified)
- Commits: 3
Accomplishments
- Der gesamte Bereich
ldap(21 ursprünglich klassifizierte Zugriffe, plus zwei nachträglich entdeckte bereits-gebundene Fundstellen) läuft jetzt entweder gebunden überforTenant()oder trägt eine ausgeschriebene, im Code stehende Begründung, warum er bewusst übergreifend bleibt. - Die Fremdzugriffslücke beim Löschen einer LDAP-Feldzuordnung (
DELETE /ldap/config/mappings/:id, T-IPC-01) ist geschlossen: der Mandant kommt jetzt aus dem Sitzungsnachweis, nicht mehr nur aus der URL-Kennung. - Die maschinelle Absicherung (
rls-access-inventory.spec.ts) erkennt jetzt gebundene Zugriffe zusätzlich zu ungebundenen und prüft eine neue Stand-Spalte im Klassifikationsdokument gegen den Quelltext — eine Umstellung kann die Prüfung nicht mehr fälschlich als "Fundstelle verschwunden" scheitern lassen (Befund G). apps/api/scripts/rls-scratch-check.mjsmisst jetzt 13 Verhaltensweisen statt 8 (5 neue für den Bereich ldap), gegen die aus der ausgelieferten Migration extrahierten, echtenLdapConfig/LdapFieldMapping-Policies.docs/mandantentrennung-etappe2-fehlerrichtung.mdbeantwortet die vom Wiedereinstieg verlangte Frage ("Woran würde ich merken, dass eine umgestellte Abfrage zu wenig liefert?") mit einer Signaltabelle je Pfad und den vier Stellen, die Leere als Abwesenheit deuten.
Task Commits
- Aufgabe 1: Fehlerrichtung schriftlich festhalten und an der echten Policy messen -
a0c9ef0(feat) - Aufgabe 2: ldap-config.service.ts binden, Fremdzugriff beim Loeschen schliessen, Absicherung erweitern -
9a57fa7(feat) - Aufgabe 3: ldap.service.ts binden, die uebergreifende Kollisionspruefung festnageln, Dokument schliessen -
e1586a4(feat)
Plan metadata: committed separately by the orchestrator after this SUMMARY.
Files Created/Modified
docs/mandantentrennung-etappe2-fehlerrichtung.md- neue Kritikschrift: Leitfrage, Messbeleg, Signaltabelle je Pfad, vier "Leere als Abwesenheit"-Stellen, drei bewusst offen gelassene Punkteapps/api/scripts/rls-scratch-check.mjs- neuer AbschnittrunLdapAreaChecks(5 Messungen gegen die aus der Migration extrahierten LdapConfig/LdapFieldMapping-Policies)apps/api/src/ldap/ldap-config.service.ts-getConfig/createConfig/updateConfig/addFieldMapping/removeFieldMappinggebunden;getAllActiveConfigs/onApplicationBootstrapbleiben ungebunden mit ausgeschriebener Begründungapps/api/src/ldap/ldap-config.service.spec.ts- forTenant-Identitätsmock ergänzt, 9 neue Bindungstestsapps/api/src/ldap/ldap.controller.ts-removeFieldMappingnimmt jetzt den Mandanten aus dem Sitzungsnachweis,addFieldMappingreicht ihn durchapps/api/src/ldap/ldap.service.ts- 11 Abfragen in 6 Methoden gebunden;resolveEmailForWritebleibt ausdrücklich ungebunden, mit ausgeschriebener Begründungapps/api/src/ldap/ldap.service.spec.ts- neuer Testblock mit zwei unterscheidbarenforTenant()-Ersatzobjekten, 6 neue Testsapps/api/src/prisma/rls-access-inventory.spec.ts- erweiterte Fundstellensuche (gebunden + ungebunden), neue Stand-Spalten-Prüfung, Ausnahmeliste fürreq.tenantPrisma-Veröffentlichungdocs/mandantentrennung-zugriffsklassifikation.md- Stand-Spalte für alle 61 Paare, 2 neu entdeckte Paare, Klassenkorrektur (ldapConfig → beides), neu gerechnete Bereichsübersicht (gebunden getrennt von ungebunden), "Hintergrunddienst als Falle"-Abschnitt für ldap.service.ts auf "geschlossen" aktualisiert
Decisions Made
- resolveEmailForWrite() bleibt dauerhaft ungebunden (Befund A, T-IPC-04):
email/usernamesind plattformweit@unique, eine Bindung würde eine echte Kollision in einen P2002-Datenbankabbruch verwandeln statt sie sauber zu melden. Lösung an Etappe 3 übergeben. - getAllActiveConfigs()/onApplicationBootstrap() bleiben dauerhaft ungebunden (Befund B): echter Planer-/Boot-Lesezugriff über alle Mandanten. Klasse von (
ldap-config.service.ts,ldapConfig) korrigiert vonmuss-mandantengebundenaufbeides. - Zwei bisher unsichtbare, bereits gebundene Fundstellen entdeckt und dokumentiert:
auth.service.ts/passwordResetTokenundldap.service.ts/groupMembershipwaren nie Teil derthis.prisma.*-Rohtrefferzahl, weil sie schon vor diesem Plan überforTenant()liefen — die alte, nurthis.prisma.*suchende Prüfung konnte sie nicht sehen. Klassen-Verteilung damit 61 statt 59 Paare. - Standardgruppen-Übergabe (Befund D) bewusst nicht in diesem Durchlauf gelöst:
reassignDefaultBeforeDelete/ensureDefaultGroupliegen ingroups.service.ts, das dieser Plan nicht anfasst. Als Reihenfolgebedingung für Etappe 4 dokumentiert —groupsist der ohnehin nächste Bereich der Etappe 2.
Deviations from Plan
None (Rule 1-3) — plan executed as written. Two minor Rule-1/technical adjustments made without changing scope:
1. [Rule 1 - Bug] TypeScript implicit-any errors in searchUsers() after binding
- Found during: Task 3, type-check
- Issue: Once
existingcame fromtenantPrisma.user.findMany(typedanyvia theas anycast pattern used throughout this file), the downstream.map((u) => ...)callbacks lost their contextual parameter types, trippingnoImplicitAny. - Fix: Added explicit inline parameter type annotations (
(u: { ldapDn: string | null }),(d: string | null),(u: { username: string })). - Files modified: apps/api/src/ldap/ldap.service.ts
- Verification:
npm --prefix apps/api run type-checkreturns 0. - Committed in:
e1586a4(part of task commit)
2. [Rule 1 - Bug] forTenant() function definition matched the new "unassigned call" detector
- Found during: Task 2, running the extended rls-access-inventory.spec.ts against the live tree
- Issue:
export function forTenant(prisma, tenantId) { ... }inprisma-tenant.extension.tsitself matched theforTenant\(pattern used to find call sites, triggering a false-positive "unassigned forTenant( call" violation. - Fix: Excluded the function definition (not a call) via a negative lookbehind for
functionin the counting regex. - Files modified: apps/api/src/prisma/rls-access-inventory.spec.ts
- Verification: the new "jedes forTenant(-Vorkommen..." test passes.
- Committed in:
9a57fa7(part of task commit)
Total deviations: 2 auto-fixed (both Rule 1, both mechanical/test-tooling correctness, no scope creep). Impact on plan: None — both fixes were necessary to make the plan's own new tooling correct; neither touched production LDAP behavior.
Issues Encountered
None beyond the two deviations above.
User Setup Required
None - no external service configuration required. The scratch-database check requires TESSERA_SCRATCH_ADMIN_URL (already an existing convention from Etappe 1, not new to this plan).
Measured Numbers (for the record)
npm --prefix apps/api run test→ 719 tests green (53 test files), baseline was 701 (+18: 9 new ldap-config bindings tests, 6 new ldap.service distinguishable-client tests, 3 new rls-access-inventory tests).npm --prefix apps/api run type-check→ 0.node apps/api/scripts/rls-scratch-check.mjs→ 13/13 Prüfungen bestanden (8 aus Etappe 1 + 5 neue aus diesem Plan), including the key evidentiary lineldapconfig-ungebunden-null-zeilen: bestanden — ungebundener SELECT auf "LdapConfig" liefert 0 Zeile(n).git diff --statconfirmsapps/api/prisma/schema.prisma,apps/api/prisma/migrations/,.env, and both compose files are untouched.DATABASE_URL/ roletessera(BYPASSRLS) is unchanged — the cutover switch stays OFF.
Deferred to Later Stages
resolveEmailForWriteaddress-collision check (Befund A) — deliberately stays cross-tenant forever; needs an Etappe-3 system-context solution (likely a fourth SECURITY-DEFINER function, mirroring the login-path pattern).- Scheduler silence (Befund E,
getAllActiveConfigs) — after cutover this reads 0 rows and the LDAP sync silently stops for every tenant with no log line. No runtime warning added deliberately (would be noise on every install without LDAP); the signal belongs in Etappe 4's pre-cutover check (rls-preflight.mjs). - Default-group handoff to
groups(Befund D) —reassignDefaultBeforeDelete/ensureDefaultGroupingroups.service.tsare not bound. Ordering condition for Etappe 4:groupsmust be converted before cutover, or a tenant could be left without a default group after a group deletion.
Next Phase Readiness
The ldap area of Etappe 2 is fully closed per this plan's success criteria. Per .planning/.continue-here.md's <next_action>, the next area is groups (37 sites), then tenders (62 sites). The docs/mandantentrennung-etappe2-fehlerrichtung.md critique and the newly-extended rls-access-inventory.spec.ts (bound-site detection, Stand column) are reusable infrastructure for those next areas — no further tooling work should be needed before starting groups.
Phase: quick-260909-ipc Completed: 2026-09-09
Self-Check: PASSED
All 9 claimed files verified present on disk; all 3 claimed commit hashes verified present in git history.