Files

25 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 04 execute 3
16-01
16-02
apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx
apps/web/src/app/(portal)/admin/groups/page.tsx
apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx
apps/web/src/messages/de.json
apps/web/src/messages/en.json
true
PERM-02
tokens raw_tokens tasks confidence
46000 46000 3 low
truths artifacts key_links prohibitions
Der Gruppen-Dialog bietet keinen Weg mehr, eine lokal angelegte Gruppe an eine AD-Gruppe zu binden: die Radio-Auswahl, der Discovery-Aufruf, der Bindung-entfernen-Link und der zweistufige Anlegen-dann-Binden-Ablauf sind vollstaendig entfernt (D-07).
Der Anlegen-Zustand sendet genau einen POST /groups und keinen Folge-PATCH.
Im Bearbeiten-Zustand einer importierten Gruppe ist das Namensfeld deaktiviert, zeigt den AD-Namen, traegt darunter den Herkunftshinweis und darueber hinaus eine reine Anzeigezeile mit dem AD-DN (D-03, D-04).
Im Bearbeiten-Zustand einer importierten Gruppe gibt es ein editierbares Feld 'Interner Name'; gespeichert wird in diesem Zustand ausschliesslich internalName, nie name (D-04).
Die Gruppenliste zeigt in der Namensspalte internalName ?? name und traegt den AD-Namen als title-Attribut zur Nachvollziehbarkeit (D-04).
Die sechs verwaisten i18n-Schluessel unter admin.groups fuer die entfallene AD-Bindungsauswahl sind aus de.json UND en.json entfernt; kein Quelltext referenziert sie noch.
Ein fehlgeschlagenes Speichern zeigt eine sichtbare Fehlerzeile im Dialog; der Dialog bleibt offen und die Eingaben bleiben erhalten. Meldet der Server eine Namenskollision, erscheint stattdessen der dafuer vorgesehene Text.
Der Bearbeiten-Dialog unterscheidet lokale und importierte Gruppen ausschliesslich am gesetzten ldapDn; eine Gruppe genau an dieser Grenze — ldapDn gesetzt, Identitaetsschluessel noch nicht nachgetragen — gilt als importiert und zeigt das gesperrte Namensfeld.
Der Anlegen-Zustand oeffnet mit leerem Namensfeld und ohne jedes AD-Steuerelement; ein leerer Name wird wie bisher als Pflichtfeld abgelehnt.
Ein interner Name aus reinen Leerzeichen wird wie ein leeres Feld behandelt und fuehrt serverseitig zu null, nicht zu einem leeren Anzeigenamen.
Der Dialog rendert seine drei Zustaende allein aus dem group-Prop und dessen ldapDn; es gibt keinen Effekt mehr, der beim Oeffnen ein AD-Verzeichnis nachlaedt.
UI E3/empty: Bestehender Empty-State der Gruppenliste unveraendert — dieser Plan aendert nur den Textwert der Namensspalte.
UI E3/loading: Bestehender Ladezustand der Gruppenliste unveraendert.
UI E3/error: Bestehendes Fehlerbanner der Gruppenliste unveraendert.
UI E3/populated: Die Namensspalte zeigt internalName ?? name; die AD-Bindungs-Spalte traegt weiterhin ihr Badge und ist nach D-07 der alleinige Marker importiert-vs-lokal.
UI E3/partial: Ist internalName einer importierten Gruppe nicht gesetzt, greift der Fallback auf name — es gibt keinen Zustand mit leerem Namen.
UI E3/overflow: Bestehendes Tabellen-Scrollverhalten unveraendert; dieser Plan fuegt keine Spalte hinzu.
UI E3/zero-one-many: Abgedeckt durch Empty-State und Listenrendering, beide unveraendert.
UI E4/empty: Der Anlegen-Zustand oeffnet mit leerem Pflichtfeld Name und dem Hinweisabsatz, dass AD-Gruppen im LDAP-Bereich importiert werden, samt Link dorthin.
UI E4/loading: Bestehender saving-Zustand — der Speichern-Button traegt den Ladetext und ist deaktiviert.
UI E4/error: Ein fehlgeschlagenes Speichern zeigt eine text-sm text-destructive Zeile im Dialog; der Dialog bleibt offen, Eingaben bleiben erhalten; bei Namenskollision greift die dafuer vorgesehene Copy.
UI E4/partial: Im Bearbeiten-Zustand einer importierten Gruppe ist der interne Name optional leer; das gesperrte Namensfeld zeigt weiterhin den AD-Namen und die AD-DN-Zeile bleibt sichtbar.
statement verification
UI E3/long-text: Ein langer interner oder AD-Name in der Namensspalte sprengt die Tabellenzeile nicht. backstop
statement verification
UI E4/long-text: Ein sehr langer AD-Name im gesperrten Eingabefeld sprengt den max-w-md-Modal-Container nicht. backstop
apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx — drei Zustaende ohne AD-Auswahl
apps/web/src/app/(portal)/admin/groups/page.tsx — Group-Interface um internalName, Namenszelle mit Fallback und title
apps/web/src/messages/de.json + en.json — neue Schluessel unter admin.groups, sechs verwaiste Schluessel entfernt
apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx — Faelle fuer D-07, gesperrtes Namensfeld, internen Namen und Namensanzeige
GroupFormModal → PATCH /groups/:id mit ausschliesslich internalName fuer importierte Gruppen — sendet der Dialog weiterhin name, antwortet das Backend aus Plan 16-02 mit 400
Group-Interface in groups/page.tsx → GroupFormModal: der Dialog importiert den Typ von dort, das Feld muss an der Quelle ergaenzt werden
Entfernte i18n-Schluessel ↔ entfernter Dialogblock: bleibt ein Verweis stehen, faellt der Dialog zur Laufzeit auf einen fehlenden Schluessel
Die Oberflaeche darf keinen zweiten Weg anbieten, eine lokal angelegte Gruppe nachtraeglich an eine AD-Gruppe zu binden — weder als Formularfeld, noch als Link, noch als versteckter Request.
Das gesperrte Namensfeld darf nicht nur optisch deaktiviert sein: der Dialog darf in diesem Zustand kein name-Feld an den Server senden.
Der Dialog darf einen Fehlschlag beim Speichern nicht stumm verschlucken — der Benutzer muss sehen, dass nichts gespeichert wurde, und seine Eingaben behalten.
Die generische Admin-Oberflaeche darf keine kunden- oder firmenspezifischen Beispielnamen als Platzhalter im Feld fuer den internen Namen tragen.
Der Gruppen-Dialog wird auf die Trennung aus D-07 zurueckgebaut: eine lokal angelegte Gruppe kann nicht mehr an eine AD-Gruppe gebunden werden, die in Plan 15-06 gebaute Radio-Auswahl verschwindet vollstaendig. An ihre Stelle treten die beiden Bausteine aus D-03 und D-04 — ein gesperrtes Namensfeld mit sichtbarer Herkunft fuer importierte Gruppen und ein frei editierbares Feld fuer den internen Namen, den der Sync nie anfasst. Die Gruppenliste zeigt ab hier den internen Namen, sobald einer gesetzt ist.

