From 8ee13275225d9efa4e48012c5fbb09f5eb0c0a12 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Wed, 27 May 2026 16:49:03 +0200
Subject: [PATCH] fixes udb
---
TOPICS.md | 6 +-
b-reference/platform/neutralization.md | 24 +-
b-reference/platform/rbac.md | 18 +-
b-reference/platform/unified-data-bar.md | 243 ++++++++++++++++++
.../2026-05-udb-polymorphic-refactor.md | 141 ++++++++++
.../2026-05-udb-toggle-spec-recovery.md | 85 ++++++
.../2026-05-udb-generic-tree-refactor.md | 14 +
c-work/_CHANGELOG.md | 65 +++--
d-guides/deployment/poweron-sec.kdbx | Bin 28958 -> 29310 bytes
9 files changed, 556 insertions(+), 40 deletions(-)
create mode 100644 b-reference/platform/unified-data-bar.md
create mode 100644 c-work/3-validate/2026-05-udb-polymorphic-refactor.md
create mode 100644 c-work/3-validate/2026-05-udb-toggle-spec-recovery.md
diff --git a/TOPICS.md b/TOPICS.md
index 40074b8..d5b6eef 100644
--- a/TOPICS.md
+++ b/TOPICS.md
@@ -1,5 +1,5 @@
-
+
# Themen-Index für AI-Kontext
@@ -35,7 +35,8 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
| Thema | Datei | Wann laden |
|-------|-------|------------|
| Neutralisierung | b-reference/platform/neutralization.md | Datenschutz, AI-Call-Pipeline, Failsafe |
-| RBAC | b-reference/platform/rbac.md | 4-Stufen-Modell, Template-Rollen, Resolution, Datenmodell |
+| Unified Data Bar (UDB) | b-reference/platform/unified-data-bar.md | Polymorphe `UdbNode`-Hierarchie, Flag-Mechanik (`neutralize`/`scope`/`ragIndexEnabled`), `POST /api/udb/tree/children`, `POST /api/udb/node/{key}/flag/{flag}`, RBAC fuer Flag-Edits (Owner vs Feature-Admin) |
+| RBAC | b-reference/platform/rbac.md | 4-Stufen-Modell, Template-Rollen, Resolution, Datenmodell, UDB-Flag-Edit-Gate (Feature-Admin) |
| Mandate-Identifier | b-reference/platform/mandate.md | `name` (Kurzzeichen) vs. `label` (Voller Name), Slug-Generierung, RBAC-Editierbarkeit, Bootstrap-Migration |
| Datenbank-Architektur | b-reference/platform/database-architecture.md | Interface-Pattern, Connector, Auto-Init, DB-Liste, Database Health, Orphan-Scanner |
| Navigation | b-reference/platform/navigation.md | Menü-Struktur, Admin-Seiten, API |
@@ -52,6 +53,7 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
| 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 CHF-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). |
+| UDB Polymorphic Refactor (Object-orientiert + Generic Router) | c-work/3-validate/2026-05-udb-polymorphic-refactor.md | Neue Klassenhierarchie `serviceKnowledge/udbNodes.py` (`UdbNode` ABC, Subklassen pro Kind). Generischer Router `routes/routeUdb.py` mit `POST /api/udb/tree/children` und `POST /api/udb/node/{key}/flag/{flag}`. Hart-Cut der alten 4 PATCH-Routen (`/scope`, `/neutralize`, `/rag-index`, `/neutralize-fields`) und der `/api/workspace/{id}/tree/children`-Route. FDS-Schema: `userId`, `workspaceInstanceId`, `scope` entfernt — FDS ist feature-owned, RBAC-gated (`-admin` Rolle auf `featureInstanceId`). Kanonische Doku: `b-reference/platform/unified-data-bar.md`. |
| 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/platform/neutralization.md b/b-reference/platform/neutralization.md
index c7147fd..e386b2e 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. **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).
+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-23 (Polymorphic UDB Refactor):** `FeatureDataSource` traegt `neutralize`, `ragIndexEnabled` und `neutralizeFields` (kein `scope`, kein `userId`, kein `workspaceInstanceId` mehr — FDS ist feature-owned, RBAC-gated). Vererbung und Cascade-Reset laufen ueber `_INHERITABLE_FDS_FLAGS = {"neutralize", "ragIndexEnabled"}` mit Coordinate `(featureInstanceId, tableName)`. Flag-Toggles laufen ausschliesslich ueber `POST /api/udb/node/{key}/flag/{flag}` (siehe `b-reference/platform/unified-data-bar.md`); die alten `PATCH /api/datasources/{id}/...`-Routen sind entfallen.
5. **FeatureDataSource (Tabellen-Level):** `_queryFeatureInstance()` setzt bei `neutralize=True` u. a. `requireNeutralization=True` für Sub-Agent-AI-Calls.
6. **FeatureDataSource (Feld-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).
@@ -57,12 +57,14 @@ Im zentralen AI-Gate (`_neutralizeRequest`):
## 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:
+Vollstaendige Doku: `b-reference/platform/unified-data-bar.md`. Hier nur Bezug zur Neutralisierung:
-- **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.
+- **Tree-Endpoint:** `POST /api/udb/tree/children` mit Body `{parents: [null, "", ...]}`. Response: `{nodesByParent}` mit pre-computed `effectiveNeutralize | "mixed"` pro Node.
+- **Flag-Toggle:** `POST /api/udb/node/{key}/flag/neutralize` mit Body `{value: true|false|null}`. Polymorph: dieselbe Route bedient DataSource, FeatureDataSource und das virtuelle FdsField (Persistenz dort in `FeatureDataSource.neutralizeFields`).
+- **Builder:** `platform-core/modules/serviceCenter/services/serviceKnowledge/_buildTree.py` orchestriert alle Kinds; effektive Werte und mixed-Aggregation kommen polymorph aus den `UdbNode`-Subklassen (`udbNodes.py`).
+- **RBAC:** DataSource → Owner-of-record; FDS und FdsField → Feature-Admin (`Role.roleLabel.endswith('-admin')` auf der Feature-Instanz). SysAdmin/PlatformAdmin haben **keinen** automatischen Edit-Bypass auf UDB-Flags.
+- **Frontend:** `SourcesTab.tsx` rendert ausschliesslich die Backend-Antwort, keine Vererbungslogik. Toggle = POST → Refetch → Render mit Spinner ueber dem Flag-Button waehrend des Round-Trips. Mixed-Symbol `U+25E9` einheitlich fuer alle drei Flags.
+- **Klick-Semantik bei mixed:** Klick auf das mixed-Symbol setzt explizit `false` (gilt fuer alle drei Flags). Begruendung: der visuelle Toggle behaelt zwei klare End-Zustaende (`true` / `false`); Reset auf `null`/inherit erfolgt ausschliesslich durch Parent-Toggle (siehe cascade-inherit). Das ist die einzige Geschaeftslogik im Frontend — alles andere kommt vom Backend.
## NeutralizationPanel (Frontend)
@@ -82,9 +84,11 @@ Seit 2026-05-18 (Generic Tree Refactor, siehe `c-work/4-done/2026-05-udb-generic
| **Ordner-Toggle / Index** | `routes/routeDataFiles.py` — `PATCH /folders/{folderId}/neutralize` (rekursive Propagierung) |
| **Workflow-Aktion** | `workflows/methods/methodContext/actions/neutralizeData.py` |
| **Kontext** | `serviceCenter/context.py` — `requireNeutralization` |
-| **Flags (Modelle)** | `datamodels/datamodelFiles.py` (`FileItem.neutralize`), `datamodelFileFolder.py` (`FileFolder.neutralize`), `datamodelDataSource.py`, `datamodelFeatureDataSource.py` (`neutralize`, `neutralizeFields`, `ragIndexEnabled` seit 2026-05-18) |
+| **Flags (Modelle)** | `datamodels/datamodelFiles.py` (`FileItem.neutralize`), `datamodelFileFolder.py` (`FileFolder.neutralize`), `datamodelDataSource.py`, `datamodelFeatureDataSource.py` (`neutralize`, `neutralizeFields`, `ragIndexEnabled` — kein `scope`/`userId`/`workspaceInstanceId` seit 2026-05-23) |
| **Effective-Flag-Resolution** | `serviceCenter/services/serviceKnowledge/_inheritFlags.py` (`getEffectiveFlag`, `getEffectiveFlagFds`, `resolveEffectiveForPath`, `resolveEffectiveForFds`, `cascadeResetDescendants`) |
-| **UDB Tree-Builder** | `serviceCenter/services/serviceKnowledge/_buildTree.py` (`getChildrenForParents` -> `POST /api/workspace/{id}/tree/children`) |
+| **UDB polymorphe Node-Klassen** | `serviceCenter/services/serviceKnowledge/udbNodes.py` (`UdbNode`-Hierarchie, `canEdit` / `getEffectiveFlag` / `setFlag` / `getLogicalChildren`) |
+| **UDB Tree-Builder** | `serviceCenter/services/serviceKnowledge/_buildTree.py` (`getChildrenForParents` -> `POST /api/udb/tree/children`) |
+| **UDB Generic Router** | `routes/routeUdb.py` (`POST /api/udb/tree/children`, `POST /api/udb/node/{key}/flag/{flag}`) |
| **AI-Operationen / Enums** | `datamodels/datamodelAi.py`, `aicore/aicorePluginPrivateLlm.py` |
## Regeln / Invarianten
diff --git a/b-reference/platform/rbac.md b/b-reference/platform/rbac.md
index 3122db6..ba13dc5 100644
--- a/b-reference/platform/rbac.md
+++ b/b-reference/platform/rbac.md
@@ -1,6 +1,6 @@
-
-
+
+
# RBAC-System
@@ -350,6 +350,20 @@ Felder `id` und Namen mit fuehrendem `_` (z.B. `_createdBy`, `_createdAt`) sind
| RBAC-Rule-Resolution | `gateway/modules/security/rbac.py` |
| TEMPLATE_ROLES (Beispiel) | `gateway/modules/features/workspace/mainWorkspace.py` |
+## Feature-Admin-Gate fuer UDB-Flag-Edits
+
+Die Unified Data Bar (siehe `b-reference/platform/unified-data-bar.md`) erlaubt das Editieren von `neutralize` / `ragIndexEnabled` / `neutralizeFields` auf `FeatureDataSource`-Nodes ausschliesslich fuer **Feature-Admins** der jeweiligen Feature-Instanz. Implementierung in `serviceKnowledge/udbNodes.py::_isFeatureAdmin(rootIf, userId, featureInstanceId)`:
+
+1. lade die `FeatureAccess`-Row fuer (userId, featureInstanceId)
+2. lade ihre `FeatureAccessRole`-Eintraege und die referenzierten `Role`-Records
+3. erlaube nur, wenn mindestens eine Rolle `Role.roleLabel.endswith("-admin")` erfuellt (z.B. `workspace-admin`, `trustee-admin`, ...)
+
+**Wichtig:**
+- Es ist kein impliziter Bypass fuer `isSysAdmin` oder `isPlatformAdmin` vorgesehen — UDB-Flag-Edits sind ein **Daten-Verantwortungs-Akt** und nicht ein Plattform-Operations-Akt. Plattform-Administratoren ohne Feature-Admin-Rolle in der Instanz bekommen 403.
+- Die DataSource-Familie (`conn|...`, `svc|...`, `ds|...`) wird ueber Ownership (`rec.userId == context.user.id`) gechecked, nicht ueber RBAC-Rollen.
+
+Validierung des Wertes erfolgt in `routeUdb._validateFlagValue`; insbesondere setzt `scope="global"` zusaetzlich `context.isSysAdmin` voraus.
+
## Regeln / Invarianten
- Mandanten-Rollen und Feature-Instanz-Rollen **strikt trennen** (keine Kreuz-Zuweisung der Label-Typen)
diff --git a/b-reference/platform/unified-data-bar.md b/b-reference/platform/unified-data-bar.md
new file mode 100644
index 0000000..3eee5fc
--- /dev/null
+++ b/b-reference/platform/unified-data-bar.md
@@ -0,0 +1,243 @@
+
+
+
+
+# Unified Data Bar (UDB)
+
+Die UDB ist die zentrale Komponente zur Anzeige und Steuerung **aller** Datenquellen, die ein User im System sieht. Sie ist **feature-unabhaengig**: sie kann in jeder Feature-Instanz eingebunden werden, ist aber von keinem konkreten Feature abhaengig. Auch ein System-Tab ohne Feature-Kontext koennte die UDB nutzen.
+
+Dieses Dokument ist die kanonische Quelle der Wahrheit fuer:
+
+1. das Domain-Modell (welche Node-Typen, wer besitzt was)
+2. die Flag-Mechanik (`neutralize` / `scope` / `ragIndexEnabled`, Vererbung, mixed-Aggregation, Cascade-Reset)
+3. die RBAC-Regeln fuer Flag-Aenderungen
+4. das API-Schema und den Frontend-Vertrag
+
+> Zugehoerige Bereiche:
+> - Neutralisierung (Daten-Gate, Engine, Failsafe): `b-reference/platform/neutralization.md`
+> - RAG-Indexierung (Ingestion-Pipeline, Inventory): `b-reference/gateway/ai-agent.md`
+> - RBAC (Roles, Permissions, Resolution): `b-reference/platform/rbac.md`
+
+## Architektur-Prinzipien
+
+- **Backend ist autoritativ.** Es liefert pro sichtbarem Tree-Node die effektiven Flag-Werte (`boolean | string | "mixed"`). Das Frontend rendert nur — keine Vererbung, keine Aggregation, keine optimistic Updates.
+- **Polymorphes Node-Modell.** Jeder Node-Typ (`UdbNode`-Subklasse) kapselt seine eigene Logik: welche Flags er traegt, wer ihn bearbeiten darf, wie ein Flag persistiert wird, wie effektive Werte berechnet werden. Es gibt keine zentralen `if kind == "fds"`-Stellen mehr.
+- **Eine generische API.** Genau ein Endpoint fuer Tree-Walks, genau ein Endpoint fuer Flag-Toggles. Keine pro-Flag-PATCH-Routen.
+- **Hart-Cut bei Refactors.** Es gibt keinen Compatibility-Layer auf den alten Endpoints; alte Aufrufer werden umgestellt oder geloescht.
+
+## Domain-Modell
+
+```mermaid
+flowchart TD
+ subgraph synth [Synthetische Container]
+ personalRoot[personalRoot]
+ mgrp["mgrp|mandateId"]
+ end
+
+ subgraph dsFamily [DataSource-Familie - user-private]
+ conn["conn|connId"]
+ svc["svc|connId|service"]
+ folder["ds|connId|sourceType|/path/"]
+ file["ds|connId|sourceType|/path/file.ext"]
+ end
+
+ subgraph fdsFamily [FeatureDataSource-Familie - feature-owned]
+ feat["feat|mandateId|featureCode|fiId"]
+ fdstbl["fdstbl|fiId|tableName"]
+ fdsfld["fdsfld|fiId|tableName|fieldName"]
+ end
+
+ personalRoot --> conn
+ conn --> svc
+ svc --> folder
+ folder --> folder
+ folder --> file
+
+ mgrp --> feat
+ feat --> fdstbl
+ fdstbl --> fdsfld
+```
+
+### Vier Node-Familien
+
+| Familie | Subklassen | DB-Record | scope | neutralize | ragIndexEnabled | RBAC zum Editieren |
+|---|---|---|---|---|---|---|
+| **SyntheticContainer** | `SyntheticContainerNode`, `MandateGroupNode` | nein | – | – | – | nie editierbar |
+| **DataSource** | `ConnectionNode`, `ServiceNode`, `FolderNode`, `FileNode` | `DataSource` (`userId`-scoped, optional virtuell) | ja (3-wertig) | ja (3-wertig) | ja (3-wertig) | Owner-of-record (`rec.userId == user`) |
+| **FdsRecord** | `FdsWorkspaceNode`, `FdsTableNode`, `FdsRowNode` | `FeatureDataSource` (`featureInstanceId`-scoped) | nein | ja (3-wertig) | ja (3-wertig) | Feature-Admin (`roleLabel.endswith('-admin')` auf der `featureInstanceId`) |
+| **FdsField** | `FdsFieldNode` | virtueller Child unter Table, persistiert in `FeatureDataSource.neutralizeFields` | nein | ja (zweiwertig: enthalten in `neutralizeFields` oder nicht) | nein | wie FdsRecord |
+
+**Wichtig — Ownership-Trennung:**
+
+- `DataSource` ist **user-privat** (`userId`-scoped). Scope (`personal` | `mandate` | `global`) bestimmt, wer sie zusaetzlich sehen darf. Geteilte Sources erscheinen anderen Usern read-only.
+- `FeatureDataSource` (FDS) ist **feature-owned** (`featureInstanceId`-scoped). Es gibt keinen `userId`, kein `workspaceInstanceId`, kein `scope`-Feld. Sichtbarkeit ergibt sich aus RBAC auf der Feature-Instanz. Editierbarkeit verlangt Feature-Admin.
+
+## Flag-Mechanik
+
+Drei Flags mit identischer 3-wertiger Semantik (Ausnahme: FdsField, siehe unten):
+
+- `null` — vererbt vom naechsten expliziten Vorfahren entlang des Path-Trees (longest-prefix wins, gleiche `connectionId` + `sourceType` fuer DS, gleiche `featureInstanceId` + `tableName` fuer FDS).
+- `True` / `False` — expliziter Override.
+- `"mixed"` — **berechneter Anzeige-Wert** auf Parent-Nodes, wenn die direkten Children unterschiedliche effektive Werte haben. Wird vom Backend pro Render geliefert (`mode="aggregate"`) und nie persistiert.
+
+### Vererbung (Path-Traversal)
+
+Implementiert in `_inheritFlags.py`:
+
+- `getEffectiveFlag(...)` / `getEffectiveFlagFds(...)` — laufen das Path-Tree hoch und liefern den ersten expliziten Wert (`True`/`False`/scope-string) oder den Default.
+- `resolveEffectiveForPath(...)` / `resolveEffectiveForFds(...)` — geben das vollstaendige Triplett zurueck plus einen virtuellen Datensatz fuer Pfade ohne eigenen DB-Record.
+
+### Aggregation (mixed)
+
+Polymorph in jeder `UdbNode`-Subklasse:
+
+```mermaid
+flowchart TD
+ Render["Render eines Parent-Nodes"] --> CallEff["node.getEffectiveFlag(flag, allDs, allFds, mode='aggregate')"]
+ CallEff --> GetChildren["node.getLogicalChildren(allDs, allFds)"]
+ GetChildren --> CompareChildren["Effective-Werte aller Children gleich?"]
+ CompareChildren -- ja --> ReturnValue["Wert"]
+ CompareChildren -- nein --> ReturnMixed["'mixed'"]
+```
+
+- **`mode="own"`** liefert nur den eigenen effektiven Wert (Path-Walk auf dem eigenen Record / der eigenen Coordinate).
+- **`mode="aggregate"`** kombiniert eigenen Wert mit den aggregierten Effective-Werten der `getLogicalChildren(...)`. Sind die Children konsistent, gewinnt deren Wert; uneinheitlich → `"mixed"`.
+
+### Cascade-Reset auf Toggle
+
+Wird ein expliziter Wert auf einem Parent gesetzt, raeumt der Backend (`cascadeResetDescendants` / `cascadeResetDescendantsFds`) **fuer genau dieses Flag** alle expliziten Werte auf Descendant-Records weg (setzt sie auf `null`). Descendants, die bereits geerbt haben, werden nicht angefasst. Der Parent-Wert ist anschliessend die effektive Quelle fuer alle.
+
+Die generische Cascade-Infrastruktur (`_inheritFlags.py`) kennt **nur** die drei Flag-Spalten. Alles darueber hinaus — wie das Aufraumen von `neutralizeFields` — ist Verantwortung der jeweiligen Node-Klasse via dem `_onSetFlag`-Hook in `_FdsFamilyNode`:
+
+- `FdsTableNode._onSetFlag`: wipe-t die eigene `neutralizeFields`-Liste wenn ein expliziter `neutralize`-Wert gesetzt wird (per-column Overrides werden durch den Tabellen-Wert obsolet).
+- `FdsWorkspaceNode._onSetFlag`: wipe-t `neutralizeFields` auf allen Descendant-Tables, damit die Cascade-Invariante auch fuer field-level State gilt.
+
+Damit bleibt die Cascade-Infrastruktur flag-agnostisch, und jede Node-Klasse kapselt ihre eigene Speziallogik (Architekturprinzip: **Polymorphes Node-Modell**).
+
+### FdsField-Sonderfall
+
+Felder unter einer FDS-Tabelle (`fdsfld|...`) haben **kein** eigenes Vererbungstriplett. Der Wert wird in der Spalte `FeatureDataSource.neutralizeFields` (Liste von Spalten-Namen) gespeichert und folgt einem Zwei-Quellen-Modell:
+
+1. `fieldName in tableRec.neutralizeFields` → expliziter Override → `effectiveNeutralize=True`.
+2. sonst → das Feld **erbt** vom effektiven `neutralize` seiner Tabelle (die ihrerseits den FDS-Ancestor-Chain bis zur Workspace-Wildcard hochlaeuft).
+
+Die Liste kann konstruktionsbedingt nur "explicit True" ausdruecken, kein "explicit False". Ein Tabellen-Toggle wirkt deshalb cascade-reset-mäßig auf alle Felder via Inheritance: der `setFlag`-Pfad wischt `neutralizeFields` bei expliziter Tabellen-Aenderung leer (siehe Cascade-Reset-Abschnitt), sodass alle Felder anschliessend den neuen Tabellen-Wert sehen.
+
+`FdsField` traegt nur `neutralize` (keine `scope`, keine `ragIndexEnabled`). Mixed-Aggregation auf der Tabelle erfolgt korrekt, weil `FdsTableNode.getLogicalChildren(...)` die Felder dynamisch als logische Children einhaengt (`_wireTableFieldsAsLogicalChildren` in `_buildTree.py`); divergierende Field-Walks (einige True via Override, andere False via Inherit) liefern `'mixed'` auf der Tabelle.
+
+## RBAC fuer Flag-Aenderungen
+
+Implementiert in `UdbNode.canEdit(context, rootIf)`. Pro Subklasse:
+
+- `SyntheticContainerNode`, `MandateGroupNode`: nie editierbar.
+- `_DataSourceFamilyNode` (Connection / Service / Folder / File): `rec.userId == context.user.id` wenn ein `DataSource`-Record existiert. Fuer **virtuelle Nodes** (Browse-Folder ohne Record) prueft `canEdit` stattdessen, ob die `UserConnection` dem User gehoert (`_isConnectionOwner`); `setFlag` erstellt dann automatisch einen DataSource-Stub-Record. Geteilte Sources (anderer User) bleiben read-only.
+- `_FdsFamilyNode` (FdsWorkspace / FdsTable / FdsRow), `FdsFieldNode`: `_isFeatureAdmin(rootIf, userId, featureInstanceId)` — verlangt eine `FeatureAccessRole` auf der Instanz, deren `Role.roleLabel` mit `-admin` endet (z.B. `workspace-admin`, `trustee-admin`). SysAdmin und PlatformAdmin haben **keine** automatische Erlaubnis (UDB-Edits sind ein Daten-Verantwortungs-Akt, nicht ein Plattform-Operations-Akt).
+
+Globaler Scope (`scope="global"`) setzt zusaetzlich `context.isSysAdmin` voraus; siehe `_validateFlagValue` in `routeUdb.py`.
+
+## Visibility
+
+Visibility (was der User sieht) ist getrennt von Editability (was er aendern darf):
+
+| Tab / Bereich | Sichtbar fuer den User |
+|---|---|
+| Chats | Chat-Workflows der Feature-Instanz, in der die UDB eingebettet ist |
+| Folders + Files | Eigene Files/Folders + mit dem User geteilte (gemaess `scope`-Attribut der jeweiligen Source); fremde Attribute read-only |
+| Personal Sources | Eigene Connections + Sources mit `scope in {mandate, global}` anderer User (read-only) |
+| Feature Data Sources | Alle FDS der Feature-Instanzen im Mandanten, in denen der User RBAC-Zugriff hat |
+
+Die Vererbung folgt **dem Path-Tree der jeweiligen Source**, nicht dem Owner. Sharing aendert daran nichts (die Berechtigungsmatrix der externen Provider — SharePoint, Drive, etc. — bleibt unberuehrt; UDB ueberlagert sie nicht).
+
+## API
+
+### `POST /api/udb/tree/children`
+
+```
+Body: { "parents": [null, "", "", ...] }
+Response: { "nodesByParent": { "__root__": [...], "": [...], ... } }
+```
+
+`null` markiert das Top-Level. Jeder zurueckgegebene Node enthaelt:
+
+```
+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,
+ fieldName, neutralizeFields)
+```
+
+Pre-computed effective Werte sind `boolean | "mixed"` bzw. `string | "mixed"`. FDS-Nodes haben keinen `effectiveScope`-Wert. `FdsFieldNode` traegt nur `effectiveNeutralize`.
+
+### `POST /api/udb/node/{nodeKey}/flag/{flag}`
+
+```
+Path: nodeKey ∈ Tree-Keys (URL-encoded; '|' bleibt nach decodieren erhalten)
+ flag ∈ {neutralize, scope, ragIndexEnabled}
+Body: { "value": }
+Response: { "nodeKey", "flag", "value", "effective", "resetDescendantIds" }
+```
+
+Ablauf:
+
+1. `buildNodeForKey(nodeKey, ...)` parsed den Key in die zustaendige `UdbNode`-Subklasse.
+2. `node.supportsFlag(flag)` → 400 wenn nicht.
+3. `node.canEdit(context, rootIf)` → 403 wenn nicht.
+4. `node.setFlag(flag, value, rootIf)` persistiert + cascade-reset, liefert die Liste der zurueckgesetzten Descendant-Ids.
+5. `_computeEffectiveAfterWrite(...)` berechnet den frischen effective Wert (inkl. mixed).
+6. Audit-Log via `audit_logger.logEvent(action="udb_flag_changed", ...)`.
+
+`value=null` setzt den eigenen Wert auf `null` (vererbe wieder) **ohne** Cascade-Reset und ohne Index-Purge.
+
+### Key-Format
+
+| Pattern | Beschreibung |
+|---|---|
+| `personalRoot` | Top-Level-Container der eigenen Connections |
+| `mgrp\|` | Mandanten-Kopfknoten |
+| `conn\|` | UserConnection-Root |
+| `svc\|\|` | Service unter einer Connection (sharepoint, outlook, drive, ...) |
+| `ds\|\|\|` | Folder oder File innerhalb eines Services |
+| `feat\|\|\|` | Feature-Instanz (Workspace-Wildcard `*`) |
+| `fdstbl\|\|` | Feature-Datentabelle |
+| `fdsfld\|\|\|` | Virtuelles Feld unter einer Table |
+
+`buildNodeForKey(...)` ist die einzige Stelle, die Keys decodiert; alle anderen Stellen erhalten bereits getypte `UdbNode`-Instanzen.
+
+## Frontend-Vertrag
+
+`ui-nyla/src/components/UnifiedDataBar/UdbSourcesProvider.tsx`:
+
+- Einziger Cache: `Map` plus expanded-Set.
+- `loadChildren(parent)` → `POST /api/udb/tree/children`.
+- `patchScope` / `patchNeutralize` / `patchRagIndex` → `POST /api/udb/node/{key}/flag/{flag}` mit `{value}`-Body.
+- Keine Vererbungs- oder Aggregations-Logik im Frontend.
+- Pro Toggle: Spinner auf dem Flag-Button → API-Call → Refetch der betroffenen Parents → Re-Render. Keine optimistic Updates.
+- Einheitliches mixed-Symbol fuer alle drei Flags (`◩`, U+25E9).
+- Klick auf das mixed-Symbol → setzt explizit `false` (gilt fuer alle drei Flags). Reset auf `null`/inherit erfolgt ausschliesslich durch Parent-Toggle (siehe Cascade-Reset).
+
+## Schluessel-Dateien
+
+| Bereich | Pfad |
+|---|---|
+| **Polymorphe Node-Klassen** | `platform-core/modules/serviceCenter/services/serviceKnowledge/udbNodes.py` |
+| **Tree-Builder** | `platform-core/modules/serviceCenter/services/serviceKnowledge/_buildTree.py` |
+| **Inheritance-Helper** | `platform-core/modules/serviceCenter/services/serviceKnowledge/_inheritFlags.py` |
+| **Generic Router** | `platform-core/modules/routes/routeUdb.py` |
+| **DataSource-Model** | `platform-core/modules/datamodels/datamodelDataSource.py` |
+| **FDS-Model** | `platform-core/modules/datamodels/datamodelFeatureDataSource.py` |
+| **Frontend-Provider** | `ui-nyla/src/components/UnifiedDataBar/UdbSourcesProvider.tsx` |
+| **Frontend-Rendering** | `ui-nyla/src/components/UnifiedDataBar/SourcesTab.tsx` |
+| **Backend-Tests** | `platform-core/tests/unit/services/test_udbNodes.py`, `test_buildTree.py`, `test_inheritFlags.py` |
+| **Frontend-Tests** | `ui-nyla/src/components/UnifiedDataBar/__tests__/UdbSourcesProvider.test.ts` |
+
+## Regeln / Invarianten
+
+- **Polymorphismus statt `if kind == ...`.** Neue Node-Typen erweitern die Klassen-Hierarchie; sie aendern nicht den Router oder den Builder.
+- **Backend liefert effective Werte.** Das Frontend rendert sie 1:1. Wenn das UI „falsch“ aussieht, liegt der Fehler im Backend (Builder oder Resolver), nicht in der UI.
+- **Eine API pro Verantwortung.** `tree/children` fuer Sichtbarkeit, `node/{k}/flag/{f}` fuer Persistenz. Keine Spezialrouten pro Flag oder pro Kind.
+- **Hart-Cut bei Refactors.** Alte Endpoints und alter Frontend-Code werden geloescht, nicht parallel gehalten. Tests werden mitgezogen.
+- **FDS hat keinen Scope und kein `userId`.** Wer das einbauen will, baut ein neues, separates Domain-Konzept — keine Vermischung.
+- **Audit ist Pflicht.** Jede `setFlag`-Ausfuehrung schreibt einen `udb_flag_changed`-Eintrag mit `nodeKey`, `flag`, `value`, `resetDescendants`, `nodeKind`.
diff --git a/c-work/3-validate/2026-05-udb-polymorphic-refactor.md b/c-work/3-validate/2026-05-udb-polymorphic-refactor.md
new file mode 100644
index 0000000..4876fc5
--- /dev/null
+++ b/c-work/3-validate/2026-05-udb-polymorphic-refactor.md
@@ -0,0 +1,141 @@
+
+
+
+
+
+# UDB Polymorphic Refactor (Object-orientiert + Generic Router)
+
+## Beschreibung und Kontext
+
+Nach dem Generic-Tree-Refactor (`2026-05-udb-generic-tree-refactor.md`) blieben strukturelle Schwaechen, die in der Praxis zu wiederholten "stuck toggle"-Bugs gefuehrt haben:
+
+- Die Flag-Logik war ueber `_buildTree.py`, `_inheritFlags.py`, vier PATCH-Routen, zwei Aggregatoren und das Frontend verstreut. Jede neue Node-Familie verlangte `if kind == ...`-Verzweigungen an mehreren Stellen.
+- `FeatureDataSource` trug ein `userId`, ein `workspaceInstanceId` und ein `scope`-Feld, obwohl FDS feature-owned ist und Scope ueber RBAC abgedeckt sein muss. Das verfuehrte zur Vermischung von Mandate-Visibility und User-Sharing.
+- Die UDB war ueber die `workspace`-Route ans Workspace-Feature verdrahtet, obwohl sie domaenenfrei und auf Systemebene nutzbar sein soll.
+- Ein Bug, bei dem `neutralize=true` auf einem DB-Feld nicht mehr zurueckschaltbar war, liess sich nicht punktuell beheben — Root Cause war fragmentierte Logik, nicht ein einzelnes Codestueck.
+
+User-Direktive: "keine optimistic hacks, keine workarounds und fallbacks, klare Logik, wo wir Fehler auch sehen, nicht verdecken". Ergo: harter Schnitt, polymorphes Backend, ein generischer Router.
+
+## Ziele
+
+1. **Polymorph statt fragmentiert.** Jeder Node-Typ ist eine Klasse mit klaren Hooks (`canEdit`, `getEffectiveFlag`, `setFlag`, `getLogicalChildren`, `toDict`). Neue Kinds erweitern die Hierarchie statt zentrale Switches.
+2. **Eine generische API.** `POST /api/udb/tree/children` fuer Walks, `POST /api/udb/node/{key}/flag/{flag}` fuer Persistenz. Adressiert via Tree-Key (funktioniert auch fuer virtuelle Nodes wie `fdsField`).
+3. **UDB feature-entkoppelt.** Eigene `/api/udb`-Route. Keine Bindung mehr ans Workspace-Feature.
+4. **Klare Ownership-Semantik.** DataSource = user-private mit `scope`. FeatureDataSource = feature-owned, RBAC-gated, kein `userId`/`workspaceInstanceId`/`scope`.
+5. **RBAC explizit verankert.** Flag-Edits auf FDS verlangen Feature-Admin (`Role.roleLabel.endswith("-admin")` auf der `featureInstanceId`). SysAdmin/PlatformAdmin sind kein Bypass.
+
+## Nicht-Ziele
+
+- Kein Kompatibilitaets-Layer. Alte Endpoints werden geloescht, nicht gespiegelt.
+- Keine Aenderung an der UDB-UI-Optik (Symbole, Klick-Semantik, mixed-Anzeige) — diese stammt aus dem vorherigen Refactor.
+- Keine Erweiterung der RAG-Pipeline. Bootstrap-Job laeuft weiter, jetzt aber per `featureInstanceId` (FDS hat keinen `workspaceInstanceId` mehr).
+
+## Betroffene Module
+
+- **Platform-Core (Backend):**
+ - Neu: `modules/serviceCenter/services/serviceKnowledge/udbNodes.py` (Klassenhierarchie)
+ - Neu: `modules/routes/routeUdb.py` (Generic Router)
+ - Refactored: `modules/serviceCenter/services/serviceKnowledge/_buildTree.py` (Builder geben `UdbNode`-Instanzen zurueck)
+ - Refactored: `modules/serviceCenter/services/serviceKnowledge/_inheritFlags.py` (FDS-Vererbung ohne `scope`, Coordinate `featureInstanceId` statt `workspaceInstanceId`)
+ - Refactored: `modules/datamodels/datamodelFeatureDataSource.py` (entfernt: `userId`, `workspaceInstanceId`, `scope`)
+ - Refactored: `modules/routes/routeDataSources.py` (4 PATCH-Endpoints entfernt; nur `/settings` und `/cost-estimate` bleiben)
+ - Refactored: `modules/features/workspace/routeFeatureWorkspace.py` (alter `/tree/children`-Endpoint entfernt; FDS-Create ohne `userId`/`workspaceInstanceId`)
+ - Refactored: `modules/routes/routeRagInventory.py` (Reindex via `featureInstanceId`)
+ - Refactored: `modules/serviceCenter/services/serviceKnowledge/subFeatureBootstrap.py` (Payload `featureInstanceId`, kein FDS-`userId` mehr → `"system"`)
+ - Refactored: `modules/serviceCenter/services/serviceAgent/coreTools/_featureSubAgentTools.py` (FDS-Lookup ohne `workspaceInstanceId`)
+ - App-Registration: `app.py` (`app.include_router(udbRouter)`)
+- **UI-Nyla (Frontend):**
+ - Refactored: `src/components/UnifiedDataBar/UdbSourcesProvider.tsx` (alle Flag-Patches gehen ueber `POST /api/udb/node/{key}/flag/{flag}`, `_patchFieldNeutralize` entfernt)
+ - Refactored: `src/api/connectionApi.ts` (alte `patchDataSourceRagIndex`-Helper entfernt)
+ - Docstring: `src/components/UnifiedDataBar/SourcesTab.tsx`
+- **Tests:**
+ - Neu: `platform-core/tests/unit/services/test_udbNodes.py` (umfassende Coverage der Klassenhierarchie)
+ - Refactored: `platform-core/tests/unit/services/test_buildTree.py`
+ - Refactored: `platform-core/tests/unit/services/test_inheritFlags.py`
+ - Refactored: `ui-nyla/src/components/UnifiedDataBar/__tests__/UdbSourcesProvider.test.ts`
+- **Wiki:**
+ - Neu: `wiki/b-reference/platform/unified-data-bar.md` (kanonische Reference)
+ - Updates: `wiki/b-reference/platform/neutralization.md`, `wiki/b-reference/platform/rbac.md`, `wiki/TOPICS.md`, `wiki/c-work/_CHANGELOG.md`
+- **DB-Migration:** nein (Schema-Felder am Model entfernt; bestehende DB-Spalten bleiben unbenutzt liegen — keine Datenverluste, keine Live-Migration noetig).
+
+## Entscheidungen
+
+| Datum | Entscheidung | Begruendung |
+|---|---|---|
+| 2026-05-23 | Hart-Cut der 4 PATCH-Routen | "Keine Workarounds, keine optimistic hacks." User-Direktive. |
+| 2026-05-23 | Tree-Key als API-Adresse statt `sourceId` | Funktioniert auch fuer virtuelle Nodes wie `fdsField` (kein eigener DB-Record). |
+| 2026-05-23 | Eigene `/api/udb`-Route, weg von `/api/workspace` | UDB ist feature-unabhaengig; sie kann auch auf System-Level ohne Feature-Kontext verwendet werden. |
+| 2026-05-23 | FDS verliert `userId`, `workspaceInstanceId`, `scope` | FDS ist feature-owned. Visibility kommt aus RBAC, Edit aus Feature-Admin. Scope-Vermischung erzeugte den Field-Toggle-Bug. |
+| 2026-05-23 | RBAC fuer FDS-Edits: `roleLabel.endswith('-admin')` | Pro Feature-Modul gibt es eigene Admin-Rollen (`workspace-admin`, `trustee-admin`, ...). Suffix-Match statt Allow-List. |
+| 2026-05-23 | Kein SysAdmin/PlatformAdmin-Bypass auf UDB-Flag-Edits | Daten-Verantwortungs-Akt, kein Plattform-Operations-Akt. |
+| 2026-05-23 | Visibility-Cascade fuer geteilte DataSources nicht eingebaut | Wuerde die Berechtigungsmatrix der externen Provider (SharePoint, Drive) ueberlagern. Sharing bleibt strikt read-only fuer Empfaenger. |
+
+## Architektur (Kurzfassung)
+
+```mermaid
+flowchart LR
+ Frontend["UdbSourcesProvider.tsx"] -->|"POST /api/udb/tree/children"| Router["routeUdb.py"]
+ Frontend -->|"POST /api/udb/node/{k}/flag/{f}"| Router
+ Router -->|"buildNodeForKey(k)"| Nodes["udbNodes.py - UdbNode + Subklassen"]
+ Router -->|"getChildrenForParents(...)"| Builder["_buildTree.py"]
+ Builder --> Nodes
+ Nodes --> InheritFlags["_inheritFlags.py - Path-Traversal + Cascade"]
+ Nodes --> Models["DataSource / FeatureDataSource"]
+```
+
+Volle Architektur und API-Details: `b-reference/platform/unified-data-bar.md`.
+
+## Umsetzungs-Checkliste
+
+- [x] `udbNodes.py`: `UdbNode` ABC + Subklassen (synthetic, DS-Familie, FDS-Familie, FdsField)
+- [x] `_aggregateChildrenToParents` geloescht (Logik jetzt polymorph in `getEffectiveFlag(mode=aggregate)`)
+- [x] `_buildTree.py`: Builder geben `UdbNode`-Instanzen zurueck; `getChildrenForParents` ohne `instanceId`-Parameter
+- [x] `routeUdb.py`: `/api/udb/tree/children` + `/api/udb/node/{key}/flag/{flag}`
+- [x] Alte PATCH-Routen geloescht (`/scope`, `/neutralize`, `/rag-index`, `/neutralize-fields`); alter `/api/workspace/{id}/tree/children` geloescht
+- [x] `FeatureDataSource`-Schema: `userId`, `workspaceInstanceId`, `scope` entfernt
+- [x] Frontend-Provider auf `/api/udb/*` umgestellt
+- [x] Backend-Tests: `test_udbNodes.py` neu, `test_buildTree.py` + `test_inheritFlags.py` angepasst
+- [x] Frontend-Tests: `UdbSourcesProvider.test.ts` angepasst
+- [x] Wiki: neue Reference-Page `unified-data-bar.md`; Updates auf `neutralization.md`, `rbac.md`, `TOPICS.md`, `_CHANGELOG.md`
+- [ ] Runtime-Smoke: lokal Backend starten, je einen Toggle pro Familie testen (`fdsField`, `fdsTable`, `ds-folder`, `connection`)
+
+## Akzeptanzkriterien
+
+| # | Kriterium (Given-When-Then) | Prio |
+|---|---|---|
+| 1 | Given ein DS-File-Node mit explizitem `neutralize=true` und ein Parent-Folder ohne expliziten Wert, When der User auf das mixed-Symbol des Parent klickt, Then setzt das Backend `neutralize=false` am Parent und cascade-resetted die expliziten Werte aller Descendants. | must |
+| 2 | Given ein User ohne `-admin`-Rolle auf der Feature-Instanz, When er versucht `neutralize` auf einer FDS-Table zu togglen, Then antwortet der Server mit 403 und schreibt einen Audit-Eintrag. | must |
+| 3 | Given eine FDS mit `neutralizeFields=["email"]`, When der User auf das Feld `email` klickt und neutralize toggelt, Then wird `email` aus der Liste entfernt (`neutralizeFields=[]`) und der naechste Refetch zeigt `effectiveNeutralize=false` auf dem Field. | must |
+| 4 | Given drei Files unter demselben Folder, alle mit explizitem `neutralize=true`, When der Folder via Refetch geladen wird, Then liefert das Backend `effectiveNeutralize=true` (nicht "mixed") am Folder. | must |
+| 5 | Given zwei Files unter demselben Folder, eines mit `neutralize=true` und eines mit `neutralize=false`, When der Folder via Refetch geladen wird, Then liefert das Backend `effectiveNeutralize="mixed"` am Folder. | must |
+| 6 | Given ein POST auf `/api/udb/node/{k}/flag/scope` mit `value="global"` von einem nicht-SysAdmin, When der Request validiert wird, Then 403 und kein DB-Write. | must |
+
+## Testplan
+
+| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
+|----|----|-----|---------------|-----------|--------|
+| T1 | 1,4,5 | unit | ja | `platform-core/tests/unit/services/test_udbNodes.py::TestGetEffectiveFlag` | done |
+| T2 | 2 | unit | ja | `platform-core/tests/unit/services/test_udbNodes.py::TestCanEditFdsFeatureAdmin`, `TestIsFeatureAdmin` | done |
+| T3 | 3 | unit | ja | `platform-core/tests/unit/services/test_udbNodes.py::TestSetFlag` (FdsFieldNode-Branch) | done |
+| T4 | 1 | unit | ja | `platform-core/tests/unit/services/test_inheritFlags.py` (cascade-reset) | done |
+| T5 | 6 | manuell | nein | Runtime-Smoke via UI/curl | pending |
+| T6 | alle | runtime | nein | Lokaler Smoke-Test mit `ui-nyla` + `platform-core` | pending |
+
+## Risiken / offene Punkte
+
+- **DB-Spalten am `FeatureDataSource` bleiben physisch in der Tabelle liegen** (`userId`, `workspaceInstanceId`, `scope`). Solange die ORM-Modelle sie nicht mehr referenzieren, sind sie inert. Eine spaetere DROP-Migration ist optional.
+- **Runtime-Smoke pendet.** Vor dem Verschieben in `4-done` ist mindestens ein End-to-End-Test pro Node-Familie erforderlich (Edit + Read-back via UI).
+
+## Links
+
+- Reference: `b-reference/platform/unified-data-bar.md`
+- Vorgaenger: `c-work/4-done/2026-05-udb-generic-tree-refactor.md`, `c-work/4-done/2026-05-udb-cascade-inherit.md`
+- Toggle-Spec-Recovery (Symptome, die den Refactor erzwungen haben): `c-work/3-validate/2026-05-udb-toggle-spec-recovery.md`
+
+## Abschluss
+
+- [ ] Runtime-Smoke gruen
+- [x] `b-reference/platform/unified-data-bar.md` angelegt
+- [x] `TOPICS.md` Eintrag ergaenzt
+- [x] `_CHANGELOG.md` Eintrag ergaenzt
+- [ ] Dieses Dokument → `c-work/4-done/` verschieben (nach Runtime-Smoke)
diff --git a/c-work/3-validate/2026-05-udb-toggle-spec-recovery.md b/c-work/3-validate/2026-05-udb-toggle-spec-recovery.md
new file mode 100644
index 0000000..f7bb530
--- /dev/null
+++ b/c-work/3-validate/2026-05-udb-toggle-spec-recovery.md
@@ -0,0 +1,85 @@
+
+
+
+
+
+# UDB Toggle Spec Recovery
+
+## Kontext
+
+Nach dem Generic Tree Refactor (2026-05-18) waren im Code sechs Aggregations-Routinen
+(`_aggregatePersonalRoot`, `_aggregateConnection`, `_aggregateMandateGroup`,
+`_resolveAttrsForKey`, `getAttributesForKeys`), ein paralleler Endpoint
+`POST /tree/attributes` und ein Bug bei virtuellen Coordinates (kein aggregate
+fuer Records ohne DB-Eintrag) entstanden. Das Frontend nutzte `refreshAttributes`
+als optionalen Pfad und `refreshAfterAction` als Opt-in-Prop, obwohl die Spec
+nur einen Pipeline-Pfad vorsieht.
+
+## Ziel
+
+Wiederherstellung der dokumentierten Spec (2026-05-18 "Generic Tree Refactor"):
+**Eine** Pipeline, **ein** Resolver, **ein** Mixed-Symbol, **drei** Flags
+generisch durch denselben Code-Pfad. Keine optimistic updates, keine
+Fallbacks, keine doppelte Logik.
+
+## Durchgefuehrte Aenderungen
+
+### S1 - Backend: virtuelle Coordinates auf aggregate-Pfad
+
+`_inheritFlags.py`: `resolveEffectiveForPath` und `resolveEffectiveForFds` rufen
+im else-Zweig (virtual record, kein DB-Match) nun `getEffectiveFlag(virtualRec, flag, allDs, mode=mode)`
+bzw. `getEffectiveFlagFds(...)` statt `_resolveWalkValue` auf. Damit koennen
+virtuelle Coordinates (z.B. Connection ohne eigenen Root-Record) korrekt
+`'mixed'` zurueckgeben, wenn ihre Descendants divergieren.
+
+### S2 - Backend: sechs Aggregatoren entfernt
+
+`_buildTree.py`: Geloescht: `_aggregatePersonalRoot`, `_aggregateConnection`,
+`_aggregateMandateGroup`, `_resolveAttrsForKey`, `getAttributesForKeys`.
+Diese waren ausschliesslich fuer den parallelen `tree/attributes`-Endpoint noetig.
+Synthetic Container (`personalRoot`, `mgrp`) haben laut Spec keine Flags
+(UI blendet sie aus); data-bearing Nodes nutzen bereits
+`resolveEffectiveForPath`/`resolveEffectiveForFds` via den tree builder.
+
+### S3 - Backend: Endpoint tree/attributes entfernt
+
+`routeFeatureWorkspace.py`: Route `POST /{instanceId}/tree/attributes` und
+Model `_TreeAttributesRequest` ersatzlos geloescht.
+
+### S4 - Frontend: refreshAttributes-Pfad entfernt
+
+- `UdbSourcesProvider.tsx`: `refreshAttributes`-Methode entfernt.
+- `FolderFileProvider.tsx`: `refreshAttributes`-Methode entfernt.
+- `FormGeneratorTree.tsx`: `_refreshVisibleAttributes` nutzt nur noch den
+ Refetch-All-Expanded-Pfad. `_runAction` ruft immer `_refreshVisibleAttributes`
+ ohne Bedingung. `refreshAfterAction`-Prop entfernt.
+- `types.ts`: `refreshAttributes` aus `TreeNodeProvider`, `refreshAfterAction`
+ aus `FormGeneratorTreeProps` entfernt.
+- `SourcesTab.tsx`, `FilesTab.tsx`: `refreshAfterAction`-Prop-Nutzung entfernt.
+
+### S5 - Klick-Semantik dokumentiert
+
+`wiki/b-reference/platform/neutralization.md`: Klick auf mixed-Symbol setzt
+explizit `false` (alle Flags). Begruendung: Toggle behaelt zwei klare
+End-Zustaende; Reset auf `null`/inherit durch Parent-Toggle.
+
+### S6 - Tests
+
+8 neue Tests in `test_inheritFlags.py::TestVirtualCoordAggregate`:
+- virtual folder mixed neutralize/scope/rag
+- virtual folder uniform returns concrete
+- virtual FDS workspace mixed/uniform
+- virtual connection root mixed via diverging services
+
+Frontend-Test `refreshAfterAction` angepasst: zweiter Test (does NOT refetch
+when false) entfernt, da Verhalten nun immer refetch ist.
+
+Resultat: `test_inheritFlags.py` 79/79 gruen, `test_buildTree.py` 19/19 gruen.
+
+## Validierung (Smoke-Tests vor Merge)
+
+1. Folder mit zwei Subfolders, einer toggeln -> Parent zeigt mixed, Root zeigt mixed, Personal Root zeigt mixed.
+2. Parent toggeln (war mixed) -> Parent + alle Descendants zeigen den Parent-Wert.
+3. Klick auf mixed-Symbol -> setzt explizit `false`, Spinner bleibt bis Refetch durch ist.
+4. Toggle in collapsed Subtree (verstecktem Knoten) -> nach expand zeigt Subtree die neuen Werte.
+5. Mandate mit zwei Workspaces, einer toggeln -> mgrp zeigt mixed.
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
index b705480..a2a8d9a 100644
--- a/c-work/4-done/2026-05-udb-generic-tree-refactor.md
+++ b/c-work/4-done/2026-05-udb-generic-tree-refactor.md
@@ -156,3 +156,17 @@ Frontend: `npx tsc --noEmit --skipLibCheck` clean.
- 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`
+
+## Nachtrag 2026-05-26: Spec Recovery
+
+Im Code waren nach dem Refactor sechs Aggregations-Routinen (`_aggregatePersonalRoot`,
+`_aggregateConnection`, `_aggregateMandateGroup`, `_resolveAttrsForKey`,
+`getAttributesForKeys`) und ein paralleler Endpoint `POST /tree/attributes`
+entstanden, die der oben dokumentierten Single-Pipeline-Architektur widersprachen.
+Zusaetzlich fehlte bei virtuellen Coordinates (Records ohne DB-Eintrag) die
+Subtree-Aggregation, sodass `'mixed'` nie zurueckgegeben werden konnte.
+Das Frontend nutzte `refreshAttributes` und `refreshAfterAction` als optionale
+Pfade statt des einen Refetch-All-Expanded-Pfads.
+
+Diese Abweichungen wurden am 2026-05-26 zurueckgefuehrt
+(siehe `c-work/3-validate/2026-05-udb-toggle-spec-recovery.md`).
diff --git a/c-work/_CHANGELOG.md b/c-work/_CHANGELOG.md
index c794b09..73111cb 100644
--- a/c-work/_CHANGELOG.md
+++ b/c-work/_CHANGELOG.md
@@ -12,12 +12,23 @@ type: `feat` `fix` `refactor` `docs` `test` `chore` `build` · scope: `gateway
Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
+## 2026-05-27
+
+- 2026-05-27 | fix | platform-core | UDB DS Auto-Create: Virtuelle DataSource-Nodes (Browse-Folder ohne DB-Record) auf eigener Connection koennen jetzt getoggelt werden. `canEdit` prueft Connection-Ownership via `UserConnection.userId`; `setFlag` erstellt automatisch einen DataSource-Stub-Record analog zu `_findOrCreateTableFds`. Behebt 403-Errors beim Toggling von Browse-Foldern. 4 neue Tests (`test_udbNodes.py`).
+- 2026-05-27 | refactor | platform-core | UDB neutralizeFields-Handling von generischer Infrastruktur in polymorphe Node-Klassen verschoben: `_inheritFlags.cascadeResetDescendantsFds` ist wieder rein generisch (keine `neutralizeFields`-Logik); `_FdsFamilyNode.setFlag` bietet generischen `_onSetFlag`-Hook; `FdsTableNode._onSetFlag` wipe-t eigene `neutralizeFields` bei explizitem neutralize-Toggle; `FdsWorkspaceNode._onSetFlag` wipe-t `neutralizeFields` auf allen Descendant-Tables. `FdsFieldNode.getEffectiveFlag` erbt korrekt vom Table (Zwei-Quellen-Modell). 122/122 Tests gruen. Wiki `unified-data-bar.md` (Cascade-Reset-Abschnitt) aktualisiert.
+
+## 2026-05-26
+
+- 2026-05-26 | fix | gateway+frontend-nyla | UDB Toggle Spec Recovery: virtuelle Coordinates nutzen neu aggregate-Pfad (mixed moeglich ohne DB-Record); sechs redundante Aggregatoren + Parallel-Endpoint tree/attributes + Frontend refreshAttributes-Pfad entfernt; eine Pipeline, ein Resolver, immer refetch nach Toggle (Spec 2026-05-18 wiederhergestellt) (c-work: 3-validate/2026-05-udb-toggle-spec-recovery.md)
+
## 2026-05-24
- 2026-05-24 | docs | infra | **Deployment-Infrastruktur Doku** -- Neue kanonische Seite `b-reference/platform/infrastructure.md`: Infomaniak-Projektstruktur (Porta, LLM, Teamsbot), Naming Convention (`{bereich}-{env}-{komponente}`), VM-Instanzenliste, Deploy-Patterns. TOPICS.md ergaenzt.
## 2026-05-23
+- 2026-05-23 | refactor | platform-core+ui-nyla+wiki | UDB Polymorphic Refactor: neue `UdbNode`-Klassenhierarchie (`udbNodes.py`), generischer Router `routeUdb.py` mit `POST /api/udb/tree/children` + `POST /api/udb/node/{key}/flag/{flag}`, Hart-Cut der 4 PATCH-Routen + altem `/api/workspace/{id}/tree/children`. FDS-Schema entschlackt (kein `userId`, kein `workspaceInstanceId`, kein `scope` mehr — feature-owned, RBAC-gated via `roleLabel.endswith('-admin')`). Neue kanonische Doku `b-reference/platform/unified-data-bar.md`; Updates auf `neutralization.md`, `rbac.md`, `TOPICS.md`. Tests: neu `test_udbNodes.py`, angepasst `test_buildTree.py` + `test_inheritFlags.py` + `UdbSourcesProvider.test.ts` (alle gruen) (c-work: 3-validate/2026-05-udb-polymorphic-refactor.md)
+- 2026-05-23 | feat | gateway+frontend | DB Migration Progress: Export und Import laufen per-DB sequentiell mit Echtzeit-Fortschrittslog (Timestamp, DB-Name, Tabellen-/Datensatzzahl, Fehler); neuer export-single Endpoint; Import via prepare-import + import-single (server-seitiger Payload-Cache mit Token); Export-Dateiname enthaelt Instanzlabel + full/partial (z.B. db_backup_main_full_2026-05-23T06-20-54.json)
- 2026-05-23 | feat | gateway+frontend-nyla | **DB Migration Backup/Restore** — Neuer Tab "Migration" auf der Datenbank-Gesundheit-Seite (SysAdmin). Backup: dynamische DB-Auswahl via Registry, Export als JSON. Restore: JSON-Upload, Validierung, Import mit Modus "Neu" (replace) oder "Zusammenfuehren" (merge). System-Objekte (Root-Mandant, Admin-User, Event-User) werden nie geloescht; ihre IDs werden beim Import automatisch auf die Live-IDs remapped. Neue Dateien: `databaseMigration.py`, 4 API-Endpoints unter `/api/admin/database-health/migration/`, MigrationTab in `AdminDatabaseHealthPage.tsx`.
## 2026-05-22
@@ -27,11 +38,18 @@ Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
## 2026-05-19
+- 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)
+- 2026-05-19 | fix | gateway | Trustee Budget-Vergleich: _exportAccountingData liefert neu eine accountSummary (1 Zeile/Konto mit closingBalance + Q1-Q4) statt 5590 rohe Balance-Records (26/Konto); AI-Prompt praezisiert Datenquelle; reduziert Payload von 355KB auf 25KB und verhindert Faktor-100-Abweichungen durch versehentliches Summieren
- 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 | 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-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.
@@ -82,6 +100,8 @@ Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
## 2026-05-12
+- 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-12 | feat | gateway, frontend-nyla, teams-bot | Avatar-Bild/Video für TeamsBot: Neues Feld `avatarFileId` in `TeamsbotConfig`/`TeamsbotUserSettings` und `defaultAvatarFileId` in `TeamsbotMeetingModule`. Benutzer können in den Bot-Einstellungen und pro Modul ein Bild oder Video aus den Systemdateien auswählen, das anstelle der statischen Farbfläche als Bot-Video im Meeting gerendert wird. Modul-Einstellung überschreibt Instanz-Default. Gateway löst die Datei beim Session-Start auf, konvertiert zu Base64, und sendet sie im Join-Payload an den Browser-Bot. Bot-seitig: `mediaGetUserMediaPatch.ts` rendert Bilder per `drawImage()` auf den Canvas, Videos per `