Merge branch 'feat/sap-om-org-model'
Some checks failed
CI / Lint, Typen, Tests, Build (push) Failing after 6m9s
CI / Integrationstests (echtes Postgres) (push) Failing after 5m20s

This commit is contained in:
2026-08-17 12:42:38 +02:00
200 changed files with 23108 additions and 3371 deletions

View File

@@ -54,14 +54,18 @@ Liste. Kurzfassung:
| Variable | Sichtbarkeit | Zweck |
|---|---|---|
| `NEXT_PUBLIC_SUPABASE_URL` | Browser + Server | Supabase-Projekt-URL |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Browser + Server | Anon-Key, RLS-gebunden |
| `SUPABASE_SERVICE_ROLE_KEY` | **Nur Server** | Umgeht RLS vollständig — niemals im Browser-Bundle, niemals loggen |
| `DATABASE_URL` | Nur Server | PostgreSQL-Verbindung. Die Rolle darf **kein** `BYPASSRLS` haben |
| `DATABASE_SSL` | Nur Server | `false` für lokal/CI ohne TLS |
| `AUTH_SECRET` | Nur Server | Signiert und verschlüsselt das Sitzungscookie |
| `AUTH_MICROSOFT_ENTRA_ID_ID` | Nur Server | Anwendungs-ID der Entra-Registrierung |
| `AUTH_MICROSOFT_ENTRA_ID_SECRET` | Nur Server | Client-Geheimnis dazu |
| `AUTH_MICROSOFT_ENTRA_ID_ISSUER` | Nur Server | Aussteller mit Mandanten-ID — nicht `common` |
| `CRON_SECRET` | Nur Server | Schützt `/api/cron/apply-pending-changes` |
`NEXT_PUBLIC_*`-Werte werden beim Build in das Client-Bundle eingebacken —
eine Änderung erfordert einen Rebuild, nicht nur einen Neustart (relevant
für Docker-Deployments, siehe unten).
**Es gibt keine `NEXT_PUBLIC_*`-Variablen mehr.** Nichts wird in das
Browser-Bundle eingebacken, weil der Browser mit nichts ausser der Anwendung
selbst spricht. Ein Docker-Abbild ist damit umgebungsneutral: einmal gebaut,
überall dasselbe — vorher brauchte jede Umgebung ihr eigenes.
## Scripts
@@ -84,11 +88,16 @@ für Docker-Deployments, siehe unten).
Ersatzkontrolle.
- **Ein Rollenmodell:** `profiles.role = 'hr'` + `profiles.is_active = true`,
geprüft über die SQL-Funktion `is_hr_user()`. Kein Sub-Rollensystem —
siehe [`docs/data-model.md`](docs/data-model.md#zugriffsmodell).
- **Service-Role-Key ist server-only.** Einzige Verwendung:
`lib/supabase/admin.ts`, geschützt durch `import "server-only"` (macht
einen versehentlichen Client-Import zu einem Build-Fehler statt einem
Laufzeitproblem).
siehe [`docs/datenkatalog.md`](docs/datenkatalog.md#zugriffsschutz).
- **Es gibt keinen privilegierten Zugang mehr.** Der Dienstschlüssel, der RLS
aushebelte, ist ersatzlos entfallen; auch der nächtliche Lauf benutzt
dieselbe Rolle ohne `BYPASSRLS`. Was ohne angemeldete Person laufen muss,
steht als `SECURITY DEFINER`-Funktion in der Datenbank und prüft dort
selbst, was es tut.
- **Jede Abfrage läuft in einer Transaktion mit gesetztem Sitzungskontext.**
Die Kysely-Instanz wird nicht exportiert — der einzige Weg an die Datenbank
ist `withUser()` (`lib/db/index.ts`), und eine ESLint-Regel verbietet den
Import von `pg` ausserhalb von `lib/db/`.
- **Audit-Log ist transaktional in der Datenbank**, nicht im App-Code: jede
mutierende SQL-Funktion schreibt ihren `audit_log`-Eintrag in derselben
Transaktion wie die Änderung selbst. Details und Prüfung siehe
@@ -113,7 +122,8 @@ für Docker-Deployments, siehe unten).
## Supabase-Hinweise
- Schema-Quelle der Wahrheit: `supabase/migrations/`. Menschlich lesbare
Zusammenfassung: [`docs/data-model.md`](docs/data-model.md).
Fassung, aus der laufenden Datenbank erzeugt:
[`docs/datenkatalog.md`](docs/datenkatalog.md).
- Migrationen einspielen: `supabase db push` (gegen das verlinkte Projekt)
bzw. `supabase start` + automatische Anwendung für lokale Entwicklung.
- `supabase/seed.ts` und `.env.test.local` sind nur für lokale