234f80b4fb
The user has no write access to the company AD, so tests 1, 3, 4 and 8 cannot be run there, and building a throwaway domain controller for them was judged disproportionate now that the one substantive defect at this spot is found and fixed (UAT test 2 -> quick task 260811-f9i). Recorded rather than hidden: A1 (objectGUID survives a rename) now rests on Microsoft's documentation, not on our own measurement. The Tessera-side rename and delete logic stays covered by unit tests against fixtures. If a rename ever fails to propagate in production, 16-VERIFICATION.md names that as the starting point. Phase 16 marked complete in STATE.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
186 lines
24 KiB
Markdown
186 lines
24 KiB
Markdown
---
|
|
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)_
|