# WorkflowAutomation als System-Komponente — `graphicalEditor` raus aus dem Feature-Modell > **Treiber:** Entscheid aus `c-work/0-ideas/2026-06-CustomerCases-step1-architecture.md` / `…-step3-features-plan.md` **A0.4**. Dort wurde die Entkopplung als Roadmap markiert; sie wird **vorgezogen**, weil sie sonst die Solution-Schicht (Ownership/RBAC/Billing) später erneut einholt. > > **Namens-Konvention:** Die System-Komponente heisst **`WorkflowAutomation`** (Code-Token `WorkflowAutomation`/`workflowAutomation`) — semantisch eindeutig und gut greppbar. Das generische Wort «Automation» ist im Bestand schon mehrfach belegt (`AutomationsDashboardPage`, `routeAutomationWorkspace`, `/automations`, `workflows/automation2`) und bleibt dort **unverändert**. UI-Label (kundensichtbar): **«Workflow-Automation»** / «Automatisierung». ## Beschreibung und Kontext `graphicalEditor` ist heute ein **Hybrid**: formal ein Feature (Discovery, `TEMPLATE_ROLES` `graphicalEditor-*`, Store-Eintrag, `FeatureInstance` pro Mandant, per-Instanz-API `/api/workflows/{instanceId}/…`), architektonisch aber **mandanten- und feature-übergreifende Orchestrierungs-Infrastruktur**: - **Kein `getDataObjects()`** — keine Domänen-/Kundendaten; `AutoWorkflow`/`AutoRun` sind Orchestrierungs-Metadaten (eigene DB `poweron_graphicaleditor`). - **Einziges Feature mit `onStart`/`onStop`** — bootet beim App-Start den **globalen** Scheduler (Email-Poller startet **on-demand** via `emailPoller.ensureRunning`, gestoppt in `onStop`). - **Scheduler/Engine liegen ohnehin unter `modules/workflows/`** (System), nicht im Feature. - **`getWorkflows()` filtert nur nach `mandateId`** («cross-instance»); ein Graph referenziert legitim mehrere Features/Instanzen über per-Node `FeatureInstanceRef`. - **Cross-Mandate-Ops-Sicht existiert bereits**: `AutomationsDashboardPage` (`/automations`, Tabs Workflows/Runs/Details) ← `routeWorkflowDashboard.py` (`/api/system/workflow-runs`), RBAC über Mandats-Mitgliedschaft + Platform-Admin — **ganz ohne** GE-FeatureInstance. **Business-Treiber:** Die Solution-Schicht (CustomerCases) verankert Ownership an **Mandant + Run-as-Principal** (A0.1). Solange `graphicalEditor` als Feature mit per-Instanz-RBAC/Billing geführt wird, kollidiert jede Solution mit einem erfundenen «Host-Owner». Wir lösen die Wurzel: **WorkflowAutomation = System-Komponente** mit eigener Navigations-Gruppe «Workflow-Automation» (mehrere Tab-Seiten, mandanten-/feature-übergreifend strukturiert an einem Ort) und sauberem Daten-/RBAC-Modell. **Risiko, wenn NICHT gemacht:** doppelte Wahrheit (Feature-Instanz-RBAC vs. mandatsweite Realität), Billing fix auf `graphicalEditor` statt auf berührte Instanz, per-Instanz-URLs (`{instanceId}`) zementieren das Feature-Modell, und die Solution-Schicht erbt den Designfehler. ## Code-Stand nach Import-Refactoring (2026-06-05/06) Das Backend wurde zwischenzeitlich auf eine **Layer-Hierarchie L0–L7** refactored (`shared`=L0, `datamodels`=L1, `connectors`=L2, `dbHelpers`=L3, `interfaces`/`system`/`security`/`auth`=L4, `serviceCenter`/`workflows`/`features`=L5, `routes`=L6, `app`=L7; Quelle: `local/notes/refernce-analysis/platform-core-import-analyse.md`). Das ändert mehrere Plan-Annahmen — vieles ist **schon erledigt**; das GE↔Engine-Coupling wurde dort **explizit auf diesen Plan vertagt** («Deferred: WorkflowAutomation»). **Schon erledigt (Plan-Schritte entfallen/reduziert):** - **Contracts + Models liegen in L1:** `PORT_TYPE_CATALOG`/`PRIMITIVE_TYPES` → `datamodels/datamodelPortTypes.py`; `AutoWorkflow/AutoVersion/AutoRun/AutoStepLog/AutoTask` → `datamodels/datamodelWorkflowAutomation.py` (kanonisch, Re-Export-Shim `features/graphicalEditor/datamodelFeatureGraphicalEditor.py`, 7 Caller). → Das geplante «Shared Contracts»-Modul **ist faktisch `datamodels` (L1)**; **kein** neues `workflowContracts/` nötig. - **Feature-Lifecycle-Hooks existieren:** `mainGraphicalEditor.onInstanceCreate()` (Template-Copy, ex `interfaceFeatures._copyTemplateWorkflows`), `onMandateDelete()` (Cascade, ex `interfaceDbApp`), `onBootstrap()` (Seeding, ex `interfaceBootstrap`) — dynamisch via `shared/featureDiscovery.loadFeatureMainModules()`. → Plan-Touchpoints in `interfaceFeatures/interfaceDbApp/interfaceBootstrap` sind jetzt **Feature-Hooks**. - **Weitere Verschiebungen:** `NAVIGATION_SECTIONS` → `datamodels/datamodelNavigation.py`; `EventManager` → `shared/eventManager.py`; `parseInlineRuns` → `shared/documentUtils.py`; `serviceHub` → `serviceCenter/serviceHub.py`; Service-Exceptions → `datamodels/serviceExceptions.py`; `WorkflowStoppedException`/`checkWorkflowStopped` → `shared/workflowState.py`. **Explizit auf diesen Plan vertagt (Deferred-Liste):** - `features.graphicalEditor → workflows` (21) + `workflows → features.graphicalEditor` (40) → Ziel **0** (kollabiert, sobald Editor + Engine **ein** Modul sind). - `serviceCenter → features.graphicalEditor` (5×, `serviceAgent/workflowTools.py`). - `interfaces/interfaceDbManagement.py:936` (lazy → `workflowArtifactVisibility`). - Re-Export-Shims entfernen: `datamodelFeatureGraphicalEditor.py` (7 Caller), `workflows/processing/shared/stateTools.py` (7 Caller). - **Boundary-Leck besteht weiter (Delaminierung offen):** `PauseForHumanTaskError` (`automation2/executors/inputExecutor.py`) und `coerceDocumentDataToBytes` (`automation2/executors/actionNodeExecutor.py`) werden von `methods/methodContext/actions/setContext.py` bzw. `methods/methodFile/actions/create.py` top-level importiert → vor Engine-Umzug extrahieren (`PauseForHumanTaskError` → `datamodels/serviceExceptions.py`, `coerceDocumentDataToBytes` → `shared/documentUtils.py`). ## Fokus und kritische Details - **Kein Big-Bang.** Parallel-Pfade bauen (mandatsweite API neben `{instanceId}`-API), dann umschalten, dann Feature-Mantel entfernen. Jede Phase ist für sich lauffähig. - **Scheduler-Boot ist fragil.** Er hängt am Feature-`onStart` (`app.py`-Lifespan iteriert alle Feature-Module). Vor dem Entfernen des Feature-Mantels **muss** der Boot in einen System-Lifespan-Hook umziehen, sonst startet der Scheduler nicht mehr. - **`featureInstanceId` hat heute zwei Bedeutungen:** (a) GE-Owner/RBAC-Scope auf `AutoWorkflow`, (b) Daten-Scope der Ausführung (`targetFeatureInstanceId` + per-Node `FeatureInstanceRef`). Nur (a) wird abgebaut; (b) bleibt — es ist die Cross-Feature-Referenz. - **Kaum DDL nötig (Code-verifiziert).** Tabellen werden **ohne `NOT NULL`** (ausser `id`-PK) und **ohne DB-FKs** angelegt (`connectorDbPostgre.py` `_create_table_from_model`); FKs sind app-level (`fkRegistry`, `app.py` `validateFkTargets`). Heisst: `featureInstanceId` ist **schon jetzt DB-nullable** → «nullable machen» ist nur `Optional[str]` im Pydantic-Modell (`datamodelFeatureGraphicalEditor.py`) + Query-/Code-Anpassungen, **keine Migration**. `runAsPrincipal` = additives `ADD COLUMN` (von `_ensureTableExists` abgedeckt). Echte Arbeit = Application-Logik (Routen/Queries, die `featureInstanceId` voraussetzen), nicht Schema. - **Bestehende Daten grandfathern.** `AutoWorkflow`-Zeilen tragen `featureInstanceId`; `FeatureAccess`-Grants und per-Instanz-Rollen existieren. Datenmigration als **einmaliges Skript** (Muster: `scripts/script_migrate_feature_instance_refs.py`; Cascade-Vorbild: `routeWorkflowDashboard._cascadeDeleteAutoWorkflow`). Migration muss diese erhalten/überführen, nicht löschen. - **RBAC-Präzedenz nutzen — aber sie deckt nur Lesen.** `routeWorkflowDashboard.py` (`_scopedWorkflowFilter`/`_getAdminMandateIds` + `isPlatformAdmin`) ist ein **Read/List/Delete**-Muster — **kein** Create/Update/Publish/Execute. Die **39** `_validateInstanceAccess`-Sites in `routeFeatureGraphicalEditor.py` sind grösstenteils Write/Execute; dafür braucht es einen **eigens entworfenen** Helper `_validateWorkflowAccess(workflow, context, action)` (read vs. write), nicht 1:1 das Dashboard-Muster. `system`-Pseudo-Feature (`instantiable=False`) ist die Vorlage für Katalog-/RBAC-Registrierung ohne Instanzen. - **Shared Contracts schon in L1, Node-Katalog noch beim Editor.** `PORT_TYPE_CATALOG` liegt seit dem Refactoring in `datamodels/datamodelPortTypes.py` (erledigt). Der Node-Katalog (`STATIC_NODE_TYPES`) ist noch unter `features/graphicalEditor/nodeDefinitions/` und wird cross-layer genutzt (`system/i18nBootSync.py`, `serviceAgent/workflowTools.py`) — er zieht mit dem Editor in die Komponente. **Offenes Boundary-Leck:** zwei Helfer (`PauseForHumanTaskError`, `coerceDocumentDataToBytes`) liegen in `automation2.executors`, werden aber von `methods/` importiert → **vor** dem Engine-Umzug delaminieren. - **Zwei UI-Oberflächen sauber trennen** (siehe «Verhältnis zur Solution-Schicht»): die **WorkflowAutomation-Gruppe** (technische Orchestrierung, cross-mandate, für Power-User/Admins) vs. die kundenseitige **«Lösungen»-Surface** pro Host-Feature (bleibt eingebettet, Präsentation). ## Ziel und Nicht-Ziele - **Ziel:** `graphicalEditor` als System-Komponente **`WorkflowAutomation`** führen — eigene Top-Level-Navigationsgruppe mit Tab-Seiten (Editor · Vorlagen · Workflows · Läufe · Tasks), mandanten-/feature-übergreifend. - **Ziel:** Mandatsweite API + RBAC (kein `{instanceId}` als RBAC-Anker), Scheduler-Boot als System-Lifespan, DB-Scoping auf **Mandant + Principal** (statt GE-Owner-Instanz). - **Ziel:** Bestehende Workflows/Runs/Tasks und Grants verlustfrei migrieren. - **Ziel:** Fundament legen für die `Solution`-Schicht (A0.1) — Solutions hängen an Mandant + Run-as-Principal, nicht an einer GE-Instanz. - **Explizit NICHT:** Graph-Format/Node-Semantik ändern (`modules/workflows/automation2/*` bleibt funktional gleich). **Ausnahme:** per-Node-Billing-Attribution (AC5) ist eine bewusste, lokal begrenzte Executor-Änderung (berührte `FeatureInstanceRef` in den `recordUsage`-Kontext threaden) — kein Graph-/Semantik-Umbau. - **Explizit NICHT:** den geteilten **Workflow-Execution-Layer** (`workflows/methods/`, `workflows/processing/`) in die Komponente ziehen — er bleibt unter `modules/workflows/` (Chats/Agents nutzen ihn **ohne** Automation). - **Explizit NICHT:** die kundenseitige «Lösungen»-Surface in diesem Plan bauen (→ CustomerCases-Pläne; dieser Plan liefert nur das Substrat). - **Explizit NICHT:** per-Node `FeatureInstanceRef` / `targetFeatureInstanceId` (Daten-Scope) abschaffen. - **Explizit NICHT:** DB-Engine/Datenbankname wechseln (`poweron_graphicaleditor` bleibt). - **Explizit NICHT:** bestehende «Automation»-Code-Namen umbenennen (`AutomationsDashboardPage`, `routeAutomationWorkspace`, `workflows/automation2`) — nur die *neue* Komponente trägt das Token `WorkflowAutomation`. ## Zielarchitektur (Kurz) ``` VORHER (Feature): Nav: Mandant → graphicalEditor-Instanz → {editor, templates, tasks} API: /api/workflows/{geInstanceId}/... RBAC: FeatureAccess auf GE-Instanz DB : AutoWorkflow.featureInstanceId = GE-Owner (RBAC-Scope) Boot: graphicalEditor.onStart → Scheduler NACHHER (System-Komponente WorkflowAutomation): Nav: Top-Level-Gruppe «Workflow-Automation» (einmal) → Tabs {Editor, Vorlagen, Workflows, Läufe, Tasks} + Mandanten-/Instanz-Picker in der Seite (cross-mandate) API: /api/workflow-automation/... RBAC: Mandats-Mitgliedschaft + System-Rollen + isPlatformAdmin DB : AutoWorkflow.mandateId + runAsPrincipal (Owner); featureInstanceId → entfällt als RBAC-Anker targetFeatureInstanceId + per-Node FeatureInstanceRef bleiben (Daten-Scope) Boot: System-Lifespan (app.py) → Scheduler ``` ## Namensgebung & modulare Struktur **Name der System-Komponente: `WorkflowAutomation`** (UI-Label «Workflow-Automation» / «Automatisierung»). `graphicalEditor` ist **nicht** der Name der Komponente, sondern **ein Modul** darin (der Graph-/Flow-Editor). Begründung für `WorkflowAutomation`: semantisch eindeutig + als Token greppbar (das generische «Automation» ist im Code mehrfach belegt und bleibt). (Verworfen: «Automation» allein — mehrdeutig; «Orchestration» — neuer Begriff ohne Code-Verankerung.) Die Komponente bündelt die bestehenden, heute verstreuten **Automation**-Teile (`features/graphicalEditor/`, `workflows/automation2/`, `workflows/scheduler/`) **plus** die kommende L3/L4-Schicht zu **einem nachvollziehbaren, modularen System**. Den geteilten **Workflow-Execution-Layer** (`workflows/methods/`, `workflows/processing/`) bündelt sie **nicht** — der bleibt eigenständig (siehe Abgrenzung unten). Jedes Modul hat eine klare Verantwortung und mappt auf die L2–L4-Schichten der Architektur. | L | Modul | Verantwortung | |---|---|---| | L2 | **editor** (= heute `graphicalEditor` Authoring) | Graph/Flow-Authoring, Node-Registry, Adapter, Editor-UI-Backend (konsumiert Shared Contracts) | | L2 | **engine** (= heute `workflows/automation2/`) | Graph-Ausführungs-Runtime (Graph → Run) | | L2 | **scheduler** | Zeit-/Event-Trigger, Email-Poller | | L3 | **solutions** | konfigurierte Workflows (Solution-Modell, Settings-Injektion) | | L4 | **launcher** | Katalog/Vorlagen + «neue Automation» (Template/AI) + Template-Instanziierung | | L4 | **monitoring** | Läufe, Tasks, Logs (Ops-Sicht) | > **NICHT Teil der Komponente (geteilt, Code-verifiziert) — Komponente *konsumiert* abwärts:** > - **L1 Toolbox (Execution)** = `workflows/methods/` (generische Actions) + `workflows/processing/` (ActionExecutor, methodDiscovery, modes) — von Chats/Agents genutzt. (Feature-eigene Actions liegen seit Refactoring beim Feature, z. B. `features/trustee/workflows/`.) > - **Contracts/Models = `datamodels` (L1, bereits erledigt):** `datamodelPortTypes.py` (`PORT_TYPE_CATALOG`), `datamodelWorkflowAutomation.py` (`AutoWorkflow…`). Die Komponente konsumiert L1, besitzt sie **nicht**. (Der Node-Katalog `STATIC_NODE_TYPES` liegt noch beim Editor und zieht mit ihm um.) ### Platform (`platform-core/modules/workflowAutomation/`) ``` modules/workflowAutomation/ # NEU — die Automation-Komponente ├─ mainWorkflowAutomation.py # System-Registrierung (instantiable=False) + Lifespan-Hook (Scheduler-Boot; Poller-Stop) + Lifecycle-Hooks (onMandateDelete/onBootstrap) │ # Models kanonisch in datamodels/datamodelWorkflowAutomation.py (L1) — Komponente importiert sie ├─ routes/ # mandatsweite API /api/workflow-automation/{workflows,versions,runs,tasks,nodes,solutions,...} ├─ editor/ # L2 — Graph-Editor-Backend (nodeRegistry, nodeAdapter, adapterValidator) — konsumiert Shared Contracts ├─ engine/ # L2 — Graph-Ausführung (heute workflows/automation2/: executionEngine, executors, graphUtils, runEnvelope, scheduleCron) ├─ scheduler/ # L2 — Scheduler + emailPoller (heute workflows/scheduler/ + graphicalEditor/emailPoller.py) ├─ solutions/ # L3 — solutionService, datamodelSolution (Settings-Injektion, run-as-Principal) └─ launcher/ # L4 — Katalog/Templates + AI-Erstellung + Template-Instanziierung modules/workflows/ # BLEIBT — geteilter Execution-Layer (NICHT Teil der Komponente) ├─ methods/ # L1-Toolbox — generische Actions (methodAi/Sharepoint/Outlook/File/Context/Clickup/Jira/Redmine) ├─ processing/ # ActionExecutor, methodDiscovery, modes/adaptive, workflowProcessor └─ workflowManager.py # Chat-/Agent-seitige Workflow-Verarbeitung modules/datamodels/ # BLEIBT — Contracts/Models (L1, bereits dorthin refactored) ├─ datamodelPortTypes.py # PORT_TYPE_CATALOG / PRIMITIVE_TYPES └─ datamodelWorkflowAutomation.py# AutoWorkflow / AutoVersion / AutoRun / AutoStepLog / AutoTask (kanonisch) ``` > **Abgrenzung Komponente ↔ Contracts ↔ Execution (Code-verifiziert, korrigiert).** Drei Schichten, strikt abwärts: **Komponente → Shared Contracts → Execution-Layer.** > - **In die Komponente:** `workflows/automation2/` (Graph-Engine) → `engine/`; `workflows/scheduler/` → `scheduler/`; `graphicalEditor/{nodeRegistry, adapterValidator, emailPoller, Editor-Backend}` → `editor/`/`scheduler/`. > - **Bleibt geteilt unter `modules/workflows/`:** `methods/` (Actions), `processing/` (`ActionExecutor`, `methodDiscovery`, `modes/`, `adaptive/`, `workflowProcessor`) — genutzt von Chats/Agents (`serviceAgent/{actionToolAdapter,mainServiceAgent}`, `serviceAi/*`, `serviceGeneration/*`) **ohne** Automation. > - **Contracts/Models:** `PORT_TYPE_CATALOG` + `AutoWorkflow…` liegen **bereits** in `datamodels/` (L1, durchs Refactoring); Komponente konsumiert sie. Der Node-Katalog (`STATIC_NODE_TYPES`) liegt noch beim Editor und zieht mit ihm um (cross-Caller: `system/i18nBootSync.py`, `serviceAgent/workflowTools.py` beachten). > - **Korrektur eines früheren Fehlers (weiterhin gültig):** «`automation2` wird von Chats nicht importiert» war **falsch** und ist **noch nicht behoben**. `methods/methodFile/actions/create.py` (→ `coerceDocumentDataToBytes`) + `methods/methodContext/actions/setContext.py` (→ `PauseForHumanTaskError`) importieren `automation2.executors` top-level — geladen via `methodDiscovery` im Chat-/Agent-Pfad. **Diese zwei Helfer vor dem Engine-Umzug extrahieren** (`→ datamodels/serviceExceptions.py` bzw. `shared/documentUtils.py`), sonst importiert Shared aufwärts in die Komponente. > > **Guard-Test:** sicherstellen, dass `modules.workflows.{methods,processing}` + `modules.serviceCenter` importierbar sind, **ohne** `modules.workflowAutomation.*` zu laden. Migration nur der Automation-Teile (Re-Export/Shims; ~20 Importer von `workflows.automation2`/`scheduler`). **Kein funktionaler Umbau der Engine** (Ausnahme: per-Node-Billing, AC5). ### UI (`ui-nyla/src/`) ``` pages/workflowAutomation/ ├─ WorkflowAutomationHubPage.tsx # Container: Top-Level-Gruppe «Workflow-Automation», Tabs, Mandanten-/Instanz-Scope-Selector └─ tabs/ ├─ LauncherTab.tsx # L4 — Katalog/Neu (Vorlage oder AI) ├─ SolutionsTab.tsx # L3 — konfigurierte Solutions (Admin/Power-User-Sicht) ├─ EditorTab.tsx # L2 — Graph-Editor (Keep-Alive) ├─ WorkflowsTab.tsx # alle Workflows (cross-mandate/-feature) ├─ RunsTab.tsx # L4 — Läufe/Monitoring └─ TasksTab.tsx # Human-Tasks components/workflowAutomation/ ├─ FlowEditor/ # heute components/GraphicalEditor/ ├─ Launcher/ ├─ Solutions/ └─ Monitoring/ api/workflowAutomationApi.ts # /api/workflow-automation/... (ersetzt workflowApi {instanceId}-Pfade) ``` - **Eine** Top-Level-Nav-Gruppe «Workflow-Automation» (nicht pro Mandant/Instanz); Tabs via `?tab=` deeplinkbar (Muster `AutomationsDashboardPage`). - **Abgrenzung zur kundenseitigen «Lösungen»-Surface:** Der `SolutionsTab` hier ist die **Admin/Power-User**-Sicht über alle Solutions; die kundenseitige `SolutionsView` (L4) bleibt **pro Host-Feature eingebettet** (Trustee usw.) und zeigt nur die für diesen Kontext konfigurierten Solutions. Beide nutzen `api/workflowAutomationApi.ts`. ## Verhältnis zur Solution-Schicht (CustomerCases) - **WorkflowAutomation-Gruppe = Substrat-UI** (Power-User/Admin): roher Editor, Templates, alle Workflows/Läufe/Tasks über Mandanten/Features. - **«Lösungen»-Surface = kundenseitig**, pro Host-Feature eingebettet (Trustee usw.) — konsumiert dasselbe Substrat, zeigt aber nur konfigurierbare Solutions. Bleibt wie in den CustomerCases-Plänen. - Beide hängen am gleichen Modell: **Mandant + Run-as-Principal**, nicht an einer GE-Instanz. Dieser Plan ist die **Voraussetzung** für A0.1/A0.2. ## Betroffene Module - **Gateway (platform-core):** - `app.py` — Scheduler/Email-Poller-Boot in **System-Lifespan** (statt Feature-`onStart`). - `modules/workflows/scheduler/mainScheduler.py` — Boot-Aufruf; `JOB_ID_PREFIX`/`_CALLBACK_NAME` (`"graphicalEditor.*"`) sind in Job-IDs persistiert → Literale behalten oder Job-IDs migrieren (in-memory APScheduler, Re-Sync beim Boot → risikoarm); `if not instanceId`-Guard entfernen (s. Phase 1); **Run-as-Principal** statt globalem `event`-Sysadmin (A0.1). - **Delaminierung zuerst (Phase 0.5):** `PauseForHumanTaskError` → `datamodels/serviceExceptions.py`, `coerceDocumentDataToBytes` → `shared/documentUtils.py` (Re-Export-Shim in `automation2/executors/`). Contracts (`portTypes`, Models) sind bereits in `datamodels` (L1). Erst dann ist der Engine-Umzug aufwärts-importfrei. - `modules/workflows/{automation2,scheduler}/` → in die Komponente: `automation2` → `workflowAutomation/engine/`, `scheduler` → `workflowAutomation/scheduler/` (Re-Export-Shims). **`automation2` = die Graph-Engine**, kein funktionaler Umbau (Ausnahme: per-Node-Billing-Kontext, AC5). - **`modules/workflows/{methods,processing}/` bleiben** geteilt (Execution-Layer), **`datamodels/`** bleibt L1 — **nicht** verschieben. - `modules/features/graphicalEditor/` → konzeptionell nach `modules/workflowAutomation/editor/` führen; `mainGraphicalEditor.py`: `getFeatureDefinition`/`TEMPLATE_ROLES`/`onStart` entfernen bzw. zu System-Registrierung umbauen (`instantiable=False`, Vorbild `mainSystem.py`). - `modules/features/graphicalEditor/routeFeatureGraphicalEditor.py` — neue **mandatsweite** Routen `/api/workflow-automation/...`; die **39** `_validateInstanceAccess`-Sites bewusst auf einen neuen `_validateWorkflowAccess(workflow, context, action)` (read vs. write) migrieren — Read-Scoping vom Dashboard, Write/Execute eigens entworfen, `isPlatformAdmin`-Bypass. `_validateTargetInstance` zusätzlich um **Mandatsgrenze** ergänzen (AC8, heute nur `FeatureAccess`). - `modules/routes/routeSystem.py` — GE-Hardcode in `_getFeatureUiObjects` entfernen; neuen statischen Nav-Block `workflowAutomation` ausliefern. - `modules/datamodels/datamodelNavigation.py` — `NAVIGATION_SECTIONS` (post-Refactoring hier, **nicht** mehr in `mainSystem.py`): Block `id="workflowAutomation"` (order ~25) ergänzen; `ui.system.workflowAutomation.*`-Objekte; Store-Eintrag `resource.store.graphicalEditor` entfernen. - `modules/features/graphicalEditor/mainGraphicalEditor.py` **`onInstanceCreate()`** — heute Template-Copy bei FeatureInstance-Erstellung (stempelt `{{featureInstanceId}}`/`targetFeatureInstanceId`, auch Trustee/Redmine). Ohne GE-Instanz **entfällt der Hook-Trigger** → Template-Instanziierung explizit in den `launcher/`-Flow verlagern (inkl. Demo-Bootstrap); Trustee/Redmine-Platzhalter-Vertrag beachten. - `modules/features/graphicalEditor/mainGraphicalEditor.py` **`onMandateDelete()`** (Cascade) / **`onBootstrap()`** (Seeding) — sind bereits Feature-Hooks (ex `interfaceDbApp`/`interfaceBootstrap`); ziehen mit der Komponente um, Cascade auf mandatsweit umstellen. - `modules/interfaces/interfaceRbac.py` — `TABLE_NAMESPACE` (8 Einträge: `AutoWorkflow/AutoVersion/AutoRun/AutoStepLog/AutoTask` + Legacy `Automation2*`): **Option B** — Namespace `feature.graphicalEditor` **beibehalten** + System-DATA-Objekte ergänzen. (Ein Flip auf `system.workflowAutomation` verwaist bestehende `AccessRule`/Rollen-Zeilen; nur mit zwingend gekoppeltem Migrations-Skript im selben Deploy.) - `modules/interfaces/interfaceBootstrap.py` — Store-Grant + ggf. neue System-Rollen `workflowAutomation-*` (Hook-Aufruf bleibt; GE-Wissen ist dynamisch via `loadFeatureMainModules`). - `modules/features/graphicalEditor/mainGraphicalEditor.py` `getGraphicalEditorServices` — `featureCode`/`featureInstanceId` im Hub (jetzt `serviceCenter/serviceHub.py`): Billing **per-Node** auf berührte Instanz (A0.2, Executor-Change), Mandate = `mandateId`. - `serviceCenter/.../toolboxRegistry.py` + `serviceCenter/services/serviceAgent/workflowTools.py` — `featureCode="graphicalEditor"` → `workflowAutomation`; `workflowTools` ist der verbleibende `serviceCenter → features.graphicalEditor` (5×, deferred) → entfällt mit Umzug. - **Weitere GE-Importer (Deferred-Liste):** `system/i18nBootSync.py` (zieht `STATIC_NODE_TYPES` — mit Node-Katalog mitziehen), `routeSystem.py` (`UI_OBJECTS` + Dashboard-`_ensureTableExists`), `routeAdminFeatures.py`, `interfaceDbManagement.py:936` (lazy `workflowArtifactVisibility`), `interfaceFeatureGraphicalEditor.getAllWorkflowsForScheduling` (Lesepfad von Scheduler **und** Dashboard — stabil halten/shimmen); Re-Export-Shims `datamodelFeatureGraphicalEditor.py` (7) + `processing/shared/stateTools.py` (7) entfernen. - **Frontend (ui-nyla):** - `src/components/Navigation/MandateNavigation.tsx` — `block.id === 'workflowAutomation'` als eigene Top-Level-Gruppe rendern (analog `admin`). - `src/App.tsx` — `/workflow-automation/*`-Routen (Hub + `?tab=` Deeplinks); Redirects der alten `/mandates/.../graphicalEditor/.../*`. - `src/pages/AutomationsDashboardPage.tsx` → in **`WorkflowAutomationHubPage`** mit Tabs überführen (Workflows, Läufe, Editor, Vorlagen, Tasks). - `src/pages/FeatureView.tsx` — `graphicalEditor` aus `VIEW_COMPONENTS` entfernen. - `src/pages/views/graphicalEditor/*` — von `useInstanceId()`/`useCurrentInstance()` auf expliziten Mandanten-/Instanz-Picker (Scope-Selector) umstellen. - `src/config/keepAliveRoutes.tsx` — Editor-Keep-Alive-Pfad auf `/workflow-automation/editor` umziehen. - `src/config/pageRegistry.tsx` — Icons für `page.system.workflowAutomation.*`. - `src/api/workflowApi.ts` — URLs `/api/workflows/{instanceId}/…` → `/api/workflow-automation/…`. - Cross-Links: `TrusteeAnalyseView`/`TrusteeAbschlussView` (`/automations?tab=…`), `Store.tsx` (GE-Karte entfernen). - **DB-Migration:** minimal — `runAsPrincipal` = additives `ADD COLUMN`; `featureInstanceId` ist **bereits DB-nullable** (kein DDL, keine DB-FK; nur Pydantic `Optional` + Code); Daten-/Grant-Backfill via Skript. **Kein** Namespace-Flip (Option B). - **Andere:** RBAC (System-Objekte/-Rollen, FeatureAccess-Grants migrieren, GE-Feature-Rollen entfallen), Billing (per-Node-Attribution), Demo-Configs (`pwgDemo2026`/`investorDemo2026`), Tests, `.cursor/rules/*` (s. Abschluss). ## Entscheidungen | Datum | Entscheidung | Begründung | |-------|-------------|------------| | 2026-06-05 | System-Komponente heisst **`WorkflowAutomation`** (Token `WorkflowAutomation`/`workflowAutomation`); UI-Label «Workflow-Automation» | semantisch eindeutig + greppbar; generisches «Automation» bleibt für Bestand | | 2026-06-05 | `graphicalEditor` = **ein Modul** (editor) innerhalb von WorkflowAutomation | graphEditor ist nur das Authoring-Element | | 2026-06-05 | **Automation ≠ Workflow-Execution.** `methods`/`processing` bleiben geteilter Layer unter `modules/workflows/`; nur `automation2` (engine) + `scheduler` + `graphicalEditor` wandern in die Komponente | Chats/Agents nutzen den Execution-Layer **ohne** Automation; Abwärts-Abhängigkeit Komponente→Execution (Code-belegt) | | 2026-06-07 | **Contracts/Models in `datamodels` (L1)** statt neuem `workflowContracts/` — durchs Import-Refactoring bereits erledigt (`datamodelPortTypes`, `datamodelWorkflowAutomation`) | Layer-Hierarchie L0–L7; Komponente konsumiert L1 abwärts | | 2026-06-07 | **Delaminierung** zweier Helfer (`PauseForHumanTaskError` → `datamodels/serviceExceptions.py`, `coerceDocumentDataToBytes` → `shared/documentUtils.py`) aus `automation2.executors` **vor** Engine-Umzug | Boundary-Leck besteht weiter: Shared `methods/` importiert sie heute aufwärts | | 2026-06-07 | Plan an Import-Refactoring (2026-06-05/06) angeglichen; Deferred-Liste aus `import-analyse.md` als Scope übernommen | refactor hat GE↔Engine-Coupling explizit an diesen Plan delegiert | | 2026-06-05 | RBAC-Namespace **Option B** (beibehalten + System-DATA-Objekte), **kein** Flip | verhindert verwaiste `AccessRule`/Rollen-Zeilen | | 2026-06-05 | Eigener **Write-RBAC-Helper** `_validateWorkflowAccess` (read/write) für die 39 Sites; GE-Feature-Rollen entfallen, Modell = Mandats-Mitgliedschaft/-Admin + `isPlatformAdmin` | Dashboard-Muster deckt nur Lesen; `instantiable=False` ⇒ keine Feature-Instanz-Rollen mehr | | 2026-06-05 | **Modulares System** (editor/engine/scheduler/solutions/launcher/monitoring), gemappt auf L2–L4; L1 (Actions/Execution) + Contracts bleiben geteilt | wartbar/nachvollziehbar; bündelt heute verstreute Automation-Teile + L3/L4 | | 2026-06-05 | Code nach **`modules/workflowAutomation/`** (Platform) bzw. `pages/workflowAutomation/` + `components/workflowAutomation/` (UI); schrittweise via Re-Export-Shims | sauberer Schnitt; Imports brechen nicht | | 2026-06-05 | `graphicalEditor` wird **System-Komponente** (Feature-Mantel entfällt) | kein DATA, eigene DB für alle Features, globaler Scheduler-Boot → ist Infrastruktur | | 2026-06-05 | Eigene Top-Level-Nav-Gruppe **«Workflow-Automation»** mit Tab-Seiten | strukturiert an einem Ort, cross-mandate/-feature; nicht als Feature-Seite | | 2026-06-05 | Scheduler-/Poller-Boot in **System-Lifespan** (`app.py`) statt Feature-`onStart` | Boot von der Feature-Plug-in-Mechanik lösen | | 2026-06-05 | API mandatsweit `/api/workflow-automation/…`; RBAC = Mandats-Mitgliedschaft + System-Rollen + `isPlatformAdmin` | `{instanceId}` als RBAC-Anker abbauen; bestehende Dashboard-Muster wiederverwenden | | 2026-06-05 | `AutoWorkflow.featureInstanceId` verliert RBAC-Bedeutung; `mandateId`+`runAsPrincipal` = Owner; `targetFeatureInstanceId`/per-Node-Ref bleiben | A0.1/A0.2; Daten-Scope bleibt funktional | | 2026-06-05 | Migration **phasenweise, parallel** (kein Big-Bang); Bestand grandfathern | Risiko begrenzen, jederzeit lauffähig | | 2026-06-05 | DB-Name `poweron_graphicaleditor` bleibt | YAGNI; Rename = unnötiges Risiko | ## Umsetzungs-Checkliste **Phase 0 — Scheduler-Boot entkoppeln (risikoarm, zuerst):** - [x] Scheduler-Start in System-Lifespan (`app.py`, **nach** `setSchedulerMainLoop`/`eventManager.start()`); Poller bleibt **on-demand** (`ensureRunning`), `onStop`-Stop verlagert (Scheduler + Email-Poller in `app.py` Shutdown Schritt 3.5); GE-`onStart`/`onStop` **entfernt** *(2026-06-07)* - [x] Routen-Registrierung via `loadFeatureMainModules()` weiterhin aktiv (Feature-Mantel noch da) - [ ] Smoke: **bestehende (grandfathered)** geplante Runs feuern weiterhin, auch ohne GE-Feature-Instanz im Mandanten → manuell testen bei nächstem Deploy **Phase 0.5 — Delaminierung (vor jedem Code-Umzug):** - [x] ~~`portTypes`/`PORT_TYPE_CATALOG` delaminieren~~ — **erledigt** durch Refactoring (`datamodels/datamodelPortTypes.py`, L1) - [x] ~~Kanonische Models nach L1~~ — **erledigt** (`datamodels/datamodelWorkflowAutomation.py`) - [x] `PauseForHumanTaskError` + `PauseForEmailWaitError` → `datamodels/serviceExceptions.py` (kanonisch); Re-Export-Shim in `automation2/executors/inputExecutor.py`; Caller `methods/methodContext/actions/setContext.py` umgebogen *(2026-06-07)* - [x] `coerceDocumentDataToBytes` + `_looksLikeAsciiBase64Payload` → `shared/documentUtils.py` (kanonisch); Re-Export-Shim in `automation2/executors/actionNodeExecutor.py`; Caller `methods/methodFile/actions/create.py` umgebogen *(2026-06-07)* - [x] Guard-Test: `modules.workflows.{methods.methodContext.actions.setContext, methods.methodFile.actions.create}` importierbar **ohne** `modules.workflows.automation2.*` → bestätigt, kein `automation2`-Modul transitiv geladen *(2026-06-07)* - [ ] Node-Katalog (`STATIC_NODE_TYPES`, `features/graphicalEditor/nodeDefinitions/`): bleibt beim Editor, zieht mit ihm um; cross-Caller `system/i18nBootSync.py` + `serviceAgent/workflowTools.py` beachten **Phase 1 — Mandatsweite API + System-RBAC (parallel zur Bestands-API):** - [x] `AutoWorkflow.runAsPrincipal` in `datamodels/datamodelWorkflowAutomation.py` ergänzt (Optional[str], nullable, softFk → UserInDB) - [x] System-RBAC + Nav-Block: `workflowAutomation` Section (order 25) mit 5 UI-Objekten (`ui.system.workflowAutomation.{workflows,editor,templates,runs,tasks}`) in `datamodelNavigation.py`; automatisch von `_buildUiObjectsFromNavigation` in RBAC-Katalog aufgenommen - [x] Neue Routen `routeWorkflowAutomation.py`: `/api/workflow-automation/{workflows,versions,runs,tasks,steps}` (10 Endpunkte) mandatsweit; **Write-RBAC-Helper** `_validateWorkflowAccess(context, workflow, action)` (read = mandate-member, write/execute/delete = mandate-admin), `isPlatformAdmin`-Bypass; registriert in `app.py` - [x] Scheduler **von `featureInstanceId` entkoppelt**: `if not instanceId`-Guard in `mainScheduler._syncScheduledWorkflows` entfernt; `instanceId` default `""` statt skip; Bestand läuft weiter - [x] Runtime-Mandatsvalidierung beim `FeatureInstanceRef`-Auflösen: `_validateFeatureInstanceMandates()` in `executionEngine.py` nach `materializeFeatureInstanceRefs()` — defence-in-depth warning-log bei cross-mandate-Referenzen (A0.2) **Phase 2 — UI «Workflow-Automation»-Gruppe:** - [x] Nav-Block `workflowAutomation` in `datamodels/datamodelNavigation.py` (`NAVIGATION_SECTIONS`) — bereits in Phase 1 erledigt - [x] `/workflow-automation`-Route + `WorkflowAutomationPage.tsx` Hub-Seite mit Tabs (Workflows · Läufe · Tasks), `?tab=`-Deeplinks; Import in `App.tsx` - [x] Icons in `pageRegistry.tsx` für `page.system.workflowAutomation.*` registriert (FaSitemap, FaProjectDiagram, FaCopy, FaPlay, FaTasks) - [x] `MandateNavigation.tsx`: `extraStaticBlocks`-Logik — unbekannte statische Blöcke werden als eigene Nav-Sektionen gerendert (nicht mehr in System-Block gemergt) - Mandanten-Scope-Selector / Editor-Entkopplung / Keep-Alive: spätere Iteration (Editor braucht instanceId bis Feature-Mantel vollständig entfernt) **Phase 3 — DB & Datenmigration:** - [x] `AutoWorkflow.featureInstanceId` → `Optional[str]` (default=None, softFk=True); Pydantic-only, **kein DDL** — Spalte bereits DB-nullable - [x] Cascade-Delete: `_cascadeDeleteAutoWorkflow` in `routeWorkflowDashboard.py` ist bereits mandatsweit (löscht nach workflowId, unabhängig von featureInstanceId) **Phase 4 — Feature-Mantel deprecaten (nicht entfernen — Koexistenz):** - [x] GE-Hardcode in `routeSystem.py` als DEPRECATED markiert (bleibt für bestehende Feature-Instanzen) - [x] Import-Umstellung: `routeWorkflowDashboard.py`, `routeAutomationWorkspace.py`, `routeSystem.py` importieren Models aus kanonischer `datamodels.datamodelWorkflowAutomation` statt aus `features.graphicalEditor.datamodelFeatureGraphicalEditor` - Bestehende Feature-Routen `/api/workflows/{instanceId}/…` bleiben aktiv (Koexistenz mit neuer mandatsweiter API) - Feature-Registration (`getFeatureDefinition`, `UI_OBJECTS`, Store) bleibt — wird erst bei vollständiger Migration entfernt **Abschluss:** - [x] `_CHANGELOG.md` aktualisiert ## Akzeptanzkriterien | # | Kriterium (Given-When-Then) | Prio | |---|---|---| | 1 | Given ein Mandant ohne GE-FeatureInstanz, When ein **bestehender (grandfathered)** geplanter Workflow fällig ist, Then feuert der Scheduler (Boot via System-Lifespan). *(Instanz-freie Workflows: erst nach Phase-1-Scheduler-Entkopplung.)* | must | | 2 | Given ein User mit Mandats-Mitgliedschaft, When er «Workflow-Automation» öffnet, Then sieht er eine Top-Level-Gruppe mit Tabs (Editor/Vorlagen/Workflows/Läufe/Tasks) — **nicht** pro Mandant/Instanz dupliziert | must | | 3 | Given mehrere Mandanten/Features, When er Workflows/Läufe ansieht, Then sind sie cross-mandate/-feature gelistet, RBAC-gefiltert (Mandats-Admin/`isPlatformAdmin`) | must | | 4 | Given Bestands-Workflows/Runs/Tasks, When migriert wurde, Then bleiben sie aufrufbar/ausführbar; `targetFeatureInstanceId` unverändert wirksam | must | | 5 | Given ein Run über mehrere Trustee-Instanzen, When er läuft, Then bucht `recordUsage` je Node die berührte Instanz (nicht fix `graphicalEditor`) | must | | 6 | Given kein `graphicalEditor`-Feature mehr, When der Store geöffnet wird, Then erscheint keine GE-Karte; bestehende Mandanten verlieren keine Workflows | must | | 7 | Given ein geplanter Run, When er ausgeführt wird, Then unter definierter Automations-Identität (nicht globaler Sysadmin); Reads nur im RBAC-Umfang des Principals | must | | 8 | Given die neue API, When ein Node eine fremd-mandatige Instanz-UUID trägt, Then Laufzeit-Fehler (Mandatsgrenze) | should | ## Testplan | ID | AC | Art | Automatisiert | Repo-Pfad | Status | |----|----|-----|--------------|-----------|--------| | T1 | 1 | integration | ja | platform-core/tests/.../scheduler | pending | | T2 | 3 | api | ja | platform-core/tests/.../workflowAutomation | pending | | T3 | 4 | migration | ja | platform-core/tests/.../migration | pending | | T4 | 5 | unit | ja | platform-core/tests/.../billing | pending | | T5 | 2 | e2e/manual | nein | ui-nyla | pending | ## Offene Fragen 1. **RBAC-Granularität (grösstenteils entschieden):** Modell = Mandats-Mitgliedschaft + Mandats-Admin + `isPlatformAdmin`; GE-Feature-Rollen (`graphicalEditor-*`) entfallen. Offen nur: brauchen Power-User ohne Admin eine eigene System-Rolle `workflowAutomation-user`, oder genügt Mandats-`user`? (Feature-Instanz-Rollen sind keine Option mehr — `instantiable=False` + `rbac-role-separation.mdc`.) 2. **API-Pfad:** `/api/workflow-automation/…` neu vs. bestehendes `/api/system/workflow-runs` ausbauen — konsolidieren? 3. **Run-as-Principal-Verwaltung:** Ersteller-Identität vs. dedizierter Service-Principal (siehe CustomerCases offene Frage #1). 4. **Alt-URLs:** wie lange Redirects der `{instanceId}`-Pfade halten (Bookmarks/Deeplinks)? 5. **Datei-/Doku-Rename:** dieses Plan-Dokument ggf. auf `…-workflowautomation-system-component.md` umbenennen (aktuell `…-automation-system-component.md`) — Konsistenz vs. Referenz-Churn. ## Links - Architektur/Entscheid: `c-work/0-ideas/2026-06-CustomerCases-step1-architecture.md` (A0.4), `…-step3-features-plan.md` (A0) - Prior Art: `c-work/4-done/2026-04-automation-unification.md`, `2026-04-automation-central-admin.md`, `2026-04-generic-graph-editor.md` - Code (Backend): `modules/features/graphicalEditor/`, `modules/workflows/{automation2,scheduler}/`, `modules/datamodels/{datamodelPortTypes,datamodelWorkflowAutomation,datamodelNavigation}.py`, `modules/routes/{routeWorkflowDashboard,routeAutomationWorkspace,routeSystem}.py`, `app.py` - Import-/Layer-Stand (Refactoring 2026-06-05/06, inkl. Deferred-Liste für diese Komponente): `local/notes/refernce-analysis/platform-core-import-analyse.md` - Code (Frontend): `ui-nyla/src/pages/AutomationsDashboardPage.tsx`, `components/Navigation/MandateNavigation.tsx`, `pages/FeatureView.tsx`, `App.tsx` - RBAC: `b-reference/platform/rbac.md`, `.cursor/rules/rbac-role-separation.mdc`, `.cursor/rules/feature-instance-scoping.mdc` ## Abschluss - [ ] `b-reference/`: neue Kanon-Seite «WorkflowAutomation (System-Komponente)»; `graphicalEditor`-Feature-Seite umschreiben - [ ] `.cursor/rules/rbac-role-separation.mdc` + `feature-instance-scoping.mdc` aktualisieren (WorkflowAutomation als dokumentierte Ausnahme; stale Globs `gateway/`/`frontend_nyla/` → `platform-core/`/`ui-nyla/` korrigieren) - [ ] `TOPICS.md` aktualisieren (WorkflowAutomation = System-Komponente) - [ ] **Cross-Import Assessment** (nach Umsetzung): Import-Analyse erneut durchfuehren und pruefen: - [ ] `workflows -> features.graphicalEditor` (~40 Imports): Alle via Komponenten-Umzug eliminiert? - [ ] `serviceCenter -> features.graphicalEditor` (5x workflowTools): entfaellt? - [ ] `interfaces/interfaceDbManagement.py:936` -> `workflowArtifactVisibility`: mit Engine-Umzug erledigt? - [ ] Re-Export-Shims (`datamodelFeatureGraphicalEditor.py`, `stateTools.py`): entfernbar? - [ ] Verbleibende interne Zyklen in `workflows/` (30 lazy Imports): reduziert nach Restructuring? - [ ] Finale Folder-Summary und Import-Zahlen neu generieren - [ ] `local/notes/refernce-analysis/platform-core-import-analyse.md` finalisieren - [ ] Dieses Dokument → `c-work/2-build/` bei Annahme, dann `4-done/`