Der Wortlaut aus CONTEXT.md: "Eine lokale Gruppe soll nicht mit einer AD verknuepft werden koennen. Das ist ja Quatsch" — die Trennung der beiden Gruppenarten soll in der Oberflaeche sichtbar sein. Nach diesem Plan ist das bestehende AD-Badge in der Bindungsspalte der alleinige Marker dafuer.

Purpose: Zwei Wege zum selben Ergebnis sind laut D-07 eine Fehlerquelle. Der Dialog wird einfacher, nicht komplizierter.

Output: Umgebauter GroupFormModal, Namensanzeige mit Fallback in der Gruppenliste, neue und bereinigte i18n-Schluessel, angepasste Komponententests.

<artifacts_this_phase_produces> Neu entstehende Symbole in diesem Plan:

Art Symbol
Typfeld Group.internalName?: string | null (apps/web/src/app/(portal)/admin/groups/page.tsx)
Frontend-State internalName in GroupFormModal
i18n-Schluessel admin.groups.nameLockedHint, admin.groups.adDnLabel, admin.groups.internalName, admin.groups.internalNameHint, admin.groups.createLdapHint, admin.groups.goToLdap, admin.groups.saveErrorNameTaken
Entfernte i18n-Schluessel der komplette Unterbaum admin.groups.ldapBind mit seinen sechs Schluesseln, in de.json und en.json

