wiki/z-archive/c-work/2026-04-node-typization-audit.md
ValueOn AG 24190f532a fixes
2026-04-26 08:36:33 +02:00

254 lines
12 KiB
Markdown

<!-- status: superseded -->
<!-- lastUpdated: 2026-04-24 -->
<!-- lastReviewed: 2026-04-25 -->
<!-- archivedOn: 2026-04-25 -->
<!-- supersededBy: b-reference/gateway/workflow.md (Typed Action Architecture) -->
<!-- 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)
> **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: "<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.