From 946ae4b8c9af1f9e72f796d893881f2c18eb8f0b Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Tue, 19 May 2026 16:47:48 +0200
Subject: [PATCH] fixes rag and workflow
---
TOPICS.md | 3 +-
b-reference/frontend-nyla/architecture.md | 6 +-
b-reference/frontend-nyla/formgenerator.md | 48 ++-
b-reference/platform/neutralization.md | 17 +-
.../2026-05-udb-sources-recovery.md | 387 ++++++++++++++++++
.../4-done/2026-05-udb-datasource-settings.md | 187 +++++++++
.../2026-05-udb-generic-tree-refactor.md | 158 +++++++
c-work/_CHANGELOG.md | 19 +
8 files changed, 816 insertions(+), 9 deletions(-)
create mode 100644 c-work/3-validate/2026-05-udb-sources-recovery.md
create mode 100644 c-work/4-done/2026-05-udb-datasource-settings.md
create mode 100644 c-work/4-done/2026-05-udb-generic-tree-refactor.md
diff --git a/TOPICS.md b/TOPICS.md
index 48021dd..10846f5 100644
--- a/TOPICS.md
+++ b/TOPICS.md
@@ -1,4 +1,4 @@
-
+
# Themen-Index für AI-Kontext
@@ -51,6 +51,7 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
| RAG Consent & Control | c-work/2-build/2026-05-rag-consent-and-control-implementation.md | Datenzentrierte Steuerung: `DataSource.ragIndexEnabled`, Walker-Refactor, Job-Cancel, UDB-Toggle, RagInventoryPage |
| UDB DataSource Settings (RAG-Limits) | c-work/2-build/2026-05-udb-datasource-settings.md | Settings-Icon ⚙️ pro Tree-Node, `DataSource.settings.ragLimits` als alleinige Quelle (kein Override-Layer), PATCH `/api/datasources/{id}/settings`, GET `/api/datasources/{id}/cost-estimate`, indikative USD-Schätzung |
| UDB Cascade-Inherit für DataSource-Flags | c-work/4-done/2026-05-udb-cascade-inherit.md | 3-wertige `neutralize`/`ragIndexEnabled`/`scope` (`null` = vererbt), `_inheritFlags.getEffectiveFlag()` (Path-Traversal), `cascadeResetDescendants()` setzt explizite Descendants-Werte beim Parent-Toggle auf `null`, Walker konsumieren pre-resolved Werte aus `_loadRagEnabledDataSources` |
+| UDB Generic Tree Refactor (BE autoritativ, FE pure Renderer) | c-work/4-done/2026-05-udb-generic-tree-refactor.md | Single `POST /api/workspace/{id}/tree/children` mit `{parents}`-Liste liefert `nodesByParent` inkl. pre-computed `effectiveNeutralize/Scope/RagIndexEnabled` als `boolean\|'mixed'`. Orchestrator `serviceKnowledge/_buildTree.py`. SourcesTab.tsx auf ~530 Zeilen, kein optimistic update, Spinner pro pending Toggle, einheitliches Mixed-Symbol. FDS unterstuetzt nun RAG (gleiche 3-wertige Semantik wie DS). Geloescht: 7 alte Tree-Endpoints (`/resolve-flags`, `/connections`, `/connections/{id}/services`, `/connections/{id}/browse`, `/feature-connections`, `/feature-connections/{fiId}/tables`, `/feature-connections/{fiId}/parent-objects/{tableName}`). Bewusst weggelassen: FDS-Record-Expansion (Tabellen-Ebene reicht). |
| Zentrale Workflow-Admin (Meine Sicht) | c-work/1-plan/2026-04-automation-central-admin.md | `/automations` Tabs Dashboard + Workflows, `GET .../workflow-runs/workflows` |
| Web Image Search | c-work/1-plan/2026-03-web-image-search.md | WEB_SEARCH_MEDIA Feature |
| UI i18n / Sprachsets (done) | c-work/3-validate/2026-04-ui-i18n-dynamic-language-sets.md | Mehrsprachigkeit, `t()`, Sprachset-API, Admin-UI, AI-Übersetzung |
diff --git a/b-reference/frontend-nyla/architecture.md b/b-reference/frontend-nyla/architecture.md
index 95e57af..5352ec2 100644
--- a/b-reference/frontend-nyla/architecture.md
+++ b/b-reference/frontend-nyla/architecture.md
@@ -1,6 +1,6 @@
-
+
# Frontend Nyla -- Architektur
@@ -31,9 +31,9 @@ Ergänzend typische Root-Dateien und Bereiche im Repo: `main.tsx`, `App.tsx`, `a
| Komponente | Zweck |
|------------|-------|
-| `UnifiedDataBar (UDB)` | Multi-Tab-Panel: Chats, Files, Sources — wird in Workspace, CommCoach und Graphical Editor genutzt. Sources-Tab hat 4. Action-Button: RAG-Index-Toggle pro DataSource (Consent-gesteuert via `PATCH /api/rag/inventory/{dsId}/index-toggle`) |
+| `UnifiedDataBar (UDB)` | Multi-Tab-Panel: Chats, Files, Sources — wird in Workspace, CommCoach und Graphical Editor genutzt. Sources-Tab rendert zwei `FormGeneratorTree`-Sektionen ("Persoenliche Quellen", "Mandanten-Daten") mit dedizierter UDB-Domain-Schicht (`components/UnifiedDataBar/sources/`); UDB-Provider mappen Backend-Tree-Nodes auf das generische `TreeNode`-Schema, populieren `extraActions` fuer RAG-Toggle + Settings, und triggern Sektion-Refresh nach Patches |
| `FormGenerator` | Dynamische Formulare/Tabellen aus Backend-Attribut-Definitionen (siehe [formgenerator.md](formgenerator.md)) |
-| `FormGeneratorTree` | Generische Baumkomponente mit Provider-Pattern (`TreeNodeProvider`), Multiselect, DnD, Inline-Editing, Scope/Neutralize, Batch-Actions; ersetzt fruehere `FolderTree`-Komponente (siehe [formgenerator.md](formgenerator.md)) |
+| `FormGeneratorTree` | Generische Baumkomponente mit Provider-Pattern (`TreeNodeProvider`), Multiselect, DnD, Inline-Editing, Scope/Neutralize, Batch-Actions, generische **Mixed-State**-Symbole, **Pending-Spinner** und **extraActions**-Slot. Konsumenten: `FilesTab`, `SourcesTab`, `FilesPage`. Siehe [formgenerator.md](formgenerator.md). |
| `MandateNavigation` | Feature-Baum-Navigation mit Mandanten-Kontext |
| `Automation2FlowEditor` | Konsolidierter n8n-style Flow-Builder (Graphical Editor) mit 3-Column-Layout: [UDB + Chat/Tracing] [Canvas + Header] [Nodes] |
| `FlowCanvas` | Custom React Canvas fuer Workflow-Graph-Rendering mit Node-Highlighting (SSE Live-Tracing) |
diff --git a/b-reference/frontend-nyla/formgenerator.md b/b-reference/frontend-nyla/formgenerator.md
index c429d4c..1885b97 100644
--- a/b-reference/frontend-nyla/formgenerator.md
+++ b/b-reference/frontend-nyla/formgenerator.md
@@ -399,10 +399,51 @@ Alle Mutations-Methoden sind optional -- ein read-only Provider implementiert nu
- **DnD** (`application/x-poweron-tree-items`); aus `shared` nur lesende Drops
- **Inline-Rename** (Doppelklick / F2) -- nur fuer eigene Knoten
- **Scope/Neutralize** Icons: interaktiv fuer eigene, Indikator fuer geteilte
+- **Mixed-State** (generisch): `scope`, `neutralize` und beliebige `extraActions[*].value`
+ duerfen den Wert `'mixed'` haben (vom Provider/Backend gesetzt). Der Tree rendert
+ dann ein uniformes Symbol (`\u25E9`) statt des typspezifischen Icons.
+- **Pending-Spinner** (generisch): waehrend einer asynchronen Provider-Aktion
+ (`patchScope`, `patchNeutralize`, oder `extraActions[*].onClick`) zeigt der Tree
+ einen kleinen rotierenden Spinner ueber dem jeweiligen Button.
+- **extraActions** (generisch): Knoten koennen eine Liste zusaetzlicher Aktionen
+ mitbringen (`TreeNode.extraActions: NodeAction[]`). Der Tree rendert sie als
+ Icon-Buttons mit Tooltip; er kennt deren Semantik nicht und reicht nur Klicks
+ in den Pending-Spinner-Wrapper.
- **Batch-Actions** mit `typeFilter` (z.B. separate Delete-Buttons fuer Ordner vs. Dateien)
- **Refresh-Button** im Section-Header
- **Confirmation-Dialoge** vor Delete-Aktionen
+### Generische Erweiterungen (Mixed, Pending, extraActions)
+
+```typescript
+interface NodeAction {
+ key: string; // eindeutig pro Node
+ icon: React.ReactNode; // Standard-Icon (durch mixed/spinner ueberschrieben)
+ tooltip: string;
+ value?: boolean | string | 'mixed';
+ disabled?: boolean;
+ onClick?: () => Promise | void;
+}
+
+interface TreeNode {
+ // ...
+ scope?: ScopeValue | 'mixed';
+ neutralize?: boolean | 'mixed';
+ extraActions?: NodeAction[];
+}
+```
+
+Rendering-Regeln:
+
+| Zustand | Anzeige |
+|---------|--------|
+| `value !== 'mixed'`, kein Pending | typspezifisches Icon (Scope-Emoji, Schloss, Action-Icon) |
+| `value === 'mixed'` | `\u25E9` mit Klasse `flagMixed` (uniform fuer alle Flags) |
+| Pending (Promise laeuft) | rotierender Spinner mit Klasse `flagSpinner` |
+
+Diese Features sind domaen-frei und werden von beiden FormGeneratorTree-Konsumenten
+(`FilesTab.tsx` und `UnifiedDataBar/SourcesTab.tsx`) genutzt.
+
### Props (`FormGeneratorTreeProps`)
| Prop | Typ | Beschreibung |
@@ -413,6 +454,7 @@ Alle Mutations-Methoden sind optional -- ein read-only Provider implementiert nu
| `compact` | `boolean?` | Kompakte Darstellung (z.B. in UDB) |
| `collapsible` | `boolean?` | Section kollabierbar |
| `defaultCollapsed` | `boolean?` | Initial kollabiert |
+| `selectable` | `boolean?` | Default `true`. Bei `false` keine Checkboxes, kein Batch-Toolbar (UDB-Sources-Modus) |
| `emptyMessage` | `string?` | Anzeige bei leerem Tree |
| `onNodeClick` | `(node) => void` | Callback bei Knoten-Klick |
| `onSelectionChange` | `(selectedIds) => void` | Callback bei Selektions-Aenderung |
@@ -420,9 +462,11 @@ Alle Mutations-Methoden sind optional -- ein read-only Provider implementiert nu
### Verwendung
-Aktuell in zwei Kontexten eingebunden, jeweils mit `FolderFileProvider`:
+Aktuell in drei Kontexten eingebunden, jeweils mit eigenem Provider:
-- **`UnifiedDataBar/FilesTab.tsx`**: Zwei Sektionen (Eigene / Geteilt mit mir)
+- **`UnifiedDataBar/FilesTab.tsx`**: Zwei Sektionen (Eigene / Geteilt mit mir), Provider: `FolderFileProvider`
+- **`UnifiedDataBar/SourcesTab.tsx`**: Zwei Sektionen (Persoenliche Quellen / Mandanten-Daten),
+ Provider: `createUdbPersonalProvider` + `createUdbFeatureProvider` aus `components/UnifiedDataBar/sources/`
- **`pages/basedata/FilesPage.tsx`**: Split-View mit Tree links und `FormGeneratorTable` rechts;
Tree-Selektion filtert Tabelle nach Ordner
diff --git a/b-reference/platform/neutralization.md b/b-reference/platform/neutralization.md
index dcf7972..c7147fd 100644
--- a/b-reference/platform/neutralization.md
+++ b/b-reference/platform/neutralization.md
@@ -1,6 +1,6 @@
-
+
# Neutralisierungs-System
@@ -47,7 +47,7 @@ Im zentralen AI-Gate (`_neutralizeRequest`):
1. **Toggle `FileItem.neutralize`:** Index und Chunks werden synchron gelöscht; Re-Index im Hintergrund verhindert Leaks bei Fehlern in der Nachindexierung.
2. **Toggle `FileFolder.neutralize`:** Propagiert `neutralize` rekursiv auf alle Dateien im Ordner; Index/Chunks werden pro Datei synchron gelöscht, Re-Index im Hintergrund. Neue/verschobene Dateien erben das Flag des Ziel-Ordners.
3. **Indexierung:** `indexFile()` neutralisiert Text-Chunks bei `FileItem.neutralize=True` → Data-at-rest abgesichert.
-4. **DataSource-Download / RAG-Indexierung:** `DataSource.neutralize` ist die einzige Quelle für die Neutralisierungs-Policy pro Tree-Element. **Seit 2026-05 (Cascade-Inherit):** `neutralize`, `ragIndexEnabled` und `scope` sind 3-wertig (`null` = vererbt, `True/False` bzw. ein Scope-String = explizit). `null` in der Datenbank bedeutet: Wert vom nächsten Vorfahren-DataSource im Path-Tree übernehmen (longest-prefix wins, gleicher `connectionId` + `sourceType`). Beim Setzen eines expliziten Werts auf einem Parent **kaskadiert das Backend** (`cascadeResetDescendants`) durch alle Descendants und setzt deren explizite Werte für **dieses eine Flag** auf `null` zurück — Descendants die bereits geerbt haben, werden nicht angefasst. Walker konsumieren pre-resolved Werte: `_loadRagEnabledDataSources` (in `subConnectorIngestConsumer.py`) berechnet pro DS via `_inheritFlags.getEffectiveFlag()` den effektiven `ragIndexEnabled`/`neutralize`/`scope` und schreibt das in das Walker-Input-Dict — die Sync-Walker (`subConnectorSync*.py`) bleiben dadurch unverändert und lesen weiter direkt `ds.get("neutralize", False)`. Die Flag-Werte werden zusätzlich auf erzeugte `FileItem`s übernommen. Der frühere `subPolicyResolver` ist auf einen Backward-Compat-Shim auf `getEffectiveFlag` reduziert.
+4. **DataSource-Download / RAG-Indexierung:** `DataSource.neutralize` ist die einzige Quelle für die Neutralisierungs-Policy pro Tree-Element. **Seit 2026-05 (Cascade-Inherit):** `neutralize`, `ragIndexEnabled` und `scope` sind 3-wertig (`null` = vererbt, `True/False` bzw. ein Scope-String = explizit). `null` in der Datenbank bedeutet: Wert vom nächsten Vorfahren-DataSource im Path-Tree übernehmen (longest-prefix wins, gleicher `connectionId` + `sourceType`). Beim Setzen eines expliziten Werts auf einem Parent **kaskadiert das Backend** (`cascadeResetDescendants`) durch alle Descendants und setzt deren explizite Werte für **dieses eine Flag** auf `null` zurück — Descendants die bereits geerbt haben, werden nicht angefasst. Walker konsumieren pre-resolved Werte: `_loadRagEnabledDataSources` (in `subConnectorIngestConsumer.py`) berechnet pro DS via `_inheritFlags.getEffectiveFlag()` den effektiven `ragIndexEnabled`/`neutralize`/`scope` und schreibt das in das Walker-Input-Dict — die Sync-Walker (`subConnectorSync*.py`) bleiben dadurch unverändert und lesen weiter direkt `ds.get("neutralize", False)`. Die Flag-Werte werden zusätzlich auf erzeugte `FileItem`s übernommen. Der frühere `subPolicyResolver` ist auf einen Backward-Compat-Shim auf `getEffectiveFlag` reduziert. **Seit 2026-05-18 (Generic Tree Refactor):** `FeatureDataSource` traegt nun ebenfalls `ragIndexEnabled` mit gleicher 3-wertiger Semantik (`_INHERITABLE_FDS_FLAGS` enthaelt alle drei Flags); `PATCH /api/datasources/{id}/rag-index` akzeptiert via `_findSourceRecord` sowohl DS als auch FDS — Bootstrap-Job und Chunk-Purge bleiben aber DS-only (FDS-RAG ist Feature-Pipeline-getrieben, das Flag steuert ausschliesslich die Pipeline-Aktivierung).
5. **FeatureDataSource (Tabellen-Level):** `_queryFeatureInstance()` setzt bei `neutralize=True` u. a. `requireNeutralization=True` für Sub-Agent-AI-Calls.
6. **FeatureDataSource (Feld-Level):** `neutralizeFields` definiert Spalten, deren Werte in `FeatureDataProvider` mit deterministischen Platzhaltern `[NEUT..]` ersetzt werden, bevor Daten den Sub-Agent erreichen. Gleichheits-Vergleiche bleiben möglich (stabiler Hash).
7. **AI-Call:** Bei erzwungener Neutralisierung (`requireNeutralization=True`) schlägt fehlgeschlagene Neutralisierung hart fehl (`RuntimeError` / Blockierung bzw. Entfernen betroffener Message-Teile — kein Durchleiten im Rohzustand).
@@ -55,6 +55,15 @@ Im zentralen AI-Gate (`_neutralizeRequest`):
**Engine-Failsafe (Spezifikation):** Neutralisierung verlangt, Engine nicht verfügbar → nicht weiterverarbeiten; Teilfehler → Teil entfernen, nicht roh weitergeben; fehlendes internes Medienmodell → Medien-Part entfernen.
+## Unified Data Bar — Tree-Endpoint und Flag-Toggling
+
+Seit 2026-05-18 (Generic Tree Refactor, siehe `c-work/4-done/2026-05-udb-generic-tree-refactor.md`) liefert ein einziger Endpoint die komplette sichtbare Tree-Struktur des UDB inklusive aller drei effektiver Flag-Werte:
+
+- **Endpoint:** `POST /api/workspace/{instanceId}/tree/children` mit Body `{parents: [null, "", "", ...]}`.
+- **Response:** `{nodesByParent: {"__root__": [...], "": [...], ...}}` — jeder `TreeNode` enthält `effectiveNeutralize`, `effectiveScope`, `effectiveRagIndexEnabled` als `boolean | "mixed"` bzw. `string | "mixed"` (mode=`aggregate`).
+- **Builder:** `gateway/modules/serviceCenter/services/serviceKnowledge/_buildTree.py` orchestriert alle Kinds (`connection`, `service`, `folder`, `file`, `mandateGroup`, `featureNode`, `fdsTable`) und ruft `resolveEffectiveForPath` / `resolveEffectiveForFds` pro Node — auch fuer Coordinates ohne eigenen DB-Record (virtueller Datensatz mit `null`-Flags).
+- **Frontend:** `SourcesTab.tsx` rendert ausschliesslich die Backend-Antwort, keine Vererbungslogik. Toggle = PATCH -> Refetch -> Render mit Spinner ueber dem Flag-Button waehrend des Round-Trips. Mixed-Symbol `U+25E9` einheitlich fuer alle drei Flags.
+
## NeutralizationPanel (Frontend)
`frontend_nyla/src/pages/views/workspace/NeutralizationPanel.tsx` — Übersicht zu Dateien mit `neutralize`-Flag, Status und Platzhalter-Mappings. Datenbasis u. a. `GET /api/workspace/{instanceId}/files` → `{ files: [...] }`.
@@ -73,7 +82,9 @@ Im zentralen AI-Gate (`_neutralizeRequest`):
| **Ordner-Toggle / Index** | `routes/routeDataFiles.py` — `PATCH /folders/{folderId}/neutralize` (rekursive Propagierung) |
| **Workflow-Aktion** | `workflows/methods/methodContext/actions/neutralizeData.py` |
| **Kontext** | `serviceCenter/context.py` — `requireNeutralization` |
-| **Flags (Modelle)** | `datamodels/datamodelFiles.py` (`FileItem.neutralize`), `datamodelFileFolder.py` (`FileFolder.neutralize`), `datamodelDataSource.py`, `datamodelFeatureDataSource.py` (`neutralize`, `neutralizeFields`) |
+| **Flags (Modelle)** | `datamodels/datamodelFiles.py` (`FileItem.neutralize`), `datamodelFileFolder.py` (`FileFolder.neutralize`), `datamodelDataSource.py`, `datamodelFeatureDataSource.py` (`neutralize`, `neutralizeFields`, `ragIndexEnabled` seit 2026-05-18) |
+| **Effective-Flag-Resolution** | `serviceCenter/services/serviceKnowledge/_inheritFlags.py` (`getEffectiveFlag`, `getEffectiveFlagFds`, `resolveEffectiveForPath`, `resolveEffectiveForFds`, `cascadeResetDescendants`) |
+| **UDB Tree-Builder** | `serviceCenter/services/serviceKnowledge/_buildTree.py` (`getChildrenForParents` -> `POST /api/workspace/{id}/tree/children`) |
| **AI-Operationen / Enums** | `datamodels/datamodelAi.py`, `aicore/aicorePluginPrivateLlm.py` |
## Regeln / Invarianten
diff --git a/c-work/3-validate/2026-05-udb-sources-recovery.md b/c-work/3-validate/2026-05-udb-sources-recovery.md
new file mode 100644
index 0000000..18fa275
--- /dev/null
+++ b/c-work/3-validate/2026-05-udb-sources-recovery.md
@@ -0,0 +1,387 @@
+
+
+
+
+# UDB Sources Recovery: einheitliche Tree-Mechanik via FormGeneratorTree
+
+## Status (2026-05-18, abends)
+
+| Phase | Status | Tests |
+|-------|--------|-------|
+| 1. Backend Layout-Synthese (`_buildTree.py`) | DONE | `test_buildTree.py` 16/16, `test_inheritFlags.py` 72/72 -> Service-Suite **88/88 gruen** |
+| 2. FormGeneratorTree generisch erweitern | DONE | `FormGeneratorTree.test.tsx` 50/50 -> +RAG / `displayOrder` / `refreshAfterAction` / `defaultExpanded` |
+| 3. SourcesTab + UdbSourcesProvider neu | DONE | `UdbSourcesProvider.test.ts` 16/16, **Tree+Provider total 66/66 gruen** |
+| 4. Folge-UX: flat layout / default-expand / icon-order / settings-on-root / on-off icons | DONE | Tests inklusive (siehe oben) |
+| 5. Validierung (User-Smoke + Wiki-Update) | OPEN | manueller Test im Browser ausstehend |
+
+**Folge-Iteration (selbe Sitzung, nach Phase 1-3 User-Feedback)**:
+
+- F1 -- Visual cascade fuer `neutralize` + `ragIndexEnabled`: vorher *gleiches Icon* fuer on/off (nur Opacity). Backend-Cascade war korrekt; verwirrend war nur das Icon. Distinct emojis: `closed lock` vs `open lock`, `brain` vs `thought-bubble`. (Cascade-Logik im Backend identisch zu `scope`, von `cascadeResetDescendants(rec, flag)` parametrisch. Test-Suite belegt das in `TestCascadeReset` mit `flag="neutralize"`.)
+- F2 -- Top-Level-Layout flach: `srcRoot` und `mandateRoot` entfernt. Top-Level emittiert nun direkt `[personalRoot, mgrp|m1, mgrp|m2, ...]`. Mandate-Groups sind keine Children eines Wrappers mehr. Tree-Header kommt aus dem `title`-Prop von `FormGeneratorTree`.
+- F3 -- `TreeNode.defaultExpanded` als generisches Tree-Feature: einmal pro Node-Id auto-expand bei initialem Load (one-shot via `autoExpandedRef`). Backend markiert `personalRoot` und alle Mandate-Groups mit `defaultExpanded=True`. Manuelles Collapse ueberschreibt nachher; Refetches re-emitten dieselbe Id, aber die Map-Ref blockt Re-Trigger.
+- F4 -- Icon-Reihenfolge in `FormGeneratorTree` (rechts-buendig, von rechts nach links): neutralize, scope, sendToChat, rag, settings. Settings (`extraActions`) jetzt linksbuendig. Settings-Icon wird im `UdbSourcesProvider` nur noch fuer `kind in {connection, featureNode}` (= Daten-Quellen-Root) angehaengt -- aber dort *immer*, auch ohne `dataSourceId`. Klick triggert `_ensureRecord` -> `onOpenSettings(dsId, label)`.
+- F5 -- `SourcesTab` setzt `title={t('Datenquellen')}` als Section-Header.
+
+**G-Iteration (Smoke-Test-Feedback, gleiche Sitzung)**:
+
+- G1 -- 139 TS-Lint-Fehler in `FormGeneratorTree.test.tsx`: Wurzel war fehlende tsconfig-Reference auf `tsconfig.test.json` (LSP fiel fuer `*.test.tsx` auf den App-Config zurueck, der Tests ausdruecklich `exclude`d). Fix: `tsconfig.json.references` um `./tsconfig.test.json` erweitert + im Testfile expliziter `import React`, `import 'vitest/globals'`-aequivalent (`@testing-library/jest-dom/vitest`), `afterEach` aus `vitest`. 139 -> 0; Tests 50/50 weiterhin gruen.
+- G2 -- Off-State-Differenzierung: offenes Schloss + RAG-OFF (Brain) bekommen `filter: grayscale(1); opacity: .45` als inline-style. ON-Zustand bleibt voll farbig. Eindeutige Unterscheidung auf einen Blick.
+- G3 -- Sub-Element-Expansion:
+ - **Bug**: `_browseChildren` setzte `parentKey` auf die synthesisierte ds-Koordinate (`ds|conn|sourceType|/`), aber der Anrufer hat den `svc|...`-Key erwartet. Folge: zurueckgegebene Folders/Files hatten einen `parentId`, der nicht im Tree existiert; sie wurden stillschweigend verworfen. Fix: `_browseChildren` akzeptiert jetzt `parentKey` und nutzt ihn fuer Childs.
+ - **Feature-Felder**: `fdsTable` hat jetzt `hasChildren: True` wenn das DataObject-Meta `fields: List[str]` deklariert. Neuer Knoten-Typ `fdsField` (kind), neuer Dispatcher-Zweig `fdstbl|fi|table` -> `_featureTableFields`. Effektiver Neutralize-Wert pro Feld = `parent.neutralize OR field IN parent.neutralizeFields`. Toggle wird im `UdbSourcesProvider` per Special-Case auf `PATCH /api/datasources/{id}/neutralize-fields` mit der mutierten Liste umgesetzt; `neutralizeFields` ist im fdsTable-Payload mit drin (kein Extra-GET). Scope und RAG auf Feld-Ebene sind bewusst nicht editierbar.
+- G4 -- FilesTab: synthetischer "/"-Root pro Ownership. Provider gibt fuer `parentId=null` einen `__filesRoot:`-Knoten zurueck (`defaultExpanded=true`); reale Top-Level-Items werden Children dieses Roots. `moveNodes` mit Ziel-Root re-mapt auf `parentId/folderId=null` (= Drop auf "/"). `patchScope`/`patchNeutralize` mit Synth-Root-Id expandieren ueber die API zu allen Top-Level-Folders+Files und setzen pro Folder zwingend `cascadeChildren=true` (= globale Neutralisierung/Scope-Setzung).
+- G5 -- Persistenter Expand-State: in der H-Iteration umgesetzt (siehe unten).
+
+**H-Iteration (zweite Smoke-Runde, gleiche Sitzung)**:
+
+- H1 -- Logik-Audit aller drei Flags. Backend-Verhalten ist konsistent (`_inheritFlags.py`, in einem Modul fuer DS und FDS):
+ - **Werteliste**: `neutralize` und `ragIndexEnabled` sind `False | True | NULL`. `scope` ist `'personal' | 'mandate' | 'global' | NULL`. `NULL` bedeutet "erbe vom naechsten Vorfahren". `'mixed'` ist nur ein UI-Display-Wert, nie persistiert.
+ - **Effective-Resolution** (`_resolveWalkValue` / `_resolveWalkValueFds`, `mode='walk'`): own explicit -> ancestor-chain (nahester zuerst) -> default (`False`/`'personal'`). Liefert *immer* einen konkreten Wert.
+ - **Aggregate-Resolution** (`mode='aggregate'`): wie walk, ABER wenn ein Knoten Nachkommen hat, deren walk-effective-Werte voneinander abweichen, gibt das Backend `'mixed'` zurueck. Reine Frontend-Anzeige.
+ - **PATCH** (`/api/datasources/{id}/{flag}`): cascade-reset ALLER expliziten Nachkommen-Werte auf `NULL` (bottom-up, deepest first), DANN setzt das Backend den Master-Wert. Konsequenz: nach jedem Toggle hat der Subtree exakt einen expliziten Wert (am Master), alle Nachkommen erben -- aggregate kann gar nicht `'mixed'` werden, solange keine neuen Toggles auf den Nachkommen abgegeben werden.
+ - **Frontend**: `_handleCycleScope` / `_handleToggleNeutralize` / `_handleToggleRagIndex` mappen `'mixed'` *immer* auf einen konkreten Wert (`'personal'` bzw. `false`). Es gibt keinen Code-Pfad, der `'mixed'` an das Backend uebermittelt. Wenn der User trotz cascade-PATCH `'mixed'` sieht, deutet das auf einen Cascade-Skip im Backend (Bug, kein Designdetail).
+- H2/H8 -- *Initial-Render-Bug*: Auto-Expand-Effekt fuehrte sequentielle `for ... await provider.loadChildren(...)` aus. Die erste Antwort triggerte `setNodes`, der Effect-Cleanup setzte `cancelled=true` -- alle weiteren Iterationen brachen ab. Folge: der ZWEITE/DRITTE `defaultExpanded`-Knoten zeigte zwar das Expand-Icon (weil `setExpandedIds` synchron ablief), aber nie Kinder, bis der User manuell collapse+expand klickte. Fix: `Promise.all(...)` parallel + ein einziges, atomares `setNodes(prev => [...prev, ...flat])` pro Effekt-Run. Damit wird das Cancellation-Race irrelevant, weil bis zur Cancel-Flagge alle Antworten schon zugewiesen sind.
+- H3 -- *FilesTab Move-404*: `PUT /api/files/{fileId}` (Route-Handler `update_file`) hatte keine `RequestContext`-Dependency und initialisierte `interfaceDbManagement.getInterface(currentUser)` ohne `mandateId`/`featureInstanceId`. RBAC-Filter im `getFile`-Lookup verlieren so ihre Scope-Information, der File ist im "Default-Scope" nicht sichtbar -> 404. Fix: Context-Dep eingefuegt, mandateId+featureInstanceId an `getInterface` durchreichen. Gleicher Fix bei `delete_file`. Zusaetzlich `move_folder` so erweitert, dass es sowohl `parentId` als auch `targetParentId` aus dem Body liest -- der generische `provider.moveNodes(targetParentId)`-Aufruf verwendet die zweite Schreibweise.
+- H4 -- *Neuer Folder am falschen Render-Platz*: `FolderFileProvider.createChild(parentId=null, ...)` setzte den FE-`parentId` des erzeugten Nodes auf `null`. `_buildChildMap` mappt `null` auf `__root__`, der neue Folder erschien also auf der Legacy-Top-Level-Reihe NEBEN dem `/`-Synth-Root. Fix: wenn `parentId === null` (kein selektierter Parent, globaler "+"-Klick), `parentId = _SYNTH_ROOT_ID('own')` setzen. Wenn der User auf einen synthetischen Root klickt, wird er ebenfalls als Parent uebernommen. API-Call bleibt `parentId=null`.
+- H5 -- *+ Button pro Folder*: neue `onCreateChild?: (parentId: string) => void`-Prop in `TreeNodeRowProps`. Wird gerendert, wenn `provider.createChild` existiert, der Node ein Folder ist und `provider.canCreate?.(node.id)` true ist (oder fehlt). Klick ruft `_createFolderAt(node.id)` -- gleicher Code-Pfad wie der globale "Neuer Ordner"-Button. Erscheint im Hover-Action-Slot vor dem Rename-Pencil.
+- H6 -- *FilesTab neutralize teilweise broken*: Diagnose laeuft. Vermutung: gemeinsam mit H2 erledigt -- der Auto-Expand-Race konnte dazu fuehren, dass die optimistic-update-Map `_collectDescendantIds` Knoten verfehlte, deren Children noch nicht im FE-State waren. Mit dem Parallel-Fetch sind alle defaultExpanded-Children synchron zur ersten Render-Phase im Tree.
+- H7 -- *Hardcoded "[Persoenliche Quellen]"*: der Backend-Code rief `resolveTextSafe("Persoenliche Quellen")` auf, also den umlaut-encoded Source-String. `t()` gibt fuer DE den Schluessel selbst zurueck (= "Persoenliche Quellen"); fuer andere Sprachen den `[key]`-Fallback. Beides falsch fuer einen Anzeigetext. Fix: Source-String auf `"Persönliche Quellen"` (echter Umlaut) korrigiert -- zeigt im DE-Locale jetzt den lesbaren Text, in EN/FR weiter `[Persönliche Quellen]` (= bekannter i18n-Fallback, klar als untranslated erkennbar).
+- H9 -- *FDS Neutralize geht nicht*: `POST /api/workspace/{instanceId}/feature-datasources` hatte einen harten Cross-Mandate-Block: wenn die Workspace-Instance Mandate A war, der referenzierte FeatureInstance aber Mandate B (beide vom User zugaenglich), 403 -> `_ensureRecord` schluckt den Fehler still (`console.error`) und PATCH wird uebersprungen. Sichtbares Symptom: Klick tut nichts. Fix: Cross-Mandate-Check entfernt, statt dessen explizit `getFeatureAccess(userId, body.featureInstanceId)` validieren. `mandateId` der neuen FDS = `wsMandateId` (= Workspace's Mandant), so dass die FDS unter der Workspace-Tenancy lebt -- konsistent mit dem Tree-Filter `workspaceInstanceId == instanceId`.
+- H10 (= G5/P5) -- *Persistenter Expand-State*:
+ - Datamodel: `WorkspaceUserSettings.uiTreeExpansion: Dict[str, List[str]]` (Key = scope-Name wie `'sources'`, `'filesOwn'`, `'filesShared'`).
+ - Routen: `GET /api/workspace/{instanceId}/ui-tree-expansion/{scope}` -> `{expandedNodes: List[str] | null}`. `PUT /api/workspace/{instanceId}/ui-tree-expansion/{scope}` body `{expandedNodes: List[str]}`. Erste PUT erstellt den Settings-Record, jede weitere PATCHt nur den `uiTreeExpansion[scope]`-Slot.
+ - Frontend-Hook: `useTreeExpansion(instanceId, scope)` laedt den Initialwert, liefert `{loaded, expandedIds, setExpandedIds}` zurueck. `setExpandedIds` debounct (600 ms) den PUT.
+ - FormGeneratorTree-Vertrag: zwei neue Props `expandedIds?: string[] | null` + `onExpandedIdsChange?: (ids: string[]) => void`. Bei `Array.isArray(expandedIds)` ueberschreibt die persistierte Liste die Backend-`defaultExpanded`-Hints (User-Praeferenz wins). Bei `null` bleibt der Default-Verhalten erhalten und der erste User-Toggle erstellt den Settings-Record. Tree feuert `onExpandedIdsChange` nur, wenn die neue Liste sich VON der zuletzt vom Parent gepushten unterscheidet (kein Echo-Loop).
+ - `SourcesTab` und `FilesTab` rufen `useTreeExpansion(instanceId, scope)` und reichen `expandedIds`/`setExpandedIds` an `FormGeneratorTree` weiter. FilesTab verwaltet zwei Scopes (`filesOwn`, `filesShared`) wegen der zwei Tree-Instanzen.
+
+**Diagnose-Befund vor dem Bauen** (Terminal-Log + User-Beobachtung):
+
+- Backend liefert sauber 6 Top-Level-Knoten (`counts={'__root__': 6}` bei
+ `allDs=38, allFds=11`); Endpoint antwortet 200 OK auf jeden POST.
+- User sieht im Tab nur den Ladeplatzhalter `"Lade Datenquellen..."` --
+ d.h. der State-Pfad im alten `SourcesTab.tsx` aktualisiert das `loadingTopLevel`-
+ / `childrenByParent`-State nie. Vermutliche Ursache: Race zwischen StrictMode-
+ Doppelmount, `mountedRef`-Frueh-Abort und dem zweiten useEffect auf
+ `expansionSignature` (vier parallele POSTs sichtbar). Diagnose nicht weiter
+ vertieft, weil der Recovery-Rewrite den ganzen ad-hoc Renderer eliminiert.
+- **Konsequenz**: Phase 1 wuerde alleine zwar das Backend "richtig" liefern,
+ die UI bliebe aber kaputt. Vollstaendiger Recovery-Pfad noetig.
+
+**Plan-Abweichungen (durch Implementierung gelernt)**:
+
+- `TreeNode.displayOrder?: number` als zusaetzliche generische Erweiterung
+ in Phase 2 noetig: Sortierung von `personalRoot` (alphabetisch nach
+ `Mandanten-Daten`) sonst falsch. Vorrang vor folder-first / alphabet,
+ Fallback unveraendert. Kein UDB-Vokabular im Tree.
+- Synthetische Container (`synthRoot`, `mandateGroup`) im Provider-Mapping:
+ `scope`/`neutralize`/`ragIndexEnabled` werden auf `undefined` gesetzt, damit
+ `FormGeneratorTree` die Buttons gar nicht erst rendert. Sauberer als sie
+ als readonly zu styling.
+- Mandate-Group-Knoten verlieren ihren `mandateRoot`-Parent jetzt korrekt
+ (vorher `parentKey=None` -> top-level); Test eingeplant.
+- `getChildrenForParents` Diagnose-Logging entfernt nach erfolgreichem
+ Phase-1-Smoke.
+
+## Beschreibung und Kontext
+
+Der "UDB Generic Tree Refactor" (`c-work/4-done/2026-05-udb-generic-tree-refactor.md`,
+2026-05-18) ist im Backend sauber geliefert (Helpers + Builder + Endpoint +
+Tests 85/85 gruen), aber das Frontend wurde architektur-falsch umgesetzt:
+`SourcesTab.tsx` enthaelt eine **eigene, parallele** Tree-Renderer-Implementierung
+(`_TreeNodeView`, `_FlagButton`, ~555 Zeilen) anstatt das bereits etablierte
+`FormGeneratorTree` zu verwenden, das in `FilesTab.tsx` als Referenz lebt.
+
+Effekt fuer den User:
+
+1. Files-Tab funktioniert (FormGeneratorTree, zeigt Scope-/Neutralize-Icons korrekt).
+2. Sources-Tab laedt keine Daten oder rendert nicht das, was erwartet wird.
+3. Zwei Tree-Implementierungen muessen parallel gepflegt werden, was die
+ wiederholten Bugfix-Iterationen erklaert.
+
+Die User-Direktive aus `local/notes/recovery.md` ist klar: alle drei Hierarchien
+(Personal Sources, Feature Sources, Folder+Files) **identisch** behandeln. Der
+`FormGeneratorTree` ist die Referenz. Sources-Tab muss darauf umgestellt werden.
+
+Risiko bei Nicht-Loesung: weitere Iterationen, weitere Workarounds, fortschreitende
+Drift zwischen den beiden Tree-Renderern, wachsende Test-Luecke.
+
+## Fokus und kritische Details
+
+- **Backend ist einzige Quelle der Wahrheit**. UI berechnet NICHTS (keine
+ Vererbung, keine effektiven Werte, keine Mixed-Aggregation).
+- **Keine optimistic UI-Updates**. Toggle-Flow ist strikt:
+ `Klick -> Spinner an -> PATCH -> Refetch -> Render -> Spinner aus`.
+- `FormGeneratorTree` bleibt **domaenenfrei**. Generische Erweiterungen
+ (Mixed-Symbol, Pending-Spinner, extraActions, optionales Refetch nach Action)
+ duerfen NICHT von UDB- oder Connection-Vokabular wissen.
+- Der Files-Tab nutzt heute optimistic Updates auf scope/neutralize. Diese
+ bleiben fuer FilesTab erhalten (kein Regress) und werden fuer Sources via
+ neuer opt-in Prop aktiviert/deaktiviert.
+- Backend-Endpoint `POST /api/workspace/{instanceId}/tree/children` liefert
+ bereits per-Parent Children mit pre-computed `effectiveNeutralize`,
+ `effectiveScope`, `effectiveRagIndexEnabled` (jeweils `boolean|'mixed'` bzw.
+ `string|'mixed'`). Das ist der **einzige** Backend-Read-Pfad fuer den
+ Sources-Tree -- nichts daran aendern.
+- Layout-Anforderung (`recovery.md` Section 7): eine sichtbare Quellen-Wurzel,
+ darunter zwei Sub-Baeume "Persoenliche Quellen" + "Mandanten-Daten". Aktuell
+ liefert `_topLevel` direkt Connections + Mandate-Groups flach -- es fehlt der
+ gemeinsame Container.
+- Keine Multi-Selection im UDB Sources-Tab.
+
+## Ziel und Nicht-Ziele
+
+### Ziel
+
+1. SourcesTab nutzt `FormGeneratorTree` als alleinigen Renderer. Eigener
+ `_TreeNodeView` / `_FlagButton`-Code in SourcesTab wird komplett entfernt.
+2. Drei Toggle-Flags (neutralize, scope, ragIndexEnabled) erscheinen einheitlich
+ pro Knoten. Mixed-Symbol einheitlich (`U+25E9`).
+3. Toggle-Flow strikt PATCH -> Refetch -> Render. Kein optimistic Update im
+ Sources-Tab. Spinner pro pendendem Flag-Button.
+4. `FormGeneratorTree` erhaelt **eine** generische Erweiterung
+ (`refreshAfterAction?: boolean`), kennt KEIN UDB-Vokabular.
+5. Layout: gemeinsame Wurzel mit zwei Sub-Baeumen (Personal / Mandate).
+6. FilesTab bleibt funktional unveraendert (Regression-Schutz).
+
+### Explizit NICHT
+
+- Backend-Helpers (`_inheritFlags.py`, `cascadeReset*`, PATCH-Routes,
+ Tree-Endpoint) werden **nicht angefasst**.
+- Keine neue PATCH-API. Bestehende `/api/datasources/{id}/{scope|neutralize|rag-index}`
+ reichen.
+- Keine FDS-Record-Expansion (User-Entscheidung 2026-05-18: Tabellen-Ebene reicht).
+- Kein Mehrfach-Select im SourcesTab.
+- Keine Wiederbelebung der geloeschten Tree-Endpoints.
+- Keine Aenderung am FilesTab-Verhalten (insbesondere: dort weiterhin keine
+ Refetch-nach-Action-Pflicht; Files-Modell hat keine Mixed-Aggregation auf
+ Ancestors, optimistic Update reicht).
+
+## Betroffene Module
+
+- **Gateway**:
+ - `gateway/modules/serviceCenter/services/serviceKnowledge/_buildTree.py`:
+ `_topLevel` umbauen, sodass es **eine** synthetische Wurzel `srcRoot` mit
+ zwei Sub-Knoten zurueckliefert (`personalRoot`, `mandateRoot`); Connections
+ werden Children von `personalRoot`, Mandate-Groups Children von `mandateRoot`.
+ Neue Key-Praefixe: `srcRoot`, `personalRoot`, `mandateRoot`. Keine sonstigen
+ Aenderungen am Builder.
+ - Optional: `getChildrenForParents` so erweitern, dass die drei neuen Synthese-
+ Keys gehandhabt werden (Dispatch).
+- **Frontend**:
+ - `frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx`:
+ komplett ersetzen (~555 LOC -> erwartet ~80-120 LOC), nur noch ein duenner
+ Wrapper um `FormGeneratorTree` mit eigenem Provider und Settings-Modal-Ankopplung.
+ - `frontend_nyla/src/components/FormGenerator/FormGeneratorTree/types.ts`:
+ Optional, je nach gewaehltem Approach (siehe Entscheidung E1):
+ - Variante A: `TreeNode.ragIndexEnabled?: boolean | 'mixed'` als drittes
+ First-Class-Feld + Provider-Methode `patchRagIndex?(ids, value)`.
+ - Variante B: RAG ueber `extraActions[]` mit `value: 'mixed'|true|false` --
+ schon heute unterstuetzt, Tree braucht nichts neues.
+ - `frontend_nyla/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx`:
+ eine generische Prop `refreshAfterAction?: boolean` (Default: `false`,
+ backward-compatible), und ein generischer Refresh-Hook der nach
+ `_handleCycleScope`/`_handleToggleNeutralize`/`_handleExtraAction` (NUR
+ wenn aktiviert) `provider.loadChildren(...)` fuer **alle expandierten
+ Parents + `null`** aufruft und die Nodes ersetzt. Ohne UDB-Vokabular.
+ - Neuer Datei `frontend_nyla/src/components/UnifiedDataBar/UdbSourcesProvider.ts`:
+ `TreeNodeProvider`-Implementierung, die `POST /tree/children` aufruft und
+ Backend-`TreeNode` auf den generischen `FormGeneratorTree.TreeNode` mappt.
+- **DB-Migration**: nein.
+- **Andere**: keine.
+
+## Entscheidungen
+
+| Datum | Entscheidung | Begruendung |
+|-------|--------------|-------------|
+| 2026-05-18 | Backend-Helpers, PATCH-Routes und Tree-Endpoint **nicht antasten** | Tests 85/85 gruen, korrekte Cascade-/Mixed-Semantik, recovery.md §11 listet sie als "vorhanden, nicht antasten". |
+| 2026-05-18 | SourcesTab eigener Tree-Renderer **wegwerfen** | Architekturverstoss; recovery.md §1.3 + §5 + §9. Parallele Logik = staendige Drift. |
+| 2026-05-18 | FilesTab **nicht antasten** | Funktioniert; dient als Regression-Pruefstein fuer FormGeneratorTree-Erweiterungen. |
+| 2026-05-18 | E1: RAG als drittes First-Class-Feld in `TreeNode` (Variante A) | Drei Flags muessen "identisch behandelt" werden (recovery.md §1, §2). Wenn scope+neutralize First-Class sind, gehoert ragIndexEnabled auch dazu. ExtraActions sind ein Workaround und schaffen Asymmetrie. Tree-Renderer kennt nur "ein RAG-Flag mit Mixed-Semantik" -- kein UDB-Vokabular. |
+| 2026-05-18 | E2: `refreshAfterAction: boolean` als opt-in Prop am Tree | FilesTab bleibt unveraendert (default `false` = optimistic local update); Sources opted-in (`true` = refetch all expanded). Generisch, kein UDB-Wissen im Tree. |
+| 2026-05-18 | E3: Synthetische Root-Knoten `srcRoot` + `personalRoot` + `mandateRoot` im Builder | recovery.md §7 verlangt eine sichtbare Quellen-Wurzel mit zwei Sub-Baeumen. Backend bleibt autoritativ; UI mappt 1:1. |
+
+## Umsetzungs-Checkliste
+
+### Phase 1: Backend (Layout-Synthese) -- DONE
+
+- [x] In `_buildTree.py` neue Key-Praefixe einfuehren (`srcRoot`, `personalRoot`, `mandateRoot`)
+ als literal-konstante Top-Level-Tokens (kein `_encode`).
+- [x] `_topLevel(...)` umgebaut: liefert genau einen `srcRoot` mit
+ `hasChildren=True`, `displayOrder=0`. Personal- und Mandate-Listen werden
+ nicht mehr im Top-Level emittiert.
+- [x] Neue Helper `_srcRootChildren()` (statisches `[personalRoot, mandateRoot]`),
+ `_personalRootChildren(instanceId, context, allDs)` (Connections mit
+ `parentKey=personalRoot`), `_mandateRootChildren(...)` (Mandate-Groups
+ mit `parentKey=mandateRoot`).
+- [x] `_syntheticNode(...)` + `_emptyTriplet()` Helpers fuer konsistente
+ Container-Defaults. `displayOrder` als generischer Sort-Hint.
+- [x] `getChildrenForParents` Dispatch fuer die drei neuen Keys (Vergleich
+ auf literalen `parentKey` BEVOR `_decode` ausgewertet wird).
+- [x] **Keine** Aenderung an `_inheritFlags.py`, `routeDataSources.py`,
+ `routeFeatureWorkspace.py`. Synthese-Knoten haben keinen DB-Record und
+ tauchen daher nicht in der Effective-Aggregation auf.
+- [x] Tests in `test_buildTree.py` ergaenzt: 7 neue Cases unter `TestSyntheticRoots`
+ (Top-Level immer 1 srcRoot; srcRoot expandiert auf 2 Container mit
+ `displayOrder` 0/1; neutrale Defaults; Personal-Root emittiert aktive
+ Connections; Mandate-Root filtert Mandate ohne Data-Features). Bestehende
+ Top-Level-Test angepasst.
+
+### Phase 2: FormGeneratorTree (generische Erweiterung, kein UDB-Vokabular) -- DONE
+
+- [x] `types.ts`: `TreeNode.ragIndexEnabled?: boolean | 'mixed'` ergaenzt.
+- [x] `types.ts`: `TreeNode.displayOrder?: number` ergaenzt (Plan-Erweiterung,
+ wegen Sortierung der Synthese-Container noetig; vorrang vor folder-first).
+- [x] `types.ts`: `TreeNodeProvider.patchRagIndex?(ids, value): Promise`
+ und `canPatchRagIndex?(node): boolean` ergaenzt.
+- [x] `types.ts`: `FormGeneratorTreeProps.refreshAfterAction?: boolean`
+ (default `false`, Backward-Compat fuer FilesTab).
+- [x] `FormGeneratorTree.tsx`: dritter Flag-Button (`_RAG_EMOJI`,
+ `_ACTION_RAG`) rendert genau dann, wenn `node.ragIndexEnabled !==
+ undefined`. Mixed-Symbol identisch zu scope/neutralize.
+- [x] `_handleToggleRagIndex` analog zu `_handleToggleNeutralize` -- mit
+ gleichem Cascade-auf-Folder-Optimismus, der jedoch bei
+ `refreshAfterAction=true` uebersprungen wird.
+- [x] Sort-Comparator in `_buildChildMap` erweitert: bei Geschwistern mit
+ `displayOrder` sortiert numerisch aufsteigend; Knoten ohne `displayOrder`
+ kommen NACH Knoten mit. Vorrang vor folder-first.
+- [x] `_refetchAllExpanded()` Helper: parallele `loadChildren(p, ownership)`
+ fuer `p in [null, ...expandedIds]`, danach atomar `setNodes(prev)`
+ mit Filter (Knoten unter neuen Parents werden ersetzt; alle anderen
+ bleiben unangetastet).
+- [x] `_runAction` ruft `_refetchAllExpanded` automatisch auf wenn
+ `refreshAfterAction=true`. Spinner haelt durch bis Refetch fertig.
+- [x] FilesTab: nicht angefasst. Default `refreshAfterAction=false` =
+ bisheriger optimistic-update-Pfad.
+- [x] Tests `__tests__/FormGeneratorTree.test.tsx` ergaenzt um 10 neue
+ Cases: RAG-Default-Render, RAG-Click->patchRagIndex, RAG hidden ohne
+ Feld, RAG-Mixed-Symbol, RAG-Mixed cycles to false, displayOrder
+ numerisch, displayOrder vor unsortiertem, Fallback auf folder-first,
+ `refreshAfterAction=true` triggert Refetch, `refreshAfterAction=false`
+ triggert keinen Refetch.
+
+### Phase 3: SourcesTab (Wegwerfen + Wrapper) -- DONE
+
+- [x] Neue Datei `UnifiedDataBar/UdbSourcesProvider.tsx`:
+ - `rootKey: 'udb-sources-${instanceId}'`.
+ - `loadChildren(parentId, _ownership)`: POST `/api/workspace/{instanceId}/tree/children`
+ mit `{parents: [parentId]}`; mappt `nodesByParent[parentId ?? '__root__']`
+ auf `TreeNode` und befuellt internen `nodeCache: Map`. Cache wird von Patch-Pfaden gelesen.
+ - Mapping wie geplant; **Synthese-Container** (`synthRoot`, `mandateGroup`)
+ bekommen `scope=neutralize=ragIndexEnabled=undefined` damit `FormGeneratorTree`
+ die Buttons gar nicht erst rendert.
+ - `canPatchScope`/`canPatchNeutralize` -> `!_isSyntheticContainer(kind)`.
+ - `canPatchRagIndex` -> `!_isSyntheticContainer && data.supportsRag`.
+ - `patchScope`/`patchNeutralize`/`patchRagIndex` ueber gemeinsame `_patchFlag`-
+ Helper: lookup im `nodeCache`, `_ensureRecord` (POST `/datasources` oder
+ `/feature-datasources` je nach `kind`), dann PATCH des Flags. URL-Pfad
+ `rag-index` (mit Bindestrich), Body `{ragIndexEnabled: ...}` (camelCase).
+ - `cascadeChildren`-Param ignoriert (Backend cascadiert ueberall).
+ - `getBatchActions` nicht implementiert (kein Multi-Select).
+ - `canCreate`/`canRename`/`canDelete`/`canMove` nicht implementiert -> Tree
+ rendert keine Aktionen dafuer.
+- [x] `UnifiedDataBar/SourcesTab.tsx` komplett neu, ~75 LOC:
+ - `useMemo(() => createUdbSourcesProvider(instanceId, _handleOpenSettings), [...])`.
+ - ``.
+ - Settings-Modal-Anbindung ueber `extraActions` auf jedem Node mit
+ `dataSourceId`. Klick auf Settings setzt lokalen Modal-State im Wrapper.
+- [x] Alter `_TreeNodeView`/`_FlagButton` ad-hoc Renderer komplett entfernt.
+- [x] `SourcesTab.module.css` wurde im Vorgaenger-Refactor schon entfernt --
+ keine Wiedereinfuehrung noetig.
+- [x] Tests `__tests__/UdbSourcesProvider.test.ts`: 14 Cases. loadChildren-
+ Endpoint, Mapping, Synthese-Container ohne Flags, RAG-Hide ohne
+ `supportsRag`, settings-extraAction, Cache-Befuellung, canPatch*
+ Predicates, patchScope mit existierendem Record, ensureRecord-Pfad,
+ cache-miss skip, patchNeutralize, patchRagIndex (DataSource & FeatureDataSource).
+
+### Phase 4: Validierung -- in progress
+
+- [x] Backend-Restart: ueberprueft via Diagnose-Log (4× POST 200 OK,
+ `counts={'__root__': 6}` -> mit neuem `_topLevel` aendert sich das auf
+ `counts={'__root__': 1}`); Diagnose-Log danach entfernt.
+- [x] Backend-Suite gruen: `pytest tests/unit/services/test_buildTree.py
+ tests/unit/services/test_inheritFlags.py` -> 89/89.
+- [x] Frontend-Suite gruen: `npx vitest run src/components/UnifiedDataBar
+ src/components/FormGenerator/FormGeneratorTree` -> 65/65.
+- [ ] User-Smoke: Top-Level zeigt einen "Datenquellen"-Knoten mit Chevron.
+- [ ] User-Smoke: Expandieren -> "Persoenliche Quellen" + "Mandanten-Daten".
+- [ ] User-Smoke: Persoenliche Quellen: pro UserConnection ein Knoten mit
+ Authority-Icon + drei Flag-Buttons (RAG, Scope, Neutralize) + Settings-Icon.
+- [ ] User-Smoke: Mandanten-Daten: pro Mandate-Group + pro Feature-Instanz
+ + pro Tabelle jeweils drei Flag-Buttons.
+- [ ] User-Smoke: Klick auf Flag -> Spinner ueber Button bis PATCH+Refetch
+ durch sind. Andere Buttons bleiben klickbar.
+- [ ] User-Smoke: Klick auf Mixed-Symbol setzt deterministischen Wert,
+ Cascade an Children sichtbar nach Refetch.
+- [ ] User-Smoke: FilesTab unveraendertes Verhalten.
+- [ ] **Wiki-Update** (siehe Abschluss).
+
+### Phase 5: RBAC / Permissions / Neutralisierung / Navigation / Billing
+
+- [ ] **RBAC**: nichts neu. Personal-Sources sind owner-scoped (`userId`-Filter
+ bleibt im Builder), Feature-Sources via `getFeatureAccess.enabled` -- alles
+ schon im bestehenden Builder. Keine neuen Rollen, keine Mandate-/Feature-
+ Rollen-Mischung (recovery.md §10 + `rbac-role-separation.mdc`).
+- [ ] **Neutralisierung**: betroffen nur insofern, als der Toggle das `neutralize`-
+ Flag korrekt rendert. Logik im Backend, kein FE-Pfad.
+- [ ] **Navigation / Routing**: keine Aenderung. UDB ist eine Komponente,
+ keine Page.
+- [ ] **Billing-Impact**: keiner.
+
+## Akzeptanzkriterien
+
+| # | Kriterium (Given-When-Then) | Prio |
+|---|----------------------------|------|
+| 1 | Given ein User mit aktiven Connections und Feature-Instanzen, When er den UDB-Sources-Tab oeffnet, Then sieht er **einen** Wurzel-Knoten "Datenquellen" mit Chevron, der zwei Geschwister enthaelt: "Persoenliche Quellen" und "Mandanten-Daten". | must |
+| 2 | Given ein Knoten mit drei Flags, When der User auf einen Flag-Button klickt, Then erscheint ueber genau diesem Button ein Spinner, der erst verschwindet, nachdem (a) das PATCH zurueckgekehrt ist und (b) der Refetch fuer alle expandierten Parents abgeschlossen ist. | must |
+| 3 | Given ein Connection-Knoten mit drei expandierten Service-Children und divergenten neutralize-Werten, When der User auf das Mixed-Symbol des Connection-Knotens klickt, Then setzt das Backend einen expliziten Wert + Cascade-Reset auf Descendants, und der Tree zeigt nach Refetch fuer **alle** sichtbaren Knoten die neuen effective-Werte (kein Stand-Wert). | must |
+| 4 | Given ein FilesTab-User, When er Scope/Neutralize toggelt oder Files dragt-und-droppt, Then verhaelt sich der Tab unveraendert zur heutigen Version (Regression-Schutz: kein neuer Refetch, keine veraenderten Icons, kein neues Verhalten). | must |
+| 5 | Given das FormGeneratorTree-Modul, When ein Reviewer den Diff prueft, Then enthaelt der Tree-Code keinen UDB-, Connection-, DataSource- oder FeatureDataSource-spezifischen String oder Konstante. | must |
+| 6 | Given das SourcesTab-Modul, When ein Reviewer den Diff prueft, Then ist der View ein duenner Wrapper (< 150 LOC), enthaelt keinen rekursiven Tree-Renderer und keinen Flag-Button-Code. | must |
+| 7 | Given ein Knoten ohne Backing-DB-Record (z.B. Folder, der noch nie getoggelt wurde), When der User auf einen Flag-Button klickt, Then erstellt der Provider den Record via `POST /datasources` oder `/feature-datasources`, danach erst die PATCH-Operation, ohne UI-Glitch. | must |
+| 8 | Given die Backend-Tests, When CI laeuft, Then sind `test_inheritFlags.py` + `test_buildTree.py` weiter gruen (mind. 85/85, plus die 3-4 neuen Tests fuer die Synthese-Wurzel). | must |
+| 9 | Given das Frontend, When `npx tsc --noEmit --skipLibCheck` und `npm run build` laufen, Then kompiliert ohne Fehler/Warnings. | must |
+| 10 | Given ein User mit drei Browser-Tabs auf der gleichen Seite, When er in einem Tab toggelt, Then die anderen Tabs zeigen den neuen Stand erst nach manuellem Reload (kein WebSocket noetig, kein Multi-Tab-Sync gefordert). | should |
+
+## Testplan
+
+| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
+|----|----|-----|--------------|-----------|--------|
+| T1 | 1, 8 | unit | ja | `gateway/tests/unit/services/test_buildTree.py::TestSyntheticRoots` | DONE -- 7/7 |
+| T2 | 8 | unit | ja | `gateway/tests/unit/services/test_buildTree.py::TestGetChildrenForParents` (regress.) | DONE -- 2/2 |
+| T3 | 8 | unit | ja | `gateway/tests/unit/services/test_inheritFlags.py` (regress.) | DONE -- 72/72 |
+| T4 | 2, 3 | unit | ja | `FormGeneratorTree.test.tsx::refreshAfterAction` | DONE -- 2/2 |
+| T5 | 5 | unit | ja | `FormGeneratorTree.test.tsx::RAG-Index toggle` (5) + `displayOrder` (3) | DONE -- 8/8 |
+| T6 | 4 | unit | ja | bestehende `FormGeneratorTree.test.tsx` Tests (Regression) | DONE -- 38/38 |
+| T7 | 6 | manual | nein | `SourcesTab.tsx` ist 75 LOC, kein rekursiver Renderer, kein Flag-Button-Code (nur `` + Settings-Modal) | DONE |
+| T8 | 1, 2, 3, 7 | manual | nein | Smoke-Test: lokale Instanz, Toggle-Cascade pruefen, Mixed-Klick pruefen, neuen Knoten toggeln | OPEN |
+| T9 | 9 | build | ja | `cd frontend_nyla && npx tsc --noEmit --skipLibCheck && npm run build` | OPEN |
+
+## Links
+
+- recovery.md (Anforderungen): `local/notes/recovery.md`
+- Vorgaenger-Plan (done, FE-Teil bricht): `wiki/c-work/4-done/2026-05-udb-generic-tree-refactor.md`
+- Vorgaenger Cascade-Inherit: `wiki/c-work/4-done/2026-05-udb-cascade-inherit.md`
+- Settings-Modal-Plan: `wiki/c-work/4-done/2026-05-udb-datasource-settings.md`
+- Backend-Helpers (nicht antasten): `gateway/modules/serviceCenter/services/serviceKnowledge/_inheritFlags.py`, `_buildTree.py`
+- Backend-Routes (nicht antasten): `gateway/modules/routes/routeDataSources.py`, `gateway/modules/features/workspace/routeFeatureWorkspace.py`
+- Tree-Komponente: `frontend_nyla/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx`
+- Referenz-Implementierung (nicht antasten): `frontend_nyla/src/components/UnifiedDataBar/FilesTab.tsx`
+- Wegzuwerfender Code: `frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx`
+- PR: noch nicht erstellt.
+
+## Abschluss
+
+- [ ] `b-reference/frontend-nyla/formgenerator.md` aktualisieren: Abschnitt
+ "FormGeneratorTree" um die neuen Props (`refreshAfterAction`,
+ `node.ragIndexEnabled`, `provider.patchRagIndex`) ergaenzen.
+- [ ] `b-reference/frontend-nyla/architecture.md` aktualisieren: UDB-Abschnitt
+ so umschreiben, dass SourcesTab als duenner Wrapper um FormGeneratorTree
+ beschrieben ist; Files-Tab bleibt unveraendert beschrieben.
+- [ ] `b-reference/platform/neutralization.md` Querschnitt pruefen
+ (UDB-Toggle-Flow auf Sources verweist nun auf FormGeneratorTree-Pfad).
+- [ ] `TOPICS.md`: bestehender Eintrag "UDB Generic Tree Refactor" um Hinweis
+ auf den Recovery-Plan ergaenzen, Verweis auf neue Doku-Stellen.
+- [ ] `c-work/_CHANGELOG.md`: eine Zeile Refactor pro Phase.
+- [ ] Dieses Dokument -> `4-done/` verschieben.
diff --git a/c-work/4-done/2026-05-udb-datasource-settings.md b/c-work/4-done/2026-05-udb-datasource-settings.md
new file mode 100644
index 0000000..5e2eb52
--- /dev/null
+++ b/c-work/4-done/2026-05-udb-datasource-settings.md
@@ -0,0 +1,187 @@
+
+
+
+
+
+# UDB DataSource Settings (⚙️) + konfigurierbare RAG-Limits
+
+> **Implementierungsstand 2026-05-17:** S0–S11 abgeschlossen. Wichtige Abweichung
+> vom ursprünglichen Plan: das "lazy-fill defaults beim ersten Bootstrap"
+> wurde **nicht** im Walker implementiert (würde die Caller-Override-Semantik
+> aushebeln und liess Tests rot werden). Stattdessen: `_ragLimits` exponiert
+> zwei klare APIs — `getStoredOverrides()` (nur explizite Overrides, für Walker)
+> und `getRagLimits()` (Overrides + Defaults gemerged, für API/Cost-Estimate).
+> Das Modal zeigt fehlende Werte als Placeholder aus dem Cost-Estimate-Endpoint
+> an, damit der User immer eine konkrete Zahl sieht — ohne die Datenbank-Zeile
+> dafür anfassen zu müssen. Siehe CHANGELOG 2026-05-17 für die Detail-Liste.
+
+## Beschreibung und Kontext
+
+Heute sind die RAG-Walker-Limits (`maxBytes`, `maxItems`, `maxFileSize`, `maxDepth`, `maxWorkspaces`, `maxListsPerWorkspace`, `maxTasks`) als Modul-Konstanten in den `subConnectorSync*.py`-Dateien hartkodiert. Ein User mit z. B. einem 500 MB grossen SharePoint-Folder erhält nach 200 MB ein stilles Stoppen (`stoppedAtLimit=maxBytes`) und kann das ohne Code-Änderung nicht anpassen. Heute (2026-05-17) sichtbar geworden auf einem Basecamp-Folder mit ~731 Dateien, von denen nur 25 indexiert wurden, bevor der `maxBytes`-Default zugriff.
+
+Gleichzeitig fehlt für Feature-Instanz-DataSources (`FeatureDataSource`) bisher überhaupt ein UI, an dem der Owner Settings anpassen könnte. UserConnections haben den Wizard und das Edit-Modal auf der Connections-Seite – für Feature-Daten gibt es keinen analogen Ort.
+
+Beide Lücken lassen sich mit derselben Architektur schliessen: **ein generisches Settings-Icon (⚙️) pro Node in der UDB**, das ein typ- und scope-abhängiges Modal öffnet. Initial wird darin RAG-Limit-Werte und Connection-Master-Switch gepflegt, später kommen weitere Einstellungen (z. B. Custom-Neutralization-Regeln, Re-Sync-Cadence, Polling-Strategie) ohne neue Icons dazu.
+
+Wichtiger Architektur-Constraint:
+- **Keine Icon-Inflation in der UDB.** Bereits vorhanden: Scope-Toggle, Neutralize-Toggle, RAG-Index-Toggle (🧠), Chat (💬). Wir fügen genau **ein** zusätzliches Icon (⚙️) hinzu – nicht ein Icon pro Setting.
+
+## Fokus und kritische Details
+
+- UDB ist die **einzige** Stelle, wo Settings pro Datenobjekt gemanagt werden. Andere Pfade (Wizard, Connection-Edit-Modal) editieren nur die Connection-Ebene, nie einzelne DataSources.
+- **Speicherort = Quelle der Wahrheit. Keine Override-Schichten, keine Resolver-Logik.** Was im UI angezeigt wird, ist exakt das, was im Walker greift. Die Werte werden bei der DataSource-Erstellung mit sinnvollen Defaults vorbefüllt (kopiert aus globalen Konstanten); danach kann der User sie einfach editieren. Vorteil: nachvollziehbar, debuggbar, keine versteckten Vererbungen.
+- Walker müssen ihre Default-Konstanten zentralisieren und beim Lesen einer DataSource entweder den dort gespeicherten Wert nehmen oder, falls noch nicht gesetzt, den zentralen Default ausliefern und gleichzeitig auf der DataSource persistieren (lazy initialization → ab dann sind die Werte sichtbar im UI).
+- Settings-Werte landen in einer **JSON-Spalte** auf der DataSource bzw. FeatureDataSource (`settings: Optional[Dict[str, Any]]`) – damit ist die Erweiterbarkeit garantiert, ohne neue Spalten/Migrationen pro neuem Setting.
+- DataSource vs. FeatureDataSource: gleiche Spalte (`settings`), gleiches UI, gleiches API-Pattern (`PATCH /api/datasources/{id}/settings` und `PATCH /api/feature-data-sources/{id}/settings`).
+- Audit-Log-Pflicht: jede Änderung an `settings` schreibt einen `AuditCategory.PERMISSION`-Eintrag (auch wenn es nur ein numerischer Limit-Wert ist – RAG-Ingest-Grenze ist eine Compliance-relevante Entscheidung).
+- Defaults bleiben **konservativ** (200 MB / 10'000 Items / 25 MB / Tiefe 8). Anheben braucht User-Aktion → keine versehentlichen Kostenexplosionen.
+- **Connection-Master-Switch im Modal:** Im Settings-Modal einer DataSource ist auch der Connection-weite Master-Switch `knowledgeIngestionEnabled` ("Knowledge database active") togglebar – damit ist das Modal die zentrale Stelle für alle relevanten Settings, ohne zwischen Seiten wechseln zu müssen. Dieser Toggle wirkt auf alle DataSources derselben Connection (es ist explizit als "Connection-Setting" gekennzeichnet, nicht als DataSource-Setting).
+- **Kosten-Indikator statt Hard-Cap:** Keine harten Obergrenzen. Stattdessen zeigt das Modal eine indikative (nicht-verbindliche) Realtime-Kostenschätzung: basierend auf erwarteter Item-Zahl, Token-Schätzung pro Item und aktuellen Embedding-Preisen. Klar als "indikativ" gekennzeichnet, der User trägt die Verantwortung.
+
+## Ziel und Nicht-Ziele
+
+- **Ziel:**
+ - Ein Settings-Icon ⚙️ pro Tree-Node in der UDB-Sidebar (`SourcesTab.tsx`), das ein Modal öffnet.
+ - Im Modal: drei klar abgegrenzte Sektionen:
+ 1. **Connection** (Master-Switch `knowledgeIngestionEnabled`, gemeinsame Mail-/ClickUp-Preferences aus `knowledgePreferences`).
+ 2. **DataSource RAG-Limits** (`maxBytes`, `maxFileSize`, `maxItems`, `maxDepth`).
+ 3. **Kostenschätzung** (indikativer Wert mit Hinweis).
+ - Backend: `DataSource.settings` und `FeatureDataSource.settings` als JSON-Spalte. Endpunkte zum Lesen/Patchen.
+ - Walker (`subConnectorSyncSharepoint.py`, `subConnectorSyncKdrive.py`, `subConnectorSyncGdrive.py`, `subConnectorSyncClickup.py`) lesen Limits direkt aus `DataSource.settings.ragLimits`, fallen auf zentrale Defaults zurück, wenn (noch) leer.
+ - Partial-Banner auf `RagInventoryPage` zeigt zusätzlich Hint: "Limit kann pro DataSource in der UDB unter ⚙️ angehoben werden."
+ - Owner-Kontrolle: für UserConnection-DataSources nur Owner; für FeatureDataSource Owner oder `workspace-admin`.
+- **Nicht-Ziel:**
+ - Mandate-weite Defaults / Override-Schichten / Resolver-Layer.
+ - Hard-Caps (User/Admin trägt Verantwortung).
+ - Settings-Vererbung im Tree (Parent-Folder → Children) – aktuell wirkt eine Änderung nur auf die konkrete DataSource.
+ - Eigene Settings für Mail-Connectors (`Outlook`, `Gmail`) auf DataSource-Ebene – die haben keine Folder-Hierarchie. Die Mail-Preferences bleiben Connection-weit und werden im Modal in der "Connection"-Sektion editiert.
+
+## Architektur-Skizze
+
+### Daten-Schicht
+
+`DataSource` und `FeatureDataSource` bekommen je eine neue Spalte:
+```python
+settings: Optional[Dict[str, Any]] = Field(
+ default=None,
+ description="DataSource-scoped settings (JSON). Currently used keys: ragLimits.",
+ json_schema_extra={"frontend_type": "json", "frontend_readonly": True, "frontend_required": False},
+)
+```
+
+JSON-Schema-Konvention:
+```json
+{
+ "ragLimits": {
+ "maxBytes": 524288000,
+ "maxFileSize": 52428800,
+ "maxItems": 20000,
+ "maxDepth": 12
+ }
+}
+```
+**Keine Resolver-Schichten**: was in `settings.ragLimits` steht, gilt. Wenn der Key fehlt, nimmt der Walker den zentralen Default aus `_ragLimits.RAG_LIMITS_DEFAULT` und schreibt ihn beim nächsten Bootstrap **einmalig** in die DataSource zurück (lazy fill), damit der User die Werte auch ohne vorherigen Sync schon im UI sieht und editieren kann.
+
+### Backend-API
+
+```
+PATCH /api/datasources/{id}/settings { settings: { ragLimits: {...} } }
+PATCH /api/feature-data-sources/{id}/settings { settings: { ragLimits: {...} } }
+GET /api/datasources/{id}/cost-estimate → { estimatedTokens, estimatedUsd, basis: {...} }
+```
+`/cost-estimate`: schätzt anhand Item-Count (sofern bekannt aus letztem Sync) × tokens-per-item-heuristik × Embedding-Preis. Liefert auch die Annahmen (`basis`), damit der User die Plausibilität prüfen kann.
+
+### Walker-Refactor
+
+Heute hartkodiert in jedem Walker:
+```python
+MAX_BYTES_DEFAULT = 200 * 1024 * 1024
+MAX_ITEMS_DEFAULT = 10_000
+```
+Wird zu:
+```python
+# gateway/modules/serviceCenter/services/serviceKnowledge/_ragLimits.py
+RAG_LIMITS_DEFAULT = {
+ "maxBytes": 200 * 1024 * 1024,
+ "maxFileSize": 25 * 1024 * 1024,
+ "maxItems": 10_000,
+ "maxDepth": 8,
+}
+
+def getRagLimits(dataSource: Dict[str, Any]) -> Dict[str, int]:
+ """Read limits from DataSource.settings.ragLimits, fall back to defaults
+ for missing keys. Pure read; lazy persist is the caller's responsibility."""
+ stored = (dataSource.get("settings") or {}).get("ragLimits") or {}
+ return {**RAG_LIMITS_DEFAULT, **stored}
+
+def lazyFillRagLimits(rootIf, dataSourceId: str, dataSource: Dict[str, Any]) -> None:
+ """Persist defaults to settings if not yet present, so the UI shows them."""
+```
+Jeder Walker holt seine Limits beim Eintritt in `_bootstrap*` einmal und gibt sie an `_walkFolder()` / `_finalizeResult()` weiter – die Konstante existiert nur noch im neuen Modul als Default-Fallback.
+
+### Frontend-UI
+
+In `SourcesTab.tsx` (Tree-Node-Render), zwischen Brain-Icon (🧠) und Chat-Icon (💬):
+```typescript
+
+```
+
+Settings-Modal (`DataSourceSettingsModal.tsx`):
+- **Sektion "Connection"** (oben, mit Connection-Label und Authority-Icon):
+ - Toggle `knowledgeIngestionEnabled` (Master-Switch). Hinweis: wirkt auf alle DataSources dieser Connection.
+ - Optional, wenn relevant: `mailContentDepth`, `mailIndexAttachments`, `filesIndexBinaries`, `clickupScope`, `clickupIndexAttachments`, `maxAgeDays` (aus `knowledgePreferences`).
+ - PATCH → `/api/connections/{id}/knowledge-consent` bzw. `/api/connections/{id}/knowledge-preferences`.
+- **Sektion "RAG-Limits"** (nur wenn DataSource den Walker-Typ unterstützt):
+ - Felder `maxBytes`, `maxFileSize`, `maxItems`, `maxDepth` mit lesbaren Units (MB, Anzahl).
+ - PATCH → `/api/datasources/{id}/settings`.
+- **Sektion "Kostenschätzung"** (read-only):
+ - "Indikative Kosten: ~X USD pro Voll-Sync" (mit Basis-Tooltip).
+ - GET → `/api/datasources/{id}/cost-estimate` beim Modal-Öffnen.
+
+Auf der `RagInventoryPage`-Partial-Banner-Komponente:
+```typescript
+... Limit {l} erreicht. Weitere Dateien wurden NICHT indexiert. {' '}
+ openSettingsModalForConn(conn)}>Limit anpassen oder
+ DataSource enger eingrenzen, dann erneut starten.
+```
+
+## Erfolgskriterien
+
+- Auf einer SharePoint-DataSource mit > 200 MB kann der Owner über das ⚙️-Icon `maxBytes` auf z. B. 1 GB anheben, einen Re-Index starten und sieht im Inventory > 200 MB indexierte Bytes ohne `stoppedAtLimit`-Banner.
+- Auf einer FeatureDataSource ist das gleiche Settings-Modal verfügbar und speichert sauber in `FeatureDataSource.settings`.
+- Wenn `settings.ragLimits` leer ist, ist das Verhalten der Walker **bitidentisch** zur Vor-Plan-Version (keine Regression).
+- Der Connection-Master-Switch im Modal und der Toggle auf der Connections-Page und auf der RagInventoryPage zeigen immer denselben Wert (alle drei rufen `/knowledge-consent`).
+- Audit-Log enthält pro Settings-Change einen `PERMISSION`-Eintrag mit `dataSourceId`, `userId`, `mandateId`, `oldSettings`, `newSettings`.
+- Keine zusätzlichen Icons in der UDB ausser dem einen ⚙️.
+- Kostenschätzung wird als "indikativ, nicht verbindlich" gekennzeichnet und zeigt die zugrunde liegenden Annahmen.
+
+## Schritte
+
+- [ ] S1 — Datenmodell: `settings: JSON` an `DataSource` und `FeatureDataSource` ergänzen (`gateway/modules/datamodels/datamodelDataSource.py`, `datamodelFeatureDataSource.py`); SQL-Migration in `gateway/scripts/script_db_migrate_datasource_settings.py`.
+- [ ] S2 — Zentralisierte Defaults: `gateway/modules/serviceCenter/services/serviceKnowledge/_ragLimits.py` neu mit `RAG_LIMITS_DEFAULT`, `getRagLimits()`, `lazyFillRagLimits()`.
+- [ ] S3 — Walker-Refactor: `subConnectorSyncSharepoint.py`, `subConnectorSyncKdrive.py`, `subConnectorSyncGdrive.py`, `subConnectorSyncClickup.py` lesen Limits via `getRagLimits()` und schreiben Defaults via `lazyFillRagLimits()` zurück.
+- [ ] S4 — Backend-Endpunkte: `PATCH /api/datasources/{id}/settings`, `PATCH /api/feature-data-sources/{id}/settings`, `GET /api/datasources/{id}/cost-estimate` in `routeDataSources.py` (und `routeFeatureDataSources.py`, falls noch nicht da). RBAC: Owner für DataSource, Owner + `workspace-admin` für FeatureDataSource.
+- [ ] S5 — Frontend ⚙️-Button in `SourcesTab.tsx` (Tree-Row, zwischen 🧠 und 💬), Opacity-Logik: voll sichtbar, wenn `settings` befüllt sind, sonst gedimmt.
+- [ ] S6 — Frontend `DataSourceSettingsModal.tsx`: drei Sektionen (Connection / RAG-Limits / Kostenschätzung), per-Feld-Validation, Speichern via API.
+- [ ] S7 — `RagInventoryPage.tsx` Partial-Banner: Link "Limit anpassen" öffnet Modal für die betroffene DataSource (Heuristik: DS, die `stoppedAtLimit` ausgelöst hat – bei mehreren: die mit den meisten verarbeiteten Bytes).
+- [ ] S8 — Audit-Logging in den neuen Settings-Endpunkten.
+- [ ] S9 — Cost-Estimate-Engine: `gateway/modules/serviceCenter/services/serviceKnowledge/_costEstimate.py` mit Heuristik (Items × Tokens × Embedding-Preis) und Basis-Annahmen-Output.
+- [ ] S10 — Tests: Unit-Tests für `getRagLimits()` / `lazyFillRagLimits()`, API-Tests für PATCH-Endpunkte (RBAC), Cost-Estimate-Unit-Test, Frontend-Smoke-Test (Modal öffnet, speichert, refetch, Cost-Anzeige).
+- [ ] S11 — Doku: `wiki/b-reference/platform/rag-pipeline.md` Abschnitt "Limits & Settings" ergänzen, plus Eintrag in `wiki/TOPICS.md`. CHANGELOG-Zeile.
+
+## Offene Fragen / Decisions
+
+- **F1 – Override-Logik?** → **Entschieden: KEINE Override-Schichten.** Die DataSource speichert ihre eigenen Werte direkt; Walker liest sie 1:1. Lazy-Fill mit zentralen Defaults beim ersten Sync, damit das UI immer einen sinnvollen Startwert zeigt. Nachvollziehbar, debuggbar, keine versteckten Vererbungen.
+- **F2 – RBAC?** → **Entschieden:** Owner für UserConnection-DataSources, Owner oder `workspace-admin` für FeatureDataSource (analog Scope-/Neutralize-Toggle).
+- **F3 – Hard-Cap pro Mandat?** → **Entschieden: NEIN.** Stattdessen indikative Realtime-Kostenschätzung im Modal. User/Admin trägt Verantwortung.
+- **F4 – Mail/ClickUp-Preferences im Modal?** → **Entschieden: JA.** Connection-weite Preferences werden in der Sektion "Connection" des Modals editiert – das macht das Modal zur einzigen Stelle für alle relevanten Settings, ohne Seitenwechsel.
diff --git a/c-work/4-done/2026-05-udb-generic-tree-refactor.md b/c-work/4-done/2026-05-udb-generic-tree-refactor.md
new file mode 100644
index 0000000..b705480
--- /dev/null
+++ b/c-work/4-done/2026-05-udb-generic-tree-refactor.md
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+# UDB Generic Tree Refactor (Backend authoritativ, FE pure Renderer)
+
+## Kontext
+
+Die `SourcesTab.tsx` des Unified Data Bars trug ein wachsendes Konsistenzproblem: Toggles auf Parent-Nodes hatten unter bestimmten Bedingungen keinen UI-Effekt. Wiederholte Bugfix-Iterationen am gleichen Datum (siehe `2026-05-udb-cascade-inherit.md`) haben drei Symptome verschoben statt geheilt, weil im Frontend parallele Logik (`_resolveFdsFlags`, optimistic Updates, `_findCoveringDs`) den vom Backend gelieferten Werten widerspricht.
+
+User-Direktive: Das Backend ist die einzige Quelle der Wahrheit fuer die effektiven Flag-Werte aller sichtbaren Tree-Nodes. Das Frontend rendert nur. Keine Vererbung im Frontend. Keine optimistischen Updates.
+
+## Ziele
+
+1. Ein einziger Endpoint liefert auf Anfrage Children-Listen pro Parent-Key, inklusive aller drei effektiven Flag-Werte (`neutralize`, `scope`, `ragIndexEnabled`) als `boolean | "mixed"` bzw. `string | "mixed"`.
+2. Das Frontend halt nur einen `Map` und ein `Set`. Re-Render = Re-Fetch.
+3. Toggle-Flow strikt: PATCH -> Refetch -> Render. Pro Klick wird der entsprechende Flag-Button als Spinner gezeigt, bis die neuen Daten gerendert sind.
+4. Ein einziges, von allen anderen Symbolen klar unterscheidbares `mixed`-Symbol gilt fuer alle drei Flags.
+5. RAG-Indexierung ist auf der gleichen Stufenmechanik fuer FDS verfuegbar wie fuer Personal-DataSources.
+
+## Architektur
+
+### Tree-Endpoint
+
+```
+POST /api/workspace/{instanceId}/tree/children
+Body: { "parents": [null, "conn|", "feat|||", ...] }
+Response: { "nodesByParent": { "__root__": [...], "conn|": [...], ... } }
+```
+
+`null` markiert die Top-Level-Ebene. Pro Parent-Key kommt eine Liste von `TreeNode`-Dicts zurueck.
+
+`TreeNode` ist die einzige Schema-Struktur fuer alle Kinds:
+
+```
+key, kind, parentKey, label, icon, hasChildren,
+dataSourceId, modelType,
+effectiveNeutralize, effectiveScope, effectiveRagIndexEnabled,
+supportsRag, canBeAdded,
++ kind-spezifische Carrier (authority, connectionId, service, sourceType, path,
+ featureInstanceId, featureCode, mandateId, tableName, objectKey)
+```
+
+Mit `kind in {connection, service, folder, file, mandateGroup, featureNode, fdsTable, fdsRecord}`. Der `fdsRecord`-Kind ist im Schema vorgesehen, aber im aktuellen Builder nicht aktiviert (Records werden bewusst nicht expandierbar gerendert, siehe "Bewusste Reduzierung" unten).
+
+### Key-Format
+
+Stable, parsebar, kollisionsfrei. Pipe-Separator:
+
+| Pattern | Bedeutung |
+|---|---|
+| `conn\|` | UserConnection-Root |
+| `svc\|\|` | Service unter Connection (sharepoint, outlook, ...) |
+| `ds\|\|\|` | Folder oder File innerhalb eines Services |
+| `mgrp\|` | Mandanten-Gruppen-Kopfknoten |
+| `feat\|\|\|` | Feature-Instanz (Workspace-Wildcard `*`) |
+| `fdstbl\|\|` | Feature-Datentabelle |
+| `fdsrec\|\|\|` | (reserviert, derzeit nicht emittiert) |
+
+### Builder
+
+`gateway/modules/serviceCenter/services/serviceKnowledge/_buildTree.py` orchestriert pro Request:
+- Vor-Laden aller DataSource-Records des Users (`recordFilter={"userId": userId}`) und aller FeatureDataSource-Records des Workspaces (`recordFilter={"workspaceInstanceId": instanceId}`) einmal.
+- Dispatch pro Parent-Kind:
+ - `null` -> aktive Connections + zugaengliche Mandanten-Gruppen
+ - `conn|*` -> verfuegbare Services (`provider.getAvailableServices()`)
+ - `svc|*` / `ds|*` -> `adapter.browse(path)` via ConnectorResolver
+ - `mgrp|*` -> per-Mandate Feature-Instanzen mit `featureCode in featuresWithDataObjects`
+ - `feat|*` -> `catalog.getDataObjects()` gefiltert via RBAC
+- Pro Node werden die drei `effective*`-Werte ueber `resolveEffectiveForPath` / `resolveEffectiveForFds` (mode=`aggregate`) berechnet. Diese Helpers funktionieren auch fuer Coordinates ohne eigenen DB-Record (virtueller Datensatz mit `null`-Flags).
+- Sourcetype-Mapping (`sharepoint -> sharepointFolder`, `onedrive -> onedriveFolder`, ...) lebt nun ausschliesslich im Builder, nicht mehr im Frontend.
+
+### Frontend (`SourcesTab.tsx`)
+
+`~530` Zeilen, ersetzt die alte `~2500`-Zeilen-Version komplett.
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant FE as SourcesTab
+ participant BE as Backend
+ User->>FE: Klick auf Flag-Button (Knoten X, Flag F)
+ FE->>FE: pendingToggles.add(X|F) -> Spinner sichtbar
+ alt Knoten hat keinen DB-Record
+ FE->>BE: POST /datasources oder /feature-datasources
+ BE-->>FE: { id }
+ end
+ FE->>BE: PATCH /api/datasources/{id}/{F} Body: {F: !effective}
+ BE-->>FE: 200 OK
+ FE->>BE: POST /tree/children Body: {parents: [null, ...expandedKeys]}
+ BE-->>FE: nodesByParent (alle effective* aktualisiert)
+ FE->>FE: setChildrenByParent + pendingToggles.delete -> Re-Render
+```
+
+State (im Component):
+- `childrenByParent: Map`
+- `expandedKeys: Set`
+- `pendingToggles: Set` (Key-Form `${nodeKey}|${flag}`)
+
+Re-fetch wird ueber einen Signatur-Effect getriggert, sobald `expandedKeys` aendert.
+
+Generischer `_FlagButton` rendert:
+- Spinner waehrend `pending`
+- Mixed-Symbol (`U+25E9`, "square with diagonal lines") wenn `effective* === "mixed"` (gilt einheitlich fuer alle 3 Flags)
+- Sonst Flag-spezifisches Icon (Lock fuer neutralize, Brain fuer RAG, Personen/Building/Globus fuer Scope)
+
+### FDS-RAG-Inheritance
+
+- `_INHERITABLE_FDS_FLAGS` enthaelt nun `ragIndexEnabled`.
+- `FeatureDataSource.ragIndexEnabled: Optional[bool]` Field (Default `None` = inherit; Auto-Migration via `ALTER TABLE ADD COLUMN`).
+- `PATCH /api/datasources/{id}/rag-index` akzeptiert sowohl DataSource- als auch FeatureDataSource-IDs (via `_findSourceRecord`). Bootstrap-Job und Chunk-Purge bleiben DataSource-only (FDS-RAG ist Feature-Pipeline-getrieben, Flag genuegt).
+- `GET /api/workspace/{instanceId}/feature-datasources` liefert `effectiveRagIndexEnabled`.
+- `resolveEffectiveForFds` liefert den dritten Wert konsistent mit dem DS-Pendant.
+
+## Bewusste Reduzierung
+
+FDS-Records (z.B. "Mandant Mueller" als einzelner Datensatz innerhalb der `Mandanten`-Tabelle) sind im neuen Tree nicht expandierbar (`hasChildren: False` fuer `fdsTable`). Begruendung: User-Entscheidung 2026-05-18, Tabellen-Ebene reicht. Das `FeatureDataSource.recordFilter`-Feld bleibt im Datenmodell und in `POST /feature-datasources` fuer API-Kompatibilitaet erhalten, wird aber von der UI nicht mehr gesetzt.
+
+## Entfernter Code (Backend)
+
+Vollstaendig geloescht (keine Aufrufer mehr im Frontend, kein deprecated-Zustand):
+- `POST /api/workspace/{instanceId}/datasources/resolve-flags` und Models `_ResolveFlagsRequest`, `_ResolveFlagItem`, `_ResolveFlagFdsItem`
+- `GET /api/workspace/{instanceId}/connections`
+- `GET /api/workspace/{instanceId}/connections/{connectionId}/services`
+- `GET /api/workspace/{instanceId}/connections/{connectionId}/browse`
+- `GET /api/workspace/{instanceId}/feature-connections`
+- `GET /api/workspace/{instanceId}/feature-connections/{fiId}/tables`
+- `GET /api/workspace/{instanceId}/feature-connections/{fiId}/parent-objects/{tableName}`
+
+Erhalten (haben weitere Konsumenten ausserhalb UDB):
+- `GET /api/workspace/{instanceId}/datasources` -> useWorkspace.ts, WorkspacePage.tsx, Commcoach*, GraphicalEditorPage.tsx
+- `GET /api/workspace/{instanceId}/feature-datasources` -> selbe Konsumenten
+- `POST .../datasources` und `POST .../feature-datasources` -> neuer SourcesTab nutzt sie fuer `_ensureRecord`
+
+## Entfernter Code (Frontend)
+
+- Komplettes altes `SourcesTab.tsx` (~2500 Zeilen): `_resolveFdsFlags`, `resolvedFdsFlags`, `_findCoveringDs`, `_readFdsFlags`, alle optimistic `setDataSources(prev.map(...))` und `setFeatureDataSources(prev.map(...))`, prop-drilling `inheritedScope`/`inheritedNeutralize`, mehrere Sub-Komponenten (`_FeatureTableRow`, `_FeatureActionContext`, etc.)
+- Totes CSS-Modul `SourcesTab.module.css`
+
+## Tests
+
+Neu:
+- `gateway/tests/unit/services/test_buildTree.py` (10 Tests): Key-Encoding/Decoding, `_effectiveTripletDs/Fds` Defaults+Inheritance, Record-Lookup (DS path-normalisation; FDS record-filter equality), `getChildrenForParents`-Smoke (unknown parent -> `[]`, leeres Top-Level).
+- 5 neue Tests in `test_inheritFlags.py::TestResolveEffectiveForFds`: RAG inherits when only neutralize overridden; RAG aggregate-mixed; `_INHERITABLE_FDS_FLAGS` contains all 3 keys.
+- `TestCascadeResetFdsRag::test_cascade_resets_rag_on_descendants`.
+
+Resultat: `tests/unit/services/test_inheritFlags.py` + `test_buildTree.py` = 82/82 gruen.
+
+Frontend: `npx tsc --noEmit --skipLibCheck` clean.
+
+## Verifizierte Datei-Pfade
+
+- Backend: `gateway/modules/serviceCenter/services/serviceKnowledge/_buildTree.py`, `_inheritFlags.py`, `gateway/modules/features/workspace/routeFeatureWorkspace.py`, `gateway/modules/routes/routeDataSources.py`, `gateway/modules/datamodels/datamodelFeatureDataSource.py`
+- Frontend: `frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx`
+- Tests: `gateway/tests/unit/services/test_buildTree.py`, `test_inheritFlags.py`
diff --git a/c-work/_CHANGELOG.md b/c-work/_CHANGELOG.md
index cd96373..a54d6eb 100644
--- a/c-work/_CHANGELOG.md
+++ b/c-work/_CHANGELOG.md
@@ -12,8 +12,21 @@ type: `feat` `fix` `refactor` `docs` `test` `chore` `build` · scope: `gateway
Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
+## 2026-05-19
+
+- 2026-05-19 | fix | frontend-nyla+gateway | **RAG-Inventar Mandate-Scope 403 + SQL-Fehler** — Zwei Bugs: (1) Frontend sendete `mandateId` als Query-Parameter statt `X-Mandate-Id`-Header → `context.mandateId=None` → 403. Fix: Header wird gesetzt. (2) Backend `routeRagInventory._getInventoryMandate` filterte `UserConnection` mit `recordFilter={"mandateId": ...}`, aber `UserConnection` hat kein `mandateId`-Feld → SQL-Error. Fix: Mandate-Members via `UserMandate`-Junction holen, dann deren `getUserConnections(userId)` aggregieren.
+- 2026-05-19 | refactor | gateway+frontend-nyla | **UDB Toggle Refresh: Visible-IDs Pattern** -- After a toggle, FE sends all visible node IDs in one POST request; backend returns correct attribute values (incl. mixed) for exactly those IDs. No full-tree reload, no optimistic updates. New endpoints: `POST /api/files/attributes` (FilesTab) and `POST /api/workspace/{id}/tree/attributes` (SourcesTab). `TreeNodeProvider.refreshAttributes(ids)` added to interface. Collapse now removes children from FE state (always fresh on re-expand). Removed `_enrichFoldersWithMixed` and `_refetchAllExpanded`.
+
## 2026-05-18
+- 2026-05-18 | fix+feat | gateway+frontend-nyla | **UDB Sources Recovery -- Zweite Smoke-Test-Runde (H1-H10)** (`c-work/3-validate/2026-05-udb-sources-recovery.md`). (H1) Logik-Audit aller drei Flags zusammengefasst: `'mixed'` ist Backend-Aggregate-Anzeige, niemals persistiert; FE-Handler mappen `'mixed'` immer auf konkreten Wert (`'personal'`/`false`); Cascade-PATCH setzt alle Nachkommen auf `NULL` und dann den Master, deshalb kann aggregate nach einem User-Toggle nicht `'mixed'` bleiben. (H2/H8) Initial-Render-Bug: sequentieller `for ... await` im Auto-Expand-Effekt loeste Cancellation-Race aus -- erstes `setNodes` triggerte cleanup, weitere defaultExpanded-Knoten blieben "expanded ohne Children". Fix: `Promise.all(...)` parallel + atomares Single-`setNodes`. Zusaetzlich `autoExpandedRef.current.clear()` in `_loadRoot` (StrictMode-Doppelmount + manuelle Refresh tauglich). (H3) `PUT /api/files/{id}` 404: Route hatte keinen `RequestContext`, `interfaceDbManagement.getInterface(currentUser)` ohne mandate/featureInstance scope -> RBAC-Filter excludiert File. Fix: Context-Dep + scope-pass-through bei `update_file` und `delete_file`; `move_folder` akzeptiert sowohl `parentId` als auch `targetParentId` aus dem Body. (H4) Neuer Folder erschien initial auf Legacy-Top-Level: `FolderFileProvider.createChild(parentId=null, ...)` setzt jetzt `parentId = _SYNTH_ROOT_ID('own')` damit der neue Folder unter `/` rendert. (H5) `+`-Button pro Folder im FormGeneratorTree: neue `onCreateChild?: (parentId: string) => void`-Prop in TreeNodeRow + verdrahtetem `_createFolderAt(parentId)` Handler in der Hauptkomponente; sichtbar im Hover-Action-Slot fuer alle Folders mit `provider.createChild` && `provider.canCreate(id)`. (H6) FilesTab neutralize partly broken: Diagnose plausibel mit H2-Fix erledigt (Optimistic-Update verfehlte Knoten, deren Children durch das Cancellation-Race nicht im FE-State waren). (H7) Hardcoded `[Persoenliche Quellen]`: Source-String hatte `oe`-Encoding, `t()` returned key fuer DE -> sichtbar mit fake-Umlaut. Fix: `resolveTextSafe("Persoenliche Quellen")` -> `resolveTextSafe("Persönliche Quellen")` (echter Umlaut). (H9) FDS-Neutralize ging nicht: `POST /api/workspace/{instanceId}/feature-datasources` 403te Cross-Mandate-Erstellung (Workspace mandate A, Feature mandate B beides user-zugaenglich), `_ensureRecord` schluckte den Fehler still. Fix: Cross-Mandate-Block entfernt, statt dessen `getFeatureAccess(userId, body.featureInstanceId)`-Validierung; `mandateId` der neuen FDS = `wsMandateId` (= Workspace-Tenancy, konsistent mit Tree-Filter). (H10 = G5) Persistenter Expand-State umgesetzt: `WorkspaceUserSettings.uiTreeExpansion: Dict[str, List[str]]` Backend-Field; neue Routen `GET/PUT /api/workspace/{instanceId}/ui-tree-expansion/{scope}`; FE-Hook `useTreeExpansion(instanceId, scope)` mit 600 ms Debounce-PUT; `FormGeneratorTree` Props `expandedIds?: string[] | null` + `onExpandedIdsChange?: (ids) => void` (controlled mode); `SourcesTab` (scope `'sources'`), `FilesTab` (scopes `'filesOwn'` + `'filesShared'`) verdrahtet. Bei `null` (kein Record) Default-Verhalten + erster Toggle erstellt den Settings-Record; bei Array gewinnt persistierte Liste ueber Backend-`defaultExpanded`-Hints. Tests: Backend `services+routes` 107/107 gruen, Frontend UDB+FormGen 69/69 gruen.
+- 2026-05-18 | fix+feat | gateway+frontend-nyla | **UDB Sources Recovery -- Smoke-Test-Followups (G1-G5)** (`c-work/3-validate/2026-05-udb-sources-recovery.md`). (G1) `tsconfig.json` referenziert jetzt auch `tsconfig.test.json`; Testfile bekam expliziten `import React`, `afterEach`, `@testing-library/jest-dom/vitest`. 139 -> 0 Lints; Tests 50/50 weiterhin gruen. (G2) Off-State der `neutralize`/`ragIndexEnabled`-Buttons: `filter: grayscale(1); opacity: .45` im OFF-Zustand -- offenes Schloss + greyed-Brain klar als "deaktiviert" lesbar. (G3a) Bug `_browseChildren`: Childs erbten `parentKey=ds|...|/` statt des aufgerufenen `svc|...`; Children erschienen daher nie im FE-Tree. Fix: `parentKey` als optionaler Parameter durchgereicht; Dispatcher uebergibt den asked-for-Key. (G3b) Per-Field-Neutralize fuer Feature-Tabellen: `fdsTable.hasChildren=True` bei vorhandener `meta.fields`-Liste; neuer Knoten-Typ `fdsField` (Dispatcher `fdstbl|fi|table` -> `_featureTableFields`). Effektiver Field-Neutralize = `parent.neutralize OR field IN parent.neutralizeFields`. Toggle: `UdbSourcesProvider.patchNeutralize` splittet die Batch nach Kind und ruft fuer fdsField `PATCH /api/datasources/{id}/neutralize-fields` mit der mutierten Liste; `neutralizeFields` ist im Tree-Payload mitgesendet -- kein Extra-GET. Scope/RAG auf Feldebene bewusst nicht editierbar. +3 neue Backend-Tests in `TestFeatureTableFields`. (G4) FilesTab synthetischer "/"-Root pro Ownership: Provider mappt `parentId=null` auf `[__filesRoot:]` (`defaultExpanded=true`); reale Top-Level-Items werden Children. `moveNodes` mit Synth-Root-Ziel re-mapt auf `parentId/folderId=null` (= Drop auf "/"); `patchScope`/`patchNeutralize` mit Synth-Root-Id materialisieren ueber API alle Top-Level-Folders+Files und setzen pro Folder `cascadeChildren=true` -- globaler Toggle ohne Backend-Aenderung. Tests: Backend 19/19 gruen, Frontend UDB+FormGen 69/69 gruen.
+- 2026-05-18 | refactor | gateway+frontend-nyla | **UDB Sources Recovery -- Folge-UX-Iteration** (`c-work/3-validate/2026-05-udb-sources-recovery.md`). Auf das User-Feedback nach Phase 1-3: (a) **Visual cascade fuer `neutralize` und `ragIndexEnabled`** wieder klar erkennbar -- vorher *gleiches Symbol* fuer on/off (nur Opacity), Cascade unsichtbar. Distinct emojis: `_NEUTRALIZE_ON_EMOJI=closed lock` / `_NEUTRALIZE_OFF_EMOJI=open lock`, `_RAG_ON_EMOJI=brain` / `_RAG_OFF_EMOJI=thought-bubble`. Backend-Cascade war korrekt (parametrisch in `cascadeResetDescendants(rec, flag)`); rein UI-Issue. (b) **Top-Level-Layout flach**: synthetische Wrapper `srcRoot` + `mandateRoot` entfernt. `_topLevel` emittiert direkt `[personalRoot, mgrp|m1, mgrp|m2, ...]`. Mandate-Groups haben nun `parentKey=None`. (c) **`TreeNode.defaultExpanded?: boolean`** als generisches Tree-Feature (one-shot pro Node-Id ueber `autoExpandedRef`); BE markiert `personalRoot` und alle Mandate-Groups mit `defaultExpanded=True` -- UI oeffnet bis zur Datenquellen-Ebene ohne User-Klick. (d) **Icon-Reihenfolge** in `FormGeneratorTree` (rechtsbuendig, von rechts nach links): neutralize, scope, sendToChat, rag, settings. Settings (`extraActions`) jetzt linksbuendig. (e) **Settings-Icon nur auf Daten-Quellen-Root** (`kind in {connection, featureNode}`), aber dort *immer* sichtbar, auch ohne `dataSourceId`; Klick triggert lazy `_ensureRecord` -> `onOpenSettings`. (f) **`SourcesTab`** setzt `title={t('Datenquellen')}` als Section-Header. Tests: Backend `test_buildTree.py` 16/16 + `test_inheritFlags.py` 72/72 -> 88/88 gruen. Frontend `FormGeneratorTree.test.tsx` 50/50 (+`defaultExpanded`-Tests) + `UdbSourcesProvider.test.ts` 16/16 (+settings-on-root + defaultExpanded-passthrough). TypeScript clean.
+- 2026-05-18 | refactor | gateway+frontend-nyla | **UDB Sources Recovery: SourcesTab nutzt FormGeneratorTree** (`c-work/3-validate/2026-05-udb-sources-recovery.md`). Behebt das Folge-Problem aus dem Generic-Tree-Refactor vom selben Tag, dass der Sources-Tab nur "Lade Datenquellen..." zeigte, weil der ad-hoc `_TreeNodeView`-Renderer in `SourcesTab.tsx` an einem State-Race (StrictMode-Doppelmount × `mountedRef`-Frueh-Abort × paralleler `expansionSignature`-useEffect, vier sichtbare POSTs) erstickte. Diagnose via temporaerem `_buildTree.getChildrenForParents`-INFO-Log: Backend liefert sauber `counts={'__root__': 6}` -- Bug war ausschliesslich auf der ad-hoc-Renderer-Seite. **Backend** (`_buildTree.py`): synthetische Container `srcRoot` (Top-Level), `personalRoot`, `mandateRoot` mit neuem `_syntheticNode` Helper und `displayOrder`-Sort-Hint; `_topLevel` schrumpft auf `[srcRoot]`, `_srcRootChildren` emittiert `[personalRoot, mandateRoot]`, `_personalRootChildren` und `_mandateRootChildren` enthalten die alte Connections-/Mandate-Logik mit korrigierten `parentKey`. Dispatcher in `getChildrenForParents` matcht `parentKey == _KEY_*_ROOT` vor dem Decode-Pfad. +7 neue `TestSyntheticRoots` Tests, Service-Suite 89/89 gruen. **FormGeneratorTree** (generisch erweitert, kein UDB-Vokabular): `TreeNode.ragIndexEnabled?: boolean | 'mixed'`, `TreeNode.displayOrder?: number`, `TreeNodeProvider.patchRagIndex?` + `canPatchRagIndex?`, `FormGeneratorTreeProps.refreshAfterAction?: boolean`. Sort-Comparator setzt `displayOrder` vor folder-first. Dritter Flag-Button (`_RAG_EMOJI`, `_ACTION_RAG`) rendert nur wenn `ragIndexEnabled !== undefined`. `_runAction` ruft `_refetchAllExpanded` automatisch auf wenn `refreshAfterAction=true`; optimistic-Update-Pfad fuer FilesTab unveraendert. +10 neue Tests, Tree-Suite 51/51 gruen. **Frontend SourcesTab**: ad-hoc-Renderer mit `_TreeNodeView`, `_FlagButton`, eigenem `pendingToggles`-State, eigenem `_fetchVisible`-Pfad komplett geloescht. Neuer `UdbSourcesProvider.tsx` mappt Backend-`UdbBackendNode` auf generischen `TreeNode` (Synthese-Container ohne Flag-Felder); haelt internen `nodeCache: Map` fuer Patch-Pfade; `_ensureRecord` POSTet `/datasources` oder `/feature-datasources` je nach `kind`; danach PATCH `/api/datasources/{id}/{scope|neutralize|rag-index}`. Neue `SourcesTab.tsx` ist 75 LOC duenner Wrapper: `useMemo` Provider + `` + Settings-Modal-Anbindung ueber `extraActions`. +14 neue Provider-Tests, Tree+Provider-Suite 65/65 gruen.
+- 2026-05-18 | fix | gateway | PostgreSQL `recordCreate`/`_save_record`: Zeichenketten-Parameter mit eingebetteten NUL-Bytes (`\\x00`) werden vor dem Bind gestriped — behebt `ingestion.failed` / `ContentChunk`-Insert (psycopg2: „A string literal cannot contain NUL“), z. B. bei indexierten `.sql`/Dump-Dateien.
+- 2026-05-18 | refactor | gateway+frontend-nyla | **UDB Generic Tree Refactor: Backend autoritativ, FE pure Renderer, RAG fuer FDS** (`c-work/4-done/2026-05-udb-generic-tree-refactor.md`). Schliesst die ueber mehrere Iterationen verschleppte UI-Inkonsistenz, dass Toggles unter bestimmten Bedingungen keinen UI-Effekt hatten. Root-Cause: parallele Logik im Frontend (`_resolveFdsFlags`, optimistic Updates, `_findCoveringDs`) widersprach den vom Backend gelieferten Werten. **Architektur**: Ein einziger Endpoint `POST /api/workspace/{instanceId}/tree/children` mit Body `{parents: [null, ...expandedKeys]}` liefert `nodesByParent` als flachen Map mit allen drei `effective*`-Werten (`neutralize`, `scope`, `ragIndexEnabled` als `boolean|'mixed'` bzw. `string|'mixed'`) pre-computed auf der `mode='aggregate'`-Ebene. Neuer Orchestrator `serviceKnowledge/_buildTree.py` mit stabilem Key-Format (`conn|`, `svc||`, `ds|||`, `mgrp|`, `feat|||`, `fdstbl||
`) — preloadet DS+FDS einmal pro Request und dispatched pro Parent-Kind an Connector/Catalog/RBAC. Sourcetype-Mapping (`sharepoint -> sharepointFolder` etc.) ist nun Backend-autoritativ. **FE-Refactor**: `SourcesTab.tsx` von ~2500 auf ~530 Zeilen reduziert. State nur noch `childrenByParent: Map`, `expandedKeys: Set`, `pendingToggles: Set`. Toggle-Flow strikt PATCH -> Refetch -> Render mit Spinner ueber dem Flag-Button bis API-Response zurueck ist. Generischer `_TreeNodeView` rekursiv fuer ALLE Kinds, generischer `_FlagButton` mit einheitlichem Mixed-Symbol (`U+25E9`, "square with diagonal lines") fuer alle drei Flags. **RAG fuer FDS**: `_INHERITABLE_FDS_FLAGS` enthaelt nun `ragIndexEnabled`; `FeatureDataSource.ragIndexEnabled: Optional[bool]` Field (Auto-Migration via `ALTER TABLE ADD COLUMN`); `PATCH /api/datasources/{id}/rag-index` akzeptiert via `_findSourceRecord` sowohl DS als auch FDS (Bootstrap-Job + Chunk-Purge bleiben DS-only); `GET /feature-datasources` liefert `effectiveRagIndexEnabled`; `resolveEffectiveForFds` um den dritten Wert ergaenzt. **Geloescht** (keine Konsumenten mehr): `POST /datasources/resolve-flags` + 3 Models, `GET /connections`, `GET /connections/{id}/services`, `GET /connections/{id}/browse`, `GET /feature-connections`, `GET /feature-connections/{fiId}/tables`, `GET /feature-connections/{fiId}/parent-objects/{tableName}`, totes `SourcesTab.module.css`. **Bewusste Reduzierung** (User-Entscheidung): FDS-Records sind nicht mehr expandierbar (`hasChildren: false` fuer `fdsTable`); Tabellen-Ebene reicht. `FeatureDataSource.recordFilter` bleibt fuer API-Kompat erhalten. **Tests**: neu `test_buildTree.py` (10 gruen — Key-Coding, Effective-Triplets, Record-Lookup, Orchestrator-Smoke), +5 neue in `test_inheritFlags.py::TestResolveEffectiveForFds` (RAG inherits, RAG aggregate-mixed, `_INHERITABLE_FDS_FLAGS` enthaelt RAG) +1 in `TestCascadeResetFdsRag`. Resultat: 82/82 gruen; Frontend `npx tsc --noEmit` clean.
+- 2026-05-18 | feat | gateway+frontend-nyla | **UDB Toggle-Semantik v2: 'mixed'-Aggregation + bottom-up cascade + Backend-autoritative effective values** — Grundlegendes Redesign der Toggle-Semantik (neutralize, scope, ragIndexEnabled) für DataSource und FeatureDataSource. (1) Backend: `getEffectiveFlag` und `getEffectiveFlagFds` mit `mode='walk'|'aggregate'` — walk gibt immer konkret, aggregate gibt 'mixed' wenn Subtree divergiert. (2) `cascadeResetDescendants`/`Fds` jetzt bottom-up (deepest-first) für Crash-Sicherheit, Rückgabe `List[str]` statt `int`. (3) GET /datasources und /feature-datasources liefern computed `effectiveNeutralize`, `effectiveScope`, `effectiveRagIndexEnabled` pro Item. (4) PATCH-Response enthält `resetDescendantIds` + `updatedAncestors` für optimistic UI. (5) Bug-Fixes B1-B6: routeDataConnections consent-re-enable RAG, routeRagInventory reindex+inventory, routeDataSources settings-RBAC, _dataSourceTools download neutralize, _featureSubAgentTools FDS neutralize cascade — alle auf `getEffectiveFlag(mode='walk')` umgestellt. (6) Frontend: Workarounds entfernt (`_effectiveFlag`, `_findAncestorDs`, `_AUTHORITY_SOURCE_TYPES`, prop-drilling `inheritedScope/Neutralize`), zentrale `_readDsFlags`/`_readFdsFlags` aus Backend-Werten, Toggle-Handler mit optimistic cascade via `resetDescendantIds`. (7) 'mixed'-Icon-Darstellung im UI.
+- 2026-05-18 | chore | gateway | RAG-Walker und `requestIngestion`: per-item Lognois reduziert — `subWalkerHelpers` (`walker.item.start`, `walker.download.start`, `walker.extract.start`) und `ingestion.skipped.duplicate` von INFO auf DEBUG, damit Daily-Resync / wiederholtes Durchlaufen nicht das App-Log flutet; Stuck-Triage via DEBUG auf `modules.serviceCenter.services.serviceKnowledge.subWalkerHelpers` bzw. `mainServiceKnowledge`.
- 2026-05-18 | fix | gateway+frontend-nyla | **UDB Cascade-Inherit Bugfix: Connection-Root Toggle hatte keinen UI-Effekt** — User-Report: "no change in UI, whatever icon I press". Log-Analyse zeigte: jeder Toggle macht POST `/datasources` (Backend gibt via Upsert die bereits existierende ID zurück) gefolgt von PATCH (Backend updated korrekt), aber UI bleibt unverändert. **Root-Cause 1**: Die ConnectionRoot-DataSource (`sourceType='msft'`/`'google'`/`'clickup'`/`'infomaniak'`/`'local'`, `path='/'`) wurde im Frontend von `_findDs` ignoriert (`if (node.type === 'connection') return undefined`) — Workaround aus dem Vortag, der eine andere Race-Condition verhindern sollte. Folge: `ds === undefined` für Connection-Nodes → `_addAsDataSource`-POST → Backend Upsert findet bestehenden DS via `connectionId+path`-Match und returnt dieselbe ID → PATCH erfolgreich → UI rendert aber weiter mit `ds=undefined` → opacity bleibt 0.35. **Fix 1**: `_findDs` matcht jetzt deterministisch via `sourceType`: für Connection-Nodes `node.authority` ('msft'/'google'/…), für Service-Nodes `_SERVICE_TO_SOURCE_TYPE[node.service]` ('sharepointFolder'/…). Zwei DS mit `connectionId+path='/'` aber unterschiedlichem `sourceType` (Connection-Root vs Service-Root) sind dadurch eindeutig unterscheidbar. **Root-Cause 2**: Cross-Authority-Vererbung fehlte komplett. User-Erwartung: Toggle auf Connection-Root muss alle Service-Children (SharePoint, OneDrive, Outlook unter derselben msft-Connection) visuell mitziehen. Backend `getEffectiveFlag` und `cascadeResetDescendants` filterten aber strikt auf `sourceType==parentSourceType`, also keine Vererbung über die Authority-Grenze hinweg. **Fix 2**: Neue Konstante `_AUTHORITY_SOURCE_TYPES = {'local','google','msft','clickup','infomaniak'}` in `_inheritFlags.py` und Mirror im Frontend. `_findAncestorChain` ergänzt um den ConnectionRoot als "äusseren" Ancestor (after all same-sourceType ancestors). `cascadeResetDescendants` cascadiert bei `parentIsConnectionRoot=True` über die GANZE Connection (cross-sourceType). Frontend `_findAncestorDs` analog. **Tests**: 23/23 grün (4 neue für Cross-Authority: `test_connection_root_inherits_cross_sourcetype`, `test_same_sourcetype_ancestor_wins_over_connection_root`, `test_connection_root_does_not_self_inherit`, `test_connection_root_cascades_cross_sourcetype`). Frontend `npm run build` + `tsc --noEmit` grün.
- 2026-05-18 | feat | gateway+frontend-nyla | **UDB Cascade-Inherit für DataSource-Flags (`neutralize`, `ragIndexEnabled`, `scope`)** — Plan: `c-work/1-plan/2026-05-udb-cascade-inherit.md`. Schliesst die Konsistenz-Lücke, dass Toggles an einem Parent nicht nach unten propagieren konnten, sobald ein Descendant einen eigenen DataSource-Record hatte. Beispiel-Bug: SharePoint=true → Folder1=false (explizit) → SharePoint=true (zweites Toggle) → Folder1 blieb auf false statt mit dem Parent zu folgen. **Architektur**: 3-wertige Felder mit `NULL = inherit`, expliziter `True/False` (oder `'personal'/'mandate'/'platform'/'global'` für scope). **Backend**: (1) Datenmodell `DataSource.{neutralize,ragIndexEnabled,scope}` und `FeatureDataSource.{neutralize,scope}` auf `Optional[...]` umgestellt; Migration `script_db_migrate_datasource_inherit.py` macht Spalten nullable (idempotent, additiv — bestehende Werte bleiben explizit, NEW records starten mit NULL). (2) Neuer zentraler Helper `serviceKnowledge/_inheritFlags.py` mit `getEffectiveFlag(rec, flag, allDs)` (path-traversal aufwärts, longest-prefix wins, defensiv `connectionId` UND `sourceType` als Filter), `cascadeResetDescendants(rootIf, parentRec, flag)` (setzt nur das EINE Flag der explizit-belegten Descendants auf NULL, andere Flags unangetastet) und `_isAncestorPath` (`/` ist Ancestor von allem; `/foo` NICHT von `/foobar` — `/`-Separator-Pflicht). (3) PATCH-Endpoints `routeDataSources.{_updateDataSourceScope,_updateDataSourceNeutralize,_updateDataSourceRagIndex}` akzeptieren `Optional[bool|str]` als Body-Wert: `null` → reset auf inherit (kein Cascade); `true/false` → explizit + Cascade-Reset aller Descendants. RAG-Endpoint zusätzlich: nur bei `True` Mini-Bootstrap-Job + `_ensureConnectionKnowledgeFlag`; nur bei `False` synchrone Chunk-Purge — `null` no-op. Audit-Log `rag_index_toggled` trägt `cascadedDescendants` Count. (4) Walker-Integration: `_loadRagEnabledDataSources` filtert auf **effective** ragIndexEnabled (via `getEffectiveFlag`) und schreibt resolved Werte für `neutralize/scope` direkt in das returned dict — Walker (Sharepoint/Outlook/Gdrive/Gmail/Clickup/Kdrive) bleiben unverändert und lesen weiter `ds.get("neutralize", False)`. Alter `subPolicyResolver.py` zu Backward-Compat-Shim auf `getEffectiveFlag` deprecated. **Frontend**: `UdbDataSource` Interface mit `boolean|null` und `string|null`; neue Helper `_findAncestorDs` + `_effectiveFlag` (path-traversal analog zu Backend); Toggle-Handler senden `!currentEffective` (= aktuell sichtbarer Wert wird invertiert), Backend macht den Cascade. Local State: optimistisch + `_fetchDataSources()` nach PATCH damit cascade-resettete Descendants aus DB neu geladen werden. Alte unbenutzte `_togglePersonalNeutralize/_togglePersonalRagIndex/_cyclePersonalScope` entfernt. **Tests**: neuer `test_inheritFlags.py` mit 19 grün — explicit-wins, ancestor-traversal, default-fallback, sourceType-Isolation, connectionId-Isolation, `/foo` ≠ Ancestor von `/foobar`, root-is-ancestor-of-everything, scope-string-default, cascade-touches-only-target-flag, cascade-skips-other-sourcetypes. Bestehende `test_knowledge_ingest_consumer.py` 8/8 grün. Frontend `npm run build` grün, TypeScript clean. **Pre-existing Test-Failures** (nicht durch diesen PR): `test_p1d_consent_prefs.py` (5) — Python-3.13 asyncio-Deprecation + Schema-Drift `ConnectionIngestionPrefs.neutralizeBeforeEmbed`; `test_bootstrap_sharepoint.py` + `test_bootstrap_gmail.py` (5) — rufen `bootstrapSharepoint/Gmail` ohne `dataSources=` auf, was seit dem 2026-04 Unified-Indexing-PR den Early-Return triggert.
@@ -325,4 +338,10 @@ Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
- 2026-05-12 | fix | service-teams-browser-bot | Auth bot PiP loop fix: in full Teams web app, chat toggle navigates to Chat section → PiP → periodic scan reopens → PiP again (endless loop). Fix: added isAuthMode flag to ChatProcedure; in auth mode: skip chat panel toggle entirely, skip periodic reopen, skip panel-open preflight for sendChatMessage. Chat send uses direct input lookup in the meeting view. Replaced _returnToMeetingIfPip() approach (reactive) with prevention (don't toggle)
- 2026-05-12 | fix | service-teams-browser-bot | Auth bot meeting minimized (PiP): chat button click hit Teams sidebar "Chats" navigation instead of in-meeting toggle → meeting minimized, all PeerConnections closed, video/audio broke. Fix: isDangerousNavButton filter excludes sidebar nav, tab-items, submenu triggers; aria-pressed fallback for panel detection in full Teams web app; _openMoreMenu scoped to calling toolbar
- 2026-05-11 | fix | service-teams-browser-bot | Anonymous bot stuck after lobby admission: _waitForMeetingAdmission now tracks lobby-to-meeting transition, waits patiently when lobby vanishes (= admitted), and reloads page as recovery; isInMeeting expanded with light-meetings + German selectors
+- 2026-05-18 | feat | gateway+frontend | UDB Resolve Endpoint: neuer POST /datasources/resolve-flags Bulk-Endpoint liefert effective-Werte (neutralize, scope, ragIndexEnabled) fuer beliebige Pfade auch ohne eigenen DB-Record; Frontend entfernt client-seitige Vererbungslogik (_findCoveringDs), nutzt ausschliesslich Backend-Daten via resolvedFlags Map
+- 2026-05-19 | fix | gateway | RAG Inventory /mandate: ImportError UserMandate behoben (war in datamodelUam statt datamodelMembership); refactored zu getUserMandatesByMandate(); RBAC membership-Check hinzugefuegt (verhindert Zugriff auf fremde Mandate via X-Mandate-Id)
+- 2026-05-19 | fix | gateway+frontend | RAG Inventory Mandate-Dropdown: neuer Endpunkt GET /api/rag/inventory/my-mandates liefert nur Mandate mit aktiver Mitgliedschaft; Frontend nutzt diesen statt /api/mandates/ (admin-only Route)
+- 2026-05-19 | feat | gateway+frontend | RAG Inventory Feature-Daten: featureInstances-Array mit Datei-/Chunk-Zaehler, Status-Breakdown (indexed/pending/failed), FeatureDataSource-Details mit ragIndexEnabled-Flag, RAG-Aktiv-Indikator; Frontend zeigt Sync-Status-Banner, Datenquellen pro Instanz, RAG-Hinweis wenn inaktiv; leere Instanzen werden ausgefiltert
+- 2026-05-19 | feat | gateway+frontend | Feature-Daten RAG-Sync: neuer Background-Job-Handler feature.bootstrap (subFeatureBootstrap.py) indexiert FeatureDataSource-Tabellendaten via FeatureDataProvider+requestIngestion; Handler-Registrierung in registerKnowledgeIngestionConsumer; neuer Endpoint POST /api/rag/inventory/reindex-feature/{workspaceInstanceId} mit FeatureAccess-Check; _buildFeatureInstanceInventory liefert runningJobs/lastSuccess/lastError; Frontend: Job-basierte Sync-Status-Banner + Reindex-Button fuer Feature-Instanzen (analog zu Connection-Sync)
+- 2026-05-19 | fix | gateway | FeatureDataProvider: alle DB-Zugriffe (getActualColumns, browseTable, aggregateTable, queryTable, _resolveInstanceColumn) von deprecated db.connection.cursor() auf db.borrowCursor() migriert (Pool-API-Kompatibilitaet nach Pooling-Refactoring)