Aus Plan 16-02 uebernommen und hier vorausgesetzt: UpdateGroupDto.internalName, die serverseitige Namenssperre, internalName in der Antwort von GET /groups. </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-02-SUMMARY.md Task 1: GroupFormModal auf drei Zustaende zurueckbauen (D-03, D-04, D-07) apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json - `apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx` — vollstaendig (255 Zeilen); der zu entfernende Umfang ist das lokale Verzeichnis-Interface am Dateikopf, der gesamte Discovery-State samt Effekt und Filter-Memo, der komplette AD-Bindungs-Block im Formular und der Folge-PATCH im Anlegen-Zweig - `apps/web/src/app/(portal)/admin/modules/grants/page.tsx` Zeilen 180-195 — der Link-Stil, der fuer den neuen Hinweis-Link gilt - `apps/web/src/messages/de.json` Zeilen 375-400 und die gleichen Zeilen in `en.json` — der `admin.groups`-Block - `.planning/phases/16-ad-gruppen-synchronisation/16-UI-SPEC.md` — Surface Contract 4 und der Copywriting Contract - `.planning/phases/16-ad-gruppen-synchronisation/16-PATTERNS.md` — Abschnitt `GroupFormModal.tsx` Entferne aus `GroupFormModal.tsx` restlos alles, was zur AD-Bindungsauswahl gehoert: das lokal deklarierte Verzeichnis-Eintrags-Interface, den `ldapDn`-State, die vier Discovery-States, die Lade-Funktion samt `useEffect`, das gefilterte Memo, den kompletten Formularblock mit Radio-Liste, Suchfeld, Bindungsanzeige und Bindung-entfernen-Link, sowie den zweiten `PATCH`-Aufruf im Anlegen-Zweig von `handleSubmit`. Der `useCallback`-, `useEffect`- und `useMemo`-Import faellt damit ebenfalls weg, ebenso die `tLdap`-Uebersetzungsinstanz. Passe den Docblock der Komponente an: er beschreibt jetzt einen reinen Anlege-/Umbenennen-Dialog mit optionalem internem Namen, ohne AD-Auswahl.

Neuer State: internalName, initialisiert aus group?.internalName ?? ''. Abgeleiteter Zustand isImported = group?.ldapDn != null — die Unterscheidung laeuft ueber die AD-Bindung, nicht ueber den Identitaetsschluessel, damit auch eine Alt-Bindung ohne nachgetragenen Schluessel sofort als importiert gilt.

Drei Renderzustaende im selben Container (max-w-md, fixed inset-0 bg-black/50, unveraendert):

(a) Anlegen (group === null): nur das bestehende Namensfeld-Markup, unveraendert und pflichtig. Darunter ein Hinweisabsatz in text-sm text-muted-foreground mit admin.groups.createLdapHint und einem Inline-Link admin.groups.goToLdap nach /admin/ldap im Stil text-primary hover:underline. handleSubmit reduziert sich in diesem Zweig auf einen einzelnen POST /groups mit { name }.

(b) Bearbeiten, lokale Gruppe (group !== null && !isImported): Namensfeld frei editierbar wie heute, kein Feld fuer den internen Namen — fuer eine lokale Gruppe ist der Name bereits der volle Anzeigename, ein zweites Namensfeld waere redundant und von keiner Entscheidung gedeckt. handleSubmit sendet PATCH /groups/{id} mit { name }.

(c) Bearbeiten, importierte Gruppe (group !== null && isImported): dasselbe Namensfeld-Markup, aber disabled, ohne onChange-Wirkung auf ein zu sendendes Feld, und mit dem AD-Namen als Wert; die bestehende disabled:opacity-50-Konvention macht den Zustand sichtbar. Darunter ein Hinweis in text-xs text-muted-foreground mit admin.groups.nameLockedHint. Darunter eine reine Anzeigezeile in font-mono text-xs text-muted-foreground mit admin.groups.adDnLabel und dem DN — kein Steuerelement, keine Aktion. Darunter das neue Feld "Interner Name" im identischen Eingabefeld-Markup wie das Namensfeld, mit Label admin.groups.internalName, ohne Platzhaltertext (insbesondere ohne firmenspezifisches Beispiel) und mit dem Hinweis admin.groups.internalNameHint darunter. handleSubmit sendet in diesem Zustand PATCH /groups/{id} mit ausschliesslich { internalName } — niemals name, da das Feld gesperrt ist und das Backend aus Plan 16-02 ein name fuer importierte Gruppen mit 400 ablehnt. Ein leerer oder nur aus Leerzeichen bestehender Wert wird als null gesendet.

