docs(quick-260911-mkj): Abschnitte abgeleitet, WINDOWS #27 geschlossen, Restmenge als eigener Eintrag
- Klassifikationsdokument: Klassen-Verteilung (35/21/14/2, 72 Paare), Uebersichtsabsatz (zweite methodische Luecke, geschlossen) und Hintergrunddienst-Nachtrag (ldap/getAllActiveConfigs reicht auch in LdapFieldMapping hinein) aus Tabelle/Greps abgeleitet, nicht abgeschrieben - Kritikschrift: Nachtrag (260911-mkj) unter (n4) im Bereich tenant, Vermerk im Etappe-2-Abschluss dass #27 geschlossen ist - WINDOWS.md: #27 fixed (Nachweis: vierte Erkennungsform, Proben, Zwischenmessung 7/3/1); neuer Eintrag #33 fuer die Empfaenger ausserhalb der vier Erkennungsformen (tenders.seed.ts, backfill-tender-source.ts) - Spec: WINDOWS #TBD-MKJ-Platzhalter durch #33 ersetzt Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
+19
-6
@@ -2,9 +2,9 @@
|
|||||||
schema_version: 1
|
schema_version: 1
|
||||||
open_count: 14
|
open_count: 14
|
||||||
waived_count: 1
|
waived_count: 1
|
||||||
fixed_count: 17
|
fixed_count: 18
|
||||||
total_count: 32
|
total_count: 33
|
||||||
last_updated: 2026-09-11T11:57:50.484Z
|
last_updated: 2026-09-11T14:48:15.447Z
|
||||||
---
|
---
|
||||||
|
|
||||||
# Broken Windows Ledger
|
# Broken Windows Ledger
|
||||||
@@ -41,12 +41,13 @@ last_updated: 2026-09-11T11:57:50.484Z
|
|||||||
| 24 | quick-260910-jab | deviation | apps/api/src/tenders/tender-rss-feed.service.ts | | Was das Schliessen von WINDOWS #19 NICHT loest: unter der Anwendungsrolle laesst sich eine plattformweite RSS-Quelle (TenderRssFeedSource, tenantId NULL) weder anlegen noch entfernen — in der alten wie in der neuen Regel, weil jede Schreibregel (Einfuegen/Aendern/Entfernen) ausdruecklich einen Mandanten verlangt (tenant_insert_policy/tenant_update_policy/tenant_delete_policy, 20260910120000_rls_widen_membership_grant_and_platform_read). Betroffen sind zwei Pfade in TenderRssFeedSourceService: createPlatform() (setzt tenantId=NULL, ein gebundenes INSERT liefe in die WITH-CHECK-Klausel und wuerde abgewiesen) und remove() (deckt fuer Administratoren auch das Entfernen einer plattformweiten Zeile ab; ein gebundenes DELETE traefe sie nie). Beide bleiben deshalb bewusst ungebunden — das ist KEINE Folge dieser Reparatur, sondern bestand bereits vor 260910-jab identisch, weil die vom Ledger vorgegebene #19-Semantik Schreibzugriffe ausdruecklich an einen Mandanten bindet. Vorabpruefung fuer Etappe 4 (rls-preflight.mjs): ein Verwaltungsweg fuer plattformweite Zeilen (Anlegen/Entfernen unter der Anwendungsrolle) muss gebaut werden, BEVOR die Rolle umgeschaltet wird — sonst kann kein Administrator nach dem Scharfschalten mehr eine plattformweite Quelle pflegen. Eigener Eintrag, damit dieser Rest nicht mit #19 verschwindet. | open | | 2026-09-10T12:35:40.000Z | |
|
| 24 | quick-260910-jab | deviation | apps/api/src/tenders/tender-rss-feed.service.ts | | Was das Schliessen von WINDOWS #19 NICHT loest: unter der Anwendungsrolle laesst sich eine plattformweite RSS-Quelle (TenderRssFeedSource, tenantId NULL) weder anlegen noch entfernen — in der alten wie in der neuen Regel, weil jede Schreibregel (Einfuegen/Aendern/Entfernen) ausdruecklich einen Mandanten verlangt (tenant_insert_policy/tenant_update_policy/tenant_delete_policy, 20260910120000_rls_widen_membership_grant_and_platform_read). Betroffen sind zwei Pfade in TenderRssFeedSourceService: createPlatform() (setzt tenantId=NULL, ein gebundenes INSERT liefe in die WITH-CHECK-Klausel und wuerde abgewiesen) und remove() (deckt fuer Administratoren auch das Entfernen einer plattformweiten Zeile ab; ein gebundenes DELETE traefe sie nie). Beide bleiben deshalb bewusst ungebunden — das ist KEINE Folge dieser Reparatur, sondern bestand bereits vor 260910-jab identisch, weil die vom Ledger vorgegebene #19-Semantik Schreibzugriffe ausdruecklich an einen Mandanten bindet. Vorabpruefung fuer Etappe 4 (rls-preflight.mjs): ein Verwaltungsweg fuer plattformweite Zeilen (Anlegen/Entfernen unter der Anwendungsrolle) muss gebaut werden, BEVOR die Rolle umgeschaltet wird — sonst kann kein Administrator nach dem Scharfschalten mehr eine plattformweite Quelle pflegen. Eigener Eintrag, damit dieser Rest nicht mit #19 verschwindet. | open | | 2026-09-10T12:35:40.000Z | |
|
||||||
| 25 | quick-260910-krx | deviation | apps/web/src/lib/stores/dashboard-store.ts | | Die beweisvernichtende Auspraegung der umgekehrten Fehlerrichtung im Bereich dashboard: ein nach dem Scharfschalten (WINDOWS #18) zu klein gebliebenes Leseergebnis auf getLayout sieht nicht wie ein Fehler aus, sondern wie eine leere Vorgabeanordnung. Drei Stellen greifen ineinander (docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich dashboard', (w3)): (1) DashboardService.getLayout liefert bei fehlendem Datensatz {lg:[],md:[],sm:[],xs:[],xxs:[]} statt eines Fehlers; (2) apps/web/src/lib/stores/dashboard-store.ts, loadDashboard setzt layouts/widgets ungeprueft auf das Ergebnis, der catch-Zweig feuert nur bei Netzwerk-/Statusfehlern, nicht bei einer erfolgreichen leeren Antwort; (3) dieselbe Datei, setEditMode(false) schreibt bei isDirty automatisch zurueck, sobald der Bearbeitungsmodus verlassen wird — ohne dass der Nutzer auf Speichern klickt. Die Folge: der Nutzer haelt ein leeres Dashboard fuer einen Fehler des Widget-Systems oder fuer verlorene Einstellungen ('das Widget-System spinnt', 'meine Einstellungen sind weg'), baut seine Anordnung neu auf (addWidget legt echte neue WidgetInstance-Zeilen an, keine Eindeutigkeitsbedingung ueber (userId, widgetType), Dubletten haeufen sich bei wiederholtem Neuaufbau an), und das automatische Zurueckschreiben ueberschreibt die layouts-Spalte der urspruenglichen Zeile — die einzige Aufzeichnung der urspruenglichen Anordnung ist verloren, bevor irgendjemand die Ursache untersuchen konnte. Zusaetzlich, kleiner: apps/web/src/components/dashboard/widgets/search-widget.tsx laesst bei einem zu kleinen custom-Ergebnis die eigenen Suchmaschinen des Nutzers aus der Auswahlliste verschwinden (der Rueckfallzweig auf DEFAULT_PROVIDERS feuert nie, weil getSearchProviders die drei Vorgaben immer voranstellt), und handleSearch faellt bei unbekannter Auswahl auf providers[0] (Google) zurueck — eine fuer ein internes Werkzeug gedachte Suchanfrage ginge dann an eine externe Suchmaschine. Konkrete Vorabpruefung fuer Etappe 4 (rls-preflight.mjs): eine physisch vorhandene DashboardLayout-Zeile fuer einen bekannten Benutzer, aber der gebundene Lesezugriff fuer dessen Mandanten liefert null — das unterscheidet den echten Erstbenutzer-Fall vom Trennungsfehler. An dieselbe Bedingung gebunden wie #18 — beobachtbar erst nach dem Scharfschalten. Das Frontend wird von 260910-krx NICHT geaendert, dieser Eintrag beschreibt es nur. Die verwandte, strukturelle Eindeutigkeitsfrage von DashboardLayout.userId (plattformweit @unique ohne Mandantenanteil) ist derselbe Fall wie WINDOWS #22 im Bereich user — dort mitgefuehrt, kein zweiter Eintrag hier. | open | | 2026-09-11T09:01:00.000Z | |
|
| 25 | quick-260910-krx | deviation | apps/web/src/lib/stores/dashboard-store.ts | | Die beweisvernichtende Auspraegung der umgekehrten Fehlerrichtung im Bereich dashboard: ein nach dem Scharfschalten (WINDOWS #18) zu klein gebliebenes Leseergebnis auf getLayout sieht nicht wie ein Fehler aus, sondern wie eine leere Vorgabeanordnung. Drei Stellen greifen ineinander (docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich dashboard', (w3)): (1) DashboardService.getLayout liefert bei fehlendem Datensatz {lg:[],md:[],sm:[],xs:[],xxs:[]} statt eines Fehlers; (2) apps/web/src/lib/stores/dashboard-store.ts, loadDashboard setzt layouts/widgets ungeprueft auf das Ergebnis, der catch-Zweig feuert nur bei Netzwerk-/Statusfehlern, nicht bei einer erfolgreichen leeren Antwort; (3) dieselbe Datei, setEditMode(false) schreibt bei isDirty automatisch zurueck, sobald der Bearbeitungsmodus verlassen wird — ohne dass der Nutzer auf Speichern klickt. Die Folge: der Nutzer haelt ein leeres Dashboard fuer einen Fehler des Widget-Systems oder fuer verlorene Einstellungen ('das Widget-System spinnt', 'meine Einstellungen sind weg'), baut seine Anordnung neu auf (addWidget legt echte neue WidgetInstance-Zeilen an, keine Eindeutigkeitsbedingung ueber (userId, widgetType), Dubletten haeufen sich bei wiederholtem Neuaufbau an), und das automatische Zurueckschreiben ueberschreibt die layouts-Spalte der urspruenglichen Zeile — die einzige Aufzeichnung der urspruenglichen Anordnung ist verloren, bevor irgendjemand die Ursache untersuchen konnte. Zusaetzlich, kleiner: apps/web/src/components/dashboard/widgets/search-widget.tsx laesst bei einem zu kleinen custom-Ergebnis die eigenen Suchmaschinen des Nutzers aus der Auswahlliste verschwinden (der Rueckfallzweig auf DEFAULT_PROVIDERS feuert nie, weil getSearchProviders die drei Vorgaben immer voranstellt), und handleSearch faellt bei unbekannter Auswahl auf providers[0] (Google) zurueck — eine fuer ein internes Werkzeug gedachte Suchanfrage ginge dann an eine externe Suchmaschine. Konkrete Vorabpruefung fuer Etappe 4 (rls-preflight.mjs): eine physisch vorhandene DashboardLayout-Zeile fuer einen bekannten Benutzer, aber der gebundene Lesezugriff fuer dessen Mandanten liefert null — das unterscheidet den echten Erstbenutzer-Fall vom Trennungsfehler. An dieselbe Bedingung gebunden wie #18 — beobachtbar erst nach dem Scharfschalten. Das Frontend wird von 260910-krx NICHT geaendert, dieser Eintrag beschreibt es nur. Die verwandte, strukturelle Eindeutigkeitsfrage von DashboardLayout.userId (plattformweit @unique ohne Mandantenanteil) ist derselbe Fall wie WINDOWS #22 im Bereich user — dort mitgefuehrt, kein zweiter Eintrag hier. | open | | 2026-09-11T09:01:00.000Z | |
|
||||||
| 26 | quick-260911-cwh | deviation | apps/web/src/components/dashboard/widgets/calendar-widget.tsx | | Bereich calendar: zu kleines Leseergebnis auf getSources/fetchAndCacheEvents sieht aus wie 'keine Quelle eingerichtet' bzw. 'keine Termine' (calendar-widget.tsx, calendar-settings-panel.tsx); das Frontend verschluckt zusaetzlich LAUTE Fehler derselben Pfade in denselben leeren Zustand (calendar-widget.tsx catch->setEvents([]), calendar-settings-panel.tsx .catch(()=>{}) auf fetchSources); der Nutzer legt seine Quelle neu an und tippt Exchange-/CalDAV-Zugangsdaten ein zweites Mal in ein scheinbar defektes System ein, die urspruengliche Zeile bleibt unsichtbar liegen und wird nach Behebung zur Dublette; Vorabpruefung fuer Etappe 4: physisch vorhandene CalendarSource-Zeilen je Mandant ueber die Wartungsrolle zaehlen und mit der gebundenen Zaehlung vergleichen (docs/mandantentrennung-etappe2-fehlerrichtung.md (k4)(e)); an dieselbe Bedingung gebunden wie WINDOWS #18; Familie mit #23 (module-registry) und #25 (dashboard); das Frontend wird von 260911-cwh NICHT geaendert. | open | | 2026-09-11T07:57:36.769Z | |
|
| 26 | quick-260911-cwh | deviation | apps/web/src/components/dashboard/widgets/calendar-widget.tsx | | Bereich calendar: zu kleines Leseergebnis auf getSources/fetchAndCacheEvents sieht aus wie 'keine Quelle eingerichtet' bzw. 'keine Termine' (calendar-widget.tsx, calendar-settings-panel.tsx); das Frontend verschluckt zusaetzlich LAUTE Fehler derselben Pfade in denselben leeren Zustand (calendar-widget.tsx catch->setEvents([]), calendar-settings-panel.tsx .catch(()=>{}) auf fetchSources); der Nutzer legt seine Quelle neu an und tippt Exchange-/CalDAV-Zugangsdaten ein zweites Mal in ein scheinbar defektes System ein, die urspruengliche Zeile bleibt unsichtbar liegen und wird nach Behebung zur Dublette; Vorabpruefung fuer Etappe 4: physisch vorhandene CalendarSource-Zeilen je Mandant ueber die Wartungsrolle zaehlen und mit der gebundenen Zaehlung vergleichen (docs/mandantentrennung-etappe2-fehlerrichtung.md (k4)(e)); an dieselbe Bedingung gebunden wie WINDOWS #18; Familie mit #23 (module-registry) und #25 (dashboard); das Frontend wird von 260911-cwh NICHT geaendert. | open | | 2026-09-11T07:57:36.769Z | |
|
||||||
| 27 | 2 | unmet-truth | apps/api/src/prisma/rls-access-inventory.spec.ts | | Die maschinelle Bestandsaufnahme (rls-access-inventory.spec.ts) ist fuer Relationszugriffe strukturell blind. Sie erkennt nur direkte Zugriffe der Form this.prisma.<Modell> bzw. <gebundener Client>.<Modell>. Ein Zugriff, der ueber include:/_count:/select: in eine ZWEITE Tabelle hineinreicht, ist fuer sie unsichtbar — obwohl Prisma daraus eine Unterabfrage auf diese zweite Tabelle macht, die unter DEREN Regel laeuft. Nachgewiesen in 260911-e2s: drei Zugriffe in tenant.controller.ts zaehlten ueber include: { _count: { select: { users } } } in die geschuetzte Tabelle User hinein (Prisma 6.19 rendert das als LEFT JOIN (SELECT tenantId, COUNT(*) FROM User ...)); nach dem Scharfschalten haette die Mandantenliste des Plattform-Administrators fuer jeden Mandanten 0 Benutzer gezeigt und der Loeschriegel T-02-09 waere vakuum geworden. Diese drei Stellen sind behoben (Fan-out je Mandant ueber gebundenen Client). Zur Planungszeit wurden alle 19 include:-Stellen und alle _count-Stellen in apps/api/src einzeln beurteilt, vom Orchestrator und vom Verifizierer unabhaengig gegengeprueft: nur diese drei waren gefaehrlich (tenders zaehlt auf dem plattformglobalen Katalog ohne Zeilenschutz, groups zaehlt ueber einen bereits gebundenen Client in eine Tabelle desselben Mandanten). OFFEN bleibt der MECHANISMUS: jede kuenftige include:/_count:-Stelle in eine fremd geschuetzte Tabelle bleibt fuer die Pruefung unsichtbar. Zu schliessen, indem der Detektor include:/select:/_count:-Bloecke auf Modellnamen durchsucht und die Zieltabelle als eigene Fundstelle fuehrt — oder durch eine Pruefung, die jede include:-Stelle einer expliziten Freigabeliste unterwirft. Gehoert vor das Scharfschalten (Etappe 4), weil die Vorabpruefung sich sonst auf eine Bestandsaufnahme stuetzt, die diese Form nicht sieht. | open | | 2026-09-11T09:08:00.435Z | |
|
| 27 | 2 | unmet-truth | apps/api/src/prisma/rls-access-inventory.spec.ts | | Die maschinelle Bestandsaufnahme (rls-access-inventory.spec.ts) ist fuer Relationszugriffe strukturell blind. Sie erkennt nur direkte Zugriffe der Form this.prisma.<Modell> bzw. <gebundener Client>.<Modell>. Ein Zugriff, der ueber include:/_count:/select: in eine ZWEITE Tabelle hineinreicht, ist fuer sie unsichtbar — obwohl Prisma daraus eine Unterabfrage auf diese zweite Tabelle macht, die unter DEREN Regel laeuft. Nachgewiesen in 260911-e2s: drei Zugriffe in tenant.controller.ts zaehlten ueber include: { _count: { select: { users } } } in die geschuetzte Tabelle User hinein (Prisma 6.19 rendert das als LEFT JOIN (SELECT tenantId, COUNT(*) FROM User ...)); nach dem Scharfschalten haette die Mandantenliste des Plattform-Administrators fuer jeden Mandanten 0 Benutzer gezeigt und der Loeschriegel T-02-09 waere vakuum geworden. Diese drei Stellen sind behoben (Fan-out je Mandant ueber gebundenen Client). Zur Planungszeit wurden alle 19 include:-Stellen und alle _count-Stellen in apps/api/src einzeln beurteilt, vom Orchestrator und vom Verifizierer unabhaengig gegengeprueft: nur diese drei waren gefaehrlich (tenders zaehlt auf dem plattformglobalen Katalog ohne Zeilenschutz, groups zaehlt ueber einen bereits gebundenen Client in eine Tabelle desselben Mandanten). OFFEN bleibt der MECHANISMUS: jede kuenftige include:/_count:-Stelle in eine fremd geschuetzte Tabelle bleibt fuer die Pruefung unsichtbar. Zu schliessen, indem der Detektor include:/select:/_count:-Bloecke auf Modellnamen durchsucht und die Zieltabelle als eigene Fundstelle fuehrt — oder durch eine Pruefung, die jede include:-Stelle einer expliziten Freigabeliste unterwirft. Gehoert vor das Scharfschalten (Etappe 4), weil die Vorabpruefung sich sonst auf eine Bestandsaufnahme stuetzt, die diese Form nicht sieht. | fixed | | 2026-09-11T09:08:00.435Z | 2026-09-11T14:48:15.447Z |
|
||||||
| 28 | quick-260911-fh9 | deviation | apps/web/src/components/layout/header.tsx | | Bereich auth: getMe liefert nach dem Scharfschalten null, der Controller antwortet 200 mit leerem Rumpf, fetchCurrentUser (auth-actions.ts) macht daraus null, header.tsx und account-settings-form.tsx tun bei null nichts — die Portalhuelle rendert ohne angemeldeten Benutzer; changePassword liest sich als networkError (nicht als falsches Kennwort); adminResetPassword als 'User not found' ohne UI-Aufrufer. 'nicht angemeldet' und 'Zeile unsichtbar' sind fuer das Frontend derselbe Wert — an dieselbe Bedingung gebunden wie WINDOWS #18; Familie #23/#25/#26; Etappe-4-Vorabpruefung: bekannten Benutzer ueber die Wartungsrolle lesen und den gebundenen findUnique unter seinem Claim-Mandanten daneben halten. Das Frontend wird von 260911-fh9 NICHT geaendert. | open | | 2026-09-11T10:00:29.558Z | |
|
| 28 | quick-260911-fh9 | deviation | apps/web/src/components/layout/header.tsx | | Bereich auth: getMe liefert nach dem Scharfschalten null, der Controller antwortet 200 mit leerem Rumpf, fetchCurrentUser (auth-actions.ts) macht daraus null, header.tsx und account-settings-form.tsx tun bei null nichts — die Portalhuelle rendert ohne angemeldeten Benutzer; changePassword liest sich als networkError (nicht als falsches Kennwort); adminResetPassword als 'User not found' ohne UI-Aufrufer. 'nicht angemeldet' und 'Zeile unsichtbar' sind fuer das Frontend derselbe Wert — an dieselbe Bedingung gebunden wie WINDOWS #18; Familie #23/#25/#26; Etappe-4-Vorabpruefung: bekannten Benutzer ueber die Wartungsrolle lesen und den gebundenen findUnique unter seinem Claim-Mandanten daneben halten. Das Frontend wird von 260911-fh9 NICHT geaendert. | open | | 2026-09-11T10:00:29.558Z | |
|
||||||
| 29 | quick-260911-fh9 | unmet-truth | apps/api/src/user/user.controller.ts | | Bereich auth: adminResetPassword schliesst die Rechteausweitung ADMIN -> SUPER_ADMIN im eigenen Handler (T-FH9-04), der Schwesterweg PATCH /users/:id tut das nicht. UserController.update (T-02-08) prueft nur, ob die Rolle SUPER_ADMIN NEU ZUGEWIESEN wird (dto.role === Role.SUPER_ADMIN), nicht ob das ZIEL diese Rolle bereits HAT — password/isActive gehen fuer ein bestehendes SUPER_ADMIN-Ziel ungeprueft durch; remove prueft ueberhaupt keine Rolle des Ziels, nur Selbstloeschung und Mandantengrenze. Kein Mandantenproblem, sondern Rechteausweitung INNERHALB des Mandanten. Reparatur in einem Satz: die Rolle des ZIELS pruefen (ist user.role === Role.SUPER_ADMIN und der Aufrufer nicht SUPER_ADMIN, ForbiddenException) — die Vorlage steht seit 260911-fh9 in AuthService.adminResetPassword. Ausserhalb der Erlaubnisliste dieses Plans, deshalb Ledger statt Reparatur; vor dem ersten Mandanten mit einem zweiten Administrator zu schliessen. | open | | 2026-09-11T10:00:38.418Z | |
|
| 29 | quick-260911-fh9 | unmet-truth | apps/api/src/user/user.controller.ts | | Bereich auth: adminResetPassword schliesst die Rechteausweitung ADMIN -> SUPER_ADMIN im eigenen Handler (T-FH9-04), der Schwesterweg PATCH /users/:id tut das nicht. UserController.update (T-02-08) prueft nur, ob die Rolle SUPER_ADMIN NEU ZUGEWIESEN wird (dto.role === Role.SUPER_ADMIN), nicht ob das ZIEL diese Rolle bereits HAT — password/isActive gehen fuer ein bestehendes SUPER_ADMIN-Ziel ungeprueft durch; remove prueft ueberhaupt keine Rolle des Ziels, nur Selbstloeschung und Mandantengrenze. Kein Mandantenproblem, sondern Rechteausweitung INNERHALB des Mandanten. Reparatur in einem Satz: die Rolle des ZIELS pruefen (ist user.role === Role.SUPER_ADMIN und der Aufrufer nicht SUPER_ADMIN, ForbiddenException) — die Vorlage steht seit 260911-fh9 in AuthService.adminResetPassword. Ausserhalb der Erlaubnisliste dieses Plans, deshalb Ledger statt Reparatur; vor dem ersten Mandanten mit einem zweiten Administrator zu schliessen. | open | | 2026-09-11T10:00:38.418Z | |
|
||||||
| 30 | quick-260911-gwh | deviation | apps/api/src/mail/mail.module.ts | | Startpfad des Mailmoduls (SettingsService.loadAnySmtpConfigForStartupTransport(), vormals getStartupSmtpConfig()) als SECHSTER Fall der Hintergrunddienst-Falle bleibt bewusst UNGEBUNDEN. HEUTE bereits falsch: findFirst() ohne Bedingung zieht bei mehreren Mandanten den SMTP-Server und Absender EINES beliebigen Mandanten fuer ALLE Kennwort-Zuruecksetzungs- und Willkommensmails ALLER Mandanten (T-GWH-03, Nutzung fremder Zugangsdaten). NACH DEM SCHARFSCHALTEN (#18) liefert dieselbe Abfrage null, mail.module.ts faellt auf MAIL_*/TESSERA_SMTP_*/localhost:1025 zurueck, MailService faengt den Transportfehler (T-02-12) -- KEINE Protokollzeile, das Verstummen ist doppelt verdeckt (Unsymmetrie zu ldap.getAllActiveConfigs [heute korrekt] UND zu dkv WINDOWS #21 [verstummt mit Protokollzeile]). Drei erwogene Formen: an einen aufgeloesten Mandanten binden (unmoeglich, kein Kontext beim Start); Mehrmandanten-Versand (abgelehnt als Funktion -- Vorlage steht in DkvMailService/TenderMailService, Transport je Versand aus getDecryptedSmtpConfig(tenantId)); als benannte Altlast weiterfuehren mit Markierung (GEWAEHLT). Eigener Eintrag statt Anschluss an #21: andere Datei, andere Reparatur, andere Verdeckungsform. Signal fuer rls-preflight.mjs gehoert in Etappe 4. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich settings' (s4)(a). | open | | 2026-09-11T11:57:36.656Z | |
|
| 30 | quick-260911-gwh | deviation | apps/api/src/mail/mail.module.ts | | Startpfad des Mailmoduls (SettingsService.loadAnySmtpConfigForStartupTransport(), vormals getStartupSmtpConfig()) als SECHSTER Fall der Hintergrunddienst-Falle bleibt bewusst UNGEBUNDEN. HEUTE bereits falsch: findFirst() ohne Bedingung zieht bei mehreren Mandanten den SMTP-Server und Absender EINES beliebigen Mandanten fuer ALLE Kennwort-Zuruecksetzungs- und Willkommensmails ALLER Mandanten (T-GWH-03, Nutzung fremder Zugangsdaten). NACH DEM SCHARFSCHALTEN (#18) liefert dieselbe Abfrage null, mail.module.ts faellt auf MAIL_*/TESSERA_SMTP_*/localhost:1025 zurueck, MailService faengt den Transportfehler (T-02-12) -- KEINE Protokollzeile, das Verstummen ist doppelt verdeckt (Unsymmetrie zu ldap.getAllActiveConfigs [heute korrekt] UND zu dkv WINDOWS #21 [verstummt mit Protokollzeile]). Drei erwogene Formen: an einen aufgeloesten Mandanten binden (unmoeglich, kein Kontext beim Start); Mehrmandanten-Versand (abgelehnt als Funktion -- Vorlage steht in DkvMailService/TenderMailService, Transport je Versand aus getDecryptedSmtpConfig(tenantId)); als benannte Altlast weiterfuehren mit Markierung (GEWAEHLT). Eigener Eintrag statt Anschluss an #21: andere Datei, andere Reparatur, andere Verdeckungsform. Signal fuer rls-preflight.mjs gehoert in Etappe 4. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich settings' (s4)(a). | open | | 2026-09-11T11:57:36.656Z | |
|
||||||
| 31 | quick-260911-gwh | deviation | apps/web/src/components/dashboard/widgets/favorites-widget.tsx | | Bereich favorites: ein nach dem Scharfschalten (#18) zu klein gebliebenes Leseergebnis auf list() sieht aus wie 'Noch keine Favoriten.' (favorites-widget.tsx Zeile um 212, de.json favorites.empty) -- fetchFavorites (favorites-api.ts) reicht die leere Liste durch. 'Nie einen gespeichert' und 'Zeile unsichtbar' sind fuer das Frontend derselbe Wert []. Etappe-4-Vorabpruefung (f4)(d): fuer einen bekannten Nutzer/Widget die Favoritenzahl ueber die Wartungsrolle und ueber den gebundenen findMany daneben halten. Familie #23/#25/#26/#28. Das Frontend wird von 260911-gwh NICHT geaendert. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich favorites' (f3)/(f4). | open | | 2026-09-11T11:57:50.276Z | |
|
| 31 | quick-260911-gwh | deviation | apps/web/src/components/dashboard/widgets/favorites-widget.tsx | | Bereich favorites: ein nach dem Scharfschalten (#18) zu klein gebliebenes Leseergebnis auf list() sieht aus wie 'Noch keine Favoriten.' (favorites-widget.tsx Zeile um 212, de.json favorites.empty) -- fetchFavorites (favorites-api.ts) reicht die leere Liste durch. 'Nie einen gespeichert' und 'Zeile unsichtbar' sind fuer das Frontend derselbe Wert []. Etappe-4-Vorabpruefung (f4)(d): fuer einen bekannten Nutzer/Widget die Favoritenzahl ueber die Wartungsrolle und ueber den gebundenen findMany daneben halten. Familie #23/#25/#26/#28. Das Frontend wird von 260911-gwh NICHT geaendert. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich favorites' (f3)/(f4). | open | | 2026-09-11T11:57:50.276Z | |
|
||||||
| 32 | quick-260911-gwh | deviation | apps/web/src/components/settings/smtp-settings-form.tsx | | Bereich settings: getSmtpConfig liefert nach dem Scharfschalten (#18) null, der Controller antwortet 200 mit leerem Rumpf, fetchSmtp (settings-api.ts) laeuft mit res.json() auf den leeren Rumpf und wirft, smtp-settings-form.tsx verschluckt das in .catch(() => {}) -- leeres Formular 'nicht eingerichtet', waehrend die Zugangsdaten physisch da sind. Ein erneutes Speichern unter der ungebundenen Form scheitert am Eindeutigkeitsindex SmtpConfig_tenantId_key (PrismaClientUnknownRequestError, gemessen in Aufgabe 1 Pruefung 8) -- nach diesem Lauf ist saveSmtpConfig gebunden und trifft die eigene Zeile, dieser Rest bestand nur unter der ungebundenen Form vor dieser Aenderung. Dieselbe 200-leerer-Rumpf-Kette wie #28. Etappe-4-Vorabpruefung (s4)(e). Das Frontend wird von 260911-gwh NICHT geaendert. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich settings' (s3)/(s4). | open | | 2026-09-11T11:57:50.484Z | |
|
| 32 | quick-260911-gwh | deviation | apps/web/src/components/settings/smtp-settings-form.tsx | | Bereich settings: getSmtpConfig liefert nach dem Scharfschalten (#18) null, der Controller antwortet 200 mit leerem Rumpf, fetchSmtp (settings-api.ts) laeuft mit res.json() auf den leeren Rumpf und wirft, smtp-settings-form.tsx verschluckt das in .catch(() => {}) -- leeres Formular 'nicht eingerichtet', waehrend die Zugangsdaten physisch da sind. Ein erneutes Speichern unter der ungebundenen Form scheitert am Eindeutigkeitsindex SmtpConfig_tenantId_key (PrismaClientUnknownRequestError, gemessen in Aufgabe 1 Pruefung 8) -- nach diesem Lauf ist saveSmtpConfig gebunden und trifft die eigene Zeile, dieser Rest bestand nur unter der ungebundenen Form vor dieser Aenderung. Dieselbe 200-leerer-Rumpf-Kette wie #28. Etappe-4-Vorabpruefung (s4)(e). Das Frontend wird von 260911-gwh NICHT geaendert. Siehe docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt 'Bereich settings' (s3)/(s4). | open | | 2026-09-11T11:57:50.484Z | |
|
||||||
|
| 33 | quick-260911-mkj | unmet-truth | apps/api/src/tenders/tenders.seed.ts | | Modellaufrufe auf Empfaengern, die weder this.prisma noch eine const X = forTenant(-Zuweisung noch ein Transaktionsparameter sind, sind fuer ALLE vier Erkennungsformen der Bestandsaufnahme unsichtbar. Gemessen 260911-mkj: tenders/tenders.seed.ts (Funktionsparameter prisma: PrismaService, tenderRssFeedSource.findFirst/create, kein Eintrag in der Bestandsaufnahme) und tenders/backfill-tender-source.ts (eigenstaendiges Skript mit new PrismaClient(), tender.findMany/update, durch RELATION_SPEC_EXCEPTIONS laut gehalten). Beide beruehren nur den plattformglobalen Katalog bzw. die plattformweite RSS-Verwaltung (WINDOWS #24), heute ungefaehrlich; OFFEN ist der Mechanismus (ein kuenftiger Dienst mit Parameter-Empfaenger auf einer geschuetzten Tabelle bliebe unsichtbar). Zu schliessen vor Etappe 4 durch eine Zaehlung ALLER <Kennung>.<Modell>.<Operation>(-Anker gegen die bekannte Empfaengermenge, Ueberschuss laut. | open | | 2026-09-11T14:48:09.723Z | |
|
||||||
|
|
||||||
````json
|
````json
|
||||||
[
|
[
|
||||||
@@ -369,10 +370,10 @@ last_updated: 2026-09-11T11:57:50.484Z
|
|||||||
"file": "apps/api/src/prisma/rls-access-inventory.spec.ts",
|
"file": "apps/api/src/prisma/rls-access-inventory.spec.ts",
|
||||||
"line": null,
|
"line": null,
|
||||||
"description": "Die maschinelle Bestandsaufnahme (rls-access-inventory.spec.ts) ist fuer Relationszugriffe strukturell blind. Sie erkennt nur direkte Zugriffe der Form this.prisma.<Modell> bzw. <gebundener Client>.<Modell>. Ein Zugriff, der ueber include:/_count:/select: in eine ZWEITE Tabelle hineinreicht, ist fuer sie unsichtbar — obwohl Prisma daraus eine Unterabfrage auf diese zweite Tabelle macht, die unter DEREN Regel laeuft. Nachgewiesen in 260911-e2s: drei Zugriffe in tenant.controller.ts zaehlten ueber include: { _count: { select: { users } } } in die geschuetzte Tabelle User hinein (Prisma 6.19 rendert das als LEFT JOIN (SELECT tenantId, COUNT(*) FROM User ...)); nach dem Scharfschalten haette die Mandantenliste des Plattform-Administrators fuer jeden Mandanten 0 Benutzer gezeigt und der Loeschriegel T-02-09 waere vakuum geworden. Diese drei Stellen sind behoben (Fan-out je Mandant ueber gebundenen Client). Zur Planungszeit wurden alle 19 include:-Stellen und alle _count-Stellen in apps/api/src einzeln beurteilt, vom Orchestrator und vom Verifizierer unabhaengig gegengeprueft: nur diese drei waren gefaehrlich (tenders zaehlt auf dem plattformglobalen Katalog ohne Zeilenschutz, groups zaehlt ueber einen bereits gebundenen Client in eine Tabelle desselben Mandanten). OFFEN bleibt der MECHANISMUS: jede kuenftige include:/_count:-Stelle in eine fremd geschuetzte Tabelle bleibt fuer die Pruefung unsichtbar. Zu schliessen, indem der Detektor include:/select:/_count:-Bloecke auf Modellnamen durchsucht und die Zieltabelle als eigene Fundstelle fuehrt — oder durch eine Pruefung, die jede include:-Stelle einer expliziten Freigabeliste unterwirft. Gehoert vor das Scharfschalten (Etappe 4), weil die Vorabpruefung sich sonst auf eine Bestandsaufnahme stuetzt, die diese Form nicht sieht.",
|
"description": "Die maschinelle Bestandsaufnahme (rls-access-inventory.spec.ts) ist fuer Relationszugriffe strukturell blind. Sie erkennt nur direkte Zugriffe der Form this.prisma.<Modell> bzw. <gebundener Client>.<Modell>. Ein Zugriff, der ueber include:/_count:/select: in eine ZWEITE Tabelle hineinreicht, ist fuer sie unsichtbar — obwohl Prisma daraus eine Unterabfrage auf diese zweite Tabelle macht, die unter DEREN Regel laeuft. Nachgewiesen in 260911-e2s: drei Zugriffe in tenant.controller.ts zaehlten ueber include: { _count: { select: { users } } } in die geschuetzte Tabelle User hinein (Prisma 6.19 rendert das als LEFT JOIN (SELECT tenantId, COUNT(*) FROM User ...)); nach dem Scharfschalten haette die Mandantenliste des Plattform-Administrators fuer jeden Mandanten 0 Benutzer gezeigt und der Loeschriegel T-02-09 waere vakuum geworden. Diese drei Stellen sind behoben (Fan-out je Mandant ueber gebundenen Client). Zur Planungszeit wurden alle 19 include:-Stellen und alle _count-Stellen in apps/api/src einzeln beurteilt, vom Orchestrator und vom Verifizierer unabhaengig gegengeprueft: nur diese drei waren gefaehrlich (tenders zaehlt auf dem plattformglobalen Katalog ohne Zeilenschutz, groups zaehlt ueber einen bereits gebundenen Client in eine Tabelle desselben Mandanten). OFFEN bleibt der MECHANISMUS: jede kuenftige include:/_count:-Stelle in eine fremd geschuetzte Tabelle bleibt fuer die Pruefung unsichtbar. Zu schliessen, indem der Detektor include:/select:/_count:-Bloecke auf Modellnamen durchsucht und die Zieltabelle als eigene Fundstelle fuehrt — oder durch eine Pruefung, die jede include:-Stelle einer expliziten Freigabeliste unterwirft. Gehoert vor das Scharfschalten (Etappe 4), weil die Vorabpruefung sich sonst auf eine Bestandsaufnahme stuetzt, die diese Form nicht sieht.",
|
||||||
"status": "open",
|
"status": "fixed",
|
||||||
"reason": "",
|
"reason": "",
|
||||||
"recorded_at": "2026-09-11T09:08:00.435Z",
|
"recorded_at": "2026-09-11T09:08:00.435Z",
|
||||||
"resolved_at": null
|
"resolved_at": "2026-09-11T14:48:15.447Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 28,
|
"id": 28,
|
||||||
@@ -433,6 +434,18 @@ last_updated: 2026-09-11T11:57:50.484Z
|
|||||||
"reason": "",
|
"reason": "",
|
||||||
"recorded_at": "2026-09-11T11:57:50.484Z",
|
"recorded_at": "2026-09-11T11:57:50.484Z",
|
||||||
"resolved_at": null
|
"resolved_at": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"kind": "unmet-truth",
|
||||||
|
"phase": "quick-260911-mkj",
|
||||||
|
"file": "apps/api/src/tenders/tenders.seed.ts",
|
||||||
|
"line": null,
|
||||||
|
"description": "Modellaufrufe auf Empfaengern, die weder this.prisma noch eine const X = forTenant(-Zuweisung noch ein Transaktionsparameter sind, sind fuer ALLE vier Erkennungsformen der Bestandsaufnahme unsichtbar. Gemessen 260911-mkj: tenders/tenders.seed.ts (Funktionsparameter prisma: PrismaService, tenderRssFeedSource.findFirst/create, kein Eintrag in der Bestandsaufnahme) und tenders/backfill-tender-source.ts (eigenstaendiges Skript mit new PrismaClient(), tender.findMany/update, durch RELATION_SPEC_EXCEPTIONS laut gehalten). Beide beruehren nur den plattformglobalen Katalog bzw. die plattformweite RSS-Verwaltung (WINDOWS #24), heute ungefaehrlich; OFFEN ist der Mechanismus (ein kuenftiger Dienst mit Parameter-Empfaenger auf einer geschuetzten Tabelle bliebe unsichtbar). Zu schliessen vor Etappe 4 durch eine Zaehlung ALLER <Kennung>.<Modell>.<Operation>(-Anker gegen die bekannte Empfaengermenge, Ueberschuss laut.",
|
||||||
|
"status": "open",
|
||||||
|
"reason": "",
|
||||||
|
"recorded_at": "2026-09-11T14:48:09.723Z",
|
||||||
|
"resolved_at": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
````
|
````
|
||||||
|
|||||||
@@ -105,8 +105,7 @@ const INTERACTIVE_TRANSACTION_EXCEPTIONS = new Set<string>([]);
|
|||||||
* Transaktionsparameter). Zusammen mit `tenders.seed.ts`
|
* Transaktionsparameter). Zusammen mit `tenders.seed.ts`
|
||||||
* (Funktionsparameter `prisma: PrismaService`, keine Relationsangabe,
|
* (Funktionsparameter `prisma: PrismaService`, keine Relationsangabe,
|
||||||
* deshalb hier nicht gelistet) als eigener Ledger-Eintrag gefuehrt:
|
* deshalb hier nicht gelistet) als eigener Ledger-Eintrag gefuehrt:
|
||||||
* WINDOWS #TBD-MKJ (Aufgabe 2 ersetzt den Platzhalter durch die vergebene
|
* WINDOWS #33.
|
||||||
* Nummer).
|
|
||||||
*/
|
*/
|
||||||
const RELATION_SPEC_EXCEPTIONS = new Set<string>(['apps/api/src/tenders/backfill-tender-source.ts']);
|
const RELATION_SPEC_EXCEPTIONS = new Set<string>(['apps/api/src/tenders/backfill-tender-source.ts']);
|
||||||
|
|
||||||
|
|||||||
@@ -2471,6 +2471,28 @@ Mandantenzahl belanglos, dieselbe Form wie
|
|||||||
Wartungsrolle gegen die gebundene Fan-out-Zaehlung ist dieselbe Pruefung wie
|
Wartungsrolle gegen die gebundene Fan-out-Zaehlung ist dieselbe Pruefung wie
|
||||||
im Bereich `user` — kein eigener Eintrag noetig.
|
im Bereich `user` — kein eigener Eintrag noetig.
|
||||||
|
|
||||||
|
**Nachtrag (260911-mkj):** WINDOWS #27 — die in (b) beschriebene
|
||||||
|
Erkennungsluecke der Bestandsaufnahme — ist geschlossen. Eine vierte
|
||||||
|
Erkennungsform in `rls-access-inventory.spec.ts` (`analyzeSource`,
|
||||||
|
`SCHEMA_RELATIONS`) loest Relationsfelder ueber `schema.prisma` auf ihr
|
||||||
|
Zielmodell auf und traegt das Zielmodell als eigene Fundstelle ein — der
|
||||||
|
Nachweis steht gegen KONTROLLIERTE Proben im Spec, nicht gegen die drei
|
||||||
|
realen Stellen aus (b): die einzige gefaehrliche Auspraegung
|
||||||
|
(`tenant.controller.ts`, ungebundener aeusserer Aufruf auf ungeschuetzter
|
||||||
|
Tabelle mit Einbindung in eine geschuetzte Tabelle) traegt die Form seit
|
||||||
|
(n3)/Aufgabe 3 (dem Fan-out-Ersatz) nicht mehr — deshalb Probestrings, die
|
||||||
|
dieselbe Form nachbilden (ungebunden und gebunden). Gemessen: SIEBEN neue
|
||||||
|
(Datei, Modell)-Paare, DREI fortgeschriebene Staende, davon
|
||||||
|
`ldap-config.service.ts`/`ldapFieldMapping` als einziger Klassenwechsel
|
||||||
|
(`muss-mandantengebunden` -> `beides`, siehe (b) oben: `getAllActiveConfigs`
|
||||||
|
reicht auch in `LdapFieldMapping` hinein, nicht nur in `Tenant`). Verbleibende
|
||||||
|
Grenze, LAUT gehalten statt still: ein Empfaenger ausserhalb aller vier
|
||||||
|
Erkennungsformen bleibt fuer JEDE Form unsichtbar — gemessen
|
||||||
|
`tenders/tenders.seed.ts` (Funktionsparameter `prisma: PrismaService`) und
|
||||||
|
`tenders/backfill-tender-source.ts` (eigenstaendiges Skript, eigener `new
|
||||||
|
PrismaClient()`), beide als eigener Ledger-Eintrag gefuehrt (nicht
|
||||||
|
stillschweigend mit #27 mitgeschlossen).
|
||||||
|
|
||||||
### (n5) Was dieser Durchlauf bewusst nicht anfasst
|
### (n5) Was dieser Durchlauf bewusst nicht anfasst
|
||||||
|
|
||||||
- Der direkte Prisma-Zugriff im Controller (Muster wie `user.controller.ts`)
|
- Der direkte Prisma-Zugriff im Controller (Muster wie `user.controller.ts`)
|
||||||
@@ -3072,7 +3094,8 @@ Bedingung gebunden wie #18), #21 (dkv-Planer-Startpfad), #22
|
|||||||
(module-registry, unterscheidbares Signal fehlt), #24 (plattformweite
|
(module-registry, unterscheidbares Signal fehlt), #24 (plattformweite
|
||||||
RSS-Verwaltung unter der Anwendungsrolle), #25 (dashboard, beweisvernichtende
|
RSS-Verwaltung unter der Anwendungsrolle), #25 (dashboard, beweisvernichtende
|
||||||
Fehlerrichtung), #26 (calendar, verschluckte Leere), #27
|
Fehlerrichtung), #26 (calendar, verschluckte Leere), #27
|
||||||
(Relationszugriffe für die Bestandsaufnahme unsichtbar), #28 (auth,
|
(Relationszugriffe für die Bestandsaufnahme unsichtbar, seit 260911-mkj
|
||||||
|
geschlossen), #28 (auth,
|
||||||
verschluckte Leere), plus die drei neuen Einträge dieses Laufs (Startpfad
|
verschluckte Leere), plus die drei neuen Einträge dieses Laufs (Startpfad
|
||||||
des Mailmoduls, verschluckte Leere `favorites`, verschluckte Leere
|
des Mailmoduls, verschluckte Leere `favorites`, verschluckte Leere
|
||||||
`settings` — Nummern siehe Aufgabe 3 dieses Plans).
|
`settings` — Nummern siehe Aufgabe 3 dieses Plans).
|
||||||
|
|||||||
@@ -131,6 +131,25 @@ Bodensatz, nicht die vollständige Zahl gebundener Zugriffe in `groups`; die
|
|||||||
Bestandsaufnahme unten (Spalte "Stand", je (Datei, Modell)-Paar) ist die
|
Bestandsaufnahme unten (Spalte "Stand", je (Datei, Modell)-Paar) ist die
|
||||||
autoritative Quelle.
|
autoritative Quelle.
|
||||||
|
|
||||||
|
**Zweite methodische Lücke, seit 260911-mkj geschlossen — aber nur in der
|
||||||
|
Bestandsaufnahme:** die beiden Rohtreffer-Greps dieser Tabelle
|
||||||
|
(`this\.prisma\.[a-zA-Z]*` bzw. `tenantPrisma\.[a-zA-Z]*\.`) zählen
|
||||||
|
Relationsziele (`include:`, `select:`, `_count:`, Relationsfilter in
|
||||||
|
`where:`/`orderBy:`) strukturell NICHT — ein `include: { module: true }`
|
||||||
|
auf `tenantPrisma.tenantModuleActivation` erzeugt keinen Rohtreffer auf
|
||||||
|
`module`, weil `module` in der Datei nie als `tenantPrisma.module` steht.
|
||||||
|
Die Spalten und die Summenzeile behalten deshalb ihre bisherige Bedeutung
|
||||||
|
und ihre bisherigen Werte (weiterhin 68 ungebunden / 178 gebunden, NACH-
|
||||||
|
GERECHNET, nicht abgeschrieben) — sie zählen weiterhin nur direkte
|
||||||
|
Modellaufrufe. Autoritativ für Relationszugriffe ist allein die
|
||||||
|
Bestandsaufnahme unten, deren vierte Erkennungsform
|
||||||
|
(`rls-access-inventory.spec.ts`, `analyzeSource`) die Ziele als eigene
|
||||||
|
Paare führt: sieben der 72 Paare dort tauchen in KEINER Rohtrefferzahl
|
||||||
|
dieser Übersicht auf, zum Beispiel
|
||||||
|
`module-registry/module-access.service.ts`/`group` (nur über den
|
||||||
|
Relationsfilter `group: { memberships: { some: { userId } } }` sichtbar,
|
||||||
|
niemals als `tenantPrisma.group` im Quelltext).
|
||||||
|
|
||||||
| Bereich | Ungebunden | Gebunden | Hinweis |
|
| Bereich | Ungebunden | Gebunden | Hinweis |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| tenders | 35 | 27 | **war 62/0**, dann 36/26 nach 260909-laa — 260910-jab (Aufgabe 2) hat `tender-rss-feed.service.ts`/`listForUser` zusätzlich auf `forTenant()` umgestellt (WINDOWS #19 geschlossen, Befund F: ungebunden hätte die Reparatur den Pfad sonst still auf nur die plattformweiten Zeilen reduziert): ein Rohtreffer wandert von ungebunden nach gebunden (36→35, 26→27). Die 35 verbleibenden ungebundenen Treffer sind die zwölf bewusst nicht angefassten Paare (D-03-Katalog, zwei Fan-out-Adapter) plus die zwei bewusst ungebundenen RSS-Pfade (`createPlatform`/`remove`, WINDOWS #24) plus die übergreifenden Hälften der beiden Hintergrunddienste (Etappe-3-Übergabe) |
|
| tenders | 35 | 27 | **war 62/0**, dann 36/26 nach 260909-laa — 260910-jab (Aufgabe 2) hat `tender-rss-feed.service.ts`/`listForUser` zusätzlich auf `forTenant()` umgestellt (WINDOWS #19 geschlossen, Befund F: ungebunden hätte die Reparatur den Pfad sonst still auf nur die plattformweiten Zeilen reduziert): ein Rohtreffer wandert von ungebunden nach gebunden (36→35, 26→27). Die 35 verbleibenden ungebundenen Treffer sind die zwölf bewusst nicht angefassten Paare (D-03-Katalog, zwei Fan-out-Adapter) plus die zwei bewusst ungebundenen RSS-Pfade (`createPlatform`/`remove`, WINDOWS #24) plus die übergreifenden Hälften der beiden Hintergrunddienste (Etappe-3-Übergabe) |
|
||||||
@@ -147,7 +166,7 @@ autoritative Quelle.
|
|||||||
| settings | 1 | 3 | **war 4/0** — 260911-gwh (Aufgabe 2) hat `getSmtpConfig`, `saveSmtpConfig`, `getDecryptedSmtpConfig` auf `forTenant()` umgestellt (3 gebundene `smtpConfig`-Rohtreffer). Der eine verbleibende ungebundene Rohtreffer ist der umbenannte Planer-Startpfad `loadAnySmtpConfigForStartupTransport()` (Befund D, WINDOWS #30) — bewusst, mit dreifacher Markierung; Befund K (`tenders`/`dkv` hängen an `getDecryptedSmtpConfig`) ist damit erfüllt |
|
| settings | 1 | 3 | **war 4/0** — 260911-gwh (Aufgabe 2) hat `getSmtpConfig`, `saveSmtpConfig`, `getDecryptedSmtpConfig` auf `forTenant()` umgestellt (3 gebundene `smtpConfig`-Rohtreffer). Der eine verbleibende ungebundene Rohtreffer ist der umbenannte Planer-Startpfad `loadAnySmtpConfigForStartupTransport()` (Befund D, WINDOWS #30) — bewusst, mit dreifacher Markierung; Befund K (`tenders`/`dkv` hängen an `getDecryptedSmtpConfig`) ist damit erfüllt |
|
||||||
| **Summe** | **68** | **178** | Ungebunden: war 118 nach 260910-das, dann 108 nach 260910-exd (module-registry 17→7), dann 107 nach 260910-jab (`tenders` 36→35, `listForUser` gebunden), dann 95 nach 260910-krx (`dashboard` 13→1), dann 83 nach 260911-cwh (`calendar` 12→0), unverändert nach 260911-e2s (`tenant` bleibt bei 8 ungebundenen Rohtreffern), dann 78 nach 260911-fh9 (`auth` 8→3), jetzt 68 nach 260911-gwh (`favorites` 7→0, `settings` 4→1). Gebunden: war 124, dann 134 nach 260910-exd (zusätzlich 10 in `module-registry`), dann 135 nach 260910-jab (zusätzlich 1 in `tenders`), dann 147 nach 260910-krx (zusätzlich 12 in `dashboard`), dann 159 nach 260911-cwh (zusätzlich 12 in `calendar`), dann 162 nach 260911-e2s (zusätzlich 3 in `tenant`), dann 167 nach 260911-fh9 (zusätzlich 5 in `auth`), jetzt 178 nach 260911-gwh (zusätzlich 8 in `favorites`, 3 in `settings`). Dies ist der ENDSTAND der Etappe 2: jeder verbleibende ungebundene Rohtreffer ist einer der in diesem Dokument benannten, bewusst ungebundenen Fälle. Diese Übersicht ist eine Buchführungshilfe; **autoritativ ist die Fundstellentabelle unten**, die `rls-access-inventory.spec.ts` bei jedem Lauf gegen den Quelltext prüft |
|
| **Summe** | **68** | **178** | Ungebunden: war 118 nach 260910-das, dann 108 nach 260910-exd (module-registry 17→7), dann 107 nach 260910-jab (`tenders` 36→35, `listForUser` gebunden), dann 95 nach 260910-krx (`dashboard` 13→1), dann 83 nach 260911-cwh (`calendar` 12→0), unverändert nach 260911-e2s (`tenant` bleibt bei 8 ungebundenen Rohtreffern), dann 78 nach 260911-fh9 (`auth` 8→3), jetzt 68 nach 260911-gwh (`favorites` 7→0, `settings` 4→1). Gebunden: war 124, dann 134 nach 260910-exd (zusätzlich 10 in `module-registry`), dann 135 nach 260910-jab (zusätzlich 1 in `tenders`), dann 147 nach 260910-krx (zusätzlich 12 in `dashboard`), dann 159 nach 260911-cwh (zusätzlich 12 in `calendar`), dann 162 nach 260911-e2s (zusätzlich 3 in `tenant`), dann 167 nach 260911-fh9 (zusätzlich 5 in `auth`), jetzt 178 nach 260911-gwh (zusätzlich 8 in `favorites`, 3 in `settings`). Dies ist der ENDSTAND der Etappe 2: jeder verbleibende ungebundene Rohtreffer ist einer der in diesem Dokument benannten, bewusst ungebundenen Fälle. Diese Übersicht ist eine Buchführungshilfe; **autoritativ ist die Fundstellentabelle unten**, die `rls-access-inventory.spec.ts` bei jedem Lauf gegen den Quelltext prüft |
|
||||||
|
|
||||||
## Klassen-Verteilung (nach (Datei, Modell)-Fundstellen, 65 Paare)
|
## Klassen-Verteilung (nach (Datei, Modell)-Fundstellen, 72 Paare)
|
||||||
|
|
||||||
Stand 260909-jts (Aufgabe 3): 61 Paare aus dem vorherigen Durchlauf
|
Stand 260909-jts (Aufgabe 3): 61 Paare aus dem vorherigen Durchlauf
|
||||||
(260909-ipc) plus ein bisher vollstaendig unsichtbares Paar
|
(260909-ipc) plus ein bisher vollstaendig unsichtbares Paar
|
||||||
@@ -236,13 +255,36 @@ bestaetigt). Zwei Paare aendern nur ihre `Stand`-Spalte, keine ihrer Klasse:
|
|||||||
der ENDSTAND der Etappe 2 — die Zahl ist der Ausgabe von
|
der ENDSTAND der Etappe 2 — die Zahl ist der Ausgabe von
|
||||||
`rls-access-inventory.spec.ts` entnommen, nicht geschaetzt.
|
`rls-access-inventory.spec.ts` entnommen, nicht geschaetzt.
|
||||||
|
|
||||||
|
**Stand 260911-mkj (Aufgabe 1/2):** 72 Paare — 65 aus dem Endstand der
|
||||||
|
Etappe 2 plus SIEBEN neue Paare der vierten Erkennungsform (WINDOWS #27):
|
||||||
|
`groups/module-grants.service.ts`/`module` (`keine-mandantengebundene-tabelle`,
|
||||||
|
`gebunden`), `ldap/ldap-config.service.ts`/`tenant`
|
||||||
|
(`keine-mandantengebundene-tabelle`, `ungebunden`),
|
||||||
|
`module-registry/module-access.service.ts`/`group` UND `/groupMembership`
|
||||||
|
(beide `muss-mandantengebunden`, `gebunden`),
|
||||||
|
`tenders/tender-digest.scheduler.ts`/`tender`
|
||||||
|
(`keine-mandantengebundene-tabelle`, `gebunden`) UND `/tenderSavedSearch`
|
||||||
|
(`muss-mandantengebunden`, `gebunden`),
|
||||||
|
`tenders/tenders.controller.ts`/`tenderSource`
|
||||||
|
(`keine-mandantengebundene-tabelle`, `ungebunden`). Drei Paare aendern ihren
|
||||||
|
Stand, eines davon zusaetzlich die Klasse:
|
||||||
|
`ldap-config.service.ts`/`ldapFieldMapping` wechselt von
|
||||||
|
`muss-mandantengebunden`/`gebunden` auf `beides`/`gemischt` — der
|
||||||
|
uebergreifende Planer-Lesepfad `getAllActiveConfigs()` reicht ueber
|
||||||
|
`include: { fieldMappings: true }` in `LdapFieldMapping` hinein, dieselbe
|
||||||
|
Unterabfrage-Form, die WINDOWS #27 aufgedeckt hat.
|
||||||
|
`module-registry.service.ts`/`module` und `tender-matching.service.ts`/
|
||||||
|
`tender` wechseln je von `ungebunden` auf `gemischt`, ihre Klasse bleibt.
|
||||||
|
Die Zahl 72 ist der Ausgabe von `rls-access-inventory.spec.ts` entnommen,
|
||||||
|
nicht geschaetzt.
|
||||||
|
|
||||||
| Klasse | Anzahl Paare |
|
| Klasse | Anzahl Paare |
|
||||||
|---|---|
|
|---|---|
|
||||||
| muss-mandantengebunden | 33 |
|
| muss-mandantengebunden | 35 |
|
||||||
| keine-mandantengebundene-tabelle | 17 |
|
| keine-mandantengebundene-tabelle | 21 |
|
||||||
| beides | 13 |
|
| beides | 14 |
|
||||||
| bewusst-uebergreifend | 2 |
|
| bewusst-uebergreifend | 2 |
|
||||||
| **Summe** | **65** |
|
| **Summe** | **72** |
|
||||||
|
|
||||||
## Der Hintergrunddienst als Falle — sechs Fälle
|
## Der Hintergrunddienst als Falle — sechs Fälle
|
||||||
|
|
||||||
@@ -278,6 +320,19 @@ beim Start — mit einer zusätzlichen Verdeckungsschicht, siehe unten:
|
|||||||
Zustand zum Zeitpunkt der ldap-Umstellung korrekt beschreibt und für
|
Zustand zum Zeitpunkt der ldap-Umstellung korrekt beschreibt und für
|
||||||
spätere Etappen als Beleg dient, siehe auch
|
spätere Etappen als Beleg dient, siehe auch
|
||||||
`docs/mandantentrennung-etappe2-fehlerrichtung.md`, Abschnitt (e), Befund D.
|
`docs/mandantentrennung-etappe2-fehlerrichtung.md`, Abschnitt (e), Befund D.
|
||||||
|
|
||||||
|
**Nachtrag (260911-mkj):** WINDOWS #27 geschlossen — derselbe Bereich
|
||||||
|
`ldap` traegt
|
||||||
|
einen zweiten, bislang unsichtbaren Planer-Lesezugriff:
|
||||||
|
`LdapConfigService.getAllActiveConfigs()` (`ldap-config.service.ts`)
|
||||||
|
reicht ueber `include: { tenant: true, fieldMappings: true }` sowohl in
|
||||||
|
`Tenant` (schutzlos, harmlos) als auch in `LdapFieldMapping` (geschuetzt
|
||||||
|
ueber Join auf `LdapConfig`) hinein. Nach dem Scharfschalten verstummt
|
||||||
|
die Feldzuordnung mit der Elternzeile, nicht getrennt von ihr — der
|
||||||
|
Etappe-3-Systemkontext muss BEIDE Tabellen sehen. Die Bestandsaufnahme
|
||||||
|
fuehrt `ldapFieldMapping` deshalb seither als `beides`/`gemischt` statt
|
||||||
|
`muss-mandantengebunden`/`gebunden` (siehe Bestandsaufnahme unten,
|
||||||
|
`ldap-config.service.ts`/`ldapFieldMapping`).
|
||||||
- **`tender-digest.scheduler.ts`** (Ausschreibungs-Digest) — **Stand
|
- **`tender-digest.scheduler.ts`** (Ausschreibungs-Digest) — **Stand
|
||||||
260909-laa, Aufgabe 3: Je-Treffer-Hälfte geschlossen, übergreifende
|
260909-laa, Aufgabe 3: Je-Treffer-Hälfte geschlossen, übergreifende
|
||||||
Hälfte an Etappe 3 übergeben.** Liest `tenderMatch` (Kandidatenabfrage,
|
Hälfte an Etappe 3 übergeben.** Liest `tenderMatch` (Kandidatenabfrage,
|
||||||
|
|||||||
Reference in New Issue
Block a user