```python:inference/core/workflows/execution_engine/v1/compiler/graph_constructor.py
from collections import defaultdict
from typing import Dict, List, Set, Tuple

import networkx as nx
from inference.core.workflows.errors import ControlFlowDefinitionError
from inference.core.workflows.execution_engine.v1.compiler.entities import \
    StepNode
from inference.core.workflows.execution_engine.v1.compiler.utils import (
    identify_lineage, node_as)
from networkx import DiGraph


def _collect_unique_control_flow_lineages_with_step_mapping(
    control_flow_steps_selectors: List[str],
    execution_graph: nx.DiGraph,
) -> Tuple[List[List[str]], Dict[int, List[str]]]:
    """
    Collect data lineages from control flow steps and deduplicate by lineage id.

    For each given control flow step selector, reads its data_lineage from the
    execution graph. Returns unique non-empty lineages (by identify_lineage id)
    and a mapping from lineage id to every step selector that produced that
    lineage (used e.g. for error reporting when lineage is incompatible).

    Args:
        control_flow_steps_selectors: Step selectors (node ids) of control
            flow steps whose data_lineage is to be collected.
        execution_graph: The workflow execution graph containing step nodes
            and their data_lineage.

    Returns:
        A tuple of (unique_lineages, lineage_id_to_step_selectors):
        - unique_lineages: list of distinct non-empty data lineages.
        - lineage_id_to_step_selectors: map from lineage id to list of step
          selectors whose data_lineage has that id (includes empty lineages).
    """
    unique_lineages: List[List[str]] = []
    seen_lineage_ids: Set[int] = set()
    lineage_id_to_step_selectors: Dict[int, List[str]] = defaultdict(list)

    for step_selector in control_flow_steps_selectors:
        step_data = node_as(
            execution_graph=execution_graph,
            node=step_selector,
            expected_type=StepNode,
        )
        lineage = step_data.data_lineage
        lineage_id = identify_lineage(lineage)
        lineage_id_to_step_selectors[lineage_id].append(step_selector)
        if lineage and lineage_id not in seen_lineage_ids:
            seen_lineage_ids.add(lineage_id)
            unique_lineages.append(lineage)

    return unique_lineages, dict(lineage_id_to_step_selectors)


def verify_compatibility_of_input_data_lineage_with_control_flow_lineage(
    step_name: str,
    inputs_lineage: List[List[str]],
    control_flow_steps_selectors: List[str],
    execution_graph: DiGraph,
) -> None:
    """
    Ensure control flow steps' data lineage is compatible with the step's inputs.

    Control flow steps that affect this step must operate on data that is
    compatible with the data fed to the step; otherwise the step could never
    execute. Compares unique control flow lineages against input lineage
    prefixes and raises ControlFlowDefinitionError if any control flow lineage
    is not covered by the inputs.

    If inputs_lineage is empty, there is no sense to verify compatibility. The lineage of the
    step should be established based on the control flow lineages.

    Args:
        step_name: Name of the step being verified (used in error messages).
        inputs_lineage: Data lineages derived from the step's input data.
        control_flow_steps_selectors: Step selectors of control flow steps
            that affect this step's execution.
        execution_graph: The workflow execution graph.

    Raises:
        ControlFlowDefinitionError: When a control flow step's lineage is not
            compatible with the step's input lineage (step would never execute).
    """
    if not inputs_lineage:
        return

    (
        batch_oriented_control_flow_lineages,
        lineage_id2control_flow_steps,
    ) = _collect_unique_control_flow_lineages_with_step_mapping(
        control_flow_steps_selectors=control_flow_steps_selectors,
        execution_graph=execution_graph,
    )

    all_input_lineage_prefixes_hashes = set()
    for lineage in inputs_lineage:
        prefix_hash = 0
        for element in lineage:
            prefix_hash += hash(element)
            all_input_lineage_prefixes_hashes.add(prefix_hash)

    for control_flow_lineage in batch_oriented_control_flow_lineages:
        control_flow_lineage_id = identify_lineage(lineage=control_flow_lineage)
        if control_flow_lineage_id not in all_input_lineage_prefixes_hashes:
            problematic_flow_control_steps = lineage_id2control_flow_steps[
                control_flow_lineage_id
            ]
            raise ControlFlowDefinitionError(
                public_message=f"Step {step_name} execution is impacted by control flow outcome of the following "
                f"steps {problematic_flow_control_steps} which make decision based on data that is "
                f"not compatible with data fed to the step {step_name} - which would cause the step "
                f"to never execute. This behaviour is invalid and prevented upfront by Workflows compiler.",
                context="workflow_compilation | execution_graph_construction | verification_of_control_flow_lineage",
            )


def get_all_batch_lineage_prefixes(lineages: List[List[str]]) -> List[List[str]]:
    result = []
    already_spotted = set()
    for lineage in lineages:
        prefix = []
        prefix_hash = 0
        for element in lineage:
            prefix.append(element)
            prefix_hash += hash(element)
            if prefix_hash not in already_spotted:
                already_spotted.add(prefix_hash)
                result.append(prefix[:])
    return result
```
```python:inference/core/workflows/execution_engine/v1/compiler/utils.py
from copy import copy
from typing import List


def get_batch_lineage_prefixes(lineage: List[str]) -> List[List[str]]:
    if not lineage:
        return []
    result = []
    prefix = []
    for element in lineage:
        prefix.append(element)
        result.append(copy(prefix))
    return result
```