wiki/c-work/4-done/2026-04-trustee-actionresult-schema-and-datapicker-crash.md
ValueOn AG d4095db4f2 fixes
2026-04-25 01:13:24 +02:00

5.6 KiB

Trustee ActionResult schema alignment + DataPicker white-screen fix

Status: done Date: 2026-04-24 Scope: gateway (trustee adapter, method definition, port catalog, tests), frontend_nyla (FlowEditor / DataPicker)

Symptoms reported

  • Trustee nodes in the expense-receipt workflow ("Spesenbeleg") could not recognise their input data: opening the DataRef picker for processDocuments.documentList or syncToAccounting.documentList showed no compatible upstream candidates even though extractFromFiles / processDocuments were obviously connected upstream.
  • Setting any parameter on the trustee.syncToAccounting node caused the whole graph editor to white-screen.

Root causes

Two independent bugs collided on the same UI path.

1. Adapter drift around ActionResult.documents

Every trustee action returns ActionResult.isSuccess(documents=[...]) at runtime (see gateway/modules/datamodels/datamodelChat.py and the three action implementations). However the typed-action surface disagreed in three places:

Layer What it said What it should say
nodeDefinitions/trustee.py::extractFromFiles.outputPorts[0].schema DocumentList ActionResult
methodTrustee.py::extractFromFiles.outputType DocumentList ActionResult
methodTrustee.py::processDocuments.outputType TrusteeProcessResult ActionResult
methodTrustee.py::syncToAccounting.outputType TrusteeSyncResult ActionResult
portTypes.py::PORT_TYPE_CATALOG['ActionResult'] only success / error / data also documents: List[ActionDocument]
portTypes.py::PORT_TYPE_CATALOG['ActionDocument'] not registered new PortSchema with documentName / documentData / mimeType / fileId / fileName
nodeDefinitions/trustee.py::processDocuments.parameters.documentList.type DocumentList List[ActionDocument] (the concrete shape _resolveDocumentList consumes)
nodeDefinitions/trustee.py::syncToAccounting.parameters.documentList.type DocumentList List[ActionDocument]
nodeDefinitions/trustee.py::processDocuments.inputPorts[0].accepts DocumentList / Transit also ActionResult

Because the catalog ActionResult schema had no documents field, the DataPicker's _buildPathsFromSchema could not surface the canonical upstream → documents → * → documentName path. Strict-filter then correctly rejected every other candidate, so the user saw "no input data".

2. Rules-of-Hooks violation in DataPicker

frontend_nyla/src/components/FlowEditor/nodes/shared/DataPicker.tsx called useMemo(... loopAncestorIds ...) after the if (!open) return null; early return. As soon as the picker opened (e.g. when clicking a parameter on the sync node), React saw "rendered more hooks than during the previous render", threw, and there is no ErrorBoundary around the canvas, so the whole tree unmounted to a white screen.

Fix

  • frontend_nyla/src/components/FlowEditor/nodes/shared/DataPicker.tsx
    • Moved both useMemo calls (connections, loopAncestorIds) above the if (!open) return null; guard.
    • Added a comment block explaining why hooks must stay above the early return so the next refactor doesn't reintroduce the bug.
  • gateway/modules/features/graphicalEditor/portTypes.py
    • Added ActionDocument PortSchema (mirrors datamodelChat.ActionDocument).
    • Added documents: List[ActionDocument] field on the ActionResult schema so the DataPicker can drill into it.
  • gateway/modules/features/graphicalEditor/nodeDefinitions/trustee.py
    • extractFromFiles.outputPorts[0].schema = "ActionResult".
    • Both consumer params (processDocuments.documentList, syncToAccounting.documentList) typed as List[ActionDocument].
    • processDocuments.inputPorts[0].accepts includes ActionResult.
  • gateway/modules/workflows/methods/methodTrustee/methodTrustee.py
    • extractFromFiles.outputType = "ActionResult".
    • processDocuments.outputType = "ActionResult", documentList.type = "List[ActionDocument]".
    • syncToAccounting.outputType = "ActionResult", documentList.type = "List[ActionDocument]".

Verification

  • gateway/tests/unit/nodeDefinitions/test_trustee_schema_compliance.py rewritten to enforce the new alignment plus two new regressions (test_catalog_ActionResult_exposes_documents_field, test_catalog_ActionDocument_is_registered).
  • python -m pytest gateway/tests/unit/graphicalEditor/ gateway/tests/unit/nodeDefinitions/ gateway/tests/unit/workflow/test_trusteeQueryData.py → 112 passed. Includes test_staticNodesHaveNoDriftAgainstLiveMethods, the strict drift gate added during the Phase-4 cleanup.
  • npx vitest run src/components/FlowEditor/ → 32 passed (DataPicker, RequiredAttributePicker, paramValidation, CanvasHeader).

Why the existing adapterValidator did not catch this

Adapter rules 1-5 only check that adapter-declared parameters / output types resolve in the catalog and that required action params are covered. They do not check whether the catalog schema is structurally complete (e.g. "ActionResult declares documents because the runtime emits it"). Capturing that would require a runtime/return-value introspection pass — tracked as a follow-up in local/notes/issues.md if the picker keeps losing fields.

Pick-not-Push contract recap

Producer always emits ActionResult { success, error, documents, data }. Consumers bind their documentList-style param via DataRef to upstream → documents (or documents → * when iterating in a loop). No auto-wire, no implicit aliasing — the binding is fully visible in the editor via DataRefRenderer.