wiki/b-reference/platform/neutralization.md

14 KiB

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 FileItems ü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.pyPATCH /{fileId}/neutralize
Ordner-Toggle / Index routes/routeDataFiles.pyPATCH /folders/{folderId}/neutralize (rekursive Propagierung)
Workflow-Aktion workflows/methods/methodContext/actions/neutralizeData.py
Kontext serviceCenter/context.pyrequireNeutralization
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.