wiki/z-archive/concepts/Multi-Mandate-Onboarding-und-Store-Konzept.md

43 KiB
Raw Permalink Blame History

Multi-Mandate Onboarding, Root-Mandant und Feature-Store — Konzept

Zweck

Dieses Dokument beschreibt die Ziel-Architektur für Marktzugang und Mandantenmodell:

  • Ein globales Benutzerkonto (User) mit mehreren Mandats-Zugehörigkeiten (UserMandate) — unverändertes Multi-Mandate-Modell.
  • Subscriptions und Kapazität bleiben pro Mandant gebunden (MandateSubscription); siehe Mandanten-Subscription-Konzept.md und Subscription-State-Machine.md.
  • Feature-Instanzen pro Mandant; Zugriff über FeatureAccess und Rollen.
  • Unified Data Layer als zentrale, mandantenweite Datenschicht mit integriertem RAG und Neutralisierung.

Abgrenzung: Konkrete Implementierungsschritte (PRs, Skripte) werden hier nur skizziert; der Fokus liegt auf fachlicher Einordnung und skalierbarer Trennung von System-Mandant, Personal-Mandant und Firmen-Mandant.


Kundenbedürfnisse (zwei Profile)

Profil A — Firma (Team-Mandant)

  • Ein Mandant repräsentiert die Organisation.
  • Eine Subscription auf Mandanten-Stufe (z. B. Standard monatlich/jährlich).
  • Mehrere Benutzer arbeiten im gleichen Mandanten; weitere Personen per Einladung.
  • Eigene Feature-Instanzen im Firmen-Mandanten (kein „Gemeinschafts-Pool" mit Fremddaten).

Profil B — Einzelperson (Solo-Mandant)

  • Registrierung ohne sofortige Firmenstruktur.
  • Eigenes Mandat (logisch „Personal"), damit Subscription, Limits und Daten klar diesem Mandanten zugeordnet sind.
  • Späterer Ausbau: Team einladen, Mandanten-Label/Name anpassen — wird fachlich zum Firmen-Mandanten (Profil A), ohne neues Benutzerkonto.

IST-Zustand (kurz, Stand Codebase-Analyse)

Thema Aktuell
Identität User ohne mandateId; Kontext pro Request über X-Mandate-Id / X-Instance-Id
Registrierung POST /api/local/register erstellt nur den User; Mandats-Zuweisung folgt über Admin, Einladung oder Folgeaktionen
Root-Mandant Bootstrap: name=root, isSystem=true, interne Subscription ROOT, Feature-Instanzen mit autoCreateInstance
Feature Store Shared-Instance-Pattern in routeStore.py: eine gemeinsame FeatureInstance pro Store-Feature im Root-Mandanten; Aktivierung = FeatureAccess + User-Rolle; Datenisolation über read="m" / _createdBy
Dashboard Keine Instanzen → Redirect auf /store (Dashboard.tsx)
Datensilos Jede Feature-Instanz hat eigene Daten (z. B. CommCoach eigene Voice-Definitionen, Workspace eigene Chats); Parallelstrukturen und Duplikation

Kernproblem: Endnutzer landen faktisch im Root-Mandanten über den Store. Subscription/Billing pro Kunde ist nicht sauber modellierbar; Upgrade-Pfad „Einzelperson → Firma" fehlt. Datensilos pro Feature verhindern Cross-Feature-Workflows und duplizieren Logik (Voice, Kontext, Dokumente).


Ziel-Architektur (Entscheidungen)

  1. Root-Mandant nur technisch: Nur System-/Betriebskonten (sysadmin-Benutzer). Keine Endkunden-Datenhaltung, keine Feature-Instanzen für Endkunden im Root.
  2. Jede Selbstregistrierung erzeugt ein eigenes Mandat (Personal oder Company), inkl. Mandats-Admin-Rolle und passender initialer Subscription (Trial vs. Standard).
  3. Feature Store mit explizitem Mandanten-Kontext und Orphan Control (siehe Abschnitt unten).
  4. Zwei Einstiege von der Homepage/Landing:
    • „Kostenlos testen" → Personal-Mandat + Trial-Plan (TRIAL_7D).
    • „Für Unternehmen" → Company-Mandat + STANDARD_MONTHLY (inkl. Firmenname).
  5. Unified Data Layer (Variante B): Zentrale mandantenweite Datenschicht mit RAG als Plattformkern; Features als spezialisierte Workflow-Oberflächen.
  6. Neutralisierung als Kerndisziplin: Flag-basiert pro Datenquelle und pro AI-Call, mit Trusted-Model-Pipeline.

Datenmodell-Erweiterung

mandateType wurde entfernt (2026-03-28). Das Feld hatte keine Geschäftslogik — kein RBAC, kein Billing, kein Feature-Gate brancht darauf. Der Root-Mandant wird durch isSystem=true geschützt.

Aktuelles Modell: Mandate hat name, label, enabled, isSystem, deletedAt. Kein mandateType mehr.

Home-Mandant: Jeder Benutzer hat ab erstem Login ein Home-Mandat mit dem Namen "Home {username}" (unique, da username unique). Dies dient als persönliche Basis für Features, Billing und Daten. Wird einheitlich bei Register, OAuth-Onboarding, Invite-Login und Store-Zugriff sichergestellt.


Onboarding-Flows (Zielbild)

flowchart TD
    subgraph entry [Homepage]
        TrialCTA[Kostenlos testen]
        CompanyCTA[Fuer Unternehmen]
    end

    subgraph personalFlow [Personal]
        TrialCTA --> RegP["Register type=personal"]
        RegP --> MP[Mandate personal]
        MP --> SubT[Subscription TRIAL_7D]
        SubT --> ProvP[Feature-Instanzen im Mandat]
    end

    subgraph companyFlow [Company]
        CompanyCTA --> RegC["Register type=company"]
        RegC --> MC[Mandate company + Firmenname]
        MC --> SubS[Subscription STANDARD]
        SubS --> ProvC[Feature-Instanzen im Mandat]
    end

    subgraph invite [Parallel: Einladung]
        Inv[Einladung annehmen]
        Inv --> UM[UserMandate / FeatureAccess im Zielmandat]
    end

OAuth-Registrierung (Google, MSAL)

Bei OAuth-Registrierung gibt es kein Formular mit „Personal vs. Company". Lösung: Onboarding-Wizard nach erstem OAuth-Login, der den User durch die Mandant-Typ-Auswahl führt. Der Wizard erscheint nur beim allerersten Login (kein Mandate vorhanden) und erstellt das Mandate mit derselben Provisionierungslogik wie die Formular-Registrierung.

Invitation-Flow

Ein User, der sich via Einladung registriert, erhält beides: Zugang zum einladenden Mandanten (via UserMandate + FeatureAccess) und bei Bedarf ein eigenes Personal-Mandate. Der User soll auch ausserhalb des einladenden Mandanten arbeiten können. Das eigene Mandate wird nicht zwingend bei Einladungs-Annahme erstellt, sondern bei Bedarf (z. B. erste Store-Aktivierung ohne Admin-Mandate — siehe Feature Store).

Subscription-Timing

Die Trial-Subscription startet nicht ab Registrierung, sondern ab erstem Login. Bei Magic-Link- oder OAuth-Flows vergehen zwischen Registrierung und erstem Login Stunden. Daher: MandateSubscription wird bei Registrierung mit Status PENDING erstellt. Bei erstem Login wird der Status auf ACTIVE gesetzt und die Trial-Periode beginnt. So verliert kein User Trial-Tage durch Registrierungs-Delays.

Backend-Orchestrierung

Zentrale interne Provisionierung nach createUser:

  1. Mandat anlegen (mit kopierten System-Rollen, wie bei createMandate).
  2. UserMandate + Mandats-Admin-Rolle.
  3. MandateSubscription mit gewähltem planKey.
  4. Für Features mit autoCreateInstance: Instanzen im neuen Mandat erzeugen + FeatureAccess mit Admin-Instanzrolle für den Owner.

RBAC-Hinweis: createMandate prüft heute Berechtigungen; für öffentliche Registrierung ist eine Root-privilegierte interne Routine nötig (kein Zirkelschluss „User braucht Rechte, die er noch nicht hat").


Feature Store: Own Instance Pattern

Prinzip: Expliziter Mandanten-Kontext — nie implizit

Ein User ist Mitglied in 1..n Mandanten. Daher darf die Store-Aktivierung nie ein Mandat erraten oder implizit wählen. Der Kontext muss immer explizit definiert sein:

  • UI: Der Store zeigt pro Feature die Mandanten des Users als Aktivierungsziel. Die Aktivierung erfolgt nicht einfach mit „Aktivieren", sondern mit dem Namen des Ziel-Mandanten (z. B. Dropdown oder Kachel-Auswahl bei Multi-Mandat-Usern; bei Solo-Mandant-Usern ist die Auswahl trivial, da nur ein Mandat existiert).
  • API: Der Activate-Endpoint erhält immer ein explizites mandateId — keine Heuristik, kein „primäres Mandat".

Mehrere Instanzen desselben Features

Es ist erlaubt, mehrere Instanzen desselben Features in einem Mandanten zu haben. Beispiel: Zwei separate Workspace-Instanzen für unterschiedliche Projekte oder Teams. Jede Aktivierung im Store erstellt eine neue FeatureInstance — auch wenn bereits eine Instanz desselben Feature-Typs existiert. Die Subscription-Kapazität (maxFeatureInstances) begrenzt die Gesamtzahl, nicht pro Feature-Typ.

Store-Zugang: Für alle User (Upselling)

Der Store ist für jeden User sichtbar — nicht nur für Admins. Der Store ist ein zentrales Upselling-Instrument.

User mit Admin-Mandate: Aktiviert Features direkt im gewählten Mandanten.

User ohne Admin-Mandate (z. B. nur als Mitglied in fremden Mandanten eingeladen): Wenn dieser User ein Feature im Store aktiviert, wird automatisch ein eigenes Personal-Mandate erstellt (inkl. Subscription), und die Feature-Instanz wird in diesem neuen Mandanten angelegt. Damit kann jeder User über den Store zu einem eigenständigen Mandanten-Admin werden — ohne separaten Registrierungsprozess.

Berechtigungen

Nur Mandanten-Admins können im Feature Store neue Features für ihren Mandanten aktivieren. Bei Aktivierung erhält der Admin die jeweilige Admin-Instanzrolle auf der neu erstellten FeatureInstance. Ein User ohne Admin-Mandate löst die Auto-Mandate-Erstellung aus (siehe oben) und wird automatisch Admin des neuen Mandanten.

Deaktivierung und Orphan Control

„Deaktivieren" im Store bedeutet: Der User entfernt seinen eigenen FeatureAccess auf die Instanz. Wenn der letzte User eine Feature-Instanz deaktiviert (d. h. kein FeatureAccess-Record mehr verbleibt), wird die Feature-Instanz gelöscht (Orphan Control). Dies verhindert verwaiste Instanzen, die Subscription-Kapazität belegen.

flowchart TD
    UserDeactivate["User deaktiviert Feature"] --> RemoveAccess["FeatureAccess entfernen"]
    RemoveAccess --> CheckOrphan{"Noch FeatureAccess-Records auf dieser Instanz?"}
    CheckOrphan -->|Ja| Done["Instanz bleibt bestehen"]
    CheckOrphan -->|Nein| DeleteInstance["FeatureInstance loeschen + Stripe-Quantity sync"]

Technische Umsetzung

  • POST /api/store/activate: Body enthält featureCode + mandateId. Prüft: User ist Admin im Ziel-Mandat, Subscription-Kapazität (maxFeatureInstances). Erstellt FeatureInstance + FeatureAccess + Admin-Instanzrolle.
  • POST /api/store/deactivate: Body enthält featureCode + mandateId. Entfernt eigenen FeatureAccess. Prüft Orphan-Bedingung; löscht Instanz wenn letzer Zugriff entfernt.

Frontend: Texte in Store.tsx anpassen auf „Feature für deinen Mandanten aktivieren" mit expliziter Mandanten-Auswahl.


Homepage / Frontend

  • Zwei CTAs (auf Login- oder Landing-Route): Links nach /register?type=personal bzw. /register?type=company.
  • Register-Formular: bei company zusätzlich Firmenname; API-Body um registrationType / companyName erweitern.
  • Einladungen: unverändert InvitePage-Flow; nach Annahme kann der User mehrere Mandate haben (bestehendes Modell).

Migration: Root-Mandant bereinigen

Logik (klar definiert)

Die Migration läuft als separates Modul/Script, das im Bootstrap aufgerufen wird und später wieder entfernt werden kann.

Algorithmus:

SCHRITT 1 — Feature-Daten migrieren (ALLE User, unabhängig von isSysAdmin):

FÜR JEDEN User MIT FeatureAccess auf Root-Mandant-Instanzen:

  1. Hat der User bereits ein eigenes Mandat (nicht Root)?
     → JA: Ziel-Mandat = bestehendes Mandat
     → NEIN: Neues Personal-Mandat erstellen (mandateType=personal, name=username)
             System-Rollen kopieren (copySystemRolesToMandate)
             UserMandate mit admin-Rolle im neuen Mandat erstellen
             MandateSubscription erstellen (TRIAL_7D oder passender Plan)
  2. FÜR JEDE FeatureAccess des Users auf Root-Shared-Instanzen:
     a. Neue FeatureInstance im Ziel-Mandat erstellen (falls nicht schon vorhanden)
     b. Daten migrieren: Alle Records mit featureInstanceId=alte_Root_Instanz
        UND _createdBy=userId → featureInstanceId auf neue Instanz umschreiben
     c. FeatureAccess + Rolle auf neue Instanz erstellen
     d. Alten FeatureAccess auf Root-Instanz entfernen

SCHRITT 2 — Root-Mandant bereinigen:

  Alle Feature-Instanzen im Root-Mandanten entfernen (keine Endkunden-Instanzen mehr)
  FÜR JEDEN User MIT UserMandate zum Root-Mandanten:
    Hat der User isSysAdmin=true?
    → JA: UserMandate bleibt (Systembetrieb)
    → NEIN: UserMandate zum Root entfernen

Wichtig: Das isSysAdmin-Flag ist irrelevant für die Feature-Migration (Schritt 1). Es steuert System-Zugriff, nicht Daten-Zugriff. Ein sysadmin mit FeatureAccess auf Root-Instanzen bekommt seine Daten genauso migriert. Das Flag ist nur in Schritt 2 relevant: sysadmin-User behalten ihre Root-Mandant-Mitgliedschaft (für Systembetrieb), alle anderen verlieren sie.

Implementierung

  • Ort: gateway/scripts/script_migrate_root_users.py (oder gateway/modules/migration/migrateRootUsers.py)
  • Aufruf: Einmalig im Bootstrap (interfaceBootstrap.py) nach initRootMandateFeatures eingehängt; nach erfolgreicher Migration wird ein Flag gesetzt (z. B. DB-Record), damit der Code nur einmal läuft.
  • Entfernung: Nach Verifikation in Produktion kann das Modul und der Bootstrap-Aufruf entfernt werden.
  • Sicherheit: Dry-Run-Modus als Parameter; Logging aller Aktionen; kein Hard-Delete ohne Verifikation.

Unified Data Layer (Entscheidung: Variante B)

Entscheidung

Variante B wird gewählt. Die Feature-Datensilos (z. B. CommCoach mit eigenen Voice-Definitionen parallel zum AI Workspace, doppelte Sprachdefinitionen) zeigen bereits heute, dass die instanz-zentrierte Datenhaltung zu Duplikation und Fragmentierung führt.

Prinzip

Der Unified Data Layer ist die zentrale mandantenweite Datenschicht. Features (Workspace, Automation, CommCoach, Trustee, ...) sind spezialisierte Oberflächen/Workflows, die auf denselben Datenpool zugreifen.

Mandate
└── Unified Data Layer (RAG Repository)
    ├── Dokumente, Extraktionen, Wissensbasis, Voice-Definitionen
    ├── Neutralisierungskonfiguration (zentral pro Mandat)
    │
    ├── Workspace nutzt: Chat, Recherche, Dokumentenkontext
    ├── Automation nutzt: Workflows mit Zugriff auf Mandantendaten
    ├── CommCoach nutzt: Coaching mit geteiltem Kontext + Voice
    └── Trustee nutzt: Fachlogik auf Mandantendaten

Datenreferenzierung (bleibt erhalten)

Daten behalten ihre bestehenden Referenzen:

  • featureInstanceId: Zeigt an, welches Feature die Daten erzeugt hat (Quellenschutz). Bleibt als Herkunftsreferenz erhalten.
  • _createdBy: Zeigt an, welcher User die Daten erzeugt hat. Bleibt für RBAC und Zuordnung.
  • mandateId: Implizit enthalten, da jede FeatureInstance einem Mandanten zugeordnet ist. Muss nicht redundant auf Daten-Records gespeichert werden.

Datenquellen-Tagging: Explizite Klassifikation durch den User

Jede Datenquelle, die in den Unified Data Layer eingebunden wird, muss vom User explizit getaggt werden. Es gibt keine implizite Zuordnung. Der User entscheidet beim Einbinden über zwei Dimensionen:

1. Sichtbarkeits-Scope (wer darf diese Daten sehen/nutzen):

Scope Bedeutung Wer hat Zugriff
personal Private Daten des Users Nur der erstellende User, in allen Features
featureInstance Daten für eine spezifische Feature-Instanz Alle User mit FeatureAccess auf diese Instanz
mandate Mandantenweite Daten Alle User im Mandanten, in allen Features
global Plattformweite Daten Alle User plattformweit (read-only)

Einschränkung scope=global: Dieser Scope ist mächtig — eine falsch getaggte Datenquelle wäre plattformweit sichtbar. Daher ist scope=global ausschliesslich durch sysadmin-User setzbar und RBAC-geschützt. Reguläre User und Mandanten-Admins können maximal scope=mandate setzen.

2. Neutralisierungs-Flag (neutralize: true/false):

Ob die Daten vor AI-Zugriff neutralisiert werden sollen (siehe Abschnitt Neutralisierung).

Tagging-Zeitpunkt: Das Tagging erfolgt beim Einbinden der Datenquelle (Upload, Verlinkung, Import). Der Scope und das Neutralisierungs-Flag können jederzeit nachträglich geändert werden — Änderungen lösen eine Re-Indizierung im RAG aus.

UI-Konzept: Einfach und unkompliziert direkt in der UDB gelöst — kein separater Dialog nötig.

Files-Tab — Jede Datei zeigt Scope- und Neutralisierungs-Symbole inline:

FILES
─────────────────────────────────────
📄 Jahresabschluss_2025.pdf    👤  🔒
📄 Firmenrichtlinien.docx      🏢
📄 Meine_Notizen.txt           👤
📄 Branchenreport.pdf          🏢  🔒
─────────────────────────────────────
👤 = personal  👥 = instanz  🏢 = mandant  🔒 = neutralisiert

Sources-Tab — Die Datenquellen selbst (Mailkonten, Datenbanken, Konnektoren) haben keine Scope-/Neutralisierungs-Icons. Die Icons erscheinen erst auf den aktiv eingebundenen Elementen (Active Sources), weil der Scope pro eingebundenem Element definiert wird, nicht pro Quelle:

SOURCES
─────────────────────────────────────
ACTIVE PERSONAL SOURCES
  ■ p.motsch@valueon.ch  Patrick Mo...  x 👤 🔒 
  
ACTIVE FEATURE SOURCES
  ■ PowerOn Prod  TrusteeDataJournal... x 👥    
  ■ PowerOn Prod  TrusteeDataJournal... x 👥 🔒 

BROWSE SOURCES
  ● p.motsch@valueon.ch
  ● patrick.motsch@pamocreate.com
  ● ag112559@gmail.com

FEATURE DATA
  ▸ Root
      PowerOn Teams            4 tables
      PowerOn Test            10 tables
  ▸ VO-Team
      CEO Com-Coach           10 tables
      PowerOn Prod            10 tables
─────────────────────────────────────

Logik: Die Datenquellen unter "Browse Sources" und "Feature Data" sind Katalog-Einträge — sie zeigen, was verfügbar ist. Erst wenn der User eine Quelle aktiviert (in die Active-Liste zieht), erscheinen die Scope- und Neutralisierungs-Symbole, weil erst dann die Entscheidung getroffen wird, wie die Daten im RAG integriert werden.

  • Klick auf das Scope-Symbol (bei Active-Elementen): Wechselt den Scope zyklisch (personal → instanz → mandant) oder zeigt ein kleines Popover.
  • Klick auf das Neutralisierungs-Symbol: Toggled neutralize on/off (🔒 / kein Symbol).
  • Default bei Aktivierung: personal + neutralize=false — der User passt bei Bedarf mit einem Klick an.
  • Die Symbole sind sofort sichtbar und verständlich, ohne dass der User einen Einstellungsdialog öffnen muss.

Datenmodell: Unified Data Layer

Das RAG-System existiert bereits in der Plattform und kann ausgebaut werden. Die Erweiterung betrifft primär die Scope- und Neutralisierungs-Metadaten auf bestehenden Entitäten:

DataSource (Erweiterung bestehender Modelle)
├── id                    -- existiert
├── mandateId             -- existiert (via FeatureInstance)
├── featureInstanceId     -- existiert (Quellenschutz)
├── _createdBy            -- existiert (User-Zuordnung)
├── scope                 -- NEU: "personal" | "featureInstance" | "mandate" | "global"
├── neutralize            -- NEU: boolean (Flag pro Datenquelle)
└── neutralizationStatus  -- NEU: "pending" | "completed" | "failed" | "not_required"

NeutralizerMapping (existiert als DataNeutralizerAttributes, wird erweitert)
├── id                    -- Platzhalter-ID (z.B. [name.uuid])
├── mandateId             -- Mandanten-Zuordnung
├── featureInstanceId     -- Quellenschutz
├── userId                -- Ersteller
├── originalText          -- Originalwert
├── patternType           -- Typ (email, phone, name, address, iban, ...)
└── fileId                -- Zugehörige Datei

RAG-Index (Vektorspeicher, bestehende Infrastruktur)
├── Dokument-Embedding    -- existiert
├── mandateId             -- NEU als Filter-Metadatum
├── scope                 -- NEU als Filter-Metadatum
├── featureInstanceId     -- NEU als Filter-Metadatum
├── userId                -- NEU als Filter-Metadatum (für scope=personal)
└── isNeutralized         -- NEU: nur neutralisierte Versionen wenn Flag gesetzt

Die bestehenden Datenmodelle (ContentExtracted, ContentPart, Konnektoren) bleiben unverändert. Die Scope- und Neutralisierungs-Felder werden als Erweiterung auf die Datenquellen-Ebene aufgesetzt. Der RAG-Index erhält Scope-Metadaten als Filterkriterien, um die Union-Query über die vier Scopes effizient auszuführen.

RAG als zentrales Repository — der Plattformkern

Das RAG-System ist das Repository, das die Plattform ausmacht — ein klarer USP. Alle getaggten Datenquellen werden im RAG indiziert und stehen den Features als Kontextquelle zur Verfügung.

Bei jeder RAG-Query wird der effektive Scope aus der Kombination der Sichtbarkeiten des anfragenden Users aufgebaut:

RAG-Query-Context = personal(userId)
                   featureInstance(instanceId)   -- falls im Feature-Kontext
                   mandate(mandateId)
                   global()

Das bedeutet: Ein User im Workspace-Feature sieht bei einer RAG-Suche seine persönlichen Daten, die Daten der Workspace-Instanz, die Mandantendaten und globale Daten — automatisch zusammengeführt, entsprechend der Tags der Datenquellen.

UI-Komponente: Unified Data Bar (UDB)

Der Unified Data Layer braucht eine einheitliche UI-Komponente, die in allen Feature-Instanzen verfügbar ist — vergleichbar mit dem Folder-/File-Tree, der heute im Workspace existiert. Diese Komponente heisst Unified Data Bar (UDB).

Herkunft: Die bestehende Sidebar im AI Workspace mit den Tabs Chats, Files, Sources ist der Ausgangspunkt. Diese Komponente wird aus dem Workspace extrahiert und als plattformweite, wiederverwendbare UI-Komponente bereitgestellt.

Tabs der UDB:

Tab Inhalt Scope-Filter
Chats Chat-Verläufe als Baumstruktur, gruppiert nach Feature-Instanzen personal + featureInstance
Files Dokumente, Uploads, Extraktionen Alle Scopes (personal / featureInstance / mandate / global)
Sources Eingebundene Datenquellen mit Scope- und Neutralisierungs-Tags Alle Scopes

Chats-Tab: Baumstruktur nach Feature-Instanzen

Der Chats-Tab rendert alle Chats des Users als hierarchischer Baum. Top-Level sind die Feature-Instanzen, darunter die Chats in dieser Instanz, ggf. mit feature-spezifischen Substrukturen:

CHATS
─────────────────────────────────────
▾ AI Workspace  Mein Workspace
    💬 Recherche Datenschutzgesetz
    💬 Report Q1 Entwurf
    💬 Brainstorming Produktstrategie
▾ CommCoach  CEO Coaching
  ▾ Coaching-Modul
      💬 Verhandlungstechnik Session 3
      💬 Feedback-Gespräch Vorbereitung
  ▾ Dossier
      💬 Analyse Kommunikationsstil
▾ Trustee  Jahresabschluss 2025
    💬 Bilanzprüfung Rückfragen
    💬 Steueroptimierung Szenarien
▸ Automation  Workflow Tests
─────────────────────────────────────

Diese Struktur gibt dem User eine featureübergreifende Übersicht aller seiner AI-Interaktionen an einem Ort. Per Klick navigiert er direkt in den Chat — die UDB wechselt dabei ggf. den Feature-Kontext. Die aktuelle Feature-Instanz ist hervorgehoben und expandiert; andere Instanzen sind zugeklappt, aber zugänglich.

Skalierung: Bei vielen Feature-Instanzen und Hunderten von Chats kann der Baum unübersichtlich werden. Daher bietet der Chats-Tab zwei Zusatzfunktionen:

  • Suchfunktion: Volltextsuche über Chat-Titel und -Inhalte, filtert den Baum auf Treffer.
  • Flat Mode: Optionale chronologische Liste (alle Chats ohne Hierarchie, sortiert nach letzter Aktivität) als Alternative zur Baumansicht. Umschaltbar per Toggle im Tab-Header.

Drag-and-Drop: Chats als Kontext im Prompt

Chats aus dem Chat-Baum können per Drag-and-Drop in den Prompt-Bereich gezogen werden. Dabei wird nicht der Chat-Inhalt selbst übergeben, sondern die RAG-Daten, die zu diesem Chat gehören (Dokumente, Extraktionen, Quellen). Das RAG wird nach den dem Chat zugeordneten Daten abgefragt und diese als Kontext für den aktuellen AI-Call bereitgestellt. Use Cases:

  • Auswertung: Mehrere Coaching-Sessions aus CommCoach in den Workspace ziehen → „Fasse die Fortschritte der letzten 5 Sessions zusammen."
  • Cross-Feature-Analyse: Trustee-Chat + Workspace-Recherche zusammen auswerten → „Vergleiche die steuerlichen Szenarien mit den Recherche-Ergebnissen."
  • Reporting: Chats aus verschiedenen Instanzen zusammenziehen → „Erstelle einen Wochenbericht aus diesen Interaktionen."
  • Coaching-Review: CommCoach-Dossier-Chats als Input für eine Meta-Analyse → „Welche Kommunikationsmuster zeigen sich über die Sessions hinweg?"

Dasselbe Prinzip gilt auch für Files und Sources aus den anderen Tabs — alle UDB-Elemente sind als Kontext in Prompts nutzbar. Die UDB wird damit zur zentralen Kontext-Steuerung für alle AI-Interaktionen.

Einbettung in Features:

┌─────────────────────────────────────────────────────┐
│ Feature-Instanz (z.B. Trustee, CommCoach, Workspace)│
│                                                     │
│  ┌──────────┐  ┌──────────────────────────────────┐ │
│  │   UDB    │  │                                  │ │
│  │          │  │  Feature-spezifischer Content    │ │
│  │ [Chats]  │  │  (Workflows, Formulare, etc.)    │ │
│  │ [Files]  │  │                                  │ │
│  │ [Sources]│  │                                  │ │
│  │          │  │                                  │ │
│  └──────────┘  └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘

Verhalten:

  • Die UDB zeigt immer die Daten im Kontext der aktuellen Feature-Instanz an, ergänzt um personal- und mandate-scope Daten.
  • Der Chats-Tab zeigt den gesamten Chat-Baum über alle Feature-Instanzen des Users.
  • Der User kann in der UDB Datenquellen einbinden (Upload, Scope/Neutralisierung taggen) — direkt aus dem Feature-Kontext heraus.
  • Die UDB ist eine gemeinsame Komponente (frontend_nyla/src/components/), nicht pro Feature dupliziert.

Implikation: Die bestehende Sidebar-Logik im AI Workspace (Chats, Files, Sources) muss in Phase 2 aus dem Workspace herausgelöst und als eigenständige, wiederverwendbare Komponente bereitgestellt werden. Features binden die UDB ein und erhalten automatisch Zugriff auf den Unified Data Layer.

Wettbewerbseinordnung: UDB als Differenzierungsmerkmal

Eine Analyse bestehender AI-Plattformen (Stand März 2026) zeigt, dass keine eine vergleichbare featureübergreifende Daten- und Chat-Baumnavigation bietet:

Plattform Chat-Organisation Datenquellen Cross-Feature-Navigation
ChatGPT (OpenAI) Flach: Ordner + Projects, ein Feature (Chat) Dateien pro Chat oder pro Projekt Kein Feature-Konzept; alles ist Chat
Langdock "Projects" gruppieren Chats flach; Agents als separate Kategorie Knowledge Folders (bis 1000 Files), 55+ Integrations Chats und Agents getrennt; keine Baumstruktur über Feature-Grenzen
PageSpace Alles ist eine "Page" in einem einheitlichen Baum (Docs, Channels, Agents) Dateien als Pages im Baum; Kontext durch Platzierung Nächster Vergleich: einheitlicher Baum, aber kein Mandanten-Modell, kein RAG-Scope-Tagging, keine Neutralisierung
Microsoft Copilot Pro App (Word, Teams, etc.); keine übergreifende Chat-Historie Microsoft Graph (SharePoint, OneDrive) Kein übergreifender Chat-Baum; jede App hat eigene AI-Interaktion
Google Gemini Flach: Chat-Liste in Gemini; separate AI in Docs/Sheets Google Drive Keine Feature-übergreifende Struktur

Was PowerON mit der UDB anders macht:

  1. Feature-Instanz-Hierarchie im Chat-Baum: Kein anderes Tool zeigt Chats gruppiert nach fachlichen Modulen (Treuhand, Coaching, Workspace, Automation) in einem navigierbaren Baum mit feature-spezifischen Substrukturen (z. B. CommCoach → Coaching-Modul → Sessions).
  2. Drei Dimensionen in einer Sidebar (Chats + Files + Sources): Langdock und ChatGPT trennen Chat-Liste, Knowledge-Ordner und Integrationen in separate Bereiche. Die UDB vereint sie in einer Komponente mit durchgängigem Scope-Modell.
  3. Scope-Tagging pro Datenquelle: Kein Wettbewerber bietet granulares Tagging (personal / instanz / mandant / global) mit visueller Inline-Steuerung.
  4. Neutralisierung als Inline-Flag: Einzigartig — keine vergleichbare Plattform bietet Daten-Neutralisierung als pro-Quelle-Toggle.
  5. Multi-Tenant mit Mandanten-Isolation: PageSpace hat den ähnlichsten Baum-Ansatz, aber ohne Mandantenmodell, ohne RBAC-Scopes und ohne Neutralisierung.

Die UDB mit hierarchischem Chat-Baum, Scope-Tagging und Neutralisierung ist nach aktuellem Stand ein Alleinstellungsmerkmal.

Phasenplan

  1. Phase 1 (jetzt): Mandate-per-Registration und Own-Instance-Pattern umsetzen — schafft die richtige Mandanten-Grenze als Voraussetzung. Daten bleiben instanz-zentriert. FeatureInstance = Datengrenze (Instanz besitzt ihre Daten exklusiv).
  2. Phase 2: UDB-Komponente aus dem Workspace extrahieren und als wiederverwendbare Plattformkomponente bereitstellen. Shared Data Scope pro Mandant einführen. Dateien, Extraktionen und Wissensbasis mandantenweit verfügbar machen. Doppelte Strukturen (z. B. Voice in CommCoach vs. Workspace) konsolidieren. FeatureInstance = Datengrenze + mandantenweiter Zugriff (Daten sind instanz-zugeordnet, aber mandantenweit les-/nutzbar via Scope).
  3. Phase 3 (Ziel): Features sind reine Workflow-Oberflächen mit eingebetteter UDB. Der Unified Data Layer (inkl. Neutralisierung, RAG, Vektorspeicher) ist die zentrale Plattformkomponente. RAG-Scopes (personal/instance/mandate/global) sind vollständig implementiert. FeatureInstance = UI-Scope (die Instanz steuert nur noch, welche Workflow-Oberfläche der User sieht, nicht mehr die Datengrenze).

Semantische Verschiebung von FeatureInstance: Die Bedeutung von FeatureInstance wandelt sich fundamental über die Phasen — von der Datengrenze (Phase 1) zum UI-Scope (Phase 3). In Phase 1 besitzt eine Instanz ihre Daten exklusiv. In Phase 3 ist der Unified Data Layer die Datengrenze, und die Instanz definiert nur noch den Workflow-Kontext. Dieser Wandel ist beabsichtigt und muss bei der Implementierung der Phasenübergänge berücksichtigt werden: Daten-Ownership verschiebt sich von der Instanz zum Mandanten.


Daten-Neutralisierung als Kerndisziplin

Prinzip: Flag-basierte Steuerung

Die Neutralisierung wird über zwei unabhängige Flags gesteuert:

  1. Flag auf der Datenquelle (neutralize: true/false): Beim Einbinden einer Datenquelle in den Unified Data Layer legt der User fest, ob diese Daten neutralisiert werden sollen. Ist das Flag gesetzt, landen ausschließlich neutralisierte Dokumente im RAG — nie die Originale.
  2. Flag pro AI-Call (requireNeutralization: true/false): Unabhängig von der Datenquelle kann ein Feature oder der User verlangen, dass ein spezifischer AI-Call nur mit neutralisierten Daten arbeitet (z. B. für User-Prompts, die sensible Daten enthalten).

Regel: Wenn mindestens eines der beiden Flags gesetzt ist, werden die Daten vor dem Senden an eine AI-Instanz neutralisiert.

Fail-Safe-Regel: Wenn die Neutralisierung fehlschlägt (Modell nicht erreichbar, Timeout, unvollständige Neutralisierung), wird das Dokument nicht weitergegeben — weder an das externe AI-Modell noch in den RAG-Index. Der AI-Call wird ohne dieses Dokument ausgeführt und der User erhält einen Hinweis, dass ein Dokument aus Datenschutzgründen nicht einbezogen werden konnte. Kein Fallback auf Originaldaten.

Zugelassene Modelle: Operation Type neutralization

Es gibt keine speziellen LLMs für Neutralisierung. Die zugelassenen AI-Modelle in der Plattform haben einen Operation Type. Modelle mit dem Operation Type neutralization dürfen für Neutralisierungsaufgaben eingesetzt werden. Dies sind Modelle, denen die Plattform vertraut (z. B. lokal gehostet oder vertraglich abgesichert). Externe LLMs ohne diesen Operation Type sehen nur neutralisierte Daten.

Die Operation-Type-Konfiguration wird zentral verwaltet und ist nicht vom User änderbar.

Bestehende Code-Basis (Ausbau, kein Neubau)

Die Neutralisierung ist in der Plattform bereits implementiert und kann ausgebaut werden:

  • StringParser (subParseString.py): Regex-basierte Erkennung und Ersetzung von Emails, Telefonnummern, Adressen, IBANs, Daten, Policy-IDs und Namen. Platzhalter im Format [type.uuid] (z. B. [name.a1b2c3d4-...]). Mapping originalText → Platzhalter wird pro Durchlauf aufgebaut und ist persistent nutzbar.
  • DataNeutralizerAttributes (datamodelFeatureNeutralizer.py): Bestehende Datenbanktabelle für Platzhalter-Mappings mit originalText, patternType, mandateId, featureInstanceId, userId, fileId. Entspricht der im Konzept beschriebenen Platzhalter-Mapping-Tabelle.
  • neutralizeData-Action (neutralizeData.py): Bestehende Workflow-Action, die Dokumente (ContentExtracted / ContentPart) durch die Neutralisierung führt und als ActionDocument mit Validierungs-Metadaten zurückgibt.
  • DataNeutraliserConfig: Pro Feature-Instanz konfigurierbar mit enabled-Flag und namesToParse-Liste.

Die bestehende Architektur (Config → StringParser → Mapping-Tabelle → neutralisierter Output) entspricht dem Zielbild. Die Erweiterung betrifft: Integration der Scope-Flags, Anbindung an den RAG-Index, Fail-Safe-Logik (Dokument nicht weitergeben bei Fehler), und Re-Hydrierung der AI-Responses.

Neutralisierungs-Pipeline

flowchart TD
    Input["Eingabedaten (Dokument / Datenquelle)"] --> Resolve["Container aufloesen"]
    Resolve --> TypeCheck{"Datentyp?"}

    TypeCheck -->|Text| NeutText["Modell mit opType=neutralization: Text neutralisieren"]
    TypeCheck -->|Bild| NeutImage["Modell mit opType=neutralization: Bild → neutraler Text"]
    TypeCheck -->|Media| Drop["Media droppen (kein AI-Zugriff)"]

    NeutText --> MappingTable["Platzhalter-Mapping-Tabelle: Original ↔ Platzhalter"]
    NeutImage --> MappingTable

    MappingTable --> RAGIndex["Nur neutralisierte Dokumente → RAG-Index"]
    MappingTable --> ChatWorkflow["Neutralisierte Dokumente im ChatWorkflow sichtbar"]

    RAGIndex --> AICall["AI-Call mit neutralisierten Daten"]
    AICall --> AIResponse["AI-Response (mit Platzhaltern)"]
    AIResponse --> Rehydrate["Platzhalter rueckwandeln via Mapping-Tabelle"]
    Rehydrate --> UserOutput["Endergebnis fuer User"]

Schritte im Detail:

  1. Container auflösen: Dokumente (PDF, DOCX, XLSX, ...) werden in ihre Bestandteile zerlegt — Text, Bilder, Medien. Dies entspricht dem bestehenden Extraction-Pattern im Code.
  2. Media droppen: Audio-/Video-Inhalte werden nicht an AI weitergegeben (Datenschutz-Default).
  3. Bilder extrahieren: Modell mit opType=neutralization extrahiert Bildinhalte als neutralen beschreibenden Text (keine Gesichter, keine erkennbaren Merkmale).
  4. Text neutralisieren: Modell mit opType=neutralization ersetzt personenbezogene und sensible Daten (Namen, Adressen, Kontonummern, etc.) durch Platzhalter (z. B. [PERSON_1], [ADRESSE_2], [KONTO_3]).
  5. Platzhalter-Mapping-Tabelle: Die Zuordnung Original → Platzhalter wird in einer Datenbanktabelle pro Mandant gespeichert. Diese Tabelle ist die Grundlage für Rückwandlung und User-Einsicht.
  6. RAG-Index: Bei neutralisierten Datenquellen landen ausschließlich die neutralisierten Versionen im RAG — die Originale werden nicht indiziert.
  7. AI-Verarbeitung: Externe Modelle arbeiten ausschließlich mit neutralisierten Texten.
  8. Rückwandlung: Die AI-Response wird über die Mapping-Tabelle re-hydriert — Platzhalter werden durch Originaldaten ersetzt, bevor der User das Ergebnis sieht.

Transparenz im ChatWorkflow

Die neutralisierten Dokumente werden im ChatWorkflow-Objekt als Dokumente mitgegeben. Der User sieht damit bei jedem AI-Call, welche Daten tatsächlich an das externe Modell gesendet werden. Dies schafft Vertrauen und Nachvollziehbarkeit:

  • Im Chat-UI: Aufklappbarer Bereich „Gesendete Daten (neutralisiert)" mit den neutralisierten Dokumenten.
  • Der User kann prüfen, ob die Neutralisierung korrekt und vollständig ist.
  • Bei Problemen kann der User die Neutralisierung der Datenquelle anpassen oder deaktivieren.

User-Kontrolle

  • Der User kann seine neutralisierten Daten einsehen (Transparenz: was wurde wie neutralisiert).
  • Der User kann Platzhalter-Mappings löschen (Einträge in der Mapping-Tabelle entfernen).
  • Pro Datenquelle ist der Neutralisierungs-Status sichtbar (Badge/Icon im UI).

Automatisierung

Die Neutralisierung läuft vollautomatisch in der Pipeline:

  • Beim Einbinden einer Datenquelle mit neutralize=true: Daten werden sofort neutralisiert und die Mapping-Tabelle befüllt. Nur die neutralisierte Version wird im RAG-Index gespeichert.
  • Bei jedem AI-Call mit requireNeutralization=true: Die Eingabedaten des Calls durchlaufen die Pipeline vor dem Senden.
  • Batch-Re-Neutralisierung: Bei Änderung des Neutralisierungs-Flags einer Datenquelle werden bestehende RAG-Einträge re-neutralisiert oder de-neutralisiert (Mapping-Tabelle und RAG-Index aktualisieren).

Subscription: Datenvolumen als Parameter

Das RAG und der Unified Data Layer sind kein limitierender Faktor — die Plattform verfügt über ausreichend Datenbank- und Speicherkapazität, und der Fokus liegt darauf, Daten breit verfügbar zu machen. Dennoch sollte das Datenvolumen als Parameter in der Subscription integriert werden:

  • maxDataVolumeMB (oder GB) pro Mandant als Subscription-Parameter in MandateSubscription.
  • Kein hartes Billing auf Datenvolumen, aber ein Soft-Limit, das dem Mandanten-Admin angezeigt wird.
  • Bei Annäherung ans Limit: Hinweis im UI (z. B. „80% des Datenvolumens genutzt"), kein Schreib-Block.
  • Upgrade-Pfad: Höherer Plan = mehr Volumen.

Dies dient der Kapazitätsplanung und dem Upselling, nicht der Einschränkung.


Onboarding-Assistant

Der Onboarding-Assistant ist ein zentrales Element der User Experience. Nach Registrierung (oder nach erstem OAuth-Login) führt ein interaktiver Assistent den neuen User durch die ersten Schritte:

  1. Mandant-Typ bestätigen (bei OAuth-Flow: Personal oder Company wählen).
  2. Erste Feature-Instanz aktivieren — der Assistant empfiehlt basierend auf dem Kundenprofil (z. B. Workspace als Startpunkt).
  3. Erste Datenquelle einbinden — Upload oder Konnektor aktivieren, Scope und Neutralisierung erklären.
  4. Erster AI-Call — der User erlebt sofort den Mehrwert.

Der Onboarding-Assistant ist kein einmaliger Wizard, sondern ein kontextsensitiver Begleiter, der bei neuen Features, nach Pausen oder bei leeren Zuständen (kein Chat, keine Daten) wieder erscheint. Er reduziert Time-to-Value und ist entscheidend für Trial-Conversion.


Mandanten-Löschung: Kaskade

Die Löschung eines Mandanten erfordert eine vollständige Kaskade über alle abhängigen Entitäten. Diese muss atomar und nachvollziehbar ablaufen:

Mandate löschen
├── Alle FeatureInstances im Mandanten
│   ├── Alle FeatureAccess-Records pro Instanz
│   ├── Alle Daten pro Instanz (Chats, Dokumente, Extraktionen)
│   └── Alle Neutralisierungs-Mappings (DataNeutralizerAttributes) pro Instanz
├── Alle DataSources im Mandanten
│   └── RAG-Index-Einträge mit mandateId entfernen
├── Alle UserMandate-Zuordnungen zum Mandanten
│   (User selbst bleiben bestehen — sie haben ggf. andere Mandate)
├── MandateSubscription
│   └── Stripe-Subscription kündigen / Stripe-Quantity auf 0
├── Mandate-spezifische Konfigurationen (Rollen, Settings)
└── Mandate-Record selbst (soft-delete mit Retention oder hard-delete)

Schutzmechanismen:

  • isSystem=true-Mandate (Root) sind nicht löschbar.
  • Löschung nur durch Mandanten-Admin oder sysadmin.
  • Bestätigung mit expliziter Eingabe des Mandantennamens (wie bei GitHub Repo-Löschung).
  • Soft-Delete mit 30-Tage-Retention als Default; endgültige Löschung per Batch-Job.

Verwandte Dokumente


Referenz: zentrale Code-Stellen

Bereich Pfad
Registrierung gateway/modules/routes/routeSecurityLocal.py
Store (IST) gateway/modules/routes/routeStore.py
Bootstrap / Root gateway/modules/interfaces/interfaceBootstrap.py
Mandat anlegen gateway/modules/interfaces/interfaceDbApp.py (createMandate, createUserMandate)
Feature-Instanz gateway/modules/interfaces/interfaceFeatures.py (createFeatureInstance)
Pläne gateway/modules/datamodels/datamodelSubscription.py (BUILTIN_PLANS)
Dashboard / Store UI frontend_nyla/src/pages/Dashboard.tsx, frontend_nyla/src/pages/Store.tsx
Migration (neu) gateway/scripts/script_migrate_root_users.py (oder gateway/modules/migration/)

Dokument-Version: 2026-03-23 v4 — Review-Integration: mandateType mutabel/informativ, isSystem-Trennung, OAuth-Wizard, Subscription-Timing (PENDING), Store für alle User mit Auto-Mandate, mehrere Instanzen pro Feature erlaubt, scope=global RBAC-geschützt, UDL-Datenmodell definiert (bestehende Infrastruktur), FeatureInstance-Semantikverschiebung über Phasen, Chat-Baum-Skalierung (Suche+Flat), Drag-and-Drop via RAG-Daten, bestehender Neutralisierungs-Code referenziert, Datenvolumen als Subscription-Parameter, Onboarding-Assistant, Mandanten-Löschung-Kaskade.