diff --git a/b-reference/frontend-nyla/architecture.md b/b-reference/frontend-nyla/architecture.md index 583c96e..95e57af 100644 --- a/b-reference/frontend-nyla/architecture.md +++ b/b-reference/frontend-nyla/architecture.md @@ -1,6 +1,6 @@ - - + + # 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=`, 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) | diff --git a/b-reference/gateway/ai-agent.md b/b-reference/gateway/ai-agent.md index cca144f..68cda9f 100644 --- a/b-reference/gateway/ai-agent.md +++ b/b-reference/gateway/ai-agent.md @@ -1,6 +1,6 @@ - - + + # 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=, 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-.{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 | diff --git a/b-reference/gateway/features/trustee.md b/b-reference/gateway/features/trustee.md index 0028a5e..c2887d0 100644 --- a/b-reference/gateway/features/trustee.md +++ b/b-reference/gateway/features/trustee.md @@ -1,6 +1,6 @@ - - + + # 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 | diff --git a/b-reference/platform/neutralization.md b/b-reference/platform/neutralization.md index 64060e8..bb01e37 100644 --- a/b-reference/platform/neutralization.md +++ b/b-reference/platform/neutralization.md @@ -1,6 +1,6 @@ - - + + # 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..]` 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). diff --git a/c-work/1-plan/2026-05-lawyer-feature.md b/c-work/1-plan/2026-05-lawyer-feature.md new file mode 100644 index 0000000..8f664fa --- /dev/null +++ b/c-work/1-plan/2026-05-lawyer-feature.md @@ -0,0 +1,195 @@ + + + + +# 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// + ├── dashboards/ + │ ├── hr (Tab) + │ ├── business (Tab) + │ ├── it (Tab) + │ └── finance (Tab, optional) + └── matter-preparation/ + ├── + └── / + ├── 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=, mandateId=, 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 diff --git a/c-work/1-plan/stt-chirp-v2-evaluation.md b/c-work/1-plan/stt-chirp-v2-evaluation.md index 020fca3..115e050 100644 --- a/c-work/1-plan/stt-chirp-v2-evaluation.md +++ b/c-work/1-plan/stt-chirp-v2-evaluation.md @@ -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`). diff --git a/c-work/3-validate/2026-05-feature-data-agent-ontology-and-repair.md b/c-work/3-validate/2026-05-feature-data-agent-ontology-and-repair.md new file mode 100644 index 0000000..1ed42ad --- /dev/null +++ b/c-work/3-validate/2026-05-feature-data-agent-ontology-and-repair.md @@ -0,0 +1,374 @@ + + + + + +# 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=": ", 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). diff --git a/c-work/1-plan/2026-04-formgenerator-grouping.md b/c-work/4-done/2026-04-formgenerator-grouping.md similarity index 96% rename from c-work/1-plan/2026-04-formgenerator-grouping.md rename to c-work/4-done/2026-04-formgenerator-grouping.md index dd88dee..3139d93 100644 --- a/c-work/1-plan/2026-04-formgenerator-grouping.md +++ b/c-work/4-done/2026-04-formgenerator-grouping.md @@ -1,6 +1,11 @@ - + + + # 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). diff --git a/c-work/1-plan/2026-05-enterprise-subscription.md b/c-work/4-done/2026-05-enterprise-subscription.md similarity index 69% rename from c-work/1-plan/2026-05-enterprise-subscription.md rename to c-work/4-done/2026-05-enterprise-subscription.md index 35386ef..d36d214 100644 --- a/c-work/1-plan/2026-05-enterprise-subscription.md +++ b/c-work/4-done/2026-05-enterprise-subscription.md @@ -1,5 +1,6 @@ - + + # 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` diff --git a/c-work/2-build/2026-05-rag-consent-and-control-implementation.md b/c-work/4-done/2026-05-rag-consent-and-control-implementation.md similarity index 89% rename from c-work/2-build/2026-05-rag-consent-and-control-implementation.md rename to c-work/4-done/2026-05-rag-consent-and-control-implementation.md index 57d723b..7ae149d 100644 --- a/c-work/2-build/2026-05-rag-consent-and-control-implementation.md +++ b/c-work/4-done/2026-05-rag-consent-and-control-implementation.md @@ -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. --- diff --git a/c-work/2-build/2026-05-rag-consent-and-control-unification.md b/c-work/4-done/2026-05-rag-consent-and-control-unification.md similarity index 74% rename from c-work/2-build/2026-05-rag-consent-and-control-unification.md rename to c-work/4-done/2026-05-rag-consent-and-control-unification.md index 2727615..23d43a7 100644 --- a/c-work/2-build/2026-05-rag-consent-and-control-unification.md +++ b/c-work/4-done/2026-05-rag-consent-and-control-unification.md @@ -1,6 +1,6 @@ - + - + # 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 1–3) -- [ ] **Audit:** existierende Verwendung von `DataSource.autoSync` prüfen → Entscheidung: Renaming `ragIndexEnabled` vs. Re-Doku. -- [ ] `datamodelDataSource.DataSource`: `ragIndexEnabled: bool = False` (default `False` — kein Auto-Index ohne explizite Wahl). `lastIndexed: Optional[float]`. -- [ ] Migration: bestehende `UserConnection`s mit `knowledgePreferences.neutralizeBeforeEmbed=true` → falls DataSources existieren, deren `neutralize=true` setzen. Pref-Feld entfernen. -- [ ] `datamodelUam.UserConnection.knowledgePreferences` — Schema-Doku: `neutralizeBeforeEmbed`, `surfaceToggles`, `mimeAllowlist` raus. -- [ ] `subConnectorPrefs.ConnectionIngestionPrefs` — tote Felder entfernen. -- [ ] `serviceBackgroundJobs.cancelJob(jobId) -> bool` API hinzufügen (setzt Status auf `CANCELLED`). -- [ ] `JobProgressCallback`: `isCancelled() -> bool` (liest Job-Status aus DB; cached für N Sekunden um DB-Last zu vermeiden). -- [ ] **Walker-Refactor:** Jeder `subConnectorSync*.py` Walker: - - iteriert über `DataSource`-Rows der Connection mit `ragIndexEnabled = true` und passendem `sourceType`, - - lädt `neutralize` aus `DataSource` (nicht aus Pref-Dict), - - prüft `progressCb.isCancelled()` mindestens einmal alle 50 Items → graceful exit + Job-Result `cancelled: true, processedSoFar: N`, - - schreibt `dataSource.lastIndexed = now()` am Ende. -- [ ] **Purge-Helpers** in `interfaceDbKnowledge`: - - `deleteFileContentIndexByConnectionAndPathPrefix(connectionId, pathPrefix) -> {indexRows, chunks}`, - - `listFileContentIndexByConnection(connectionId) -> List[FileContentIndex]`, - - `listFileContentIndexByDataSource(dataSourceId) -> List[FileContentIndex]` (matched über `connectionId` + `contextRef.path`-Prefix; oder DataSource-ID in `chunkMetadata` mitführen — Entscheidung in Phase A). +- [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 3–5) -- [ ] `GET /api/connections/` Antwort um `knowledgeIngestionEnabled` und `knowledgePreferences` erweitern. -- [ ] `PATCH /api/connections/{id}/knowledge-consent` `{ enabled: boolean }` — bei `false`: synchroner Purge **aller** Connection-Chunks + Flag setzen + alle laufenden Bootstrap-Jobs dieser Connection cancellen. Bei `true`: enqueue Bootstrap (nur wenn DataSources mit `ragIndexEnabled=true` existieren). -- [ ] `PATCH /api/connections/{id}/knowledge-preferences` `{ ...prefs }` — speichert Mail-Tiefe etc.; optional Resync-Trigger. -- [ ] `POST /api/connections/{id}/knowledge-stop` — cancelled alle laufenden Bootstrap-Jobs der Connection. -- [ ] **`PATCH /api/datasources/{id}/rag-index`** `{ enabled: boolean }`: - - bei `true`: setzt Flag, enqueue Mini-Bootstrap-Job für **diese eine** DataSource. - - bei `false`: setzt Flag, enqueue Purge-Job (`deleteFileContentIndexByConnectionAndPathPrefix`). -- [ ] **Neue Route `routeRagInventory`:** - - `GET /api/rag/inventory/me` — aktueller User: alle Connections mit Knowledge-Status, deren DataSources (mit Index-Counts), letzter Sync, nächster Resync. - - `GET /api/rag/inventory/mandate/{mandateId}` — Mandant-Admin only: Aggregation pro User, pro Connection, pro Feature. - - `GET /api/rag/inventory/platform` — PlatformAdmin only: System-Stats, Cost-Tracking-Hooks (placeholder für v2). -- [ ] `GET /api/rag/connection/{id}/status` — letzter Bootstrap-Run, aktuelle Job-ID falls running, Counter (`indexedNew`, `skippedDuplicate`, `failed`), `nextScheduledResync`. -- [ ] **Workspace-RAG-Endpoint löschen:** `routeFeatureWorkspace.rag-statistics` raus. +- [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 5–6) -- [ ] **Löschen:** `WorkspaceRagInsightsPage.tsx` + `.module.css`. -- [ ] `mandate.ts`: `rag-insights` Workspace-View raus. -- [ ] `FeatureView.tsx`: Mapping + Sonderbehandlung raus. -- [ ] `App.tsx`: Route `rag-insights` raus. -- [ ] **Wizard-Refactor (`AddConnectionWizard`) — Connector-Type-Aware Steps:** - - Step 0: Anbieter wählen — Cards für **alle vier** Typen (Google / Microsoft / ClickUp / Infomaniak). - - Step-Definition pro Typ (`WIZARD_STEPS_BY_TYPE`) — Wizard rendert dynamisch: - - **Microsoft:** *(optional)* Admin-Zustimmung → Consent → OAuth. - - **Google / ClickUp:** Consent → OAuth. - - **Infomaniak:** PAT-Eingabe (Pflicht) → Consent → Submit. - - **Microsoft Admin-Zustimmung-Step:** integriert die Logik von `handleAdminConsent` (Popup zu `/api/msft/adminconsent`). Im UI klar als **„nur falls Tenant es erfordert, sonst überspringen"** beschriftet. - - **Infomaniak-PAT-Step:** integriert die Logik von `infomaniakModal` (Connection wird beim PAT-Submit erstellt; bei Cancel rollback via `deleteConnection`). - - Stepper passt sich dynamisch an (3 Punkte für Google/ClickUp, 4 für Microsoft, 4 für Infomaniak). - - `KnowledgePreferences`-Felder werden **nicht mehr** beim Create gesendet (Defaults im Backend). - - `computeCostEstimate` + Cost-Hint-Render-Blöcke + `AddConnectionWizard.module.css` Cost-Stile **entfernen**. -- [ ] **`ConnectionsPage` Cleanup:** - - Buttons **„Admin-Zustimmung"** und **„Infomaniak"** in der Header-Action-Bar **entfernen**. - - `handleAdminConsent`, `handleCreateInfomaniak`, `handleInfomaniakCancel`, `handleInfomaniakSubmit`, `infomaniakModal`-State, `adminConsentPending`-State **entfernen**. - - Modal `Infomaniak verbinden` **entfernen** (Logik wandert in den Wizard). -- [ ] **`useConnections.ts`:** Hook erweitern um Wizard-Pfade für Infomaniak (`createInfomaniakConnection` + `submitInfomaniakToken` bleiben verfügbar; werden vom Wizard konsumiert). +- [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 6–9) -- [ ] **`SourcesTab.tsx`:** Vierter Action-Button (Icon-Vorschlag: 🧠 oder 📚) pro Tree-Node. Mechanik analog zu `🔒` (Neutralize): - - Klick: wenn DataSource existiert → Toggle `ragIndexEnabled`. Sonst → DataSource anlegen + Toggle setzen. - - Visueller State: aktiv / inaktiv / **inherited** (gestrichelt, gedimmt — analog zu `inheritedNeutralize` Pattern). - - **Vererbung:** Container-Knoten propagiert `ragIndexEnabled` an alle Kinder bis zur expliziten Überschreibung. Identische Mechanik wie `inheritedScope`/`inheritedNeutralize` (siehe Sektion „Granularität"). - - Bei Toggle-Off: Confirm-Dialog mit Item-Count-Vorschau („X Inhalte werden aus dem RAG entfernt"). -- [ ] **`ConnectionsPage.tsx` neue UI-Elemente pro Row:** - - Master-Toggle „Wissensdatenbank" → `PATCH /knowledge-consent`. - - Bei `runningJobId`: Status-Pill „Indexierung läuft (X / Y)" + roter **Stop**-Button → `POST /knowledge-stop`. - - Bei finished: kompakte Anzeige „Letzter Sync: , X indexiert / Y skipped / Z failed". - - Knowledge-Preferences-Edit-Drawer (Mail-Tiefe etc.) — als separater Dialog, nicht im Wizard. -- [ ] **Globales Header-Indikator:** - - Kleines Badge oben rechts (z.B. neben User-Menü): `🔄 N` wenn N>0 Bootstrap-Jobs des aktuellen Users laufen. - - Polling alle 5–10s gegen `GET /api/rag/inventory/me` (oder einen schlanken Sub-Endpoint, der nur `runningJobsTotal` liefert). - - Klick → Navigation zu `Start > Nutzung > RAG-Inventar`. - - Implementierung: neuer Component `RagRunningBadge.tsx` im Header-Layout. +- [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 8–11) -- [ ] **Neue Seite `RagInventoryPage`** unter `/system/rag-inventory`: - - **Tab „Meine Daten"** (default): pro Connection eine Card mit Master-Toggle (off-Confirm), Preferences-Edit-Link, Liste aller DataSources (mit `ragIndexEnabled`-Status, Item-Count, letztes Sync-Datum, „Stop"-Button bei laufendem Job, Toggle-Off-Knopf). - - **Tab „Mandant"** (Mandant-Admin only): Aggregation; sichtbar pro User wer wie viel beigesteuert hat. - - **Tab „Plattform"** (PlatformAdmin only): System-Stats; Placeholder für Cost-Tracking. -- [ ] `pageRegistry.tsx`: `page.system.ragInventory` → Icon `` + 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 11–13) -- [ ] **DSGVO-Audit-Log:** Jeder Knowledge-Consent-Toggle und jeder Index-Toggle wird in `aiAuditLogger` (oder vergleichbarem Audit-Log) festgehalten. Inkl. User, Timestamp, Connection/DataSource, alter und neuer Wert. -- [ ] **Backend-Tests:** - - Migration (Pref → Per-DataSource). - - Walker konsumiert nur DataSources mit `ragIndexEnabled=true`. - - DataSource-Toggle off → Purge-Job korrekt scoped. - - Connection-Consent off → Purge + Job-Cancel. - - `cancelJob` → Walker exit + Job-Status `CANCELLED`. - - Inventory-API liefert korrekte Counts. -- [ ] **Frontend-Tests:** - - `AddConnectionWizard` zeigt **keine** Cost-Hint und **keine** Preferences-Step. - - `SourcesTab` zeigt Index-Toggle, Toggle-Off triggert Confirm + API. - - `RagInventoryPage` rendert pro Tab korrekt. - - `ConnectionsPage` Stop-Button erscheint bei `running` und ruft API. -- [ ] **Wiki-Updates:** `b-reference/gateway/ai-agent.md` (Knowledge Lifecycle, DataSource-getriebener Walker), `b-reference/platform/neutralization.md` (DataSource = SSoT), `b-reference/frontend-nyla/architecture.md` (RagInventoryPage, UDB 4. Action-Button), `TOPICS.md` (RAG-Inventar als neuer Eintrag). +- [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` diff --git a/c-work/_CHANGELOG.md b/c-work/_CHANGELOG.md index 6751002..a8b449f 100644 --- a/c-work/_CHANGELOG.md +++ b/c-work/_CHANGELOG.md @@ -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 `