# Changelog (c-work) Eine Zeile pro Change, NEUESTE EINTRAEGE ZUOBERST. Begruendungen gehoeren ins zugehoerige `c-work//.md` oder die PR-Beschreibung. Format: `- YYYY-MM-DD | | | [(c-work: )] [(PR: #123)]` type: `feat` `fix` `refactor` `docs` `test` `chore` `build` · scope: `gateway` `frontend-nyla` `private-llm` `teams-bot` `wiki` `infra` `*` Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler. ## 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 | 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 - 2026-05-22 | fix | frontend-nyla | **UDB FormGeneratorTree: RAG-Icon + Mixed-Symbol nach Merge-Konflikt-Verlust restauriert** — In Commit `9488a7d` ("build errors") wurden bei einer Merge-Auflösung die RAG-Konstanten (`_RAG_ON_EMOJI` 🧠, `_RAG_OFF_EMOJI`), `_MIXED_SYMBOL` (◩), `_OFF_STATE_STYLE` sowie der RAG-Button-JSX-Block und die Mixed-/Spinner-Branches in Scope/Neutralize entfernt. Surgical Restore: alle Stellen wieder eingebaut, Idas Neutralize-On/Off-Emoji-Distinction und `hideRowActionButtons`/`dragDropEnabled`-Refactor beibehalten. Auch verlorenes Auto-Expand-`useEffect` für `defaultExpanded` Nodes wiederhergestellt (Test grün: 53/53). - 2026-05-22 | refactor | gateway+frontend-nyla | **RAG Cost-Estimate Währung USD→CHF** — `_costEstimate.estimateBootstrapCost()` liefert nun `estimatedChf` (statt `estimatedUsd`); Konstante `EMBEDDING_CHF_PER_MTOKEN` (Wert 0.02, Projekt-Konvention: Anbieter-Listenpreise werden direkt als CHF behandelt, identisch zu `calculatepriceCHF` in `aicorePluginOpenai.py`). Mit-aktualisiert: `routeDataSources.py` Docstring, `test_costEstimate.py` (6/6 grün), FE `CostEstimate`-Type, `DataSourceSettingsModal` Anzeige. Wiki: `b-reference/gateway/ai-agent.md` Endpoint-Doku, `TOPICS.md`, `c-work/2-build` + `4-done/2026-05-udb-datasource-settings.md`. ## 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. ## 2026-05-17 - 2026-05-17 | fix | frontend-nyla | **UDB Settings-Modal: RAG-Limits nur auf DataSource-Root** — Settings-Icon (⚙️) bleibt auf allen Nodes sichtbar, aber RAG-Limits- und Kostenschätzungs-Sektionen werden nur noch auf DataSource-Root-Nodes (Level 2 = `service`) angezeigt. Subelemente (Folder/File) können weiterhin die Connection-Settings sehen, erben aber die Walker-Limits vom Root. Neue Modal-Prop `showRagSection`. Neutralisierung/RAG-Toggle: Vererbungslogik ist korrekt (Parent aktiviert → Kinder werden mitgezogen, volle Opacity). Kein visueller Unterschied nötig — das ist gewolltes Verhalten. - 2026-05-17 | feat | gateway+frontend-nyla | **i18n für BackgroundJob-Progress-Messages (Backend-translated)** — User-Report: RAG-Page zeigte "145 Dateien verarbeitet, 106 indexiert" auch bei UI-Sprache=`en`, weil walker das Plaintext-Deutsch direkt in `BackgroundJob.progressMessage` schrieben und das Frontend es 1:1 rendert. Root-Cause: BackgroundWorker hat keinen Request-Sprach-Kontext (`_CURRENT_LANGUAGE` ist ContextVar pro Request), und `progressMessage` wird persistiert — wäre selbst dann gefroren, wenn der User später die Sprache wechselt. **Architektur (regelkonform zu `wiki/b-reference/gateway/architecture.md#i18n`):** Backend speichert strukturiert + übersetzt server-side beim Route-Read; Frontend rendert 1:1 — kein `t()` auf Backend-Werten. (1) Neue JSONB-Spalte `BackgroundJob.progressMessageData = {key, params}` (Migration `script_db_migrate_backgroundjob_progress_data.py`, additiv + idempotent). (2) `JobProgressCallback.__call__` akzeptiert `messageKey="LITERAL"` + `messageParams={…}` und schreibt beides als JSON; zusätzlich rendert es einen DE-Fallback in `progressMessage` für Logs/Audit/Legacy-Clients. (3) Alle Walker (6 RAG + `subConnectorIngestConsumer` + Trustee push/sync/import + `accountingDataSync._progress`) umgestellt — `messageKey=` ist immer ein String-Literal. (4) Key-Registrierung über string-literale `t("…")` Calls: neues `serviceKnowledge/_progressMessages.py` (Side-Effect-Import in `app.py` lifespan, 5 RAG-Keys), Trustee 14 Keys in `mainTrustee.py` — KEINE Variable-Aufrufe von `t()` (Wiki-Regel #1: `t(variable)` ist verboten). (5) Neuer Helper `i18nRegistry.resolveJobMessage(messageData)` analog zu `resolveText(value)` — der einzige zulässige `t(variable)`-Pfad, weil er in der i18n-Infrastruktur lebt; nutzt `_CURRENT_LANGUAGE` aus dem Request-Kontext und substituiert Params via `.format(**params)`. (6) `routeJobs._serialiseJob` und `routeRagInventory` rufen `resolveJobMessage` beim Read und schreiben das Ergebnis in `progressMessage` — Frontend bekommt einen fertigen, übersetzten String. (7) Frontend zurückgebaut: `utils/jobProgressUtils.ts` Helper **gelöscht**, DTOs (`useBackgroundJob`, `connectionApi`, `trusteeApi`) ohne `progressMessageData`-Feld, Render-Stellen (`RagInventoryPage`, `RagRunningBadge`, `TrusteeAccountingSettingsView`) lesen direkt `job.progressMessage`. Tests: 22/26 grün; die 4 Failures in `test_knowledge_ingest_consumer.py` sind pre-existing (verifiziert via `git stash` Diff). Frontend `npm run build` grün. Smoke: `resolveJobMessage({'key': '{n} Dateien verarbeitet, {indexed} indexiert', 'params': {'n': 145, 'indexed': 106}})` → `'145 Dateien verarbeitet, 106 indexiert'`. Wiki: neuer Abschnitt "BackgroundJob-Progress-Messages" in `b-reference/gateway/architecture.md` mit den 4 Schritten (Walker → Registrierung → Route-Resolve → Frontend-Render). - 2026-05-17 | feat | gateway+frontend-nyla | **UDB DataSource Settings (⚙️) + konfigurierbare RAG-Limits** (Plan & Build: `c-work/2-build/2026-05-udb-datasource-settings.md`). Schliesst zwei Lücken: (1) RAG-Walker-Limits (`maxBytes=200 MB` etc.) waren hartkodiert — User mit 500-MB-Folder konnte nur Code-Änderung machen; (2) FeatureDataSource hatte gar keinen Settings-Ort. **Backend**: JSONB-Spalte `settings` auf `DataSource` + `FeatureDataSource` (Migration `script_db_migrate_datasource_settings.py`, additiv + idempotent). Neues Modul `serviceKnowledge/_ragLimits.py` mit `FILES_LIMITS_DEFAULT` / `CLICKUP_LIMITS_DEFAULT` als zentrale Source-of-Truth — die alten `MAX_*_DEFAULT`-Konstanten in den 4 Walkern (`subConnectorSyncSharepoint/Kdrive/Gdrive/Clickup.py`) sind nur noch Aliase. Kritische Semantik: `getStoredOverrides(ds, kind)` liefert NUR explizit gesetzte Overrides → Walker mergen sie auf den **caller-supplied** `limits=`-Parameter, damit Test-/Caller-Overrides weiter gewinnen (`test_bootstrap_maxTasks_caps_ingestion=3` bleibt grün); `getRagLimits(ds, kind)` mergt auf Defaults → API/Cost-Estimate-Pfad. **Keine Override-Schicht, keine Resolver-Logik** — was im Modal steht, ist exakt was der Walker liest. Zwei neue Endpunkte in `routeDataSources.py`: `PATCH /api/datasources/{id}/settings` (akzeptiert nur Top-Level-Key `ragLimits`, unknown → 400, positive Ints only, Owner-only/Mandate-Admin, Audit-Log `datasource_settings_changed`) und `GET /api/datasources/{id}/cost-estimate` (indikative USD-Schätzung via `_costEstimate.py`-Heuristik: `text-embedding-3-small @ $0.02/1M Token`, `BYTES_PER_TOKEN=4`, `EXTRACTABLE_FRACTION=0.4`; Antwort trägt vollständiges `basis`-Objekt mit Annahmen/Formel/Notes). **Frontend**: Neues ⚙️-Icon pro Node im `UnifiedDataBar/SourcesTab.tsx` (vor dem 🧠) öffnet den neuen `DataSourceSettingsModal.tsx` mit drei klar abgegrenzten Sektionen: (1) **Connection** — `knowledgeIngestionEnabled`-Toggle via `patchKnowledgeConsent` (mit Confirm-Dialog beim Deaktivieren); (2) **RAG-Limits** — Felder editierbar, Bytes in MB im UI; (3) **Kostenschätzung** — refresh nach Save. Dasselbe Modal wird auf der `RagInventoryPage.tsx` vom amber Partial-Banner (`stoppedAtLimit`) via neuen "Limit anpassen"-Button geöffnet → User hat direkten Pfad vom Symptom zur Behebung. Workspace-Route `GET /api/workspace/{instanceId}/connections` liefert jetzt `knowledgeIngestionEnabled` mit, damit der Modal-Initial-Toggle korrekt vorbelegt. **Tests**: 12 neue Unit-Tests in `tests/unit/services/test_ragLimits.py` + 6 in `test_costEstimate.py` (Defaults-Isolation, partial-override, non-int dropped, doubling-formula, basis-shape) — alle grün; bestehende Bootstrap-Tests für Sharepoint/Kdrive/Gdrive/ClickUp weiter grün (caller-limits-Override respektiert). Frontend `npm run build` grün, keine neuen Lint-Errors. Doku: `b-reference/gateway/ai-agent.md` (Abschnitt "Konfigurierbare RAG-Limits"), `TOPICS.md` (neuer Eintrag). Verbleibende Hard-Limits in `subConnectorSyncOutlook/Gmail.py` haben aktuell kein UI-Override, bleiben aber als next-step (gleicher Helper anwendbar). - 2026-05-17 | feat | frontend-nyla+gateway | **Knowledge-Consent Toggle auf ConnectionsPage + Forward-Sync DataSource→Connection.** Zwei Lücken in der RAG-Consent-UX geschlossen, die zu der Beobachtung "valueon hat Index aktiviert, aber Checkbox fehlt" geführt haben: (1) Die ConnectionsPage zeigte `knowledgeIngestionEnabled` nur als generische "Ja/Nein"-Spalte der FormGeneratorTable — kein Toggle-Element. Neu: zwei CustomActions (`FaToggleOn`/`FaToggleOff`, je nach State sichtbar via `visible`-Filter), Klick ruft `patchKnowledgeConsent` → `/api/connections/{id}/knowledge-consent` und refetcht die Liste. Damit ist die UI 1:1 konsistent mit dem Master-Switch auf der RagInventoryPage (gleiches Backend-Endpoint, gleiches Icon, gleicher Confirm-Dialog beim Deaktivieren). (2) Backend: `routeDataSources._updateDataSourceRagIndex` propagierte bisher nicht auf die Parent-Connection. Neuer Helper `_ensureConnectionKnowledgeFlag(rootIf, connectionId)` setzt **forward-only** `UserConnection.knowledgeIngestionEnabled=True`, sobald min. eine DataSource auf `ragIndexEnabled=true` toggelt — kein Auto-Disable, weil der Master-Switch dem User gehört (verhindert versehentliches Zurücksetzen eines explizit gegebenen Consents, z.B. einer Connection ohne aktive DataSource, aber mit `knowledgePreferences`). Plan-Doc für die UDB-Settings-Erweiterung (Issue 2): `c-work/1-plan/2026-05-udb-datasource-settings.md`. Der Fix für Limit-Transparenz wirkte UI-seitig nicht, weil `_bootstrapJobHandler` in `subConnectorIngestConsumer.py` die Sub-Service-Results in ein wrappendes Dict packt (`{"authority", "connectionId", "sharepoint": {...}, "outlook": {...}}` für msft; analog für `drive`/`gmail`/`clickup`/`kdrive`). `routeRagInventory._buildConnectionInventory` griff aber auf Top-Level `result.stoppedAtLimit`/`indexed`/etc. zu — alle `None`. Folge: amber Banner blieb aus UND die Statistik-Zeile zeigte gar keine Zahlen ("Sync erfolgreich" ohne "— 25 unverändert"). Neuer `_flattenJobResult()`-Helper aggregiert über alle bekannten Sub-Keys (sum für counters, max für durationMs, erstes Limit-Hit für `stoppedAtLimit`/`limits`). Verifiziert anhand Job `2374aecd-3e17-460a-a13e-530f9f1115e6`: `bytesProcessed=209894527` ≥ `maxBytes=209715200`, jetzt korrekt als `stoppedAtLimit="maxBytes"` an die UI durchgereicht. Diagnose-Skript `gateway/scripts/debug_rag_job_result.py` zeigt vor/nach-Flatten und bleibt für künftige Bootstrap-Result-Debugging im Repo. - 2026-05-17 | feat | gateway+frontend-nyla | **RAG-Inventar: echte Chunks + Limit-Transparenz.** Drei Probleme behoben: (1) `routeRagInventory._buildConnectionInventory` zählte bisher `len(FileContentIndex)` (= indizierte Dateien) und labelte das im UI als "Chunks" — bei einer 99-Seiten-PDF erscheint dort statt der echten ~99 Chunks die Zahl 1. Neue `interfaceDbKnowledge.countChunksByFileIds()` macht eine einzige Aggregat-SQL `SELECT "fileId", COUNT(*) FROM "ContentChunk" WHERE "fileId" = ANY(%s) GROUP BY "fileId"` (kein Vector-Body geladen), die Response trägt jetzt `fileCount` UND `chunkCount` pro DataSource + `totalFiles/totalChunks` pro Connection. (2) `RagInventoryPage.tsx` / `connectionApi.ts` zeigen beide Werte getrennt ("25 Dateien · 1240 Chunks") mit Tooltip-Definition für Chunks (~400 Tokens). (3) **Limit-Transparenz**: SharePoint/kDrive/gDrive-Bootstrap stoppen bei den ersten Limits (`MAX_BYTES_DEFAULT=200 MB`, `MAX_ITEMS_DEFAULT=500`, `MAX_DEPTH_DEFAULT=4`, `MAX_FILE_SIZE_DEFAULT=25 MB`); ClickUp analog (`MAX_TASKS_DEFAULT=500`, `MAX_WORKSPACES_DEFAULT=3`, `MAX_LISTS_PER_WORKSPACE_DEFAULT=20`). Bisher: `return` ohne Log + ohne Marker im Bootstrap-Result → User sah "Sync erfolgreich" obwohl 706 Dateien fehlten. Fix: neuer `_recordLimitStop()`-Helper in allen 4 Connectoren setzt `BootstrapResult.stoppedAtLimit` (1. exhausted Budget), schreibt 1 WARNING in den Log und liefert das Feld + die effektiven `limits` im `_finalizeResult` Dict an `BackgroundJob.result`. `routeRagInventory` reicht `lastSuccess.stoppedAtLimit/limits/bytesProcessed` ans Frontend durch. Neuer amber `partialBanner` auf der RagInventoryPage warnt mit "Limit maxBytes=200 MB (200 MB verarbeitet) erreicht — Weitere Dateien wurden NICHT indexiert" und bietet "Erneut indexieren". Verifiziert anhand `local/logs/log_app_20260517.log`: SharePoint-Sync hat genau bei `bytesProcessed=209_894_527 ≥ MAX_BYTES_DEFAULT (209_715_200)` gestoppt (Kumulative Summe der 25 indizierten Dateigrößen = 200.17 MB). ClickUp hat bei `skippedDup=500 >= maxTasks=500` gestoppt. Outlook/Gmail brauchen das gleiche Pattern noch (haben aktuell keine harten Limits im Code, daher kein Bug, aber wenn welche kommen → gleicher Helper). - 2026-05-17 | fix | gateway | **Secrets Decryption TTL-Cache** (`gateway/modules/shared/configuration.py`): `decryptValue()` cached jetzt erfolgreich entschlüsselte Plaintexts process-wide für 60 s (Key = Ciphertext, thread-safe, `clearDecryptionCache()` für Rotation/Tests). Root-Cause aus S7-Smoke-Test (`local/logs/log_app_20260517.log:609`): RAG-Inventory-Polling + paralleler Walker-Burst triggerte für `system`/`DB_PASSWORD_SECRET` >10 Decrypts/s, das Brute-Force-Schutz-Rate-Limit warf `ValueError: Decryption rate limit exceeded` → `routeRagInventory._getInventoryPlatform` HTTP 500. Hot-Path war `mainBackgroundJobService._getDb()`, das pro Call `APP_CONFIG.get("DB_PASSWORD_SECRET")` evaluiert (eager arg eval), bevor `getCachedConnector` überhaupt seinen Wrapper-Cache prüfen kann. Cache-Hit umgeht das Rate-Limit (kein neuer Krypto-Op, nur Re-Read eines bereits autorisierten Plaintexts); Cache-Miss konsumiert weiter Rate-Budget — die Schutzfunktion gegen wiederholt falsche Decrypts bleibt damit erhalten. Wirkt global für alle `_SECRET`-Reader (`auditLogger`, `routeI18n`, alle Feature-Interfaces), nicht nur für den BackgroundJobService. - 2026-05-17 | refactor | gateway | PostgreSQL Connection Pool — Steps S3–S6 abgeschlossen (`c-work/2-build/2026-05-postgres-connection-pool.md`). **S3**: `getCachedConnector` Docstring präzisiert (Cache = Wrapper-Recycling + DB-Init-Spam-Schutz, Pool = echte Connection-Verwaltung). **S4**: Shutdown-Hook `closeAllPools()` in `gateway/app.py` lifespan als letzter Schritt nach Feature-`onStop`-Hooks. **S5**: Neuer Test-File `gateway/tests/unit/connectors/test_connectorDbPostgre_pool.py` mit 6 Concurrency-Tests gegen live-Postgres (auto-skip wenn keine DB erreichbar): 50 Threads × 20 Reads (0 Errors), 20 Threads × 50 Reads (p99 < 5 s), interleaved load/save, `statement_timeout=500ms` triggert `QueryCanceled` und gibt Connection sauber zurück, Pool-Identity pro (host, db, port), `closeAllPools` leert Registry. Beim ersten Lauf entdeckt: psycopg2-Pool wirft `PoolError` sofort bei Exhaustion statt zu blockieren → `borrowConn()` um bounded Wait-Retry erweitert (`_BORROW_WAIT_TIMEOUT_S=30s`, `_BORROW_WAIT_BACKOFF_S=50ms`). Alter `test_connectorDbPostgre_failLoud.py` auf das neue `borrowConn`-Mocking umgestellt (alle 6 weiter grün). **S6**: Regression-Run: 639/656 unit grün (vorher 638) — der eine durch den Refactor verursachte Fail (`test_folder_crud._FakeDb` brauchte `borrowCursor`-Stub) gefixt, die übrigen 17 Failures sind pre-existing RAG/Adapter/Workflow-Drift ohne Pool-Bezug. 76/79 integration grün (3 pre-existing Trustee-Workflow-Fails). Backward-Compat-Stub `borrowCursor` auch in `test_folderRbac._FakeDb` ergänzt. Offen: **S7** (manueller 1 h Smoke-Test, Anleitung in der Plan-Doc) und **S8** (`b-reference/platform/database-architecture.md`). - 2026-05-17 | refactor | gateway | **CORE**: PostgreSQL Connection Pooling implementiert (S1+S2 von `c-work/2-build/2026-05-postgres-connection-pool.md`). Root-Cause: `DatabaseConnector` hielt **eine** psycopg2-Connection pro Instanz und teilte sie via `getCachedConnector(...)` über den gesamten FastAPI-Thread-Pool **und** über asyncio-Tasks. psycopg2-Connections sind NICHT thread-safe — paralleler Zugriff aus z.B. RAG-Polling, `routeI18n`, `mainBackgroundJobService` und Hintergrund-Jobs führte zu `another command in progress` und — viel schlimmer — unendlichem Block in `recv()` (kein `statement_timeout` gesetzt). Das `self._lock` in `DatabaseConnector` war zwar deklariert, aber **nirgends** verwendet. Folge: Backend hat sich komplett aufgehängt, sogar die i18n-API für die Login-Seite lieferte nichts mehr. **Lösung**: Neuer `_PoolRegistry` (`gateway/modules/connectors/connectorDbPostgre.py`) mit `psycopg2.pool.ThreadedConnectionPool` pro `(host, db, port)`, lazy-init, thread-safe (`min=2`, `max=20` per `DB_POOL_MAX_CONN`, `statement_timeout=30s`, `connect_timeout=10s`). Jede DB-Operation borrowed eine Connection via neuem `db.borrowConn()`-Context-Manager (auto-commit/rollback/return); `db.borrowCursor()` als 1:1-Ersatz für das alte `with db.connection.cursor() as cursor:` Pattern. `getCachedConnector` bleibt API-kompatibel, Connector-Wrapper sind nun leichtgewichtig (kein per-instance Socket). Backward-Compat-Shim `db.connection` (no-op `commit()`/`rollback()`/`closed`, RuntimeError auf `cursor()` damit kein Stillschweigend-Bruch). 18 interne Cursor-Stellen + ~30 externe (interfaceRbac, interfaceDbManagement, interfaceDbBilling, interfaceFeatureRealEstate, routeWorkflowDashboard, routeHelpers, gdprDeletion, dbMultiTenantOptimizations, _featureSubAgentTools, scripts/stage0) auf das neue Pattern umgestellt. `_ensure_connection` als No-Op gehalten (Pool re-connectet selbständig). Public Shutdown-Hook `closeAllPools()` wartet auf S4 (FastAPI-Lifespan-Integration). Working-Doc: `c-work/2-build/2026-05-postgres-connection-pool.md` (Step S1+S2). ## 2026-05-16 - 2026-05-16 | fix | frontend-nyla | `AddConnectionWizard` Admin-Consent-Button war NoOp: `ConnectionsPage` hat das `onMsftAdminConsent`-Prop nie übergeben, der `?.()`-Aufruf im Wizard wurde schweigend übersprungen und der Wizard sprang einfach zum nächsten Schritt. Im Backend kam kein einziger Request an `/api/msft/adminconsent` an — User aus Multi-Tenants, in denen die App-Registration noch nie admin-consented wurde, hingen unauflöslich im "Anforderung gesendet"-Screen fest. Fix: `ConnectionsPage._handleMsftAdminConsent` öffnet jetzt ein Popup auf `/api/msft/adminconsent`, und das Prop wird an den Wizard durchgereicht. Doc-Sync: `d-guides/microsoft-entra-registration-checklist.md` (Redirect-URI-Tabelle um `/api/msft/adminconsent/callback` ergänzt — Fehlen führte zu `AADSTS50011`; Multi-Tenant-Hinweis: Admin Consent gilt pro Tenant). ## 2026-05-15 - 2026-05-15 | fix | gateway | `subAiCallLooping.py`: fehlender Top-Level-Import `extractJsonString` (und `repairBrokenJson`) im Modul-Header ergänzt. Bei `getContexts()`-Success (jsonParsingSuccess=True, overlapContext='') flog ein `NameError`, wurde vom zu breiten `except Exception` als "completePart not serializable" geschluckt, hat `mergeFailCount` hochgezählt und nach 3 Iterationen leeren String zurückgegeben — Folge: `subStructureFilling` bekam `""` und meldete `tryParseJson failed: Expecting value` / `No elements produced`. Zusätzlich `except`-Hygiene: `(json.JSONDecodeError, KeyError, TypeError)` → WARNING + retry (erwartete Daten-Probleme), generischer `except Exception` → ERROR mit `exc_info=True` und Re-Raise (Code-Bugs werden nicht mehr 3× silently verfressen). Logging enthält jetzt den Exception-Typ als Prefix. - 2026-05-15 | feat | gateway | Feature Data Sub-Agent Phase 1.5 + Phase 2 abgeschlossen. **Eval-Harness** (`gateway/tests/eval/runTrusteeBenchmark.py`, standalone Runner) fährt 19 Goldstandard-Fragen × 3 Modi (baseline / phase1 / phase2) mit echten AI-Calls gegen einen `FakeFeatureDataProvider` (in-memory `BenchmarkFixture`, kein DB-Setup) und schreibt Markdown + JSON-Report nach `local/notes/`. **Trustee-Ontologie** (`gateway/modules/features/trustee/trusteeOntology.py`): 6 Entities (Account + BankAccount/CashAccount-Spezialisierungen, AccountBalance, JournalEntry/Line), 3 Relations, 6 Constraints (NEVER_AGGREGATE auf alle 4 Balance/Total-Felder, REQUIRES_FILTER_ON für AccountBalance, PREFERRED_TABLE_FOR_INTENT), 8 CanonicalQueryPatterns. **OntologyToPromptCompiler** (`ontologyToPromptCompiler.py`) rendert die Descriptor deterministisch als kompakten Prompt-Block. `mainTrustee.getAgentOntology()` Hook + `_AGENT_DOMAIN_HINTS_LEGACY` geparkt. `featureDataAgent._buildSchemaContext` nutzt `_loadFeatureOntologyBlock()` bevorzugt (Fallback auf `_loadFeatureDomainHints`); `_buildValidatorForFeature()` verkabelt Validator+Ontologie automatisch (Single Source of Truth). Eval-only Override `POWERON_DISABLE_FEATURE_ONTOLOGY=1`. **Side-Fix**: `aggregateTable` exponiert jetzt `filters` im Tool-Schema (war Code-Bug, blockierte SUM-pro-Konto-Anfragen); Validator validiert filters analog zu queryTable. **Resultate** (`local/notes/trustee-benchmark-20260515-161126.md`): Accuracy Baseline 89.5 % → Phase 1 94.7 % (Validator triggert in q13 2× und steuert Agent eigenständig auf queryTable um) → Phase 2 100 % (Ontologie verhindert q09-Halluzination "creditTotal statt closingBalance" präemptiv). +18 neue Unit-Tests in `test_trusteeOntology.py` (Descriptor, Compiler, Validator-Integration, mainTrustee-Hook, _buildValidatorForFeature); `test_featureDataAgent_schema.py` um Ontology-Pfad + Eval-Override erweitert. Working-Doc verschoben nach `c-work/3-validate/2026-05-feature-data-agent-ontology-and-repair.md`. Doc-Sync: `b-reference/gateway/ai-agent.md` (neuer Abschnitt "FeatureDataAgent: Query-Repair-Loop + Ontologie"), `b-reference/gateway/features/trustee.md` (neuer Abschnitt "Agent-Ontologie"), `d-guides/coding-conventions.md` (Migrations-Muster `getAgentDomainHints` → `getAgentOntology`). - 2026-05-15 | feat | gateway | Feature Data Sub-Agent Phase 1 (Query-Repair-Loop) implementiert. Neuer `QueryValidator` (`gateway/modules/serviceCenter/services/serviceAgent/queryValidator.py`) prüft `browseTable`/`queryTable`/`aggregateTable`-Calls **vor** dem Provider gegen das Pydantic-Modell und liefert strukturierte Repair-Hints statt SQL-Exceptions. Vier Constraint-Klassen: `FIELD_NOT_FOUND` (mit Fuzzy-Suggestion via difflib), `OPERATOR_INCOMPATIBLE` (LIKE/ILIKE nur auf String, `>=`/`<=` nur auf Numerisch), `INVALID_AGGREGATE_TARGET` (SUM/AVG auf `*Balance`/`*Total` -- fängt die flagship Trustee-Halluzination `SUM(closingBalance) × 7 Jahre × 13 Perioden ≈ 90× Saldo` deterministisch ab), `ORDER_BY_INVALID`. Neues Datenmodell `datamodelOntology.py` mit `QueryValidationError`, `Constraint`, `OntologyDescriptor` (Phase-2-Ready); Phase-2-Override via Ontologie-Constraints schon eingebaut, aber inaktiv solange keine Ontologie pro Feature definiert ist. `ToolResult.errorDetails: Optional[Dict]` und `ToolCallLog.validationFailureCode: Optional[str]` zu `datamodelAgent.py` ergänzt (LLM bekommt machine-readable Repair-Hint, Audit-Log behält Kurz-String). `agentLoop._computeRepairCounters` aggregiert pro Sub-Agent-Run `validationFailures`, `repairAttempts`, `successAfterRepair` aus den `ToolCallLog`-Einträgen und legt sie auf `AgentTrace` + ins `AGENT_SUMMARY`-Event (Eval-Harness-Ready). Tool-Descriptions der drei Sub-Agent-Tools erklären das `errorDetails`-Format explizit. 35 neue Unit-Tests (24 für QueryValidator, 3 für Tool-Integration, 8 für Trace-Aggregation), alle 62 bestehenden serviceAgent-Tests bleiben grün. Working-Doc: `c-work/2-build/2026-05-feature-data-agent-ontology-and-repair.md`. - 2026-05-15 | chore | wiki | Plan-Finalisierung: (1) RAG C&C Unification+Implementation → status `done`, alle Testplan-Einträge manuell verifiziert, `b-reference/platform/neutralization.md` (DataSource=SSoT, kein neutralizeBeforeEmbed, Tree-Vererbung) und `b-reference/frontend-nyla/architecture.md` (RagInventoryPage, UDB 4. Button, RagRunningBadge, AddConnectionWizard) aktualisiert, beide Pläne nach `4-done/` verschoben. (2) Enterprise Subscription → `4-done/`. (3) FormGenerator Grouping → `4-done/`. (4) STT Benchmark Page implementiert (Backend + Frontend, SysAdmin-only). - 2026-05-15 | docs | wiki | Feature Data Sub-Agent Plan (`c-work/1-plan/2026-05-feature-data-agent-ontology-and-repair.md`) gegen Codebase verifiziert und um 5 Klärungen geschärft: (A) `ToolResult.errorDetails: Optional[Dict]` neu in `datamodelAgent.py` statt JSON-in-error-String; (B) Repair-Telemetrie wandert von `aiAuditLogger` in `AgentTrace`/`ToolCallLog` (per-Run-Aggregation passt nicht in per-Call-Audit-Log); (C) Eval-Fixture als Python-Loader + Pydantic/`recordCreate` statt `fixture.sql` (konsistent mit Trustee-Tests); (D) AC#3 als `repairConversionRate ≥ 0.8` über Repair-Triggered-Subset präzisiert, nicht als End-Accuracy; (E) Doc-Sync-Liste um `b-reference/gateway/features/trustee.md` und `d-guides/coding-conventions.md` erweitert. Plan ist damit umsetzungsreif für Phase 1. ## 2026-05-16 - 2026-05-16 | fix | gateway, frontend-nyla | RAG-Reindex hängt nach manuellem Trigger im Status RUNNING/PENDING, ohne dass ein Walker startet — UI zeigt endlosen Spinner bis Zombie-Killer nach 30 min eingreift. Root Cause: drei Job-Submit-Routen waren sync (`def`, nicht `async def`) und enthielten ein "loop = asyncio.get_event_loop(); ... except RuntimeError: asyncio.run(_enqueue())"-Konstrukt. FastAPI führt sync-Routes im Worker-Threadpool aus → kein laufender Event-Loop → `asyncio.run` öffnet einen temporären Loop, in dem `startJob` `_runJob` via `create_task` registriert → `asyncio.run` schliesst den Loop sofort nach Return → Task wird gecancelled, bevor er ein einziges Statement ausführt. Daily-Resync funktionierte, weil er aus APScheduler im Main-Loop läuft. Fix: alle drei Routen auf `async def` umgestellt und `await startJob(...)` direkt verwendet — keine Loop-Hack-Logik mehr. Betroffene Routes: `routeRagInventory._reindexConnection`, `routeDataSources._updateDataSourceRagIndex` (UDB-Toggle 🧠), `routeDataConnections._updateKnowledgeConsent` (globaler Consent-Toggle). Zusätzlich: Inventar-Response liefert jetzt `lastSuccess` mit `{jobId, finishedAt, indexed, skippedDuplicate, skippedPolicy, failed, durationMs}` plus `lastError.finishedAt` — Frontend zeigt grünes Erfolgs-Banner mit Stats + relativem Zeitstempel ("Sync erfolgreich vor 3 Min — 4 neu indexiert · 183 unverändert (198.3s)") statt nur leerem Spinner; Error wird nur angezeigt wenn er neuer ist als der letzte Erfolg, sonst gewinnt das Success-Banner. `RagInventoryPage` und `RagRunningBadge` pollen jetzt schnell (5s) während Jobs laufen und langsam (60s) im Idle. Badge zeigt einen 4s-"Sync abgeschlossen ✓"-Toast wenn der letzte aktive Job verschwindet. Hängender Job 7ee09ca7 wurde via Onetime-Skript gekillt. ## 2026-05-15 - 2026-05-15 | fix | gateway | RAG-Sync Log-Spam + DB-Reinit-Storm. Root Cause: `mainBackgroundJobService._getDb()` rief bei jeder Job-CRUD-Operation `DatabaseConnector(...)` direkt auf — pro Reindex-Klick mehrfach (`submitJob`, `_updateJob`, `getJob`, `listJobs`, `cancelJob`). Jeder Konstruktor-Call lief komplett durch `_create_database_if_not_exists` → `_create_tables` → neue psycopg2-Connection → `_initializeSystemTable` und loggte "PostgreSQL database system initialized successfully" auf INFO. Folgen: Log-Lärm bei jedem Reindex (~5-10 INFO-Zeilen) + echter DB-Overhead pro Job-Operation. Fix: (1) `_getDb()` nutzt jetzt `getCachedConnector()` (Cache-Key = host/db/port, FIFO-Eviction nach 32, Thread-safe), pro Worker-Lifetime nur noch 1× Init für die jobs-DB. Smoke-Test bestätigt: 3× `_getDb()` → identische Instanz. (2) Init-Log in `connectorDbPostgre.initDbSystem` von INFO auf DEBUG gesenkt + um `db/host/port`-Felder ergänzt — wenn doch mal ein Connector neu erzeugt wird, ist das im Steady-State nicht mehr wert auf INFO-Level zu schreien. Andere Hot-Path-Komponenten (`interfaceDbApp`, `interfaceDbKnowledge`, `aiAuditLogger`) nutzten den Cache bereits korrekt; `auditLogger` ist Singleton, also unkritisch. ## 2026-05-14 - 2026-05-14 | fix | gateway | RAG-Sync: Zombie-Job-Handling + Walker-Timeouts. (1) Neuer 5-Minuten-Cron `background_jobs.zombie_killer` in `mainBackgroundJobService.killZombieJobs()` markiert RUNNING-Jobs ohne Progress-Update >30 min als ERROR und beendet so auf Dauer hängende Bootstraps. (2) Neuer Helper `subWalkerHelpers.py` (`downloadWithTimeout` 60s, `extractWithTimeout` 90s via `asyncio.to_thread`+`wait_for`, `ingestWithTimeout` 60s, `logItemStart`) — alle Walker (sharepoint, kdrive, gdrive, gmail, outlook, clickup) loggen jetzt vor jedem Item `walker.item.start service=… path=…`, sodass das letzte solche Log bei einem Hang das verursachende Item exakt benennt. Sync-Extraktion läuft auf Worker-Thread, blockiert das Event-Loop nicht mehr. (3) Aktive Zombies (msft 81min, infomaniak 81min, clickup 81min) wurden via Onetime-Skript gekillt. - 2026-05-14 | docs | wiki | WW Stephan-Lieferablage: `c-work/2-build/walderwyss-stephan-output/` (Stephan legt nichts in pamocreate ab). Auftrags-PDFs Homepage + Infomaniak für Stephan. - 2026-05-14 | docs | wiki | Lawyer-Feature geplant (Mandatsvorbereitung + Dashboards für Anwaltskanzleien), ausgelöst durch WW-Pitch-Bedarf (David Vasella). Working-Doc in `c-work/1-plan/2026-05-lawyer-feature.md`. ## 2026-05-12 - 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 `