All checks were successful
Deploy Development / deploy (push) Successful in 48s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m35s
Document cross-app architecture patterns, Mitai alignment, and family entitlement standards. Documentation only; no runtime changes. Co-authored-by: Cursor <cursoragent@cursor.com>
7.8 KiB
7.8 KiB
Access Layer & Tenant – Designprinzipien (Extraktion)
Status: Analyse / Arbeitspapier
Stand: 2026-07-04
Geltungsbereich: Modul „Access Layer & Tenant Governance“ — keine Shinkan-Gesamtarchitektur, keine Kampfsport-Domänenlogik
Serie: Designprinzipien für Produktfamilie · Shinkan Dokument 1 von 15
Mitai-Vergleich: kein direktes Gegenstück (Mitai ist profil-zentriert, kein Vereins-Mandant)
Kernkomponenten:
| Bereich | Pfade |
|---|---|
| TenantContext | backend/tenant_context.py |
| Governance-Helfer | backend/club_tenancy.py |
| Listenfilter SQL | library_content_visibility_sql() in tenant_context.py |
| Endpoint-Audit | .claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md |
| Cursor-Regel | .cursor/rules/access-layer.mdc |
| Heuristik-Check | backend/scripts/check_access_layer_hints.py |
| Normative Spec | .claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md |
Modul
Access Layer & Tenant Governance
Zentraler Querschnitt für Mandanten-Kontext (club_id), Sichtbarkeit (private/club/official) und einheitliche Les-/Schreibregeln für Bibliotheksartefakte (Übungen, Medien, Rahmenprogramme, Vorlagen, Progressionsgraphen).
Fachliche Verantwortung
- TenantContext pro HTTP-Request — Auflösung aus Session + Header
X-Active-Club-Id+ Profilfeldactive_club_id. - Datenisolierung —
club_idals Grenze für vereinsgeteilte Inhalte; Cross-Verein ausgeschlossen. - Einheitliche Sichtbarkeits-Semantik — gleiche Enums und Prüflogik über alle Bibliotheksmodule.
- Governance-Transitionen — Regeln beim Wechsel
private→club→officialund beim Löschen. - Listenfilter — SQL-Baustein statt „SELECT *“ in jedem Router.
Administrierte Konfigurationen
| Konfiguration | Speicherort | Inhalt |
|---|---|---|
| Aktiver Verein | profiles.active_club_id |
Persistierter UI-Kontext |
| Request-Override | Header X-Active-Club-Id |
Client-seitiger Mandantenwechsel |
| Sichtbarkeit | Spalte visibility je Objekt |
private, club, official |
| Vereinszuordnung | Spalte club_id |
Pflicht bei club-Inhalten |
Bewusst nicht hardcodiert
- Welche Objekte welchen Verein haben (Daten)
- Individuelle Freigabeentscheidungen (Workflow)
Hardcodiert (Code)
- Enum-Werte und Leseregeln in
club_tenancy.py/library_content_visibility_sql - Plattform-Admin-Ausnahmen (
is_platform_admin,is_superadmin) - Rollencodes für Schreib-/Löschregeln (
club_admin,trainer, …)
Designprinzipien
1. Ein Mandant pro Request (TenantContext)
| Prinzip | Depends(get_tenant_context) liefert profile_id, global_role, effective_club_id, Mitgliedschaften — einmal pro Request. |
| Begründung | Kein verteiltes „Rates“ aus Headers; konsistente Filter in allen Routern. |
| Quelle | tenant_context.py; ACCESS_LAYER_AND_GOVERNANCE_PLAN.md §2 |
| Tragfähigkeit | hoch |
| Einschränkung | Nicht alle Endpoints migriert; Audit-Tabelle zeigt Restbestand mit nur require_auth. |
2. club_id als Datenisolierungsgrenze
| Prinzip | Vereinsgeteilte Inhalte sind nur für aktive Mitglieder des Objekt-club_id lesbar — nie Cross-Verein. |
| Begründung | Mandantenfähigkeit für Vereinsplattform; Compliance bei geteilten Trainingsinhalten. |
| Quelle | library_content_visibility_sql(); Tests test_access_layer*.py |
| Tragfähigkeit | hoch |
| Einschränkung | division-Verschärfung noch nicht durchgängig; reserviert im Plan. |
3. Einheitliche Visibility-Semantik
| Prinzip | private | club | official mit gleicher Bedeutung für Übungen, Medien, Rahmen, Module, Graphen. |
| Begründung | Nutzer und Trainer verstehen ein Freigabemodell; UI-Feld „Freigabelevel“ durchgängig. |
| Quelle | club_tenancy.py; FACHLICHE_NUTZERFUNKTIONEN.md §4.7 |
| Tragfähigkeit | hoch |
| Einschränkung | community-Stufe nur dokumentiert, nicht implementiert. |
4. Zentraler SQL-Filter für Bibliothekslisten
| Prinzip | Listen nutzen library_content_visibility_sql(alias, profile_id, role, effective_club_id) — nicht handgeschriebene WHERE-Kopien. |
| Begründung | Drift-Vermeidung; ein Fix gilt für alle Kataloge. |
| Quelle | tenant_context.py; Router exercises.py, training_framework_programs.py, … |
| Tragfähigkeit | hoch |
| Einschränkung | Einzelne Legacy-Queries können noch abweichen. |
5. Governance-Transitionen explizit prüfen
| Prinzip | Wechsel von visibility/club_id über assert_library_content_governance_transition + assert_valid_governance_visibility. |
| Begründung | „Privat → Verein teilen“ und „official herabstufen“ sind sicherheitsrelevante Aktionen. |
| Quelle | club_tenancy.py |
| Tragfähigkeit | hoch |
| Einschränkung | Nicht jedes Modul ruft Transition-Helper bei PATCH auf. |
6. Löschregeln nach Visibility-Stufe
| Prinzip | assert_library_content_deletable: privat → Ersteller/Vereinsadmin-Kontext; club → Vereinsadmin; official → Plattform-Admin. |
| Begründung | Schutz vor versehentlichem Löschen fremder oder offizieller Inhalte. |
| Quelle | club_tenancy.py |
| Tragfähigkeit | hoch |
| Einschränkung | Medien-Lifecycle hat zusätzliche Stufen (Papierkorb) — Schnittmenge beachten. |
7. Aktiver Verein: Header + Profil synchron
| Prinzip | Frontend sendet X-Active-Club-Id; Backend validiert gegen Mitgliedschaft; Profil speichert active_club_id. |
| Begründung | Einheitlicher Mandanten-Kontext über API und UI. |
| Quelle | frontend/src/api/client.js (mergeActiveClubHeader); profiles Router |
| Tragfähigkeit | hoch |
| Einschränkung | Onboarding-Nutzer ohne Verein: eingeschränkter Nav-Modus. |
8. Plattform-Admin als Audit-Pfad, nicht als Bypass
| Prinzip | Plattform-Admins sehen fremde club-Inhalte nur mit expliziter Regel (Mitgliedschaft oder Audit-Ausnahme in SQL). |
| Begründung | Superuser-Zugriff ohne Mandanten-Leak in normalen Trainer-Flows. |
| Quelle | library_content_visibility_sql — club_ok_plat-Zweig |
| Tragfähigkeit | mittel |
| Einschränkung | Feinheiten zwischen admin und superadmin (z. B. official, Legal Hold) separat geregelt. |
9. Endpoint-Audit als lebendes Inventar
| Prinzip | Jeder sicherheitsrelevante Endpoint-Eintrag in ACCESS_LAYER_ENDPOINT_AUDIT.md; PR-Checkliste verlangt Update. |
| Begründung | Sichtbarkeit des Migrationsstands; kein stilles Ausweichen auf require_auth allein. |
| Quelle | .claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md; check_access_layer_hints.py |
| Tragfähigkeit | hoch (prozessual) |
| Einschränkung | CI-Strict-Modus optional, nicht überall aktiv. |
Nicht übernehmen
- Endpoints nur mit
require_authbei tenant-sensitiven Daten — führt zu IDOR und fehlenden Listenfiltern. - Visibility-Logik pro Router duplizieren — historische Drift zwischen Übungen und Planung.
divisionvor stabiler Vereins-Isolation — Reihenfolge im Plan: erst Stufe C, dann D.- Community-Freigabe ohne additive Felder — würde
club-Isolation brechen. - Client-seitige Mandantenfilter ohne Server-Enforcement — UI-Hiding reicht nicht.