# Neutralisierungs-System ## Überblick Neutralisierung ersetzt mandantensensible Inhalte durch stabile Platzhalter, bevor Text an externe KI-Modelle geht oder dauerhaft im RAG landet. Rohdaten aus Tools werden nicht separat „nachgefiltert“: der Schutz liegt am **Content-Einstieg** (Indexierung, Extraktion, zentrales AI-Gate) und an vererbten Flags auf `FileItem` / Session / Feature-Konfiguration. ## Architektur: Einzige zentrale Stelle **Für jeden AI-Modell-Call** gilt: Prompt, RAG-Kontext und Chat-Nachrichten werden **ausschließlich** in `mainServiceAi.py` geprüft und neutralisiert (`_shouldNeutralize`, `_neutralizeRequest`). Kein anderes Modul entscheidet parallel „stattdessen“ für dieselbe Modell-Schnittstelle. Orthogonal dazu (kein Ersatz des Gates, sondern Data-at-rest bzw. Workflow): - **RAG:** `mainServiceKnowledge.indexFile()` neutralisiert Chunks bei `FileItem.neutralize=True`. **Seit RAG Consent & Control (2026-05): `DataSource.neutralize` ist die Single Source of Truth für Neutralisierung bei RAG-Indexierung.** Das frühere `UserConnection.knowledgePreferences.neutralizeBeforeEmbed` existiert nicht mehr — Walker lesen `neutralize` ausschließlich aus der `DataSource`-Row, und seit 2026-05 (Cascade-Inherit) folgt der Wert via Path-Traversal dem nächsten expliziten Vorfahren (`null` = vererbt). Effective-Resolution: `serviceKnowledge/_inheritFlags.getEffectiveFlag()`. - **Agent:** DataSource- und FeatureDataSource-Flags setzen bzw. vererben Neutralisierungspflicht; Sub-Agent-Calls können `requireNeutralization=True` tragen. - **Workflows:** Aktion `neutralizeData` neutralisiert explizit extrahierte ContentParts (prüft u. a. `NeutralizationConfig.enabled`, nicht das Session-Flag). ## Neutralisierungs-Trigger (Prioritätsreihenfolge) **Am AI-Gate (`_shouldNeutralize`)** (Auswertung in der dokumentierten Reihenfolge): 1. **Feature-Konfiguration:** `NeutralizationConfig.enabled` (DataNeutraliser / Feature-Instanz). 2. **Chat-/Session-Level:** `ServiceCenterContext.requireNeutralization` (z. B. Workspace, Automation). 3. **Pro Request:** `AiCallRequest.requireNeutralization` — expliziter Override auf dem Call. **Quellen gesamt (OR-Logik):** Ist irgendwo Neutralisierung erforderlich (Feature-Instanz, Workflow/Session, konkretes Objekt mit `FileItem.neutralize` / `FileFolder.neutralize` / `DataSource.neutralize` / `FeatureDataSource.neutralize` / `FeatureDataSource.neutralizeFields`), wird entsprechend verarbeitet. Dokumentierte Produktregel: **Kein `False` von einer Quelle hebt ein `True` einer anderen auf.** ## Was neutralisiert wird Im zentralen AI-Gate (`_neutralizeRequest`): - `request.prompt` - `request.context` (RAG-Kontext) - `request.messages` (OpenAI-artige Nachrichten) **Engine** (`NeutralizationService`): `processText`, `processFile`, `processBinaryBytes` (synchron/asynchron); Rückübersetzung über `resolveText` / Mappings. Modell-Familien u. a. `NEUTRALIZATION_TEXT` / `NEUTRALIZATION_IMAGE` (Private-LLM-Plugin; externe Provider ohne diese Ratings werden für Neutralisierung nicht gewählt). **Nicht erneut neutralisieren:** Bereits neutral indexierte RAG-Chunks; Web-Suchergebnisse ohne Mandantendaten (laut Spezifikation). **Bekannter Ist-Stand Medien:** Bild-Binary wird in der Engine derzeit weitgehend durchgereicht bzw. übersprungen — dedizierter Bild-Neutralisierungspfad ist in der Fachdoku als Lücke / Soll beschrieben, nicht als abgeschlossene Implementierung ausgewiesen. ## Failsafe-Kette 1. **Toggle `FileItem.neutralize`:** Index und Chunks werden synchron gelöscht; Re-Index im Hintergrund verhindert Leaks bei Fehlern in der Nachindexierung. 2. **Toggle `FileFolder.neutralize`:** Propagiert `neutralize` rekursiv auf alle Dateien im Ordner; Index/Chunks werden pro Datei synchron gelöscht, Re-Index im Hintergrund. Neue/verschobene Dateien erben das Flag des Ziel-Ordners. 3. **Indexierung:** `indexFile()` neutralisiert Text-Chunks bei `FileItem.neutralize=True` → Data-at-rest abgesichert. 4. **DataSource-Download / RAG-Indexierung:** `DataSource.neutralize` ist die einzige Quelle für die Neutralisierungs-Policy pro Tree-Element. **Seit 2026-05 (Cascade-Inherit):** `neutralize`, `ragIndexEnabled` und `scope` sind 3-wertig (`null` = vererbt, `True/False` bzw. ein Scope-String = explizit). `null` in der Datenbank bedeutet: Wert vom nächsten Vorfahren-DataSource im Path-Tree übernehmen (longest-prefix wins, gleicher `connectionId` + `sourceType`). Beim Setzen eines expliziten Werts auf einem Parent **kaskadiert das Backend** (`cascadeResetDescendants`) durch alle Descendants und setzt deren explizite Werte für **dieses eine Flag** auf `null` zurück — Descendants die bereits geerbt haben, werden nicht angefasst. Walker konsumieren pre-resolved Werte: `_loadRagEnabledDataSources` (in `subConnectorIngestConsumer.py`) berechnet pro DS via `_inheritFlags.getEffectiveFlag()` den effektiven `ragIndexEnabled`/`neutralize`/`scope` und schreibt das in das Walker-Input-Dict — die Sync-Walker (`subConnectorSync*.py`) bleiben dadurch unverändert und lesen weiter direkt `ds.get("neutralize", False)`. Die Flag-Werte werden zusätzlich auf erzeugte `FileItem`s übernommen. Der frühere `subPolicyResolver` ist auf einen Backward-Compat-Shim auf `getEffectiveFlag` reduziert. **Seit 2026-05-23 (Polymorphic UDB Refactor):** `FeatureDataSource` traegt `neutralize`, `ragIndexEnabled` und `neutralizeFields` (kein `scope`, kein `userId`, kein `workspaceInstanceId` mehr — FDS ist feature-owned, RBAC-gated). Vererbung und Cascade-Reset laufen ueber `_INHERITABLE_FDS_FLAGS = {"neutralize", "ragIndexEnabled"}` mit Coordinate `(featureInstanceId, tableName)`. Flag-Toggles laufen ausschliesslich ueber `POST /api/udb/node/{key}/flag/{flag}` (siehe `b-reference/platform/unified-data-bar.md`); die alten `PATCH /api/datasources/{id}/...`-Routen sind entfallen. 5. **FeatureDataSource (Tabellen-Level):** `_queryFeatureInstance()` setzt bei `neutralize=True` u. a. `requireNeutralization=True` für Sub-Agent-AI-Calls. 6. **FeatureDataSource (Feld-Level):** `neutralizeFields` definiert Spalten, deren Werte in `FeatureDataProvider` mit deterministischen Platzhaltern `[NEUT..]` ersetzt werden, bevor Daten den Sub-Agent erreichen. Gleichheits-Vergleiche bleiben möglich (stabiler Hash). 7. **AI-Call:** Bei erzwungener Neutralisierung (`requireNeutralization=True`) schlägt fehlgeschlagene Neutralisierung hart fehl (`RuntimeError` / Blockierung bzw. Entfernen betroffener Message-Teile — kein Durchleiten im Rohzustand). 8. **`_rehydrateResponse()`:** Als Hilfsmethode vorhanden; automatische Rückübersetzung der Modellantwort ist nicht mehr der Standardpfad. **Engine-Failsafe (Spezifikation):** Neutralisierung verlangt, Engine nicht verfügbar → nicht weiterverarbeiten; Teilfehler → Teil entfernen, nicht roh weitergeben; fehlendes internes Medienmodell → Medien-Part entfernen. ## Unified Data Bar — Tree-Endpoint und Flag-Toggling Vollstaendige Doku: `b-reference/platform/unified-data-bar.md`. Hier nur Bezug zur Neutralisierung: - **Tree-Endpoint:** `POST /api/udb/tree/children` mit Body `{parents: [null, "", ...]}`. Response: `{nodesByParent}` mit pre-computed `effectiveNeutralize | "mixed"` pro Node. - **Flag-Toggle:** `POST /api/udb/node/{key}/flag/neutralize` mit Body `{value: true|false|null}`. Polymorph: dieselbe Route bedient DataSource, FeatureDataSource und das virtuelle FdsField (Persistenz dort in `FeatureDataSource.neutralizeFields`). - **Builder:** `platform-core/modules/serviceCenter/services/serviceKnowledge/_buildTree.py` orchestriert alle Kinds; effektive Werte und mixed-Aggregation kommen polymorph aus den `UdbNode`-Subklassen (`udbNodes.py`). - **RBAC:** DataSource → Owner-of-record; FDS und FdsField → Feature-Admin (`Role.roleLabel.endswith('-admin')` auf der Feature-Instanz). SysAdmin/PlatformAdmin haben **keinen** automatischen Edit-Bypass auf UDB-Flags. - **Frontend:** `SourcesTab.tsx` rendert ausschliesslich die Backend-Antwort, keine Vererbungslogik. Toggle = POST → Refetch → Render mit Spinner ueber dem Flag-Button waehrend des Round-Trips. Mixed-Symbol `U+25E9` einheitlich fuer alle drei Flags. - **Klick-Semantik bei mixed:** Klick auf das mixed-Symbol setzt explizit `false` (gilt fuer alle drei Flags). Begruendung: der visuelle Toggle behaelt zwei klare End-Zustaende (`true` / `false`); Reset auf `null`/inherit erfolgt ausschliesslich durch Parent-Toggle (siehe cascade-inherit). Das ist die einzige Geschaeftslogik im Frontend — alles andere kommt vom Backend. ## NeutralizationPanel (Frontend) `ui-nyla/src/pages/views/workspace/NeutralizationPanel.tsx` — Übersicht zu Dateien mit `neutralize`-Flag, Status und Platzhalter-Mappings. Datenbasis u. a. `GET /api/workspace/{instanceId}/files` → `{ files: [...] }`. ## Schlüssel-Dateien | Bereich | Pfad | |--------|------| | **Zentrales AI-Gate** | `serviceCenter/services/serviceAi/mainServiceAi.py` | | **Neutralisierungs-Engine** | `features/neutralization/serviceNeutralization/mainServiceNeutralization.py` (+ `subProcess*.py`) | | **Feature-Config / DB** | `features/neutralization/datamodelFeatureNeutralizer.py`, `interfaceFeatureNeutralizer.py` | | **RAG-Index** | `serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py` | | **Agent / Quellen** | `serviceCenter/services/serviceAgent/mainServiceAgent.py` (`_resolveDataSource`, `_downloadFromDataSource`, `_queryFeatureInstance`) | | **Feld-Neutralisierung (DB)** | `serviceCenter/services/serviceAgent/featureDataProvider.py` — `_neutralizeRowFields`, `_applyFieldNeutralization` | | **Datei-Toggle / Index** | `routes/routeDataFiles.py` — `PATCH /{fileId}/neutralize` | | **Ordner-Toggle / Index** | `routes/routeDataFiles.py` — `PATCH /folders/{folderId}/neutralize` (rekursive Propagierung) | | **Workflow-Aktion** | `workflows/methods/methodContext/actions/neutralizeData.py` | | **Kontext** | `serviceCenter/context.py` — `requireNeutralization` | | **Flags (Modelle)** | `datamodels/datamodelFiles.py` (`FileItem.neutralize`), `datamodelFileFolder.py` (`FileFolder.neutralize`), `datamodelDataSource.py`, `datamodelFeatureDataSource.py` (`neutralize`, `neutralizeFields`, `ragIndexEnabled` — kein `scope`/`userId`/`workspaceInstanceId` seit 2026-05-23) | | **Effective-Flag-Resolution** | `serviceCenter/services/serviceKnowledge/_inheritFlags.py` (`getEffectiveFlag`, `getEffectiveFlagFds`, `resolveEffectiveForPath`, `resolveEffectiveForFds`, `cascadeResetDescendants`) | | **UDB polymorphe Node-Klassen** | `serviceCenter/services/serviceKnowledge/udbNodes.py` (`UdbNode`-Hierarchie, `canEdit` / `getEffectiveFlag` / `setFlag` / `getLogicalChildren`) | | **UDB Tree-Builder** | `serviceCenter/services/serviceKnowledge/_buildTree.py` (`getChildrenForParents` -> `POST /api/udb/tree/children`) | | **UDB Generic Router** | `routes/routeUdb.py` (`POST /api/udb/tree/children`, `POST /api/udb/node/{key}/flag/{flag}`) | | **AI-Operationen / Enums** | `datamodels/datamodelAi.py`, `aicore/aicorePluginPrivateLlm.py` | ## Regeln / Invarianten - **Ein Gate pro Modell-Call:** Sämtlicher Text in Richtung externes LLM für diesen Call läuft über `mainServiceAi` — keine parallele „zweite“ Neutralisierungstelle für dieselbe Schnittstelle. - **Hard-Mode:** `requireNeutralization=True` und Neutralisierung scheitert → Call blockieren bzw. Inhalt nicht im Klartext an das Modell geben. - **Kein stilles Weiterreichen** sensibler Inhalte bei Pflicht zur Neutralisierung. - **Platzhalter:** Mapping `[typ.uuid]` in der DB; `resolveText` für kontrollierte Rückübersetzung. - Detaillierte Compliance- und Prozessbeschreibung: `wiki/e-compliance/neutralisierung-detail.md`.