This commit is contained in:
ValueOn AG 2026-04-25 01:13:24 +02:00
parent dcfa99bd5b
commit d4095db4f2
16 changed files with 1798 additions and 15 deletions

View file

@ -1,5 +1,5 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-16 -->
<!-- lastReviewed: 2026-04-24 -->
# Themen-Index für AI-Kontext
@ -25,7 +25,7 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
| Billing & Subscriptions | b-reference/gateway/billing.md | Abrechnung, Prepaid, State Machine |
| Frontend Nyla | b-reference/frontend-nyla/architecture.md | UI-Seiten, Komponenten, Hooks, Routing |
| Private LLM | b-reference/private-llm/architecture.md | Internes LLM, Neutralisierung |
| Teams Bot | b-reference/teams-bot/architecture.md | Meeting-Bot, WebSocket |
| Teams Bot | b-reference/teams-bot/architecture.md | Meeting-Bot, WebSocket, Director Prompts (Hybrid Agent-Routing) |
## Cross-Cutting (repo-übergreifend)
@ -52,9 +52,12 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
| Gateway i18n Phase 7 (done) | c-work/3-validate/2026-04-gateway-i18n-phase-7-implementation.md | RBAC-Keys (rbac.*) im xx-Set, `translate-field` API, FormGenerator KI-Button |
| Gateway Duplicate Class Names (done) | c-work/3-validate/2026-04-gateway-duplicate-class-names.md | TaskResult, AiResponse, TableData, Token Umbenennungen |
| Generic Graph Editor (Typed Nodes, done) | c-work/3-validate/2026-04-generic-graph-editor.md | Port-Typen, Extraktoren, FrontendType-Renderer, System-Variablen |
| Typed Generic Handover (Pick-not-Push, done) | c-work/4-done/2026-04-typed-generic-handover.md | DataRef-only runtime, `validateGraph` Port-Hard-Fail, upstream-paths API, `bindNodeParameter` / `listUpstreamPaths` Tools, graph-defined FormPayload, Migrationsskript |
| Unified Document Model (UDM) & Workflows (done) | z-archive/2026-04-unified-document-model.md, b-reference/gateway/workflow.md (Abschnitt UDM) | UDM-Baum, Extract-Node, Loop/Consolidate, Agent-UDM-Tools |
| i18n Static Text Elimination (Ph. 12 done) | c-work/2-build/2026-04-i18n-static-text-elimination.md | Gateway Feature+Nodes: Dicts → de-Keys; Ph. 35 offen |
| Database Health & Data Cleanup (done) | z-archive/2026-04-database-health-and-data-cleanup.md | DB-Registry, FK-Discovery, Orphan-Scanner, Admin-Seite |
| Teamsbot Director Prompts (done) | c-work/4-done/2026-04-teamsbot-director-prompts.md | Private Operator-Prompts (One-Shot/Persistent), Hybrid SPEECH_TEAMS+Agent (`needsAgent`), `_activeServices`-Registry, Reconnect-Persistenz, 26 Backend-Tests |
| **Typed Action Architecture** (canonical) | b-reference/gateway/architecture.md (Abschnitt 4-Schichten), b-reference/gateway/workflow.md (Abschnitt Typed Action Architecture), b-reference/gateway/ai-agent.md (Tool-Generierung aus Catalog), b-reference/gateway/features/trustee.md, b-reference/frontend-nyla/architecture.md (FlowEditor), c-work/3-validate/2026-04-typed-action-architecture.md, c-work/1-plan/2026-04-typed-action-followups.md | Catalog → Methods → Adapter → Runtime; FeatureInstanceRef-Envelope; Pick-not-Push; `*`-Wildcard; Save-with-errors (AC-9); DB-CLI `script_migrate_feature_instance_refs.py`; Vitest+RTL FE-Tests |
## Prozess & Betrieb

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-13 -->
<!-- verifiedAgainst: frontend_nyla (codebase audit 2026-04-07, post Automation Unification) -->
<!-- lastReviewed: 2026-04-24 -->
<!-- verifiedAgainst: frontend_nyla (codebase audit 2026-04-07, post Automation Unification) + Typed Action Architecture Phasen 1-5 + Vitest+RTL Setup -->
# Frontend Nyla -- Architektur
@ -102,6 +102,45 @@ Wenn eine Feature-Seite N verwandte Modelle in eigenen Tabs zeigen soll (Beispie
5. **Tab-Bar** im Stil bestehender Trustee-Tab-Seiten (`TrusteeAbschlussView`, `TrusteeAccountingSettingsView`); Gruppierung per `TabGroupDef` (z.B. "Stammdaten", "Lokale Daten", "Konfiguration", "Buchhaltungs-Daten") erhoeht Lesbarkeit bei vielen Tabs.
6. **Spalten-Definitionen** kommen aus `getModelAttributeDefinitions(...)` (Backend) -- nie hardcodiert. So bleibt das UI automatisch synchron mit Pydantic-Modellaenderungen.
## FlowEditor -- Typed Action Architecture (Phasen 1-5)
Seit April 2026 stuetzt sich der FlowEditor (Graphical Editor) auf eine typisierte Action-Architektur. Quellen: `wiki/c-work/3-validate/2026-04-typed-action-architecture.md`, Tests in `frontend_nyla/src/components/FlowEditor/**/*.test.tsx`.
### Picker-Komponenten
| Komponente | Datei | Zweck |
|------------|-------|-------|
| `RequiredAttributePicker` | `nodes/shared/RequiredAttributePicker.tsx` | Auflistung + Auswahl von 0/1/N Pflicht-Attributen (`featureInstanceId`, `connection`, ...). Bei genau einer Quelle: Auto-Bind mit Override-Knopf; bei N: explizite Auswahl; bei 0: Hint "Iterieren-Vorschlag" oder Ressource fehlt. |
| `DataPicker` | `nodes/shared/DataPicker.tsx` | Strict-Type-Filter (`expectedParamType`) + Object-Drill-Down + `*`-Wildcard fuer List-Iteration. Default ist Strict-Mode (`Nur kompatible`-Toggle). Hard-Mismatches werden ausgeblendet, Coerce-Faelle (z.B. `int -> str`) bleiben sichtbar. |
| `paramValidation.ts` | `nodes/shared/paramValidation.ts` | Single source of truth fuer Pflicht-Feld-Pruefung. Liefert `nodeErrors: List[NodeError]` (genutzt von Save-Toast, Run-Block, Node-Highlighting). |
### Save / Run Gating (AC-9)
- **Save** ist immer erlaubt (auch mit Pflicht-Fehlern). `CanvasHeader.tsx` zeigt nach Save bei Fehlern einen amber-Banner ("Gespeichert mit X Pflicht-Fehlern in Y Nodes").
- **Run** wird per `executeBlockedReason` blockiert; der Button wechselt auf "Pflicht-Felder fehlen", behaelt `aria-disabled` und triggert `onExecuteBlockedClick` (markiert die First-Error-Node).
- Hintergrund: `executeGraph` validiert serverseitig zusaetzlich (Port-Mismatch, fehlende Pflicht-Bindings) und liefert klare Fehlermeldungen falls jemand den Editor-Block umgeht.
### FeatureInstanceRef im Frontend
- `featureInstanceId`-Bindings werden als typisierter Envelope `{$type: "FeatureInstanceRef", id, featureCode}` ans Backend geschickt; das Backend persistiert sie 1:1 (Phase 5).
- Der Picker zeigt `[<featureCode>] <Label>` (z.B. `[trustee] Buchhaltung PWG`), damit die Variante auf einen Blick erkennbar ist.
- Legacy-Workflows (rohe UUIDs) bleiben funktionsfaehig -- die Backend-Engine migriert sie bei jedem Run automatisch (`materializeFeatureInstanceRefs`).
### Tests (Vitest + RTL)
`frontend_nyla` nutzt seit Track A1 Vitest 2.x + jsdom + `@testing-library/react`. Die Test-Dateien liegen neben den Komponenten als `*.test.tsx`:
| Test | Datei | Akzeptanzkriterium |
|------|-------|--------------------|
| RequiredAttributePicker (0/1/N + Override + Iterate) | `RequiredAttributePicker.test.tsx` | T5, T6 |
| DataPicker -- strict-mode + coerce | `DataPicker.strict.test.tsx` (in `DataPicker.test.tsx`) | T7 |
| DataPicker -- generic object drill-down + `*`-Wildcard | `DataPicker.drillDown.test.tsx` (in `DataPicker.test.tsx`) | T8 |
| CanvasHeader Run/Save-Gating + Warning-Toast | `CanvasHeader.test.tsx` | T10, AC-9 |
Befehle: `npm test` (CI-Mode), `npm run test:ui` (Vitest UI). Konfiguration: `vitest.config.ts`, `vitest.setup.ts`.
---
## Regeln / Invarianten
- **Feature-Organisation:** UI, Logic (`*Logic`), Types und CSS Modules pro Feature-Modul trennen; Exporte über `index.ts` wo üblich.

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-16 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) -->
<!-- lastReviewed: 2026-04-24 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification); gateway/modules/features/teamsbot/service.py (Hybrid Agent Escalation 2026-04-24); Typed Action Architecture Phasen 1-5 -->
# AI Agent & Knowledge Store
@ -104,6 +104,8 @@ Tools sind registrierte Handler mit JSON-Schema für Argumente, **`readOnly`-Fla
Zusätzlich zu den unten genannten **Kern-Tools** existieren **dynamische Tools** aus dem Workflow-System: `ActionToolAdapter` liest `methodDiscovery.methods` und registriert jede Action mit `dynamicMode=True` unter einem zusammengesetzten Namen (`{methodShort}_{actionName}`); im Adapter sind diese derzeit **alle als nicht-readOnly** registriert.
> **Tool-Generierung aus dem Catalog (Typed Action Architecture, 2026-04):** `ActionToolAdapter` leitet das JSON-Schema fuer jeden Tool-Aufruf direkt aus der **typisierten Action-Signatur** ab (Pflicht-Felder, Typen, Default-Werte, `FeatureInstanceRef`-Discriminator). Es gibt keine handgepflegte Heuristik mehr -- Editor, AI-Agent und Adapter konsumieren denselben Catalog (`/api/automation2/catalog`). Aenderungen an einer Action-Signatur schlagen automatisch auf das Tool-Schema durch; veraltete oder gedriftete Felder werden vom Snapshot-Test `test_staticNodesHaveNoDriftAgainstLiveMethods` blockiert (`_KNOWN_ADAPTER_DRIFTS = frozenset()`). Details: `wiki/c-work/3-validate/2026-04-typed-action-architecture.md`.
**Toolbox-Zuordnung:** Kern-Tools sind den Toolboxes `core`, `ai` und `datasources` zugeordnet (siehe Toolbox Registry oben). Connection-abhaengige Tools (`email`, `sharepoint`, `clickup`, `jira`) werden nur aktiviert, wenn der User eine passende Connection hat. Workflow-Editing-Tools (`workflow` Toolbox) werden separat via `workflowTools.py` registriert.
### Kern-Tools (registriert via `registerCoreTools``coreTools/`; Stand 2026-04 inkl. UDM-Helfer)
@ -249,6 +251,25 @@ Erweiterte Hilfen (z. B. **`readSection`**, Caching) für selektives Lesen sind
---
## Teamsbot-Integration (Hybrid-Routing, kein eigenes Toolset)
Der Teamsbot ruft den **selben** `AgentService.runAgent` über das ServiceCenter auf — es gibt **kein** Teamsbot-spezifisches Toolset. Aufrufer ist `gateway/modules/features/teamsbot/service.py::_runAgentForMeeting` mit `AgentConfig(maxRounds=5, maxCostCHF=0.10, toolSet="core", initialToolboxes=["core","web"], excludeActionTools=True)`.
| Trigger | Pfad | Wer ruft `runAgent`? |
|---|---|---|
| Operator schickt **Director Prompt** (One-Shot oder Persistent) via Regie-Panel | `routeFeatureTeamsbot.submitDirectorPrompt``service.submitDirectorPrompt``asyncio.create_task(_processDirectorPrompt)``_runAgentForMeeting` | direkt, umgeht `SPEECH_TEAMS` |
| `SPEECH_TEAMS` setzt **`needsAgent=true`** + `agentReason` (z. B. „recherchier das im Internet") | `service._analyzeAndRespond` erkennt `needsAgent`, ruft `_runAgentForMeeting` mit `taskBrief = agentReason` | Eskalation aus dem schnellen Pfad |
**FINAL-Delivery:** Der `FINAL`-Event-Text wird im Teamsbot-Service über die bestehenden Kanäle (TTS + `sendChatMessage`) ins Meeting gespielt — der Agent „spricht" nicht selbst, sondern liefert nur den Text. Damit braucht es **kein** Teamsbot-spezifisches Tool wie `sendChat` oder `readAloud` im Agent.
**Workflow-ID-Konvention:** `workflowId = f"teamsbot:{sessionId}"` — RoundMemory und RAG akkumulieren pro Meeting, getrennt von anderen Sessions.
**Persistente Direktiven:** `service._buildPersistentDirectorContext` rendert aktive `persistent`-Direktiven als `OPERATOR_DIRECTIVES`-Block in den `SPEECH_TEAMS`-Kontext, damit sie auch ohne erneuten Director-Prompt-Aufruf wirken (z. B. „Antworte immer in Englisch").
Siehe [`b-reference/teams-bot/architecture.md`](../teams-bot/architecture.md) für die vollständige Hybrid-Architektur und Director-Prompt-Lifecycle.
---
## Schlüssel-Dateien
| Datei | Rolle |

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-11 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) -->
<!-- lastReviewed: 2026-04-24 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) + Typed Action Architecture Phasen 1-5 -->
# Gateway -- Architektur
@ -174,6 +174,24 @@ Alle sieben Endpunkte teilen sich den Helper `_paginatedReadEndpoint` (`routeFea
UI-seitig sind diese Endpunkte unter `/mandates/{m}/trustee/{i}/data-tables[?tab=<key>]` (View `TrusteeDataTablesView`) zugaenglich; UI-Sichtbarkeit der Seite haengt am Permission-Eintrag `ui.feature.trustee.data-tables` (Template-Rollen `trustee-viewer`, `trustee-user`, `trustee-accountant`, Admin via Wildcard).
## Typed Action Architecture (4-Schichten)
Seit Phase 1-5 (April 2026) folgt jede Workflow-Aktion einem strikten 4-Schichten-Modell. Quellen: `wiki/c-work/3-validate/2026-04-typed-action-architecture.md`, Tests in `gateway/tests/unit/workflows/`, `gateway/tests/integration/trustee/`.
| Schicht | Modul / Verantwortung | Type-Anker |
|---------|-----------------------|------------|
| **1. Editor (Frontend)** | `frontend_nyla/src/components/FlowEditor/nodes/shared/` -- `RequiredAttributePicker`, `DataPicker` (strict-mode + Object-Drill-Down mit `*`-Wildcard), `paramValidation.ts` | Action-Signaturen aus dem Catalog (`/api/automation2/catalog`) |
| **2. Action / Method** | `gateway/modules/workflows/methods/method<Feature>/actions/*.py` (`extractFromFiles`, `processDocuments`, `syncToAccounting`, ...) | Typisierte Pydantic-Eingabe + `ActionResult`-Output, `FeatureInstanceRef` als Discriminator-Envelope |
| **3. Adapter** | `gateway/modules/features/graphicalEditor/nodeDefinitions/registerNodeWithMethod` -- leitet Node-Definitions aus Action-Signaturen ab, liefert Snapshot fuer den Editor (`adapter_validator`) | Snapshot-Test `test_staticNodesHaveNoDriftAgainstLiveMethods` (`_KNOWN_ADAPTER_DRIFTS = frozenset()`) |
| **4. Runtime** | `gateway/modules/workflows/automation2/executionEngine.py` -- `executeGraph` mit `materializeFeatureInstanceRefs` + `materializeConnectionRefs`, `_unwrapTypedRef` fuer Action-Calls | Migrations-Helper in `automation2/featureInstanceRefMigration.py` + DB-CLI `scripts/script_migrate_feature_instance_refs.py` |
Konsequenzen:
- **Single source of truth** ist die Action-Signatur. Editor, Adapter, Runtime und AI-Agent (`actionToolAdapter`) lesen alle dieselbe Catalog-Sicht.
- **Pick-not-push** auf Schicht 4: Runtime resolviert `connectionReference` und unwrappt typisierte Envelopes (`FeatureInstanceRef`, `DataRef`) automatisch -- Legacy-Aktionen bleiben funktionsfaehig.
- **Save-with-errors** ist explizit erlaubt (AC-9-Patch im `CanvasHeader`); Run wird mit `executeBlockedReason` blockiert, der Save-Toast meldet Pflicht-Fehler-Anzahl.
- **DB-Hygiene** ist optional via `script_migrate_feature_instance_refs.py` (`--dry-run` standard) -- ohne Skript laufen Workflows korrekt, der Editor zeigt aber bis zum naechsten Save noch das Legacy-Format.
## Regeln / Invarianten
- **Schichten:** Connectors sind anbieterspezifisch und ersetzbar; **Services hängen von Interfaces ab, nicht direkt von Connectors**. Geschäftslogik und Guardrails liegen in den Services.

View file

@ -1,5 +1,5 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-12 -->
<!-- lastReviewed: 2026-04-23 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) -->
# Automation (Graphical Editor)
@ -17,6 +17,7 @@ PowerOn hat ein **konsolidiertes Automatisierungssystem** basierend auf dem Feat
**Leitentscheide:**
- **Typed Pick-not-Push (2026-04):** Action-Nodes füllen Parameter nur noch über explizite `DataRef` / Static / SystemVar (`resolveParameterReferences`). Laufzeit-Wire-Handover (`applyWireHandover`) ist entfernt. `executeGraph` ruft `materializeConnectionRefs` (leere `connectionReference` → Ref auf `connection.id`) und `validateGraph` mit **harter** Port-Typprüfung (Ausnahme: Ziel-Port nur `Transit` = untypisierter Sink). Upstream-Pfade: `POST/GET …/upstream-paths` und Agent-Tools `listUpstreamPaths` / `bindNodeParameter`.
- Legacy-Features `automation` und `automation2` entfernt (Gateway + Frontend)
- Greenfield-DB `poweron_graphicaleditor` (keine Migration von Altdaten)
- v1-Scheduler-Patterns (inkrementell, `eventId`, `replaceExisting`, Stale-Removal) in konsolidierten Scheduler uebernommen

View file

@ -0,0 +1,144 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-24 -->
<!-- verifiedAgainst: gateway/modules/workflows/methods/methodTrustee/ + gateway/modules/features/trustee/ + Typed Action Architecture Phasen 1-5 -->
# Feature: Trustee
## Ueberblick
Das **Trustee**-Feature digitalisiert Spesenbelege und synchronisiert sie in ein externes Buchhaltungssystem (ABACUS, Bexio, Banana, ...). Es kombiniert eine **Workflow-Method** (`trustee` mit fuenf typisierten Actions) mit einer **Feature-Domain** (`features/trustee/` -- Datenmodelle, Interface, Routes, AccountingBridge).
Datenfluss:
```
SharePoint / Upload
|
v
trustee.extractFromFiles (AI extrahiert Belegtyp + Felder -> ActionDocument-Liste)
|
v
trustee.processDocuments (legt TrusteeDocument + TrusteePosition an, verlinkt Bankkonten)
|
v
trustee.syncToAccounting (pusht Positions via AccountingBridge ins Zielsystem)
```
Alle drei Actions konsumieren `featureInstanceId` als typisierten **`FeatureInstanceRef`**-Envelope (Typed Action Architecture). Sub-Action `trustee.refreshAccountingData` und Read-only `trustee.queryData` erweitern den Funktionsumfang fuer Sync-Refresh und AI-Tool-Zugriffe.
---
## Module
| Modul | Pfad | Verantwortung |
|-------|------|---------------|
| Method (Workflow) | `gateway/modules/workflows/methods/methodTrustee/` | `MethodTrustee` registriert die fuenf Actions; jede Action liegt in `actions/<name>.py` mit eigener typisierter Eingabe + `ActionResult`-Output. |
| Feature-Domain | `gateway/modules/features/trustee/` | Datenmodell (`datamodelTrustee.py`), Interface (`interfaceTrustee.py`), Route-Module (`routeFeatureTrustee*.py`), `AccountingBridge` (`accountingBridge.py`). |
| Editor-Adapter | `gateway/modules/features/graphicalEditor/nodeDefinitions/trustee.py` | Bindet die fuenf Actions als Editor-Nodes (`trustee.extractFromFiles`, ...) -- Ports + Pflicht-Felder werden via `registerNodeWithMethod` aus den Action-Signaturen abgeleitet. |
| Frontend | `frontend_nyla/src/components/Trustee/` + `TrusteeDataTablesView` | UI fuer Document/Position-Verwaltung, Daten-Tabellen, Connector-Konfiguration. |
---
## Actions (typisierte Inputs + Outputs)
| Action | Eingabe (Pflicht) | Output | Zweck |
|--------|-------------------|--------|-------|
| `trustee.extractFromFiles` | `featureInstanceId` (FeatureInstanceRef), entweder `fileIds: List[str]` oder `sourceFolder` | `DocumentList` (`documents: List[ActionDocument]`) | AI extrahiert Belegtyp + Strukturdaten aus PDFs / JPGs (SharePoint oder Upload). |
| `trustee.processDocuments` | `featureInstanceId`, `documentList: DocumentList` | `ActionResult` (mit `documents: List[ActionDocument]`) | Legt `TrusteeDocument`-Records + zugehoerige `TrusteePosition`-Eintraege an, auto-verlinkt Bankauszuege via `_linkBankPositions`. |
| `trustee.syncToAccounting` | `featureInstanceId`, `documentList: DocumentList` (typisch DataRef auf `processDocuments.documents`) | `ActionResult` (mit `documents`) | Pusht alle gebuchten Positions per `AccountingBridge.pushBatch` ins Zielsystem; markiert erfolgreiche Sync-Records. |
| `trustee.refreshAccountingData` | `featureInstanceId` | `ActionResult` (`success`, `error`) | Refresht Kontenplan + Saldo + Kontakte aus dem Buchhaltungssystem. |
| `trustee.queryData` | `featureInstanceId`, freie Filter-Argumente | `ActionResult` mit Query-Resultat | Read-only Zugriff fuer den AI-Agent (`dynamicMode=True`). |
> Schemas leben in `gateway/modules/datamodels/datamodelWorkflowActions.py` + `gateway/modules/features/trustee/datamodelTrustee.py`. **Single source of truth** fuer Editor + AI-Agent + Adapter ist die Action-Signatur (`/api/automation2/catalog`).
---
## Datenmodell
| Modell | Datei | Kommentar |
|--------|-------|-----------|
| `TrusteeOrganisation` | `datamodelTrustee.py` | Buchhaltungs-Mandant innerhalb der FeatureInstance. |
| `TrusteeRole`, `TrusteeAccess` | dito | Rollen + Zugriffsregeln je Organisation. |
| `TrusteeContract` | dito | Vertragsdatensatz je Mieter / Kontakt. |
| `TrusteeDocument` | dito | Originaldokument + AI-Klassifikation + Statusfelder; Quelle fuer Positionen. |
| `TrusteePosition` | dito | Buchungszeile (Debit/Kredit, Konto, Betrag, Datum, Sync-Status). |
| `TrusteeData*` (Account, JournalEntry, JournalLine, Contact, AccountBalance) | dito | Read-only Spiegel des Buchhaltungssystems (Kontenplan, Buchungen, Saldenliste). |
| `TrusteeAccountingConfig` | dito | Connector-Konfiguration (Bridge-Typ, Secrets via Encryption). |
| `TrusteeAccountingSync` | dito | Audit-Eintrag pro Sync-Lauf. |
---
## FeatureInstanceRef (Typed Action Architecture)
Seit Phase 5 ist `featureInstanceId` ueberall als **typisierter Envelope** persistiert:
```json
{ "$type": "FeatureInstanceRef",
"id": "11111111-1111-1111-1111-111111111111",
"featureCode": "trustee" }
```
- **Schreiben:** `materializeFeatureInstanceRefs` (`automation2/featureInstanceRefMigration.py`) wandelt rohe UUIDs **bei jedem Run** automatisch ins Envelope-Format. Optional persistiert `gateway/scripts/script_migrate_feature_instance_refs.py` die Aenderung in der DB (`--dry-run` Default).
- **Lesen:** `_unwrapTypedRef` (`graphUtils`) entpackt den Envelope vor dem Action-Call -- Trustee-Actions sehen weiterhin `featureInstanceId: "<uuid>"` und brauchen keinen Patch.
- **Discriminator:** `featureCode = "trustee"` macht die Trustee-Variante eindeutig (Editor zeigt `[trustee] <Label>` im Picker).
---
## Pick-not-Push fuer DocumentList
Die typische Spesenbelege-Kette ist:
```
trigger.manual -> trustee.extractFromFiles -> trustee.processDocuments -> trustee.syncToAccounting
```
`trustee.processDocuments` und `trustee.syncToAccounting` ziehen ihren `documentList`-Input ueber **`DataRef`**-Bindings aus dem Vorgaengerknoten:
```json
{ "type": "ref", "nodeId": "process", "path": ["documents"] }
```
`*`-Wildcard-Bindings (`["documents", "*", "name"]`) werden vom Resolver iteriert und liefern eine Liste -- noetig fuer Loop-Vorschlaege im DataPicker.
---
## AccountingBridge
`gateway/modules/features/trustee/accountingBridge.py` ist die einheitliche Abstraktion ueber alle Buchhaltungssysteme. `AccountingBridge.pushBatch(positions, config)` validiert + pusht; konkrete Adapter (`abacus.py`, `bexio.py`, `banana.py`, ...) implementieren das Protokoll. Sync-Ergebnisse landen als `TrusteeAccountingSync`-Audit-Eintraege.
`trustee.refreshAccountingData` ruft `AccountingBridge.fetchSnapshots` -- die Tabellen `TrusteeDataAccount`, `TrusteeDataJournal*`, `TrusteeDataContact`, `TrusteeDataAccountBalance` werden read-only befuellt.
---
## REST-Endpunkte (Auswahl)
Liste in `wiki/b-reference/gateway/architecture.md` (Abschnitt "Feature: Trustee -- Daten-Tabellen-Endpunkte"); zentral:
| Endpunkt | Modell | Zweck |
|----------|--------|-------|
| `GET /api/trustee/{instanceId}/documents` | `TrusteeDocument` | Paginierte Document-Liste (RBAC + Filter). |
| `GET /api/trustee/{instanceId}/positions` | `TrusteePosition` | Paginierte Position-Liste. |
| `GET /api/trustee/{instanceId}/data/...` | `TrusteeData*` | Read-only Spiegel (Konten, Journal, Saldo, Kontakte). |
| `GET /api/trustee/{instanceId}/accounting/configs` | `TrusteeAccountingConfig` | Connector-Konfiguration (Secrets maskiert). |
| `GET /api/trustee/{instanceId}/accounting/syncs` | `TrusteeAccountingSync` | Audit der Sync-Laeufe. |
UI-Sichtbarkeit der Daten-Tabellen-Seite haengt am Permission-Eintrag `ui.feature.trustee.data-tables` (Template-Rollen `trustee-viewer`, `trustee-user`, `trustee-accountant`).
---
## Tests
| Test | Datei | Was er beweist |
|------|-------|----------------|
| Unit | `gateway/tests/unit/methods/test_methodTrustee.py` (sofern vorhanden) | Action-Schemas + Pflicht-Felder. |
| Adapter-Drift | `gateway/tests/unit/graphicalEditor/test_adapter_validator.py` | Editor-Nodes synchron zu Method-Signaturen (`assert report.errors == []`). |
| FeatureInstanceRef | `gateway/tests/unit/workflows/test_featureInstanceRefMigration.py` | Materialisierung + Auto-Unwrap, Idempotenz. |
| Live-E2E | `gateway/tests/integration/trustee/test_spesenbelege_workflow_e2e.py` | `executeGraph` durch `processDocuments + syncToAccounting` mit In-Memory-Fakes. |
| DB-CLI | `gateway/tests/unit/scripts/test_migrate_feature_instance_refs.py` | Dry-run + Live-Migration des persistierten Graphs. |
---
## Links
- Architektur-Plan: [`wiki/c-work/3-validate/2026-04-typed-action-architecture.md`](../../../c-work/3-validate/2026-04-typed-action-architecture.md)
- Folge-Plan: [`wiki/c-work/1-plan/2026-04-typed-action-followups.md`](../../../c-work/1-plan/2026-04-typed-action-followups.md)
- Adapter-Drift-Backlog (abgeschlossen): [`wiki/c-work/4-done/2026-04-adapter-drift-cleanup.md`](../../../c-work/4-done/2026-04-adapter-drift-cleanup.md)

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-16 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) -->
<!-- lastReviewed: 2026-04-24 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) + Typed Action Architecture Phasen 1-5 -->
# Workflow-Engine
@ -11,7 +11,7 @@ Die Workflow-Engine im Gateway orchestriert KI-gestützte Aufgabenpläne und die
**Einsatzorte (Konsumenten der Action Library):**
- **Workspace (Dynamic Mode):** `WorkflowProcessor` / `modeDynamic` → Planung (Stage 1/2) → `ActionExecutor`
- **Graphical Editor:** `executionEngine.executeGraph` mappt Nodes (`_method` / `_action`) auf dieselbe Library
- **Graphical Editor:** `executionEngine.executeGraph` mappt Nodes (`_method` / `_action`) auf dieselbe Library; vor dem Lauf validiert `validateGraph` u.a. **Port-Kompatibilität** (kein impliziter Wire-Handover mehr). Graph-definierte Outputs (z.B. `input.form` / `trigger.form`) werden über `parse_graph_defined_output_schema` / `{ kind: "fromGraph", parameter: "fields" }` aus den Node-Parametern abgeleitet.
- **Workspace-Agent:** `ActionToolAdapter` exponiert `dynamicMode=True`-Aktionen als Tools → `ActionExecutor`
*Hinweis:* Die frueheren separaten Systeme Automation v1 und Automation2 wurden im Rahmen der Automation Unification (2026-04) entfernt. Der Graphical Editor ist der einzige Graph-basierte Konsument.
@ -79,6 +79,42 @@ Methoden sind Python-Klassen unter `gateway/modules/workflows/methods/`; der **M
---
## Typed Action Architecture (Phasen 1-5)
Seit April 2026 ist jede Action typisiert; Editor, Adapter und Runtime lesen dieselbe Catalog-Sicht (siehe `wiki/c-work/3-validate/2026-04-typed-action-architecture.md`). Konkrete Bausteine:
### Action-Signaturen + Catalog
- `gateway/modules/workflows/methods/method<Feature>/actions/<action>.py` deklariert pro Action eine **typisierte Pydantic-Eingabe** (Pflicht-Felder, Typen, Default-Werte) und liefert `ActionResult` (`success`, `error`, `documents`).
- `FeatureInstanceRef` (`{$type, id, featureCode}`) ist der **Discriminator-Envelope** fuer feature-instance-Parameter -- ersetzt rohe UUID-Strings, behaelt Backwards-Compat durch Auto-Unwrap im Runtime.
- `/api/automation2/catalog` exportiert die normalisierten Signaturen; Editor + AI-Agent + Adapter konsumieren genau diesen Stream.
### Adapter-Layer (`registerNodeWithMethod`)
- `gateway/modules/features/graphicalEditor/nodeDefinitions/registerNodeWithMethod.py` leitet die Editor-Node-Definitionen automatisch aus den Action-Signaturen ab (Ports, Pflicht-Felder, Typen, Frontend-Hints).
- Snapshot-Test `gateway/tests/unit/graphicalEditor/test_adapter_validator.py::test_staticNodesHaveNoDriftAgainstLiveMethods` haelt das Adapter-Layer hart gegen die Methods (`assert report.errors == []`, `_KNOWN_ADAPTER_DRIFTS = frozenset()`).
- Drift-Backlog wird in `wiki/c-work/4-done/2026-04-adapter-drift-cleanup.md` (abgeschlossen) tracked; neue Drifts MUESSEN entweder behoben oder bewusst ergaenzt werden.
### Runtime (`executeGraph`) -- Pick-not-Push + Materialisierung
- `gateway/modules/workflows/automation2/executionEngine.py::executeGraph` ruft beim Start zwei Migrations-Helper:
- `materializeFeatureInstanceRefs` (`automation2/featureInstanceRefMigration.py`) -- wandelt rohe `featureInstanceId: "<uuid>"` in `FeatureInstanceRef`-Envelope.
- `materializeConnectionRefs` (`automation2/pickNotPushMigration.py`) -- resolviert leere `connectionReference` aus Upstream-DataRefs.
- `resolveParameterReferences` / `_unwrapTypedRef` (`graphUtils`) entpacken Envelopes vor dem Action-Call -- Legacy-Aktionen sehen weiterhin den nackten Wert (`id`).
- DB-Hygiene: `gateway/scripts/script_migrate_feature_instance_refs.py` migriert Stored Graphs persistent (Editor-Reads sehen sofort die Envelopes; Workflows laufen aber auch ohne Skript korrekt).
### Bindings-Resolver mit `*`-Wildcard
- DataRefs unterstuetzen `path = ["documents", "*", "name"]` -- der Resolver iteriert ueber Listen und liefert ein `List[str]` zurueck. Das ist die Basis fuer **Loop-Vorschlaege** im DataPicker (`buildPickablePathsForNode` im Editor-Frontend).
- Generic-object drill-down kennt zwei Stufen: konkretes Item-Schema (wenn bekannt) oder generic `*`-Pfad (wenn der Item-Typ erst zur Laufzeit feststeht).
### Save-with-errors
- Der Editor erlaubt **Save bei Pflicht-Fehlern** (AC-9 / `CanvasHeader.tsx`); Run wird per `executeBlockedReason` blockiert, der Save-Toast meldet die Fehler-Anzahl.
- Backend-Validierung erfolgt in `validateGraph` (Port-Mismatch, fehlende Pflicht-Bindings) -- der eigentliche Run schlaegt mit klarer Fehlermeldung fehl, falls jemand den Editor-Block umgeht.
---
## Schlüssel-Dateien
| Pfad (unter `gateway/modules/`) | Rolle |

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-05 -->
<!-- verifiedAgainst: service-teams-browser-bot (documentation review 2026-02-18) -->
<!-- lastReviewed: 2026-04-24 -->
<!-- verifiedAgainst: service-teams-browser-bot (documentation review 2026-02-18); gateway/modules/features/teamsbot/{service,routeFeatureTeamsbot,interfaceFeatureTeamsbot,datamodelTeamsbot}.py + gateway/tests/unit/teamsbot/test_directorPrompts.py (Director Prompts review 2026-04-24) -->
# Teams Meeting Bot -- Architektur
@ -40,6 +40,8 @@ AI-gesteuerter Meeting-Bot für Microsoft Teams. Tritt Meetings als regulärer T
| Live Transcription | Echtzeit-Transkript-Stream für Teilnehmer ausserhalb des Meetings |
| Meeting Summary | AI-generierte Zusammenfassung nach Meeting-Ende |
| Multi-Bot | Mehrere parallele Sessions in verschiedenen Meetings |
| Director Prompts | Operator gibt dem laufenden Bot **private** Regieanweisungen (One-Shot oder Persistent), Antwort wird ins Meeting eingespielt |
| Hybrid Agent Escalation | SPEECH_TEAMS-Pfad kann komplexe Anfragen via `needsAgent=true` an den vollen Agent (`agentService.runAgent`) eskalieren |
## Integration mit Gateway
@ -62,3 +64,60 @@ Der Gateway (Feature `teamsbot`) verwaltet Sessions und stellt die AI-Pipeline b
- Jede Session läuft in einer **eigenen Browser-Instanz** (Isolation)
- Authentifizierter Join (mit Microsoft-Account) oder Anonymous Guest -- je nach Konfiguration
- Gateway ist die **einzige** Schnittstelle für AI-Aufrufe und TTS -- der Bot-Service selbst hat keine AI-Logik
## Hybrid-Routing: SPEECH_TEAMS + Agent
Der Teamsbot läuft auf zwei kooperierenden Pfaden:
```mermaid
flowchart LR
Audio["Meeting Audio"] --> STT["STT"]
STT --> ST["SPEECH_TEAMS<br/>(fast, low-latency)"]
ST -->|shouldRespond=true| TTS["TTS + Chat"]
ST -->|needsAgent=true| Agent["agentService.runAgent<br/>(toolSet=core, web=on,<br/>maxRounds=5, maxCostCHF=0.10)"]
Agent -->|FINAL text| TTS
UI["Operator UI<br/>(Regie-Panel + UDB)"] -->|POST directorPrompt| Route["routeFeatureTeamsbot"]
Route -->|submitDirectorPrompt| Svc["TeamsbotService<br/>(_activeServices)"]
Svc -->|asyncio.create_task| Agent
TTS --> Meeting
Svc -->|SSE 'directorPrompt'| UI
```
- **`SPEECH_TEAMS`** bleibt der Default-Pfad mit niedrigster Latenz. Der dazugehörige System-Prompt erlaubt dem Modell explizit, `needsAgent=true` + `agentReason` zu setzen, wenn die Anfrage Web-Recherche, Mail oder Multi-Step-Tools erfordert.
- **Director Prompts** umgehen `SPEECH_TEAMS` komplett und feuern direkt einen `runAgent`-Lauf, dessen `FINAL`-Event-Text wieder über die bestehenden TTS-/Chat-Kanäle ins Meeting geliefert wird.
## Director Prompts (private Operator-Anweisungen)
Operator-Prompts sind **privat** (nur per SSE an den Session-Owner sichtbar) und werden in PostgreSQL gespeichert (`poweron_teamsbot.TeamsbotDirectorPrompt`).
| Modus | Verhalten |
|---|---|
| `oneShot` | Einmaliger Agent-Lauf, danach Status `consumed` |
| `persistent` | Agent-Lauf wird ausgeführt **und** der Text wird als `OPERATOR_DIRECTIVES`-Block in jeden folgenden `SPEECH_TEAMS`-Trigger eingemischt, bis der Operator den Prompt löscht |
| Lifecycle-Status | Bedeutung |
|---|---|
| `queued` | Eingereicht, Agent noch nicht gestartet |
| `running` | Agent läuft |
| `succeeded` | Agent fertig, persistent bleibt aktiv |
| `consumed` | One-Shot abgeschlossen oder persistent gelöscht |
| `failed` | Agent-Lauf fehlgeschlagen, persistent wird automatisch aus aktiven Direktiven entfernt |
**Persistenz beim Reconnect:** Bei jedem WebSocket-Reconnect ruft der Service `interface.getActivePersistentPrompts(sessionId)` auf und füllt `_activePersistentPrompts` neu, damit Direktiven Network-Drops überleben.
**Limits:** `DIRECTOR_PROMPT_TEXT_LIMIT = 8000` Zeichen, `DIRECTOR_PROMPT_FILE_LIMIT = 10` UDB-Dateien (Pflicht-Prüfung in der Route, validiert auch von `TeamsbotDirectorPromptCreateRequest`).
**RBAC:** Routes prüfen `_validateInstanceAccess` + `_validateSessionOwnership`. Ein Nicht-Owner sieht 404, niemals 403, um die Existenz der Session nicht preiszugeben.
**Rate-Limit:** `30/minute` pro Operator (slowapi).
## Schicht-Trennung (Plan #5 abgeschlossen 2026-04-24)
| Verantwortung | Datei |
|---|---|
| Persistenz + Lifecycle | `interfaceFeatureTeamsbot.py` (`createDirectorPrompt`, `getActivePersistentPrompts`, `updateDirectorPrompt`, `deleteDirectorPrompt`) |
| Orchestrierung + Agent-Lauf + SSE | `service.py` (`submitDirectorPrompt`, `_processDirectorPrompt`, `_runAgentForMeeting`, `_buildPersistentDirectorContext`, `removePersistentPrompt`) |
| HTTP + RBAC + Limits | `routeFeatureTeamsbot.py` (POST/GET/DELETE `/sessions/{id}/directorPrompts`) |
| Frontend Regie-Panel + UDB-Sidebar + SSE-Listener | `frontend_nyla/src/pages/views/teamsbot/TeamsbotSessionView.tsx` + `Teamsbot.module.css` |
| Frontend API-Wrapper | `frontend_nyla/src/api/teamsbotApi.ts` (`submitDirectorPrompt`, `listDirectorPrompts`, `deleteDirectorPrompt`) |
| Tests | `gateway/tests/unit/teamsbot/test_directorPrompts.py` (26 Tests, AC 5 + 6 abgedeckt; AC 14 manuell live) |

View file

@ -0,0 +1,252 @@
<!-- status: reference -->
<!-- lastUpdated: 2026-04-24 -->
<!-- lastReviewed: 2026-04-24 -->
<!-- related: c-work/4-done/2026-04-typed-action-architecture.md, c-work/4-done/2026-04-typed-action-followups.md -->
# Node-Typisierungs-Audit (Stand 2026-04-24)
Faktenbasis fuer den abgeschlossenen Plan
[`2026-04-typed-action-architecture.md`](../4-done/2026-04-typed-action-architecture.md)
sowie den Folge-Plan
[`2026-04-typed-action-followups.md`](../4-done/2026-04-typed-action-followups.md).
Beide Plaene sind erledigt; das Audit bleibt als historische Referenz unter
`1-plan/`, weil es weiterhin die Faktenbasis fuer Drift-Diskussionen ist.
Quelle: `gateway/modules/features/graphicalEditor/nodeDefinitions/*.py`
manuell ausgelesen aus 12 Node-Definition-Dateien (53 Nodes total).
## Frage des Audits
> Haben die Nodes eine klare Logik, welche Input-Attribute mit welchem
> Format-Typ (Str, Float, UserConnection, FeatureInstanceId,
> TrusteeDatabaseObject, etc.) erforderlich sind, und ist die Ausgabe
> pro Node klar definiert?
## Antwort
**Nein.** Nur **9 von 53 Nodes** sind sauber typisiert.
| Status | Anzahl | Bedeutung |
|---|---|---|
| ✓ Klar | 9 / 53 | Input-Typen + Output-Typ klar definiert ueber Katalog-Schemas |
| ⚠ Pseudo-Typen | 21 / 53 | Funktioniert, aber `type: "string"` mit Frontend-Magic statt typisierter Schema-Typ |
| ✗ Defekt | 23 / 53 | Pflicht-Param `hidden` ODER Output ist generisches `ActionResult` ODER beides |
## Score pro Modul
| Modul | Total | ✓ | ⚠ | ✗ | Haeufigster Defekt |
|---|---|---|---|---|---|
| `trustee.*` | 5 | 0 | 0 | 5 | `featureInstanceId:string,hidden` + Output `ActionResult` |
| `redmine.*` | 6 | 0 | 0 | 6 | `featureInstanceId:string,hidden` + Output `ActionResult` |
| `sharepoint.*` | 6 | 0 | 6 | 0 | Pseudo `connectionReference` + Folder-/File-Refs |
| `clickup.*` | 6 | 0 | 6 | 0 | Pseudo `connectionReference` + List-/Team-Refs |
| `email.*` | 3 | 0 | 2 | 1 | Pseudo `connectionReference`; `draftEmail` Output `ActionResult` (statt existierendem `EmailDraft`) |
| `ai.*` | 8 | 3 | 4 | 1 | `ai.prompt` mit `documentList:hidden` + `context:hidden`; Wildcard-`accepts` |
| `input.*` | 7 | 6 | 1 | 0 | sauber; `input.review` mit Pseudo-`contentRef` |
| `flow.*` | 4 | 1 | 3 | 0 | `condition`/`value`/`items` als Pseudo-strings |
| `triggers.*` | 3 | 2 | 1 | 0 | `trigger.schedule.cron:string` |
| `data.*` | 3 | 2 | 1 | 0 | `data.filter.condition` Pseudo + Wildcard-`accepts` |
| `file.*` | 1 | 0 | 0 | 1 | `context:string,hidden` Pflicht-Inhalt nicht pickbar |
| `context.*` | 1 | 1 | 0 | 0 | sauber |
| **Summe** | 53 | 9 | 21 | 23 | |
## Vier wiederkehrende Defekt-Muster
### Muster 1 — `featureInstanceId: string, hidden` als Pflicht-Param
**Vorkommen:** 11x (5 trustee + 6 redmine)
**Code heute:**
```python
{"name": "featureInstanceId", "type": "string", "required": True,
"frontendType": "hidden", "description": t("Trustee Feature-Instanz-ID")},
```
**Konsequenz:** User kann den Pflicht-Param nicht setzen. Engine raet
ihn aus `WorkflowFeatureContext` oder Session — bricht still bei
Mehr-Mandant-Setups.
**Soll:**
```python
{"name": "target", "type": "FeatureInstanceRef[trustee]", "required": True,
"ui": {"label": "Trustee-System"}},
```
mit neuem Katalog-Typ in `portTypes.py`:
```python
"FeatureInstanceRef": PortSchema(name="FeatureInstanceRef", fields=[
PortField(name="featureCode", type="str", description="trustee | redmine | clickup | ..."),
PortField(name="id", type="str", description="FeatureInstance.id"),
PortField(name="label", type="str", required=False),
]),
```
Picker-Optionen kommen vom neuen Endpoint `/options/featureInstance?featureCode=trustee&mandateId=…` (analog zu `user.connection`).
---
### Muster 2 — `connectionReference: string, frontendType: userConnection`
**Vorkommen:** 15x (6 sharepoint + 6 clickup + 3 email)
**Code heute:**
```python
{"name": "connectionReference", "type": "string", "required": True,
"frontendType": "userConnection",
"frontendOptions": {"authority": "msft"},
"description": t("SharePoint-Verbindung")},
```
**Konsequenz:** Type ist string — der Picker im Backend kennt das
Konzept "ConnectionRef" nicht. Adapter im Frontend macht einen
Spezialfall fuer `frontendType: "userConnection"`. Wenn ein Folge-Node
auch eine Connection braucht, kann der Output keiner Vorgaenger-Node
sie liefern, weil Connection nirgends als Output deklariert ist.
**Soll:**
```python
{"name": "connection", "type": "ConnectionRef[msft]", "required": True,
"ui": {"label": "SharePoint-Verbindung"}},
```
`ConnectionRef` existiert bereits im Katalog — wird heute aber nur
implizit benutzt (im `DocumentList.connection`-Subfeld). Mit
`ConnectionRef[authority]` als Discriminator wird die Authority
deterministisch gefiltert.
---
### Muster 3 — Output `ActionResult` statt typed Schema
**Vorkommen:** 17x
| Node | Heute | Soll-Schema |
|---|---|---|
| `trustee.refreshAccountingData` | `ActionResult` | `TrusteeRefreshResult { syncCounts, dateRange, errors }` |
| `trustee.processDocuments` | `ActionResult` | `TrusteeProcessResult { documents: List[Document], errors: List[ProcessError] }` |
| `trustee.syncToAccounting` | `ActionResult` | `TrusteeSyncResult { synced: int, failed: int, journalLines: List[JournalLine] }` |
| `trustee.queryData` | `ActionResult` | `QueryResult { rows, columns, count }` (Schema **existiert** im Katalog!) |
| `email.draftEmail` | `ActionResult` | `EmailDraft { subject, body, to, cc, attachments, connection }` (Schema **existiert** im Katalog!) |
| `redmine.readTicket` | `ActionResult` | `RedmineTicket { id, subject, status, tracker, assignee, ... }` (neu) |
| `redmine.listTickets` | `ActionResult` | `RedmineTicketList { tickets: List[RedmineTicket], count, filters }` (neu) |
| `redmine.createTicket` | `ActionResult` | `RedmineTicket` |
| `redmine.updateTicket` | `ActionResult` | `RedmineTicket` |
| `redmine.getStats` | `ActionResult` | `RedmineStats { kpis, throughput, statusDistribution, backlog }` (neu) |
| `redmine.runSync` | `ActionResult` | `TrusteeRefreshResult`-aehnlich oder bleibt `ActionResult` (fire-and-forget) |
| `sharepoint.uploadFile` | `ActionResult` | `SharePointFileRef` (Upload-Ziel als Ref) oder bleibt |
| `sharepoint.copyFile` | `ActionResult` | `SharePointFileRef` (neue Datei-Position) |
| `clickup.uploadAttachment` | `ActionResult` | `TaskAttachmentRef` (neu) oder bleibt |
| `file.create` | `DocumentList` | OK, bleibt (1 Document drin) |
**Konsequenz heute:** Folge-Nodes koennen keine spezifischen Felder
pickrn. Workflow-Bauer muss raten, was im `data: Dict`-Black-Box ist.
**Soll:** Pro Action ein typisiertes Schema im Katalog. `ActionResult`
bleibt nur fuer echte fire-and-forget-Aktionen (z.B. `runSync`).
---
### Muster 4 — Wildcard-`accepts` mit ≥ 4 Typen inkl. `Transit`
**Vorkommen:** 8x
| Node | Heute `accepts` | Problem |
|---|---|---|
| `trustee.extractFromFiles` | `[DocumentList, Transit, AiResult, LoopItem, ActionResult]` | Was wenn `AiResult` reinkommt? Heuristik im Engine raet, was wo befuellt wird |
| `ai.prompt` | `[DocumentList, AiResult, TextResult, Transit, LoopItem, ActionResult]` | Pflicht-Param `documentList:hidden` wird aus dem Wire heuristisch befuellt |
| `email.draftEmail` | `[EmailDraft, AiResult, Transit, ConsolidateResult, DocumentList]` | Wie wird `subject/body/to` aus `AiResult` oder `ConsolidateResult` abgeleitet? |
| `flow.loop` | `[Transit, UdmDocument, EmailList, DocumentList, FileList, TaskList, ActionResult]` | Loop ueber `ActionResult`? Was iteriert dann? |
| `data.filter` | `[AggregateResult, FileList, TaskList, EmailList, DocumentList, UdmDocument, UdmNodeList]` | OK — alle sind Listen-aehnlich, aber kein Pflicht-Output-Schema |
| `ai.summarize/translate/convert` | `[DocumentList, Transit]` | `Transit` als generisches "irgendwas" |
| `data.aggregate` | `[Transit, AiResult, LoopItem]` | Loop-Body Output kann alles sein |
| `data.consolidate` | `[AggregateResult, Transit]` | `Transit` |
**Konsequenz:** Wires bringen "irgendwas" vorbei, Backend muss
heuristisch zuordnen. Keine deterministische Validierung im Frontend
oder Backend moeglich.
**Soll:** `accepts` enthaelt **exakt** den Schema-Typ, der vom
Pflicht-Input-Param erwartet wird. Wenn mehrere Modi existieren (z.B.
`flow.loop` ueber Liste oder Dokument), wird das ueber ein typisiertes
**Union** im Katalog ausgedrueckt — nicht ueber eine Wildcard-Liste.
---
## Schemas die im Katalog existieren aber nirgends benutzt werden
Diese sind heute schon im `PORT_TYPE_CATALOG` deklariert, werden aber
von keiner Node-Definition als Output verwendet:
| Schema | Sollte Output sein von | Aktuell Output |
|---|---|---|
| `EmailDraft` | `email.draftEmail` | `ActionResult` |
| `QueryResult` | `trustee.queryData` | `ActionResult` |
| `TaskItem` (alleinstehend, nicht List) | `clickup.getTask` | `TaskResult` (OK, aber inkonsistent) |
| `UdmPage`, `UdmBlock`, `UdmNodeList` | sind nur `accepts`-Eintraege fuer `data.filter`, kein Producer | — |
**Konsequenz:** Schon das Aktivieren der existierenden Schemas wuerde
2 der ✗-Faelle ohne neue Katalog-Eintraege loesen.
---
## Schemas die fehlen und neu gebraucht werden
| Schema | Brauchen wir fuer |
|---|---|
| `FeatureInstanceRef` (mit Discriminator) | 11x — alle trustee + redmine Nodes |
| `ConnectionRef[authority]`-Discriminator | 15x — sharepoint + clickup + email |
| `TrusteeRefreshResult` | `trustee.refreshAccountingData` |
| `TrusteeProcessResult` | `trustee.processDocuments` |
| `TrusteeSyncResult` | `trustee.syncToAccounting` |
| `RedmineTicket` | `redmine.readTicket/createTicket/updateTicket` |
| `RedmineTicketList` | `redmine.listTickets` |
| `RedmineStats` | `redmine.getStats` |
| `JournalLine`, `Account`, `Tenant` | Drill-Down in Trustee-Outputs |
| `ProcessError` | Fehler-Listen in `*Result`-Schemas |
| `CronExpression` | `trigger.schedule` |
| `ConditionExpression` | `flow.ifElse`, `data.filter` |
| `PromptTemplateRef` | optional — wenn Prompt-Library kommt |
Total: **~13 neue Katalog-Schemas**, davon 2 mit Discriminator
(`FeatureInstanceRef`, `ConnectionRef`).
---
## Pflicht-Param-Konzepte die heute als `frontendType` versteckt sind
Diese laufen heute als Frontend-only-Magic mit `type: "string"` und
`frontendType: "<custom>"`. Sie sollten echte Katalog-Typen werden:
| `frontendType` | Vorkommen | Soll-Katalog-Typ |
|---|---|---|
| `userConnection` | 15x | `ConnectionRef[authority]` |
| `sharepointFolder` | 4x | `SharePointFolderRef` (existiert!) |
| `sharepointFile` | 3x | `SharePointFileRef` (existiert!) |
| `clickupList` | 3x | `ClickUpListRef` (neu) |
| `hidden` (als Pflicht) | 11x | meist `FeatureInstanceRef`, manchmal `DataRef`-Kontext-Param |
| `dataRef` | 2x (kuerzlich gefixt) | typed Param mit `accepts` matchend |
| `fieldBuilder` | 2x | OK — bleibt UI-Hint, Output-Schema dynamisch |
| `attachmentBuilder` | 1x | `List[AttachmentSpec]` (neu) |
| `condition`, `filterExpression`, `caseList` | 4x | `ConditionExpression` |
| `cron` | 1x | `CronExpression` |
---
## Folgerung fuer den Architektur-Plan
Diese Audit-Daten bestaetigen den Plan
[`2026-04-typed-action-architecture.md`](../3-validate/2026-04-typed-action-architecture.md):
1. **Schicht 1** (Katalog) muss zuerst um ~13 Schemas erweitert werden.
2. **Schicht 2** (Methods/Actions) muss `WorkflowActionParameter.type`
auf Katalog-Typen umstellen — beseitigt automatisch Muster 1
(`hidden`-Pflicht-Params).
3. **Schicht 3** (Adapter) ersetzt das parallele
`frontendType`-Schatten-System durch echte Typen — beseitigt
Muster 2 (Pseudo-`connectionReference`).
4. **Outputs typisieren** beseitigt Muster 3 (Output
`ActionResult`-Black-Box).
5. **`accepts` strikt** beseitigt Muster 4 (Wildcards).
Der Aufwand ist nicht trivial, aber er beseitigt **alle 4
Defekt-Muster** in einem Refactor — kein einzelner Workaround.

View file

@ -0,0 +1,120 @@
<!-- status: done -->
<!-- started: 2026-04-24 -->
<!-- moved-to-4-done: 2026-04-24 -->
<!-- component: gateway -->
# Adapter Drift Cleanup — abgeschlossen
## Beschreibung und Kontext
Phase 3 der [Typed Action Architecture](../3-validate/2026-04-typed-action-architecture.md)
hat den `adapterValidator` (CI-Saftynet) gebaut. Beim ersten scharfen Lauf
gegen `STATIC_NODE_TYPES` + die live `Method`-Registry hat er **26 reale
Drifts** zwischen Editor-Node-Adaptern (Schicht 3) und Action-Signaturen
(Schicht 2) erkannt.
Plan #4 hat alle 26 Drifts behoben. `_KNOWN_ADAPTER_DRIFTS` ist jetzt
`frozenset()` und der Snapshot-Test verlangt strikt
`assert report.errors == []`.
---
## Ergebnis
| Status | Wert |
|---|---|
| Drifts vor Cleanup | 26 |
| Drifts nach Cleanup | **0** |
| `_KNOWN_ADAPTER_DRIFTS` | `frozenset()` |
| `test_staticNodesHaveNoDriftAgainstLiveMethods` | `assert report.errors == []` |
| Test-Sweep | 428/428 Unit-Tests grün |
---
## Vorgehen pro Drift-Kategorie
### A) Felder umbenennen (UI ↔ Action-Arg-Namen)
Die UI hatte die richtige Semantik aber falsche Feldnamen.
| Adapter Node | Vorher | Nachher |
|---|---|---|
| `ai.prompt` | `outputFormat` | `resultType` (mit Action-Optionen) |
| `ai.generateCode` | `language` | `resultType` (Datei-Endungen wie `py`, `js`, …) |
| `ai.summarizeDocument` | `summaryLength` `["short", "medium", "long"]` | `["brief", "medium", "detailed"]` (Action-Vokabular) |
### B) UI-Felder ohne Action-Backing entfernt
Die UI exponierte Felder, die das Action gar nicht akzeptiert. Statt einen
neuen Composite-UI-Mechanismus im Adapter-Layer zu bauen (out of scope), wurden
die UI-Felder entfernt — User füllen das echte Action-Arg direkt aus.
| Adapter Node | Entferntes UI-Feld | Wo Funktionalität bleibt |
|---|---|---|
| `ai.prompt` | `context` | via Wire (DocumentList → Input) |
| `email.checkEmail` | `fromAddress`, `subjectContains`, `hasAttachment` | konsolidiert in `filter` |
| `email.searchEmail` | `filter`, `fromAddress`, `toAddress`, `subjectContains`, `bodyContains`, `hasAttachment` | konsolidiert in `query` (jetzt required) |
| `email.draftEmail` | `subject`, `body` | via `context` (KI-Komposition) ODER `emailContent` (Direkt-Override) |
| `email.draftEmail` | `attachments` | umgestellt auf `documentList` |
| `clickup.createTask` | `teamId` | bereits über `pathQuery`/`listId` identifizierbar |
| `clickup.updateTask` | `taskUpdateEntries` (keyValueRows) | konsolidiert in `taskUpdate` (JSON) |
| `context.extractContent` | `outputDetail`, `includeImages`, `includeTables` | konsolidiert in `extractionOptions` (JSON) |
### C) Pflicht-Action-Args als hidden DataRef-Felder ergänzt
Die Action verlangt einen Pflicht-Parameter, aber der Adapter exponierte ihn
weder als `userParams.actionArg` noch als `contextParams`. Lösung: das Feld
als `frontendType: hidden` ergänzt, sodass der DataPicker es bedienen kann
und der Validator es als gemappt anerkennt.
| Adapter Node | Hinzugefügtes Feld |
|---|---|
| `ai.summarizeDocument` | `documentList` (hidden, required) |
| `ai.translateDocument` | `documentList` (hidden, required) |
| `ai.convertDocument` | `documentList` (hidden, required) |
| `sharepoint.uploadFile` | `content` (hidden, required) |
| `clickup.uploadAttachment` | `content` (hidden, required) |
| `context.extractContent` | `documentList` (hidden, required) |
### D) Actions ohne Editor-Adapter (Warnings — bewusst belassen)
| Action | Begründung |
|---|---|
| `context.neutralizeData` | wird bewusst nicht im Editor angeboten — Backend-only Pre-Processing |
| `context.triggerPreprocessingServer` | wird bewusst nicht im Editor angeboten — Server-Op, kein Editor-UX |
Beide sind Warnings, keine Errors. Wenn später benötigt, einfach auf
`dynamicMode=True` umstellen oder Editor-Node bauen.
---
## Akzeptanzkriterien
| # | Kriterium | Status |
|---|---|---|
| 1 | Jede Zeile in den Drift-Tabellen hat einen abgeschlossenen Fix | DONE |
| 2 | `_KNOWN_ADAPTER_DRIFTS` ist `frozenset()` | DONE |
| 3 | `test_staticNodesHaveNoDriftAgainstLiveMethods` auf `assert report.errors == []` umgestellt | DONE |
| 4 | Dokument nach `c-work/4-done/` verschoben | DONE |
---
## Berührte Dateien
- `gateway/modules/features/graphicalEditor/nodeDefinitions/ai.py`
- `gateway/modules/features/graphicalEditor/nodeDefinitions/email.py`
- `gateway/modules/features/graphicalEditor/nodeDefinitions/clickup.py`
- `gateway/modules/features/graphicalEditor/nodeDefinitions/sharepoint.py`
- `gateway/modules/features/graphicalEditor/nodeDefinitions/context.py`
- `gateway/tests/unit/graphicalEditor/test_adapter_validator.py`
Keine Action-Implementierungen geändert → kein Runtime-Regressionsrisiko.
---
## Links
- Architektur-Plan: [../3-validate/2026-04-typed-action-architecture.md](../3-validate/2026-04-typed-action-architecture.md)
- Validator: `gateway/modules/features/graphicalEditor/adapterValidator.py`
- Snapshot-Test: `gateway/tests/unit/graphicalEditor/test_adapter_validator.py`
- Konsolidierter Folge-Plan: [../1-plan/2026-04-typed-action-followups.md](../1-plan/2026-04-typed-action-followups.md)

View file

@ -0,0 +1,154 @@
<!-- status: done -->
<!-- started: 2026-04-23 -->
<!-- completed: 2026-04-24 -->
<!-- component: gateway, frontend-nyla, teams-bot -->
<!--
Vollstaendig umgesetzt: Datamodel, Interface-CRUD, _activeServices-Registry,
submit/_processDirectorPrompt + _runAgentForMeeting, Reconnect-Persistenz,
Hybrid-Routing (needsAgent), SystemPrompt mit Eskalations-Regeln, Routes
POST/GET/DELETE mit RBAC + Limits + Rate-Limit, Frontend-API + SSE-Listener
+ UDB-Sidebar + Regie-Panel + CSS. 26 Backend-Unit-Tests gruen
(gateway/tests/unit/teamsbot/test_directorPrompts.py). AC 5 + 6 abgedeckt;
AC 1-4 sind manuelle Live-Meeting-Tests.
b-reference: teams-bot/architecture.md (Hybrid + Director Prompts) und
gateway/ai-agent.md (Teamsbot-Integration, kein eigenes Toolset) sind
aktualisiert; TOPICS.md gepflegt.
-->
# Teamsbot Regieanweisungen (Director Prompts)
## Beschreibung und Kontext
Der Teamsbot laeuft heute auf einem hochspezialisierten, schnellen Pfad
(`OperationTypeEnum.SPEECH_TEAMS`), der reaktiv auf Sprache reagiert.
Der Operator soll dem Bot waehrend des Meetings *privat* Anweisungen geben
koennen ("Recherchiere das gerade diskutierte Thema und gib eine Empfehlung",
"Lies die Datei XY vor", "Schreib das ins Chat"). Diese **Regieanweisungen**
sind fuer die anderen Teilnehmer unsichtbar und triggern den Bot sofort,
unabhaengig von Cooldowns oder Sprach-Triggern.
Long-term-clean: Voller Agent-Pfad mit RAG, Datenschnittstellen, Toolboxen.
## Fokus und kritische Details
- **Hybrid-Routing**: `SPEECH_TEAMS` bleibt der schnelle Default-Pfad.
Der Agent (`agentService.runAgent`) wird nur fuer (a) Director Prompts und
(b) `SPEECH_TEAMS`-Eskalation (`needsAgent=true`) verwendet.
- **Privacy**: Director-Prompts werden in der DB gespeichert und ueber SSE
ausschliesslich an den Operator (Eigentuemer der Session) ausgeliefert.
- **Kein neues Teamsbot-Toolset**: Der Agent verwendet bestehende Core-Tools
(`webSearch`, `readUrl`, `sendMail`, `renderDocument`, `requestToolbox`,
Datenschnittstellen-Tools etc.). Sein finaler `FINAL`-Event-Text wird wie
eine `SPEECH_TEAMS`-Antwort ueber TTS+Chat ans Meeting ausgeliefert.
- **Persistenz**: Persistente Director-Prompts werden bei jedem Trigger
(SPEECH_TEAMS und neuer Director-Prompt) als zusaetzlicher Kontext in den
System-Prompt gemischt, bis der Operator sie loescht.
- **Service-Registry**: Damit eine HTTP-Route auf die laufende WS-Session
zugreifen kann, registriert `handleBotWebSocket` den `TeamsbotService` mit
`websocket` + `voiceInterface` modul-weit.
## Ziel und Nicht-Ziele
- Ziel: Operator kann private Text-Prompts (mit optional UDB-Files) an den
laufenden Bot senden; Bot fuehrt Agent-Run aus, antwortet ins Meeting per
TTS/Chat und kann Tools (Web, Mail, SharePoint, ...) nutzen.
- Ziel: SPEECH_TEAMS kann selbststaendig signalisieren `needsAgent=true`,
damit komplexe Spoken-Requests (z. B. "recherchier das im Internet") den
Agent triggern, statt nur eine Floskel zurueckzugeben.
- Nicht-Ziel: Eigene Teamsbot-spezifische Tools fuer den Agent (sendChat,
readAloud) — diese bleiben im SPEECH_TEAMS-Pfad als `commands`. Der Agent
spricht nur ueber seinen Final-Text, der vom Service ausgeliefert wird.
- Nicht-Ziel: PDF-Anhang in Teams-Chat (Browser-Bot-Limit). Stattdessen wird
der vom Agent generierte Link gepostet.
## Betroffene Module
- Gateway: `modules/features/teamsbot/datamodelTeamsbot.py`,
`interfaceFeatureTeamsbot.py`, `service.py`, `routeFeatureTeamsbot.py`,
`modules/serviceCenter/services/serviceAi/mainServiceAi.py`
- Frontend: `src/api/teamsbotApi.ts`, `src/pages/views/teamsbot/TeamsbotSessionView.tsx`,
`src/pages/views/teamsbot/Teamsbot.module.css`
- DB-Migration: ja (neue Tabelle `teamsbotDirectorPrompts` via `registerDatabase`/Auto-Migrate)
- Andere: `service-teams-browser-bot` — keine Aenderungen (Agent nutzt
bestehende WS-Commands `sendChatMessage`, `playAudio`).
## Architektur
```mermaid
flowchart LR
UI["Operator UI (Regie-Panel + UDB)"] -- POST directorPrompt --> Route["routeFeatureTeamsbot"]
Route -- submitDirectorPrompt --> Svc["TeamsbotService (active)"]
Svc -- runAgent --> Agent["agentService.runAgent (toolSet=core, web aktiv, maxRounds=5)"]
Agent -- FINAL text --> Svc
Svc -- TTS+sendChatMessage --> WS["Browser Bot (WS)"]
WS -- audio/chat --> Meeting
Audio["Meeting Audio"] --> STT["STT"]
STT --> SpeechTeams["SPEECH_TEAMS (fast)"]
SpeechTeams -- needsAgent? --> Agent
SpeechTeams -- direct text/commands --> Svc
```
## Entscheidungen
| Datum | Entscheidung | Begruendung |
|------------|-------------------------------------------------|-------------|
| 2026-04-23 | Hybrid-Routing SPEECH_TEAMS + Agent | Latenz fuer Reaktivitaet, Funktionalitaet fuer Komplexes |
| 2026-04-23 | Kein eigenes Teamsbot-Toolset im Agent | Agent-Final-Text wird wie SPEECH_TEAMS-Text ausgeliefert; vermeidet Tool-Duplikation |
| 2026-04-23 | `web`-Toolbox als Default + `requestToolbox` aktiv | Recherche ist Standard, andere Toolboxen on-demand |
| 2026-04-23 | `maxRounds=5`, `maxCostCHF=0.10` | Live-Performance + Kosten-Cap |
| 2026-04-23 | UDB-Sidebar wie in CommcoachDossierView | Bestehendes Muster, Files via fileIds |
## Umsetzungs-Checkliste
- [x] Datamodel `TeamsbotDirectorPrompt` + `SpeechTeamsResponse.needsAgent`
- [x] Interface CRUD + DB-Registrierung
- [x] `_activeServices` Registry + WS/Voice auf Instanz
- [x] `submitDirectorPrompt` + `_processDirectorPrompt` (Agent-Call + Delivery)
- [x] Persistente Prompts beim Reconnect aus DB laden
- [x] Hybrid: `_analyzeAndRespond` triggert Agent bei `needsAgent=true`
- [x] `_buildSpeechTeamsSystemPrompt` um `needsAgent`-Felder erweitern
- [x] Routes `POST/GET/DELETE /sessions/{id}/directorPrompts`
- [x] Frontend API + SSE-Event-Type
- [x] Frontend UDB-Sidebar + Regie-Panel + CSS
- [x] RBAC: nur Session-Owner darf submitten/listen/loeschen (`_validateInstanceAccess` + `_validateSessionOwnership`)
- [x] Neutralisierung: Director-Prompt-Text geht durch Standard-AiService -> automatisch im Scope
- [x] Navigation: keine neue Route, nur Panel in TeamsbotSessionView
- [x] Billing: laeuft ueber `agentService.runAgent` (Standard-Billing) und `aiService.callAi` (SPEECH_TEAMS Billing); kein neuer Pfad
- [x] Unit-Tests (26 Tests, T5 + AC 5 + AC 6 abgedeckt)
- [x] b-reference + TOPICS.md aktualisiert
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|-----------------------------|------|
| 1 | Given laufendes Meeting, When Operator sendet One-Shot Prompt "Was ist die Hauptstadt von Frankreich?", Then Bot antwortet im Meeting per Voice "Paris" innerhalb 10s | must |
| 2 | Given laufendes Meeting + UDB-File ausgewaehlt, When Operator sendet Prompt "Fasse das ausgewaehlte Dokument zusammen", Then Bot liest Datei via `readFile`, fasst zusammen, antwortet Voice+Chat | must |
| 3 | Given laufendes Meeting, When Operator sendet Persistent-Prompt "Antworte immer in Englisch", Then jede folgende SPEECH_TEAMS-Antwort ist auf Englisch, bis Operator den Prompt loescht | must |
| 4 | Given Sprecher sagt "Nyla, recherchier was im Internet ueber SBB Schweiz", When SPEECH_TEAMS verarbeitet, Then `needsAgent=true` und Agent uebernimmt mit `webSearch`, antwortet ueber TTS | must |
| 5 | Given Operator sendet Prompt mit fileIds=[A,B,C], When Limit DIRECTOR_PROMPT_FILE_LIMIT (10) ueberschritten oder Text > 8000 Zeichen, Then Route gibt 400 zurueck | must |
| 6 | Given anderer User (nicht Owner) ruft GET /directorPrompts auf, Then 404 | must |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|---------------|-----------|--------|
| T1 | 1 | manual e2e | nein | live meeting | pending (manual) |
| T2 | 2 | manual e2e | nein | live meeting | pending (manual) |
| T3 | 3 | manual e2e | nein | live meeting | pending (manual) |
| T4 | 4 | manual e2e | nein | live meeting | pending (manual) |
| T5 | 5,6| api unit | ja | gateway/tests/unit/teamsbot/test_directorPrompts.py | done (26 Tests) |
## Links
- PR: TBD
- Issue: TBD
## Abschluss
- [x] `wiki/b-reference/teams-bot/architecture.md` (Director Prompts + Hybrid + Tool-Set)
- [x] `wiki/b-reference/gateway/ai-agent.md` (kein neues Teamsbot-Toolset, Hybrid-Routing)
- [x] `wiki/TOPICS.md` (Teams-Bot-Eintrag um Director Prompts ergaenzt + neuer Aktive-Arbeiten-Eintrag)
- [x] Diesen Plan nach `4-done/` verschieben

View file

@ -0,0 +1,109 @@
# Trustee ActionResult schema alignment + DataPicker white-screen fix
Status: done
Date: 2026-04-24
Scope: gateway (trustee adapter, method definition, port catalog, tests),
frontend_nyla (FlowEditor / DataPicker)
## Symptoms reported
- Trustee nodes in the expense-receipt workflow ("Spesenbeleg") could not
recognise their input data: opening the DataRef picker for
`processDocuments.documentList` or `syncToAccounting.documentList` showed no
compatible upstream candidates even though `extractFromFiles` /
`processDocuments` were obviously connected upstream.
- Setting any parameter on the `trustee.syncToAccounting` node caused the
whole graph editor to white-screen.
## Root causes
Two independent bugs collided on the same UI path.
### 1. Adapter drift around `ActionResult.documents`
Every trustee action returns `ActionResult.isSuccess(documents=[...])` at
runtime (see `gateway/modules/datamodels/datamodelChat.py` and the three
action implementations). However the typed-action surface disagreed in three
places:
| Layer | What it said | What it should say |
| --- | --- | --- |
| `nodeDefinitions/trustee.py::extractFromFiles.outputPorts[0].schema` | `DocumentList` | `ActionResult` |
| `methodTrustee.py::extractFromFiles.outputType` | `DocumentList` | `ActionResult` |
| `methodTrustee.py::processDocuments.outputType` | `TrusteeProcessResult` | `ActionResult` |
| `methodTrustee.py::syncToAccounting.outputType` | `TrusteeSyncResult` | `ActionResult` |
| `portTypes.py::PORT_TYPE_CATALOG['ActionResult']` | only `success / error / data` | also `documents: List[ActionDocument]` |
| `portTypes.py::PORT_TYPE_CATALOG['ActionDocument']` | not registered | new `PortSchema` with `documentName / documentData / mimeType / fileId / fileName` |
| `nodeDefinitions/trustee.py::processDocuments.parameters.documentList.type` | `DocumentList` | `List[ActionDocument]` (the concrete shape `_resolveDocumentList` consumes) |
| `nodeDefinitions/trustee.py::syncToAccounting.parameters.documentList.type` | `DocumentList` | `List[ActionDocument]` |
| `nodeDefinitions/trustee.py::processDocuments.inputPorts[0].accepts` | `DocumentList / Transit` | also `ActionResult` |
Because the catalog `ActionResult` schema had no `documents` field, the
DataPicker's `_buildPathsFromSchema` could not surface the canonical
`upstream → documents → * → documentName` path. Strict-filter then
correctly rejected every other candidate, so the user saw "no input data".
### 2. Rules-of-Hooks violation in `DataPicker`
`frontend_nyla/src/components/FlowEditor/nodes/shared/DataPicker.tsx` called
`useMemo(... loopAncestorIds ...)` *after* the `if (!open) return null;`
early return. As soon as the picker opened (e.g. when clicking a parameter
on the sync node), React saw "rendered more hooks than during the previous
render", threw, and there is no `ErrorBoundary` around the canvas, so the
whole tree unmounted to a white screen.
## Fix
- `frontend_nyla/src/components/FlowEditor/nodes/shared/DataPicker.tsx`
- Moved both `useMemo` calls (`connections`, `loopAncestorIds`) **above**
the `if (!open) return null;` guard.
- Added a comment block explaining why hooks must stay above the early
return so the next refactor doesn't reintroduce the bug.
- `gateway/modules/features/graphicalEditor/portTypes.py`
- Added `ActionDocument` `PortSchema` (mirrors
`datamodelChat.ActionDocument`).
- Added `documents: List[ActionDocument]` field on the `ActionResult`
schema so the DataPicker can drill into it.
- `gateway/modules/features/graphicalEditor/nodeDefinitions/trustee.py`
- `extractFromFiles.outputPorts[0].schema = "ActionResult"`.
- Both consumer params (`processDocuments.documentList`,
`syncToAccounting.documentList`) typed as `List[ActionDocument]`.
- `processDocuments.inputPorts[0].accepts` includes `ActionResult`.
- `gateway/modules/workflows/methods/methodTrustee/methodTrustee.py`
- `extractFromFiles.outputType = "ActionResult"`.
- `processDocuments.outputType = "ActionResult"`,
`documentList.type = "List[ActionDocument]"`.
- `syncToAccounting.outputType = "ActionResult"`,
`documentList.type = "List[ActionDocument]"`.
## Verification
- `gateway/tests/unit/nodeDefinitions/test_trustee_schema_compliance.py`
rewritten to enforce the new alignment plus two new regressions
(`test_catalog_ActionResult_exposes_documents_field`,
`test_catalog_ActionDocument_is_registered`).
- `python -m pytest gateway/tests/unit/graphicalEditor/
gateway/tests/unit/nodeDefinitions/
gateway/tests/unit/workflow/test_trusteeQueryData.py` → 112 passed.
Includes `test_staticNodesHaveNoDriftAgainstLiveMethods`, the strict
drift gate added during the Phase-4 cleanup.
- `npx vitest run src/components/FlowEditor/` → 32 passed (DataPicker,
RequiredAttributePicker, paramValidation, CanvasHeader).
## Why the existing adapterValidator did not catch this
Adapter rules 1-5 only check that adapter-declared parameters / output
types resolve in the catalog and that required action params are covered.
They do **not** check whether the catalog schema is structurally complete
(e.g. "ActionResult declares `documents` because the runtime emits it").
Capturing that would require a runtime/return-value introspection pass —
tracked as a follow-up in `local/notes/issues.md` if the picker keeps
losing fields.
## Pick-not-Push contract recap
Producer always emits `ActionResult { success, error, documents, data }`.
Consumers bind their `documentList`-style param via DataRef to
`upstream → documents` (or `documents → *` when iterating in a loop). No
auto-wire, no implicit aliasing — the binding is fully visible in the
editor via `DataRefRenderer`.

View file

@ -0,0 +1,528 @@
<!-- status: done -->
<!-- started: 2026-04-24 -->
<!-- moved-to-3-validate: 2026-04-24 -->
<!-- moved-to-4-done: 2026-04-24 -->
<!-- component: gateway, frontend-nyla -->
<!--
Phase 15 sind code-fertig (237 Backend-Tests gruen, FE TypeScript-Compile
sauber). Verbleibende Folge-Schritte (FE-Test-Setup, Trustee Live-E2E,
Adapter-Drift-Cleanup, b-reference-Sync, Doc-Lifecycle) sind konsolidiert
in: ../1-plan/2026-04-typed-action-followups.md
Dieses Dokument bleibt im 3-validate/-Stadium, bis die Folge-Schritte
abgeschlossen sind — dann nach 4-done/ und spaeter z-archive/.
-->
# Typed Action Architecture — 4-Schichten-Modell fuer deterministische Workflows
## Beschreibung und Kontext
Im Graphical Editor (Workflows) und im AI-Agent-Tooling laufen heute **mehrere
parallele, unkoordinierte Type-Welten**, was zu Workflow-Bugs (z.B. Trustee
Spesenbelege), nicht-pickbaren Pflicht-Inputs (`hidden`) und
Heuristik-Befuellung von Parametern fuehrt.
Diese Plan-Seite beschreibt **eine** klare 4-schichtige Architektur, die
deterministisch macht, welche Datentypen es gibt, welche Backend-Funktionen
sie produzieren/konsumieren, wie sie als UI-Bausteine (Nodes / AI-Tools)
exponiert werden und wie Workflow-Instanzen sie verkabeln.
**Business-Treiber:** Workflows werden nur robust, wenn jeder Pflicht-Input
explizit, typisiert und pickbar ist. Heute scheitern Trustee-Workflows still,
AI-Agent-Tools laufen mit unscharfen Parametern, und die UI-Logik im Editor
ist nicht erklaerbar. Long-term blockiert das auch AI-generierte Workflows.
**Risiko ohne Umsetzung:** Stille Bugs in komplexen Workflows, anhaltende
Heuristik-Wartung, kein verlaesslicher AI-Agent-Aufruf, unmoeglich Workflows
maschinell zu validieren oder zu generieren.
**Voraussetzung:** Ersetzt den vorherigen Plan
`2026-04-required-attribute-picker.md`. Der Picker wird zu einer **trivialen
Folge** dieser Architektur, nicht zu deren Treiber.
---
## Heute — Mindmap der Code-Realitaet
```mermaid
mindmap
root((Workflow-Code<br/>HEUTE))
Datentypen
PORT_TYPE_CATALOG
gateway/modules/features/graphicalEditor/portTypes.py
Schemas: ConnectionRef, DocumentList, EmailDraft,<br/>FileList, AiResult, ActionResult, ...
Nur fuer Output-Ports + accepts genutzt
NICHT fuer Input-Param-Typen
Pseudo-Typen-Strings
type "string" / "number" / "boolean" / "json"
ueberall im Code parallel
Keine Verbindung zum Katalog
WorkflowActionParameter
gateway/modules/datamodels/datamodelWorkflowActions.py
Pydantic-Modell fuer Action-Argumente
Eigene Type-Strings, nicht zentral abgeglichen
Backend-Funktionen
Methods
gateway/modules/workflows/methods/methodTrustee/
methodSharepoint, methodOutlook, methodAi,<br/>methodClickup, methodRedmine, methodFile, ...
Jede Method-Klasse hat _actions Dict
Action-Definition WorkflowActionDefinition
@action Decorator markiert Methoden
Action-Discovery
modules/workflows/processing/shared/methodDiscovery.py
Sammelt alle Methods + Actions zur Laufzeit
Konsumenten der Actions
Workflow-Engine
modules/workflows/automation2/executionEngine.py
Ruft Actions ueber actionExecutor.executeAction
Engine kennt nur ActionResult / Transit
AI-Agent
modules/serviceCenter/services/serviceAgent/
ActionToolAdapter wrappt Actions als Tools
nur Actions mit dynamicMode True
Konvertiert Param-Typen JSON-Schema heuristisch
Graphical-Editor-Nodes
modules/features/graphicalEditor/nodeDefinitions/*.py
STATIC_NODE_TYPES manuell gepflegt
DUPLIZIERT Action-Signatur in Node-Form
DRIFT-RISIKO sehr hoch
Nutzt PORT_TYPE_CATALOG nur fuer Outputs / accepts
Pflicht-Params oft als hidden deklariert
```
**Beobachtung aus dem Mindmap:**
Die Methods/Actions sind die einzige verlaessliche Quelle dessen, was das
Backend kann. Aber drei Konsumenten (Engine, Agent, Node-Editor) bauen
**jeweils eigene** Sichtweisen darauf — und die sind nicht miteinander
abgeglichen. Das ist die Wurzel der Konfusion.
---
## Ziel — das 4-Schichten-Modell (Soll)
Jede Schicht weiss nur von der Schicht **ueber sich**. Macht das System
deterministisch und auditierbar.
```mermaid
flowchart TB
subgraph S1["Schicht 1 — TYPEN-KATALOG"]
direction TB
PRIM["Primitive<br/>Str, Int, Bool, Float, DateTime, Url"]
REF["Refs<br/>ConnectionRef, FeatureInstanceRef[trustee|...],<br/>SharePointFolderRef, PromptTemplateRef"]
REC["Records<br/>Document, EmailItem, Task, RedmineTicket,<br/>JournalLine, Account, ..."]
LST["Lists<br/>List[Document], List[Email], List[Task]"]
RET["ActionReturns<br/>TrusteeProcessResult, EmailDraft,<br/>QueryResult, RedmineTicketList, ..."]
end
subgraph S2["Schicht 2 — METHODS / ACTIONS (Backend)"]
direction LR
ACT["methodTrustee.processDocuments<br/>(documents: List[Document],<br/> target: FeatureInstanceRef[trustee],<br/> prompt: Str) -> TrusteeProcessResult"]
end
subgraph S3["Schicht 3 — ADAPTERS"]
direction LR
NODE["Editor-Node<br/>bindsAction + userParams +<br/>contextParams + uiHints"]
TOOL["AI-Agent-Tool<br/>JSON-Schema, autom. ueber<br/>Katalog-Typen"]
end
subgraph S4["Schicht 4 — INSTANZ-BINDINGS"]
direction TB
WF["Workflow-Bindings<br/>{ kind: ref, fromNode, path }<br/>{ kind: static, value }<br/>{ kind: sysVar, variable }"]
CHAT["Agent-Tool-Calls<br/>vom LLM zur Laufzeit befuellt"]
end
S1 -->|Typen referenzieren| S2
S2 -->|Adapter benutzen| S3
S3 -->|User/LLM bindet pro Lauf| S4
classDef s1 fill:#e3f2fd,stroke:#1565c0
classDef s2 fill:#e8f5e9,stroke:#2e7d32
classDef s3 fill:#fff3e0,stroke:#ef6c00
classDef s4 fill:#f3e5f5,stroke:#6a1b9a
class S1,PRIM,REF,REC,LST,RET s1
class S2,ACT s2
class S3,NODE,TOOL s3
class S4,WF,CHAT s4
```
### Verantwortlichkeit pro Schicht
| Schicht | Verantwortet | Single Source of Truth |
|---|---|---|
| **1 Typen-Katalog** | "Welche Datenstrukturen gibt es?" | `gateway/modules/features/graphicalEditor/portTypes.py` (erweitert) |
| **2 Methods/Actions** | "Was kann das Backend? Mit welchen typisierten Args?" | `gateway/modules/workflows/methods/method*/method*.py` |
| **3 Adapter (Node + Tool)** | "Wie wird das im Editor / Agent exponiert?" | `gateway/modules/features/graphicalEditor/nodeDefinitions/*.py` (umgebaut zu Adapter), `actionToolAdapter.py` (existiert schon) |
| **4 Instanz-Bindings** | "Wie ist DIESER Workflow / DIESER Tool-Call konkret verkabelt?" | gespeicherter Workflow-Graph, LLM-Tool-Call zur Laufzeit |
---
## Adapter-Layer — warum NICHT 1:1-Generator
Schichten 2 und 3 haben unterschiedliche Bedarfe. Generator scheitert an
realen Faellen. Beispiel:
### Beispiel A: `trustee.queryData` (heute eine Node, viele Modi)
**Heute (Editor-Node):** Eine UI-Node, die je nach `mode/entity`-Kombi
unterschiedliche interne Pfade aufruft.
```python
# nodeDefinition (heute, vereinfacht)
"id": "trustee.queryData",
"parameters": [
{"name": "mode", "options": ["lookup", "raw", "aggregate"]},
{"name": "entity", "options": ["tenantWithRent", "contact", "journalLines", "accounts", "balances"]},
{"name": "tenantNameRef", "showWhen.entity": ["tenantWithRent", "contact"]},
{"name": "rentAccountPattern", "showWhen.entity": ["tenantWithRent"]},
{"name": "filterJson", "showWhen.mode": ["raw", "aggregate"]},
]
```
Ein 1:1-Generator wuerde aus 5 Modi 5 Nodes machen — UX-maessig falsch
(`Mietzins abfragen` vs `Konten abfragen` vs `Salden abfragen` als
separate Nodes ist Quatsch, das ist konzeptionell **eine** Aktion).
### Beispiel B: `methodOutlook.composeAndDraftEmailWithContext`
Backend-Action hat **8+ Args** (Auth-Context, ConfigOverrides, Telemetry-Tags
etc.). Im Editor will der User aber nur 4 sehen (subject, body, to,
attachments). Generator wuerde 8 Felder anzeigen — UX-Garbage.
### Beispiel C: AI-Agent-Tool vs Editor-Node fuer dieselbe Action
`methodSharepoint.findDocumentPath` wird sowohl im Editor als auch vom Agent
verwendet:
- **Editor**: braucht Picker fuer `connection`, Drill-Down im Folder
- **Agent**: braucht JSON-Schema mit Beschreibung, kein UI-Picker
Beide Adapter zeigen dieselbe Action unterschiedlich. 1:1-Generator
funktioniert nicht.
### Loesung — Adapter-Layer
Adapter referenziert die Action und mappt explizit:
```python
NODE_DEFINITION = {
"id": "trustee.processDocuments",
"bindsAction": "methodTrustee.processDocuments", # Schicht-2-Referenz
"userParams": [ # Sichtbar im Editor, vom User gebunden
{"actionArg": "documents", "ui": {"label": "Dokumente"}},
{"actionArg": "target", "ui": {"label": "Trustee-System"}},
{"actionArg": "prompt", "ui": {"label": "Prompt", "renderAs": "textarea"}},
],
"contextParams": { # Aus Session/Run-Context, NICHT vom User
"mandateId": "$session.mandateId",
"userId": "$session.userId",
},
# output Type kommt automatisch aus Action.return-Type
}
```
**CI-Validator** (Test im Build) prueft hart:
1. Jeder `actionArg` in `userParams` existiert als Arg in der Action.
2. Jeder Pflicht-Action-Arg ist entweder in `userParams` oder `contextParams` abgedeckt.
3. Jeder Param-Typ aus der Action existiert im Typen-Katalog.
4. Output-Typ der Action existiert im Katalog.
5. Es gibt keine Action ohne Adapter (oder explizit auf "agent-only" markiert).
→ Beste aus beiden Welten:
- Action-Signaturen sind die Wahrheit fuer Typen
- Nodes/Tools haben UI/Tool-spezifische Flexibilitaet
- KEINE stille Drift moeglich
- Typen-Katalog bleibt einzige Wahrheit fuer Datenstrukturen
---
## Was sich pro Datei aendert
```mermaid
flowchart LR
subgraph BACKEND["Backend"]
PT["portTypes.py<br/>(Typen-Katalog)<br/>+ FeatureInstanceRef<br/>+ TrusteeProcessResult<br/>+ TrusteeSyncResult<br/>+ QueryResult aktivieren<br/>+ EmailDraft aktivieren<br/>+ RedmineTicket / TicketList<br/>+ Primitive als Buerger"]
MTH["methods/method*/method*.py<br/>Action-Signaturen<br/>auf Katalog-Typen umstellen<br/>(WorkflowActionParameter.type<br/>= Katalog-Typname)"]
ND["nodeDefinitions/*.py<br/>Umbau zu Adaptern<br/>(bindsAction + userParams +<br/>contextParams + uiHints)<br/>Keine eigenen 'type'-Strings mehr"]
TA["actionToolAdapter.py<br/>nutzt Katalog-Typen statt<br/>Heuristik-Mapping in JSON-Schema"]
VAL["nodeRegistry.py + neuer<br/>adapterValidator.py<br/>Hart-Check Drift Action vs Adapter"]
ENG["automation2/executionEngine.py<br/>Bindings aufloesen ueber<br/>typisierte Pfade<br/>(mit '*' fuer Iteration)"]
end
subgraph FRONTEND["Frontend"]
DP["DataPicker.tsx<br/>strikt typisiert<br/>Object-Drill-Down generisch<br/>List-Iteration als Loop-Vorschlag"]
NCP["NodeConfigPanel.tsx<br/>Pflicht-Params zuerst<br/>RequiredAttributePicker<br/>(0/1/N-Logik)"]
FC["FlowCanvas.tsx<br/>Fehler-Badge auf Node<br/>bei fehlender Pflicht-Bindung"]
SAVE["Save-Button immer aktiv<br/>Run-Button blockiert<br/>bei Pflicht-Fehlern"]
end
subgraph SCRIPT["Migration"]
MIG["pickNotPushMigration.py<br/>+ neue Migration:<br/>alte 'featureInstanceId: <uuid>'<br/>auf neuen Ref-Typ umstellen<br/>alte hidden-Bindings auflosen"]
end
PT --> MTH
MTH --> ND
MTH --> TA
ND --> VAL
PT --> VAL
VAL --> NCP
PT --> DP
DP --> NCP
NCP --> FC
NCP --> SAVE
MIG --> MTH
```
---
## Fokus und kritische Details
- **Schicht 1 (Katalog) muss vor allem anderen sauber sein.** Erst danach
Schicht 2 anfassen. Erst danach Schicht 3.
- **`featureInstanceId` ist heute 11x als generischer string deklariert** —
das ist der haeufigste Bug-Treiber. `FeatureInstanceRef[discriminator]`
loest das (Discriminator unterscheidet trustee/redmine/clickup/...).
- **Object-Drill-Down generisch** in der Picker-UI: jeder Schema-Typ mit
Feldern wird rekursiv expandierbar. Kein Sonderfall pro Node.
- **List-Iteration als Loop-Vorschlag**: wenn der Picker `List[X]` bietet
und der Param-Typ `X` ist, schlaegt das System vor "iteriere ueber Liste"
und wuerde einen Loop-Container vorschlagen / einrichten.
- **Save niemals blockieren.** Save ist immer erlaubt — auch mit
unvollstaendigen Bindings. Run ist blockiert bei fehlenden
Pflicht-Bindings, Fehler werden auf der Node visualisiert.
- **`accepts` strikt** — kein "Transit"/"ActionResult"-Wildcard mehr.
`accepts` enthaelt exakt die Schema-Typen, die vom Pflicht-Input
erwartet werden.
- **Adapter-Validator ist Build-Zeit**: jeder Drift Action vs Adapter ist
ein roter CI-Build. Keine stille Abweichung erlaubt.
- **Bestands-Workflows brechen** — Migrations-Skript `pickNotPushMigration`
wird erweitert; Workflows werden einmalig konvertiert.
- **AI-Agent gewinnt automatisch**: `actionToolAdapter` nutzt nach Umstellung
Katalog-Typen, also bekommt das LLM klare typed-Arg-Schemas und kann
endlich deterministisch Tools aufrufen.
---
## Ziel und Nicht-Ziele
- **Ziel:** EINE Typenwelt (Katalog) — Param-Typen, Output-Typen, accepts und
AI-Agent-Tool-Schemas referenzieren denselben Katalog.
- **Ziel:** Methods/Actions sind die einzige Wahrheit fuer Backend-Faehigkeiten;
Nodes und Agent-Tools sind Adapter.
- **Ziel:** Pflicht-Params sind sichtbar, typisiert, deterministisch pickbar.
- **Ziel:** Picker-UI ohne Heuristik — nur typkompatible Optionen, generischer
Object-Drill-Down + List-Iteration.
- **Ziel:** Drift Action vs Adapter wird build-time verhindert.
- **Ziel:** AI-Agent kann mit klaren, typed-Args jeden Action aufrufen.
- **Ziel:** Bestands-Workflows werden migriert (Skript).
- **Nicht-Ziel:** Drag&Drop von Output-Ports auf Input-Felder (separater Plan).
- **Nicht-Ziel:** Schema-Badges direkt auf Canvas-Nodes (vom User abgelehnt).
- **Nicht-Ziel:** Auto-Wire zwischen Nodes (User bindet im Picker).
- **Nicht-Ziel:** Backwards-Compat fuer alte `frontendType`-Spezialwerte
(`hidden`, `userConnection`, `sharepointFolder`, `dataRef` etc.) — werden
alle entfernt.
---
## Betroffene Module
- **Gateway:**
- `modules/features/graphicalEditor/portTypes.py` — Katalog erweitern
- `modules/features/graphicalEditor/nodeDefinitions/*.py` — Adapter-Format
- `modules/features/graphicalEditor/nodeRegistry.py` + neuer
`adapterValidator.py` — Drift-Pruefung im Build
- `modules/workflows/methods/method*/method*.py` — Action-Signaturen auf
Katalog-Typen
- `modules/datamodels/datamodelWorkflowActions.py`
`WorkflowActionParameter.type` referenziert Katalog-Typname
- `modules/serviceCenter/services/serviceAgent/actionToolAdapter.py`
nutzt Katalog statt Heuristik
- `modules/workflows/automation2/executionEngine.py` — typed
Bindings-Resolver
- `modules/workflows/automation2/pickNotPushMigration.py` — Migration der
Bestands-Workflows
- **Frontend:**
- `components/FlowEditor/nodes/shared/DataPicker.tsx` — strikte Filter,
generischer Drill-Down, Loop-Vorschlag
- `components/FlowEditor/nodes/shared/RequiredAttributePicker.tsx` (neu)
- `components/FlowEditor/editor/NodeConfigPanel.tsx` — Pflicht-Params
zuerst
- `components/FlowEditor/editor/FlowCanvas.tsx` — Fehler-Badge
- Save-/Run-Button-Logik — Save immer aktiv, Run blockiert bei Fehlern
- **DB-Migration:** nein (Workflow-JSON-Format aendert sich, Migration im
pickNotPushMigration-Skript)
- **Andere Komponenten:** AI-Agent-Tool-Definitionen werden automatisch
besser durch Katalog-Nutzung; keine eigene Aenderung dort noetig.
---
## Entscheidungen
| Datum | Entscheidung | Begruendung |
|---|---|---|
| 2026-04-24 | EINE Typenwelt: Primitive werden Schema-Eintraege im Katalog | User-Vorgabe; eliminiert die zwei parallelen Type-Systeme |
| 2026-04-24 | Action-Signatur ist single source of truth fuer Typen | Backend-Vertrag bleibt explizit; AI-Agent + Editor referenzieren denselben Vertrag |
| 2026-04-24 | Adapter-Layer (NICHT 1:1-Generator) | UI- und Tool-spezifische Flexibilitaet noetig (z.B. queryData mit Modi, Outlook mit Auth-Context); CI-Validator schuetzt vor Drift |
| 2026-04-24 | UI-Hints am Action-Param (Annotations) | Single source of truth; Adapter kann sie weiterreichen oder ueberschreiben |
| 2026-04-24 | Outputs pro Action eigener typisierter Schema-Typ | Pick-not-Push fuer Folgenodes; ActionResult bleibt nur fuer "fire-and-forget"-Actions |
| 2026-04-24 | Object-Drill-Down generisch im Picker; List → Loop-Vorschlag | User-Vorgabe; vermeidet Spezialcode pro Node |
| 2026-04-24 | accepts strikt (kein Transit/ActionResult-Wildcard mehr) | User-Vorgabe; verhindert Heuristik-Befuellung |
| 2026-04-24 | Save niemals blockieren, Run blockiert bei Fehlern | User-Vorgabe; Editor-UX (work-in-progress muss persistierbar sein) |
| 2026-04-24 | Big-Bang-Refactor (alle Module gleichzeitig) | User-Vorgabe; sauberer Schnitt, einmalige Migration |
| 2026-04-24 | Bestands-Workflows: Migration-Skript einmalig | User-Vorgabe; vermeidet Lazy-Migration-Komplexitaet |
---
## Umsetzungs-Checkliste
### Phase 1 — Schicht 1 (Typen-Katalog)
- [ ] `portTypes.py`: Primitive als erste Buerger (`Str`, `Int`, `Bool`,
`Float`, `DateTime`, `Url`)
- [ ] Neue Refs: `FeatureInstanceRef` (mit `featureCode`-Discriminator),
`PromptTemplateRef`
- [ ] Neue Action-Returns: `TrusteeRefreshResult`, `TrusteeProcessResult`,
`TrusteeSyncResult`, `TrusteeQueryResult`, `RedmineTicket`,
`RedmineTicketList`, `RedmineStats`, `ClickupSearchResult`,
`FileCreateResult`
- [ ] `EmailDraft` aktivieren (existiert), `QueryResult` aktivieren
- [ ] Katalog-Validator: Test prueft Vollstaendigkeit + zyklische Refs
### Phase 2 — Schicht 2 (Methods/Actions)
- [ ] `WorkflowActionParameter.type` referenziert Katalog-Typname
(statt freier String)
- [ ] Alle Methods (`methodTrustee`, `methodRedmine`, `methodSharepoint`,
`methodOutlook`, `methodAi`, `methodClickup`, `methodFile`,
`methodContext`) auf Katalog-Typen umstellen
- [ ] UI-Hints am Param annotieren (z.B. `uiHint='textarea'`)
- [ ] Action-Discovery liefert typisierte Signatur
### Phase 3 — Schicht 3 (Adapter)
- [ ] `nodeDefinitions/*.py` — Umbau zu Adapter-Format
(`bindsAction`, `userParams`, `contextParams`, `uiHints`)
- [ ] Neuer `adapterValidator.py` — CI-Test prueft 5 Drift-Regeln
- [ ] `actionToolAdapter.py` — nutzt Katalog statt
`_pythonTypeToJsonType`-Heuristik
- [ ] `nodeRegistry.py` — exportiert vollstaendigen Katalog +
Adapter-Definitionen ans Frontend
### Phase 4 — Schicht 4 (Frontend + Engine)
- [x] `RequiredAttributePicker.tsx` (neu) mit 0/1/N-Logik
- [x] `DataPicker.tsx` — strikte Type-Filter + generischer Drill-Down +
Loop-Vorschlag
- [x] `NodeConfigPanel.tsx` — Pflicht-Params zuerst
- [x] `FlowCanvas.tsx` — Fehler-Badge oben rechts pro Node
- [x] Save-Button immer aktiv, Run-Button blockiert bei Fehlern
- [x] Save-Toast meldet Pflicht-Fehler-Count (`Gespeichert mit X Pflicht-Fehlern in Y Nodes`),
Amber-Optik statt Erfolg-Grün (AC 9)
- [x] `graphUtils.py` (Bindings-Resolver des Execution-Engines) — Pfad mit
`*` fuer Iteration (`_get_by_path` + `_pathContainsWildcard`,
18 Unit-Tests)
- [x] `paramValidation.ts` (neu, FE) — `isParamBound`,
`findRequiredErrors`, `findGraphErrors`, `findSourceCandidates`
als single source of truth fuer Validierung + Quellen-Discovery
### Phase 5 — Migration + Tests
- [x] `featureInstanceRefMigration.py` (neu) — `materializeFeatureInstanceRefs`:
konvertiert Bestands-Workflow-Param `featureInstanceId: "<uuid>"` zu
typisierter Envelope `{$type: FeatureInstanceRef, id, featureCode}`,
idempotent
- [x] `executionEngine.executeGraph` ruft Migration vor `materializeConnectionRefs`
auf — alte Workflows werden bei jedem Run automatisch on-the-fly
typisiert (keine separate DB-Migration noetig fuer korrekten Run)
- [x] `graphUtils._unwrapTypedRef` + `_isTypedRefEnvelope`
`resolveParameterReferences` entpackt typisierte Envelopes auf den
kanonischen Primitivwert (z.B. `FeatureInstanceRef.id` → str), damit
Legacy-Action-Code unveraendert weiterlaeuft
- [x] Unit-Tests Schicht 1 (`test_portTypes_catalog.py` — 39 Tests)
- [x] Unit-Tests Schicht 2 (`test_action_signature_validator.py` — 45 Tests)
- [x] Unit-Tests Schicht 3 (`test_adapter_validator.py` — 15 Tests,
`test_node_adapter.py` — 18 Tests, `test_action_tool_adapter_typed.py`
— 17 Tests)
- [x] Unit-Tests Schicht 4 BE (`test_automation2_graphUtils.py` — 18 Tests,
`test_featureInstanceRefMigration.py` — 33 Tests)
- [x] Integration-Test: Trustee-Bindings-Pipeline
(`test_pick_not_push_migration_v2.py` — 9 Tests, T11) — beweist dass
Legacy + migrated Trustee-Graphs identische Action-Params produzieren
- [x] AI-Agent-Tool-Test (T12): typisierte Args via
`test_action_tool_adapter_typed.py` (existiert seit Phase 3, 17 Tests)
- [ ] Trustee-Spesenbelege Live-E2E (T4) — out of scope hier; benoetigt
Trustee-DB-Setup, ist eigenstaendige Plan-Seite
- [ ] FE-Unit-Tests (T5T8, T10) — out of scope: kein Vitest/Jest-Setup im
Repo, eigene Vorlauf-Story noetig
- [ ] FE-E2E Save-with-errors (T9) — siehe FE-Test-Setup-Story
- [x] RBAC / Permissions: keine Aenderungen
- [x] Neutralisierung betroffen? Nein
- [x] Navigation / Routing: keine Aenderungen
- [x] Billing-Impact: keiner
---
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|---|---|
| 1 | Given Katalog erweitert, When `validateCatalog()`, Then alle Schemas vollstaendig + keine zyklischen Refs | must |
| 2 | Given Method-Signatur typisiert, When `validateActionSignatures()`, Then jeder Param-Typ existiert im Katalog | must |
| 3 | Given Adapter-Definition, When `validateAdapter()`, Then 5 Drift-Regeln gruen (sonst CI-rot) | must |
| 4 | Given Trustee-Workflow Spesenbelege, When ausgefuehrt, Then Dokumente landen in `TrusteeData*`-Tabellen (Bug behoben) | must |
| 5 | Given Node mit Pflicht-Param ohne Quelle, When selektiert, Then roter Pill + rotes Badge auf Node, Save bleibt aktiv | must |
| 6 | Given Node mit Pflicht-Param + 1 typkompatibler Quelle, When selektiert, Then auto-bound mit Override-Knopf | must |
| 7 | Given Node mit Pflicht-Param + N Quellen, When Picker geoeffnet, Then **nur** typkompatible Optionen sichtbar (kein mismatch) | must |
| 8 | Given Output `TrusteeProcessResult { documents: List[Document] }`, When in Picker fuer DocumentList-Param, Then `documents` als ganze Liste auswaehlbar UND "iterieren" als Loop-Vorschlag sichtbar | must |
| 9 | Given Workflow mit Pflicht-Fehlern, When Save geklickt, Then Workflow gespeichert + Toast "Gespeichert mit X Fehlern" | must |
| 10 | Given Workflow mit Pflicht-Fehlern, When Run geklickt, Then Run blockiert + Tooltip + Klick selektiert erste Fehler-Node | must |
| 11 | Given Bestands-Workflow vor Migration, When Migration laeuft, Then alle alten `featureInstanceId: <uuid>` als typisierte `FeatureInstanceRef`-Bindings persistiert | must |
| 12 | Given AI-Agent ruft `trustee_processDocuments`, When Tool-Schema generiert, Then JSON-Schema enthaelt typed Refs (nicht generisches "string") | should |
---
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|---|---|---|---|---|---|
| T1 | 1 | unit | ja | gateway/tests/unit/graphicalEditor/test_portTypes_catalog.py (Datei statt `test_catalog_validator.py`) | done (39 ok) |
| T2 | 2 | unit | ja | gateway/tests/unit/methods/test_action_signature_validator.py | done (45 ok) |
| T3 | 3 | unit | ja | gateway/tests/unit/graphicalEditor/test_adapter_validator.py | done (15 ok) |
| T4 | 4 | integration | ja | gateway/tests/integration/trustee/test_spesenbelege_workflow_e2e.py | deferred (eigene Story — Trustee-DB-Setup) |
| T5 | 5 | unit-fe | ja | frontend_nyla/src/components/FlowEditor/nodes/shared/__tests__/RequiredAttributePicker.test.tsx | deferred (FE-Test-Infrastruktur fehlt) |
| T6 | 6 | unit-fe | ja | frontend_nyla/src/components/FlowEditor/nodes/shared/__tests__/RequiredAttributePicker.test.tsx | deferred (FE-Test-Infrastruktur fehlt) |
| T7 | 7 | unit-fe | ja | frontend_nyla/src/components/FlowEditor/nodes/shared/__tests__/DataPicker.strict.test.tsx | deferred (FE-Test-Infrastruktur fehlt) |
| T8 | 8 | unit-fe | ja | frontend_nyla/src/components/FlowEditor/nodes/shared/__tests__/DataPicker.drillDown.test.tsx | deferred (FE-Test-Infrastruktur fehlt) |
| T9 | 9 | e2e | ja | frontend_nyla/e2e/workflows/save-with-errors.spec.ts | deferred (FE-E2E-Setup fehlt) |
| T10 | 10 | unit-fe | ja | frontend_nyla/src/components/FlowEditor/editor/__tests__/RunButton.test.tsx | deferred (FE-Test-Infrastruktur fehlt) |
| T11 | 11 | migration | ja | gateway/tests/integration/automation2/test_pick_not_push_migration_v2.py | done (9 ok) + Unit-Coverage in tests/unit/workflows/test_featureInstanceRefMigration.py (33 ok) |
| T12 | 12 | unit | ja | gateway/tests/unit/serviceAgent/test_action_tool_adapter_typed.py | done (17 ok) |
| T13 | — | manual | nein | local/temp/uxPrototype.html — angepasst auf neue Architektur | pending |
---
## Links
- Vorgaenger-Plan (wird ersetzt): [2026-04-required-attribute-picker.md](./2026-04-required-attribute-picker.md)
- Verwandter Audit: [2026-04-typed-generic-handover-schema-audit.md](./2026-04-typed-generic-handover-schema-audit.md)
- HTML-Prototyp (volatil): `local/temp/uxPrototype.html` — wird auf neue
Architektur angepasst, sobald Schicht 1 + 2 + 3 stabil
- Issue-Backlog: `local/notes/issues.md`
---
## Abschluss
- [ ] `b-reference/gateway/architecture.md` aktualisiert (4-Schichten-Modell)
- [ ] `b-reference/gateway/workflow.md` aktualisiert (Action-Signaturen +
Adapter)
- [ ] `b-reference/gateway/ai-agent.md` aktualisiert (Tool-Generierung aus
Katalog statt Heuristik)
- [ ] `b-reference/gateway/features/trustee.md` aktualisiert (typisierte
Inputs/Outputs)
- [ ] `b-reference/frontend-nyla/architecture.md` aktualisiert
(RequiredAttributePicker, Object-Drill-Down, Loop-Vorschlag)
- [ ] `TOPICS.md` aktualisiert (neues Thema "Typed Action Architecture")
- [ ] Dieses Dokument → `c-work/2-build/` waehrend Umsetzung
- [ ] Dieses Dokument → `c-work/3-validate/` waehrend Tests
- [ ] Dieses Dokument → `c-work/4-done/` nach Merge, dann → `z-archive/`

View file

@ -0,0 +1,296 @@
<!-- status: done -->
<!-- started: 2026-04-24 -->
<!-- completed: 2026-04-24 -->
<!-- component: gateway, frontend-nyla, wiki -->
<!-- depends-on: c-work/4-done/2026-04-typed-action-architecture.md -->
# Typed Action Architecture — Folge-Plan (Validate → Done)
## Beschreibung und Kontext
Die [Typed Action Architecture](../3-validate/2026-04-typed-action-architecture.md)
ist code-fertig durch Phase 15 (237 Backend-Tests gruen, FE TypeScript-Compile
sauber, alle 12 Akzeptanzkriterien adressiert — 10 voll erfuellt, 1 deferred,
1 mit Patch nachgezogen).
Bevor das Architektur-Dokument von `3-validate/` nach `4-done/` (und spaeter
`z-archive/`) wandert, muessen folgende **separate, kleinere Stories**
abgeschlossen werden. Dieses Dokument ist die **einzige** Klammer um diese
Folge-Schritte — kein neues Konzept, sondern Aufraeumarbeit + Test-Hardening
+ Wissens-Sync.
**Business-Treiber:** Ohne diese Folge-Schritte bleibt das System
funktional korrekt, aber (a) ohne FE-Test-Sicherheitsnetz, (b) ohne harten
End-to-End-Beweis fuer den Trustee-Real-Workflow, und (c) ohne aktualisierte
b-reference-Doku, was AI-Agenten + neue Entwickler ausbremst.
**Risiko ohne Umsetzung:**
- FE-Regressions (RequiredAttributePicker, DataPicker, Save/Run-Gate) werden
erst durch User entdeckt.
- Trustee Spesenbelege koennte durch unentdeckte Action-Side-Effects brechen,
selbst wenn die Bindings korrekt sind.
- Adapter-Drift-Backlog bleibt offen — Editor zeigt weiterhin Felder an, die
nie ans Backend wandern (UX-Garbage, schwer zu debuggen).
- b-reference verzettelt sich gegenueber der Realitaet — `lastReviewed`-Werte
werden uralt.
---
## Folge-Schritte als Mindmap
```mermaid
mindmap
root((Typed Action<br/>Folge-Schritte))
A) Test-Hardening
A1 FE-Test-Setup<br/>(Vitest + RTL)
RequiredAttributePicker.test.tsx
DataPicker.strict.test.tsx
DataPicker.drillDown.test.tsx
RunButton.test.tsx
A2 Trustee Live-E2E<br/>(integration)
test_spesenbelege_workflow_e2e.py
Mock SharePoint + AI + Trustee-DB
echtes processDocuments + syncToAccounting
A3 FE-E2E (Playwright)<br/>save-with-errors.spec.ts
nur falls Vitest etabliert
B) Adapter-Drift-Cleanup
B1 26 Drifts aus Backlog
../2-build/2026-04-adapter-drift-cleanup.md
Eine Drift pro PR
_KNOWN_ADAPTER_DRIFTS leeren
B2 Composite-UI-Mechanismus
Eigene Plan-Seite noetig
Vor-Konsolidierung UI-Felder
C) Operatives
C1 DB-CLI-Migration<br/>(optional)
scripts/script_migrate_feature_instance_refs.py
Walkt Automation2Workflow.graph in DB
Persistiert typisierte Envelopes<br/>(Editor-Reads sehen sofort Typen)
C2 HTML-Prototype
local/temp/uxPrototype.html
Auf neue Architektur anpassen
D) Wissens-Sync (b-reference)
D1 architecture.md<br/>(4-Schichten-Modell)
D2 workflow.md<br/>(Action-Signaturen + Adapter)
D3 ai-agent.md<br/>(Tool-Generierung aus Katalog)
D4 features/trustee.md<br/>(typisierte Inputs/Outputs)
D5 frontend-nyla/architecture.md<br/>(RequiredAttributePicker,<br/>Object-Drill-Down,<br/>Loop-Vorschlag)
D6 TOPICS.md<br/>(neuer Eintrag<br/>"Typed Action Architecture")
E) Doc-Lifecycle
E1 Architektur-Plan ->4-done<br/>nach Abschluss D
E2 Architektur-Plan -> z-archive<br/>nach 30 Tagen ohne Touchups
E3 Dieses Folge-Plan-Doc<br/>-> 4-done bei Abschluss
```
---
## Fokus und kritische Details
- **A1 (FE-Test-Setup) ist der Eintrittspunkt.** Solange kein Vitest+RTL
laeuft, sind A3, T5T10 blockiert. Empfehlung: dedizierte kleine Story
"Frontend Test-Infrastruktur einfuehren" — vermutlich 0.5 Tage Setup
(vitest, jsdom, @testing-library/react, eine Beispiel-Test-Datei) +
Anpassung CI-Pipeline.
- **A2 (Trustee Live-E2E)** braucht echte Trustee-DB-Tabellen
(`TrusteeDocument`, `TrusteePosition`) und mockbare AI-Antworten. Realistisch
12 Tage Setup. Sollte EINE Test-Datei bleiben, nicht 5 fragmentierte.
- **B1 (Adapter-Drift-Cleanup)** ist als laufender Backlog in
`../2-build/2026-04-adapter-drift-cleanup.md` getrackt. **Nicht hier
duplizieren** — diese Folge-Plan-Seite verweist nur darauf. Der Adapter-Drift-
Cleanup wird abgeschlossen, wenn `_KNOWN_ADAPTER_DRIFTS = frozenset()`.
- **C1 (DB-CLI-Migration)** ist optional. Die Runtime-Migration in
`executeGraph` macht alle Workflows zur Laufzeit korrekt. Die DB-CLI ist nur
fuer "saubere Reads" im Editor noetig (heute zeigt der Editor noch das alte
String-Format auf bestehenden Workflows, bis zum naechsten Save).
- **D1D6 (b-reference)** ist Wissens-Sync, nicht Konzept-Arbeit. Quelle ist
immer die `3-validate/`-Plan-Seite + die fertigen Tests. `lastReviewed` und
`verifiedAgainst` Header setzen.
- **Reihenfolge:** A1 → (parallel A2, B1, D1D6) → A3, C1 → E1, E2, E3.
D-Tasks koennen vor A2 abgeschlossen werden, da sie das System wie es jetzt
steht beschreiben.
---
## Ziel und Nicht-Ziele
- **Ziel:** Alle 12 Akzeptanzkriterien aus dem Architektur-Plan haben
automatisierten Test-Coverage (Backend ist da, FE + E2E folgen).
- **Ziel:** Die b-reference-Seiten sind synchron mit der neuen 4-Schichten-
Realitaet.
- **Ziel:** Die Adapter-Drift-Liste ist leer, der Snapshot-Test wird auf
hartes `assert report.errors == []` umgestellt.
- **Ziel:** Saubere Editor-Reads — entweder durch DB-CLI-Migration (C1) oder
bewusste Entscheidung dagegen.
- **Ziel:** Die Typed-Action-Architektur-Plan-Seite ist final unter `4-done/`.
- **Nicht-Ziel:** Neue Architektur-Erweiterungen (z.B. Drag&Drop-Picker,
Schema-Badges, Auto-Wire). Das sind eigene Plan-Seiten, falls gewuenscht.
- **Nicht-Ziel:** Backwards-Compat fuer entfernte Legacy-`frontendType`-Werte
(`hidden`, `userConnection`, `dataRef`) — bewusst entfernt.
- **Nicht-Ziel:** AI-generierte Workflows (separater Plan, baut auf typisierter
Action-Architektur auf).
---
## Betroffene Module
- **Gateway:**
- `tests/integration/trustee/test_spesenbelege_workflow_e2e.py` (neu) — A2
- `scripts/script_migrate_feature_instance_refs.py` (neu, optional) — C1
- **Frontend:**
- `vitest.config.ts` (neu) + `package.json` Test-Scripts — A1
- `tsconfig.json` Test-Includes — A1
- `components/FlowEditor/nodes/shared/__tests__/*.test.tsx` (neu) — A1
- `components/FlowEditor/editor/__tests__/RunButton.test.tsx` (neu) — A1
- `e2e/workflows/save-with-errors.spec.ts` (neu, falls Playwright) — A3
- **Wiki:**
- `b-reference/gateway/architecture.md` — D1
- `b-reference/gateway/workflow.md` — D2
- `b-reference/gateway/ai-agent.md` — D3
- `b-reference/gateway/features/trustee.md` — D4
- `b-reference/frontend-nyla/architecture.md` — D5
- `TOPICS.md` — D6
- `c-work/3-validate/2026-04-typed-action-architecture.md`
bei Abschluss → `4-done/` (E1) → `z-archive/` (E2)
- **Adapter-Drift:** `c-work/2-build/2026-04-adapter-drift-cleanup.md`
laufender Backlog, nicht hier dupliziert — B1
- **DB-Migration:** nein
- **Andere Komponenten:** keine
---
## Entscheidungen
| Datum | Entscheidung | Begruendung |
|---|---|---|
| 2026-04-24 | Folge-Schritte in EINEM Plan-Dokument buendeln | User-Vorgabe; vermeidet Plan-Seiten-Inflation |
| 2026-04-24 | Adapter-Drift-Cleanup nicht hier duplizieren, sondern verlinken | Existierender Backlog in `2-build/`; eigenstaendiger Track |
| 2026-04-24 | DB-CLI-Migration als optional markieren | Runtime-Migration im Engine reicht fuer korrekte Ausfuehrung; CLI nur fuer Editor-Read-Hygiene |
| 2026-04-24 | A1 (FE-Test-Setup) explizit als Vorlauf-Story herausgehoben | Blockiert A3, T5T10; eigene minimale Setup-Geschichte sauberer als Setup + Tests vermischen |
| 2026-04-24 | T4 Trustee Live-E2E bleibt **eine** Test-Datei | Erfahrung aus Phase 5 Slim-Test: konsolidiert gewinnt gegen 5 fragmentierte Smoke-Tests |
| 2026-04-24 | b-reference-Sync (D-Tasks) kann VOR Trustee Live-E2E starten | D-Tasks beschreiben den Status quo, der bereits stabil ist |
---
## Umsetzungs-Checkliste
### A) Test-Hardening
- [x] A1: FE-Test-Infrastruktur — Vitest 2.x + jsdom + `@testing-library/react`
installiert, `vitest.config.ts` + `vitest.setup.ts` + `package.json`-Scripts
angelegt, 34/34 FE-Tests gruen
- [x] A1.1: `RequiredAttributePicker.test.tsx` — 0/1/N-Cases, Override-Knopf,
Iterieren-Suggestion (deckt T5, T6 aus Architektur-Plan)
- [x] A1.2: `DataPicker.test.tsx` (strict-Sektion) — `expectedParamType` filtert
Hard-Mismatches, Toggle "Nur kompatible" laesst Coerce-Faelle (`int -> str`)
zu (T7)
- [x] A1.3: `DataPicker.test.tsx` (drillDown-Sektion) — generischer Object-Drill-Down,
`*`-Wildcard wird fuer List-Iteration angeboten (T8)
- [x] A1.4: `CanvasHeader.test.tsx` — Run blockiert bei `executeBlockedReason`,
Klick triggert `onExecuteBlockedClick`, Save bleibt aktiv, AC-9 Warning-
Banner erscheint (T10 + AC-9)
- [x] A2: Trustee Spesenbelege Live-E2E
(`tests/integration/trustee/test_spesenbelege_workflow_e2e.py`)
mit In-Memory-Fakes (`_FakeTrusteeInterface`, `_FakeAccountingBridge`),
`processDocuments` + `syncToAccounting` end-to-end durch `executeGraph` (T4),
3/3 Tests gruen, Legacy-UUID + leere Inputs abgedeckt
- [x] A3: Optional Playwright-E2E `save-with-errors.spec.ts` (T9) — Playwright
ist nicht installiert; das AC-9-Verhalten ist durch `CanvasHeader.test.tsx`
vollstaendig abgedeckt. Wird wieder aufgenommen, sobald Playwright im
Frontend etabliert ist.
### B) Adapter-Drift-Cleanup (existiert als eigener Backlog)
- [x] B1: 26 Drift-Eintraege aus
[`../4-done/2026-04-adapter-drift-cleanup.md`](../4-done/2026-04-adapter-drift-cleanup.md)
abgearbeitet, `_KNOWN_ADAPTER_DRIFTS = frozenset()`,
Snapshot-Test laeuft auf `assert report.errors == []`.
- [ ] B2: Composite-UI-Mechanismus — bewusst zurueckgestellt (eigene Plan-Seite
bei Bedarf, derzeit nicht erforderlich)
### C) Operatives
- [x] C1: DB-CLI-Migrationsskript
`gateway/scripts/script_migrate_feature_instance_refs.py`
walkt `Automation2Workflow` + `AutoVersion`, ruft
`materializeFeatureInstanceRefs`, persistiert mit `--dry-run`-Flag,
Tests in `gateway/tests/unit/scripts/test_migrate_feature_instance_refs.py`
(9/9 gruen, idempotent)
- [x] C2: HTML-Prototype `local/temp/uxPrototype.html` aktualisiert —
FeatureInstanceRef-Envelope sichtbar, Strict-Mode-Toggle, `*`-Wildcard-
Loop-Hint, AC-9 Save-Warning (T13)
### D) Wissens-Sync (b-reference)
- [x] D1: `b-reference/gateway/architecture.md` — Abschnitt
"Typed Action Architecture (4-Schichten)" ergaenzt, `lastReviewed` 2026-04-24
- [x] D2: `b-reference/gateway/workflow.md` — Abschnitt "Typed Action Architecture
(Phasen 1-5)": Catalog, Adapter, Pick-not-Push, `*`-Wildcard, Save-with-errors
- [x] D3: `b-reference/gateway/ai-agent.md``ActionToolAdapter`-Notiz mit
Tool-Generierung aus Catalog statt Heuristik
- [x] D4: `b-reference/gateway/features/trustee.md` — neue kanonische Seite
mit Actions, Datenmodell, FeatureInstanceRef, AccountingBridge, Tests
- [x] D5: `b-reference/frontend-nyla/architecture.md` — Abschnitt
"FlowEditor -- Typed Action Architecture" mit Picker-Komponenten,
Save/Run-Gating und Vitest+RTL-Tests
- [x] D6: `wiki/TOPICS.md` — Eintrag "Typed Action Architecture" mit Verweisen
auf alle b-reference-Seiten
### E) Doc-Lifecycle
- [x] E1: Architektur-Plan
`c-work/3-validate/2026-04-typed-action-architecture.md` nach
`c-work/4-done/` verschoben
- [ ] E2: Nach 30 Tagen ohne weitere Touchups → `z-archive/c-work/` (noch offen,
tritt automatisch ein)
- [x] E3: Dieses Folge-Plan-Doc → `c-work/4-done/` verschoben
---
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|---|---|
| 1 | Given Vitest+RTL installiert, When `npm test` laeuft, Then alle FE-Test-Files gruen | must |
| 2 | Given alle FE-Tests gruen (A1.1A1.4), Then `T5T8` + `T10` im Architektur-Plan auf "done" | must |
| 3 | Given Trustee Spesenbelege-Workflow + Mocks, When ausgefuehrt, Then `TrusteeDocument` + `TrusteePosition` korrekt geschrieben (deckt AC 4 aus Architektur-Plan) | must |
| 4 | Given `_KNOWN_ADAPTER_DRIFTS = frozenset()`, When CI laeuft, Then `test_staticNodesHaveNoDriftAgainstLiveMethods` auf `assert report.errors == []` umgestellt | must |
| 5 | Given DB-CLI-Skript, When `--dry-run` gegen poweron_automation2, Then Report listet zu migrierende Workflows ohne zu schreiben | should |
| 6 | Given DB-CLI-Skript ohne `--dry-run`, When ausgefuehrt, Then alle gespeicherten Workflows haben `featureInstanceId` als typisierten Envelope | should |
| 7 | Given `b-reference/`-Seiten, When Datum gepruepft, Then `lastReviewed >= heute` und `verifiedAgainst` zeigt auf reale Quellen | must |
| 8 | Given `TOPICS.md`, When ein neuer Entwickler einsteigt, Then "Typed Action Architecture" ist als Hauptthema sichtbar | must |
| 9 | Given alle Folge-Schritte abgeschlossen, When der Architektur-Plan reviewt, Then er kann nach `4-done/` verschoben werden | must |
---
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|---|---|---|---|---|---|
| F1 | 1 | smoke | ja | `frontend_nyla/vitest.config.ts` + `vitest.setup.ts` | done |
| F2 | 2 | unit-fe | ja | `frontend_nyla/src/components/FlowEditor/nodes/shared/*.test.tsx`, `editor/CanvasHeader.test.tsx` | done (34/34) |
| F3 | 3 | integration | ja | `gateway/tests/integration/trustee/test_spesenbelege_workflow_e2e.py` | done (3/3) |
| F4 | 4 | unit | ja | `gateway/tests/unit/graphicalEditor/test_adapter_validator.py` (Snapshot leer, `_KNOWN_ADAPTER_DRIFTS = frozenset()`) | done |
| F5 | 5,6 | manual+integration | ja | `gateway/scripts/script_migrate_feature_instance_refs.py` + `gateway/tests/unit/scripts/test_migrate_feature_instance_refs.py` | done (9/9) |
| F6 | 7,8 | manual | nein | `wiki/b-reference/gateway/{architecture,workflow,ai-agent,features/trustee}.md`, `wiki/b-reference/frontend-nyla/architecture.md`, `wiki/TOPICS.md` (alle `lastReviewed: 2026-04-24`) | done |
| F7 | 9 | manual | nein | `wiki/c-work/4-done/2026-04-typed-action-architecture.md` | done |
---
## Links
- Quell-Plan: [./2026-04-typed-action-architecture.md](./2026-04-typed-action-architecture.md)
- Adapter-Drift-Backlog (abgeschlossen): [./2026-04-adapter-drift-cleanup.md](./2026-04-adapter-drift-cleanup.md)
- Audit (Faktenbasis): [../1-plan/2026-04-node-typization-audit.md](../1-plan/2026-04-node-typization-audit.md)
- Issue-Backlog: `local/notes/issues.md`
---
## Abschluss
- [x] Alle Folge-Schritte in den 5 Tracks (AE) abgeschlossen (B2 bewusst
zurueckgestellt, A3 deferred bis Playwright-Setup)
- [x] Architektur-Plan + dieses Folge-Plan-Doc in `c-work/4-done/`
- [x] `b-reference/`-Seiten gruen (D1D6), `lastReviewed: 2026-04-24`
- [x] `TOPICS.md` zeigt "Typed Action Architecture" prominent
- [ ] Nach 30 Tagen → `z-archive/c-work/`

View file

@ -1,5 +1,6 @@
<!-- status: plan -->
<!-- status: done -->
<!-- started: 2026-04-23 -->
<!-- completed: 2026-04-23 -->
<!-- component: gateway, frontend-nyla -->
# Typed Generic Handover für den Graphical Editor — PicknotPush, StaticOnly Schemas, Hard Validation

View file

@ -1,4 +1,6 @@
<!-- status: obsolete -->
<!-- archivedFrom: c-work/4-done/2026-04-graph-editor-node-config-fixes.md -->
<!-- archivedAt: 2026-04-23 -->
<!-- started: 2026-04-23 -->
<!-- obsoletedAt: 2026-04-23 -->
<!-- supersededBy: c-work/1-plan/2026-04-typed-generic-handover.md -->