Files
tessera-ctl/.planning/quick/260909-ab3-matrix-suche-und-sync-meldungen-reparier/260909-ab3-SUMMARY.md
T
schalli efcf11c988
Tessera CI/CD / Lint & Type Check (push) Successful in 48s
Tessera CI/CD / Tests (push) Successful in 56s
Tessera CI/CD / Build & Publish Images (push) Successful in 1m45s
docs(quick-260909-ab3): Matrix-Suche und Sync-Meldungen — Plan, Bericht, Verifikation
Verifikation unabhaengig nachgemessen: 651/651 API-Tests, 233/233 Web-Tests,
beide Typpruefungen sauber.

Der Sicherheitsfund T-Q3-01 wurde nicht nur gruen getestet, sondern
falsifiziert: der Verifizierer hat die neue Besitzpruefung testweise
zurueckgebaut, woraufhin der Test fehlschlug und die fremde Adresse
tatsaechlich in prisma.user.update() landete. Danach sauber zurueckgesetzt.

Ebenso gegengeprueft: beide Anlege-Wege (Sync und Einzelimport) nutzen
denselben Kollisionsentscheider, im ausgelieferten Code stehen keine
kundenspezifischen Namen oder Adressen, rohe ORM-Texte erreichen die
Oberflaeche nicht mehr, und die Gruppensuche unter internem wie AD-Namen
(#6c) ist per Regressionstest gesichert.

Eine Abweichung des Ausfuehrenden ist dokumentiert und bestaetigt: die im
Plan vorgesehene Testvorlage war wegen Kurzschlussauswertung schon gegen den
unveraenderten Code gruen; mit einem zweiten, nicht passenden Konto
reproduziert sie den Absturz nun wirklich.

Status human_needed: die fuenf Browser-Pruefungen brauchen einen Neubau und
sind Sache des Users. WINDOWS #14 und #15 bleiben bis dahin offen.

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

12 KiB
Raw Blame History

phase, plan, subsystem, tags, status, dependency-graph, tech-stack, actuals, decisions, metrics
phase plan subsystem tags status dependency-graph tech-stack actuals decisions metrics
quick-260909-ab3 01 ldap
ldap
sync
i18n
ui
prisma
react
complete
requires provides affects
Freigaben-Matrix: Suche filtert Modul- und Gruppen-Achse unabhaengig — ein Treffer auf nur einer Achse leert die andere nicht mehr
resolveEmailForWrite(): geteilter Kollisionsentscheider fuer AD-Kontoanlage (LdapService.upsertMappedUser + importUsersByDn)
LdapSyncResult.{emailConflicts,skippedNoLogin,entryFailures}: strukturierter Sync-Bericht ohne rohen ORM-/englischen Techniktext
User.email optional (Migration geschrieben, nicht ausgefuehrt)
apps/web/src/app/(portal)/admin/modules/grants/page.tsx
apps/api/src/ldap/ldap.service.ts
apps/api/src/user/user.service.ts
apps/web/src/app/(portal)/admin/ldap/page.tsx
apps/web/src/app/(portal)/admin/groups/components/GroupMembersModal.tsx
apps/web/src/app/(portal)/admin/users/page.tsx
added patterns
Kollisionsentscheider als private Methode, die vor jeder E-Mail-Schreibung prisma.user.findUnique befragt und nur zurueckgibt, wenn niemand die Adresse haelt oder der Halter der eigene Datensatz ist — nie ein Umhaengen zwischen Konten
Rohe ORM-/Techniktexte gehen ausschliesslich ueber logger.error ins Serverprotokoll; der Administrator sieht nur die Kennung des betroffenen Eintrags
tokens tasks commits
10082 3 3
Task 3 Testfixture von der Plan-Vorgabe abgewichen: ein einzelnes Konto mit passendem Benutzernamen loest den Absturz wegen OR-Kurzschlussauswertung nie aus (bestaetigt: Test war mit der Plan-Fixture gruen gegen den unveraenderten Bestand). Ein zweites, nicht-treffendes Konto ohne Adresse in derselben Mock-Liste macht den Test echt rot — siehe Deviations.
duration completed
13 min 2026-09-09

Quick Task 260909-ab3: Matrix-Suche und Sync-Meldungen reparieren Summary

Freigaben-Matrix-Suche filtert Modul- und Gruppenachse jetzt unabhaengig (WINDOWS #14 geschlossen); AD-Sync legt Konten mit kollidierender E-Mail-Adresse an — ohne Adresse — statt sie stillschweigend zu uebergehen, und der Bericht spricht durchgehend verstaendliches Deutsch (WINDOWS #15 geschlossen).

What Was Built

Drei getrennte, jeweils fuer sich lauffaehige Commits — exakt wie im Plan vorgegeben.

Task 1 — Matrix-Suche filtert nur noch die getroffene Achse (commit e8c2411)

  • page.tsx: filteredModules/filteredGroups durch einen gemeinsamen useMemo ersetzt, der moduleHits/groupHits getrennt ermittelt und nach der im Plan festgelegten Wahrheitstabelle kombiniert (leeres Feld -> alles; nur eine Achse trifft -> die andere Achse bleibt vollstaendig stehen; beide treffen -> beide gefiltert; nichts trifft -> noMatch).
  • Regressionsschutz fuer WINDOWS #6c (Spaltensuche findet eine importierte Gruppe sowohl unter internem als auch AD-Namen) unveraendert erhalten und mit einem eigenen Testfall abgesichert.
  • Neue Meldung adminModules.grants.noSearchResults in de.json/en.json — de.json folgt der im Bestand bereits etablierten typografischen Anfuehrungszeichen-Konvention („...\", siehe nameCollisionError).
  • Vier neue Testfaelle in grants-matrix.test.tsx vorab gegen den unveraenderten Bestand rot gelaufen — alle vier aus den im Plan vorhergesagten Gruenden (fehlende Modul-/Gruppennamen bzw. fehlender Meldungstext).

Task 2 — Kollidierende AD-Konten werden angelegt, ohne Adresse (commit 1222951)

  • schema.prisma: User.email auf String? @unique gestellt; Migration 20260909120000_user_email_optional geschrieben (nur DROP NOT NULL, Eindeutigkeitsindex unangetastet) — nicht ausgefuehrt, wie vom Plan verlangt.
  • ldap.service.ts: neue private Methode resolveEmailForWrite(desiredEmail, ownRecordId) fragt prisma.user.findUnique({where:{email}})) und gibt die Adresse nur frei, wenn niemand sie haelt oder der Halter der eigene Datensatz ist (T-Q3-01) — sonst wird kein Konto umgehaengt, sondern ein Kollisionsvermerk erzeugt.
  • In upsertMappedUser (Sync-Pfad) UND importUsersByDn (Handimport-Pfad) verdrahtet, damit der zweite Anlageweg nicht als Luecke mit rohem Datenbanktext bestehen bleibt.
  • LdapSyncResult um emailConflicts ({account, email}[]), skippedNoLogin (string[]) und entryFailures (string[]) erweitert. Die englische "no username mapped"-Meldung im Sync-Pfad ist verschwunden (wandert jetzt in skippedNoLogin); der generische Catch-Block schreibt den technischen Wortlaut nur noch via this.logger.error, nie in den Bericht (T-Q3-02).
  • UserService.create nimmt email jetzt optional entgegen. Type-Check deckte genau die im Plan vorhergesagten zwei Fundstellen auf (tender-digest.scheduler.ts:140, tender-matching.service.ts:131) — beide brechen jetzt mit continue ab, wenn der Empfaenger keine Adresse hat.
  • Fuenf neue Testfaelle in ldap.service.spec.ts vorab gegen den unveraenderten Bestand rot gelaufen, jeweils aus dem im Plan vorhergesagten Grund. Eine bestehende toEqual-Assertion (empty base DN no-op-Test) musste um die drei neuen Ergebnisfelder ergaenzt werden (legitime Bestandspflege, keine Verhaltensaenderung).

Task 3 — Verstaendlicher Sync-Bericht und Konten ohne Adresse in der UI (commit 2167046)

  • admin/ldap/page.tsx: SyncResult-Interface um die drei neuen Felder erweitert; drei neue Berichtsabschnitte in der vorgegebenen Reihenfolge (Kollision: Bernstein wie defaultMarkerMoved; uebersprungen ohne Anmeldenamen: text-muted-foreground, ausdruecklich kein Rot; unerwartete Fehler: text-destructive).
  • Vier neue Sprachschluessel unter admin.ldap.sync in de.json/en.json; Umlaut-Gate lief gruen ohne neue Allowlist-Eintraege (nur "Adresse" traf das ss-Muster, war bereits erlaubt).
  • GroupMembersModal.tsx: TenantUser.email auf string | null, Suchvergleich (Zeile 104) analog zum bestehenden displayName-Muster mit (u.email ?? '') abgesichert.
  • users/page.tsx: User.email auf string | null; Formularuebernahme faellt auf '' zurueck, Tabellenzelle zeigt einen Gedankenstrich (–) statt einer leeren Zelle. UserFormData.email bleibt Pflichtfeld fuer die Handanlage.

Verification Results

  • API-Testsuite: pnpm --filter @tessera/api exec vitest run — 651/651 gruen (46 Dateien), inklusive ldap.service.spec.ts mit 67/67 (62 bestehend + 5 neu).
  • API-Typpruefung: pnpm --filter @tessera/api type-check — sauber.
  • prisma validate/prisma generate: beide sauber; Migration existiert, wurde nicht ausgefuehrt.
  • Web-Testsuite: pnpm --filter @tessera/web exec vitest run — 233/233 gruen (38 Dateien), inklusive grants-matrix.test.tsx (12/12) und groups-page.test.tsx (13/13).
  • Web-Typpruefung: pnpm --filter @tessera/web type-check — sauber.
  • Sprachschluessel-/Umlaut-Gate: umlaut-guard.spec.ts gruen bei jedem Zwischenstand — alle neuen Schluessel existieren in de.json UND en.json, kein neues, ungeprueftes Umlaut-Ersatzwort.
  • Alle vier Befehle aus <verification> des Plans ein weiteres Mal am Ende, vom Wurzelverzeichnis aus, gruen.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Task-3-Testfixture konnte den bezeichneten Absturz nicht ausloesen

  • Found during: Task 3, RED-Lauf vor der Implementierung
  • Issue: Der Plan gibt exakt einen Mock-Benutzer vor ({ id: 'u12', username: 'funktionskonto', displayName: null, email: null }) und den Suchbegriff 'funktion'. Da 'funktionskonto'.includes('funktion') bereits ueber u.username matcht, greift die JavaScript-OR-Kurzschlussauswertung (||) VOR der ungesicherten u.email.toLowerCase()-Zeile — der Testlauf gegen den unveraenderten Bestand war gruen, nicht rot wie vom Plan vorhergesagt. Empirisch mit node -e bestaetigt (siehe Commit-Nachricht).
  • Fix: Ein zweites Konto ({ id: 'u13', username: 'anderes.konto', displayName: null, email: null }) in dieselbe Mock-/users-Antwort aufgenommen. Sein Benutzername matcht den Suchbegriff nicht, sodass die OR-Kette bis zur ungesicherten Zeile durchlaeuft und dort tatsaechlich abstuerzt — Testlauf danach korrekt rot, exakt an GroupMembersModal.tsx:104. funktionskonto bleibt Teil der Fixture und der Assertion, wie vom Plan verlangt.
  • Files modified: apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx
  • Verification: Test rot vor der Implementierung (TypeError an der vorhergesagten Zeile), gruen danach; volle Testsuite (233/233) bleibt gruen.
  • Committed in: 2167046 (Task-3-Commit)

Total deviations: 1 auto-fixed (Rule 1 — Testfixture-Korrektur, kein Produktionscode-Verhalten geaendert) Impact on plan: Der zugrunde liegende Defekt (ungesichertes .toLowerCase() auf einer moeglicherweise leeren Adresse) war real und wurde wie geplant behoben; nur die Testdaten mussten angepasst werden, damit der Test die Behauptung tatsaechlich pruefte statt trivial gruen zu sein.

Threat Model Verification

Alle sechs mitigate-Eintraege aus dem Plan-Threat-Register sind umgesetzt:

  • T-Q3-01 (Spoofing, upsertMappedUser-Aktualisierungszweig): Mitigiert — resolveEmailForWrite vergleicht Halter-ID gegen die eigene Datensatz-ID; Test 3 aus Task 2 belegt, dass prisma.user.update ohne email-Feld aufgerufen wird, wenn die Adresse einem Dritten gehoert.
  • T-Q3-02 (Information Disclosure, Sync-Bericht): Mitigiert — Test 5 aus Task 2 belegt per JSON.stringify(result)-Negativpruefung, dass der geworfene ORM-Text nirgends im Bericht landet; er erscheint nur im logger.error-Aufruf.
  • T-Q3-04 (Spoofing, Passwort-Zuruecksetzung bei leerer Adresse): Bestaetigt ohne Codeaenderung — die Suche in auth.service.ts laeuft ueber eine angefragte Zeichenkette, ein null-Wert in der Spalte wird davon nie getroffen.
  • T-Q3-03/T-Q3-05/T-Q3-06/T-Q3-SC (accept): Keine Codeaenderung noetig, Einschaetzung des Plans bestaetigt (keine neuen Pakete installiert, kein zusaetzliches DoS-Risiko bei den gemessenen Groessenordnungen).

Self-Check: PASSED

Files:

  • FOUND: apps/web/src/app/(portal)/admin/modules/grants/page.tsx
  • FOUND: apps/web/src/app/(portal)/admin/modules/grants/grants-matrix.test.tsx
  • FOUND: apps/api/prisma/schema.prisma
  • FOUND: apps/api/prisma/migrations/20260909120000_user_email_optional/migration.sql
  • FOUND: apps/api/src/ldap/ldap.service.ts
  • FOUND: apps/api/src/ldap/ldap.service.spec.ts
  • FOUND: apps/api/src/user/user.service.ts
  • FOUND: apps/api/src/tenders/tender-digest.scheduler.ts
  • FOUND: apps/api/src/tenders/tender-matching.service.ts
  • FOUND: apps/web/src/app/(portal)/admin/ldap/page.tsx
  • FOUND: apps/web/src/app/(portal)/admin/users/page.tsx
  • FOUND: apps/web/src/app/(portal)/admin/groups/components/GroupMembersModal.tsx
  • FOUND: apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx
  • FOUND: apps/web/src/messages/de.json
  • FOUND: apps/web/src/messages/en.json

Commits:

Deployment Note (vom Plan uebernommen)

Dieser Plan hat nichts ausgerollt: kein docker compose build, kein up, kein prisma migrate deploy. Die Migration 20260909120000_user_email_optional steckt im API-Image und wird beim naechsten Neubau/Neustart der API automatisch angewandt (apps/api/Dockerfile:38 ruft prisma migrate deploy vor dem Start auf). Solange das nicht geschehen ist, bleibt User.email in der laufenden Datenbank ein Pflichtfeld — ein kollidierendes Konto wird dann weiterhin nicht angelegt, scheitert aber sauber als gezaehlter entryFailures-Eintrag statt mit rohem Techniktext.

Die fuenf im Plan genannten Browser-Nachpruefungen (Punkte 1-5 unter <verification>) sind vom Nutzer nach seinem naechsten Neubau von API und Web nachzuholen — nicht Teil dieser Ausfuehrung.