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.
With a hold check, a held condition is never evaluated. Its edge is rejected as
ConditionHeld, and the first such edge ranked above every followable one is reported as Held
— the same question the dispatcher asks of an exclusive group ("could an unevaluable edge have
beaten the winner?"). A held edge into a missing or inactive step is rejected for that instead:
it could never have been followed, so it cannot have been the winner.
An UNANSWERABLE condition is not evaluated either, but it does not hold: its edge can never be
followed in this traversal, so the edges after it are considered as if it were false. Only when
no edge is followable is it reported as Held.
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.
With a hold check, a held condition is never evaluated. Its edge is rejected as
ConditionHeld, and the first such edge ranked above every followable one is reported asHeld— the same question the dispatcher asks of an exclusive group ("could an unevaluable edge have beaten the winner?"). A held edge into a missing or inactive step is rejected for that instead: it could never have been followed, so it cannot have been the winner.An UNANSWERABLE condition is not evaluated either, but it does not hold: its edge can never be followed in this traversal, so the edges after it are considered as if it were false. Only when no edge is followable is it reported as
Held.