Die urspruengliche findIndex((c) => c === null)-Pruefung erzeugte einen
neuen lint/complexity/useIndexOf-Fund (info) und hob TOTAL dadurch auf 430
statt der erwarteten 429 an — die Endverifikation des Plans deckte das auf
(D-06: TOTAL darf nirgends anders steigen).
@types/node-forge deklariert Bag.cert als "Certificate | undefined", die
node-forge-Laufzeit setzt bei einem unlesbaren Bag aber "null" (nicht
undefined). Ein blosses indexOf(null) ist deshalb nicht typsicher; die
Pruefung testet jetzt ausdruecklich auf beide Werte, was fuer biome kein
Single-Value-Vergleich mehr ist und keinen useIndexOf-Vorschlag ausloest.
TOTAL steht jetzt bei 429 wie geplant, NONNULL unveraendert bei 6, tsc und
beide Testsuiten weiterhin gruen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
imapflow typisiert uid in FetchMessageObject als Pflichtfeld (lib/imap-flow.d.ts
Zeile 469, Kommentar "Always included in the response"). Die beiden
Zusicherungen msg.uid! sicherten also einen Wert ab, der ohnehin nicht fehlen
kann. Reine Lesbarkeitsaenderung ohne Verhaltensaenderung, tsc bleibt gruen.
Die drei verbliebenen Zusicherungen (tenders.controller.ts:244,
favorites-widget.tsx:149, sidebar.tsx:80) bleiben unveraendert — jede durch
eine konkrete vorgelagerte Zeile garantiert (Provider-Eintrag, gemeinsame
Herleitung aus favorites, Anlegen des Map-Eintrags direkt davor).
NONNULL 8 -> 6, keines davon mehr in imap.provider.ts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
node-forge setzt bag.cert bei einem wohlgeformten, aber nicht als X.509
lesbaren Zertifikats-Bag auf null (lib/pkcs12.js Zeile 703-709). Die drei
Zusicherungen in parseCert, mergeCerts und convertCert behaupteten also
etwas Falsches, obwohl die Folge bereits behandelt war: alle vier Pfade
antworteten schon vorher mit 400, nie mit 500 (mit einer selbst gebauten
83-Byte-PFX nachgemessen).
Ersetzt die drei Zusicherungen durch ausdrueckliche Pruefungen mit
praeziser BadRequestException. In mergeCerts wird kein Zertifikat mehr
verschluckt: alle Bags werden auf Vollstaendigkeit geprueft, bevor die
Liste ueber ein Typpraedikat zurueckgegeben wird.
RED-Tests zuerst geschrieben und mit den heutigen Meldungen ("Failed to
extract certificate details" / "Failed to create merged certificate
output" / "Failed to convert certificate to pem: serialization error")
rot bestaetigt, dann die Waechter ergaenzt: GREEN.
Verhaltensaenderung ausdruecklich beabsichtigt (D-02): nur der Text der
Fehlermeldung fuer diese eine Eingabeklasse aendert sich, der Statuscode
bleibt in allen vier Pfaden 400.
NONNULL 11 -> 8, davon 3 in cert-manager.service.ts (Zeilen 133/516/661
bleiben unveraendert — durch Hash-Laenge bzw. vorgelagerte Passwort-
Pruefung garantiert).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
Drei while ((m = re.exec(t)) !== null)-Schleifen (dkv-parser.service.ts,
dkv-parser.validate.ts, icon-discovery.service.ts) sind die korrekte
Standardform fuer globale Regexe - kein verrutschtes "=". Umgeschrieben auf
eine verhaltensgleiche for-Schleife, die ohne noAssignInExpressions-
Unterdrueckung auskommt: Zuweisung wandert in Initialisierung und
Fortschaltung der for-Schleife, Bedingung prueft weiterhin auf null.
Abfolge der exec-Aufrufe, lastIndex-Fortschritt und Rumpfinhalte
unveraendert. Neue Spezifikation dkv-parser.service.spec.ts deckt
parseDkvText erstmals eigenstaendig ab (zwei Fahrzeugbloecke, Rechnungsnummer
und -datum aus einer gemockten pdf-parse-Attrappe) - das Rueckfall-Tor fuer
diesen Umbau. dkv-parser.validate.ts bleibt bei 27 Fahrzeugbloecken/66
Transaktionen gegen die reale invoice.pdf identisch.
LdapService.escapeLdapFilterValue bleibt zeichengleich: der NUL-Treffer in
der Regel ist die von RFC 4515 vorgeschriebene \00-Maskierung, kein Fehler.
Ein biome-ignore-Kommentar dokumentiert das, statt die Funktion zu aendern.
locale-switcher.tsx setzt jetzt SameSite=Lax auf dem NEXT_LOCALE-Cookie -
path=/ und max-age waren bereits korrekt, es lag also kein Persistenzdefekt
vor. Ohne SameSite haengt die Uebertragung am Browservorgabewert statt an
einer Festlegung. Neue Spezifikation locale-switcher.test.tsx haelt die
vollstaendige geschriebene Cookie-Zeichenkette fest.
Biome-Warnungen 446 -> 434 (noControlCharactersInRegex/useIterableCallbackReturn/
noGlobalIsNan auf 0, noAssignInExpressions auf 4 und noDocumentCookie auf 17 -
beide Reste ausschliesslich in Testdateien, suppressions/unused auf 0).
Quick-Vorgang 260921-i8x, Task 3/3.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
JwtStrategy.validate liess mustChangePassword auf dem Weg vom Token zu
request.user fallen; der global registrierte ForcePasswordChangeInterceptor
prueft genau dieses Feld und hat seit seiner Einfuehrung nie etwas
blockiert. validate() reicht das Feld jetzt durch (strenger Vergleich mit
true, Alt-Sitzungen ohne den Anspruch bleiben unveraendert unbetroffen).
Zusaetzlich die Erlaubnisliste des Abfangers von Teilstring-Vergleich auf
exakten Abgleich von Methode UND Pfad umgestellt (Absicherung gegen eine
kuenftige kollidierende Route, heute nicht ausnutzbar).
Nahttest gepinnt, der gegen den alten Quelltext nachweislich scheitert
(6 von 12 neuen Faellen rot vor der Aenderung, gruen danach).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
- Aufgabe 2: vier sichere Biome-Regeln (useImportType pfadgebunden auf
apps/web+packages, noUselessEscapeInRegex, useConst,
useExponentiationOperator) sowie fuenf ungesicherte Regeln
(useNodejsImportProtocol, useLiteralKeys, useOptionalChain, useTemplate,
useParseIntRadix) angewendet und den gesamten Diff von Hand gelesen
(ldap.service.ts zeichenweise gegen Gross-/Kleinschreibung der
AD-Merkmale, auth.service.ts/jwt.strategy.ts gegen Durchwinken bei
fehlender Sitzung geprueft)
- noUselessSwitchCase bleibt bewusst stehen (tender-normalizer.service.ts:60,
die Fallmarke dokumentiert Absicht)
- Toter Code (D-03): fuenf folgenlose Auffangvariablen entfernt, eine
nicht benutzte Funktion (forSystemQuery, Pruefskript) entfernt, ein
positionsgebundener Dekoratorparameter umbenannt (current-user.decorator.ts),
fuenf Symptomfunde entfernt und als Folgeaufgaben zu melden (siehe unten)
- Sechs weitere, im Plan nicht namentlich gelistete aber
gleich-kategorische Dead-Code-Fundstellen in Testdateien zusaetzlich
bereinigt (groups.service.spec.ts, cert-manager.test.tsx,
ldap.service.spec.ts, prisma-tenant.extension.spec.ts x3) — noetig, um
die vom Plan selbst verlangten Nullstaende bei noUnusedVariables/
noUnusedImports/noUnusedFunctionParameters zu erreichen
Dekoratordaten aus apps/api unveraendert (593 Zeilen, sha256 6e1583f1...).
Endstand 620 Befunde (541 echt, 79 Test) statt der im Plan geschaetzten
621/542 — eine Differenz von 1, weil das Streichen des Namens aus
`catch (e: any)` in calendar.service.ts (Symptom-Fix) den dort ebenfalls
gemeldeten noExplicitAny-Befund miteliminiert; das ist eine erwuenschte
Nebenwirkung, keine Regression. Fehlerstufe 0, beide Testlaeufe
punktgleich gruen (69/1124, 66/459), pnpm type-check 4/4, pnpm lint
--force 5/5.
Folgeaufgaben aus D-03 (nicht in diesem Vorgang behoben):
- force-password-change.interceptor.ts: Freigabeliste prueft nur den Pfad,
nicht die HTTP-Methode
- change-password/page.tsx: nach erzwungenem Wechsel bleibt die Person auf
der Seite stehen (keine Weiterleitung, keine Aktualisierung der
Benutzerablage)
- VehicleTable.tsx: Loeschschaltflaeche hat keinen Besetztzustand, laesst
sich doppelt ausloesen
- SplitTab.tsx: downloadAllAsZip erhielt eine ungenutzte
Uebersetzungsfunktion, Hinweis auf fest verdrahtete Texte im Zip-Pfad
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
WebView2 (Windows) sieht im User-Agent aus wie Edge, WebKitGTK (Linux) wie
Safari — im Postfach war eine Client-Meldung von einer Browser-Meldung
nicht zu unterscheiden. Neuer reiner Helfer origin.ts leitet aus vier
optionalen DTO-Feldern (Desktop-App) bzw. dem User-Agent (Browser) ein
Betreff-Kuerzel und eine Zeile "Herkunft: ..." ab; rein informativ,
laengenbegrenzt, nichts wird gespeichert (T-GZA-01). Browser-Pfad ist
damit Ende-zu-Ende fertig.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016g2npLxzH5gZpg8s2S6vKh
- Neuer oeffentlicher Endpunkt (vor download/:platform): base-Origin wird
per safeOrigin validiert (nur http/https, kein Pfad/Query/Fragment/
Userinfo, sonst 400) und nur zum Bau der absoluten Download-URL genutzt,
nie serverseitig abgerufen
- 204 ohne Body bei fremder Plattform/Architektur, fehlendem Manifest,
fehlender Signatur oder fehlendem/ungueltigem updateVersion; sonst
{ version, pub_date (nur RFC 3339), url, signature, notes }
- Manifest-Felder signature (je Plattform) und updateVersion optional in
@tessera/shared, Eintragspruefung akzeptiert nur String-Signaturen
- 10 neue Spec-Tests, Test 11 erweitert (23 gesamt); latest/download
unveraendert
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- CHANGELOG.md: je ein Stichpunkt unter Neu (Sortierpfeile) und Behoben
(Symbol trotz Zertifikatsfehler/interner Adresse)
- docs/anleitung-anwender.md: Tabellenzeile „Favoriten" nennt die Pfeile
und den Browser-Ersatzweg fuer das Symbol
- docs/mandantentrennung-zugriffsklassifikation.md: Nachtrag zu
favoriteLink — reorder() laeuft ueber withTenantTransaction() ohne
Benutzerdimension in der Sitzung, Stand bleibt gebunden
- prisma-tenant.extension.ts: Kopfkommentar-Nachtrag, favorites.service.ts
(reorder) ist der erste Nutzer-CRUD-Aufrufer von withTenantTransaction()
— nur Kommentartext, Funktionscode unveraendert (prisma-tenant.extension.spec.ts
und rls-access-inventory.spec.ts weiterhin gruen)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- icon-discovery.service.ts: undicis eigenes fetch mit Modul-Singleton
LENIENT_TLS_AGENT (Agent({ connect: { rejectUnauthorized: false } }))
als dispatcher in fetchWithRedirectGuard, der einzigen Ausgangsstelle
fuer HTML-Ermittlung und Icon-Byte-Holen; SSRF-Schutz unveraendert
- undici 7.28.0 (bereits im Lockfile aufgeloest) als direkte Abhaengigkeit
von @tessera/api via pnpm add --offline
- PUT /favorites/order (ReorderFavoritesDto) vor den :id-Routen;
FavoritesService.reorder() setzt position=index fuer die Favoriten
eines Widgets in EINER withTenantTransaction, userId+widgetId in jeder
Bedingung (zweites Netz), eine BadRequestException fuer alle
Abweichungen (T-JDD-06)
- getIcon: X-Content-Type-Options nosniff + restriktive CSP (T-JDD-02)
- 10 neue Tests (3 Dispatcher, 7 reorder); volle API-Suite 68 Dateien/
1101 Tests und type-check gruen
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Zeichen-Whitelist /^[A-Za-z0-9._-]+$/ liess Namen wie ".." durch, weil
Punkt und Bindestrich erlaubte Zeichen sind -- path.join(desktopDistDir, '..')
loest aber in den Elternordner auf und unterlaeuft genau die Verteidigung in
der Tiefe (T-18-02), die diese Zeile laut Kommentar herstellen soll. Jetzt
werden "." und ".." explizit abgelehnt UND der aufgeloeste Pfad zusaetzlich
gegen desktopDistDir geprueft (haelt auch kuenftige Varianten ab, falls die
Whitelist anderswo wiederverwendet wird). Neue Testfaelle fuer manifest-Eintraege
namens "..", "." und "../manifest.json".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ein kaputter Plattform-Eintrag (z. B. fehlendes name-Feld oder ungueltiger
sha256) fiel bisher erst spaeter unbemerkt durch -- entry.name === undefined
wurde zu "undefined" gecoerct und als Dateiname gesucht. getManifest()
prueft jetzt jeden vorhandenen Plattform-Eintrag (name: string, size: number,
sha256: 64-stelliger Hex-String) und behandelt ein kaputtes Manifest wie ein
fehlendes (404 + Warn-Log), statt die kaputte Form durchzureichen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die duenne Strecke Skript -> Abbild -> API beweisen (D-08, D-10):
- desktop-collect.sh sammelt AppImage/exe ein, schreibt manifest.json
(Version, Kanal, Commit, Groesse, SHA-256) ohne Secrets
- apps/api/src/desktop/: neues Modul mit GET /desktop/latest und
GET /desktop/download/:platform, beide @Public(); Plattform-Whitelist
vor jedem Dateisystemzugriff, Dateiname ausschliesslich aus dem
Manifest (T-18-01, T-18-02)
- packages/shared: DesktopPlatform/-Manifest(File)/-Latest(Response)
Typen
- Dockerfile kopiert desktop-dist/ in die runner-Stufe; desktop-dist/
per .gitkeep + .gitignore versioniert (leeres Verzeichnis, Pakete
bleiben ungetrackt)
- HTTP-Durchstich-Spec (8 Tests) via NestFactory, kein fs-Mock; echtes
Temp-Verzeichnis + unabhaengig berechneter SHA-256
Abweichung: DesktopController braucht @Inject(DesktopService) explizit
— Vitest transpiliert ueber esbuild, das emitDecoratorMetadata nicht
abbildet, sonst bleibt desktopService bei einem echten NestFactory-Bau
undefined (Rule 3, Blocker).
Lokal bewiesen: neu gebautes API-Abbild liefert /desktop/latest (200)
und /desktop/download/linux (200, attachment) aus, auch ueber
/api-proxy/ des Web-Containers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Dockerfiles: globale ARG APP_VERSION/APP_CHANNEL/APP_COMMIT/APP_BUILD_TIME (Vorgabe dev),
web-builder setzt NEXT_PUBLIC_APP_* vor pnpm build (Bauzeit-Einbettung), runner-Stufen
setzen ENV APP_* fuer die Laufzeit
- .gitea/scripts/publish-images.sh: Kanal und Etiketten allein aus GITHUB_REF, --print-plan
ohne Docker, Zweig live ohne Tag = nichts zu tun (T-KU1-07)
- ci.yml: Trigger main, live und Tags v*; publish mit fetch-depth: 0 und Skriptaufruf
- docker-compose.prod.yml: image ...:${IMAGE_TAG:-beta} fuer web und api
- Falsifizierung lokal: mit Build-Args v9.9.9-test in API-Env, dist-Zeile und Web-Bundle
(1 Datei), ohne Build-Args dev/dev und 0 Treffer im Bundle
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
- auth.service.ts: letzter Satz des Kopfkommentars ueber adminResetPassword nennt T-FH9-05 nicht mehr als offen, sondern verweist auf den seit 260914-ebg (WINDOWS #29) identischen Riegel in UserController.update()/remove()
- Falsifizierung: Rueckbau des Task-1-Commits (git apply -R) macht Test 9 und Test 13 rot (Tests 2 failed | 14 passed (16)), danach byte-identisch wiederhergestellt (git checkout --, git status --porcelain leer)
- Rule 1 Nebenfund: acht neue Tests in user.controller.spec.ts trugen sechs ueberfluessige `as any`-Umschreibungen (UpdateUserDto ist vollstaendig optional, siehe planning_measurements), die die Biome-Warnungen dieser Datei von 25 auf 31 trieben — entfernt, damit die relative Biome-Schwelle der Baseline (25) wieder eingehalten wird, ohne die Schwelle anzuheben
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
- update(): Riegel nach der Mandantengrenze, vor der dto.role-Pruefung — Nicht-SUPER_ADMIN darf SUPER_ADMIN-Ziel nicht mehr aendern (Kennwort, isActive, Rolle, Anmeldename, E-Mail)
- remove(): derselbe Riegel nach der Mandantengrenze, vor userService.delete
- acht neue Tests (Test 9-16): drei Angriffsformen, SUPER_ADMIN-gegen-SUPER_ADMIN-Regression, ADMIN-gegen-USER-Regression, Reihenfolge-Ordnungstests je Handler
- RED-Lauf vor dem Riegel: Tests 2 failed | 14 passed (16) (Test 9, Test 13 rot); GREEN danach: Tests 16 passed (16)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
- 30 verbleibende forTenant()-Aufrufstellen in sieben Diensten (calendar 6,
dashboard 9, favorites 5, tender-email-config 3, tender-notification-pref 2,
tender-rss-feed 2, tender-triage 3) reichen userId als drittes Argument
durch. tender-digest.scheduler.ts bleibt zweistellig (Hintergrunddienst,
Etappe 3c), mit Begruendung im Kommentar. Keine Methodensignatur, kein
Controller angefasst, keine anwendungsseitige userId-Filterung entfernt.
- rls-scratch-check.mjs: zwoelf Extraktionsstellen auf die neue Migration
umgeleitet (TenderEmailConfig/TenderNotificationPref/TenderSavedSearch/
TenderTriage/TenderRssFeedSource in runTendersAreaChecks, SearchProvider in
runSearchProviderAreaChecks/runDashboardAreaChecks, DashboardLayout/
WidgetInstance, CalendarSource/FavoriteLink samt regelstand-eindeutig-Gates).
SearchProvider/TenderRssFeedSource jetzt mit extractAllPolicySql (4 Regeln).
runUserDimensionChecks() um die uebrigen neun Tabellen erweitert (neue
Routine runCommandSeparatedPersonalTableCheck fuer die zwei NULL-faehigen
Tabellen inkl. gemeinsame-Zeile-Pruefungen).
- Sechs Loch-Pruefungen umgedreht (dashboardlayout, widgetinstance,
searchprovider, calendarsource, favoritelink-Doppelaussage getrennt) —
alte Messung ohne Benutzer bleibt unter neuem Namen, Umkehrung MIT
Benutzer erwartet das Gegenteil; kein alter Name mehr als Kennung.
- Baseline: 1020/62 Tests weiterhin gruen, Typpruefung sauber, Werkzeug
203/203 bestanden (vorher 146).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- Neue Migration 20260911120000_rls_user_dimension_personal_tables: current_user_id()
(NULLIF-gefaltet), zehn persoenliche Tabellen umgestellt (acht als eine Regel,
SearchProvider/TenderRssFeedSource als je vier befehlsgetrennte Regeln), vier
Verwaltungstabellen bewusst unveraendert. Lokal angewendet (migrate deploy,
Prisma-Binary aus apps/api/node_modules/.bin), schema.prisma unveraendert.
- forTenant(prisma, tenantId, userId?): beide set_config in EINER getaggten
Anweisung, $transaction-Array bleibt bei zwei Eintraegen (WINDOWS #20),
Leerstring ohne Benutzer statt Weglassen.
- tender-saved-search.service.ts: alle vier forTenant()-Aufrufe reichen userId
durch; Detektor-Regex bestaetigt 4 Treffer.
- rls-scratch-check.mjs: current_user_id() aus der neuen Migration geschnitten
(nicht getippt), drei Funktionsfaelle gemessen, neue runUserDimensionChecks()
mit generiertem Client fuer TenderSavedSearch (vier Wahrheiten + Spaltenabgleich),
die alte Loch-Pruefung tendersavedsearch-fremder-nutzer-desselben-mandanten-sichtbar
umgedreht (alte Messung unter neuem Namen erhalten, neue Umkehrung MIT Benutzer).
sqlStateOf() um Message-Fallback ergaenzt (RLS-Ablehnung ueber generierten
Client traegt den SQLSTATE nur im Fehlertext, nicht in .meta.code).
- Baseline: 1020/62 Tests, Typpruefung sauber, Werkzeug 146/146 bestanden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- 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
- rls-access-inventory.spec.ts: analyzeFile in analyzeSource(source, relPath)
herausgeloest, parseSchemaRelations() liest schema.prisma zur Testzeit,
vierte Erkennung loest include:/select:/_count:/Relationsfilter ueber
SCHEMA_RELATIONS auf das Zielmodell auf und traegt es als eigene
Fundstelle (gebunden/ungebunden nach Empfaenger) ein
- drei Waechter (Rohzahl vs. erkannte Modellaufrufe, Stale-Check fuer
RELATION_SPEC_EXCEPTIONS, unresolvedRelationSpecValues leer), zwei
Schema-Tests, acht gepinnte Proben (WINDOWS #27 ungebunden/gebunden,
reale ldap-Form, verschachtelte where-Kette, Negativprobe, _count: true,
unbekannter Empfaenger, Konstantenaufloesung)
- Bestandsaufnahme (docs/mandantentrennung-zugriffsklassifikation.md): sieben
neue Paare, drei fortgeschriebene Staende (davon ldapFieldMapping mit
Klassenwechsel auf beides), Kopfabsatz "Erkennungsluecke GESCHLOSSEN"
ersetzt den alten "seit 260911-e2s vermessen"-Absatz, 65 -> 72 Paare
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- .planning/WINDOWS.md: drei neue offene Eintraege (#30 Startpfad des
Mailmoduls, #31 verschluckte Leere favorites, #32 verschluckte Leere
settings); Platzhalter WINDOWS #TBD-GWH in settings.service.ts und
mail.module.ts durch #30 ersetzt
- docs/mandantentrennung-zugriffsklassifikation.md: Uebersichtszeilen
favorites (0/8, war 7/0) und settings (1/3, war 4/0) neu gemessen;
Summenzeile 68/178 (Endstand Etappe 2); Bestandsaufnahme (favoriteLink
gebunden, smtpConfig gemischt, neue Zeile widgetInstance/gebunden);
Klassen-Verteilung 65 Paare (33/17/13/2); Hintergrunddienst-Abschnitt mit
sechstem Fall (mail.module.ts, WINDOWS #30) und erfuellter
Befund-K-Bedingung; neuer Punkt in "Was diese Etappe NICHT entscheidet"
- docs/mandantentrennung-etappe2-fehlerrichtung.md: Nachtraege unter Befund
K in (t4) und im Uebergaben-Absatz von (d4) -- Reihenfolgebedingung
erfuellt; ## Etappe 2 -- Abschluss mit den derivierten Endzahlen
- docs/anleitung-entwicklung.md: 23 RLS-Tabellen statt sieben, FavoriteLink
nicht mehr als Tabelle ohne Regel, tenantPrisma statt manuellem
tenantId-Filter als gelebter Stil
- rls-access-inventory.spec.ts wieder gruen (11/11), volle Suite 994/994,
Werkzeug 137/137; zwei Dokument-Falsifizierungen durchgefuehrt und
zurueckgenommen (siehe SUMMARY)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- favorites.service.ts: alle fuenf Methoden nehmen tenantId als ersten
Parameter, laufen je ueber EINEN Klienten tenantPrisma (7 gebundene
Favoritenzugriffe, 5 Aufrufstellen); create() prueft vor der Icon-Suche,
dass das Ziel-Widget dem Aufrufer gehoert (T-GWH-05, Befund F aus Aufgabe 1
bestaetigt den Fremdschluessel-Durchgriff) -- Widget not found fuer alle
drei Faelle (existiert nicht/Kollege/fremder Mandant)
- favorites.controller.ts: reicht tenantId an alle fuenf Aufrufe durch,
extractContext unveraendert (dashboard-Praezedenzfall)
- settings.service.ts: getSmtpConfig/saveSmtpConfig/getDecryptedSmtpConfig
je EIN Klient (3 gebundene Zugriffe, 3 Aufrufstellen) -- Befund K damit
erfuellt; Startpfad umbenannt in loadAnySmtpConfigForStartupTransport(),
bleibt bewusst ungebunden (sechster Fall der Hintergrunddienst-Falle,
WINDOWS #TBD-GWH -- Aufgabe 3 vergibt die Nummer)
- mail.module.ts: ruft den umbenannten Startpfad auf, Kommentar nennt beide
Zustaende statt "single-tenant default"
- Vier Falsifizierungsnachweise durchgefuehrt und zurueckgenommen (siehe
SUMMARY): (a) 3 Faelle rot, (b) 4 Faelle rot, (c) 9 Faelle rot, (d) 4 Faelle
rot
Bekannt und erwartet (siehe SUMMARY, Praezedenzfall 260911-fh9): zwischen
dieser Aufgabe und Aufgabe 3 ist rls-access-inventory.spec.ts rot (2
Faelle) -- die Klassifikationstabelle ist noch nicht nachgezogen, das ist
Aufgabe 3.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- favorites.service.spec.ts (NEU, 23 Faelle): ungebundener Nachbau hat KEIN
favoriteLink/widgetInstance-Modell; erwartet die Zielsignatur
list/create/update/remove/getIconBytes(tenantId, ...) und den
Widget-Besitzriegel in create -- scheitert erwartungsgemaess an
"Cannot read properties of undefined" gegen den heutigen Dienst (22/23 rot)
- settings.service.spec.ts (NEU, 20 Faelle): ungebundener Nachbau bietet fuer
smtpConfig NUR findFirst, gebundener Klient NUR findUnique/upsert; erwartet
loadAnySmtpConfigForStartupTransport() (Startpfad-Umbenennung) und den
Null-Klienten-Nachweis fuer den Startpfad -- 18/20 rot
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- runFavoritesAreaChecks (8 Pruefungen) und runSettingsAreaChecks (9 Pruefungen)
erweitern rls-scratch-check.mjs auf 137 bestandene Pruefungen
- FavoriteLink-Wegwerftabelle traegt den Fremdschluessel auf WidgetInstance
(Befund C, WINDOWS #27), SmtpConfig-Wegwerftabelle den Eindeutigkeitsindex
- Ergebnis Pruefung 7: der Fremdschluessel prueft am Zeilenschutz vorbei
(steuert den Besitzriegel in Aufgabe 2); Pruefung 8: ungebundenes upsert
wirft PrismaClientUnknownRequestError, dieselbe Klasse wie 260910-krx
- docs/mandantentrennung-etappe2-fehlerrichtung.md: Abschnitte
## Bereich favorites (f1-f5), ## Bereich settings (s1-s5) und
## Etappe 2 -- Abschluss; Nachtraege unter Befund K in (t4) und im
Uebergaben-Absatz von (d4) -- die Reihenfolgebedingung ist erfuellt
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- auth.controller.spec.ts: NEU. Mandantenquelle je Handler (das Claim fuer
me/changePassword; fuer die oberste Rolle der Mandant des Ziels aus dem
Fan-out), unbekanntes Ziel, null-Durchreichung von me, Rollen-Metadaten
(ROLES_KEY) und Public-Metadaten (IS_PUBLIC_KEY) fuer alle sieben
Handler — 23 Faelle; Falsifizierungsnachweis am Fan-out-Zweig
durchgefuehrt und zurueckgenommen
- docs/mandantentrennung-zugriffsklassifikation.md: Uebersichtszeile auth
auf 3/10 (war 8/5), Summenzeile 78/167, Bestandsaufnahme-Zeile
auth.service.ts/user auf gebunden (keine gemischt-Zeile mehr fuer diese
Datei), Klassen-Verteilung unveraendert bei 64 Paaren mit
Stand-Vermerk, Hintergrunddienst-Abschnitt ohne sechsten Fall,
Etappe-3-Anmeldeweg-Punkt in "Was diese Etappe NICHT entscheidet";
beide Dokument-Falsifizierungen durchgefuehrt und zurueckgenommen
- .planning/WINDOWS.md: zwei neue offene Eintraege (#28 verschluckte Leere
im Frontend, Familie #23/#25/#26; #29 Rechteausweitung ADMIN->SUPER_ADMIN
im Schwesterweg PATCH /users/:id, T-FH9-05)
Baseline wiederhergestellt: 951/951 Tests gruen in 60 Dateien (927+23
neue Faelle plus der in Aufgabe 2 erwartungsgemaess rote Test), Werkzeug
120/120, Typpruefung sauber.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- auth.service.ts: die drei Nach-Anmeldungs-Methoden binden je ueber genau
einen Klienten tenantPrisma; adminResetPassword verweigert einem
Nicht-SUPER_ADMIN das Kennwort eines SUPER_ADMIN (T-FH9-04); die drei
$queryRaw-Anmeldesuchen bleiben unveraendert auf dem ungebundenen Klienten
- auth.controller.ts: me/changePassword reichen user.tenantId aus dem Claim
durch; adminResetPassword verzweigt ueber resolveTargetTenantId nach Rolle
(ADMIN: eigener Mandant; SUPER_ADMIN: gebundener Fan-out
UserService.findByIdForPlatformAdmin) — schliesst die Rechteausweitung
ueber die Mandantengrenze (T-FH9-01)
- auth.module.ts: importiert UserModule, zyklusfrei gemessen
- auth.service.spec.ts: Identitaets-Attrappe ersetzt durch zwei
unterscheidbare Klienten (__makeBoundClient); 29 Faelle, drei
Falsifizierungsnachweise durchgefuehrt und zurueckgenommen
Bekannt und erwartet: rls-access-inventory.spec.ts ist nach diesem Commit
kurzzeitig rot (Bestandsaufnahme-Zeile auth.service.ts/user zeigt noch
"gemischt", gemessen ist jetzt "gebunden") — wird in Aufgabe 3 desselben
Plans geschlossen (927/928 Tests gruen, ein bekannter, in Aufgabe 3
behobener Fehlschlag, kein neuer).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- rls-scratch-check.mjs: dreizehnter Abschnitt runAuthAreaChecks, getrennt
von runAuthLookupChecks — misst die Grenze zwischen Anmeldeweg (drei
SECURITY-DEFINER-Funktionen, unveraendert) und Nach-Anmeldung (getMe,
changePassword, adminResetPassword) ueber den generierten Client; neuer
Helfer readSchemaModelScalarFieldNames() filtert Relationsfelder
ueber ihren Typ heraus
- zehn neue, namentlich benannte Pruefungen (120/120 insgesamt), sechs davon
ueber den generierten Client an der auf 15 Spalten erweiterten
Wegwerf-Tabelle "User"; pg_proc bestaetigt SECURITY DEFINER/STABLE/festen
Suchpfad/LIMIT 1 fuer alle drei Anmeldefunktionen
- docs/mandantentrennung-etappe2-fehlerrichtung.md: neuer Abschnitt
"## Bereich auth" (h1)-(h5) vor "## Verweis" — Signaltabelle je Pfad,
getMe-Kette als "verschluckt" statt "laut", Etappe-3-Vorbehalt,
Schwesterweg-Luecke in user.controller.ts
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
findAll/findOne/remove zaehlen Benutzer je Mandant jetzt ueber drei
gebundene Aufrufstellen (tenantPrisma.user.count mit where: { tenantId
}, in remove zusaetzlich isActive: true) statt ueber den
Relationszaehler, der nach dem Scharfschalten unbemerkt unter der
Regel von User gelaufen waere (260911-e2s, Aufgabe 1, Pruefungen 5-7).
Fan-out-Muster aus UserService.findAllForPlatformAdmin uebernommen; die
vier tenant-Zugriffe bleiben ungebunden (Tenant ohne Regel). Antwortform,
Meldungen und Statuscodes unveraendert.
tenant.controller.spec.ts legt die Testlage aus dem Nichts an (20
Faelle): Zwei-Klienten-Nachweis ueber __makeBoundClient, Rollen-
Metadaten-Test (Klasse SUPER_ADMIN, kein Handler ueberschreibt), Wachhund
gegen mehrfache Klientenerzeugung. Falsifizierungsnachweis durchgefuehrt:
der probeweise ungebundene Zaehler in findOne macht 2 Faelle rot mit
"Cannot read properties of undefined (reading 'count')" — die dkv-Form
der Falsifizierung, nicht nur eine falsche Zahl —, danach zurueckgenommen.
Klassifikation und Entwicklungsanleitung nachgezogen: 64 Paare (ein
neues, tenant.controller.ts/user), Uebersichtszeile 8/3, Klassen-
Verteilung 32 muss-mandantengebunden, Erkennungsluecke fuer
Relationseinbindungen im Kopf der Bestandsaufnahme benannt, "Zwei
belegte Befunde" und "Was diese Etappe NICHT entscheidet" (erster
Punkt aufgeloest). Beide Dokument-Falsifizierungsnachweise durchgefuehrt
(falsche Klasse macht rls-access-inventory.spec.ts rot, falsche
Uebersichtszahl macht das herleitende Gate rot), zurueckgenommen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
Die vorige Aufgabe-2-Teilcommit (11f5731) hatte nur die Loeschung von
tenant.middleware.ts und die neue tenant.guard.spec.ts erfasst — ein
`git add` mit mehreren Pfaden schlug wegen eines bereits entfernten
Pfads fataler fehl und liess die restlichen fuenf Dateien unstaged,
ohne dass das beim Commit auffiel (Rule 1 — Prozessfehler, hier
korrigiert). Dieser Commit traegt den eigentlichen Umbau nach:
tenant.guard.ts ohne Prisma-Abhaengigkeit, die geleerte
FORTENANT_ASSIGNMENT_EXCEPTIONS samt Wachhund-Test in
rls-access-inventory.spec.ts, und die drei berichtigten
Kommentarzeilen (app.module.ts, module.guard.ts, dkv.controller.ts).
Inhaltlich identisch mit dem, was bereits verifiziert wurde (891 Tests
gruen, Typpruefung sauber) — nur die Staging-Reihenfolge war fehlerhaft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
Die seit Etappe 1 offene Architekturfrage zum gebundenen Klienten auf
dem Anfrageobjekt ist entschieden: neun umgestellte Bereiche binden
ausnahmslos dienst-intern (ein Klient je Methode), ein Klient auf
req.tenantPrisma ohne Leser war tote Verdrahtung, die wie ein
Sicherheitsmechanismus aussah. tenant.guard.ts verliert die
Prisma-Abhaengigkeit und setzt nur noch req.tenantId; die nie
verdrahtete tenant.middleware.ts (identische Logik, in keinem Modul
registriert) ist geloescht.
tenant.guard.spec.ts legt die Testlage aus dem Nichts an — alle fuenf
Zweige (kein Nutzer, USER, ADMIN mit ignorierter x-tenant-id-Kopfzeile
T-04-03, SUPER_ADMIN mit/ohne Wechsel, mandantenloser Nicht-SUPER_ADMIN)
sowie die Abwesenheit der alten Eigenschaft in jedem Durchlass-Fall.
Falsifizierungsnachweis durchgefuehrt: das probeweise Wiedereinfuehren
der alten Zuweisung macht 4 der 7 Faelle rot (u. a. "expected true to
be false" auf 'tenantPrisma' in req), danach zurueckgenommen.
rls-access-inventory.spec.ts: FORTENANT_ASSIGNMENT_EXCEPTIONS ist leer
und selbstpruefend (neuer Wachhund gegen veraltete Eintraege). Drei
Fremdkommentare (app.module.ts, module.guard.ts, dkv.controller.ts)
korrigiert, die noch auf die nie verdrahtete Middleware verwiesen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
runTenantAreaChecks (9 neue Pruefungen, 6 davon ueber den generierten
Client) belegt: auf "Tenant" ist nichts zu binden (keine Regel in allen
34 Migrationen einschliesslich 20260910120000), aber der
Relationszaehler in findAll/findOne/remove liefert nach dem
Scharfschalten userCount=0 fuer jeden Mandanten und laesst den
Loeschriegel T-02-09 vakuum werden — der Fremdschluessel faengt das
nur laut (500) statt mit der verstaendlichen 400-Meldung ab.
docs/mandantentrennung-etappe2-fehlerrichtung.md bekommt den Abschnitt
"## Bereich tenant" (n1-n5) mit der tatsaechlich beobachteten
Werkzeugausgabe, der Signaltabelle je Pfad, den Frontend-Stellen, die
die falsche Zahl unkommentiert durchlassen, und der Entscheidung zur
Anfrageobjekt-Eigenschaft (Vorbereitung fuer Aufgabe 2).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- calendar.service.spec.ts: NEU, Zwei-Klienten-Nachweis ueber __makeBoundClient
(Muster dkv.service.spec.ts), Attrappen fuer CryptoService und die drei
Provider, 23 Testfaelle: getSources/addSource, updateSource inkl. drei
Erhaltungsfaelle, alle drei Besitzpruefungen je Ausnahmeart, testConnection
Erfolgs-/Fehlerpfad, aggregateEvents/fetchAndCacheEvents inkl. beider
Synchronstatus-Rueckschreibungen, Cache-Verhalten inkl. Nutzer-Trennung,
testConnectionFromConfig ohne DB-Zugriff, Wachhund fuer genau einen
gebundenen Klienten je Aufruf
- calendar.service.ts: alle 12 Zugriffe auf forTenant() umgestellt, ein
tenantPrisma-Klient je Methode (getSources, addSource, updateSource,
deleteSource, testConnection, fetchAndCacheEvents); Cache-Schluessel-Urteil
und die kein-sechster-Hintergrunddienst-Begruendung als Kommentare
festgehalten
- calendar.controller.ts: alle sechs kontextnutzenden Handler reichen
Benutzer- UND Mandantenkennung aus extractContext durch, keine neue
Vertrauensquelle
- Falsifizierungsnachweis durchgefuehrt: eine Rueckschreibung testweise
entbunden, benannter Test ging rot ("aggregateEvents, Erfolgspfad"), Fund
bestaetigt, zurueckgenommen
- docs/mandantentrennung-zugriffsklassifikation.md: Bestandsaufnahme-Zeile
calendar.service.ts/calendarSource auf gebunden gezogen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
- rls-scratch-check.mjs: elfter Abschnitt runCalendarAreaChecks mit 13
namentlich benannten Pruefungen gegen die aus 20260909140000_rls_remaining_tenant_tables
geschnittene Regel, davon 4 ueber den generierten Client an einer
schemagleichen Wegwerf-Tabelle (17 Spalten, gegen schema.prisma
laufzeitgeprueft); Laufzeitpruefung, dass 20260910120000 keine eigene
CalendarSource-Regel traegt (Messfalle 260910-jab)
- Alle 101 Pruefungen bestehen (88 bisherige + 13 neue)
- docs/mandantentrennung-etappe2-fehlerrichtung.md: Abschnitt
"## Bereich calendar" mit (k1)-(k5) — Messung, Signaltabelle je Pfad,
Leere-als-Abwesenheit in Backend UND Frontend samt Fehlerverschluckung,
bewusst nicht geloest (Cache-Schluessel-Urteil, Zugangsdaten-Erhaltung,
fehlende Benutzerdimension, 403/404), bewusst nicht angefasst
- Wettlauf-Fehlerklasse gemessen: PrismaClientKnownRequestError (P2025),
NICHT PrismaClientUnknownRequestError wie im Bereich dashboard — Aufgabe
2 braucht deshalb keine neue Fehleruebersetzung fuer die Besitzpruefungen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
Aufgabe 3 — TDD zuerst (7 weitere Faelle in dashboard.service.spec.ts, 20
vorher/27 nach dieser Aufgabe), dann die Umstellung:
- getSearchProviders/addSearchProvider/removeSearchProvider laufen ueber
forTenant(); removeSearchProvider fuehrt Besitzpruefung UND Schreibzugriff
ueber DENSELBEN gebundenen Klienten. Die drei Vorgabe-Suchmaschinen aus
der Konstante bleiben unveraendert vorangestellt.
- Der eine Katalogzugriff (this.prisma.module in getWidgets) bleibt
begruendet ungebunden: Messung und Bedingung getrennt (Tabelle traegt
heute keinen Zeilenschutz, wirkungslos statt katastrophal — katastrophal
erst, wenn Etappe 3 eine Regel gibt), unter Berufung auf die bestehende
Werkzeugpruefung module-tabelle-traegt-keinen-zeilenschutz statt einer
neuen Behauptung. Ein Wachhund-Testfall haelt den Katalogzugriff aus dem
Bindungsprotokoll heraus (und beweist zuerst, dass der Katalogpfad
tatsaechlich durchlaufen wird, nicht nur theoretisch geprueft ist).
- docs/mandantentrennung-zugriffsklassifikation.md an allen fuenf
handgepflegten Stellen nachgezogen: vier Bestandsaufnahme-Zeilen (inkl.
eigenstaendiger Nachpruefung der widerlegten SearchProvider-Praemisse),
Uebersichtszeile (13/0 -> 1/12), Summenzeile (95/147), Klassen-Verteilung
(unveraendert 63 Paare, ausdruecklich vermerkt), Hintergrunddienst-
Abschnitt (dashboard hat keinen sechsten Fall, mit Messanweisung), "Was
diese Etappe NICHT entscheidet" (dienst-interner forTenant()-Weg wie alle
sieben Bereiche vor ihm).
- .planning/WINDOWS.md traegt Eintrag #25 (offen, Tabelle + JSON): die
beweisvernichtende Schleife (leeres Dashboard -> Neuaufbau ->
automatisches Zurueckschreiben -> ueberschriebene Anordnung, Widget-
Dubletten) samt der Vorabpruefung fuer Etappe 4 und dem Verweis auf #22
fuer die verwandte Eindeutigkeitsfrage.
Zwei weitere Falsifizierungsnachweise durchgefuehrt: (1) den Katalogzugriff
probeweise gebunden (tenantPrisma.module.findMany) — acht Tests werden rot
mit "TypeError: Cannot read properties of undefined (reading 'findMany')",
weil `module` bewusst nicht in der Testdouble-Bindungsliste steht; Rueckbau
zurueckgenommen, 27/27 wieder gruen. (2) den Stand von dashboardLayout in
der Klassifikationsdatei probeweise auf "ungebunden" gesetzt —
rls-access-inventory.spec.ts wird rot mit "Abweichender Stand (Dokument vs.
Quelltext): ... dokumentiert=ungebunden, gemessen=gebunden"; Ruecknahme,
Testlauf wieder gruen (10/10).
Baseline gehalten: 858 Tests / 56 Dateien gruen, Typpruefung sauber,
Wegwerf-Werkzeug 87/87. Schalter bleibt aus.
Aufgabe 2 — TDD zuerst (20 Faelle in dashboard.service.spec.ts, 8 vorher/12
neu, Zwei-Klienten-Nachweis ueber __makeBoundClient nach dem Muster von
module-access.service.spec.ts), dann die Umstellung:
- getLayout/saveLayout laufen GEMEINSAM gebunden (ein Testfall nagelt das
fest); saveLayout uebersetzt eine gebundene Konflikt-Schreibung
(PrismaClientUnknownRequestError, gemessen in Aufgabe 1 — NICHT P2002)
in eine deutsche ConflictException.
- getWidgets/addWidget/updateWidgetConfig/removeWidget laufen gebunden;
die drei Besitzpruefungen ueber die Benutzerkennung bleiben unveraendert
bestehen (die Regeln dieses Bereichs kennen keine Benutzerdimension).
updateWidgetConfig/removeWidget fuehren Besitzpruefung UND Schreibzugriff
ueber DENSELBEN gebundenen Klienten.
- dashboard.controller.ts reicht den bereits aufgeloesten Mandanten bei
getLayout/updateWidgetConfig/removeWidget durch (keine neue
Vertrauensquelle, weiterhin aus extractContext/Sitzungsnachweis).
- Der Modulkatalog und die vier Suchmaschinenzugriffe bleiben in dieser
Aufgabe unveraendert (Aufgabe 3).
- Falsifizierungsnachweis durchgefuehrt: tenantPrisma.widgetInstance.delete
probeweise auf this.prisma zurueckgebaut — Test "Widget entfernen: ebenso,
beide Abfragen ueber denselben Klienten" wird rot mit "erwarteter
gebundener Aufruf widgetInstance.delete(tenant=tenant-1) fehlt im
Protokoll"; Rueckbau zurueckgenommen, Testlauf wieder gruen (20/20).
Zwei dokumentierte Abweichungen (Rule 3): (1) Befund A hatte fuer
widgetInstance sieben Treffer vorhergesagt, gemessen sind sechs (macht
zusammen mit dashboardLayout acht statt neun) — der Verify-Schwellwert
wird entsprechend auf >=8 gelesen. (2) Die Stand-Spalte fuer
dashboardLayout/widgetInstance in der Klassifikationsdatei wird bereits
hier minimal nachgezogen (nicht erst in Aufgabe 3), weil
rls-access-inventory.spec.ts sonst am Ende dieser Aufgabe rot waere —
derselbe Praezedenzfall wie 260910-exd, Aufgabe 2.
Baseline gehalten: 851 Tests / 56 Dateien gruen (839 + 12 neue), Typpruefung
sauber, Wegwerf-Werkzeug 87/87.
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.
- 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
- 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
- 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
- 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
- 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
- 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