"""Validation for the semantic planning artifact between a request and CDSL.""" from __future__ import annotations import json from copy import deepcopy from pathlib import Path from typing import Any from jsonschema import Draft202012Validator from jsonschema.exceptions import SchemaError class DesignIntentError(ValueError): """A plan-contract violation with a stable error code for the Agent.""" def __init__(self, code: str, message: str) -> None: super().__init__(message) self.code = code def _schema_path(engine: Any) -> Path: return Path(str(engine.__file__)).with_name("design_intent_schema.json") def load_design_intent_schema(engine: Any) -> dict[str, Any]: path = _schema_path(engine) try: schema = json.loads(path.read_text(encoding="utf-8")) Draft202012Validator.check_schema(schema) except (OSError, json.JSONDecodeError, SchemaError) as error: raise RuntimeError("The local DesignIntent JSON Schema is unavailable or invalid") from error return schema def _location(error: Any) -> str: return "$" + "".join( f"[{item}]" if isinstance(item, int) else f".{item}" for item in error.absolute_path ) def _schema_validate(intent: dict[str, Any], engine: Any) -> None: errors = sorted( Draft202012Validator(load_design_intent_schema(engine)).iter_errors(intent), key=lambda error: (list(error.absolute_path), error.message), ) if errors: error = errors[0] raise DesignIntentError( "INVALID_DESIGN_INTENT", f"DesignIntent schema violation at {_location(error)}: {error.message}", ) def _runtime_capabilities(engine: Any) -> tuple[set[str], set[str], dict[str, dict[str, Any]]]: try: profile_schema = json.loads(Path(str(engine.__file__)).with_name("profile_schema.json").read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as error: raise RuntimeError("The local CDSL profile schema is unavailable or invalid") from error atomic_ids = set(profile_schema.get("runtime_supported_atomic_ids") or getattr(engine, "SUPPORTED_ATOMIC_IDS", ())) profiles = { name for name, contract in (profile_schema.get("profiles") or {}).items() if isinstance(contract, dict) and contract.get("agent_allowed") is True } atomic_contracts = { str(atomic_id): contract for atomic_id, contract in (profile_schema.get("feature_atomic_ids") or {}).items() if isinstance(contract, dict) } return {str(item) for item in atomic_ids}, {str(item) for item in profiles}, atomic_contracts def _blocking(intent: dict[str, Any]) -> bool: questions = intent.get("open_questions") or [] gaps = intent.get("capability_gaps") or [] return any(isinstance(item, dict) and item.get("blocking") is True for item in [*questions, *gaps]) def validate_design_intent( intent: dict[str, Any], engine: Any, *, current_revision_id: str = "", ) -> dict[str, Any]: """Validate DesignIntent syntax and semantic ordering against runtime capability.""" if not isinstance(intent, dict): raise DesignIntentError("INVALID_DESIGN_INTENT", "DesignIntent must be a JSON object") _schema_validate(intent, engine) normalized = deepcopy(intent) mode = str(normalized["mode"]) base_revision_id = str(normalized["base_revision_id"] or "") if mode == "create" and base_revision_id: raise DesignIntentError("INVALID_DESIGN_INTENT", "create DesignIntent must not set base_revision_id") if mode == "revise": if not base_revision_id: raise DesignIntentError("INVALID_DESIGN_INTENT", "revise DesignIntent requires base_revision_id") if current_revision_id and base_revision_id != current_revision_id: raise DesignIntentError( "INVALID_DESIGN_INTENT", "revise DesignIntent base_revision_id must be the current successful revision", ) structures = normalized["structures"] ids = [str(item["id"]) for item in structures] feature_ids = [str(item["cdsl_feature_id"]) for item in structures] if len(set(ids)) != len(ids): raise DesignIntentError("INVALID_DESIGN_INTENT", "DesignIntent structure ids must be unique") if len(set(feature_ids)) != len(feature_ids): raise DesignIntentError("INVALID_DESIGN_INTENT", "DesignIntent cdsl_feature_id values must be unique") feature_order = [str(item) for item in normalized["feature_order"]] if len(set(feature_order)) != len(feature_order) or set(feature_order) != set(ids): raise DesignIntentError( "INVALID_DESIGN_INTENT", "feature_order must list every DesignIntent structure exactly once", ) order = {structure_id: index for index, structure_id in enumerate(feature_order)} atomic_ids, profile_types, atomic_contracts = _runtime_capabilities(engine) for structure in structures: structure_id = str(structure["id"]) for dependency in structure["depends_on"]: dependency_id = str(dependency) if dependency_id not in order: raise DesignIntentError( "INVALID_DESIGN_INTENT", f"Structure {structure_id} depends on an unknown structure {dependency_id}", ) if order[dependency_id] >= order[structure_id]: raise DesignIntentError( "INVALID_DESIGN_INTENT", f"Structure {structure_id} must depend only on earlier feature_order entries", ) strategy = structure["cdsl_strategy"] atomic_id = str(strategy["atomic_id"]) if atomic_id not in atomic_ids: raise DesignIntentError( "INVALID_DESIGN_INTENT", f"Structure {structure_id} uses unsupported atomic_id {atomic_id}", ) profile_type = str(strategy.get("profile_type") or "") if atomic_contracts.get(atomic_id, {}).get("requires_sketch") and not profile_type: raise DesignIntentError( "INVALID_DESIGN_INTENT", f"Structure {structure_id} uses {atomic_id} and requires a profile_type", ) if profile_type and profile_type not in profile_types: raise DesignIntentError( "INVALID_DESIGN_INTENT", f"Structure {structure_id} uses unsupported or agent-disallowed profile_type {profile_type}", ) if normalized["status"] == "ready" and _blocking(normalized): raise DesignIntentError( "DESIGN_INTENT_BLOCKED", "A DesignIntent with blocking questions or capability gaps must need clarification", ) if normalized["status"] == "needs_clarification" and not _blocking(normalized): raise DesignIntentError( "INVALID_DESIGN_INTENT", "needs_clarification requires a blocking question or capability gap", ) if normalized["status"] == "ready" and not structures: raise DesignIntentError("INVALID_DESIGN_INTENT", "A ready DesignIntent needs at least one structure") if normalized["status"] == "ready" and mode == "create" and not any(item["role"] == "base" for item in structures): raise DesignIntentError("INVALID_DESIGN_INTENT", "A ready create DesignIntent needs a base structure") nonblocking_gaps = [item for item in normalized["capability_gaps"] if not item["blocking"]] if nonblocking_gaps and not normalized["assumptions"]: raise DesignIntentError( "INVALID_DESIGN_INTENT", "Non-blocking capability gaps require an explicit approximation assumption", ) for gap in normalized["capability_gaps"]: if str(gap["structure_id"]) not in set(ids): raise DesignIntentError( "INVALID_DESIGN_INTENT", f"Capability gap references an unknown structure {gap['structure_id']}", ) valid_expectation_ids = set(ids) | set(feature_ids) for expectation in normalized["verification_expectations"]: if expectation["type"] == "feature_count" and str(expectation["feature_id"]) not in valid_expectation_ids: raise DesignIntentError( "INVALID_DESIGN_INTENT", f"feature_count expectation references an unknown structure or CDSL feature {expectation['feature_id']}", ) return normalized def _has_selector_evidence(value: Any, role: str) -> bool: if isinstance(value, dict): for key, child in value.items(): if role in {"selector", "selectors"} and key == "selectors" and isinstance(child, list) and child: return True if key == role and child not in (None, "", [], {}): if not (isinstance(child, dict) and set(child) == {"unresolved"}): return True if _has_selector_evidence(child, role): return True elif isinstance(value, list): return any(_has_selector_evidence(item, role) for item in value) return False def validate_intent_cdsl( intent: dict[str, Any], cdsl: dict[str, Any], engine: Any, *, current_cdsl: dict[str, Any] | None = None, ) -> None: """Ensure an executable CDSL document faithfully realizes one accepted plan.""" normalized = validate_design_intent(intent, engine) if normalized["status"] != "ready": raise DesignIntentError("DESIGN_INTENT_BLOCKED", "CDSL generation is blocked until the DesignIntent is ready") if not isinstance(cdsl, dict): raise DesignIntentError("INTENT_CDSL_MISMATCH", "CDSL must be a JSON object") features = cdsl.get("features") sketches = (cdsl.get("geometry") or {}).get("sketches") if not isinstance(features, list) or not isinstance(sketches, list): raise DesignIntentError("INTENT_CDSL_MISMATCH", "CDSL must contain features and sketches") feature_by_id = {str(feature.get("id") or ""): feature for feature in features if isinstance(feature, dict)} if len(feature_by_id) != len(features): raise DesignIntentError("INTENT_CDSL_MISMATCH", "CDSL features must have unique ids") structure_by_id = {str(item["id"]): item for item in normalized["structures"]} expected_feature_ids = [str(structure_by_id[item]["cdsl_feature_id"]) for item in normalized["feature_order"]] if normalized["mode"] == "revise" and isinstance(current_cdsl, dict): base_feature_ids = { str(feature.get("id") or "") for feature in current_cdsl.get("features") or [] if isinstance(feature, dict) and feature.get("id") } if not base_feature_ids.issubset(set(expected_feature_ids)): missing = ", ".join(sorted(base_feature_ids - set(expected_feature_ids))) raise DesignIntentError( "INTENT_CDSL_MISMATCH", f"revise DesignIntent must explicitly preserve current features: {missing}", ) actual_feature_ids = [str(feature.get("id") or "") for feature in features] if actual_feature_ids != expected_feature_ids: raise DesignIntentError( "INTENT_CDSL_MISMATCH", "CDSL feature ids and order must exactly match DesignIntent feature_order", ) sketch_by_id = {str(sketch.get("id") or ""): sketch for sketch in sketches if isinstance(sketch, dict)} feature_for_structure = { str(structure["id"]): feature_by_id[str(structure["cdsl_feature_id"])] for structure in normalized["structures"] } for structure in normalized["structures"]: structure_id = str(structure["id"]) feature = feature_for_structure[structure_id] strategy = structure["cdsl_strategy"] if feature.get("atomic_id") != strategy["atomic_id"]: raise DesignIntentError( "INTENT_CDSL_MISMATCH", f"CDSL feature {feature.get('id')} atomic_id does not match structure {structure_id}", ) expected_dependencies = { str(structure_by_id[dependency]["cdsl_feature_id"]) for dependency in structure["depends_on"] } actual_dependencies = {str(item) for item in feature.get("depends_on") or []} if not expected_dependencies.issubset(actual_dependencies): raise DesignIntentError( "INTENT_CDSL_MISMATCH", f"CDSL feature {feature.get('id')} is missing planned dependencies", ) profile_type = str(strategy.get("profile_type") or "") if profile_type: sketch_id = str(feature.get("sketch_id") or "") sketch = sketch_by_id.get(sketch_id) actual_profile_type = str(((sketch or {}).get("profile") or {}).get("type") or "") if actual_profile_type != profile_type: raise DesignIntentError( "INTENT_CDSL_MISMATCH", f"CDSL feature {feature.get('id')} must use profile_type {profile_type}", ) for selector_role in strategy["selector_roles"]: if not _has_selector_evidence(feature, str(selector_role)): raise DesignIntentError( "INTENT_CDSL_MISMATCH", f"CDSL feature {feature.get('id')} is missing selector evidence for {selector_role}", ) def design_intent_from_cdsl(cdsl: dict[str, Any], *, request: str, mode: str = "create", base_revision_id: str = "") -> dict[str, Any]: """Create a conservative audit-only plan for legacy revisions when needed.""" sketches = (cdsl.get("geometry") or {}).get("sketches") or [] profile_by_sketch_id = { str(sketch.get("id") or ""): str((sketch.get("profile") or {}).get("type") or "") for sketch in sketches if isinstance(sketch, dict) } structures: list[dict[str, Any]] = [] for index, feature in enumerate(cdsl.get("features") or []): if not isinstance(feature, dict): continue feature_id = str(feature.get("id") or f"feature_{index + 1}") atomic_id = str(feature.get("atomic_id") or "") profile_type = profile_by_sketch_id.get(str(feature.get("sketch_id") or ""), "") strategy: dict[str, Any] = { "atomic_id": atomic_id, "parameter_roles": sorted(str(key) for key in (feature.get("params") or {}).keys()), "selector_roles": [], } if profile_type: strategy["profile_type"] = profile_type structures.append({ "id": feature_id, "cdsl_feature_id": feature_id, "role": "base" if index == 0 else "subtractive" if "cut" in atomic_id or atomic_id.startswith("hole") else "additive", "purpose": str(feature.get("name") or feature_id), "depends_on": [str(item) for item in feature.get("depends_on") or []], "cdsl_strategy": strategy, }) return { "schema": "cad.cdsl.design-intent.v1", "schema_version": "1.0", "mode": mode, "request": request, "base_revision_id": base_revision_id, "structures": structures, "feature_order": [item["id"] for item in structures], "assumptions": ["Synthesized from a legacy CDSL revision."], "open_questions": [], "capability_gaps": [], "verification_expectations": [], "status": "ready", }