Copilot commented on code in PR #6584:
URL: 
https://github.com/apache/incubator-kie-drools/pull/6584#discussion_r2772636759


##########
drools-core/src/main/java/org/drools/core/reteoo/BiLinearJoinNode.java:
##########
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo;
+
+import org.drools.base.reteoo.NodeTypeEnums;
+import org.drools.core.common.BetaConstraints;
+import org.drools.core.common.BiLinearBetaConstraints;
+import org.drools.core.reteoo.builder.BuildContext;
+import org.drools.core.util.FastIterator;
+
+public class BiLinearJoinNode extends JoinNode {
+
+    private static final long serialVersionUID = 510l;
+
+    // Cross-network declaration context for variable resolution
+    protected BiLinearDeclarationContext declarationContext;
+
+    public BiLinearJoinNode() {
+    }
+
+    public BiLinearJoinNode(final int id,
+                           final LeftTupleSource leftInput,
+                           final BetaRightInput rightInput,
+                           final BetaConstraints constraints,
+                           final BuildContext context) {
+        super(id, leftInput, rightInput, 
createBiLinearConstraints(constraints, leftInput), context);
+    }
+
+    @Override
+    public void doAttach(BuildContext context) {
+        super.doAttach(context);
+    }
+
+    /**
+     * Override setPartitionId to handle LeftTupleSource as second input 
instead of ObjectSource.
+     * BiLinear has LeftTupleSource as second input, not ObjectSource, so we 
need special handling.
+     */
+    @Override
+    public void setPartitionId(BuildContext context, 
org.drools.base.common.RuleBasePartitionId partitionId) {
+        LeftTupleSource secondInput = getSecondLeftInput();
+        if (secondInput != null) {
+            org.drools.base.common.RuleBasePartitionId parentId = 
secondInput.getPartitionId();
+            if (parentId != 
org.drools.base.common.RuleBasePartitionId.MAIN_PARTITION && 
!parentId.equals(partitionId)) {
+                this.partitionId = parentId;
+                rightInput.setPartitionId(context, this.partitionId);
+                context.setPartitionId(this.partitionId);
+                leftInput.setSourcePartitionId(context, this.partitionId);
+                return;
+            }
+        }
+        this.partitionId = partitionId;
+    }
+
+    private static BetaConstraints createBiLinearConstraints(BetaConstraints 
originalConstraints,
+                                                           LeftTupleSource 
leftInput) {
+        return new BiLinearBetaConstraints(originalConstraints);
+    }
+
+    private BiLinearDeclarationContext 
createDeclarationContext(LeftTupleSource leftInput,
+                                                              LeftTupleSource 
secondLeftInput) {
+        int secondNetworkOffset = leftInput != null ? 
leftInput.getObjectCount() : 0;
+
+        return new BiLinearDeclarationContext(
+            leftInput,
+            secondLeftInput,
+            secondNetworkOffset
+        );
+    }
+
+    public LeftTupleSource getSecondLeftInput() {
+        return ((org.drools.core.reteoo.builder.BiLinearLeftInputWrapper) 
rightInput).getWrappedSecondLeftInput();
+    }
+
+    public int getFirstNetworkSize() {
+        return getLeftTupleSource().getObjectCount();
+    }
+
+    public void linkOutsideLeftInput(LeftTupleSource secondLeftInput) {
+
+        ((org.drools.core.reteoo.builder.BiLinearLeftInputWrapper) 
rightInput).setWrappedSecondLeftInput(secondLeftInput);
+
+        if (secondLeftInput != null && 
!secondLeftInput.equals(getLeftTupleSource())) {
+            secondLeftInput.addTupleSink(this);
+        }
+
+        this.setObjectCount(leftInput.getObjectCount() + 
secondLeftInput.getObjectCount());
+
+        this.declarationContext = 
createDeclarationContext(getLeftTupleSource(), secondLeftInput);
+               getBiLinearConstraints().setDeclarationContext(
+                       declarationContext
+               );

Review Comment:
   The linkOutsideLeftInput method does not handle the case where 
secondLeftInput is null. If called with a null argument, it will throw a 
NullPointerException when calling secondLeftInput.getObjectCount(). Consider 
adding null validation at the start of the method.
   ```suggestion
           if (secondLeftInput == null) {
               // No second network; object count and declaration context are 
based solely on the primary left input
               this.setObjectCount(leftInput.getObjectCount());
               this.declarationContext = 
createDeclarationContext(getLeftTupleSource(), null);
               
getBiLinearConstraints().setDeclarationContext(declarationContext);
               return;
           }
   
           if (!secondLeftInput.equals(getLeftTupleSource())) {
               secondLeftInput.addTupleSink(this);
           }
   
           this.setObjectCount(leftInput.getObjectCount() + 
secondLeftInput.getObjectCount());
   
           this.declarationContext = 
createDeclarationContext(getLeftTupleSource(), secondLeftInput);
           getBiLinearConstraints().setDeclarationContext(
                   declarationContext
           );
   ```



##########
drools-core/src/main/java/org/drools/core/reteoo/BiLinearJoinNode.java:
##########
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo;
+
+import org.drools.base.reteoo.NodeTypeEnums;
+import org.drools.core.common.BetaConstraints;
+import org.drools.core.common.BiLinearBetaConstraints;
+import org.drools.core.reteoo.builder.BuildContext;
+import org.drools.core.util.FastIterator;
+
+public class BiLinearJoinNode extends JoinNode {
+
+    private static final long serialVersionUID = 510l;
+
+    // Cross-network declaration context for variable resolution
+    protected BiLinearDeclarationContext declarationContext;
+
+    public BiLinearJoinNode() {
+    }
+
+    public BiLinearJoinNode(final int id,
+                           final LeftTupleSource leftInput,
+                           final BetaRightInput rightInput,
+                           final BetaConstraints constraints,
+                           final BuildContext context) {
+        super(id, leftInput, rightInput, 
createBiLinearConstraints(constraints, leftInput), context);
+    }
+
+    @Override
+    public void doAttach(BuildContext context) {
+        super.doAttach(context);
+    }
+
+    /**
+     * Override setPartitionId to handle LeftTupleSource as second input 
instead of ObjectSource.
+     * BiLinear has LeftTupleSource as second input, not ObjectSource, so we 
need special handling.
+     */
+    @Override
+    public void setPartitionId(BuildContext context, 
org.drools.base.common.RuleBasePartitionId partitionId) {
+        LeftTupleSource secondInput = getSecondLeftInput();
+        if (secondInput != null) {
+            org.drools.base.common.RuleBasePartitionId parentId = 
secondInput.getPartitionId();
+            if (parentId != 
org.drools.base.common.RuleBasePartitionId.MAIN_PARTITION && 
!parentId.equals(partitionId)) {
+                this.partitionId = parentId;
+                rightInput.setPartitionId(context, this.partitionId);
+                context.setPartitionId(this.partitionId);
+                leftInput.setSourcePartitionId(context, this.partitionId);
+                return;
+            }
+        }
+        this.partitionId = partitionId;
+    }
+
+    private static BetaConstraints createBiLinearConstraints(BetaConstraints 
originalConstraints,
+                                                           LeftTupleSource 
leftInput) {
+        return new BiLinearBetaConstraints(originalConstraints);
+    }
+
+    private BiLinearDeclarationContext 
createDeclarationContext(LeftTupleSource leftInput,
+                                                              LeftTupleSource 
secondLeftInput) {
+        int secondNetworkOffset = leftInput != null ? 
leftInput.getObjectCount() : 0;
+
+        return new BiLinearDeclarationContext(
+            leftInput,
+            secondLeftInput,
+            secondNetworkOffset
+        );
+    }
+
+    public LeftTupleSource getSecondLeftInput() {
+        return ((org.drools.core.reteoo.builder.BiLinearLeftInputWrapper) 
rightInput).getWrappedSecondLeftInput();

Review Comment:
   The method getSecondLeftInput() casts rightInput to BiLinearLeftInputWrapper 
without a type check. If rightInput is not of this type (e.g., due to 
deserialization or incorrect initialization), this will throw a 
ClassCastException at runtime. Consider adding a type check or document that 
this method should only be called when the node is properly initialized.
   ```suggestion
           if (rightInput instanceof 
org.drools.core.reteoo.builder.BiLinearLeftInputWrapper) {
               return 
((org.drools.core.reteoo.builder.BiLinearLeftInputWrapper) 
rightInput).getWrappedSecondLeftInput();
           }
           return null;
   ```



##########
drools-core/src/main/java/org/drools/core/reteoo/CompositeLeftTupleSinkAdapter.java:
##########
@@ -67,10 +78,16 @@ public LeftTupleSink[] getSinks() {
             return sinkArray;
         }
 
-        LeftTupleSink[] sinks = new LeftTupleSink[this.sinks.size()];
+        // Count actual nodes in case of list inconsistency (BiLinear shared 
nodes)
+        int actualCount = 0;
+        for ( LeftTupleSinkNode sink = this.sinks.getFirst(); sink != null; 
sink = sink.getNextLeftTupleSinkNode() ) {
+            actualCount++;
+        }
+        
+        LeftTupleSink[] sinks = new LeftTupleSink[actualCount];
 
         int i = 0;
-        for ( LeftTupleSinkNode sink = this.sinks.getFirst(); sink != null; 
sink = sink.getNextLeftTupleSinkNode() ) {
+        for ( LeftTupleSinkNode sink = this.sinks.getFirst(); sink != null && 
i < actualCount; sink = sink.getNextLeftTupleSinkNode() ) {
             sinks[i++] = sink;
         }
 

Review Comment:
   The getSinks method has a potential race condition. It first counts nodes 
(actualCount), then creates an array of that size, then iterates again to fill 
it. Between these operations, if the list is modified concurrently, the counts 
could be inconsistent. Consider synchronizing this method if concurrent access 
is possible, or document that it's not thread-safe.



##########
drools-core/src/main/java/org/drools/core/phreak/RuleNetworkEvaluatorImpl.java:
##########
@@ -710,19 +723,24 @@ public void propagate(SegmentMemory sourceSegment, 
TupleSets leftTuples) {
         }
     }
 
-    private static void processPeers(SegmentMemory sourceSegment, TupleSets 
leftTuples) {
+    private void processPeers(SegmentMemory sourceSegment, TupleSets 
leftTuples) {

Review Comment:
   The processPeers method signature changed from static to non-static, but 
there's no clear reason why instance state is needed. The method doesn't access 
any instance variables of RuleNetworkEvaluatorImpl. Consider keeping it static 
or document why instance access is required.
   ```suggestion
       private static void processPeers(SegmentMemory sourceSegment, TupleSets 
leftTuples) {
   ```



##########
drools-core/src/main/java/org/drools/core/reteoo/CompositeLeftTupleSinkAdapter.java:
##########
@@ -40,7 +40,18 @@ public CompositeLeftTupleSinkAdapter(final 
RuleBasePartitionId partitionId) {
     }
 
     public void addTupleSink(final LeftTupleSink sink) {
-        this.sinks.add( (LeftTupleSinkNode) sink );
+        // Prevent duplicate BiLinear shared nodes to avoid RETE corruption
+        LeftTupleSinkNode sinkNode = (LeftTupleSinkNode) sink;
+        
+        // Check if this exact sink instance is already registered
+        for (LeftTupleSinkNode existing = this.sinks.getFirst(); existing != 
null; existing = existing.getNextLeftTupleSinkNode()) {
+            if (existing == sinkNode) {
+                // Duplicate BiLinear shared node - skip to prevent corruption
+                return;
+            }
+        }
+        
+        this.sinks.add( sinkNode );
         sinkArray = null;

Review Comment:
   In the addTupleSink method, the duplicate detection loop uses object 
identity (==) comparison at line 48, which is correct. However, if sinks are 
equal but not identical (same configuration but different instances), they 
could still be added multiple times. Consider whether semantic equality should 
also be checked, or document why identity comparison is sufficient.



##########
drools-core/src/main/java/org/drools/core/reteoo/builder/RuleOrderOptimizer.java:
##########
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo.builder;
+
+import org.drools.base.definitions.rule.impl.RuleImpl;
+import org.kie.api.definition.rule.Rule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Optimizes rule build order to ensure BiLinear optimization succeeds.
+ *
+ * BiLinear optimization requires that rules defining shared patterns
+ * build before rules that reuse those patterns. This class analyzes
+ * BiLinear dependencies and reorders rules using topological sorting to 
satisfy
+ * these constraints.
+ *
+ * The reordering is transparent to users and maintains relative order of rules
+ * not involved in BiLinear optimization.
+ */
+public class RuleOrderOptimizer {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(RuleOrderOptimizer.class);
+
+    public static Collection<? extends Rule> reorderForBiLinear(
+            Collection<? extends Rule> rules,
+            BiLinearDetector.BiLinearContext biLinearContext) {
+
+        if (!BiLinearDetector.isBiLinearEnabled()) {
+            return rules;
+        }
+
+        if (biLinearContext.sharedChains().isEmpty()) {
+            return rules;
+        }
+
+        Map<String, Set<String>> dependencyGraph = 
buildDependencyGraph(biLinearContext);
+
+        Map<String, RuleImpl> ruleMap = new HashMap<>();
+        List<RuleImpl> originalOrder = new ArrayList<>();
+        for (Rule rule : rules) {
+            if (rule instanceof RuleImpl ruleImpl) {
+                ruleMap.put(rule.getName(), ruleImpl);
+                originalOrder.add(ruleImpl);
+            }
+        }
+
+        return topologicalSort(originalOrder, ruleMap, dependencyGraph);
+    }
+
+    /**
+     * Builds a dependency graph from BiLinear pairs.
+     *
+     * For each Pair(chainId, consumerRuleName, providerRuleName):
+     *   - providerRule = creates the shared JoinNode (simpler pattern, e.g., 
C-D)
+     *   - consumerRule = uses BiLinearJoinNode linking to providerRule's 
JoinNode (complex pattern, e.g., A-B-C-D)
+     *   - providerRule MUST build before consumerRule (so consumerRule can 
link to providerRule's node)
+     *
+     * @param biLinearContext BiLinear context with detected pairs
+     * @return Adjacency list: consumerRule -> set of providerRules that must 
build before it
+     */
+    private static Map<String, Set<String>> buildDependencyGraph(
+            BiLinearDetector.BiLinearContext biLinearContext) {
+
+        Map<String, Set<String>> graph = new HashMap<>();
+
+        for (Map.Entry<String, List<BiLinearDetector.Pair>> entry : 
biLinearContext.sharedChains().entrySet()) {
+            for (BiLinearDetector.Pair pair : entry.getValue()) {
+                String consumerRule = pair.consumerRuleName();
+                String providerRule = pair.providerRuleName();
+
+                graph.computeIfAbsent(consumerRule, k -> new 
HashSet<>()).add(providerRule);
+
+                graph.putIfAbsent(providerRule, new HashSet<>());
+            }
+        }
+
+        return graph;
+    }
+
+    /**
+     * Algorithm:
+     * 1. Calculate in-degree for each rule (number of rules that must build 
before it)
+     * 2. Start with rules with in-degree 0 (no dependencies)
+     * 3. Process nodes in order, choosing from available rules based on 
original order
+     * 4. When a rule is processed, decrement in-degree of rules that depend 
on it
+     * 5. Detect and handle cycles gracefully
+     *
+     * Rules not involved in BiLinear are added in their original positions.
+     * The algorithm preserves relative order as much as possible (stable 
sort).
+     *
+     */
+    private static List<Rule> topologicalSort(
+            List<? extends Rule> originalOrder,
+            Map<String, RuleImpl> ruleMap,
+            Map<String, Set<String>> dependencyGraph) {
+
+        Map<String, Integer> inDegree = new HashMap<>();
+        Set<String> rulesInGraph = dependencyGraph.keySet();
+
+        for (String ruleName : rulesInGraph) {
+            inDegree.put(ruleName, 0);
+        }
+
+        for (Map.Entry<String, Set<String>> entry : 
dependencyGraph.entrySet()) {
+            for (String target : entry.getValue()) {
+                inDegree.put(target, inDegree.getOrDefault(target, 0) + 1);
+            }
+        }

Review Comment:
   The inDegree map is initialized with 0 for all rules in the graph at line 
126, but then at line 131 it uses getOrDefault with 0 as the default. This 
could lead to incorrect in-degree counts if a target node is not in 
rulesInGraph. The logic should ensure all nodes mentioned as targets are also 
in rulesInGraph, or handle missing nodes explicitly.



##########
drools-core/src/main/java/org/drools/core/reteoo/BiLinearTuple.java:
##########
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo;
+
+import org.drools.base.rule.Declaration;
+import org.drools.core.common.InternalFactHandle;
+import org.kie.api.runtime.rule.FactHandle;
+
+/**
+ * BiLinearTuple represents a tuple that combines facts from two separate left 
input networks.
+ * This specialized tuple enables cross-network variable resolution for 
BiLinearJoinNode,
+ * allowing constraints to reference variables from both input networks.
+ * 
+ * The tuple maintains references to both source network tuples and provides 
unified
+ * variable access across networks through enhanced Declaration resolution.
+ */
+public class BiLinearTuple extends TupleImpl {
+    
+    private static final long serialVersionUID = 540l;
+    
+    /** First network tuple (primary left input) */
+    private final TupleImpl firstNetworkTuple;
+    
+    /** Second network tuple (secondary left input) */ 
+    private final TupleImpl secondNetworkTuple;
+
+    /**
+     * Creates a BiLinearTuple combining tuples from two networks
+     * 
+     * @param firstNetworkTuple Tuple from the first left input network
+     * @param secondNetworkTuple Tuple from the second left input network  
+     * @param rightFactHandle Right input fact handle (may be null for some 
scenarios)
+     * @param sink The sink node for this tuple
+     */
+    public BiLinearTuple(TupleImpl firstNetworkTuple,
+                        TupleImpl secondNetworkTuple,
+                        InternalFactHandle rightFactHandle,
+                        Sink sink) {
+        super(rightFactHandle, sink, false);
+
+        this.firstNetworkTuple = firstNetworkTuple;
+        this.secondNetworkTuple = secondNetworkTuple;
+
+        // Set up parent chain for proper traversal by code generator
+        // The combined index is firstSize + secondSize - 1 (0-based)
+        int firstSize = firstNetworkTuple != null ? firstNetworkTuple.size() : 
0;
+        int secondSize = secondNetworkTuple != null ? 
secondNetworkTuple.size() : 0;
+
+        // Create a virtual parent chain that allows proper index traversal
+        // Start from the end of second network and chain through first network
+        if (secondSize > 0) {
+            // Set this tuple's handle to the last fact of second network
+            this.handle = secondNetworkTuple.getFactHandle();
+        }
+
+        setIndex(firstSize + secondSize - 1);
+    }
+
+    /**
+     * Override getParent to provide virtual parent chain for code generator 
traversal.
+     * Returns a BiLinearParentView that continues the parent chain across 
both networks.
+     */
+    @Override
+    public TupleImpl getParent() {
+        int currentIdx = getIndex();
+        if (currentIdx <= 0) {
+            return null;
+        }
+        return new BiLinearParentView(this, currentIdx - 1);
+    }
+
+    /**
+     * Inner class that provides a virtual parent view for a specific index.
+     * This allows the code generator to traverse the parent chain correctly.
+     */
+    private static class BiLinearParentView extends TupleImpl {
+        private final BiLinearTuple biLinearTuple;
+        private final int viewIndex;
+
+        BiLinearParentView(BiLinearTuple biLinearTuple, int viewIndex) {
+            this.biLinearTuple = biLinearTuple;
+            this.viewIndex = viewIndex;
+        }
+
+        @Override
+        public int getIndex() {
+            return viewIndex;
+        }
+
+        @Override
+        public InternalFactHandle getFactHandle() {
+            return (InternalFactHandle) biLinearTuple.get(viewIndex);
+        }
+
+        @Override
+        public TupleImpl getParent() {
+            if (viewIndex <= 0) {
+                return null;
+            }
+            return new BiLinearParentView(biLinearTuple, viewIndex - 1);
+        }

Review Comment:
   The BiLinearParentView class creates parent views recursively which could 
lead to stack overflow for very deep tuple chains. Consider implementing an 
iterative approach or adding a depth limit to prevent potential stack overflow 
errors.



##########
drools-core/src/main/java/org/drools/core/reteoo/BiLinearDeclarationContext.java:
##########
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo;
+
+import org.drools.base.rule.Declaration;
+import org.drools.base.rule.Pattern;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * BiLinearDeclarationContext manages variable declarations across two input 
networks
+ * for BiLinearJoinNode. It provides unified declaration lookup that can 
resolve
+ * variables from either input network and handles potential naming conflicts.
+ */
+public class BiLinearDeclarationContext {
+    
+    private final Map<String, Declaration> firstNetworkDeclarations;
+    
+    private final Map<String, Declaration> secondNetworkDeclarations;
+    
+    private final Map<String, Declaration> combinedDeclarations;
+    
+    private final Map<String, Integer> declarationNetworkMapping;
+    
+    private final int secondNetworkOffset;
+
+    public BiLinearDeclarationContext(Map<String, Declaration> 
firstNetworkDeclarations,
+                                     Map<String, Declaration> 
secondNetworkDeclarations,
+                                     int secondNetworkOffset) {
+        this.firstNetworkDeclarations = new 
HashMap<>(firstNetworkDeclarations);
+        this.secondNetworkDeclarations = new 
HashMap<>(secondNetworkDeclarations);
+        this.secondNetworkOffset = secondNetworkOffset;
+        this.combinedDeclarations = new HashMap<>();
+        this.declarationNetworkMapping = new HashMap<>();
+        
+        buildCombinedDeclarations();
+    }
+
+    public BiLinearDeclarationContext(LeftTupleSource firstNetworkSource,
+                                     LeftTupleSource secondNetworkSource,
+                                     int secondNetworkOffset) {
+        this.secondNetworkOffset = secondNetworkOffset;
+        this.firstNetworkDeclarations = 
extractDeclarations(firstNetworkSource);
+        this.secondNetworkDeclarations = 
extractDeclarations(secondNetworkSource);
+        this.combinedDeclarations = new HashMap<>();
+        this.declarationNetworkMapping = new HashMap<>();
+        
+        buildCombinedDeclarations();
+    }
+    
+    private void buildCombinedDeclarations() {
+        // Add first network declarations (no offset needed)
+        for (Map.Entry<String, Declaration> entry : 
firstNetworkDeclarations.entrySet()) {
+            String name = entry.getKey();
+            Declaration declaration = entry.getValue();
+            
+            combinedDeclarations.put(name, declaration);
+            declarationNetworkMapping.put(name, 1);
+        }
+        
+        // Add second network declarations with offset and conflict resolution
+        for (Map.Entry<String, Declaration> entry : 
secondNetworkDeclarations.entrySet()) {
+            String name = entry.getKey();
+            Declaration originalDeclaration = entry.getValue();
+            
+            // Check for naming conflicts
+            if (firstNetworkDeclarations.containsKey(name)) {
+                // Conflict detected - need to handle this
+                handleDeclarationConflict(name, originalDeclaration);
+            } else {
+                // No conflict - create offset declaration for second network
+                Declaration offsetDeclaration = 
createOffsetDeclaration(originalDeclaration);
+                combinedDeclarations.put(name, offsetDeclaration);
+                declarationNetworkMapping.put(name, 2);
+            }
+        }
+    }
+    
+    /**
+     * Handles naming conflicts between networks.
+     * When the same variable name exists in both networks, we use the second 
network's
+     * declaration with offset, keeping the original variable name from the 
rule.
+     * This allows rules to reference variables by their original names.
+     */
+    private void handleDeclarationConflict(String name, Declaration 
secondNetworkDeclaration) {
+        Declaration offsetDeclaration = 
createOffsetDeclaration(secondNetworkDeclaration);
+
+        // Replace the first network's declaration with the second network's 
(with offset)
+        combinedDeclarations.put(name, offsetDeclaration);
+        declarationNetworkMapping.put(name, 2);
+    }
+
+    private Declaration createOffsetDeclaration(Declaration original) {
+        Declaration offsetDeclaration = new Declaration(
+            original.getIdentifier(),
+            original.getExtractor(),
+            createOffsetPattern(original.getPattern())
+        );
+        
+        offsetDeclaration.setDeclarationClass(original.getDeclarationClass());
+        
+        return offsetDeclaration;
+    }
+
+    private Pattern createOffsetPattern(Pattern original) {
+        if (original == null) {
+            return null;
+        }
+        
+        Pattern offsetPattern = new Pattern(
+            original.getPatternId(),
+            original.getTupleIndex() + secondNetworkOffset,
+            original.getObjectIndex() + secondNetworkOffset,
+            original.getObjectType(),
+            original.getDeclaration() != null ? 
original.getDeclaration().getIdentifier() : null
+        );
+        
+        return offsetPattern;
+    }
+    
+    private Map<String, Declaration> extractDeclarations(LeftTupleSource 
source) {
+        return new HashMap<>();

Review Comment:
   The extractDeclarations method returns an empty HashMap regardless of the 
source parameter. This looks like an incomplete implementation. Either 
implement proper declaration extraction or document why returning empty is 
intentional.
   ```suggestion
           Map<String, Declaration> result = new HashMap<>();
   
           if (source == null) {
               return result;
           }
   
           Map<String, Declaration> outerDeclarations = 
source.getOuterDeclarations();
           if (outerDeclarations != null && !outerDeclarations.isEmpty()) {
               result.putAll(outerDeclarations);
           }
   
           return result;
   ```



##########
drools-core/src/main/java/org/drools/core/phreak/PhreakBiLinearJoinNode.java:
##########
@@ -0,0 +1,471 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.phreak;
+
+import org.drools.core.common.ReteEvaluator;
+import org.drools.core.common.TupleSets;
+import org.drools.core.reteoo.BetaMemory;
+import org.drools.core.common.BiLinearBetaConstraints;
+import org.drools.core.reteoo.BiLinearContextEntry;
+import org.drools.base.reteoo.NodeTypeEnums;
+import org.drools.core.reteoo.BiLinearJoinNode;
+import org.drools.core.reteoo.BiLinearRuleTerminalNodeLeftTuple;
+import org.drools.core.reteoo.BiLinearTuple;
+import org.drools.core.reteoo.LeftTupleSink;
+import org.drools.core.reteoo.TupleImpl;
+import org.drools.core.reteoo.TupleMemory;
+import org.drools.core.util.FastIterator;
+
+/**
+ * Phreak processor for BiLinearJoinNode that handles joining two left input 
networks.
+ * Unlike traditional joins that have one left and one right input, 
BiLinearJoinNode
+ * has two left inputs that get joined together.
+ */
+public class PhreakBiLinearJoinNode {
+
+    private final ReteEvaluator reteEvaluator;
+    
+    public PhreakBiLinearJoinNode(ReteEvaluator reteEvaluator) {
+        this.reteEvaluator = reteEvaluator;
+    }
+
+    public void doNode(BiLinearJoinNode biLinearJoinNode,
+                       LeftTupleSink sink,
+                       BetaMemory bm,
+                       TupleSets srcLeftTuples,
+                       TupleSets stagedLeftTuples,
+                       TupleSets trgLeftTuples) {
+
+        // Get tuples from second left input (stored in "right" memory for 
BiLinear)
+        TupleSets srcRightTuples = bm.getStagedRightTuples().takeAll();
+
+        if (srcRightTuples.getDeleteFirst() != null) {
+            doRightDeletes(bm, srcRightTuples, trgLeftTuples, 
stagedLeftTuples);
+        }
+        if (srcLeftTuples.getDeleteFirst() != null) {
+            doLeftDeletes(bm, srcLeftTuples, trgLeftTuples, stagedLeftTuples);
+        }
+
+        // Process updates from both inputs
+        if (srcRightTuples.getUpdateFirst() != null) {
+            PhreakNodeOperations.doUpdatesReorderRightMemory(bm, 
srcRightTuples);
+        }
+        if (srcLeftTuples.getUpdateFirst() != null) {
+            PhreakNodeOperations.doUpdatesReorderLeftMemory(bm, srcLeftTuples);
+        }
+        
+        if (srcRightTuples.getUpdateFirst() != null) {
+            doRightUpdates(biLinearJoinNode, sink, bm, srcRightTuples, 
trgLeftTuples, stagedLeftTuples);
+        }
+        if (srcLeftTuples.getUpdateFirst() != null) {
+            doLeftUpdates(biLinearJoinNode, sink, bm, srcLeftTuples, 
trgLeftTuples, stagedLeftTuples);
+        }
+
+        // Process inserts from both inputs
+        if (srcRightTuples.getInsertFirst() != null) {
+            doRightInserts(biLinearJoinNode, sink, bm, srcRightTuples, 
trgLeftTuples);
+        }
+        if (srcLeftTuples.getInsertFirst() != null) {
+            doLeftInserts(biLinearJoinNode, sink, bm, srcLeftTuples, 
trgLeftTuples);
+        }
+
+        srcRightTuples.resetAll();
+        srcLeftTuples.resetAll();
+    }
+
+    /**
+     * Process left tuple inserts for BiLinearJoinNode.
+     * This method joins tuples from the primary left input with tuples from 
the second left input.
+     */
+    public void doLeftInserts(BiLinearJoinNode biLinearJoinNode,
+                              LeftTupleSink sink,
+                              BetaMemory<?> bm,
+                              TupleSets srcLeftTuples,
+                              TupleSets trgLeftTuples) {
+
+        TupleMemory ltm = bm.getLeftTupleMemory();   // Memory for first left 
input
+        TupleMemory rtm = bm.getRightTupleMemory();  // Memory for second left 
input (treated as "right" for memory purposes)
+        Object contextEntry = bm.getContext();
+
+        // Get BiLinear constraints wrapper (all BiLinearJoinNode constraints 
are wrapped)
+        BiLinearBetaConstraints biLinearConstraints = 
biLinearJoinNode.getBiLinearConstraints();
+        
+        if (biLinearConstraints == null) {
+            for (TupleImpl leftTuple = srcLeftTuples.getInsertFirst(); 
leftTuple != null; ) {
+                TupleImpl next = leftTuple.getStagedNext();
+                leftTuple.clearStaged();
+                leftTuple = next;
+            }
+            return;
+        }
+        
+        BiLinearContextEntry biLinearContext = contextEntry instanceof 
BiLinearContextEntry ?
+            (BiLinearContextEntry) contextEntry : null;
+        
+        if (biLinearContext == null) {
+            for (TupleImpl leftTuple = srcLeftTuples.getInsertFirst(); 
leftTuple != null; ) {
+                TupleImpl next = leftTuple.getStagedNext();
+                leftTuple.clearStaged();
+                leftTuple = next;
+            }
+            return;
+        }

Review Comment:
   The doLeftInserts and doRightInserts methods silently consume all tuples and 
return early when biLinearConstraints or biLinearContext are null (lines 
109-128). This could hide configuration errors where BiLinearJoinNode is not 
properly initialized. Consider logging a warning or error when this happens, as 
it indicates a serious misconfiguration.



##########
drools-core/src/main/java/org/drools/core/reteoo/BiLinearRuleTerminalNodeLeftTuple.java:
##########
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo;
+
+import org.drools.base.rule.Declaration;
+import org.kie.api.runtime.rule.FactHandle;
+
+/**
+ * BiLinearRuleTerminalNodeLeftTuple is a specialized terminal tuple for 
BiLinearJoinNode.
+ * It extends RuleTerminalNodeLeftTuple to be compatible with 
PhreakRuleTerminalNode,
+ * while also providing cross-network fact access like BiLinearTuple.
+ *
+ * This allows rules using BiLinearJoinNode to access variables from both 
input networks
+ * in their consequences.
+ */
+public class BiLinearRuleTerminalNodeLeftTuple extends 
RuleTerminalNodeLeftTuple {
+
+    private static final long serialVersionUID = 540l;
+
+    private final TupleImpl firstNetworkTuple;
+
+    private final TupleImpl secondNetworkTuple;
+
+    /** Offset for second network tuple indices */
+    private final int secondNetworkOffset;
+
+    public BiLinearRuleTerminalNodeLeftTuple(TupleImpl firstNetworkTuple,
+                                            TupleImpl secondNetworkTuple,
+                                            Sink sink) {
+        super();
+        setSink(sink);
+
+        this.firstNetworkTuple = firstNetworkTuple;
+        this.secondNetworkTuple = secondNetworkTuple;
+
+        // Calculate offset: second network indices start after first network
+        int firstSize = firstNetworkTuple != null ? firstNetworkTuple.size() : 
0;
+        int secondSize = secondNetworkTuple != null ? 
secondNetworkTuple.size() : 0;
+        this.secondNetworkOffset = firstSize;
+
+        // Set the index to the last position in the combined network
+        setIndex(firstSize + secondSize - 1);
+
+        // Set handle to the last fact in the chain (from second network)
+        if (secondNetworkTuple != null) {
+            setFactHandle(secondNetworkTuple.getFactHandle());
+        }
+    }
+
+    /**
+     * Override get to provide cross-network access.
+     * Index mapping:
+     * - 0 to firstNetworkSize-1: First network facts
+     * - firstNetworkSize to firstNetworkSize+secondNetworkSize-1: Second 
network facts
+     */
+    @Override
+    public FactHandle get(int index) {
+        int firstSize = firstNetworkTuple != null ? firstNetworkTuple.size() : 
0;
+        int secondSize = secondNetworkTuple != null ? 
secondNetworkTuple.size() : 0;
+
+        // First network range
+        if (index < firstSize) {
+            return firstNetworkTuple.get(index);
+        }
+
+        // Second network range
+        if (index < firstSize + secondSize) {
+            int secondNetworkIndex = index - firstSize;
+            return secondNetworkTuple.get(secondNetworkIndex);
+        }
+
+        throw new IndexOutOfBoundsException("Tuple index " + index + " is out 
of bounds. " +
+            "First network size: " + firstSize + ", Second network size: " + 
secondSize);
+    }
+
+    @Override
+    public FactHandle get(Declaration declaration) {
+        return get(declaration.getTupleIndex());
+    }
+
+    @Override
+    public Object getObject(int index) {
+        FactHandle handle = get(index);
+        return handle != null ? handle.getObject() : null;
+    }
+
+    @Override
+    public Object getObject(Declaration declaration) {
+        return getObject(declaration.getTupleIndex());
+    }
+
+    @Override
+    public int size() {
+        int firstSize = firstNetworkTuple != null ? firstNetworkTuple.size() : 
0;
+        int secondSize = secondNetworkTuple != null ? 
secondNetworkTuple.size() : 0;
+        return firstSize + secondSize;
+    }
+
+    @Override
+    public TupleImpl getParent() {
+        int currentIdx = getIndex();
+        if (currentIdx <= 0) {
+            return null;
+        }
+        return new BiLinearParentView(this, currentIdx - 1);
+    }
+
+    /**
+     * Inner class providing virtual parent view for index traversal.
+     */
+    private static class BiLinearParentView extends TupleImpl {
+        private final BiLinearRuleTerminalNodeLeftTuple biLinearTuple;
+        private final int viewIndex;
+
+        BiLinearParentView(BiLinearRuleTerminalNodeLeftTuple biLinearTuple, 
int viewIndex) {
+            this.biLinearTuple = biLinearTuple;
+            this.viewIndex = viewIndex;
+        }
+
+        @Override
+        public int getIndex() {
+            return viewIndex;
+        }
+
+        @Override
+        public org.drools.core.common.InternalFactHandle getFactHandle() {
+            return (org.drools.core.common.InternalFactHandle) 
biLinearTuple.get(viewIndex);
+        }
+
+        @Override
+        public TupleImpl getParent() {
+            if (viewIndex <= 0) {
+                return null;
+            }
+            return new BiLinearParentView(biLinearTuple, viewIndex - 1);

Review Comment:
   Similar to BiLinearTuple, the BiLinearParentView recursion in getParent() 
could lead to stack overflow for very deep tuple chains. Consider an iterative 
approach or depth limiting.



##########
drools-core/src/main/java/org/drools/core/phreak/RuleNetworkEvaluatorImpl.java:
##########
@@ -710,19 +723,24 @@ public void propagate(SegmentMemory sourceSegment, 
TupleSets leftTuples) {
         }
     }
 
-    private static void processPeers(SegmentMemory sourceSegment, TupleSets 
leftTuples) {
+    private void processPeers(SegmentMemory sourceSegment, TupleSets 
leftTuples) {
         SegmentMemory firstSmem = sourceSegment.getFirst();
 
         processPeerDeletes( leftTuples.getDeleteFirst(), firstSmem );
         processPeerDeletes( leftTuples.getNormalizedDeleteFirst(), firstSmem );
         processPeerUpdates( leftTuples, firstSmem );
-        processPeerInserts( leftTuples, firstSmem );
+        processPeerInserts( leftTuples, firstSmem, sourceSegment );
 
-        firstSmem.getStagedLeftTuples().addAll( leftTuples );
+        // Check if target is BiLinearJoinNode receiving from second input
+        if (shouldRouteToBiLinearRightMemory(sourceSegment, firstSmem)) {
+            routeToBiLinearRightMemory(firstSmem, leftTuples);
+        } else {
+            firstSmem.getStagedLeftTuples().addAll( leftTuples );
+        }
         leftTuples.resetAll();
     }
 
-    private static void processPeerInserts(TupleSets leftTuples, SegmentMemory 
firstSmem) {
+    private static void processPeerInserts(TupleSets leftTuples, SegmentMemory 
firstSmem, SegmentMemory sourceSegment) {

Review Comment:
   The method signature for processPeerInserts was changed to add a 
sourceSegment parameter, but this parameter is only used in the BiLinear 
routing check. Consider renaming the parameter to make its purpose clearer, or 
refactor to avoid passing unnecessary parameters through the call chain.



##########
drools-core/src/main/java/org/drools/core/reteoo/ReteooBuilder.java:
##########
@@ -381,16 +382,13 @@ private void updateLeafSet(BaseNode baseNode, NodeSet 
leafSet) {
     public static class IdGenerator implements Externalizable {
 
         private static final long serialVersionUID = 510l;
+        private static final int FIRST_ID = 1;
 
         private Queue<Integer>    recycledIds;
         private int               nextId;
 
         public IdGenerator() {
-            this(1);
-        }
-
-        public IdGenerator(final int firstId) {
-            this.nextId = firstId;
+            this.nextId = FIRST_ID;

Review Comment:
   The IdGenerator constructor parameter was removed and FIRST_ID constant was 
added, but the constructor that took firstId parameter was also removed. This 
could break any code that was instantiating IdGenerator with a custom starting 
ID. Consider deprecating rather than removing, or ensure no callers exist.
   ```suggestion
               this(FIRST_ID);
           }
   
           /**
            * @deprecated Use the no-argument constructor instead. This 
constructor is retained
            * for backward compatibility with code that explicitly specifies a 
starting id.
            */
           @Deprecated
           public IdGenerator(int firstId) {
               this.nextId = firstId;
   ```



##########
drools-core/src/main/java/org/drools/core/reteoo/builder/RuleOrderOptimizer.java:
##########
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo.builder;
+
+import org.drools.base.definitions.rule.impl.RuleImpl;
+import org.kie.api.definition.rule.Rule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Optimizes rule build order to ensure BiLinear optimization succeeds.
+ *
+ * BiLinear optimization requires that rules defining shared patterns
+ * build before rules that reuse those patterns. This class analyzes
+ * BiLinear dependencies and reorders rules using topological sorting to 
satisfy
+ * these constraints.
+ *
+ * The reordering is transparent to users and maintains relative order of rules
+ * not involved in BiLinear optimization.
+ */
+public class RuleOrderOptimizer {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(RuleOrderOptimizer.class);
+
+    public static Collection<? extends Rule> reorderForBiLinear(
+            Collection<? extends Rule> rules,
+            BiLinearDetector.BiLinearContext biLinearContext) {
+
+        if (!BiLinearDetector.isBiLinearEnabled()) {
+            return rules;
+        }
+
+        if (biLinearContext.sharedChains().isEmpty()) {
+            return rules;
+        }
+
+        Map<String, Set<String>> dependencyGraph = 
buildDependencyGraph(biLinearContext);
+
+        Map<String, RuleImpl> ruleMap = new HashMap<>();
+        List<RuleImpl> originalOrder = new ArrayList<>();
+        for (Rule rule : rules) {
+            if (rule instanceof RuleImpl ruleImpl) {
+                ruleMap.put(rule.getName(), ruleImpl);
+                originalOrder.add(ruleImpl);
+            }
+        }
+
+        return topologicalSort(originalOrder, ruleMap, dependencyGraph);
+    }
+
+    /**
+     * Builds a dependency graph from BiLinear pairs.
+     *
+     * For each Pair(chainId, consumerRuleName, providerRuleName):
+     *   - providerRule = creates the shared JoinNode (simpler pattern, e.g., 
C-D)
+     *   - consumerRule = uses BiLinearJoinNode linking to providerRule's 
JoinNode (complex pattern, e.g., A-B-C-D)
+     *   - providerRule MUST build before consumerRule (so consumerRule can 
link to providerRule's node)
+     *
+     * @param biLinearContext BiLinear context with detected pairs
+     * @return Adjacency list: consumerRule -> set of providerRules that must 
build before it
+     */
+    private static Map<String, Set<String>> buildDependencyGraph(
+            BiLinearDetector.BiLinearContext biLinearContext) {
+
+        Map<String, Set<String>> graph = new HashMap<>();
+
+        for (Map.Entry<String, List<BiLinearDetector.Pair>> entry : 
biLinearContext.sharedChains().entrySet()) {
+            for (BiLinearDetector.Pair pair : entry.getValue()) {
+                String consumerRule = pair.consumerRuleName();
+                String providerRule = pair.providerRuleName();
+
+                graph.computeIfAbsent(consumerRule, k -> new 
HashSet<>()).add(providerRule);
+
+                graph.putIfAbsent(providerRule, new HashSet<>());
+            }
+        }
+
+        return graph;
+    }
+
+    /**
+     * Algorithm:
+     * 1. Calculate in-degree for each rule (number of rules that must build 
before it)
+     * 2. Start with rules with in-degree 0 (no dependencies)
+     * 3. Process nodes in order, choosing from available rules based on 
original order
+     * 4. When a rule is processed, decrement in-degree of rules that depend 
on it
+     * 5. Detect and handle cycles gracefully
+     *
+     * Rules not involved in BiLinear are added in their original positions.
+     * The algorithm preserves relative order as much as possible (stable 
sort).
+     *
+     */
+    private static List<Rule> topologicalSort(
+            List<? extends Rule> originalOrder,
+            Map<String, RuleImpl> ruleMap,
+            Map<String, Set<String>> dependencyGraph) {
+
+        Map<String, Integer> inDegree = new HashMap<>();
+        Set<String> rulesInGraph = dependencyGraph.keySet();
+
+        for (String ruleName : rulesInGraph) {
+            inDegree.put(ruleName, 0);
+        }
+
+        for (Map.Entry<String, Set<String>> entry : 
dependencyGraph.entrySet()) {
+            for (String target : entry.getValue()) {
+                inDegree.put(target, inDegree.getOrDefault(target, 0) + 1);
+            }
+        }
+
+        List<Rule> result = new ArrayList<>();
+        Set<String> remaining = new HashSet<>(rulesInGraph);
+
+        while (!remaining.isEmpty()) {
+            List<String> available = new ArrayList<>();
+            for (Rule rule : originalOrder) {
+                String ruleName = rule.getName();
+                if (remaining.contains(ruleName) && inDegree.get(ruleName) == 
0) {
+                    available.add(ruleName);
+                }
+            }
+
+            // If no rules available but rules remain, we have a cycle
+            if (available.isEmpty()) {
+                logger.warn("Circular BiLinear dependencies detected. " +
+                           "Remaining rules will be built in original order: 
{}", remaining);
+
+                for (Rule rule : originalOrder) {
+                    if (remaining.contains(rule.getName())) {
+                        result.add(rule);
+                    }
+                }
+                break;
+            }
+
+            for (String ruleName : available) {
+                RuleImpl rule = ruleMap.get(ruleName);
+                if (rule != null) {
+                    result.add(rule);
+                    remaining.remove(ruleName);
+
+                    Set<String> dependents = dependencyGraph.get(ruleName);
+                    if (dependents != null) {
+                        for (String dependent : dependents) {
+                            inDegree.put(dependent, inDegree.get(dependent) - 
1);
+                        }
+                    }
+                }
+            }
+        }

Review Comment:
   The topologicalSort method has a potential infinite loop if the circular 
dependency detection logic fails. The while loop at line 138 continues as long 
as 'remaining' is not empty, but if the cycle detection at line 148 doesn't 
properly break, and no rules are added to 'available', the loop will continue 
indefinitely. Consider adding a safety counter to break after a maximum number 
of iterations.



##########
drools-core/src/main/java/org/drools/core/reteoo/builder/BiLinearLeftInputWrapper.java:
##########
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo.builder;
+
+import org.drools.base.common.NetworkNode;
+import org.drools.base.common.RuleBasePartitionId;
+import org.drools.base.reteoo.BaseTerminalNode;
+import org.drools.core.common.*;
+import org.drools.core.reteoo.*;
+import org.drools.util.bitmask.BitMask;
+import org.drools.util.bitmask.EmptyBitMask;
+import org.kie.api.definition.rule.Rule;
+
+import java.util.Objects;
+
+public class BiLinearLeftInputWrapper implements BetaRightInput {
+
+    private LeftTupleSource wrappedSecondLeftInput;
+    private BiLinearJoinNode betaNode;
+    public LeftTupleSource getWrappedSecondLeftInput() {
+        return wrappedSecondLeftInput;
+    }
+
+    public void setWrappedSecondLeftInput(LeftTupleSource source) {
+        this.wrappedSecondLeftInput = source;
+    }
+
+    @Override
+    public void setBetaNode(BetaNode betaNode) {
+        this.betaNode = (BiLinearJoinNode) betaNode;
+    }
+
+    @Override
+    public void setPartitionId(BuildContext context, RuleBasePartitionId 
partitionId) {
+
+    }
+
+    @Override
+    public void initInferredMask() {
+
+    }
+
+    @Override
+    public boolean inputIsTupleToObjectNode() {
+        return false;
+    }
+
+    @Override
+    public ObjectSource getParent() {
+        // BiLinear wraps LeftTupleSource, not ObjectSource
+        // BiLinearJoinNode overrides setPartitionId() to handle this properly
+        // Return null since LeftTupleSource cannot be cast to ObjectSource
+        return null;
+    }
+
+    @Override
+    public ObjectTypeNode getObjectTypeNode() {
+        return null;
+    }
+
+    @Override
+    public void doAttach(BuildContext context) {
+        // Register BiLinearJoinNode as sink on second left input
+        // Only register if it's different from first input to avoid duplicate 
registration
+        if (wrappedSecondLeftInput != null && betaNode != null &&
+            !wrappedSecondLeftInput.equals(betaNode.getLeftTupleSource())) {
+            wrappedSecondLeftInput.addTupleSink(betaNode, context);
+        }
+    }

Review Comment:
   The doAttach method in BiLinearLeftInputWrapper checks if secondLeftInput 
equals leftTupleSource but doesn't handle the case where both could be null. If 
both are null, the equals check will pass but secondLeftInput.addTupleSink will 
throw NullPointerException on line 83. Add null checks before the equals 
comparison.



##########
drools-core/src/main/java/org/drools/core/common/BiLinearBetaConstraints.java:
##########
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.common;
+
+import org.drools.base.base.ObjectType;
+import org.drools.base.base.ValueResolver;
+import org.drools.base.reteoo.BaseTuple;
+import org.drools.base.rule.Pattern;
+import org.drools.base.rule.constraint.BetaConstraint;
+import org.drools.core.RuleBaseConfiguration;
+import org.drools.core.reteoo.*;
+import org.drools.core.reteoo.builder.BuildContext;
+import org.drools.core.util.index.TupleList;
+import org.drools.util.bitmask.BitMask;
+import org.kie.api.runtime.rule.FactHandle;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * BiLinearBetaConstraints wraps standard BetaConstraints to provide 
cross-network
+ * variable resolution for BiLinearJoinNode. It intercepts constraint 
evaluation
+ * calls and ensures that variables from both input networks are available 
during
+ * evaluation.
+ */
+public class BiLinearBetaConstraints implements 
BetaConstraints<BiLinearContextEntry> {
+
+    private final BetaConstraints wrappedConstraints;
+
+    private BiLinearDeclarationContext declarationContext;
+
+    public BiLinearBetaConstraints(BetaConstraints wrappedConstraints) {
+        this.wrappedConstraints = wrappedConstraints;
+    }
+
+    public BiLinearBetaConstraints(BetaConstraints wrappedConstraints,
+                                   BiLinearDeclarationContext 
declarationContext) {
+        this(wrappedConstraints);
+        this.declarationContext = declarationContext;
+    }
+    public void setDeclarationContext(BiLinearDeclarationContext 
declarationContext) {
+        this.declarationContext = declarationContext;
+    }
+
+    @Override
+    public void init(BuildContext context, int betaNodeType) {
+        wrappedConstraints.init(context, betaNodeType);
+    }
+
+    @Override
+    public void initIndexes(int depth, int betaNodeType, RuleBaseConfiguration 
config) {
+        wrappedConstraints.initIndexes(depth, betaNodeType, config);
+    }
+
+    @Override
+    public BiLinearContextEntry createContext() {
+        return new BiLinearContextEntry(declarationContext);
+    }
+
+    @Override
+    public boolean isIndexed() {
+        return wrappedConstraints.isIndexed();
+    }
+
+    @Override
+    public int getIndexCount() {
+        return wrappedConstraints.getIndexCount();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return wrappedConstraints.isEmpty();
+    }
+
+    @Override
+    public BetaMemory createBetaMemory(RuleBaseConfiguration config, int 
betaNodeType) {
+        return new BetaMemory<BiLinearContextEntry>(
+                config.isSequential() ? null : new TupleList(),
+                new TupleList(),
+                createContext(),
+                betaNodeType
+        );
+    }
+
+    @Override
+    public void updateFromTuple(BiLinearContextEntry context, ValueResolver 
valueResolver, Tuple tuple) {
+        context.updateFromTuple(valueResolver, tuple);
+
+        Object wrappedContext = getWrappedContext(context);
+        if (tuple != null) {
+            wrappedConstraints.updateFromTuple(wrappedContext, valueResolver, 
tuple);
+        }
+    }
+
+    public void updateFromBiLinearTuples(BiLinearContextEntry context,
+                                         ValueResolver valueResolver,
+                                         Tuple firstNetworkTuple,
+                                         Tuple secondNetworkTuple) {
+        context.updateFromBiLinearTuples(valueResolver, firstNetworkTuple, 
secondNetworkTuple);
+
+        Object wrappedContext = getWrappedContext(context);
+        wrappedConstraints.updateFromTuple(wrappedContext, valueResolver, 
(Tuple) firstNetworkTuple);
+    }
+
+    @Override
+    public void updateFromFactHandle(BiLinearContextEntry context, 
ValueResolver valueResolver, FactHandle handle) {
+        context.updateFromFactHandle(valueResolver, handle);
+
+        Object wrappedContext = getWrappedContext(context);
+        wrappedConstraints.updateFromFactHandle(wrappedContext, valueResolver, 
handle);
+    }
+
+    @Override
+    public void resetTuple(BiLinearContextEntry context) {
+        context.resetTuple();
+
+        Object wrappedContext = getWrappedContext(context);
+        wrappedConstraints.resetTuple(wrappedContext);
+    }
+
+    @SuppressWarnings("unchecked")
+    public void resetTupleContext(Object context) {
+        if (context instanceof BiLinearContextEntry) {
+            resetTuple((BiLinearContextEntry) context);
+        } else {
+            wrappedConstraints.resetTuple(context);
+        }
+    }
+
+    @Override
+    public void resetFactHandle(BiLinearContextEntry context) {
+        context.resetFactHandle();
+
+        Object wrappedContext = getWrappedContext(context);
+        wrappedConstraints.resetFactHandle(wrappedContext);
+    }
+
+    @Override
+    public boolean isAllowedCachedLeft(BiLinearContextEntry context, 
FactHandle handle) {
+        if (wrappedConstraints instanceof 
org.drools.core.common.EmptyBetaConstraints) {
+            return 
wrappedConstraints.isAllowedCachedLeft(wrappedConstraints.createContext(), 
handle);
+        }
+
+        Object wrappedContext = getWrappedContext(context);
+        return wrappedConstraints.isAllowedCachedLeft(wrappedContext, handle);
+    }
+
+    @Override
+    public boolean isAllowedCachedRight(BaseTuple tuple, BiLinearContextEntry 
context) {
+        if (wrappedConstraints instanceof 
org.drools.core.common.EmptyBetaConstraints) {
+            return wrappedConstraints.isAllowedCachedRight(tuple, 
wrappedConstraints.createContext());
+        }
+
+        Object wrappedContext = getWrappedContext(context);
+        return wrappedConstraints.isAllowedCachedRight(tuple, wrappedContext);
+    }
+
+    @SuppressWarnings("unchecked")
+    private Object getWrappedContext(BiLinearContextEntry context) {
+        Object wrappedContext = wrappedConstraints.createContext();
+
+        if (context.getFirstNetworkTuple() != null) {
+            wrappedConstraints.updateFromTuple(wrappedContext, 
context.getValueResolver(), (Tuple) context.getFirstNetworkTuple());
+        }
+        if (context.getRightHandle() != null) {
+            wrappedConstraints.updateFromFactHandle(wrappedContext, 
context.getValueResolver(), context.getRightHandle());
+        }
+
+        return wrappedContext;
+    }
+
+    @Override
+    public BetaConstraint[] getConstraints() {
+        return wrappedConstraints.getConstraints();
+    }
+
+    @Override
+    public BetaConstraints getOriginalConstraint() {
+        return wrappedConstraints.getOriginalConstraint();
+    }
+
+    @Override
+    public <T> T cloneIfInUse() {
+        BetaConstraints clonedWrapped = (BetaConstraints) 
wrappedConstraints.cloneIfInUse();
+        BiLinearBetaConstraints cloned = new 
BiLinearBetaConstraints(clonedWrapped, declarationContext);
+        return (T) cloned;
+    }
+
+    @Override
+    public BitMask getListenedPropertyMask(Pattern pattern, ObjectType 
modifiedType, List<String> settableProperties) {
+        return wrappedConstraints.getListenedPropertyMask(pattern, 
modifiedType, settableProperties);
+    }
+
+    @Override
+    public boolean isLeftUpdateOptimizationAllowed() {
+        return wrappedConstraints.isLeftUpdateOptimizationAllowed();
+    }
+
+    @Override
+    public void registerEvaluationContext(BuildContext buildContext) {
+        wrappedConstraints.registerEvaluationContext(buildContext);
+    }
+
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeObject(wrappedConstraints);
+        out.writeObject(declarationContext);
+    }
+
+    @Override
+    public void readExternal(ObjectInput in) throws IOException, 
ClassNotFoundException {
+        throw new UnsupportedOperationException("Deserialization not yet 
implemented for BiLinearBetaConstraints");

Review Comment:
   The readExternal method throws UnsupportedOperationException, which means 
BiLinearBetaConstraints cannot be deserialized. This could break session 
serialization/persistence. Either implement proper deserialization or ensure 
this is documented and that BiLinear nodes are excluded from serialization 
flows.
   ```suggestion
       @SuppressWarnings("unchecked")
       public void readExternal(ObjectInput in) throws IOException, 
ClassNotFoundException {
           wrappedConstraints = (BetaConstraints<BiLinearContextEntry>) 
in.readObject();
           declarationContext = in.readObject();
   ```



##########
drools-core/src/main/java/org/drools/core/reteoo/BiLinearTuple.java:
##########
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo;
+
+import org.drools.base.rule.Declaration;
+import org.drools.core.common.InternalFactHandle;
+import org.kie.api.runtime.rule.FactHandle;
+
+/**
+ * BiLinearTuple represents a tuple that combines facts from two separate left 
input networks.
+ * This specialized tuple enables cross-network variable resolution for 
BiLinearJoinNode,
+ * allowing constraints to reference variables from both input networks.
+ * 
+ * The tuple maintains references to both source network tuples and provides 
unified
+ * variable access across networks through enhanced Declaration resolution.
+ */
+public class BiLinearTuple extends TupleImpl {
+    
+    private static final long serialVersionUID = 540l;
+    
+    /** First network tuple (primary left input) */
+    private final TupleImpl firstNetworkTuple;
+    
+    /** Second network tuple (secondary left input) */ 
+    private final TupleImpl secondNetworkTuple;
+
+    /**
+     * Creates a BiLinearTuple combining tuples from two networks
+     * 
+     * @param firstNetworkTuple Tuple from the first left input network
+     * @param secondNetworkTuple Tuple from the second left input network  
+     * @param rightFactHandle Right input fact handle (may be null for some 
scenarios)
+     * @param sink The sink node for this tuple
+     */
+    public BiLinearTuple(TupleImpl firstNetworkTuple,
+                        TupleImpl secondNetworkTuple,
+                        InternalFactHandle rightFactHandle,
+                        Sink sink) {
+        super(rightFactHandle, sink, false);
+
+        this.firstNetworkTuple = firstNetworkTuple;
+        this.secondNetworkTuple = secondNetworkTuple;
+
+        // Set up parent chain for proper traversal by code generator
+        // The combined index is firstSize + secondSize - 1 (0-based)
+        int firstSize = firstNetworkTuple != null ? firstNetworkTuple.size() : 
0;
+        int secondSize = secondNetworkTuple != null ? 
secondNetworkTuple.size() : 0;
+
+        // Create a virtual parent chain that allows proper index traversal
+        // Start from the end of second network and chain through first network
+        if (secondSize > 0) {
+            // Set this tuple's handle to the last fact of second network
+            this.handle = secondNetworkTuple.getFactHandle();
+        }
+
+        setIndex(firstSize + secondSize - 1);
+    }
+
+    /**
+     * Override getParent to provide virtual parent chain for code generator 
traversal.
+     * Returns a BiLinearParentView that continues the parent chain across 
both networks.
+     */
+    @Override
+    public TupleImpl getParent() {
+        int currentIdx = getIndex();
+        if (currentIdx <= 0) {
+            return null;
+        }
+        return new BiLinearParentView(this, currentIdx - 1);
+    }
+
+    /**
+     * Inner class that provides a virtual parent view for a specific index.
+     * This allows the code generator to traverse the parent chain correctly.
+     */
+    private static class BiLinearParentView extends TupleImpl {
+        private final BiLinearTuple biLinearTuple;
+        private final int viewIndex;
+
+        BiLinearParentView(BiLinearTuple biLinearTuple, int viewIndex) {
+            this.biLinearTuple = biLinearTuple;
+            this.viewIndex = viewIndex;
+        }
+
+        @Override
+        public int getIndex() {
+            return viewIndex;
+        }
+
+        @Override
+        public InternalFactHandle getFactHandle() {
+            return (InternalFactHandle) biLinearTuple.get(viewIndex);
+        }
+
+        @Override
+        public TupleImpl getParent() {
+            if (viewIndex <= 0) {
+                return null;
+            }
+            return new BiLinearParentView(biLinearTuple, viewIndex - 1);
+        }
+
+        @Override
+        public FactHandle get(int index) {
+            return biLinearTuple.get(index);
+        }
+
+        @Override
+        public FactHandle get(Declaration declaration) {
+            return biLinearTuple.get(declaration);
+        }
+
+        @Override
+        public InternalFactHandle getOriginalFactHandle() {
+            InternalFactHandle fh = getFactHandle();
+            if (fh != null && fh.isEvent()) {
+                InternalFactHandle linkedFH =
+                    
((org.drools.core.common.DefaultEventHandle)fh).getLinkedFactHandle();
+                return linkedFH != null ? linkedFH : fh;
+            }
+            return fh;
+        }
+
+        @Override
+        public ObjectTypeNodeId getInputOtnId() {
+            return biLinearTuple.getInputOtnId();
+        }
+
+        @Override
+        public boolean isLeftTuple() {
+            return true;
+        }
+
+        @Override
+        public void reAdd() {
+            // No-op for view
+        }
+    }
+
+    @Override
+    public FactHandle get(Declaration declaration) {
+        return get(declaration.getTupleIndex());
+    }
+    
+    /**
+     * Enhanced get method that resolves indices across both networks
+     * 
+     * Index mapping:
+     * - 0 to firstNetworkSize-1: First network facts
+     * - firstNetworkSize to firstNetworkSize+secondNetworkSize-1: Second 
network facts  
+     * - firstNetworkSize+secondNetworkSize: Right fact (if present)
+     */
+    @Override
+    public FactHandle get(int index) {
+        int firstSize = firstNetworkTuple != null ? firstNetworkTuple.size() : 
0;
+        int secondSize = secondNetworkTuple != null ? 
secondNetworkTuple.size() : 0;
+        
+        // First network range
+        if (index < firstSize) {
+            return firstNetworkTuple.get(index);
+        }
+        
+        // Second network range  
+        if (index < firstSize + secondSize) {
+            int secondNetworkIndex = index - firstSize;
+            return secondNetworkTuple.get(secondNetworkIndex);
+        }
+        
+        // Right fact
+        if (index == firstSize + secondSize && this.handle != null) {
+            return this.handle;
+        }
+        
+        throw new IndexOutOfBoundsException("Tuple index " + index + " is out 
of bounds. " +
+            "First network size: " + firstSize + ", Second network size: " + 
secondSize + 
+            ", Has right fact: " + (this.handle != null));
+    }
+    
+    /**
+     * Enhanced getObject method for cross-network object access
+     */
+    @Override
+    public Object getObject(Declaration declaration) {
+        return getObject(declaration.getTupleIndex());
+    }
+
+    @Override
+    public Object getObject(int index) {
+        FactHandle handle = get(index);
+        return handle != null ? handle.getObject() : null;
+    }
+
+    @Override
+    public int size() {
+        int firstSize = firstNetworkTuple != null ? firstNetworkTuple.size() : 
0;
+        int secondSize = secondNetworkTuple != null ? 
secondNetworkTuple.size() : 0;
+        int rightSize = this.handle != null ? 1 : 0;
+        return firstSize + secondSize + rightSize;
+    }
+
+    @Override
+    public int getIndex() {
+        return size() - 1;

Review Comment:
   The getIndex() method returns size() - 1, which will return -1 when size() 
is 0. This could cause issues in code that expects getIndex() to return 
non-negative values. Consider returning 0 for empty tuples or document this 
behavior clearly.
   ```suggestion
           int tupleSize = size();
           return tupleSize == 0 ? 0 : tupleSize - 1;
   ```



##########
drools-core/src/main/java/org/drools/core/phreak/BiLinearRoutingHelper.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.phreak;
+
+import org.drools.core.common.Memory;
+import org.drools.core.common.TupleSets;
+import org.drools.core.reteoo.BetaMemory;
+import org.drools.core.reteoo.PathMemory;
+import org.drools.core.reteoo.SegmentMemory;
+import org.drools.core.reteoo.TupleImpl;
+import org.drools.core.reteoo.builder.BiLinearDetector;
+
+import java.util.List;
+
+public final class BiLinearRoutingHelper {
+
+    private BiLinearRoutingHelper() {
+    }
+
+    /**
+     * Check if tuples should be routed to BiLinearJoinNode's right memory 
instead of left memory.
+     * This happens when the source segment's tip node is a BiLinearJoinNode's 
second left input.
+     */
+    public static boolean shouldRouteToBiLinearRightMemory(SegmentMemory 
sourceSegment, SegmentMemory targetSegment) {
+        if (!BiLinearDetector.isBiLinearEnabled()) {
+            return false;
+        }
+
+        SegmentMemory.SegmentPrototype proto = 
targetSegment.getSegmentPrototype();
+
+        return proto.hasBiLinearNode() &&
+               sourceSegment.getTipNode().getId() == 
proto.getBiLinearSecondInputId();
+    }
+
+    /**
+     * Routes tuples to BiLinearJoinNode's right memory (staged right tuples)
+     * instead of the normal left memory staging.
+     */
+    public static void routeToBiLinearRightMemory(SegmentMemory targetSegment, 
TupleSets leftTuples) {
+        SegmentMemory.SegmentPrototype proto = 
targetSegment.getSegmentPrototype();
+        int biLinearIndex = proto.getBiLinearNodeIndex();
+
+        Memory[] nodeMemories = targetSegment.getNodeMemories();
+        BetaMemory bm = (BetaMemory) nodeMemories[biLinearIndex];
+
+        if (bm.getStagedRightTuples().isEmpty()) {
+            bm.setNodeDirtyWithoutNotify();
+        }
+
+        bm.getStagedRightTuples().addAll(leftTuples);
+
+        targetSegment.notifyRuleLinkSegment();
+
+        List<PathMemory> pathMems = targetSegment.getPathMemories();
+        for (PathMemory pmem : pathMems) {
+            pmem.getOrCreateRuleAgendaItem();
+            pmem.queueRuleAgendaItem();
+        }
+    }
+
+    /**
+     * Routes a single peer tuple to BiLinearJoinNode's right memory.
+     * Used when processing peer inserts for segments that need BiLinear right 
memory routing.
+     */
+    public static void routePeerToBiLinearRightMemory(SegmentMemory 
targetSegment, TupleImpl peerTuple) {
+        SegmentMemory.SegmentPrototype proto = 
targetSegment.getSegmentPrototype();
+        int biLinearIndex = proto.getBiLinearNodeIndex();
+
+        Memory[] nodeMemories = targetSegment.getNodeMemories();
+        BetaMemory bm = (BetaMemory) nodeMemories[biLinearIndex];

Review Comment:
   The routeToBiLinearRightMemory and routePeerToBiLinearRightMemory methods 
don't validate that biLinearIndex is within bounds before accessing 
nodeMemories array. If getBiLinearNodeIndex() returns -1 (indicating no 
BiLinear node) or an invalid index, this will cause 
ArrayIndexOutOfBoundsException. Add bounds checking before array access.



##########
drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/BiLinearTest.java:
##########
@@ -0,0 +1,836 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.mvel.integrationtests;
+
+import org.drools.base.common.NetworkNode;
+import org.drools.mvel.integrationtests.phreak.A;
+import org.drools.mvel.integrationtests.phreak.B;
+import org.drools.mvel.integrationtests.phreak.C;
+import org.drools.mvel.integrationtests.phreak.D;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.api.KieBase;
+import org.kie.api.io.ResourceType;
+import org.kie.api.runtime.KieSession;
+import org.kie.internal.utils.KieHelper;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class BiLinearTest {
+
+    @BeforeEach
+    public void setUp() {
+        System.setProperty("drools.bilinear.enabled", "true");
+    }
+
+    @AfterEach
+    public void cleanup() {
+        System.clearProperty("drools.bilinear.enabled");
+    }
+
+    @Test
+    public void testBiLinear() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(2, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+    @Test
+    public void testBiLinearSwapOrder() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(2, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+
+    @Test
+    public void testBiLinear3RuleSetup() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(3, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+    @Test
+    public void testBiLinear3RuleSetupShort() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(3, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+    @Test
+    public void testBiLinear3RuleSetup2() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(3, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 2);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+    @Test
+    public void testBiLinearSayAAAAAAAAA() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        System.setProperty("drools.bilinear.enabled", "false");

Review Comment:
   The test testBiLinearSayAAAAAAAAA sets the system property 
"drools.bilinear.enabled" to "false" at line 350, but the setUp() method in the 
test class sets it to "true" for all tests. This test-specific override could 
cause issues if tests run in parallel or if the cleanup doesn't properly 
restore state. Consider using a dedicated test method with @AfterEach to 
restore the property, or use a different approach for disabling BiLinear in 
specific tests.



##########
drools-core/src/main/java/org/drools/core/reteoo/TupleImpl.java:
##########
@@ -671,6 +671,10 @@ public int getIndex() {
         return this.index;
     }
 
+    protected void setIndex(int index) {
+        this.index = index;
+    }

Review Comment:
   The setIndex method was added as protected, breaking encapsulation of the 
index field. This could allow subclasses to set inconsistent index values. 
Consider whether this is necessary or if there's a safer way to initialize the 
index for BiLinear tuples.



##########
drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/BiLinearTest.java:
##########
@@ -0,0 +1,836 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.mvel.integrationtests;
+
+import org.drools.base.common.NetworkNode;
+import org.drools.mvel.integrationtests.phreak.A;
+import org.drools.mvel.integrationtests.phreak.B;
+import org.drools.mvel.integrationtests.phreak.C;
+import org.drools.mvel.integrationtests.phreak.D;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.api.KieBase;
+import org.kie.api.io.ResourceType;
+import org.kie.api.runtime.KieSession;
+import org.kie.internal.utils.KieHelper;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class BiLinearTest {
+
+    @BeforeEach
+    public void setUp() {
+        System.setProperty("drools.bilinear.enabled", "true");
+    }
+
+    @AfterEach
+    public void cleanup() {
+        System.clearProperty("drools.bilinear.enabled");
+    }
+
+    @Test
+    public void testBiLinear() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(2, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+    @Test
+    public void testBiLinearSwapOrder() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(2, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+
+    @Test
+    public void testBiLinear3RuleSetup() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(3, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+    @Test
+    public void testBiLinear3RuleSetupShort() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(3, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+    @Test
+    public void testBiLinear3RuleSetup2() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(3, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 2);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+    @Test
+    public void testBiLinearSayAAAAAAAAA() {

Review Comment:
   The testBiLinearSayAAAAAAAAA test name contains repeated 'A's which appears 
to be a debugging artifact or placeholder name. Consider renaming to something 
more descriptive like 'testBiLinearDisabledWithMultipleSameTypePatterns'.



##########
drools-core/src/test/java/org/drools/core/reteoo/MockTupleSource.java:
##########
@@ -69,4 +71,13 @@ public ObjectTypeNode getObjectTypeNode() {
     public boolean isLeftTupleMemoryEnabled() {
         return true;
     }
+
+    public void setObjectCount(int objectCount) {
+        this.objectCount = objectCount;
+    }

Review Comment:
   The setObjectCount method is added to MockTupleSource but there's no 
corresponding getter override documentation. Consider adding a JavaDoc comment 
explaining when and why this method should be called in mock scenarios.



##########
drools-core/src/main/java/org/drools/core/reteoo/BiLinearJoinNode.java:
##########
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.core.reteoo;
+
+import org.drools.base.reteoo.NodeTypeEnums;
+import org.drools.core.common.BetaConstraints;
+import org.drools.core.common.BiLinearBetaConstraints;
+import org.drools.core.reteoo.builder.BuildContext;
+import org.drools.core.util.FastIterator;
+
+public class BiLinearJoinNode extends JoinNode {
+
+    private static final long serialVersionUID = 510l;
+
+    // Cross-network declaration context for variable resolution
+    protected BiLinearDeclarationContext declarationContext;
+
+    public BiLinearJoinNode() {
+    }
+
+    public BiLinearJoinNode(final int id,
+                           final LeftTupleSource leftInput,
+                           final BetaRightInput rightInput,
+                           final BetaConstraints constraints,
+                           final BuildContext context) {
+        super(id, leftInput, rightInput, 
createBiLinearConstraints(constraints, leftInput), context);
+    }
+
+    @Override
+    public void doAttach(BuildContext context) {
+        super.doAttach(context);
+    }
+
+    /**
+     * Override setPartitionId to handle LeftTupleSource as second input 
instead of ObjectSource.
+     * BiLinear has LeftTupleSource as second input, not ObjectSource, so we 
need special handling.
+     */
+    @Override
+    public void setPartitionId(BuildContext context, 
org.drools.base.common.RuleBasePartitionId partitionId) {
+        LeftTupleSource secondInput = getSecondLeftInput();
+        if (secondInput != null) {
+            org.drools.base.common.RuleBasePartitionId parentId = 
secondInput.getPartitionId();
+            if (parentId != 
org.drools.base.common.RuleBasePartitionId.MAIN_PARTITION && 
!parentId.equals(partitionId)) {
+                this.partitionId = parentId;
+                rightInput.setPartitionId(context, this.partitionId);
+                context.setPartitionId(this.partitionId);
+                leftInput.setSourcePartitionId(context, this.partitionId);
+                return;
+            }
+        }
+        this.partitionId = partitionId;
+    }
+
+    private static BetaConstraints createBiLinearConstraints(BetaConstraints 
originalConstraints,
+                                                           LeftTupleSource 
leftInput) {
+        return new BiLinearBetaConstraints(originalConstraints);
+    }
+
+    private BiLinearDeclarationContext 
createDeclarationContext(LeftTupleSource leftInput,
+                                                              LeftTupleSource 
secondLeftInput) {
+        int secondNetworkOffset = leftInput != null ? 
leftInput.getObjectCount() : 0;
+
+        return new BiLinearDeclarationContext(
+            leftInput,
+            secondLeftInput,
+            secondNetworkOffset
+        );
+    }
+
+    public LeftTupleSource getSecondLeftInput() {
+        return ((org.drools.core.reteoo.builder.BiLinearLeftInputWrapper) 
rightInput).getWrappedSecondLeftInput();
+    }
+
+    public int getFirstNetworkSize() {
+        return getLeftTupleSource().getObjectCount();
+    }
+
+    public void linkOutsideLeftInput(LeftTupleSource secondLeftInput) {
+
+        ((org.drools.core.reteoo.builder.BiLinearLeftInputWrapper) 
rightInput).setWrappedSecondLeftInput(secondLeftInput);
+
+        if (secondLeftInput != null && 
!secondLeftInput.equals(getLeftTupleSource())) {
+            secondLeftInput.addTupleSink(this);
+        }
+
+        this.setObjectCount(leftInput.getObjectCount() + 
secondLeftInput.getObjectCount());
+
+        this.declarationContext = 
createDeclarationContext(getLeftTupleSource(), secondLeftInput);
+               getBiLinearConstraints().setDeclarationContext(
+                       declarationContext
+               );
+    }
+
+    public BiLinearBetaConstraints getBiLinearConstraints() {
+        return (BiLinearBetaConstraints) getRawConstraints();

Review Comment:
   The getBiLinearConstraints method casts without checking if the constraints 
are actually BiLinearBetaConstraints. If getRawConstraints() returns a 
different type, this will throw a ClassCastException. Add a type check or 
document that this should only be called after proper initialization in the 
constructor.
   ```suggestion
           BetaConstraints rawConstraints = getRawConstraints();
           if (rawConstraints instanceof BiLinearBetaConstraints) {
               return (BiLinearBetaConstraints) rawConstraints;
           }
           throw new IllegalStateException(
                   "Expected BiLinearBetaConstraints but found " +
                   (rawConstraints != null ? 
rawConstraints.getClass().getName() : "null")
           );
   ```



##########
drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/BiLinearTest.java:
##########
@@ -0,0 +1,836 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.mvel.integrationtests;
+
+import org.drools.base.common.NetworkNode;
+import org.drools.mvel.integrationtests.phreak.A;
+import org.drools.mvel.integrationtests.phreak.B;
+import org.drools.mvel.integrationtests.phreak.C;
+import org.drools.mvel.integrationtests.phreak.D;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.api.KieBase;
+import org.kie.api.io.ResourceType;
+import org.kie.api.runtime.KieSession;
+import org.kie.internal.utils.KieHelper;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class BiLinearTest {
+
+    @BeforeEach
+    public void setUp() {
+        System.setProperty("drools.bilinear.enabled", "true");
+    }
+
+    @AfterEach
+    public void cleanup() {
+        System.clearProperty("drools.bilinear.enabled");
+    }
+
+    @Test
+    public void testBiLinear() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(2, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+    @Test
+    public void testBiLinearSwapOrder() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(2, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+
+    @Test
+    public void testBiLinear3RuleSetup() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(3, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+    @Test
+    public void testBiLinear3RuleSetupShort() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(3, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+    @Test
+    public void testBiLinear3RuleSetup2() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(3, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 2);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+    @Test
+    public void testBiLinearSayAAAAAAAAA() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        System.setProperty("drools.bilinear.enabled", "false");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : A()\n" +
+                        "    $d : A()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : A()\n" +
+                        "    $c : A()\n" +
+                        "    $d : A()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : A()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertNoBiLinearNodes(visitor, kieBase);
+
+        assertEquals(3, firedRules);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+    @Test
+    public void testBiLinearShortNoBiLinear() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $c : B()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertNoBiLinearNodes(visitor, kieBase);
+
+        assertEquals(2, firedRules);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+    @Test
+    public void testBiLinearEqualRulesNoBiLinear() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $c : B()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertNoBiLinearNodes(visitor, kieBase);
+
+        assertEquals(2, firedRules);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+    @Test
+    public void testBiLinearChain() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule4\"\n" +
+                        "when\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule4 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert(new A(5));
+        session.insert(new B(10));
+        session.insert(new C(10));
+        session.insert(new D(10));
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertEquals(4, firedRules);
+
+        assertBiLinearNodeCount(visitor, kieBase, 2);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+
+    @Test
+    public void testBiLinearNoNetworkShouldBeMade() {
+        System.out.println("\nšŸ” Testing BiLinear functionality...");
+
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $s1 : String()\n" +
+                        "    $s2 : String()\n" +
+                        "    eval( 1 == 1 )\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule1 fired\" );\n" +
+                        "end\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $s1 : String()\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule2 fired\" );\n" +
+                        "end\n" +
+                        "\n" +
+                        "\n" +
+                        "rule \"Rule3\"\n" +
+                        "when\n" +
+                        "    $s1 : String()\n" +
+                        "    $s2 : String()\n" +
+                        "    $s3 : String()\n" +
+                        "    eval( 1 == 1 )\n" +
+                        "then\n" +
+                        "    System.out.println(\"Rule3 fired\" );\n" +
+                        "end\n";
+
+        System.out.println("šŸ”§ Building KieBase with BiLinear enabled...");
+        KieBase kieBase = buildKieBase(drl);
+
+        System.out.println("šŸ“Š Network structure:");
+        NetworkVisitor visitor = new NetworkVisitor();
+        visitor.debugNetworkStructure(kieBase);
+
+        System.out.println("\nšŸš€ Testing execution...");
+        KieSession session = kieBase.newKieSession();
+        session.insert("test");
+
+        int firedRules = session.fireAllRules();
+        System.out.println("šŸ“ˆ Rules fired: " + firedRules);
+
+        assertNoBiLinearNodes(visitor, kieBase);
+
+        assertEquals(3, firedRules);
+
+        session.dispose();
+        System.out.println("āœ… BiLinear test completed");
+    }
+
+    private KieBase buildKieBase(String drl) {
+        // Note: BiLinear property should be set by the calling test before 
calling this method
+        KieHelper kieHelper = new KieHelper();
+        kieHelper.addContent(drl, ResourceType.DRL);
+        return kieHelper.build();
+    }
+
+    @Test
+    public void testBiLinearJoinNodesPresentInNetwork() {
+        String drl =
+                "import " + A.class.getCanonicalName() + "\n" +
+                        "import " + B.class.getCanonicalName() + "\n" +
+                        "import " + C.class.getCanonicalName() + "\n" +
+                        "import " + D.class.getCanonicalName() + "\n" +
+                        "\n" +
+                        "rule \"Rule1\"\n" +
+                        "when\n" +
+                        "    $a : A()\n" +
+                        "    $b : B()\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "end\n" +
+                        "\n" +
+                        "rule \"Rule2\"\n" +
+                        "when\n" +
+                        "    $c : C()\n" +
+                        "    $d : D()\n" +
+                        "then\n" +
+                        "end\n";
+
+        KieBase kieBase = buildKieBase(drl);
+
+        NetworkVisitor visitor = new NetworkVisitor();
+        assertBiLinearNodeCount(visitor, kieBase, 1);
+
+        System.out.println("BiLinear network structure:");
+        visitor.debugNetworkStructure(kieBase);
+    }
+
+    private void assertBiLinearNodeCount(NetworkVisitor visitor, KieBase 
kieBase, int bilinearNodeCount) {
+
+        List<NetworkNode> biLinearNodes = 
visitor.findBiLinearJoinNodes(kieBase);
+
+        assertThat(biLinearNodes)
+                .as("BiLinear optimization should create BiLinearJoinNode(s)")
+                .isNotEmpty();
+
+        assertThat(biLinearNodes).hasSize(bilinearNodeCount);
+    }
+
+    @Test
+    public void testNoBiLinearJoinNodesWhenDisabled() {
+        System.setProperty("drools.bilinear.enabled", "false");

Review Comment:
   Similar to the previous issue, testNoBiLinearJoinNodesWhenDisabled sets the 
property to "false" without ensuring it's restored. This could affect 
subsequent tests. The cleanup() method in @AfterEach clears all properties, but 
if this test fails before reaching cleanup, the property state could be 
corrupted for other tests.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to