Files

19 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, estimate, must_haves
phase plan type wave depends_on files_modified autonomous requirements estimate must_haves
16-ad-gruppen-synchronisation 05 execute 4
16-03
16-04
apps/web/src/app/(portal)/admin/ldap/page.tsx
apps/web/src/app/(portal)/admin/modules/grants/page.tsx
apps/web/src/messages/de.json
apps/web/src/messages/en.json
true
PERM-02
tokens raw_tokens tasks confidence
44000 44000 2 low
truths artifacts key_links prohibitions
Der Sync-Bericht zeigt neben den Benutzerzahlen auch die Gruppenmitgliedschafts-Zahlen (Backend-Luecke seit D-21) und die Gruppen-Zahlen aus dieser Phase — kein vom Backend geliefertes Zaehlfeld bleibt unangezeigt.
Wurde die Standardgruppen-Markierung im Lauf neu vergeben, erscheint dazu eine eigene, informativ-warnende Zeile — nur dann, nicht als Dauerzeile mit 0 (D-06).
Ein insgesamt fehlgeschlagener Sync-Request zeigt einen sichtbaren Fehlerzustand statt eines Berichts mit Nullen.
Die Spaltenkoepfe der Freigabe-Matrix zeigen internalName ?? name als Text und als title (D-04).
Die Suche ueber den Gruppen-Spaltenfilter findet eine Gruppe sowohl ueber ihren internen als auch ueber ihren AD-Namen.
UI E2/empty: Vor dem ersten Sync-Lauf wird der Ergebnis-Container gar nicht gerendert — kein leerer Bericht mit Nullen.
UI E2/loading: Waehrend des Laufs traegt der Sync-Button seinen Ladetext und ist deaktiviert; der Bericht erscheint erst nach Abschluss, ohne Zwischenzustand mit Teilzahlen.
UI E2/error: Schlaegt der Sync-Request insgesamt fehl, erscheint ein sichtbarer Fehlerzustand statt eines stillen No-ops.
UI E2/populated: Der Bericht rendert die drei festen Zahlenzeilen (Benutzer, Gruppenmitgliedschaften, AD-Gruppen) immer, auch bei Werten von 0.
UI E2/partial: Ein teilweise fehlgeschlagener Lauf zeigt Zahlenzeilen und Fehlerliste gleichzeitig — die Zahlen werden nicht unterdrueckt, wenn Fehler vorliegen.
UI E2/zero-one-many: Die Zeile zur verschobenen Standardmarkierung erscheint nur bei einem Wert groesser 0; die Fehlerliste nur bei nicht-leerer Fehlerliste.
UI E5/empty: Bestehender Empty-State der Matrix unveraendert; dieser Plan aendert nur den Textwert der Gruppen-Spaltenkoepfe.
UI E5/loading: Bestehender Ladezustand der Matrix unveraendert.
UI E5/error: Bestehendes Fehlerverhalten der Matrix unveraendert.
UI E5/populated: Jeder Gruppen-Spaltenkopf traegt den Anzeigenamen als Text und als title; das aria-label der Matrix-Checkboxen folgt automatisch demselben Wert.
UI E5/partial: Fehlt der interne Name, greift der Fallback auf den gespeicherten Namen — kein leerer Spaltenkopf.
UI E5/overflow: Bestehendes sticky-Verhalten mit horizontalem Scroll unveraendert; keine zusaetzliche Spalte.
UI E5/zero-one-many: Die Spaltenanzahl folgt der gefilterten Gruppenliste wie bisher, unveraendert.
UI E5/long-text: Die bestehende Breitenbegrenzung mit Abschneiden plus title-Tooltip wirkt jetzt auf den berechneten Anzeigenamen.
statement verification
UI E2/overflow: Eine lange Fehlerliste aus einem Lauf ueber viele Gruppen sprengt den Berichts-Container nicht. backstop
statement verification
UI E2/long-text: Eine lange Fehlermeldung mit langem Gruppennamen bricht im Bericht um statt horizontal ueberzulaufen. backstop
apps/web/src/app/(portal)/admin/ldap/page.tsx — erweitertes SyncResult-Interface, drei bis vier Berichtszeilen, sichtbarer Sync-Request-Fehler
apps/web/src/app/(portal)/admin/modules/grants/page.tsx — Group-Interface um internalName, Spaltenkopf und Suchfilter mit Fallback
apps/web/src/messages/de.json + en.json — admin.ldap.sync.resultGroupMemberships, .resultGroups, .defaultMarkerMoved, .requestError
LdapSyncResult (Backend, Plan 16-03) ↔ SyncResult (Frontend): beide muessen im selben Schritt um dieselben Feldnamen wachsen, sonst wiederholt sich die stille Luecke aus D-21
GET /module-grants/matrix liefert vollstaendige Group-Zeilen ohne select — internalName kommt seit der Migration aus Plan 16-01 automatisch mit; ein spaeter eingefuegtes select wuerde den Spaltenkopf still auf den AD-Namen zuruecksetzen
Der Sync-Bericht darf kein vom Backend geliefertes Zaehlfeld unterschlagen — ein Feld, das der Bericht nicht anzeigt, ist genau die stumme Luecke, die seit D-21 offen war.
Die Meldung ueber eine neu vergebene Standardgruppen-Markierung darf nicht wie ein Fehler aussehen — sie ist ein bemerkenswerter, aber bewusst akzeptierter Vorgang.
Ein fehlgeschlagener Sync-Request darf nicht als Bericht mit lauter Nullen erscheinen — das wuerde einen nie gelaufenen Sync als erfolgreichen No-Op ausgeben.
Der Sync-Bericht sagt endlich, was der Lauf getan hat. Er schliesst dabei zwei Luecken auf einmal: die bereits seit D-21 bestehende (das Frontend kennt die Mitgliedschafts-Zahlen des Backends nicht) und die neue aus dieser Phase (uebernommene, umbenannte, geloeschte Gruppen sowie eine neu vergebene Standardmarkierung). Das ist die einzige vorgesehene Massnahme gegen den in D-05 bewusst akzeptierten Zugriffsverlust: eine sync-getriebene Loeschung ist nirgends sonst sichtbar.

