From 70649218f1a0436fb00fce4eec87bc500ba5d0fc Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Sun, 29 Mar 2026 21:55:05 +0200 Subject: [PATCH] unified failsafe neutralization architecture --- compliance/Neutralisierung.md | 200 +++++++++++++++++++++++ concepts/Kontext-20260328.md | 129 --------------- concepts/Kontext-20260329.md | 288 ++++++++++++++++++++++++++++++++++ 3 files changed, 488 insertions(+), 129 deletions(-) create mode 100644 compliance/Neutralisierung.md delete mode 100644 concepts/Kontext-20260328.md create mode 100644 concepts/Kontext-20260329.md diff --git a/compliance/Neutralisierung.md b/compliance/Neutralisierung.md new file mode 100644 index 0000000..e2f81a2 --- /dev/null +++ b/compliance/Neutralisierung.md @@ -0,0 +1,200 @@ +# Neutralisierung + +**Stand:** 2026-03-29 + +--- + +## 1. Grundidee + +Neutralisierung passiert dort, wo **Content entsteht oder ins System einfliesst** — nicht als nachgelagerter Filter vor dem Modell-Call. Wenn Content einmal neutralisiert ist (z. B. im RAG), bleibt er neutralisiert. Kein doppeltes Verarbeiten. + +--- + +## 2. Wer fordert Neutralisierung? + +Drei Quellen, von breit nach spezifisch: + +| Quelle | Flag im Code | Bedeutung | +|--------|-------------|-----------| +| **Feature-Instanz** | `DataNeutraliserConfig.enabled` (hat `featureInstanceId` + `mandateId`) | Alle Daten in dieser Feature-Instanz werden neutralisiert. Betrifft jeden Content-Einstieg innerhalb dieser Instanz. | +| **Chat-Workflow / Session** | `ServiceCenterContext.requireNeutralization` (gesetzt z. B. vom AI-Workspace oder Automation) | Dieser Workflow/Turn: jeder Content, der hier verarbeitet wird, wird neutralisiert. | +| **Dokument oder Quelle** | `FileItem.neutralize`, `DataSource.neutralize`, `FeatureDataSource.neutralize` | Dieses konkrete Objekt: Content daraus wird neutralisiert, egal ob die Feature-Instanz oder der Workflow es sonst fordern würden. | + +**Auswertung:** Irgendeine Quelle sagt `True` → neutralisieren. Keine Quelle kann eine andere aufheben. Es gibt kein `False`-Override, das ein `True` von woanders aushebelt. + +--- + +## 3. Wo passiert die Neutralisierung? + +Am **Punkt der Content-Einspeisung** — dort wo Rohdaten zu verarbeitbarem Content werden: + +| Content-Einstieg | Was passiert | Wer prüft das Flag | +|------------------|-------------|---------------------| +| **Datei-Indexierung** (RAG) | Text-Chunks werden über `processText` neutralisiert, bevor sie ins Embedding gehen. Medien: nur internes Modell oder nicht indexieren. Ergebnis: RAG enthält nur neutralisierten Content. | `mainServiceKnowledge.indexFile` liest `FileItem.neutralize` | +| **Content-Extraktion** (Dokumente für KI-Verarbeitung) | Extrahierter Text/Tabellen werden neutralisiert. PDF, DOCX, XLSX, PPTX über `processFile` (Extract → neutralisieren → zurückschreiben). | Caller kennt die Quelle und deren Flag | +| **User-Prompt** (Chat-Eingabe) | Prompt-Text wird durch `processText` neutralisiert, wenn Feature-Instanz oder Workflow es fordern. | `mainServiceAi` prüft Context-Flag / Config | +| **DataSource-Download** | Flag wird auf erstellte `FileItem`s vererbt → bei Extraktion/Indexierung greift das File-Flag automatisch. | `mainServiceAgent._resolveDataSource` / `_downloadFromDataSource` | +| **FeatureDataSource-Abfrage** | Wenn eine `FeatureDataSource.neutralize=True` hat → Content aus diesem Sub-Agent-Call wird neutralisiert. | `mainServiceAgent._queryFeatureInstance` setzt `requireNeutralization=True` | +| **Workflow-Aktion `neutralizeData`** | Expliziter Neutralisierungs-Schritt auf bereits extrahierten ContentParts. | Workflow-Config + `NeutralizationConfig.enabled` | + +**Was NICHT nochmal neutralisiert werden muss:** +- RAG-Chunks, die bereits neutralisiert indexiert sind — die sind schon sauber. +- Web-Suchergebnisse — enthalten keine Mandantendaten, brauchen keine Neutralisierung. + +**Flag-Änderung:** Ändert sich `FileItem.neutralize` (Toggle) → Index löschen → Re-Indexierung triggern → neuer Index ist im richtigen Zustand. + +--- + +## 4. Engine: `NeutralizationService` + +Eine Engine, drei Einstiege, aufgerufen an den Content-Einstiegspunkten aus Abschnitt 3. + +| API | Eingabe | Status | +|-----|---------|--------| +| `processText(text)` | Rohtext (Prompt, Message, Text-ContentPart, Text-Chunk) | **Ist** | +| `processFile(fileId)` | Datei aus DB — wird nach MIME geroutet | **Ist** | +| `processBinaryBytes` / `Async` | Bytes + Dateiname + MIME | **Ist** | +| **`processImage(imageBytes, fileName)`** | Bild-Datei oder Bild-ContentPart (image/png, image/jpeg, …) | **Soll — fehlt heute** | + +**Ist-Zustand Bilder:** `_processBinaryFile` überspringt Parts mit `typeGroup == 'image'` (unverändert durchgereicht). Eigenständige Bild-Dateien (`image/*` MIME) werden in `_isBinaryMimeType` als Skip behandelt. **Es gibt keinen Bild-Neutralisierungs-Pfad.** + +**Soll:** `processImage` als eigener Einstieg. Ruft internes Vision-Modell (`NEUTRALIZATION_IMAGE` → Private-LLM) auf, um sensible Inhalte in Bildern zu erkennen/entfernen. Kein externer Provider. Modell nicht verfügbar → Bild blockieren (nicht unbehandelt weiterreichen). + +**Ablauf nach MIME:** + +| MIME | Pfad | +|------|------| +| PDF, DOCX, XLSX, PPTX | `runExtraction` → Text/Table-Parts durch `_neutralizeText`, Bild-Parts durch `processImage` → PDF in-place oder Office-Renderer | +| Text, JSON, CSV, XML | `_neutralizeText` direkt (TextProcessor, ListProcessor, BinaryProcessor je nach Inhalt) | +| Bilder (image/*) | **`processImage`** → internes Vision-Modell (`NEUTRALIZATION_IMAGE`). Nicht verfügbar → blockieren. | +| Video, Audio | Nur internes Modell oder blockieren (analog Bilder, aber niedrigere Priorität) | +| Anderes Binary | Skip (nicht unterstützt) | + +**Mapping:** Platzhalter `[typ.uuid]` → Attribute in DB. `resolveText(text)` für Rückübersetzung. + +--- + +## 5. Modellauswahl + +| Operation Type | Modelle | Zweck | +|----------------|---------|-------| +| `NEUTRALIZATION_TEXT` | `poweron-text-general` (Rating 9) | Falls Engine ein LLM für Text braucht | +| `NEUTRALIZATION_IMAGE` | `poweron-vision-general` (9), `poweron-vision-deep` (9) | Medien bei Neutralisierungspflicht: nur intern | + +Registriert in `aicorePluginPrivateLlm.py`. Externe Provider haben keine `NEUTRALIZATION_*`-Ratings → werden nie für Neutralisierung gewählt. + +--- + +## 6. Fail-safe + +| Situation | Verhalten | +|-----------|-----------| +| Neutralisierung gefordert, Engine nicht verfügbar | Content **nicht** weiterverarbeiten (blockieren) | +| Neutralisierung eines Teils schlägt fehl | Teil entfernen, nicht im Rohzustand weiterleiten | +| Kein internes Modell für Medien verfügbar | Medien-Part entfernen | + +--- + +## 7. Code-Karte + +| Bereich | Pfad | +|---------|------| +| Engine | `features/neutralization/serviceNeutralization/mainServiceNeutralization.py` + `subProcess*.py` | +| Config + Attribute DB | `features/neutralization/datamodelFeatureNeutralizer.py`, `interfaceFeatureNeutralizer.py` | +| Index (Data-at-rest) | `serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py` | +| Prompt-Neutralisierung | `serviceCenter/services/serviceAi/mainServiceAi.py` (`_shouldNeutralize`, `_neutralizeRequest`) | +| Agent / DataSource / Feature | `serviceCenter/services/serviceAgent/mainServiceAgent.py` | +| Workflow-Aktion | `workflows/methods/methodContext/actions/neutralizeData.py` | +| Modelle / Enums | `datamodels/datamodelAi.py`, `aicore/aicorePluginPrivateLlm.py` | +| Flags | `datamodelFiles.py` (`FileItem.neutralize`), `datamodelDataSource.py`, `datamodelFeatureDataSource.py` | +| Context | `serviceCenter/context.py` (`ServiceCenterContext.requireNeutralization`) | + +--- + +## 8. Umsetzungsplan + +### Schritt 1: `processImage` in Engine bauen + +**Datei:** `mainServiceNeutralization.py` +**Was:** Neue Methode `processImage(imageBytes: bytes, fileName: str, mimeType: str) -> Dict` und `processImageAsync`. +**Wie:** Internes Vision-Modell aufrufen (`NEUTRALIZATION_IMAGE` via Model Selector / `aiObjects`). Prompt: sensible Inhalte im Bild erkennen und beschreiben, damit Caller entscheiden kann ob Bild blockiert wird. Kein internes Modell verfügbar → `{'status': 'blocked'}` zurückgeben. +**Abhängigkeit:** `OperationTypeEnum.NEUTRALIZATION_IMAGE` + Modell-Ratings in `aicorePluginPrivateLlm.py` — **bereits erledigt**. + +### Schritt 2: `_shouldNeutralize` vereinfachen + +**Datei:** `mainServiceAi.py` (Zeile ~560) +**Ist:** Prüft `request.requireNeutralization` → `context.requireNeutralization` → `NeutralizationConfig.enabled` (Mandant-Level Config). `request.requireNeutralization=False` bricht sofort ab. +**Soll:** Drei Quellen gemäss Abschnitt 2, kein `False`-Override: +1. Feature-Instanz: `NeutralizationConfig.enabled` (hat `featureInstanceId`) +2. Workflow/Session: `context.requireNeutralization` +3. Request: `request.requireNeutralization` + +Irgendein `True` → neutralisieren. `False` in einer Quelle hebt `True` in einer anderen **nicht** auf. +**Änderung:** `if request.requireNeutralization is False: return False` **entfernen**. Stattdessen OR-Verknüpfung aller drei Quellen. + +### Schritt 3: `_neutralizeRequest` auf Prompt/Messages beschränken + +**Datei:** `mainServiceAi.py` (Zeile ~592) +**Ist:** Neutralisiert `prompt`, `context`, `messages` (String-Content). +**Soll:** Zusätzlich Text in `contentParts` und Text-Teile in multimodalen Messages (content-Liste mit `type==text`). Bild-Teile in Messages: bei Neutralisierungspflicht über `processImage` prüfen oder entfernen. +**Wichtig:** Hier geht es nur um Prompt-/Sessiondaten. File-Content aus RAG ist bereits neutralisiert (Schritt 5), muss hier nicht nochmal durch. + +### Schritt 4: `indexFile` — Bild-Chunks behandeln + +**Datei:** `mainServiceKnowledge.py` (Zeile ~146 ff.) +**Ist:** Nur `textObjects` (contentType == "text") werden bei `_shouldNeutralize` durch `processText` geschickt. Bild-Chunks (`contentType == "image"`) werden unverändert indexiert. +**Soll:** Bild-Chunks bei `_shouldNeutralize` über `processImage` (Schritt 1) prüfen. Ergebnis `blocked` → Bild-Chunk nicht indexieren. Ergebnis OK → Bild-Chunk indexieren (internes Modell hat es gesehen, kein externer Provider nötig). +**Abhängigkeit:** Schritt 1. + +### Schritt 5: Agent-Tool `readFile` — Content bei Extraktion neutralisieren + +**Datei:** `mainServiceAgent.py`, Tool `_readFile` (Zeile ~540 ff.) +**Ist:** Liest aus Knowledge Store (bereits indexiert) oder extrahiert on-demand. Kein Neutralisierungs-Check. +**Soll:** +- **Knowledge Store Pfad (Zeile ~548):** RAG-Chunks sind bereits neutralisiert wenn `FileItem.neutralize=True` war (Schritt 4) → nichts zu tun. +- **On-Demand Extraktion (Zeile ~630 ff.):** Nach `runExtraction` und vor Rückgabe der Text-Objekte: `FileItem.neutralize` laden. Wenn `True` → Text-Objekte durch `processText`, Bild-Objekte durch `processImage`. + +### Schritt 6: Agent-Tool `summarizeContent` — File-Flag prüfen + +**Datei:** `mainServiceAgent.py`, Tool `_summarizeContent` (Zeile ~1921 ff.) +**Ist:** Liest Content-Objects aus Knowledge Store, baut `combinedText` und ruft `callAi` auf. Kein Neutralisierungs-Check. +**Soll:** Content aus Knowledge Store ist bereits neutralisiert (Schritt 4) → nichts zu tun, **sofern** File indexiert war. Wenn nicht indexiert: File-Flag prüfen, Text vor `callAi` durch `processText`. +**Hinweis:** Durch Schritt 4 ist der Regelfall abgedeckt. + +### Schritt 7: Agent-Tool `describeImage` — File-Flag prüfen + +**Datei:** `mainServiceAgent.py`, Tool `_describeImage` (Zeile ~2015 ff.) +**Ist:** Lädt Bild-Chunk oder Rohdaten, sendet per `callAi` mit `IMAGE_ANALYSE` an **beliebigen** Provider (inkl. extern). Kein Neutralisierungs-Check. +**Soll:** `FileItem.neutralize` laden (fileId ist bekannt). Wenn `True`: +- `operationType` auf `NEUTRALIZATION_IMAGE` statt `IMAGE_ANALYSE` → Model Selector wählt nur internes Modell. +- Kein internes Modell → Tool gibt Fehler zurück statt Bild an externen Provider zu senden. + +### Schritt 8: `_processBinaryFile` — Bild-Parts nicht mehr überspringen + +**Datei:** `mainServiceNeutralization.py` (Zeile ~298) +**Ist:** `if type_group in ('binary', 'image'): neutralized_parts.append(part); continue` — Bild-Parts werden übersprungen. +**Soll:** `binary` weiterhin skip. `image` → durch `processImage` (Schritt 1). Ergebnis `blocked` → Part entfernen. Ergebnis OK → Part behalten. +**Abhängigkeit:** Schritt 1. + +### Schritt 9: Workflow-Aktion `neutralizeData` — Bild-Parts + +**Datei:** `neutralizeData.py` (Zeile ~130 ff.) +**Ist:** Iteriert über ContentParts, neutralisiert nur Parts mit `part.data` (Text). `typeGroup != text/table` → unverändert durchgereicht. +**Soll:** Bild-Parts durch `processImage`. Ergebnis `blocked` → Part entfernen. +**Abhängigkeit:** Schritt 1. + +### Schritt 10: Tests + +| Test | Was | +|------|-----| +| T1 | `FileItem.neutralize=True` → `indexFile` → Text-Chunks neutralisiert, Bild-Chunks geprüft/blockiert | +| T2 | `readFile` on-demand auf File mit `neutralize=True` → Text neutralisiert | +| T3 | `describeImage` auf File mit `neutralize=True` → nur internes Modell | +| T4 | Feature-Instanz Config `enabled=True` → Prompt in `callAi` neutralisiert | +| T5 | Workflow `requireNeutralization=True` → Prompt neutralisiert | +| T6 | `processFile` auf PDF mit Bildern → Text neutralisiert, Bild-Parts durch `processImage` | +| T7 | Flag-Toggle → Index gelöscht → Re-Index im richtigen Zustand | + +### Schritt 11: Logging + +**Alle Stellen (Schritte 2–9):** Bei Neutralisierung loggen: welche Quelle (Feature-Instanz / Workflow / File-Flag), welche `fileId` falls vorhanden, Ergebnis (OK / Part entfernt / blockiert). **Kein Klartext** in Logs. diff --git a/concepts/Kontext-20260328.md b/concepts/Kontext-20260328.md deleted file mode 100644 index 986b60a..0000000 --- a/concepts/Kontext-20260328.md +++ /dev/null @@ -1,129 +0,0 @@ -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=` -- 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=`, `featureInstanceId=`, `mandateId=` -- Templates: `isSystemRole=False`, `mandateId=NULL`, `featureInstanceId=NULL`, `featureCode=` (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.py` — `POST /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`) diff --git a/concepts/Kontext-20260329.md b/concepts/Kontext-20260329.md new file mode 100644 index 0000000..1c7b7b4 --- /dev/null +++ b/concepts/Kontext-20260329.md @@ -0,0 +1,288 @@ +# PowerOn Applikations-Kontext (Stand 2026-03-29) + +Multi-Tenant SaaS-Applikation mit Feature-Store-Modell, AI-Agent-Workspace und mandantenweiter Datenneutralisierung. + +--- + +## 1. Technologie-Stack + +| Layer | Technologie | Pfad | +|-------|------------|------| +| Backend (Gateway) | FastAPI (Python), PostgreSQL | `@poweron/gateway` | +| Frontend | React/TypeScript (Vite) | `@poweron/frontend_nyla` | +| AI Core | Multi-Provider (Anthropic, OpenAI, Mistral, Perplexity, Tavily, PrivateLLM) | `@poweron/gateway/modules/aicore` | +| DB-Connector | PostgreSQL mit pgvector (Embeddings) | `@poweron/gateway/modules/connectors/connectorDbPostgre.py` | + +--- + +## 2. Gateway-Architektur (Backend) + +### Modulstruktur (`gateway/modules/`) + +| Modul | Zweck | +|-------|-------| +| `aicore/` | Model-Registry, Model-Selector, Provider-Plugins (Anthropic, OpenAI, Mistral, Perplexity, Tavily, PrivateLLM) | +| `auth/` | Authentifizierung | +| `connectors/` | DB-Connector (PostgreSQL), externe Konnektoren | +| `datamodels/` | Pydantic-Datenmodelle (30+ Dateien: Ai, Billing, Chat, Content, Files, Knowledge, Rbac, Subscription, Workflow...) | +| `features/` | Feature-Module (autonome Domänen): workspace, automation, automation2, chatbot, commcoach, neutralization, realEstate, trustee, teamsbot | +| `interfaces/` | DB-Interfaces (App, Billing, Chat, Knowledge, Management, Subscription), AI-Objects, RBAC, Features, Messaging | +| `migration/` | Daten-Migrationen | +| `routes/` | REST-API-Routen (30+ Dateien: Admin, Billing, DataFiles, DataSources, Security, Store, System...) | +| `security/` | Sicherheits-Middleware | +| `serviceCenter/` | Zentrale Service-Orchestrierung (siehe Abschnitt 3) | +| `serviceHub/` | Service-Registry und Dependency Injection | +| `shared/` | Gemeinsame Utilities (attributeUtils, etc.) | +| `system/` | System-Konfiguration | +| `workflows/` | Workflow-Engine mit Methoden und Aktionen (siehe Abschnitt 4) | + +### Interfaces (DB-Schicht) + +Die Interfaces kapseln alle Datenbankzugriffe: + +| Interface | Verantwortlich für | +|-----------|--------------------| +| `interfaceDbApp.py` | User, Mandate, FeatureAccess, UserConnections, Preferences | +| `interfaceDbBilling.py` | BillingAccount, BillingTransaction, Subscriptions | +| `interfaceDbChat.py` | ChatWorkflow, ChatMessage, ChatDocument | +| `interfaceDbKnowledge.py` | FileContentIndex, ContentChunk, RoundMemory (RAG/Knowledge Store) | +| `interfaceDbManagement.py` | FileItem, Folder, Prompt, DataSource (mandantenweite Stammdaten) | +| `interfaceDbSubscription.py` | Subscription-Verwaltung | +| `interfaceAiObjects.py` | AI-Call-Abstraction (Text, Embedding, Vision, Streaming) | +| `interfaceFeatures.py` | Feature-Instanz-Lifecycle, Template-Rollen-Kopie | +| `interfaceRbac.py` | RBAC-Regelauswertung | + +--- + +## 3. ServiceCenter (Kern-Orchestrierung) + +Das ServiceCenter ist die zentrale Schicht, die alle Services verbindet. Es wird pro Request/Session erstellt und propagiert einen **ServiceCenterContext** (`context.py`). + +### Services (`serviceCenter/services/`) + +| Service | Pfad | Zweck | +|---------|------|-------| +| `serviceAgent` | `mainServiceAgent.py` (3400+ Zeilen) | AI-Agent mit 30+ Tools (readFile, writeFile, searchInFileContent, browseContainer, summarizeContent, readContentObjects, webSearch, sendMail, etc.) | +| `serviceAi` | `mainServiceAi.py` (1700+ Zeilen) | AI-Call-Gateway: Neutralisierung, Billing-Preflight, Provider-Selection, Streaming, Extraction, Generation | +| `serviceKnowledge` | `mainServiceKnowledge.py` | Knowledge Store: Indexierung, Semantic Search, RAG-Kontext-Aufbau (`buildAgentContext`) | +| `serviceBilling` | | Billing-Checks, Transaktionen | +| `serviceChat` | | Chat-Persistence | +| `serviceExtraction` | | Dokument-Extraktion (PDF, DOCX, XLSX...) | +| `serviceGeneration` | | Dokument-Generierung | +| `serviceMessaging` | | E-Mail, Notifications | +| `serviceSubscription` | | Subscription-Verwaltung | +| `serviceWeb` | | Web-Scraping | + +### ServiceCenterContext (`context.py`) + +Propagiert pro Request: +- `userId`, `mandateId`, `featureInstanceId` +- `requireNeutralization: Optional[bool]` — Chat-Level Neutralisierungsflag +- Provider-Restrictions, Billing-Context + +### Agent-Tools (in `mainServiceAgent.py`) + +Der Agent hat 30+ registrierte Tools. Tools liefern direkt Rohdaten — Neutralisierung erfolgt zentral im AI-Service (`mainServiceAi.py`) bevor Daten an ein AI-Modell gesendet werden. + +Datenschutz-relevante DataSource-Tools: +- `_resolveDataSource()` — liest `neutralize`-Flag der DataSource als 4. Tuple-Element +- `_downloadFromDataSource()` — vererbt `neutralize`-Flag auf erstellte FileItems +- `_queryFeatureInstance()` — setzt `requireNeutralization=True` auf Sub-Agent AI-Calls wenn FeatureDataSource `neutralize=True` + +--- + +## 4. Workflow-Engine + +### WorkflowManager (`workflows/workflowManager.py`, 1400+ Zeilen) + +Zentrale Workflow-Steuerung für Automation und dynamische Workflows. + +### Methoden (`workflows/methods/`) + +| Methode | Aktionen | +|---------|----------| +| `methodContext` | `extractContent`, `neutralizeData`, `saveContent`, `transformContent` | +| `methodAi` | AI-Analyse, Summarization, Prompt-Processing | +| `methodChatbot` | Chatbot-spezifische Aktionen | +| `methodSharepoint` | SharePoint-Integration | +| `methodOutlook` | Outlook/E-Mail-Integration | +| `methodJira` | Jira-Integration | +| `methodTrustee` | Trustee-Feature-Aktionen | + +--- + +## 5. Neutralisierungs-System (KRITISCH) + +**Architektur, Prozess und Code-Karte (ein Dokument):** [wiki/compliance/Neutralisierung.md](../compliance/Neutralisierung.md) + +### Prinzip: Zentrales Gate — ALLE Daten, die an ein AI-Modell gehen, werden AUSSCHLIESSLICH in `mainServiceAi.py` neutralisiert. + +### Architektur: Einzige zentrale Stelle + +Neutralisierung findet nur an **einer einzigen Stelle** statt: `_shouldNeutralize()` und `_neutralizeRequest()` in `mainServiceAi.py`. Kein anderer Service, kein Tool und kein Workflow prüft oder neutralisiert eigenständig. Failsafe: Wenn Neutralisierung erforderlich ist (`requireNeutralization=True`) und fehlschlägt, wird der AI-Call blockiert (`RuntimeError`). + +### Neutralisierungs-Trigger (Prioritätsreihenfolge) + +1. **Per-Request:** `AiCallRequest.requireNeutralization = True/False` — expliziter Override +2. **Chat-Level:** `ServiceCenterContext.requireNeutralization = True` — Session-Level-Flag +3. **Mandate-Config:** `NeutralizationConfig.enabled` — mandantenweite Konfiguration + +### Was neutralisiert wird (in `_neutralizeRequest`) + +- `request.prompt` — Benutzer-Prompt +- `request.context` — RAG-Kontext (externe Dokumente) +- `request.messages` — Chat-Nachrichten (OpenAI-Style) + +### Schlüssel-Dateien + +| Datei | Rolle | +|-------|-------| +| `features/neutralization/serviceNeutralization/mainServiceNeutralization.py` | `processText(text)` → `{neutralized_text, mappings}`, `resolveText(text)` → Rehydrierung | +| `routes/routeDataFiles.py` | `PATCH /{fileId}/neutralize` — Toggle löscht Index synchron, re-indexiert im Background | +| `serviceAi/mainServiceAi.py` | **Zentrales Gate:** `_shouldNeutralize()` prüft Request → Context → Config; `_neutralizeRequest()` neutralisiert prompt, context, messages. Hard-Mode bei `requireNeutralization=True` | +| `serviceKnowledge/mainServiceKnowledge.py` | `indexFile()` neutralisiert bei `FileItem.neutralize=True` (Data-at-rest-Schutz). RAG-Chunks werden neutral gespeichert | +| `serviceAgent/mainServiceAgent.py` | `_resolveDataSource()` liest `neutralize`-Flag der DataSource. `_downloadFromDataSource()` vererbt Flag auf erstellte FileItems. `_queryFeatureInstance()` setzt `requireNeutralization=True` wenn FeatureDataSource neutralize-Flag gesetzt | +| `workflows/methods/methodContext/actions/neutralizeData.py` | Explizite Neutralisierungs-Aktion in Workflows (prüft nur Config, nicht Context-Flag) | + +### Failsafe-Kette + +1. **Toggle ON:** Index + Chunks werden synchron gelöscht → kein Leak bei Background-Re-Index-Fehler +2. **Indexierung:** `indexFile()` neutralisiert Chunks bei `FileItem.neutralize=True` → Data-at-rest ist sicher +3. **DataSource-Download:** neutralize-Flag wird auf erstellte FileItems vererbt +4. **FeatureDataSource:** `_queryFeatureInstance()` setzt `requireNeutralization=True` auf Sub-Agent AI-Calls +5. **AI-Calls (Zentrales Gate):** Hard-Mode (`requireNeutralization=True`) → Prompt-/Context-Fehler = `RuntimeError` (AI-Call blockiert), Message-Fehler = Message entfernt +6. **`_rehydrateResponse()`** bleibt als Utility-Methode verfügbar, wird aber nicht mehr automatisch aufgerufen + +### NeutralizationPanel (Frontend) + +`frontend_nyla/src/pages/views/workspace/NeutralizationPanel.tsx` — zeigt alle Files mit `neutralize`-Flag, deren Status und Platzhalter-Mappings. API: `GET /api/workspace/{instanceId}/files` → `{files: [...]}`. + +--- + +## 6. Frontend-Architektur + +### Struktur (`frontend_nyla/src/`) + +| Ordner | Inhalt | +|--------|--------| +| `pages/` | Seiten: admin/, basedata/, billing/, settings/, views/ (workspace, commcoach, chatbot, trustee, automation) | +| `components/` | Wiederverwendbar: FormGenerator, FolderTree, Navigation, UnifiedDataBar, Automation2FlowEditor, OnboardingAssistant | +| `hooks/` | useApi, useFiles, useNavigation, useConfirm, usePrompt, useResizablePanels, etc. | +| `contexts/` | FileContext, PekContext, ToastContext, WorkflowSelectionContext | +| `api/` | API-Client (api.ts) und Feature-spezifische API-Module | +| `core/` | PageManager | +| `layouts/` | Layout-Komponenten | +| `locales/` | i18n | +| `types/` | TypeScript-Typen | +| `utils/` | Utility-Funktionen | + +### Wichtige UI-Komponenten + +| Komponente | Zweck | +|------------|-------| +| `UnifiedDataBar (UDB)` | Multi-Tab-Panel: Chats, Files, Sources — wird in Workspace und CommCoach genutzt | +| `FormGenerator` | Dynamische Formulare/Tabellen aus Backend-Attribut-Definitionen | +| `FolderTree` | Rekursiver Ordnerbaum mit Drag&Drop, Multi-Selection, Inline-Editing | +| `MandateNavigation` | Feature-Baum-Navigation mit Mandanten-Kontext | +| `Automation2FlowEditor` | n8n-style Flow-Builder für Automation2 | +| `WorkspacePage` | AI-Workspace mit Chat, UDB, Agent-Streaming | + +### UI-Regeln + +- **Keine Browser-Dialoge** (alert/confirm/prompt) — stattdessen `useConfirm()` und `usePrompt()` Hooks +- Alle internen Funktionen mit `_` Prefix +- camelCase für Variablen und Funktionen + +--- + +## 7. RBAC-System (Zwei getrennte Rollensysteme!) + +### Mandantenrollen +- Labels: `admin`, `user`, `viewer` +- Scope: `mandateId`, kein `featureCode`/`featureInstanceId` +- Junction: `UserMandateRole` + +### Feature-Instanz-Rollen +- Labels: `workspace-admin`, `workspace-user`, `workspace-viewer`, etc. +- Scope: `mandateId` + `featureCode` + `featureInstanceId` +- Junction: `FeatureAccessRole` + +**Strikte Regel:** NIE eine Mandantenrolle einer FeatureAccessRole zuweisen oder umgekehrt. + +--- + +## 8. Billing & Subscriptions + +- **Modell:** PREPAY_MANDATE (kein PREPAY_USER) +- **Billing-Check:** Vor jedem AI-Call via `_preflightBillingCheck` und `_checkBillingBeforeAiCall` +- **Datenvolumen:** `assertCapacity("dataVolumeMB")` prüft RAG-Index-Grösse +- **Trial:** 5 CHF, Standard: 10 CHF/Monat + +--- + +## 9. AI-Core (Provider-Abstraction) + +### Model-Registry (`aicoreModelRegistry.py`) +Registriert alle Provider-Plugins und deren Modelle dynamisch. + +### Model-Selector (`aicoreModelSelector.py`) +Wählt optimal basierend auf: Operation Type, Prompt-Grösse, Provider-Restrictions, Score-Ranking. + +### Provider-Plugins +- `aicorePluginAnthropic.py` — Claude Sonnet/Haiku/Opus +- `aicorePluginOpenai.py` — GPT-4o, Embeddings (text-embedding-3-small/large), DALL-E +- `aicorePluginMistral.py` — Mistral Large/Small, Mistral Embed +- `aicorePluginPerplexity.py` — Sonar/Sonar-Pro +- `aicorePluginTavily.py` — Web-Search +- `aicorePluginPrivateLlm.py` — Private LLM +- `aicorePluginInternal.py` — Interne Extraktoren/Generatoren/Renderer + +### Operation Types +`dataExtract`, `dataAnalyse`, `agent`, `embedding`, `imageGeneration`, `vision`, `webSearch` + +--- + +## 10. Knowledge Store (RAG) + +### Datenmodelle (`datamodelKnowledge.py`) +- `FileContentIndex` — Pro File: Struktur, Metadaten, `isNeutralized`, `neutralizationStatus`, `totalObjects`, `totalSize` +- `ContentChunk` — Semantische Chunks mit Embedding-Vektor, `fileId`, `data`, `metadata` +- `RoundMemory` — Kontext aus früheren Runden (file_ref, etc.) + +### Indexierung (`serviceKnowledge.indexFile()`) +1. Extraktion via `serviceExtraction` +2. Optional: Neutralisierung via `serviceNeutralization.processText()` +3. Chunking + Embedding +4. Speicherung in `FileContentIndex` + `ContentChunk` + +### RAG-Kontext (`serviceKnowledge.buildAgentContext()`) +1. Semantic Search via pgvector +2. Context-Budget: Priorisierte Layer (File-Refs, Instance Docs, Mandate Docs) +3. Neutralisierung erfolgt zentral im AI-Service wenn der RAG-Kontext als `request.context` an ein AI-Modell geht + +--- + +## 11. Feature-Module + +| Feature | Beschreibung | +|---------|-------------| +| `workspace` | AI-Agent-Workspace mit Chat, Tools, RAG, Streaming | +| `automation` | Workflow-Automatisierung (v1) | +| `automation2` | Flow-Editor n8n-Style (v2) | +| `chatbot` | Chatbot-Feature | +| `commcoach` | Kommunikations-Coach mit Voice, Dossier, UDB | +| `neutralization` | Datenneutralisierungs-Service und Config | +| `trustee` | Trustee/Treuhand-Feature (Buchhaltung, Positionen) | +| `realEstate` | Immobilien-Feature | +| `teamsbot` | MS Teams Bot | + +--- + +## 12. Coding-Konventionen + +- Alle internen Funktionen beginnen mit `_` Prefix +- camelCase für Variablen und Funktionsnamen (kein snake_case) +- Keine Browser-Dialoge — `useConfirm()` / `usePrompt()` Hooks +- Fehler propagieren — keine stillen Fallbacks bei kritischen Pfaden +- Pydantic-Models als einzige Quelle für UI-Feld-Definitionen +- `PowerOnModel` als Basis mit `sysCreatedAt`, `sysCreatedBy`, `sysModifiedAt`, `sysModifiedBy`