Files
schalli ff70b4efba
Tessera CI/CD / Lint & Type Check (push) Successful in 48s
Tessera CI/CD / Tests (push) Successful in 49s
Tessera CI/CD / Build & Publish Images (push) Successful in 28s
docs(quick-260805-fok): Standardgruppe bei Mandanten-Anlage + Startup-Reparatur
2026-08-05 11:37:43 +02:00

146 lines
9.5 KiB
Markdown

---
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.