# Node-Typisierungs-Audit (Stand 2026-04-24) > **ARCHIVIERT 2026-04-25.** Die 4 Defekt-Muster sind durch die > [Typed Action Architecture](../../b-reference/gateway/workflow.md) > und ihre [Folge-Schritte](../c-work/2026-04-typed-action-followups.md) > strukturell beseitigt. Bug-Restbestand (Trustee/Redmine-Adapter noch nicht > migriert) wurde in `c-work/4-done/2026-04-feature-instance-ref-adapter-migration.md` > erledigt. Diese Seite bleibt nur als historische Faktenbasis erhalten. 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: ""`. 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.