Skip to content

Fix Pipeline Validation Circular Dependency Detection [6](#header-6) #34

@fuzziecoder

Description

@fuzziecoder

🎯 Issue Summary

Fix bug in circular dependency detection algorithm that fails to detect certain circular dependency patterns.

📋 Current Behavior

The _has_circular_dependencies() method in PipelineEngine has a bug that allows some circular dependencies to pass validation.

Affected Code:

  • File: backend/core/pipeline_engine.py (lines 116-145)
  • Method: _has_circular_dependencies()

Bug Description:
The current DFS implementation doesn't properly track visited nodes in recursive calls, allowing cycles like A→B→C→A to pass validation.

✨ Expected Behavior

All circular dependencies should be detected and rejected during pipeline validation.

🔧 Technical Requirements

1. Fix DFS Algorithm

  • Update _has_circular_dependencies() to use proper visited tracking
  • Add recursion stack to detect back edges
  • Handle self-loops (stage depends on itself)

2. Add Test Cases

  • Test simple cycle: A→B→A
  • Test complex cycle: A→B→C→A
  • Test self-loop: A→A
  • Test valid DAG with no cycles

3. Error Messages

  • Return specific cycle path in error message
  • Example: "Circular dependency detected: stage1 → stage2 → stage1"

📝 Acceptance Criteria

  • ✅ All circular dependencies detected correctly
  • ✅ Valid DAGs pass validation
  • ✅ Error messages show exact cycle path
  • ✅ Unit tests cover all edge cases
  • ✅ Performance: O(V + E) time complexity

🏷️ Labels

apertre3.0 Medium bug pipeline-engine

💡 Correct Implementation

# backend/core/pipeline_engine.py  [7](#header-7)
@staticmethod  
def _has_circular_dependencies(pipeline: Pipeline) -> bool:  
    """Detect cycles using DFS with recursion stack"""  
    graph = {stage.id: stage.dependencies for stage in pipeline.stages}  
    visited = set()  
    rec_stack = set()  
      
    def dfs(node):  
        visited.add(node)  
        rec_stack.add(node)  
          
        for neighbor in graph.get(node, []):  
            if neighbor not in visited:  
                if dfs(neighbor):  
                    return True  
            elif neighbor in rec_stack:  
                return True  # Back edge found  
          
        rec_stack.remove(node)  
        return False  
      
    for stage_id in graph:  
        if stage_id not in visited:  
            if dfs(stage_id):  
                return True  
      
    return False

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions