docs(16-01): add plan summary
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
---
|
||||
phase: 16-ad-gruppen-synchronisation
|
||||
plan: 01
|
||||
subsystem: auth
|
||||
tags: [prisma, postgresql, nestjs, ldap, ldapts, nextjs, react, rls]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 15-gruppen-und-modulzuweisung
|
||||
provides: "Group-Modell, forTenant()-RLS-Helper, GroupsService, tenant_isolation_policy auf Group/GroupMembership/ModuleGrant"
|
||||
provides:
|
||||
- "Group.internalName + Group.ldapObjectGuid Spalten samt Unique-Index (tenantId, ldapObjectGuid), versioniert und lokal angewendet (D-04)"
|
||||
- "LdapService.listGroups(config, tenantId) mit alreadyImported-Flag und stabiler Sortierung"
|
||||
- "LdapService.importGroupsByDn(config, tenantId, dns) — selektiver Import mit Namenskollisions- und Duplikat-Behandlung"
|
||||
- "LdapService.escapeLdapFilterBuffer() — binäres LDAP-Filter-Escaping (noch ungenutzt, Vorbereitung für Plan 16-03)"
|
||||
- "POST /ldap/groups/import Route (RBAC ADMIN/SUPER_ADMIN)"
|
||||
- "Sektion 'AD-Gruppen importieren' in /admin/ldap mit sichtbaren Fehlerzuständen"
|
||||
affects: [16-02-anzeige-fallback, 16-03-rekonziliation, 16-04-dialog-umbau, 16-05-anzeige-fallback-frontend]
|
||||
|
||||
# Actuals (#2632)
|
||||
actuals:
|
||||
tokens: 10020
|
||||
tasks: 3
|
||||
commits: 2
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "explicitBufferAttributes: ['objectGUID'] bei jeder LDAP-Suche, die objectGUID liest — ldapts würde sonst UTF-8-dekodieren und den 16-Byte-Wert korrumpieren"
|
||||
- "Reject-with-Report je Zeile statt Batch-Abbruch: P2002 auf (tenantId, name) -> nameCollisions, P2002 auf (tenantId, ldapObjectGuid) -> skipped, alles andere -> errors"
|
||||
- "Baselinen einer lokalen Dev-Datenbank via `prisma migrate resolve --applied <name>` je Altmigration, bevor `prisma migrate deploy` nur die neue Migration anwendet — Ersatz für `migrate dev`, das in dieser Shell keine TTY hat"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- apps/api/prisma/migrations/20260806133916_add_group_internal_name_and_object_guid/migration.sql
|
||||
modified:
|
||||
- apps/api/prisma/schema.prisma
|
||||
- apps/api/src/ldap/ldap.service.ts
|
||||
- apps/api/src/ldap/ldap.controller.ts
|
||||
- apps/api/src/ldap/dto/ldap-config.dto.ts
|
||||
- apps/api/src/ldap/ldap.service.spec.ts
|
||||
- apps/api/src/groups/migration-sql.spec.ts
|
||||
- apps/web/src/app/(portal)/admin/ldap/page.tsx
|
||||
- apps/web/src/messages/de.json
|
||||
- apps/web/src/messages/en.json
|
||||
|
||||
key-decisions:
|
||||
- "Checkpoint 1 (Task 1, checkpoint:decision): approve-both — beide nullable Spalten (internalName, ldapObjectGuid) plus Unique-Index in einer versionierten Migration. Freigegeben 2026-08-06."
|
||||
- "Post-Tracer human-verify-Checkpoint: verified — die Tracer-Scheibe (Diff gelesen, alle automatisierten Verifies grün) wurde geprüft und akzeptiert."
|
||||
- "Drei kosmetische Beobachtungen aus dem Checkpoint-Review bewusst zurückgestellt (siehe Abschnitt unten) — keine davon blockiert die Success Criteria dieses Plans."
|
||||
- "Migrationsverfahren angepasst: `prisma migrate dev` verweigert in dieser nicht-interaktiven Shell den Betrieb (auch mit CI=true, gepipetem stdin oder --create-only). Statt eine Pseudo-TTY zu erzwingen wurde der äquivalente, nicht-interaktive Weg genutzt: `prisma migrate diff` liefert die exakte SQL, die Migration wurde von Hand als Datei angelegt (Stil identisch zu den bestehenden hand-editierten Migrationen des Repos) und per `prisma migrate deploy` angewendet."
|
||||
|
||||
patterns-established:
|
||||
- "Nicht-interaktive lokale Migrationen: migrate diff (SQL erzeugen) -> Datei von Hand anlegen -> migrate deploy (anwenden), statt migrate dev in einer Shell ohne TTY"
|
||||
|
||||
requirements-completed: [PERM-02]
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "Group.internalName und Group.ldapObjectGuid existieren in Schema, Migration und laufender lokaler Datenbank samt Unique-Index (tenantId, ldapObjectGuid) (D-04)"
|
||||
requirement: "PERM-02"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/api/src/groups/migration-sql.spec.ts#add_group_internal_name_and_object_guid migration.sql (D-04)"
|
||||
status: pass
|
||||
- kind: other
|
||||
ref: "docker exec tessera-ctl-db-1 psql -U tessera -d tessera -c '\\d \"Group\"' — zeigt internalName, ldapObjectGuid, Group_tenantId_ldapObjectGuid_key"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "LdapService.listGroups()/importGroupsByDn() — Discovery mit alreadyImported-Flag und stabiler Sortierung, Import mit Duplikat-/Namenskollisions-Behandlung, Buffer-basiertem objectGUID-Handling (SC-1, SC-2, D-01, D-02)"
|
||||
requirement: "PERM-02"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/api/src/ldap/ldap.service.spec.ts#LdapService — AD group import (SC-1/SC-2, D-01/D-02) (8 it-Fälle)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: "POST /ldap/groups/import Route hinter @Roles(ADMIN, SUPER_ADMIN), tenantId ausschließlich aus req.tenantId, ImportGroupsDto lehnt leeres dns-Array ab"
|
||||
requirement: "PERM-02"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/api/src/ldap/ldap.service.spec.ts (Controller-Verhalten indirekt über Service-Vertrag) + acceptance-criteria grep auf ldap.controller.ts"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D4
|
||||
description: "Sektion 'AD-Gruppen importieren' in /admin/ldap — Auswahl, Import-Button mit Auswahlzahl, alreadyImported-Badge, sichtbare Fehlerzustände statt stillem Verschlucken"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "cd apps/web && npx tsc --noEmit (typkorrekt) + acceptance-criteria grep auf handleDiscoverGroupsToImport/handleImportGroups (setGroupImportError >= 4x)"
|
||||
status: pass
|
||||
human_judgment: true
|
||||
rationale: "Visuelle Interaktion (Checkbox-Zustände, Badge-Styling, Fehlerbanner) wurde im Rahmen des Post-Tracer-Checkpoints per Code-Review als 'verified' bestätigt, aber nicht gegen einen live gerenderten Browser erneut geprüft — echte UAT bleibt sinnvoll, bevor Plan 16-02 darauf aufbaut."
|
||||
|
||||
duration: 34min
|
||||
completed: 2026-08-06
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase 16 Plan 1: AD-Gruppen-Import (Tracer + Schema) Summary
|
||||
|
||||
**Selektiver AD-Gruppen-Import über alle Schichten (Prisma bis Next.js), rename-stabile Identität via objectGUID, Migration lokal angewendet und per SQL-Test festgenagelt**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 34 min (Tracer-Commit 15:09 Uhr bis Migrations-Commit 15:43 Uhr; Checkpoint-Wartezeiten nicht mitgerechnet)
|
||||
- **Started:** 2026-08-06T13:09:35Z (Tracer-Commit)
|
||||
- **Completed:** 2026-08-06T13:43:19Z (Migrations-Commit)
|
||||
- **Tasks:** 3 (Checkpoint:decision, Tracer, Migration)
|
||||
- **Files modified:** 10
|
||||
|
||||
## Accomplishments
|
||||
- `Group.internalName` (sync-immuner Anzeigename) und `Group.ldapObjectGuid` (rename-stabiler Identitätsschlüssel) als versionierte, one-way-Migration angelegt, lokal angewendet und per SQL-Textest festgenagelt
|
||||
- `LdapService.listGroups()` erweitert um `alreadyImported`-Kennzeichnung und stabile Sortierung; `LdapService.importGroupsByDn()` neu, mit Duplikat-Erkennung (skip), Namenskollisions-Meldung (reject-with-report) und Fehlerzeilen statt Batch-Abbruch
|
||||
- `objectGUID` wird korrekt als `Buffer` gelesen (`explicitBufferAttributes`) und als 32-stelliger Hex-String gespeichert — der zuvor als offene Annahme geführte Punkt (RESEARCH.md A2) ist für den Schreibpfad bewiesen
|
||||
- `POST /ldap/groups/import` (RBAC ADMIN/SUPER_ADMIN) und neue Sektion "AD-Gruppen importieren" in `/admin/ldap` mit sichtbaren Discovery-/Import-Fehlerzuständen (bewusste Abweichung vom sonst stillen Fehler-Verschlucken der Seite)
|
||||
- Vollständige API-Testsuite grün: 40 Testdateien, 526 Tests
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Freigabe der one-way Schema-Erweiterung (D-04)** — `checkpoint:decision`, kein eigener Commit; Freigabe `approve-both`
|
||||
2. **Task 2: Tracer — AD-Gruppe auswählen und importieren, durch alle Schichten** - `3523e43` (feat, tdd)
|
||||
3. **Task 3: Migration erzeugen, gegen die lokale Datenbank anwenden, per SQL-Test festnageln** - `626e296` (feat)
|
||||
|
||||
**Plan metadata:** wird nach diesem Summary committet
|
||||
|
||||
## Files Created/Modified
|
||||
- `apps/api/prisma/schema.prisma` - `Group.internalName`, `Group.ldapObjectGuid`, `@@unique([tenantId, ldapObjectGuid])`
|
||||
- `apps/api/prisma/migrations/20260806133916_add_group_internal_name_and_object_guid/migration.sql` - neue, versionierte Migration (zwei `ADD COLUMN`, ein `CREATE UNIQUE INDEX`, keine RLS-Anweisung)
|
||||
- `apps/api/src/ldap/ldap.service.ts` - `listGroups(config, tenantId)` mit `alreadyImported`, `importGroupsByDn()`, `escapeLdapFilterBuffer()`, `LdapGroupImportResult`
|
||||
- `apps/api/src/ldap/ldap.controller.ts` - `GET groups` reicht `tenantId` durch, neue Route `POST groups/import`
|
||||
- `apps/api/src/ldap/dto/ldap-config.dto.ts` - `ImportGroupsDto`
|
||||
- `apps/api/src/ldap/ldap.service.spec.ts` - neuer describe-Block "LdapService — AD group import (SC-1/SC-2, D-01/D-02)" (8 Fälle + Sortierung + alreadyImported)
|
||||
- `apps/api/src/groups/migration-sql.spec.ts` - neuer describe-Block für die neue Migration (4 Fälle)
|
||||
- `apps/web/src/app/(portal)/admin/ldap/page.tsx` - Sektion "AD-Gruppen importieren", sieben neue State-Variablen, zwei Handler mit sichtbarem Fehlerzustand
|
||||
- `apps/web/src/messages/de.json` / `en.json` - `admin.ldap.groupImport.*`
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- **Checkpoint 1 (Task 1):** `approve-both` — beide Spalten plus Unique-Index in einer Migration. Freigegeben 2026-08-06 vom Projektinhaber.
|
||||
- **Post-Tracer-Checkpoint:** `verified` — Tracer-Scheibe per Code-Review akzeptiert, bevor Task 3 (Migration) begonnen wurde.
|
||||
- **Drei zurückgestellte kosmetische Beobachtungen aus dem Review** (bewusst nicht umgesetzt in diesem Plan):
|
||||
1. `listGroups()` und `existingByGuid` in `importGroupsByDn()` nutzen `this.prisma` statt `tenantPrisma` — beide Lesepfade filtern aber explizit auf `tenantId`, kein Cross-Tenant-Leck. Belassen wie ist.
|
||||
2. `escapeLdapFilterBuffer()` ist aktuell ungenutzt — Plan 16-03 ist der erste Aufrufer. Belassen im Code.
|
||||
3. Legacy-Gruppen mit `ldapDn`, aber ohne `ldapObjectGuid`, melden `alreadyImported: false`. Wird durch das GUID-Backfill in Plan 16-03 aufgelöst. Belassen wie ist.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 3 - Blocking] `prisma migrate dev` verweigert nicht-interaktive Shell — Migrationsablauf über `migrate diff` + Handdatei + `migrate deploy` ersetzt**
|
||||
- **Found during:** Task 3
|
||||
- **Issue:** Der geplante Befehl `prisma migrate dev --name add_group_internal_name_and_object_guid` bricht in dieser Shell sofort mit "Prisma Migrate has detected that the environment is non-interactive" ab — auch mit `CI=true`, gepipetem `y`-stdin und `--create-only`. Ein Versuch, über `script` eine Pseudo-TTY zu erzwingen, hing unbegrenzt an einem echten interaktiven Prompt und musste per SIGKILL beendet werden.
|
||||
- **Fix:** Äquivalenter, vollständig nicht-interaktiver Weg: `prisma migrate diff --from-migrations ... --to-schema-datamodel ... --script` erzeugt exakt dieselbe SQL, die dann von Hand in ein neues Migrationsverzeichnis (`20260806133916_add_group_internal_name_and_object_guid/migration.sql`) geschrieben wurde — im Stil der bereits im Repo vorhandenen hand-editierten Migrationen. `prisma migrate deploy` (nicht-interaktiv per Design) hat sie anschließend angewendet.
|
||||
- **Files modified:** apps/api/prisma/migrations/20260806133916_add_group_internal_name_and_object_guid/migration.sql
|
||||
- **Verification:** `prisma migrate status` zeigt "Database schema is up to date!"; `\d "Group"` zeigt beide Spalten und den Unique-Index
|
||||
- **Committed in:** 626e296
|
||||
|
||||
**2. [Rule 3 - Blocking] `_prisma_migrations`-Tabelle fehlte in der lokalen Datenbank — Baseline nachgezogen**
|
||||
- **Found during:** Task 3
|
||||
- **Issue:** Nach dem SIGKILL des hängenden `script`-Prozesses (siehe Punkt 1) meldete `prisma migrate status`, dass ALLE 25 Migrationen unangewendet seien, und `\dt` zeigte, dass die Tabelle `_prisma_migrations` in der Datenbank gar nicht existierte — obwohl alle 28 Anwendungstabellen inklusive der bereits angewendeten Group-RLS-Policy vorhanden und `Group` mit 0 Zeilen leer war. Kein Datenverlust, aber die Migrationshistorie war nicht (mehr) nachverfolgbar.
|
||||
- **Fix:** Vor dem Anwenden der neuen Migration wurden die 24 bereits im Schema sichtbaren Altmigrationen einzeln per `prisma migrate resolve --applied <name>` als bereits angewendet markiert (reine Metadaten-Operation, keine DDL). Erst danach lieferte `migrate status` korrekt genau eine ausstehende Migration (die neue), die per `migrate deploy` angewendet wurde.
|
||||
- **Files modified:** keine Codedateien — nur Datenbank-Metadaten (`_prisma_migrations`-Tabelle) auf dem lokalen Dev-Container
|
||||
- **Verification:** `SELECT migration_name, finished_at FROM "_prisma_migrations"` zeigt alle 25 Migrationen mit `finished_at` gesetzt; volle Testsuite (526 Tests) grün
|
||||
- **Committed in:** 626e296 (kein DB-Zustand wird versioniert; die Baseline-Aktion selbst ist nicht committbar)
|
||||
|
||||
**3. [Rule 1 - Bug] Eigene Migrations-Kommentarzeile enthielt versehentlich das Suchmuster "row level security"**
|
||||
- **Found during:** Task 3
|
||||
- **Issue:** Der erklärende Kommentar in der neuen `migration.sql` begründete den fehlenden RLS-Schritt mit dem wörtlichen Ausdruck "FORCE ROW LEVEL SECURITY" — dadurch schlug sowohl das Acceptance-Criterion (`grep -ci 'row level security'` soll 0 ergeben) als auch der eigene neue Spec-Test fehl.
|
||||
- **Fix:** Kommentar umformuliert, ohne die Begründung zu verlieren ("die entsprechende Absicherung" statt der wörtlichen SQL-Phrase); Spec-Test auf ein Regex-Muster für tatsächliche `ALTER TABLE ... ENABLE/FORCE ROW LEVEL SECURITY`-Anweisungen umgestellt statt auf reinen Substring-Match.
|
||||
- **Files modified:** apps/api/prisma/migrations/20260806133916_add_group_internal_name_and_object_guid/migration.sql, apps/api/src/groups/migration-sql.spec.ts
|
||||
- **Verification:** `grep -ci 'row level security' migration.sql` = 0; `npx vitest run src/groups/migration-sql.spec.ts` grün (14 Tests)
|
||||
- **Committed in:** 626e296
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 3 auto-fixed (2 blocking / Migrationsverfahren, 1 Bug in eigenem Kommentartext)
|
||||
**Impact on plan:** Alle drei Anpassungen waren notwendig, um Task 3 überhaupt abzuschließen bzw. um die eigenen Acceptance-Kriterien zu erfüllen. Kein Scope Creep — die Migration selbst entspricht exakt dem im Plan vorgesehenen Inhalt (zwei nullable Spalten, ein Unique-Index, keine RLS-Anweisung).
|
||||
|
||||
## Issues Encountered
|
||||
- Siehe Deviations oben — alle drei Punkte betrafen ausschließlich das Migrationsverfahren in dieser nicht-interaktiven Shell, nicht die fachliche Logik des Plans.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - keine externe Service-Konfiguration erforderlich. Der Testserver wurde von diesem Plan nicht angefasst; `prisma migrate deploy` läuft dort ohnehin automatisch beim Container-Start (`apps/api/Dockerfile:38`).
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- Beide Identitätsspalten (`internalName`, `ldapObjectGuid`) existieren jetzt in Schema, Migration und laufender lokaler Datenbank — Plan 16-03 (Rekonziliation/Rename-Erkennung) kann darauf aufsetzen
|
||||
- `escapeLdapFilterBuffer()` liegt bereit, aber ungenutzt, für den binären Existenz-Sweep in Plan 16-03
|
||||
- Die drei zurückgestellten kosmetischen Beobachtungen (siehe Decisions) sind dokumentiert und sollten bei der Planung von 16-02/16-03 im Blick behalten werden
|
||||
- **Flagged assumption weiterhin offen:** RESEARCH.md A1 (objectGUID bleibt über AD-Umbenennung stabil) und A2 (Binärfilter-Syntax) sind gegen kein echtes Verzeichnis geprüft — dieser Plan schreibt die GUID nur, liest sie nicht filternd zurück. Live-Prüfung ist Bestandteil von Plan 16-03 gegen ViCoTest.
|
||||
|
||||
---
|
||||
*Phase: 16-ad-gruppen-synchronisation*
|
||||
*Completed: 2026-08-06*
|
||||
Reference in New Issue
Block a user