```python:inference/core/cache/air_gapped.py
import functools
import logging
from typing import Dict, List, Optional

logger = logging.getLogger(__name__)




@functools.cache
def _load_blocks() -> list:
    """Load workflow blocks, isolating the heavy import for testability."""
    from inference.core.workflows.execution_engine.introspection.blocks_loader import \
        load_workflow_blocks

    return load_workflow_blocks()




# Cache mapping from block.identifier -> resolved type identifier
_BLOCK_TYPE_ID_CACHE: Dict[str, str] = {}


def get_task_type_to_block_mapping(
    blocks: Optional[list] = None,
) -> Dict[str, List[str]]:
    """Build a reverse mapping from task_type to compatible block type identifiers.

    Uses ``get_compatible_task_types()`` on block manifests.  Blocks whose
    method returns *None* (the base-class default) are skipped.

    Args:
        blocks: Optional pre-loaded list of block specifications.  When
            *None* (the default) the blocks are loaded via the engine's
            block loader.
    """
    mapping: Dict[str, List[str]] = {}
    if blocks is None:
        try:
            blocks = _load_blocks()
        except Exception:
            logger.debug(
                "Could not load workflow blocks for task-type mapping",
                exc_info=True,
            )
            return mapping

    for block in blocks:
        manifest_cls = block.manifest_class
        task_types = manifest_cls.get_compatible_task_types()
        if task_types is None:
            continue

        block_type_id = _get_block_type_identifier(block)
        for tt in task_types:
            mapping.setdefault(tt, []).append(block_type_id)

    return mapping


def _get_block_type_identifier(block) -> str:
    """Extract the canonical ``type`` identifier from a block specification."""
    # Use a simple identifier-keyed cache to avoid reparsing schema for the same block type.
    # This preserves behavior while improving repeated-call performance.
    key = getattr(block, "identifier", None)
    if key is not None:
        cached = _BLOCK_TYPE_ID_CACHE.get(key)
        if cached is not None:
            return cached

    try:
        schema = block.manifest_class.model_json_schema()
        type_prop = schema.get("properties", {}).get("type", {})
        # The type field is typically a const or enum with one value.
        if "const" in type_prop:
            result = type_prop["const"]
            if key is not None:
                _BLOCK_TYPE_ID_CACHE[key] = result
            return result
        if "enum" in type_prop and type_prop["enum"]:
            result = type_prop["enum"][0]
            if key is not None:
                _BLOCK_TYPE_ID_CACHE[key] = result
            return result
    except Exception:
        pass
    # Fallback: use the block's identifier; cache if possible
    fallback = block.identifier
    if key is not None:
        _BLOCK_TYPE_ID_CACHE[key] = fallback
    return fallback
```