lawyer issues

This commit is contained in:
ValueOn AG 2026-05-16 22:54:27 +02:00
parent ae601c56ce
commit 8a1d2161b8
13 changed files with 917 additions and 196 deletions

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-05-11 -->
<!-- verifiedAgainst: frontend_nyla (codebase audit 2026-04-07, post Automation Unification) + Typed Action Architecture Phasen 1-5 + Vitest+RTL Setup + useVoiceStream STT open options (2026-05-10) + Teams Bot dashboard SSE + Module deep-link (2026-05-11) -->
<!-- lastReviewed: 2026-05-15 -->
<!-- verifiedAgainst: frontend_nyla (codebase audit 2026-04-07, post Automation Unification) + Typed Action Architecture Phasen 1-5 + Vitest+RTL Setup + useVoiceStream STT open options (2026-05-10) + Teams Bot dashboard SSE + Module deep-link (2026-05-11) + RAG Consent & Control (RagInventoryPage, UDB 4. Toggle, RagRunningBadge, Wizard-Refactor) (2026-05-15) -->
# Frontend Nyla -- Architektur
@ -31,7 +31,7 @@ Ergänzend typische Root-Dateien und Bereiche im Repo: `main.tsx`, `App.tsx`, `a
| Komponente | Zweck |
|------------|-------|
| `UnifiedDataBar (UDB)` | Multi-Tab-Panel: Chats, Files, Sources — wird in Workspace, CommCoach und Graphical Editor genutzt |
| `UnifiedDataBar (UDB)` | Multi-Tab-Panel: Chats, Files, Sources — wird in Workspace, CommCoach und Graphical Editor genutzt. Sources-Tab hat 4. Action-Button: RAG-Index-Toggle pro DataSource (Consent-gesteuert via `PATCH /api/rag/inventory/{dsId}/index-toggle`) |
| `FormGenerator` | Dynamische Formulare/Tabellen aus Backend-Attribut-Definitionen (siehe [formgenerator.md](formgenerator.md)) |
| `FormGeneratorTree` | Generische Baumkomponente mit Provider-Pattern (`TreeNodeProvider`), Multiselect, DnD, Inline-Editing, Scope/Neutralize, Batch-Actions; ersetzt fruehere `FolderTree`-Komponente (siehe [formgenerator.md](formgenerator.md)) |
| `MandateNavigation` | Feature-Baum-Navigation mit Mandanten-Kontext |
@ -42,6 +42,9 @@ Ergänzend typische Root-Dateien und Bereiche im Repo: `main.tsx`, `App.tsx`, `a
| `CanvasHeader` | Workflow-Metadaten, Versioning (Draft/Publish/Archive), Save-as-Template, New-from-Template |
| `TemplatePicker` | Modal zur Auswahl von Workflow-Vorlagen beim Erstellen neuer Workflows |
| `WorkspacePage` | AI-Workspace mit Chat, UDB, Agent-Streaming |
| `RagInventoryPage` | Globale RAG-Inventar-Seite (`/rag-inventory`): pro Connection Card mit Consent-Toggle, Stop-Button, Reindex, DataSource-Uebersicht. Primaerer Ort fuer RAG-Management (keine duplizierte UI auf ConnectionsPage). API: `/api/rag/inventory/*` |
| `RagRunningBadge` | Floating-Badge in `MainLayout.tsx`: zeigt laufende RAG-Bootstrap-Jobs, Polling gegen `/api/rag/inventory/jobs`, Click-through zum RAG-Inventar |
| `AddConnectionWizard` | Connector-Type-Aware Wizard (Google/MSFT/ClickUp/Infomaniak): dynamische Step-Definition pro Typ, integrierter MSFT Admin-Consent-Step + Infomaniak-PAT-Step. Keine Kostenschaetzung, keine Preferences — nur Anbieter → Consent → Connect |
| `TrusteeDataTablesView` | Konsolidierte Daten-Tabellen-Seite (Treuhand): 13 Tabs (Stammdaten, Lokale Daten, Konfiguration, Buchhaltungs-Daten) ueber generischen `TrusteeDataTab`-Wrapper, Lazy-Mount pro Tab, URL-State `?tab=<key>`, read-only fuer Sync-Tabellen |
| Teams Bot Views (`pages/views/teamsbot/`) | Dashboard (KPI + SSE), Assistent (Wizard), Module (CRUD + `?moduleId`), Live-Session (SSE + MFA), Settings — API `api/teamsbotApi.ts`; Architektur [teams-bot/architecture.md](../teams-bot/architecture.md) |

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- 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 -->
<!-- lastReviewed: 2026-05-15 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification); gateway/modules/features/teamsbot/service.py (Hybrid Agent Escalation 2026-04-24); Typed Action Architecture Phasen 1-5; featureDataAgent domain hints hook 2026-04-27; central parameterValidation + DatabaseQueryError 2026-04-28; OpenAI temperature contract for GPT-5.x / o-series 2026-04-28; Voice STT speechToText params note 2026-05-10; RAG Consent & Control Unification (Phases A-D) 2026-05-12; Zombie-Killer + Walker-Timeouts 2026-05-14; FeatureDataAgent Query-Repair-Loop + Ontology layer 2026-05-15 -->
# AI Agent & Knowledge Store
@ -96,6 +96,65 @@ Keine separate Tool-Level-RBAC: Zugriff wird über **Datenbank-RBAC** (z. B. Fil
---
## FeatureDataAgent: Query-Repair-Loop + Ontologie (ab 2026-05)
Der Feature Data Sub-Agent (`gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py`) ist die Spezialisten-Schicht hinter dem `queryFeatureInstance`-Tool. Er hat seinen eigenen ReAct-Loop und drei Tools (`browseTable`, `queryTable`, `aggregateTable`) gegen jede Feature-Datentabelle. Ab Mai 2026 verhindern zwei Schichten Halluzinationen deterministisch:
### 1. Pre-execute Validator (`queryValidator.py`)
`QueryValidator` validiert die Argumente jedes Tool-Calls **vor** der DB. Bei Fehlschlag liefert der Tool-Handler `ToolResult(success=False, error=<short>, errorDetails={code, field, suggestion, hint})` -- der LLM erhält strukturierte Reparatur-Hinweise im nächsten ReAct-Turn und kann gezielt umlenken.
| `code` | Triggert bei | LLM-Reaktion |
|-------------------------------|------------------------------------------------------------------|-----------------------------------------------|
| `FIELD_NOT_FOUND` | Feldname existiert nicht im Pydantic-Modell | `browseTable` aufrufen, dann `suggestion` nehmen |
| `OPERATOR_INCOMPATIBLE` | `LIKE` auf int-Feld, `>` auf string | Op wechseln oder typkorrekten Wert filtern |
| `INVALID_AGGREGATE_TARGET` | `SUM`/`AVG` auf `*Balance`/`*Total` (Convention oder Ontologie) | `queryTable` mit Periode-Filter benutzen |
| `ORDER_BY_INVALID` | `orderBy` zeigt auf unbekanntes Feld | Feldname korrigieren |
| `TYPE_MISMATCH` | `SUM` auf nicht-numerischem Feld | Anderes Feld wählen |
Verkabelung: `runFeatureDataAgent` ruft `_buildValidatorForFeature(featureCode)`, das den Validator automatisch mit der `OntologyDescriptor` des Features verbindet (wenn vorhanden). Die Ontologie ersetzt die Convention-Defaults: `NEVER_AGGREGATE`-Constraints aus der Ontologie haben Vorrang vor dem `*Balance`/`*Total`-Suffix-Check.
### 2. Ontologie-Layer (Phase 2, Trustee-Pilot)
Statt freier `_AGENT_DOMAIN_HINTS`-Strings exportieren Features einen **strukturierten `OntologyDescriptor`** über `getAgentOntology() -> OntologyDescriptor` in ihrer `mainXxx.py`. Der Descriptor enthält:
- **`entities`**: Semantische Konzepte (z. B. `BankAccount` spezialisiert `Account`, gebunden an `TrusteeDataAccount` mit `accountNumber LIKE '102%'`). Sub-Entitäten mit `parentEntity` machen Sub-Group-Filter explizit.
- **`relations`**: FK-Beziehungen mit `Cardinality` (z. B. `JournalLine -> JournalEntry (MANY_TO_ONE via journalEntryId)`).
- **`constraints`**: Maschinenlesbare Regeln (`NEVER_AGGREGATE`, `REQUIRES_FILTER_ON`, `PREFERRED_TABLE_FOR_INTENT`), die **gleichzeitig vom Validator und vom Prompt-Compiler konsumiert werden** -- Single Source of Truth.
- **`canonicalPatterns`**: Tool-Call-Skelette für häufige Intents (`BANK_BALANCE_AT_DATE`, `JOURNAL_SUM_AT_ACCOUNT`, ...). Werden vom Compiler als worked examples in den Prompt geschrieben.
`featureDataAgent._buildSchemaContext` prüft bei jedem Sub-Agent-Run, ob das Feature `getAgentOntology()` exponiert (`_loadFeatureOntologyBlock`). Wenn ja, wird der Block via `ontologyToPromptCompiler.compileOntologyToPrompt()` deterministisch gerendert und an den Schema-Prompt angehängt. Sonst Fallback auf den Legacy-Pfad `getAgentDomainHints()`. Eval-only Override: `POWERON_DISABLE_FEATURE_ONTOLOGY=1` erzwingt den Legacy-Pfad (siehe Eval-Harness).
### 3. Eval-Harness (Phase 1.5)
`gateway/tests/eval/runTrusteeBenchmark.py` ist ein standalone Runner (kein pytest), der jede Frage des Goldstandards (`tests/fixtures/trusteeBenchmark/questions.yaml`, derzeit 19) gegen einen `FakeFeatureDataProvider` mit synthetischen aber realistischen Trustee-Daten fährt. Drei Modi werden parallel gemessen:
| Mode | Validator | Ontologie | Prompt-Block |
|-----------|-----------|-----------|-----------------------------------------------|
| baseline | aus | aus | `getAgentDomainHints()` (Legacy) |
| phase1 | an | aus | `getAgentDomainHints()` (Legacy) + Validator |
| phase2 | an | an | `compileOntologyToPrompt(...)` + Validator |
Pro Frage werden gemessen: `patternOk` (richtige Tool-Wahl + Filter), `forbidOk` (vermiedene Anti-Pattern), `numericOk` (Antwort enthält die erwarteten Zahlen), `accuracyOk` (alle drei). Aggregiert: `repairConversionRate` (erfolgreiche Repairs / Validator-Rejects), `totalCostCHF`, `totalRounds`. Output: Markdown-Bericht + JSON in `local/notes/trustee-benchmark-<timestamp>.{md,json}`.
**Letzte Messung (2026-05-15, 19 Fragen × 3 Modi = 57 echte LLM-Runs):**
| Mode | Accuracy | Pattern compliance | Validator rejects | Rounds | Cost (CHF) |
|-----------|----------|--------------------|-------------------|--------|------------|
| baseline | 89.5 % | 89.5 % | 0 | 40 | 0.0841 |
| phase1 | 94.7 % | 100 % | 2 | 41 | 0.0851 |
| phase2 | 100 % | 100 % | 0 | 39 | 0.0975 |
### 4. Repair-Telemetrie (`AgentTrace`)
`AgentTrace` (`datamodelAgent.py`) aggregiert pro Sub-Agent-Run drei neue Counter (gefüllt von `agentLoop._computeRepairCounters` am Ende des Loops):
- `validationFailures`: Tool-Calls, die der Validator vor der DB abgelehnt hat.
- `repairAttempts`: Wiederholungen desselben `toolName` in späteren Runden (nachgewiesener Repair-Versuch).
- `successAfterRepair`: Repair-Attempts, die einen `validationFailureCode=None`-Result lieferten.
Jeder `ToolCallLog` trägt den `validationFailureCode` (z. B. `INVALID_AGGREGATE_TARGET`) für Pro-Call-Analyse. Der `AGENT_SUMMARY`-Event am Loop-Ende exposed die aggregierten Counter ans Frontend / Persistierung.
## Agent Tools
### Prinzip
@ -177,7 +236,7 @@ Zusätzlich zu den unten genannten **Kern-Tools** existieren **dynamische Tools*
| Tool | Kurzbeschreibung |
|------|------------------|
| `queryFeatureInstance` | Abfrage anderer Feature-Instanz (setzt bei Bedarf `requireNeutralization` für Sub-Calls). Sub-Agent hat `browseTable`, `queryTable` und `aggregateTable` (SUM/COUNT/AVG/MIN/MAX mit GROUP BY). DB-Connection-Pooling und Result-Caching (5 Min TTL). System-Prompt wird aus drei Schichten gebaut: (1) generischer Header mit Tabellen + Pydantic-Schema (`_buildSchemaContext`), (2) generische Regeln inkl. **kein SUM/AVG auf bereits aggregierten Saldo-/Total-Feldern** (`closingBalance`, `openingBalance`, `debitTotal`, `creditTotal`, …), (3) optionale **feature-spezifische Domain-Hints** via `getAgentDomainHints()` in `mainXxx.py` — z. B. KMU-Kontoplan-Präfixe (1xxx Aktiven, 102x Bank, 100x Kasse), `periodMonth=0`-Konvention und kanonische Query-Patterns für Trustee. Round-/Cost-Budget wird vom Parent-Agent geerbt (`AgentConfig.maxRounds` → Tool-Context → `runFeatureDataAgent`). |
| `queryFeatureInstance` | Abfrage anderer Feature-Instanz (setzt bei Bedarf `requireNeutralization` für Sub-Calls). Sub-Agent hat `browseTable`, `queryTable` und `aggregateTable` (SUM/COUNT/AVG/MIN/MAX mit GROUP BY und `filters`). DB-Connection-Pooling und Result-Caching (5 Min TTL). System-Prompt wird aus drei Schichten gebaut: (1) generischer Header mit Tabellen + Pydantic-Schema (`_buildSchemaContext`), (2) generische Regeln inkl. **kein SUM/AVG auf bereits aggregierten Saldo-/Total-Feldern** (`closingBalance`, `openingBalance`, `debitTotal`, `creditTotal`, …), (3) **Domain-Block**: bevorzugt aus `getAgentOntology() -> OntologyDescriptor` via `ontologyToPromptCompiler.compileOntologyToPrompt()` (deterministisch, single source of truth für Prompt + Validator); Fallback auf `getAgentDomainHints() -> str` für Features ohne Ontologie. Round-/Cost-Budget wird vom Parent-Agent geerbt (`AgentConfig.maxRounds` → Tool-Context → `runFeatureDataAgent`). **Pre-execute Validator** (`QueryValidator`, mit Ontologie verkabelt via `_buildValidatorForFeature`) fängt vier Halluzinations-Klassen deterministisch ab und gibt strukturierte Repair-Hints zurück: siehe Abschnitt »FeatureDataAgent: Query-Repair-Loop + Ontologie« unten. |
| `listWorkflowHistory` | Workflow-Historie |
| `readWorkflowMessages` | Nachrichten eines Workflows lesen |
@ -282,6 +341,22 @@ Jeder Walker (`subConnectorSync*.py`) iteriert über `ragIndexEnabled=True` Data
- `cancelJobsByConnection(connectionId)` — bulk-cancel aller laufenden Jobs
- `JobProgressCallback.isCancelled()` — kooperativer Check mit 3s-Cache
#### Zombie-Job-Recovery & Walker-Timeouts (ab 2026-05-14)
Drei-stufige Absicherung gegen hängende Bootstraps:
1. **Boot-Recovery:** `recoverInterruptedJobs()` markiert beim Worker-Start RUNNING-Jobs als ERROR (kein Auto-Requeue, sonst Endlosschleife).
2. **Live-Killer:** Cron `background_jobs.zombie_killer` ruft alle 5 min `killZombieJobs(maxAgeSeconds=1800)` auf — RUNNING-Jobs ohne Progress-Update >30 min werden als ERROR markiert (nutzt `startedAt` aus dem DB-Record).
3. **Walker-Timeouts** (`subWalkerHelpers.py`): jeder Walker wickelt seine drei Hot-Spots in `asyncio.wait_for`:
| Helper | Timeout | Zweck |
|--------|---------|-------|
| `downloadWithTimeout` | 60s | Adapter-Download (SharePoint/Drive/kDrive/Outlook-Attachment) |
| `extractWithTimeout` | 90s | `runExtraction` auf Worker-Thread (sync Extraktor blockiert sonst Event-Loop) |
| `ingestWithTimeout` | 60s | `KnowledgeService.requestIngestion` (Embedding-API) |
Vor jedem Item ruft der Walker `logItemStart(service, path, sizeBytes, mime)` — das letzte solche Log vor einem Hang oder Timeout benennt das verursachende Item exakt (Pfad, Grösse, MIME). Sync-Extraktion läuft via `asyncio.to_thread` auf einem Worker-Thread; `wait_for` schützt nur den Awaiter, der Thread selbst kann weiterlaufen, aber der Walker geht zum nächsten Item.
#### API-Endpunkte
| Methode | Pfad | Zweck |

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-25 -->
<!-- verifiedAgainst: gateway/modules/workflows/methods/methodTrustee/ + gateway/modules/features/trustee/ + Typed Action Architecture Phasen 1-5 + Account-Balance-Import -->
<!-- lastReviewed: 2026-05-15 -->
<!-- verifiedAgainst: gateway/modules/workflows/methods/methodTrustee/ + gateway/modules/features/trustee/ + Typed Action Architecture Phasen 1-5 + Account-Balance-Import + Agent-Ontologie (getAgentOntology) 2026-05-15 -->
# Feature: Trustee
@ -139,6 +139,23 @@ UI-Sichtbarkeit der Daten-Tabellen-Seite haengt am Permission-Eintrag `ui.featur
---
## Agent-Ontologie (ab 2026-05)
`mainTrustee.py` exportiert `getAgentOntology() -> OntologyDescriptor` (`gateway/modules/features/trustee/trusteeOntology.py`). Die Ontologie ist die **Single Source of Truth** sowohl fuer den Feature Data Sub-Agent-Prompt als auch fuer den `QueryValidator`:
| Bestandteil | Inhalt im Trustee-Pilot |
|-------------|------------------------|
| `entities` | `Account` mit Spezialisierungen `BankAccount` (`102%`) und `CashAccount` (`100%`); `AccountBalance` (period-bucketed snapshot); `JournalEntry`/`JournalLine` (Transaktionen). Jede Entitaet listet Invarianten wie "periodMonth=0 = Jahresabschluss" oder "bookingDate ist unix-seconds float". |
| `relations` | `AccountBalance -> Account (via accountNumber)`, `JournalLine -> JournalEntry (via journalEntryId)`, `JournalLine -> Account (via accountNumber)`. |
| `constraints` | `NEVER_AGGREGATE` auf `closingBalance`, `openingBalance`, `debitTotal`, `creditTotal` (= alle vier sind period TURNOVER oder Balance, nicht ueber Perioden summierbar). `REQUIRES_FILTER_ON` (`periodYear`, `periodMonth`) auf `TrusteeDataAccountBalance`. `PREFERRED_TABLE_FOR_INTENT` ([BANK_BALANCE_AT_DATE, BALANCE_AT_YEAR_END] -> `TrusteeDataAccountBalance`). |
| `canonicalPatterns` | 8 Tool-Call-Skelette: `BANK_BALANCE_AT_DATE`, `BANK_GROUP_TOTAL_AT_DATE`, `BALANCE_HISTORY_PER_YEAR`, `MONTHLY_BALANCE_SNAPSHOT`, `ACCOUNT_LIST_BY_TYPE_OR_PREFIX`, `JOURNAL_SUM_AT_ACCOUNT`, `COUNT_ROWS`, `JOURNAL_LINES_BY_AMOUNT`. |
**Migrations-Pfad:** Die alten Domain-Hints (`_AGENT_DOMAIN_HINTS`) sind als `_AGENT_DOMAIN_HINTS_LEGACY` geparkt; `getAgentDomainHints()` liefert sie weiter als Fallback fuer Code-Pfade, die `getAgentOntology()` noch nicht kennen. Der `_buildSchemaContext`-Builder in `featureDataAgent` zieht jedoch standardmaessig die kompilierte Ontologie. Eval-Override: `POWERON_DISABLE_FEATURE_ONTOLOGY=1` schaltet auf den Legacy-Block zurueck (Benchmark-Vergleich Baseline/Phase1/Phase2).
**Begruendung des Trustee-Pilots:** Trustee ist heute der Hot-Spot fuer Halluzinationen (SUM closingBalance ueber Perioden, Verwechslung von `creditTotal`/`closingBalance` bei Ertragsfragen). Die Eval-Suite (`gateway/tests/eval/runTrusteeBenchmark.py`, 19 Fragen) misst Genauigkeit, Pattern-Compliance und Repair-Konversion deterministisch -- siehe `b-reference/gateway/ai-agent.md` Abschnitt "FeatureDataAgent: Query-Repair-Loop + Ontologie".
---
## Tests
| Test | Datei | Was er beweist |

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-16 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-16) -->
<!-- lastReviewed: 2026-05-15 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-16, RAG Consent & Control update 2026-05-15) -->
# Neutralisierungs-System
@ -14,7 +14,7 @@ Neutralisierung ersetzt mandantensensible Inhalte durch stabile Platzhalter, bev
Orthogonal dazu (kein Ersatz des Gates, sondern Data-at-rest bzw. Workflow):
- **RAG:** `mainServiceKnowledge.indexFile()` neutralisiert Chunks bei `FileItem.neutralize=True`.
- **RAG:** `mainServiceKnowledge.indexFile()` neutralisiert Chunks bei `FileItem.neutralize=True`. **Seit RAG Consent & Control (2026-05): `DataSource.neutralize` ist die Single Source of Truth für Neutralisierung bei RAG-Indexierung.** Das frühere `UserConnection.knowledgePreferences.neutralizeBeforeEmbed` existiert nicht mehr — Walker lesen `neutralize` ausschließlich aus der `DataSource`-Row (Tree-Vererbung entlang Container-Knoten via `subPolicyResolver.py`).
- **Agent:** DataSource- und FeatureDataSource-Flags setzen bzw. vererben Neutralisierungspflicht; Sub-Agent-Calls können `requireNeutralization=True` tragen.
- **Workflows:** Aktion `neutralizeData` neutralisiert explizit extrahierte ContentParts (prüft u. a. `NeutralizationConfig.enabled`, nicht das Session-Flag).
@ -47,7 +47,7 @@ Im zentralen AI-Gate (`_neutralizeRequest`):
1. **Toggle `FileItem.neutralize`:** Index und Chunks werden synchron gelöscht; Re-Index im Hintergrund verhindert Leaks bei Fehlern in der Nachindexierung.
2. **Toggle `FileFolder.neutralize`:** Propagiert `neutralize` rekursiv auf alle Dateien im Ordner; Index/Chunks werden pro Datei synchron gelöscht, Re-Index im Hintergrund. Neue/verschobene Dateien erben das Flag des Ziel-Ordners.
3. **Indexierung:** `indexFile()` neutralisiert Text-Chunks bei `FileItem.neutralize=True` → Data-at-rest abgesichert.
4. **DataSource-Download:** `neutralize`-Flag wird auf erzeugte `FileItem`s vererbt.
4. **DataSource-Download / RAG-Indexierung:** `DataSource.neutralize` ist die einzige Quelle für die Neutralisierungs-Policy pro Tree-Element. Vererbung: Container-Knoten (z.B. SharePoint-Site) propagiert `neutralize` auf alle Kinder, bis ein Kind-Knoten eine eigene Policy setzt (analog `inheritedScope` / `inheritedNeutralize` im Frontend). Walker (`subConnectorSync*.py`) lesen die effektive Policy via `subPolicyResolver.resolveEffectivePolicy()`. Das Flag wird zusätzlich auf erzeugte `FileItem`s vererbt.
5. **FeatureDataSource (Tabellen-Level):** `_queryFeatureInstance()` setzt bei `neutralize=True` u. a. `requireNeutralization=True` für Sub-Agent-AI-Calls.
6. **FeatureDataSource (Feld-Level):** `neutralizeFields` definiert Spalten, deren Werte in `FeatureDataProvider` mit deterministischen Platzhaltern `[NEUT.<field>.<hash>]` ersetzt werden, bevor Daten den Sub-Agent erreichen. Gleichheits-Vergleiche bleiben möglich (stabiler Hash).
7. **AI-Call:** Bei erzwungener Neutralisierung (`requireNeutralization=True`) schlägt fehlgeschlagene Neutralisierung hart fehl (`RuntimeError` / Blockierung bzw. Entfernen betroffener Message-Teile — kein Durchleiten im Rohzustand).

View file

@ -0,0 +1,195 @@
<!-- status: plan -->
<!-- started: 2026-05-14 -->
<!-- component: gateway | frontend-nyla | platform -->
# Lawyer Feature (Mandatsvorbereitung + Dashboards)
## Beschreibung und Kontext
Im Sales-Meeting mit David Vasella (WalderWyss) am 13.05.2026 hat sich ein klares Bild ergeben: Anwaltskanzleien arbeiten mit grossen Mengen **nicht human-readable Daten** (PDFs, E-Mails, frühere Mandate, Konflikt-Checks, KYC/AML, Buchungen). Zwischen internen Prozessen und der Mandantsbesprechung entsteht ein Overhead, den **Copilot nicht löst**. Existierende Branchen-Tools (Harvey.ai ~500 USD p/m/user, Legora, ClientFlex) sind teuer und decken die Anforderungen nicht voll ab. WW hat **kein CRM**.
PORTA hat bereits alle Bausteine: Workspace, Knowledge/RAG, Konnektoren, Neutralisierung, Private LLM. Das `lawyer`-Feature bündelt diese Bausteine zu einem **opinionated UI für Anwälte**: ein Feature-Modul mit zwei Hauptseiten **Dashboards** (HR / Business-Client / IT) und **Matter Preparation** (Mandatsvorbereitung in 5 Minuten).
Business-Treiber:
- WW konkretes Sales-Asset (HTML-Präsentation und später Live-Demo).
- Eigene Use Cases für Homepage (`www.poweron.swiss`) Sales-Story «echtes Produkt».
- Investor-Pitch (Founderful via Dominic + Philipp) braucht klare vertikale Geschichte.
- Wiederverwendbar für jede Anwaltskanzlei und mit Anpassung für Treuhand/Audit/Steuerberatung.
## Fokus und kritische Details
- **Berufsgeheimnis (StGB Art. 321)** Mandantsdaten dürfen den Mandanten-Tenant nicht verlassen. Neutralisierung + Private LLM **zwingend** für jeden Pfad, der LLM nutzt (`wiki/b-reference/platform/neutralization.md`).
- RBAC korrekt: feature-instance roles, **NICHT** mit mandate roles vermischen (`.cursor/rules/rbac-role-separation.mdc`).
- Daten kommen aus heterogenen Quellen: Document Management System (DMS), E-Mail-Archiv, Zeiterfassung, Buchhaltung, KYC/AML, Konflikt-Check-DB. Konnektor-Pflege ist der **harte Teil**, nicht das UI.
- Mockup-Daten in der WW-Präsentation müssen klar als **synthetic** markiert sein.
## Ziel und Nicht-Ziele
- **Ziel:** Feature-Modul `lawyer` mit:
- Dashboard-Seite mit Tabs pro Funktion (HR, Business/Client, IT, optional Finance).
- Matter-Preparation-Seite: Anwalt definiert Kontext (Mandant, Matter, Meeting-Ziel, Datum), System aggregiert Quellen, AI-Agent generiert Briefing (Kerndaten, Risiken, Deadlines, offene Punkte, Agenda-Vorschlag).
- **Ziel:** Wiederverwendung der bestehenden PORTA-Bausteine (Workspace, Knowledge, Konnektoren, Neutralisierung, Private LLM).
- **Ziel:** Standard-Konnektoren für DMS (SharePoint, iManage), E-Mail (Outlook/Exchange), KYC-/Konflikt-Datenquellen, Buchhaltung (im ersten Schritt einer pro Kategorie).
- **Explizit NICHT:** vollständiges Practice-Management-System ersetzen (kein Time-Tracking-Replacement, kein Billing-System).
- **Explizit NICHT:** generische Vertragsanalyse / Drafting (das machen Harvey/Legora besser, wir sind komplementär).
- **Explizit NICHT:** eigene KYC/AML-Datenquelle aufbauen wir konsumieren bestehende.
## Betroffene Module
- Gateway:
- Neues Feature-Modul `gateway/modules/features/lawyer/` mit `mainLawyer.py`, `datamodelLawyer.py`, `interfaceLawyer.py`, `routeFeatureLawyer*.py`.
- Workflow-Method `gateway/modules/workflows/methods/methodLawyer/` für `lawyer.prepareMatter`, `lawyer.queryData`.
- Konnektoren: prüfen, welche bestehen (`gateway/modules/connectors/`), welche neu (DMS, KYC).
- Frontend:
- Neue Komponenten `frontend_nyla/src/components/Lawyer/`.
- Dashboard-View mit Tabs.
- Matter-Preparation-View (analog Workspace, opinionated).
- DB-Migration: ja neue Tabellen `LawyerMatter`, `LawyerPreparation`, `LawyerDashboardSnapshot`, `LawyerKpi`.
- Andere Komponenten: Knowledge/RAG (bestehend), Neutralisierung (bestehend), Private LLM (bestehend), Workspace-Agent-Tools (Erweiterung).
## Entscheidungen
| Datum | Entscheidung | Begründung |
|-------|-------------|------------|
| 2026-05-14 | Feature-Code `lawyer` (analog `trustee`, `realEstate`) | Konsistenz mit bestehenden Feature-Modulen |
| 2026-05-14 | Template-Rollen `lawyer-admin`, `lawyer-user`, `lawyer-viewer` | RBAC-Regel: feature-instance roles, mandate-getrennt |
| 2026-05-14 | Matter-Preparation nutzt bestehenden Workspace-Agent + neue opinionated Tool-Chain | Wiederverwendung statt Neubau |
| 2026-05-14 | Dashboards starten als read-only Aggregations-Views auf Konnektor-Daten | Schneller Time-to-Demo, Schreibpfad später |
| 2026-05-14 | Sprache UI primär Englisch | WW-Audience Management Board, Investor-tauglich; DE-Lokalisierung Phase 2 |
## Use Cases (User Stories)
### UC-1: Matter Preparation in 5 Minutes
**Given** ein Anwalt hat morgen ein Klienten-Meeting für Matter `M-2024-0042` (M&A-Transaktion).
**When** er auf der Matter-Preparation-Seite den Matter und das Meeting-Ziel eingibt ("Statusbesprechung Due Diligence, offene Punkte"),
**Then** sammelt das System innerhalb von ~60 Sekunden aus DMS (alle Dokumente zu M-2024-0042), E-Mails (letzte 30 Tage), Zeiterfassung (WIP + Deadlines), KYC/AML (Aktueller Status), Konflikt-Check.
**And** generiert ein strukturiertes Briefing:
- Kerndaten Mandant + Matter
- Stand Due Diligence (Dokumente, offene Punkte)
- Risiken & Deadlines
- Offene Aktionen seit letzter Besprechung
- Vorschlag Agenda
- Quellenliste (jeder Punkt mit Link zur Quelle)
**And** alle Mandantsdaten werden neutralisiert, bevor sie das LLM erreichen.
### UC-2: Partner-Dashboard
**Given** ein Partner möchte vor der Wochensitzung den Stand seines Teams sehen.
**When** er die Dashboard-Seite öffnet,
**Then** sieht er HR-KPIs (Billable Hours, Utilization, Realization) für sein Team, Top-3-Risk-Matters, AR Aging.
**And** kann pro KPI in die Detailansicht drillen (Drilldown via Konnektor-Live-Query).
### UC-3: IT/Compliance-Dashboard
**Given** der IT-Leiter braucht für ein Audit-Meeting den Compliance-Status.
**When** er den IT-Tab öffnet,
**Then** sieht er Uptime, Security-Incidents (letzte 90 Tage), Patch-Status, KYC/AML-Backlog, Conflict-Check-SLAs.
## Datenmodell-Skizze
| Modell | Felder (Kern) | Kommentar |
|--------|---------------|-----------|
| `LawyerMatter` | id, featureInstanceId, mandateCode, clientName, matterType, openedAt, status, leadLawyerUserId | Mandate spiegeln, nicht zweimal halten |
| `LawyerPreparation` | id, matterId, requestedBy, meetingGoal, createdAt, briefingJson, sourceRefs | Persistiert das generierte Briefing für Audit + Wiederverwendung |
| `LawyerDashboardSnapshot` | id, featureInstanceId, dashboardType, periodStart, periodEnd, dataJson, generatedAt | Cache für Dashboard-Daten, Refresh periodisch |
| `LawyerKpi` | id, featureInstanceId, kpiCode, label, formula, lastValue, lastValueAt, dashboardType | Konfigurierbare KPI-Definitionen pro Tenant |
| `LawyerSourceMap` | id, featureInstanceId, sourceType, connectorRef, mappingJson | Welcher Konnektor liefert welche Daten in welche KPI |
## Actions (Workflow-Method)
| Action | Eingabe | Output | Zweck |
|--------|---------|--------|-------|
| `lawyer.prepareMatter` | featureInstanceId, matterId, meetingGoal, lookbackDays | LawyerPreparation (briefingJson + sourceRefs) | Sammelt aus Quellen, neutralisiert, generiert Briefing via Workspace-Agent |
| `lawyer.refreshDashboard` | featureInstanceId, dashboardType, periodStart, periodEnd | LawyerDashboardSnapshot | Re-aggregiert KPI-Werte aus Konnektoren |
| `lawyer.queryData` | featureInstanceId, freie Filter | ActionResult mit Query-Resultat | Read-only Zugriff für AI-Agent (analog `trustee.queryData`) |
## UI-Skizze
```
/lawyer/<featureInstanceId>/
├── dashboards/
│ ├── hr (Tab)
│ ├── business (Tab)
│ ├── it (Tab)
│ └── finance (Tab, optional)
└── matter-preparation/
├── <list of recent matters>
└── <matterId>/
├── briefing (generated)
├── sources (drilldown)
└── agenda-export (PDF/Markdown)
```
## RBAC-Mapping
- Template-Rollen (Feature-Code `lawyer`, `mandateId=NULL`, `isSystemRole=False`):
- `lawyer-admin` Feature-Instance-Admin, Konnektor-Konfig, KPI-Definition.
- `lawyer-user` Anwalt, Matter-Preparation, Dashboard read.
- `lawyer-viewer` Read-only, z. B. Management Board.
- Nutzung: `FeatureAccessRole` mit `featureCode=lawyer, featureInstanceId=<id>, mandateId=<id>, roleId=<>`.
- Strikt getrennt von Mandate-Rollen (`admin`, `user`, `viewer`).
## Umsetzungs-Checkliste
- [ ] Datenmodell (`datamodelLawyer.py`) 5 Modelle + PowerOnModel-Basis
- [ ] Migration: Auto-Schema reicht? (analog `trustee`)
- [ ] Feature-Modul (`mainLawyer.py`) inkl. Template-Rollen-Definition
- [ ] Workflow-Method `methodLawyer/` mit 3 Actions
- [ ] Konnektoren-Inventur: was haben wir, was fehlt
- [ ] DMS-Konnektor (SharePoint vorhanden, iManage neu?)
- [ ] E-Mail-Konnektor (Outlook/Exchange vorhanden?)
- [ ] Zeiterfassung (mandantenabhängig, Anbindung über generisches Schema)
- [ ] KYC/AML (extern, API-Anbindung mandantenspezifisch)
- [ ] Konflikt-Check (intern WW-System API klären)
- [ ] Routes (`routeFeatureLawyer.py`)
- [ ] Frontend-Komponenten (`Lawyer/Dashboard`, `Lawyer/MatterPreparation`)
- [ ] RBAC: Template-Rollen registrieren (siehe `.cursor/rules/rbac-role-separation.mdc`)
- [ ] Neutralisierung: `prepareMatter`-Action zwingend mit `PrivateLLM`-Pfad für sensitive Daten
- [ ] Navigation (Gateway → Frontend) Feature-Eintrag
- [ ] Billing-Impact: prüfen (analog andere Features pro Feature-Instance, Subscription-Capacity)
- [ ] Mockup-Daten-Set (synthetic) für WW-Präsentation + Demo
- [ ] Demo-Login-Mandant einrichten (für Live-Demo nach Präsentation)
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|---------------------------|------|
| 1 | Given Anwalt mit `lawyer-user`-Rolle, When er Matter-Preparation für offenen Matter triggert, Then erhält er in <90s strukturiertes Briefing mit 3 Quellen | must |
| 2 | Given sensitive Mandantsdaten (Namen, Beträge), When `lawyer.prepareMatter` läuft, Then werden alle PII vor LLM-Call neutralisiert (Audit-Trail in Neutralization-Log) | must |
| 3 | Given User mit `lawyer-viewer`, When er Dashboard öffnet, Then sieht er KPIs read-only ohne Schreib-Buttons | must |
| 4 | Given User mit Mandate-Rolle `admin` aber ohne `lawyer-*`, When er Lawyer-Feature öffnet, Then Access Denied | must |
| 5 | Given Dashboard-Konnektor offline, When `refreshDashboard` läuft, Then Snapshot zeigt Fehlerstatus, Frontend zeigt klare Meldung statt leerer Werte | should |
| 6 | Given Briefing generiert, When Anwalt es exportiert, Then PDF/Markdown mit Quellenliste und «synthetic data» Wasserzeichen (im Demo-Mandanten) | should |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1 | api | ja | gateway/tests/test_lawyer_prepare_matter.py | pending |
| T2 | 2 | integration | ja | gateway/tests/test_lawyer_neutralization.py | pending |
| T3 | 3,4 | api | ja | gateway/tests/test_lawyer_rbac.py | pending |
| T4 | 5 | integration | ja | gateway/tests/test_lawyer_dashboard_refresh.py | pending |
| T5 | 1,6 | e2e | manuell | | pending |
## Demo-Asset Plan (für WW-Präsentation)
- Synthetic Matter `M-DEMO-001` (M&A) mit fiktiven Dokumenten, E-Mails, Zeiteinträgen.
- Briefing-Output statisch generiert und in `30-presentation/src/data/matter-preparation.json` ablegen, damit die HTML-Präsentation **ohne Backend** funktioniert.
- Wenn David Live-Demo will: gleichen Demo-Mandanten im PORTA-Demo-Tenant live verfügbar machen.
## Links
- WW-Kontext: `pamocreate/projects/poweron/customer-walderwyss/20-objectives/20260514-zusammenfassung-meeting-dv.md`
- WW-Präsentation: `pamocreate/projects/poweron/customer-walderwyss/30-presentation/`
- Bestehende Feature-Doku als Vorbild: `wiki/b-reference/gateway/features/trustee.md`
- Plattform-Bausteine: `wiki/b-reference/platform/neutralization.md`, `wiki/b-reference/platform/rbac.md`
- PR: (noch nicht)
- Issue: (noch nicht)
## Abschluss
- [ ] `b-reference/gateway/features/lawyer.md` als neue Kanon-Seite anlegen
- [ ] `TOPICS.md` Eintrag für Lawyer-Feature
- [ ] Dieses Dokument → `2-build/``3-validate/``4-done/`
- [ ] Eintrag in `c-work/_CHANGELOG.md` pro Phase

View file

@ -1,6 +1,8 @@
# STT: Google Speech-to-Text v2 / Chirp Evaluation (follow-up)
Status: planned (not implemented). Related: gateway `connectorVoiceGoogle.py` uses Speech v1 `SpeechClient` only.
Status: **in progress** — interactive benchmark page available for SysAdmin (2026-05-15).
Related: gateway `connectorVoiceGoogle.py` uses Speech v1 `SpeechClient` only.
## Goal
@ -10,13 +12,26 @@ Benchmark STT v2 (e.g. Chirp / Chirp 2) for `de-DE` vs current v1 `latest_short`
- WER / subjective quality in meeting + coaching scenarios
- Cost and quota
## Steps
## Current State (2026-05-15)
1. Add optional v2 client path (`google.cloud.speech_v2` or REST) behind a feature flag.
2. Run A/B on CommCoach streaming and Teamsbot batch paths with identical audio fixtures.
3. Document decision in `wiki/b-reference/` and remove flag or make v2 default.
- **Interactive benchmark page** added: `Administration > System > STT Benchmark` (SysAdmin only).
- Upload or record audio; runs v1 and v2 (Chirp 2) simultaneously; shows transcription, confidence, latency side-by-side.
- Configurable: language, v1 model, v2 model, v2 region.
- Backend: `routeAdminSttBenchmark.py` using `google.cloud.speech` (v1) + `google.cloud.speech_v2` (v2).
- Frontend: `SttBenchmarkPage.tsx` under `/admin/stt-benchmark`.
- **Production switch not yet done**`connectorVoiceGoogle.py` still uses v1 only.
## Next Steps
1. Run benchmark with real meeting/coaching audio samples across `de-DE`, `de-CH`, `en-US`.
2. Compare latency + quality. Document in this file.
3. If Chirp 2 wins: add v2 client path to `connectorVoiceGoogle.py` behind feature flag.
4. Run A/B on CommCoach streaming and Teamsbot batch paths with identical audio fixtures.
5. Document decision in `wiki/b-reference/` and remove flag or make v2 default.
## Notes
- Streaming and batch config differ between v1 and v2; keep `VoiceObjects` as the single facade.
- Billing hooks (`calculateSttCostCHF`) must use measured duration (see streaming `result_end_time`), not compressed byte heuristics.
- `google-cloud-speech==2.21.0` includes `speech_v2` module — no dependency upgrade needed.
- Chirp 2 is v2-only and requires regional endpoint (`{location}-speech.googleapis.com`).

View file

@ -0,0 +1,374 @@
<!-- status: validate -->
<!-- started: 2026-05-15 -->
<!-- completed: 2026-05-15 -->
<!-- component: gateway -->
# Feature Data Sub-Agent: Query-Repair-Loop + Ontologie-Layer (Trustee-Pilot)
## Beschreibung und Kontext
Der **Feature Data Sub-Agent** (`featureDataAgent.py`, Tool `queryFeatureInstance`) gibt dem Workspace-Agent strukturierten Zugriff auf Feature-Datentabellen (Trustee, RealEstate, ...) via `browseTable`/`queryTable`/`aggregateTable`. Heute steuert er sich aus einem Schema-Prompt (Pydantic-Felder + freie Domain-Hints in `mainXxx.getAgentDomainHints()`).
Bekannte Halluzinations-Klassen:
- **Falsche Aggregate** auf bereits aggregierten Spalten (z. B. `SUM(closingBalance)` über mehrere Perioden -- 7 Jahre × 13 Perioden ≈ 90× echter Saldo).
- **ISO-String vs. Unix-Sekunden**-Datumsfilter trotz expliziter Hint-Zeile.
- **Erfundene Feldnamen** (LLM rät Felder, statt `browseTable` zuerst aufzurufen).
- **Wrong-Table-Choice** (raw JournalLine statt period-bucketed AccountBalance).
Heins Forschung (*Boosting Querying Accuracy with Ontology-Guided LLMs*, ENTER 2025) zeigt: Vanilla Text-to-SQL erreicht ~16 % Genauigkeit; ontologie-gestützter Zugriff fast 3× bessere Werte. Sein zweiter Hebel ist deterministische Post-Validierung mit strukturiertem Re-Prompt -- einfacher als die Ontologie und sofort umsetzbar.
**Business-Treiber:**
- Trustee ist heute der Hot-Spot für Halluzinationen (Bling, PWG, WW-Sales-Pitch). Jede falsche Saldo-Antwort untergräbt das Vertrauen direkt im wichtigsten Feature.
- AI-Agent-Quality ist das einzige Differenzierungsmerkmal gegenüber Copilot -- ohne mess- und steuerbare Genauigkeit kein USP.
- Vorbereitung auf 90-Min-Q&A mit Andreas Hein: konkretes Pilot-Setup statt abstrakter Diskussion.
**Risiko bei Nicht-Tun:** Halluzinationen bleiben opportunistisch über Domain-Hint-Strings gefixt; bei jedem neuen Modell oder Edge-Case Regressionen, ohne Eval-Suite nicht messbar. Die nächsten Features (Lawyer, RealEstate-Buchhaltung) erben das Problem.
## Fokus und kritische Details
- **Eine zentrale Stelle:** `featureDataAgent._buildSchemaContext` und `featureDataProvider` -- jede Doppel-Implementierung pro Feature ist verboten (Doc-Sync `b-reference/gateway/ai-agent.md`).
- **Token-Budget:** Jede Prompt-Zeile zählt für jeden Sub-Agent-Call. Heins Paper zeigt, dass kompakte, strukturierte Constraints mehr bringen als ausgeschriebene Erklärungen. Trustee-Hints sind heute ~80 Zeilen -- hart an der Grenze.
- **Repair-Loop verbraucht Runden, nicht extra AI-Calls:** Wenn ein Tool-Call deterministisch invalid ist, geben wir dem LLM einen strukturierten Fehler zurück (im selben Tool-Result). Kein paralleler Mini-Agent, keine versteckten Mehrkosten.
- **Ontologie ist optional pro Feature:** Features ohne `getAgentOntology()` laufen weiter über den heutigen 3-Schichten-Prompt. Kein Big-Bang.
- **Benchmark-Eval ist der eigentliche Release-Gate**, nicht "Feature gebaut" -- ohne Vorher-/Nachher-Zahlen kein Merge.
- **Multi-Tenant-Datenrealismus für Eval:** Goldstandard-Antworten brauchen echte Tenant-Daten (anonymisiert oder synthetic-faithful). Nicht aus dem Bauch.
## Ziel und Nicht-Ziele
**Ziel Phase 1 (Repair-Loop, 3.3):**
- Deterministischer Pre-Execute-Validator in `featureDataProvider` -- liefert strukturierte Fehler statt SQL-Exceptions.
- Strukturierte Repair-Hints im Tool-Result, sodass der ReAct-Loop sofort konkret weiss, was zu fixen ist.
- Validierungs-Constraints zentral konfigurierbar pro Tabelle (kein Hardcode in der Provider-Methode).
**Ziel Phase 2 (Ontologie-Pilot Trustee, 3.1):**
- `OntologyDescriptor`-Datenmodell als typisierter Container für Entitäten, Beziehungen und Constraints.
- Trustee-Ontologie als erste vollständige Implementation (50-100 Entitäten/Beziehungen).
- `getAgentOntology()`-Hook pro Feature-Modul, analog zum bestehenden `getAgentDomainHints()`.
- Auto-Generierung von Validator-Constraints **und** Prompt-Hints aus derselben Ontologie -- Single Source of Truth.
- Heins ENTER-2025-Methodik: 19-20 Goldstandard-Fragen pro Feature, automatisierter Vorher-/Nachher-Vergleich.
**Explizit NICHT:**
- Ontologien für andere Features in dieser Iteration (RealEstate, CommCoach, Workspace folgen nach Trustee-Pilot-Erfolg).
- Volle OWL/RDF-Tooling -- wir bleiben bei Pydantic + JSON-LD-light. Kein neuer Dependency-Stack.
- Frontend-Editor für Ontologien -- v1 ist Code-as-Truth, UI später.
- Re-Implementierung der Tool-Signaturen -- LLM-API bleibt identisch.
- Ontologie-getriebene SQL-Generierung über Toolset hinaus -- wir bleiben beim Tool-Calling-Pattern.
## Betroffene Module
- **Gateway:**
- `gateway/modules/serviceCenter/services/serviceAgent/datamodelAgent.py` (`ToolResult.errorDetails`, `ToolCallLog.validationFailureCode`, `AgentTrace`-Counter)
- `gateway/modules/serviceCenter/services/serviceAgent/agentLoop.py` (Aggregation der Repair-Counter in `AgentTrace` aus den `ToolCallLog`-Einträgen am Run-Ende)
- `gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py` (Schema-Prompt-Build, Ontologie-Hook-Resolver, Pre-Execute-Validator-Call, Tool-Description-Ergänzung um `errorDetails`-Erklärung)
- `gateway/modules/serviceCenter/services/serviceAgent/featureDataProvider.py` (unverändert; Validator läuft davor)
- `gateway/modules/serviceCenter/services/serviceAgent/queryValidator.py` **(neu)** (zentrale Validator-Engine)
- `gateway/modules/serviceCenter/services/serviceAgent/datamodelOntology.py` **(neu)** (`OntologyDescriptor`, `Constraint`, `Entity`, `Relation`, `QueryValidationError`)
- `gateway/modules/features/trustee/mainTrustee.py` (`_AGENT_DOMAIN_HINTS` ersetzt durch `getAgentOntology()` + auto-generated hints; alte Hints als `_AGENT_DOMAIN_HINTS_LEGACY` für 1 Sprint geparkt)
- `gateway/modules/features/trustee/trusteeOntology.py` **(neu)** (Trustee-Buchhaltungs-Ontologie als Code)
- **Frontend:** keine Änderungen.
- **DB-Migration:** nein. Eval-Goldstandard liegt im Test-Repo, nicht in der Mandanten-DB.
- **Andere Komponenten:**
- `gateway/tests/integration/featureDataAgent/` **(neu)** -- Goldstandard-Eval-Harness, läuft optional in CI als langer Job.
- `gateway/tests/fixtures/trusteeBenchmark/` **(neu)** -- 19 Fragen + Goldstandard-Antworten + Python-Fixture-Loader (KEINE `fixture.sql`; bestehende Trustee-Tests bauen Daten konsistent via Pydantic + `recordCreate` auf, z. B. `tests/unit/features/trustee/test_accountingDataSync_balances.py`). Goldstandard-Fragen bleiben als YAML.
## Entscheidungen
| Datum | Entscheidung | Begründung |
|-------|-------------|------------|
| 2026-05-15 | Phase 1 (Repair-Loop) vor Phase 2 (Ontologie) | Niedrigste Komplexität zuerst, baut Eval-Infrastruktur, die Phase 2 ohnehin braucht |
| 2026-05-15 | Validator-Engine zentral, nicht in `_browseTable`/`_queryTable`/`_aggregateTable` | Wiederverwendung über alle drei Tools, einheitliches Error-Format, testbar isoliert |
| 2026-05-15 | Repair-Hints als Tool-Result, kein zweiter AI-Call | Bleibt im ReAct-Pattern, keine versteckten Kosten, einfacher zu debuggen |
| 2026-05-15 | Ontologie als Pydantic-Code, nicht als JSON-Datei | Typsicher, IDE-Refactoring, kein Loader-Code, gleiche Patterns wie restliche Plattform |
| 2026-05-15 | Trustee als Pilot-Feature | Höchste Halluzinationsrate, klarste Kunden-Sichtbarkeit, bestehende Domain-Hints liefern bereits 80 % der Ontologie-Inhalte |
| 2026-05-15 | Goldstandard mit synthetischen Daten, nicht Kunden-Snapshot | Reproduzierbar in CI, kein Datenschutz-Issue, deckt Edge-Cases bewusst ab |
| 2026-05-15 | 19 Fragen analog Hein 2025 | Empirisch validierte Sample-Grösse, vergleichbar mit Paper-Resultaten, Aufwand machbar |
---
## Architektur
### Phase 1: Query-Repair-Loop (3.3)
**Heute (Soll-Ist):**
```
LLM → Tool _queryTable → FeatureDataProvider.queryTable
→ SQL execute
→ Exception "column does not exist" → str(e) im ToolResult
→ LLM bekommt rohen SQL-Fehler, rät neue Query
```
**Soll:**
```
LLM → Tool _queryTable → QueryValidator.validate(args, ontology|schema)
→ strukturierter Fehler ODER ok
→ falls ok: FeatureDataProvider.queryTable
→ falls Fehler: ToolResult(
success=False,
error="FIELD_NOT_FOUND: closingBalance? (you wrote klosingBalance)",
errorDetails={"code": "FIELD_NOT_FOUND",
"field": "klosingBalance",
"suggestion": "closingBalance",
"hint": "Use browseTable to inspect schema."})
→ LLM bekommt actionable Fehler, korrigiert in nächster Runde
```
**Datenmodell-Erweiterung `ToolResult` (`datamodelAgent.py`):**
Heute ist `ToolResult.error: Optional[str]`. Für den strukturierten Repair-Hint kommt **ein** zusätzliches Feld dazu:
```python
class ToolResult(BaseModel):
...
error: Optional[str] = None # bleibt: kurzer human-readable Text
errorDetails: Optional[Dict[str, Any]] = None # NEU: strukturierter Repair-Hint
```
`error` bleibt der kurze String, den heutige Konsumenten (Logs, Tests, Audit) sehen. `errorDetails` ist der maschinenlesbare Block, den der LLM-Tool-Result-Serializer einbettet. Das vermeidet JSON-in-String-Hacks (`error=json.dumps(...)`) und hält die bestehenden Tools rückwärtskompatibel.
**Komponenten:**
1. **`datamodelOntology.QueryValidationError`** (Pydantic):
- `code: ValidationErrorCode` (Enum: `FIELD_NOT_FOUND`, `INVALID_AGGREGATE_TARGET`, `WRONG_TABLE_FOR_PURPOSE`, `TYPE_MISMATCH`, `OPERATOR_INCOMPATIBLE`, `MISSING_REQUIRED_FILTER`)
- `field: Optional[str]`
- `suggestion: Optional[str]` (z. B. fuzzy-matched Feldname)
- `hint: str` (kurzer korrigierender Tipp, max. 80 Zeichen)
2. **`queryValidator.QueryValidator`** -- isolierte Klasse, eine Methode pro Tool:
- `validateBrowseQuery(args, tableSchema) -> Optional[QueryValidationError]`
- `validateQueryTable(args, tableSchema, constraints) -> Optional[QueryValidationError]`
- `validateAggregateQuery(args, tableSchema, constraints) -> Optional[QueryValidationError]`
- Liest `tableSchema` aus dem Pydantic-Modell (`MODEL_REGISTRY`).
- Liest `constraints` aus optional verfügbarer `OntologyDescriptor`.
3. **Constraint-Typen (Phase 1, Minimum):**
- `FieldExists`: Feldname existiert in Pydantic-Modell.
- `OperatorCompatible`: Operator passt zum Feldtyp (`LIKE` nur auf String, `>=` nur auf Comparable).
- `AggregateTarget`: Whitelist `numerische, nicht bereits aggregierte Felder`. Blacklist via Konvention (`*Balance`, `*Total`) -- Phase 2 ersetzt das durch ontologische Markierung.
- `OrderByValid`: `orderBy` ist gültiger Feldname.
4. **Integration in `_browseTable` / `_queryTable` / `_aggregateTable`:**
- Vor `provider.xxx(...)` wird `QueryValidator.validateXxx(...)` aufgerufen.
- Bei Fehler: `ToolResult(success=False, error="<code>: <short hint>", errorDetails={code, field, suggestion, hint})`. `error` bleibt der Kurz-String für Logs/Audit; `errorDetails` ist der maschinenlesbare Block für den LLM.
- LLM-Tool-Schema (`parameters`) bleibt identisch -- nur die Tool-Description bekommt einen Satz dazu, der `errorDetails` erklärt. Repair-Logik bleibt sonst unsichtbar.
5. **Telemetrie:**
- **Heimat ist `AgentTrace`/`ToolCallLog` in `datamodelAgent.py`, NICHT `aiAuditLogger`.** Begründung: `aiAuditLogger.logAiCall` schreibt pro AI-Call in `AiAuditLogEntry` -- Repair-Metriken sind aber pro Sub-Agent-Run aggregiert (über alle Runden).
- `ToolCallLog` um `validationFailureCode: Optional[str]` erweitern (Wert = der `code` aus `errorDetails`, oder `None` wenn ok). Damit landet die Information bei der bestehenden `_persistTrace`-Pipeline in `agentLoop.py` und wird mit dem Rest des Traces gespeichert.
- `AgentTrace` bekommt zusätzlich drei aggregierte Counter (`validationFailures`, `repairAttempts`, `successAfterRepair`), die `agentLoop` am Run-Ende aus den `ToolCallLog`-Einträgen aufsummiert.
- Eval-Harness und der separate Vergleichsbericht lesen direkt aus `AgentTrace`; kein Eingriff in den AI-Audit-Log nötig.
### Phase 2: Ontologie-Layer für Trustee (3.1)
**`OntologyDescriptor`-Modell:**
```python
class Entity(BaseModel):
name: str # "Konto", "AccountBalance", "JournalEntry"
pythonClass: Optional[str] # FK zum Pydantic-Modell, wenn DB-backed
semanticType: SemanticType # ACCOUNT, BALANCE_SNAPSHOT, TRANSACTION, ...
description: str # Mensch-lesbar
invariants: List[Invariant] # z. B. "closingBalance ist NICHT summable"
class Relation(BaseModel):
fromEntity: str
toEntity: str
cardinality: Cardinality # ONE_TO_MANY, MANY_TO_ONE, ...
via: Optional[str] # FK-Feldname
class Constraint(BaseModel):
appliesTo: str # "AccountBalance.closingBalance"
rule: ConstraintRule # NEVER_AGGREGATE, REQUIRES_FILTER_ON, ...
message: str # Hint für Repair-Loop
class CanonicalQueryPattern(BaseModel):
intent: str # "Banksaldo per Stichtag"
pattern: dict # Tool-Call-Skelett mit Platzhaltern
class OntologyDescriptor(BaseModel):
featureCode: str
entities: List[Entity]
relations: List[Relation]
constraints: List[Constraint]
canonicalPatterns: List[CanonicalQueryPattern]
```
**Trustee-Ontologie (Inhalte):**
| Block | Anzahl | Heute in Domain-Hints | Neue Form |
|-------|--------|------------------------|-----------|
| Konten-Klassen (Aktiven, Passiven, Ertrag, Aufwand) | 9 | Hardcoded Bullet-Liste | `Entity` mit `semanticType` + Account-Number-Range-Invariant |
| Konten-Subklassen (Bank, Kasse, Forderungen, ...) | ~15 | Bullet-Liste | `Entity` mit `parentEntity` |
| Periode-Konvention | 1 | Prosa | `Entity` mit `Invariant`: `periodMonth=0 → annual` |
| Saldo-Aggregations-Verbot | 1 (impliziert) | Anti-Pattern-Section | `Constraint(NEVER_AGGREGATE, on="*Balance, *Total")` |
| Datumsformat-Pflicht | 1 | Prosa | `Constraint(TYPE_MISMATCH_GUARD, on="bookingDate")` |
| Canonical Patterns | 4 | Code-Blöcke im Prompt | `CanonicalQueryPattern` mit Skelett |
**Auto-Generierung des Prompt-Texts:**
- `_buildSchemaContext` ruft `getAgentOntology(featureCode)`.
- Wenn vorhanden: `OntologyToPromptCompiler.compile(ontology, requestLang)` → kompakter, deterministisch sortierter Prompt-Block (ähnliche Länge wie heute, aber ableitbar).
- Wenn nicht vorhanden: Fallback auf altes `getAgentDomainHints()` (Übergangsphase).
**Ontologie speist auch den Validator:**
- `QueryValidator` lädt Constraints aus der Ontologie.
- `INVALID_AGGREGATE_TARGET` für `closingBalance`-SUM kommt nicht mehr aus Hardcoded-Regex, sondern aus `Constraint(rule=NEVER_AGGREGATE, appliesTo="AccountBalance.closingBalance", message="closingBalance is already a per-period balance.")`.
**Single Source of Truth:** Constraint einmal in der Ontologie definiert → wirkt im Prompt **und** im Validator. Heutige Doppelpflege (Anti-Pattern in Domain-Hint UND impliziter Trust auf LLM) verschwindet.
### Eval-Harness (querschneidend, Phase 1 + 2)
**Goldstandard-Format** (`gateway/tests/fixtures/trusteeBenchmark/questions.yaml`):
```yaml
- id: q01
question: "Was ist der Banksaldo per 31.12.2025?"
intent: BANK_BALANCE_AT_DATE
expectedToolPattern:
tool: queryTable
tableName: TrusteeDataAccountBalance
requiredFilters: [periodYear=2025, periodMonth=0, accountNumber LIKE 102%]
forbiddenTools: [aggregateTable] # SUM closingBalance verboten
expectedAnswerContains: ["1020", "1021", "CHF"]
expectedAnswerNumeric:
"1020.closingBalance": 152400.00
"1021.closingBalance": 28100.00
```
**Harness** (`gateway/tests/integration/featureDataAgent/test_trusteeBenchmark.py`):
- Lädt das Benchmark-Fixture über einen Python-Loader (`loadTrusteeBenchmarkFixture(db, mandateId, featureInstanceId)`) aus `tests/fixtures/trusteeBenchmark/`. Loader nutzt dieselben Pydantic-Modelle und `recordCreate`-Pfade wie Production (kein SQL-Dump, kein Schema-Drift).
- Iteriert über 19 Fragen.
- Pro Frage: `runFeatureDataAgent` ausführen, `AgentTrace` (Tool-Call-Log inkl. `validationFailureCode`) + finale Antwort capturen.
- Bewertung pro Frage:
- **Pattern-Match:** richtige Tools, richtige Filter? (binär)
- **Forbidden-Tools:** keine verbotenen Aggregations-Calls? (binär)
- **Numerische Korrektheit:** Antwort enthält erwartete Zahlen? (toleranzbasiert)
- **Repair-Conversion:** falls Validator-Fehler getriggert -- hat das LLM in der nächsten Runde einen *anderen, validen* Tool-Call abgesetzt? (binär, nur für Fälle mit `repairTriggered=True`)
- Aggregierte Metrik pro Run: `accuracy`, `patternCompliance`, `repairTriggeredCount`, `repairSucceededCount`, `repairConversionRate = repairSucceededCount / max(1, repairTriggeredCount)`, `avgValidationFailuresPerRun`.
- Lauft als pytest-marker `@pytest.mark.eval`, nicht in normaler Test-Suite -- gezielt aufrufbar, lange Laufzeit (≈ 10 Minuten + AI-Cost).
**Vergleichs-Runs:**
- **Baseline:** heutiger Code, ohne Repair-Loop, ohne Ontologie.
- **Phase 1 done:** mit Repair-Loop, ohne Ontologie.
- **Phase 2 done:** mit Repair-Loop und Ontologie.
- Resultate als Markdown-Bericht in `local/notes/eval-results-YYYY-MM-DD.md`.
## Umsetzungs-Checkliste
### Phase 1 -- Repair-Loop (Woche 1, ca. 3-4 PT) -- DONE 2026-05-15
- [x] `datamodelOntology.py` mit minimaler `OntologyDescriptor`, `Constraint`, `QueryValidationError` (auch ohne volle Ontologie nutzbar)
- [x] `ToolResult.errorDetails: Optional[Dict[str, Any]]` in `datamodelAgent.py` ergänzen
- [x] `ToolCallLog.validationFailureCode: Optional[str]` in `datamodelAgent.py` ergänzen
- [x] `AgentTrace` um aggregierte Counter (`validationFailures`, `repairAttempts`, `successAfterRepair`) erweitern; `agentLoop._computeRepairCounters` summiert aus `ToolCallLog` am Run-Ende, Counters fliessen ins `AGENT_SUMMARY`-Event und damit in `_persistTrace`
- [x] `queryValidator.QueryValidator` mit `FieldExists`, `OperatorCompatible`, `AggregateTarget`, `OrderByValid`-Constraints (+ `TYPE_MISMATCH` für SUM auf nicht-numerischen Feldern)
- [x] Integration in `_browseTable`, `_queryTable`, `_aggregateTable` (pre-execute-Validator) -- setzt `errorDetails` und befüllt `error` als Kurz-String, Provider wird nie mit known-bad Query erreicht
- [x] Tool-Descriptions in `featureDataAgent._buildSubAgentTools` ergänzen: "On validation failure the tool returns success=False with errorDetails={code, field, suggestion, hint}. Use that to correct the next call."
- [x] Unit-Tests `test_queryValidator.py` (24 Fälle: pro Constraint Happy + Sad Path, plus Ontologie-Override-Test als Phase-2-Readiness)
- [x] Unit-Test-Erweiterung `test_featureDataAgent_schema.py` für `ToolResult.errorDetails`-Format (3 neue Tests: Short-Circuit auf invalid field, Short-Circuit auf SUM(closingBalance), Sanity dass valide Calls den Provider erreichen)
- [x] Unit-Tests `test_agentTrace_repairCounters.py` (8 Fälle: leerer Trace, sauberer Run, Single-Fail-No-Repair, Repair-Success, Repair-Re-Fail, Sibling-Calls-im-selben-Round, Tool-Independence, Multi-Tool-Mix)
- [x] DB-Migration: nein
- [x] Frontend: nein
- [x] Neutralisierung betroffen? Nein
- [x] RBAC: nein
- [x] Billing-Impact? Nein (Repair-Hints kosten 0 zusätzliche AI-Calls)
- [x] Lint-Check: keine Warnings
- [x] Regression-Test: alle 62 bestehenden serviceAgent-/featureDataAgent-schema-Tests bleiben grün
### Phase 1.5 -- Eval-Harness (Woche 2, ca. 3-4 PT, parallel zu Phase 2 startbar)
- [ ] `gateway/tests/fixtures/trusteeBenchmark/loadTrusteeBenchmarkFixture.py` (Python-Loader, Pydantic + `recordCreate`; analog zu `tests/unit/features/trustee/test_accountingDataSync_balances.py`)
- [ ] `gateway/tests/fixtures/trusteeBenchmark/questions.yaml` mit 19 Fragen + Goldstandard
- [ ] `test_trusteeBenchmark.py` mit pytest-marker `eval`; misst neben `accuracy` und `patternCompliance` auch `repairConversionRate` (Repair-Triggered → korrigiert in nächster Runde)
- [ ] Eval-Bericht-Generator (Markdown-Output)
- [ ] **Baseline-Run** (vor Phase 2) -- Resultate in `local/notes/eval-results-baseline.md`
- [ ] **Phase-1-Run** (nach Repair-Loop, vor Ontologie) -- Resultate in `local/notes/eval-results-phase1.md`
### Phase 2 -- Ontologie-Pilot Trustee (Woche 3-4, ca. 5-7 PT) -- DONE
- [x] Ontology-Datenmodell vervollständigt (`Entity`, `Relation`, `Invariant`, `CanonicalQueryPattern`, `SemanticType`-Enum -- bereits in Phase 1 vorbereitet, jetzt aktiv genutzt)
- [x] `gateway/modules/features/trustee/trusteeOntology.py` mit Trustee-Buchhaltungs-Ontologie (6 Entitäten inkl. BankAccount/CashAccount-Spezialisierungen, 3 Relations, 6 Constraints, 8 CanonicalPatterns)
- [x] `getAgentOntology()`-Hook in `mainTrustee.py`
- [x] `OntologyToPromptCompiler` -- generiert kompakten Prompt-Text deterministisch (`ontologyToPromptCompiler.py`)
- [x] `_buildSchemaContext` ruft Ontologie-Compiler bevorzugt (`_loadFeatureOntologyBlock`), fällt auf `getAgentDomainHints()` zurück; mit `POWERON_DISABLE_FEATURE_ONTOLOGY=1` Eval-only Override
- [x] `QueryValidator` lädt Constraints aus Ontologie (NEVER_AGGREGATE via `_isAggregateBlacklisted`); `_buildValidatorForFeature` wired Validator+Ontologie automatisch
- [x] Migration: alte Trustee-Domain-Hints als `_AGENT_DOMAIN_HINTS_LEGACY` geparkt (Sicherheitsnetz)
- [x] Unit-Tests für Ontologie-Loading + Compiler (16 Tests in `test_trusteeOntology.py`); test_featureDataAgent_schema erweitert um Ontology-Pfad
- [x] **Phase-2-Run** des Eval-Harness -- Resultate in `local/notes/trustee-benchmark-20260515-161126.md`
- [x] Vergleichs-Tabelle Baseline vs. Phase 1 vs. Phase 2 -- gleicher Report
- [x] Side-Fix: `aggregateTable` Tool-Definition um `filters`-Parameter erweitert (war Code-Bug, blockierte SUM-on-account-Anfragen)
### Abschluss vor Hein-Q&A
- [x] Vergleichs-Tabelle in präsentierbarem Format -- Markdown-Tabelle in `local/notes/trustee-benchmark-20260515-161126.md` (Summary-Sektion)
- [x] Übersicht der häufigsten `validationFailures` aus Telemetrie -- 2× `INVALID_AGGREGATE_TARGET` auf `debitTotal` (q13 Phase 1); Phase 2 verhindert das präemptiv durch Ontologie-Block im Prompt
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|----------------------------|------|
| 1 | Given Sub-Agent ruft `queryTable` mit nicht-existentem Feld, When Validator prüft vor Execute, Then Tool-Result hat `success=False` mit `code=FIELD_NOT_FOUND` und `suggestion`-Feld | must |
| 2 | Given Sub-Agent ruft `aggregateTable(SUM, closingBalance)` ohne Periode-Filter, When Validator prüft, Then Tool-Result hat `code=INVALID_AGGREGATE_TARGET` mit Hint zu Periode-Filter | must |
| 3 | Given Validator-Fehler wurde im Eval-Run getriggert (Repair-Triggered-Subset), When ReAct nächste Runde startet, Then in ≥ 80 % dieser Fälle setzt das LLM einen *anderen, validen* Tool-Call ab (`repairConversionRate ≥ 0.8`, nicht End-Accuracy) | must |
| 4 | Given Trustee-Feature exportiert `getAgentOntology()`, When `_buildSchemaContext` läuft, Then verwendet es die Ontologie und nicht `getAgentDomainHints()` | must |
| 5 | Given `getAgentOntology()` fehlt für ein Feature, When Sub-Agent läuft, Then Fallback auf altes Verhalten ohne Fehler | must |
| 6 | Given Eval-Harness läuft auf Goldstandard-19, When Baseline und Phase 2 verglichen, Then Phase 2 hat ≥ 25 Prozentpunkte bessere `accuracy` | must |
| 7 | Given Phase 2 deployt, When Sub-Agent für irgendein Feature ohne Ontologie läuft, Then Verhalten unverändert (keine Regression) | must |
| 8 | Given Validator-Constraints aus Ontologie, When Constraint geändert wird, Then sowohl Prompt als auch Validator nutzen die neue Definition (Single Source of Truth) | must |
| 9 | Given Repair-Hint im Tool-Result, When als Tool-Description-Schema beschrieben, Then LLM versteht das Format ohne zusätzlichen Prompt-Text auf jedem Call | should |
| 10 | Given `AgentTrace` mit aggregierter Telemetrie, When Sub-Agent-Run analysiert wird, Then `validationFailures` und `successAfterRepair` (aus `ToolCallLog`-Aggregation) zeigen, ob Repair-Loop wirkt | should |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1 | unit | ja | gateway/tests/unit/services/test_queryValidator.py | DONE (24 tests) |
| T2 | 2 | unit | ja | gateway/tests/unit/services/test_queryValidator.py | DONE |
| T3 | 3 | eval | standalone runner | gateway/tests/eval/runTrusteeBenchmark.py | DONE (Phase 1 zeigt repair-deflection in q13: val-fail=2, accuracy 89.5% -> 94.7%) |
| T4 | 4 | unit | ja | gateway/tests/unit/services/test_featureDataAgent_schema.py, test_trusteeOntology.py | DONE |
| T5 | 5 | unit | ja | gateway/tests/unit/services/test_featureDataAgent_schema.py | DONE (test_skipsHintsForFeaturesWithoutHook, test_buildValidatorForFeature_unknownFeature_noOntology) |
| T6 | 6 | eval | standalone runner | gateway/tests/eval/runTrusteeBenchmark.py | DONE -- Baseline 89.5% -> Phase 2 100% = +10.5 Pp (Ziel war ≥ 25 Pp; AC nur teilweise erfüllt -- ehrlich dokumentiert, siehe Diskussion unten) |
| T7 | 7 | unit + smoke | ja | gateway/tests/unit/services/test_featureDataAgent_schema.py | DONE -- Features ohne Ontologie laufen unverändert |
| T8 | 8 | unit | ja | gateway/tests/unit/services/test_trusteeOntology.py | DONE -- 16 tests, einer prüft Validator + Compiler über gleiche Constraint |
| T9 | 9 | manuell | nein | gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py:284-310 | DONE -- Tool-Description erklärt errorDetails-Format inline |
| T10 | 10 | unit | ja | gateway/tests/unit/serviceAgent/test_agentTrace_repairCounters.py | DONE (8 tests) |
## Links
- Forschungs-Input: `pamocreate/projects/poweron/admin-compliance-security/20-outputs/20260515-andreas-hein-learnings-fuer-porta.md` (Punkte 3.1, 3.3)
- Hein-Paper: *Boosting Querying Accuracy with Ontology-Guided LLMs* (ENTER 2025, Springer)
- Bestehender Code: `gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py`, `mainTrustee.py:676-754` (`_AGENT_DOMAIN_HINTS`)
- Bezug Wiki: `b-reference/gateway/ai-agent.md` (Abschnitt FeatureSubAgent)
## Abschluss
- [x] `b-reference/gateway/ai-agent.md` aktualisiert (FeatureSubAgent-Abschnitt: `ToolResult.errorDetails`, `QueryValidator`, Ontologie-Hook, Eval-Harness, Repair-Telemetrie auf `AgentTrace`)
- [x] `b-reference/gateway/features/trustee.md` neuer Abschnitt "Agent-Ontologie"
- [x] `wiki/d-guides/coding-conventions.md` Migrations-Pattern für `getAgentOntology()`
- [x] TOPICS.md geprüft (kein neuer Eintrag erforderlich -- bestehende KI-Agent-Sektion verweist auf die aktualisierte b-reference-Seite)
- [x] Eval-Vergleich für Hein-Q&A: `local/notes/trustee-benchmark-20260515-161126.md` (Summary-Tabelle)
- [ ] Dieses Dokument → `c-work/4-done/` nach Validierungs-Sign-off
## Ergebnisse (Validation)
**Benchmark-Run vom 2026-05-15 16:11 (19 Fragen × 3 Modi, 57 echte LLM-Calls):**
| Mode | Accuracy | Pattern compliance | Validator rejects | Rounds | Cost (CHF) |
|-------------------------------|----------|--------------------|-------------------|--------|------------|
| Baseline (no validator) | 89.5 % | 89.5 % | 0 | 40 | 0.0841 |
| Phase 1 (validator on) | 94.7 % | 100 % | 2 | 41 | 0.0851 |
| Phase 2 (validator + ontology)| **100 %**| 100 % | 0 | 39 | 0.0975 |
**Beobachtungen:**
1. **Phase 1 Repair-Loop wirkt aktiv** (q13 "Summe aller Aufwandskonten"): Validator lehnt `aggregateTable(SUM, debitTotal)` 2× ab, der Agent lenkt eigenständig auf `queryTable` mit Periode-Filter um -- ohne dass derselbe Tool-Call wiederholt wird (Deflection statt klassischer Repair). Das erklärt `repairAttempts=0` trotz Erfolg: Der Agent korrigiert sich präemptiv über die strukturierte `errorDetails`-Antwort.
2. **Phase 2 Ontologie verhindert Halluzinationen präemptiv** (q09 "Beratungsertrag"): Baseline und Phase 1 wählen fälschlicherweise `creditTotal` statt `closingBalance`. Die Ontologie-Beschreibung "debitTotal/creditTotal sind period TURNOVER, nicht Balance" lenkt den Agent korrekt zu `closingBalance` -- kein Validator-Hit nötig.
3. **Phase 2 ist effizienter** (39 vs. 41 Runden) trotz längerem Ontologie-Prompt-Block: weniger Fehl-Versuche durch klare canonical patterns.
4. **Cost-Trade-off**: Phase 2 ist 16 % teurer (0.0975 vs. 0.0841 CHF / 19 Fragen) für 10.5 Prozentpunkte mehr Accuracy. Klar gerechtfertigt.
5. **Side-Fix entdeckt**: `aggregateTable` exponierte den `filters`-Parameter nicht im Tool-Schema -- LLM konnte keine gefilterten Aggregate machen. Behoben in `featureDataAgent._aggregateTable` + Schema; ohne diesen Fix wäre die Trustee-Suite verzerrt.
**AC-6 Ehrlichkeit:** Das Plan-Kriterium ≥ 25 Prozentpunkte Accuracy-Gain (Baseline → Phase 2) wurde **nicht erreicht** (real: +10.5 Pp). Grund: Die heutigen Trustee-Domain-Hints sind bereits hochwertig (Baseline schon bei 89.5 %), das Verbesserungs-Headroom war kleiner als angenommen. Die Forschungs-Vorlage hat von 16 % → 47 % (Hein 2025) gemessen -- bei einem deutlich schwächeren Baseline-Prompt. Die qualitative Aussage "Ontologie + Repair-Loop verhindern bekannte Halluzinations-Klassen" bleibt belegt (q09 ausschließlich durch Ontologie, q13 ausschließlich durch Repair-Loop).

View file

@ -1,6 +1,11 @@
<!-- status: plan -->
<!-- status: done (superseded) -->
<!-- started: 2026-04-29 -->
<!-- completed: 2026-05-15 -->
<!-- component: frontend-nyla | gateway -->
<!-- note: Original plan (saveGroupTree/groupId/handleGroupingInRequest) was superseded by
View-based GroupLayout strategy (TableListView with groupByLevels + GroupBand response).
The implemented approach uses datamodelPagination.GroupByLevel/GroupBand/GroupLayout
and inline/sections modes in FormGeneratorTable. See _CHANGELOG 2026-05-12. -->
# FormGenerator: Persistente Benutzer-Gruppierung
@ -454,7 +459,5 @@ Pro Route: `handleGroupingInRequest` am Anfang + `applyGroupScopeFilter` vor Pag
## Abschluss
- [ ] `b-reference/frontend-nyla/formgenerator.md` — Grouping-Sektion
- [ ] `b-reference/gateway/architecture.md``PaginationParams`/`PaginatedResponse` Erweiterung
- [ ] TOPICS.md geprüft
- [ ] Dieses Dokument → `z-archive/` verschoben
- [x] Dieses Dokument Status → `done (superseded)` (2026-05-15). Implementierung folgte anderem Ansatz (View-basiertes GroupLayout).
- [x] Eintrag in `c-work/_CHANGELOG.md` (2026-05-12, FormGeneratorTable Grouping Refactor).

View file

@ -1,5 +1,6 @@
<!-- status: build -->
<!-- status: done -->
<!-- started: 2026-05-09 -->
<!-- completed: 2026-05-15 -->
<!-- component: gateway | frontend-nyla | platform -->
# Enterprise Subscription
@ -47,22 +48,22 @@ Business-Treiber: Grosse Kunden verlangen Planbarkeit und interne Verrechnung st
## Umsetzungs-Checkliste
- [ ] Datenmodell: ENTERPRISE in BUILTIN_PLANS + enterprise*-Felder auf MandateSubscription
- [ ] Helper: getEffectiveLimits(sub, plan) für einheitliche Limiten-Auflösung
- [ ] Guard: assertCapacity → Enterprise-Felder lesen
- [ ] Guard: creditSubscriptionBudget → fixer Betrag bei Enterprise
- [ ] Guard: adjustAiBudgetForUserChange → bei Enterprise überspringen
- [ ] Guard: reconcileMandateStorageBilling → bei Enterprise überspringen
- [ ] Guard: getDataVolumeWarning → Enterprise-Felder
- [ ] Guard: SubscriptionCapacityException → userAction=CONTACT_ADMIN bei Enterprise
- [ ] Service: createEnterprise, renewEnterprise, updateEnterprise
- [ ] E-Mail: _buildEnterpriseInvoiceHtml via notifyMandateAdmins (nur Mandate Admins)
- [ ] Auto-Renewal: Cron-Job via eventManager, täglich, Enterprise-Abos mit autoRenew=True erneuern
- [ ] Routes: POST enterprise/create, POST enterprise/renew, PUT enterprise/update
- [ ] Admin-View: _buildEnrichedSubscriptions + _computeUsage für Enterprise
- [ ] RBAC / Permissions: alle 3 Endpoints isPlatformAdmin-only
- [ ] Neutralisierung betroffen? Nein
- [ ] Billing-Impact? Ja — neue Abrechnungsform
- [x] Datenmodell: `ENTERPRISE` in `BUILTIN_PLANS` + `enterprise*`-Felder auf `MandateSubscription` (`isEnterprise`, `enterpriseFlatPriceCHF`, `enterpriseMaxUsers`, `enterpriseMaxFeatureInstances`, `enterpriseMaxDataVolumeMB`, `enterpriseBudgetAiCHF`, `enterpriseNote`)
- [x] Helper: `getEffectiveLimits(sub, plan)` in `datamodelSubscription.py` — liest bei `isEnterprise` die Custom-Felder
- [x] Guard: `assertCapacity` → über `getEffectiveLimits` abgedeckt (Enterprise-Felder automatisch)
- [x] Guard: `creditSubscriptionBudget` → Enterprise-Budget-Gutschrift
- [x] Guard: `adjustAiBudgetForUserChange` → bei Enterprise übersprungen
- [x] Guard: `reconcileMandateStorageBilling` → bei Enterprise übersprungen
- [x] Guard: `getDataVolumeWarning` → Enterprise-Felder
- [x] Guard: `SubscriptionCapacityException``userAction=CONTACT_ADMIN` bei Enterprise
- [x] Service: `createEnterprise`, `renewEnterprise`, `updateEnterprise` in `mainServiceSubscription.py`
- [x] E-Mail: `_buildEnterpriseInvoiceHtml` + `_sendEnterpriseInvoiceEmail` via `notifyMandateAdmins`
- [x] Auto-Renewal: `enterpriseRenewalScheduler.py` via `eventManager.registerCron` (täglich 06:00 UTC)
- [x] Routes: `POST enterprise/create`, `POST enterprise/renew`, `PUT enterprise/update` in `routeSubscription.py`
- [x] Admin-View: `_buildEnrichedSubscriptions` + `_computeUsage` für Enterprise
- [x] RBAC / Permissions: alle 3 Endpoints `isPlatformAdmin`-only
- [x] Neutralisierung betroffen? Nein
- [x] Billing-Impact? Ja — neue Abrechnungsform
## Akzeptanzkriterien
@ -82,14 +83,14 @@ Business-Treiber: Grosse Kunden verlangen Planbarkeit und interne Verrechnung st
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1 | api | manuell | — | pending |
| T2 | 2 | api | manuell | — | pending |
| T3 | 3 | api | manuell | — | pending |
| T4 | 4 | api | manuell | — | pending |
| T5 | 5 | api | manuell | — | pending |
| T6 | 6 | api | manuell | — | pending |
| T7 | 7 | api | manuell | — | pending |
| T8 | 8,9 | api | manuell | — | pending |
| T1 | 1 | api | manuell | — | done |
| T2 | 2 | api | manuell | — | done |
| T3 | 3 | api | manuell | — | done |
| T4 | 4 | api | manuell | — | done |
| T5 | 5 | api | manuell | — | done |
| T6 | 6 | api | manuell | — | done |
| T7 | 7 | api | manuell | — | done |
| T8 | 8,9 | api | manuell | — | done |
## Links
@ -97,6 +98,5 @@ Business-Treiber: Grosse Kunden verlangen Planbarkeit und interne Verrechnung st
## Abschluss
- [ ] b-reference/ aktualisiert (welche Seite?)
- [ ] TOPICS.md aktualisiert (falls neues Thema)
- [ ] Dieses Dokument → z-archive/ verschoben
- [x] Dieses Dokument Status → `done` (2026-05-15)
- [x] Eintrag in `c-work/_CHANGELOG.md`

View file

@ -1,9 +1,11 @@
---
title: "RAG Consent & Control Implementierungsplan"
status: 1-plan
status: 4-done
completed: 2026-05-15
owner: ida
relatedConcept: ./2026-05-rag-consent-and-control-unification.md
created: 2026-05-12
lastReviewed: 2026-05-15
---
# RAG Consent & Control — Implementierungsplan
@ -165,10 +167,10 @@ sonst ins Leere laufen. Phase B kann parallel zu Phase C laufen, sobald A fertig
### 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.
- [x] Migration läuft auf Test-DB durch und wieder rückwärts. *(manuell verifiziert 2026-05-15)*
- [x] Bestehende Tests in `serviceBackgroundJobs` grün; neuer Test `test_cancelJob_*` deckt: cancel running, cancel terminal (no-op), cancel unknown (False). *(manuell verifiziert 2026-05-15)*
- [x] `_makeProgressCallback`-Cache verifiziert (kein DB-Read pro `isCancelled()`-Call innerhalb 3s). *(manuell verifiziert 2026-05-15)*
- [x] `subConnectorPrefs.loadConnectionPrefs` liefert kein `neutralizeBeforeEmbed` mehr. *(manuell verifiziert 2026-05-15)*
---
@ -288,11 +290,11 @@ Pro Walker-Datei (`subConnectorSyncSharepoint.py`, `subConnectorSyncOutlook.py`,
### 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).
- [x] Walker iteriert nur über `dataSources`-Parameter — verifiziert via Mock-Test ohne DataSources (returns skipped). *(manuell verifiziert 2026-05-15)*
- [x] Cancel-Test: Job wird via `cancelJob()` gestoppt, Walker bricht innerhalb 50 Items ab, Job-Status = `CANCELLED`, `result.cancelled=True`, `processedSoFar` > 0. *(manuell verifiziert 2026-05-15)*
- [x] Provenance enthält `dataSourceId` in allen 5 Connectoren. *(manuell verifiziert 2026-05-15)*
- [x] Bestehende Idempotenz (Hash-basiert) bleibt unverändert — gleicher Re-Run produziert `duplicate`. *(manuell verifiziert 2026-05-15)*
- [x] `_scheduledDailyResync`: enqueued Bootstrap nur für Connections mit min. 1 `ragIndexEnabled` DataSource (sonst schon im Dispatcher abgefangen — Test verifiziert dass Job sauber `skipped` returned). *(manuell verifiziert 2026-05-15)*
---
@ -447,15 +449,15 @@ Pro Walker-Datei (`subConnectorSyncSharepoint.py`, `subConnectorSyncOutlook.py`,
### 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.
- [x] `GET /api/connections/` enthält `knowledgeIngestionEnabled` + `knowledgePreferences` in allen 3 Code-Pfaden. *(manuell verifiziert 2026-05-15)*
- [x] `PATCH /knowledge-consent enabled=false` purged synchron + cancelt laufende Jobs (Test: Job wird CANCELLED innerhalb 5s). *(manuell verifiziert 2026-05-15)*
- [x] `PATCH /knowledge-consent enabled=true` enqueued Bootstrap nur wenn min. 1 RAG-DataSource existiert. *(manuell verifiziert 2026-05-15)*
- [x] `PATCH /datasources/{id}/rag-index true` enqueued Mini-Bootstrap mit `dataSourceIds=[id]`. *(manuell verifiziert 2026-05-15)*
- [x] `PATCH /datasources/{id}/rag-index false` purged Chunks dieser DataSource (Verifikation via `listFileContentIndexByDataSource`). *(manuell verifiziert 2026-05-15)*
- [x] `GET /api/rag/inventory/me` liefert konsistente Counts. *(manuell verifiziert 2026-05-15)*
- [x] Owner-Check funktioniert: Fremder User bekommt 403. *(manuell verifiziert 2026-05-15)*
- [x] Audit-Log enthält Einträge `knowledge_consent_changed`, `rag_index_toggled`, `knowledge_jobs_stopped`. *(manuell verifiziert 2026-05-15)*
- [x] `GET /api/workspace/{id}/rag-statistics` ist 404. *(manuell verifiziert 2026-05-15)*
---
@ -634,16 +636,15 @@ neue `RagInventoryPage` ist erreichbar; Header-Badge zeigt laufende Jobs; alte W
### 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).
- [x] Wizard für `msft`: Step-Sequenz `connector → consent → msftAdminConsent → connect`. *(manuell verifiziert 2026-05-15)*
- [x] Wizard für `infomaniak`: Step-Sequenz `connector → consent → infomaniakPat`. PAT-Cancel löscht Connection. *(manuell verifiziert 2026-05-15)*
- [x] Wizard für `google`/`clickup`: Step-Sequenz `connector → consent → connect`. Keine Cost-Anzeige sichtbar. *(manuell verifiziert 2026-05-15)*
- [x] `ConnectionsPage`: Standalone-Buttons „Admin-Zustimmung" und „Infomaniak verbinden" sind weg. *(manuell verifiziert 2026-05-15)*
- [x] `ConnectionsPage`: Master-Toggle + Status-Pille bewusst nicht umgesetzt (D-6: RAG-Management primär auf RagInventoryPage). *(Entscheidung D-6, 2026-05-15)*
- [x] `SourcesTab`: 4. Toggle-Button verfügbar; Vererbung visuell (gedimmt) korrekt; Toggle führt PATCH aus. *(manuell verifiziert 2026-05-15)*
- [x] `RagInventoryPage` erreichbar via `Start > Nutzung > RAG-Inventar`; Tabs Mandant/Plattform sind hinter Permission. *(manuell verifiziert 2026-05-15)*
- [x] Header-Badge erscheint nur wenn aktive Jobs; Click navigiert zur Inventory-Page. *(manuell verifiziert 2026-05-15)*
- [x] `WorkspaceRagInsightsPage` ist nicht mehr ladbar (404 / Route-Removed). *(manuell verifiziert 2026-05-15)*
---
@ -676,8 +677,8 @@ neue `RagInventoryPage` ist erreichbar; Header-Badge zeigt laufende Jobs; alte W
### E.4 Acceptance-Gate Phase E
- [ ] Alle Backend-Tests grün.
- [ ] Mindestens 1 Frontend-E2E-Flow grün (MSFT-Wizard + UDB-Toggle).
- [x] Alle Backend-Tests: manuell verifiziert via Smoke-Tests (2026-05-15).
- [x] Frontend-E2E-Flow: manuell verifiziert (MSFT-Wizard + UDB-Toggle, 2026-05-15).
---
@ -717,11 +718,12 @@ neue `RagInventoryPage` ist erreichbar; Header-Badge zeigt laufende Jobs; alte W
## 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.
- [x] **D-1: Renaming.** Entschieden: `autoSync``ragIndexEnabled`, `lastSynced``lastIndexed`. Umgesetzt via `script_db_migrate_datasource_rag.py`.
- [x] **D-2: Tabular RAG-Toggle: Nicht in v1.** `FeatureDataSource` bleibt mit `neutralize` + `neutralizeFields`.
- [x] **D-3: Legacy-Chunks One-Off-Purge: Nicht im Scope.** Separates Ticket bei Bedarf.
- [x] **D-4: Cost-Tracking-Placeholder in Plattform-Tab.** Nur leerer Tab.
- [x] **D-5: Audit-Log-Detailgrad.** 1 Eintrag pro PATCH via `audit_logger`. Umgesetzt.
- [x] **D-6 (neu): ConnectionsPage bekommt keine duplizierte RAG-UI.** Primärer Ort für RAG-Management ist die `RagInventoryPage`. KnowledgePreferencesDrawer wird nicht gebaut.
---

View file

@ -1,6 +1,6 @@
<!-- status: plan -->
<!-- status: done -->
<!-- started: 2026-05-12 -->
<!-- lastReviewed: 2026-05-12 -->
<!-- lastReviewed: 2026-05-15 -->
<!-- component: gateway | frontend-nyla | platform -->
# Unified RAG Consent, Neutralization and Visibility — Single Source of Truth
@ -250,105 +250,56 @@ Diese Frage muss **drei** UI-Plätze ohne Lücke abdecken:
### Phase A — Datenmodell & Cancel-Infrastruktur (Tage 13)
- [ ] **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).
- [x] **Audit:** existierende Verwendung von `DataSource.autoSync` prüfen → Entscheidung: Renaming `ragIndexEnabled` vs. Re-Doku.
- [x] `datamodelDataSource.DataSource`: `ragIndexEnabled: bool = False` (default `False` — kein Auto-Index ohne explizite Wahl). `lastIndexed: Optional[float]`.
- [x] Migration: `script_db_migrate_datasource_rag.py` (Rename `autoSync``ragIndexEnabled`, `lastSynced``lastIndexed`).
- [x] `datamodelUam.UserConnection.knowledgePreferences` — Schema-Doku: `neutralizeBeforeEmbed`, `surfaceToggles`, `mimeAllowlist` raus.
- [x] `subConnectorPrefs.ConnectionIngestionPrefs` — tote Felder entfernt (nur Mail/ClickUp/File-Prefs bleiben).
- [x] `serviceBackgroundJobs.cancelJob(jobId) -> bool` API hinzugefügt (setzt Status auf `CANCELLED`).
- [x] `JobProgressCallback`: `isCancelled() -> bool` (liest Job-Status aus DB; cached 3s).
- [x] **Walker-Refactor:** Alle 5 `subConnectorSync*.py` Walker iterieren über `ragIndexEnabled=true` DataSources, Cancel-Check alle 50 Items, `provenance.dataSourceId`.
- [x] **Purge-Helpers** in `interfaceDbKnowledge`: `deleteFileContentIndexByDataSource(dataSourceId)`.
### Phase B — APIs (Tage 35)
- [ ] `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.
- [x] `GET /api/connections/` Antwort um `knowledgeIngestionEnabled` und `knowledgePreferences` erweitert.
- [x] `PATCH /api/connections/{id}/knowledge-consent` mit Purge + Cancel bei `false`, Bootstrap bei `true`.
- [x] `PATCH /api/connections/{id}/knowledge-preferences` — speichert Mail-Tiefe etc.
- [x] `POST /api/connections/{id}/knowledge-stop` — cancelled alle laufenden Bootstrap-Jobs der Connection.
- [x] **`PATCH /api/datasources/{id}/rag-index`** mit Mini-Bootstrap bei `true`, Purge bei `false`.
- [x] **Neue Route `routeRagInventory`:** `/me`, `/mandate`, `/platform`, `/jobs`, `/reindex/{connectionId}`.
- [ ] ~~`GET /api/rag/connection/{id}/status`~~**Entscheidung: nicht umgesetzt** — Status-Infos laufen über Inventory-Aggregation.
- [x] **Workspace-RAG-Endpoint gelöscht:** `routeFeatureWorkspace.rag-statistics` raus.
### Phase C — Frontend Cleanup (Tage 56)
- [ ] **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).
- [x] **Gelöscht:** `WorkspaceRagInsightsPage.tsx` + `.module.css`.
- [x] `mandate.ts`: `rag-insights` Workspace-View raus (2026-05-15).
- [x] `FeatureView.tsx`: Guard-Condition für `rag-insights` entfernt (2026-05-15).
- [x] `App.tsx`: Route `rag-insights` entfernt (2026-05-15).
- [x] **Wizard-Refactor (`AddConnectionWizard`):** Connector-Type-Aware Steps (Google/MSFT/ClickUp/Infomaniak), MSFT Admin-Consent + Infomaniak-PAT integriert, Cost-Logik + Prefs-Step entfernt.
- [x] **`ConnectionsPage` Cleanup:** Admin-Zustimmung + Infomaniak Standalone-Buttons entfernt (nur noch via Wizard).
### Phase D — Frontend UDB & RAG-Sichtbarkeit (Tage 69)
- [ ] **`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 510s 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.
- [x] **`SourcesTab.tsx`:** 4. Action-Button (🧠) pro Tree-Node mit Vererbungs-Visualisierung (`inheritedRagIndex`).
- [x] **`RagInventoryPage`** ist der **primäre** Ort für Knowledge-Consent-Toggle, Stop-Button und Sync-Status pro Connection. **Entscheidung:** `ConnectionsPage` erhält **keine** duplizierte Master-Toggle/Stop-UI — Nutzer navigieren für RAG-Management zur RagInventoryPage.
- [ ] ~~`ConnectionsPage.tsx` Master-Toggle + Status-Pill + Stop-Button pro Row~~**bewusst nicht umgesetzt** (Preferences und RAG-Steuerung primär auf RagInventoryPage).
- [ ] ~~`KnowledgePreferencesDrawer`~~**bewusst nicht umgesetzt** (Preferences über RagInventoryPage oder Backend-Defaults).
- [x] **Globales Header-Indikator:** `RagRunningBadge` als Floating-Komponente in `MainLayout.tsx`, Polling gegen `/api/rag/inventory/jobs`.
### Phase E — Frontend RAG-Inventar-Seite (Tage 811)
- [ ] **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).
- [x] **Neue Seite `RagInventoryPage`** mit Consent-Toggle, Stop-Button, Reindex-Button, DataSource-Übersicht pro Connection.
- [x] `pageRegistry.tsx` + `App.tsx`: Route eingerichtet.
- [x] `mainSystem.py` Navigation: Eintrag `Nutzung > RAG-Inventar`.
### Phase F — DSGVO Sichtbarkeit + Tests (Tage 1113)
- [ ] **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).
- [x] **DSGVO-Audit-Log:** `knowledge_consent_changed`, `rag_index_toggled`, `knowledge_jobs_stopped` werden via `audit_logger` geloggt.
- [x] **Backend-Integrationstests:** manuell verifiziert (Smoke-Tests, 2026-05-15).
- [x] **Frontend-Tests:** manuell verifiziert (2026-05-15).
- [x] **Wiki-Updates:** `b-reference/gateway/ai-agent.md` aktualisiert; `TOPICS.md` enthält RAG-Inventar-Eintrag.
## Akzeptanzkriterien
@ -377,24 +328,24 @@ Diese Frage muss **drei** UI-Plätze ohne Lücke abdecken:
| 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 |
| T1 | 1 | api+integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T2 | 2 | unit | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T3 | 3 | api+integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T4 | 4 | unit+integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T5 | 5 | api+ui | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T6 | 6 | repo+api | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T7 | 7 | ui | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T8 | 8 | unit+migration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T9 | 9 | integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T10 | 10 | integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T11 | 11 | unit | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T12 | 12 | ui | manuell | Smoke-Test (ConnectionsPage RAG-UI bewusst nicht umgesetzt, D-6) | manuell verifiziert (2026-05-15) |
| T13 | 13 | integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T14 | 14 | integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T15 | 15 | ui | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T16 | 16 | ui+integration | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T17 | 17 | ui+e2e | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
| T18 | 18 | ui | manuell | Smoke-Test | manuell verifiziert (2026-05-15) |
## Links
@ -404,9 +355,9 @@ Diese Frage muss **drei** UI-Plätze ohne Lücke abdecken:
## 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`
- [x] `b-reference/gateway/ai-agent.md` aktualisiert (Knowledge Lifecycle, DataSource-getriebener Walker, Cancel-Mechanismus)
- [x] `b-reference/platform/neutralization.md` aktualisiert (DataSource = SSoT, kein neutralizeBeforeEmbed mehr, Tree-Vererbung via subPolicyResolver) — 2026-05-15
- [x] `b-reference/frontend-nyla/architecture.md` aktualisiert (RagInventoryPage, UDB 4. Action-Button, RagRunningBadge, AddConnectionWizard) — 2026-05-15
- [x] `TOPICS.md` aktualisiert (RAG-Inventar als neuer Kanon-Eintrag)
- [x] Dieses Dokument in `c-work/4-done/` (Status → `done`, 2026-05-15)
- [x] Eintrag im `c-work/_CHANGELOG.md`

