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.documentListorsyncToAccounting.documentListshowed no compatible upstream candidates even thoughextractFromFiles/processDocumentswere obviously connected upstream. - Setting any parameter on the
trustee.syncToAccountingnode 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
useMemocalls (connections,loopAncestorIds) above theif (!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.
- Moved both
gateway/modules/features/graphicalEditor/portTypes.py- Added
ActionDocumentPortSchema(mirrorsdatamodelChat.ActionDocument). - Added
documents: List[ActionDocument]field on theActionResultschema so the DataPicker can drill into it.
- Added
gateway/modules/features/graphicalEditor/nodeDefinitions/trustee.pyextractFromFiles.outputPorts[0].schema = "ActionResult".- Both consumer params (
processDocuments.documentList,syncToAccounting.documentList) typed asList[ActionDocument]. processDocuments.inputPorts[0].acceptsincludesActionResult.
gateway/modules/workflows/methods/methodTrustee/methodTrustee.pyextractFromFiles.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.pyrewritten 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. Includestest_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.