--- phase: quick-260911-mkj plan: 01 subsystem: testing tags: [prisma, rls, multi-tenancy, static-analysis, vitest] requires: - phase: quick-260911-e2s provides: "Bestandsaufnahme-Grundgeruest (rls-access-inventory.spec.ts, drei Erkennungsformen), WINDOWS #27 aufgedeckt" provides: - "Vierte Erkennungsform in rls-access-inventory.spec.ts: Relationszugriffe (include:/select:/_count:/Relationsfilter) werden ueber schema.prisma auf ihr Zielmodell aufgeloest und als eigene Fundstelle gefuehrt" - "72 (Datei, Modell)-Paare in der Bestandsaufnahme (65 -> 72, sieben neue, drei fortgeschriebene Staende)" - "WINDOWS #27 fixed mit Nachweis; neuer Ledger-Eintrag #33 fuer die von allen vier Formen unerreichbare Restmenge (tenders.seed.ts, backfill-tender-source.ts)" affects: [etappe-4-scharfschalten, rls-preflight] actuals: tokens: 19657 tasks: 2 commits: 2 plan_head_before: 926359b067596b8d627660d926831b885e74d8d9 tech-stack: added: [] patterns: - "Kontextstapel-Parser (scanRelationKeys) fuer verschachtelte Prisma-Query-Objekte, aufgeloest gegen ein zur Testzeit geparstes schema.prisma" - "String-Blanking (blankStringLiterals) fuer klammertiefen-sichere Argumentbereichs-Erkennung, getrennt von der unveraenderten Kommentarfrei-Quelle der Formen 1-3" key-files: created: [] modified: - apps/api/src/prisma/rls-access-inventory.spec.ts - docs/mandantentrennung-zugriffsklassifikation.md - docs/mandantentrennung-etappe2-fehlerrichtung.md - .planning/WINDOWS.md key-decisions: - "Vierte Erkennung schreibt Relationsziele direkt in unboundModels/boundModels (Klientenname = Modellname mit kleinem Anfangsbuchstaben) statt eines eigenen Fundstellentyps, damit findAccessSites/computeStandByKey und die bestehenden Vergleichstests unveraendert bleiben" - "ldap-config.service.ts/ldapFieldMapping wechselt von muss-mandantengebunden auf beides — der Elternpfad getAllActiveConfigs() ist bereits als Hintergrunddienst-Fall an Etappe 3 uebergeben, kein neuer Befund" - "RELATION_SPEC_EXCEPTIONS bleibt schmal (eine Datei, backfill-tender-source.ts); die zweite unerreichbare Datei (tenders.seed.ts, kein Relationszugriff) wird stattdessen als eigener Ledger-Eintrag #33 gefuehrt, nicht stillschweigend mit #27 mitgeschlossen" requirements-completed: [WINDOWS-27, ETAPPE-4-VORAUSSETZUNG] duration: ~45min completed: 2026-09-11 status: complete --- # Phase quick-260911-mkj: WINDOWS #27 schliessen — Relations-Blindstelle Summary **Vierte Erkennungsform in `rls-access-inventory.spec.ts` macht Relationszugriffe (`include:`/`select:`/`_count:`/Relationsfilter) sichtbar, die Prisma als Unterabfrage auf eine zweite Tabelle unter DEREN Regel rendert — Bestandsaufnahme waechst von 65 auf 72 Paare, WINDOWS #27 ist geschlossen.** ## Performance - **Duration:** ~45 min - **Completed:** 2026-09-11 ## Nachweis WINDOWS #27 **Zwischenmessung (Aufgabe 1, VOR dem Nachziehen der Bestandsaufnahme) — woertlich, wie im Plan verlangt:** ``` × jede im Quelltext gefundene (Datei, Modell)-Fundstelle ist im Dokument eingetragen Fehlende Eintraege im Dokument: apps/api/src/groups/module-grants.service.ts::module apps/api/src/ldap/ldap-config.service.ts::tenant apps/api/src/module-registry/module-access.service.ts::group apps/api/src/module-registry/module-access.service.ts::groupMembership apps/api/src/tenders/tender-digest.scheduler.ts::tender apps/api/src/tenders/tender-digest.scheduler.ts::tenderSavedSearch apps/api/src/tenders/tenders.controller.ts::tenderSource × der eingetragene Stand stimmt mit dem im Quelltext gemessenen ueberein Abweichender Stand (Dokument vs. Quelltext): apps/api/src/ldap/ldap-config.service.ts::ldapFieldMapping — dokumentiert=gebunden, gemessen=gemischt apps/api/src/module-registry/module-registry.service.ts::module — dokumentiert=ungebunden, gemessen=gemischt apps/api/src/tenders/tender-matching.service.ts::tender — dokumentiert=ungebunden, gemessen=gemischt Test Files 1 failed (1) Tests 2 failed | 22 passed (24) ``` Das ist EXAKT die zur Planungszeit gemessene Untergrenze: 7 fehlende Paare, 3 Stand-Abweichungen. Der Detektor SIEHT die Relationszugriffe, bevor das Dokument nachgezogen ist — der geforderte Beweis der Wirkung. **Zahlen (Aufgabe 1/2, aus der Ausgabe von `rls-access-inventory.spec.ts` und den Gates entnommen, nicht geschaetzt):** - 7 neue Paare, 3 fortgeschriebene Staende, davon 1 Klassenwechsel (`ldap-config.service.ts`/`ldapFieldMapping`: `muss-mandantengebunden` → `beides`) - 1 Waechter-(a)-Ausnahme (`RELATION_SPEC_EXCEPTIONS`: `apps/api/src/tenders/backfill-tender-source.ts`), 0 Waechter-(b)-Verstoesse - Bestandsaufnahme: 65 → **72 Paare** (35 muss-mandantengebunden, 21 keine-mandantengebundene-tabelle, 14 beides, 2 bewusst-uebergreifend — exakt die zur Planungszeit projizierte Verteilung) - Acht gepinnte Proben, darunter A (WINDOWS #27 ungebunden) und B (WINDOWS #27 gebunden, derselbe Aufruf auf einem `forTenant(`-Klienten) — beide bestanden - Gesamtsuite: **1007/1007 Tests, 62/62 Dateien gruen** (Baseline 994/62); Typpruefung sauber; `rls-scratch-check.mjs` **137/137** bestanden - WINDOWS-Ledger: #27 `fixed`; neuer Eintrag **#33** fuer die von allen vier Erkennungsformen unerreichbare Restmenge (`tenders/tenders.seed.ts`, `tenders/backfill-tender-source.ts`). Kopf nach diesem Lauf: `open_count: 14`, `total_count: 33`. ## Accomplishments - **Task 1** (`test(quick-260911-mkj): vierte Erkennungsform fuer Relationszugriffe, WINDOWS #27`, Commit `5ad23d0`): - `analyzeFile` in `analyzeSource(source, relPath)` herausgeloest (testbar gegen Probestrings) - `parseSchemaRelations()` liest `schema.prisma` zur Testzeit, `SCHEMA_RELATIONS` (Map Modell -> Map Feld -> Zielmodell), `CLIENT_NAME_TO_MODEL` fuer den Anker; Lookahead `(?=\s|$)` bewusst gesetzt (ohne ihn verliert die Feldzeilen-Regex jede Listenrelation am Zeilenende — der im Plan benannte erste Prototyp-Fehler, hier vermieden) - Vierte Erkennung: Ankerregex nur ueber bekannte Empfaenger (`this.prisma`, `boundNames`, Transaktionsparameter beider Formen), Argumentbereich per Klammertiefe auf einer string-geblankten Kopie (`blank`), Wertform-Aufloesung gegen gleichnamige Konstanten derselben Datei, Kontextstapel-Scan (`scanRelationKeys`) fuer verschachtelte Relationsfelder inkl. `_count: true` - Drei neue Waechter (Rohzahl vs. erkannte Modellaufrufe, Stale-Check `RELATION_SPEC_EXCEPTIONS`, `unresolvedRelationSpecValues` leer), zwei Schema-Tests, acht gepinnte Proben — alle 24 Tests der Spec-Datei gruen - Bestandsaufnahme fortgeschrieben: sieben neue Zeilen, drei geaenderte Staende, Kopfabsatz "Erkennungsluecke GESCHLOSSEN" ersetzt den alten "seit 260911-e2s vermessen"-Absatz - **Task 2** (`docs(quick-260911-mkj): Abschnitte abgeleitet, WINDOWS #27 geschlossen, Restmenge als eigener Eintrag`, Commit `388690f`): - Klassifikationsdokument: Uebersichtsabsatz ("Zweite methodische Luecke ... geschlossen"), Klassen-Verteilung (Ueberschrift + Summenzeile + Tabelle, aus der Bestandsaufnahme GEZAEHLT: 35/21/14/2/72), Hintergrunddienst-Nachtrag zum ersten Fall (`getAllActiveConfigs()` reicht auch in `LdapFieldMapping` hinein — kein neuer Fall, Ueberschrift bleibt "sechs Faelle") - Kritikschrift: `Nachtrag (260911-mkj)` unter `(n4)` im `## Bereich tenant` (verweist auf Befund G/(b) und die Restmenge), Vermerk im `## Etappe 2 — Abschluss`, dass #27 geschlossen ist - Ledger: neuer Eintrag `#33` angelegt (Ledger fuer die Restmenge), dann `#27` auf `fixed` gesetzt, dann der `WINDOWS #TBD-MKJ`-Platzhalter im Spec-Kommentar durch `WINDOWS #33` ersetzt ## Deviations from Plan ### Auto-fixed Issues None — plan executed exactly as specified for the actual detector/document logic. ### Documented Drift (nicht auto-fixed, nicht Teil dieses Plans) **1. [Vorbestehende Abweichung, nicht dieser Plan] `docs/mandantentrennung-etappe3-auftrag.md` und Teile von `.planning/HANDOFF.json` weichen bereits VOR Beginn dieser Ausfuehrung von der Basis `cc26197` ab.** - **Gefunden bei:** dem Allowlist-Diff-Gate in Aufgabe 1 (`git diff --name-only cc26197`) - **Ursache:** Commit `926359b` ("docs: Auftrag fuer Etappe 3 der Mandantentrennung, gemessen statt erinnert", Autor Schalli) landete auf `main`, BEVOR diese Ausfuehrung begann — die im Auftrag genannte Basis-HEAD `261e736` war zu diesem Zeitpunkt bereits um einen fremden Commit veraltet. - **Pruefung:** `git show --stat 926359b` zeigt ausschliesslich `docs/mandantentrennung-etappe3-auftrag.md` (neu) und `.planning/HANDOFF.json` — nichts unter `apps/api/src`, kein Schema, keine Migration, keine Compose-/Umgebungsdatei. Ausserhalb des Geltungsbereichs dieses Plans, nicht angefasst. - **Auswirkung auf die Verify-Gates:** das im Plan definierte Allowlist-Gate (`git diff --name-only cc26197` gegen eine feste Dateiliste) meldet `docs/mandantentrennung-etappe3-auftrag.md` als "unerwartete Aenderung", weil es diesen fremden, bereits gemergten Commit mitzaehlt. Die tatsaechlich von dieser Ausfuehrung veraenderten Dateien sind ausschliesslich die vier oben genannten (bestaetigt durch `git status --short` vor jedem Commit und durch die beiden Commits `5ad23d0`/`388690f` selbst — je genau die im Plan als Deliverables genannten Dateien, keine mehr). - **Nicht behoben, weil:** ausserhalb der Erlaubnisliste dieses Plans (nur die Spec-Datei, `apps/api/prisma/**`, zwei benannte Dokumente, `.planning/`) — ein fremder, bereits abgeschlossener Commit gehoert nicht in diesen Auftrag. ## Known Stubs None — dieser Plan aendert ausschliesslich Testcode und Dokumentation, keine UI/keine Laufzeitpfade. ## Threat Flags None — die Aenderungen liegen vollstaendig innerhalb des im Plan definierten `threat_model` (T-MKJ-01 bis T-MKJ-05), keine neue Angriffsflaeche ausserhalb dessen. ## BEFUND — nicht behoben Keiner. Die Zwischenmessung lieferte exakt 7 fehlende Paare / 3 Stand-Abweichungen (die geplante Untergrenze, keine zusaetzlichen ungenannten Funde); jedes der sieben neuen Paare wurde einzeln am Quelltext geprueft (siehe Zeilenverweise in der Bestandsaufnahme-Tabelle) und ist entweder `keine-mandantengebundene-tabelle` (Elternbindung wirkungslos, nicht schaedlich) oder `muss-mandantengebunden`/`gebunden` (Relationsfilter auf einem bereits gebundenen Klienten in eine Tabelle desselben Mandanten). Keine ungebundene Einbindung in eine geschuetzte Tabelle auf ungeschuetztem Elternpfad, die nicht bereits als Hintergrunddienst-Fall benannt waere. ## Self-Check: PASSED - `apps/api/src/prisma/rls-access-inventory.spec.ts`: FOUND, enthaelt `analyzeSource`, `parseSchemaRelations`, `SCHEMA_RELATIONS`, `RELATION_SPEC_EXCEPTIONS`, 24 `it(`-Bloecke, alle gruen - `docs/mandantentrennung-zugriffsklassifikation.md`: FOUND, 72 (Datei, Modell)-Paare, Klassen-Verteilung 35/21/14/2 - `docs/mandantentrennung-etappe2-fehlerrichtung.md`: FOUND, `Nachtrag (260911-mkj)` unter `(n4)` und im Abschluss-Abschnitt - `.planning/WINDOWS.md`: FOUND, `#27` Status `fixed`, `#33` neu mit Status `open`, Kopfzaehler (`open_count: 14`, `total_count: 33`) konsistent mit den Tabellenzeilen - Commit `5ad23d0`: FOUND in `git log --oneline --all` - Commit `388690f`: FOUND in `git log --oneline --all` - Baseline: 1007/1007 Tests, 62/62 Dateien gruen; `npm --prefix apps/api run type-check` sauber; `rls-scratch-check.mjs` 137/137 bestanden - Schalter aus, kein Dienstcode, kein Schema/Migration, keine Compose-/Umgebungsdatei veraendert (bestaetigt per `git status --short` vor jedem Commit)