wiki/b-reference/platform/neutralization.md

110 lines
14 KiB
Markdown

<!-- status: canonical -->
<!-- lastReviewed: 2026-05-23 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-16, RAG Consent & Control update 2026-05-15, Cascade-Inherit + Generic Tree Refactor update 2026-05-18, Polymorphic UDB Refactor 2026-05-23) -->
# 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-/Typ-Level, seit 2026-06 A2):** `FeatureDataProvider.finalizeRowsAsync()` wendet eine typ- und vererbungsbewusste Policy pro Tabelle an (`{tableActive, explicitFields}`; `tableActive` = effektives, eigenes ODER geerbtes `neutralize`, via `resolveEffectiveForFds`). Regeln: (1) **Strings** (inkl. JSON/Text) werden **substring-neutralisiert** (Platzhalter im Text, nicht Ganzwert-Hash) sobald für das Feld effektiv (explizit ODER geerbt) — der Feldname wird der lokalen Neutralisierungs-Engine als Typ-Hint (`"<feld>: "`-Prefix) vorangestellt und danach entfernt; (2) **Binary** wird **nie** neutralisiert, sondern **gedroppt**, sobald Neutralisierung greift; (3) **andere Skalare** (number/float/int/date/bool) werden nur bei **explizitem** Feld-Flag (nicht vererbt) als Ganzwert-Platzhalter `[NEUT.<field>.<hash>]` ersetzt. Identifikatoren/System-Spalten (`id`, `*Id`, `sys*`, `_*`) sind ausgenommen. Engine-Ausfall = fail-closed (`[REDACTED]`). Der RAG-Bootstrap (`subFeatureBootstrap._featureBootstrapHandler`) nutzt **dieselbe** `finalizeRowsAsync`-Policy → Query-Pfad und Index-Pfad sind konsistent. (Legacy: reines `neutralizeFields` ohne Policy → weiterhin Ganzwert-Hash via `_neutralizeRowFields`.)
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. **Keine Auto-Rehydration; sichere De-Neutralisierung nur on demand (seit 2026-06 A1):** Die Modellantwort wird **nie** automatisch zurückübersetzt und mit Klartext gespeichert (`_rehydrateResponse` wurde entfernt). De-Neutralisierung erfolgt ausschliesslich explizit über das read-only Agent-Tool `revealDocument`, das Platzhalter `[typ.uuid]` **nur** über das lokale Mapping (`NeutralizationService.resolveText`, kein externes LLM) auflöst und den Klartext als **transienten Einmal-Download** (SSE-SideEvent `revealDownload`) zurückgibt — kein Save, kein Index, keine Chat-Historie (das persistierte ToolResult enthält nur eine Bestätigung).
**Engine-Failsafe (Spezifikation):** Neutralisierung verlangt, Engine nicht verfügbar → nicht weiterverarbeiten; Teilfehler → Teil entfernen, nicht roh weitergeben; fehlendes internes Medienmodell → Medien-Part entfernen.
### Bekannte Klartext-at-rest-Pfade (Audit-Hinweis, A1 2026-06)
Die Rückübersetzung (Denormalisierung) der Modellantwort ist **kein** Klartext-Risiko mehr (keine Auto-Rehydration; `revealDocument` ist transient). Die verbleibenden Stellen, an denen *unneutralisierter* Klartext bewusst at-rest landen kann, sind **vom Neutralisierungs-Flag der Quelle abhängig** und hier dokumentiert, damit sie bei Audits nicht mit der Denormalisierung verwechselt werden:
- **`downloadFromDataSource` (`mainServiceAgent._downloadFromDataSource`):** lädt Roh-Bytes der Quelle. Bei `neutralize=True` greift die Indexierungs-/Download-Policy (Failsafe 4); ohne aktives Flag sind dies bewusst Originaldaten.
- **`writeFile` / `renderDocument`:** agent-komponierter Inhalt wird als Datei persistiert; Neutralisierung richtet sich nach Quelle/Flag, nicht nach diesem Schritt.
- **`RoundMemory.fullData`:** Roh-Tool-Ergebnisse im Agent-Round-Memory (Laufzeit/Persistenz je nach Session).
Diese Pfade sind **kein Bug der A1-Änderung**; eine engere Härtung (z. B. generelles Redigieren von `RoundMemory.fullData`) ist als separates Folge-Ticket zu führen.
## 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, "<key1>", ...]}`. 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`.