""" Schema validation quality gate. Implements FR-9.1: Validate generated artifacts against JSON schemas. Uses the jsonschema library for validation. """ import json import uuid from typing import Any, Dict, Optional import jsonschema from markitect.prompts.quality.models import ( GateType, QualityGate, ValidationDiagnostic, ValidationResult, ValidationStatus, ) class SchemaValidationGate(QualityGate): """ Validates artifact content against a JSON schema. Parses content as JSON and validates against the provided schema using the jsonschema library. """ def __init__( self, schema: Dict[str, Any], gate_id: Optional[str] = None, name: str = "schema", ): """ Initialize with a JSON schema. Args: schema: JSON Schema dictionary gate_id: Optional gate identifier (auto-generated if not provided) name: Human-readable gate name """ super().__init__( gate_id=gate_id or str(uuid.uuid4()), name=name, gate_type=GateType.SCHEMA, ) self.schema = schema def validate(self, content: str, artifact_id: str) -> ValidationResult: """ Validate content against the JSON schema. Parses the content as JSON, then validates against the schema. Returns FAIL if content is not valid JSON or fails schema validation. Args: content: JSON content string to validate artifact_id: ID of the artifact being validated Returns: ValidationResult with status and diagnostics """ diagnostics = [] # Parse JSON try: data = json.loads(content) except (json.JSONDecodeError, TypeError) as e: diagnostics.append( ValidationDiagnostic( code="INVALID_JSON", message=f"Content is not valid JSON: {e}", severity="error", ) ) return ValidationResult.create( gate_id=self.id, gate_type=self.gate_type, artifact_id=artifact_id, status=ValidationStatus.FAIL, score=0.0, diagnostics=diagnostics, ) # Validate against schema validator = jsonschema.Draft7Validator(self.schema) errors = list(validator.iter_errors(data)) if not errors: return ValidationResult.create( gate_id=self.id, gate_type=self.gate_type, artifact_id=artifact_id, status=ValidationStatus.PASS, score=1.0, diagnostics=[], ) for error in errors: path = ".".join(str(p) for p in error.absolute_path) or "(root)" diagnostics.append( ValidationDiagnostic( code="SCHEMA_VIOLATION", message=f"At '{path}': {error.message}", severity="error", ) ) # Score based on proportion of passing validations total_checks = len(errors) + 1 # approximate score = max(0.0, 1.0 - len(errors) / total_checks) return ValidationResult.create( gate_id=self.id, gate_type=self.gate_type, artifact_id=artifact_id, status=ValidationStatus.FAIL, score=score, diagnostics=diagnostics, )