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 |
|
|
true |
|
|
|
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>
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:
- Die bestehende Benutzerzeile, unveraendert, in
text-sm font-medium text-foreground. - Neu: die Mitgliedschaftszeile mit
admin.ldap.sync.resultGroupMemberships, intext-sm text-muted-foreground— sekundaere Information gegenueber Zeile 1. - Neu: die Gruppenzeile mit
admin.ldap.sync.resultGroups, ebenfallstext-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. - Neu und bedingt: nur bei einem Wert groesser 0 die Zeile
admin.ldap.sync.defaultMarkerMovedintext-sm font-mediummittext-amber-700 dark:text-amber-400. Bewusst nicht in der Destruktiv-Farbe: die Verschiebung der Standardmarkierung ist ein bemerkenswerter, aber kein fehlerhafter Vorgang. - 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.
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> |
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>