change
This commit is contained in:
@@ -8,6 +8,9 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
from jsonschema.exceptions import SchemaError
|
||||
|
||||
from vendor.cdsl_preview_runtime import step_to_glb
|
||||
from app.services.storage import WorkspaceStore, now_iso, write_json
|
||||
from app.settings import Settings
|
||||
@@ -34,11 +37,50 @@ def _walk(value: Any) -> list[tuple[str, Any]]:
|
||||
return result
|
||||
|
||||
|
||||
def _engine_schema(engine: Any) -> dict[str, Any]:
|
||||
schema_path = Path(str(engine.__file__)).with_name("profile_schema.json")
|
||||
try:
|
||||
schema = json.loads(schema_path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise RuntimeError("The local engine schema document is unavailable or invalid") from error
|
||||
if not isinstance(schema, dict) or not isinstance(schema.get("feature_atomic_ids"), dict):
|
||||
raise RuntimeError("The local engine schema has no feature_atomic_ids contract")
|
||||
return schema
|
||||
|
||||
|
||||
def load_cdsl_json_schema(engine: Any) -> dict[str, Any]:
|
||||
document = _engine_schema(engine)
|
||||
schema_name = str(document.get("cdsl_json_schema_file") or "")
|
||||
if not schema_name or Path(schema_name).name != schema_name:
|
||||
raise RuntimeError("The local engine schema has an invalid CDSL JSON Schema path")
|
||||
schema_path = Path(str(engine.__file__)).with_name(schema_name)
|
||||
try:
|
||||
schema = json.loads(schema_path.read_text(encoding="utf-8"))
|
||||
Draft202012Validator.check_schema(schema)
|
||||
except (OSError, json.JSONDecodeError, SchemaError) as error:
|
||||
raise RuntimeError("The local CDSL JSON Schema is unavailable or invalid") from error
|
||||
return schema
|
||||
|
||||
|
||||
def _validate_cdsl_json_schema(cdsl: dict[str, Any], engine: Any) -> None:
|
||||
validator = Draft202012Validator(load_cdsl_json_schema(engine))
|
||||
errors = sorted(validator.iter_errors(cdsl), key=lambda error: (list(error.absolute_path), error.message))
|
||||
if not errors:
|
||||
return
|
||||
error = errors[0]
|
||||
location = "$" + "".join(
|
||||
f"[{item}]" if isinstance(item, int) else f".{item}"
|
||||
for item in error.absolute_path
|
||||
)
|
||||
raise ValueError(f"CDSL schema violation at {location}: {error.message}")
|
||||
|
||||
|
||||
def validate_cdsl(cdsl: dict[str, Any], engine: Any) -> None:
|
||||
if not isinstance(cdsl, dict):
|
||||
raise ValueError("CDSL must be a JSON object")
|
||||
if cdsl.get("schema") != "cad.cdsl.llm.v1":
|
||||
raise ValueError("Unsupported CDSL schema")
|
||||
_validate_cdsl_json_schema(cdsl, engine)
|
||||
part_id = str(cdsl.get("part_id") or "")
|
||||
if not re.fullmatch(r"[a-zA-Z0-9_-]{3,80}", part_id):
|
||||
raise ValueError("part_id must use letters, numbers, underscores, or hyphens")
|
||||
@@ -51,16 +93,42 @@ def validate_cdsl(cdsl: dict[str, Any], engine: Any) -> None:
|
||||
if not isinstance(features, list) or not features or not isinstance(sketches, list) or not sketches:
|
||||
raise ValueError("CDSL requires features and parameterized sketches")
|
||||
sketch_ids = {str(sketch.get("id")) for sketch in sketches}
|
||||
semantic_contract = _engine_schema(engine)
|
||||
atomic_contracts = semantic_contract["feature_atomic_ids"]
|
||||
supported_atomic_ids = sorted(
|
||||
str(atomic_id)
|
||||
for atomic_id in semantic_contract.get("runtime_supported_atomic_ids", atomic_contracts)
|
||||
)
|
||||
feature_ids: set[str] = set()
|
||||
for feature in features:
|
||||
fid = str(feature.get("id") or "")
|
||||
if not fid or fid in feature_ids:
|
||||
raise ValueError("Feature ids must be unique")
|
||||
feature_ids.add(fid)
|
||||
if feature.get("execution_status") not in (None, "supported"):
|
||||
raise ValueError(f"Feature {fid} is deferred and cannot be rebuilt by the current engine")
|
||||
if str(feature.get("sketch_id") or "") not in sketch_ids:
|
||||
raise ValueError(f"Feature {fid} refers to a missing sketch")
|
||||
if not str(feature.get("atomic_id") or ""):
|
||||
atomic_id = str(feature.get("atomic_id") or "")
|
||||
if not atomic_id:
|
||||
raise ValueError(f"Feature {fid} has no atomic_id")
|
||||
contract = atomic_contracts.get(atomic_id)
|
||||
if atomic_id not in supported_atomic_ids or not isinstance(contract, dict):
|
||||
raise ValueError(
|
||||
f"Unsupported CDSL atomic_id: {atomic_id}. Supported: {', '.join(supported_atomic_ids)}"
|
||||
)
|
||||
params = feature.get("params")
|
||||
if not isinstance(params, dict):
|
||||
raise ValueError(f"Feature {fid} params must be an object")
|
||||
for parameter_name in contract.get("required_params") or []:
|
||||
if params.get(parameter_name) is None:
|
||||
raise ValueError(f"Feature {fid} ({atomic_id}) is missing required parameter: {parameter_name}")
|
||||
if atomic_id.startswith("extrude_") and float(params.get("distance_mm") or 0) <= 0:
|
||||
raise ValueError(f"Feature {fid} ({atomic_id}) requires distance_mm > 0 for runtime rebuild")
|
||||
if atomic_id.startswith("revolve_") and float(params.get("angle_deg") or 0) <= 0:
|
||||
raise ValueError(f"Feature {fid} ({atomic_id}) requires angle_deg > 0 for runtime rebuild")
|
||||
if contract.get("requires_sketch") and str(feature.get("sketch_id") or "") not in sketch_ids:
|
||||
raise ValueError(f"Feature {fid} ({atomic_id}) requires a valid sketch_id")
|
||||
for dependency in feature.get("depends_on") or []:
|
||||
if dependency not in feature_ids:
|
||||
raise ValueError(f"Feature {fid} has a forward or missing dependency")
|
||||
@@ -76,6 +144,10 @@ def validate_cdsl(cdsl: dict[str, Any], engine: Any) -> None:
|
||||
raise ValueError("Polygon profiles require vertices")
|
||||
elif profile_type not in engine.SHAPE_GENERATORS:
|
||||
raise ValueError(f"Unsupported CDSL profile: {profile_type}")
|
||||
try:
|
||||
engine.compile_cdsl(copy.deepcopy(cdsl))
|
||||
except Exception as error:
|
||||
raise ValueError(f"CDSL engine compile preflight failed: {error}") from error
|
||||
|
||||
|
||||
def _parameter_id(path: list[str]) -> str:
|
||||
|
||||
Reference in New Issue
Block a user