catch (e: any) in groups, module-grants, ldap, user, admin-seed, calendar
und vier tenders-Diensten auf catch (e: unknown) umgestellt. Die
Eingrenzung passiert an der Verwendungsstelle, nicht per Zusicherung.
Neu: apps/api/src/prisma/prisma-error.ts mit prismaErrorCode() und
prismaErrorTarget(). Bewusst Form-Pruefungen statt instanceof
Prisma.PrismaClientKnownRequestError - gemessen: samtliche Testdoppel in
apps/api werfen new Error(...) mit angehaengtem .code (groups, user, ldap,
tenders, module-grants, admin-seed) und dashboard.service.spec.ts:451 ein
reines { code: 'P2002' }. Ein instanceof-Test haette all diese Werte in den
anderen Zweig geschickt - Verhaltensaenderung, verboten nach D-03/T-M34-06.
Die Helfer bilden err?.code und err?.meta?.target eins zu eins ab.
ldap.service.ts liest zusaetzlich meta.target; prismaErrorTarget() gibt
unknown zurueck, weil der Bestand dort Array UND Zeichenkette getrennt
behandelt - ein engerer Typ waere eine Behauptung.
calendar.service.ts:341 nutzt instanceof Error statt e?.message: gemessen
wirft validateUrlNotPrivate() ausschliesslich ForbiddenException (der
eigene catch dort setzt jeden Fremdfehler in eine um), also trifft
instanceof dieselben Faelle. Ersatzzweig 'URL not allowed' unveraendert.
noExplicitAny in apps/api/src: 56 -> 38. type-check 4/4, lint 5/5 (0
error), apps/api 72/1143, apps/web 73/531, rls-access-inventory 30/30.
noNonNullAssertion 56, as unknown as 33, Unterdrueckungsmarker 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
- prisma-tenant.extension.ts: (prisma as any) und die Handannotation an
$allOperations in forTenant()/forSystem() entfernt; Kopfkommentar
unveraendert. .then((results: any[]) => ...) auf unknown[] umgestellt.
- 105 Aufrufstellen `const X = forTenant(...) as any` / `forSystem(...) as
any` von der Zusicherung befreit, Zuweisungsform woertlich erhalten
(rls-access-inventory.spec.ts bleibt scharf, 30/30 gruen einzeln
geprueft).
- withTenantTransaction(): Prisma.TransactionClient fuer tx probiert,
gemessen verworfen - bricht das Testdoppel in
prisma-tenant.extension.spec.ts (TS2322 auf einem absichtlich
unvollstaendigen Fake-Objekt). tx bleibt any, mit Begruendung am Typ.
- Gefolge des jetzt getypten Klienten entfernt: any[]-Annotationen und
.map((x: any) => ...) in groups.service.ts, module-grants.service.ts,
dkv.service.ts, ldap-config.service.ts, tenders.controller.ts:270.
- Befund (D-03): tender-matching.service.ts:159 trug eine Handannotation
(match: { tender: unknown }), die den Wert nur deshalb auf unknown
verengte, um TS7006 unter dem alten any-Klienten zu vermeiden - mit dem
getypten Klienten war das falsch. Annotation geloescht, kein Ersatz
durch Zusicherung.
- Zwei any bleiben gezielt in groups.service.ts (u/a in
ensureDefaultGroup(), gefolge von tx: any) - Begruendung am Code.
noExplicitAny apps/api/src: 288 -> 149 (Schranke 155). type-check 4/4,
lint 5/5 (0 error). apps/api 72/1143 gruen, apps/web 73/531 gruen,
rls-access-inventory.spec.ts 30/30 gruen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
- 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
tender-saved-search.service.ts, tender-triage.service.ts,
tender-notification-pref.service.ts und tender-email-config.service.ts
laufen jetzt vollstaendig ueber forTenant() — vier neue Parameter (list,
update, remove, listForUser, favoriteIds, getForUser, getConfigForApi,
testConnection bekommen tenantId), die anwendungsseitige userId-Filterung
bleibt unveraendert (Befund E: die Policies haben keine Benutzerdimension).
tender-rss-feed.service.ts bindet nur createForUser (Zaehler + Anlage,
beide ausschliesslich auf persoenlichen Zeilen); listForUser, createPlatform
und remove bleiben mit Codekommentar bewusst ungebunden (WINDOWS #19 —
eine gebundene plattformweite Zeile waere unter jedem Mandanten unsichtbar,
ein gebundenes Einfuegen ohne Mandant wuerde abgewiesen).
tender-notification-pref.service.ts und tender-email-config.service.ts
uebersetzen eine P2002-Verletzung auf dem tenantlosen upsert-Schluessel
(Befund F) in eine verstaendliche deutsche Meldung statt eines rohen
Fehlers.
tenders.controller.ts reicht tenantId an den acht betroffenen
Aufrufstellen durch extractTriageContext() durch (kein neuer
Aufloesungsweg); die drei RSS-Aufrufstellen bleiben unveraendert, da ihre
Dienstmethoden nicht binden.
Alle sieben angefassten Testdateien bekommen den Zwei-Client-Nachweis
(__makeBoundClient ueber demselben Speicher) und Bindungstests je
umgestellter Methode; tender-rss-feed.service.spec.ts zusaetzlich den
Gegentest, dass die drei unveraendert bleibenden Pfade forTenant() NICHT
aufrufen. Falsifiziert: ein probeweiser Rueckbau der list()-Bindung in
tender-saved-search.service.ts machte genau den erwarteten Bindungstest
rot, danach zurueckgenommen.
docs/mandantentrennung-zugriffsklassifikation.md: Stand der fuenf Paare
auf gebunden bzw. gemischt nachgezogen; tenderRssFeedSource von
muss-mandantengebunden auf beides umklassifiziert (derselbe Praezedenzfall
wie ldapConfig in 260909-ipc).
761 Tests gruen (743 + 18 neue Bindungsnachweise), Typpruefung sauber,
Wegwerf-Werkzeug 32/32, kein Schema-/Migrations-/Compose-/
Umgebungsdatei-Diff.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
TenderTriageService upserts per-user read/favourite state on the
@@unique([userId,tenderId]) target (idempotent), scopes every query by
userId (V4/IDOR, FavoritesService pattern), and exposes favoriteIds() for
the upcoming favOnly filter. Migration 20260721160000_add_tender_triage
applied to the local dev DB (FK ON DELETE CASCADE verified via \d),
Prisma client regenerated. All 8 spec cases green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>