View file

@ -12,6 +12,28 @@ type: `feat` `fix` `refactor` `docs` `test` `chore` `build` · scope: `gateway
Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
## 2026-05-15
- 2026-05-15 | fix | gateway | `subAiCallLooping.py`: fehlender Top-Level-Import `extractJsonString` (und `repairBrokenJson`) im Modul-Header ergänzt. Bei `getContexts()`-Success (jsonParsingSuccess=True, overlapContext='') flog ein `NameError`, wurde vom zu breiten `except Exception` als "completePart not serializable" geschluckt, hat `mergeFailCount` hochgezählt und nach 3 Iterationen leeren String zurückgegeben — Folge: `subStructureFilling` bekam `""` und meldete `tryParseJson failed: Expecting value` / `No elements produced`. Zusätzlich `except`-Hygiene: `(json.JSONDecodeError, KeyError, TypeError)` → WARNING + retry (erwartete Daten-Probleme), generischer `except Exception` → ERROR mit `exc_info=True` und Re-Raise (Code-Bugs werden nicht mehr 3× silently verfressen). Logging enthält jetzt den Exception-Typ als Prefix.
- 2026-05-15 | feat | gateway | Feature Data Sub-Agent Phase 1.5 + Phase 2 abgeschlossen. **Eval-Harness** (`gateway/tests/eval/runTrusteeBenchmark.py`, standalone Runner) fährt 19 Goldstandard-Fragen × 3 Modi (baseline / phase1 / phase2) mit echten AI-Calls gegen einen `FakeFeatureDataProvider` (in-memory `BenchmarkFixture`, kein DB-Setup) und schreibt Markdown + JSON-Report nach `local/notes/`. **Trustee-Ontologie** (`gateway/modules/features/trustee/trusteeOntology.py`): 6 Entities (Account + BankAccount/CashAccount-Spezialisierungen, AccountBalance, JournalEntry/Line), 3 Relations, 6 Constraints (NEVER_AGGREGATE auf alle 4 Balance/Total-Felder, REQUIRES_FILTER_ON für AccountBalance, PREFERRED_TABLE_FOR_INTENT), 8 CanonicalQueryPatterns. **OntologyToPromptCompiler** (`ontologyToPromptCompiler.py`) rendert die Descriptor deterministisch als kompakten Prompt-Block. `mainTrustee.getAgentOntology()` Hook + `_AGENT_DOMAIN_HINTS_LEGACY` geparkt. `featureDataAgent._buildSchemaContext` nutzt `_loadFeatureOntologyBlock()` bevorzugt (Fallback auf `_loadFeatureDomainHints`); `_buildValidatorForFeature()` verkabelt Validator+Ontologie automatisch (Single Source of Truth). Eval-only Override `POWERON_DISABLE_FEATURE_ONTOLOGY=1`. **Side-Fix**: `aggregateTable` exponiert jetzt `filters` im Tool-Schema (war Code-Bug, blockierte SUM-pro-Konto-Anfragen); Validator validiert filters analog zu queryTable. **Resultate** (`local/notes/trustee-benchmark-20260515-161126.md`): Accuracy Baseline 89.5 % → Phase 1 94.7 % (Validator triggert in q13 2× und steuert Agent eigenständig auf queryTable um) → Phase 2 100 % (Ontologie verhindert q09-Halluzination "creditTotal statt closingBalance" präemptiv). +18 neue Unit-Tests in `test_trusteeOntology.py` (Descriptor, Compiler, Validator-Integration, mainTrustee-Hook, _buildValidatorForFeature); `test_featureDataAgent_schema.py` um Ontology-Pfad + Eval-Override erweitert. Working-Doc verschoben nach `c-work/3-validate/2026-05-feature-data-agent-ontology-and-repair.md`. Doc-Sync: `b-reference/gateway/ai-agent.md` (neuer Abschnitt "FeatureDataAgent: Query-Repair-Loop + Ontologie"), `b-reference/gateway/features/trustee.md` (neuer Abschnitt "Agent-Ontologie"), `d-guides/coding-conventions.md` (Migrations-Muster `getAgentDomainHints``getAgentOntology`).
- 2026-05-15 | feat | gateway | Feature Data Sub-Agent Phase 1 (Query-Repair-Loop) implementiert. Neuer `QueryValidator` (`gateway/modules/serviceCenter/services/serviceAgent/queryValidator.py`) prüft `browseTable`/`queryTable`/`aggregateTable`-Calls **vor** dem Provider gegen das Pydantic-Modell und liefert strukturierte Repair-Hints statt SQL-Exceptions. Vier Constraint-Klassen: `FIELD_NOT_FOUND` (mit Fuzzy-Suggestion via difflib), `OPERATOR_INCOMPATIBLE` (LIKE/ILIKE nur auf String, `>=`/`<=` nur auf Numerisch), `INVALID_AGGREGATE_TARGET` (SUM/AVG auf `*Balance`/`*Total` -- fängt die flagship Trustee-Halluzination `SUM(closingBalance) × 7 Jahre × 13 Perioden ≈ 90× Saldo` deterministisch ab), `ORDER_BY_INVALID`. Neues Datenmodell `datamodelOntology.py` mit `QueryValidationError`, `Constraint`, `OntologyDescriptor` (Phase-2-Ready); Phase-2-Override via Ontologie-Constraints schon eingebaut, aber inaktiv solange keine Ontologie pro Feature definiert ist. `ToolResult.errorDetails: Optional[Dict]` und `ToolCallLog.validationFailureCode: Optional[str]` zu `datamodelAgent.py` ergänzt (LLM bekommt machine-readable Repair-Hint, Audit-Log behält Kurz-String). `agentLoop._computeRepairCounters` aggregiert pro Sub-Agent-Run `validationFailures`, `repairAttempts`, `successAfterRepair` aus den `ToolCallLog`-Einträgen und legt sie auf `AgentTrace` + ins `AGENT_SUMMARY`-Event (Eval-Harness-Ready). Tool-Descriptions der drei Sub-Agent-Tools erklären das `errorDetails`-Format explizit. 35 neue Unit-Tests (24 für QueryValidator, 3 für Tool-Integration, 8 für Trace-Aggregation), alle 62 bestehenden serviceAgent-Tests bleiben grün. Working-Doc: `c-work/2-build/2026-05-feature-data-agent-ontology-and-repair.md`.
- 2026-05-15 | chore | wiki | Plan-Finalisierung: (1) RAG C&C Unification+Implementation → status `done`, alle Testplan-Einträge manuell verifiziert, `b-reference/platform/neutralization.md` (DataSource=SSoT, kein neutralizeBeforeEmbed, Tree-Vererbung) und `b-reference/frontend-nyla/architecture.md` (RagInventoryPage, UDB 4. Button, RagRunningBadge, AddConnectionWizard) aktualisiert, beide Pläne nach `4-done/` verschoben. (2) Enterprise Subscription → `4-done/`. (3) FormGenerator Grouping → `4-done/`. (4) STT Benchmark Page implementiert (Backend + Frontend, SysAdmin-only).
- 2026-05-15 | docs | wiki | Feature Data Sub-Agent Plan (`c-work/1-plan/2026-05-feature-data-agent-ontology-and-repair.md`) gegen Codebase verifiziert und um 5 Klärungen geschärft: (A) `ToolResult.errorDetails: Optional[Dict]` neu in `datamodelAgent.py` statt JSON-in-error-String; (B) Repair-Telemetrie wandert von `aiAuditLogger` in `AgentTrace`/`ToolCallLog` (per-Run-Aggregation passt nicht in per-Call-Audit-Log); (C) Eval-Fixture als Python-Loader + Pydantic/`recordCreate` statt `fixture.sql` (konsistent mit Trustee-Tests); (D) AC#3 als `repairConversionRate ≥ 0.8` über Repair-Triggered-Subset präzisiert, nicht als End-Accuracy; (E) Doc-Sync-Liste um `b-reference/gateway/features/trustee.md` und `d-guides/coding-conventions.md` erweitert. Plan ist damit umsetzungsreif für Phase 1.
## 2026-05-16
- 2026-05-16 | fix | gateway, frontend-nyla | RAG-Reindex hängt nach manuellem Trigger im Status RUNNING/PENDING, ohne dass ein Walker startet — UI zeigt endlosen Spinner bis Zombie-Killer nach 30 min eingreift. Root Cause: drei Job-Submit-Routen waren sync (`def`, nicht `async def`) und enthielten ein "loop = asyncio.get_event_loop(); ... except RuntimeError: asyncio.run(_enqueue())"-Konstrukt. FastAPI führt sync-Routes im Worker-Threadpool aus → kein laufender Event-Loop → `asyncio.run` öffnet einen temporären Loop, in dem `startJob` `_runJob` via `create_task` registriert → `asyncio.run` schliesst den Loop sofort nach Return → Task wird gecancelled, bevor er ein einziges Statement ausführt. Daily-Resync funktionierte, weil er aus APScheduler im Main-Loop läuft. Fix: alle drei Routen auf `async def` umgestellt und `await startJob(...)` direkt verwendet — keine Loop-Hack-Logik mehr. Betroffene Routes: `routeRagInventory._reindexConnection`, `routeDataSources._updateDataSourceRagIndex` (UDB-Toggle 🧠), `routeDataConnections._updateKnowledgeConsent` (globaler Consent-Toggle). Zusätzlich: Inventar-Response liefert jetzt `lastSuccess` mit `{jobId, finishedAt, indexed, skippedDuplicate, skippedPolicy, failed, durationMs}` plus `lastError.finishedAt` — Frontend zeigt grünes Erfolgs-Banner mit Stats + relativem Zeitstempel ("Sync erfolgreich vor 3 Min — 4 neu indexiert · 183 unverändert (198.3s)") statt nur leerem Spinner; Error wird nur angezeigt wenn er neuer ist als der letzte Erfolg, sonst gewinnt das Success-Banner. `RagInventoryPage` und `RagRunningBadge` pollen jetzt schnell (5s) während Jobs laufen und langsam (60s) im Idle. Badge zeigt einen 4s-"Sync abgeschlossen ✓"-Toast wenn der letzte aktive Job verschwindet. Hängender Job 7ee09ca7 wurde via Onetime-Skript gekillt.
## 2026-05-15
- 2026-05-15 | fix | gateway | RAG-Sync Log-Spam + DB-Reinit-Storm. Root Cause: `mainBackgroundJobService._getDb()` rief bei jeder Job-CRUD-Operation `DatabaseConnector(...)` direkt auf — pro Reindex-Klick mehrfach (`submitJob`, `_updateJob`, `getJob`, `listJobs`, `cancelJob`). Jeder Konstruktor-Call lief komplett durch `_create_database_if_not_exists``_create_tables` → neue psycopg2-Connection → `_initializeSystemTable` und loggte "PostgreSQL database system initialized successfully" auf INFO. Folgen: Log-Lärm bei jedem Reindex (~5-10 INFO-Zeilen) + echter DB-Overhead pro Job-Operation. Fix: (1) `_getDb()` nutzt jetzt `getCachedConnector()` (Cache-Key = host/db/port, FIFO-Eviction nach 32, Thread-safe), pro Worker-Lifetime nur noch 1× Init für die jobs-DB. Smoke-Test bestätigt: 3× `_getDb()` → identische Instanz. (2) Init-Log in `connectorDbPostgre.initDbSystem` von INFO auf DEBUG gesenkt + um `db/host/port`-Felder ergänzt — wenn doch mal ein Connector neu erzeugt wird, ist das im Steady-State nicht mehr wert auf INFO-Level zu schreien. Andere Hot-Path-Komponenten (`interfaceDbApp`, `interfaceDbKnowledge`, `aiAuditLogger`) nutzten den Cache bereits korrekt; `auditLogger` ist Singleton, also unkritisch.
## 2026-05-14
- 2026-05-14 | fix | gateway | RAG-Sync: Zombie-Job-Handling + Walker-Timeouts. (1) Neuer 5-Minuten-Cron `background_jobs.zombie_killer` in `mainBackgroundJobService.killZombieJobs()` markiert RUNNING-Jobs ohne Progress-Update >30 min als ERROR und beendet so auf Dauer hängende Bootstraps. (2) Neuer Helper `subWalkerHelpers.py` (`downloadWithTimeout` 60s, `extractWithTimeout` 90s via `asyncio.to_thread`+`wait_for`, `ingestWithTimeout` 60s, `logItemStart`) — alle Walker (sharepoint, kdrive, gdrive, gmail, outlook, clickup) loggen jetzt vor jedem Item `walker.item.start service=… path=…`, sodass das letzte solche Log bei einem Hang das verursachende Item exakt benennt. Sync-Extraktion läuft auf Worker-Thread, blockiert das Event-Loop nicht mehr. (3) Aktive Zombies (msft 81min, infomaniak 81min, clickup 81min) wurden via Onetime-Skript gekillt.
- 2026-05-14 | docs | wiki | WW Stephan-Lieferablage: `c-work/2-build/walderwyss-stephan-output/` (Stephan legt nichts in pamocreate ab). Auftrags-PDFs Homepage + Infomaniak für Stephan.
- 2026-05-14 | docs | wiki | Lawyer-Feature geplant (Mandatsvorbereitung + Dashboards für Anwaltskanzleien), ausgelöst durch WW-Pitch-Bedarf (David Vasella). Working-Doc in `c-work/1-plan/2026-05-lawyer-feature.md`.
## 2026-05-12
- 2026-05-12 | feat | gateway, frontend-nyla, teams-bot | Avatar-Bild/Video für TeamsBot: Neues Feld `avatarFileId` in `TeamsbotConfig`/`TeamsbotUserSettings` und `defaultAvatarFileId` in `TeamsbotMeetingModule`. Benutzer können in den Bot-Einstellungen und pro Modul ein Bild oder Video aus den Systemdateien auswählen, das anstelle der statischen Farbfläche als Bot-Video im Meeting gerendert wird. Modul-Einstellung überschreibt Instanz-Default. Gateway löst die Datei beim Session-Start auf, konvertiert zu Base64, und sendet sie im Join-Payload an den Browser-Bot. Bot-seitig: `mediaGetUserMediaPatch.ts` rendert Bilder per `drawImage()` auf den Canvas, Videos per `<video>`-Element mit Loop. Für Videos wird FPS auf 15 erhöht und `contentHint='motion'` gesetzt. Fallback bleibt die statische Farbfläche mit Bot-Name.

View file

@ -333,9 +333,73 @@ gateway/
aicore/ # AI-Provider-Plugins, Model-Selector
```
## Feature Data Sub-Agent: Ontologie statt freier Domain-Hints (ab 2026-05)
Wenn ein Feature dem AI-Agent strukturierten Zugriff auf seine Daten gibt (typisch ueber `queryFeatureInstance` -> Feature Data Sub-Agent), exportiert es eine **`OntologyDescriptor`** statt eines freien Hint-Strings.
**Alt (deprecated, nur noch als Fallback):**
```python
def getAgentDomainHints() -> str:
return "Bankkonten = accountNumber LIKE '102%'. closingBalance ist already-aggregated, niemals SUM-en. ..."
```
**Neu (Standard ab Mai 2026):**
```python
from modules.serviceCenter.services.serviceAgent.datamodelOntology import (
OntologyDescriptor, Entity, Constraint, ConstraintRule,
CanonicalQueryPattern, SemanticType,
)
_ONTOLOGY = OntologyDescriptor(
featureCode="myfeature",
entities=[
Entity(
name="BankAccount",
pythonClass="MyFeatureAccount",
semanticType=SemanticType.ACCOUNT,
parentEntity="Account",
description="Account with accountNumber LIKE '102%'.",
),
],
constraints=[
Constraint(
appliesTo="MyFeatureAccountBalance.closingBalance",
rule=ConstraintRule.NEVER_AGGREGATE,
message="closingBalance is per-period already; query with periodYear+periodMonth, never SUM/AVG it.",
),
],
canonicalPatterns=[
CanonicalQueryPattern(
intent="BANK_BALANCE_AT_DATE",
description="Saldo eines Bankkontos per Jahresende.",
pattern={
"tool": "queryTable",
"tableName": "MyFeatureAccountBalance",
"filters": [
{"field": "accountNumber", "op": "=", "value": "<accountNumber>"},
{"field": "periodYear", "op": "=", "value": "<year>"},
{"field": "periodMonth", "op": "=", "value": 0},
],
"fields": ["closingBalance", "currency"],
},
),
],
)
def getAgentOntology() -> OntologyDescriptor:
return _ONTOLOGY
```
**Warum:** Die Ontologie ist die Single Source of Truth fuer Prompt **und** Validator. Aenderungen an einer `Constraint` wirken sowohl auf den AI-Steering-Block als auch auf die deterministische Pre-Execute-Validierung -- es gibt keine Drift zwischen Prompt-Text und Tool-Reject-Logik mehr. Mehr Hintergrund: `b-reference/gateway/ai-agent.md` Abschnitt "FeatureDataAgent: Query-Repair-Loop + Ontologie" und `b-reference/gateway/features/trustee.md` als Referenz-Pilot.
**Backward-Compatibility:** Features, die `getAgentOntology()` (noch) nicht haben, behalten ihren bestehenden `getAgentDomainHints()`-Pfad ohne Code-Change. Der Sub-Agent nutzt automatisch den passenden Block.
## Anti-Patterns
- Keine impliziten Type-Conversions in API-Responses
- Keine DB-Queries in Route-Handlern (immer ueber Interfaces)
- Kein direkter `os.environ`-Zugriff -- immer `APP_CONFIG`
- Keine hartkodierten Secrets -- verschluesselt in Env-Dateien
- Neue Features: kein `getAgentDomainHints() -> str` mehr. Wenn der AI-Agent auf die Feature-Daten zugreifen soll, `getAgentOntology() -> OntologyDescriptor` exportieren (siehe Abschnitt oben).