wiki/concepts/Kontext-20260328.md

10 KiB

Kontext: Wir arbeiten an einer Multi-Tenant-Applikation mit Feature-Store-Modell. Ich möchte UI und Gateway austesten und offene Themen weiterführen. Hier ist der vollständige architektonische Kontext:


Architektur-Überblick

  • Backend (Gateway): @poweron/gateway — FastAPI (Python), PostgreSQL
  • Frontend: @poweron/frontend_nyla — React/TypeScript (Vite)
  • Konzeptdokument: @poweron/wiki/concepts/Multi-Mandate-Onboarding-und-Store-Konzept.md
  • Umsetzungsbericht (103 Punkte OK): @poweron/wiki/concepts/Multi-Mandate-Umsetzungsbericht.md

RBAC-Rollenmodell (KRITISCH — nie verwechseln!)

Es gibt ZWEI vollständig getrennte Rollensysteme. Siehe auch: @poweron/.cursor/rules/rbac-role-separation.mdc

1. Mandantenrollen (Mandate Roles)

  • Labels: admin, user, viewer (generisch)
  • Gespeichert mit: featureCode=NULL, featureInstanceId=NULL, mandateId=<id>
  • Templates: isSystemRole=True, mandateId=NULL, featureCode=NULL
  • Verknüpfung: UserMandateRole Junction Table
  • Logik: Zuordnung User → Mandant erfolgt via createUserMandate() in interfaceDbApp.py, welche IMMER mindestens eine Mandate-Rolle verlangt (ValueError wenn leer)

2. Feature-Instanz-Rollen (Feature Instance Roles)

  • Labels: workspace-admin, workspace-user, workspace-viewer, automation-admin, etc. (Feature-Prefix!)
  • Gespeichert mit: featureCode=<code>, featureInstanceId=<id>, mandateId=<id>
  • Templates: isSystemRole=False, mandateId=NULL, featureInstanceId=NULL, featureCode=<code> (globale Vorlagen)
  • Verknüpfung: FeatureAccessRole Junction Table
  • Logik: Zuordnung User → Feature-Instanz erfolgt via createFeatureAccess() in interfaceDbApp.py, welche IMMER mindestens eine Instanz-Rolle verlangt (ValueError wenn leer)
  • Jedes Feature-Modul definiert seine TEMPLATE_ROLES in mainXxx.py

Strikte Regeln:

  1. NIE eine Mandantenrolle (admin) einer FeatureAccessRole zuweisen
  2. NIE eine Feature-Rolle (workspace-admin) einer UserMandateRole zuweisen
  3. Admin-Lookup für Feature-Instanzen: roleLabel.endswith("-admin"), NICHT "admin" in roleLabel
  4. _copyTemplateRoles sucht: featureCode=X, mandateId=NULL, featureInstanceId=NULL — nie isSystemRole=True
  5. Alle Pfade, die FeatureAccess erstellen, MÜSSEN mindestens eine instanz-scoped roleId mitgeben

Datenmodelle:

  • Role: @poweron/gateway/modules/datamodels/datamodelRbac.py — Felder: id, roleLabel, description, mandateId, featureInstanceId, featureCode, isSystemRole
  • UserMandate: @poweron/gateway/modules/datamodels/datamodelMembership.py — User-Mitgliedschaft in Mandant
  • FeatureAccess: @poweron/gateway/modules/datamodels/datamodelMembership.py — User-Zugriff auf Feature-Instanz
  • FeatureInstance: @poweron/gateway/modules/datamodels/datamodelFeatures.py — Instanz eines Features in einem Mandanten
  • Mandate: @poweron/gateway/modules/datamodels/datamodelUam.py — Mandantenmodell mit isSystem (Root-Schutz), deletedAt. mandateType wurde entfernt (keine Geschäftslogik).

