96 lines
11 KiB
Markdown
96 lines
11 KiB
Markdown
<!-- status: canonical -->
|
|
<!-- lastReviewed: 2026-05-18 -->
|
|
<!-- verifiedAgainst: gateway (codebase audit 2026-04-16, RAG Consent & Control update 2026-05-15, Cascade-Inherit + Generic Tree Refactor update 2026-05-18) -->
|
|
|
|
# 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-18 (Generic Tree Refactor):** `FeatureDataSource` traegt nun ebenfalls `ragIndexEnabled` mit gleicher 3-wertiger Semantik (`_INHERITABLE_FDS_FLAGS` enthaelt alle drei Flags); `PATCH /api/datasources/{id}/rag-index` akzeptiert via `_findSourceRecord` sowohl DS als auch FDS — Bootstrap-Job und Chunk-Purge bleiben aber DS-only (FDS-RAG ist Feature-Pipeline-getrieben, das Flag steuert ausschliesslich die Pipeline-Aktivierung).
|
|
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.<field>.<hash>]` 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
|
|
|
|
Seit 2026-05-18 (Generic Tree Refactor, siehe `c-work/4-done/2026-05-udb-generic-tree-refactor.md`) liefert ein einziger Endpoint die komplette sichtbare Tree-Struktur des UDB inklusive aller drei effektiver Flag-Werte:
|
|
|
|
- **Endpoint:** `POST /api/workspace/{instanceId}/tree/children` mit Body `{parents: [null, "<key1>", "<key2>", ...]}`.
|
|
- **Response:** `{nodesByParent: {"__root__": [...], "<key1>": [...], ...}}` — jeder `TreeNode` enthält `effectiveNeutralize`, `effectiveScope`, `effectiveRagIndexEnabled` als `boolean | "mixed"` bzw. `string | "mixed"` (mode=`aggregate`).
|
|
- **Builder:** `gateway/modules/serviceCenter/services/serviceKnowledge/_buildTree.py` orchestriert alle Kinds (`connection`, `service`, `folder`, `file`, `mandateGroup`, `featureNode`, `fdsTable`) und ruft `resolveEffectiveForPath` / `resolveEffectiveForFds` pro Node — auch fuer Coordinates ohne eigenen DB-Record (virtueller Datensatz mit `null`-Flags).
|
|
- **Frontend:** `SourcesTab.tsx` rendert ausschliesslich die Backend-Antwort, keine Vererbungslogik. Toggle = PATCH -> Refetch -> Render mit Spinner ueber dem Flag-Button waehrend des Round-Trips. Mixed-Symbol `U+25E9` einheitlich fuer alle drei Flags.
|
|
|
|
## NeutralizationPanel (Frontend)
|
|
|
|
`frontend_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` seit 2026-05-18) |
|
|
| **Effective-Flag-Resolution** | `serviceCenter/services/serviceKnowledge/_inheritFlags.py` (`getEffectiveFlag`, `getEffectiveFlagFds`, `resolveEffectiveForPath`, `resolveEffectiveForFds`, `cascadeResetDescendants`) |
|
|
| **UDB Tree-Builder** | `serviceCenter/services/serviceKnowledge/_buildTree.py` (`getChildrenForParents` -> `POST /api/workspace/{id}/tree/children`) |
|
|
| **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`.
|