Fehlerbehandlung: das bereits vorhandene Fehlerbanner bleibt und wird konsequent fuer alle drei Zustaende genutzt. Neu ist die Unterscheidung der Ursache — antwortet der Server mit Status 409, wird admin.groups.saveErrorNameTaken gesetzt, sonst admin.groups.saveError. Der Dialog schliesst in keinem Fehlerfall und setzt keine Eingabe zurueck.

i18n: die sieben neuen Schluessel unter admin.groups in de.json und en.json anlegen, deutsche Texte woertlich aus dem Copywriting Contract, englische sinngleich. Die verwaisten Schluessel werden erst in Task 3 entfernt — bis dahin bleibt der Baum unverletzt, damit ein Zwischenstand keinen fehlenden Schluessel erzeugt. cd apps/web && npx tsc --noEmit cd apps/web && npx vitest run "src/app/(portal)/admin/groups/groups-page.test.tsx" node -e "const de=require('./apps/web/src/messages/de.json'),en=require('./apps/web/src/messages/en.json');for(const k of ['nameLockedHint','adDnLabel','internalName','internalNameHint','createLdapHint','goToLdap','saveErrorNameTaken']){if(!de.admin.groups[k])throw new Error('de fehlt '+k);if(!en.admin.groups[k])throw new Error('en fehlt '+k)}" <acceptance_criteria> - Die Komponente stellt keinen Verzeichnisabruf mehr: grep -c "ldap/groups" "apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx" ergibt 0 - Kein Radio-Steuerelement mehr: grep -c "type=\"radio\"" "apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx" ergibt 0 - Kein zweiter Schreibaufruf im Anlegen-Zweig: grep -c "method: 'PATCH'" "apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx" ergibt 1 - grep -c "internalName" "apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx" ist >= 4 (State, Feld, Payload, Initialisierung) - grep -q "disabled" "apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx" trifft und das Namensfeld traegt es zustandsabhaengig - grep -q "409" "apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx" trifft (Kollisionsfall wird von anderen Fehlern unterschieden) - cd apps/web && npx tsc --noEmit ist fehlerfrei - Der Node-Schluesselcheck oben laeuft ohne Wurf durch </acceptance_criteria> Der Dialog kennt drei klar getrennte Zustaende, bietet in keinem davon einen Weg zur AD-Bindung, sperrt den Namen importierter Gruppen sichtbar, laesst den internen Namen editieren und zeigt jeden Speicherfehler mit unterscheidbarer Ursache an.

Task 2: Namensanzeige mit Fallback in der Gruppenliste (D-04) apps/web/src/app/(portal)/admin/groups/page.tsx, apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx - `apps/web/src/app/(portal)/admin/groups/page.tsx` — das exportierte `Group`-Interface (Zeilen 12-21), die Namenszelle und die Badge-Zelle (Zeilen 179-190), das Fehlerbanner (135-140), der Empty-/Ladezustand (141-145) - `apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx` — die vorhandene Uebersetzungs-Stub-Mechanik und der Fall `renders a populated table with name, AD-binding badge, default star and member count` (ab Zeile 163) - `.planning/phases/16-ad-gruppen-synchronisation/16-UI-SPEC.md` — Surface Contract 3 und die E3-Zeilen im Abschnitt UI Considerations Erweitere das exportierte `Group`-Interface in `apps/web/src/app/(portal)/admin/groups/page.tsx` um `internalName: string | null` — dieselbe Nullbarkeit wie `ldapDn`, damit `GroupFormModal` den Typ ohne Zusatzarbeit mitnutzt. Das Backend liefert das Feld seit Plan 16-02 in `GET /groups`.

In der Namenszelle den gerenderten Wert auf den internen Namen mit Rueckfall auf den gespeicherten Namen umstellen und den umschliessenden Text in ein <span> mit title={group.name} fassen — damit bleibt der AD-Name per Hover nachvollziehbar, auch wenn ein interner Name gesetzt ist. Verwende die Nullish-Variante, nicht die Oder-Variante: das Backend normalisiert leere Werte bereits auf null, und eine Truthiness-Pruefung wuerde diese Normalisierung nachtraeglich umdeuten. Die Zellklassen bleiben unveraendert.

