Shared entry/terminal classification and output-port resolution for
structural node types, used by both PluginNode (canvas handle rendering)
and GraphCanvas's save-time unwired-port check.
These two call sites previously computed "what outputs does this node
have" independently: PluginNode hard-coded the entry/terminal special
cases inline, while GraphCanvas's findUnwiredPorts looked up the
catalog spec (or DEFAULT_PORT_SPEC) directly. That duplication let
the two drift — findUnwiredPorts had no terminal-type special case, so
the non-catalog output/error supernode boundary pseudo-nodes fell
through to DEFAULT_PORT_SPEC (success+error) and got a bogus success
output demanded, even though PluginNode correctly renders zero output
handles for them and src/graph/validation.rs::validate_supernode forbids
any outgoing edge from output/error in the first place. Routing both
call sites through resolveOutputs closes that gap structurally.
Shared entry/terminal classification and output-port resolution for structural node types, used by both PluginNode (canvas handle rendering) and GraphCanvas's save-time unwired-port check.
These two call sites previously computed "what outputs does this node have" independently: PluginNode hard-coded the entry/terminal special cases inline, while GraphCanvas's
findUnwiredPortslooked up the catalog spec (or DEFAULT_PORT_SPEC) directly. That duplication let the two drift —findUnwiredPortshad no terminal-type special case, so the non-catalogoutput/errorsupernode boundary pseudo-nodes fell through toDEFAULT_PORT_SPEC(success+error) and got a bogussuccessoutput demanded, even though PluginNode correctly renders zero output handles for them andsrc/graph/validation.rs::validate_supernodeforbids any outgoing edge fromoutput/errorin the first place. Routing both call sites through resolveOutputs closes that gap structurally.