Files

17 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 01 auth
prisma
postgresql
nestjs
ldap
ldapts
nextjs
react
rls
phase provides
15-gruppen-und-modulzuweisung Group-Modell, forTenant()-RLS-Helper, GroupsService, tenant_isolation_policy auf Group/GroupMembership/ModuleGrant
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
16-02-anzeige-fallback
16-03-rekonziliation
16-04-dialog-umbau
16-05-anzeige-fallback-frontend
tokens tasks commits
10020 3 2
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
created modified
apps/api/prisma/migrations/20260806133916_add_group_internal_name_and_object_guid/migration.sql
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
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.
Nicht-interaktive lokale Migrationen: migrate diff (SQL erzeugen) -> Datei von Hand anlegen -> migrate deploy (anwenden), statt migrate dev in einer Shell ohne TTY
PERM-02
id description requirement verification human_judgment
D1 Group.internalName und Group.ldapObjectGuid existieren in Schema, Migration und laufender lokaler Datenbank samt Unique-Index (tenantId, ldapObjectGuid) (D-04) PERM-02
kind ref status
unit apps/api/src/groups/migration-sql.spec.ts#add_group_internal_name_and_object_guid migration.sql (D-04) pass
kind ref status
other docker exec tessera-ctl-db-1 psql -U tessera -d tessera -c '\d "Group"' — zeigt internalName, ldapObjectGuid, Group_tenantId_ldapObjectGuid_key pass
false
id description requirement verification human_judgment
D2 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) PERM-02
kind ref status
unit apps/api/src/ldap/ldap.service.spec.ts#LdapService — AD group import (SC-1/SC-2, D-01/D-02) (8 it-Fälle) pass
false
id description requirement verification human_judgment
D3 POST /ldap/groups/import Route hinter @Roles(ADMIN, SUPER_ADMIN), tenantId ausschließlich aus req.tenantId, ImportGroupsDto lehnt leeres dns-Array ab PERM-02
kind ref status
unit apps/api/src/ldap/ldap.service.spec.ts (Controller-Verhalten indirekt über Service-Vertrag) + acceptance-criteria grep auf ldap.controller.ts pass
false
id description verification human_judgment rationale
D4 Sektion 'AD-Gruppen importieren' in /admin/ldap — Auswahl, Import-Button mit Auswahlzahl, alreadyImported-Badge, sichtbare Fehlerzustände statt stillem Verschlucken
kind ref status
unit cd apps/web && npx tsc --noEmit (typkorrekt) + acceptance-criteria grep auf handleDiscoverGroupsToImport/handleImportGroups (setGroupImportError >= 4x) pass
true 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.
34min 2026-08-06 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

4. [Rule 1 - Bug] requirements mark-complete PERM-02 hätte den Requirement-Status verfälscht — Checkbox zurückgesetzt

  • Found during: State-Update-Schritt nach Task 3
  • Issue: Die Plan-Frontmatter listet requirements: [PERM-02], und der Standard-State-Update-Schritt hakt daraufhin PERM-02 in REQUIREMENTS.md als erledigt ab. PERM-02 beschreibt aber die vollständige AD-Sync-Kette (Umbenennung folgt nach, Verschwinden löscht kaskadierend, memberOf pflegt Mitgliedschaften) — dieser Plan liefert nur den selektiven Import (den Anfang der Kette). Die verbleibenden vier Pläne der Phase 16 liefern Rekonziliation, Anzeige-Fallback und Dialog-Umbau erst noch. Ein Abhaken jetzt hätte Requirements-Tracking und spätere Audit-/Ship-Gates fälschlich glauben lassen, PERM-02 sei vollständig erfüllt.
  • Fix: Checkbox in REQUIREMENTS.md Zeile 67 manuell auf [ ] zurückgesetzt, nachdem der Standard-Befehl sie automatisch gesetzt hatte. requirements-completed im Frontmatter dieses Summarys bleibt trotzdem [PERM-02] (Plan-Frontmatter-Vertrag), aber die eigentliche Traceability-Tabelle bleibt korrekt „Pending", bis der letzte Plan der Phase liefert.
  • Files modified: .planning/REQUIREMENTS.md
  • Verification: grep -n 'PERM-02' .planning/REQUIREMENTS.md zeigt weiterhin [ ] und Traceability-Zeile „Pending"
  • Committed in: wird mit dem abschließenden Metadaten-Commit dieses Plans committet

Total deviations: 4 auto-fixed (2 blocking / Migrationsverfahren, 1 Bug in eigenem Kommentartext, 1 Bug in Requirements-Tracking) 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