Zusaetzlich zieht die dritte Anzeigestelle des internen Namens aus D-04 nach: die Spaltenkoepfe der Freigabe-Matrix.

Purpose: Ohne diesen Plan klickt der Admin auf "Jetzt synchronisieren", sieht drei Benutzerzahlen und hat keine Ahnung, ob Gruppen geloescht wurden.

Output: Erweiterter Bericht mit sichtbarem Fehlerzustand, Anzeigename mit Fallback in der Freigabe-Matrix, vier neue i18n-Schluessel in beiden Sprachen.

<artifacts_this_phase_produces> Neu entstehende Symbole in diesem Plan:

Art Symbol
Interface-Felder SyncResult.groupMembershipsAdded, .groupMembershipsRemoved, .groupsAdopted, .groupsRenamed, .groupsDeleted, .defaultMarkerMoved (admin/ldap/page.tsx)
Frontend-State syncRequestError (admin/ldap/page.tsx)
Typfeld Group.internalName?: string | null (admin/modules/grants/page.tsx)
i18n-Schluessel admin.ldap.sync.resultGroupMemberships, admin.ldap.sync.resultGroups, admin.ldap.sync.defaultMarkerMoved, admin.ldap.sync.requestError

Aus Plan 16-03 uebernommen und hier vorausgesetzt: die vier neuen Zaehlfelder auf LdapSyncResult. </artifacts_this_phase_produces>

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/16-ad-gruppen-synchronisation/16-CONTEXT.md @.planning/phases/16-ad-gruppen-synchronisation/16-UI-SPEC.md @.planning/phases/16-ad-gruppen-synchronisation/16-PATTERNS.md @.planning/phases/16-ad-gruppen-synchronisation/16-03-SUMMARY.md @.planning/phases/16-ad-gruppen-synchronisation/16-04-SUMMARY.md

<decisions_recorded>

  • Abweichung vom Copywriting Contract, ausdruecklich vermerkt: Die UI-SPEC formuliert Berichtszeile 3 als "AD-Gruppen: {groupsImported} importiert, {groupsRenamed} umbenannt, {groupsDeleted} geloescht". Der Sync importiert aber per D-02 nie eine Gruppe — Import ist ausschliesslich die ausdrueckliche Auswahl im Import-Bereich aus Plan 16-01. Eine Zahl, die nach jedem Lauf zwingend 0 ist, waere dauerhaft bedeutungslos. Das Feld heisst groupsAdopted (Plan 16-03) und die Zeile lautet "AD-Gruppen: {groupsAdopted} neu uebernommen, {groupsRenamed} umbenannt, {groupsDeleted} geloescht". Die uebrigen drei Zeilen folgen dem Contract woertlich. </decisions_recorded>
