18 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-jts | 01 | database |
|
|
|
|
|
|
|
|
|
|
~70min | 2026-09-09 | complete |
Quick Task 260909-jts: Mandantentrennung Etappe 2, Bereich groups — Summary
34 sichtbare und 9 zuvor fuer jede Pruefung unsichtbare Datenbankzugriffe in groups.service.ts (12 Methoden) und module-grants.service.ts (5 Methoden) auf forTenant()/withTenantTransaction() umgestellt — die Umstellung, auf der die gesamte Etappe ruht, wurde per Messung entschieden, nicht angenommen, und die Reihenfolgebedingung fuer Etappe 4 aus dem ldap-Durchlauf ist geschlossen.
Performance
- Duration: ~70 min
- Tasks: 3/3 completed
- Files modified: 10 (0 created, 10 modified)
- Commits: 3
Accomplishments
- Der Bereich
groups(37 Rohtreffer, davon 34 tatsaechliche Modellzugriffe, plus 9 bislang unsichtbare Zugriffe ueber den Rueckgabeparameter einer interaktiven Transaktion) laeuft jetzt vollstaendig ueber den Mandantenkontext. - Die einzige offene Architekturfrage der gesamten Umstellung — welche Transaktionsform den Mandantenkontext auf derselben Verbindung traegt — ist an der echten Datenbank gemessen: die Array-Form auf dem gebundenen Client versagt nachweisbar (zwei verschiedene
pg_backend_pid()fuer zwei Teilschritte einer angeblich gemeinsamen Transaktion), und von den beiden bestehenden Formen ueberlebt nur eine (withTenantTransaction, interaktiv auf dem ungebundenen Client) eine zusaetzliche Lastprobe mit 40 parallelen Aufrufen. - Zwei latente Mandantenluecken sind gemessen und dokumentiert: die
GroupMembership-Regel prueft nur die Gruppenseite (Befund E, T-JTS-02 — jetzt inaddUserToDefaultGroupdurch eine Anwendungspruefung geschlossen), und dieModuleGrant-Regel prueft nur die Mandantenkennung der Zeile, nicht die referenzierte Gruppe (Befund F, T-JTS-03 — die bestehendeassertTargetBelongsToTenant-Pruefung bleibt deshalb der primaere Schutz). - Die maschinelle Absicherung (
rls-access-inventory.spec.ts) sieht jetzt Modellzugriffe ueber den Rueckgabeparameter einer interaktiven Transaktion — macht das Paar (groups.service.ts,tenantModuleActivation) erstmals sichtbar, das genau auf der Schreibstelle mit der groessten Wirkung sass (Modulfreigaben fuer neu angelegte Standardgruppen). - Die Reihenfolgebedingung fuer Etappe 4 aus dem ldap-Durchlauf (Befund D: die Standardgruppen-Uebergabe vor einer Gruppenloeschung) ist geschlossen und in beiden Dokumenten als erledigt vermerkt.
docs/mandantentrennung-etappe2-fehlerrichtung.mdbekommt einen eigenengroups-Abschnitt mit den tatsaechlich beobachteten Messwerten (nicht erwarteten), einer Signaltabelle je Pfad und der Stelle, an der zu wenig Lesen zu viel Schreiben ausloest.
Task Commits
- Aufgabe 1: Fehlerrichtung fuer groups schreiben und die Transaktionsfrage messen -
fd0b9f7(feat) - Aufgabe 2: groups.service.ts binden, die Transaktionen tragfaehig machen, die Absicherung sehend machen -
7f08b27(feat) - Aufgabe 3: module-grants.service.ts binden und beide Dokumente schliessen -
abb6c8b(feat)
Plan metadata: committed separately by the orchestrator after this SUMMARY.
Files Created/Modified
apps/api/scripts/rls-scratch-check.mjs- neuer AbschnittrunGroupsAreaChecks(9 Messungen gegen die aus zwei ausgelieferten Migrationen extrahierten Group/GroupMembership/ModuleGrant/TenantModuleActivation-Policies) plusrunTransactionShapeMeasurement(die eine Transaktionspruefung, benennt namentlich welche der drei Formen tragen)apps/api/src/prisma/prisma-tenant.extension.ts- neueswithTenantTransaction(prisma, tenantId, fn), Kopfkommentar um die gemessene Entscheidung fuer diesen Bereich erweitertapps/api/src/prisma/prisma-tenant.extension.spec.ts- bestehender "keine interaktive Form"-Test aufforTenant()selbst verengt (nicht mehr die ganze Datei), 4 neue Tests fuerwithTenantTransactionapps/api/src/prisma/rls-access-inventory.spec.ts- dritte Erkennung fuer Modellzugriffe ueber Transaktionsparameter (zwei Empfaengerformen), neue Vollstaendigkeits-Pruefung, Ausnahmeliste startet leerapps/api/src/groups/groups.service.ts- alle 12 Methoden gebunden, drei Transaktionsstellen aufwithTenantTransaction()umgestellt,addUserToDefaultGroupprueft neu den Zielbenutzer-Mandanten (Befund E)apps/api/src/groups/groups.service.spec.ts- zwei unterscheidbare Clients ueber demselben Speicher-Fake, 13 neue Bindungsnachweise, alle 42 Bestandstests unveraendert gruen (zwei Fixture-Ergaenzungen fuer den neuen Mandantencheck)apps/api/src/groups/module-grants.service.ts- alle 5 Methoden gebunden, T-JTS-03-Begruendung angrant()ergaenzt, veralteter Kommentar ingetUserAccess()ersetztapps/api/src/groups/module-grants.service.spec.ts- derselbe Bindungsnachweis-Mock, 6 neue Tests, alle 28 Bestandstests unveraendert gruendocs/mandantentrennung-etappe2-fehlerrichtung.md- neuer Abschnitt "Bereich groups" (Messbeleg, Transaktionsform-Entscheidung inkl. Lastprobe, Signaltabelle, vier Pflichtpunkte, was nicht geloest wird), ldap-Abschnitt (e) um Nachtrag zu Befund D erweitertdocs/mandantentrennung-zugriffsklassifikation.md- neun Zeilen fuergroups.service.ts/module-grants.service.tsaufgebunden, neue Zeile fuer das bislang unsichtbare Paar (groups.service.ts,tenantModuleActivation), Bereichsuebersicht neu gemessen (0/31, mit dokumentiertem methodischen Bodensatz), Klassen-Verteilung auf 62 Paare, "Hintergrunddienst als Falle" fuerldap.service.tsauf geschlossen aktualisiert
Decisions Made
withTenantTransaction()fuer alle drei Transaktionsstellen gewaehlt, nicht nur wo der Plan es zwingend verlangte. Die Einzelmessung aus Aufgabe 1 liess technisch zwei Formen bestehen (interaktiv auf gebundenem Client; interaktiv auf ungebundenem Client). Eine zusaetzliche, ueber den Plan hinausgehende Lastprobe (40 parallele Aufrufe) zeigte, dass die erste Form unter echter Nebenlaeufigkeit mitP2028(Transaction API error) abbricht — ein Denial-of-Service-Risiko genau an der von T-JTS-08 benannten Stelle (Startreparatur ueber mehrere Mandanten). Die zweite Form zeigte 0 Verletzungen unter derselben Last und wurde deshalb durchgehend gewaehlt.addUserToDefaultGroup()prueft jetzt den Mandanten des Zielbenutzers (Befund E, T-JTS-02), nach dem Vorbild vonaddMembers(). Zwei Bestandstests brauchten dafuer einen zusaetzlichen__seedUser-Aufruf (die Methode wurde vorher mit einer nie existierendenuserIdgetestet — eine Luecke im urspruenglichen Testaufbau, die die Umstellung sichtbar machte).- Der veraltete Kommentar in
ModuleGrantsService.getUserAccess()wurde ersetzt, nicht stehen gelassen — nach der Bindung waere die alte Begruendung ("kein forTenant hier") aktiv irrefuehrend gewesen. listForTenant()bekam eine expliziteany[]-Annotation statt der implizitenany, nachdem eine minimale Reproduktion zeigte, dass TypeScript sonstnoImplicitAnyin JEDEM Aufrufer auslöst, der.find()/.map()auf dem Rueckgabewert aufruft — ein reinerany-Typ propagiert diese Pruefung nicht ab, einany[]-Typ schon.
Deviations from Plan
None (Rule 1-3) — plan executed as written, with one auto-fixed mechanical issue:
1. [Rule 1 - Bug] TypeScript implicit-any errors in twelve test-file call sites after binding
- Found during: Task 2, type-check
- Issue:
listForTenant()'s inferred return type collapsed from a concrete Prisma-generated shape to a bareanyonce its underlying query ran through theany-castforTenant()client; every caller ingroups.service.spec.tschaining.find()/.map()on that result trippednoImplicitAny(TS7006), confirmed via a minimal standalone repro before fixing broadly. - Fix: Annotated the intermediate
groupsvariable insidelistForTenant()asany[]instead of leaving it unannotated. - Files modified: apps/api/src/groups/groups.service.ts
- Verification:
npm --prefix apps/api run type-checkreturns 0. - Committed in:
7f08b27(part of task commit)
Total deviations: 1 auto-fixed (Rule 1, mechanical/type-inference correctness, no scope creep). Impact on plan: None — the fix was necessary to make the plan's own conversion type-clean; it touched no production behavior.
Issues Encountered
None beyond the deviation above. The RED-first TDD cycle for Aufgabe 2 and Aufgabe 3 both ran cleanly: the new binding-proof tests failed against the pre-conversion code for the expected reason (missing bound-call-log entries), then passed after conversion, without needing a second red/green iteration.
User Setup Required
None - no external service configuration required. The scratch-database check requires TESSERA_SCRATCH_ADMIN_URL (existing convention, not new to this plan).
Measured Numbers (for the record)
npm --prefix apps/api run test→ 743 tests green (53 test files), baseline was 719 (+24: 13 new groups.service.ts binding tests, 6 new module-grants.service.ts binding tests, 4 new withTenantTransaction tests, 1 new inventory completeness test).npm --prefix apps/api run type-check→ 0.node apps/api/scripts/rls-scratch-check.mjs→ 22/22 Pruefungen bestanden (13 aus den Vorlaeufern + 9 neue Verhaltenspruefungen des Bereichs groups). Belegzeile:group-ungebunden-null-zeilen: bestanden — ungebundener SELECT auf "Group" liefert 0 Zeile(n).- Transaktionsmessung, namentlich:
bestanden: [Form (ii) — interaktive Callback-Form auf gebundenem Client ; Form (iii) — interaktive Callback-Form auf ungebundenem Client (set_config auf tx)] — nicht bestanden: [Form (i) — Array-Form auf gebundenem Client]. Zusaetzliche, ueber den Plan hinausgehende Lastprobe (40 parallele Aufrufe, nicht im Werkzeug persistiert, manuell im Kritikschrift-Abschnitt dokumentiert): Form (ii) brach mitP2028ab, Form (iii) zeigte 0 Verletzungen. - Klassifikationsdokument: 62 (Datei, Modell)-Paare (war 61), Klasse
muss-mandantengebundenjetzt 33 (war 32). Bereichsuebersichtgroups: 0 ungebunden / 31 gebunden (Rohtrefferzaehlung, dokumentierter Bodensatz — 9 weitere uebertxgebundene Zugriffe zaehlt die einfache Grep-Konvention strukturell nicht, die rigorose (Datei,Modell)-Bestandsaufnahme sieht sie). git status --porcelain -- apps/api/prisma/schema.prisma apps/api/prisma/migrations docker-compose*.yml→ leer, bestaetigt ueber den gesamten Plan.DATABASE_URL/ Rolletessera(BYPASSRLS) unveraendert — der Schalter bleibt AUS.
Deferred to Later Stages
- Die offene Architekturfrage
req.tenantPrisma— auchgroupsentscheidet sie nicht; bindet dienst-intern wieldapundauth.service.ts. - Die zwei gemessenen Regel-Luecken (Befund E:
GroupMembershipprueft nur die Gruppenseite; Befund F:ModuleGrantprueft nur die eigene Mandantenkennung, nicht die referenzierte Gruppe) bleiben Anwendungspruefungen — sie werden durch dieses Vorgehen NICHT durch eine Datenbankregel ersetzt. Eine etwaige Schemaerweiterung (z. B. eine zusammengesetzte Fremdschluessel-Regel) ist ausdruecklich nicht Teil dieses Durchlaufs (Schema-Tor greift nicht, aber eine Erweiterung waere ein Abbruchgrund gewesen — trat nicht ein). - Der naechste Bereich der Etappe 2 ist
tenders(62 Rohtreffer, groesster verbleibender Bereich) — die in diesem Durchlauf gebautewithTenantTransaction()-Infrastruktur und die dritte Erkennung inrls-access-inventory.spec.tssind wiederverwendbar, fallstendersebenfalls mehrschrittige Transaktionen enthaelt (noch nicht gemessen).
Next Phase Readiness
Der Bereich groups der Etappe 2 ist per Erfolgskriterien dieses Plans vollstaendig geschlossen. Die Reihenfolgebedingung fuer Etappe 4 aus dem ldap-Durchlauf (Befund D) ist erfuellt. Naechster Bereich laut Klassen-Verteilung: tenders (62 Rohtreffer, groesster verbleibender Bereich der Etappe 2) — noch nicht dahingehend gemessen, ob dort eigene Transaktionen vorkommen; falls ja, ist withTenantTransaction() bereits vorhanden und muss nicht neu gebaut werden.
Phase: quick-260909-jts Completed: 2026-09-09
Self-Check: PASSED
All 11 claimed files verified present on disk; all 3 claimed commit hashes (fd0b9f7, 7f08b27, abb6c8b) verified present in git history.