From 34145bfe3809bd42911e05cface7a0b8bf451a74 Mon Sep 17 00:00:00 2001 From: ValueOn AG
. → Feature-Views
+```
+
+### DATA Objekte
+```
+data.system. → System-Tabellen
+data.feature.. → Feature-Tabellen
+```
+
+### RESOURCE Objekte
+```
+resource.system. → System-Aktionen
+resource.feature.. → Feature-Aktionen
+```
+
+---
+
+## UI Mapping & Fehlerbehandlung
+
+### UI Code Registry
+
+Das UI definiert ein Mapping von Codes zu Komponenten:
+
+```typescript
+// frontend_nyla/src/config/pageRegistry.ts
+
+export const PAGE_REGISTRY: Record = {
+ // Static pages
+ 'page.system.home': {
+ component: HomePage,
+ icon: FaHome,
+ layout: 'default',
+ },
+ 'page.system.settings': {
+ component: SettingsPage,
+ icon: FaCog,
+ layout: 'default',
+ },
+ 'page.admin.users': {
+ component: AdminUsersPage,
+ icon: FaUsers,
+ layout: 'admin',
+ },
+
+ // Feature pages
+ 'page.feature.trustee.dashboard': {
+ component: TrusteeDashboardView,
+ icon: FaChartBar,
+ layout: 'feature',
+ },
+ 'page.feature.trustee.positions': {
+ component: TrusteePositionsView,
+ icon: FaListAlt,
+ layout: 'feature',
+ },
+ // ...
+};
+
+export const FEATURE_REGISTRY: Record = {
+ 'feature.trustee': {
+ icon: FaBriefcase,
+ color: '#e74c3c',
+ },
+ 'feature.realestate': {
+ icon: FaBuilding,
+ color: '#3498db',
+ },
+};
+```
+
+### Fehlerbehandlung
+
+Wenn ein `uiComponent` Code nicht gemappt werden kann:
+
+```typescript
+function renderNavItem(item: NavItem) {
+ const pageDef = PAGE_REGISTRY[item.uiComponent];
+
+ if (!pageDef) {
+ // Fehler sichtbar machen!
+ return (
+
+ ⚠️ Unbekannter Code: {item.uiComponent}
+ ObjectKey: {item.objectKey}
+
+ );
+ }
+
+ return (
+
+ {item.uiLabel}
+
+ );
+}
+```
+
+**Wichtig:** Fehler werden im Nav-Tree angezeigt, nicht versteckt! So werden Synchronisationsprobleme zwischen Gateway und UI sofort sichtbar.
+
+### Sortierung im UI
+
+Das UI sortiert die bereits vom Gateway sortierten Elemente NICHT neu. Die Sortierung erfolgt ausschliesslich im Gateway basierend auf:
+
+1. Default-Order aus Konfiguration
+2. User-Override aus Settings (falls vorhanden)
+
+```typescript
+// UI rendert in der Reihenfolge wie empfangen
+function renderBlock(block: Block) {
+ // items sind bereits sortiert
+ return block.items.map(item => renderNavItem(item));
+}
+```
+
+---
+
+## Implementierungsschritte
+
+### Phase 1: Gateway - Navigation Endpoint
+
+1. **Neuer Endpoint** `/api/navigation` erstellen
+2. **Static Blocks** aus `NAVIGATION_SECTIONS` generieren
+3. **Dynamic Block** aus Feature-Instanzen generieren
+4. **Permission-Filter** anwenden (nur sichtbare Elemente)
+5. **uiComponent Codes** gemäß Konvention setzen
+6. **Default-Order** für alle Elemente setzen
+
+### Phase 2: Gateway - Access Rules
+
+1. **TEMPLATE_ROLES** auf vollqualifizierte `objectKey` umstellen
+2. **Permission-Check** auf `objectKey` umstellen
+3. **Migration** bestehender Access Rules (falls nötig)
+
+### Phase 3: Gateway - User Settings
+
+1. **User Settings Modell** erweitern mit `navOrder`
+2. **API Endpoint** `PUT /api/user/settings/navOrder` erstellen
+3. **Merge-Logik** implementieren (User-Override → Default)
+4. **Sortierung** vor Response durchführen
+
+### Phase 4: Frontend
+
+1. **PAGE_REGISTRY** erstellen mit uiComponent → Component Mapping
+2. **FEATURE_REGISTRY** erstellen mit Feature-Code → Style Mapping
+3. **useNavigation Hook** auf neuen Endpoint umstellen
+4. **MandateNavigation** refactoren für neue Datenstruktur
+5. **Fehlerhandling** für unbekannte uiComponents implementieren
+
+### Phase 5: Frontend - Drag & Drop (Optional)
+
+1. **Drag & Drop UI** für Nav-Tree Sortierung
+2. **API Call** bei Order-Änderung
+3. **Optimistic Update** für schnelle UX
+
+### Phase 6: Cleanup
+
+1. **FEATURE_REGISTRY** (alte Version in mandate.ts) entfernen
+2. **Hardcodierte Navigation** im Frontend entfernen
+3. **Tests** für Synchronisation Gateway ↔ UI
+
+---
+
+## Beispiel: Vollständige Response
+
+```json
+{
+ "language": "de",
+ "blocks": [
+ {
+ "type": "static",
+ "id": "system",
+ "title": "SYSTEM",
+ "order": 10,
+ "items": [
+ {
+ "uiComponent": "page.system.home",
+ "uiLabel": "Übersicht",
+ "uiPath": "/",
+ "order": 10,
+ "objectKey": "ui.system.home"
+ },
+ {
+ "uiComponent": "page.system.settings",
+ "uiLabel": "Einstellungen",
+ "uiPath": "/settings",
+ "order": 20,
+ "objectKey": "ui.system.settings"
+ }
+ ]
+ },
+ {
+ "type": "static",
+ "id": "workflows",
+ "title": "WORKFLOWS",
+ "order": 20,
+ "items": [
+ {
+ "uiComponent": "page.system.playground",
+ "uiLabel": "Chat Playground",
+ "uiPath": "/workflows/playground",
+ "order": 10,
+ "objectKey": "ui.system.playground"
+ },
+ {
+ "uiComponent": "page.system.chats",
+ "uiLabel": "Chats",
+ "uiPath": "/workflows/list",
+ "order": 20,
+ "objectKey": "ui.system.chats"
+ }
+ ]
+ },
+ {
+ "type": "static",
+ "id": "basedata",
+ "title": "BASISDATEN",
+ "order": 30,
+ "items": [
+ {
+ "uiComponent": "page.system.prompts",
+ "uiLabel": "Prompts",
+ "uiPath": "/basedata/prompts",
+ "order": 10,
+ "objectKey": "ui.system.prompts"
+ },
+ {
+ "uiComponent": "page.system.files",
+ "uiLabel": "Dateien",
+ "uiPath": "/basedata/files",
+ "order": 20,
+ "objectKey": "ui.system.files"
+ }
+ ]
+ },
+ {
+ "type": "static",
+ "id": "admin",
+ "title": "ADMINISTRATION",
+ "order": 200,
+ "items": [
+ {
+ "uiComponent": "page.admin.users",
+ "uiLabel": "Benutzer",
+ "uiPath": "/admin/users",
+ "order": 10,
+ "objectKey": "ui.admin.users"
+ },
+ {
+ "uiComponent": "page.admin.mandates",
+ "uiLabel": "Mandanten",
+ "uiPath": "/admin/mandates",
+ "order": 20,
+ "objectKey": "ui.admin.mandates"
+ }
+ ]
+ },
+ {
+ "type": "dynamic",
+ "id": "features",
+ "title": "MEINE FEATURES",
+ "order": 100,
+ "mandates": [
+ {
+ "id": "5ce04434-c4ce-4269-9861-19ff7ebc2a1d",
+ "uiLabel": "SOHA Treuhand AG",
+ "order": 10,
+ "features": [
+ {
+ "uiComponent": "feature.trustee",
+ "uiLabel": "Treuhand",
+ "order": 10,
+ "instances": [
+ {
+ "id": "2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0",
+ "uiLabel": "ValueOn AG 2026",
+ "order": 10,
+ "views": [
+ {
+ "uiComponent": "page.feature.trustee.dashboard",
+ "uiLabel": "Übersicht",
+ "uiPath": "/mandates/5ce04434-c4ce-4269-9861-19ff7ebc2a1d/trustee/2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0/dashboard",
+ "order": 10,
+ "objectKey": "ui.feature.trustee.dashboard"
+ },
+ {
+ "uiComponent": "page.feature.trustee.positions",
+ "uiLabel": "Positionen",
+ "uiPath": "/mandates/5ce04434-c4ce-4269-9861-19ff7ebc2a1d/trustee/2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0/positions",
+ "order": 20,
+ "objectKey": "ui.feature.trustee.positions"
+ },
+ {
+ "uiComponent": "page.feature.trustee.documents",
+ "uiLabel": "Dokumente",
+ "uiPath": "/mandates/5ce04434-c4ce-4269-9861-19ff7ebc2a1d/trustee/2fc48f66-ad1b-4581-aa87-6aaa1e7c16e0/documents",
+ "order": 30,
+ "objectKey": "ui.feature.trustee.documents"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "abc123-mandate-2",
+ "uiLabel": "Partner AG",
+ "order": 20,
+ "features": [
+ {
+ "uiComponent": "feature.realestate",
+ "uiLabel": "Immobilien",
+ "order": 10,
+ "instances": [
+ {
+ "id": "def456-instance",
+ "uiLabel": "Objektstudien ZH",
+ "order": 10,
+ "views": [
+ {
+ "uiComponent": "page.feature.realestate.dashboard",
+ "uiLabel": "Übersicht",
+ "uiPath": "/mandates/abc123-mandate-2/realestate/def456-instance/dashboard",
+ "order": 10,
+ "objectKey": "ui.feature.realestate.dashboard"
+ },
+ {
+ "uiComponent": "page.feature.realestate.projects",
+ "uiLabel": "Projekte",
+ "uiPath": "/mandates/abc123-mandate-2/realestate/def456-instance/projects",
+ "order": 20,
+ "objectKey": "ui.feature.realestate.projects"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+```
+
+---
+
+## Vorteile dieses Ansatzes
+
+1. **Single Source of Truth**: Gateway definiert, was sichtbar ist
+2. **Keine Permission-Logik im UI**: Alles was kommt, wird gerendert
+3. **Flexibles UI**: Verschiedene UIs können dieselben Daten anders darstellen
+4. **Fehlertoleranz**: Synchronisationsprobleme werden sofort sichtbar
+5. **Saubere Trennung**: Daten (Gateway) vs. Darstellung (UI)
+6. **Testbarkeit**: API-Response kann einfach getestet werden
+7. **Dokumentation**: Codes und ObjectKeys sind selbstdokumentierend
+8. **Personalisierung**: User können Navigation individuell sortieren
+9. **Konsistente Sortierung**: Gateway sortiert zentral, UI respektiert die Reihenfolge
+
+---
+
+## Offene Fragen (Beantwortet)
+
+1. Soll der `dynamic` Block vor oder nach den `static` Blocks kommen? → **Diese initiale Reihenfolge: System, ``, workflows, basisdaten, migrate to features, administration**
+2. Braucht es eine Versionierung der API für Breaking Changes? → **Nein**
+3. Wie werden Feature-spezifische Sub-Navigationen behandelt (z.B. Tabs innerhalb einer View)? → **Tabs innerhalb einer View werden als separate Views behandelt**
+4. Soll der Default-Order-Abstand 10 sein (erlaubt Einfügen zwischen Elementen)? → **Ja**
+5. Soll die Order-Personalisierung auch für Admins pro Mandate/Feature-Instanz möglich sein (System-Default vs. User-Override)? → **Ja**
+
+---
+
+## Code-Analyse: IST-Zustand
+
+### Gateway (Backend)
+
+#### 1. `mainSystem.py` - Statische Navigation
+
+```python
+# IST: Items haben id, label, path, icon, objectKey
+# NEU: Brauchen uiComponent, uiLabel, uiPath, order (ohne icon)
+NAVIGATION_SECTIONS = [
+ {
+ "id": "system",
+ "order": 10, # ✓ Vorhanden
+ "items": [
+ {
+ "id": "home",
+ "objectKey": "ui.system.home", # ✓ Korrekt
+ "label": {"en": "Home", "de": "Übersicht"},
+ "icon": "FaHome", # ✗ Muss entfernt werden
+ "path": "/",
+ },
+ ]
+ }
+]
+```
+
+**Änderungen:**
+- `icon` entfernen (UI mappt selbst)
+- Item-Level `order` hinzufügen
+- Felder umbenennen für API Response
+
+#### 2. `routeSystem.py` - Navigation Endpoint
+
+```python
+# IST: GET /api/system/navigation
+# Gibt zurück: { "sections": [...], "language": "de" }
+
+# NEU: GET /api/navigation
+# Soll zurückgeben: { "blocks": [...], "language": "de" }
+```
+
+**Änderungen:**
+- Neuer kombinierter Endpoint
+- Static Blocks + Dynamic Block zusammenführen
+- Response-Struktur gemäss Konzept
+
+#### 3. `mainTrustee.py` - Feature Views
+
+```python
+# IST: TEMPLATE_ROLES verwendet Kurznamen
+TEMPLATE_ROLES = [
+ {
+ "roleLabel": "trustee-client",
+ "accessRules": [
+ {"context": "UI", "item": "dashboard", "view": True}, # ✗ Kurzname!
+ {"context": "UI", "item": "positions", "view": True}, # ✗ Kurzname!
+ ]
+ }
+]
+
+# ABER: UI_OBJECTS verwendet vollqualifizierte Namen
+UI_OBJECTS = [
+ {"objectKey": "ui.feature.trustee.dashboard", ...}, # ✓ Vollständig
+]
+```
+
+**Inkonsistenz gefunden!** `item` in AccessRules muss `objectKey` entsprechen.
+
+#### 4. `routeAdminFeatures.py` - Permission Logic
+
+```python
+# IST: _deriveViewPermissions ist TRUSTEE-spezifisch!
+def _deriveViewPermissions(permissions):
+ # Hard-coded: TrusteePosition → positions view
+ # Hard-coded: TrusteeDocument → documents view
+```
+
+**Problem:** Nicht generisch, funktioniert nur für Trustee.
+
+### Frontend (UI)
+
+#### 1. `MandateNavigation.tsx`
+
+```typescript
+// IST: Zwei separate Datenquellen
+const { sections } = useNavigation(); // Static von API
+const mandates = useMandates(); // Dynamic von API
+
+// IST: FEATURE_ICONS definiert im Frontend
+const FEATURE_ICONS = { trustee: };
+```
+
+**Problem:** Navigation wird im Frontend zusammengebaut, nicht vom Backend.
+
+#### 2. `mandate.ts` - FEATURE_REGISTRY
+
+```typescript
+// IST: Doppelte Definition von Views!
+export const FEATURE_REGISTRY = {
+ trustee: {
+ views: [
+ { code: 'dashboard', label: {...}, path: 'dashboard' }, // ✗ Kurzname
+ { code: 'positions', label: {...}, path: 'positions' }, // ✗ Kurzname
+ ]
+ }
+};
+```
+
+**Duplizierung:** Views sind in `mainTrustee.py` UND `mandate.ts` definiert!
+
+---
+
+## Impact-Analyse
+
+### Phase 1: Gateway - Navigation Endpoint
+
+| Datei | Änderungstyp | Aufwand | Breaking |
+|-------|--------------|---------|----------|
+| `routeSystem.py` | Neu/Refactor | Hoch | Ja |
+| `mainSystem.py` | Update | Mittel | Nein |
+| `routeAdminFeatures.py` | Integrieren | Hoch | Nein |
+
+**Konkrete Aufgaben:**
+1. Neuen Endpoint `GET /api/navigation` erstellen
+2. Static Blocks aus `NAVIGATION_SECTIONS` generieren
+3. Dynamic Block aus `_getMyFeatureInstances`-Logik generieren
+4. Permission-Filter auf beide anwenden
+5. `icon` aus Response entfernen
+6. Felder umbenennen: `label→uiLabel`, `path→uiPath`, neu `uiComponent`
+7. `order` auf Item-Level hinzufügen
+
+### Phase 2: Gateway - Access Rules Migration
+
+| Datei | Änderungstyp | Aufwand | Breaking |
+|-------|--------------|---------|----------|
+| `mainTrustee.py` | Update | Mittel | Ja (DB) |
+| `mainRealEstate.py` | Update | Mittel | Ja (DB) |
+| Migration Script | Neu | Mittel | - |
+
+**Konkrete Aufgaben:**
+1. `TEMPLATE_ROLES.item` auf vollqualifizierte ObjectKeys umstellen
+2. Migration: Bestehende AccessRules in DB aktualisieren
+3. Permission-Check in `_checkUiPermission` anpassen
+
+### Phase 3: Gateway - User Settings
+
+| Datei | Änderungstyp | Aufwand | Breaking |
+|-------|--------------|---------|----------|
+| `datamodelUam.py` | Erweitern | Gering | Nein |
+| `routeSystem.py` | Endpoint | Gering | Nein |
+
+**Konkrete Aufgaben:**
+1. `UserSettings` Tabelle oder JSON-Feld in User
+2. `PUT /api/user/settings/navOrder` Endpoint
+3. Merge-Logik in Navigation-Endpoint
+
+### Phase 4: Frontend
+
+| Datei | Änderungstyp | Aufwand | Breaking |
+|-------|--------------|---------|----------|
+| `pageRegistry.ts` | Neu | Hoch | - |
+| `useNavigation.ts` | Refactor | Mittel | Ja |
+| `MandateNavigation.tsx` | Refactor | Hoch | Ja |
+| `mandate.ts` | Cleanup | Gering | Ja |
+
+**Konkrete Aufgaben:**
+1. `PAGE_REGISTRY` erstellen mit uiComponent → Component Mapping
+2. `FEATURE_REGISTRY` mit `feature.*` Codes
+3. `useNavigation` für neuen Endpoint anpassen
+4. `MandateNavigation` für Blocks-Struktur refactoren
+5. Error-Handling für unbekannte uiComponents
+
+---
+
+## Kritische Punkte
+
+### 1. Breaking Changes
+Die Umstellung erfordert **simultane Änderungen** in Gateway UND Frontend. Empfehlung:
+- Feature-Flag für neuen Endpoint
+- Alte Endpoints temporär behalten
+- Frontend schrittweise migrieren
+
+### 2. Datenbank-Migration
+Bestehende AccessRules haben `item="dashboard"`, neu soll `item="ui.feature.trustee.dashboard"` sein.
+```sql
+-- Beispiel-Migration
+UPDATE access_rules
+SET item = 'ui.feature.trustee.' || item
+WHERE role_id IN (SELECT id FROM roles WHERE feature_code = 'trustee')
+ AND context = 'UI'
+ AND item IS NOT NULL;
+```
+
+### 3. Performance
+Navigation mit vielen Instanzen kann langsam werden. Empfehlungen:
+- Response cachen pro User
+- Cache invalidieren bei Permission-Änderungen
+- Lazy-Loading für sehr grosse Deployments prüfen
+
+### 4. Feature-spezifische View-Ableitung
+`_deriveViewPermissions` ist Trustee-spezifisch. Optionen:
+- **A)** Jedes Feature definiert seine Ableitungslogik
+- **B)** Keine Ableitung, nur explizite UI-Rules
+- **C)** Generische Mapping-Konfiguration pro Feature
+
+**Empfehlung:** Option B - Explizite UI-Rules sind klarer und testbarer.
+
+---
+
+## Empfohlene Reihenfolge
+
+1. **Schritt 1:** `PAGE_REGISTRY` im Frontend erstellen (unabhängig)
+2. **Schritt 2:** Neuen `/api/navigation` Endpoint mit Feature-Flag
+3. **Schritt 3:** Frontend auf neuen Endpoint umstellen
+4. **Schritt 4:** AccessRules-Migration
+5. **Schritt 5:** User-Settings für Order
+6. **Schritt 6:** Alte Endpoints/Code entfernen
diff --git a/implementation/RBAC-AccessRules-Refactoring.md b/implementation/RBAC-AccessRules-Refactoring.md
new file mode 100644
index 0000000..55a5f4c
--- /dev/null
+++ b/implementation/RBAC-AccessRules-Refactoring.md
@@ -0,0 +1,981 @@
+# RBAC Access Rules Editor - Refactoring Vorschlag
+
+## Übersicht
+
+Dieses Dokument beschreibt die geplante Überarbeitung der RBAC Access Rules UI-Komponenten basierend auf drei Hauptanforderungen:
+
+1. **System-Rollen bearbeitbar machen** - AccessRules für System-Rollen editierbar
+2. **Checkbox-basiertes UI** - Kompakteres Layout mit Checkboxen statt Dropdowns
+3. **Dot-Notation & Objekt-Katalog** - Verfügbare Objekte als Dropdown auswählbar
+
+---
+
+## 1. System-Rollen mit bearbeitbaren AccessRules
+
+### Ist-Zustand
+
+Aktuell in `AdminMandateRolePermissionsPage.tsx`:
+```typescript
+
+```
+
+Die `readOnly`-Prop wird basierend auf `isSystemRole` gesetzt, was dazu führt, dass AccessRules für System-Rollen weder angezeigt noch bearbeitet werden können.
+
+### Soll-Zustand
+
+- **Rollen selbst** (roleLabel, description) bleiben geschützt für System-Rollen
+- **AccessRules** sind für alle Rollen bearbeitbar (inkl. System-Rollen)
+- Zugriffskontrolle erfolgt über RBAC selbst (`rbac.rules.manage` Permission)
+
+### Implementierung
+
+#### Option A: Separate Prop für Rollen-Protection
+
+```typescript
+// AdminMandateRolePermissionsPage.tsx
+
+```
+
+#### Option B: Immer bearbeitbar (empfohlen)
+
+Entferne die `readOnly`-Logic basierend auf `isSystemRole`:
+
+```typescript
+// AdminMandateRolePermissionsPage.tsx
+
+```
+
+Die Zugriffskontrolle wird durch Backend-RBAC sichergestellt (nur SysAdmin kann AccessRules ändern).
+
+### Betroffene Dateien
+
+| Datei | Änderung |
+|-------|----------|
+| `frontend_nyla/src/pages/admin/AdminMandateRolePermissionsPage.tsx` | `readOnly={false}` setzen |
+
+---
+
+## 2. Checkbox-basiertes Kompakt-Layout
+
+### Ist-Zustand
+
+Aktuell werden AccessLevels (n/m/g/a) über Dropdown-Selects ausgewählt:
+
+```
+┌──────────────────────────────────────────────────────────────────┐
+│ 📊 TrusteeContract [🗑] │
+├──────────────────────────────────────────────────────────────────┤
+│ VIEW READ CREATE UPDATE DELETE │
+│ [✓] [Gruppe▼] [Eigene▼] [Eigene▼] [Keine▼] │
+└──────────────────────────────────────────────────────────────────┘
+```
+
+**Probleme:**
+- Dropdowns benötigen Klicks zum Öffnen
+- Nicht auf einen Blick erkennbar welche Berechtigungen gesetzt sind
+- Mehrere Regeln brauchen viel vertikalen Platz
+
+### Soll-Zustand
+
+Kompaktes Checkbox-Grid in einer Zeile pro Objekt:
+
+```
+┌────────────────────────────────────────────────────────────────────────────┐
+│ OBJEKT │ VIEW │ EIGENE │ GRUPPE │ ALLE │
+│ │ │ C R U D │ C R U D │C R U D│
+├────────────────────────────────────────────────────────────────────────────┤
+│ data.TrusteeContract │ [✓] │ [✓] [✓] [✓] [ ]│[ ] [✓] [ ] [ ]│[ ][ ][ ][ ]│
+│ data.TrusteePosition │ [✓] │ [✓] [✓] [ ] [ ]│[✓] [✓] [✓] [ ]│[ ][ ][ ][ ]│
+│ ui.feature.trustee.* │ [✓] │ - - - - │ - - - - │ - - - - │
+└────────────────────────────────────────────────────────────────────────────┘
+```
+
+**Vorteile:**
+- Alle Berechtigungen auf einen Blick sichtbar
+- Schnelle Toggle-Aktionen mit einzelnem Klick
+- Kompakter - mehr Regeln pro Bildschirm
+- Intuitive Matrix-Darstellung (bekannt von Unix-Permissions)
+
+### Neue Komponenten-Struktur
+
+```
+AccessRulesEditor/
+├── AccessRulesEditor.tsx # Haupt-Container
+├── AccessRulesTable.tsx # Tabellen-basierte Darstellung (NEU)
+├── AccessRuleRow.tsx # Eine Zeile = ein AccessRule (NEU)
+├── AccessLevelCheckboxGroup.tsx # Checkbox-Gruppe für m/g/a (NEU)
+└── AccessRules.module.css # Styles
+```
+
+### Neue Komponente: `AccessRulesTable.tsx`
+
+```typescript
+interface AccessRulesTableProps {
+ rules: AccessRule[];
+ context: RuleContext;
+ readOnly?: boolean;
+ onUpdate: (ruleId: string, updates: Partial) => void;
+ onDelete: (ruleId: string) => void;
+}
+
+const AccessRulesTable: React.FC = ({
+ rules,
+ context,
+ readOnly,
+ onUpdate,
+ onDelete,
+}) => {
+ const isDataContext = context === 'DATA';
+
+ return (
+
+
+
+ Objekt (Dot-Notation)
+ View
+ {isDataContext && (
+ <>
+ Eigene (m)
+ Gruppe (g)
+ Alle (a)
+ >
+ )}
+
+
+ {isDataContext && (
+
+
+
+ C R U D
+ C R U D
+ C R U D
+
+
+ )}
+
+
+ {rules.map(rule => (
+
+ ))}
+
+
+ );
+};
+```
+
+### Neue Komponente: `AccessRuleRow.tsx`
+
+```typescript
+interface AccessRuleRowProps {
+ rule: AccessRule;
+ isDataContext: boolean;
+ readOnly?: boolean;
+ onUpdate: (ruleId: string, updates: Partial) => void;
+ onDelete: (ruleId: string) => void;
+}
+
+const AccessRuleRow: React.FC = ({
+ rule,
+ isDataContext,
+ readOnly,
+ onUpdate,
+ onDelete,
+}) => {
+ // Hilfsfunktion: Prüft ob Level mindestens X erreicht
+ const hasLevel = (level: AccessLevel | null, minLevel: 'm' | 'g' | 'a'): boolean => {
+ if (!level || level === 'n') return false;
+ const hierarchy = ['n', 'm', 'g', 'a'];
+ return hierarchy.indexOf(level) >= hierarchy.indexOf(minLevel);
+ };
+
+ // Hilfsfunktion: Setzt Level basierend auf Checkbox-Änderung
+ const toggleLevel = (
+ field: 'read' | 'create' | 'update' | 'delete',
+ targetLevel: 'm' | 'g' | 'a',
+ checked: boolean
+ ) => {
+ const currentLevel = rule[field] || 'n';
+ let newLevel: AccessLevel;
+
+ if (checked) {
+ // Aktiviere mindestens dieses Level
+ newLevel = targetLevel;
+ } else {
+ // Deaktiviere dieses Level, setze auf nächst-niedrigeres
+ const hierarchy = ['n', 'm', 'g', 'a'];
+ const targetIndex = hierarchy.indexOf(targetLevel);
+ newLevel = hierarchy[targetIndex - 1] as AccessLevel || 'n';
+ }
+
+ onUpdate(rule.id, { [field]: newLevel });
+ };
+
+ return (
+
+ {/* Objekt-Name in Dot-Notation */}
+
+ {rule.item || '(global)'}
+
+
+ {/* View Checkbox */}
+
+ onUpdate(rule.id, { view: e.target.checked })}
+ disabled={readOnly}
+ />
+
+
+ {/* CRUD Checkboxen für DATA-Kontext */}
+ {isDataContext && (
+ <>
+ {/* Eigene (m) */}
+ {['create', 'read', 'update', 'delete'].map(op => (
+
+ toggleLevel(op as any, 'm', e.target.checked)}
+ disabled={readOnly}
+ />
+
+ ))}
+
+ {/* Gruppe (g) */}
+ {['create', 'read', 'update', 'delete'].map(op => (
+
+ toggleLevel(op as any, 'g', e.target.checked)}
+ disabled={readOnly}
+ />
+
+ ))}
+
+ {/* Alle (a) */}
+ {['create', 'read', 'update', 'delete'].map(op => (
+
+ toggleLevel(op as any, 'a', e.target.checked)}
+ disabled={readOnly}
+ />
+
+ ))}
+ >
+ )}
+
+ {/* Löschen-Button */}
+
+ {!readOnly && (
+
+ )}
+
+
+ );
+};
+```
+
+### CSS für Tabellen-Layout
+
+```css
+/* AccessRules.module.css - Erweiterungen */
+
+.accessRulesTable {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 0.875rem;
+}
+
+.accessRulesTable th,
+.accessRulesTable td {
+ padding: 0.5rem 0.375rem;
+ border-bottom: 1px solid var(--border-color);
+ text-align: center;
+}
+
+.accessRulesTable th {
+ background: var(--bg-secondary);
+ font-weight: 600;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ color: var(--text-secondary);
+}
+
+.colObject {
+ text-align: left !important;
+ min-width: 200px;
+}
+
+.colView {
+ width: 50px;
+}
+
+.colGroup {
+ border-left: 2px solid var(--border-color);
+}
+
+.subHeader th {
+ font-size: 0.6875rem;
+ padding: 0.25rem;
+ background: var(--bg-tertiary);
+}
+
+.objectCell {
+ text-align: left !important;
+}
+
+.objectCell code {
+ font-family: 'Monaco', 'Menlo', monospace;
+ font-size: 0.8125rem;
+ background: var(--bg-tertiary);
+ padding: 0.125rem 0.375rem;
+ border-radius: 3px;
+}
+
+.checkboxCell {
+ width: 32px;
+}
+
+.checkboxCell input[type="checkbox"] {
+ width: 16px;
+ height: 16px;
+ cursor: pointer;
+ accent-color: var(--primary-color);
+}
+
+.actionsCell {
+ width: 40px;
+}
+
+.deleteButton {
+ padding: 0.25rem;
+ background: transparent;
+ border: none;
+ color: var(--text-tertiary);
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.deleteButton:hover {
+ background: #fed7d7;
+ color: #c53030;
+}
+```
+
+### Betroffene Dateien
+
+| Datei | Änderung |
+|-------|----------|
+| `frontend_nyla/src/components/AccessRules/AccessRulesTable.tsx` | NEU erstellen |
+| `frontend_nyla/src/components/AccessRules/AccessRuleRow.tsx` | NEU erstellen |
+| `frontend_nyla/src/components/AccessRules/AccessRulesEditor.tsx` | Integriere `AccessRulesTable` |
+| `frontend_nyla/src/components/AccessRules/AccessRules.module.css` | Tabellen-Styles hinzufügen |
+| `frontend_nyla/src/components/AccessRules/index.ts` | Exports aktualisieren |
+
+---
+
+## 3. Dot-Notation & RBAC-Objekt-Katalog
+
+### Ist-Zustand
+
+Aktuell wird das `item`-Feld als Freitext eingegeben:
+
+```typescript
+// AddRuleForm.tsx (aktuell)
+ setItem(e.target.value)}
+ placeholder="z.B. TrusteeContract oder TrusteeContract.salary"
+/>
+```
+
+**Probleme:**
+- Benutzer muss gültige Objekt-Namen kennen
+- Keine Validierung gegen registrierte Objekte
+- Inkonsistente Schreibweisen möglich
+- Keine Übersicht der verfügbaren Objekte
+
+### Backend: RBAC Catalog Service
+
+Der `RbacCatalogService` (`gateway/modules/security/rbacCatalog.py`) registriert bereits alle verfügbaren Objekte:
+
+```python
+class RbacCatalogService:
+ def __init__(self):
+ self._uiObjects: Dict[str, Dict[str, Any]] = {}
+ self._resourceObjects: Dict[str, Dict[str, Any]] = {}
+ self._dataObjects: Dict[str, Dict[str, Any]] = {} # ← NEU
+
+ def getUiObjects(self, featureCode: Optional[str] = None) -> List[Dict[str, Any]]:
+ # Gibt UI-Objekte zurück (z.B. "ui.feature.trustee.dashboard")
+
+ def getResourceObjects(self, featureCode: Optional[str] = None) -> List[Dict[str, Any]]:
+ # Gibt Resource-Objekte zurück (z.B. "resource.feature.trustee.documents.create")
+```
+
+**Fehlt:** DATA-Objekte werden nicht im Katalog registriert!
+
+### Soll-Zustand
+
+1. **Backend:** Neuer API-Endpoint zum Abrufen verfügbarer RBAC-Objekte
+2. **Backend:** DATA-Objekte (Tabellen/Entitäten) im Katalog registrieren
+3. **Frontend:** Dropdown zur Auswahl aus verfügbaren Objekten
+4. **Frontend:** Konsistente Dot-Notation für alle Objekte
+
+### Backend-Implementierung
+
+#### 1. DATA-Objekte im Katalog registrieren
+
+Erweitere `rbacCatalog.py`:
+
+```python
+# gateway/modules/security/rbacCatalog.py
+
+class RbacCatalogService:
+ def __init__(self):
+ self._uiObjects: Dict[str, Dict[str, Any]] = {}
+ self._resourceObjects: Dict[str, Dict[str, Any]] = {}
+ self._dataObjects: Dict[str, Dict[str, Any]] = {} # NEU
+
+ def registerDataObject(
+ self,
+ featureCode: str,
+ objectKey: str,
+ label: Dict[str, str],
+ meta: Optional[Dict[str, Any]] = None
+ ) -> bool:
+ """Register a DATA object (table/entity) for a feature."""
+ try:
+ self._dataObjects[objectKey] = {
+ "objectKey": objectKey,
+ "featureCode": featureCode,
+ "label": label,
+ "meta": meta or {},
+ "type": "DATA"
+ }
+ return True
+ except Exception as e:
+ logger.error(f"Failed to register DATA object {objectKey}: {e}")
+ return False
+
+ def getDataObjects(self, featureCode: Optional[str] = None) -> List[Dict[str, Any]]:
+ """Get all DATA objects, optionally filtered by feature."""
+ if featureCode:
+ return [obj for obj in self._dataObjects.values()
+ if obj["featureCode"] == featureCode]
+ return list(self._dataObjects.values())
+
+ def getAllCatalogObjects(self, featureCode: Optional[str] = None) -> Dict[str, List[Dict[str, Any]]]:
+ """Get all catalog objects grouped by type."""
+ return {
+ "DATA": self.getDataObjects(featureCode),
+ "UI": self.getUiObjects(featureCode),
+ "RESOURCE": self.getResourceObjects(featureCode)
+ }
+```
+
+#### 2. DATA-Objekte in Feature registrieren
+
+Erweitere `mainTrustee.py`:
+
+```python
+# gateway/modules/features/trustee/mainTrustee.py
+
+# DATA Objects for RBAC catalog (Tabellen/Entitäten)
+DATA_OBJECTS = [
+ {
+ "objectKey": "data.feature.trustee.TrusteeContract",
+ "label": {"en": "Contract", "de": "Vertrag", "fr": "Contrat"},
+ "meta": {"table": "TrusteeContract", "fields": ["id", "name", "salary", ...]}
+ },
+ {
+ "objectKey": "data.feature.trustee.TrusteePosition",
+ "label": {"en": "Position", "de": "Position", "fr": "Position"},
+ "meta": {"table": "TrusteePosition", "fields": ["id", "label", ...]}
+ },
+ {
+ "objectKey": "data.feature.trustee.TrusteeDocument",
+ "label": {"en": "Document", "de": "Dokument", "fr": "Document"},
+ "meta": {"table": "TrusteeDocument", "fields": ["id", "filename", ...]}
+ },
+]
+
+def registerFeature(catalogService) -> bool:
+ # ... bestehende UI/Resource Registrierung ...
+
+ # NEU: DATA-Objekte registrieren
+ for dataObj in DATA_OBJECTS:
+ catalogService.registerDataObject(
+ featureCode=FEATURE_CODE,
+ objectKey=dataObj["objectKey"],
+ label=dataObj["label"],
+ meta=dataObj.get("meta")
+ )
+```
+
+#### 3. Neuer API-Endpoint für Katalog-Objekte
+
+Erweitere `routeAdminRbacRules.py`:
+
+```python
+# gateway/modules/routes/routeAdminRbacRules.py
+
+from modules.security.rbacCatalog import getCatalogService
+
+@router.get("/catalog/objects", response_model=Dict[str, Any])
+@limiter.limit("60/minute")
+async def get_catalog_objects(
+ request: Request,
+ context: Optional[str] = Query(None, description="Filter by context (DATA, UI, RESOURCE)"),
+ featureCode: Optional[str] = Query(None, description="Filter by feature code"),
+ currentUser: User = Depends(requireSysAdmin)
+) -> Dict[str, Any]:
+ """
+ Get available RBAC catalog objects.
+ Returns all registered DATA, UI and RESOURCE objects that can be used in AccessRules.
+
+ Query Parameters:
+ - context: Optional filter by context type (DATA, UI, RESOURCE)
+ - featureCode: Optional filter by feature (e.g., "trustee")
+
+ Returns:
+ - Dictionary with objects grouped by context type, each with:
+ - objectKey: Dot-notation identifier (e.g., "data.feature.trustee.TrusteeContract")
+ - label: Multilingual label
+ - featureCode: Owning feature
+ - meta: Additional metadata
+
+ Examples:
+ - GET /api/rbac/catalog/objects → all objects
+ - GET /api/rbac/catalog/objects?context=DATA → only DATA objects
+ - GET /api/rbac/catalog/objects?featureCode=trustee → only trustee objects
+ """
+ try:
+ catalog = getCatalogService()
+
+ if context:
+ # Einzelner Context
+ try:
+ accessContext = AccessRuleContext(context.upper())
+ except ValueError:
+ raise HTTPException(
+ status_code=400,
+ detail=f"Invalid context '{context}'. Must be one of: DATA, UI, RESOURCE"
+ )
+
+ if accessContext == AccessRuleContext.DATA:
+ objects = catalog.getDataObjects(featureCode)
+ elif accessContext == AccessRuleContext.UI:
+ objects = catalog.getUiObjects(featureCode)
+ else:
+ objects = catalog.getResourceObjects(featureCode)
+
+ return {context.upper(): objects}
+ else:
+ # Alle Contexts
+ return catalog.getAllCatalogObjects(featureCode)
+
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.error(f"Error getting catalog objects: {str(e)}")
+ raise HTTPException(
+ status_code=500,
+ detail=f"Failed to get catalog objects: {str(e)}"
+ )
+```
+
+### Frontend-Implementierung
+
+#### 1. Hook für Katalog-Objekte
+
+Erstelle `useCatalogObjects.ts`:
+
+```typescript
+// frontend_nyla/src/hooks/useCatalogObjects.ts
+
+import { useState, useCallback } from 'react';
+import api from '../api';
+import { RuleContext } from './useAccessRules';
+
+export interface CatalogObject {
+ objectKey: string;
+ featureCode: string;
+ label: { [lang: string]: string };
+ meta?: Record;
+ type: RuleContext;
+}
+
+interface CatalogObjects {
+ DATA: CatalogObject[];
+ UI: CatalogObject[];
+ RESOURCE: CatalogObject[];
+}
+
+export function useCatalogObjects() {
+ const [objects, setObjects] = useState({ DATA: [], UI: [], RESOURCE: [] });
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const fetchObjects = useCallback(async (
+ context?: RuleContext,
+ featureCode?: string
+ ): Promise => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const params = new URLSearchParams();
+ if (context) params.append('context', context);
+ if (featureCode) params.append('featureCode', featureCode);
+
+ const url = `/api/rbac/catalog/objects${params.toString() ? `?${params}` : ''}`;
+ const response = await api.get(url);
+
+ const data = response.data as CatalogObjects;
+ setObjects(data);
+ return data;
+ } catch (err: any) {
+ const errorMsg = err.response?.data?.detail || err.message || 'Fehler beim Laden';
+ setError(errorMsg);
+ return { DATA: [], UI: [], RESOURCE: [] };
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ const getObjectsByContext = useCallback((context: RuleContext): CatalogObject[] => {
+ return objects[context] || [];
+ }, [objects]);
+
+ return {
+ objects,
+ loading,
+ error,
+ fetchObjects,
+ getObjectsByContext,
+ };
+}
+```
+
+#### 2. Objekt-Auswahl Dropdown
+
+Aktualisiere `AddRuleForm`:
+
+```typescript
+// AccessRulesEditor.tsx - AddRuleForm Komponente
+
+interface AddRuleFormProps {
+ context: RuleContext;
+ availableObjects: CatalogObject[]; // NEU
+ onAdd: (rule: AccessRuleCreate) => void;
+ onCancel: () => void;
+}
+
+const AddRuleForm: React.FC = ({
+ context,
+ availableObjects, // NEU
+ onAdd,
+ onCancel
+}) => {
+ const [item, setItem] = useState('');
+ const [useCustom, setUseCustom] = useState(false); // NEU: Toggle für Freitext
+ // ... rest state
+
+ // Gruppiere Objekte nach Feature
+ const groupedObjects = useMemo(() => {
+ const grouped: Record = {};
+ availableObjects.forEach(obj => {
+ if (!grouped[obj.featureCode]) {
+ grouped[obj.featureCode] = [];
+ }
+ grouped[obj.featureCode].push(obj);
+ });
+ return grouped;
+ }, [availableObjects]);
+
+ // Aktuelle Sprache für Labels
+ const lang = useLanguage(); // oder 'de' als Default
+
+ return (
+
+ );
+};
+```
+
+#### 3. Katalog-Objekte im Editor laden
+
+```typescript
+// AccessRulesEditor.tsx
+
+export const AccessRulesEditor: React.FC = ({
+ roleId,
+ roleName,
+ readOnly = false,
+ apiBasePath = '/api/rbac',
+ mandateId,
+}) => {
+ // ... bestehende hooks
+
+ // NEU: Katalog-Objekte laden
+ const { objects: catalogObjects, fetchObjects } = useCatalogObjects();
+
+ useEffect(() => {
+ fetchObjects(); // Alle Objekte laden beim Mount
+ }, [fetchObjects]);
+
+ // Objekte für aktuellen Tab filtern
+ const currentContextObjects = useMemo(() => {
+ return catalogObjects[activeTab] || [];
+ }, [catalogObjects, activeTab]);
+
+ return (
+
+ {/* ... header, tabs */}
+
+
+ {activeTab !== 'JSON' && (
+
+ )}
+ {/* ... JSON tab */}
+
+
+ {/* ... action bar */}
+
+ );
+};
+```
+
+### Dot-Notation Schema
+
+Konsistentes Namensschema für alle RBAC-Objekte:
+
+```
+...[.]
+
+Beispiele:
+├── DATA
+│ ├── data.feature.trustee.TrusteeContract
+│ ├── data.feature.trustee.TrusteeContract.salary (Feld-Level)
+│ ├── data.feature.trustee.TrusteePosition
+│ ├── data.system.UserInDB (System-Entität)
+│ └── data.system.Mandate
+│
+├── UI
+│ ├── ui.feature.trustee.dashboard
+│ ├── ui.feature.trustee.positions
+│ ├── ui.admin.users (Admin-Bereich)
+│ ├── ui.playground.voice.settings (Playground)
+│ └── ui.nav.trustee (Navigation)
+│
+└── RESOURCE
+ ├── resource.feature.trustee.documents.create
+ ├── resource.feature.trustee.instance-roles.manage
+ ├── resource.ai.model.anthropic (AI-Resources)
+ └── resource.connector.sharepoint (Connectors)
+```
+
+### Betroffene Dateien
+
+| Datei | Änderung |
+|-------|----------|
+| **Backend** ||
+| `gateway/modules/security/rbacCatalog.py` | `registerDataObject()`, `getDataObjects()`, `getAllCatalogObjects()` |
+| `gateway/modules/routes/routeAdminRbacRules.py` | Neuer Endpoint `GET /api/rbac/catalog/objects` |
+| `gateway/modules/features/trustee/mainTrustee.py` | `DATA_OBJECTS` Liste, Registrierung erweitern |
+| `gateway/modules/features/*/main*.py` | DATA_OBJECTS in allen Features |
+| **Frontend** ||
+| `frontend_nyla/src/hooks/useCatalogObjects.ts` | NEU erstellen |
+| `frontend_nyla/src/hooks/index.ts` | Export hinzufügen |
+| `frontend_nyla/src/components/AccessRules/AccessRulesEditor.tsx` | Katalog-Objekte integrieren |
+
+---
+
+## Zusammenfassung der Änderungen
+
+### Backend
+
+| Priorität | Datei | Beschreibung |
+|-----------|-------|--------------|
+| 1 | `rbacCatalog.py` | DATA-Objekte Registrierung |
+| 1 | `routeAdminRbacRules.py` | `/catalog/objects` Endpoint |
+| 2 | `mainTrustee.py` | DATA_OBJECTS definieren |
+| 2 | Alle Feature `main*.py` | DATA_OBJECTS in allen Features |
+
+### Frontend
+
+| Priorität | Datei | Beschreibung |
+|-----------|-------|--------------|
+| 1 | `AdminMandateRolePermissionsPage.tsx` | `readOnly={false}` für System-Rollen |
+| 1 | `AccessRulesTable.tsx` | Neue Tabellen-Komponente |
+| 1 | `AccessRuleRow.tsx` | Zeilen-Komponente mit Checkboxen |
+| 2 | `useCatalogObjects.ts` | Hook für Katalog-Objekte |
+| 2 | `AccessRulesEditor.tsx` | Integration Katalog + Tabelle |
+| 2 | `AccessRules.module.css` | Styles für Tabelle |
+
+### Migrations-Strategie
+
+1. **Phase 1:** Backend-Erweiterungen (Katalog + Endpoint)
+2. **Phase 2:** Frontend Checkbox-UI (ersetzt Dropdown)
+3. **Phase 3:** Katalog-Integration im Frontend
+4. **Phase 4:** System-Rollen bearbeitbar machen
+
+---
+
+## Mockups
+
+### Kompakte Tabellen-Ansicht (Phase 2)
+
+```
+┌─────────────────────────────────────────────────────────────────────────────────┐
+│ 📊 DATEN-REGELN [+ Neue Regel] │
+├─────────────────────────────────────────────────────────────────────────────────┤
+│ │ │ EIGENE │ GRUPPE │ ALLE │
+│ OBJEKT │VIEW │ C R U D │ C R U D │C R U D│
+├─────────────────────────────────────────────────────────────────────────────────┤
+│ data.feature.trustee.* │ ✓ │ ✓ ✓ ✓ □ │ □ ✓ □ □ │□ □ □ □│ 🗑
+│ data.feature.trustee.docs │ ✓ │ ✓ ✓ ✓ ✓ │ ✓ ✓ ✓ ✓ │✓ ✓ ✓ ✓│ 🗑
+└─────────────────────────────────────────────────────────────────────────────────┘
+
+┌─────────────────────────────────────────────────────────────────────────────────┐
+│ 🖥 UI-REGELN [+ Neue Regel] │
+├─────────────────────────────────────────────────────────────────────────────────┤
+│ OBJEKT │ VIEW │ │
+├─────────────────────────────────────────────────────────────────────────────────┤
+│ ui.feature.trustee.dashboard │ ✓ │ 🗑 │
+│ ui.feature.trustee.positions │ ✓ │ 🗑 │
+│ ui.feature.trustee.documents │ ✓ │ 🗑 │
+│ ui.admin.mandate-roles │ □ │ 🗑 │
+└─────────────────────────────────────────────────────────────────────────────────┘
+```
+
+### Objekt-Auswahl Dropdown (Phase 3)
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Neue Regel hinzufügen │
+├─────────────────────────────────────────────────────────────────┤
+│ Objekt auswählen: [Freie Eingabe →] │
+│ ┌─────────────────────────────────────────────────────────────┐ │
+│ │ -- Global (alle Objekte) -- ▼│ │
+│ ├─────────────────────────────────────────────────────────────┤ │
+│ │ ▸ TRUSTEE │ │
+│ │ data.feature.trustee.TrusteeContract - Vertrag │ │
+│ │ data.feature.trustee.TrusteePosition - Position │ │
+│ │ data.feature.trustee.TrusteeDocument - Dokument │ │
+│ │ ▸ SYSTEM │ │
+│ │ data.system.UserInDB - Benutzer │ │
+│ │ data.system.Mandate - Mandant │ │
+│ └─────────────────────────────────────────────────────────────┘ │
+│ │
+│ [✓] Sichtbar (View) │
+│ │
+│ [Abbrechen] [Hinzufügen] │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## Offene Fragen
+
+1. **Feature-Filter:** Soll der Katalog nach aktiven Features des Mandanten gefiltert werden? Ja, der Katalog soll nach aktiven Features des Mandanten gefiltert werden.
+2. **Wildcard-Patterns:** Unterstützung für `data.feature.trustee.*` (alle Objekte eines Features)? Ja, die Unterstützung für `data.feature.trustee.*` (alle Objekte eines Features) soll implementiert werden.
+3. **Feld-Level Permissions:** Sollen einzelne Felder (z.B. `TrusteeContract.salary`) unterstützt werden? Ja, die Unterstützung für einzelne Felder (z.B. `TrusteeContract.salary`) soll implementiert werden.
+4. **Vererbung:** Sollen Berechtigungen von übergeordneten Objekten vererbt werden? Ja, die Vererbung von Berechtigungen von übergeordneten Objekten soll implementiert werden.
+
+---
+
+*Erstellt: 2026-01-24*
+*Status: Entwurf zur Diskussion*