43 KiB
Unified RAG Consent, Neutralization and Visibility — Single Source of Truth
Beschreibung und Kontext
Mit dem Plan 2026-04-id-unified-knowledge-indexing-rag-concept.md (P0–P1d) wurde der technische Backend-Pfad für RAG-Ingestion implementiert (Connection-OAuth → Bootstrap → Walker → requestIngestion → interfaceDbKnowledge) und der AddConnectionWizard ausgeliefert (Consent + Preferences + Kostenabschätzung).
Bei der Architektur-Review (2026-05-12) wurde jedoch festgestellt:
- Wizard ist standalone, ohne geschlossenen Lebenszyklus für seine Setzungen.
- Consent ohne Revoke-UI — User kann „Wissensdatenbank: Ja" wählen, aber nirgends widerrufen ohne die Connection zu löschen.
- Doppelte Neutralisierungs-Quelle:
UserConnection.knowledgePreferences.neutralizeBeforeEmbed(global pro Connection, gesetzt im Wizard) undDataSource.neutralize(UDB, pro Pfad). Beide wirken unabhängig — verwirrt den User. - Surface Toggles und MIME-Allowlist sind tot: Werden geparst, aber in keinem Walker referenziert.
GET /api/connections/zeigt Knowledge-Felder nicht. Selbst wenn UI gebaut wäre, fehlt die Datenquelle.- Bootstrap-Job-Status für User unsichtbar: Bootstrap-Jobs ohne
mandateId→/api/jobsblockt. User klickt „Verbinden" und sieht kein Feedback. - Indexierung kann nicht gestoppt werden: Wenn ein Bootstrap einer 50'000-Item-Mailbox losläuft, gibt es keinen Stop-Button. Kein Cost-Cap.
WorkspaceRagInsightsPageam falschen Ort: Workspace-instance-scoped Stats für ein plattformweites Konzept (page.system.ragInventorywäre richtig).- „Was ist neu zu indexieren" ist undurchsichtig: Walker walked alles, dedupliziert via Content-Hash. Es ist korrekt, aber für den User nirgends erklärt.
- Walker-Scope ist OAuth-global, nicht user-gesteuert. User kann nicht wählen „SharePoint-Site A ja, Site B nein".
Business-Treiber: DSGVO-Compliance (Art. 7: Widerruf so einfach wie Erteilung), Kosten-Kontrolle (Stop-fähig, scope-fähig), und Vertrauen (Transparenz: was ist drin, wann reinprozessiert, wie raus).
Fokus und kritische Details
Architektonisches Leitprinzip (vom User formuliert)
„Der Wizard ist nur eine UI-Komponente. Dies ist keine Architektur. Die Architektur muss im Backend datenzentriert sein und jeder Schritt muss transparent sein und im UI für den User ersichtlich."
Daraus ergeben sich vier harte Regeln:
- Jede Einwilligung im Wizard MUSS an genau einem Ort im UI revozierbar sein. „Knowledge Ingestion"
on/offmuss inConnectionsPageund auf der RAG-Inventar-Seite umstellbar sein, mit demselben Backend-Effekt wie Connection-Revoke (Purge). - Neutralisierung wird AUSSCHLIESSLICH in der UDB pro Tree-Element verwaltet. Es darf keine „Neutralisierung global einer Datenquelle" geben, die „einfach so" wirkt.
UserConnection.knowledgePreferences.neutralizeBeforeEmbedist Anti-Pattern und wird entfernt. - Was im RAG ist, muss der User sehen UND auf der Granularität entfernen können, auf der er es hinzugefügt hat.
- Indexierung muss jederzeit stoppbar sein — sowohl als „Stopp-Knopf" auf laufenden Jobs als auch als impliziter Stop durch das Entfernen des Index-Toggles in der UDB.
Kern-Idee: Per-Tree-Element-Toggle (analog zu Scope und Neutralize)
Heute in der UDB hat jedes Tree-Element drei Aktionen-Buttons: 💬 Chat / 🧑 Scope-Cycle / 🔒 Neutralize-Toggle. Die DataSource-Row materialisiert sich, sobald der User eine dieser Aktionen für einen Pfad konfiguriert (scope, neutralize).
Morgen kommt ein vierter Button hinzu: 🧠 RAG-Index-Toggle. Selbe Mechanik:
- Toggle aktivieren → DataSource bekommt
ragIndexEnabled = true→ wird beim nächsten Bootstrap/Resync indexiert - Toggle deaktivieren →
ragIndexEnabled = false+ sofortiger Purge-Job für diesen Pfad - Damit ist der Index-Lebenszyklus strukturidentisch mit Scope und Neutralize. Konsistent und intuitiv.
Konsequenz: Walker iterieren nicht mehr OAuth-global. Sie iterieren über DataSource-Rows mit ragIndexEnabled = true der jeweiligen Connection. Der User entscheidet explizit pro Tree-Element was indexiert wird.
Granularität: Tree-Kaskade vs. Tabellen-Felder
Die UDB unterscheidet schon heute zwei Daten-Typologien — beide Policies (neutralize, scope, künftig ragIndexEnabled) müssen sich konsistent dazu verhalten:
| Daten-Typ | Beispiele | Wie wirken Policies |
|---|---|---|
Tree (DataSource) |
SharePoint-Folder, Outlook-Mail-Folder, Drive-Folder, ClickUp-Liste | Kaskadierend: Eine Policy auf einem Container-Knoten (Folder/Site) vererbt sich auf alle Kinder, bis ein Kind eine eigene Policy explizit überschreibt. Code-Pattern existiert bereits (inheritedScope, inheritedNeutralize in SourcesTab.tsx). ragIndexEnabled muss denselben Vererbungs-Mechanismus nutzen — sonst inkonsistent. |
Tabular (FeatureDataSource) |
Trustee-Positionen-Tabelle, RealEstate-Bauvorschriften, beliebige Feature-DATA_OBJECT-Tabellen | Pro Feld: FeatureDataSource.neutralizeFields: List[str] — User kann sagen „indexier diese Tabelle, aber email-Feld neutralisieren, name-Feld lassen". Bestehender Code: _toggleNeutralizeField in SourcesTab.tsx. Für ragIndexEnabled auf Tabellen-Ebene ist Per-Feld-Granularität nicht sinnvoll (entweder Zeile geht in RAG oder nicht) — also: ragIndexEnabled gilt pro Tabelle/FeatureDataSource, nicht pro Feld. |
Verbindliche Regeln:
neutralizeauf Container vererbt sich nach unten, bis ein Child-Knoten einen eigenenneutralize-Wert hat (true oder false). „Inherited"-Visualisierung im UI bleibt wie heute (gestrichelt / gedimmt).ragIndexEnabledauf Container vererbt sich gleich: Toggle auf einem SharePoint-Site-Knoten = alle Subfolders+Files werden indexiert (mit der vererbtenneutralize-Policy), ausser ein Subfolder-Knoten setzt explizitragIndexEnabled = false.- Tabellarisch (
FeatureDataSource):neutralizeFields: List[str]ist die einzige Granularitäts-Stufe für Neutralisierung.ragIndexEnabledgilt pro Tabelle. Wenn der User bestimmte Spalten gar nicht im RAG haben will, ist die Lösung „Tabelle nicht indexieren" oder ein neuerexcludeFields: List[str](Roadmap-Item, nicht in v1). - Walker-Implementierung muss die Vererbung respektieren: Wenn User SharePoint-Site indiziert (Container) und nicht jeden Subfolder einzeln, durchläuft der Walker die Tree-Hierarchie und nutzt für jedes File die effektive Policy (eigene > geerbt > default
false).
Vorteile:
- Granularität: User wählt „SharePoint Site A → Folder 'Reports' indexieren, Folder 'Drafts' nicht".
- Revoke trivial: Ein Klick auf den Toggle in der UDB → automatischer Purge.
- Einheitliche UX-Pattern (gleiche Iconisierung wie Scope/Neutralize).
- Kein „Auto-Default-DataSources beim Connect"-Hack — User muss aktiv was wählen, was DSGVO-konformer ist (kein Default-Indexing ohne explizite Wahl).
- DataSource braucht kein separates
autoSync/autoIndex-Attribut auf Row-Level — dieragIndexEnabled-Spalte IST die Speicherung; das Icon IST die Bedienung.
Konsequenz für knowledgeIngestionEnabled auf der Connection:
- Bleibt als rechtlicher Master-Schalter („darf PowerOn überhaupt etwas aus dieser Connection in die Wissensdatenbank legen?").
- Wenn
false: Kein Walker startet, egal welcheragIndexEnabled-DataSources existieren. Existierende Chunks werden gepurged. - Wenn
true: Walker iteriert über DataSources mitragIndexEnabled = true. Wenn keine existiert → Walker macht nichts (kein leerer Default-Walk).
Indexierung jederzeit stoppbar
Drei Stop-Mechanismen:
- Implizit pro Element: In der UDB den Index-Toggle einer DataSource ausschalten → Purge dieser Items + Walker überspringt sie beim nächsten Lauf.
- Implizit pro Connection: Master-Toggle auf der Connection ausschalten (Knowledge-Consent revoke) → Purge aller Chunks dieser Connection + nächste Bootstrap-/Resync-Jobs werden gar nicht erst gestartet.
- Explizit pro laufendem Job: Stop-Button in der RAG-Inventar-Seite und auf der ConnectionsPage, sobald ein Bootstrap-Job
RUNNINGist. Backend setzt Job-Status aufCANCELLED; Walker prüft alle ~50 Items das Cancel-Flag und beendet graceful.- Bereits indexierte Items bleiben drin (kein Rollback). Wer komplett raus will → Master-Toggle off.
BackgroundJobStatusEnum.CANCELLEDexistiert bereits im Enum — fehlt nur diecancelJob()-API + Cancel-Check im Walker-Loop.
„Was wird neu indexiert?" — Dedup-Mechanismus transparent gemacht
Die Frage taucht beim User auf: „Wenn ich in 24h meinen täglichen Resync laufen lasse — wie weiss das System was neu ist?"
Die Antwort existiert technisch bereits, ist aber nirgends im UI sichtbar gemacht:
| Ebene | Mechanismus | Im Code |
|---|---|---|
| Pro Item, vor API-Call (Cheap-Skip) | Walker speichert externen Revisions-Token (eTag für SharePoint, modifiedTime für Drive, historyId für Gmail, changeKey für Outlook, date_updated für ClickUp). Wenn unverändert → Walker skippt den API-Download. |
Bootstrap-Walker, Provider-Adapter |
| Pro Item, nach Extraction (Embedding-Skip) | requestIngestion baut Content-Hash über (contentType, data, extractor-order) — Pure Function des Inhalts. Wenn FileContentIndex.structure._ingestion.hash matcht → Status bleibt indexed, kein neues Embedding. |
mainServiceKnowledge.requestIngestion |
| Bei Connection-Revoke | deleteFileContentIndexByConnectionId purged alle Rows mit connectionId = X. |
interfaceDbKnowledge |
| Bei DataSource-Revoke (NEU in diesem Plan) | deleteFileContentIndexByConnectionAndPathPrefix purged Rows mit connectionId = X und contextRef.path startet mit Y. |
NEU |
Daraus folgt für den Plan:
- Keine neue Mechanik nötig für „was ist neu" — die Hash-Idempotenz reicht.
- UI-Aufgabe: Auf der RAG-Inventar-Seite muss klar dokumentiert/sichtbar sein: „Letzter Sync: vor 18h. Neue Items werden täglich um 02:00 ergänzt. Unveränderte Items werden nicht erneut verarbeitet."
- Pro Sync sichtbare Counter:
indexedNew,skippedDuplicate,failed(existieren als Log-Events; müssen pro Connection auf der Inventar-Seite aggregiert werden).
Konsequenzen für die Datenmodellierung
| Heute | Wird zu |
|---|---|
UserConnection.knowledgeIngestionEnabled |
bleibt — rechtlicher Master-Gate, mit Revoke-UI |
UserConnection.knowledgePreferences.neutralizeBeforeEmbed |
entfernen — Migration: bei true werden alle existierenden DataSources der Connection auf neutralize=true gesetzt |
UserConnection.knowledgePreferences.{mailContentDepth, mailIndexAttachments, filesIndexBinaries, clickupScope, maxAgeDays} |
bleiben als Connection-weite Default-Sync-Einstellungen, Edit-UI in Connection-Detail-Drawer |
UserConnection.knowledgePreferences.{mimeAllowlist, surfaceToggles} |
entfernen — durch Per-Element-Toggle obsolet |
DataSource.neutralize |
bleibt — einzige Quelle für Neutralisierungs-Policy pro Tree-Element |
DataSource.scope |
bleibt unverändert |
DataSource.autoSync |
wird umbenannt zu ragIndexEnabled (oder bleibt + Doku — Entscheidung in Phase A) — neuer Sinn: „dieses Tree-Element wird in den RAG indexiert" |
WorkspaceRagInsightsPage (Workspace-scoped) |
gelöscht, ersetzt durch globale Start > Nutzung > RAG-Inventar |
/api/workspace/{instanceId}/rag-statistics |
gelöscht, ersetzt durch /api/rag/inventory/{me|mandate|platform} |
BackgroundJob ohne Cancel-API |
erweitert: cancelJob(jobId), Walker-Cancel-Check |
Wizard-Vereinfachung — alle Connector-Typen über denselben Pfad
Der AddConnectionWizard ist die einzige Eintragungs-UI für neue Connections. Alle heute parallelen Buttons auf der ConnectionsPage (separater „Infomaniak"-Button mit eigener Modal-Logik, separater „Admin-Zustimmung"-Button für Microsoft) werden in den Wizard integriert. Auf der ConnectionsPage bleibt nur „Verbindung hinzufügen" als Eintrittspunkt.
Connector-Type-Aware Wizard: Der Wizard hat eine Basis-Sequenz und typ-spezifische Zusatz-Schritte, die nach „Anbieter wählen" eingehängt werden.
Basis-Sequenz (alle Typen)
- Anbieter wählen — Cards für Google / Microsoft 365 / ClickUp / Infomaniak (Infomaniak kommt also wieder in die Auswahl).
- (typ-spezifischer Zusatz-Schritt — falls vorhanden, siehe unten)
- Rechtliche Einwilligung (Knowledge Ingestion ja/nein) — kurzer Hinweistext: „Du wählst nach dem Verbinden in der Datenleiste konkret, welche Ordner/Postfächer/Listen indexiert werden sollen."
- Verbindungsschritt — typ-abhängig: OAuth-Popup (google/msft/clickup) oder PAT-Eingabe (infomaniak).
Connector-Type-spezifische Zusatz-Schritte
| Connector | Zusatz-Schritt nach „Anbieter wählen" | Inhalt |
|---|---|---|
| Microsoft 365 | Optional: Admin-Zustimmung (Tenant-weit) | Hinweis: „Falls dein Tenant einen Admin-Consent erfordert, kann er vor der persönlichen OAuth einmalig erteilt werden. Sonst überspringe diesen Schritt." Button öffnet /api/msft/adminconsent Popup. Nach Schliessen → weiter zu Step 3. |
| – | (Standard OAuth) | |
| ClickUp | – | (Standard OAuth) |
| Infomaniak | Pflicht: Personal Access Token | Inline-Eingabefeld + Erklärung wie heute im infomaniakModal. Erst nach valider Token-Validierung → weiter zu Step 3. Connection wird beim PAT-Submit erstellt (vergleichbar mit createInfomaniakConnection + submitInfomaniakToken); kein OAuth-Popup. |
Mechanik: Der Wizard hält pro Connector-Typ eine kleine Step-Definition (z.B. WIZARD_STEPS_BY_TYPE: Record<ConnectorType, WizardStep[]>), die der Reihe nach durchlaufen wird. Stepper im UI passt sich dynamisch an (z.B. 4 Punkte für Microsoft mit Admin-Zustimmung, 3 für Google).
Entfernt von ConnectionsPage:
Button „Admin-Zustimmung"— wandert in den Wizard (Microsoft-Pfad).Button „Infomaniak"— wandert in den Wizard (eigener Connector-Typ in der Anbieter-Auswahl).— wandert in den Wizard.infomaniakModal-State— wandert in den Wizard.handleAdminConsent
Entfernt aus dem heutigen Wizard:
Step 2 (Preferences mit Neutralisierung-Checkbox, Mail-Tiefe etc.)— wandert in Connection-Detail-Drawer bzw. in die UDB (Neutralize per Element).Kostenabschätzung— wird komplett entfernt. Begründung: Schätzung war unzuverlässig (Token-Annahmen pro Mail spekulativ; Drive/SharePoint-Datenmenge nicht bekannt vor dem ersten Walk), erzeugt falsche Sicherheit, und stoppt User unnötig vom Connect. Kostenkontrolle erfolgt nachgelagert: über die Stop-Funktion und über das Per-Element-Opt-In.
Vorteil: Genau ein Eintrittspunkt für jeden Connector-Typ. Konsistente UX. Die echte Konfiguration (was indexieren, was neutralisieren) passiert dort, wo der User die Daten sieht — in der UDB.
Wo sieht und stoppt der User die Indexierung? (Sichtbarkeit-Spec)
Diese Frage muss drei UI-Plätze ohne Lücke abdecken:
| Ort | Was sichtbar | Aktionen |
|---|---|---|
| Globales Header-Indikator (klein, oben rechts neben User-Menü) | Badge mit Anzahl gerade laufender Bootstrap-/Resync-Jobs des aktuellen Users (z.B. „🔄 2"). Klick → öffnet RAG-Inventar-Seite. Verschwindet, wenn keine Jobs laufen. Polling: alle 5–10s. | Click-through zur RAG-Inventar |
ConnectionsPage, pro Row |
Pro Connection mit aktivem Bootstrap-Job: Status-Pill „Indexierung läuft" + Progress (processed / known total oder Prozent) + Roter Stop-Button. Bei finished: „Letzter Sync: , X indexiert / Y skipped / Z failed". |
Stop-Button → POST /api/connections/{id}/knowledge-stop. Master-Toggle (off → Confirm → Purge + Cancel). |
RagInventoryPage Tab „Meine Daten" |
Vollständige Sicht: pro Connection eine Card; pro Card alle indexierten DataSources mit Item-Count + letztem Sync; Status-Pill bei laufendem Bootstrap; pro DataSource der UDB-Index-Toggle nochmals als Quick-Off. | Stop-Button pro Connection (gleiche API). Toggle off pro DataSource → Mini-Purge. Master-Toggle der Connection. „Jetzt synchronisieren"-Button → enqueue Bootstrap on-demand. |
Backend-Voraussetzung für die Sichtbarkeit: GET /api/rag/connection/{id}/status liefert für eine Connection { runningJobId?: string, runningJobProgress?: number, lastRun?: { finishedAt, indexedNew, skippedDuplicate, failed }, nextScheduledResync?: timestamp }. GET /api/rag/inventory/me aggregiert das für alle Connections und liefert zusätzlich runningJobsTotal für das Header-Badge.
Kritische Stellen im Code
gateway/modules/serviceCenter/services/serviceKnowledge/subConnectorIngestConsumer.py— Bootstrap-Dispatcher: muss Walker auf DataSource-Iteration umstellen + Cancel-Flag prüfen.gateway/modules/serviceCenter/services/serviceKnowledge/subConnectorSync*.py(5 Walker) — konsumierenDataSource-Liste statt globalem Authority-Walk; lesenneutralizeaus DataSource; prüfen periodischcancelRequested.gateway/modules/serviceCenter/services/serviceKnowledge/subConnectorPrefs.py—neutralizeBeforeEmbed,mimeAllowlist,surfaceTogglesraus.gateway/modules/datamodels/datamodelUam.py—knowledgePreferencesSchema-Doku anpassen.gateway/modules/datamodels/datamodelDataSource.py—autoSync→ragIndexEnabled(oder umdokumentieren); ggf.lastIndexed-Feld hinzufügen.gateway/modules/serviceCenter/services/serviceBackgroundJobs/mainBackgroundJobService.py—cancelJob(jobId)API hinzufügen;JobProgressCallbackumisCancelled()erweitern.gateway/modules/routes/routeDataConnections.py—GETliefert Knowledge-Felder; neue PATCH-Endpoints für Consent + Preferences + Stop.gateway/modules/routes/routeDataSources.py— neuer PATCH/{id}/rag-indexmit Purge bei Off-Toggle.gateway/modules/interfaces/interfaceDbKnowledge.py—deleteFileContentIndexByConnectionAndPathPrefix(connectionId, pathPrefix)undlistFileContentIndexByConnection(connectionId)undlistFileContentIndexByDataSource(dataSourceId).gateway/modules/features/workspace/routeFeatureWorkspace.py—rag-statisticsMethode löschen.- NEU
gateway/modules/routes/routeRagInventory.py—/api/rag/inventory/{me,mandate,platform}und/api/rag/connection/{id}/status. gateway/modules/system/mainSystem.py— NavigationNutzung > RAG-Inventar.frontend_nyla/src/pages/views/workspace/WorkspaceRagInsightsPage.tsx+.module.css— löschen.frontend_nyla/src/types/mandate.ts—rag-insightsraus.frontend_nyla/src/pages/FeatureView.tsx,App.tsx— Route + Mapping raus.frontend_nyla/src/components/AddConnectionWizard/AddConnectionWizard.tsx— Connector-Type-Aware Step-Definition; integrierte Microsoft-Admin-Consent + Infomaniak-PAT Steps; Kostenabschätzung + Preferences-Step entfernen.frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx— vierter Action-Button fürragIndexEnabled(analog zu 🔒/Neutralize) inklusive Vererbungs-Visualisierung (inheritedRagIndexEnabled-Prop, gestrichelt/gedimmt für vererbte Werte).frontend_nyla/src/api/connectionApi.ts—KnowledgePreferencesreduzieren; neue API-Methoden.- NEU
frontend_nyla/src/pages/system/RagInventoryPage.tsx— Drei-Tab-Seite (Meine / Mandant / Plattform). frontend_nyla/src/pages/basedata/ConnectionsPage.tsx— Buttons „Admin-Zustimmung" + „Infomaniak" + Modal entfernen; Master-Toggle pro Row + Status-Pill + Stop-Button bei laufendem Bootstrap hinzufügen.- NEU
frontend_nyla/src/components/Header/RagRunningBadge.tsx— globales Header-Badge mit Anzahl laufender Bootstrap-Jobs + Click-through zur RAG-Inventar.
Bekannte Fallstricke
- Migration
neutralizeBeforeEmbed=true→ Pro-DataSource: Bei Schema-Update werden alle existierenden DataSources der Connection mitneutralize=truemarkiert. Falls keine DataSources existieren (was wahrscheinlich ist, weil heute Walker OAuth-global läuft), gehen die Werte verloren — Release-Note nötig. - Migration
autoSync-Bedeutung: WennautoSyncheute schon mit anderem Sinn benutzt wird, ist Renaming sicherer als Umdeutung. Phase A startet mit Code-Audit. - Bestehende RAG-Daten ohne DataSource-Bezug: Heute haben SharePoint-/Outlook-Chunks
connectionIdaber keinen DataSource-Link (Walker walked OAuth-global). Migration: bestehende Chunks bleiben drin, neue Indexierung läuft nur über DataSources. „Legacy-Chunks" sichtbar machen oder einmalig purgen — offene Entscheidung. - Cancel-Race-Condition: Walker prüft Cancel-Flag alle N Items. Im Worst Case dauert Stop noch N Items lang. Akzeptabel für v1.
- Daily Resync vs. Cancel: Wenn ein Daily-Resync läuft und User stoppt — startet er morgen neu? Ja, das ist gewollt. Wenn User dauerhaft Pause will → Master-Toggle off oder einzelne DataSource-Toggles off.
Ziel und Nicht-Ziele
Ziel:
- Single Source of Truth für Neutralisierung: ausschliesslich
DataSource.neutralize, gemanagt in der UDB. - Per-Element RAG-Index-Toggle in der UDB (analog zu Scope/Neutralize).
- Vollständiger Lebenszyklus für Wizard-Consent (Erteilen, Widerrufen) im UI sichtbar.
- Stop-Funktion für laufende Bootstrap-Jobs (Job-Cancel + Walker-Cancel-Check).
- Globale
Start > Nutzung > RAG-InventarSeite mit drei Tabs (Meine Daten / Mandant / Plattform). - Bootstrap-Walker konsumieren
DataSource-Liste (nur jene mitragIndexEnabled = true). WorkspaceRagInsightsPageist gelöscht.- Wizard ist auf Anbieter + Consent reduziert (keine Kostenschätzung, keine Preferences, keine Neutralisierung).
- Tote Pref-Felder entfernt (
mimeAllowlist,surfaceToggles,neutralizeBeforeEmbed).
Explizit NICHT:
- Wir bauen keinen zweiten Knowledge-Store.
- Wir verändern nicht die Retrieval-Seite (
buildAgentContext). - Wir bauen kein Per-Item-Revoke (Granularität: Element-Toggle in UDB oder Master-Connection-Toggle — nicht eine einzelne Mail).
- Wir bauen keine Auto-Default-DataSources beim Connect — User muss aktiv Indexierung pro Element wählen.
- Wir bauen keine Echtzeit-Cost-Caps (z.B. „stop bei CHF 5.-") — v1: Stop ist manuell. Cost-Caps sind Roadmap v2.
Betroffene Module
- Gateway:
serviceKnowledge(Walker-Refactor, Purge-Endpoints, Cancel-Check),serviceBackgroundJobs(Cancel-API),routeDataConnections(Knowledge-Felder + PATCH-Endpoints),routeDataSources(PATCH/rag-index), neuerrouteRagInventory,mainSystem(Navigation),datamodelUam,datamodelDataSource. - Frontend:
WorkspaceRagInsightsPagelöschen,AddConnectionWizardstark vereinfachen (Steps, keine Kostenschätzung),ConnectionsPage(Master-Toggle + Stop-Button),SourcesTab(4. Index-Toggle-Button), neueRagInventoryPage,pageRegistry. - DB-Migration: ja —
knowledgePreferences.neutralizeBeforeEmbed/mimeAllowlist/surfaceTogglesraus;DataSource.autoSync→ragIndexEnabled(oder Re-Doku). - Andere Komponenten: keine.
Entscheidungen
| Datum | Entscheidung | Begründung |
|---|---|---|
| 2026-05-12 | Neutralisierung wird ausschliesslich pro DataSource verwaltet |
Single Source of Truth; UDB ist der einzige Ort für Datenobjekt-Policies |
| 2026-05-12 | Index-Aktivierung wird pro Tree-Element via UDB-Toggle gesteuert (analog Scope/Neutralize) | Konsistente UX, granular, revoke-trivial |
| 2026-05-12 | WorkspaceRagInsightsPage wird gelöscht, ersetzt durch globale Start > Nutzung > RAG-Inventar |
RAG ist plattformweit, nicht workspace-scoped |
| 2026-05-12 | Walker iterieren über DataSource-Rows mit ragIndexEnabled=true der Connection |
User-kontrollierter Scope; per-Source Revoke trivial |
| 2026-05-12 | Keine Auto-Default-DataSources beim OAuth-Connect | DSGVO-konformer (kein Default-Indexing); User muss aktiv wählen |
| 2026-05-12 | Kostenabschätzung im Wizard wird entfernt | Unzuverlässig, erzeugt falsche Sicherheit; Kostenkontrolle via Stop-Button + Per-Element-Opt-In |
| 2026-05-12 | Stop-Mechanismus: implizit (Toggle off) + explizit (Cancel-Job-Button) | Jederzeit stoppbar, kein „Walker rennt weg" |
| 2026-05-12 | „Was ist neu zu indexieren" wird nicht neu erfunden — bestehende Hash-Idempotenz reicht | Pure Function über Content; bereits implementiert und getestet |
| 2026-05-12 | Surface Toggles und MIME-Allowlist werden aus Schema entfernt (nicht implementiert) | Per-Element-Toggle macht sie obsolet |
| 2026-05-12 | Vererbungs-Mechanik für Tree-Policies: neutralize und ragIndexEnabled kaskadieren auf Container, lokale Overrides möglich (analog zu inheritedScope/inheritedNeutralize Pattern) |
Konsistenz mit existierender UDB-Logik; spart redundante Per-File-Toggles |
| 2026-05-12 | Tabellen-Granularität: Neutralisierung pro Feld (neutralizeFields[]); RAG-Index pro Tabelle (kein Per-Feld) |
Bestehende UI-Pattern bleiben; Per-Feld-Index wäre Over-Engineering für v1 |
| 2026-05-12 | Wizard ist einziger Eintrittspunkt für alle Connector-Typen — Buttons „Admin-Zustimmung" + „Infomaniak" auf ConnectionsPage werden entfernt und in den Wizard integriert |
Genau ein Pfad pro Connector-Typ; eliminiert Parallelmechanismen |
| 2026-05-12 | Drei Sichtbarkeits-Orte für laufende Indexierung: Header-Badge, ConnectionsPage-Row, RagInventoryPage-Tab | User darf nirgends „raten" was läuft; Stop-Aktion an mindestens zwei Orten erreichbar |
Umsetzungs-Checkliste
Phase A — Datenmodell & Cancel-Infrastruktur (Tage 1–3)
- Audit: existierende Verwendung von
DataSource.autoSyncprüfen → Entscheidung: RenamingragIndexEnabledvs. Re-Doku. datamodelDataSource.DataSource:ragIndexEnabled: bool = False(defaultFalse— kein Auto-Index ohne explizite Wahl).lastIndexed: Optional[float].- Migration: bestehende
UserConnections mitknowledgePreferences.neutralizeBeforeEmbed=true→ falls DataSources existieren, derenneutralize=truesetzen. Pref-Feld entfernen. datamodelUam.UserConnection.knowledgePreferences— Schema-Doku:neutralizeBeforeEmbed,surfaceToggles,mimeAllowlistraus.subConnectorPrefs.ConnectionIngestionPrefs— tote Felder entfernen.serviceBackgroundJobs.cancelJob(jobId) -> boolAPI hinzufügen (setzt Status aufCANCELLED).JobProgressCallback:isCancelled() -> bool(liest Job-Status aus DB; cached für N Sekunden um DB-Last zu vermeiden).- Walker-Refactor: Jeder
subConnectorSync*.pyWalker:- iteriert über
DataSource-Rows der Connection mitragIndexEnabled = trueund passendemsourceType, - lädt
neutralizeausDataSource(nicht aus Pref-Dict), - prüft
progressCb.isCancelled()mindestens einmal alle 50 Items → graceful exit + Job-Resultcancelled: true, processedSoFar: N, - schreibt
dataSource.lastIndexed = now()am Ende.
- iteriert über
- Purge-Helpers in
interfaceDbKnowledge:deleteFileContentIndexByConnectionAndPathPrefix(connectionId, pathPrefix) -> {indexRows, chunks},listFileContentIndexByConnection(connectionId) -> List[FileContentIndex],listFileContentIndexByDataSource(dataSourceId) -> List[FileContentIndex](matched überconnectionId+contextRef.path-Prefix; oder DataSource-ID inchunkMetadatamitführen — Entscheidung in Phase A).
Phase B — APIs (Tage 3–5)
GET /api/connections/Antwort umknowledgeIngestionEnabledundknowledgePreferenceserweitern.PATCH /api/connections/{id}/knowledge-consent{ enabled: boolean }— beifalse: synchroner Purge aller Connection-Chunks + Flag setzen + alle laufenden Bootstrap-Jobs dieser Connection cancellen. Beitrue: enqueue Bootstrap (nur wenn DataSources mitragIndexEnabled=trueexistieren).PATCH /api/connections/{id}/knowledge-preferences{ ...prefs }— speichert Mail-Tiefe etc.; optional Resync-Trigger.POST /api/connections/{id}/knowledge-stop— cancelled alle laufenden Bootstrap-Jobs der Connection.PATCH /api/datasources/{id}/rag-index{ enabled: boolean }:- bei
true: setzt Flag, enqueue Mini-Bootstrap-Job für diese eine DataSource. - bei
false: setzt Flag, enqueue Purge-Job (deleteFileContentIndexByConnectionAndPathPrefix).
- bei
- Neue Route
routeRagInventory:GET /api/rag/inventory/me— aktueller User: alle Connections mit Knowledge-Status, deren DataSources (mit Index-Counts), letzter Sync, nächster Resync.GET /api/rag/inventory/mandate/{mandateId}— Mandant-Admin only: Aggregation pro User, pro Connection, pro Feature.GET /api/rag/inventory/platform— PlatformAdmin only: System-Stats, Cost-Tracking-Hooks (placeholder für v2).
GET /api/rag/connection/{id}/status— letzter Bootstrap-Run, aktuelle Job-ID falls running, Counter (indexedNew,skippedDuplicate,failed),nextScheduledResync.- Workspace-RAG-Endpoint löschen:
routeFeatureWorkspace.rag-statisticsraus.
Phase C — Frontend Cleanup (Tage 5–6)
- Löschen:
WorkspaceRagInsightsPage.tsx+.module.css. mandate.ts:rag-insightsWorkspace-View raus.FeatureView.tsx: Mapping + Sonderbehandlung raus.App.tsx: Routerag-insightsraus.- Wizard-Refactor (
AddConnectionWizard) — Connector-Type-Aware Steps:- Step 0: Anbieter wählen — Cards für alle vier Typen (Google / Microsoft / ClickUp / Infomaniak).
- Step-Definition pro Typ (
WIZARD_STEPS_BY_TYPE) — Wizard rendert dynamisch:- Microsoft: (optional) Admin-Zustimmung → Consent → OAuth.
- Google / ClickUp: Consent → OAuth.
- Infomaniak: PAT-Eingabe (Pflicht) → Consent → Submit.
- Microsoft Admin-Zustimmung-Step: integriert die Logik von
handleAdminConsent(Popup zu/api/msft/adminconsent). Im UI klar als „nur falls Tenant es erfordert, sonst überspringen" beschriftet. - Infomaniak-PAT-Step: integriert die Logik von
infomaniakModal(Connection wird beim PAT-Submit erstellt; bei Cancel rollback viadeleteConnection). - Stepper passt sich dynamisch an (3 Punkte für Google/ClickUp, 4 für Microsoft, 4 für Infomaniak).
KnowledgePreferences-Felder werden nicht mehr beim Create gesendet (Defaults im Backend).computeCostEstimate+ Cost-Hint-Render-Blöcke +AddConnectionWizard.module.cssCost-Stile entfernen.
ConnectionsPageCleanup:- Buttons „Admin-Zustimmung" und „Infomaniak" in der Header-Action-Bar entfernen.
handleAdminConsent,handleCreateInfomaniak,handleInfomaniakCancel,handleInfomaniakSubmit,infomaniakModal-State,adminConsentPending-State entfernen.- Modal
Infomaniak verbindenentfernen (Logik wandert in den Wizard).
useConnections.ts: Hook erweitern um Wizard-Pfade für Infomaniak (createInfomaniakConnection+submitInfomaniakTokenbleiben verfügbar; werden vom Wizard konsumiert).
Phase D — Frontend UDB & RAG-Sichtbarkeit (Tage 6–9)
SourcesTab.tsx: Vierter Action-Button (Icon-Vorschlag: 🧠 oder 📚) pro Tree-Node. Mechanik analog zu🔒(Neutralize):- Klick: wenn DataSource existiert → Toggle
ragIndexEnabled. Sonst → DataSource anlegen + Toggle setzen. - Visueller State: aktiv / inaktiv / inherited (gestrichelt, gedimmt — analog zu
inheritedNeutralizePattern). - Vererbung: Container-Knoten propagiert
ragIndexEnabledan alle Kinder bis zur expliziten Überschreibung. Identische Mechanik wieinheritedScope/inheritedNeutralize(siehe Sektion „Granularität"). - Bei Toggle-Off: Confirm-Dialog mit Item-Count-Vorschau („X Inhalte werden aus dem RAG entfernt").
- Klick: wenn DataSource existiert → Toggle
ConnectionsPage.tsxneue UI-Elemente pro Row:- Master-Toggle „Wissensdatenbank" →
PATCH /knowledge-consent. - Bei
runningJobId: Status-Pill „Indexierung läuft (X / Y)" + roter Stop-Button →POST /knowledge-stop. - Bei finished: kompakte Anzeige „Letzter Sync: , X indexiert / Y skipped / Z failed".
- Knowledge-Preferences-Edit-Drawer (Mail-Tiefe etc.) — als separater Dialog, nicht im Wizard.
- Master-Toggle „Wissensdatenbank" →
- Globales Header-Indikator:
- Kleines Badge oben rechts (z.B. neben User-Menü):
🔄 Nwenn N>0 Bootstrap-Jobs des aktuellen Users laufen. - Polling alle 5–10s gegen
GET /api/rag/inventory/me(oder einen schlanken Sub-Endpoint, der nurrunningJobsTotalliefert). - Klick → Navigation zu
Start > Nutzung > RAG-Inventar. - Implementierung: neuer Component
RagRunningBadge.tsxim Header-Layout.
- Kleines Badge oben rechts (z.B. neben User-Menü):
Phase E — Frontend RAG-Inventar-Seite (Tage 8–11)
- Neue Seite
RagInventoryPageunter/system/rag-inventory:- Tab „Meine Daten" (default): pro Connection eine Card mit Master-Toggle (off-Confirm), Preferences-Edit-Link, Liste aller DataSources (mit
ragIndexEnabled-Status, Item-Count, letztes Sync-Datum, „Stop"-Button bei laufendem Job, Toggle-Off-Knopf). - Tab „Mandant" (Mandant-Admin only): Aggregation; sichtbar pro User wer wie viel beigesteuert hat.
- Tab „Plattform" (PlatformAdmin only): System-Stats; Placeholder für Cost-Tracking.
- Tab „Meine Daten" (default): pro Connection eine Card mit Master-Toggle (off-Confirm), Preferences-Edit-Link, Liste aller DataSources (mit
pageRegistry.tsx:page.system.ragInventory→ Icon<FaDatabase />+ lazy-imported Component.mainSystem.pyNavigation: EintragNutzung > RAG-Inventar(page.system.ragInventory, IconFaDatabase, order25zwischen Abrechnung und Statistiken).
Phase F — DSGVO Sichtbarkeit + Tests (Tage 11–13)
- DSGVO-Audit-Log: Jeder Knowledge-Consent-Toggle und jeder Index-Toggle wird in
aiAuditLogger(oder vergleichbarem Audit-Log) festgehalten. Inkl. User, Timestamp, Connection/DataSource, alter und neuer Wert. - Backend-Tests:
- Migration (Pref → Per-DataSource).
- Walker konsumiert nur DataSources mit
ragIndexEnabled=true. - DataSource-Toggle off → Purge-Job korrekt scoped.
- Connection-Consent off → Purge + Job-Cancel.
cancelJob→ Walker exit + Job-StatusCANCELLED.- Inventory-API liefert korrekte Counts.
- Frontend-Tests:
AddConnectionWizardzeigt keine Cost-Hint und keine Preferences-Step.SourcesTabzeigt Index-Toggle, Toggle-Off triggert Confirm + API.RagInventoryPagerendert pro Tab korrekt.ConnectionsPageStop-Button erscheint beirunningund ruft API.
- Wiki-Updates:
b-reference/gateway/ai-agent.md(Knowledge Lifecycle, DataSource-getriebener Walker),b-reference/platform/neutralization.md(DataSource = SSoT),b-reference/frontend-nyla/architecture.md(RagInventoryPage, UDB 4. Action-Button),TOPICS.md(RAG-Inventar als neuer Eintrag).
Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|---|---|
| 1 | Given ich habe Wizard-Consent true, when ich auf der ConnectionsPage den Master-Toggle auf off setze und bestätige, then werden alle FileContentIndex + ContentChunk-Rows der Connection synchron gelöscht, knowledgeIngestionEnabled = false gesetzt, und alle laufenden Bootstrap-Jobs dieser Connection cancelled. |
must |
| 2 | Given in der UDB ist für DataSource X ragIndexEnabled = true und neutralize = true, when der Bootstrap/Resync läuft, then werden Items aus diesem Pfad mit Neutralisierung indexiert; and Items aus anderen DataSources der gleichen Connection werden nur indexiert wenn ihr eigener ragIndexEnabled = true ist. |
must |
| 3 | Given in der UDB klicke ich den Index-Toggle einer DataSource auf off und bestätige, then wird ragIndexEnabled = false gesetzt, ein Purge-Job entfernt alle FileContentIndex-Rows mit matching connectionId + pathPrefix, und Toast zeigt „X Inhalte aus RAG entfernt". |
must |
| 4 | Given ein Bootstrap-Job läuft (status=RUNNING), when ich den Stop-Button drücke, then wird cancelJob aufgerufen, der Walker beendet sich nach max. ~50 Items, Job-Status wird CANCELLED, Result enthält cancelled: true, processedSoFar: N, bereits indexierte Items bleiben drin. |
must |
| 5 | Given ich öffne Start > Nutzung > RAG-Inventar, then sehe ich Tab „Meine Daten" mit allen meinen Connections + DataSources + Item-Counts + letztem Sync + Stop-Button bei laufenden Jobs. And Tab „Mandant" / „Plattform" nur, wenn ich entsprechende Rolle habe. |
must |
| 6 | Given der WorkspaceRagInsightsPage-Code, then existiert er nicht mehr im Repo; rag-insights ist aus mandate.ts, FeatureView.tsx, App.tsx entfernt; /api/workspace/{instanceId}/rag-statistics liefert 404. |
must |
| 7 | Given der AddConnectionWizard, then zeigt er keine Kostenabschätzung, keine Neutralisierungs-Checkbox und keinen Preferences-Step. Nur: Anbieter → Consent → Connect. |
must |
| 8 | Given UserConnection.knowledgePreferences, then existieren die Felder neutralizeBeforeEmbed, surfaceToggles, mimeAllowlist nicht mehr im Schema und sind in keinem Walker referenziert. Migration: bestehende neutralizeBeforeEmbed=true-Werte wurden auf zugehörige DataSources übertragen. |
must |
| 9 | Given ich frisch Microsoft verbinde mit Wizard-Consent true und ich habe noch keine DataSources mit ragIndexEnabled=true angelegt, then läuft kein Bootstrap (Walker findet leere DataSource-Liste, Job endet sofort mit processed: 0). |
must |
| 10 | Given in der UDB sind 3 Tree-Elemente mit ragIndexEnabled=true markiert, when ich auf einer Connection-Row in ConnectionsPage „Knowledge sync starten" drücke (oder der Daily-Resync läuft), then indexiert der Walker exakt diese 3 Pfade, und der dedup-Mechanismus skippt unveränderte Items per Hash. |
must |
| 11 | Given jeder Toggle (Connection-Master, DataSource-Index, DataSource-Neutralize), when er geändert wird, then wird der Wechsel im aiAuditLogger (oder Audit-Log) festgehalten mit userId, timestamp, target, oldValue, newValue. |
should |
| 12 | Given ich öffne den Knowledge-Preferences-Edit-Drawer auf einer Connection-Row, then sehe und editiere ich mailContentDepth, mailIndexAttachments, clickupScope, maxAgeDays (aber nicht neutralizeBeforeEmbed — dieses Feld existiert nicht mehr). |
should |
| 13 | Given ich setze in der UDB neutralize=true auf einem SharePoint-Site-Knoten (Container) und ragIndexEnabled=true auf demselben, when der Walker den Site indexiert, then werden alle Files unter dem Site mit Neutralisierung indexiert; and wenn ein Subfolder eine eigene neutralize=false Policy hat, werden dessen Files ohne Neutralisierung indexiert (Vererbung mit lokaler Override). |
must |
| 14 | Given ein FeatureDataSource für eine Trustee-Tabelle mit neutralizeFields=["email","name"] und ragIndexEnabled=true, when der Walker indexiert, then sind in den persistierten Chunks die Felder email und name neutralisiert, alle anderen Felder im Klartext. |
must |
| 15 | Given auf der ConnectionsPage existieren keine separaten Buttons mehr für „Admin-Zustimmung" und „Infomaniak", when ich „Verbindung hinzufügen" klicke und „Microsoft" wähle, then zeigt der Wizard einen optionalen „Admin-Zustimmung"-Step mit Hinweistext und Skip-Möglichkeit; and wenn ich „Infomaniak" wähle, zeigt er einen PAT-Eingabe-Step. |
must |
| 16 | Given ich starte im Wizard einen Infomaniak-Connect, when ich im PAT-Step abbreche, then wird die im Backend angelegte pending Connection wieder gelöscht (kein Orphan), analog zur heutigen handleInfomaniakCancel-Logik. |
must |
| 17 | Given ein Bootstrap-Job einer Connection läuft, then sehe ich an drei Orten den Status: (a) globales Header-Badge mit Anzahl laufender Jobs, (b) ConnectionsPage-Row mit Status-Pill + Stop-Button, (c) RagInventoryPage Tab „Meine Daten" mit Detail + Stop-Button. Alle drei Orte zeigen dieselben Counter (gleicher Backend-Endpoint). |
must |
| 18 | Given ich klicke das globale Header-Badge, then werde ich zur RagInventoryPage (Tab „Meine Daten") navigiert. |
should |
Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|---|---|---|---|---|---|
| T1 | 1 | api+integration | ja | gateway/tests/integration/test_knowledge_consent_revoke.py |
pending |
| T2 | 2 | unit | ja | gateway/tests/unit/services/test_walker_uses_datasource_neutralize.py |
pending |
| T3 | 3 | api+integration | ja | gateway/tests/integration/test_datasource_index_toggle_purge.py |
pending |
| T4 | 4 | unit+integration | ja | gateway/tests/unit/services/test_job_cancel_walker_exit.py + gateway/tests/integration/test_bootstrap_cancel_endpoint.py |
pending |
| T5 | 5 | api+ui | ja | gateway/tests/integration/test_rag_inventory_endpoints.py + frontend_nyla/src/pages/system/__tests__/RagInventoryPage.test.tsx |
pending |
| T6 | 6 | repo+api | ja | grep + gateway/tests/integration/test_workspace_rag_endpoint_404.py |
pending |
| T7 | 7 | ui | ja | frontend_nyla/src/components/AddConnectionWizard/__tests__/AddConnectionWizard.test.tsx |
pending |
| T8 | 8 | unit+migration | ja | gateway/tests/unit/migrations/test_neutralize_pref_to_datasource.py |
pending |
| T9 | 9 | integration | ja | gateway/tests/integration/test_bootstrap_empty_when_no_datasource.py |
pending |
| T10 | 10 | integration | ja | gateway/tests/integration/test_bootstrap_iterates_only_enabled_datasources.py |
pending |
| T11 | 11 | unit | ja | gateway/tests/unit/audit/test_consent_toggle_audit.py |
pending |
| T12 | 12 | ui | ja | frontend_nyla/src/pages/basedata/__tests__/ConnectionsPage.test.tsx |
pending |
| T13 | 13 | integration | ja | gateway/tests/integration/test_walker_neutralize_inheritance_tree.py |
pending |
| T14 | 14 | integration | ja | gateway/tests/integration/test_walker_featuredatasource_field_neutralize.py |
pending |
| T15 | 15 | ui | ja | frontend_nyla/src/components/AddConnectionWizard/__tests__/AddConnectionWizard.test.tsx (per-type Steps) + grep-Test für entfernte Buttons in ConnectionsPage |
pending |
| T16 | 16 | ui+integration | ja | frontend_nyla/src/components/AddConnectionWizard/__tests__/InfomaniakCancelRollback.test.tsx |
pending |
| T17 | 17 | ui+e2e | teilweise | manuell + smoke-tests für die drei Orte | pending |
| T18 | 18 | ui | ja | frontend_nyla/src/components/Header/__tests__/RagRunningBadge.test.tsx |
pending |
Links
- Vorgänger-Plan:
wiki/c-work/4-done/2026-04-id-unified-knowledge-indexing-rag-concept.md - Architektonisch betroffen:
wiki/b-reference/platform/neutralization.md,wiki/b-reference/gateway/ai-agent.md,wiki/b-reference/frontend-nyla/architecture.md - Code-Touchpoints: siehe Abschnitt „Kritische Stellen im Code"
Abschluss
b-reference/gateway/ai-agent.mdaktualisiert (Knowledge Lifecycle, DataSource-getriebener Walker, Cancel-Mechanismus)b-reference/platform/neutralization.mdaktualisiert (DataSource = SSoT)b-reference/frontend-nyla/architecture.mdaktualisiert (RagInventoryPage, UDB 4. Action-Button)TOPICS.mdaktualisiert (RAG-Inventar als neuer Kanon-Eintrag)- Dieses Dokument →
c-work/2-build/→c-work/3-validate/→c-work/4-done/verschoben - Eintrag im
c-work/_CHANGELOG.md