Die Badge-Zelle daneben bleibt vollstaendig unveraendert. Sie ist nach D-07 der alleinige visuelle Marker fuer "importiert vs. lokal"; es entsteht kein neues Badge, keine neue Farbe und keine neue Spalte.

Ergaenze in groups-page.test.tsx zwei Faelle: eine Gruppe mit gesetztem internem Namen wird mit diesem angezeigt und traegt den AD-Namen als title; eine Gruppe ohne internen Namen wird mit ihrem gespeicherten Namen angezeigt. Beide Faelle nutzen die vorhandene Fetch- und Uebersetzungs-Stub-Mechanik der Datei. cd apps/web && npx vitest run "src/app/(portal)/admin/groups/groups-page.test.tsx" cd apps/web && npx tsc --noEmit <acceptance_criteria> - grep -q "internalName: string | null" "apps/web/src/app/(portal)/admin/groups/page.tsx" trifft - Die Namenszelle nutzt den Fallback und den Tooltip: grep -c "internalName ?? group.name" "apps/web/src/app/(portal)/admin/groups/page.tsx" ist >= 1 und grep -c "title={group.name}" "apps/web/src/app/(portal)/admin/groups/page.tsx" ist >= 1 - Keine Truthiness-Variante: grep -c "internalName ||" "apps/web/src/app/(portal)/admin/groups/page.tsx" ergibt 0 - Die Badge-Zelle ist unveraendert: grep -c "boundBadge" "apps/web/src/app/(portal)/admin/groups/page.tsx" ergibt weiterhin 1 - cd apps/web && npx vitest run "src/app/(portal)/admin/groups/groups-page.test.tsx" ist gruen mit mindestens 2 neuen Faellen </acceptance_criteria> Die Gruppenliste zeigt den internen Namen, sobald einer gesetzt ist, und sonst den gespeicherten Namen; der AD-Name bleibt per Tooltip nachvollziehbar; die Bindungsspalte ist unangetastet.

Task 3: Verwaiste Uebersetzungsschluessel entfernen und D-07 im Test festnageln apps/web/src/messages/de.json, apps/web/src/messages/en.json, apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx - `apps/web/src/messages/de.json` Zeilen 375-400 und `apps/web/src/messages/en.json` Zeilen 375-400 — der `admin.groups`-Block mit dem zu entfernenden Unterbaum - `apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx` — die Fetch-Stub-Mechanik und die bestehenden Modal-Faelle - `.planning/phases/16-ad-gruppen-synchronisation/16-UI-SPEC.md` — Abschnitt "Zu entfernende Copy" im Copywriting Contract - `.planning/phases/16-ad-gruppen-synchronisation/16-RESEARCH.md` — Annahme A3 (beide Sprachdateien tragen denselben Unterbaum und muessen parallel bereinigt werden) Entferne in **beiden** Sprachdateien den Unterbaum unter `admin.groups`, der die sechs Schluessel der entfallenen AD-Bindungsauswahl enthaelt (Hinweistext, Bindungsanzeige, Loesen-Link, Suchplatzhalter, Ladefehler, Leerergebnis). Er ist mit dem Umbau aus Task 1 vollstaendig verwaist. Alle uebrigen Schluessel unter `admin.groups` bleiben unangetastet — insbesondere der Spaltenkopf fuer die AD-Bindung, die beiden Badge-Texte und der Checkbox-Schluessel der Freigabe-Matrix. Der Unterschied zwischen dem Spaltenkopf-Schluessel und dem entfallenden Unterbaum ist ein einziges angehaengtes Wortende; loesche gezielt den Unterbaum, nicht den Praefix-Treffer.

Pruefe anschliessend das gesamte Web-Verzeichnis auf verbliebene Verweise. Findet sich noch einer, ist der Umbau aus Task 1 unvollstaendig — nicht den Schluessel wieder einfuegen, sondern die Referenz entfernen.

