Commit Graph

876 Commits

Author SHA1 Message Date
schalli 6744918a01 feat(quick-260910-krx): Fehlerrichtung fuer Bereich dashboard gemessen
Aufgabe 1 — misst die umgekehrte Fehlerrichtung des Bereichs dashboard an
den Regeln nach Migration 20260910120000, VOR der Umstellung:

- rls-scratch-check.mjs bekommt einen neunten Abschnitt
  (runDashboardAreaChecks) mit 13 neuen, namentlich benannten Pruefungen
  gegen die aus der ausgelieferten Migration geschnittenen Regeln fuer
  DashboardLayout, WidgetInstance und SearchProvider. Alle 87 Pruefungen
  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. Zusaetzlich am
  echten generierten Prisma Client gemessen: prisma.dashboardLayout.upsert()
  wirft PrismaClientUnknownRequestError (nicht P2002) — das tenders-Muster
  laesst sich deshalb nicht woertlich uebernehmen.
- Die widerlegte Praemisse zu SearchProvider (WINDOWS #19) ist in diesem
  Durchlauf eigenstaendig nachgeprueft, mit benannter Suchreichweite.
- docs/mandantentrennung-etappe2-fehlerrichtung.md bekommt den Abschnitt
  "## Bereich dashboard" (w1)-(w5) samt der beweisvernichtenden Schleife
  (leeres Dashboard -> Neuaufbau -> automatisches Zurueckschreiben ->
  ueberschriebene Anordnung) und einen Nachtrag im Abschnitt
  "## Bereich module-registry" zur geerbten Bindungsentlastung.

Baseline gehalten: 839 Tests / 56 Dateien gruen, Typpruefung sauber.
Schalter bleibt aus.
2026-09-11 08:46:46 +02:00
schalli 89fb02797a docs: zwei Produktentscheidungen des Users fuer Etappe 3 festgehalten
Tessera CI/CD / Lint & Type Check (push) Successful in 50s
Tessera CI/CD / Tests (push) Successful in 56s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m9s
2026-09-11 08:32:41 +02:00
schalli c7d93f235c docs(quick-260910-krx): Plan fuer Etappe 2, Bereich dashboard
Dreizehn Zugriffe, alle in dashboard.service.ts: zwoelf werden gebunden,
einer (der plattformweite Modulkatalog) bleibt begruendet ungebunden.

Zur Planungszeit gemessen statt angenommen:
- Die Zahl 13 haelt beim Hineinschauen.
- Die Besitzpruefungen sind ECHT (Pruefung auf die Benutzerkennung nach
  dem Laden), anders als die gleich geformte Luecke im Bereich ldap.
- Die widerlegte Praemisse zu SearchProvider haelt einer eigenstaendigen
  Suche stand; die Reichweite der Suche ist benannt, damit sie
  widerlegbar bleibt.
- Die Modul-Zugriffsaufloesung ist bereits gebunden — die geerbte
  Entlastung wird nachgeprueft, nicht ein zweites Mal repariert.
- Alle drei Regeln kennen keine Benutzerdimension; die
  anwendungsseitigen Pruefungen bleiben der einzige Quer-Lese-Schutz.
- Die beweisvernichtende Schleife ist an den beiden Web-Dateien belegt:
  leeres Dashboard, Neuaufbau, automatisches Zurueckschreiben beim
  Verlassen des Bearbeitungsmodus, ueberschriebene Aufzeichnung.

Umfang als Erlaubnisliste gegen den Ausgangsstand f205120 gegatet statt
als Verbotsliste; die Zaehlgates der Klassifikation leiten ihre Werte aus
den dokumenteigenen Messanweisungen ab.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 15:16:58 +02:00
schalli f2051202d8 docs(quick-260910-jab): drei Datenbankregeln geschlossen und verifiziert 2026-09-10 14:57:20 +02:00
schalli 03fb3bf9c7 docs(quick-260910-jab): Aktenstand kohaerent machen — Ledger, Klassifikation, Kritikschrift, Betriebsanleitung
- WINDOWS.md: #19 auf fixed gesetzt (Tabelle + JSON-Block), mit Beleg
  (Migrationsname + benannte Pruefungen) und ausdruecklicher Feststellung,
  dass die SearchProvider-Haelfte als widerlegte Praemisse schliesst, nicht
  als geloestes Problem. #18/#20/#21/#22/#23 bleiben unveraendert offen. Ein
  neuer Eintrag #24 haelt den fehlenden Verwaltungsweg fuer plattformweite
  Zeilen unter der Anwendungsrolle offen (verschwindet nicht mit #19). Die
  vier Kopfzahlen sind aus dem JSON-Block abgeleitet (6 offen, 17 behoben, 1
  zurueckgestellt, 24 gesamt).
- Klassifikation: #19-Block von offener Frage zu beantwortet, die
  Uebersichtszeile tenders (35/27) und Summenzeile (107/135) aus dem
  Quelltext neu abgeleitet, vier Bestandsaufnahme-Zeilen nachgezogen
  (searchProvider, groups.service.ts/user, module-grants.service.ts/
  moduleGrant, tender-rss-feed.service.ts/tenderRssFeedSource), der Punkt in
  "Was diese Etappe NICHT entscheidet" aufgeloest.
- Kritikschrift: neuer Abschnitt "Regelschluss T-JTS-02, T-JTS-03 und
  WINDOWS #19" mit tatsaechlich beobachteter Ausgabe, Regelliste der
  lebenden Datenbank, Signaltabelle mit beiden Fehlerrichtungen je Regel,
  der neuen Stelle aus Befund F, der selbst ausgefuehrten Messung zur
  fehlenden Benutzerdimension (keine zweite Sitzungsvariable gefunden) und
  dem, was dieser Durchlauf nicht loest/nicht anfasst. Fuenf ueberholte
  Bestandsstellen mit Nachtraegen versehen (g4, t4, die Signaltabellenzeile
  zu den RSS-Pfaden, drei aufgezeichnete Werkzeugausgaben, m4), die alten
  Messprotokolle bleiben woertlich stehen.
- Betriebsanleitung: die eine Stelle, die #19 als offen fuehrte, nennt jetzt
  den Aufloesungsstand und WINDOWS #24.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 14:44:34 +02:00
schalli 6b237351e9 feat(quick-260910-jab): listForUser binden, die vier Aufzeichnungen im Quelltext richtigstellen
- TenderRssFeedSourceService.listForUser() nimmt jetzt (userId, tenantId)
  entgegen und laeuft ueber einen gebundenen Klienten (forTenant) — die neue
  Leseregel schliesst plattformweite Zeilen ein, die Reparatur haette den
  ungebundenen Pfad sonst still auf nur die plattformweiten Zeilen reduziert
  (Befund F). createPlatform/remove bleiben bewusst ungebunden, Kommentare an
  der neuen Regel richtiggestellt.
- TendersController.listRssFeeds reicht die Mandantenkennung aus dem
  Aufrufzusammenhang durch.
- Vier Aufzeichnungen im Quelltext (module-access.service.ts,
  groups.service.ts, module-grants.service.ts, rls-coverage.spec.ts) sagen
  jetzt, dass die Datenbankregel seit 20260910120000_rls_widen_membership_
  grant_and_platform_read beide Seiten prueft; die Anwendungspruefungen
  bleiben unveraendert bestehen (zweites Netz, wirkt vor dem Scharfschalten
  als einziger Schutz).
- Zwei-Klienten-Nachweis in module-grants.service.spec.ts ergaenzt (Kommentar,
  warum die beiden Cross-Tenant-Tests nach der Regelaenderung nicht entfallen
  duerfen) und in tender-rss-feed.service.spec.ts umgekehrt (listForUser
  bindet jetzt).
- Rule 1: implizites any beim Destrukturieren in listRssFeeds (feeds ist seit
  der Bindung `any`) mit expliziter Annotation behoben.
- Falsifizierungsnachweis durchgefuehrt: Bindungsaufruf zurueckgenommen,
  genau ein Test wurde rot (AssertionError, 0 statt der erwarteten
  Aufrufe), Ruecknahme rueckgaengig gemacht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 14:35:13 +02:00
schalli f4f3115d5a feat(quick-260910-jab): drei zu kurz greifende RLS-Regeln schliessen (T-JTS-02, T-JTS-03, WINDOWS #19)
- Neue, handgeschriebene Migration 20260910120000_rls_widen_membership_grant_and_platform_read:
  GroupMembership prueft jetzt beide Seiten der Beziehung (Gruppe UND Benutzer),
  ModuleGrant prueft zusaetzlich die referenzierte Gruppe/den referenzierten Benutzer
  (mit Leer-Zulassung, D-04), TenderRssFeedSource bekommt vier nach Befehl getrennte
  Regeln statt einer (Lesen schliesst plattformweite Zeilen ein, Schreiben verlangt
  weiterhin einen Mandanten). SearchProvider bewusst unveraendert (Befund E: Praemisse
  widerlegt). Lokal angewandt und gegen den Systemkatalog der lebenden Datenbank
  gemessen. Der Schalter bleibt aus (Rolle tessera).
- rls-scratch-check.mjs: die drei loch-behauptenden Pruefungen umgekehrt (nicht
  geloescht), Gegenmessungen ueber die Wartungsrolle ergaenzt, vier Befehlsrichtungen
  fuer TenderRssFeedSource gemessen, neuer Abschnitt fuer SearchProvider, Extraktion
  auf die neue Migration umgeleitet und um eine mehrfach-treffer-faehige Form ergaenzt
  (extractAllPolicySql).
- migration-sql.spec.ts: neuer Beschreibungsblock fuer die neue Migration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 14:29:25 +02:00
schalli 93444aa91e docs(quick-260910-jab): Plan fuer die drei zu kurz greifenden Datenbankregeln
Schliesst T-JTS-02, T-JTS-03 und WINDOWS #19 in einer handgeschriebenen
Migration. Drei zur Planungszeit gemessene Funde praegen den Zuschnitt:
eine DRITTE loch-behauptende Pruefung im Wegwerf-Werkzeug (nicht zwei), eine
Zeile, auf der zwei spaetere Pruefungen aufsetzen und die nach der Reparatur
nicht mehr entsteht, und genau ein Anwendungspfad, den die Reparatur still
falsch machen wuerde.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 14:11:39 +02:00
schalli 48430589e7 docs(quick-260910-exd): Etappe 2 Bereich module-registry abgeschlossen und verifiziert 2026-09-10 11:39:46 +02:00
schalli 9c0eefee90 feat(quick-260910-exd): ModuleRegistryService binden, Klassifikation abschliessen
- module-registry.service.ts: findActiveForTenant, activateForTenant,
  isModuleActive binden je einen Aktivierungszugriff, deactivateForTenant
  bindet beide (Lesen+Schreiben) ueber EINEN Klienten unter tenantPrisma;
  alle sechs Katalogzugriffe (findAll/findBySlug/beide
  Existenzpruefungen/isModuleActive-Katalogsuche/seedModule) bleiben
  bewusst ungebunden, mit Kommentar der Messung von Bedingung trennt
- isModuleActive-Kopfkommentar richtiggestellt: der Waechter ruft sie
  nicht auf (0 Aufrufer, TEIL 3 von Aufgabe 1) — Waechter nimmt findBySlug
  + ModuleAccessService.getAccessibleModuleIds
- module-registry.service.spec.ts: NEU, Zwei-Klienten-Nachweis, deckt die
  bislang ungetestete Datei mit elf der siebzehn Zugriffe des Bereichs ab,
  inkl. der lauten (deactivate ohne Aktivierung) und stillen (isModuleActive
  ohne Aktivierung) Richtung und dem Katalog-Wachhund
- tender-scheduler.service.spec.ts: forTenant() auf Identitaet gemockt
  (dieselbe Konvention wie ldap.service.spec.ts) — cross-area Bruch durch
  die Umstellung von activateForTenant behoben (Rule 1/3)
- docs/mandantentrennung-zugriffsklassifikation.md: alle fuenf
  handgepflegten Stellen nachgezogen (Bestandsaufnahme, Uebersichtszeile
  7/10, Summenzeile 108/134, Klassen-Verteilung unveraendert bei 63 Paaren,
  Hintergrunddienst-Abschnitt haelt die Abwesenheit eines sechsten Falls
  fest) — alle gemessen, nicht abgeschrieben, Befund K haelt exakt
- docs/mandantentrennung-etappe2-fehlerrichtung.md: Nachtrag mit
  tatsaechlich umgesetzten Pfaden, beiden Falsifizierungsnachweisen
  (Testname+Meldung), und der Feststellung zum unveraenderten
  Controller-Kommentar
- .planning/WINDOWS.md: neuer offener Eintrag #23 (deviation) — kein Signal
  unterscheidet "keine Freigabe" von "Abfrage fand nichts", mit
  Vorabpruefung fuer Etappe 4 und begruendeter Verwerfung einer
  Laufzeitwarnung
- 833 Tests gruen (56 Dateien), Typpruefung sauber, Wegwerf-Werkzeug 66/66

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 11:30:37 +02:00
schalli 3df72687c1 feat(quick-260910-exd): ModuleAccessService an forTenant() binden
- module-access.service.ts: getAccessibleModuleIds (Kurzschlusszweig,
  Direktweg, Gruppenweg, Schnittmenge) und getCatalogFlags' eigener
  Aktivierungs-Lesezugriff laufen ueber forTenant(), EIN Klient je Methode
  unter dem Namen tenantPrisma; der Katalogzugriff in findAccessibleModules
  bleibt bewusst ungebunden (Modulkatalog traegt keine Regel), mit Kommentar
  der Messung und Bedingung trennt
- Bestehende where-Filter mit tenantId bleiben als zweites Netz stehen
- module-access.service.spec.ts: Zwei-Klienten-Nachweis ueber
  __makeBoundClient (Muster aus module-grants.service.spec.ts), alle 15
  bestehenden Faelle erhalten, neue Faelle fuer jede in <behavior> genannte
  Bindungseigenschaft inkl. Wachhund gegen eine kuenftige Katalogbindung
- module.guard.spec.ts: ein Fall, der die Abwesenheit eines
  unterscheidenden Signals fuer "keine Freigabe" vs. "Abfrage fand nichts"
  festnagelt
- Falsifizierungsnachweis durchgefuehrt: Gruppenweg-Bindung probeweise
  zurueckgebaut, Test "USER-Zweig bindet BEIDE Freigabe-Lesezugriffe..."
  wurde rot ("expected 1 to be 2"), Ruecknahme bestaetigt wieder gruen
- mandantentrennung-zugriffsklassifikation.md: Stand fuer
  module-access.service.ts/moduleGrant und /tenantModuleActivation auf
  gebunden nachgezogen (Rule 3 — sonst waere rls-access-inventory.spec.ts
  rot geblieben); die uebrigen vier Bestandsaufnahme-Stellen bleiben
  Aufgabe 3 vorbehalten
- 817 Tests gruen (55 Dateien), Typpruefung sauber, Wegwerf-Werkzeug 66/66

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 11:22:32 +02:00
schalli 7d45e2fffd test(quick-260910-exd): Fehlerrichtung fuer module-registry messen, kein Produktivcode
- rls-scratch-check.mjs: achter Abschnitt runModuleRegistryAreaChecks mit 13
  benannten Pruefungen gegen die echten, aus den ausgelieferten Migrationen
  geschnittenen Regeln (Group/GroupMembership/ModuleGrant/TenantModuleActivation),
  neu angelegt nur: Modulkatalog-Tabelle ohne Zeilenschutz, Eindeutigkeitsindex
  auf TenantModuleActivation, zwei Direkt-Freigaben, eine fremde Mitgliedschaft
- Alle 66 Pruefungen bestanden (53 bisherige + 13 neue), 810 Tests gruen,
  Typpruefung sauber
- docs/mandantentrennung-etappe2-fehlerrichtung.md: neuer Abschnitt
  "Bereich module-registry" mit den fuenf Unterabschnitten (m1-m5), inklusive
  Praezisierung aus Befund E (Katalogbindung ist HEUTE wirkungslos, nicht
  katastrophal — die Bedingung wird als Bedingung notiert) und der
  unbeschoenigten Antwort auf die Signalfrage ("keines")

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 11:17:07 +02:00
schalli a2516a9852 docs(quick-260910-exd): Plan fuer Etappe 2, Bereich module-registry 2026-09-10 11:06:58 +02:00
schalli f657e24007 docs(quick-260910-das): Etappe 2 Bereich user abgeschlossen und verifiziert 2026-09-10 10:44:44 +02:00
schalli 3a9391d9c8 feat(quick-260910-das): Steuerungsschicht binden, Selbstloesch-Riegel schliessen
- user.controller.ts: alle sieben Zugriffe binden. ADMIN-Zweig der
  Benutzerliste laeuft ueber forTenant() mit weiterhin bestehender
  Mandantenbedingung im where; SUPER_ADMIN-Zweig ueber die neue
  UserService.findAllForPlatformAdmin(). Die drei Wege ueber die Kennung
  loesen den Zielbenutzer rollenabhaengig ueber resolveTargetUser() auf
  (ADMIN gebunden an eigenen Mandanten, SUPER_ADMIN uebergreifend); der
  Schreibzugriff bei update/delete bindet an den Mandanten des
  Zielbenutzers, nicht des Aufrufers, damit die uebergreifende
  Verwaltung durch die oberste Rolle erhalten bleibt
- Selbstloesch-Riegel (Befund H) repariert: verglich bisher gegen
  currentUser.sub, ein Feld, das der Sitzungsnachweis nicht traegt --
  der Riegel griff nie. Jetzt gegen currentUser.id. Verhaltensaenderung:
  ein Administrator kann sein eigenes Konto nun nicht mehr loeschen
- Alle fuenf Selbstbedienungszugriffe (Bild hochladen/loeschen/
  ausliefern, Akzentfarbe) binden an die Mandantenkennung aus dem
  Sitzungsnachweis
- user.controller.spec.ts (neu): Zwei-Klienten-Nachweis fuer die
  vorher testlose Steuerungsschicht, 8 Testfaelle, Falsifizierungsnachweis
  fuer eine gebundene Stelle sowie Rot-vor-Reparatur-Nachweis fuer den
  Selbstloesch-Riegel (siehe SUMMARY)
- docs/mandantentrennung-zugriffsklassifikation.md: alle vier
  handgepflegten Stellen nachgezogen (Uebersichtszeile 8/14, Summenzeile
  118/124, Klassen-Verteilung 63 Paare, Hintergrunddienst-Abschnitt auf
  fuenf Faelle inkl. admin-seed.service.ts als erster beidseitig
  korrekter Fall) sowie zwei Klassenkorrekturen (user.service.ts/user
  und admin-seed.service.ts/user je auf "beides")
- docs/mandantentrennung-etappe2-fehlerrichtung.md: Nachtrag zum
  user-Abschnitt mit den tatsaechlich umgesetzten Pfaden, der
  geschlossenen Luecke und den Falsifizierungsnachweisen
- 810 Tests gruen (8 neue in user.controller.spec.ts), Typpruefung
  sauber, Wegwerf-Werkzeug meldet weiterhin alle 53 Pruefungen bestanden

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 10:35:43 +02:00
schalli 888f66003c feat(quick-260910-das): user-Dienst binden, Linie ziehen, Startsperre entschaerfen
- user.service.ts: findById/update/deactivate/delete bekommen einen
  Pflicht-Mandanten und laufen ueber forTenant(); create/update
  uebersetzen die plattformweite Eindeutigkeitsverletzung (P2002) in eine
  deutsche Konfliktmeldung ohne Halter/Mandant zu nennen; zwei neue
  Methoden (findAllForPlatformAdmin, findByIdForPlatformAdmin) bilden die
  Plattform-Administratorsicht als Schleife ueber alle Mandanten mit je
  einem gebundenen Lesezugriff nach; findByUsername bleibt bewusst
  ungebunden, Kopfkommentar richtiggestellt (Anmeldeweg laeuft seit
  Etappe 1 ueber SECURITY-DEFINER-Funktionen, kein Aufrufer mehr)
- admin-seed.service.ts: Erstanlage-Pruefung bleibt ungebunden (mit
  Begruendung), Erstanlage des Administrators bindet an den unmittelbar
  zuvor angelegten Mandanten (Befund J-Korrektur); P2002 bei der Anlage
  wird wie "Administrator existiert bereits" behandelt statt den Start
  abzubrechen -- jeder andere Fehler bricht weiterhin ab
- user.controller.ts: die vier Aufrufstellen der geaenderten Signaturen
  auf currentUser.tenantId umgestellt (Signatur-Minimalanpassung; die
  Rollenlogik inkl. Plattform-Administratorsicht folgt in Aufgabe 3)
- Zwei-Klienten-Nachweis in beiden Testdateien (Muster
  groups.service.spec.ts), Falsifizierungsnachweis fuer beide Bereiche
  durchgefuehrt und zurueckgenommen (siehe SUMMARY)
- docs/mandantentrennung-zugriffsklassifikation.md: Zwischenstand fuer
  (user.service.ts, user) und (admin-seed.service.ts, user) auf gemischt
  korrigiert, neue Zeile (user.service.ts, tenant) ergaenzt -- volle
  Klassenkorrektur mit Begruendung sowie die vier handgepflegten
  Uebersichtstabellen folgen in Aufgabe 3
- .planning/WINDOWS.md: offener Eintrag fuer die plattformweite
  Eindeutigkeit von username/email (Produktentscheidung fuer Etappe 3)
- 802 Tests gruen (13 neue in user.service.spec.ts, 4 neue in
  admin-seed.service.spec.ts), Typpruefung sauber, Wegwerf-Werkzeug
  meldet weiterhin alle 53 Pruefungen bestanden

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 10:27:14 +02:00
schalli b848ba6baa feat(quick-260910-das): messen die Kette user unsichtbar->frei->Eindeutigkeitsfehler
- runUserAreaChecks in rls-scratch-check.mjs: 12 neue Pruefungen gegen die
  ausgelieferte User-Policy (baut auf der vom Anmeldeweg-Abschnitt
  angelegten Tabelle auf, legt zusaetzlich Tenant ohne Zeilenschutz an)
- Belegt: ungebundene Suche nach vorhandenem Benutzernamen liefert 0
  Zeilen, gebundene Suche nach fremdem Benutzernamen ebenso ("frei"), und
  das anschliessende gebundene INSERT scheitert hart an SQLSTATE 23505
  (Eindeutigkeitsverletzung), nicht an 42501 (Zeilenschutz)
- SQLSTATE wird aus err.meta.code gelesen, nicht err.code (das bei
  $executeRaw-Fehlern immer den generischen Prisma-Code P2010 traegt,
  empirisch gegen tessera-ctl-db-1 geprueft)
- docs/mandantentrennung-etappe2-fehlerrichtung.md: neuer Abschnitt
  "Bereich user" (u1-u5) mit der tatsaechlich beobachteten Ausgabe,
  Signaltabelle, der vollstaendigen Kette (Befund L) und den Grenzen zu
  auth.service.ts/ldap.service.ts
- Teil 2/3 gemessen: keine Transaktion in apps/api/src/user (Befund B
  haelt), findByUsername hat genau einen Treffer, die eigene Definition
  (Befund D haelt)
- 789 Tests weiterhin gruen, Typpruefung sauber, Wegwerf-Werkzeug meldet
  alle 53 Pruefungen bestanden (41 bisherige + 12 neue)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 10:16:12 +02:00
schalli 7e7a697e3c docs(quick-260910-das): Benennungsauflage auch in Aufgabe 2 nennen 2026-09-10 10:06:26 +02:00
schalli 4fdd6eed34 docs(quick-260910-das): Plan-Revision — alle vier handgepflegten Dokumentstellen maschinell gegatet
Der Plan benannte vier handgepflegte Stellen als die, die im dkv-Durchlauf
uebersehen wurden, gatete aber nur zwei davon. Ein Ausfuehrender haette Aufgabe 3
abschliessen, jede Pruefung bestehen und Uebersichtszeile, Summenzeile und
Klassen-Verteilung stehen lassen koennen — derselbe Fehler, gegen den der Plan
schuetzen sollte, im Gate des Plans selbst.

Die beiden fehlenden Pruefungen leiten ihre Erwartung ab, statt sie zu raten:
die Uebersichtszeile gegen die am Quelltext neu ermittelten Rohtrefferzahlen
(mit den beiden Befehlen, die das Dokument selbst nennt), die Summenzeile gegen
die Addition der zwoelf Bereichszeilen, die Klassen-Verteilung gegen die ueber
die Bestandsaufnahme nachgezaehlten Klassen samt Summe und Ueberschrift.

Falsifiziert statt behauptet: gruen gegen das heutige, in sich stimmige Dokument;
rot gegen den heutigen unumgestellten Baum; und rot in vier getrennten Mutationen,
je eine stehen gelassene Handstelle — Uebersichtszeile, Summenzeile, Klassenzahl,
Ueberschrift. Gruen erst, wenn Code umgestellt UND alle vier nachgezogen sind.

Zusatzauflage, die daraus folgt: der gebundene Klient heisst in jeder Methode
tenantPrisma (Konvention aus ldap/groups/dkv/auth) — ein anderer Name liesse die
Gebunden-Zaehlung untertreiben und die Zeile ihrer Aussage berauben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 10:02:39 +02:00
schalli 5f6088cc11 docs(quick-260910-das): Plan fuer Etappe 2, Bereich user
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 09:53:42 +02:00
schalli ccb5996428 docs(quick-260909-mir): Etappe 2 Bereich dkv abgeschlossen, zwei Doku-Luecken behoben 2026-09-10 09:01:03 +02:00
schalli 5e8237d313 feat(quick-260909-mir): dkv-Historie und Fahrzeugstammdaten binden, Download-Besitzriegel schliessen 2026-09-10 08:48:54 +02:00
schalli 222f453747 feat(quick-260909-mir): dkv-Testlage herstellen, Konfigurationspfade binden, Planer-Pfad benennen
- apps/api/src/dkv/dkv.service.spec.ts (neu): Zwei-Klienten-Nachweis nach
  dem Muster aus groups.service.spec.ts/tender-triage.service.spec.ts —
  dieser Bereich hatte vorher KEINE Testdatei (Befund J). 7 Testfaelle
  decken getConfigForApi, saveConfig (Zugangsdaten-Erhaltung), testConnection,
  die Verarbeitungsstrecke und den bewusst ungebundenen Planer-Startpfad ab
- dkv.service.ts: loadConfig(tenantId?) in zwei Methoden geteilt —
  loadConfig(tenantId) [Pflicht-Mandant, gebunden] und die neue, eigene
  Methode loadAnyActiveConfigForScheduler() [bewusst UNGEBUNDEN, eigener
  Kopfkommentar mit beiden Zustaenden]. getConfigForApi/saveConfig/
  testConnection/_runPipeline binden je EINEN Klienten pro Methode
  vollstaendig ueber forTenant()
- dkv-scheduler.service.ts: Kopfkommentar fortgeschrieben (beide Zustaende,
  Praezedenzfall, Unsymmetrie), Aufruf auf loadAnyActiveConfigForScheduler()
  umgestellt — an der Ablauflogik des Planers nichts geaendert
- .planning/WINDOWS.md: Eintrag #21 (deviation) fuer die benannte Altlast
  des Planer-Startpfads angelegt
- docs/mandantentrennung-zugriffsklassifikation.md: dkvModuleConfig-Zeile
  auf den jetzt gemessenen Stand "gemischt" nachgezogen (Rule 3 — noetig,
  damit rls-access-inventory.spec.ts nach der Aufteilung von loadConfig()
  gruen bleibt; die uebrigen zwei dkv-Zeilen und die Uebersichtstabelle
  bleiben Aufgabe 3 vorbehalten)
- Falsifizierungsnachweis erbracht: getConfigForApi's erster gebundener
  Client probeweise durch this.prisma ersetzt, genau Test 1 wurde rot
  (6 andere blieben gruen), Rueckbau zurueckgenommen, Dateien identisch
  zum Ausgangsstand bestaetigt

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 16:46:09 +02:00
schalli 761e5e2c36 feat(quick-260909-mir): dkv-Fehlerform messen und Kritikschrift erweitern
- rls-scratch-check.mjs: runDkvAreaChecks() misst die drei ausgelieferten
  Policies (DkvInvoiceHistory/DkvModuleConfig/DkvVehicleMaster) wortgleich
  aus der Migration, plus die Nebenlaeufigkeitsform von getHistory()
  (Promise.all ueber zwei gebundene Einzelabfragen); alle 9 neuen plus
  32 bestehende Pruefungen bestehen (41 gesamt)
- Belegt Befund H (kein P2002-Fall, Mandant ist Teil des zusammengesetzten
  Schluessels) und Befund I (gebundenes INSERT mit fremder tenantId wird
  ohne eigene WITH-CHECK-Klausel trotzdem abgewiesen) an der echten
  Datenbank statt am Policy-Text
- docs/mandantentrennung-etappe2-fehlerrichtung.md: neuer Abschnitt
  "Bereich dkv" mit der dritten Fehlerform der Etappe (ein Einzelobjekt
  wird null, wo null bereits "nicht eingerichtet" bedeutet), der
  Signaltabelle je umzustellendem Pfad, den sieben Stellen aus Befund K
  (zerstoerend/lautlos/irrefuehrend) und der ausgeschriebenen
  Planer-Entscheidung (Form c, mit Unsymmetrie zum ldap-Praezedenzfall)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 16:38:57 +02:00
schalli 748f0b513e docs(quick-260909-mir): Plan fuer Etappe 2, Bereich dkv 2026-09-09 16:27:47 +02:00
schalli 6464ccb821 docs(quick-260909-laa): Etappe 2 Bereich tenders abgeschlossen, Luecke behoben 2026-09-09 16:12:49 +02:00
schalli 8cbf4c12b8 fix(quick-260909-laa): dritte Eindeutigkeitsverletzung uebersetzen statt sie zu behaupten 2026-09-09 16:12:08 +02:00
schalli df5c5b728b feat(laa-03): binde die Je-Treffer-Haelften der Hintergrunddienste und schliesse die Dokumente
tender-digest.scheduler.ts: die uebergreifende Kandidatenabfrage
(tenderMatch.findMany mit distinct:['userId']) bleibt bewusst ungebunden
und waehlt zusaetzlich das denormalisierte tenantId der Treffer-Zeile mit
aus; innerhalb der Schleife binden tenderNotificationPref.findUnique,
tenderMatch.findMany/updateMany und user.findUnique an den Mandanten
DIESER Kandidatenzeile.

tender-matching.service.ts: die Profilabfrage (tenderSavedSearch.findMany)
und der Lesezugriff auf den plattformweiten Tender-Katalog (D-03) bleiben
ungebunden; innerhalb der Profilschleife bindet die Treffer-Anlage
(tenderMatch.upsert), im nachgelagerten Instant-Dispatch binden
tenderMatch.findMany/updateMany und user.findUnique — je EIN gebundener
Client pro Profil, nicht neu je Treffer.

Beide Dateien tragen Codekommentare, die die uebergreifenden Abfragen
ausdruecklich als Etappe-3-Uebergabe benennen — die Trennlinie zwischen
Etappe 2 und Etappe 3 wird hier gezogen, nicht verwischt.

Alle drei betroffenen Testdateien (inkl. der gemeinsamen
Integrationsdatei) bekommen den Zwei-Client-Nachweis, Tests fuer
Zwei-Mandanten-Laeufe und die lautlose Fehlerform (kein Versand,
notifiedAt bleibt NULL). Falsifiziert: ein probeweiser Rueckbau der
user.findUnique-Bindung im Instant-Dispatch von tender-matching.service.ts
machte genau den erwarteten Test rot, danach zurueckgenommen.

rls-access-inventory.spec.ts gemessen und docs/mandantentrennung-zugriffsklassifikation.md
nachgezogen: Stand der fuenf betroffenen Paare (tender-digest.scheduler.ts/
tenderMatch,tenderNotificationPref,user; tender-matching.service.ts/
tenderMatch,user) auf gemischt bzw. gebunden; Bereichsuebersicht und
Klassen-Verteilung neu gemessen (tenders jetzt 36 ungebunden/26 gebunden);
"Der Hintergrunddienst als Falle" um den Abschluss beider Dateien ergaenzt.

docs/mandantentrennung-etappe2-fehlerrichtung.md: (t4) um den Nachtrag
ergaenzt, dass die Je-Treffer-Haelften geschlossen sind und die
uebergreifenden Haelften an Etappe 3 uebergeben bleiben.

770 Tests gruen, Typpruefung sauber, Wegwerf-Werkzeug 32/32, kein
Schema-/Migrations-/Compose-/Umgebungsdatei-Diff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 16:02:40 +02:00
schalli 3336a6e419 feat(laa-02): binde die fuenf Nutzer-CRUD-Dienste des Bereichs tenders an forTenant()
tender-saved-search.service.ts, tender-triage.service.ts,
tender-notification-pref.service.ts und tender-email-config.service.ts
laufen jetzt vollstaendig ueber forTenant() — vier neue Parameter (list,
update, remove, listForUser, favoriteIds, getForUser, getConfigForApi,
testConnection bekommen tenantId), die anwendungsseitige userId-Filterung
bleibt unveraendert (Befund E: die Policies haben keine Benutzerdimension).

tender-rss-feed.service.ts bindet nur createForUser (Zaehler + Anlage,
beide ausschliesslich auf persoenlichen Zeilen); listForUser, createPlatform
und remove bleiben mit Codekommentar bewusst ungebunden (WINDOWS #19 —
eine gebundene plattformweite Zeile waere unter jedem Mandanten unsichtbar,
ein gebundenes Einfuegen ohne Mandant wuerde abgewiesen).

tender-notification-pref.service.ts und tender-email-config.service.ts
uebersetzen eine P2002-Verletzung auf dem tenantlosen upsert-Schluessel
(Befund F) in eine verstaendliche deutsche Meldung statt eines rohen
Fehlers.

tenders.controller.ts reicht tenantId an den acht betroffenen
Aufrufstellen durch extractTriageContext() durch (kein neuer
Aufloesungsweg); die drei RSS-Aufrufstellen bleiben unveraendert, da ihre
Dienstmethoden nicht binden.

Alle sieben angefassten Testdateien bekommen den Zwei-Client-Nachweis
(__makeBoundClient ueber demselben Speicher) und Bindungstests je
umgestellter Methode; tender-rss-feed.service.spec.ts zusaetzlich den
Gegentest, dass die drei unveraendert bleibenden Pfade forTenant() NICHT
aufrufen. Falsifiziert: ein probeweiser Rueckbau der list()-Bindung in
tender-saved-search.service.ts machte genau den erwarteten Bindungstest
rot, danach zurueckgenommen.

docs/mandantentrennung-zugriffsklassifikation.md: Stand der fuenf Paare
auf gebunden bzw. gemischt nachgezogen; tenderRssFeedSource von
muss-mandantengebunden auf beides umklassifiziert (derselbe Praezedenzfall
wie ldapConfig in 260909-ipc).

761 Tests gruen (743 + 18 neue Bindungsnachweise), Typpruefung sauber,
Wegwerf-Werkzeug 32/32, kein Schema-/Migrations-/Compose-/
Umgebungsdatei-Diff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 15:53:53 +02:00
schalli 349814747e feat(laa-01): messe die drei Sonderfaelle des Bereichs tenders und schreibe die Fehlerrichtung
rls-scratch-check.mjs bekommt einen fuenften Abschnitt (runTendersAreaChecks)
mit den fuenf Policies von TenderEmailConfig/TenderNotificationPref/
TenderRssFeedSource/TenderSavedSearch/TenderTriage, wortgleich aus der
ausgelieferten Migration extrahiert. Neun neue Pruefungen belegen: die
Policies haben keine Benutzerdimension (Befund E), eine plattformweite
RSS-Zeile ist unter jedem Mandantenkontext unsichtbar und ein gebundenes
Einfuegen ohne Mandant wird abgewiesen (WINDOWS #19), und ein gebundenes
upsert auf eine unsichtbare Zeile verletzt die Eindeutigkeitsbedingung,
nicht die Policy (Befund F). Alle 32 Pruefungen (23 bisherige + 9 neue)
bestehen. Nachmessung bestaetigt Befund A: genau eine $transaction in
diesem Bereich, Array-Form auf der plattformweiten Tabelle Tender,
ausserhalb jeder Mandantenbindung — withTenantTransaction() wird hier
nicht gebraucht.

docs/mandantentrennung-etappe2-fehlerrichtung.md bekommt einen
`## Bereich tenders`-Abschnitt mit der beobachteten Messausgabe, einer
Signaltabelle je umzustellendem Pfad und einem eigenen Unterabschnitt zur
lautlosen Fehlerform der beiden Hintergrunddienste (fuenf benannte
Stellen, keine protokolliert etwas).

743 Tests gruen, Typpruefung sauber, kein Schema-/Migrations-/Compose-/
Umgebungsdatei-Diff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 15:43:35 +02:00
schalli b86675b549 docs(quick-260909-laa): Plan fuer Etappe 2 Bereich tenders
Drei Aufgaben: zuerst messen (fuenf Policies wortgleich aus der
ausgelieferten Migration, dazu die drei Sonderfaelle dieses Bereichs —
fehlende Benutzerdimension, nullbarer Mandant bei den RSS-Quellen,
Einfuegen auf eine unsichtbare Zeile), dann die fuenf Nutzerdienste
binden, dann die Je-Treffer-Haelften der beiden Hintergrunddienste und
beide Dokumente schliessen.

Zur Planungszeit gemessen statt zitiert: 743 Tests gruen, Typpruefung
sauber, Wegwerf-Werkzeug 23/23. Von den 62 Rohtreffern ist genau einer
kein Modellzugriff. Der Bereich hat keine mandantengebundene
Transaktion, das Hilfsmittel aus 260909-jts wird hier nicht gebraucht.
Keine der sieben Testdateien kennt die Erweiterung — sie waeren nach
der Umstellung aus dem falschen Grund rot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 15:34:13 +02:00
schalli 4cf7cea01f docs(quick-260909-jts): Etappe 2 Bereich groups abgeschlossen und verifiziert 2026-09-09 15:19:27 +02:00
schalli 604428a91d fix(quick-260909-jts): Lastprobe nachreichen statt sie zu behaupten 2026-09-09 15:18:55 +02:00
schalli abb6c8bea3 feat(jts-03): module-grants.service.ts binden, beide Dokumente schliessen
Alle fuenf Methoden von ModuleGrantsService (assertTargetBelongsToTenant,
grant, revoke, getMatrix, getUserAccess) laufen jetzt ueber forTenant(); bei
den beiden Datenlieferungen teilen sich alle parallel abgesetzten
Teilabfragen denselben gebundenen Client. Die Mandanten-Gegenpruefung vor
jedem Erteilen bleibt ausdruecklich bestehen und bekommt einen Verweis auf
Befund F/T-JTS-03: die Regel auf ModuleGrant prueft nur die
Mandantenkennung der Zeile, nicht die referenzierte Gruppe. Der veraltete
Kommentar ueber der Mitgliedschaftsabfrage im Benutzer-Detail ("kein
forTenant hier") ist durch den neuen Stand ersetzt.

module-grants.service.spec.ts bekommt denselben Bindungsnachweis-Mock wie
groups.service.spec.ts (zwei unterscheidbare Clients ueber demselben
Speicher) und sechs neue Bindungsnachweise; alle 28 Bestandstests bleiben
gruen.

Beide Dokumente geschlossen: die Bereichsuebersicht fuer groups ist neu
gemessen (0 ungebunden, 31 gebunden — ein dokumentierter methodischer
Bodensatz, da die einfache Rohtrefferzaehlung die neun ueber `tx` gebundenen
Zugriffe innerhalb der drei Transaktionen nicht sieht), die
Klassen-Verteilung auf 62 Paare aktualisiert, und der als offen gefuehrte
Befund D aus dem ldap-Abschnitt der Fehlerrichtung ist mit Verweis auf
diesen Durchlauf als erledigt vermerkt (Nachtrag, nicht Neuschrieb). 743
Tests und die Typpruefung gruen; Schema, Migrationen und alle vier
Compose-Dateien unveraendert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 15:07:09 +02:00
schalli 7f08b27eea feat(jts-02): groups.service.ts binden, Transaktionen tragfaehig machen, Absicherung sehend machen
prisma-tenant.extension.ts bekommt withTenantTransaction(prisma, tenantId, fn)
— die interaktive Transaktion auf dem UNgebundenen Client mit set_config als
erster Anweisung direkt auf tx, die in Aufgabe 1 als einzige der drei
gemessenen Formen sowohl die Einzelmessung als auch eine Lastprobe unter
echter Nebenlaeufigkeit bestand (die Array-Form auf dem gebundenen Client
verteilt jede Operation auf eine eigene Teiltransaktion; die interaktive Form
auf dem gebundenen Client brach unter 40 parallelen Aufrufen mit P2028 ab).

rls-access-inventory.spec.ts bekommt eine dritte Erkennung fuer
Modellzugriffe ueber den Rueckgabeparameter einer interaktiven Transaktion
(zwei Formen: direkter Empfaenger.$transaction(async...) und das neue
Hilfsmittel withTenantTransaction(...)) — macht das Paar
(groups.service.ts, tenantModuleActivation) erstmals sichtbar, das bislang
keine Pruefung dieses Projekts je gesehen hat.

groups.service.ts: alle zwoelf Methoden inklusive der drei Transaktionen
(update() isDefault:true, reassignDefaultBeforeDelete(), ensureDefaultGroup())
laufen jetzt ueber den Mandantenkontext. Zaehler und Transaktion in
ensureDefaultGroup() sind gemeinsam gebunden (T-JTS-05). addUserToDefaultGroup()
prueft neu, dass der Zielbenutzer zum Mandanten gehoert (Befund E, T-JTS-02) —
die Regel auf GroupMembership prueft nachweislich nur die Gruppenseite.

groups.service.spec.ts bekommt zwei unterscheidbare Clients ueber demselben
Speicher-Fake (Muster aus 260909-ipc, auf die interaktive Form uebertragen)
und 13 neue Bindungsnachweise; alle 42 Bestandstests bleiben gruen.
Klassifikationsdokument nachgezogen. 737 Tests und die Typpruefung gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 15:01:24 +02:00
schalli fd0b9f7d21 feat(jts-01): messen statt annehmen — groups-Policies und Transaktionsform vor jedem Dienstcode
rls-scratch-check.mjs bekommt einen vierten Abschnitt (Group/GroupMembership/
ModuleGrant/TenantModuleActivation, Policies woertlich aus den ausgelieferten
Migrationen) sowie eine eigene Messung, welche der drei Transaktionsformen
(Array auf gebundenem Client, interaktiv auf gebundenem Client, interaktiv auf
ungebundenem Client mit set_config auf tx) den Mandantenkontext tatsaechlich
auf derselben Verbindung traegt. Ergebnis: Form (i) versagt nachweisbar
(unterschiedliche pg_backend_pid() je Teilschritt); Form (ii) und (iii)
bestehen die Einzelmessung, aber eine zusaetzliche Lastprobe mit 40 parallelen
Aufrufen zeigt, dass Form (ii) unter echter Nebenlaeufigkeit mit P2028
(Transaction API error) abbricht, waehrend Form (iii) 0 Verletzungen zeigt.
Die Kritikschrift bekommt einen eigenen groups-Abschnitt mit den tatsaechlich
beobachteten Werten, der Signaltabelle je Pfad und der Liste der Stellen, die
Leere als Abwesenheit deuten (inkl. der einen Stelle, an der zu wenig Lesen zu
viel Schreiben ausloest). Kein Dienstcode angefasst; 719 Tests und die
Typpruefung bleiben gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 14:48:04 +02:00
schalli b532eaa2ad docs(quick-260909-jts): Plan fuer Etappe 2, Bereich groups 2026-09-09 14:32:42 +02:00
schalli fdca2bc452 docs(quick-260909-ipc): Etappe 2 Bereich ldap abgeschlossen und verifiziert 2026-09-09 14:19:45 +02:00
schalli eec885a9f7 docs(quick-260909-ipc): complete Etappe 2 Bereich ldap plan 2026-09-09 14:13:27 +02:00
schalli e1586a41dd feat(quick-260909-ipc): ldap.service.ts an forTenant() binden, Adress-Kollisionspruefung bewusst uebergreifend belassen
Aufgabe 3 der Etappe 2: elf Abfragen in sechs Methoden (listGroups,
upsertMappedUser, searchUsers, importUsersByDn, importGroupsByDn,
syncUsersForTenant) laufen jetzt ueber forTenant(), teils mit einem neu
erzeugten, teils mit dem in derselben Methode bereits vorhandenen gebundenen
Client. resolveEmailForWrite bleibt ausdruecklich ungebunden (Befund A,
T-IPC-04): email/username sind plattformweit eindeutig, eine Bindung wuerde
einen fremden Halter uebersehen und eine saubere Kollisionsmeldung in einen
P2002-Abbruch verwandeln.

Ein neuer Testblock biegt forTenant() auf ein zweites, unterscheidbares
Client-Objekt um (der bisherige Identitaets-Mock haette die Umstellung nicht
bemerkt, Befund F) und belegt damit, dass die Adressabfrage weiterhin am
ungebundenen und der Rest am gebundenen Client landet. Alle 67 Bestandstests
bleiben unveraendert gruen.

docs/mandantentrennung-zugriffsklassifikation.md ist fuer den Bereich ldap
geschlossen: gemessener Stand je Fundstelle, neu gerechnete Bereichsuebersicht
(gebunden getrennt von ungebunden gezaehlt) und die Uebergabe des
Standardgruppen-Punkts an den Bereich groups vor Etappe 4 dokumentiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 14:10:51 +02:00
schalli 9a57fa79f5 feat(quick-260909-ipc): ldap-config.service.ts an forTenant() binden, Loesch-Fremdzugriff schliessen
Aufgabe 2 der Etappe 2: getConfig/createConfig/updateConfig sowie
addFieldMapping/removeFieldMapping laufen jetzt ueber forTenant(), gebunden
an den aus der Anfrage bekannten Mandanten. removeFieldMapping nimmt den
Mandanten neu als Pflichtparameter entgegen und der Controller holt ihn aus
dem Sitzungsnachweis statt nur die URL-Kennung weiterzureichen (T-IPC-01) --
ein Administrator konnte bisher die Feldzuordnung eines fremden Mandanten
loeschen, wenn er ihre Kennung kannte. getAllActiveConfigs() und die
Start-Nachverschluesselung bleiben bewusst uebergreifend, mit ausgeschriebener
Begruendung im Code (Befund B).

rls-access-inventory.spec.ts erkennt jetzt neben `this.prisma.<Modell>` auch
gebundene `<Name>.<Modell>`-Zugriffe (Befund F/G) und prueft eine neue
Stand-Spalte (gebunden/ungebunden/gemischt) im Klassifikationsdokument gegen
den Quelltext. Das macht zwei bisher unsichtbare, weil schon laenger
gebundene Fundstellen sichtbar (auth.service.ts/passwordResetToken,
ldap.service.ts/groupMembership) und deckt auf, dass
(ldap-config.service.ts, ldapConfig) tatsaechlich "beides" ist, nicht
"muss-mandantengebunden" (Befund B).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 14:01:28 +02:00
schalli a0c9ef070f feat(quick-260909-ipc): Fehlerrichtung des Bereichs ldap messen und schriftlich festhalten
Aufgabe 1 der Etappe 2: erweitert das Wegwerf-Werkzeug rls-scratch-check.mjs
um fuenf Messungen des forTenant()-Musters gegen die echte, aus der
ausgelieferten Migration geschnittene LdapConfig/LdapFieldMapping-Policy
unter einer Rolle ohne BYPASSRLS. Belegt insbesondere, dass ein ungebundener
Zugriff nach dem Scharfschalten 0 Zeilen liefert, nicht alle -- die
Fehlerrichtung dreht sich um. Die neue Kritikschrift
docs/mandantentrennung-etappe2-fehlerrichtung.md haelt das schriftlich fest,
mit Signaltabelle je Pfad und den vier Stellen, die Leere als Abwesenheit
deuten. Kein Dienstcode angefasst.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 13:53:56 +02:00
schalli b34500b452 docs(quick-260909-ipc): Plan fuer Etappe 2, Bereich ldap
Drei Aufgaben: Fehlerrichtung schriftlich festhalten und an der
ausgelieferten Policy messen, dann ldap-config.service.ts und
ldap.service.ts auf forTenant() umstellen. Alle Fundstellen zur
Planungszeit einzeln aufgeschlagen statt gezaehlt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 13:43:06 +02:00
schalli 9641592a8c docs: Handoff fuer die Fortsetzung der Mandantentrennung
Tessera CI/CD / Lint & Type Check (push) Successful in 46s
Tessera CI/CD / Tests (push) Successful in 52s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
WIP-Uebergabe vor Etappe 2. Der Arbeitsbaum ist sauber, alles gepusht, die CI
gruen — pausiert wird an einer Etappengrenze, nicht mitten in einer Aufgabe.

Die Uebergabe haelt vier Anti-Patterns fest, die alle aus tatsaechlichen
Fehlschlaegen dieser Sitzung stammen. Der wichtigste: auf einem ungeprueften
Fundament bauen. forTenant() war kaputt, und ohne die empirische Probe waere
das erst nach dem Scharfschalten aufgefallen — als stiller Datenverlust, weil
der LDAP-Loeschzweig leere Ergebnisse als "Gruppe verschwunden" deutet.

Ebenfalls festgehalten, weil beides Zeit gekostet hat: eine grep-Zaehlung, die
Kommentare mitzaehlte (36 vermeintliche Aufrufstellen, tatsaechlich 6), und die
falsche Compose-Datei auf dem Server, weil COMPOSE_FILE auf prod.yml zeigt.

Der Einstiegspunkt fuer Etappe 2 ist bewusst nicht der groesste Bereich,
sondern ldap: dort sitzt der gefaehrlichste Loeschzweig, und die Wirkung ist
dort am besten pruefbar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 11:18:12 +02:00
schalli 5228f28cbe docs(quick-260909-eor): Etappe 1 der Mandantentrennung — Bericht und Klassifikation
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 55s
Tessera CI/CD / Build & Publish Images (push) Successful in 29s
Der Kernfund dieser Etappe war ein Defekt im Fundament: forTenant() setzte den
Mandantenkontext auf der Transaktionsverbindung, dispatchte die Abfrage aber
ueber den aeusseren Client. Empirisch reproduziert statt hergeleitet —
set_config auf Backend-PID 254999, Abfrage auf 255000, Kontext dort NULL.

Damit hat die Mandantentrennung nie funktioniert, auch nicht dort, wo sie
scheinbar benutzt wurde. Nach einem Scharfschalten haette sich das umgekehrt:
die betroffenen Abfragen liefern dann null Zeilen, und der LDAP-Loeschzweig
haette das als "Gruppe im Verzeichnis verschwunden" gedeutet und sie samt
Mitgliedschaften und Modulfreigaben geloescht. Aufgefallen, weil vor dem
Umbau geprueft wurde, ob das Fundament traegt.

Der Anmeldeweg bekam eine bewusst schmale Ausnahme: drei SECURITY-DEFINER-
Funktionen mit fester Spaltenliste, Gleichheitsbedingung und LIMIT 1. Eine
Policy waere hier untauglich — sie ist ein Zeilenpraedikat und haette
zwangslaeufig die ganze Tabelle freigegeben.

Die Klassifikation macht die restliche Arbeit planbar: 227 Zugriffe in 59
Einheiten, davon 31 umzustellen, 9 teilweise, 16 ohne Mandantenbezug und 3
bewusst uebergreifend. Ein Test haelt die Einteilung gegen Abdriften fest.

Browser-Gegenprobe lokal bestanden. 701 Tests gruen. Der Schalter bleibt aus;
#18, #19 und #20 bleiben offen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 11:14:58 +02:00
schalli da0ac04e73 fix(quick-260909-eor): WINDOWS #20 offen halten bis Wirkung nach Scharfschalten belegt ist
Aufgabe 4: die Browser-Gegenprobe der Anmeldung ist bestanden (siehe SUMMARY).
Auf ausdruecklichen Wunsch bleibt WINDOWS #20 (forTenant()-Verbindungsdefekt)
dennoch OPEN statt fixed — die technische Reparatur ist nachgewiesen
(rls-scratch-check.mjs, 8/8 gegen eine Wegwerf-Datenbank mit Rolle ohne
BYPASSRLS), aber die Wirkung unter der echten Anwendungsrolle tessera_app
ist erst nach dem Scharfschalten (#18) beobachtbar. #20 bleibt damit an
dieselbe Bedingung gebunden wie #18 und #19.

Nebenbefund beim Zuruecksetzen: die vorherige Markierung als "fixed" hatte
nur die Markdown-Tabelle veraendert, nicht den massgeblichen JSON-Block am
Dateiende (die eigentliche Quelle der Wahrheit fuer gsd-tools windows status)
— `gsd-tools windows status` scheiterte seitdem mit
"Ledger counts disagree with entries". Behoben durch Neuaufbau aus der
Vorversion via der broken-windows.cjs-Bibliothek (parseLedger/renderLedger),
damit Tabelle und JSON-Block wieder uebereinstimmen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 11:11:28 +02:00
schalli 5f3a39c2c3 docs(quick-260909-eor): alle 227 Datenbankzugriffe klassifiziert und maschinell abgesichert
WINDOWS #18/#20, Aufgabe 3: docs/mandantentrennung-zugriffsklassifikation.md
haelt fuer jede der 227 this.prisma.*-Fundstellen (32 Dateien, zusammengefasst
zu 59 Datei-Modell-Paaren) eine Klasse fest — muss-mandantengebunden (31),
keine-mandantengebundene-tabelle (16), bewusst-uebergreifend (3, mit
ausgeschriebenem Grund) oder beides (9, der Hintergrunddienst-Sonderfall:
uebergreifend lesen, je Zeile mandantengebunden schreiben — betrifft
ldap.service.ts, tender-digest.scheduler.ts, tender-matching.service.ts).

rls-access-inventory.spec.ts ermittelt die Fundstellen bei jedem Testlauf neu
aus dem Quelltext und vergleicht sie gegen die Tabelle im Dokument — Datei und
Modellname als Schluessel, keine Zeilennummer. Scheitert nachweislich, sobald
eine Fundstelle fehlt oder ein Eintrag verwaist (per Testlauf geprueft, danach
zurueckgesetzt).

Zwei belegte Befunde im Dokument festgehalten: req.tenantPrisma wird gesetzt,
aber nirgends gelesen; WINDOWS #19 (nullbares tenantId bei SearchProvider/
TenderRssFeedSource) bleibt benannter Blocker fuer Etappe 3.

docs/mandantentrennung-datenbankrolle.md verweist jetzt auf das neue
Dokument und korrigiert die ueberholte Zahl 182 auf den nachgemessenen Stand
(227/59). WINDOWS.md #18/#19 um Nachtrag auf diesen Plan ergaenzt; #20 (der
in Aufgabe 1 gemessene und behobene forTenant()-Verbindungsdefekt) als
"fixed" markiert.

Deviation (Rule 3, blockierend fuer die Bestandsaufnahme-Pruefung):
auth.service.ts-Kommentar umformuliert, der zuvor woertlich
"this.prisma.user.findUnique" als erklaerenden Text enthielt und dadurch
einen Eigentreffer der grep-basierten Inventur-Pruefung erzeugte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 11:04:29 +02:00
schalli de50297467 feat(quick-260909-eor): schmale SECURITY-DEFINER-Ausnahme fuer den Anmeldeweg
WINDOWS #18/#20, Aufgabe 2: der Anmeldeweg muss den passenden Benutzer
finden, bevor sein Mandant bekannt ist — unter der kuenftigen Rolle ohne
BYPASSRLS (tessera_app) wuerde ein gewoehnlicher SELECT auf "User" sonst
null Zeilen liefern und die Anmeldung waere unmoeglich.

Drei SECURITY-DEFINER-Funktionen (STABLE, fester Suchpfad public/pg_temp,
fester Spaltensatz, LIMIT 1, Ausfuehrungsrecht ausschliesslich fuer
tessera_app) ersetzen die drei pre-tenant Lesezugriffe in auth.service.ts:

- auth_lookup_user_by_username (validateUser)
- auth_lookup_user_by_email (requestPasswordReset)
- auth_lookup_reset_token (resetPassword)

Sobald der Benutzer und damit sein Mandant bekannt sind, laufen alle
Schreibzugriffe (lastLoginAt, passwordHash, Reset-Token) ueber forTenant(),
gebunden an genau diesen Mandanten (Aufgabe 1). getMe/changePassword/
adminResetPassword bleiben bewusst unangetastet — sie kennen den Mandanten
bereits aus dem Sitzungsnachweis und gehoeren in Etappe 2.

rls-scratch-check.mjs um einen zweiten Abschnitt erweitert: spielt die
Migration in die Wegwerf-Datenbank ein und misst live unter der Rolle ohne
BYPASSRLS — Anmeldesuche findet den Benutzer, unbekannter Name liefert
nichts ohne zu werfen, gewoehnlicher SELECT auf "User" liefert null Zeilen.
Alle 8 Pruefungen (5 aus Aufgabe 1 + 3 neue) bestehen gegen die lokale
Datenbank. Volle Testsuite (695 Tests) und type-check bleiben gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 10:56:41 +02:00
schalli bbf179503c fix(quick-260909-eor): forTenant() bindet Mandantenkontext auf dieselbe Verbindung
WINDOWS #20: set_config() lief auf einer anderen Postgres-Verbindung als die
eigentliche Abfrage, weil die interaktive Callback-Form von $transaction
verwendet wurde. Ersetzt durch die Array-Form, die set_config und Abfrage als
eine Transaktion auf einer Verbindung ausfuehrt (Prismas empfohlenes Muster
fuer RLS-ueber-Extensions). Injektionsfestigkeit (T-02-05) bleibt ueber ein
getaggtes $executeRaw-Template statt $executeRawUnsafe erhalten.

- prisma-tenant.extension.spec.ts: prueft die Form des Aufrufs (Array mit
  zwei Eintraegen, Rueckgabewert ist der zweite Eintrag) ohne laufende
  Datenbank
- rls-scratch-check.mjs: neues Werkzeug, das eine Wegwerf-Datenbank anlegt
  und live misst — gleiche Backend-Verbindung, gesetzter Kontext, keine
  Fremdmandanten-Zeilen, keine Zeilen ohne Kontext. Alle 5 Pruefungen
  bestehen gegen die lokale Datenbank.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 10:50:37 +02:00
schalli d0393ac4af docs: forTenant setzt den Kontext auf der falschen Verbindung (#20)
Empirisch reproduziert, nicht hergeleitet: set_config landete auf Backend-PID
254999, die eigentliche Abfrage auf 255000, dort war app.current_tenant NULL.
Die Erweiterung setzt den Kontext auf tx, dispatcht die Abfrage aber ueber den
aeusseren Client.

Folge: die Mandantentrennung hat nie funktioniert, auch nicht an den Stellen,
die sie scheinbar nutzen. Heute unsichtbar, weil die Rolle ohnehin BYPASSRLS
hat (#18). Nach dem Scharfschalten kehrt es sich um — die Abfragen liefern
dann null Zeilen, und der LDAP-Loeschzweig deutet das als "Gruppe im
Verzeichnis verschwunden" und loescht sie samt Mitgliedschaften und
Modulfreigaben.

Zusatz: von 36 vermeintlichen forTenant-Vorkommen sind die meisten Kommentare,
die dessen Fehlen erklaeren — echte Aufrufstellen sind 6. Und das von
tenant.middleware.ts:44 und tenant.guard.ts:41 gesetzte req.tenantPrisma liest
niemand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 10:44:39 +02:00