Task 1: Sync-Bericht vollstaendig verdrahten und Fehlschlag sichtbar machen (D-06, RESEARCH.md Pitfall 3) apps/web/src/app/(portal)/admin/ldap/page.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json - `apps/web/src/app/(portal)/admin/ldap/page.tsx` — das `SyncResult`-Interface (Zeilen 54-59), `handleSync` (231-249) inklusive des heutigen catch-Zweigs, der ein dreifeldriges Ergebnisobjekt konstruiert, der Sync-Button (1059-1066) und der Ergebnis-Container (1068-1088) - `apps/api/src/ldap/ldap.service.ts` — `LdapSyncResult` in der nach Plan 16-03 gueltigen Fassung, als verbindliche Feldnamensquelle - `apps/web/src/messages/de.json` und `en.json` — der `admin.ldap.sync`-Block mit dem bestehenden Ergebnis-Schluessel als Formatvorbild - `.planning/phases/16-ad-gruppen-synchronisation/16-UI-SPEC.md` — Surface Contract 2 und die E2-Zeilen im Abschnitt UI Considerations - `.planning/phases/16-ad-gruppen-synchronisation/16-RESEARCH.md` — Pitfall 3 Erweitere das `SyncResult`-Interface additiv um die sechs Zahlenfelder, die das Backend heute schon liefert bzw. seit Plan 16-03 liefert: die beiden Mitgliedschafts-Zaehler aus D-21 und die vier Gruppen-Zaehler dieser Phase. Die Feldnamen muessen exakt denen auf `LdapSyncResult` entsprechen — lies sie dort ab, statt sie zu raten.

Neuer State syncRequestError: string | null. handleSync setzt ihn zu Beginn jedes Laufs auf null. Der heutige catch-Zweig konstruiert ein Ergebnisobjekt mit drei Nullen und einer Netzwerkfehlermeldung — das ist mit dem erweiterten Interface nicht mehr typkorrekt und inhaltlich irrefuehrend: es gibt einen nie gelaufenen Sync als erfolgreichen Lauf ohne Aenderungen aus. Ersetze ihn: sowohl im catch-Zweig als auch bei !res.ok wird syncResult auf null gesetzt und syncRequestError auf admin.ldap.sync.requestError. Rendere diesen Zustand als text-sm text-destructive-Zeile unterhalb des Sync-Buttons, anstelle des Berichts-Containers.

Baue den bestehenden Ergebnis-Container von einem Absatz auf mehrere um — derselbe Container (rounded-md border border-border bg-muted/30 p-4), nur mit mehreren Zeilen:

  1. Die bestehende Benutzerzeile, unveraendert, in text-sm font-medium text-foreground.
  2. Neu: die Mitgliedschaftszeile mit admin.ldap.sync.resultGroupMemberships, in text-sm text-muted-foreground — sekundaere Information gegenueber Zeile 1.
  3. Neu: die Gruppenzeile mit admin.ldap.sync.resultGroups, ebenfalls text-sm text-muted-foreground, immer sichtbar, auch wenn alle drei Werte 0 sind. Dieselbe Transparenzhaltung wie bei Zeile 1: eine 0 ist eine Aussage, kein Grund zum Verschweigen.
  4. Neu und bedingt: nur bei einem Wert groesser 0 die Zeile admin.ldap.sync.defaultMarkerMoved in text-sm font-medium mit text-amber-700 dark:text-amber-400. Bewusst nicht in der Destruktiv-Farbe: die Verschiebung der Standardmarkierung ist ein bemerkenswerter, aber kein fehlerhafter Vorgang.
  5. Die bestehende Fehlerliste darunter, unveraendert in text-xs text-destructive. Gruppenbezogene Fehler tragen laut Backend-Muster bereits ein Gruppenpraefix und brauchen keine visuelle Sonderbehandlung.

Die Zahlenzeilen werden nicht unterdrueckt, wenn die Fehlerliste gefuellt ist — ein teilweise fehlgeschlagener Lauf zeigt beides.

