wiki/c-work/4-done/2026-04-trustee-workflow-audit-and-run-workspace.md

324 lines
19 KiB
Markdown

<!-- status: plan -->
<!-- started: 2026-04-29 -->
<!-- component: gateway, frontend-nyla -->
# Trustee Workflow-Audit (A1) & Generischer Workflow-Run-Workspace (A2)
## Beschreibung und Kontext
Der User klickt im Trustee-Dashboard Service-Karten an (Budget-Vergleich,
KPI-Dashboard, Cashflow, Forecast, Jahresabschluss-Pruefung) und landet auf
Tabs in `TrusteeAnalyseView` / `TrusteeAbschlussView`, wo der jeweilige
Workflow gestartet wird.
Zwei Themen:
- **A1 Audit:** Sind diese Workflows wirklich auf dem aktuellen
Pick-not-Push / Typed-Action-Stack? -- **JA, alle GREEN** (siehe Befund
unten). Hier wird nur dokumentiert.
- **A2 Result-Sichtbarkeit:** Wenn der User waehrend des Runs die Seite
wechselt, ist das Resultat (KI-Antwort + generierte Files) anschliessend
weg -- es lebt nur im React-State der Source-View. Persistenz existiert
zwar in `AutoRun`/`AutoStepLog`, ist aber nirgends als User-UI
erschlossen.
**Geschaeftstreiber:** UX-Bruch ("ich war kurz auf einer anderen Seite, jetzt
ist mein Report weg") + fehlende Single-Source-of-Truth fuer Workflow-
Outputs. Plus: Workflow muss eine klare **Ziel-Feature-Instanz** haben, damit
Datenquellen, RBAC und Workspace-Filterung sauber funktionieren.
## Architekturentscheidung: GraphicalEditor bleibt Feature
Der GraphicalEditor (GE) ist konzeptuell eine **Plattform-Capability** (nicht
ein Domain-Feature wie Trustee). Er bleibt aber aus pragmatischen Gruenden
als Feature registriert (isolierte DB, Feature-Roles, Template-Kopier-
Mechanismus). Die URL-Struktur `/api/workflows/{geInstanceId}/...` nutzt
die GE-Instanz als RBAC-Scope/Eigentuemer.
**Neues Konzept: `targetFeatureInstanceId`** -- das Feld am Workflow, das
bestimmt welche Daten (Dateien, Connections, Domain-Objekte) zur Verfuegung
stehen. Dies ist NICHT die GE-Instanz (Eigentuemer), sondern die
**Ziel-Feature-Instanz** (Daten-Scope bei Execution).
### Kein Multi-Instance: Ein Workflow = Ein Ziel-Scope
- ALLE Nodes eines Workflows operieren auf derselben `targetFeatureInstanceId`.
- Kein per-Node Override -- Engine resolved `{{featureInstanceId}}` zentral
aus dem Workflow-Level-Feld.
- Wenn Daten aus einer anderen Instanz benoetigt werden: diese Daten im
Ziel-Scope bereitstellen (z.B. via Data-Connection, File-Import), NICHT
einen Multi-Instance-Workflow bauen.
- RBAC bei Execution: ein Check auf `targetFeatureInstanceId` reicht.
### targetFeatureInstanceId kann auch die GE-Instanz selbst sein
Fuer generische Workflows ohne Domain-Daten (z.B. "AI verarbeitet
Eingabetext und generiert Report") waehlt der User die GE-Instanz selbst
("Automation allgemein") als Ziel. Dann stehen nur Files/Connections zur
Verfuegung, die direkt an der GE-Instanz haengen.
## Fokus und kritische Details
- `POST /api/workflows/{instanceId}/execute` ist heute **synchron** (`await
executeGraph(...)`, kein BackgroundTask). Der Workspace muss den Request
nicht aendern -- er macht nur die persistierten `AutoRun`-Daten
auffindbar.
- `AutoWorkflow.featureInstanceId` = GE-Instanz (Eigentuemer). NICHT
anfassen. Neues Feld `targetFeatureInstanceId` daneben.
Hinweis: Pydantic-Model hat `featureInstanceId: str` (required, nicht
Optional), aber DB-Spalte ist `nullable=YES`. System-Templates setzen
`featureInstanceId=""` oder ueberspringen die Validation.
- System-Templates (`isTemplate=True`) haben `targetFeatureInstanceId=NULL`
(Template hat kein konkretes Ziel, erst die Kopie).
- Scheduler bindet `targetFeatureInstanceId` vom persistierten Workflow --
kein Override moeglich.
- Tab-Position: User wuenscht Workspace explizit unter `/automations`
(Seite "Nutzung > Automation") als zusaetzlichen Tab neben Dashboard +
Workflows.
- `AutoRun` hat heute KEIN `featureInstanceId`-Feld -- Instanz-Zuordnung
laeuft indirekt ueber `AutoRun.workflowId -> AutoWorkflow.targetFeatureInstanceId`.
## Ziel und Nicht-Ziele
- Ziel A1: Audit-Befund GREEN dokumentiert (Wiki-Update).
- Ziel A2: Generische Workflow-Run-Workspace-View; jeder Workflow (non-
template) hat Pflicht-`targetFeatureInstanceId`; TrusteeAnalyseView
verweist statt Inline-Result auf den Workspace.
- NICHT: Async-Umstellung des Execute-Endpoints.
- NICHT: Browser-Push-Notification.
- NICHT: De-Featuring des GraphicalEditors (bewusster pragmatischer
Kompromiss, siehe Architekturentscheidung).
- NICHT: Multi-Instance-Workflows.
## Betroffene Module
- Gateway:
- `gateway/modules/features/graphicalEditor/datamodelFeatureGraphicalEditor.py`
(neues Feld `AutoWorkflow.targetFeatureInstanceId`).
- `gateway/modules/features/graphicalEditor/routeFeatureGraphicalEditor.py`
(Save-Validation: non-template braucht `targetFeatureInstanceId`).
- `gateway/modules/features/graphicalEditor/interfaceFeatureGraphicalEditor.py`
(createWorkflow/updateWorkflow: Feld durchreichen).
- `gateway/modules/workflows/automation2/executionEngine.py`
(`executeGraph`: `{{featureInstanceId}}` aus `targetFeatureInstanceId`
resolven).
- `gateway/modules/workflows/scheduler/mainScheduler.py` (Schedule-Fire
nutzt `targetFeatureInstanceId` vom Workflow).
- `gateway/modules/features/trustee/mainTrustee.py` (Templates pruefen:
`targetFeatureInstanceId` wird bei `_copyTemplateWorkflows` gesetzt).
- `gateway/modules/interfaces/interfaceFeatures.py`
(`_copyTemplateWorkflows`: `targetFeatureInstanceId` auf Ziel-Instanz
setzen bei Kopie).
- Neue Route-Datei: `gateway/modules/routes/routeAutomationWorkspace.py`
(User-facing `/api/automations/runs/...` Endpoints).
- Frontend:
- Neuer Tab in `frontend_nyla/src/pages/AutomationsDashboardPage.tsx`.
- Neue Komponenten `WorkflowRunWorkspaceView`,
`WorkflowRunDetailView`.
- FlowEditor CanvasHeader: Pflicht-Dropdown "Ziel-Instanz"
(`targetFeatureInstanceId`).
- `TrusteeAnalyseView.tsx`: Inline-Result-Anzeige raus, Link zum
Workspace-Detail.
- `TrusteeAbschlussView.tsx`: analog -- Link zum Workspace-Detail.
- DB-Migration: `AutoWorkflow` bekommt neue Spalte
`targetFeatureInstanceId` (nullable, wegen Templates). Bestand:
Dev-Audit zeigt 0 non-template Workflows ohne Instance (alle 24
regulaeren Workflows haben bereits `featureInstanceId` aus dem URL-
Context). Migration: fuer Bestand `targetFeatureInstanceId :=
featureInstanceId` setzen (selbe Instanz wie GE = Default fuer alte
generische Workflows ODER aus Graph-Nodes extrahieren wenn dort
konkreter Wert steht).
- RBAC: Neue Endpoints pruefen `targetFeatureInstanceId` gegen User-
Berechtigungen via FeatureAccess.
## Befund A1 (Audit) -- bereits GREEN
| Service | Workflow-ID | Backend-Definition | Status |
|---------|-------------|---------------------|--------|
| Budget-Vergleich | `trustee-budget-comparison` | `mainTrustee.py` ~430-461 | GREEN |
| KPI-Dashboard | `trustee-kpi-dashboard` | `mainTrustee.py` ~463-478 | GREEN |
| Cashflow-Rechnung | `trustee-cashflow` | `mainTrustee.py` ~480-492 | GREEN |
| Prognose | `trustee-forecast` | `mainTrustee.py` ~494-507 | GREEN |
| Jahresabschluss-Pruefung | `trustee-year-end-check` | `mainTrustee.py` ~509-522 | GREEN |
Engine-Pipeline: `executeGraph` (in `workflows/automation2/executionEngine.py`
Z. ~305-350) ruft `materializeFeatureInstanceRefs` (typed-ref envelopes,
NICHT Placeholder-Substitution) + `materializeConnectionRefs` +
`validateGraph` vor jedem Lauf auf.
**ACHTUNG:** `{{featureInstanceId}}`-Placeholder werden heute NUR in
`_copyTemplateWorkflows` pre-baked (`graphJson.replace(...)`, Z. ~336-338
in `interfaceFeatures.py`). `executeGraph` selbst hat KEINE
Placeholder-Substitution -- diese muss NEU gebaut werden (Phase 1).
Zusaetzlich zu den 5 Analyse-Workflows existieren noch 2 weitere
Trustee-Templates (`trustee-receipt-import`, `trustee-sync-accounting`)
in `TEMPLATE_WORKFLOWS`. Diese verwenden ebenfalls
`{{featureInstanceId}}`-Placeholders und sind GREEN.
## Entscheidungen
| Datum | Entscheidung | Begruendung |
|-------|-------------|------------|
| 2026-04-29 | GE bleibt Feature, neues Feld `targetFeatureInstanceId` | De-Featuring zu teuer; `targetFeatureInstanceId` loest das eigentliche Problem sauber |
| 2026-04-29 | Kein Multi-Instance: 1 Workflow = 1 Ziel-Scope | Einfach, klar, RBAC auf Workflow-Ebene pruefbar; bei Bedarf Daten im Ziel-Scope bereitstellen |
| 2026-04-29 | `targetFeatureInstanceId` kann auch die GE-Instanz selbst sein | Generische Workflows ohne Domain-Daten brauchen einen Scope |
| 2026-04-29 | Workspace ist GENERISCH plattformweit, nicht Trustee-spezifisch | Doppelt-Bauen vermeiden; alle Features profitieren |
| 2026-04-29 | Workspace lebt unter `/automations` als Tab "Workspace" | User-Vorgabe; Nutzungspfad "Nutzung > Automation > Workspace" |
| 2026-04-29 | Neuer Endpoint `/api/automations/runs/...` statt Erweiterung von System-Dashboard | Klare Trennung User-Workspace (RBAC-gefiltert) vs. Admin-Dashboard |
| 2026-04-29 | Bestand-Migration: `targetFeatureInstanceId := featureInstanceId` (GE-Instanz) | Dev-Audit: 0 non-template ohne Instance; 24 regulaere haben GE-Instance aus URL |
| 2026-04-29 | TrusteeAnalyseView: Inline-Result raus, Workspace-Link rein | Single Source of Truth im Workspace; kein verlorener State bei Seitenwechsel |
| 2026-04-29 | Kein interaktives Migrations-Skript noetig | Dev-Audit: 0 echte Workflows betroffen (nur 2 System-Templates, die NULL behalten duerfen) |
## Umsetzungs-Checkliste
### Phase 1 -- targetFeatureInstanceId + Validation
- [ ] `datamodelFeatureGraphicalEditor.py`: `targetFeatureInstanceId: Optional[str] = Field(default=None, ...)`
(nullable wegen Templates).
- [ ] `interfaceFeatureGraphicalEditor.py` `createWorkflow`: aus Body oder
Fallback `self.featureInstanceId` uebernehmen.
- [ ] `interfaceFeatureGraphicalEditor.py` `updateWorkflow`: Feld im
Update-Payload erlauben (NICHT strippen wie `featureInstanceId`).
- [ ] `routeFeatureGraphicalEditor.py` Save-Endpoint: wenn
`isTemplate=False` und `targetFeatureInstanceId` fehlt/leer -> 400.
- [ ] `routeFeatureGraphicalEditor.py` Execute-Endpoint: Vorab-Check
`targetFeatureInstanceId` vorhanden, User hat FeatureAccess darauf.
- [ ] `executionEngine.py` `executeGraph`: **NEU BAUEN** -- VOR
`materializeFeatureInstanceRefs` eine Placeholder-Substitution
einfuegen: alle `{{featureInstanceId}}`-Vorkommen im serialisierten
Graph-JSON durch `targetFeatureInstanceId` (uebergeben als neuer
Parameter) ersetzen. Bestehende hart-kodierte UUIDs in Nodes bleiben
unangetastet (Backward-Compat fuer kopierte Templates, wo
`_copyTemplateWorkflows` bereits pre-baked hat).
Hinweis: `materializeFeatureInstanceRefs` macht typed-ref-envelope-
Rewriting, NICHT Placeholder-Substitution -- beides ist noetig.
- [ ] `mainScheduler.py`: Schedule-Fire liest heute
`workflow["featureInstanceId"]` (= GE-Instanz) und uebergibt es als
`instanceId` an `executeGraph`. Aendern: zusaetzlich
`workflow["targetFeatureInstanceId"]` lesen und als neuen Parameter
`targetFeatureInstanceId` an `executeGraph` uebergeben (fuer die
Placeholder-Substitution). `instanceId` (GE-Instanz) bleibt fuer
RBAC-Scope bestehen.
- [ ] `interfaceFeatures.py` `_copyTemplateWorkflows`: bei Kopie
`targetFeatureInstanceId = instanceId` (die Ziel-Feature-Instanz)
EXPLIZIT im `createWorkflow`-Payload setzen. Hinweis: heute wird
`featureInstanceId` NICHT im Payload gesetzt, sondern kommt implizit
aus dem GE-Interface-Context (`getGraphicalEditorInterface(...,
instanceId)`). `targetFeatureInstanceId` muss explizit uebergeben
werden. In-Graph-Placeholders werden wie bisher auch pre-baked
(`graphJson.replace("{{featureInstanceId}}", instanceId)`).
- [ ] Bestand-Migration (einmalig, idempotent):
`UPDATE "AutoWorkflow" SET "targetFeatureInstanceId" = "featureInstanceId" WHERE "targetFeatureInstanceId" IS NULL AND "isTemplate" IS NOT TRUE`
(kann als Boot-Telemetrie oder im Audit-Skript laufen).
- [ ] Unit-Test: Workflow-Save ohne `targetFeatureInstanceId` -> 400.
- [ ] Unit-Test: Execute mit `targetFeatureInstanceId` das User nicht
zugreifen darf -> 403.
### Phase 2 -- FlowEditor Toolbar Dropdown
- [ ] CanvasHeader: neues Dropdown "Ziel-Instanz" (Pflicht fuer non-
template). Optionen: alle FeatureInstances des Mandats (inkl. GE-
Instanz als "Automation allgemein"), gefiltert nach User-FeatureAccess.
- [ ] Bei Wechsel: Workflow-Update mit neuem `targetFeatureInstanceId`,
Datenquellen-Browser (SourcesTab, UDB-Listen) refreshen auf neuen
Scope.
- [ ] Wenn `targetFeatureInstanceId` leer (z.B. frisch aus Template):
User MUSS erst Instanz waehlen bevor Save/Run moeglich.
### Phase 3 -- Generischer WorkflowRunWorkspace
- [ ] Neue Route-Datei `gateway/modules/routes/routeAutomationWorkspace.py`:
- `GET /api/automations/runs` (Query-Params: `scope=mine|mandate`,
`status`, `targetInstanceId`, `workflowId`, `limit`, `offset`).
RBAC: nur Runs sichtbar wo User FeatureAccess auf
`targetFeatureInstanceId` hat.
- `GET /api/automations/runs/{runId}/detail` -- kombiniert
AutoRun + AutoStepLog + verlinkte FileItems.
- [ ] Tab "Workspace" in `AutomationsDashboardPage.tsx` (neben Dashboard,
Workflows).
- [ ] Komponente `WorkflowRunWorkspaceView`: Liste mit Filtern
(Status, Workflow-Name, Ziel-Instanz, Zeitraum), Pagination.
- [ ] Komponente `WorkflowRunDetailView`: Chat-aehnliche Ansicht
- Header: Workflow-Name, Status, Start/Ende, Ziel-Instanz.
- Eingabe-Bubble: Trigger-Payload (formatiert).
- Step-Bubbles: chronologisch, pro Step-Output kollabierbar.
- Final-Bubble: KI-Antwort als Markdown.
- Documents-Sektion: alle generierten FileItems als Karten mit
Direkt-Download.
### Phase 4 -- Trustee-Views Refactor + Notifications
- [ ] `TrusteeAnalyseView`: `resultText` / `resultDocuments` State
(Z. ~124-125) und die zugehoerige Output-Extraktion (Z. ~202-253)
raus. Nach Run-Ende: Navigation/Link zum Workspace-Detail (`runId`).
- [ ] `TrusteeAbschlussView`: hat KEIN `resultText`/`resultDocuments` --
zeigt nur Status/`runSummary`/`runError`. Aenderung: nach Run-Ende
zusaetzlich Link zum Workspace-Detail einfuegen (fuer die
Detail-Ansicht der Step-Outputs).
- [ ] Toast bei Run-Ende: erweitern um Klick-Action zum Detail-View.
- [ ] Sidebar-Badge auf "Automation" (Counter "neu seit letztem Besuch",
`localStorage`-basiert).
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|-----------------------------|------|
| 1 | Given non-template Workflow ohne `targetFeatureInstanceId`, When Save, Then 400 | must |
| 2 | Given Workflow mit `targetFeatureInstanceId`, When User keinen FeatureAccess auf diese Instanz hat, When Execute, Then 403 | must |
| 3 | Given Trustee-Run gestartet, When User Seite wechselt und zu Tab "Workspace" geht, Then Run mit allen Outputs sichtbar | must |
| 4 | Given Run mit generiertem File, When User auf Document-Karte klickt, Then File wird direkt heruntergeladen | must |
| 5 | Given FlowEditor offen, When User "Ziel-Instanz" waehlt, Then Datenquellen-Browser zeigt nur Daten dieser Instanz | must |
| 6 | Given Run-Ende-Toast, When User klickt, Then Navigation direkt zum WorkflowRunDetailView | should |
| 7 | Given neue Runs seit letztem Besuch, When User Sidebar sieht, Then "Automation" hat Counter-Badge | should |
| 8 | Given Template-Workflow kopiert, When Kopie in Mandate entsteht, Then `targetFeatureInstanceId` automatisch auf Ziel-Instanz gesetzt | must |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1 | api | ja | gateway/tests/features/graphicalEditor/test_workflow_save_target_instance.py | pending |
| T2 | 2 | api | ja | gateway/tests/features/graphicalEditor/test_workflow_execute_rbac.py | pending |
| T3 | 3,4 | e2e | ja | frontend_nyla/tests/e2e/workflow-run-workspace.spec.ts | pending |
| T4 | 5 | unit | ja | frontend_nyla/src/components/flowEditor/__tests__/TargetInstanceDropdown.test.tsx | pending |
| T5 | 8 | api | ja | gateway/tests/features/test_copy_template_workflows.py | pending |
## Dev-DB-Befund (2026-04-29)
- DB: `poweron_graphicaleditor`, Tabelle `AutoWorkflow`
- Spalte `featureInstanceId`: `text`, nullable=YES (kein NOT NULL)
- Total: 26 Workflows (24 regulaer, 2 System-Templates)
- 0 regulaere Workflows ohne `featureInstanceId` -> KEINE Daten-Migration noetig
- 2 System-Templates (`isTemplate=True`, `templateScope='system'`):
`featureInstanceId=NULL`, `mandateId=NULL` -> korrekt, bleiben NULL
- 2 orphan `AutoRun`-Rows (`workflowId='transient-XXX'`) -> Test-Reste,
zu loeschen
- Bestand-Migration: `targetFeatureInstanceId := featureInstanceId` fuer
alle non-template Rows (= GE-Instanz als initialer Default, da diese
Workflows generisch sind bzw. Graph-intern bereits konkrete Instance-
UUIDs haben)
## Risiken und Mitigationen
| Risiko | Impact | Mitigation |
|--------|--------|-----------|
| `executeGraph` Placeholder-Substitution aendert Graph-Semantik fuer bestehende Workflows | hoch | Pre-baked Templates haben bereits konkrete UUIDs -- Substitution greift nur bei `{{featureInstanceId}}`-Literals, die in kopierten Workflows nicht mehr vorkommen. Unit-Test mit beiden Faellen (pre-baked + Placeholder). |
| Bestand-Migration setzt `targetFeatureInstanceId := featureInstanceId` (GE-Instanz), aber manche kopierten Trustee-Workflows haben die echte Trustee-Instanz nur in den Graph-Nodes (pre-baked) | mittel | Fuer Bestand ist das OK: die pre-baked Nodes funktionieren weiterhin, da `executeGraph` existierende UUIDs nicht ueberschreibt. Nur neue Runs benoetigen korrekte `targetFeatureInstanceId`. User kann im FlowEditor nachtraeglich aendern. |
| Neue RBAC-Pruefung (`targetFeatureInstanceId`) koennte bestehende Execute-Calls brechen wenn User keinen expliziten FeatureAccess auf Ziel-Instanz hat | mittel | Audit-Query vor Release: alle non-template Workflows pruefen ob `ownerId` FeatureAccess auf `targetFeatureInstanceId` hat. Ggf. Soft-Rollout mit Warning statt 403 fuer 1 Sprint. |
| Pydantic `featureInstanceId: str` (required) vs DB nullable -- Templates koennten Save-Fehler erzeugen | niedrig | System-Templates werden im Code erzeugt, nicht via API. Pydantic-Validation wird bei API-Save erzwungen, bei internem `createWorkflow` umgangen. Bestehendes Verhalten. |
## Links
- Audit-Quelle: Subagent-Report + DB-Query 2026-04-29.
- Code-Cross-Check: Subagent 2026-04-29 (16 Annahmen verifiziert).
- Wiki: `wiki/b-reference/gateway/workflow.md`,
`wiki/b-reference/gateway/features/trustee.md`.
## Abschluss
- [ ] `wiki/b-reference/gateway/workflow.md` Abschnitt
"targetFeatureInstanceId + Workspace" anlegen
- [ ] `wiki/b-reference/gateway/features/trustee.md` Result-UX-Sektion
aktualisieren
- [ ] `wiki/TOPICS.md` ggf. Tab-Beschreibung
- [ ] Dieses Dokument -> `4-done/` verschoben