data source flag cascading issue
This commit is contained in:
parent
656cf3eacb
commit
57579141cf
7 changed files with 411 additions and 4 deletions
|
|
@ -49,6 +49,7 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
|
|||
| Automation Unification | c-work/1-plan/2026-04-automation-unification.md | Refactoring v1/v2/Workspace |
|
||||
| Unified Knowledge Indexing (RAG) | c-work/4-done/2026-04-id-unified-knowledge-indexing-rag-concept.md | Ingestion-Fassade `requestIngestion`, Idempotenz, Connector-Lifecycle |
|
||||
| RAG Consent & Control | c-work/2-build/2026-05-rag-consent-and-control-implementation.md | Datenzentrierte Steuerung: `DataSource.ragIndexEnabled`, Walker-Refactor, Job-Cancel, UDB-Toggle, RagInventoryPage |
|
||||
| UDB DataSource Settings (RAG-Limits) | c-work/2-build/2026-05-udb-datasource-settings.md | Settings-Icon ⚙️ pro Tree-Node, `DataSource.settings.ragLimits` als alleinige Quelle (kein Override-Layer), PATCH `/api/datasources/{id}/settings`, GET `/api/datasources/{id}/cost-estimate`, indikative USD-Schätzung |
|
||||
| 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 |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- status: canonical -->
|
||||
<!-- lastReviewed: 2026-05-15 -->
|
||||
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification); gateway/modules/features/teamsbot/service.py (Hybrid Agent Escalation 2026-04-24); Typed Action Architecture Phasen 1-5; featureDataAgent domain hints hook 2026-04-27; central parameterValidation + DatabaseQueryError 2026-04-28; OpenAI temperature contract for GPT-5.x / o-series 2026-04-28; Voice STT speechToText params note 2026-05-10; RAG Consent & Control Unification (Phases A-D) 2026-05-12; Zombie-Killer + Walker-Timeouts 2026-05-14; FeatureDataAgent Query-Repair-Loop + Ontology layer 2026-05-15 -->
|
||||
<!-- lastReviewed: 2026-05-17 -->
|
||||
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification); gateway/modules/features/teamsbot/service.py (Hybrid Agent Escalation 2026-04-24); Typed Action Architecture Phasen 1-5; featureDataAgent domain hints hook 2026-04-27; central parameterValidation + DatabaseQueryError 2026-04-28; OpenAI temperature contract for GPT-5.x / o-series 2026-04-28; Voice STT speechToText params note 2026-05-10; RAG Consent & Control Unification (Phases A-D) 2026-05-12; Zombie-Killer + Walker-Timeouts 2026-05-14; FeatureDataAgent Query-Repair-Loop + Ontology layer 2026-05-15; UDB DataSource Settings + configurable RAG-Limits 2026-05-17 -->
|
||||
|
||||
# AI Agent & Knowledge Store
|
||||
|
||||
|
|
@ -384,6 +384,32 @@ Vor jedem Item ruft der Walker `logItemStart(service, path, sizeBytes, mime)`
|
|||
- Eltern-DataSource mit explizitem `ragIndexEnabled` vererbt an Kind-Pfade
|
||||
- Gleiches Pattern wie `neutralize` und `scope`
|
||||
|
||||
#### Konfigurierbare RAG-Limits (ab 2026-05-17)
|
||||
|
||||
Walker-Limits (`maxBytes`, `maxFileSize`, `maxItems`, `maxDepth` für File-Walker; `maxTasks`, `maxWorkspaces`, `maxListsPerWorkspace` für ClickUp) sind nicht mehr in den Walker-Modulen hartkodiert, sondern aus zwei Quellen zusammengesetzt:
|
||||
|
||||
1. **Zentraler Default** — `modules/serviceCenter/services/serviceKnowledge/_ragLimits.py` (`FILES_LIMITS_DEFAULT`, `CLICKUP_LIMITS_DEFAULT`). Die alten `MAX_*_DEFAULT`-Konstanten in den Walkern sind dünne Aliase und bleiben für Rückwärtskompatibilität bestehen.
|
||||
2. **DataSource-Override** — `DataSource.settings.ragLimits.<key>` (oder `FeatureDataSource.settings`). JSONB-Spalte, optional, vollständig vom User editierbar.
|
||||
|
||||
**Semantik** (kritisch, weil leicht zu missverstehen):
|
||||
- `_ragLimits.getStoredOverrides(ds, kind)` liefert NUR die explizit gesetzten Overrides → Walker mergen sie auf den **caller-supplied** `limits=`-Parameter (Test-Override gewinnt weiterhin).
|
||||
- `_ragLimits.getRagLimits(ds, kind)` mergt Overrides auf die globalen Defaults → API/Cost-Estimate-Pfad.
|
||||
- **Keine Override-Schicht, kein Resolver, keine Vererbung** für `ragLimits`. Was im Settings-Modal steht, ist exakt das, was der Walker liest.
|
||||
|
||||
**Settings-API:**
|
||||
|
||||
| Methode | Pfad | Zweck |
|
||||
|---------|------|-------|
|
||||
| `PATCH` | `/api/datasources/{id}/settings` | Partial-Update auf `DataSource.settings`/`FeatureDataSource.settings`. Nur Top-Level-Key `ragLimits` akzeptiert; unknown keys → 400. Audit-Log: `AuditCategory.PERMISSION/datasource_settings_changed`. Owner-only (Personal); für Mandate-Scope auch Mandate-Admin. |
|
||||
| `GET` | `/api/datasources/{id}/cost-estimate` | Indikative USD-Schätzung für einen Voll-Sync mit den aktuellen Limits. Antwort: `{estimatedTokens, estimatedUsd, basis: {kind, limits, assumptions, notes}, sourceId}`. Default-Heuristik: `text-embedding-3-small` @ `$0.02 / 1M Token`, `BYTES_PER_TOKEN=4`, `EXTRACTABLE_FRACTION=0.4`. Quelle: `_costEstimate.py`. |
|
||||
|
||||
**UDB Settings-Modal** (`DataSourceSettingsModal.tsx`): einziges UI für DataSource-Settings, geöffnet via ⚙️-Icon pro Tree-Node im `SourcesTab`. Drei Sektionen:
|
||||
1. **Connection** — `knowledgeIngestionEnabled` Master-Toggle (= `patchKnowledgeConsent`-Pfad).
|
||||
2. **DataSource RAG-Limits** — Editierbare Felder; Bytes-Limits in MB im UI, in Bytes am Backend.
|
||||
3. **Kostenschätzung** — Indikativ, nicht-verbindlich, ändert sich live nach `PATCH /settings`.
|
||||
|
||||
Das gleiche Modal wird auf der `RagInventoryPage` aus dem Partial-Banner (`stoppedAtLimit`) via "Limit anpassen"-Button geöffnet → User hat direkten Pfad vom Symptom zur Behebung.
|
||||
|
||||
---
|
||||
|
||||
## Teamsbot-Integration (Hybrid-Routing, kein eigenes Toolset)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- status: canonical -->
|
||||
<!-- lastReviewed: 2026-05-10 -->
|
||||
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) + Typed Action Architecture Phasen 1-5 + Infomaniak-Connector (2026-04-28) + MSFT/Google Calendar+Contacts + Reconnect (2026-04-28) + Google Voice STT/TTS (2026-05-10) -->
|
||||
<!-- lastReviewed: 2026-05-17 -->
|
||||
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) + Typed Action Architecture Phasen 1-5 + Infomaniak-Connector (2026-04-28) + MSFT/Google Calendar+Contacts + Reconnect (2026-04-28) + Google Voice STT/TTS (2026-05-10) + BackgroundJob i18n payload (2026-05-17) -->
|
||||
|
||||
# Gateway -- Architektur
|
||||
|
||||
|
|
@ -163,6 +163,45 @@ Felder vom Typ `TextMultilingual` speichern Benutzertexte mehrsprachig. `xx` ist
|
|||
|
||||
**Entries-Identitaet:** Ein Entry wird durch `(key, context)` eindeutig identifiziert — derselbe Text kann mit verschiedenen Contexts existieren.
|
||||
|
||||
### BackgroundJob-Progress-Messages
|
||||
|
||||
Hintergrundjobs laufen ausserhalb des Request-Kontexts und haben deshalb keinen `_CURRENT_LANGUAGE`-Wert. Walker schreiben deshalb einen **strukturierten i18n-Payload** in die DB und der Route-Handler uebersetzt server-side beim Read -- der Frontend ruft `t()` nie auf Backend-supplied Werten auf (Regel #2).
|
||||
|
||||
**1. Walker schreibt strukturiert** (`gateway/modules/serviceCenter/.../subConnectorSync*.py`):
|
||||
|
||||
```python
|
||||
progressCb(
|
||||
0,
|
||||
messageKey="{n} Dateien verarbeitet, {indexed} indexiert",
|
||||
messageParams={"n": processed, "indexed": result.indexed},
|
||||
)
|
||||
```
|
||||
|
||||
`messageKey` ist ein **String-Literal** (Pflicht, damit es scanbar bleibt). `JobProgressCallback` speichert `{key, params}` in `BackgroundJob.progressMessageData` (JSONB) und einen deutschen Best-Effort-Fallback in `progressMessage` (fuer Logs/Audit/Legacy).
|
||||
|
||||
**2. Key-Registrierung** -- `progressCb(..., messageKey="…")` durchlaeuft NICHT `t()`, deshalb braucht jeder Key ein **string-literales** `t("…")` in der Feature-Registrierungsdatei (siehe `serviceKnowledge/_progressMessages.py`, `features/trustee/mainTrustee.py`). KEINE Schleifen ueber Listen mit `t(variable)` -- jede Zeile muss `t("LITERAL")` sein, sonst nimmt der Boot-Scan den Key nicht in `UiLanguageSet` auf.
|
||||
|
||||
**3. Route uebersetzt server-side** (`routeJobs._serialiseJob`, `routeRagInventory`):
|
||||
|
||||
```python
|
||||
from modules.shared.i18nRegistry import resolveJobMessage
|
||||
|
||||
out["progressMessage"] = (
|
||||
resolveJobMessage(j.get("progressMessageData"))
|
||||
or j.get("progressMessage", "")
|
||||
)
|
||||
```
|
||||
|
||||
`resolveJobMessage(messageData)` ruft intern `t(key)` mit der Request-Sprache und substituiert die Params. Das ist analog zu `resolveText(value)` -- es ist der **einzige** zulaessige `t(variable)`-Pfad, weil er innerhalb der i18n-Infrastruktur liegt.
|
||||
|
||||
**4. Frontend rendert 1:1** -- `progressMessage` ist bereits uebersetzt, die Render-Stellen lesen das Feld direkt:
|
||||
|
||||
```tsx
|
||||
<span>{conn.runningJobs[0].progressMessage || t('Synchronisierung läuft...')}</span>
|
||||
```
|
||||
|
||||
Das `t('Synchronisierung läuft...')` ist ein lokales UI-Fallback (string-literal, ok), das `progressMessage` aus dem Backend geht nicht durch `t()`.
|
||||
|
||||
## Feature: Trustee -- Daten-Tabellen-Endpunkte
|
||||
|
||||
Alle 13 Trustee-Tabellen sind ueber paginierte, RBAC-gefilterte GET-Endpunkte abrufbar. Die sechs CRUD-Modelle (`TrusteeOrganisation`, `TrusteeRole`, `TrusteeAccess`, `TrusteeContract`, `TrusteeDocument`, `TrusteePosition`) haben weiterhin die etablierten REST-Routen; sieben weitere (zuvor nur als JSON-Export oder Aggregat-Endpunkt verfuegbar) wurden ergaenzt:
|
||||
|
|
|
|||
150
c-work/1-plan/2026-05-udb-cascade-inherit.md
Normal file
150
c-work/1-plan/2026-05-udb-cascade-inherit.md
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<!-- status: plan -->
|
||||
<!-- started: 2026-05-18 -->
|
||||
<!-- component: gateway | frontend-nyla -->
|
||||
|
||||
# UDB Cascade-Inherit für DataSource-Flags (neutralize, ragIndexEnabled, scope)
|
||||
|
||||
## Beschreibung und Kontext
|
||||
|
||||
Das aktuelle UDB-Tree hat ein Konsistenz-Loch: Die drei Flags `neutralize`, `ragIndexEnabled`, `scope` auf `DataSource` sind als nicht-nullable Felder modelliert (`bool`/`str` mit Default `false`/`'personal'`). Das Frontend interpretiert "kein DataSource-Record" als "vererbt", aber sobald ein Knoten einen eigenen Record hat, bleibt sein Wert **explizit gesetzt** — auch dann, wenn der Parent später getoggelt wird.
|
||||
|
||||
Beispiel-Szenario, das aktuell falsch läuft:
|
||||
1. User toggled SharePoint (Level 2) → `neutralize=true`
|
||||
2. User toggled Folder1 (Level 3 unter SharePoint) → `neutralize=false` (explizit)
|
||||
3. User klickt SharePoint nochmal → `neutralize=false`
|
||||
4. User klickt SharePoint nochmal → `neutralize=true`. Erwartung: alle Folders auch true. **Aber**: Folder1 bleibt explizit `false` und überschreibt die Vererbung.
|
||||
|
||||
Die User-Direktive: Bei Status-Änderung muss eine **Cascade** durch alle verschachtelten Subobjekte mit explizitem Wert für dieses Flag laufen — diese werden auf "vererbt" zurückgesetzt. Subobjekte, die bereits "vererbt" sind, werden ignoriert (sie folgen automatisch).
|
||||
|
||||
## Ziele
|
||||
|
||||
1. Klare 3-Wertige Semantik: `null` (vererbt) | `true` | `false` für `neutralize` und `ragIndexEnabled`
|
||||
2. Scope analog: `null` (vererbt) | `'personal'` | `'mandate'` | `'platform'`
|
||||
3. Cascade beim Toggle resettet alle Descendants mit explizitem Wert für das **gleiche** Flag
|
||||
4. Andere Flags des Descendants bleiben unangetastet (z.B. Toggle `neutralize` lässt `ragIndexEnabled` der Descendants in Ruhe)
|
||||
5. Walker-Logik (RAG-Engine, Neutralisierungs-Pipeline) berechnet Effective-Value via Path-Traversal
|
||||
6. Kein manueller "Reset to inherit" im UI nötig — Vererbung wird ausschliesslich durch Parent-Toggle wiederhergestellt
|
||||
|
||||
## Architektur-Entscheidungen
|
||||
|
||||
### Datenmodell
|
||||
|
||||
`DataSource` (auch `FeatureDataSource` analog):
|
||||
- `neutralize: Optional[bool]` (default `NULL` = inherit)
|
||||
- `ragIndexEnabled: Optional[bool]` (default `NULL` = inherit)
|
||||
- `scope: Optional[str]` (default `NULL` = inherit; legacy default `'personal'` ändern)
|
||||
|
||||
Migration: bestehende Records behalten ihre expliziten Werte (`true`/`false`/`'personal'`). Erst neue Records starten mit `NULL`. Damit ist die Migration nicht-destruktiv.
|
||||
|
||||
### Effective-Value Computation (Walker + Frontend)
|
||||
|
||||
```
|
||||
effective(node, flag) = node.flag if node.flag is not None
|
||||
else effective(parent, flag)
|
||||
else False # default für Root mit NULL
|
||||
```
|
||||
|
||||
Path-Traversal: ein DS-Record gehört zu `(connectionId, path)`. Parent-Pfade werden via String-Operationen ermittelt (`/foo/bar` → Parent `/foo` → Root `/`). Der Root einer Connection ist `path='/'` mit `sourceType=<service>`.
|
||||
|
||||
### Cascade beim Toggle
|
||||
|
||||
PATCH-Endpoint:
|
||||
- `PATCH /api/datasources/{id}/{flag}` mit Body `{value: bool | null}`
|
||||
- Bei `null` → reset auf inherit (kein Cascade nötig, da der Knoten selbst dann erbt)
|
||||
- Bei `true`/`false` → Cascade:
|
||||
1. Setze `target.{flag} = value` für den geklickten Knoten
|
||||
2. Finde alle Descendant-DataSources: `connectionId == target.connectionId AND path STARTSWITH target.path AND path != target.path`
|
||||
3. Für jeden Descendant mit `descendant.{flag} IS NOT NULL` → setze `descendant.{flag} = NULL`
|
||||
4. Audit-Log: `datasource_cascade_reset` mit Anzahl betroffener Records
|
||||
|
||||
### Walker-Integration
|
||||
|
||||
RAG-Walker (`subConnectorSync*.py`) und Neutralisierungs-Pipeline rufen aktuell direkt `ds.neutralize` / `ds.ragIndexEnabled` ab. Das wird zu einem Helper `getEffectiveFlag(ds, flag, allDataSources)`:
|
||||
|
||||
```python
|
||||
def getEffectiveFlag(ds, flag, allDsByConnection):
|
||||
"""Path-traversal von ds aufwärts bis zum ersten DS mit explizitem Wert."""
|
||||
current = ds
|
||||
while current is not None:
|
||||
value = getattr(current, flag)
|
||||
if value is not None:
|
||||
return value
|
||||
current = _findParentDs(current, allDsByConnection)
|
||||
return False # default
|
||||
```
|
||||
|
||||
Wichtig: Der Walker iteriert über Files/Folders, nicht über DataSources. Aber die Entscheidung "neutralisieren ja/nein" wird pro Item getroffen. Der Item-Path wird auf den nächsten DS mit explizitem Flag-Wert gemappt. Falls kein expliziter Wert in der Kette, wird der Root-DS verwendet (`path='/'`).
|
||||
|
||||
### Frontend
|
||||
|
||||
`UdbDataSource`-Interface:
|
||||
- `neutralize: boolean | null`
|
||||
- `ragIndexEnabled: boolean | null`
|
||||
- `scope: string | null`
|
||||
|
||||
Effective-Value-Computation analog zu Backend (path-basiert):
|
||||
```typescript
|
||||
function _effectiveValue(ds, allDs, flag) {
|
||||
if (ds[flag] !== null && ds[flag] !== undefined) return ds[flag];
|
||||
const parent = _findParentDs(ds, allDs);
|
||||
if (parent) return _effectiveValue(parent, allDs, flag);
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
UI-Verhalten beim Toggle:
|
||||
- 2-state Toggle bleibt (true ↔ false). Cascade automatisch im Backend.
|
||||
- Kein "Reset to inherit"-UI — User toggled stattdessen den Parent erneut.
|
||||
|
||||
## Schritte
|
||||
|
||||
### S1: Datenmodell + Migration
|
||||
- `datamodelDataSource.py` und `datamodelFeatureDataSource.py`: `Optional[bool]`/`Optional[str]` für die drei Flags
|
||||
- Migration-Skript `script_db_migrate_datasource_inherit.py` (additiv-idempotent): ALTER COLUMN für die Spalten zu nullable. Bestehende Werte bleiben.
|
||||
|
||||
### S2: Cascade-Helper im Backend
|
||||
- Neue Datei `gateway/modules/serviceCenter/services/serviceUdb/_cascadeInherit.py`:
|
||||
- `cascadeResetDescendants(rootIf, dataSourceId, flag) -> int` — gibt Anzahl betroffener Records zurück
|
||||
- Audit-Logging via `recordModify` mit Reason `cascade_reset_<flag>`
|
||||
|
||||
### S3: PATCH-Endpoints anpassen
|
||||
- `routeDataSources.py`: `_updateDataSourceNeutralize`, `_updateDataSourceRagIndex`, `_updateDataSourceScope`
|
||||
- Body akzeptiert `value: Optional[bool|str]`
|
||||
- Bei nicht-null Wert: führe `cascadeResetDescendants` aus
|
||||
- Bei null Wert: nur setzen, kein Cascade
|
||||
|
||||
### S4: Walker-Integration
|
||||
- `serviceKnowledge/_effectiveFlags.py` (neu): `getEffectiveFlag(ds, flag, allDsByConnection)`, `getAncestorChain(ds, allDs)`
|
||||
- Walker (RAG-Sync, Neutralisierungs-Pipeline) nutzen den Helper statt direkten `ds.neutralize`-Zugriffs
|
||||
- `_loadRagEnabledDataSources`: filtert nach **effektivem** `ragIndexEnabled` (statt direktem)
|
||||
|
||||
### S5: Frontend
|
||||
- `connectionApi.ts`: `UdbDataSource` Interface auf `Optional`-Werte
|
||||
- `SourcesTab.tsx`: `_effectiveValue` Helper, `_findParentDs` Helper
|
||||
- `_TreeNodeView`: `effectiveValue` via Helper, nicht direkt `ds.neutralize ?? inheritedNeutralize`
|
||||
- Toggle-Handler unverändert: PATCH macht Cascade automatisch
|
||||
- Nach PATCH: `_fetchDataSources()` damit Cascade-Reset im UI sichtbar
|
||||
|
||||
### S6: Tests
|
||||
- `test_cascade_inherit.py`: Cascade-Reset löscht nur das Flag, nicht andere Flags
|
||||
- `test_effectiveFlag.py`: Path-traversal returns Root-Default wenn alle Ancestors NULL sind
|
||||
- Bestehende RAG-Bootstrap-Tests: `ragIndexEnabled` Werte anpassen (explicit true statt implicit)
|
||||
|
||||
### S7: Walker-Smoke
|
||||
- Manuell: Connection mit Sub-Folders, Toggle Parent → Sub-Folder folgen ohne Cascade-DELETE
|
||||
|
||||
### S8: Wiki + Changelog
|
||||
- `b-reference/gateway/architecture.md`: neuer Abschnitt "DataSource Cascade-Inherit"
|
||||
- `_CHANGELOG.md` mit Begründung
|
||||
|
||||
## Risiken
|
||||
|
||||
- **Breaking Change** für DataSource-DTO: Frontend muss `null` korrekt handeln. Mitigation: TypeScript-Types eng machen, alle Lese-Stellen prüfen.
|
||||
- **Walker-Performance**: Path-Traversal pro Item könnte O(N×depth) werden. Mitigation: Pre-Computed Map `path → effectiveFlag` vor Walker-Run.
|
||||
- **Migrations-Race**: bestehende Records bleiben mit ihren Werten. Falls ein User einen DS auf `false` explizit gesetzt hat (in der alten Welt = "false default"), bleibt es nach Migration `false` explizit, nicht `NULL`. Das ist OK — User-Wille bleibt respektiert.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 3-state UI (inherit/true/false) auf einem einzigen Toggle-Button — bleibt 2-state
|
||||
- Settings-Modal "Reset to inherit"-Button — explizit verworfen vom User
|
||||
- FeatureDataSource Cascade-Tests — analog zu DataSource, separater Folge-PR
|
||||
187
c-work/2-build/2026-05-udb-datasource-settings.md
Normal file
187
c-work/2-build/2026-05-udb-datasource-settings.md
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
<!-- status: build -->
|
||||
<!-- started: 2026-05-17 -->
|
||||
<!-- completed: 2026-05-17 -->
|
||||
<!-- component: gateway | frontend-nyla -->
|
||||
|
||||
# UDB DataSource Settings (⚙️) + konfigurierbare RAG-Limits
|
||||
|
||||
> **Implementierungsstand 2026-05-17:** S0–S11 abgeschlossen. Wichtige Abweichung
|
||||
> vom ursprünglichen Plan: das "lazy-fill defaults beim ersten Bootstrap"
|
||||
> wurde **nicht** im Walker implementiert (würde die Caller-Override-Semantik
|
||||
> aushebeln und liess Tests rot werden). Stattdessen: `_ragLimits` exponiert
|
||||
> zwei klare APIs — `getStoredOverrides()` (nur explizite Overrides, für Walker)
|
||||
> und `getRagLimits()` (Overrides + Defaults gemerged, für API/Cost-Estimate).
|
||||
> Das Modal zeigt fehlende Werte als Placeholder aus dem Cost-Estimate-Endpoint
|
||||
> an, damit der User immer eine konkrete Zahl sieht — ohne die Datenbank-Zeile
|
||||
> dafür anfassen zu müssen. Siehe CHANGELOG 2026-05-17 für die Detail-Liste.
|
||||
|
||||
## Beschreibung und Kontext
|
||||
|
||||
Heute sind die RAG-Walker-Limits (`maxBytes`, `maxItems`, `maxFileSize`, `maxDepth`, `maxWorkspaces`, `maxListsPerWorkspace`, `maxTasks`) als Modul-Konstanten in den `subConnectorSync*.py`-Dateien hartkodiert. Ein User mit z. B. einem 500 MB grossen SharePoint-Folder erhält nach 200 MB ein stilles Stoppen (`stoppedAtLimit=maxBytes`) und kann das ohne Code-Änderung nicht anpassen. Heute (2026-05-17) sichtbar geworden auf einem Basecamp-Folder mit ~731 Dateien, von denen nur 25 indexiert wurden, bevor der `maxBytes`-Default zugriff.
|
||||
|
||||
Gleichzeitig fehlt für Feature-Instanz-DataSources (`FeatureDataSource`) bisher überhaupt ein UI, an dem der Owner Settings anpassen könnte. UserConnections haben den Wizard und das Edit-Modal auf der Connections-Seite – für Feature-Daten gibt es keinen analogen Ort.
|
||||
|
||||
Beide Lücken lassen sich mit derselben Architektur schliessen: **ein generisches Settings-Icon (⚙️) pro Node in der UDB**, das ein typ- und scope-abhängiges Modal öffnet. Initial wird darin RAG-Limit-Werte und Connection-Master-Switch gepflegt, später kommen weitere Einstellungen (z. B. Custom-Neutralization-Regeln, Re-Sync-Cadence, Polling-Strategie) ohne neue Icons dazu.
|
||||
|
||||
Wichtiger Architektur-Constraint:
|
||||
- **Keine Icon-Inflation in der UDB.** Bereits vorhanden: Scope-Toggle, Neutralize-Toggle, RAG-Index-Toggle (🧠), Chat (💬). Wir fügen genau **ein** zusätzliches Icon (⚙️) hinzu – nicht ein Icon pro Setting.
|
||||
|
||||
## Fokus und kritische Details
|
||||
|
||||
- UDB ist die **einzige** Stelle, wo Settings pro Datenobjekt gemanagt werden. Andere Pfade (Wizard, Connection-Edit-Modal) editieren nur die Connection-Ebene, nie einzelne DataSources.
|
||||
- **Speicherort = Quelle der Wahrheit. Keine Override-Schichten, keine Resolver-Logik.** Was im UI angezeigt wird, ist exakt das, was im Walker greift. Die Werte werden bei der DataSource-Erstellung mit sinnvollen Defaults vorbefüllt (kopiert aus globalen Konstanten); danach kann der User sie einfach editieren. Vorteil: nachvollziehbar, debuggbar, keine versteckten Vererbungen.
|
||||
- Walker müssen ihre Default-Konstanten zentralisieren und beim Lesen einer DataSource entweder den dort gespeicherten Wert nehmen oder, falls noch nicht gesetzt, den zentralen Default ausliefern und gleichzeitig auf der DataSource persistieren (lazy initialization → ab dann sind die Werte sichtbar im UI).
|
||||
- Settings-Werte landen in einer **JSON-Spalte** auf der DataSource bzw. FeatureDataSource (`settings: Optional[Dict[str, Any]]`) – damit ist die Erweiterbarkeit garantiert, ohne neue Spalten/Migrationen pro neuem Setting.
|
||||
- DataSource vs. FeatureDataSource: gleiche Spalte (`settings`), gleiches UI, gleiches API-Pattern (`PATCH /api/datasources/{id}/settings` und `PATCH /api/feature-data-sources/{id}/settings`).
|
||||
- Audit-Log-Pflicht: jede Änderung an `settings` schreibt einen `AuditCategory.PERMISSION`-Eintrag (auch wenn es nur ein numerischer Limit-Wert ist – RAG-Ingest-Grenze ist eine Compliance-relevante Entscheidung).
|
||||
- Defaults bleiben **konservativ** (200 MB / 10'000 Items / 25 MB / Tiefe 8). Anheben braucht User-Aktion → keine versehentlichen Kostenexplosionen.
|
||||
- **Connection-Master-Switch im Modal:** Im Settings-Modal einer DataSource ist auch der Connection-weite Master-Switch `knowledgeIngestionEnabled` ("Knowledge database active") togglebar – damit ist das Modal die zentrale Stelle für alle relevanten Settings, ohne zwischen Seiten wechseln zu müssen. Dieser Toggle wirkt auf alle DataSources derselben Connection (es ist explizit als "Connection-Setting" gekennzeichnet, nicht als DataSource-Setting).
|
||||
- **Kosten-Indikator statt Hard-Cap:** Keine harten Obergrenzen. Stattdessen zeigt das Modal eine indikative (nicht-verbindliche) Realtime-Kostenschätzung: basierend auf erwarteter Item-Zahl, Token-Schätzung pro Item und aktuellen Embedding-Preisen. Klar als "indikativ" gekennzeichnet, der User trägt die Verantwortung.
|
||||
|
||||
## Ziel und Nicht-Ziele
|
||||
|
||||
- **Ziel:**
|
||||
- Ein Settings-Icon ⚙️ pro Tree-Node in der UDB-Sidebar (`SourcesTab.tsx`), das ein Modal öffnet.
|
||||
- Im Modal: drei klar abgegrenzte Sektionen:
|
||||
1. **Connection** (Master-Switch `knowledgeIngestionEnabled`, gemeinsame Mail-/ClickUp-Preferences aus `knowledgePreferences`).
|
||||
2. **DataSource RAG-Limits** (`maxBytes`, `maxFileSize`, `maxItems`, `maxDepth`).
|
||||
3. **Kostenschätzung** (indikativer Wert mit Hinweis).
|
||||
- Backend: `DataSource.settings` und `FeatureDataSource.settings` als JSON-Spalte. Endpunkte zum Lesen/Patchen.
|
||||
- Walker (`subConnectorSyncSharepoint.py`, `subConnectorSyncKdrive.py`, `subConnectorSyncGdrive.py`, `subConnectorSyncClickup.py`) lesen Limits direkt aus `DataSource.settings.ragLimits`, fallen auf zentrale Defaults zurück, wenn (noch) leer.
|
||||
- Partial-Banner auf `RagInventoryPage` zeigt zusätzlich Hint: "Limit kann pro DataSource in der UDB unter ⚙️ angehoben werden."
|
||||
- Owner-Kontrolle: für UserConnection-DataSources nur Owner; für FeatureDataSource Owner oder `workspace-admin`.
|
||||
- **Nicht-Ziel:**
|
||||
- Mandate-weite Defaults / Override-Schichten / Resolver-Layer.
|
||||
- Hard-Caps (User/Admin trägt Verantwortung).
|
||||
- Settings-Vererbung im Tree (Parent-Folder → Children) – aktuell wirkt eine Änderung nur auf die konkrete DataSource.
|
||||
- Eigene Settings für Mail-Connectors (`Outlook`, `Gmail`) auf DataSource-Ebene – die haben keine Folder-Hierarchie. Die Mail-Preferences bleiben Connection-weit und werden im Modal in der "Connection"-Sektion editiert.
|
||||
|
||||
## Architektur-Skizze
|
||||
|
||||
### Daten-Schicht
|
||||
|
||||
`DataSource` und `FeatureDataSource` bekommen je eine neue Spalte:
|
||||
```python
|
||||
settings: Optional[Dict[str, Any]] = Field(
|
||||
default=None,
|
||||
description="DataSource-scoped settings (JSON). Currently used keys: ragLimits.",
|
||||
json_schema_extra={"frontend_type": "json", "frontend_readonly": True, "frontend_required": False},
|
||||
)
|
||||
```
|
||||
|
||||
JSON-Schema-Konvention:
|
||||
```json
|
||||
{
|
||||
"ragLimits": {
|
||||
"maxBytes": 524288000,
|
||||
"maxFileSize": 52428800,
|
||||
"maxItems": 20000,
|
||||
"maxDepth": 12
|
||||
}
|
||||
}
|
||||
```
|
||||
**Keine Resolver-Schichten**: was in `settings.ragLimits` steht, gilt. Wenn der Key fehlt, nimmt der Walker den zentralen Default aus `_ragLimits.RAG_LIMITS_DEFAULT` und schreibt ihn beim nächsten Bootstrap **einmalig** in die DataSource zurück (lazy fill), damit der User die Werte auch ohne vorherigen Sync schon im UI sieht und editieren kann.
|
||||
|
||||
### Backend-API
|
||||
|
||||
```
|
||||
PATCH /api/datasources/{id}/settings { settings: { ragLimits: {...} } }
|
||||
PATCH /api/feature-data-sources/{id}/settings { settings: { ragLimits: {...} } }
|
||||
GET /api/datasources/{id}/cost-estimate → { estimatedTokens, estimatedUsd, basis: {...} }
|
||||
```
|
||||
`/cost-estimate`: schätzt anhand Item-Count (sofern bekannt aus letztem Sync) × tokens-per-item-heuristik × Embedding-Preis. Liefert auch die Annahmen (`basis`), damit der User die Plausibilität prüfen kann.
|
||||
|
||||
### Walker-Refactor
|
||||
|
||||
Heute hartkodiert in jedem Walker:
|
||||
```python
|
||||
MAX_BYTES_DEFAULT = 200 * 1024 * 1024
|
||||
MAX_ITEMS_DEFAULT = 10_000
|
||||
```
|
||||
Wird zu:
|
||||
```python
|
||||
# gateway/modules/serviceCenter/services/serviceKnowledge/_ragLimits.py
|
||||
RAG_LIMITS_DEFAULT = {
|
||||
"maxBytes": 200 * 1024 * 1024,
|
||||
"maxFileSize": 25 * 1024 * 1024,
|
||||
"maxItems": 10_000,
|
||||
"maxDepth": 8,
|
||||
}
|
||||
|
||||
def getRagLimits(dataSource: Dict[str, Any]) -> Dict[str, int]:
|
||||
"""Read limits from DataSource.settings.ragLimits, fall back to defaults
|
||||
for missing keys. Pure read; lazy persist is the caller's responsibility."""
|
||||
stored = (dataSource.get("settings") or {}).get("ragLimits") or {}
|
||||
return {**RAG_LIMITS_DEFAULT, **stored}
|
||||
|
||||
def lazyFillRagLimits(rootIf, dataSourceId: str, dataSource: Dict[str, Any]) -> None:
|
||||
"""Persist defaults to settings if not yet present, so the UI shows them."""
|
||||
```
|
||||
Jeder Walker holt seine Limits beim Eintritt in `_bootstrap*` einmal und gibt sie an `_walkFolder()` / `_finalizeResult()` weiter – die Konstante existiert nur noch im neuen Modul als Default-Fallback.
|
||||
|
||||
### Frontend-UI
|
||||
|
||||
In `SourcesTab.tsx` (Tree-Node-Render), zwischen Brain-Icon (🧠) und Chat-Icon (💬):
|
||||
```typescript
|
||||
<button
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const dsId = ds?.id ?? await onEnsureDs(node);
|
||||
if (dsId) openSettingsModal(dsId, scope);
|
||||
}}
|
||||
title={t('Einstellungen')}
|
||||
style={{ ... }}
|
||||
>
|
||||
⚙️
|
||||
</button>
|
||||
```
|
||||
|
||||
Settings-Modal (`DataSourceSettingsModal.tsx`):
|
||||
- **Sektion "Connection"** (oben, mit Connection-Label und Authority-Icon):
|
||||
- Toggle `knowledgeIngestionEnabled` (Master-Switch). Hinweis: wirkt auf alle DataSources dieser Connection.
|
||||
- Optional, wenn relevant: `mailContentDepth`, `mailIndexAttachments`, `filesIndexBinaries`, `clickupScope`, `clickupIndexAttachments`, `maxAgeDays` (aus `knowledgePreferences`).
|
||||
- PATCH → `/api/connections/{id}/knowledge-consent` bzw. `/api/connections/{id}/knowledge-preferences`.
|
||||
- **Sektion "RAG-Limits"** (nur wenn DataSource den Walker-Typ unterstützt):
|
||||
- Felder `maxBytes`, `maxFileSize`, `maxItems`, `maxDepth` mit lesbaren Units (MB, Anzahl).
|
||||
- PATCH → `/api/datasources/{id}/settings`.
|
||||
- **Sektion "Kostenschätzung"** (read-only):
|
||||
- "Indikative Kosten: ~X USD pro Voll-Sync" (mit Basis-Tooltip).
|
||||
- GET → `/api/datasources/{id}/cost-estimate` beim Modal-Öffnen.
|
||||
|
||||
Auf der `RagInventoryPage`-Partial-Banner-Komponente:
|
||||
```typescript
|
||||
<span>... Limit {l} erreicht. Weitere Dateien wurden NICHT indexiert. {' '}
|
||||
<a onClick={() => openSettingsModalForConn(conn)}>Limit anpassen</a> oder
|
||||
DataSource enger eingrenzen, dann erneut starten.</span>
|
||||
```
|
||||
|
||||
## Erfolgskriterien
|
||||
|
||||
- Auf einer SharePoint-DataSource mit > 200 MB kann der Owner über das ⚙️-Icon `maxBytes` auf z. B. 1 GB anheben, einen Re-Index starten und sieht im Inventory > 200 MB indexierte Bytes ohne `stoppedAtLimit`-Banner.
|
||||
- Auf einer FeatureDataSource ist das gleiche Settings-Modal verfügbar und speichert sauber in `FeatureDataSource.settings`.
|
||||
- Wenn `settings.ragLimits` leer ist, ist das Verhalten der Walker **bitidentisch** zur Vor-Plan-Version (keine Regression).
|
||||
- Der Connection-Master-Switch im Modal und der Toggle auf der Connections-Page und auf der RagInventoryPage zeigen immer denselben Wert (alle drei rufen `/knowledge-consent`).
|
||||
- Audit-Log enthält pro Settings-Change einen `PERMISSION`-Eintrag mit `dataSourceId`, `userId`, `mandateId`, `oldSettings`, `newSettings`.
|
||||
- Keine zusätzlichen Icons in der UDB ausser dem einen ⚙️.
|
||||
- Kostenschätzung wird als "indikativ, nicht verbindlich" gekennzeichnet und zeigt die zugrunde liegenden Annahmen.
|
||||
|
||||
## Schritte
|
||||
|
||||
- [ ] S1 — Datenmodell: `settings: JSON` an `DataSource` und `FeatureDataSource` ergänzen (`gateway/modules/datamodels/datamodelDataSource.py`, `datamodelFeatureDataSource.py`); SQL-Migration in `gateway/scripts/script_db_migrate_datasource_settings.py`.
|
||||
- [ ] S2 — Zentralisierte Defaults: `gateway/modules/serviceCenter/services/serviceKnowledge/_ragLimits.py` neu mit `RAG_LIMITS_DEFAULT`, `getRagLimits()`, `lazyFillRagLimits()`.
|
||||
- [ ] S3 — Walker-Refactor: `subConnectorSyncSharepoint.py`, `subConnectorSyncKdrive.py`, `subConnectorSyncGdrive.py`, `subConnectorSyncClickup.py` lesen Limits via `getRagLimits()` und schreiben Defaults via `lazyFillRagLimits()` zurück.
|
||||
- [ ] S4 — Backend-Endpunkte: `PATCH /api/datasources/{id}/settings`, `PATCH /api/feature-data-sources/{id}/settings`, `GET /api/datasources/{id}/cost-estimate` in `routeDataSources.py` (und `routeFeatureDataSources.py`, falls noch nicht da). RBAC: Owner für DataSource, Owner + `workspace-admin` für FeatureDataSource.
|
||||
- [ ] S5 — Frontend ⚙️-Button in `SourcesTab.tsx` (Tree-Row, zwischen 🧠 und 💬), Opacity-Logik: voll sichtbar, wenn `settings` befüllt sind, sonst gedimmt.
|
||||
- [ ] S6 — Frontend `DataSourceSettingsModal.tsx`: drei Sektionen (Connection / RAG-Limits / Kostenschätzung), per-Feld-Validation, Speichern via API.
|
||||
- [ ] S7 — `RagInventoryPage.tsx` Partial-Banner: Link "Limit anpassen" öffnet Modal für die betroffene DataSource (Heuristik: DS, die `stoppedAtLimit` ausgelöst hat – bei mehreren: die mit den meisten verarbeiteten Bytes).
|
||||
- [ ] S8 — Audit-Logging in den neuen Settings-Endpunkten.
|
||||
- [ ] S9 — Cost-Estimate-Engine: `gateway/modules/serviceCenter/services/serviceKnowledge/_costEstimate.py` mit Heuristik (Items × Tokens × Embedding-Preis) und Basis-Annahmen-Output.
|
||||
- [ ] S10 — Tests: Unit-Tests für `getRagLimits()` / `lazyFillRagLimits()`, API-Tests für PATCH-Endpunkte (RBAC), Cost-Estimate-Unit-Test, Frontend-Smoke-Test (Modal öffnet, speichert, refetch, Cost-Anzeige).
|
||||
- [ ] S11 — Doku: `wiki/b-reference/platform/rag-pipeline.md` Abschnitt "Limits & Settings" ergänzen, plus Eintrag in `wiki/TOPICS.md`. CHANGELOG-Zeile.
|
||||
|
||||
## Offene Fragen / Decisions
|
||||
|
||||
- **F1 – Override-Logik?** → **Entschieden: KEINE Override-Schichten.** Die DataSource speichert ihre eigenen Werte direkt; Walker liest sie 1:1. Lazy-Fill mit zentralen Defaults beim ersten Sync, damit das UI immer einen sinnvollen Startwert zeigt. Nachvollziehbar, debuggbar, keine versteckten Vererbungen.
|
||||
- **F2 – RBAC?** → **Entschieden:** Owner für UserConnection-DataSources, Owner oder `workspace-admin` für FeatureDataSource (analog Scope-/Neutralize-Toggle).
|
||||
- **F3 – Hard-Cap pro Mandat?** → **Entschieden: NEIN.** Stattdessen indikative Realtime-Kostenschätzung im Modal. User/Admin trägt Verantwortung.
|
||||
- **F4 – Mail/ClickUp-Preferences im Modal?** → **Entschieden: JA.** Connection-weite Preferences werden in der Sektion "Connection" des Modals editiert – das macht das Modal zur einzigen Stelle für alle relevanten Settings, ohne Seitenwechsel.
|
||||
|
|
@ -14,6 +14,10 @@ Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
|
|||
|
||||
## 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`).
|
||||
|
|
|
|||
Loading…
Reference in a new issue