Feature-Store-Konzept ("Own Instance Pattern")

  • UI: @poweron/frontend_nyla/src/pages/Store.tsx — Pro Mandant ein "Aktivieren für [Mandant]" Button
  • Backend: @poweron/gateway/modules/routes/routeStore.pyPOST /api/store/activate
  • Flow: User wählt Feature → wählt Mandant → Backend erstellt FeatureInstance mit copyTemplateRoles=True → findet Admin-Rolle (endswith("-admin")) → weist User via createFeatureAccess(userId, instanceId, roleIds=[adminRoleId]) zu
  • Auto-Provisioning: GET /api/store/mandates erstellt automatisch "Home {username}"-Mandate, wenn User keine Admin-Mandate hat
  • Home-Mandant: Jeder User hat ab erstem Login ein Home-Mandat ("Home {username}"). Wird in Login, Register, OAuth-Onboarding und Store einheitlich sichergestellt.
  • Template-Rollen-Kopie: @poweron/gateway/modules/interfaces/interfaceFeatures.py_copyTemplateRoles() und syncRolesFromTemplate()
  • Orphan Control: Letzer FeatureAccess entfernt → FeatureInstance wird gelöscht

Schlüssel-Backend-Dateien

Datei Zweck
@poweron/gateway/modules/interfaces/interfaceDbApp.py Kern-DB-Interface: createUserMandate, createFeatureAccess, _provisionMandateForUser; Domain-Models nutzen PowerOnModel / sys*-Metadaten
@poweron/gateway/modules/interfaces/interfaceFeatures.py Feature-Instanz-Verwaltung: _copyTemplateRoles, syncRolesFromTemplate
@poweron/gateway/modules/routes/routeStore.py Store-API: activate, mandates-list
@poweron/gateway/modules/routes/routeAdminFeatures.py Admin-API: add_user_to_feature_instance (Rollen-Validierung: nur Instanz-Rollen!), rename instance
@poweron/gateway/modules/routes/routeSystem.py /api/navigation — liefert isAdmin pro FeatureInstance
@poweron/gateway/modules/routes/routeSecurityLocal.py Login, Register, _provisionMandateForUser
@poweron/gateway/modules/shared/attributeUtils.py Generiert Attribute aus Pydantic-Models für FormGenerator
@poweron/gateway/modules/connectors/connectorDbPostgre.py DB-Connector: sysCreatedAt / sysCreatedBy / sysModifiedAt / sysModifiedBy; Migration alter _createdAt-Spalten via migrateLegacyUnderscoreSysColumns (Bootstrap)
@poweron/gateway/modules/migration/migrateRootUsers.py Root→Personal-Mandate-Migration

Schlüssel-Frontend-Dateien

Datei Zweck
@poweron/frontend_nyla/src/pages/Store.tsx Feature-Store mit Mandate-Kontext
@poweron/frontend_nyla/src/components/UnifiedDataBar/UnifiedDataBar.tsx UDB: Chats, Files, Sources Tabs
@poweron/frontend_nyla/src/components/UnifiedDataBar/ChatsTab.tsx Chats: Volltext-Suche, Feature-Code-Gruppierung, Flat-Mode nach lastMessageAt
@poweron/frontend_nyla/src/components/UnifiedDataBar/FilesTab.tsx Files: Scope/Neutralize Icons
@poweron/frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx Sources: Scope/Neutralize auf Active Sources
@poweron/frontend_nyla/src/components/Navigation/MandateNavigation.tsx Nav-Tree mit Rename-Icon für Instanz-Admins
@poweron/frontend_nyla/src/components/Navigation/TreeNavigation/TreeNavigation.tsx Generischer Tree mit actions-Slot
@poweron/frontend_nyla/src/components/OnboardingAssistant.tsx Globaler Onboarding-Assistent (Dashboard, dismissible, reaktivierbar via UserSection)
@poweron/frontend_nyla/src/components/Navigation/UserSection.tsx User-Kontextmenü: Guthaben, Einstellungen, Onboarding
@poweron/frontend_nyla/src/pages/Settings.tsx User-Settings: profile, appearance, voice, neutralization, privacy
@poweron/frontend_nyla/src/components/FormGenerator/ Dynamische Formulare aus Backend-Attribut-Definitionen
@poweron/frontend_nyla/src/hooks/useNavigation.ts Navigation-Hook mit FeatureInstance.isAdmin
@poweron/frontend_nyla/src/pages/views/workspace/WorkspacePage.tsx Workspace-Feature-Seite mit Chat-Drag&Drop + RAG-Resolve
@poweron/frontend_nyla/src/pages/views/commcoach/CommcoachDossierView.tsx CommCoach mit UDB (hideTabs: chats), gleicher instanceId/mandateId-Kontext wie Workspace
@poweron/gateway/modules/datamodels/datamodelBase.py PowerOnModel: sysCreatedAt, sysCreatedBy, sysModifiedAt, sysModifiedBy (camelCase, UI-readonly vorbereitet)

