"""
Level Validators
This module provides consolidated validation logic for each validation level,
replacing the separate level tester classes with a unified approach.
"""
from typing import Dict, Any, List, Optional
import logging
from ....step_catalog import StepCatalog
from ..config import ValidationLevel
logger = logging.getLogger(__name__)
[docs]
class LevelValidators:
"""Consolidated validation logic for each level."""
def __init__(self, workspace_dirs: Optional[List[str]] = None):
"""
Initialize level validators.
Args:
workspace_dirs: Optional list of workspace directories to search
"""
self.workspace_dirs = workspace_dirs
self.step_catalog = StepCatalog(workspace_dirs=workspace_dirs)
workspace_count = len(workspace_dirs) if workspace_dirs else 0
logger.info(
f"Initialized LevelValidators with {workspace_count} workspace directories"
)
[docs]
def run_level_1_validation(self, step_name: str) -> Dict[str, Any]:
"""
Level 1: Script ↔ Contract validation.
Args:
step_name: Name of the step to validate
Returns:
Dictionary containing validation results
"""
logger.info(f"Running Level 1 validation for step: {step_name}")
try:
# Use existing script_contract_alignment logic
from .script_contract_alignment import ScriptContractAlignmentTester
alignment = ScriptContractAlignmentTester(
workspace_dirs=self.workspace_dirs
)
result = alignment.validate_script(step_name)
logger.info(f"Level 1 validation completed for {step_name}")
return {
"level": 1,
"step_name": step_name,
"validation_type": "script_contract",
"status": "COMPLETED",
"result": result,
}
except Exception as e:
logger.error(f"Level 1 validation failed for {step_name}: {str(e)}")
return {
"level": 1,
"step_name": step_name,
"validation_type": "script_contract",
"status": "ERROR",
"error": str(e),
}
[docs]
def run_level_3_validation(self, step_name: str) -> Dict[str, Any]:
"""
Level 3: Specification ↔ Dependencies validation (Universal).
This level is universal and applies to all non-excluded step types.
Args:
step_name: Name of the step to validate
Returns:
Dictionary containing validation results
"""
logger.info(f"Running Level 3 validation for step: {step_name}")
try:
# Use existing spec_dependency_alignment logic
from .spec_dependency_alignment import (
SpecificationDependencyAlignmentTester,
)
alignment = SpecificationDependencyAlignmentTester(
workspace_dirs=self.workspace_dirs
)
result = alignment.validate_specification(step_name)
logger.info(f"Level 3 validation completed for {step_name}")
return {
"level": 3,
"step_name": step_name,
"validation_type": "spec_dependency",
"status": "COMPLETED",
"result": result,
"universal": True, # Mark as universal validation
}
except Exception as e:
logger.error(f"Level 3 validation failed for {step_name}: {str(e)}")
return {
"level": 3,
"step_name": step_name,
"validation_type": "spec_dependency",
"status": "ERROR",
"error": str(e),
"universal": True,
}
[docs]
def run_level_4_validation(
self, step_name: str, validator_class: Optional[str] = None
) -> Dict[str, Any]:
"""
Level 4: Builder ↔ Configuration validation (Step-type-specific).
Args:
step_name: Name of the step to validate
validator_class: Name of the step-type-specific validator class to use
Returns:
Dictionary containing validation results
"""
logger.info(
f"Running Level 4 validation for step: {step_name} with validator: {validator_class}"
)
if not validator_class:
logger.warning(
f"No validator class specified for Level 4 validation of {step_name}"
)
return {
"level": 4,
"step_name": step_name,
"validation_type": "builder_config",
"status": "SKIPPED",
"reason": "No validator class specified",
}
try:
# Use step-type-specific validator
validator = self._get_step_type_validator(validator_class)
if not validator:
logger.warning(
f"Validator {validator_class} is not implemented for step {step_name}"
)
return {
"level": 4,
"step_name": step_name,
"validation_type": "builder_config",
"status": "SKIPPED",
"reason": f"Validator {validator_class} is not yet implemented",
"validator_class": validator_class,
"implementation_needed": True,
}
result = validator.validate_builder_config_alignment(step_name)
logger.info(f"Level 4 validation completed for {step_name}")
return {
"level": 4,
"step_name": step_name,
"validation_type": "builder_config",
"status": "COMPLETED",
"validator_class": validator_class,
"result": result,
}
except Exception as e:
logger.error(f"Level 4 validation failed for {step_name}: {str(e)}")
return {
"level": 4,
"step_name": step_name,
"validation_type": "builder_config",
"status": "ERROR",
"validator_class": validator_class,
"error": str(e),
}
def _get_step_type_validator(self, validator_class: str):
"""
Get the Level-4 validator instance.
FZ 31e1d3g3 Phase D3: Level 4 is now the single, step-type-AGNOSTIC B3
``RegistryBindingValidator`` — it validates that the step is realizable from its registry
row + ``.step.yaml`` + config (handler binds, builder loads, config covers), replacing the
per-step-type source-scanning validators that reported every declarative shell as FAILED.
The ``validator_class`` arg (the ruleset's ``level_4_validator_class`` string) is ignored;
every routable step uses B3. Returns ``None`` only if B3 itself can't be constructed.
Args:
validator_class: Legacy ruleset validator-class name (ignored; kept for signature compat)
Returns:
The B3 RegistryBindingValidator instance, or None on construction failure
"""
try:
from ..validators.registry_binding_validator import RegistryBindingValidator
return RegistryBindingValidator(self.workspace_dirs)
except Exception as e:
logger.error(f"Failed to create RegistryBindingValidator: {str(e)}")
return None
[docs]
def validate_level_configuration(self, level: ValidationLevel) -> List[str]:
"""
Validate that a validation level can be executed.
Args:
level: Validation level to check
Returns:
List of validation issues (empty if valid)
"""
import importlib.util
issues = []
# Probe the backing module for each level via find_spec (no import side effects, no
# unused-import). FZ 31e1d3g3 Phase D5: Level 4 is the B3 registry_binding_validator
# (the per-step-type ValidatorFactory + source-scanning validators were removed in D3).
_module_for_level = {
ValidationLevel.SCRIPT_CONTRACT: (
".script_contract_alignment",
"Level 1",
),
ValidationLevel.SPEC_DEPENDENCY: (
".spec_dependency_alignment",
"Level 3",
),
ValidationLevel.BUILDER_CONFIG: (
"..validators.registry_binding_validator",
"Level 4",
),
}
entry = _module_for_level.get(level)
if entry is not None:
rel_module, level_label = entry
if importlib.util.find_spec(rel_module, package=__package__) is None:
issues.append(
f"{rel_module} module not available for {level_label} validation"
)
return issues
[docs]
def get_available_levels(self) -> List[ValidationLevel]:
"""
Get list of validation levels that can be executed.
Returns:
List of available validation levels
"""
available_levels = []
for level in ValidationLevel:
issues = self.validate_level_configuration(level)
if not issues:
available_levels.append(level)
return available_levels