All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 47s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m40s
- Introduced the `consume_club_feature_with_usage` function to standardize feature consumption across endpoints, improving code reusability and clarity. - Implemented `merge_feature_usage_into_response` to embed feature usage data in API responses, streamlining frontend integration. - Updated various backend routers to utilize the new consumption logic, ensuring consistent feature usage tracking during AI-related actions. - Enhanced tests to validate the new consumption and logging behavior. - Incremented application version to 0.8.199 and updated module version for 'club_features' to 1.6.0 to reflect these changes.
479 lines
18 KiB
Markdown
479 lines
18 KiB
Markdown
# Vereins-Membership & Feature-System Shinkan v1
|
||
|
||
**Status:** Konzept + M1–M3 teilweise produktiv (siehe Entscheidungs-Doc §2)
|
||
**Stand:** 2026-06-06
|
||
**Bezüge:** Schwesterprojekt Mitai (`v9c_subscription_system.sql`, `FEATURE_ENFORCEMENT.md`), `CAPABILITY_CATALOG.v1.md`, `MULTI_TENANCY_RBAC_ARCHITECTURE.md` §4.6, `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`, **`MEMBERSHIP_RBAC_DECISIONS_2026-06.md`**
|
||
|
||
---
|
||
|
||
## 1. Zweck
|
||
|
||
Shinkan verkauft und limitiert **nicht Einzelpersonen** (wie Mitai), sondern **Vereine**. Dieses Dokument definiert:
|
||
|
||
- das **Feature-Registry**-Muster (limitierbare Funktionen),
|
||
- das **Vereins-Abo** (`club_plans`, `club_subscriptions`),
|
||
- **Kontingente** und Enforcement,
|
||
- die **Abbildung von Mitai** und **Vermeidung von Refactoring-Schulden**.
|
||
|
||
Capabilities (Rollen: *darf ich die Funktion?*) → `CAPABILITY_CATALOG.v1.md`.
|
||
|
||
---
|
||
|
||
## 2. Grundprinzip: Zwei Achsen
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph cap [Achse 1 — Capabilities]
|
||
CR[club_role_capability_grants]
|
||
PR[portal_role_capability_grants]
|
||
end
|
||
|
||
subgraph feat [Achse 2 — Features / Kontingente]
|
||
FP[club_plans]
|
||
FPL[club_plan_limits]
|
||
FS[club_subscriptions]
|
||
FU[club_feature_usage]
|
||
end
|
||
|
||
subgraph gov [Achse 3 — Governance]
|
||
GV[visibility / club_id / created_by]
|
||
end
|
||
|
||
REQ[HTTP Request] --> ACCT[Account-Lifecycle]
|
||
ACCT --> cap
|
||
cap --> gov
|
||
gov --> feat
|
||
feat --> EXEC[Ausführung + increment]
|
||
```
|
||
|
||
| Frage | System | Subjekt |
|
||
|-------|--------|---------|
|
||
| Darf Trainer X KI nutzen? | Capability `exercises.ai.suggest` | `profile_id` + `club_role` |
|
||
| Wie viele KI-Aufrufe hat Verein Y? | Feature `ai_calls` | **`club_id`** |
|
||
| Darf ich diese Übung ändern? | Governance | Objekt + Mitgliedschaft |
|
||
|
||
**Beide Achsen müssen erfüllt sein** (AND), außer dokumentierte Plattform-Ausnahmen.
|
||
|
||
---
|
||
|
||
## 3. Mitai-Mapping (was übernehmen, was nicht)
|
||
|
||
### 3.1 Übernehmen (Pattern)
|
||
|
||
| Mitai (Person) | Shinkan (Verein) | Anmerkung |
|
||
|----------------|------------------|-----------|
|
||
| `features` (TEXT-PK, Registry) | `features` (`app='shinkan'`) | Gemeinsames Muster, ggf. später Jinkendo-weit |
|
||
| `tiers` | `club_plans` | Produktdefinition |
|
||
| `tier_limits` | `club_plan_limits` | Matrix Plan × Feature |
|
||
| `user_feature_restrictions` | `club_feature_overrides` | Admin-Override pro Verein |
|
||
| `user_feature_usage` | `club_feature_usage` | Verbrauch pro Verein |
|
||
| `access_grants` | `club_access_grants` | Trial, Promo, manuelle Freischaltung |
|
||
| `check_feature_access()` | `check_club_feature_access()` | Subjekt `club_id` |
|
||
| `increment_feature_usage()` | `increment_club_feature_usage()` | Nur bei INSERT / KI-Call |
|
||
| 4-Phasen-Rollout | identisch | Log → UI → Hard-Block |
|
||
| `GET /api/features/usage` | `GET /api/clubs/{id}/entitlements` | siehe Capability-Doc §7 |
|
||
|
||
### 3.2 Nicht übernehmen
|
||
|
||
| Mitai | Shinkan-Grund |
|
||
|-------|---------------|
|
||
| `profiles.tier` als Haupt-Abo | Verein zahlt, nicht Einzeltrainer |
|
||
| `subscriptions` (Shinkan `001`, INT-Features) | Ungenutzt, Schema-Drift |
|
||
| `get_effective_tier(profile_id)` für Shinkan-Limits | Ersetzen durch `get_effective_club_plan(club_id)` |
|
||
| Profil-zentrierte Enforcement-Hooks allein | Primär `club_id`; Profil nur für Attribution |
|
||
|
||
### 3.3 Parallelität Jinkendo-Familie (später)
|
||
|
||
`CENTRAL_SUBSCRIPTION_SYSTEM.md` (Mitai): zentrales Personen-Abo über Apps.
|
||
|
||
**Zielbild ohne Refactoring:**
|
||
|
||
```
|
||
features.enforcement_subject ∈ { 'club', 'profile', 'portal' }
|
||
|
||
effektives_limit(feature) = merge(
|
||
club_plan_limit(club_id, feature), # Shinkan-Hauptquelle
|
||
profile_grant_limit(profile_id, feature) # optional Jinkendo-Bonus
|
||
)
|
||
```
|
||
|
||
Merge-Regel (Vorschlag): **Maximum** der erlaubten Kontingente, boolean = OR. Details vor Stripe festlegen.
|
||
|
||
---
|
||
|
||
## 4. Ist-Zustand Shinkan (Drift — zuerst bereinigen)
|
||
|
||
| Artefakt | Problem |
|
||
|----------|---------|
|
||
| `backend/migrations/001_auth_membership.sql` | `features.id SERIAL`, `tier_limits.tier VARCHAR` |
|
||
| `backend/auth.py` `check_feature_access()` | Erwartet Mitai-v9c-Schema (`features.id TEXT`, `tier_id`, `limit_type`, …) |
|
||
| Kein Router | Ruft `check_feature_access` auf |
|
||
| `profiles.tier` | Existiert, ohne Shinkan-Enforcement |
|
||
|
||
**Pflicht vor Phase 3 (Enforcement):** Migration `0XX_club_features_v1.sql` — v9c-kompatibles Feature-Schema + Vereins-Tabellen; alte `001`-Feature-Zeilen migrieren oder deprecaten.
|
||
|
||
---
|
||
|
||
## 5. Ziel-Schema (v1)
|
||
|
||
### 5.1 Feature-Registry (app-weit, Mitai-kompatibel)
|
||
|
||
```sql
|
||
-- Konzept — Implementierung als nummerierte Migration
|
||
CREATE TABLE features (
|
||
id TEXT PRIMARY KEY, -- z.B. 'ai_calls'
|
||
app TEXT NOT NULL DEFAULT 'shinkan',
|
||
name TEXT NOT NULL,
|
||
description TEXT,
|
||
category TEXT NOT NULL, -- 'content'|'planning'|'ai'|'org'|'integration'|'platform'
|
||
limit_type TEXT NOT NULL DEFAULT 'count', -- 'count' | 'boolean'
|
||
reset_period TEXT NOT NULL DEFAULT 'never', -- 'never' | 'daily' | 'monthly'
|
||
default_limit INTEGER, -- NULL=∞, 0=aus
|
||
enforcement_subject TEXT NOT NULL DEFAULT 'club', -- 'club'|'profile'|'portal'
|
||
active BOOLEAN NOT NULL DEFAULT true,
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### 5.2 Vereins-Produkte & Abo
|
||
|
||
```sql
|
||
CREATE TABLE club_plans (
|
||
id TEXT PRIMARY KEY, -- 'free', 'verein_starter', 'verein_pro'
|
||
name TEXT NOT NULL,
|
||
description TEXT,
|
||
price_monthly_cents INTEGER,
|
||
price_yearly_cents INTEGER,
|
||
stripe_price_id_monthly TEXT,
|
||
stripe_price_id_yearly TEXT,
|
||
active BOOLEAN NOT NULL DEFAULT true,
|
||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
CREATE TABLE club_subscriptions (
|
||
id SERIAL PRIMARY KEY,
|
||
club_id INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
|
||
plan_id TEXT NOT NULL REFERENCES club_plans(id),
|
||
status TEXT NOT NULL DEFAULT 'active', -- active|trial|past_due|cancelled
|
||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
ends_at TIMESTAMPTZ,
|
||
trial_ends_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||
UNIQUE (club_id) -- ein aktiver Plan pro Verein (v1)
|
||
);
|
||
|
||
CREATE TABLE club_plan_limits (
|
||
id SERIAL PRIMARY KEY,
|
||
plan_id TEXT NOT NULL REFERENCES club_plans(id) ON DELETE CASCADE,
|
||
feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
|
||
limit_value INTEGER, -- NULL=∞, 0=deaktiviert
|
||
UNIQUE (plan_id, feature_id)
|
||
);
|
||
```
|
||
|
||
### 5.3 Overrides, Grants, Verbrauch
|
||
|
||
```sql
|
||
CREATE TABLE club_feature_overrides (
|
||
id SERIAL PRIMARY KEY,
|
||
club_id INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
|
||
feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
|
||
limit_value INTEGER NOT NULL,
|
||
reason TEXT,
|
||
set_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
UNIQUE (club_id, feature_id)
|
||
);
|
||
|
||
CREATE TABLE club_access_grants (
|
||
id SERIAL PRIMARY KEY,
|
||
club_id INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
|
||
plan_id TEXT REFERENCES club_plans(id),
|
||
feature_id TEXT REFERENCES features(id), -- optional Einzel-Feature
|
||
grant_limit INTEGER,
|
||
starts_at TIMESTAMPTZ NOT NULL,
|
||
ends_at TIMESTAMPTZ NOT NULL,
|
||
reason TEXT,
|
||
created_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE TABLE club_feature_usage (
|
||
id SERIAL PRIMARY KEY,
|
||
club_id INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
|
||
feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
|
||
usage_count INTEGER NOT NULL DEFAULT 0,
|
||
reset_at TIMESTAMPTZ,
|
||
last_used_at TIMESTAMPTZ,
|
||
UNIQUE (club_id, feature_id)
|
||
);
|
||
|
||
-- Optional: Attribution / Fairness / Audit
|
||
CREATE TABLE club_feature_usage_events (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
club_id INT NOT NULL,
|
||
feature_id TEXT NOT NULL,
|
||
profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
|
||
action TEXT NOT NULL, -- 'ai_suggest', 'exercise_create', ...
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### 5.4 Capabilities (Rollen — Kurzreferenz)
|
||
|
||
Siehe `CAPABILITY_CATALOG.v1.md` für IDs. Tabellen:
|
||
|
||
```sql
|
||
CREATE TABLE capabilities (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
description TEXT,
|
||
domain TEXT NOT NULL,
|
||
min_account_state TEXT NOT NULL DEFAULT 'active_member',
|
||
linked_feature_id TEXT REFERENCES features(id), -- optional Kontingent
|
||
active BOOLEAN NOT NULL DEFAULT true
|
||
);
|
||
|
||
CREATE TABLE club_role_capability_grants (
|
||
role_code TEXT NOT NULL, -- club_admin, trainer, ...
|
||
capability_id TEXT NOT NULL REFERENCES capabilities(id) ON DELETE CASCADE,
|
||
PRIMARY KEY (role_code, capability_id)
|
||
);
|
||
|
||
CREATE TABLE portal_role_capability_grants (
|
||
portal_role TEXT NOT NULL, -- admin, superadmin
|
||
capability_id TEXT NOT NULL REFERENCES capabilities(id) ON DELETE CASCADE,
|
||
PRIMARY KEY (portal_role, capability_id)
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Shinkan Feature-Katalog (Seed v1)
|
||
|
||
Übernahme aus `001_auth_membership.sql` + Ist-Endpoints, angereichert:
|
||
|
||
| feature_id | category | limit_type | reset_period | enforcement_subject | Default Free | Beschreibung |
|
||
|------------|----------|------------|--------------|---------------------|--------------|--------------|
|
||
| `exercises` | content | count | never | club | 100 | Anzahl Übungen im Verein (Bestand) |
|
||
| `exercise_media` | content | count | monthly | club | 20 | Medien-Uploads / Monat |
|
||
| `training_units` | planning | count | monthly | club | 40 | Geplante/durchgeführte Einheiten |
|
||
| `training_programs` | planning | count | never | club | 5 | Module + Rahmenprogramme (kombiniert v1) |
|
||
| `training_groups` | org | count | never | club | 10 | Trainingsgruppen |
|
||
| `active_members` | org | count | never | club | 25 | Aktive Mitglieder |
|
||
| `ai_calls` | ai | count | monthly | club | 0 | KI-Aufrufe (Suggest, Regenerate, Planung) |
|
||
| `ai_pipeline` | ai | boolean | never | club | 0 | Erweiterte KI-Pipelines (Batch, später) |
|
||
| `wiki_import` | integration | boolean | never | portal | 0 | MediaWiki-Import (Superadmin) |
|
||
| `data_export` | integration | boolean | never | club | 0 | Export-Funktionen (wenn eingeführt) |
|
||
|
||
**Hinweis:** Free-Defaults sind Produktentscheidung — Tabelle dient Implementierung.
|
||
|
||
### 6.1 Beispiel-Pläne (Seed)
|
||
|
||
| plan_id | ai_calls/Monat | exercises | active_members |
|
||
|---------|----------------|-----------|----------------|
|
||
| `free` | 0 | 100 | 25 |
|
||
| `verein_starter` | 30 | 500 | 80 |
|
||
| `verein_pro` | 200 | NULL (∞) | NULL |
|
||
| `pilot` | 100 | NULL | NULL |
|
||
|
||
Jeder Verein erhält bei Anlage durch Superadmin initial `club_subscriptions.plan_id = 'free'` (oder `pilot`).
|
||
|
||
---
|
||
|
||
## 7. Auflösungslogik
|
||
|
||
### 7.1 Effektiver Vereinsplan
|
||
|
||
```python
|
||
def get_effective_club_plan(cur, club_id: int) -> str:
|
||
"""
|
||
1. Aktiver club_access_grants mit plan_id (höchste Priorität, Zeitfenster)
|
||
2. club_subscriptions.status == 'active' → plan_id
|
||
3. Fallback 'free'
|
||
"""
|
||
```
|
||
|
||
### 7.2 Feature-Limit (analog Mitai `check_feature_access`)
|
||
|
||
```python
|
||
def check_club_feature_access(
|
||
cur,
|
||
club_id: int,
|
||
feature_id: str,
|
||
*,
|
||
profile_id: int | None = None, # nur für Logging / optionale Profil-Boni später
|
||
) -> dict:
|
||
"""
|
||
Priorität:
|
||
1. club_feature_overrides (club_id, feature_id)
|
||
2. club_plan_limits für get_effective_club_plan(club_id)
|
||
3. features.default_limit
|
||
|
||
Auswertung:
|
||
- limit_type boolean: limit_value == 1
|
||
- limit_type count: used < limit (club_feature_usage, reset beachten)
|
||
|
||
Returns: { allowed, limit, used, remaining, reason, reset_at }
|
||
"""
|
||
```
|
||
|
||
### 7.3 Vollständige Request-Kette
|
||
|
||
```
|
||
1. require_auth
|
||
2. assert_account_state(min_state) # unverified / verified_pending_club / active_member
|
||
3. get_tenant_context
|
||
4. assert_capability(tenant, cap_id) # Rollen-Achse
|
||
5. assert_content_governance(...) # nur bei Objekt-Endpoints
|
||
6. check_club_feature_access(club_id, feature_id)
|
||
7. … Business-Logik …
|
||
8. consume_club_feature_with_usage(…) + merge_feature_usage_into_response(payload, usage)
|
||
# Standard: zählen, JSON-Log phase=consume, feature_usage in Response
|
||
9. optional: club_feature_usage_events (profile_id, action)
|
||
```
|
||
|
||
**Response-Standard (alle Consume-Endpoints):** JSON-Feld `feature_usage` — Map `feature_id → { allowed, used, limit, remaining, reason, … }` wie `GET /me/entitlements`. Frontend: `request()` synchronisiert Entitlements automatisch (`featureUsageSync.js`); UI-Komponenten brauchen keinen Einzelcode.
|
||
|
||
### 7.4 Wer zählt als Verbrauch?
|
||
|
||
| Aktion | increment | Subjekt |
|
||
|--------|-----------|---------|
|
||
| `POST /exercises` (neu) | `exercises` | `club_id` des Objekts oder `effective_club_id` |
|
||
| Medien-Upload | `exercise_media` | Verein des Mediums |
|
||
| KI Suggest/Regenerate | `ai_calls` | `effective_club_id` |
|
||
| Mitglied hinzufügen | `active_members` | Ziel-`club_id` |
|
||
| Trainingsgruppe anlegen | `training_groups` | `club_id` |
|
||
|
||
**Mitai-Regel:** Counter **nicht** bei UPDATE/DELETE erhöhen.
|
||
|
||
---
|
||
|
||
## 8. API-Oberfläche
|
||
|
||
### 8.1 Nutzer / Vereinsadmin
|
||
|
||
```
|
||
GET /api/clubs/{club_id}/entitlements
|
||
```
|
||
|
||
Kombiniert Capabilities + Feature-Kontingente (siehe `CAPABILITY_CATALOG.v1.md` §7.1).
|
||
|
||
```
|
||
GET /api/me/entitlements?club_id=12
|
||
```
|
||
|
||
Bequemer Alias für aktiven Verein.
|
||
|
||
### 8.2 Superadmin / Plattform
|
||
|
||
| Endpoint | Zweck |
|
||
|----------|-------|
|
||
| `GET/PUT /api/admin/club-plans` | Plan-CRUD |
|
||
| `GET/PUT /api/admin/club-plan-limits` | Matrix |
|
||
| `GET/PUT /api/admin/clubs/{id}/subscription` | Verein-Abo |
|
||
| `GET/PUT /api/admin/clubs/{id}/feature-overrides` | Sonderkontingente |
|
||
| `POST /api/admin/clubs/{id}/access-grants` | Trial/Promo |
|
||
|
||
Vorbild UI: Mitai `AdminTierLimitsPage.jsx`, `AdminUserRestrictionsPage.jsx` → Vereins-Kontext.
|
||
|
||
### 8.3 Geplant: Vereinsgründung
|
||
|
||
```
|
||
POST /api/club-creation-requests # Nutzer (verified_pending_club)
|
||
GET /api/admin/club-creation-requests
|
||
POST /api/admin/club-creation-requests/{id}/approve # legt club + subscription an
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Vier-Phasen-Rollout (aus Mitai)
|
||
|
||
| Phase | Shinkan-Aktivität | Nutzer sichtbar? |
|
||
|-------|-------------------|------------------|
|
||
| **0** | Schema-Migration, Seed `features` + `club_plans`, Drift `001` bereinigen | Nein |
|
||
| **1** | Account-Gates + Capability-Grants (ohne Limits) | Onboarding-Hinweise |
|
||
| **2** | `check_club_feature_access` — **nur JSON-Log** (`feature_logger` analog Mitai) | Nein |
|
||
| **3** | `GET …/entitlements` + UsageBadge im UI | Ja (Kontingent-Anzeige) |
|
||
| **4** | HTTP 403 bei Limit + `increment` | Ja (Hard-Block) |
|
||
|
||
**Reihenfolge innerhalb Phase 4:** zuerst `ai_calls`, dann `exercise_media`, dann Bestands-Limits (`exercises`, `active_members`).
|
||
|
||
---
|
||
|
||
## 10. CI / Test-Isolation (Betrieb)
|
||
|
||
Unabhängig vom Membership-System — **Pflicht** wegen Prod-Vorfälle (`access_layer_it_*@test.local`):
|
||
|
||
| Regel | Umsetzung |
|
||
|-------|-----------|
|
||
| Integrationstests nie gegen Prod-DB | Eigene Test-DB oder Job-Postgres in Gitea |
|
||
| `ENVIRONMENT=production` + `ALLOW_INTEGRATION_TESTS` | Default `0`, Tests abbrechen |
|
||
| Test-Accounts | E-Mail `@test.local` oder `profiles.is_test_account` |
|
||
| Cleanup | Fixture-`finally` + Nightly-Job löscht Leichen |
|
||
|
||
`.gitea/workflows/test.yml`: pytest-backend gegen Deploy-DB **ersetzen** durch isolierte DB (eigenes Epic, parallel zu Membership).
|
||
|
||
---
|
||
|
||
## 11. Implementierungs-Roadmap (gesamt)
|
||
|
||
| Schritt | Deliverable | Membership-relevant |
|
||
|---------|-------------|-------------------|
|
||
| M0 | CI-Isolation + Prod-Cleanup-Runbook | Nein |
|
||
| M1 | Migration Feature-Schema v9c + `club_plans`/`club_subscriptions` (leer nutzbar) | **Ja** |
|
||
| M2 | `check_club_feature_access` + Seed Pläne | **Ja** |
|
||
| M3 | Account-Lifecycle + Capability-Grants | Capabilities |
|
||
| M4 | `GET /me/entitlements` | **Ja** |
|
||
| M5 | Enforcement `ai_calls` (Phase 4) | **Ja** |
|
||
| M6 | Admin Plan-Matrix UI | **Ja** |
|
||
| M7 | `club_creation_requests` | Prozess |
|
||
| M8 | Stripe / Rechnung | Später |
|
||
|
||
**Nach Produktentscheidungen 2026-06-06** (Details `MEMBERSHIP_RBAC_DECISIONS_2026-06.md` §4):
|
||
|
||
| Phase | Paket | Priorität |
|
||
|-------|--------|-----------|
|
||
| A | Onboarding-Gates vollständig (`verified_pending_club`) | **Als Nächstes** |
|
||
| B | M7 Vereinsgründung beantragen | hoch |
|
||
| C | M5 Hard-Block `ai_calls` | danach |
|
||
| D | M6 Superadmin-UI | danach |
|
||
| E | Systemrolle `co_trainer` + Frontend-Entitlements | v1 Rollen |
|
||
| F | Trainer-Member-Budgets (v2) | später |
|
||
|
||
---
|
||
|
||
## 12. Offene Produktentscheidungen
|
||
|
||
Vor M6 festlegen:
|
||
|
||
1. **Zählen `active_members`:** alle Mitglieder oder nur Rollen mit Planungsrecht?
|
||
2. **Soft-Limit vs. Hard-Stop:** Warnung bei 80 % oder sofort 403?
|
||
3. **Pilotverein:** eigener Plan `pilot` mit hohen Limits?
|
||
4. **KI-Fairness:** nur Vereinslimit oder zusätzlich Max pro Trainer/Monat?
|
||
5. **Offizielle Inhalte:** für `verified_pending_club` sichtbar oder gesperrt? → **entschieden: gesperrt** (`MEMBERSHIP_RBAC_DECISIONS_2026-06.md` §1.1)
|
||
6. **Portal `admin` vs. `superadmin`:** Wer darf Vereine anlegen? (Ziel: nur `superadmin` für Freigabe)
|
||
|
||
---
|
||
|
||
## 13. Referenzen
|
||
|
||
| Pfad | Inhalt |
|
||
|------|--------|
|
||
| `c:/dev/mitai-jinkendo/backend/migrations/v9c_subscription_system.sql` | Mitai-Schema-Vorlage |
|
||
| `c:/dev/mitai-jinkendo/.claude/docs/architecture/FEATURE_ENFORCEMENT.md` | 4-Phasen-Modell |
|
||
| `c:/dev/mitai-jinkendo/.claude/docs/technical/MEMBERSHIP_SYSTEM.md` | Mitai-Hauptdoku |
|
||
| `c:/dev/mitai-jinkendo/.claude/docs/technical/CENTRAL_SUBSCRIPTION_SYSTEM.md` | Jinkendo-Familie später |
|
||
| `CAPABILITY_CATALOG.v1.md` | Rollen & Capabilities |
|
||
| `MULTI_TENANCY_RBAC_ARCHITECTURE.md` §4.6 | Ursprüngliches Vereinsabo-Zielbild |
|
||
| `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` | Stufe E/F |
|
||
|
||
---
|
||
|
||
**Changelog**
|
||
|
||
- 2026-06-06: v1 — Mitai-Mapping, Ziel-Schema, Feature-Seed, Auflösungslogik, Rollout.
|