Ergaenze in groups-page.test.tsx die D-07-Absicherung: ein Fall, der den Dialog im Anlegen-Zustand oeffnet und danach prueft, dass kein Aufruf gegen den Verzeichnis-Endpunkt gegangen ist (Assertion ueber die vorhandene Fetch-Stub-Aufrufliste), sowie ein Fall, der den Dialog fuer eine importierte Gruppe oeffnet und prueft, dass das Namensfeld deaktiviert ist und das Feld fuer den internen Namen existiert. Passe bestehende Faelle an, die noch von der Radio-Auswahl ausgehen — dieser Task darf bestehende Tests aendern, nicht nur ergaenzen. cd apps/web && npx vitest run cd apps/web && npx tsc --noEmit grep -rc '"ldapBind"' apps/web/src/messages/de.json apps/web/src/messages/en.json | grep -qv ':[1-9]' <acceptance_criteria> - grep -c '"ldapBind"' apps/web/src/messages/de.json ergibt 0 - grep -c '"ldapBind"' apps/web/src/messages/en.json ergibt 0 - Der Spaltenkopf-Schluessel bleibt erhalten: grep -c '"ldapBinding"' apps/web/src/messages/de.json ergibt 1 und dasselbe fuer en.json - Kein Quelltext referenziert den entfernten Unterbaum mehr: grep -rc "ldapBind\." apps/web/src --include=*.tsx --include=*.ts | grep -qv ':[1-9]' - Beide Sprachdateien tragen unter admin.groups denselben Schluesselsatz: node -e "const de=require('./apps/web/src/messages/de.json'),en=require('./apps/web/src/messages/en.json');const a=JSON.stringify(Object.keys(de.admin.groups).sort()),b=JSON.stringify(Object.keys(en.admin.groups).sort());if(a!==b)throw new Error(a+' vs '+b)" - cd apps/web && npx vitest run ist vollstaendig gruen </acceptance_criteria> Beide Sprachdateien sind synchron und frei von verwaisten Schluesseln, kein Quelltext verweist mehr darauf, und der Verzicht auf jede AD-Auswahl im Dialog ist durch einen Test abgesichert statt nur behauptet.

<threat_model>

Trust Boundaries

Boundary Description
Browser → API (POST /groups, PATCH /groups/:id) Admin-Eingaben aus dem Dialog; der Dialog ist Bequemlichkeit, nicht Schutz
Gerenderter AD-Name/DN im Dialog und in der Liste Werte stammen aus dem Verzeichnis und werden im Browser angezeigt

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-16-07 Tampering Gesperrtes Namensfeld high mitigate Die Sperre ist im Browser sichtbar, aber durchgesetzt wird sie serverseitig durch die Ablehnung eines name fuer Gruppen mit gesetztem Identitaetsschluessel (Plan 16-02). Der Dialog sendet in diesem Zustand kein name; ein manipulierter Client kommt an der Backend-Invariante nicht vorbei
T-16-12 Cross-Site Scripting Anzeige von AD-Name und AD-DN medium mitigate Beide Werte werden als React-Textknoten gerendert; in keiner der beiden Dateien wird eine Roh-HTML-Einbettung verwendet
T-16-13 Elevation of Privilege Zugriff auf die Gruppenverwaltung high accept Die Rollenpruefung auf dieser Seite ist reine Anzeige; jede /groups-Route ist serverseitig durch den bestehenden RolesGuard geschuetzt (dokumentiert in groups/page.tsx, T-15-22). Dieser Plan aendert daran nichts
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.groups` denselben Schluesselsatz 4. Kein Quelltext im Web-Verzeichnis verweist noch auf den entfernten Unterbaum

Manuell nachzuholen (kein Browser-Tool in der Ausfuehrungssitzung vorgesehen): Dialog im Browser fuer alle drei Zustaende durchklicken — Anlegen mit Hinweis-Link, lokale Gruppe umbenennen, importierte Gruppe mit gesperrtem Namen und internem Namen speichern. Nach dem Speichern eines internen Namens erscheint dieser in der Gruppenliste, der AD-Name im Tooltip.

<success_criteria>

  • Der Dialog bietet keinen Weg mehr, eine lokale Gruppe an eine AD-Gruppe zu binden (D-07)
  • Das Namensfeld importierter Gruppen ist gesperrt und die Herkunft ist im Dialog sichtbar (D-03)
  • Der interne Name ist im Dialog setz- und aenderbar und gewinnt in der Gruppenliste (D-04)
  • Speicherfehler sind sichtbar und in der Ursache unterscheidbar
  • Keine verwaisten Uebersetzungsschluessel in einer der beiden Sprachen </success_criteria>
Create `.planning/phases/16-ad-gruppen-synchronisation/16-04-SUMMARY.md` when done