12 KiB
Node-Typisierungs-Audit (Stand 2026-04-24)
ARCHIVIERT 2026-04-25. Die 4 Defekt-Muster sind durch die Typed Action Architecture und ihre Folge-Schritte strukturell beseitigt. Bug-Restbestand (Trustee/Redmine-Adapter noch nicht migriert) wurde in
c-work/4-done/2026-04-feature-instance-ref-adapter-migration.mderledigt. 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:
{"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:
{"name": "target", "type": "FeatureInstanceRef[trustee]", "required": True,
"ui": {"label": "Trustee-System"}},
mit neuem Katalog-Typ in portTypes.py:
"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:
{"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:
{"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:
- Schicht 1 (Katalog) muss zuerst um ~13 Schemas erweitert werden.
- Schicht 2 (Methods/Actions) muss
WorkflowActionParameter.typeauf Katalog-Typen umstellen — beseitigt automatisch Muster 1 (hidden-Pflicht-Params). - Schicht 3 (Adapter) ersetzt das parallele
frontendType-Schatten-System durch echte Typen — beseitigt Muster 2 (Pseudo-connectionReference). - Outputs typisieren beseitigt Muster 3 (Output
ActionResult-Black-Box). acceptsstrikt beseitigt Muster 4 (Wildcards).
Der Aufwand ist nicht trivial, aber er beseitigt alle 4 Defekt-Muster in einem Refactor — kein einzelner Workaround.