Evaluates a node's outgoing edges and reports which are followable.
Differences from the behavior this replaces, all deliberate:
Every satisfied edge is returned, not just the first. The old code fetched the full list and
then indexed [0], silently discarding the rest — so a genuine fan-out node ran one branch and
dropped the others with no diagnostic. Which of the returned edges actually get followed is now
the caller's decision via TraversalMode, which is where that policy belongs.
A missing destination and an inactive destination are treated alike. Previously an inactive
destination fell through to the next-priority alternate while a missing one failed the graph
outright. Both are "this edge cannot be entered", and a dangling edge is a data problem that
should not be more fatal than a deliberately disabled step. Both are now rejections with a
reason, so a caller that finds no followable edge can say precisely why.
A condition that throws is reported, not swallowed. It still does not follow the edge — a
broken expression must not become an accidental true — but it lands in Rejected with
ConditionError, distinguishable from a condition that legitimately evaluated false. The old
code logged and dropped, making "your expression is broken" and "your expression said no"
indistinguishable at the call site.
There is deliberately no "priority <= 0 fallback" rule. The original had one, but it was
unreachable: unconditional edges are already collected in the main pass, so the fallback filter
could only ever run when every edge had a condition — in which case it matched nothing. Fallbacks
work, and always did, by writing an unconditional edge at low priority.
Evaluates a node's outgoing edges and reports which are followable.
Differences from the behavior this replaces, all deliberate:
Every satisfied edge is returned, not just the first. The old code fetched the full list and then indexed
[0], silently discarding the rest — so a genuine fan-out node ran one branch and dropped the others with no diagnostic. Which of the returned edges actually get followed is now the caller's decision via TraversalMode, which is where that policy belongs.A missing destination and an inactive destination are treated alike. Previously an inactive destination fell through to the next-priority alternate while a missing one failed the graph outright. Both are "this edge cannot be entered", and a dangling edge is a data problem that should not be more fatal than a deliberately disabled step. Both are now rejections with a reason, so a caller that finds no followable edge can say precisely why.
A condition that throws is reported, not swallowed. It still does not follow the edge — a broken expression must not become an accidental
true— but it lands inRejectedwithConditionError, distinguishable from a condition that legitimately evaluated false. The old code logged and dropped, making "your expression is broken" and "your expression said no" indistinguishable at the call site.There is deliberately no "priority <= 0 fallback" rule. The original had one, but it was unreachable: unconditional edges are already collected in the main pass, so the fallback filter could only ever run when every edge had a condition — in which case it matched nothing. Fallbacks work, and always did, by writing an unconditional edge at low priority.