docs(16-03): complete Gruppen-Rekonziliation plan

This commit is contained in:
2026-08-06 16:24:25 +02:00
parent 68aca81f71
commit 5a687a9d01
4 changed files with 254 additions and 12 deletions
+2 -2
View File
@@ -562,7 +562,7 @@ Plans:
**Offen für die Planung**: ob ein Admin eine automatisch angelegte Gruppe in Tessera umbenennen darf (würde beim nächsten Sync überschrieben), und wie sich die Auswahl-Oberfläche zur bestehenden AD-Gruppenauswahl in `/admin/groups` verhält.
**Plans**: 2/5 plans executed
**Plans**: 3/5 plans executed
Plans:
**Wave 1**
@@ -575,7 +575,7 @@ Plans:
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 16-03-PLAN.md — Sync-Rekonziliation: Umbenennung, Loeschung, Alt-Bindungen; eingehaengt vor dem Mitgliedschafts-Abgleich (D-03, D-05, D-06)
- [x] 16-03-PLAN.md — Sync-Rekonziliation: Umbenennung, Loeschung, Alt-Bindungen; eingehaengt vor dem Mitgliedschafts-Abgleich (D-03, D-05, D-06)
- [ ] 16-04-PLAN.md — GroupFormModal ohne AD-Bindung, gesperrtes Namensfeld, interner Name, i18n-Bereinigung (D-03, D-04, D-07)
**Wave 4** *(blocked on Wave 3 completion)*
+11 -7
View File
@@ -5,15 +5,15 @@ milestone_name: Ausschreibungs-Radar
current_phase: 16
current_phase_name: AD-Gruppen-Synchronisation
status: executing
stopped_at: Completed 16-02-PLAN.md
last_updated: "2026-08-06T14:03:42.541Z"
stopped_at: Completed 16-03-PLAN.md
last_updated: "2026-08-06T14:24:13.324Z"
last_activity: 2026-08-06
last_activity_desc: Phase 16 execution started
progress:
total_phases: 16
completed_phases: 13
total_plans: 80
completed_plans: 75
completed_plans: 76
---
# Project State
@@ -28,11 +28,11 @@ See: .planning/PROJECT.md (updated 2026-07-17)
## Current Position
Phase: 16 (AD-Gruppen-Synchronisation) — EXECUTING
Plan: 3 of 5
Plan: 4 of 5
Status: Ready to execute
Last activity: 2026-08-06 — Phase 16 execution started
Progress: [█████████░] 94%
Progress: [██████████] 95%
## Performance Metrics
@@ -109,6 +109,7 @@ Progress: [█████████░] 94%
| Phase 15 P08 | 30min | 2 tasks | 12 files |
| Phase 16 P01 | 34min | 3 tasks | 10 files |
| Phase 16-ad-gruppen-synchronisation P02 | 5min | 3 tasks | 5 files |
| Phase 16-ad-gruppen-synchronisation P03 | 12min | 2 tasks | 3 files |
## Accumulated Context
@@ -262,6 +263,9 @@ Recent decisions affecting current work:
- [Phase ?]: [16-02]: reassignDefaultBeforeDelete() nutzt bewusst nicht findOwned() — eigenes still-false-Muster fuer Batch-Sync-Laeufe (D-06), kein NotFoundException-Abbruch
- [Phase ?]: [16-02]: Namenssperre fuer importierte Gruppen (D-03) ist eine Backend-Invariante in GroupsService.update() (BadRequestException), nicht nur ein UI-Disable
- [Phase ?]: [16-02]: PERM-02 bleibt in REQUIREMENTS.md bewusst auf [ ] — dieser Plan liefert nur den Backend-Teil, das Requirement schliesst erst mit Plan 16-05
- [Phase ?]: [16-03]: syncBoundGroupsForTenant() als eigene, in Task 1 noch unverdrahtete Methode gebaut; Task 2 liefert ausschliesslich die Verdrahtung als Schritt 5a vor 5b samt Call-Order-Test — Reihenfolge ist die zentrale Korrektheitsbedingung der Phase
- [Phase ?]: [16-03]: A1/A2-Live-Pruefung gegen ViCoTest nicht durchfuehrbar (kein erreichbares AD in dieser Sandbox) — als WINDOWS.md #4 (unrun-verify) festgehalten, negatives Ergebnis ist Stopp-Grund fuer die D-05-Loeschsemantik
- [Phase ?]: [16-03]: PERM-02 bleibt in REQUIREMENTS.md bewusst auf [ ] — schliesst erst mit Plan 16-05
### Pending Todos
@@ -306,7 +310,7 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-08-06T14:03:42.506Z
Stopped at: Completed 16-02-PLAN.md
Last session: 2026-08-06T14:24:13.279Z
Stopped at: Completed 16-03-PLAN.md
Resume file: None
Last activity: 2026-07-14 - Built LDAP per-user exclude/denylist filter (9d1323f), migration applied on live DB, verified via Playwright: sync deactivated 4 excluded service accounts (administrator/krbtgt/guest/dns-ldap), 2 real LDAP users stay active, 0 wrongly created
+16 -3
View File
@@ -1,10 +1,10 @@
---
schema_version: 1
open_count: 3
open_count: 4
waived_count: 0
fixed_count: 0
total_count: 3
last_updated: 2026-08-04T17:50:13.320Z
total_count: 4
last_updated: 2026-08-06T14:21:59.987Z
---
# Broken Windows Ledger
@@ -18,6 +18,7 @@ last_updated: 2026-08-04T17:50:13.320Z
| 1 | 15 | unrun-verify | .planning/phases/15-modul-berechtigungen-gruppen-user-grants/15-06-PLAN.md | | 15-06 <verification>: manueller Browser-Durchklick (Anlegen/Umbenennen/Standardmarkierung/AD-Bindung/Mitglieder/Loeschdialog + Fehlerpfade bei abgeschalteter API) nicht ausgefuehrt — kein Browser-Tool in dieser Session verfuegbar | open | | 2026-08-04T17:05:42.026Z | |
| 2 | 15 | unrun-verify | .planning/phases/15-modul-berechtigungen-gruppen-user-grants/15-07-PLAN.md | | Manueller Browser-Durchklick aus dem Plan-Verification-Block (Matrix-Freigabe setzen/entziehen, Aktivierungsdialog beide Wege, Direkt-Grant neben Gruppen-Grant, Fehlerfall bei gestoppter API, lange Namen) nicht ausgefuehrt -- kein Browser-Tool in dieser Session (15-07-SUMMARY.md D4) | open | | 2026-08-04T17:27:42.267Z | |
| 3 | 15 | unrun-verify | .planning/phases/15-modul-berechtigungen-gruppen-user-grants/15-08-PLAN.md | | Manueller Browser-Durchklick aus dem Plan-Verification-Block (USER ohne Freigabe: Sidebar/403-Seite/Marketplace-Badge+Toast/API-403 identisch; ADMIN: alle vier Ebenen zugaenglich) nicht ausgefuehrt - kein Browser-Tool in dieser Session verfuegbar. | open | | 2026-08-04T17:50:13.320Z | |
| 4 | 16 | unrun-verify | .planning/phases/16-ad-gruppen-synchronisation/16-03-PLAN.md | | 16-03 Task 2 human-check: RESEARCH.md Annahmen A1 (objectGUID uebersteht AD-Umbenennung) und A2 (byteweise Hex-Filter-Syntax) sind gegen kein echtes Active Directory geprueft — kein erreichbares AD in dieser Sandbox. Negatives Ergebnis bei A1 oder A2 ist Stopp-Grund fuer die Loeschsemantik (D-05). | open | | 2026-08-06T14:21:59.987Z | |
````json
[
@@ -56,6 +57,18 @@ last_updated: 2026-08-04T17:50:13.320Z
"reason": "",
"recorded_at": "2026-08-04T17:50:13.320Z",
"resolved_at": null
},
{
"id": 4,
"kind": "unrun-verify",
"phase": "16",
"file": ".planning/phases/16-ad-gruppen-synchronisation/16-03-PLAN.md",
"line": null,
"description": "16-03 Task 2 human-check: RESEARCH.md Annahmen A1 (objectGUID uebersteht AD-Umbenennung) und A2 (byteweise Hex-Filter-Syntax) sind gegen kein echtes Active Directory geprueft — kein erreichbares AD in dieser Sandbox. Negatives Ergebnis bei A1 oder A2 ist Stopp-Grund fuer die Loeschsemantik (D-05).",
"status": "open",
"reason": "",
"recorded_at": "2026-08-06T14:21:59.987Z",
"resolved_at": null
}
]
````
@@ -0,0 +1,225 @@
---
phase: 16-ad-gruppen-synchronisation
plan: 03
subsystem: auth
tags: [nestjs, prisma, ldap, ldapts, groups, vitest, rls]
# Dependency graph
requires:
- phase: 16-ad-gruppen-synchronisation (Plan 16-01)
provides: "Group.ldapObjectGuid (Migration angewendet), LdapService.escapeLdapFilterBuffer() (bislang ungenutzt)"
- phase: 16-ad-gruppen-synchronisation (Plan 16-02)
provides: "GroupsService.reassignDefaultBeforeDelete(tenantId, groupId), GroupsService.ensureDefaultGroup(tenantId), DEFAULT_GROUP_NAME"
provides:
- "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"
affects: [16-04-dialog-umbau, 16-05-anzeige-fallback-frontend]
# Actuals (#2632)
actuals:
tokens: 11091
tasks: 2
commits: 2
# Tech tracking
tech-stack:
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)"
key-files:
created: []
modified:
- apps/api/src/ldap/ldap.service.ts
- apps/api/src/ldap/ldap.module.ts
- apps/api/src/ldap/ldap.service.spec.ts
key-decisions:
- "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."
patterns-established:
- "Reihenfolge-Kopplung zwischen zwei privaten Sync-Schritten wird durch einen beobachtenden Call-Order-Test abgesichert, nicht nur durch Kommentartext an der Aufrufstelle"
requirements-completed: [PERM-02] # Plan-Frontmatter-Vertrag; REQUIREMENTS.md-Checkbox bleibt bewusst [ ] (siehe Decisions) — schliesst erst mit Plan 16-05
coverage:
- id: D1
description: "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)"
requirement: "PERM-02"
verification:
- kind: unit
ref: "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)"
status: pass
- kind: other
ref: "grep -c internalName im Methodenkoerper (awk-Range) = 0"
status: pass
human_judgment: false
- id: D2
description: "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)"
requirement: "PERM-02"
verification:
- kind: unit
ref: "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...'"
status: pass
- kind: other
ref: "awk-Range-Grep: reassignDefaultBeforeDelete-Zeile < group.delete-Zeile"
status: pass
human_judgment: false
- id: D3
description: "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)"
requirement: "PERM-02"
verification:
- kind: unit
ref: "apps/api/src/ldap/ldap.service.spec.ts — Faelle 'a legacy binding ... whose DN still resolves ...' und '... whose DN no longer resolves ...'"
status: pass
human_judgment: false
- id: D4
description: "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)"
requirement: "PERM-02"
verification:
- kind: unit
ref: "apps/api/src/ldap/ldap.service.spec.ts#'an invalid stored ldapObjectGuid (not 32 [0-9a-f] chars) never reaches a filter'"
status: pass
human_judgment: false
- id: D5
description: "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)"
requirement: "PERM-02"
verification:
- kind: unit
ref: "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)"
status: pass
- kind: other
ref: "grep -n Aufrufzeilen: syncBoundGroupsForTenant-Zeile < syncGroupMembershipsForTenant-Zeile"
status: pass
human_judgment: false
- id: D6
description: "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"
requirement: "PERM-02"
verification:
- kind: other
ref: "Kein erreichbares Active Directory in dieser Sandbox (Umgebungsvorgabe) — Live-Pruefung nicht durchgefuehrt"
status: fail
human_judgment: true
rationale: "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."
duration: 12min
completed: 2026-08-06
status: 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
---
*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.