# Feature & Entitlement System – Designprinzipien (Extraktion) **Status:** Analyse / Arbeitspapier **Stand:** 2026-07-04 **Geltungsbereich:** Membership-, Tier- und Feature-Limit-System (v9c) — keine Mitai-Domänenlogik, kein zentrales SSO/Stripe (Vision) **Serie:** Designprinzipien für Produktfamilie · Dokument 3 von n **Vorgänger:** [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) **Kernkomponenten:** | Bereich | Pfade | |---------|-------| | Entitlement-Auflösung | `backend/auth.py` (`get_effective_tier`, `check_feature_access`, `increment_feature_usage`) | | Monitoring | `backend/feature_logger.py` | | Nutzer-API | `backend/routers/subscription.py`, `backend/routers/features.py` | | Admin | `routers/tiers_mgmt.py`, `tier_limits.py`, `coupons.py`, `access_grants.py`, `user_restrictions.py` | | Widget-Gating | `backend/dashboard_widget_entitlements.py`, `widget_feature_requirements_db.py` | | Frontend | `UsageBadge.jsx`, Feature-Usage in Seiten (z. B. `Analysis.jsx`, `WeightPage`) | | Doku | `MEMBERSHIP_SYSTEM.md`, `FEATURE_ENFORCEMENT.md`, `CENTRAL_SUBSCRIPTION_SYSTEM.md` (Vision) | --- ## Modul **Feature & Entitlement System** (Membership v9c) Zentrale Schicht für **„Darf dieser Nutzer diese Funktion wie oft nutzen?“** — unabhängig von Auth (Identität) und unabhängig von fachlicher Business-Logik in Routern. --- ## Fachliche Verantwortung Das System übernimmt: 1. **Feature-Registry** — Deklarative Liste aller limitierbaren Produktfunktionen mit Metadaten. 2. **Tier-Auflösung** — Effektiver Tarif eines Profils (Basis-Tier + zeitliche Grants). 3. **Limit-Auflösung** — Pro Feature: Override → Tier-Limit → Feature-Default. 4. **Usage-Tracking** — Zähler für Count-Features mit optionalem Reset (daily/monthly/never). 5. **Enforcement** — HTTP 403 bei Überschreitung; Frontend-Vorschaum via Badges. 6. **Beobachtbarkeit** — Strukturiertes JSON-Logging aller Access-Checks. 7. **Promotionen** — Coupons → Access Grants (temporäre Tier-Elevation, Pause/Resume). Es übernimmt **nicht**: - Login, Session, Passwort (Auth-Modul) - Zahlungsabwicklung / Stripe (geplant, `CENTRAL_SUBSCRIPTION_SYSTEM.md`) - Mandanten-Isolation (Org/Workspace) — Entitlements sind **profile-scoped** - Inhaltliche Berechtigung pro Datensatz (nur Feature-Gates) ### Zwei Entscheidungsebenen | Ebene | Frage | Funktion | |-------|--------|----------| | **Tier** | Welcher Tarif gilt? | `get_effective_tier()` | | **Feature** | Darf Feature X genutzt werden (wie oft)? | `check_feature_access()` | Tier beeinflusst Feature-Limits über `tier_limits`; User-Overrides können Limits unabhängig vom Tier setzen. ### Administrierte Konfigurationen | Konfiguration | Speicherort | Admin-UI | |---------------|-------------|----------| | Feature-Definitionen | `features` | Admin Features | | Tier-Stufen | `tiers` | Admin Tiers | | Tier × Feature Matrix | `tier_limits` | Admin Tier Limits | | User-Overrides | `user_feature_restrictions` | Admin User Restrictions | | Coupons | `coupons`, `coupon_redemptions` | Admin Coupons | | Temporäre Tier-Grants | `access_grants` | (via Coupon/Admin) | | Widget → Feature Mapping | `widget_feature_requirements`, Katalog | Admin Widget Features | | Usage-Zähler | `user_feature_usage` | (automatisch) | **Nicht hardcodiert:** Limits pro Tier, Feature-Metadaten, Coupon-Parameter, User-Overrides. **Hardcodiert (Code):** Feature-IDs in Routern (`'ai_calls'`, `'weight_entries'`, …), Reset-Berechnung, 4-Phasen-Muster, 11 initial registrierte Features. ### Auflösungs-Hierarchien **Effektiver Tier** (`get_effective_tier`): 1. Aktiver `access_grants`-Eintrag (`is_active`, `valid_from`/`valid_until`) 2. Fallback: `profiles.tier` **Feature-Limit** (`check_feature_access` → `_check_impl`): 1. `user_feature_restrictions.limit_value` (höchste Priorität) 2. `tier_limits` für effektiven Tier 3. `features.default_limit` **Limit-Semantik:** | `limit_type` | Bedeutung | |--------------|-----------| | `count` | Zählbares Kontingent; `used < limit` | | `boolean` | An/Aus; `limit == 1` erlaubt, `0` gesperrt | | `limit_value` | Bedeutung | |---------------|-----------| | `NULL` | Unbegrenzt | | `0` | Deaktiviert | | `> 0` | Kontingent oder Boolean „an“ | ### Rollen | Rolle | Darf | |-------|------| | **Admin** | Features/Tiers/Limits/Coupons/Restrictions CRUD; alle Nutzer-Overrides | | **Nutzer** | Eigene Subscription/Usage lesen (`/subscription/me`, `/features/usage`); keine Limit-Änderung | Enforcement gilt für alle authentifizierten Nutzer gleich — Admins haben keine automatische Bypass-Logik in `check_feature_access`. ### Versionierung, Freigabe, Test | Mechanismus | Status | |-------------|--------| | 4-Phasen-Rollout (Monitor → UI → Enforce) | ✅ dokumentiert & angewendet | | JSON-Log `feature-usage.log` | ✅ Phase 2 Monitoring | | DB-Migration v9c für Schema | ✅ | | Automatisierte Enforcement-Tests pro Router | ⚠️ teilweise (Widgets getestet) | | Zentrale Policy „jeder Endpoint muss checken“ | ❌ nicht erzwungen | --- ## Designprinzipien ### 1. Feature-Registry statt hardcodierter Limits | | | |---|---| | **Prinzip** | Jedes limitierbare Produkt-Feature ist Zeile in `features` — neue Features ohne Schema-Migration für Limits. | | **Begründung** | Admin-UI, Usage-API und Backend-Checks teilen dieselbe ID und Metadaten. | | **Quelle** | `MEMBERSHIP_SYSTEM.md` § Feature-Registry; `routers/features.py` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Feature-IDs müssen trotzdem in Router-Code referenziert werden. | ### 2. Eine Auflösungsfunktion für Entitlements | | | |---|---| | **Prinzip** | Alle Backend- und Widget-Checks rufen `check_feature_access(profile_id, feature_id)` auf. | | **Begründung** | Keine duplizierte Tier/Limit-Logik in Routern, Widgets oder Frontend. | | **Quelle** | `auth.py`; `dashboard_widget_entitlements.py` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Nicht alle Endpoints nutzen es (z. B. `/prompts/execute` fehlt). | ### 3. Getrennte Tier- und Feature-Auflösung | | | |---|---| | **Prinzip** | `get_effective_tier()` für Tarif; `check_feature_access()` für konkretes Feature — Tier ist Input, nicht Output der Feature-Prüfung. | | **Begründung** | Temporäre Grants heben Tier an; User-Override kann einzelnes Feature unabhängig anpassen. | | **Quelle** | `auth.py` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | `get_effective_tier` im Code einfacher als in `MEMBERSHIP_SYSTEM.md` (kein `tier_locked`, Trial nicht in Tier-Funktion). | ### 4. Prioritäts-Kette für Limits | | | |---|---| | **Prinzip** | User-Override > Tier-Limit > Feature-Default — explizit und dokumentiert. | | **Begründung** | Support/Beta-Fälle ohne Tier-Wechsel; vorhersehbares Verhalten. | | **Quelle** | `_check_impl()` in `auth.py`; `MEMBERSHIP_SYSTEM.md` § Zugriffs-Hierarchie | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | `user_feature_restrictions.enabled` im Schema, aber nicht in `_check_impl` ausgewertet. | ### 5. Count vs. Boolean als zwei Feature-Klassen | | | |---|---| | **Prinzip** | Zählbare Aktionen (`count` + Usage) vs. Schalter-Features (`boolean`, kein Counter). | | **Begründung** | Pipeline-An/Aus vs. monatliche KI-Calls — unterschiedliche UX und Backend-Logik. | | **Quelle** | `features.limit_type`; `FEATURE_ENFORCEMENT.md` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Boolean-Features nutzen `limit_value` 0/1 — leicht mit Count zu verwechseln. | ### 6. Reset-Perioden für Count-Features | | | |---|---| | **Prinzip** | `reset_period`: `never` \| `daily` \| `monthly` — Counter-Reset in `check_feature_access` bei abgelaufenem `reset_at`. | | **Begründung** | Monats-Kontingente vs. Lifetime-Limits in einem Modell. | | **Quelle** | `auth.py` → `_calculate_next_reset()` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Reset beim Check, nicht per Cron — Edge Cases bei seltenem Zugriff. | ### 7. Usage nur bei neuen Entitäten incrementieren | | | |---|---| | **Prinzip** | `increment_feature_usage()` nur nach **INSERT**, nicht nach UPDATE/Upsert-Deduplikat. | | **Begründung** | Limits messen „neue Nutzung“, nicht Bearbeitung bestehender Daten. | | **Quelle** | `FEATURE_ENFORCEMENT.md` § Wichtige Regeln | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Bulk-Import muss explizit zählen; Fehler anfällig. | ### 8. Vier-Phasen-Rollout (Observe before Enforce) | | | |---|---| | **Prinzip** | Phase 1 Cleanup → 2 Logging → 3 Frontend-Badges → 4 HTTP 403. | | **Begründung** | Limits einführen ohne blind Nutzer zu blockieren; Daten für Limit-Kalibrierung. | | **Quelle** | `FEATURE_ENFORCEMENT.md` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Disziplin pro Feature; kein zentraler Feature-Flag pro Endpoint-Phase. | ### 9. Strukturiertes Feature-Logging | | | |---|---| | **Prinzip** | Jeder Check: `log_feature_usage(profile_id, feature_id, access, action)` → JSON in `feature-usage.log`. | | **Begründung** | Audit, Debugging, Kalibrierung — auch wenn noch nicht enforced. | | **Quelle** | `feature_logger.py` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Log-Pfad `/app/logs` container-spezifisch. | ### 10. Defense in Depth: API 403 + Frontend-Gate | | | |---|---| | **Prinzip** | Backend blockiert autoritativ; Frontend zeigt `UsageBadge`, deaktiviert Buttons, Tooltip bei Limit. | | **Begründung** | UX (frühes Feedback) + Sicherheit (API nicht umgehbar via curl). | | **Quelle** | `FEATURE_ENFORCEMENT.md`; `UsageBadge.jsx` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Frontend-Gate optional pro Seite; nicht generisch erzwungen. | ### 11. Nutzer-Usage-API ohne Code-Änderung bei neuen Features | | | |---|---| | **Prinzip** | `GET /features/usage` iteriert alle aktiven `features` und ruft `check_feature_access` pro Zeile. | | **Begründung** | Neues DB-Feature erscheint automatisch in Quota-Übersicht. | | **Quelle** | `routers/features.py` → `get_feature_usage()` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Frontend muss Feature-ID kennen, um Badge zu binden. | ### 12. Access Grants für temporäre Tier-Elevation | | | |---|---| | **Prinzip** | Coupons/Admin erzeugen `access_grants`; effektiver Tier steigt zeitlich begrenzt. | | **Begründung** | Promotions, Partner (Wellpass), Trials ohne permanente Tier-Änderung. | | **Quelle** | `access_grants`; `routers/coupons.py` (Pause/Resume) | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Coupon-Stacking-Logik komplex; dokumentiert vs. Code prüfen bei Neuentwicklung. | ### 13. Entitlements als Querschnitt für UI-Module (Widgets) | | | |---|---| | **Prinzip** | Dashboard-Widgets mappen auf `features.id`; Katalog liefert `allowed` pro Profil. | | **Begründung** | Tier-Logik nicht in React-Widgets duplizieren (`DASHBOARD_WIDGETS_AGENT_GUIDE` §0). | | **Quelle** | `dashboard_widget_entitlements.py`; `ARCHITECTURE.md` §9 | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Widget-Sichtbarkeit ≠ API-Schutz — Chart-Endpoints brauchen eigenes Gating (A4). | ### 14. Admin-konfigurierbare Tier × Feature Matrix | | | |---|---| | **Prinzip** | `tier_limits` trennt Tier-Definition von Limits; Tiers ohne hardcodierte Spalten pro Feature. | | **Begründung** | Neue Tiers/Preise ohne Code-Deploy der Limit-Logik. | | **Quelle** | `MEMBERSHIP_SYSTEM.md` § Tier-System; `tier_limits` Tabelle | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Tier-Namen in Seed-Daten (`free`, `premium`, …) — erweiterbar, aber Konvention. | ### 15. NULL = unlimited, 0 = disabled | | | |---|---| | **Prinzip** | Einheitliche Semantik für Limit-Werte in allen Schichten. | | **Begründung** | Vermeidet Sonderfälle „-1 means unlimited“; klare Admin-UI. | | **Quelle** | `_check_impl()` in `auth.py` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | SQL NULL vs. Python None — konsistent, aber in UI erklärungsbedürftig. | --- ## Registrierte Features (Referenz) | Feature ID | Typ | Reset | Typische Aktion | |------------|-----|-------|-----------------| | `weight_entries` | count | never | Gewicht anlegen | | `circumference_entries` | count | never | Umfang anlegen | | `caliper_entries` | count | never | Caliper anlegen | | `activity_entries` | count | monthly | Training anlegen/import | | `nutrition_entries` | count | monthly | Ernährung anlegen/import | | `photos` | count | monthly | Foto hochladen | | `ai_calls` | count | monthly | KI-Analyse | | `ai_pipeline` | boolean | — | Pipeline-Analyse | | `data_export` | count | monthly | Export/PDF | | `data_import` | count | monthly | ZIP/Universal-Import | **Enforcement-Lücken (Ist):** `routers/prompts.py` (`/execute`, `/execute-stream`) ohne `check_feature_access` — Legacy `insights.py` hat Enforcement für `ai_calls`/`ai_pipeline`. --- ## Nicht übernehmen 1. **Dokumentations-Drift** — `MEMBERSHIP_SYSTEM.md` („Enforcement deaktiviert“) vs. `FEATURE_ENFORCEMENT.md` (Phase 4 komplett) vs. Ist-Code; bei Neuentwicklung einen Kanon festlegen. 2. **Unvollständige Tier-Auflösung** — Doku beschreibt `tier_locked`, Trial-in-Tier; Code nutzt primär Grants + `profiles.tier`. Trial (`trial_ends_at`) eher UI-Banner als Tier-Engine. 3. **Feature-IDs in Routern verstreut** — kein zentraler Endpoint-Registry-Eintrag „welcher Router prüft welches Feature“. 4. **Check und Increment nicht atomar** — Race bei parallelen Requests möglich; kein DB-Level Locking. 5. **Legacy Profil-Spalten parallel** — `ai_enabled`, `ai_limit_day`, `export_enabled` in Sessions-Query neben Feature-System. 6. **Frontend ohne Backend-Gate** — reine UI-Deaktivierung ohne 403 ist unsicher (manche Seiten nur teilweise umgesetzt). 7. **Boolean via limit_value 0/1** — funktioniert, aber für Familien-Architektur explizites `enabled`-Flag oder Capability-Tokens erwägen. 8. **Unused Schema-Felder** — `user_feature_restrictions.enabled` nicht in Auflösung eingebunden. 9. **App-lokales Abo ohne Zahlungsanbindung** — Stripe/SSO nur Vision (`CENTRAL_SUBSCRIPTION_SYSTEM.md`); nicht als fertiges Familien-Muster übernehmen. 10. **Profile als Entitlement-Subject** — kein Org/Mandant; Multi-App-Familie braucht separates Identity/Subscription-Boundary. 11. **Self-hosted Tier als Sonderfall** — `selfhosted` ist Deploy-Modell, kein generisches SaaS-Tier-Muster. 12. **Increment-Schleifen bei Bulk** — `for _ in range(new_entries): increment_feature_usage()` — ineffizient; batch-Inkrement besser. --- ## Modul-Inventar (Ist-Stand) ``` backend/ ├── auth.py # get_effective_tier, check_feature_access, increment_feature_usage ├── feature_logger.py # JSON-Logging ├── dashboard_widget_entitlements.py # Widget allowed + Layout-Sanitisierung ├── widget_feature_requirements_db.py └── routers/ ├── subscription.py # /me, /usage, /limits (Nutzer) ├── features.py # Admin CRUD + /usage, /check-access ├── tiers_mgmt.py, tier_limits.py ├── coupons.py, access_grants.py └── user_restrictions.py frontend/src/components/ └── UsageBadge.jsx # Quota-Anzeige (Phase 3) ``` **DB (v9c):** `features`, `tiers`, `tier_limits`, `user_feature_restrictions`, `user_feature_usage`, `coupons`, `coupon_redemptions`, `access_grants`, `user_activity_log` --- ## Verwandte Dokumentation - Membership-Detail: [MEMBERSHIP_SYSTEM.md](../../technical/MEMBERSHIP_SYSTEM.md) - Enforcement-Howto: [FEATURE_ENFORCEMENT.md](../../architecture/FEATURE_ENFORCEMENT.md) - Vision Produktfamilie: [CENTRAL_SUBSCRIPTION_SYSTEM.md](../../technical/CENTRAL_SUBSCRIPTION_SYSTEM.md) - Widget-Gating: [DASHBOARD_WIDGETS_AGENT_GUIDE.md](../../technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md) §0 - Prompt Engine (Enforcement-Lücke): [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md) --- ## Geplante Folgedokumente (Serie) | # | Modul | Status | |---|-------|--------| | 1 | Prompt Engine | ✅ | | 2 | Data Layer | ✅ | | 3 | Feature & Entitlement | ✅ dieses Dokument | | 4 | Registry-/Plugin-Muster | ✅ | | 5 | Auth & Session | ✅ | | 6 | Universal Import | ✅ | | 7 | Dashboard Widgets | ✅ | | 8 | Navigation / IA | ✅ | | 9 | Migration & Deploy | ✅ `MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md` |