363 lines
38 KiB
Markdown
363 lines
38 KiB
Markdown
<!-- status: done -->
|
||
<!-- started: 2026-05-12 -->
|
||
<!-- lastReviewed: 2026-05-15 -->
|
||
<!-- component: gateway | ui-nyla | platform -->
|
||
|
||
# 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) **und** `DataSource.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/jobs` blockt. 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.
|
||
- **`WorkspaceRagInsightsPage` am falschen Ort:** Workspace-instance-scoped Stats für ein plattformweites Konzept (`page.system.ragInventory` wä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**:
|
||
|
||
1. **Jede Einwilligung im Wizard MUSS an genau einem Ort im UI revozierbar sein.** „Knowledge Ingestion" `on/off` muss in `ConnectionsPage` und auf der RAG-Inventar-Seite umstellbar sein, mit demselben Backend-Effekt wie Connection-Revoke (Purge).
|
||
2. **Neutralisierung wird AUSSCHLIESSLICH in der UDB pro Tree-Element verwaltet.** Es darf keine „Neutralisierung global einer Datenquelle" geben, die „einfach so" wirkt. `UserConnection.knowledgePreferences.neutralizeBeforeEmbed` ist Anti-Pattern und wird **entfernt**.
|
||
3. **Was im RAG ist, muss der User sehen UND auf der Granularität entfernen können, auf der er es hinzugefügt hat.**
|
||
4. **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:**
|
||
|
||
1. **`neutralize` auf Container vererbt sich nach unten**, bis ein Child-Knoten einen eigenen `neutralize`-Wert hat (true oder false). „Inherited"-Visualisierung im UI bleibt wie heute (gestrichelt / gedimmt).
|
||
2. **`ragIndexEnabled` auf Container vererbt sich gleich**: Toggle auf einem SharePoint-Site-Knoten = alle Subfolders+Files werden indexiert (mit der vererbten `neutralize`-Policy), ausser ein Subfolder-Knoten setzt explizit `ragIndexEnabled = false`.
|
||
3. **Tabellarisch (`FeatureDataSource`)**: `neutralizeFields: List[str]` ist die einzige Granularitäts-Stufe für Neutralisierung. `ragIndexEnabled` gilt **pro Tabelle**. Wenn der User bestimmte Spalten **gar nicht** im RAG haben will, ist die Lösung „Tabelle nicht indexieren" oder ein neuer `excludeFields: List[str]` (Roadmap-Item, **nicht** in v1).
|
||
4. **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** — die `ragIndexEnabled`-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 welche `ragIndexEnabled`-DataSources existieren. Existierende Chunks werden gepurged.
|
||
- Wenn `true`: Walker iteriert über DataSources mit `ragIndexEnabled = true`. Wenn keine existiert → Walker macht nichts (kein leerer Default-Walk).
|
||
|
||
### Indexierung jederzeit stoppbar
|
||
|
||
Drei Stop-Mechanismen:
|
||
|
||
1. **Implizit pro Element:** In der UDB den Index-Toggle einer DataSource ausschalten → Purge dieser Items + Walker überspringt sie beim nächsten Lauf.
|
||
2. **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.
|
||
3. **Explizit pro laufendem Job:** **Stop-Button** in der RAG-Inventar-Seite und auf der ConnectionsPage, sobald ein Bootstrap-Job `RUNNING` ist. Backend setzt Job-Status auf `CANCELLED`; 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.CANCELLED` existiert bereits im Enum — fehlt nur die `cancelJob()`-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)
|
||
|
||
1. **Anbieter wählen** — Cards für Google / Microsoft 365 / ClickUp / Infomaniak (Infomaniak kommt also wieder in die Auswahl).
|
||
2. *(typ-spezifischer Zusatz-Schritt — falls vorhanden, siehe unten)*
|
||
3. **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."
|
||
4. **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. |
|
||
| **Google** | – | (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).
|
||
- ~~`infomaniakModal`-State~~ — wandert in den Wizard.
|
||
- ~~`handleAdminConsent`~~ — wandert in den Wizard.
|
||
|
||
**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: <Datum>, 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
|
||
|
||
- `platform-core/modules/serviceCenter/services/serviceKnowledge/subConnectorIngestConsumer.py` — Bootstrap-Dispatcher: muss Walker auf DataSource-Iteration umstellen + Cancel-Flag prüfen.
|
||
- `platform-core/modules/serviceCenter/services/serviceKnowledge/subConnectorSync*.py` (5 Walker) — konsumieren `DataSource`-Liste statt globalem Authority-Walk; lesen `neutralize` aus DataSource; prüfen periodisch `cancelRequested`.
|
||
- `platform-core/modules/serviceCenter/services/serviceKnowledge/subConnectorPrefs.py` — `neutralizeBeforeEmbed`, `mimeAllowlist`, `surfaceToggles` raus.
|
||
- `platform-core/modules/datamodels/datamodelUam.py` — `knowledgePreferences` Schema-Doku anpassen.
|
||
- `platform-core/modules/datamodels/datamodelDataSource.py` — `autoSync` → `ragIndexEnabled` (oder umdokumentieren); ggf. `lastIndexed`-Feld hinzufügen.
|
||
- `platform-core/modules/serviceCenter/services/serviceBackgroundJobs/mainBackgroundJobService.py` — `cancelJob(jobId)` API hinzufügen; `JobProgressCallback` um `isCancelled()` erweitern.
|
||
- `platform-core/modules/routes/routeDataConnections.py` — `GET` liefert Knowledge-Felder; neue PATCH-Endpoints für Consent + Preferences + Stop.
|
||
- `platform-core/modules/routes/routeDataSources.py` — neuer PATCH `/{id}/rag-index` mit Purge bei Off-Toggle.
|
||
- `platform-core/modules/interfaces/interfaceDbKnowledge.py` — `deleteFileContentIndexByConnectionAndPathPrefix(connectionId, pathPrefix)` und `listFileContentIndexByConnection(connectionId)` und `listFileContentIndexByDataSource(dataSourceId)`.
|
||
- `platform-core/modules/features/workspace/routeFeatureWorkspace.py` — `rag-statistics` Methode löschen.
|
||
- **NEU** `platform-core/modules/routes/routeRagInventory.py` — `/api/rag/inventory/{me,mandate,platform}` und `/api/rag/connection/{id}/status`.
|
||
- `platform-core/modules/system/mainSystem.py` — Navigation `Nutzung > RAG-Inventar`.
|
||
- `ui-nyla/src/pages/views/workspace/WorkspaceRagInsightsPage.tsx` + `.module.css` — **löschen**.
|
||
- `ui-nyla/src/types/mandate.ts` — `rag-insights` raus.
|
||
- `ui-nyla/src/pages/FeatureView.tsx`, `App.tsx` — Route + Mapping raus.
|
||
- `ui-nyla/src/components/AddConnectionWizard/AddConnectionWizard.tsx` — Connector-Type-Aware Step-Definition; integrierte Microsoft-Admin-Consent + Infomaniak-PAT Steps; Kostenabschätzung + Preferences-Step entfernen.
|
||
- `ui-nyla/src/components/UnifiedDataBar/SourcesTab.tsx` — vierter Action-Button für `ragIndexEnabled` (analog zu 🔒/Neutralize) **inklusive Vererbungs-Visualisierung** (`inheritedRagIndexEnabled`-Prop, gestrichelt/gedimmt für vererbte Werte).
|
||
- `ui-nyla/src/api/connectionApi.ts` — `KnowledgePreferences` reduzieren; neue API-Methoden.
|
||
- **NEU** `ui-nyla/src/pages/system/RagInventoryPage.tsx` — Drei-Tab-Seite (Meine / Mandant / Plattform).
|
||
- `ui-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** `ui-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 mit `neutralize=true` markiert. 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:** Wenn `autoSync` heute 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 `connectionId` aber 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-Inventar`** Seite mit drei Tabs (Meine Daten / Mandant / Plattform).
|
||
- **Bootstrap-Walker konsumieren `DataSource`-Liste** (nur jene mit `ragIndexEnabled = true`).
|
||
- **`WorkspaceRagInsightsPage` ist 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`), neuer `routeRagInventory`, `mainSystem` (Navigation), `datamodelUam`, `datamodelDataSource`.
|
||
- **Frontend:** `WorkspaceRagInsightsPage` löschen, `AddConnectionWizard` stark vereinfachen (Steps, keine Kostenschätzung), `ConnectionsPage` (Master-Toggle + Stop-Button), `SourcesTab` (4. Index-Toggle-Button), neue `RagInventoryPage`, `pageRegistry`.
|
||
- **DB-Migration:** ja — `knowledgePreferences.neutralizeBeforeEmbed/mimeAllowlist/surfaceToggles` raus; `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)
|
||
|
||
- [x] **Audit:** existierende Verwendung von `DataSource.autoSync` prüfen → Entscheidung: Renaming `ragIndexEnabled` vs. Re-Doku.
|
||
- [x] `datamodelDataSource.DataSource`: `ragIndexEnabled: bool = False` (default `False` — kein Auto-Index ohne explizite Wahl). `lastIndexed: Optional[float]`.
|
||
- [x] Migration: `script_db_migrate_datasource_rag.py` (Rename `autoSync` → `ragIndexEnabled`, `lastSynced` → `lastIndexed`).
|
||
- [x] `datamodelUam.UserConnection.knowledgePreferences` — Schema-Doku: `neutralizeBeforeEmbed`, `surfaceToggles`, `mimeAllowlist` raus.
|
||
- [x] `subConnectorPrefs.ConnectionIngestionPrefs` — tote Felder entfernt (nur Mail/ClickUp/File-Prefs bleiben).
|
||
- [x] `serviceBackgroundJobs.cancelJob(jobId) -> bool` API hinzugefügt (setzt Status auf `CANCELLED`).
|
||
- [x] `JobProgressCallback`: `isCancelled() -> bool` (liest Job-Status aus DB; cached 3s).
|
||
- [x] **Walker-Refactor:** Alle 5 `subConnectorSync*.py` Walker iterieren über `ragIndexEnabled=true` DataSources, Cancel-Check alle 50 Items, `provenance.dataSourceId`.
|
||
- [x] **Purge-Helpers** in `interfaceDbKnowledge`: `deleteFileContentIndexByDataSource(dataSourceId)`.
|
||
|
||
### Phase B — APIs (Tage 3–5)
|
||
|
||
- [x] `GET /api/connections/` Antwort um `knowledgeIngestionEnabled` und `knowledgePreferences` erweitert.
|
||
- [x] `PATCH /api/connections/{id}/knowledge-consent` mit Purge + Cancel bei `false`, Bootstrap bei `true`.
|
||
- [x] `PATCH /api/connections/{id}/knowledge-preferences` — speichert Mail-Tiefe etc.
|
||
- [x] `POST /api/connections/{id}/knowledge-stop` — cancelled alle laufenden Bootstrap-Jobs der Connection.
|
||
- [x] **`PATCH /api/datasources/{id}/rag-index`** mit Mini-Bootstrap bei `true`, Purge bei `false`.
|
||
- [x] **Neue Route `routeRagInventory`:** `/me`, `/mandate`, `/platform`, `/jobs`, `/reindex/{connectionId}`.
|
||
- [ ] ~~`GET /api/rag/connection/{id}/status`~~ — **Entscheidung: nicht umgesetzt** — Status-Infos laufen über Inventory-Aggregation.
|
||
- [x] **Workspace-RAG-Endpoint gelöscht:** `routeFeatureWorkspace.rag-statistics` raus.
|
||
|
||
### Phase C — Frontend Cleanup (Tage 5–6)
|
||
|
||
- [x] **Gelöscht:** `WorkspaceRagInsightsPage.tsx` + `.module.css`.
|
||
- [x] `mandate.ts`: `rag-insights` Workspace-View raus (2026-05-15).
|
||
- [x] `FeatureView.tsx`: Guard-Condition für `rag-insights` entfernt (2026-05-15).
|
||
- [x] `App.tsx`: Route `rag-insights` entfernt (2026-05-15).
|
||
- [x] **Wizard-Refactor (`AddConnectionWizard`):** Connector-Type-Aware Steps (Google/MSFT/ClickUp/Infomaniak), MSFT Admin-Consent + Infomaniak-PAT integriert, Cost-Logik + Prefs-Step entfernt.
|
||
- [x] **`ConnectionsPage` Cleanup:** Admin-Zustimmung + Infomaniak Standalone-Buttons entfernt (nur noch via Wizard).
|
||
|
||
### Phase D — Frontend UDB & RAG-Sichtbarkeit (Tage 6–9)
|
||
|
||
- [x] **`SourcesTab.tsx`:** 4. Action-Button (🧠) pro Tree-Node mit Vererbungs-Visualisierung (`inheritedRagIndex`).
|
||
- [x] **`RagInventoryPage`** ist der **primäre** Ort für Knowledge-Consent-Toggle, Stop-Button und Sync-Status pro Connection. **Entscheidung:** `ConnectionsPage` erhält **keine** duplizierte Master-Toggle/Stop-UI — Nutzer navigieren für RAG-Management zur RagInventoryPage.
|
||
- [ ] ~~`ConnectionsPage.tsx` Master-Toggle + Status-Pill + Stop-Button pro Row~~ — **bewusst nicht umgesetzt** (Preferences und RAG-Steuerung primär auf RagInventoryPage).
|
||
- [ ] ~~`KnowledgePreferencesDrawer`~~ — **bewusst nicht umgesetzt** (Preferences über RagInventoryPage oder Backend-Defaults).
|
||
- [x] **Globales Header-Indikator:** `RagRunningBadge` als Floating-Komponente in `MainLayout.tsx`, Polling gegen `/api/rag/inventory/jobs`.
|
||
|
||
### Phase E — Frontend RAG-Inventar-Seite (Tage 8–11)
|
||
|
||
- [x] **Neue Seite `RagInventoryPage`** mit Consent-Toggle, Stop-Button, Reindex-Button, DataSource-Übersicht pro Connection.
|
||
- [x] `pageRegistry.tsx` + `App.tsx`: Route eingerichtet.
|
||
- [x] `mainSystem.py` Navigation: Eintrag `Nutzung > RAG-Inventar`.
|
||
|
||
### Phase F — DSGVO Sichtbarkeit + Tests (Tage 11–13)
|
||
|
||
- [x] **DSGVO-Audit-Log:** `knowledge_consent_changed`, `rag_index_toggled`, `knowledge_jobs_stopped` werden via `audit_logger` geloggt.
|
||
- [x] **Backend-Integrationstests:** manuell verifiziert (Smoke-Tests, 2026-05-15).
|
||
- [x] **Frontend-Tests:** manuell verifiziert (2026-05-15).
|
||
- [x] **Wiki-Updates:** `b-reference/platform-core/ai-agent.md` aktualisiert; `TOPICS.md` enthält RAG-Inventar-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 | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T2 | 2 | unit | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T3 | 3 | api+integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T4 | 4 | unit+integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T5 | 5 | api+ui | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T6 | 6 | repo+api | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T7 | 7 | ui | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T8 | 8 | unit+migration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T9 | 9 | integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T10 | 10 | integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T11 | 11 | unit | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T12 | 12 | ui | manuell | Smoke-Test (ConnectionsPage RAG-UI bewusst nicht umgesetzt, D-6) | manuell verifiziert (2026-05-15) |
|
||
| T13 | 13 | integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T14 | 14 | integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T15 | 15 | ui | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T16 | 16 | ui+integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T17 | 17 | ui+e2e | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
| T18 | 18 | ui | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
|
||
|
||
## 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/platform-core/ai-agent.md`, `wiki/b-reference/ui-nyla/architecture.md`
|
||
- Code-Touchpoints: siehe Abschnitt „Kritische Stellen im Code"
|
||
|
||
## Abschluss
|
||
|
||
- [x] `b-reference/platform-core/ai-agent.md` aktualisiert (Knowledge Lifecycle, DataSource-getriebener Walker, Cancel-Mechanismus)
|
||
- [x] `b-reference/platform/neutralization.md` aktualisiert (DataSource = SSoT, kein neutralizeBeforeEmbed mehr, Tree-Vererbung via subPolicyResolver) — 2026-05-15
|
||
- [x] `b-reference/ui-nyla/architecture.md` aktualisiert (RagInventoryPage, UDB 4. Action-Button, RagRunningBadge, AddConnectionWizard) — 2026-05-15
|
||
- [x] `TOPICS.md` aktualisiert (RAG-Inventar als neuer Kanon-Eintrag)
|
||
- [x] Dieses Dokument in `c-work/4-done/` (Status → `done`, 2026-05-15)
|
||
- [x] Eintrag im `c-work/_CHANGELOG.md`
|