i18n: die vier neuen Schluessel unter admin.ldap.sync in beiden Sprachdateien anlegen. Die deutschen Texte fuer die Mitgliedschaftszeile, die Markierungszeile und den Fehlertext folgen dem Copywriting Contract woertlich; die Gruppenzeile folgt der im Abschnitt <decisions_recorded> festgehaltenen, ausdruecklich vermerkten Abweichung. Englische Texte sinngleich. Die Interpolationsnamen entsprechen exakt den Feldnamen des Interface. cd apps/web && npx tsc --noEmit cd apps/web && npx vitest run node -e "const de=require('./apps/web/src/messages/de.json'),en=require('./apps/web/src/messages/en.json');for(const k of ['resultGroupMemberships','resultGroups','defaultMarkerMoved','requestError']){if(!de.admin.ldap.sync[k])throw new Error('de fehlt '+k);if(!en.admin.ldap.sync[k])throw new Error('en fehlt '+k)}" <acceptance_criteria> - Das Frontend-Interface kennt alle sechs neuen Zahlenfelder: awk '/interface SyncResult/,/^}/' "apps/web/src/app/(portal)/admin/ldap/page.tsx" | grep -c 'groupMembershipsAdded\|groupMembershipsRemoved\|groupsAdopted\|groupsRenamed\|groupsDeleted\|defaultMarkerMoved' ergibt 6 - Der catch-Zweig konstruiert kein Ergebnisobjekt mehr: awk '/const handleSync/,/^ };/' "apps/web/src/app/(portal)/admin/ldap/page.tsx" | grep -c 'deactivated: 0' ergibt 0 - Der Fehlerzustand ist verdrahtet: awk '/const handleSync/,/^ };/' "apps/web/src/app/(portal)/admin/ldap/page.tsx" | grep -c 'setSyncRequestError' ist >= 3 - Die bedingte Markierungszeile haengt an einem Groesser-0-Test: grep -c 'defaultMarkerMoved > 0' "apps/web/src/app/(portal)/admin/ldap/page.tsx" ist >= 1 - Die Markierungszeile nutzt die Amber-Familie, nicht die Destruktiv-Farbe: grep -c 'text-amber-700' "apps/web/src/app/(portal)/admin/ldap/page.tsx" ist >= 1 - cd apps/web && npx tsc --noEmit ist fehlerfrei - Der Node-Schluesselcheck oben laeuft ohne Wurf durch </acceptance_criteria> Der Sync-Bericht zeigt Benutzer-, Mitgliedschafts- und Gruppenzahlen, meldet eine neu vergebene Standardmarkierung eigens und informativ, und ein insgesamt fehlgeschlagener Lauf erscheint als sichtbarer Fehler statt als Bericht mit Nullen.

Task 2: Anzeigename mit Fallback in den Spaltenkoepfen der Freigabe-Matrix (D-04) apps/web/src/app/(portal)/admin/modules/grants/page.tsx - `apps/web/src/app/(portal)/admin/modules/grants/page.tsx` — das lokale `Group`-Interface (Zeilen 16-19), `fetchMatrix` (55-70), das Suchfilter-Memo `filteredGroups` (140-143) und der Spaltenkopf-Block (206-213) - `apps/api/src/groups/module-grants.service.ts` — `getMatrix()` (Zeilen 172-202); die Gruppen-Query nutzt kein `select`, das neue Feld kommt damit ohne Backend-Aenderung mit - `.planning/phases/16-ad-gruppen-synchronisation/16-UI-SPEC.md` — Surface Contract 5 und die E5-Zeilen im Abschnitt UI Considerations Erweitere das lokale `Group`-Interface in `apps/web/src/app/(portal)/admin/modules/grants/page.tsx` um `internalName?: string | null`. Der Matrix-Endpunkt liefert vollstaendige Gruppenzeilen ohne Feldauswahl, das Feld ist seit der Migration aus Plan 16-01 also bereits in der Antwort enthalten — es fehlt nur der Typ.

Im Spaltenkopf-Block sowohl den title-Wert als auch den gerenderten Text auf den internen Namen mit Rueckfall auf den gespeicherten Namen umstellen. Kein Layoutunterschied: dieselbe Zelle mit ihrer Breitenbegrenzung, ihrem Abschneiden und ihrem Tooltip-Mechanismus, nur der zugrundeliegende Textwert aendert sich. Das aria-label der Matrix-Checkboxen interpoliert bereits denselben Wert und folgt damit automatisch.

Passe zusaetzlich das Suchfilter-Memo an: es filtert heute ausschliesslich ueber den gespeicherten Namen. Nach dieser Aenderung sieht der Admin einen anderen Text, als er durchsucht. Lass den Filter auf beide Werte matchen — Anzeigename und gespeicherter Name — damit weder die Suche nach dem internen noch die nach dem AD-Namen ins Leere laeuft. Verwende an beiden Stellen die Nullish-Variante, nicht die Oder-Variante.

