# Membership, RBAC & Kontingente — Produktentscheidungen **Status:** Verbindlich (Zielbild & Roadmap-Priorisierung) **Stand:** 2026-06-06 **Bezüge:** `CAPABILITY_CATALOG.v1.md`, `CLUB_MEMBERSHIP_AND_FEATURES.v1.md`, `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` Dieses Dokument hält **getroffene Produktentscheidungen** fest (Session 2026-06-06) und ergänzt die v1-Konzept-Specs um Umsetzungsrichtung. Technischer Implementierungsstand: Abschnitt 2. --- ## 1. Getroffene Entscheidungen ### 1.1 Onboarding: `verified_pending_club` Nutzer **ohne aktive Vereinsmitgliedschaft** (E-Mail verifiziert) dürfen **nur**: | Erlaubt | Nicht erlaubt (Zielbild) | |---------|---------------------------| | Konto / Einstellungen | Übungen, Planung, KI, Medien | | Vereinsverzeichnis lesen | Vereinsinterne Inhalte (`club`), private Fremdinhalte | | **Beitrittsantrag** an bestehenden Verein | Vollzugriff auf Bibliothek / offizielle Inhalte (Lesen) — **bewusst gesperrt** bis Mitgliedschaft | | **Vereinsgründung beantragen** (Prozess M7, Superadmin-Freigabe) | | **Kein** „Bibliothek durchstöbern“ für Bewerber — reduziert Datenexposition und vereinfacht UX („erst Verein, dann Arbeit“). Technischer Zustand: `account_state = verified_pending_club` (siehe `CAPABILITY_CATALOG.v1.md` §3). --- ### 1.2 Rollenmodell: Risikoarm statt Big-Bang **Zielbild (langfristig):** - **Fest:** nur `superadmin` (Plattform) als nicht konfigurierbare Systemrolle. - **Dynamisch konfigurierbar:** alle Vereinsrollen und deren Capability-Bundles (später `club_custom_roles`). - Optional: `admin` (Plattform) als abgeschwächter Portal-Admin bleibt vorerst bestehen (Ist-Code). **Entscheidung v1 (risikoarm):** | Maßnahme | Jetzt | Später | |----------|-------|--------| | Alte Helfer (`can_plan_in_club`, `if (club_admin)` in JSX) | **Behalten** — weiter produktiv | Schrittweise durch `entitlements` ersetzen | | Neue Endpoints / Features | Nur über **Capability-IDs** + Audit | — | | Neue Vereinsrollen | Als **Systemrollen** ergänzen (z. B. `co_trainer`) | Custom Roles UI | | `club_custom_roles` | **Nicht** in v1 | v2 Epic | **Begründung:** Backend und Frontend haben hunderte Verdrahtungen auf `trainer` / `club_admin` / Plattform-Rollen. Parallelbetrieb Capability-System + Legacy-Helfer ist sicherer als einmaliges Aufbrechen. **Co-Trainer (geplant als Systemrolle):** weniger Capabilities als `trainer` (z. B. kein `planning.*`, kein `exercises.create`) — Umsetzung nach Onboarding-Gates + Entitlements-Rollout, nicht vorher. --- ### 1.3 Vereins-Kontingente (Membership-Pakete) **Jetzt:** Schema und Anzeige vorbereiten; **keine** detaillierte Paket-Logik (z. B. „3 Trainer + 10 Co-Trainer“) implementieren. | Vorbereitet (DB/Module) | Bewusst zurückgestellt | |-------------------------|-------------------------| | `features`, `club_plans`, `club_subscriptions` | Eigene Feature-IDs `trainer_seats` / `co_trainer_seats` | | Bestands-Limits (`exercises`, `training_groups`, `ai_calls`, …) | Zählregel „nur planungsberechtigte Mitglieder“ vs. alle Mitglieder | | `GET /me/entitlements` Feature-Teil | Stripe / Rechnung (M8) | **Prinzip:** Neue Kontingent-Typen = neue `features`-Zeile + Plan-Limits + optional Capability-`linked_feature_id` — ohne Schema-Bruch. --- ### 1.4 Trainer-Budget innerhalb Vereins-Kontingent (v2) **Anforderung:** Vereins-KI-Kontingent liegt beim Verein; **Vereinsadmin** kann pro Trainer ein **Sub-Budget** vergeben (Fairness, „Kontingent-Fresser“). **Entscheidung:** - v1: nur **Vereins-Ebene** (`club_plan_limits`, `club_feature_usage`). - v2: neue Tabellen (Skizze): ```sql -- Skizze — noch nicht migriert club_member_feature_budgets (club_id, profile_id, feature_id, limit_value, …) club_member_feature_usage (club_id, profile_id, feature_id, usage_count, reset_at, …) ``` **Prüf-Kette v2:** Capability → Mitglieds-Budget (falls gesetzt, `profile_id` aus Session) → Vereins-Kontingent. **Fairness-Modell (offen, Tendenz):** harte Sub-Budgets (Modell A) — Trainer darf sein Budget nicht überschreiten, auch wenn Verein noch Rest hat. **Roadmap:** Phase 5b / Meilenstein **M9** in `docs/working/RBAC_ENFORCEMENT_ROADMAP.md` — Vereinsadmin-UI zur Verteilung, Entitlements mit persönlichem + Vereins-Rest, Auswertung je Person. --- ### 1.5 Enforcement-Phasen (unverändert, bestätigt) | Phase | Verhalten | Nutzer sichtbar | |-------|-----------|-----------------| | 2 (M2/M3) | JSON-Log, kein Block | Nein (außer Logs) | | 3 (M4) | `GET /me/entitlements` + Badge | Kontingent-Anzeige | | 4 (M5+) | HTTP 403 + `increment` | Hard-Block | Env-Schalter: `ACCOUNT_GATE_ENFORCE` (Default `1`, Endpoint-Helfer), `ACCOUNT_GATE_API_ENFORCE` (Default `1`, API-Middleware Phase A), `CAPABILITY_ENFORCE` / `CLUB_FEATURE_ENFORCE` (Default `0`). --- ## 2. Implementierungsstand (Ist, Codebase) **DB-Schema:** `20260606083` · App **0.8.199** (`backend/version.py`) **Roadmap (detailliert):** `docs/working/RBAC_ENFORCEMENT_ROADMAP.md` ### M1 — Feature-Schema v9c ✅ | Deliverable | Status | |-------------|--------| | Migration `078_club_features_and_plans.sql` | ✅ | | Legacy `001` archiviert | ✅ | | `club_plans`, `club_subscriptions`, Usage-Tabellen | ✅ | | Seed Features + Pläne (`free`, …) | ✅ | | `club_features.py`: `check_club_feature_access`, `get_effective_club_plan` | ✅ | | Backfill Vereine → Plan `free` | ✅ | ### M2 — Feature-Probe (Log only) ✅ | Deliverable | Status | |-------------|--------| | `club_feature_logger.py` → `club-feature-usage.log` | ✅ | | `probe_club_feature_access()` | ✅ | | Hooks: KI-Endpoints, `POST /exercises`, Medien-Upload, Planungs-KI | ✅ | | Consume-Standard + `feature_usage` in Response (`ai_calls`) | ✅ | | `CLUB_FEATURE_ENFORCE=0` (Default) | ✅ | ### M3 — Account-Lifecycle + Capability-Grants ⚠️ teilweise | Deliverable | Status | Lücke | |-------------|--------|-------| | Migration `079_capabilities.sql` + Seed | ✅ | — | | `account_lifecycle.py`, `resolve_account_state` | ✅ | — | | `capabilities.py`, `check_capability`, `probe_capability` | ✅ | — | | `TenantContext.account_state` | ✅ | — | | `GET /profiles/me` → `account_state`, `club_roles` | ✅ | — | | Account-Gates auf **Schreib-/KI-Endpoints** | ✅ | Lesepfade für Bewerber noch offen | | `CAPABILITY_ENFORCE=0` (nur Log) | ✅ | — | | Onboarding UX: nur Bewerbung/Gründung | ✅ | Phase A: API-Middleware + `/onboarding` + reduzierte Nav | | `club_creation_requests` (M7) | ✅ Basis | Capabilities + Admin-Freigabe | | Quota-Bypass via Capability-Grants (083) | ✅ | kein paralleles Exemption-Schema | | Custom Roles / Co-Trainer | ❌ | bewusst v2 | | Legacy-Helfer entfernt | ❌ | bewusst parallel | ### M4 — Anzeige ✅ teilweise | Deliverable | Status | |-------------|--------| | `GET /api/me/entitlements` | ✅ | | `EntitlementsContext`, `hasCapability()` | ✅ (UI nutzt noch kaum) | | `FeatureUsageBadge` | ✅ nur KI im Übungsformular | | `featureUsageSync` in `request()` | ✅ | ### M5 — Hard-Block + vollständiger Verbrauch ⚠️ | Deliverable | Status | |-------------|--------| | `consume_club_feature_with_usage` Standard | ✅ `ai_calls` | | `CLUB_FEATURE_ENFORCE=1` produktiv | ❌ Default 0 | | Consume `exercises`, `exercise_media`, … | ❌ | ### M6 — Admin UI Rollen & Rechte ⚠️ | Deliverable | Status | |-------------|--------| | `/admin/rights` Capability-Matrix (Portal + Verein) | ✅ | | Klartext zuerst, Enforcement-Badge | ✅ 2026-06-07 | | Kontingent-Bypass + Vereinspläne (Seed) | ✅ | | Neue Pläne / Rollen anlegen (CRUD) | ❌ | ### Bewusst zurückgestellt | ID | Inhalt | |----|--------| | M0 | CI-Isolation / Test-DB | | M8 | Stripe | | v2 | Trainer-Budgets, Custom Roles | --- ## 3. Architektur-Zielbild (kompakt) ``` Request → require_auth → account_state (Gate) → TenantContext → assert_capability (Rolle / Funktion) → check_club_feature_access (Vereins-Kontingent) → [v2] member_feature_budget (Trainer-Budget) → Governance (Objekt) ``` **Drei Achsen:** Account-Lifecycle · Capabilities · Features (Kontingente). Governance bleibt vierte Prüfung. --- ## 4. Empfohlene Roadmap (nach Entscheidungen) | Phase | Paket | Warum zuerst | |-------|--------|--------------| | **A** | **Onboarding-Gates vollständig** | ✅ umgesetzt (API + Frontend `/onboarding`) | | **B** | **M7 Vereinsgründung beantragen** | **Als Nächstes** — zweiter Pfad für `verified_pending_club` | | **C** | **M5 Hard-Block `ai_calls`** | Free-Plan `0` wird real; Badge (M4) liefert Erklärung | | **D** | **M6 voll** | Pläne-CRUD, Rollen-CRUD | ⚠️ Matrix da | | **E** | Entitlements im Frontend (`hasCapability`) | Entscheidung 1.2 risikoarm | | **F** | **M9 Kontingent-Verteilung** — Vereinsadmin vergibt Sub-Budgets pro Person (`profile_id`); Prüfung + Consume personenbezogen; UI Vereinsorga | Entscheidung 1.4, Roadmap Phase 5b | | **G** | `co_trainer` + Custom Roles (v2) | Entscheidung 1.2 | M0 parallel, nicht blockierend. --- ## 5. Offene Punkte (vor M6 / v2) 1. Fairness Modell A/B/C für Trainer-Budget (Tendenz: A). 2. Ob `admin` (Portal) langfristig neben `superadmin` bleibt. 3. Ob offizielle Inhalte für Bewerber **nie** lesbar bleiben (aktuell: ja). --- ## 6. Referenzen | Pfad | Inhalt | |------|--------| | `CAPABILITY_CATALOG.v1.md` | Capability-IDs, Account-States | | `CLUB_MEMBERSHIP_AND_FEATURES.v1.md` | Feature-Registry, Kontingente | | `backend/club_features.py` | Vereins-Features | | `backend/capabilities.py` | Capability-Auflösung | | `backend/account_lifecycle.py` | Account-Gates | ## 7. Superadmin im Verein (FAQ) Siehe **`docs/working/RBAC_ENFORCEMENT_ROADMAP.md` §4**: Plattform-Admin (`admin`, `superadmin`) erhält **Capability-Bypass** für Vereins-Funktionen ohne `club_admin`-Mitgliedschaft. Mandant über aktiven Verein wählen; Kontingente via Bypass. Einzelne Legacy-Pfade (z. B. Löschen `visibility=club`) sind noch nicht vereinheitlicht — Ziel Phase 3. --- **Changelog** - 2026-06-06: Initial — Entscheidungen Onboarding, Rollen-Risiko, Kontingente, Trainer-Budget v2; Ist-Stand M1–M3; Roadmap A–F. - 2026-06-06: Phase A — `account_onboarding_gate.py`, Frontend `/onboarding`, reduzierte Navigation. - 2026-06-07: M4–M6 Ist-Stand, Roadmap-Verweis, Superadmin-FAQ; Admin-Matrix UX + Enforcement-Audit. - 2026-06-08: Roadmap Phase 5b / M9 — Vereinsadmin-Kontingentverteilung pro Person; Enforce Dev verifiziert (0.8.202).