This commit is contained in:
2026-08-21 11:13:18 +08:00
parent beba8744b4
commit 41ed846c23
2028 changed files with 1327194 additions and 293 deletions
+73 -1
View File
@@ -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: