ADR-0051: Sysadmin legt Organisationen an; Control-DB nur Metadaten
- Status: accepted
- Datum: 2026-09-02
- Phase: Multi-Tenant
- Bezug: ADR-0031, ADR-0032
- Ändert: ADR-0032 (Selbstanlage, impliziter Plattform-Operator)
Kontext
ADR-0032 band den Konto-Lebenszyklus an Selbstanlage: ein User ohne tenant_id legt per POST /tenants eine Organisation an und wird Org-Admin. Der Plattform-Operator war nur role=admin plus tenant_id null. Lokal setzte LINETRA_SHARED_DB=true tenants.dbname auf die Control-DB linetra, sodass GIS, Projekte und föderierte Entities neben app_users landeten.
Gewollt ist: ein Bootstrap-Sysadmin (Env) legt Organisationen an und bestimmt Org-Admins; Org-Admins verwalten nur die eigene Orga; die Control-DB hält nur Web-Metadaten.
Entscheidung
app_users.is_system_administ das Plattform-Flag. Org-Rolle bleibtrole(admin/editor/readerplus Fachrollen).- Bootstrap: leere
app_users→ ein Sysadmin ausLINETRA_BOOTSTRAP_ADMIN_*(ohnetenant_id). Keine automatische Organisation und kein Org-Admin aus der Env. POST /tenantsnur Sysadmin. Body enthält den ersten Org-Admin (Username, Passwort, Vorname, Nachname, E-Mail). Keine Selbstanlage.- Weitere Sysadmins legt nur ein Sysadmin an (
POST /usersmitis_system_admin). Der letzte aktive Sysadmin bleibt unantastbar. - Mitgliedschaft bleibt 1:1 (
app_users.tenant_id). Sysadmins dürfentenant_idnull haben und per Session /X-Tenant-Ideine Orga wählen. - Control-DB
linetra:tenants,app_users,editor_access_tokens,client_cert_grants. Fachdaten (Projekte, GIS, Schema-CMS, föderierte Records, Akteure) nur inlinetra_<slug>. - Kein stilles Shared-Fallback außerhalb von Tests:
tenant.dbname == POSTGRES_DBist ein Fehler, wennLINETRA_SHARED_DBaus ist. Pytest behält Shared-DB.
Begründung (für dieses Setup)
Zwei Ebenen (Plattform / Organisation) reichen: Isolationseinheit bleibt die Orga-DB (ADR-0030/0031). Ein explizites Sysadmin-Flag verhindert, dass jeder Org-Admin Plattformrechte erbt. Personfelder am Org-Admin gehören in persons der Tenant-DB, nicht auf app_users. Die bestehende Tenant-Session (get_db_session) schreibt Fachdaten bereits gegen tenants.dbname — die Lücke war Shared-DB, nicht die Route.
Verworfene Alternativen
| Alternative | Warum verworfen |
|---|---|
| Selbstanlage (ADR-0032 Punkt 2) | Orgs entstehen durch Sysadmins, nicht durch den ersten Login |
role=system_admin statt Flag |
Flag + Role-Code bleiben getrennt; Bootstrap legt nur Sysadmin an |
| M:N User ↔ Org | Ein Mitarbeiter gehört einer Orga |
| Drei Ebenen Tenant → Orgs | Organisation = Tenant = eine DB |
| Casbin / Oso | Vier klare Stufen, Principal existiert schon |
| RLS / Schema-pro-Tenant | Isolation war ADR-0030/0031 |
Namensfelder auf app_users |
Vorname/E-Mail sind Akteur (persons) |
| Control-Alembic-Geschichte umschreiben | Fach-DDL bleibt historisch; neue Fach-DDL nur unter alembic/tenant/ |
Folgen
- ADR-0032 Punkte 2 und 7 gelten nicht mehr. Föderierter Stamm und GIS-
project_idbleiben. - Principal:
can_manage_tenants/can_view_platform_settingsnur Sysadmin;can_manage_usersfür Org-Admin oder Sysadmin;can_create_projectfür Org-Admin oder Sysadmin im Org-Kontext. - Settings → System (Sysadmin): weitere Sysadmins, Org-Katalog. Settings → Organisation: kein Selbst-Create.
- QGIS
/auth/me.qgis_connection.databaseistlinetra_<slug>, nichtlinetra. - Optionaler Ops-Schritt nach dem Cutover:
scripts/strip_control_fachdaten.sh(Keep-Liste inkl.client_cert_grants). - Teardown (Sysadmin):
GET /tenants/{id}/backupliefert optional ein ZIP (pg_dumpder Tenant-DB + Control-Metadaten + Projektdateien) — nie ein Dump der Control-DB.DELETE /tenants/{id}mitconfirm_sluglöscht Control-Zeilen und droppt nurlinetra_<slug>(+{slug}_edit/_read).LINETRA_SHARED_DB=true(nur Pytest) blockiert Löschen. Legacy-Zeilen mitdbname=POSTGRES_DBlöschen nur den Katalog, nicht die Control-DB. Restore bleibt manuell aus dem ZIP. - Shared-DB: nur unter Pytest (
conftest). Compose-Dev und Produktion provisionieren echtelinetra_<slug>-DBs;LINETRA_SHARED_DBaußerhalb von Pytest führt zum Fail-fast beim Start.