--- phase: quick-260805-fok plan: 01 subsystem: api tags: [nestjs, prisma, groups, multi-tenancy, onapplicationbootstrap] requires: - phase: 15-modul-berechtigungen-gruppen-user-grants provides: "Group/GroupMembership/ModuleGrant-Schema, partieller Unique-Index Group_one_default_per_tenant, GroupsService.addUserToDefaultGroup" provides: - "GroupsService.ensureDefaultGroup(tenantId) — race-sichere, idempotente Standardgruppen-Anlage mit Null-Gruppen-Wächter (D-13)" - "TenantService.create verdrahtet mit ensureDefaultGroup" - "AdminSeedService.ensureDefaultGroupsForAllTenants() — Startup-Reparatur über alle Mandanten" affects: [groups, tenant, user, admin-seed] tech-stack: added: [] patterns: - "Sequenzielle await-Kette in onApplicationBootstrap statt Verlass auf Nest-Hook-Reihenfolge zwischen Modulen (Muster aus tender-scheduler.service.ts übernommen)" - "Interaktive Prisma-Transaktion ($transaction mit async-Callback) statt Array-Form, wenn spätere Schritte die id eines vorherigen Schritts brauchen" key-files: created: - apps/api/src/tenant/tenant.service.spec.ts - apps/api/src/user/admin-seed.service.spec.ts modified: - apps/api/src/groups/groups.service.ts - apps/api/src/groups/groups.service.spec.ts - apps/api/src/tenant/tenant.service.ts - apps/api/src/tenant/tenant.module.ts - apps/api/src/user/admin-seed.service.ts key-decisions: - "Wächter prüft ausschließlich group.count === 0, nie das Fehlen der isDefault-Markierung — ein Mandant mit Gruppen ohne Markierung ist eine bewusste Admin-Entscheidung (D-13) und wird nie überschrieben" - "Reparatur läuft als zweiter sequenzieller await-Schritt in AdminSeedService.onApplicationBootstrap, NICHT als eigener Hook in GroupsModule — GroupsModule ist Dependency von UserModule, seine Hooks liefen sonst vor der Tenant-Anlage" - "P2002 aus dem partiellen Index Group_one_default_per_tenant wird in ensureDefaultGroup abgefangen und liefert null, propagiert nicht — der Index bleibt der eigentliche Durchsetzungspunkt" requirements-completed: [260805-fok] coverage: - id: D1 description: "GroupsService.ensureDefaultGroup(tenantId) legt für einen Mandanten ohne jede Gruppe die Standardgruppe 'Alle Benutzer' an, mit Mitgliedern und Modul-Grants, race-sicher und idempotent" requirement: "260805-fok" verification: - kind: unit ref: "apps/api/src/groups/groups.service.spec.ts#ensureDefaultGroup()" status: pass human_judgment: false - id: D2 description: "TenantService.create ruft ensureDefaultGroup auf; Fehler werden protokolliert, nicht propagiert" requirement: "260805-fok" verification: - kind: unit ref: "apps/api/src/tenant/tenant.service.spec.ts" status: pass human_judgment: false - id: D3 description: "AdminSeedService führt die Standardgruppen-Anlage vor dem Super-Admin-Anlegen aus und repariert am Ende von onApplicationBootstrap alle Mandanten ohne Gruppe, unabhängig von den frühen Rückkehrpfaden" requirement: "260805-fok" verification: - kind: unit ref: "apps/api/src/user/admin-seed.service.spec.ts" status: pass human_judgment: false - id: D4 description: "Kein Schemawechsel, keine neue Migration — 24 Migrationsverzeichnisse unverändert, schema.prisma unverändert" verification: - kind: other ref: "test \"$(ls -1d apps/api/prisma/migrations/*/ | wc -l)\" -eq 24 && git diff --quiet -- apps/api/prisma/schema.prisma" status: pass human_judgment: false duration: 6min completed: 2026-08-05 status: complete --- # Quick Task 260805-fok: Standardgruppe bei Mandanten-Anlage und Startup-Reparatur Summary **GroupsService.ensureDefaultGroup(tenantId) verlegt die "jeder Mandant hat eine Standardgruppe"-Invariante von der einmaligen Migration in beide Mandanten-Entstehungspfade (TenantService.create, AdminSeedService) plus eine Startup-Reparatur über alle Bestandsmandanten.** ## Performance - **Duration:** 6 min - **Started:** 2026-08-05T11:25:00+02:00 (circa) - **Completed:** 2026-08-05T11:30:49+02:00 - **Tasks:** 2 - **Files modified:** 7 (2 neu, 5 geändert) ## Accomplishments - `GroupsService.ensureDefaultGroup(tenantId)`: legt für einen Mandanten ohne jede Gruppe "Alle Benutzer" (isDefault:true) an, nimmt alle Bestandsbenutzer als MANUAL-Mitglieder auf und erzeugt Grants für alle aktiven Module — derselbe Endzustand wie die drei Backfill-INSERTs der Migration `20260804130130_add_groups_and_module_grants`. Wächter prüft ausschließlich `group.count === 0` (D-13), fängt P2002 aus dem partiellen Index `Group_one_default_per_tenant` ab und liefert `null` statt zu werfen. - `TenantService.create` ruft `ensureDefaultGroup` nach der Mandanten-Anlage auf; ein Fehler wird protokolliert, die Mandanten-Anlage schlägt trotzdem nicht fehl. - `AdminSeedService.onApplicationBootstrap` läuft jetzt als zwei sequenzielle await-Schritte: `seedAdmin()` (bisheriger Rumpf, jetzt mit `ensureDefaultGroup` nach dem Tenant-Upsert und VOR `user.create`) und danach `ensureDefaultGroupsForAllTenants()` — die einmalige Reparatur über jeden Mandanten aus `prisma.tenant.findMany`, unabhängig von `seedAdmin()`s beiden frühen Rückkehrpfaden (fehlende ENV-Variablen, Admin existiert bereits — genau der Fall auf dem Testserver). ## Task Commits Each task was committed atomically: 1. **Task 1: GroupsService.ensureDefaultGroup(tenantId) + Spec** - `9d1254c` (feat) 2. **Task 2: Beide Mandanten-Entstehungspfade verdrahten + Startup-Reparatur + Specs** - `0d7d8a5` (feat) **Plan metadata:** wird vom Orchestrator committet (SUMMARY.md, STATE.md) ## Files Created/Modified - `apps/api/src/groups/groups.service.ts` - neue Methode `ensureDefaultGroup(tenantId)` vor `addUserToDefaultGroup` - `apps/api/src/groups/groups.service.spec.ts` - Fake erweitert (`group.count`, `tenantModuleActivation`, `moduleGrant.findMany/createMany`, `$transaction`-Callback-Form) plus voller `ensureDefaultGroup`-Testblock - `apps/api/src/tenant/tenant.service.ts` - `GroupsService` injiziert, `create()` ruft `ensureDefaultGroup` in try/catch mit Logger auf - `apps/api/src/tenant/tenant.module.ts` - `GroupsModule` importiert - `apps/api/src/tenant/tenant.service.spec.ts` (neu) - beweist Aufruf und nicht-fatalen Fehlerpfad - `apps/api/src/user/admin-seed.service.ts` - `seedAdmin()` extrahiert, `ensureDefaultGroup` nach dem Tenant-Upsert eingefügt, `ensureDefaultGroupsForAllTenants()` als abschließende Reparatur - `apps/api/src/user/admin-seed.service.spec.ts` (neu) - Reihenfolge, Reparatur über alle Mandanten, Idempotenz über zwei Bootstrap-Läufe, Fehlerisolation je Mandant ## Decisions Made - Wächter prüft ausschließlich `group.count === 0`, nie das Fehlen der `isDefault`-Markierung — D-13 erlaubt dem Admin ausdrücklich, die Markierung abzuhängen oder umzuhängen; die Reparatur darf diese Entscheidung nie überschreiben. - Die Reparatur läuft als zweiter sequenzieller `await`-Schritt in `AdminSeedService.onApplicationBootstrap`, nicht als eigener Hook in `GroupsModule`: `GroupsModule` ist Dependency von `UserModule`, seine `onApplicationBootstrap`-Hooks liefen deshalb vor dem Admin-Seed und würden auf einer frischen Installation den noch nicht existierenden Default-Mandanten übergehen — dieselbe Falle, die `tender-scheduler.service.ts` für den DÖE-Poll-Config-Seed dokumentiert. - `ensureDefaultGroup` nutzt eine interaktive Transaktion (`$transaction` mit async-Callback) statt der Array-Form, weil Schritt 2 (Mitgliedschaften) und Schritt 3 (Grants) die `id` der in Schritt 1 erzeugten Gruppe brauchen. - P2002 aus `group.create` wird ausschließlich anhand `err.code === 'P2002'` abgefangen und in `null` übersetzt; jeder andere Fehler wird weitergeworfen. Der partielle Unique-Index `Group_one_default_per_tenant` (15-01) bleibt der eigentliche Durchsetzungspunkt. ## Deviations from Plan None — Plan exakt wie geschrieben umgesetzt. ## Issues Encountered Keine funktionalen Probleme. Beim ersten Testlauf für `admin-seed.service.spec.ts` waren zwei Testerwartungen selbst fehlerhaft formuliert (Reihenfolge-Filter schloss `user.findUnique` nicht aus; Aufrufzähler für den Neustart-Test berücksichtigte nicht, dass `seedAdmin()` beim ersten Lauf zusätzlich zur Reparatur einen eigenen `ensureDefaultGroup`-Aufruf auslöst) — beide Assertions korrigiert, die Implementierung war in beiden Fällen bereits korrekt. ## User Setup Required None - keine externe Konfiguration nötig. **Kein Deploy auf den Testserver aus diesem Plan heraus** — das übernimmt der Benutzer laut Vorgabe. Erwarteter Effekt dort nach dem nächsten Container-Start (unbeaufsichtigter `prisma migrate deploy` + API-Start): der Mandant „Default" bekommt die Gruppe „Alle Benutzer" mit `isDefault:true`, die vier vorhandenen Benutzer werden Mitglieder, und für jedes aktive Modul entsteht ein Gruppen-Grant. ## Next Phase Readiness - Lokaler Stack geprüft: der einzige lokale Mandant hat bereits genau eine Gruppe (`docker exec tessera-ctl-db-1 psql` bestätigt `groups=1` für den Tenant `default`) — die Startup-Reparatur lässt ihn beim nächsten API-Neustart unangetastet, genau wie im behavior-Block gefordert. - Live-Testserver (192.168.13.12, `tenants=1 users=4 groups=0`) profitiert vom nächsten Container-Neustart automatisch — kein manueller Eingriff nötig, keine neue Migration. - Keine offenen Punkte. --- *Phase: quick-260805-fok* *Completed: 2026-08-05* ## Self-Check: PASSED Alle 7 in `files_modified` gelisteten Dateien vorhanden, beide Task-Commits (`9d1254c`, `0d7d8a5`) im Git-Log gefunden.