Files
schalli 2a954f5cee docs(16): record WR-01..WR-04 fix resolution in review and 16-03 summary
Marks all four Warning findings from 16-REVIEW.md as resolved with their
fix commit hashes; the two Info findings (IN-01/IN-02) remain open and
out of scope for this fix pass. Appends a note to 16-03-SUMMARY.md
recording the WR-02/WR-03/WR-04 behavior change in
syncBoundGroupsForTenant(), since that summary still described the
pre-fix behavior.
2026-08-06 17:02:35 +02:00

22 KiB

phase, plan, subsystem, tags, requires, provides, affects, actuals, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
16-ad-gruppen-synchronisation 03 auth
nestjs
prisma
ldap
ldapts
groups
vitest
rls
phase provides
16-ad-gruppen-synchronisation (Plan 16-01) Group.ldapObjectGuid (Migration angewendet), LdapService.escapeLdapFilterBuffer() (bislang ungenutzt)
phase provides
16-ad-gruppen-synchronisation (Plan 16-02) GroupsService.reassignDefaultBeforeDelete(tenantId, groupId), GroupsService.ensureDefaultGroup(tenantId), DEFAULT_GROUP_NAME
LdapService.syncBoundGroupsForTenant(client, config, tenantId, result): Rename-Erkennung (SC-3), Verschwinden-Loeschung mit Default-Handoff (SC-4/D-05/D-06), Alt-Bindungs-GUID-Nachtrag (D-07), 32-Hex-Validierung vor jeder Filter-Interpolation (T-16-01)
LdapSyncResult erweitert um groupsAdopted, groupsRenamed, groupsDeleted, defaultMarkerMoved
syncUsersForTenant() Schritt 5a: syncBoundGroupsForTenant() laeuft nachweislich vor Schritt 5b (syncGroupMembershipsForTenant, D-21) — Kernkorrektheitsbedingung der Phase (RESEARCH.md Pitfall 1)
LdapService(prisma, userService, groupsService) — dritter Konstruktor-Parameter; LdapModule importiert GroupsModule
16-04-dialog-umbau
16-05-anzeige-fallback-frontend
tokens tasks commits
11091 2 2
added patterns
Erster tatsaechlicher Aufrufer von escapeLdapFilterBuffer() (aus Plan 16-01) — der binaere Existenz-Sweep-Filter (objectGUID=...)
Reihenfolge-Kopplung zweier privater Sync-Schritte durch einen beobachtenden Test (Call-Order-Spy via vi.spyOn mit mockImplementation, das die Originalmethode aufruft) statt nur behaupteter Kommentar-Reihenfolge
32-Zeichen-Hex-Validierung als eigener Guard VOR jeder Buffer-Rueckwandlung — ein Wert, der die Regex nicht besteht, erzeugt eine Fehlerzeile statt einer Filter-Interpolation (T-16-01)
created modified
apps/api/src/ldap/ldap.service.ts
apps/api/src/ldap/ldap.module.ts
apps/api/src/ldap/ldap.service.spec.ts
syncBoundGroupsForTenant() wird in Task 1 als eigenstaendige, noch nicht eingehaengte Methode gebaut und per direktem Cast ((service as any).syncBoundGroupsForTenant(...)) getestet — die Verdrahtung in syncUsersForTenant() ist bewusst Task 2 vorbehalten (Plan-Vorgabe), damit die Reihenfolge-Korrektheit als eigener, isoliert testbarer Schritt sichtbar bleibt.
D-21-Testfixtures (Plan 15/vor Plan 16) hatten nie ein ldapObjectGuid-Feld gesetzt. Nach der Verdrahtung in Task 2 durchlaeuft jede dieser Gruppen jetzt zwingend Schritt 5a (Alt-Bindungs-Nachtrag). Statt jede der ueber zehn Test-Fixtures im D-21-Block einzeln um ein Feld zu erweitern, wurde der gemeinsame mockSearch so erweitert, dass er 5a's zwei Filterformen (Backfill-Probe, Existenz-Sweep) deterministisch als No-Op aufloest (DN-abgeleitete GUID, Identitaets-Treffer) — kleinerer, zentralerer Diff als 11 Einzelaenderungen.
PERM-02 bleibt in REQUIREMENTS.md bewusst auf [ ] (Pending) stehen — dieser Plan liefert Rekonziliation und Verdrahtung, das Requirement schliesst laut Plan-Vorgabe erst mit Plan 16-05.
A1/A2-Live-Pruefung gegen ViCoTest nicht durchfuehrbar: kein erreichbares Active Directory in dieser Sandbox (Umgebungsvorgabe fuer diesen Lauf). Als offener Punkt im Broken-Windows-Ledger (WINDOWS.md #4) und unten dokumentiert, statt stillschweigend uebersprungen.
Reihenfolge-Kopplung zwischen zwei privaten Sync-Schritten wird durch einen beobachtenden Call-Order-Test abgesichert, nicht nur durch Kommentartext an der Aufrufstelle
PERM-02
id description requirement verification human_judgment
D1 syncBoundGroupsForTenant() erkennt Rename (cn/dn-Aenderung -> Group.name/ldapDn-Update, internalName bleibt unberuehrt) und Verschwinden (kein Treffer -> Loeschung) korrekt, ohne lokale Gruppen (ldapObjectGuid+ldapDn beide null) jemals abzufragen (SC-3/SC-4/SC-5, D-04) PERM-02
kind ref status
unit apps/api/src/ldap/ldap.service.spec.ts#LdapService.syncBoundGroupsForTenant — Rekonziliation gegen das Verzeichnis (SC-3/SC-4/SC-5, D-05/D-06) (14 it-Faelle) pass
kind ref status
other grep -c internalName im Methodenkoerper (awk-Range) = 0 pass
false
id description requirement verification human_judgment
D2 Loesch-Zweig ruft reassignDefaultBeforeDelete() nachweislich VOR group.delete(); nach jeder Loeschung laeuft ensureDefaultGroup() einmal — kein Mandant bleibt ohne markierte Standardgruppe (D-06, RESEARCH.md Pitfall 5) PERM-02
kind ref status
unit apps/api/src/ldap/ldap.service.spec.ts — Faelle 'no hit, IS the default group...' und 'no hit, is the ONLY group of the tenant...' pass
kind ref status
other awk-Range-Grep: reassignDefaultBeforeDelete-Zeile < group.delete-Zeile pass
false
id description requirement verification human_judgment
D3 Alt-Bindungen (ldapDn gesetzt, ldapObjectGuid null) werden per Base-Scoped-Lookup einmalig nachgetragen (groupsAdopted) und im selben Lauf regulaer weiterverarbeitet; loest der DN nicht mehr auf, wird NICHT geloescht, nur eine Fehlerzeile erzeugt (D-07, T-16-11) PERM-02
kind ref status
unit apps/api/src/ldap/ldap.service.spec.ts — Faelle 'a legacy binding ... whose DN still resolves ...' und '... whose DN no longer resolves ...' pass
false
id description requirement verification human_judgment
D4 Der gespeicherte Hex-Wert wird vor jeder Filter-Interpolation auf exakt 32 [0-9a-f]-Zeichen validiert; ein ungueltiger Wert erzeugt eine Fehlerzeile statt eines Filters (T-16-01) PERM-02
kind ref status
unit apps/api/src/ldap/ldap.service.spec.ts#'an invalid stored ldapObjectGuid (not 32 [0-9a-f] chars) never reaches a filter' pass
false
id description requirement verification human_judgment
D5 syncBoundGroupsForTenant() laeuft in syncUsersForTenant() nachweislich VOR syncGroupMembershipsForTenant() (Schritt 5a vor 5b); ein im selben Lauf erkannter Rename fuehrt beobachtbar zu keinem memberOf-Filter mit der alten DN mehr (RESEARCH.md Pitfall 1) PERM-02
kind ref status
unit apps/api/src/ldap/ldap.service.spec.ts#LdapService.syncUsersForTenant — Schrittreihenfolge Gruppen vor Mitgliedschaften (3 it-Faelle: Call-Order-Spy, No-Op-Waechter, Regressions-Filter-Check) pass
kind ref status
other grep -n Aufrufzeilen: syncBoundGroupsForTenant-Zeile < syncGroupMembershipsForTenant-Zeile pass
false
id description requirement verification human_judgment rationale
D6 RESEARCH.md-Annahmen A1 (objectGUID uebersteht AD-Umbenennung) und A2 (byteweise Hex-Filter-Syntax vom Ziel-AD akzeptiert) read-only gegen ViCoTest verifiziert, bevor die Loeschsemantik (D-05) als produktionsreif gilt PERM-02
kind ref status
other Kein erreichbares Active Directory in dieser Sandbox (Umgebungsvorgabe) — Live-Pruefung nicht durchgefuehrt fail
true A1/A2 bleiben unverifizierte Annahmen aus RESEARCH.md. Als WINDOWS.md-Eintrag #4 (unrun-verify) festgehalten. Ein negatives Ergebnis bei A1 ODER A2 ist ein Stopp-Grund fuer die Loeschsemantik aus D-05 — vor produktivem Einsatz gegen ViCoTest (balios.ctl.local) nachzuholen.
12min 2026-08-06 complete

Phase 16 Plan 3: Gruppen-Rekonziliation gegen das Verzeichnis Summary

syncBoundGroupsForTenant() unterscheidet AD-Umbenennung von AD-Verschwinden ueber den rename-stabilen ldapObjectGuid, laeuft nachweislich vor dem Mitgliedschafts-Abgleich, uebergibt die Standardmarkierung vor jeder Loeschung und traegt Alt-Bindungen aus Plan 15-06 nach — die A1/A2-Live-Pruefung gegen ein echtes Active Directory bleibt offen (kein AD in dieser Sandbox erreichbar).

Performance

  • Duration: 12 min (Commit-Spanne 16:15:09 Uhr bis 16:21:10 Uhr; Lesen von PLAN.md, 16-01-/16-02-SUMMARY.md, PATTERNS.md, RESEARCH.md und der Zieldateien davor nicht mitgerechnet)
  • Started: 2026-08-06T14:15:09Z (erster Task-Commit)
  • Completed: 2026-08-06T14:21:10Z (zweiter Task-Commit)
  • Tasks: 2
  • Files modified: 3

Accomplishments

  • Neue private Methode LdapService.syncBoundGroupsForTenant(): fragt alle Gruppen mit gesetztem ldapObjectGuid ODER ldapDn ab (der D-07-Anschluss fuer Alt-Bindungen), tut fuer eine rein lokale Gruppe (beide Felder null) nichts und loest dafuer keine einzige LDAP-Suche aus
  • Rename-Erkennung (SC-3): ein AD-Treffer mit geaendertem cn/dn zieht Group.name/Group.ldapDn nach, internalName bleibt in jedem Codepfad der Methode unberuehrt (D-04); ein P2002 aus einer Namenskollision wird abgefangen und als Fehlerzeile gemeldet, der Lauf geht weiter
  • Verschwinden-Loeschung (SC-4/D-05): kein Treffer im binaeren Existenz-Sweep loescht die Gruppe — aber immer erst NACH dem Standardmarkierungs-Handoff (reassignDefaultBeforeDelete, Plan 16-02), nachweislich in dieser Zeilenreihenfolge; nach jeder Loeschung laeuft ensureDefaultGroup() einmal, damit kein Mandant ohne Standardgruppe dasteht (D-06)
  • Alt-Bindungs-Nachtrag (D-07): eine Gruppe mit ldapDn, aber ohne ldapObjectGuid, bekommt den Identitaetsschluessel per Base-Scoped-Lookup einmalig nachgetragen (groupsAdopted) und wird im selben Lauf reguelaer weiterverarbeitet; loest der DN nicht mehr auf, wird nicht geloescht, nur eine Fehlerzeile erzeugt — ohne stabilen Schluessel waere eine Loeschung eine Vermutung
  • Der gespeicherte Hex-Wert wird vor jeder Filter-Interpolation auf exakt 32 [0-9a-f]-Zeichen validiert (T-16-01); ein ungueltiger Wert erzeugt eine Fehlerzeile statt einer Filter-Interpolation
  • Schritt 5a (syncBoundGroupsForTenant) laeuft in syncUsersForTenant() nachweislich VOR Schritt 5b (syncGroupMembershipsForTenant, D-21) — abgesichert durch einen beobachtenden Call-Order-Test (nicht nur durch Kommentartext), plus einen Regressionstest, der zeigt, dass ein im selben Lauf erkannter Rename zu keinem memberOf-Filter mit der alten DN mehr fuehrt
  • LdapService-Konstruktor nimmt neu GroupsService entgegen; LdapModule importiert GroupsModule (kein Zyklus, da GroupsModule weder LdapModule noch UserModule importiert)
  • 17 neue Testfaelle (14 fuer die neue Methode isoliert, 3 fuer die Verdrahtungsreihenfolge); vollstaendige API-Testsuite gruen: 40 Testdateien, 560 Tests

Task Commits

Each task was committed atomically:

  1. Task 1: Gruppen-Rekonziliation gegen das Verzeichnis (SC-3, SC-4, SC-5, D-05, D-06) — 5222934 (feat, tdd)
  2. Task 2: Einhaengen als Schritt 5a vor dem Mitgliedschafts-Abgleich (RESEARCH.md Pitfall 1) — 68aca81 (feat, tdd)

Plan metadata: wird mit diesem Summary committet

Files Created/Modified

  • apps/api/src/ldap/ldap.service.ts — LdapSyncResult um groupsAdopted/groupsRenamed/groupsDeleted/defaultMarkerMoved erweitert; Konstruktor nimmt GroupsService; neue private Methode syncBoundGroupsForTenant(); Aufruf als Schritt 5a in syncUsersForTenant() vor Schritt 5b
  • apps/api/src/ldap/ldap.module.ts — GroupsModule-Import ergaenzt
  • apps/api/src/ldap/ldap.service.spec.ts — zwei neue describe-Bloecke (syncBoundGroupsForTenant mit 14 Faellen, Schrittreihenfolge Gruppen vor Mitgliedschaften mit 3 Faellen); alle acht new LdapService(...)-Konstruktionen um einen dritten Mock-Parameter ergaenzt; D-21-Block-Fixtures um eine 5a-No-Op-Aufloesung im gemeinsamen mockSearch erweitert, ohne jede der ueber zehn Einzel-Fixtures anzufassen

Decisions Made

  • syncBoundGroupsForTenant() wurde in Task 1 bewusst noch NICHT in syncUsersForTenant() eingehaengt (Plan-Vorgabe) — Task 1 testet die Methode per direktem Cast auf die private Methode, Task 2 liefert ausschliesslich die Verdrahtung samt Reihenfolge-Test. Diese Trennung macht die zentrale Korrektheitsbedingung der Phase (Reihenfolge) als eigenen, isoliert nachvollziehbaren Schritt sichtbar.
  • Statt jede der ueber zehn D-21-Test-Fixtures (aus Plan 15, vor Plan 16 gebaut, nie mit ldapObjectGuid versehen) einzeln um ein Feld zu erweitern, wurde der gemeinsame mockSearch im D-21-Block um eine deterministische No-Op-Aufloesung fuer beide 5a-Filterformen erweitert (DN-abgeleitete GUID, Identitaets-Treffer bei unveraendertem cn/dn). Kleinerer, zentralerer Diff; die D-21-Tests bleiben inhaltlich unveraendert auf die Mitgliedschafts-Reconciliation fokussiert.
  • PERM-02 bleibt in REQUIREMENTS.md bewusst auf [ ] (Pending) — dieser Plan liefert Rekonziliation und Verdrahtung, das Requirement schliesst laut Plan-Vorgabe erst mit Plan 16-05.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Bestehender D-21-Test brach nach der additiven LdapSyncResult-Erweiterung (exaktes toEqual)

  • Found during: Task 1
  • Issue: Der Test „creates nobody and deactivates nobody when the base DN is empty" verglich result per toEqual gegen ein Literal ohne die vier neuen Felder — nach der additiven Interface-Erweiterung schlug der Vergleich fehl, obwohl das Verhalten unveraendert korrekt war.
  • Fix: Die vier neuen Felder (groupsAdopted: 0, groupsRenamed: 0, groupsDeleted: 0, defaultMarkerMoved: 0) im erwarteten Literal ergaenzt.
  • Files modified: apps/api/src/ldap/ldap.service.spec.ts
  • Verification: npx vitest run src/ldap/ldap.service.spec.ts gruen
  • Committed in: 5222934

2. [Rule 1 - Bug] Wiederverwendete GUID-Test-Fixture aus Plan 16-01 war tatsaechlich nur 31 Hex-Zeichen lang

  • Found during: Task 1
  • Issue: Der bestehende guidBuffer-Fixture-String im "AD group import"-Block ('0123456789abcdef0123456789abcde') ist bei genauer Zaehlung 31, nicht 32 Zeichen lang — Buffer.from(..., 'hex') schneidet das letzte ungerade Zeichen stumm ab und liefert 15 statt 16 Bytes zurueck. Das fiel dort nie auf, weil importGroupsByDn() die Laenge nie validiert; die neue 32-Hex-Validierung in syncBoundGroupsForTenant() (T-16-01) schlug mit demselben Wert sofort fehl.
  • Fix: Eigene, lokal im neuen describe-Block definierte, garantiert 32 Zeichen lange Fixture ('0123456789abcdef'.repeat(2)) statt den bestehenden (fehlerhaften) Fixture-String zu teilen. Der bestehende Fixture-String im 16-01-Block wurde NICHT angefasst (aussarhalb des Scopes dieses Plans, dortige Tests bleiben unveraendert gruen).
  • Files modified: apps/api/src/ldap/ldap.service.spec.ts
  • Verification: npx vitest run src/ldap/ldap.service.spec.ts gruen, neuer Kommentar dokumentiert den Unterschied explizit
  • Committed in: 5222934

3. [Rule 1 - Bug] Verdrahtung in Task 2 brach vier bestehende D-21-Tests (Schritt 5a lief nun ungewollt gegen deren Fixtures)

  • Found during: Task 2
  • Issue: Nach dem Einhaengen von Schritt 5a liefen die vorher isoliert getesteten D-21-Membership-Tests real durch syncUsersForTenant() — deren Gruppen-Fixtures (nur ldapDn, kein ldapObjectGuid, vor Plan 16 gebaut) loesten in jedem Lauf den Alt-Bindungs-Nachtragspfad aus und produzierten unerwuenschte Fehlerzeilen bzw. veraenderten result.errors.
  • Fix: Der gemeinsame mockSearch im D-21-beforeEach (und die eine Test-lokale Ueberschreibung im „records a search failure"-Test) um eine deterministische Aufloesung beider 5a-Filterformen erweitert (resolve5aNoOp-Helfer auf Describe-Ebene), die pro Fixture-Gruppe einen DN-abgeleiteten, immer gueltigen GUID annimmt und einen Identitaets-Treffer liefert — 5a wird dadurch fuer jede D-21-Fixture ein reiner No-Op, ohne die ueber zehn einzelnen groups = [...]-Literale anzufassen.
  • Files modified: apps/api/src/ldap/ldap.service.spec.ts
  • Verification: npx vitest run src/ldap/ldap.service.spec.ts — alle 55 Faelle gruen; npx vitest run — vollstaendige API-Suite (560 Tests) gruen
  • Committed in: 68aca81

Total deviations: 3 auto-fixed (alle Rule 1, direkte Folgen der additiven Interface-Erweiterung bzw. der Verdrahtung — kein Scope Creep, kein Architekturwechsel) Impact on plan: Keine Auswirkung auf Funktionalitaet oder Scope der neuen Methode selbst — alle drei Korrekturen betrafen ausschliesslich bestehende Testinfrastruktur, die durch additive/verdrahtende Aenderungen aus diesem Plan beruehrt wurde.

Outstanding Live Verification (A1/A2)

RESEARCH.md fuehrt zwei Annahmen, die dieser Plan gegen ein echtes Active Directory pruefen sollte (Task 2 <human-check>), aber in dieser Sandbox nicht pruefen konnte — es ist kein Active-Directory-Server erreichbar (Umgebungsvorgabe fuer diesen Ausfuehrungslauf, siehe <environment> im Auftrag):

  1. A1 — objectGUID uebersteht eine reine CN-Umbenennung unveraendert. Unverifiziert. Die gesamte Rename-Erkennung (SC-3) haengt an dieser Annahme — ist sie falsch, wuerde jede echte AD-Umbenennung weiterhin wie ein Verschwinden+Neuanlage behandelt.
  2. A2 — die byteweise \XX-Hex-Escaping-Syntax fuer den binaeren (objectGUID=...)-Filter wird vom Ziel-AD korrekt interpretiert. Unverifiziert. Ist sie falsch, liefert der Existenz-Sweep fuer JEDE gebundene Gruppe null Treffer — SC-4 (Loeschung) wuerde dann faelschlich auf jede weiterhin existierende Gruppe angewendet.

Beide Antworten stehen noch aus. Sie sind als offener Eintrag im projektweiten Broken-Windows-Ledger festgehalten: .planning/WINDOWS.md Eintrag #4 (kind: unrun-verify, phase: 16). Ein negatives Ergebnis bei A1 ODER A2 ist ein Stopp-Grund fuer die Loeschsemantik aus D-05 — die Implementierung darf in diesem Fall NICHT einfach "umgebogen" werden (so RESEARCH.md/PLAN.md ausdruecklich), sondern erfordert eine erneute Planungsentscheidung. Vor produktivem Einsatz der Loeschsemantik muss die read-only Pruefung gegen ViCoTest (balios.ctl.local) aus 16-VALIDATION.md nachgeholt werden: (1) eine AD-Gruppe suchen, objectGUID notieren, im AD umbenennen, erneut suchen, GUID vergleichen; (2) eine Suche mit dem byteweise escaped objectGUID-Filter absetzen und pruefen, ob sie exakt diese Gruppe zurueckliefert.

Gemaess workflow.human_verify_mode: end-of-phase wurde dieser Plan trotz der offenen Pruefung bis zum Ende ausgefuehrt, statt mittendrin anzuhalten — die offene Verifikation ist hier klar dokumentiert und im Ledger sichtbar, nicht stillschweigend uebersprungen.

Issues Encountered

Siehe Deviations oben — alle drei Punkte betrafen ausschliesslich bestehende Testinfrastruktur, die durch die additiven bzw. verdrahtenden Aenderungen dieses Plans beruehrt wurde, nicht die fachliche Logik der neuen Methode selbst.

User Setup Required

None — keine externe Service-Konfiguration erforderlich. Der laufende Docker-Stack (lokal) wurde von diesem Plan nicht angefasst; die Aenderungen liegen im Quellcode und werden erst mit einem --build/--force-recreate durch den Nutzer wirksam. Kein Deploy auf den Testserver durch diesen Lauf (Projektregel).

Next Phase Readiness

  • syncBoundGroupsForTenant() ist vollstaendig implementiert, verdrahtet und getestet — die Kernkorrektheitsbedingung der Phase (Reihenfolge vor dem Mitgliedschafts-Abgleich) ist durch einen beobachtenden Test abgesichert, nicht nur behauptet
  • Plan 16-04 (Dialog-Umbau) und 16-05 (Anzeige-Fallback Frontend, Requirement-Abschluss PERM-02) koennen auf dieser Grundlage aufsetzen
  • Offener Punkt bleibt bestehen (WINDOWS.md #4): A1/A2-Live-Pruefung gegen ein echtes Active Directory (ViCoTest) muss nachgeholt werden, bevor die Loeschsemantik aus D-05 als produktionsreif gilt — siehe Abschnitt „Outstanding Live Verification" oben

Post-Review Fix (16-REVIEW.md, 2026-08-06)

Der Code-Review dieser Phase (16-REVIEW.md) fand drei Warnungen an genau der in diesem Plan gebauten syncBoundGroupsForTenant()-Methode. Alle drei sind seither behoben — die obige Beschreibung dieses Plans spiegelt noch den Vor-Fix-Stand wider, daher dieser Nachtrag:

  • WR-02 (Commit 1971795): Der Rename-Zweig meldete jeden P2002 beim Zurückschreiben von name/ldapDn pauschal als Namenskollision, ohne updateError.meta.target zu prüfen — obwohl der Aufruf zwei verschiedene Unique-Indizes treffen kann. Behoben durch dieselbe Ziel-Prüfung, die importGroupsByDn() bereits nutzte.
  • WR-03 (Commit 00de6a6, der wichtigste der drei Punkte): Der Existenz-Sweep vor einer Löschung suchte ausschließlich unter den konfigurierten Base-DNs. Eine AD-Gruppe, die in eine andere OU verschoben (nicht gelöscht) wurde, lieferte dort searchEntries.length === 0 und wurde fälschlich als „verschwunden" gelesen und samt Mitgliedschaften/Modulfreigaben gelöscht — Zugriffsverlust durch eine reine Verzeichnis-Umstrukturierung, kein tatsächliches AD-Löschen. Die Methode führt jetzt vor jeder Löschung einen zweiten, weiter gefassten Sweep gegen die Domänen-Wurzel jeder konfigurierten Base-DN aus; ein Treffer dort verhindert die Löschung und wird stattdessen als Fehlerzeile gemeldet. Erst wenn auch dieser weite Sweep leer bleibt, gilt SC-4/D-05 als tatsächlich erfüllt.
  • WR-04 (Commit 2779d42): Die Umbenennungs-Erkennung verglich cn/dn byteweise. Eine reine Groß-/Kleinschreibungs-Abweichung zwischen zwei Sync-Läufen (z. B. nach einem Domain-Controller-Wechsel) hätte jedes Mal fälschlich eine Umbenennung ausgelöst und die Idempotenz-Garantie verletzt. Der Vergleich zur Entscheidung „ist das eine Umbenennung" ist jetzt case-insensitive; der tatsächlich gespeicherte Wert bleibt weiterhin exakt der vom Verzeichnis gemeldete (D-03 unangetastet).

(WR-01, in groups.service.ts, betrifft nicht diese Methode — siehe 16-02-SUMMARY.md. Commit dd59bf5.)

Alle vier Fixes sind mit neuen Tests in ldap.service.spec.ts bzw. groups.service.spec.ts abgesichert; die volle API-Suite lief zuletzt gruen. Details siehe 16-REVIEW.md.


Phase: 16-ad-gruppen-synchronisation Completed: 2026-08-06

Self-Check: PASSED

Alle 3 in diesem Plan genannten Quelldateien sowie dieses Summary existieren auf der Festplatte; beide Task-Commit-Hashes (5222934, 68aca81) sind im lokalen Git-Log auffindbar.