ADR-0031: Control-Plane und Mandanten-Datenbanken
Kontext
ADR-0030 verlangt eine Postgres-Datenbank pro Linetra-Mandant. Der MVP nutzte eine gemeinsame DB linetra und die Cluster-Rollen linetra_edit / linetra_read. Postgres-Rollen sind clusterweit: dasselbe Cert-CN auf allen Mandanten-DBs würde bedeuten, dass ein Client den Datenbanknamen wechseln und fremde Fachdaten öffnen kann.
organizations sind Stammdaten (Firma, Gemeinde) in einer Mandanten-DB, nicht die Isolationseinheit. Die Web-App braucht eine globale Login- und Katalog-DB.
Entscheidung
- Control-Plane bleibt die bestehende Datenbank
linetra:tenants,app_users(mittenant_id), Sessions/Cert-Metadaten, Editor-Bearer-Tokens. FastAPI-Login spricht nur diese DB. - Mandant ist eine Control-Zeile
tenants(nichtorganizations). Fachdaten liegen inlinetra_<slug>. - DB-Rollen pro Mandant: Cluster-Logins
{slug}_editund{slug}_read.CONNECTnur auflinetra_<slug>. Cert-CN = Rollenname. Control-Rollenlinetra_edit/linetra_readbehaltenCONNECTnur auflinetra. - PgBouncer:
[databases]Wildcard* = host=postgres port=5432plus expliziteslinetra. Auth überauth_user+auth_query(Funktionpgbouncer.get_authliefert HMAC-Klartext, nichtpg_authid.rolpassword), nicht über eine wachsendeuserlist.txt. Nach außen bleibtauth_type=cert. - Alembic: Control-Revisionen weiter unter
alembic/versions/gegenlinetra. Mandanten-Revisionen unteralembic/tenant/gegen jedelinetra_<slug>. Neue Mandanten:CREATE DATABASEvon Template oder Kopie, dann Tenant-Stamp; Fach-Upgrades perscripts/alembic_upgrade_all.sh. - FastAPI: Control-Engines für Auth; Tenant-Engine-Cache keyed by
tenant_id(Pool erst beim ersten Request). Session-Cookie trägttenant_id. Celery-Tasks bekommentenant_idals primitiven Arg. - QGIS: nach API-Login liefert
/auth/me(und Token-user) Host, Port,dbname, CN. Das Plugin schreibt das Profil; keine Fallbackslinetra/linetra_edit. - Person-Bindung:
app_users.person_idist eine schwache UUID (kein FK nachpersons). Personen leben in der Mandanten-DB.
Lokal darf LINETRA_SHARED_DB=true denselben Cluster-DSN für Control und einen logischen Dev-Mandanten nutzen (pytest, frische Compose ohne Superuser-Provisioning). Produktion provisioniert echte DBs.
Begründung (für dieses Setup)
Database-per-tenant war in ADR-0030 gewählt. Ein Rollenpaar pro Mandant schließt dbname-Guessing. Control-Plane in der bestehenden linetra-DB vermeidet eine dritte Datenbank nur für Login. PgBouncer und mTLS bleiben (ADR-0002); nur das Mapping ändert sich. HMAC-Passwörter aus LINETRA_TENANT_SECRET speichern keine Rollenpasswörter in tenants.
Verworfene Alternativen
| Alternative | Warum verworfen |
|---|---|
organizations als Mandant |
Stammdaten-Kind; zweite Mandantenart würde die Tabelle umbenennen müssen |
Globales linetra_edit auf allen DBs |
Jedes Edit-Cert kann jede Mandanten-DB öffnen |
| Postgres-Rolle pro Person | GRANT-Pflege; Widerruf bleibt das Zertifikat (ADR-0002) |
| Schema-per-tenant / RLS | ADR-0030 |
| Eine PgBouncer-ini-Zeile pro Mandant + Reload | Ops-lastig; Wildcard ist der PgBouncer-Weg für N DBs |
Statische userlist.txt für N Rollen |
Muss bei jedem Provisioning neu geschrieben werden |
| Ein Alembic-Head für Control und Mandant | Würde app_users in Mandanten-DBs anlegen |
| Subdomain pro Mandant | Kein DNS-Modell; Session reicht |
Rollenpasswörter in tenants speichern |
Secret-Drift; HMAC aus LINETRA_TENANT_SECRET reicht intern |
Folgen
- ADR-0002: CN ist nicht mehr global
linetra_edit/linetra_read, sondern{slug}_edit/{slug}_read. - ADR-0020: Satz „keine extra tenants-Tabelle in v1“ gilt nur für die Shared-DB; Isolationseinheit ist
tenants. - ADR-0030 Punkt 5 (MVP eine DB) ist durch diese ADR abgelöst; Punkt 4 (Ziel) bleibt.
- Lastenheft offener Punkt 7: kein DB-User pro Person; ein edit/read-Paar pro Mandant.