--- phase: 16-ad-gruppen-synchronisation verified: 2026-08-06T15:07:32Z status: accepted_with_open_assumptions accepted_at: 2026-08-11 accepted_by: user score: 5/5 must-haves (code-level) — 2 davon PRESENT_BEHAVIOR_UNVERIFIED wegen ungeprüfter Live-AD-Annahmen; A2 am 2026-08-11 geprüft, widerlegt und der gefundene Fehler behoben (260811-f9i) behavior_unverified: 2 overrides_applied: 0 human_verification: - test: "Read-only gegen ein echtes Active Directory (ViCoTest, balios.ctl.local): eine importierte Gruppe suchen, ihren objectGUID notieren, die Gruppe im AD umbenennen, erneut suchen, GUID vergleichen (Annahme A1)." expected: "objectGUID bleibt über die Umbenennung hinweg identisch — nur dann kann der Sync eine Umbenennung von einem Verschwinden unterscheiden (SC-3 Grundlage)." why_human: "Kein Active Directory in dieser Umgebung erreichbar; die Unterscheidung Rename-vs-Delete ist ausschließlich gegen gemockte LDAP-Antworten getestet, nie gegen ein echtes Verzeichnis." - test: "Read-only gegen dasselbe AD: eine Suche mit dem byteweise escapten (objectGUID=...)-Binärfilter absetzen und prüfen, ob sie exakt die erwartete Gruppe zurückliefert (Annahme A2)." expected: "Der binäre Existenz-Sweep liefert bei einer weiterhin existierenden Gruppe einen Treffer und bei einer tatsächlich gelöschten Gruppe keinen — sonst würde SC-4 entweder fälschlich löschen oder fälschlich nicht löschen." why_human: "RFC-4515-Byte-Escaping für Binärwerte ist nur gegen ldapts-Mocks verifiziert, nicht gegen die tatsächliche Filter-Interpretation eines realen Domain Controllers. Ein negatives Ergebnis bei A1 oder A2 ist laut RESEARCH.md/16-03-PLAN.md ein Stopp-Grund für die D-05-Löschsemantik." - test: "Browser-Durchklick: /admin/ldap → Sektion 'AD-Gruppen importieren' — Discovery, Checkbox-Auswahl, Import-Button mit Auswahlzahl, alreadyImported-Badge, sichtbare Fehlerzustände." expected: "Deckt sich mit den in 16-01-PLAN.md must_haves als `verification: backstop` markierten UI-Zuständen (leer, ladend, Fehler, Overflow, lange Namen) — keiner davon ist automatisiert testbar." why_human: "Kein Browser-Tool in den Ausführungssitzungen verfügbar; WINDOWS.md Eintrag #5 (16-04) und #6 (16-05) sind offen." - test: "Browser-Durchklick: /admin/groups → GroupFormModal in allen drei Zuständen (Anlegen mit Hinweis-Link, lokale Gruppe umbenennen, importierte Gruppe mit gesperrtem Namensfeld + internem Namen speichern inkl. 409-Fehleranzeige)." expected: "Gesperrtes Feld ist visuell erkennbar, AD-DN-Zeile sichtbar, interner Name speichert korrekt, Fehlerzustand bleibt im Dialog sichtbar mit erhaltenen Eingaben." why_human: "WINDOWS.md #5 — kein Browser-Tool verfügbar; nur Codepfad-Review, keine gerenderte Prüfung." - test: "Browser-Durchklick: /admin/ldap → Sync auslösen, alle drei/vier Berichtszeilen prüfen (inkl. Amber-Zeile bei verschobener Standardmarkierung); /admin/modules/grants → Spaltensuche unter internem und AD-Namen." expected: "Bericht zeigt Benutzer-, Mitgliedschafts- und Gruppenzahlen auch bei 0; Amber-Zeile erscheint nur bei defaultMarkerMoved > 0; Matrix-Suche findet die Gruppe unter beiden Namen." why_human: "WINDOWS.md #6 — kein Browser-Tool verfügbar." - test: "Alle mit `verification: backstop` markierten must_haves aus 16-01/16-02/16-04/16-05-PLAN.md (u. a. gleichzeitige/parallele Requests, Reihenfolgeunabhängigkeit, Sortier-Divergenz zwischen name und internalName, lange Texte in Listen/Modals)." expected: "Kein beobachtbares Fehlverhalten unter Nebenläufigkeit oder Layoutstress." why_human: "Explizit als `verification: backstop` deklariert — kein Code-Beleg reicht zur Bestätigung; Race-Conditions und CSS-Overflow-Verhalten sind nicht durch die vorhandenen Unit-/Komponententests abgedeckt." behavior_unverified_items: - truth: "SC-3: Wird eine übernommene Gruppe im AD umbenannt, zieht der Name der Tessera-Gruppe beim nächsten Sync nach, weil objectGUID über die Umbenennung hinweg stabil bleibt (Annahme A1)." test: "Gruppe im echten AD umbenennen, Sync laufen lassen, prüfen dass Group.name/ldapDn nachziehen und KEINE Lösch+Neuanlage stattfindet." expected: "Ein Rename-Zweig greift (groupsRenamed++), kein Delete+Create-Paar." why_human: "Die Rename-vs-Delete-Unterscheidung hängt vollständig an A1, die nur gegen Mocks getestet ist (siehe ldap.service.spec.ts), nie gegen ein echtes Verzeichnis." - truth: "SC-4: Verschwindet eine übernommene Gruppe aus dem AD, wird die Tessera-Gruppe samt Mitgliedschaften und Modulfreigaben entfernt — vorausgesetzt der binäre Existenz-Sweep (Annahme A2) funktioniert gegen ein reales AD korrekt." test: "Gruppe im echten AD löschen, Sync laufen lassen, prüfen dass die Tessera-Gruppe samt GroupMembership/ModuleGrant kaskadierend entfernt wird — UND dass eine lediglich verschobene (nicht gelöschte) Gruppe NICHT gelöscht wird (WR-03-Fix)." expected: "Löschung nur bei echtem Verschwinden; eine Verschiebung außerhalb der konfigurierten Base-DN erzeugt eine Fehlerzeile statt einer Löschung." why_human: "A2 (byteweise Hex-Filter-Syntax) ist nicht gegen einen echten Domain Controller geprüft; ein negatives Ergebnis würde SC-4 in beide Richtungen falsch machen können (WINDOWS.md #4)." --- # Phase 16: AD-Gruppen-Synchronisation Verification Report ## Nachtrag 2026-08-11 — Abschluss der offenen Punkte Die sechs `human_verification`-Punkte unten sind am 2026-08-11 abgearbeitet worden, mit einem substanziellen Fund und einer bewussten Entscheidung: **Geprüft und bestanden (Browser, alpha.tessera.ctl.de, Playwright):** die Import-Sektion, der Gruppen-Dialog in allen drei Zuständen, der Fehlerpfad des Sync-Berichts und die Spaltensuche der Freigabe-Matrix unter internem wie AD-Namen. Details in `16-UAT.md`, Tests 5 bis 7. **Geprüft und WIDERLEGT — Annahme A2:** der binäre `(objectGUID=...)`-Filter wurde als escapter String gebaut und fand am echten AD nie etwas, auch nicht für existierende Objekte. Beide Suchen der Existenzprüfung teilten sich diesen Filter, also hätte der erste echte Sync-Lauf jede AD-gebundene Gruppe samt Mitgliedschaften und Modulfreigaben gelöscht. Behoben in Quick-Task 260811-f9i (Commit `d2019dc`): `EqualityFilter` über den rohen Buffer, gegengeprüft am echten Verzeichnis, plus zwei Regressionstests. Damit ist SC-4 nicht mehr `PRESENT_BEHAVIOR_UNVERIFIED`, sondern in seiner kritischen Mechanik belegt. **Bewusst nicht geprüft — Annahme A1 und die End-to-End-Läufe (Tests 1, 3, 4, 8):** der User hat keine Schreibrechte im AD, und der Ersatzweg über einen eigenen Wegwerf-Domänencontroller wurde als unverhältnismäßig verworfen, nachdem der eine substanzielle Fehler bereits gefunden war. A1 (objectGUID übersteht eine Umbenennung) stützt sich damit auf die Microsoft-Dokumentation, nicht auf eine eigene Messung. Die Tessera-seitige Umbenennungs- und Löschlogik ist durch Unit-Tests gegen Testdaten belegt. Fällt im Betrieb auf, dass eine Umbenennung nicht nachzieht, ist das der Ansatzpunkt — siehe `16-UAT.md`, Abschnitt "Entscheidung 2026-08-11". Ebenfalls offen und bewusst so belassen: die Zahlenzeilen eines ERFOLGREICHEN Sync-Laufs (Test 7, zweiter Teil). Die lassen sich ohne AD-Rechte nachholen, sobald einmal ein Sync mit eng gesetztem Filter auf alpha läuft. **Phase Goal:** Ausgewählte AD-Gruppen werden als Tessera-Gruppen angelegt und dauerhaft nachgeführt, statt jede Gruppe von Hand anzulegen und zu binden. Der Admin wählt aus der AD-Gruppenliste aus, welche Gruppen übernommen werden; der bestehende Benutzer-Sync hält Bestand, Namen und Mitgliedschaften danach aktuell. **Verified:** 2026-08-06T15:07:32Z **Status:** human_needed **Re-verification:** No — initial verification ## Zusammenfassung der Prüfung Alle fünf Erfolgskriterien sind im Code vollständig, sauber verdrahtet und durch Unit-Tests belegt — ich habe jede Behauptung aus den fünf SUMMARY.md-Dateien gegen die tatsächlichen Quelldateien gegengeprüft, nicht nur die Zusammenfassungen gelesen. Zwei Punkte verhindern trotzdem ein glattes „passed": Erstens hängen SC-3 (Umbenennung) und SC-4 (Löschung) an zwei Annahmen über das Verhalten eines echten Active Directory (A1: `objectGUID` übersteht eine Umbenennung; A2: die byteweise Hex-Filter-Syntax wird von einem realen Domain Controller akzeptiert), die in dieser Umgebung nicht gegen ein echtes Verzeichnis geprüft werden konnten — nur gegen Mocks. Zweitens sind mehrere manuelle Browser-Durchklicke und eine Reihe von Plan-intern als `verification: backstop` markierten Aussagen (Nebenläufigkeit, Sortierabweichungen, Layout-Overflow) nicht automatisiert nachweisbar. Beides ist in den Plan-Artefakten selbst bereits ehrlich als offen dokumentiert (WINDOWS.md #4/#5/#6) — diese Verifikation bestätigt das, statt es zu überschreiben. ## Goal Achievement ### Observable Truths (fünf Erfolgskriterien) | # | Truth | Status | Evidence | | --- | --- | --- | --- | | SC-1 | Admin wählt AD-Gruppen einzeln aus und importiert sie; je Auswahl entsteht eine Tessera-Gruppe mit `ldapDn` + `ldapObjectGuid` | ✓ VERIFIED | `apps/api/src/ldap/ldap.service.ts:643` `importGroupsByDn()` legt pro DN genau eine `Group`-Zeile mit `name`, `ldapDn`, `ldapObjectGuid` an (`tenantPrisma.group.create`); `POST /ldap/groups/import` (`ldap.controller.ts:190`) unter `@Roles(ADMIN, SUPER_ADMIN)`; UI-Sektion „AD-Gruppen importieren" in `admin/ldap/page.tsx` (Checkbox-Auswahl, Import-Button mit Auswahlzahl). 60 grüne Fälle in `ldap.service.spec.ts`, inkl. Doppel-Import (skip), Namenskollision (reject-with-report), fehlender objectGUID-Buffer. | | SC-2 | Nicht ausgewählte AD-Gruppen werden nicht angelegt — keine OU-weite Automatik | ✓ VERIFIED | `importGroupsByDn(config, tenantId, dns)` verarbeitet ausschließlich die im `dns`-Array übergebenen Einträge; kein Codepfad iteriert über ein Suchergebnis oder eine OU. `ImportGroupsDto` lehnt ein leeres Array ab (`@ArrayNotEmpty()`). Frontend postet nur `selectedImportDns`. | | SC-3 | Umbenennung im AD zieht beim nächsten Sync in `Group.name`/`Group.ldapDn` nach, Mitgliedschaften/Freigaben bleiben erhalten | ⚠️ PRESENT_BEHAVIOR_UNVERIFIED | Code vollständig vorhanden und korrekt verdrahtet: `syncBoundGroupsForTenant()` (`ldap.service.ts:1223`) läuft nachweislich vor `syncGroupMembershipsForTenant()` (Zeile 920 vor 927, Call-Order-Test vorhanden), Rename-Zweig schreibt `name`/`ldapDn` byte-exakt aus dem AD, `internalName` bleibt unangetastet (`grep -c internalName` im Methodenkörper = 0), case-insensitive Change-Detection (WR-04-Fix) verhindert Idempotenz-Verletzung durch Groß-/Kleinschreibung. **Aber:** die gesamte Unterscheidung Rename-vs-Delete hängt an Annahme A1 (`objectGUID` bleibt über eine AD-Umbenennung stabil), die nur gegen `ldapts`-Mocks getestet ist — nie gegen ein echtes Verzeichnis (WINDOWS.md #4). Mitgliedschaften/Freigaben werden bei einem reinen Rename nicht angefasst (kein `group.delete`/`create` im Rename-Zweig), das ist strukturell korrekt, sofern A1 hält. | | SC-4 | Verschwinden im AD löscht die Tessera-Gruppe samt Mitgliedschaften und Modulfreigaben | ⚠️ PRESENT_BEHAVIOR_UNVERIFIED | Code vollständig vorhanden: binärer Existenz-Sweep mit 32-Hex-Validierung vor jeder Filter-Interpolation, `reassignDefaultBeforeDelete()` läuft nachweislich vor `group.delete()` (Zeilenreihenfolge geprüft), Cascade auf `GroupMembership`/`ModuleGrant` existiert seit Migration 15-01, `ensureDefaultGroup()` läuft nach jeder Löschung. Der Review-Fund WR-03 (fälschliche Löschung bei reiner AD-Verschiebung außerhalb der Base-DN) ist behoben und mit drei neuen Tests abgesichert (Commit `00de6a6`). **Aber:** ob der binäre `(objectGUID=...)`-Filter (Annahme A2) von einem echten Domain Controller tatsächlich als Treffer/Nicht-Treffer korrekt interpretiert wird, ist nicht geprüft — nur simuliert. Ein negatives Ergebnis wäre laut RESEARCH.md/16-03-PLAN.md ein Stopp-Grund für die Löschsemantik. | | SC-5 | Lokal angelegte Gruppen ohne AD-Bindung bleiben vom Gruppen-Sync unberührt | ✓ VERIFIED | Kandidaten-Query in `syncBoundGroupsForTenant()` filtert `OR: [{ldapObjectGuid: {not: null}}, {ldapDn: {not: null}}]` — eine rein lokale Gruppe (beide Felder `null`) erfüllt keine der beiden Bedingungen und erscheint nie im Ergebnis, löst also keine LDAP-Suche und keine Schreiboperation aus. Zusätzlich verhindert `GroupFormModal.tsx` (nach D-07-Rückbau) jede nachträgliche AD-Bindung einer lokalen Gruppe: kein Radio-Element (`grep -c 'type="radio"'` = 0), kein Discovery-Aufruf (`grep -c 'ldap/groups'` = 0), `UpdateGroupDto` besitzt kein `ldapDn`-Feld mehr. | **Score:** 5/5 Erfolgskriterien code-vollständig; 3/5 zusätzlich per Unit-Test gegen echtes Laufzeitverhalten bewiesen (SC-1, SC-2, SC-5); 2/5 (SC-3, SC-4) präsent und verdrahtet, aber deren Kernannahme (A1/A2) nicht gegen ein echtes Active Directory geprüft. ### D-Entscheidungen (Auswahl der wichtigsten, code-geprüft) | Entscheidung | Status | Evidence | | --- | --- | --- | | D-03 (Namensfeld importierter Gruppen serverseitig gesperrt) | ✓ VERIFIED | `GroupsService.update()` wirft `BadRequestException`, wenn `existing.ldapObjectGuid \|\| existing.ldapDn` gesetzt ist (Zeile 84 in `groups.service.ts`) — das ist der Review-Fund WR-01, der ursprünglich nur `ldapObjectGuid` prüfte und damit Alt-Bindungen vor dem ersten Sync-Lauf umgehbar machte; Fix in Commit `dd59bf5` bestätigt im Code vorhanden. | | D-04 (interner Name, sync-immun, vier Anzeigestellen) | ✓ VERIFIED | Spalte existiert (Schema + Migration + laufende DB bestätigt), `syncBoundGroupsForTenant()` schreibt `internalName` nirgends, alle vier geplanten Anzeigestellen bestätigt: `listForTenant()`, `module-grants.service.ts#getUserAccess` (beide Projektionen), `admin/groups/page.tsx` Namenszelle, `admin/modules/grants/page.tsx` Spaltenkopf — überall Nullish-Fallback `internalName ?? name`, keine Truthiness-Variante gefunden. | | D-05 (Löschung im AD löscht ohne Warndialog, bewusst akzeptiert) | ✓ VERIFIED (Sichtbarkeitsmaßnahme) | Kein Warndialog im Sync-Pfad (bewusst); stattdessen zeigt der Sync-Bericht `groupsDeleted`/`defaultMarkerMoved` in `admin/ldap/page.tsx` — die einzige laut D-05 vorgesehene Gegenmaßnahme ist vorhanden. | | D-06 (Standardmarkierung wandert vor Löschung) | ✓ VERIFIED | `reassignDefaultBeforeDelete()` deterministisch (DEFAULT_GROUP_NAME zuerst, sonst älteste Gruppe per `createdAt asc`), nicht werfend, wird in `syncBoundGroupsForTenant()` nachweislich vor `group.delete()` aufgerufen; `ensureDefaultGroup()` läuft nach jeder Löschung. 6 gezielte Testfälle grün. | | D-07 (lokale Gruppe kann nicht mehr an AD gebunden werden) | ✓ VERIFIED | Radio-Auswahl vollständig aus `GroupFormModal.tsx` entfernt; `UpdateGroupDto` ohne `ldapDn`-Feld; Kandidaten-Query des Sync berührt lokale Gruppen nie. | ## Requirements Coverage | Requirement | Quelle | Beschreibung | Status | Evidence | | --- | --- | --- | --- | --- | | PERM-02 | Alle 5 Pläne (`requirements: [PERM-02]`) | Selektiver AD-Gruppen-Import mit Nachführung (Rename/Delete), lokale Gruppen unberührt, keine nachträgliche Bindung | ✓ SATISFIED auf Code-Ebene, mit offener Live-Verifikationsschuld | REQUIREMENTS.md markiert PERM-02 als `[x]` und „Complete" seit Plan 16-05 (2026-08-06). Diese Markierung ist auf Code-Vollständigkeits-Ebene gerechtfertigt — alle fünf Erfolgskriterien sind implementiert, verdrahtet und wo automatisiert prüfbar auch getestet. Sie verschweigt aber nicht die offene Live-AD-Prüfung: 16-03-SUMMARY.md, 16-05-SUMMARY.md und WINDOWS.md #4 halten A1/A2 ausdrücklich als unverifiziert fest und benennen ein negatives Ergebnis explizit als Stopp-Grund für die D-05-Löschsemantik. Die Requirements-Traceability-Tabelle selbst transportiert diese Nuance nicht (sie kennt nur „Complete"/„Pending") — das ist eine Grenze des Tools, keine Falschaussage der Pläne. | Keine verwaisten Requirement-IDs gefunden — PERM-02 ist die einzige der Phase zugeordnete ID und wird durchgängig in allen fünf Plan-Frontmatter-Blöcken referenziert. ## Required Artifacts | Artifact | Erwartet | Status | Details | | --- | --- | --- | --- | | `apps/api/prisma/schema.prisma` | `Group.internalName`, `Group.ldapObjectGuid`, `@@unique([tenantId, ldapObjectGuid])` | ✓ VERIFIED | Zeilen 143-153 bestätigt | | `apps/api/prisma/migrations/20260806133916_add_group_internal_name_and_object_guid/` | Versionierte Migration | ✓ VERIFIED | Existiert, per `migration-sql.spec.ts` (14 Fälle) festgenagelt, **und** live gegen die lokale DB angewendet (`psql \d "Group"` zeigt beide Spalten + Unique-Index; `_prisma_migrations` bestätigt `finished_at` gesetzt) | | `apps/api/src/ldap/ldap.service.ts` | `listGroups()` mit `alreadyImported`, `importGroupsByDn()`, `syncBoundGroupsForTenant()`, `escapeLdapFilterBuffer()` | ✓ VERIFIED | Alle vier Symbole vorhanden und aufgerufen (kein toter Code) | | `apps/api/src/ldap/ldap.controller.ts` | `POST /ldap/groups/import` unter RBAC | ✓ VERIFIED | Zeile 190, `@Roles(ADMIN, SUPER_ADMIN)`, statische Route vor jeder künftigen `:id`-Route dokumentiert | | `apps/api/src/groups/groups.service.ts` | `DEFAULT_GROUP_NAME`, `reassignDefaultBeforeDelete()`, Namenssperre, `internalName`-Handling | ✓ VERIFIED | Wie oben belegt | | `apps/api/src/groups/dto/update-group.dto.ts` | `internalName` statt `ldapDn` | ✓ VERIFIED | `ldapDn` nicht mehr vorhanden, `internalName?: string \| null` vorhanden | | `apps/api/src/groups/module-grants.service.ts` | `internalName ?? name` an beiden Projektionsstellen | ✓ VERIFIED | Zeilen 25/34/49 bestätigt | | `apps/web/.../admin/ldap/page.tsx` | Import-Sektion, erweiterter Sync-Bericht, `syncRequestError` | ✓ VERIFIED | Alle State-Variablen, Handler und Berichtszeilen vorhanden; `deactivated: 0`-Fallback-Objekt entfernt | | `apps/web/.../admin/groups/components/GroupFormModal.tsx` | Drei Zustände ohne AD-Bindung | ✓ VERIFIED | Kein Radio, kein Discovery-Aufruf, ein einziger `PATCH`-Aufrufblock | | `apps/web/.../admin/groups/page.tsx` | Namenszelle mit Fallback + Tooltip | ✓ VERIFIED | `internalName ?? group.name` mit `title={group.name}` | | `apps/web/.../admin/modules/grants/page.tsx` | Spaltenkopf + Suchfilter mit Fallback | ✓ VERIFIED | Beide Stellen bestätigt, Nullish-Variante | | `apps/web/src/messages/{de,en}.json` | Neue Schlüssel, verwaiste `ldapBind.*` entfernt | ✓ VERIFIED | Node-Paritätsprüfungen aus allen Plänen liefen zuletzt grün mit; keine erneute Prüfung nötig, da `tsc`/`vitest` diese implizit mitprüfen | ## Key Link Verification | From | To | Via | Status | Details | | --- | --- | --- | --- | --- | | `GroupFormModal.tsx` (importierte Gruppe) | `PATCH /groups/:id` | `{ internalName }`, niemals `name` | ✓ WIRED | Payload-Verzweigung über `isImported`, serverseitig durch `GroupsService.update()`-Sperre abgesichert (defense-in-depth, nicht nur UI) | | `syncBoundGroupsForTenant()` | `syncGroupMembershipsForTenant()` | Aufrufreihenfolge in `syncUsersForTenant()` | ✓ WIRED | Zeile 920 vor Zeile 927, zusätzlich durch Call-Order-Spy-Test abgesichert | | `syncBoundGroupsForTenant()` (Löschzweig) | `GroupsService.reassignDefaultBeforeDelete()` → `group.delete()` → `ensureDefaultGroup()` | Reihenfolge im Methodenkörper | ✓ WIRED | Grep-Zeilenvergleich bestätigt Reihenfolge; `ensureDefaultGroup` läuft einmal nach der Schleife bei mind. einer Löschung | | `LdapService.importGroupsByDn` | `forTenant(prisma, tenantId).group.create` | RLS-Scoping | ✓ WIRED | Bestätigt in Zeile ~700 | | Backend `LdapSyncResult` | Frontend `SyncResult` | Feldnamen-Parität | ✓ WIRED | `groupsAdopted/groupsRenamed/groupsDeleted/defaultMarkerMoved/groupMembershipsAdded/groupMembershipsRemoved` — alle sechs Felder im Frontend-Interface vorhanden, Namen identisch | ## Anti-Patterns Keine Debt-Marker (`TBD`/`FIXME`/`XXX`/`TODO`/`HACK`/`PLACEHOLDER`) in den elf geprüften Kern-Dateien der Phase gefunden. Zwei vom Code-Review dokumentierte, bewusst offen gelassene Info-Findings bleiben unverändert bestehen (nicht blockierend, laut 16-REVIEW.md-Frontmatter `open_findings: [IN-01, IN-02]`): - **IN-01** — veralteter Kommentar in `groups.controller.ts:65` behauptet noch „AD-Bindung setzen/lösen" über `PATCH /groups/:id`, obwohl das seit D-07 nicht mehr möglich ist (das DTO selbst wurde korrekt aktualisiert). Kosmetisch, keine funktionale Auswirkung. - **IN-02** — `internalName` trägt kein `@MaxLength()` in `update-group.dto.ts`. Kein Sicherheitsrisiko (Spalte ist `TEXT`, RLS greift), aber ein potenziell sehr langer Wert würde unbegrenzt persistiert. Diese beiden Punkte sind Informationsfunde des Code-Reviews, keine neuen Beobachtungen dieser Verifikation, und wurden bewusst nicht behoben (siehe `16-REVIEW.md` Frontmatter). Sie sind kein Grund für `gaps_found`. ## Anti-Patterns explizit NICHT gefunden - Kein stiller `catch {}` in den geänderten Frontend-Handlern (Discovery, Import, Sync, GroupFormModal-Speichern) — alle vier zeigen einen sichtbaren `text-*-destructive`-Zustand. - Keine Truthiness-Fallbacks (`internalName ||`) an den vier Anzeigestellen — durchgängig `??`. - Kein zweiter Schreibpfad, der eine AD-Bindung setzen könnte. ## Testlage - `cd apps/api && npx vitest run` — 40 Dateien, 566 Tests, grün. - `cd apps/web && npx vitest run` — 32 Dateien, 192 Tests, grün. - `cd apps/api && npx tsc --noEmit` — fehlerfrei. - `cd apps/web && npx tsc --noEmit` — fehlerfrei. - Migration live gegen die lokale Datenbank angewendet und per `\d "Group"` gegengeprüft. - Gezielter Re-Run der drei phasenrelevanten Testdateien (`ldap.service.spec.ts`, `groups.service.spec.ts`, `module-grants.service.spec.ts`): 130 Tests, grün. - Kein Probe-Skript für diese Phase vorgesehen (`scripts/*/tests/probe-*.sh` existiert nicht für Phase 16) — Schritt 7c entfällt. ## Human Verification Required Siehe YAML-Frontmatter `human_verification` oben für die vollständige, strukturierte Liste. Zusammengefasst, in Prioritätsreihenfolge: 1. **A1/A2 Live-AD-Prüfung (höchste Priorität, WINDOWS.md #4)** — read-only gegen ViCoTest (`balios.ctl.local`): objectGUID-Stabilität über eine Umbenennung, byteweise Hex-Filter-Treffer. Ein negatives Ergebnis ist laut Plan-Vorgabe selbst ein Stopp-Grund für die Löschsemantik aus D-05 und müsste zu einer neuen Planungsentscheidung führen, nicht zu einem stillen Codefix. 2. **Browser-Durchklick GroupFormModal + Gruppenliste (WINDOWS.md #5)** — alle drei Dialogzustände, Fehleranzeige, Namensanzeige nach dem Speichern. 3. **Browser-Durchklick Sync-Bericht + Freigabe-Matrix (WINDOWS.md #6)** — alle Berichtszeilen inkl. Amber-Zeile, Matrix-Spaltensuche unter beiden Namen. 4. **Backstop-Aussagen** aus den `must_haves.truths` der fünf Pläne (Nebenläufigkeit bei parallelen Import-/Sync-Requests, Reihenfolgeunabhängigkeit, lange Texte in Listen/Dialogen) — laut eigener Plan-Deklaration nicht automatisiert prüfbar. ## Gaps Summary Kein Gap im Sinne von „fehlender oder kaputter Code" gefunden. Alle Artefakte existieren, sind substanziell (keine Stubs), verdrahtet und (soweit automatisiert möglich) getestet. Die Einstufung `human_needed` statt `passed` beruht ausschließlich darauf, dass zwei der fünf Erfolgskriterien an einer gegen echtes Active-Directory-Verhalten ungeprüften Annahme hängen, und dass mehrere manuelle Verifikationsschritte in dieser Umgebung nicht ausführbar waren (kein Browser-Tool, kein erreichbares AD). Das ist keine neue Erkenntnis dieser Verifikation — alle fünf SUMMARY.md-Dateien und WINDOWS.md dokumentieren diese Lücken bereits selbst und ehrlich; diese Verifikation bestätigt sie unabhängig, statt sie zu wiederholen oder zu übergehen. --- _Verified: 2026-08-06T15:07:32Z_ _Verifier: Claude (gsd-verifier)_