System-Felder (DB-Metadaten)

  • Spalten / Modelle: sysCreatedAt, sysCreatedBy, sysModifiedAt, sysModifiedBy — gepflegt durch den Connector (connectorDbPostgre.py); viele Domain-Models erben von PowerOnModel in datamodelBase.py.
  • Legacy: Frühere _createdAt-Spalten werden bei Bootstrap durch migrateLegacyUnderscoreSysColumns / migrateLegacyUnderscoreSysColumnsAllPoweronDatabases in die sys*-Felder übernommen.
  • UI: FormGenerator unterstützt readonly-Attribute; wo Metadaten in Listen/Forms explizit gezeigt werden sollen, Felder als readonly markieren (schrittweise je Screen).

Produkt-Themen (Stand 2026-03-28)

  1. CommCoach UDB + Voice: UDB in CommcoachDossierView. Voice-Controller bezieht STT-Sprache dynamisch aus zentralen Preferences.
  2. Neutralisierung End-to-End: requireNeutralization vom UI-Toggle bis AiCallRequest durchgereicht via ServiceCenterContext. Mandanten-RAG in CommCoach via searchSessionsByTopicRag.
  3. Settings (5 Tabs): Profil, Darstellung, Stimme, Datenneutralisierung, Datenschutz.
  4. Billing: nur PREPAY_MANDATE: PREPAY_USER komplett entfernt. budgetAiCHF in Subscriptions (Trial: 5 CHF, Standard: 10 CHF/Monat). Auto-Recharge-Felder in BillingSettings.
  5. mandateType entfernt: Enum, Feld, Validator in 13+ Dateien gelöscht. isSystem als einziger Root-Schutz.
  6. Home-Mandant: "Home {username}" bei erstem Login (Register/OAuth/Invitation) via _ensureHomeMandate().
  7. UDB ChatsTab: Server-seitige Volltext-Suche (searchWorkflowsByContent), Flat-Mode nach lastMessageAt, zweistufige Baumansicht (Feature-Code-Sektionen > Feature-Instanzen).
  8. Chat Drag & Drop + RAG: Chat-Items aus ChatsTab auf Workspace droppbar. Backend /resolve-rag liefert Zusammenfassung.
  9. Datenvolumen: assertCapacity("dataVolumeMB") prüft RAG-Index-Grösse (FileContentIndex.totalSize). API /api/subscription/data-volume/{mandateId} liefert ragIndexMB, filesMB, percentUsed, warning (>=80%).
  10. Mandate-Cascade: deleteMandate(force=True) löscht alle abhängigen Tabellen: ContentChunk, FileContentIndex, DataNeutralizerAttributes, DataSource, FeatureDataSource, FileItem, ChatMessage/Log/Workflow, FeatureAccessRole, FeatureAccess, FeatureInstance, UserMandateRole, UserMandate, Stripe-Subscriptions, BillingTransaction/Account/Settings, AccessRule, Role, Mandate. Frontend: Namensbestätigungs-Dialog.

Coding-Konventionen

  • Alle internen Funktionen beginnen mit _ Prefix (z.B. _copyTemplateRoles)
  • camelCase für alle Variablen und Funktionsnamen (kein snake_case)
  • Keine unnötigen Fallbacks — Fehler müssen propagiert werden
  • Keine Deprecations — alte Logik löschen statt deprecaten
  • Keine Backwards-Compatibility-Workarounds
  • Pydantic-Models sind die einzige Quelle für UI-Feld-Definitionen (via attributeUtils.py)