Diese Datei bekommt in diesem Plan keine neuen Uebersetzungsschluessel. cd apps/web && npx tsc --noEmit cd apps/web && npx vitest run <acceptance_criteria> - grep -q "internalName" "apps/web/src/app/(portal)/admin/modules/grants/page.tsx" trifft - Spaltenkopf-Text und Tooltip nutzen beide den Fallback: awk '/filteredGroups.map/,/<\/th>/' "apps/web/src/app/(portal)/admin/modules/grants/page.tsx" | grep -c 'internalName ?? g.name' ist >= 2 - Keine Truthiness-Variante: grep -c "internalName ||" "apps/web/src/app/(portal)/admin/modules/grants/page.tsx" ergibt 0 - Der Suchfilter beruecksichtigt beide Namen: awk '/const filteredGroups/,/\);/' "apps/web/src/app/(portal)/admin/modules/grants/page.tsx" | grep -c 'internalName' ist >= 1 - Die Zellklassen sind unveraendert: grep -c 'min-w-\[120px\] max-w-\[160px\] truncate' "apps/web/src/app/(portal)/admin/modules/grants/page.tsx" ergibt weiterhin 1 - cd apps/web && npx vitest run ist vollstaendig gruen </acceptance_criteria> Die Spaltenkoepfe der Freigabe-Matrix zeigen den internen Namen, sobald einer gesetzt ist, der AD-Name bleibt im Tooltip, und die Spaltensuche findet eine Gruppe unter beiden Namen.

<threat_model>

Trust Boundaries

Boundary Description
API → Browser (Sync-Bericht, Matrix) Vom Backend gelieferte Zahlen, Fehlermeldungen und Gruppennamen werden angezeigt; Fehlermeldungen koennen AD-gelieferte Gruppennamen enthalten

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-16-12 Cross-Site Scripting Fehlerzeilen im Sync-Bericht, Spaltenkoepfe der Matrix medium mitigate Alle Werte werden als React-Textknoten gerendert; in beiden Dateien wird keine Roh-HTML-Einbettung verwendet
T-16-14 Information Disclosure Fehlerzeilen enthalten Gruppennamen und ggf. DN-Fragmente aus dem Verzeichnis low accept Der Bericht ist ausschliesslich fuer ADMIN und SUPER_ADMIN desselben Mandanten erreichbar; die enthaltenen Werte stammen aus dem Verzeichnis dieses Mandanten und sind fuer diese Rolle ohnehin sichtbar
T-16-05 Repudiation Sichtbarkeit sync-getriebener Loeschungen high mitigate Genau dieser Plan ist die in D-05 vorgesehene Massnahme: die Zahlen fuer geloeschte Gruppen und verschobene Standardmarkierung erscheinen im Bericht. Ohne sie waere der akzeptierte Zugriffsverlust vollstaendig unsichtbar
T-16-SC Tampering Paketinstallation low accept Keine neuen Pakete in diesem Plan
</threat_model>
1. `cd apps/web && npx vitest run` — vollstaendig gruen 2. `cd apps/web && npx tsc --noEmit` — fehlerfrei 3. Beide Sprachdateien tragen unter `admin.ldap.sync` denselben Schluesselsatz 4. Die Feldnamen im Frontend-Interface stimmen zeichengenau mit denen auf `LdapSyncResult` ueberein

Manuell nachzuholen (Browser): Sync ausloesen und pruefen, dass alle drei Zahlenzeilen erscheinen; einen Lauf mit verschobener Standardmarkierung provozieren und pruefen, dass die vierte Zeile in Amber erscheint; die Freigabe-Matrix mit einer Gruppe mit gesetztem internem Namen oeffnen und die Spaltensuche unter beiden Namen probieren.

<success_criteria>

  • Kein vom Backend geliefertes Zaehlfeld bleibt im Sync-Bericht unangezeigt
  • Eine sync-getriebene Loeschung und eine verschobene Standardmarkierung sind im Bericht sichtbar (D-05-Massnahme, D-06)
  • Ein fehlgeschlagener Sync-Request erscheint als Fehler, nicht als Bericht mit Nullen
  • Die Spaltenkoepfe der Freigabe-Matrix zeigen den internen Namen (D-04, dritte Anzeigestelle) </success_criteria>
Create `.planning/phases/16-ad-gruppen-synchronisation/16-05-SUMMARY.md` when done