When a TbRuleChainInputNode has `forwardMsgToDefaultRuleChain=true` and the originator's
default rule chain is the same as the rule chain containing this node, the message enters
an infinite loop: the node forwards to the default rule chain, which routes back to the
same node, which forwards again, causing unbounded recursion and 100% CPU on rule-engine.
Fix: detect the loop in DefaultTbContext.input() by checking whether the calling rule node
is already present in the message's return stack (TbMsgProcessingCtx). On the second+
iteration the stack already contains the (ruleChainId, ruleNodeId) pair of the node,
so the call is a cycle. In that case tellFailure() is called with a descriptive message
and a WARN log is emitted instead of re-enqueuing the message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delay and deduplication rule nodes were creating brand new TbMsg objects
instead of copying the original, which reset the ruleNodeExecCounter to 0.
This allowed bypassing the maxRuleNodeExecutionsPerMessage limit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increased timing tolerance gap from 500ms to 1000ms in both
testRateLimitWithGreedyRefill and testRateLimitWithIntervalRefill
to prevent ConditionTimeoutException caused by scheduling jitter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>