rag+teams
This commit is contained in:
parent
4bb1ed232f
commit
50760a380f
8 changed files with 1249 additions and 6 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<!-- status: canonical -->
|
||||
<!-- lastReviewed: 2026-05-11 -->
|
||||
<!-- lastReviewed: 2026-05-12 -->
|
||||
|
||||
# Themen-Index für AI-Kontext
|
||||
|
||||
|
|
@ -47,7 +47,8 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
|
|||
| Thema | Datei | Wann laden |
|
||||
|-------|-------|------------|
|
||||
| Automation Unification | c-work/1-plan/2026-04-automation-unification.md | Refactoring v1/v2/Workspace |
|
||||
| Unified Knowledge Indexing (RAG) | c-work/2-build/2026-04-id-unified-knowledge-indexing-rag-concept.md | Ingestion-Fassade `requestIngestion`, Idempotenz, Connector-Lifecycle |
|
||||
| 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 |
|
||||
| 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-10 -->
|
||||
<!-- 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 -->
|
||||
<!-- lastReviewed: 2026-05-12 -->
|
||||
<!-- 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 -->
|
||||
|
||||
# AI Agent & Knowledge Store
|
||||
|
||||
|
|
@ -249,6 +249,66 @@ Rückgabe: formatierter String für Injektion in den Agent-Systemkontext. **Wenn
|
|||
|
||||
Erweiterte Hilfen (z. B. **`readSection`**, Caching) für selektives Lesen sind im selben Service dokumentiert.
|
||||
|
||||
### RAG Consent & Control (ab 2026-05)
|
||||
|
||||
Volle Architektur für transparente, datenzentrierte Steuerung der RAG-Indexierung durch den Benutzer.
|
||||
|
||||
#### Prinzipien
|
||||
|
||||
1. **Datenquelle ist die Single Source of Truth** — `DataSource.ragIndexEnabled` steuert, ob ein Baum-Element indexiert wird. Vererbung entlang der Baumstruktur (wie `scope` und `neutralize`).
|
||||
2. **Kein Wizard-Seiteneffekt ohne UI-Revoke** — Jede Einstellung die im AddConnectionWizard gesetzt wird, ist in der UDB (Unified Data Bar) sicht- und revertbar.
|
||||
3. **Konsent-Entzug = sofortige Purge** — Deaktivierung von `ragIndexEnabled` oder `knowledgeIngestionEnabled` löscht zugehörige Chunks synchron.
|
||||
|
||||
#### Datenmodell-Erweiterungen
|
||||
|
||||
| Modell | Feld | Zweck |
|
||||
|--------|------|-------|
|
||||
| `DataSource` | `ragIndexEnabled: bool` | Pro Baum-Element: Auto-Indexierung an/aus |
|
||||
| `DataSource` | `lastIndexed: Optional[float]` | Zeitstempel des letzten erfolgreichen Index-Laufs |
|
||||
| `UserConnection` | `knowledgeIngestionEnabled: bool` | Globaler Konsent pro Verbindung |
|
||||
|
||||
#### Walker-Architektur
|
||||
|
||||
Jeder Walker (`subConnectorSync*.py`) iteriert über `ragIndexEnabled=True` DataSources:
|
||||
- Empfängt `dataSources: List[Dict]` und `progressCb: JobProgressCallback`
|
||||
- Prüft `progressCb.isCancelled()` periodisch für graceful Abort
|
||||
- Enthält `dataSourceId` in der Provenance jedes `IngestionJob`
|
||||
- Nutzt per-DataSource `neutralize`-Policy (aus `subPolicyResolver`)
|
||||
|
||||
#### Job-Cancellation
|
||||
|
||||
`mainBackgroundJobService.py` bietet:
|
||||
- `cancelJob(jobId)` — setzt Status auf `CANCELLED`
|
||||
- `cancelJobsByConnection(connectionId)` — bulk-cancel aller laufenden Jobs
|
||||
- `JobProgressCallback.isCancelled()` — kooperativer Check mit 3s-Cache
|
||||
|
||||
#### API-Endpunkte
|
||||
|
||||
| Methode | Pfad | Zweck |
|
||||
|---------|------|-------|
|
||||
| `PATCH` | `/api/datasources/{id}/rag-index` | Toggle `ragIndexEnabled` (Trigger mini-bootstrap oder Purge) |
|
||||
| `PATCH` | `/api/connections/{id}/knowledge-consent` | Globaler Konsent-Toggle |
|
||||
| `POST` | `/api/connections/{id}/knowledge-stop` | Alle laufenden Jobs stoppen |
|
||||
| `GET` | `/api/rag/inventory/me` | Persönliche RAG-Übersicht |
|
||||
| `GET` | `/api/rag/inventory/mandate` | Mandant-Aggregation (Admin) |
|
||||
| `GET` | `/api/rag/inventory/platform` | Plattform-Statistik (SysAdmin) |
|
||||
| `GET` | `/api/rag/inventory/jobs` | Aktive RAG-Jobs des Users |
|
||||
|
||||
#### Frontend-Komponenten
|
||||
|
||||
| Komponente | Ort | Funktion |
|
||||
|-----------|-----|----------|
|
||||
| UDB `SourcesTab` | 4. Toggle (🧠) | ragIndexEnabled pro Tree-Element |
|
||||
| `AddConnectionWizard` | Connector-aware Steps | MS Admin Consent + Infomaniak PAT integriert |
|
||||
| `RagInventoryPage` | Start > Nutzung > RAG | Globale Übersicht & Steuerung |
|
||||
| `RagRunningBadge` | MainLayout (fixed) | Floating-Badge bei aktiven Jobs |
|
||||
|
||||
#### Vererbung (Policy Resolver)
|
||||
|
||||
`subPolicyResolver.py` implementiert Longest-Prefix-Matching:
|
||||
- Eltern-DataSource mit explizitem `ragIndexEnabled` vererbt an Kind-Pfade
|
||||
- Gleiches Pattern wie `neutralize` und `scope`
|
||||
|
||||
---
|
||||
|
||||
## Teamsbot-Integration (Hybrid-Routing, kein eigenes Toolset)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- status: canonical -->
|
||||
<!-- lastReviewed: 2026-05-11 -->
|
||||
<!-- verifiedAgainst: service-teams-browser-bot (documentation review 2026-02-18); gateway/modules/features/teamsbot/{service,routeFeatureTeamsbot,interfaceFeatureTeamsbot,datamodelTeamsbot}.py + `GET /api/teamsbot/{instanceId}/dashboard/stream` (2026-05-11); gateway/tests/unit/teamsbot/test_directorPrompts.py (Director Prompts 2026-04-24); gateway Voice STT batch + linear16 (2026-05-10); frontend_nyla TeamsbotDashboardView / TeamsbotModulesView / TeamsbotSessionView (IA + SSE 2026-05-11) -->
|
||||
<!-- lastReviewed: 2026-05-12 -->
|
||||
<!-- verifiedAgainst: service-teams-browser-bot (audioCaptureProcedure WebRTC-Wrapper Gating 2026-05-12); service-teams-browser-bot (documentation review 2026-02-18); gateway/modules/features/teamsbot/{service,routeFeatureTeamsbot,interfaceFeatureTeamsbot,datamodelTeamsbot}.py + `GET /api/teamsbot/{instanceId}/dashboard/stream` (2026-05-11); gateway/tests/unit/teamsbot/test_directorPrompts.py (Director Prompts 2026-04-24); gateway Voice STT batch + linear16 (2026-05-10); frontend_nyla TeamsbotDashboardView / TeamsbotModulesView / TeamsbotSessionView (IA + SSE 2026-05-11) -->
|
||||
|
||||
# Teams Meeting Bot -- Architektur
|
||||
|
||||
|
|
@ -84,6 +84,21 @@ Der Gateway (Feature `teamsbot`) verwaltet Sessions und stellt die AI-Pipeline b
|
|||
|
||||
**STT auf dem Gateway:** Meeting-Audio-Chunks (WebSocket `audioChunk`, PCM) werden pro Chunk mit `VoiceObjects.speechToText` transkribiert (Batch `recognize`, gemeinsamer Connector mit CommCoach). Konfiguration u. a. `audioFormat=linear16`, `skipFallbacks=True`; Details und Modell-Defaults: [voice-google.md](../gateway/voice-google.md).
|
||||
|
||||
### Audio-Capture: WebRTC-Wrapper-Gating
|
||||
|
||||
Der Bot installiert einen `RTCPeerConnection`-Wrapper per `addInitScript` (Browser-Start, vor jeder Teams-Navigation), damit später keine PC unbeobachtet bleibt. **Während Pre-Join, Lobby und SDP-Renegotiation darf der Wrapper aber NICHTS am Audio-Stream anfassen** — kein `clone()`, kein `createMediaStreamSource()`, kein `AudioContext`. Jeder Eingriff in dieser Phase löst in Teams' aktuellem `light-meetings`-Bundle den Renderer-Crash `Cannot read properties of null (reading 'rejectMediaDescriptionsUpdateAsync')` aus und der anonyme Bot landet entweder dauerhaft in der Lobby oder wird wieder zur Pre-Join-Seite geworfen.
|
||||
|
||||
Mechanik:
|
||||
|
||||
1. Wrapper-Init setzt `window.__audioCaptureEnabled = false` und legt einen `track`-Listener pro PC an, der bei `false` ausschließlich Diagnostik loggt.
|
||||
2. Erst nach `orchestrator._setState('in_meeting')` ruft `_enableTranscriptCapture` → `audioCaptureProcedure.startCapture()` auf.
|
||||
3. `startCapture()` flippt `__audioCaptureEnabled = true` und iteriert `getReceivers()` aller PCs mit `connectionState === 'connected'`. Für jede live Audio-Spur wird `__audioCaptureAttachTrack(pc, track)` ausgeführt (AudioContext + `track.clone()` + `MediaStreamSource` + AudioWorklet/ScriptProcessor → PCM16 16 kHz Mono).
|
||||
4. Spätere `track`-Events bauen ihren Audio-Graph automatisch, sobald die zugehörige PC `connected` ist.
|
||||
|
||||
Es wird **keine Label-Filterung** angewendet — die Tracks haben je nach Session/Layout entweder `mainAudio-<n>` oder UUID-Labels. Der einzige saubere Trigger ist der Bot-State.
|
||||
|
||||
Refs: `service-teams-browser-bot/src/bot/audioCaptureProcedure.ts` (`__audioCaptureEnabled`, `__audioCaptureAttachTrack`, `startCapture`); Aufrufer `orchestrator.ts` (`_attemptJoin` STEP 4 → `_setState('in_meeting')` → `_enableTranscriptCapture`).
|
||||
|
||||
## Hybrid-Routing: SPEECH_TEAMS + Agent
|
||||
|
||||
Der Teamsbot läuft auf zwei kooperierenden Pfaden:
|
||||
|
|
|
|||
744
c-work/2-build/2026-05-rag-consent-and-control-implementation.md
Normal file
744
c-work/2-build/2026-05-rag-consent-and-control-implementation.md
Normal file
|
|
@ -0,0 +1,744 @@
|
|||
---
|
||||
title: "RAG Consent & Control – Implementierungsplan"
|
||||
status: 1-plan
|
||||
owner: ida
|
||||
relatedConcept: ./2026-05-rag-consent-and-control-unification.md
|
||||
created: 2026-05-12
|
||||
---
|
||||
|
||||
# RAG Consent & Control — Implementierungsplan
|
||||
|
||||
> **Lese-Kontext:** Architektonisches Konzept und Begründungen siehe Schwesterdokument
|
||||
> [`2026-05-rag-consent-and-control-unification.md`](./2026-05-rag-consent-and-control-unification.md).
|
||||
> Dieses Dokument ist die **fein granulare, code-zentrierte Umsetzung** mit Phasen, Datei-Änderungen,
|
||||
> Acceptance-Gates und Test-Hooks. Reihenfolge ist verbindlich (Dependency-Order).
|
||||
|
||||
---
|
||||
|
||||
## 0. Audit-Befunde (Stand 2026-05-12)
|
||||
|
||||
Die folgenden Befunde sind das Ergebnis des Code-Audits und sind die **Grundannahmen** dieses Plans.
|
||||
Jede Phase referenziert sie.
|
||||
|
||||
| # | Befund | Datei / Stelle | Konsequenz für Plan |
|
||||
|---|--------|----------------|---------------------|
|
||||
| F1 | `DataSource.autoSync` ist nur im Model deklariert, **wird im Code nirgends gelesen oder geschrieben** (Suche in `gateway`, `frontend_nyla` ergab Null Hits ausserhalb der Modeldatei). | `datamodels/datamodelDataSource.py:65–69` | Renaming auf `ragIndexEnabled` ist **risikofrei**; Migration = `ALTER TABLE … RENAME COLUMN`. |
|
||||
| F2 | `BackgroundJobStatusEnum.CANCELLED` und `TERMINAL_JOB_STATUSES = {SUCCESS, ERROR, CANCELLED}` existieren bereits, aber `cancelJob()`-API fehlt; `JobProgressCallback` hat nur `(progress, message)`-Signatur. | `serviceBackgroundJobs/mainBackgroundJobService.py:52, 157`, `datamodels/datamodelBackgroundJob.py:34, 37–41` | Cancel-Infrastruktur muss ergänzt werden, **nicht** das Status-Enum. |
|
||||
| F3 | Bootstrap-Dispatcher ruft Walker mit `connectionId` auf; Walker enumerieren OAuth-global (`adapter.browse("/")`) und laden Prefs intern. | `subConnectorIngestConsumer.py:168–221`, `subConnectorSyncSharepoint.py:110–174` | Walker-API muss um optionalen `dataSources: List[DataSource]`-Parameter erweitert werden; Default-Verhalten bleibt rückwärtskompatibel **bis Phase D** umstellt. |
|
||||
| F4 | `GET /api/connections/` liefert `knowledgeIngestionEnabled` / `knowledgePreferences` **nicht** im Response. | `routes/routeDataConnections.py:188–201, 249–264, 282–298` (3 Stellen) | Response-Builder zentralisieren als Helper, dann `_buildEnhancedItems` + `groupSummary` + Haupt-Block daraus speisen. |
|
||||
| F5 | `routeDataSources.py` hat sauberes PATCH-Pattern für `/scope`, `/neutralize`, `/neutralize-fields`. | `routes/routeDataSources.py:43–127` | Neues PATCH `/rag-index` als 4. Endpoint mit identischer Struktur. |
|
||||
| F6 | `audit_logger.logEvent(...)` mit `AuditCategory.PERMISSION` existiert — keine neue Audit-Infrastruktur nötig. | `shared/auditLogger.py:96`, `datamodels/datamodelAudit.py:26–34` | Consent- und Toggle-Änderungen via existierendem Logger → eine Codezeile pro PATCH. |
|
||||
| F7 | `WorkspaceRagInsightsPage` ist via UI-Permission `ui.feature.workspace.rag-insights` an Workspace-Rollen gebunden (3 Vorkommen in `mainWorkspace.py`). | `features/workspace/mainWorkspace.py:37–38, 89, 100`; `features/workspace/routeFeatureWorkspace.py:2219` | Löschung muss UI-Permission **und** Backend-Route entfernen — sonst tote Permission im RBAC-Tree. |
|
||||
| F8 | `AddConnectionWizard` hat 4-Schritt-Flow: Connector → Consent → Prefs → Summary mit `computeCostEstimate` (Z. 71–151), `neutralizeBeforeEmbed`-Checkbox (Z. 314–320), Cost-Tabelle (Z. 458–493). MSFT Admin-Consent + Infomaniak laufen über separate Buttons in `ConnectionsPage.tsx` (Z. 245–263, `adminConsentPending` State Z. 57). | `components/AddConnectionWizard/AddConnectionWizard.tsx`, `pages/basedata/ConnectionsPage.tsx` | Wizard wird komplett umgebaut: 3-Schritt-Basis-Flow + connector-spezifische Zwischen-Steps; Cost-Logik + Prefs-Step + Neutralize-Checkbox raus. |
|
||||
| F9 | `SourcesTab.tsx` hat existierendes `inheritedScope`/`inheritedNeutralize`-Pattern in Tree-Renderern (Z. 1112–1592). Toggle-Implementierungen via `_toggleNeutralizeField` (Z. 715), `cycleScope`-Handler bei Zeile 1694. | `components/UnifiedDataBar/SourcesTab.tsx` | `inheritedRagIndexEnabled` folgt **exakt** demselben Pattern; kein neuer Mechanismus, nur Erweiterung der bestehenden Props + Render-Pfade. |
|
||||
| F10 | `IngestionJob` hat bereits `neutralize: bool`-Feld (siehe SharePoint-Walker Z. 359). | `subConnectorSyncSharepoint.py:349–362` | Kein Schema-Wechsel an `IngestionJob` nötig — Walker liefert `neutralize` aus DataSource statt aus Pref. |
|
||||
|
||||
---
|
||||
|
||||
## 1. Phasen-Übersicht (Dependency-Order)
|
||||
|
||||
```
|
||||
Phase A: Datenmodell + Cancel-API (Backend, keine UI-Wirkung)
|
||||
│
|
||||
├─► Phase B: Walker-Refactor (Backend; nutzt A)
|
||||
│ │
|
||||
│ └─► Phase E: Tests E2E (parallel ab Phase D-Mitte möglich)
|
||||
│
|
||||
├─► Phase C: Connection-Routes (Backend; nutzt A; benötigt für Phase D)
|
||||
│ │
|
||||
│ └─► Phase D-Frontend (siehe unten)
|
||||
│
|
||||
└─► Phase D: Frontend (UDB-Toggle + Wizard + ConnectionsPage + RagInventoryPage)
|
||||
│
|
||||
└─► Phase F: WorkspaceRagInsights-Löschung + Doku
|
||||
```
|
||||
|
||||
Reihenfolge ist verbindlich. Phase D darf **nicht** vor Phase A+C abgeschlossen sein, da Frontend-API-Calls
|
||||
sonst ins Leere laufen. Phase B kann parallel zu Phase C laufen, sobald A fertig ist.
|
||||
|
||||
---
|
||||
|
||||
## 2. Phase A — Datenmodell & Cancel-Infrastruktur
|
||||
|
||||
**Ziel:** Schema-Änderungen + Cancel-API verfügbar, ohne dass irgendeine bestehende Funktion bricht.
|
||||
|
||||
### A.1 `DataSource`-Modell umbenennen
|
||||
|
||||
- **Datei:** `gateway/modules/datamodels/datamodelDataSource.py`
|
||||
- **Änderung:**
|
||||
- `autoSync: bool = Field(default=False, ...)` → **umbenennen** zu
|
||||
`ragIndexEnabled: bool = Field(default=False, description="Wenn true: dieses Tree-Element wird in den RAG indexiert.", json_schema_extra={"label": "Im RAG indexieren", "frontend_type": "checkbox"})`
|
||||
- `lastSynced: Optional[float]` → **umbenennen** zu `lastIndexed: Optional[float]` (gleicher Sinn, klarere Semantik)
|
||||
- **Begründung:** F1 — `autoSync` und `lastSynced` werden nirgends gelesen/geschrieben (Audit-Suche).
|
||||
|
||||
### A.2 DB-Migration
|
||||
|
||||
- **Datei:** `gateway/modules/migrations/migrationXXX_renameDataSourceFields.py` (neu — Nummer aus aktueller Migrations-Folge ableiten)
|
||||
- **SQL:**
|
||||
```sql
|
||||
ALTER TABLE poweron_app."DataSource" RENAME COLUMN "autoSync" TO "ragIndexEnabled";
|
||||
ALTER TABLE poweron_app."DataSource" RENAME COLUMN "lastSynced" TO "lastIndexed";
|
||||
```
|
||||
- **Rollback:** triviale Umkehrung.
|
||||
|
||||
### A.3 `UserConnection.knowledgePreferences` Schema-Doku reduzieren
|
||||
|
||||
- **Datei:** `gateway/modules/datamodels/datamodelUam.py`
|
||||
- **Änderung:** Field-Description von `knowledgePreferences` aktualisieren — entferne Erwähnung von:
|
||||
- `neutralizeBeforeEmbed` (kommt nach `DataSource.neutralize`)
|
||||
- `surfaceToggles` (obsolet — pro DataSource via `ragIndexEnabled`)
|
||||
- `mimeAllowlist` (obsolet — Per-File-Toggle in UDB)
|
||||
- Behalten: `mailContentDepth`, `mailIndexAttachments`, `clickupScope`, `clickupIndexAttachments`, `maxAgeDays`.
|
||||
- **Keine Pflicht-Migration der Daten** — Frontend liest künftig nur die behaltenen Keys; Rest ist „silent dropped".
|
||||
|
||||
### A.4 Pref-Loader bereinigen
|
||||
|
||||
- **Datei:** `gateway/modules/serviceCenter/services/serviceKnowledge/subConnectorPrefs.py`
|
||||
- **Änderung:** Felder `neutralizeBeforeEmbed`, `mimeAllowlist`, `surfaceToggles` aus `loadConnectionPrefs`-Output entfernen (DTO + Defaults).
|
||||
- **Wirkung:** Walker dürfen `prefs.neutralizeBeforeEmbed` nicht mehr lesen — wird in Phase B durch DataSource-Lookup ersetzt.
|
||||
|
||||
### A.5 Cancel-API in Background-Jobs
|
||||
|
||||
- **Datei:** `gateway/modules/serviceCenter/services/serviceBackgroundJobs/mainBackgroundJobService.py`
|
||||
- **Neue Funktion** (nach `_markError`, vor `_makeProgressCallback`):
|
||||
```python
|
||||
def cancelJob(jobId: str, *, reason: str = "user_requested") -> bool:
|
||||
"""Mark job as CANCELLED. Walker code reads this via JobProgressCallback.isCancelled().
|
||||
|
||||
Returns False if job is already in a terminal state. No effect if job is unknown.
|
||||
"""
|
||||
job = _loadJob(jobId)
|
||||
if not job:
|
||||
return False
|
||||
if isTerminalStatus(job.get("status", "")):
|
||||
return False
|
||||
_updateJob(jobId, {
|
||||
"status": BackgroundJobStatusEnum.CANCELLED.value,
|
||||
"errorMessage": f"cancelled: {reason}"[:1000],
|
||||
"finishedAt": datetime.now(timezone.utc).timestamp(),
|
||||
})
|
||||
logger.info("BackgroundJob %s cancelled (reason=%s)", jobId, reason)
|
||||
return True
|
||||
```
|
||||
- **`JobProgressCallback`-Erweiterung:**
|
||||
- Aktuell `Callable[[int, Optional[str]], None]`. Erweitern zu **Protocol**:
|
||||
```python
|
||||
class JobProgressCallback(Protocol):
|
||||
def __call__(self, progress: int, message: Optional[str] = None) -> None: ...
|
||||
def isCancelled(self) -> bool: ...
|
||||
```
|
||||
- In `_makeProgressCallback(jobId)`: Closure mit Cache zurückgeben.
|
||||
- Cache: `{"status": str, "checkedAt": float}`; Re-Read aus DB **max. alle 3s** (DB-Last vermeiden).
|
||||
- **Public Re-Export:** `serviceBackgroundJobs/__init__.py` → `cancelJob` exportieren.
|
||||
|
||||
### A.6 Stop-Job-Helfer für Connection
|
||||
|
||||
- **Datei:** `gateway/modules/serviceCenter/services/serviceBackgroundJobs/mainBackgroundJobService.py`
|
||||
- **Neue Funktion:**
|
||||
```python
|
||||
def cancelJobsByConnection(connectionId: str, *, jobType: str = "connection.bootstrap") -> int:
|
||||
"""Cancel all RUNNING/PENDING jobs of a given type whose payload.connectionId matches.
|
||||
|
||||
Returns count of jobs marked cancelled.
|
||||
"""
|
||||
db = _getDb()
|
||||
rows = db.getRecordset(BackgroundJob, recordFilter={"jobType": jobType})
|
||||
count = 0
|
||||
for row in rows:
|
||||
if row.get("status") not in (BackgroundJobStatusEnum.PENDING.value, BackgroundJobStatusEnum.RUNNING.value):
|
||||
continue
|
||||
payload = row.get("payload") or {}
|
||||
if payload.get("connectionId") == connectionId:
|
||||
if cancelJob(row["id"], reason=f"connection_revoked:{connectionId}"):
|
||||
count += 1
|
||||
return count
|
||||
```
|
||||
|
||||
### A.7 Knowledge-Interface Purge-Methoden ergänzen
|
||||
|
||||
- **Datei:** `gateway/modules/interfaces/interfaceDbKnowledge.py`
|
||||
- **Bestehend:** `deleteFileContentIndexByConnectionId(connectionId)` (Z. 96).
|
||||
- **Neu:**
|
||||
- `deleteFileContentIndexByDataSource(dataSourceId: str) -> Dict[str, int]` — purgt Chunks deren `provenance.dataSourceId == dataSourceId`.
|
||||
- `listFileContentIndexByDataSource(dataSourceId: str) -> List[Dict]` — für Inventar-Anzeige.
|
||||
- **Voraussetzung:** Walker müssen `dataSourceId` ins `provenance`-Dict schreiben (siehe Phase B.3).
|
||||
|
||||
### A.8 Acceptance-Gate Phase A
|
||||
|
||||
- [ ] Migration läuft auf Test-DB durch und wieder rückwärts.
|
||||
- [ ] Bestehende Tests in `serviceBackgroundJobs` grün; neuer Test `test_cancelJob_*` deckt: cancel running, cancel terminal (no-op), cancel unknown (False).
|
||||
- [ ] `_makeProgressCallback`-Cache verifiziert (kein DB-Read pro `isCancelled()`-Call innerhalb 3s).
|
||||
- [ ] `subConnectorPrefs.loadConnectionPrefs` liefert kein `neutralizeBeforeEmbed` mehr.
|
||||
|
||||
---
|
||||
|
||||
## 3. Phase B — Walker-Refactor (DataSource-getriebene Iteration)
|
||||
|
||||
**Ziel:** Walker iterieren **nur noch** über `DataSource`-Rows mit `ragIndexEnabled=true`.
|
||||
Cancel-Check ist überall verbaut. Provenance enthält `dataSourceId`.
|
||||
|
||||
### B.1 Bootstrap-Dispatcher umstellen
|
||||
|
||||
- **Datei:** `gateway/modules/serviceCenter/services/serviceKnowledge/subConnectorIngestConsumer.py`
|
||||
- **Änderung in `_bootstrapJobHandler`** (Z. 125–238):
|
||||
1. **Vor jedem Authority-Branch:** DataSources der Connection laden:
|
||||
```python
|
||||
from modules.interfaces.interfaceDbApp import getRootInterface
|
||||
from modules.datamodels.datamodelDataSource import DataSource
|
||||
dataSources = getRootInterface().db.getRecordset(
|
||||
DataSource, recordFilter={"connectionId": connectionId, "ragIndexEnabled": True}
|
||||
)
|
||||
if not dataSources:
|
||||
logger.info("ingestion.connection.bootstrap.skipped — no rag-enabled DataSources connectionId=%s", connectionId)
|
||||
return {"connectionId": connectionId, "authority": authority, "skipped": True, "reason": "no_data_sources"}
|
||||
```
|
||||
2. **Walker-Aufrufe** erhalten zusätzlich `dataSources=dataSources_filtered_by_sourceType`:
|
||||
```python
|
||||
spDataSources = [ds for ds in dataSources if ds["sourceType"] == "sharepointFolder"]
|
||||
olDataSources = [ds for ds in dataSources if ds["sourceType"] in ("outlookFolder", "calendarFolder", "contactFolder")]
|
||||
# ...
|
||||
spResult, olResult = await asyncio.gather(
|
||||
bootstrapSharepoint(connectionId=connectionId, progressCb=progressCb, dataSources=spDataSources),
|
||||
bootstrapOutlook(connectionId=connectionId, progressCb=progressCb, dataSources=olDataSources),
|
||||
return_exceptions=True,
|
||||
)
|
||||
```
|
||||
- **Authority-Mapping (DataSource.sourceType → Walker):**
|
||||
| Authority | sourceType-Werte | Walker |
|
||||
|-----------|------------------|--------|
|
||||
| `msft` | `sharepointFolder`, `onedriveFolder` | `bootstrapSharepoint` |
|
||||
| `msft` | `outlookFolder`, `calendarFolder`, `contactFolder` | `bootstrapOutlook` |
|
||||
| `google` | `googleDriveFolder` | `bootstrapGdrive` |
|
||||
| `google` | `gmailFolder` | `bootstrapGmail` |
|
||||
| `clickup` | `clickupList` | `bootstrapClickup` |
|
||||
| `infomaniak` | `kdriveFolder` | `bootstrapKdrive` *(falls implementiert)* |
|
||||
|
||||
### B.2 Walker-Signatur erweitern (alle 5)
|
||||
|
||||
Pro Walker-Datei (`subConnectorSyncSharepoint.py`, `subConnectorSyncOutlook.py`, `subConnectorSyncGdrive.py`,
|
||||
`subConnectorSyncGmail.py`, `subConnectorSyncClickup.py`):
|
||||
|
||||
- **Signatur-Erweiterung:**
|
||||
```python
|
||||
async def bootstrapSharepoint(
|
||||
connectionId: str,
|
||||
*,
|
||||
dataSources: Optional[List[Dict[str, Any]]] = None, # NEU
|
||||
progressCb: Optional[JobProgressCallback] = None, # JobProgressCallback statt Callable
|
||||
adapter: Any = None,
|
||||
connection: Any = None,
|
||||
knowledgeService: Any = None,
|
||||
limits: Optional[SharepointBootstrapLimits] = None,
|
||||
runExtractionFn: Optional[Callable[..., Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
```
|
||||
- **Logik-Kern:**
|
||||
- Wenn `dataSources` `None` oder leer → **frühzeitig return** mit `{"skipped": True, "reason": "no_data_sources"}`.
|
||||
- Sonst: Schleife über `dataSources`. Für jede `ds`:
|
||||
- Effektive `neutralize`-Policy berechnen (siehe B.4).
|
||||
- `_walkFolder(folderPath=ds["path"], ...)` aufrufen — statt heutigem `adapter.browse("/")`.
|
||||
- **Vor jedem File-Call** und alle 50 Items: `if progressCb.isCancelled(): break`.
|
||||
- **Pref-Lookup raus:** `loadConnectionPrefs(connectionId).neutralizeBeforeEmbed` → entfernen; ersetzen durch DataSource-Wert.
|
||||
|
||||
### B.3 Provenance um `dataSourceId` erweitern
|
||||
|
||||
- **In `_ingestOne`** (alle Walker): provenance-Dict erhält neuen Key:
|
||||
```python
|
||||
provenance: Dict[str, Any] = {
|
||||
"connectionId": connectionId,
|
||||
"dataSourceId": dataSourceId, # NEU
|
||||
"authority": "msft",
|
||||
"service": "sharepoint",
|
||||
...
|
||||
}
|
||||
```
|
||||
- **`KnowledgeService.requestIngestion`**: keine Schema-Änderung nötig — `provenance` ist bereits ein freies Dict.
|
||||
- **`interfaceDbKnowledge.deleteFileContentIndexByDataSource(dataSourceId)`** (aus Phase A.7) liest exakt diesen Key.
|
||||
|
||||
### B.4 Effektive Policy aus DataSource (mit Tree-Vererbung)
|
||||
|
||||
- **Datei:** `gateway/modules/serviceCenter/services/serviceKnowledge/subPolicyResolver.py` **(neu)**
|
||||
- **Funktion:**
|
||||
```python
|
||||
def resolveEffectivePolicy(
|
||||
ds: Dict[str, Any],
|
||||
allDataSources: List[Dict[str, Any]],
|
||||
) -> Dict[str, Any]:
|
||||
"""Compute effective neutralize / ragIndexEnabled by walking up the path tree.
|
||||
|
||||
Inheritance rule: nearest ancestor DataSource with explicit value wins.
|
||||
Fallback: own value or False.
|
||||
"""
|
||||
```
|
||||
- **Aufrufer:** Walker während der Iteration (nur einmal pro DataSource, nicht pro File — Performance).
|
||||
- **Test-Hook:** Unit-Test mit synthetischer DataSource-Hierarchie (`/Site/Docs`, `/Site/Docs/Sub`, `/Site/Docs/Sub/Other`).
|
||||
|
||||
### B.5 Cancel-Check-Punkte (verbindlich)
|
||||
|
||||
| Walker | Cancel-Check-Stellen |
|
||||
|--------|----------------------|
|
||||
| `subConnectorSyncSharepoint._walkFolder` | Vor `_walkFolder`-Rekursion + alle 50 `_ingestOne` |
|
||||
| `subConnectorSyncOutlook` | Vor jedem Folder-Wechsel + alle 50 Mails |
|
||||
| `subConnectorSyncGdrive` | Vor jeder Page der Drive-Listing + alle 50 Files |
|
||||
| `subConnectorSyncGmail` | Pro Thread-Page + alle 50 Mails |
|
||||
| `subConnectorSyncClickup` | Pro Liste + alle 50 Tasks |
|
||||
|
||||
- **Wenn Cancel erkannt:** Funktion gibt `result.cancelled = True` zurück. `_finalizeResult` propagiert Feld.
|
||||
- **Job-Result:** Dispatcher hängt `cancelled: true, processedSoFar: N` an.
|
||||
|
||||
### B.6 Acceptance-Gate Phase B
|
||||
|
||||
- [ ] Walker iteriert nur über `dataSources`-Parameter — verifiziert via Mock-Test ohne DataSources (returns skipped).
|
||||
- [ ] Cancel-Test: Job wird via `cancelJob()` gestoppt, Walker bricht innerhalb 50 Items ab, Job-Status = `CANCELLED`, `result.cancelled=True`, `processedSoFar` > 0.
|
||||
- [ ] Provenance enthält `dataSourceId` in allen 5 Connectoren.
|
||||
- [ ] Bestehende Idempotenz (Hash-basiert) bleibt unverändert — gleicher Re-Run produziert `duplicate`.
|
||||
- [ ] `_scheduledDailyResync`: enqueued Bootstrap nur für Connections mit min. 1 `ragIndexEnabled` DataSource (sonst schon im Dispatcher abgefangen — Test verifiziert dass Job sauber `skipped` returned).
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase C — Routes (Connection + DataSource + Inventar)
|
||||
|
||||
**Ziel:** Frontend kann Consent + RAG-Index-Toggle + Stop überall ansprechen; Inventar-Daten lesen.
|
||||
|
||||
### C.1 `routeDataConnections` GET-Response erweitern
|
||||
|
||||
- **Datei:** `gateway/modules/routes/routeDataConnections.py`
|
||||
- **Refactor:** `_buildEnhancedItems` (Z. 183–202) zu Helper-Funktion erheben:
|
||||
```python
|
||||
def _buildConnectionDict(connection, tokenStatus, tokenExpiresAt) -> Dict[str, Any]:
|
||||
return {
|
||||
"id": connection.id,
|
||||
"userId": connection.userId,
|
||||
"authority": connection.authority.value if hasattr(connection.authority, 'value') else str(connection.authority),
|
||||
# ... (bestehende Felder)
|
||||
# NEU:
|
||||
"knowledgeIngestionEnabled": getattr(connection, "knowledgeIngestionEnabled", False),
|
||||
"knowledgePreferences": getattr(connection, "knowledgePreferences", {}) or {},
|
||||
}
|
||||
```
|
||||
- An allen **3 Aufrufstellen** (`_buildEnhancedItems`, groupSummary-Block Z. 248–264, Haupt-Block Z. 282–298) Helper aufrufen.
|
||||
- **Zusätzliche Anreicherung** (für `RagInventoryPage`-Effizienz, optional v1):
|
||||
- `dataSourceCount`, `ragEnabledDataSourceCount`, `runningJobCount`, `lastIndexedAt` — leichte SQL-Aggregations-Calls; können in v2 nachgeliefert werden.
|
||||
|
||||
### C.2 Neue PATCH-Endpoints `routeDataConnections`
|
||||
|
||||
- **Datei:** `gateway/modules/routes/routeDataConnections.py`
|
||||
- **Endpoints:**
|
||||
|
||||
```python
|
||||
@router.patch("/{connectionId}/knowledge-consent")
|
||||
@limiter.limit("10/minute")
|
||||
def updateKnowledgeConsent(
|
||||
request: Request,
|
||||
connectionId: str = Path(...),
|
||||
enabled: bool = Body(..., embed=True),
|
||||
currentUser: User = Depends(getCurrentUser),
|
||||
):
|
||||
"""Master switch: kann PowerOn aus dieser Connection in den RAG ingesten?
|
||||
|
||||
enabled=False:
|
||||
- synchronous purge ALL chunks (deleteFileContentIndexByConnectionId)
|
||||
- cancelJobsByConnection(connectionId)
|
||||
- audit_logger.logEvent(category=PERMISSION, action=PERMISSION_REVOKED, ...)
|
||||
enabled=True:
|
||||
- flag setzen
|
||||
- if any DataSource with ragIndexEnabled=True exists: enqueue bootstrap
|
||||
- audit_logger.logEvent(...)
|
||||
"""
|
||||
```
|
||||
```python
|
||||
@router.patch("/{connectionId}/knowledge-preferences")
|
||||
@limiter.limit("20/minute")
|
||||
def updateKnowledgePreferences(
|
||||
request: Request,
|
||||
connectionId: str = Path(...),
|
||||
preferences: Dict[str, Any] = Body(..., embed=True),
|
||||
currentUser: User = Depends(getCurrentUser),
|
||||
):
|
||||
"""Mail-Tiefe / ClickUp-Scope / Anhänge / maxAgeDays.
|
||||
|
||||
Validierung: nur whitelisted Keys (mailContentDepth, mailIndexAttachments,
|
||||
clickupScope, clickupIndexAttachments, maxAgeDays) werden gespeichert.
|
||||
Optional: Resync-Trigger (Body-Flag triggerResync=False default).
|
||||
"""
|
||||
```
|
||||
```python
|
||||
@router.post("/{connectionId}/knowledge-stop")
|
||||
@limiter.limit("10/minute")
|
||||
def stopKnowledgeJobs(
|
||||
request: Request,
|
||||
connectionId: str = Path(...),
|
||||
currentUser: User = Depends(getCurrentUser),
|
||||
):
|
||||
"""Cancel alle laufenden Bootstrap-Jobs dieser Connection.
|
||||
|
||||
Returns: { cancelled: int, jobIds: List[str] }
|
||||
"""
|
||||
```
|
||||
- **Owner-Check:** Alle drei Endpoints validieren `connection.userId == currentUser.id` (oder MandantAdmin/SysAdmin) — Pattern aus `delete_connection` (Z. 669) übernehmen.
|
||||
- **Audit:** Jede der drei Operationen loggt via `audit_logger.logEvent(category=AuditCategory.PERMISSION, action="knowledge_consent_changed" / "knowledge_preferences_changed" / "knowledge_jobs_stopped", details={...})`.
|
||||
|
||||
### C.3 Neuer PATCH `routeDataSources/rag-index`
|
||||
|
||||
- **Datei:** `gateway/modules/routes/routeDataSources.py`
|
||||
- **Neuer Endpoint nach Z. 127:**
|
||||
```python
|
||||
@router.patch("/{sourceId}/rag-index")
|
||||
@limiter.limit("30/minute")
|
||||
def _updateDataSourceRagIndex(
|
||||
request: Request,
|
||||
sourceId: str = Path(..., description="ID of the DataSource"),
|
||||
ragIndexEnabled: bool = Body(..., embed=True),
|
||||
context: RequestContext = Depends(getRequestContext),
|
||||
) -> Dict[str, Any]:
|
||||
"""Toggle RAG-Indexierung für eine DataSource.
|
||||
|
||||
true: Flag setzen + mini-bootstrap-Job (Walker filtert auf diese eine DataSource).
|
||||
false: Flag setzen + sync purge (deleteFileContentIndexByDataSource).
|
||||
Audit-Log via PERMISSION-Kategorie.
|
||||
"""
|
||||
```
|
||||
- **Mini-Bootstrap-Job:** Reuse `connection.bootstrap` mit Payload-Erweiterung `{"dataSourceIds": [sourceId]}` — Dispatcher honoriert das Filter-Set (Phase B.1 Erweiterung).
|
||||
- **Tabular (FeatureDataSource):** **Nicht** in v1. `FeatureDataSource` wird über `neutralize` + `neutralizeFields` gesteuert, RAG-Tabellen-Indexierung kommt in separater Iteration (siehe Konzept Sektion „Granularität").
|
||||
|
||||
### C.4 Neuer Route-Block `routeRagInventory`
|
||||
|
||||
- **Datei:** `gateway/modules/routes/routeRagInventory.py` **(neu)**
|
||||
- **Endpoints:**
|
||||
```
|
||||
GET /api/rag/inventory/me → eigene Daten (Connections + DataSources + Chunks-Counts)
|
||||
GET /api/rag/inventory/mandate → Mandate-Aggregation (MandantAdmin only)
|
||||
GET /api/rag/inventory/platform → System-Aggregation (PlatformAdmin only)
|
||||
GET /api/rag/inventory/jobs → Active jobs für Header-Badge (alle Scopes je nach Role)
|
||||
```
|
||||
- **Response-Schema (`/me`):**
|
||||
```jsonc
|
||||
{
|
||||
"connections": [
|
||||
{
|
||||
"id": "...", "authority": "msft",
|
||||
"knowledgeIngestionEnabled": true,
|
||||
"preferences": { "mailContentDepth": "full", ... },
|
||||
"dataSources": [
|
||||
{ "id": "...", "label": "Documents/Reports", "path": "...",
|
||||
"sourceType": "sharepointFolder", "ragIndexEnabled": true,
|
||||
"neutralize": false, "lastIndexed": 1234567890.0, "chunkCount": 412 }
|
||||
],
|
||||
"runningJobs": [{ "jobId": "...", "progress": 47, "progressMessage": "sharepoint processed=200" }]
|
||||
}
|
||||
],
|
||||
"totals": { "chunks": 5432, "bytes": 12345678 }
|
||||
}
|
||||
```
|
||||
- **Implementation:** zusammengesetzt aus existierenden Interfaces:
|
||||
- `getRootInterface().getUserConnections(userId)` → Connections
|
||||
- `getRootInterface().db.getRecordset(DataSource, ...)` → DataSources
|
||||
- `interfaceDbKnowledge.listFileContentIndexByConnection(connectionId)` → Chunk-Counts
|
||||
- `serviceBackgroundJobs.listJobs(jobType="connection.bootstrap", ...)` → Running-Jobs
|
||||
|
||||
### C.5 WorkspaceRagInsights-Backend-Route entfernen
|
||||
|
||||
- **Datei:** `gateway/modules/features/workspace/routeFeatureWorkspace.py`
|
||||
- **Löschen:** Ab Z. 2219 (`@router.get("/{instanceId}/rag-statistics")`) bis Funktionsende.
|
||||
- **Datei:** `gateway/modules/features/workspace/mainWorkspace.py`
|
||||
- **Löschen:** UI-Permission-Block für `ui.feature.workspace.rag-insights` an Z. 37–38; Permission-Einträge an Z. 89, 100. (RBAC-Tree wird durch die nächste `_copyTemplateRoles`-Auflösung sauber.)
|
||||
- **Datei:** `gateway/modules/interfaces/interfaceDbKnowledge.py`
|
||||
- **Löschen oder behalten?** `getRagStatisticsForInstance` (Z. 421) — falls keine anderen Aufrufer (Audit per Suche), löschen. Sonst behalten.
|
||||
|
||||
### C.6 Acceptance-Gate Phase C
|
||||
|
||||
- [ ] `GET /api/connections/` enthält `knowledgeIngestionEnabled` + `knowledgePreferences` in allen 3 Code-Pfaden.
|
||||
- [ ] `PATCH /knowledge-consent enabled=false` purged synchron + cancelt laufende Jobs (Test: Job wird CANCELLED innerhalb 5s).
|
||||
- [ ] `PATCH /knowledge-consent enabled=true` enqueued Bootstrap nur wenn min. 1 RAG-DataSource existiert.
|
||||
- [ ] `PATCH /datasources/{id}/rag-index true` enqueued Mini-Bootstrap mit `dataSourceIds=[id]`.
|
||||
- [ ] `PATCH /datasources/{id}/rag-index false` purged Chunks dieser DataSource (Verifikation via `listFileContentIndexByDataSource`).
|
||||
- [ ] `GET /api/rag/inventory/me` liefert konsistente Counts.
|
||||
- [ ] Owner-Check funktioniert: Fremder User bekommt 403.
|
||||
- [ ] Audit-Log enthält Einträge `knowledge_consent_changed`, `rag_index_toggled`, `knowledge_jobs_stopped`.
|
||||
- [ ] `GET /api/workspace/{id}/rag-statistics` ist 404.
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase D — Frontend
|
||||
|
||||
**Ziel:** Wizard auf 3-Step-Basis-Flow; UDB hat 4. Toggle-Button; ConnectionsPage zeigt Master-Toggle + Stop;
|
||||
neue `RagInventoryPage` ist erreichbar; Header-Badge zeigt laufende Jobs; alte WorkspaceRagInsights-Seite weg.
|
||||
|
||||
### D.1 `connectionApi.ts` reduzieren
|
||||
|
||||
- **Datei:** `frontend_nyla/src/api/connectionApi.ts`
|
||||
- **Änderung:**
|
||||
- `KnowledgePreferences`-Type: `neutralizeBeforeEmbed`, `surfaceToggles`, `mimeAllowlist` entfernen.
|
||||
- **Neue API-Methoden:**
|
||||
```ts
|
||||
export async function patchKnowledgeConsent(connectionId: string, enabled: boolean): Promise<void>;
|
||||
export async function patchKnowledgePreferences(connectionId: string, prefs: KnowledgePreferences, opts?: { triggerResync?: boolean }): Promise<void>;
|
||||
export async function postKnowledgeStop(connectionId: string): Promise<{ cancelled: number; jobIds: string[] }>;
|
||||
export async function patchDataSourceRagIndex(dataSourceId: string, enabled: boolean): Promise<void>;
|
||||
export async function getRagInventoryMe(): Promise<RagInventoryDto>;
|
||||
export async function getRagInventoryMandate(): Promise<RagInventoryDto>;
|
||||
export async function getRagInventoryPlatform(): Promise<RagInventoryDto>;
|
||||
export async function getRagJobsActive(): Promise<RagJobDto[]>;
|
||||
```
|
||||
|
||||
### D.2 `useConnections` Hook erweitern
|
||||
|
||||
- **Datei:** `frontend_nyla/src/hooks/useConnections.ts`
|
||||
- **Neu exportieren:**
|
||||
- `setKnowledgeConsent(connectionId, enabled)` — wrapt `patchKnowledgeConsent` + `refetch()`.
|
||||
- `setKnowledgePreferences(connectionId, prefs, opts)` — wrapt `patchKnowledgePreferences` + `refetch()`.
|
||||
- `stopKnowledgeJobs(connectionId)` — wrapt `postKnowledgeStop`.
|
||||
- **Bestehend bleibt:** `createInfomaniakConnection`, `submitInfomaniakToken` werden vom Wizard intern aufgerufen (siehe D.3).
|
||||
|
||||
### D.3 `AddConnectionWizard.tsx` Komplett-Refactor
|
||||
|
||||
- **Datei:** `frontend_nyla/src/components/AddConnectionWizard/AddConnectionWizard.tsx`
|
||||
- **Entfernen:**
|
||||
- `computeCostEstimate` (Z. 71–151).
|
||||
- Step 2 (Preferences): Z. 302–397.
|
||||
- `neutralizeBeforeEmbed`-Checkbox: Z. 314–323.
|
||||
- Cost-Block in Summary: Z. 458–493.
|
||||
- `KnowledgePreferences`-Defaults: nur noch leere Defaults für die behaltenen Keys.
|
||||
- **Neu strukturieren — Step-Definition wird connector-aware:**
|
||||
```ts
|
||||
type StepId = 'connector' | 'consent' | 'msftAdminConsent' | 'infomaniakPat' | 'connect';
|
||||
|
||||
function getStepsForConnector(c: ConnectorType | null): StepId[] {
|
||||
if (c === 'msft') return ['connector', 'consent', 'msftAdminConsent', 'connect'];
|
||||
if (c === 'infomaniak') return ['connector', 'consent', 'infomaniakPat', 'connect'];
|
||||
return ['connector', 'consent', 'connect']; // google, clickup
|
||||
}
|
||||
```
|
||||
- **Neuer Step `msftAdminConsent`:**
|
||||
- Logik aus `ConnectionsPage.handleAdminConsent` hereinholen (Pattern: Popup-Window mit `…/api/security/msft/admin-consent` URL).
|
||||
- Hinweistext: „Falls du Mandant-Administrator bist, kannst du jetzt für deine ganze Organisation zustimmen, sodass nicht jeder User einzeln zustimmen muss."
|
||||
- **Optional**-Step: User kann „Überspringen" klicken; Standard-User-Consent läuft trotzdem im finalen `connect`-Step.
|
||||
- **Neuer Step `infomaniakPat`:**
|
||||
- Logik aus `handleCreateInfomaniak` + `handleInfomaniakSubmit` (Z. 245+) hereinholen.
|
||||
- Sequenz: Wizard ruft `createInfomaniakConnection()` → User pasted PAT → `submitInfomaniakToken(connectionId, token)` → Wizard schliesst, `connect`-Step entfällt für Infomaniak (Connection ist schon da).
|
||||
- Cancel: `deleteConnection(connectionId)` aufrufen, falls User Wizard schliesst ohne Token.
|
||||
- **Neuer Connector hinzufügen:** `ConnectorType = 'google' | 'msft' | 'clickup' | 'infomaniak'`; Icon + Label entsprechend ergänzen.
|
||||
- **`onConnect`-Signatur:** vereinfacht zu
|
||||
```ts
|
||||
onConnect: (type: ConnectorType, knowledgeEnabled: boolean) => Promise<void>;
|
||||
```
|
||||
(kein `prefs` mehr — Standard-Defaults werden Backend-seitig bei OAuth-Callback gesetzt.)
|
||||
|
||||
### D.4 `ConnectionsPage.tsx` aufräumen
|
||||
|
||||
- **Datei:** `frontend_nyla/src/pages/basedata/ConnectionsPage.tsx`
|
||||
- **Entfernen:**
|
||||
- `adminConsentPending` State + Handler (Z. 57, alle Verwendungen).
|
||||
- `infomaniakModal` State + `handleCreateInfomaniak`, `handleInfomaniakCancel`, `handleInfomaniakSubmit` (Z. 78–...).
|
||||
- Den Standalone-Button „Admin-Zustimmung" (Suche im File: `Admin-Zustimmung` oder `adminConsent`).
|
||||
- Den Standalone-Button „Infomaniak verbinden" (Suche: `Infomaniak`-Button-Render).
|
||||
- **Neue UI pro Row** (in `FormGeneratorTable`-Custom-Renderer oder Side-Drawer):
|
||||
- **Master-Toggle „Wissensdatenbank"** (Switch-Style) — bound an `connection.knowledgeIngestionEnabled`.
|
||||
- Off-Confirm-Dialog: „X Inhalte werden aus dem RAG entfernt".
|
||||
- **Status-Pille** (rechts neben Toggle):
|
||||
- „Indexierung läuft (47%) · ✕ Stop" wenn `runningJobs.length > 0`.
|
||||
- „Letzte Indexierung: vor 3 Stunden" sonst.
|
||||
- **„Einstellungen"-Link** öffnet `KnowledgePreferencesDrawer` (siehe D.5).
|
||||
- **Banner `syncBanner`** (Z. 60+): bleibt; wird aber durch `runningJobs`-Polling abgelöst (alle 5s).
|
||||
|
||||
### D.5 Neue Komponente `KnowledgePreferencesDrawer`
|
||||
|
||||
- **Datei:** `frontend_nyla/src/components/KnowledgePreferencesDrawer/KnowledgePreferencesDrawer.tsx` **(neu)**
|
||||
- **Inhalt:** Felder analog zu altem Wizard-Step 2, aber **ohne** `neutralizeBeforeEmbed`:
|
||||
- `mailContentDepth` (Select: metadata / snippet / full)
|
||||
- `mailIndexAttachments` (Checkbox)
|
||||
- `clickupScope` (Select: titles / title_description / with_comments)
|
||||
- `clickupIndexAttachments` (Checkbox)
|
||||
- `maxAgeDays` (Number)
|
||||
- **Connector-aware Sichtbarkeit:** Mail-Felder nur bei `google`/`msft`; ClickUp-Felder nur bei `clickup`.
|
||||
- **Speichern:** `setKnowledgePreferences(connectionId, prefs, { triggerResync: true })` — User wird gefragt ob Resync.
|
||||
|
||||
### D.6 `SourcesTab.tsx` — 4. Action-Button für `ragIndexEnabled`
|
||||
|
||||
- **Datei:** `frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx`
|
||||
- **Erweiterungen:**
|
||||
1. Type `UdbDataSource` (Z. 36–45): `ragIndexEnabled: boolean` ergänzen.
|
||||
2. **Konstante neu:**
|
||||
```ts
|
||||
const _RAG_INDEX_ICON = '🧠'; // oder Material-Icon
|
||||
```
|
||||
3. **Render-Erweiterung** in jedem Tree-Renderer (`TreeNode`, `FeatureRecordRenderer`, `ParentGroupRenderer`):
|
||||
- Neue Prop: `inheritedRagIndexEnabled?: boolean` analog zu `inheritedNeutralize`.
|
||||
- `effectiveRagIndex = ds?.ragIndexEnabled ?? inheritedRagIndexEnabled ?? false`.
|
||||
- 4. Button rendern, parallele CSS zu `inheritedNeutralize` (Opazität 0.35 wenn `false`/inherited).
|
||||
4. **Toggle-Handler** (analog zu `_toggleNeutralize`):
|
||||
```ts
|
||||
const _toggleRagIndex = useCallback(async (ds: UdbDataSource | null, ...) => {
|
||||
if (!ds) {
|
||||
// DataSource on-demand erstellen via POST /api/datasources, dann PATCH /rag-index
|
||||
} else {
|
||||
await api.patch(`/api/datasources/${ds.id}/rag-index`, { ragIndexEnabled: !ds.ragIndexEnabled });
|
||||
// Bei Off: confirm "Chunks werden entfernt"
|
||||
}
|
||||
onSourcesChanged?.();
|
||||
}, [onSourcesChanged]);
|
||||
```
|
||||
5. **Vererbungs-Visualisierung:** identisch zur `inheritedNeutralize`-Implementierung (gestrichelter Border, gedimmt).
|
||||
|
||||
### D.7 Neue Seite `RagInventoryPage.tsx`
|
||||
|
||||
- **Datei:** `frontend_nyla/src/pages/system/RagInventoryPage.tsx` **(neu)**
|
||||
- **3 Tabs (`PageTabsLayout`-Komponente):**
|
||||
| Tab | Endpoint | Sichtbarkeit | Inhalt |
|
||||
|-----|----------|--------------|--------|
|
||||
| **Meine Daten** | `GET /api/rag/inventory/me` | Alle User | Pro Connection: Card mit Master-Toggle + Preferences-Link + DataSource-Liste (Toggle, Last-Indexed, Chunk-Count, Stop-Button bei Job) |
|
||||
| **Mandant** | `GET /api/rag/inventory/mandate` | MandantAdmin | Aggregation pro User; Top-Contributors; Total-Chunks |
|
||||
| **Plattform** | `GET /api/rag/inventory/platform` | PlatformAdmin | System-Stats; Cost-Tracker-Placeholder |
|
||||
- **Polling:** `getRagJobsActive()` alle 5s, um Progress-Bars + Stop-Buttons aktuell zu halten.
|
||||
|
||||
### D.8 Navigation: `Start > Nutzung > RAG-Inventar`
|
||||
|
||||
- **Datei:** `gateway/modules/system/mainSystem.py`
|
||||
- **Neuer Eintrag** unter Gruppe „Nutzung" (vermutlich `usage` / `nutzung`):
|
||||
```python
|
||||
{
|
||||
"objectKey": "page.system.ragInventory",
|
||||
"label": t("RAG-Inventar"),
|
||||
"uiComponent": "page.system.ragInventory",
|
||||
"permission": "page.system.ragInventory",
|
||||
"group": "nutzung",
|
||||
"icon": "FaDatabase",
|
||||
"order": NN,
|
||||
}
|
||||
```
|
||||
- **Datei:** `frontend_nyla/src/config/pageRegistry.tsx`
|
||||
- **Mapping:**
|
||||
```tsx
|
||||
'page.system.ragInventory': { component: lazy(() => import('../pages/system/RagInventoryPage')), icon: FaDatabase },
|
||||
```
|
||||
|
||||
### D.9 Header-Badge `RagRunningBadge`
|
||||
|
||||
- **Datei:** `frontend_nyla/src/components/Header/RagRunningBadge.tsx` **(neu)**
|
||||
- **Logik:**
|
||||
- `useEffect`: alle 5s `getRagJobsActive()`; State `count: number`.
|
||||
- Render: nur wenn `count > 0` — kleines Icon (`FaSync` rotierend) mit Badge `count`; Click → Navigate `/system/rag-inventory`.
|
||||
- **Einbindung:** in `Header.tsx` neben anderen Status-Badges.
|
||||
|
||||
### D.10 Workspace-Insights-Seite löschen
|
||||
|
||||
- **Löschen:**
|
||||
- `frontend_nyla/src/pages/views/workspace/WorkspaceRagInsightsPage.tsx`
|
||||
- `frontend_nyla/src/pages/views/workspace/WorkspaceRagInsightsPage.module.css`
|
||||
- **Datei:** `frontend_nyla/src/pages/FeatureView.tsx`
|
||||
- **Entfernen:** Import Z. 38, Mapping `'rag-insights': WorkspaceRagInsightsPage` Z. 158.
|
||||
- **Datei:** `frontend_nyla/src/pages/views/workspace/WorkspacePage.tsx`
|
||||
- **Prüfen:** Ob Verweise auf die Insights-Seite (Buttons, Links) drin sind — entfernen.
|
||||
- **Datei:** `frontend_nyla/src/api/mandate.ts` (oder vergleichbare Mapping-Stelle)
|
||||
- **Prüfen:** Ob Workspace-Sub-View `'rag-insights'` enumeriert ist — entfernen.
|
||||
|
||||
### D.11 Acceptance-Gate Phase D
|
||||
|
||||
- [ ] Wizard für `msft`: Step-Sequenz `connector → consent → msftAdminConsent → connect`.
|
||||
- [ ] Wizard für `infomaniak`: Step-Sequenz `connector → consent → infomaniakPat`. PAT-Cancel löscht Connection.
|
||||
- [ ] Wizard für `google`/`clickup`: Step-Sequenz `connector → consent → connect`. Keine Cost-Anzeige sichtbar.
|
||||
- [ ] `ConnectionsPage`: Standalone-Buttons „Admin-Zustimmung" und „Infomaniak verbinden" sind weg.
|
||||
- [ ] `ConnectionsPage`: Master-Toggle pro Row togglet `knowledgeIngestionEnabled`; Off-Confirm zeigt Item-Count.
|
||||
- [ ] `ConnectionsPage`: Status-Pille zeigt Progress + Stop-Button bei laufendem Job.
|
||||
- [ ] `SourcesTab`: 4. Toggle-Button verfügbar; Vererbung visuell (gedimmt) korrekt; Toggle führt PATCH aus.
|
||||
- [ ] `RagInventoryPage` erreichbar via `Start > Nutzung > RAG-Inventar`; Tabs Mandant/Plattform sind hinter Permission.
|
||||
- [ ] Header-Badge erscheint nur wenn aktive Jobs; Click navigiert zur Inventory-Page.
|
||||
- [ ] `WorkspaceRagInsightsPage` ist nicht mehr ladbar (404 / Route-Removed).
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase E — Tests E2E
|
||||
|
||||
### E.1 Backend-Integrationstests
|
||||
|
||||
- **Datei:** `gateway/modules/serviceCenter/services/serviceKnowledge/tests/test_bootstrapDispatcher.py` (neu/erweitern)
|
||||
- **Tests:**
|
||||
- `test_bootstrap_skipsIfNoRagEnabledDataSources`
|
||||
- `test_bootstrap_iteratesOnlyRagEnabledDataSources`
|
||||
- `test_bootstrap_respectsCancelFlag` (Walker stoppt innerhalb 50 Items)
|
||||
- `test_dataSourceToggleOff_purgesChunks` (E2E PATCH → Chunk-Count = 0)
|
||||
- `test_consentOff_purgesAllConnectionChunks_andCancelsJobs`
|
||||
- `test_consentOn_enqueuesBootstrap_onlyIfDataSourcesExist`
|
||||
|
||||
### E.2 Backend-Migration-Test
|
||||
|
||||
- `test_migration_renamesAutoSyncToRagIndexEnabled` — auf Test-DB.
|
||||
|
||||
### E.3 Frontend-Integrationstests
|
||||
|
||||
- **Datei:** `frontend_nyla/cypress/integration/ragConsentControl.spec.ts` (neu, falls Cypress vorhanden)
|
||||
- **Tests:**
|
||||
- Wizard MSFT-Flow inkl. Admin-Consent-Step
|
||||
- Wizard Infomaniak-Flow inkl. PAT-Cancel
|
||||
- Master-Toggle Off → Confirm → Purge → leerer State
|
||||
- SourcesTab-RAG-Toggle Vererbung visuell + Backend-Call
|
||||
- RagInventoryPage Tab-Wechsel + Permission-Block
|
||||
|
||||
### E.4 Acceptance-Gate Phase E
|
||||
|
||||
- [ ] Alle Backend-Tests grün.
|
||||
- [ ] Mindestens 1 Frontend-E2E-Flow grün (MSFT-Wizard + UDB-Toggle).
|
||||
|
||||
---
|
||||
|
||||
## 7. Phase F — Doku & Cleanup
|
||||
|
||||
### F.1 Wiki-Updates
|
||||
|
||||
- **Datei:** `wiki/b-reference/integrations.md` (oder `data-sources.md`) — neue Architektur dokumentieren.
|
||||
- **Datei:** `wiki/b-reference/security.md` — Consent-/Audit-Pattern aktualisieren.
|
||||
- **Datei:** `wiki/TOPICS.md` — RAG-Inventar als neuen Topic-Eintrag.
|
||||
- **Datei:** `wiki/c-work/_CHANGELOG.md` — Implementierungs-Phasen-Abschluss-Eintrag.
|
||||
|
||||
### F.2 Konzept-Plan archivieren
|
||||
|
||||
- Nach Merge: beide Dokumente von `1-plan/` nach `4-done/` schieben:
|
||||
- `2026-05-rag-consent-and-control-unification.md`
|
||||
- `2026-05-rag-consent-and-control-implementation.md`
|
||||
|
||||
### F.3 Release-Notes
|
||||
|
||||
- Pro Release-Note-Format: User-facing Beschreibung der neuen RAG-Inventar-Seite, Wizard-Vereinfachung, Per-Element-Toggle.
|
||||
|
||||
---
|
||||
|
||||
## 8. Risiken & Mitigations
|
||||
|
||||
| Risiko | Mitigation |
|
||||
|--------|------------|
|
||||
| Migration `autoSync` → `ragIndexEnabled` betrifft Production-Daten ohne Verlust, aber falls historische Daten den alten Spaltennamen referenzieren (Backups, externe Reports) → unklar. | Migration-Skript auf Staging zuerst; Backup vor Prod-Run. |
|
||||
| Cancel-Check via DB-Read alle 3s könnte unter Last skalierungs-kritisch werden. | 3s-Cache ist Worst-Case 1 Read pro Walker-Iteration; bei <100 parallelen Walkern unkritisch. Re-Evaluate bei Production-Load. |
|
||||
| Walker-Refactor bricht bestehende Production-Bootstraps in der Übergangsphase. | Phase A+B in **einem Deployment** mergen; Migration `ragIndexEnabled DEFAULT FALSE` heisst: Bestehende Connections walken **nichts** mehr → Daily-Resync ist faktisch No-Op bis User explizit Toggles setzt. **Akzeptabel** weil: heutige Walks sind ohnehin OAuth-global und gehören rechtlich neu opt-in'd zu werden (DSGVO-Rationale). User-Kommunikation in Release-Notes nötig. |
|
||||
| Bestehende RAG-Chunks haben kein `provenance.dataSourceId` → können nicht per DataSource-Purge entfernt werden. | Legacy-Chunks bleiben drin bis Connection-weiter Purge ausgelöst wird (Master-Toggle Off oder Disconnect). One-Off-Cleanup-Job kann später als separates Ticket erfolgen. |
|
||||
| Wizard-Refactor + ConnectionsPage gleichzeitig → grosses Frontend-Diff. | Wizard und Page in **separaten Commits**, getrennt review-bar. |
|
||||
| Header-Badge-Polling alle 5s × N Tabs könnte API-Last erzeugen. | Polling pausieren wenn Tab inaktiv (Page Visibility API); nur 1 Polling pro App-Mount via React-Context. |
|
||||
|
||||
---
|
||||
|
||||
## 9. Decision Points (vor Phase A festziehen)
|
||||
|
||||
- [ ] **D-1: Renaming oder Re-Doku?** Vorgeschlagen: **Renaming** (`autoSync` → `ragIndexEnabled`, `lastSynced` → `lastIndexed`). Begründung: F1 zeigt 0 Code-Verwendungen — risikofrei.
|
||||
- [ ] **D-2: Tabular RAG-Toggle?** Vorgeschlagen: **Nicht in v1** — `FeatureDataSource` bleibt mit `neutralize` + `neutralizeFields`; RAG-Tabellen-Indexierung kommt in Folge-Iteration.
|
||||
- [ ] **D-3: Legacy-Chunks One-Off-Purge?** Vorgeschlagen: **Nicht im Scope dieses Plans**; separates Ticket später. Begründung: Bestehende Chunks sind funktional, gefährden keine UX.
|
||||
- [ ] **D-4: Cost-Tracking-Placeholder in Plattform-Tab?** Vorgeschlagen: **Nur leerer Tab + TODO** — echtes Cost-Tracking ist eigenes Epic.
|
||||
- [ ] **D-5: Audit-Log-Detailgrad?** Vorgeschlagen: 1 Eintrag pro PATCH (User, Connection-ID, Old-Value, New-Value). Genügt DSGVO + Forensik.
|
||||
|
||||
---
|
||||
|
||||
## 10. Time Estimate (T-Shirt)
|
||||
|
||||
| Phase | Schätzung |
|
||||
|-------|-----------|
|
||||
| Phase A | 2 Tage (Model + Migration + Cancel + Tests) |
|
||||
| Phase B | 3 Tage (5 Walker × Refactor + Cancel-Hooks + PolicyResolver) |
|
||||
| Phase C | 2 Tage (4 Routes + Audit-Hooks + Owner-Checks) |
|
||||
| Phase D | 4 Tage (Wizard + ConnectionsPage + UDB + Inventory + Badge + Löschungen) |
|
||||
| Phase E | 1.5 Tage (Tests) |
|
||||
| Phase F | 0.5 Tage (Doku) |
|
||||
| **Gesamt** | **~13 Tage** (1 Person, fokussiert) |
|
||||
|
||||
Pufferung für Code-Review-Iterationen + Edge-Cases: **+30 %** → realistisch 17 Werktage.
|
||||
|
||||
---
|
||||
|
||||
*Erstellt 2026-05-12 — Audit-basiert. Decision-Points D-1 bis D-5 vor Implementierungsstart entscheiden.*
|
||||
412
c-work/2-build/2026-05-rag-consent-and-control-unification.md
Normal file
412
c-work/2-build/2026-05-rag-consent-and-control-unification.md
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
<!-- status: plan -->
|
||||
<!-- started: 2026-05-12 -->
|
||||
<!-- lastReviewed: 2026-05-12 -->
|
||||
<!-- component: gateway | frontend-nyla | platform -->
|
||||
|
||||
# Unified RAG Consent, Neutralization and Visibility — Single Source of Truth
|
||||
|
||||
## Beschreibung und Kontext
|
||||
|
||||
Mit dem Plan **`2026-04-id-unified-knowledge-indexing-rag-concept.md`** (P0–P1d) wurde der **technische Backend-Pfad** für RAG-Ingestion implementiert (Connection-OAuth → Bootstrap → Walker → `requestIngestion` → `interfaceDbKnowledge`) und der **`AddConnectionWizard`** ausgeliefert (Consent + Preferences + Kostenabschätzung).
|
||||
|
||||
Bei der Architektur-Review (2026-05-12) wurde jedoch festgestellt:
|
||||
|
||||
- **Wizard ist standalone**, ohne geschlossenen Lebenszyklus für seine Setzungen.
|
||||
- **Consent ohne Revoke-UI** — User kann „Wissensdatenbank: Ja" wählen, aber nirgends widerrufen ohne die Connection zu löschen.
|
||||
- **Doppelte Neutralisierungs-Quelle:** `UserConnection.knowledgePreferences.neutralizeBeforeEmbed` (global pro Connection, gesetzt im Wizard) **und** `DataSource.neutralize` (UDB, pro Pfad). Beide wirken unabhängig — verwirrt den User.
|
||||
- **Surface Toggles und MIME-Allowlist sind tot:** Werden geparst, aber in keinem Walker referenziert.
|
||||
- **`GET /api/connections/` zeigt Knowledge-Felder nicht.** Selbst wenn UI gebaut wäre, fehlt die Datenquelle.
|
||||
- **Bootstrap-Job-Status für User unsichtbar:** Bootstrap-Jobs ohne `mandateId` → `/api/jobs` blockt. User klickt „Verbinden" und sieht **kein Feedback**.
|
||||
- **Indexierung kann nicht gestoppt werden:** Wenn ein Bootstrap einer 50'000-Item-Mailbox losläuft, gibt es keinen Stop-Button. Kein Cost-Cap.
|
||||
- **`WorkspaceRagInsightsPage` am falschen Ort:** Workspace-instance-scoped Stats für ein plattformweites Konzept (`page.system.ragInventory` wäre richtig).
|
||||
- **„Was ist neu zu indexieren" ist undurchsichtig:** Walker walked alles, dedupliziert via Content-Hash. Es ist korrekt, aber für den User nirgends erklärt.
|
||||
- **Walker-Scope ist OAuth-global**, nicht user-gesteuert. User kann nicht wählen „SharePoint-Site A ja, Site B nein".
|
||||
|
||||
**Business-Treiber:** DSGVO-Compliance (Art. 7: Widerruf so einfach wie Erteilung), Kosten-Kontrolle (Stop-fähig, scope-fähig), und Vertrauen (Transparenz: was ist drin, wann reinprozessiert, wie raus).
|
||||
|
||||
## Fokus und kritische Details
|
||||
|
||||
### Architektonisches Leitprinzip (vom User formuliert)
|
||||
|
||||
> „Der Wizard ist nur eine UI-Komponente. Dies ist keine Architektur. Die Architektur muss im Backend datenzentriert sein und jeder Schritt muss transparent sein und im UI für den User ersichtlich."
|
||||
|
||||
Daraus ergeben sich **vier harte Regeln**:
|
||||
|
||||
1. **Jede Einwilligung im Wizard MUSS an genau einem Ort im UI revozierbar sein.** „Knowledge Ingestion" `on/off` muss in `ConnectionsPage` und auf der RAG-Inventar-Seite umstellbar sein, mit demselben Backend-Effekt wie Connection-Revoke (Purge).
|
||||
2. **Neutralisierung wird AUSSCHLIESSLICH in der UDB pro Tree-Element verwaltet.** Es darf keine „Neutralisierung global einer Datenquelle" geben, die „einfach so" wirkt. `UserConnection.knowledgePreferences.neutralizeBeforeEmbed` ist Anti-Pattern und wird **entfernt**.
|
||||
3. **Was im RAG ist, muss der User sehen UND auf der Granularität entfernen können, auf der er es hinzugefügt hat.**
|
||||
4. **Indexierung muss jederzeit stoppbar sein** — sowohl als „Stopp-Knopf" auf laufenden Jobs als auch als impliziter Stop durch das Entfernen des Index-Toggles in der UDB.
|
||||
|
||||
### Kern-Idee: Per-Tree-Element-Toggle (analog zu Scope und Neutralize)
|
||||
|
||||
**Heute** in der UDB hat jedes Tree-Element drei Aktionen-Buttons: 💬 Chat / 🧑 Scope-Cycle / 🔒 Neutralize-Toggle. Die `DataSource`-Row materialisiert sich, sobald der User **eine** dieser Aktionen für einen Pfad konfiguriert (`scope`, `neutralize`).
|
||||
|
||||
**Morgen** kommt ein **vierter** Button hinzu: 🧠 **RAG-Index-Toggle**. Selbe Mechanik:
|
||||
- Toggle aktivieren → DataSource bekommt `ragIndexEnabled = true` → wird beim nächsten Bootstrap/Resync indexiert
|
||||
- Toggle deaktivieren → `ragIndexEnabled = false` + sofortiger Purge-Job für diesen Pfad
|
||||
- Damit ist der Index-Lebenszyklus **strukturidentisch** mit Scope und Neutralize. Konsistent und intuitiv.
|
||||
|
||||
**Konsequenz:** Walker iterieren **nicht** mehr OAuth-global. Sie iterieren über **`DataSource`-Rows mit `ragIndexEnabled = true`** der jeweiligen Connection. Der User entscheidet **explizit pro Tree-Element** was indexiert wird.
|
||||
|
||||
#### Granularität: Tree-Kaskade vs. Tabellen-Felder
|
||||
|
||||
Die UDB unterscheidet schon heute zwei Daten-Typologien — beide Policies (`neutralize`, `scope`, künftig `ragIndexEnabled`) müssen sich konsistent dazu verhalten:
|
||||
|
||||
| Daten-Typ | Beispiele | Wie wirken Policies |
|
||||
|-----------|-----------|---------------------|
|
||||
| **Tree (`DataSource`)** | SharePoint-Folder, Outlook-Mail-Folder, Drive-Folder, ClickUp-Liste | **Kaskadierend**: Eine Policy auf einem Container-Knoten (Folder/Site) **vererbt** sich auf alle Kinder, bis ein Kind eine eigene Policy explizit überschreibt. Code-Pattern existiert bereits (`inheritedScope`, `inheritedNeutralize` in `SourcesTab.tsx`). `ragIndexEnabled` muss **denselben** Vererbungs-Mechanismus nutzen — sonst inkonsistent. |
|
||||
| **Tabular (`FeatureDataSource`)** | Trustee-Positionen-Tabelle, RealEstate-Bauvorschriften, beliebige Feature-DATA_OBJECT-Tabellen | **Pro Feld**: `FeatureDataSource.neutralizeFields: List[str]` — User kann sagen „indexier diese Tabelle, aber `email`-Feld neutralisieren, `name`-Feld lassen". Bestehender Code: `_toggleNeutralizeField` in `SourcesTab.tsx`. Für `ragIndexEnabled` auf Tabellen-Ebene ist Per-Feld-Granularität **nicht** sinnvoll (entweder Zeile geht in RAG oder nicht) — also: `ragIndexEnabled` gilt **pro Tabelle/`FeatureDataSource`**, **nicht** pro Feld. |
|
||||
|
||||
**Verbindliche Regeln:**
|
||||
|
||||
1. **`neutralize` auf Container vererbt sich nach unten**, bis ein Child-Knoten einen eigenen `neutralize`-Wert hat (true oder false). „Inherited"-Visualisierung im UI bleibt wie heute (gestrichelt / gedimmt).
|
||||
2. **`ragIndexEnabled` auf Container vererbt sich gleich**: Toggle auf einem SharePoint-Site-Knoten = alle Subfolders+Files werden indexiert (mit der vererbten `neutralize`-Policy), ausser ein Subfolder-Knoten setzt explizit `ragIndexEnabled = false`.
|
||||
3. **Tabellarisch (`FeatureDataSource`)**: `neutralizeFields: List[str]` ist die einzige Granularitäts-Stufe für Neutralisierung. `ragIndexEnabled` gilt **pro Tabelle**. Wenn der User bestimmte Spalten **gar nicht** im RAG haben will, ist die Lösung „Tabelle nicht indexieren" oder ein neuer `excludeFields: List[str]` (Roadmap-Item, **nicht** in v1).
|
||||
4. **Walker-Implementierung muss die Vererbung respektieren**: Wenn User SharePoint-Site indiziert (Container) und nicht jeden Subfolder einzeln, durchläuft der Walker die Tree-Hierarchie und nutzt für jedes File die **effektive** Policy (eigene > geerbt > default `false`).
|
||||
|
||||
**Vorteile:**
|
||||
- Granularität: User wählt „SharePoint Site A → Folder 'Reports' indexieren, Folder 'Drafts' nicht".
|
||||
- Revoke trivial: Ein Klick auf den Toggle in der UDB → automatischer Purge.
|
||||
- Einheitliche UX-Pattern (gleiche Iconisierung wie Scope/Neutralize).
|
||||
- Kein „Auto-Default-DataSources beim Connect"-Hack — User muss aktiv was wählen, was DSGVO-konformer ist (kein Default-Indexing ohne explizite Wahl).
|
||||
- **DataSource braucht kein separates `autoSync`/`autoIndex`-Attribut auf Row-Level** — die `ragIndexEnabled`-Spalte IST die Speicherung; das Icon IST die Bedienung.
|
||||
|
||||
**Konsequenz für `knowledgeIngestionEnabled` auf der Connection:**
|
||||
- **Bleibt** als rechtlicher Master-Schalter („darf PowerOn überhaupt etwas aus dieser Connection in die Wissensdatenbank legen?").
|
||||
- Wenn `false`: Kein Walker startet, egal welche `ragIndexEnabled`-DataSources existieren. Existierende Chunks werden gepurged.
|
||||
- Wenn `true`: Walker iteriert über DataSources mit `ragIndexEnabled = true`. Wenn keine existiert → Walker macht nichts (kein leerer Default-Walk).
|
||||
|
||||
### Indexierung jederzeit stoppbar
|
||||
|
||||
Drei Stop-Mechanismen:
|
||||
|
||||
1. **Implizit pro Element:** In der UDB den Index-Toggle einer DataSource ausschalten → Purge dieser Items + Walker überspringt sie beim nächsten Lauf.
|
||||
2. **Implizit pro Connection:** Master-Toggle auf der Connection ausschalten (Knowledge-Consent revoke) → Purge **aller** Chunks dieser Connection + nächste Bootstrap-/Resync-Jobs werden gar nicht erst gestartet.
|
||||
3. **Explizit pro laufendem Job:** **Stop-Button** in der RAG-Inventar-Seite und auf der ConnectionsPage, sobald ein Bootstrap-Job `RUNNING` ist. Backend setzt Job-Status auf `CANCELLED`; Walker prüft alle ~50 Items das Cancel-Flag und beendet graceful.
|
||||
- Bereits indexierte Items bleiben drin (kein Rollback). Wer komplett raus will → Master-Toggle off.
|
||||
- `BackgroundJobStatusEnum.CANCELLED` existiert bereits im Enum — fehlt nur die `cancelJob()`-API + Cancel-Check im Walker-Loop.
|
||||
|
||||
### „Was wird neu indexiert?" — Dedup-Mechanismus transparent gemacht
|
||||
|
||||
Die Frage taucht beim User auf: „Wenn ich in 24h meinen täglichen Resync laufen lasse — wie weiss das System was neu ist?"
|
||||
|
||||
Die Antwort existiert technisch **bereits**, ist aber nirgends im UI sichtbar gemacht:
|
||||
|
||||
| Ebene | Mechanismus | Im Code |
|
||||
|-------|-------------|---------|
|
||||
| **Pro Item, vor API-Call** (Cheap-Skip) | Walker speichert externen Revisions-Token (`eTag` für SharePoint, `modifiedTime` für Drive, `historyId` für Gmail, `changeKey` für Outlook, `date_updated` für ClickUp). Wenn unverändert → Walker skippt den API-Download. | Bootstrap-Walker, Provider-Adapter |
|
||||
| **Pro Item, nach Extraction** (Embedding-Skip) | `requestIngestion` baut Content-Hash über `(contentType, data, extractor-order)` — Pure Function des Inhalts. Wenn `FileContentIndex.structure._ingestion.hash` matcht → Status bleibt `indexed`, kein neues Embedding. | `mainServiceKnowledge.requestIngestion` |
|
||||
| **Bei Connection-Revoke** | `deleteFileContentIndexByConnectionId` purged alle Rows mit `connectionId = X`. | `interfaceDbKnowledge` |
|
||||
| **Bei DataSource-Revoke** (NEU in diesem Plan) | `deleteFileContentIndexByConnectionAndPathPrefix` purged Rows mit `connectionId = X` und `contextRef.path` startet mit Y. | NEU |
|
||||
|
||||
Daraus folgt für den Plan:
|
||||
- **Keine neue Mechanik nötig** für „was ist neu" — die Hash-Idempotenz reicht.
|
||||
- **UI-Aufgabe:** Auf der RAG-Inventar-Seite muss klar dokumentiert/sichtbar sein: „Letzter Sync: vor 18h. Neue Items werden täglich um 02:00 ergänzt. Unveränderte Items werden nicht erneut verarbeitet."
|
||||
- **Pro Sync sichtbare Counter:** `indexedNew`, `skippedDuplicate`, `failed` (existieren als Log-Events; müssen pro Connection auf der Inventar-Seite aggregiert werden).
|
||||
|
||||
### Konsequenzen für die Datenmodellierung
|
||||
|
||||
| Heute | Wird zu |
|
||||
|-------|--------|
|
||||
| `UserConnection.knowledgeIngestionEnabled` | **bleibt** — rechtlicher Master-Gate, mit Revoke-UI |
|
||||
| `UserConnection.knowledgePreferences.neutralizeBeforeEmbed` | **entfernen** — Migration: bei `true` werden alle existierenden DataSources der Connection auf `neutralize=true` gesetzt |
|
||||
| `UserConnection.knowledgePreferences.{mailContentDepth, mailIndexAttachments, filesIndexBinaries, clickupScope, maxAgeDays}` | **bleiben** als Connection-weite Default-Sync-Einstellungen, Edit-UI in Connection-Detail-Drawer |
|
||||
| `UserConnection.knowledgePreferences.{mimeAllowlist, surfaceToggles}` | **entfernen** — durch Per-Element-Toggle obsolet |
|
||||
| `DataSource.neutralize` | **bleibt** — einzige Quelle für Neutralisierungs-Policy pro Tree-Element |
|
||||
| `DataSource.scope` | **bleibt unverändert** |
|
||||
| `DataSource.autoSync` | **wird umbenannt zu `ragIndexEnabled`** (oder bleibt + Doku — Entscheidung in Phase A) — neuer Sinn: „dieses Tree-Element wird in den RAG indexiert" |
|
||||
| `WorkspaceRagInsightsPage` (Workspace-scoped) | **gelöscht**, ersetzt durch globale `Start > Nutzung > RAG-Inventar` |
|
||||
| `/api/workspace/{instanceId}/rag-statistics` | **gelöscht**, ersetzt durch `/api/rag/inventory/{me\|mandate\|platform}` |
|
||||
| `BackgroundJob` ohne Cancel-API | **erweitert**: `cancelJob(jobId)`, Walker-Cancel-Check |
|
||||
|
||||
### Wizard-Vereinfachung — alle Connector-Typen über denselben Pfad
|
||||
|
||||
Der `AddConnectionWizard` ist **die einzige** Eintragungs-UI für neue Connections. Alle heute parallelen Buttons auf der `ConnectionsPage` (separater **„Infomaniak"**-Button mit eigener Modal-Logik, separater **„Admin-Zustimmung"**-Button für Microsoft) **werden in den Wizard integriert**. Auf der `ConnectionsPage` bleibt nur **„Verbindung hinzufügen"** als Eintrittspunkt.
|
||||
|
||||
**Connector-Type-Aware Wizard:** Der Wizard hat eine Basis-Sequenz und **typ-spezifische Zusatz-Schritte**, die nach „Anbieter wählen" eingehängt werden.
|
||||
|
||||
#### Basis-Sequenz (alle Typen)
|
||||
|
||||
1. **Anbieter wählen** — Cards für Google / Microsoft 365 / ClickUp / Infomaniak (Infomaniak kommt also wieder in die Auswahl).
|
||||
2. *(typ-spezifischer Zusatz-Schritt — falls vorhanden, siehe unten)*
|
||||
3. **Rechtliche Einwilligung** (Knowledge Ingestion ja/nein) — kurzer Hinweistext: „Du wählst nach dem Verbinden in der Datenleiste konkret, welche Ordner/Postfächer/Listen indexiert werden sollen."
|
||||
4. **Verbindungsschritt** — typ-abhängig: OAuth-Popup (google/msft/clickup) **oder** PAT-Eingabe (infomaniak).
|
||||
|
||||
#### Connector-Type-spezifische Zusatz-Schritte
|
||||
|
||||
| Connector | Zusatz-Schritt nach „Anbieter wählen" | Inhalt |
|
||||
|-----------|--------------------------------------|--------|
|
||||
| **Microsoft 365** | **Optional**: Admin-Zustimmung (Tenant-weit) | Hinweis: „Falls dein Tenant einen Admin-Consent erfordert, kann er **vor** der persönlichen OAuth einmalig erteilt werden. Sonst überspringe diesen Schritt." Button öffnet `/api/msft/adminconsent` Popup. Nach Schliessen → weiter zu Step 3. |
|
||||
| **Google** | – | (Standard OAuth) |
|
||||
| **ClickUp** | – | (Standard OAuth) |
|
||||
| **Infomaniak** | **Pflicht**: Personal Access Token | Inline-Eingabefeld + Erklärung wie heute im `infomaniakModal`. Erst nach valider Token-Validierung → weiter zu Step 3. Connection wird beim PAT-Submit erstellt (vergleichbar mit `createInfomaniakConnection` + `submitInfomaniakToken`); kein OAuth-Popup. |
|
||||
|
||||
**Mechanik:** Der Wizard hält pro Connector-Typ eine kleine Step-Definition (z.B. `WIZARD_STEPS_BY_TYPE: Record<ConnectorType, WizardStep[]>`), die der Reihe nach durchlaufen wird. Stepper im UI passt sich dynamisch an (z.B. 4 Punkte für Microsoft mit Admin-Zustimmung, 3 für Google).
|
||||
|
||||
**Entfernt von `ConnectionsPage`:**
|
||||
- ~~Button „Admin-Zustimmung"~~ — wandert in den Wizard (Microsoft-Pfad).
|
||||
- ~~Button „Infomaniak"~~ — wandert in den Wizard (eigener Connector-Typ in der Anbieter-Auswahl).
|
||||
- ~~`infomaniakModal`-State~~ — wandert in den Wizard.
|
||||
- ~~`handleAdminConsent`~~ — wandert in den Wizard.
|
||||
|
||||
**Entfernt aus dem heutigen Wizard:**
|
||||
- ~~Step 2 (Preferences mit Neutralisierung-Checkbox, Mail-Tiefe etc.)~~ — wandert in Connection-Detail-Drawer bzw. in die UDB (Neutralize per Element).
|
||||
- ~~**Kostenabschätzung**~~ — wird komplett entfernt. Begründung: Schätzung war unzuverlässig (Token-Annahmen pro Mail spekulativ; Drive/SharePoint-Datenmenge nicht bekannt vor dem ersten Walk), erzeugt falsche Sicherheit, und stoppt User unnötig vom Connect. Kostenkontrolle erfolgt **nachgelagert**: über die Stop-Funktion und über das Per-Element-Opt-In.
|
||||
|
||||
**Vorteil:** Genau ein Eintrittspunkt für jeden Connector-Typ. Konsistente UX. Die echte Konfiguration (was indexieren, was neutralisieren) passiert dort, wo der User die Daten sieht — in der UDB.
|
||||
|
||||
### Wo sieht und stoppt der User die Indexierung? (Sichtbarkeit-Spec)
|
||||
|
||||
Diese Frage muss **drei** UI-Plätze ohne Lücke abdecken:
|
||||
|
||||
| Ort | Was sichtbar | Aktionen |
|
||||
|-----|--------------|----------|
|
||||
| **Globales Header-Indikator** (klein, oben rechts neben User-Menü) | Badge mit Anzahl gerade laufender Bootstrap-/Resync-Jobs des aktuellen Users (z.B. „🔄 2"). Klick → öffnet RAG-Inventar-Seite. Verschwindet, wenn keine Jobs laufen. Polling: alle 5–10s. | Click-through zur RAG-Inventar |
|
||||
| **`ConnectionsPage`, pro Row** | Pro Connection mit aktivem Bootstrap-Job: Status-Pill „Indexierung läuft" + Progress (`processed / known total` oder Prozent) + Roter **Stop**-Button. Bei finished: „Letzter Sync: <Datum>, X indexiert / Y skipped / Z failed". | **Stop**-Button → `POST /api/connections/{id}/knowledge-stop`. **Master-Toggle** (off → Confirm → Purge + Cancel). |
|
||||
| **`RagInventoryPage` Tab „Meine Daten"** | Vollständige Sicht: pro Connection eine Card; pro Card alle indexierten DataSources mit Item-Count + letztem Sync; Status-Pill bei laufendem Bootstrap; pro DataSource der UDB-Index-Toggle nochmals als Quick-Off. | **Stop**-Button pro Connection (gleiche API). **Toggle off** pro DataSource → Mini-Purge. **Master-Toggle** der Connection. **„Jetzt synchronisieren"**-Button → enqueue Bootstrap on-demand. |
|
||||
|
||||
**Backend-Voraussetzung für die Sichtbarkeit:** `GET /api/rag/connection/{id}/status` liefert für eine Connection `{ runningJobId?: string, runningJobProgress?: number, lastRun?: { finishedAt, indexedNew, skippedDuplicate, failed }, nextScheduledResync?: timestamp }`. `GET /api/rag/inventory/me` aggregiert das für alle Connections und liefert zusätzlich `runningJobsTotal` für das Header-Badge.
|
||||
|
||||
### Kritische Stellen im Code
|
||||
|
||||
- `gateway/modules/serviceCenter/services/serviceKnowledge/subConnectorIngestConsumer.py` — Bootstrap-Dispatcher: muss Walker auf DataSource-Iteration umstellen + Cancel-Flag prüfen.
|
||||
- `gateway/modules/serviceCenter/services/serviceKnowledge/subConnectorSync*.py` (5 Walker) — konsumieren `DataSource`-Liste statt globalem Authority-Walk; lesen `neutralize` aus DataSource; prüfen periodisch `cancelRequested`.
|
||||
- `gateway/modules/serviceCenter/services/serviceKnowledge/subConnectorPrefs.py` — `neutralizeBeforeEmbed`, `mimeAllowlist`, `surfaceToggles` raus.
|
||||
- `gateway/modules/datamodels/datamodelUam.py` — `knowledgePreferences` Schema-Doku anpassen.
|
||||
- `gateway/modules/datamodels/datamodelDataSource.py` — `autoSync` → `ragIndexEnabled` (oder umdokumentieren); ggf. `lastIndexed`-Feld hinzufügen.
|
||||
- `gateway/modules/serviceCenter/services/serviceBackgroundJobs/mainBackgroundJobService.py` — `cancelJob(jobId)` API hinzufügen; `JobProgressCallback` um `isCancelled()` erweitern.
|
||||
- `gateway/modules/routes/routeDataConnections.py` — `GET` liefert Knowledge-Felder; neue PATCH-Endpoints für Consent + Preferences + Stop.
|
||||
- `gateway/modules/routes/routeDataSources.py` — neuer PATCH `/{id}/rag-index` mit Purge bei Off-Toggle.
|
||||
- `gateway/modules/interfaces/interfaceDbKnowledge.py` — `deleteFileContentIndexByConnectionAndPathPrefix(connectionId, pathPrefix)` und `listFileContentIndexByConnection(connectionId)` und `listFileContentIndexByDataSource(dataSourceId)`.
|
||||
- `gateway/modules/features/workspace/routeFeatureWorkspace.py` — `rag-statistics` Methode löschen.
|
||||
- **NEU** `gateway/modules/routes/routeRagInventory.py` — `/api/rag/inventory/{me,mandate,platform}` und `/api/rag/connection/{id}/status`.
|
||||
- `gateway/modules/system/mainSystem.py` — Navigation `Nutzung > RAG-Inventar`.
|
||||
- `frontend_nyla/src/pages/views/workspace/WorkspaceRagInsightsPage.tsx` + `.module.css` — **löschen**.
|
||||
- `frontend_nyla/src/types/mandate.ts` — `rag-insights` raus.
|
||||
- `frontend_nyla/src/pages/FeatureView.tsx`, `App.tsx` — Route + Mapping raus.
|
||||
- `frontend_nyla/src/components/AddConnectionWizard/AddConnectionWizard.tsx` — Connector-Type-Aware Step-Definition; integrierte Microsoft-Admin-Consent + Infomaniak-PAT Steps; Kostenabschätzung + Preferences-Step entfernen.
|
||||
- `frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx` — vierter Action-Button für `ragIndexEnabled` (analog zu 🔒/Neutralize) **inklusive Vererbungs-Visualisierung** (`inheritedRagIndexEnabled`-Prop, gestrichelt/gedimmt für vererbte Werte).
|
||||
- `frontend_nyla/src/api/connectionApi.ts` — `KnowledgePreferences` reduzieren; neue API-Methoden.
|
||||
- **NEU** `frontend_nyla/src/pages/system/RagInventoryPage.tsx` — Drei-Tab-Seite (Meine / Mandant / Plattform).
|
||||
- `frontend_nyla/src/pages/basedata/ConnectionsPage.tsx` — Buttons „Admin-Zustimmung" + „Infomaniak" + Modal **entfernen**; Master-Toggle pro Row + Status-Pill + Stop-Button bei laufendem Bootstrap hinzufügen.
|
||||
- **NEU** `frontend_nyla/src/components/Header/RagRunningBadge.tsx` — globales Header-Badge mit Anzahl laufender Bootstrap-Jobs + Click-through zur RAG-Inventar.
|
||||
|
||||
### Bekannte Fallstricke
|
||||
|
||||
- **Migration `neutralizeBeforeEmbed=true` → Pro-DataSource:** Bei Schema-Update werden alle existierenden DataSources der Connection mit `neutralize=true` markiert. Falls keine DataSources existieren (was wahrscheinlich ist, weil heute Walker OAuth-global läuft), gehen die Werte verloren — Release-Note nötig.
|
||||
- **Migration `autoSync`-Bedeutung:** Wenn `autoSync` heute schon mit anderem Sinn benutzt wird, ist Renaming sicherer als Umdeutung. Phase A startet mit Code-Audit.
|
||||
- **Bestehende RAG-Daten ohne DataSource-Bezug:** Heute haben SharePoint-/Outlook-Chunks `connectionId` aber keinen DataSource-Link (Walker walked OAuth-global). Migration: bestehende Chunks bleiben drin, neue Indexierung läuft nur über DataSources. „Legacy-Chunks" sichtbar machen oder einmalig purgen — **offene Entscheidung**.
|
||||
- **Cancel-Race-Condition:** Walker prüft Cancel-Flag alle N Items. Im Worst Case dauert Stop noch N Items lang. Akzeptabel für v1.
|
||||
- **Daily Resync vs. Cancel:** Wenn ein Daily-Resync läuft und User stoppt — startet er morgen neu? Ja, das ist gewollt. Wenn User dauerhaft Pause will → Master-Toggle off oder einzelne DataSource-Toggles off.
|
||||
|
||||
## Ziel und Nicht-Ziele
|
||||
|
||||
**Ziel:**
|
||||
|
||||
- **Single Source of Truth** für Neutralisierung: ausschliesslich `DataSource.neutralize`, gemanagt in der UDB.
|
||||
- **Per-Element RAG-Index-Toggle** in der UDB (analog zu Scope/Neutralize).
|
||||
- **Vollständiger Lebenszyklus** für Wizard-Consent (Erteilen, Widerrufen) im UI sichtbar.
|
||||
- **Stop-Funktion** für laufende Bootstrap-Jobs (Job-Cancel + Walker-Cancel-Check).
|
||||
- **Globale `Start > Nutzung > RAG-Inventar`** Seite mit drei Tabs (Meine Daten / Mandant / Plattform).
|
||||
- **Bootstrap-Walker konsumieren `DataSource`-Liste** (nur jene mit `ragIndexEnabled = true`).
|
||||
- **`WorkspaceRagInsightsPage` ist gelöscht.**
|
||||
- **Wizard ist auf Anbieter + Consent reduziert** (keine Kostenschätzung, keine Preferences, keine Neutralisierung).
|
||||
- **Tote Pref-Felder entfernt** (`mimeAllowlist`, `surfaceToggles`, `neutralizeBeforeEmbed`).
|
||||
|
||||
**Explizit NICHT:**
|
||||
|
||||
- Wir bauen **keinen** zweiten Knowledge-Store.
|
||||
- Wir verändern **nicht** die Retrieval-Seite (`buildAgentContext`).
|
||||
- Wir bauen **kein** Per-Item-Revoke (Granularität: Element-Toggle in UDB oder Master-Connection-Toggle — nicht eine einzelne Mail).
|
||||
- Wir bauen **keine Auto-Default-DataSources** beim Connect — User muss aktiv Indexierung pro Element wählen.
|
||||
- Wir bauen **keine Echtzeit-Cost-Caps** (z.B. „stop bei CHF 5.-") — v1: Stop ist manuell. Cost-Caps sind Roadmap v2.
|
||||
|
||||
## Betroffene Module
|
||||
|
||||
- **Gateway:** `serviceKnowledge` (Walker-Refactor, Purge-Endpoints, Cancel-Check), `serviceBackgroundJobs` (Cancel-API), `routeDataConnections` (Knowledge-Felder + PATCH-Endpoints), `routeDataSources` (PATCH `/rag-index`), neuer `routeRagInventory`, `mainSystem` (Navigation), `datamodelUam`, `datamodelDataSource`.
|
||||
- **Frontend:** `WorkspaceRagInsightsPage` löschen, `AddConnectionWizard` stark vereinfachen (Steps, keine Kostenschätzung), `ConnectionsPage` (Master-Toggle + Stop-Button), `SourcesTab` (4. Index-Toggle-Button), neue `RagInventoryPage`, `pageRegistry`.
|
||||
- **DB-Migration:** ja — `knowledgePreferences.neutralizeBeforeEmbed/mimeAllowlist/surfaceToggles` raus; `DataSource.autoSync` → `ragIndexEnabled` (oder Re-Doku).
|
||||
- **Andere Komponenten:** keine.
|
||||
|
||||
## Entscheidungen
|
||||
|
||||
| Datum | Entscheidung | Begründung |
|
||||
|-------|-------------|------------|
|
||||
| 2026-05-12 | Neutralisierung wird ausschliesslich pro `DataSource` verwaltet | Single Source of Truth; UDB ist der einzige Ort für Datenobjekt-Policies |
|
||||
| 2026-05-12 | Index-Aktivierung wird pro Tree-Element via UDB-Toggle gesteuert (analog Scope/Neutralize) | Konsistente UX, granular, revoke-trivial |
|
||||
| 2026-05-12 | `WorkspaceRagInsightsPage` wird gelöscht, ersetzt durch globale `Start > Nutzung > RAG-Inventar` | RAG ist plattformweit, nicht workspace-scoped |
|
||||
| 2026-05-12 | Walker iterieren über `DataSource`-Rows mit `ragIndexEnabled=true` der Connection | User-kontrollierter Scope; per-Source Revoke trivial |
|
||||
| 2026-05-12 | **Keine Auto-Default-DataSources** beim OAuth-Connect | DSGVO-konformer (kein Default-Indexing); User muss aktiv wählen |
|
||||
| 2026-05-12 | **Kostenabschätzung im Wizard wird entfernt** | Unzuverlässig, erzeugt falsche Sicherheit; Kostenkontrolle via Stop-Button + Per-Element-Opt-In |
|
||||
| 2026-05-12 | **Stop-Mechanismus**: implizit (Toggle off) + explizit (Cancel-Job-Button) | Jederzeit stoppbar, kein „Walker rennt weg" |
|
||||
| 2026-05-12 | „Was ist neu zu indexieren" wird **nicht** neu erfunden — bestehende Hash-Idempotenz reicht | Pure Function über Content; bereits implementiert und getestet |
|
||||
| 2026-05-12 | Surface Toggles und MIME-Allowlist werden aus Schema **entfernt** (nicht implementiert) | Per-Element-Toggle macht sie obsolet |
|
||||
| 2026-05-12 | **Vererbungs-Mechanik für Tree-Policies**: `neutralize` und `ragIndexEnabled` kaskadieren auf Container, lokale Overrides möglich (analog zu `inheritedScope`/`inheritedNeutralize` Pattern) | Konsistenz mit existierender UDB-Logik; spart redundante Per-File-Toggles |
|
||||
| 2026-05-12 | **Tabellen-Granularität**: Neutralisierung pro Feld (`neutralizeFields[]`); RAG-Index pro Tabelle (kein Per-Feld) | Bestehende UI-Pattern bleiben; Per-Feld-Index wäre Over-Engineering für v1 |
|
||||
| 2026-05-12 | **Wizard ist einziger Eintrittspunkt für alle Connector-Typen** — Buttons „Admin-Zustimmung" + „Infomaniak" auf `ConnectionsPage` werden entfernt und in den Wizard integriert | Genau ein Pfad pro Connector-Typ; eliminiert Parallelmechanismen |
|
||||
| 2026-05-12 | **Drei Sichtbarkeits-Orte für laufende Indexierung**: Header-Badge, ConnectionsPage-Row, RagInventoryPage-Tab | User darf nirgends „raten" was läuft; Stop-Aktion an mindestens zwei Orten erreichbar |
|
||||
|
||||
## Umsetzungs-Checkliste
|
||||
|
||||
### Phase A — Datenmodell & Cancel-Infrastruktur (Tage 1–3)
|
||||
|
||||
- [ ] **Audit:** existierende Verwendung von `DataSource.autoSync` prüfen → Entscheidung: Renaming `ragIndexEnabled` vs. Re-Doku.
|
||||
- [ ] `datamodelDataSource.DataSource`: `ragIndexEnabled: bool = False` (default `False` — kein Auto-Index ohne explizite Wahl). `lastIndexed: Optional[float]`.
|
||||
- [ ] Migration: bestehende `UserConnection`s mit `knowledgePreferences.neutralizeBeforeEmbed=true` → falls DataSources existieren, deren `neutralize=true` setzen. Pref-Feld entfernen.
|
||||
- [ ] `datamodelUam.UserConnection.knowledgePreferences` — Schema-Doku: `neutralizeBeforeEmbed`, `surfaceToggles`, `mimeAllowlist` raus.
|
||||
- [ ] `subConnectorPrefs.ConnectionIngestionPrefs` — tote Felder entfernen.
|
||||
- [ ] `serviceBackgroundJobs.cancelJob(jobId) -> bool` API hinzufügen (setzt Status auf `CANCELLED`).
|
||||
- [ ] `JobProgressCallback`: `isCancelled() -> bool` (liest Job-Status aus DB; cached für N Sekunden um DB-Last zu vermeiden).
|
||||
- [ ] **Walker-Refactor:** Jeder `subConnectorSync*.py` Walker:
|
||||
- iteriert über `DataSource`-Rows der Connection mit `ragIndexEnabled = true` und passendem `sourceType`,
|
||||
- lädt `neutralize` aus `DataSource` (nicht aus Pref-Dict),
|
||||
- prüft `progressCb.isCancelled()` mindestens einmal alle 50 Items → graceful exit + Job-Result `cancelled: true, processedSoFar: N`,
|
||||
- schreibt `dataSource.lastIndexed = now()` am Ende.
|
||||
- [ ] **Purge-Helpers** in `interfaceDbKnowledge`:
|
||||
- `deleteFileContentIndexByConnectionAndPathPrefix(connectionId, pathPrefix) -> {indexRows, chunks}`,
|
||||
- `listFileContentIndexByConnection(connectionId) -> List[FileContentIndex]`,
|
||||
- `listFileContentIndexByDataSource(dataSourceId) -> List[FileContentIndex]` (matched über `connectionId` + `contextRef.path`-Prefix; oder DataSource-ID in `chunkMetadata` mitführen — Entscheidung in Phase A).
|
||||
|
||||
### Phase B — APIs (Tage 3–5)
|
||||
|
||||
- [ ] `GET /api/connections/` Antwort um `knowledgeIngestionEnabled` und `knowledgePreferences` erweitern.
|
||||
- [ ] `PATCH /api/connections/{id}/knowledge-consent` `{ enabled: boolean }` — bei `false`: synchroner Purge **aller** Connection-Chunks + Flag setzen + alle laufenden Bootstrap-Jobs dieser Connection cancellen. Bei `true`: enqueue Bootstrap (nur wenn DataSources mit `ragIndexEnabled=true` existieren).
|
||||
- [ ] `PATCH /api/connections/{id}/knowledge-preferences` `{ ...prefs }` — speichert Mail-Tiefe etc.; optional Resync-Trigger.
|
||||
- [ ] `POST /api/connections/{id}/knowledge-stop` — cancelled alle laufenden Bootstrap-Jobs der Connection.
|
||||
- [ ] **`PATCH /api/datasources/{id}/rag-index`** `{ enabled: boolean }`:
|
||||
- bei `true`: setzt Flag, enqueue Mini-Bootstrap-Job für **diese eine** DataSource.
|
||||
- bei `false`: setzt Flag, enqueue Purge-Job (`deleteFileContentIndexByConnectionAndPathPrefix`).
|
||||
- [ ] **Neue Route `routeRagInventory`:**
|
||||
- `GET /api/rag/inventory/me` — aktueller User: alle Connections mit Knowledge-Status, deren DataSources (mit Index-Counts), letzter Sync, nächster Resync.
|
||||
- `GET /api/rag/inventory/mandate/{mandateId}` — Mandant-Admin only: Aggregation pro User, pro Connection, pro Feature.
|
||||
- `GET /api/rag/inventory/platform` — PlatformAdmin only: System-Stats, Cost-Tracking-Hooks (placeholder für v2).
|
||||
- [ ] `GET /api/rag/connection/{id}/status` — letzter Bootstrap-Run, aktuelle Job-ID falls running, Counter (`indexedNew`, `skippedDuplicate`, `failed`), `nextScheduledResync`.
|
||||
- [ ] **Workspace-RAG-Endpoint löschen:** `routeFeatureWorkspace.rag-statistics` raus.
|
||||
|
||||
### Phase C — Frontend Cleanup (Tage 5–6)
|
||||
|
||||
- [ ] **Löschen:** `WorkspaceRagInsightsPage.tsx` + `.module.css`.
|
||||
- [ ] `mandate.ts`: `rag-insights` Workspace-View raus.
|
||||
- [ ] `FeatureView.tsx`: Mapping + Sonderbehandlung raus.
|
||||
- [ ] `App.tsx`: Route `rag-insights` raus.
|
||||
- [ ] **Wizard-Refactor (`AddConnectionWizard`) — Connector-Type-Aware Steps:**
|
||||
- Step 0: Anbieter wählen — Cards für **alle vier** Typen (Google / Microsoft / ClickUp / Infomaniak).
|
||||
- Step-Definition pro Typ (`WIZARD_STEPS_BY_TYPE`) — Wizard rendert dynamisch:
|
||||
- **Microsoft:** *(optional)* Admin-Zustimmung → Consent → OAuth.
|
||||
- **Google / ClickUp:** Consent → OAuth.
|
||||
- **Infomaniak:** PAT-Eingabe (Pflicht) → Consent → Submit.
|
||||
- **Microsoft Admin-Zustimmung-Step:** integriert die Logik von `handleAdminConsent` (Popup zu `/api/msft/adminconsent`). Im UI klar als **„nur falls Tenant es erfordert, sonst überspringen"** beschriftet.
|
||||
- **Infomaniak-PAT-Step:** integriert die Logik von `infomaniakModal` (Connection wird beim PAT-Submit erstellt; bei Cancel rollback via `deleteConnection`).
|
||||
- Stepper passt sich dynamisch an (3 Punkte für Google/ClickUp, 4 für Microsoft, 4 für Infomaniak).
|
||||
- `KnowledgePreferences`-Felder werden **nicht mehr** beim Create gesendet (Defaults im Backend).
|
||||
- `computeCostEstimate` + Cost-Hint-Render-Blöcke + `AddConnectionWizard.module.css` Cost-Stile **entfernen**.
|
||||
- [ ] **`ConnectionsPage` Cleanup:**
|
||||
- Buttons **„Admin-Zustimmung"** und **„Infomaniak"** in der Header-Action-Bar **entfernen**.
|
||||
- `handleAdminConsent`, `handleCreateInfomaniak`, `handleInfomaniakCancel`, `handleInfomaniakSubmit`, `infomaniakModal`-State, `adminConsentPending`-State **entfernen**.
|
||||
- Modal `Infomaniak verbinden` **entfernen** (Logik wandert in den Wizard).
|
||||
- [ ] **`useConnections.ts`:** Hook erweitern um Wizard-Pfade für Infomaniak (`createInfomaniakConnection` + `submitInfomaniakToken` bleiben verfügbar; werden vom Wizard konsumiert).
|
||||
|
||||
### Phase D — Frontend UDB & RAG-Sichtbarkeit (Tage 6–9)
|
||||
|
||||
- [ ] **`SourcesTab.tsx`:** Vierter Action-Button (Icon-Vorschlag: 🧠 oder 📚) pro Tree-Node. Mechanik analog zu `🔒` (Neutralize):
|
||||
- Klick: wenn DataSource existiert → Toggle `ragIndexEnabled`. Sonst → DataSource anlegen + Toggle setzen.
|
||||
- Visueller State: aktiv / inaktiv / **inherited** (gestrichelt, gedimmt — analog zu `inheritedNeutralize` Pattern).
|
||||
- **Vererbung:** Container-Knoten propagiert `ragIndexEnabled` an alle Kinder bis zur expliziten Überschreibung. Identische Mechanik wie `inheritedScope`/`inheritedNeutralize` (siehe Sektion „Granularität").
|
||||
- Bei Toggle-Off: Confirm-Dialog mit Item-Count-Vorschau („X Inhalte werden aus dem RAG entfernt").
|
||||
- [ ] **`ConnectionsPage.tsx` neue UI-Elemente pro Row:**
|
||||
- Master-Toggle „Wissensdatenbank" → `PATCH /knowledge-consent`.
|
||||
- Bei `runningJobId`: Status-Pill „Indexierung läuft (X / Y)" + roter **Stop**-Button → `POST /knowledge-stop`.
|
||||
- Bei finished: kompakte Anzeige „Letzter Sync: <Datum>, X indexiert / Y skipped / Z failed".
|
||||
- Knowledge-Preferences-Edit-Drawer (Mail-Tiefe etc.) — als separater Dialog, nicht im Wizard.
|
||||
- [ ] **Globales Header-Indikator:**
|
||||
- Kleines Badge oben rechts (z.B. neben User-Menü): `🔄 N` wenn N>0 Bootstrap-Jobs des aktuellen Users laufen.
|
||||
- Polling alle 5–10s gegen `GET /api/rag/inventory/me` (oder einen schlanken Sub-Endpoint, der nur `runningJobsTotal` liefert).
|
||||
- Klick → Navigation zu `Start > Nutzung > RAG-Inventar`.
|
||||
- Implementierung: neuer Component `RagRunningBadge.tsx` im Header-Layout.
|
||||
|
||||
### Phase E — Frontend RAG-Inventar-Seite (Tage 8–11)
|
||||
|
||||
- [ ] **Neue Seite `RagInventoryPage`** unter `/system/rag-inventory`:
|
||||
- **Tab „Meine Daten"** (default): pro Connection eine Card mit Master-Toggle (off-Confirm), Preferences-Edit-Link, Liste aller DataSources (mit `ragIndexEnabled`-Status, Item-Count, letztes Sync-Datum, „Stop"-Button bei laufendem Job, Toggle-Off-Knopf).
|
||||
- **Tab „Mandant"** (Mandant-Admin only): Aggregation; sichtbar pro User wer wie viel beigesteuert hat.
|
||||
- **Tab „Plattform"** (PlatformAdmin only): System-Stats; Placeholder für Cost-Tracking.
|
||||
- [ ] `pageRegistry.tsx`: `page.system.ragInventory` → Icon `<FaDatabase />` + lazy-imported Component.
|
||||
- [ ] `mainSystem.py` Navigation: Eintrag `Nutzung > RAG-Inventar` (`page.system.ragInventory`, Icon `FaDatabase`, order `25` zwischen Abrechnung und Statistiken).
|
||||
|
||||
### Phase F — DSGVO Sichtbarkeit + Tests (Tage 11–13)
|
||||
|
||||
- [ ] **DSGVO-Audit-Log:** Jeder Knowledge-Consent-Toggle und jeder Index-Toggle wird in `aiAuditLogger` (oder vergleichbarem Audit-Log) festgehalten. Inkl. User, Timestamp, Connection/DataSource, alter und neuer Wert.
|
||||
- [ ] **Backend-Tests:**
|
||||
- Migration (Pref → Per-DataSource).
|
||||
- Walker konsumiert nur DataSources mit `ragIndexEnabled=true`.
|
||||
- DataSource-Toggle off → Purge-Job korrekt scoped.
|
||||
- Connection-Consent off → Purge + Job-Cancel.
|
||||
- `cancelJob` → Walker exit + Job-Status `CANCELLED`.
|
||||
- Inventory-API liefert korrekte Counts.
|
||||
- [ ] **Frontend-Tests:**
|
||||
- `AddConnectionWizard` zeigt **keine** Cost-Hint und **keine** Preferences-Step.
|
||||
- `SourcesTab` zeigt Index-Toggle, Toggle-Off triggert Confirm + API.
|
||||
- `RagInventoryPage` rendert pro Tab korrekt.
|
||||
- `ConnectionsPage` Stop-Button erscheint bei `running` und ruft API.
|
||||
- [ ] **Wiki-Updates:** `b-reference/gateway/ai-agent.md` (Knowledge Lifecycle, DataSource-getriebener Walker), `b-reference/platform/neutralization.md` (DataSource = SSoT), `b-reference/frontend-nyla/architecture.md` (RagInventoryPage, UDB 4. Action-Button), `TOPICS.md` (RAG-Inventar als neuer Eintrag).
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
| # | Kriterium (Given-When-Then) | Prio |
|
||||
|---|---------------------------|------|
|
||||
| 1 | **Given** ich habe Wizard-Consent `true`, **when** ich auf der `ConnectionsPage` den Master-Toggle auf `off` setze und bestätige, **then** werden alle `FileContentIndex` + `ContentChunk`-Rows der Connection synchron gelöscht, `knowledgeIngestionEnabled = false` gesetzt, und alle laufenden Bootstrap-Jobs dieser Connection cancelled. | must |
|
||||
| 2 | **Given** in der UDB ist für DataSource X `ragIndexEnabled = true` und `neutralize = true`, **when** der Bootstrap/Resync läuft, **then** werden Items aus diesem Pfad **mit** Neutralisierung indexiert; **and** Items aus anderen DataSources der gleichen Connection werden nur indexiert wenn ihr eigener `ragIndexEnabled = true` ist. | must |
|
||||
| 3 | **Given** in der UDB klicke ich den Index-Toggle einer DataSource auf `off` und bestätige, **then** wird `ragIndexEnabled = false` gesetzt, ein Purge-Job entfernt alle `FileContentIndex`-Rows mit matching `connectionId` + `pathPrefix`, und Toast zeigt „X Inhalte aus RAG entfernt". | must |
|
||||
| 4 | **Given** ein Bootstrap-Job läuft (`status=RUNNING`), **when** ich den Stop-Button drücke, **then** wird `cancelJob` aufgerufen, der Walker beendet sich nach max. ~50 Items, Job-Status wird `CANCELLED`, Result enthält `cancelled: true, processedSoFar: N`, bereits indexierte Items bleiben drin. | must |
|
||||
| 5 | **Given** ich öffne `Start > Nutzung > RAG-Inventar`, **then** sehe ich Tab „Meine Daten" mit allen meinen Connections + DataSources + Item-Counts + letztem Sync + Stop-Button bei laufenden Jobs. **And** Tab „Mandant" / „Plattform" nur, wenn ich entsprechende Rolle habe. | must |
|
||||
| 6 | **Given** der `WorkspaceRagInsightsPage`-Code, **then** existiert er nicht mehr im Repo; `rag-insights` ist aus `mandate.ts`, `FeatureView.tsx`, `App.tsx` entfernt; `/api/workspace/{instanceId}/rag-statistics` liefert 404. | must |
|
||||
| 7 | **Given** der `AddConnectionWizard`, **then** zeigt er **keine** Kostenabschätzung, **keine** Neutralisierungs-Checkbox und **keinen** Preferences-Step. Nur: Anbieter → Consent → Connect. | must |
|
||||
| 8 | **Given** `UserConnection.knowledgePreferences`, **then** existieren die Felder `neutralizeBeforeEmbed`, `surfaceToggles`, `mimeAllowlist` nicht mehr im Schema und sind in keinem Walker referenziert. Migration: bestehende `neutralizeBeforeEmbed=true`-Werte wurden auf zugehörige DataSources übertragen. | must |
|
||||
| 9 | **Given** ich frisch Microsoft verbinde mit Wizard-Consent `true` und ich habe noch keine DataSources mit `ragIndexEnabled=true` angelegt, **then** läuft **kein** Bootstrap (Walker findet leere DataSource-Liste, Job endet sofort mit `processed: 0`). | must |
|
||||
| 10 | **Given** in der UDB sind 3 Tree-Elemente mit `ragIndexEnabled=true` markiert, **when** ich auf einer Connection-Row in `ConnectionsPage` „Knowledge sync starten" drücke (oder der Daily-Resync läuft), **then** indexiert der Walker exakt diese 3 Pfade, und der dedup-Mechanismus skippt unveränderte Items per Hash. | must |
|
||||
| 11 | **Given** jeder Toggle (Connection-Master, DataSource-Index, DataSource-Neutralize), **when** er geändert wird, **then** wird der Wechsel im `aiAuditLogger` (oder Audit-Log) festgehalten mit `userId, timestamp, target, oldValue, newValue`. | should |
|
||||
| 12 | **Given** ich öffne den Knowledge-Preferences-Edit-Drawer auf einer Connection-Row, **then** sehe und editiere ich `mailContentDepth`, `mailIndexAttachments`, `clickupScope`, `maxAgeDays` (aber **nicht** `neutralizeBeforeEmbed` — dieses Feld existiert nicht mehr). | should |
|
||||
| 13 | **Given** ich setze in der UDB `neutralize=true` auf einem **SharePoint-Site-Knoten** (Container) und `ragIndexEnabled=true` auf demselben, **when** der Walker den Site indexiert, **then** werden **alle** Files unter dem Site mit Neutralisierung indexiert; **and** wenn ein Subfolder eine eigene `neutralize=false` Policy hat, werden dessen Files **ohne** Neutralisierung indexiert (Vererbung mit lokaler Override). | must |
|
||||
| 14 | **Given** ein `FeatureDataSource` für eine Trustee-Tabelle mit `neutralizeFields=["email","name"]` und `ragIndexEnabled=true`, **when** der Walker indexiert, **then** sind in den persistierten Chunks die Felder `email` und `name` neutralisiert, alle anderen Felder im Klartext. | must |
|
||||
| 15 | **Given** auf der `ConnectionsPage` existieren keine separaten Buttons mehr für „Admin-Zustimmung" und „Infomaniak", **when** ich „Verbindung hinzufügen" klicke und „Microsoft" wähle, **then** zeigt der Wizard einen optionalen „Admin-Zustimmung"-Step mit Hinweistext und Skip-Möglichkeit; **and** wenn ich „Infomaniak" wähle, zeigt er einen PAT-Eingabe-Step. | must |
|
||||
| 16 | **Given** ich starte im Wizard einen Infomaniak-Connect, **when** ich im PAT-Step abbreche, **then** wird die im Backend angelegte pending Connection wieder gelöscht (kein Orphan), analog zur heutigen `handleInfomaniakCancel`-Logik. | must |
|
||||
| 17 | **Given** ein Bootstrap-Job einer Connection läuft, **then** sehe ich an **drei** Orten den Status: (a) globales Header-Badge mit Anzahl laufender Jobs, (b) `ConnectionsPage`-Row mit Status-Pill + Stop-Button, (c) `RagInventoryPage` Tab „Meine Daten" mit Detail + Stop-Button. Alle drei Orte zeigen dieselben Counter (gleicher Backend-Endpoint). | must |
|
||||
| 18 | **Given** ich klicke das globale Header-Badge, **then** werde ich zur `RagInventoryPage` (Tab „Meine Daten") navigiert. | should |
|
||||
|
||||
## Testplan
|
||||
|
||||
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|
||||
|----|----|-----|--------------|-----------|--------|
|
||||
| T1 | 1 | api+integration | ja | `gateway/tests/integration/test_knowledge_consent_revoke.py` | pending |
|
||||
| T2 | 2 | unit | ja | `gateway/tests/unit/services/test_walker_uses_datasource_neutralize.py` | pending |
|
||||
| T3 | 3 | api+integration | ja | `gateway/tests/integration/test_datasource_index_toggle_purge.py` | pending |
|
||||
| T4 | 4 | unit+integration | ja | `gateway/tests/unit/services/test_job_cancel_walker_exit.py` + `gateway/tests/integration/test_bootstrap_cancel_endpoint.py` | pending |
|
||||
| T5 | 5 | api+ui | ja | `gateway/tests/integration/test_rag_inventory_endpoints.py` + `frontend_nyla/src/pages/system/__tests__/RagInventoryPage.test.tsx` | pending |
|
||||
| T6 | 6 | repo+api | ja | grep + `gateway/tests/integration/test_workspace_rag_endpoint_404.py` | pending |
|
||||
| T7 | 7 | ui | ja | `frontend_nyla/src/components/AddConnectionWizard/__tests__/AddConnectionWizard.test.tsx` | pending |
|
||||
| T8 | 8 | unit+migration | ja | `gateway/tests/unit/migrations/test_neutralize_pref_to_datasource.py` | pending |
|
||||
| T9 | 9 | integration | ja | `gateway/tests/integration/test_bootstrap_empty_when_no_datasource.py` | pending |
|
||||
| T10 | 10 | integration | ja | `gateway/tests/integration/test_bootstrap_iterates_only_enabled_datasources.py` | pending |
|
||||
| T11 | 11 | unit | ja | `gateway/tests/unit/audit/test_consent_toggle_audit.py` | pending |
|
||||
| T12 | 12 | ui | ja | `frontend_nyla/src/pages/basedata/__tests__/ConnectionsPage.test.tsx` | pending |
|
||||
| T13 | 13 | integration | ja | `gateway/tests/integration/test_walker_neutralize_inheritance_tree.py` | pending |
|
||||
| T14 | 14 | integration | ja | `gateway/tests/integration/test_walker_featuredatasource_field_neutralize.py` | pending |
|
||||
| T15 | 15 | ui | ja | `frontend_nyla/src/components/AddConnectionWizard/__tests__/AddConnectionWizard.test.tsx` (per-type Steps) + grep-Test für entfernte Buttons in `ConnectionsPage` | pending |
|
||||
| T16 | 16 | ui+integration | ja | `frontend_nyla/src/components/AddConnectionWizard/__tests__/InfomaniakCancelRollback.test.tsx` | pending |
|
||||
| T17 | 17 | ui+e2e | teilweise | manuell + smoke-tests für die drei Orte | pending |
|
||||
| T18 | 18 | ui | ja | `frontend_nyla/src/components/Header/__tests__/RagRunningBadge.test.tsx` | pending |
|
||||
|
||||
## Links
|
||||
|
||||
- Vorgänger-Plan: `wiki/c-work/4-done/2026-04-id-unified-knowledge-indexing-rag-concept.md`
|
||||
- Architektonisch betroffen: `wiki/b-reference/platform/neutralization.md`, `wiki/b-reference/gateway/ai-agent.md`, `wiki/b-reference/frontend-nyla/architecture.md`
|
||||
- Code-Touchpoints: siehe Abschnitt „Kritische Stellen im Code"
|
||||
|
||||
## Abschluss
|
||||
|
||||
- [ ] `b-reference/gateway/ai-agent.md` aktualisiert (Knowledge Lifecycle, DataSource-getriebener Walker, Cancel-Mechanismus)
|
||||
- [ ] `b-reference/platform/neutralization.md` aktualisiert (DataSource = SSoT)
|
||||
- [ ] `b-reference/frontend-nyla/architecture.md` aktualisiert (RagInventoryPage, UDB 4. Action-Button)
|
||||
- [ ] `TOPICS.md` aktualisiert (RAG-Inventar als neuer Kanon-Eintrag)
|
||||
- [ ] Dieses Dokument → `c-work/2-build/` → `c-work/3-validate/` → `c-work/4-done/` verschoben
|
||||
- [ ] Eintrag im `c-work/_CHANGELOG.md`
|
||||
|
|
@ -12,6 +12,17 @@ type: `feat` `fix` `refactor` `docs` `test` `chore` `build` · scope: `gateway
|
|||
|
||||
Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
|
||||
|
||||
## 2026-05-12
|
||||
|
||||
- 2026-05-12 | fix | teams-bot | TeamsBot Chat-Scraper: Container-Fallback für light-meetings DOM-Branche. **`chatProcedure.ts`** periodischer Scan promoviert `target` zu `document` wenn der Chat-Container (`message-pane-layout`) `offsetHeight=0` hat ODER Selectoren 0 Treffer liefern, aber global `[class*="fui-ChatMessage"]` existiert (light-meetings rendert Chat-Bubbles in paralleler DOM-Branche). Zusätzlich Root-Cause-Guard `if (!text && author !== 'Unknown')` in beiden Pfaden (MutationObserver Strategy 1 + periodischer Scan) restauriert — verhindert dass strukturelle Fragmente ohne Author als `Unknown: 22:04` Transcripts durchsickern. (c-work: 4-done/2026-04-teamsbot-greenfield-ia-and-live-update.md)
|
||||
- 2026-05-12 | fix | teams-bot | Anonymer Bot: Audio-Capture WebRTC-Wrapper greift nicht mehr in Teams' Pre-Join/Lobby-SDP-Zyklen ein (Ursache des `rejectMediaDescriptionsUpdateAsync`-Crashes und der Lobby-Hänger). `audioCaptureProcedure.ts`: neues `__audioCaptureEnabled`-Flag (default false), `track`-Handler im Wrapper macht nur noch DIAG-Log solange Flag false ist; `__audioCaptureAttachTrack` als window-exponierter Builder; `startCapture()` setzt Flag erst nach `_setState('in_meeting')` und iteriert `getReceivers()` aller `connected` PCs für bereits existierende Audio-Spuren. Label-Filter `mainAudio-*` entfernt (war falsch generalisiert — manche Sessions haben nur eine PC mit UUID-Label). (c-work: 4-done/2026-04-teamsbot-greenfield-ia-and-live-update.md)
|
||||
- 2026-05-12 | feat | frontend-nyla, gateway | FormGeneratorTable Grouping Refactor: Default auf Inline-Modus umgestellt; Toggle-Icon (Inline/Sections) in TableViewsBar; Sections-Mode Multi-Level (alle Permutationen als flache Sections); pageSizeOptions um 1000/2000/10000 erweitert; Collapse-All/Expand-All Buttons fuer Inline-Grouping; 4 Pages (Connections, Prompts, Files, BillingData) auf Inline-Default umgestellt.
|
||||
- 2026-05-12 | feat | frontend-nyla | RAG Consent & Control Phase D Ergänzungen: UDB `SourcesTab` 4. Toggle-Button (🧠 ragIndexEnabled) mit Vererbung; `RagRunningBadge` Floating-Komponente (Polling, Job-Dropdown); `WorkspaceRagInsightsPage` physisch gelöscht; `ConnectionsPage` AdminConsent+Infomaniak Standalone-Buttons entfernt (nur noch via Wizard).
|
||||
- 2026-05-12 | docs | wiki | `b-reference/gateway/ai-agent.md` Abschnitt "RAG Consent & Control" ergänzt (Prinzipien, Datenmodell, Walker-Architektur, API, Frontend-Komponenten, Vererbung); `TOPICS.md` RAG-C&C-Eintrag.
|
||||
- 2026-05-12 | feat | gateway, frontend-nyla | RAG Consent & Control: Phase A–D implementiert. Backend: `DataSource.autoSync` → `ragIndexEnabled` (rename + Migration-Script); `cancelJob()` + `cancelJobsByConnection()` in BackgroundJobService; `JobProgressCallback.isCancelled()` (3s-cached); Walker-Refactor (alle 5 Connectoren iterieren nur über ragIndexEnabled DataSources + Cancel-Check alle 50 Items + `provenance.dataSourceId`); neuer `subPolicyResolver.py` (Tree-Vererbung); neue Routes `/knowledge-consent`, `/knowledge-preferences`, `/knowledge-stop`, `/datasources/{id}/rag-index`; neuer `routeRagInventory` (4 Endpoints); `GET /connections/` liefert Knowledge-Felder; `WorkspaceRagInsights` Route+Permission gelöscht. Frontend: `AddConnectionWizard` vereinfacht (connector-aware Steps, MSFT AdminConsent + Infomaniak PAT integriert, keine Kostenschätzung/Prefs); `connectionApi.ts` neue Methoden + bereinigte Types; neue `RagInventoryPage` + CSS; `pageRegistry` + `App.tsx` Route + Navigation-Eintrag; `FeatureView` `rag-insights` Mapping entfernt.
|
||||
- 2026-05-12 | docs | wiki | Implementierungsplan: `1-plan/2026-05-rag-consent-and-control-implementation.md` — code-grounded Phasenplan (A→F) mit Audit-Befunden (`autoSync` ungenutzt → safe rename; `cancelJob`-API fehlt; `routeDataConnections` GET liefert Knowledge-Felder nicht; PATCH-Pattern aus `routeDataSources` als Vorlage), Datei-genaue Änderungen, Acceptance-Gates pro Phase, Risiko-/Decision-Liste, ~17 Tage T-Shirt-Schätzung.
|
||||
- 2026-05-12 | docs | wiki | Plan: `1-plan/2026-05-rag-consent-and-control-unification.md` — datenzentrierte RAG-Architektur: Per-Element-Index-Toggle in UDB analog Scope/Neutralize (kaskadierende Tree-Vererbung; per-Feld bei Tabellen via `neutralizeFields[]`); Walker iteriert nur über `ragIndexEnabled=true` DataSources; Stop-Mechanismus an drei Sichtbarkeits-Orten (Header-Badge, ConnectionsPage-Row, RagInventoryPage); Wizard ist einziger Eintrittspunkt für alle Connector-Typen (integrierter MSFT Admin-Consent-Step + Infomaniak-PAT-Step); globale `Start > Nutzung > RAG-Inventar` Seite; Wizard ohne Kostenschätzung/Preferences; Löschung der workspace-scoped `WorkspaceRagInsightsPage`.
|
||||
|
||||
## 2026-05-11
|
||||
|
||||
- 2026-05-11 | docs | wiki | d-guides: `google-registration-checklist.md`, `microsoft-entra-registration-checklist.md` (Vendor-Registrierung + Gateway-Env/Code-Referenzen); TOPICS ergänzt.
|
||||
|
|
|
|||
Loading…
Reference in a new issue