修改agent流程,分步构建cdsl
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from functools import lru_cache
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -13,22 +13,9 @@ from jsonschema import Draft202012Validator
|
||||
from jsonschema.exceptions import SchemaError
|
||||
|
||||
from vendor.cdsl_preview_runtime import step_to_glb
|
||||
from app.services.quality import evaluate_quality, validate_verification
|
||||
from app.services.storage import WorkspaceStore, now_iso, write_json
|
||||
from app.services.cdsl_fragment import selector_bindings
|
||||
from app.settings import Settings
|
||||
|
||||
|
||||
class QualityVerificationError(RuntimeError):
|
||||
"""A built CDSL document missed a blocking generic verification rule."""
|
||||
|
||||
def __init__(self, quality_report: dict[str, Any]) -> None:
|
||||
super().__init__("Blocking CDSL verification checks failed")
|
||||
self.quality_report = quality_report
|
||||
self.task_id = ""
|
||||
self.revision_id = ""
|
||||
|
||||
|
||||
def load_engine(settings: Settings) -> Any:
|
||||
parent = str(settings.engine_root.parent)
|
||||
if parent not in sys.path:
|
||||
@@ -50,8 +37,11 @@ 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")
|
||||
@lru_cache(maxsize=16)
|
||||
def _read_schema_document(path_value: str, modified_ns: int) -> dict[str, Any]:
|
||||
"""Load an immutable runtime schema once per on-disk version."""
|
||||
del modified_ns # The mtime is intentionally part of the cache key.
|
||||
schema_path = Path(path_value)
|
||||
try:
|
||||
schema = json.loads(schema_path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
@@ -61,12 +51,63 @@ def _engine_schema(engine: Any) -> dict[str, Any]:
|
||||
return schema
|
||||
|
||||
|
||||
def load_cdsl_json_schema(engine: Any) -> dict[str, Any]:
|
||||
def _engine_schema(engine: Any) -> dict[str, Any]:
|
||||
schema_path = Path(str(engine.__file__)).with_name("profile_schema.json")
|
||||
try:
|
||||
modified_ns = schema_path.stat().st_mtime_ns
|
||||
except OSError as error:
|
||||
raise RuntimeError("The local engine schema document is unavailable or invalid") from error
|
||||
return _read_schema_document(str(schema_path), modified_ns)
|
||||
|
||||
|
||||
def feature_atomic_contract(engine: Any, atomic_id: str) -> dict[str, Any]:
|
||||
"""Return the runtime-supported parameter contract for one atomic feature."""
|
||||
schema = _engine_schema(engine)
|
||||
normalized_id = str(atomic_id or "").strip()
|
||||
contracts = schema["feature_atomic_ids"]
|
||||
contract = contracts.get(normalized_id)
|
||||
registered = {str(item) for item in getattr(engine, "SUPPORTED_ATOMIC_IDS", ())}
|
||||
declared = {str(item) for item in schema.get("runtime_supported_atomic_ids") or ()}
|
||||
if normalized_id not in registered or normalized_id not in declared or not isinstance(contract, dict):
|
||||
raise ValueError(f"Unsupported runtime atomic_id: {normalized_id}")
|
||||
required = contract.get("required_params") or []
|
||||
optional = contract.get("optional_params") or []
|
||||
if not all(isinstance(item, str) and item for item in [*required, *optional]):
|
||||
raise RuntimeError(f"Runtime feature contract is invalid for {normalized_id}")
|
||||
return {
|
||||
"atomic_id": normalized_id,
|
||||
"summary": str(contract.get("summary") or ""),
|
||||
"required_params": list(dict.fromkeys(required)),
|
||||
"optional_params": [item for item in dict.fromkeys(optional) if item not in required],
|
||||
"position_format": str(contract.get("position_format") or ""),
|
||||
"requires_sketch": contract.get("requires_sketch") is True,
|
||||
"selector_slot": copy.deepcopy(contract.get("selector_slot")) if isinstance(contract.get("selector_slot"), dict) else None,
|
||||
}
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def _cdsl_validator(path_value: str, modified_ns: int) -> Draft202012Validator:
|
||||
"""Compile the JSON Schema once per runtime schema revision."""
|
||||
del modified_ns
|
||||
schema_path = Path(path_value)
|
||||
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 Draft202012Validator(schema)
|
||||
|
||||
|
||||
def _cdsl_schema_path(engine: Any) -> Path:
|
||||
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)
|
||||
return Path(str(engine.__file__)).with_name(schema_name)
|
||||
|
||||
|
||||
def load_cdsl_json_schema(engine: Any) -> dict[str, Any]:
|
||||
schema_path = _cdsl_schema_path(engine)
|
||||
try:
|
||||
schema = json.loads(schema_path.read_text(encoding="utf-8"))
|
||||
Draft202012Validator.check_schema(schema)
|
||||
@@ -76,7 +117,11 @@ def load_cdsl_json_schema(engine: Any) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _validate_cdsl_json_schema(cdsl: dict[str, Any], engine: Any) -> None:
|
||||
validator = Draft202012Validator(load_cdsl_json_schema(engine))
|
||||
schema_path = _cdsl_schema_path(engine)
|
||||
try:
|
||||
validator = _cdsl_validator(str(schema_path), schema_path.stat().st_mtime_ns)
|
||||
except OSError as error:
|
||||
raise RuntimeError("The local CDSL JSON Schema is unavailable or invalid") from error
|
||||
errors = sorted(validator.iter_errors(cdsl), key=lambda error: (list(error.absolute_path), error.message))
|
||||
if not errors:
|
||||
return
|
||||
@@ -88,76 +133,6 @@ def _validate_cdsl_json_schema(cdsl: dict[str, Any], engine: Any) -> None:
|
||||
raise ValueError(f"CDSL schema violation at {location}: {error.message}")
|
||||
|
||||
|
||||
def _legacy_workplane(plane: str, offset: Any) -> dict[str, list[float]] | None:
|
||||
if isinstance(offset, bool) or not isinstance(offset, (int, float)):
|
||||
return None
|
||||
distance = float(offset)
|
||||
definitions = {
|
||||
"XY": ([0.0, 0.0, distance], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]),
|
||||
"XZ": ([0.0, distance, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]),
|
||||
"YZ": ([distance, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]),
|
||||
}
|
||||
definition = definitions.get(plane.upper())
|
||||
if definition is None:
|
||||
return None
|
||||
origin, x_dir, normal = definition
|
||||
return {"origin_mm": origin, "x_dir": x_dir, "normal": normal}
|
||||
|
||||
|
||||
def normalize_cdsl_for_engine(cdsl: dict[str, Any]) -> tuple[dict[str, Any], list[str]]:
|
||||
"""Convert unambiguous legacy LLM aliases into the current CDSL dialect.
|
||||
|
||||
This intentionally does not infer dimensions, selectors, or feature
|
||||
dependencies. Any non-mechanical error remains visible to the validator.
|
||||
"""
|
||||
normalized = copy.deepcopy(cdsl)
|
||||
repairs: list[str] = []
|
||||
geometry = normalized.get("geometry")
|
||||
sketches = geometry.get("sketches") if isinstance(geometry, dict) else None
|
||||
if isinstance(sketches, list):
|
||||
for index, sketch in enumerate(sketches):
|
||||
if not isinstance(sketch, dict):
|
||||
continue
|
||||
if "id" not in sketch and isinstance(sketch.get("sketch_id"), str):
|
||||
sketch["id"] = sketch.pop("sketch_id")
|
||||
repairs.append(f"geometry.sketches[{index}]: sketch_id -> id")
|
||||
|
||||
legacy_plane: Any = sketch.get("plane")
|
||||
legacy_offset: Any = sketch.get("offset_mm", 0)
|
||||
workplane_value = sketch.get("workplane")
|
||||
if isinstance(workplane_value, dict) and "origin_mm" not in workplane_value:
|
||||
legacy_plane = workplane_value.get("plane")
|
||||
legacy_offset = workplane_value.get("offset_mm", 0)
|
||||
elif "workplane" in sketch:
|
||||
continue
|
||||
|
||||
workplane = _legacy_workplane(legacy_plane, legacy_offset) if isinstance(legacy_plane, str) else None
|
||||
if workplane is None:
|
||||
continue
|
||||
sketch["workplane"] = workplane
|
||||
sketch.pop("plane", None)
|
||||
sketch.pop("offset_mm", None)
|
||||
repairs.append(f"geometry.sketches[{index}]: legacy plane/offset_mm -> workplane")
|
||||
|
||||
features = normalized.get("features")
|
||||
if isinstance(features, list):
|
||||
for index, feature in enumerate(features):
|
||||
if not isinstance(feature, dict):
|
||||
continue
|
||||
if "sketch_id" not in feature and isinstance(feature.get("sketch"), str):
|
||||
feature["sketch_id"] = feature.pop("sketch")
|
||||
repairs.append(f"features[{index}]: sketch -> sketch_id")
|
||||
if "depends_on" not in feature:
|
||||
feature["depends_on"] = []
|
||||
repairs.append(f"features[{index}]: added empty depends_on")
|
||||
params = feature.get("params")
|
||||
axis = params.get("axis") if isinstance(params, dict) else None
|
||||
if isinstance(axis, dict) and "origin_mm" not in axis and "point_mm" in axis:
|
||||
axis["origin_mm"] = axis.pop("point_mm")
|
||||
repairs.append(f"features[{index}].params.axis: point_mm -> origin_mm")
|
||||
return normalized, repairs
|
||||
|
||||
|
||||
def validate_cdsl(cdsl: dict[str, Any], engine: Any) -> None:
|
||||
if not isinstance(cdsl, dict):
|
||||
raise ValueError("CDSL must be a JSON object")
|
||||
@@ -490,285 +465,3 @@ def topology_sidecars(
|
||||
for name, point, normal, x_dir, y_dir in definitions
|
||||
]
|
||||
return ({"schema_version": "1.0", "references": references}, {"schema_version": "1.0", "edges": []})
|
||||
|
||||
|
||||
def _set_parameter_value(document: dict[str, Any], path: list[str], value: float) -> None:
|
||||
target: Any = document
|
||||
for index, key in enumerate(path):
|
||||
final = index == len(path) - 1
|
||||
if isinstance(target, list):
|
||||
item_index = int(key)
|
||||
if item_index < 0 or item_index >= len(target):
|
||||
raise ValueError("Parameter path is no longer valid")
|
||||
if final:
|
||||
target[item_index] = value
|
||||
else:
|
||||
target = target[item_index]
|
||||
elif isinstance(target, dict):
|
||||
if key not in target:
|
||||
raise ValueError("Parameter path is no longer valid")
|
||||
if final:
|
||||
target[key] = value
|
||||
else:
|
||||
target = target[key]
|
||||
else:
|
||||
raise ValueError("Parameter path is no longer valid")
|
||||
|
||||
|
||||
def apply_parameter_updates(cdsl: dict[str, Any], values: dict[str, float]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
contract = parameter_contract(cdsl)
|
||||
entries = {str(item.get("id")): item for item in contract["parameters"]}
|
||||
updated = copy.deepcopy(cdsl)
|
||||
for parameter_id, raw_value in values.items():
|
||||
entry = entries.get(parameter_id)
|
||||
value = float(raw_value)
|
||||
if entry is None or not entry.get("editable", False):
|
||||
raise ValueError(f"Unknown editable parameter: {parameter_id}")
|
||||
if not math.isfinite(value):
|
||||
raise ValueError("Parameter values must be finite")
|
||||
minimum, maximum = entry.get("minimum"), entry.get("maximum")
|
||||
if isinstance(minimum, (int, float)) and value < float(minimum):
|
||||
raise ValueError(f"{parameter_id} is below its minimum")
|
||||
if isinstance(maximum, (int, float)) and value > float(maximum):
|
||||
raise ValueError(f"{parameter_id} is above its maximum")
|
||||
path = entry.get("path")
|
||||
if not isinstance(path, list) or not all(isinstance(item, str) for item in path):
|
||||
raise ValueError(f"{parameter_id} has an invalid path")
|
||||
_set_parameter_value(updated, path, value)
|
||||
declared = updated.get("meta", {}).get("editable_parameters")
|
||||
if isinstance(declared, list):
|
||||
for declared_entry in declared:
|
||||
if isinstance(declared_entry, dict) and str(declared_entry.get("id")) == parameter_id:
|
||||
declared_entry["value"] = value
|
||||
return updated, parameter_contract(updated)
|
||||
|
||||
|
||||
def _part_skill_audit(
|
||||
part_skills: dict[str, Any] | None,
|
||||
request: str,
|
||||
generation_assumptions: list[str] | None,
|
||||
) -> dict[str, Any]:
|
||||
"""Normalize the planning audit persisted beside a product revision."""
|
||||
audit = copy.deepcopy(part_skills) if isinstance(part_skills, dict) else {}
|
||||
skills = [item for item in audit.get("skills") or [] if isinstance(item, dict)]
|
||||
skill_ids = [str(item) for item in audit.get("skill_ids") or [] if str(item)]
|
||||
if not skill_ids:
|
||||
skill_ids = [str(item.get("id")) for item in skills if item.get("id")]
|
||||
audit.update({
|
||||
"schema_version": str(audit.get("schema_version") or "1.0"),
|
||||
"request": str(audit.get("request") or request),
|
||||
"structural_intent": str(audit.get("structural_intent") or request),
|
||||
"skill_ids": skill_ids,
|
||||
"skills": skills,
|
||||
"inherited_skill_ids": [str(item) for item in audit.get("inherited_skill_ids") or [] if str(item)],
|
||||
"assumptions": [str(item) for item in generation_assumptions or []],
|
||||
})
|
||||
return audit
|
||||
|
||||
|
||||
def _generation_context(
|
||||
reference_ids: list[str],
|
||||
part_skill_audit: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
skills = [item for item in part_skill_audit.get("skills") or [] if isinstance(item, dict)]
|
||||
return {
|
||||
"cdsl_reference_ids": list(reference_ids),
|
||||
"part_skill_ids": list(part_skill_audit.get("skill_ids") or []),
|
||||
"part_skill_paths": [
|
||||
{
|
||||
"id": str(item.get("id") or ""),
|
||||
"bridge": str(item.get("bridge") or ""),
|
||||
"source": str(item.get("source") or ""),
|
||||
}
|
||||
for item in skills
|
||||
],
|
||||
"generation_assumptions": list(part_skill_audit.get("assumptions") or []),
|
||||
}
|
||||
|
||||
|
||||
def build_revision(
|
||||
*,
|
||||
settings: Settings,
|
||||
store: WorkspaceStore,
|
||||
task_id: str | None,
|
||||
request: str,
|
||||
cdsl: dict[str, Any],
|
||||
reference_ids: list[str],
|
||||
summary: str,
|
||||
parent_revision_id: str | None = None,
|
||||
operation: dict[str, Any] | None = None,
|
||||
input_attachments: list[dict[str, Any]] | None = None,
|
||||
part_skills: dict[str, Any] | None = None,
|
||||
generation_assumptions: list[str] | None = None,
|
||||
repair_attempts: int = 0,
|
||||
verification: dict[str, Any] | None = None,
|
||||
reference_records: list[dict[str, Any]] | None = None,
|
||||
feature_plan: dict[str, Any] | None = None,
|
||||
node_id: str = "",
|
||||
fragment: dict[str, Any] | None = None,
|
||||
branch_id: str = "main",
|
||||
visibility: str = "final",
|
||||
) -> dict[str, Any]:
|
||||
engine = load_engine(settings)
|
||||
task = store.ensure_task(task_id, request)
|
||||
revision_id, revision_dir = store.next_revision(task["task_id"])
|
||||
cdsl_path = revision_dir / "model.cdsl.json"
|
||||
step_path = revision_dir / "model.step"
|
||||
glb_path = revision_dir / "model.glb"
|
||||
report_path = revision_dir / "rebuild-report.json"
|
||||
request_path = revision_dir / "request.json"
|
||||
references_path = revision_dir / "references.json"
|
||||
parameters_path = revision_dir / "parameters.json"
|
||||
selector_path = revision_dir / "model.selector.json"
|
||||
edges_path = revision_dir / "model.edges.json"
|
||||
topology_path = revision_dir / "model.topology.json"
|
||||
part_skills_path = revision_dir / "part-skills.json"
|
||||
quality_path = revision_dir / "quality-report.json"
|
||||
snapshot_manifest_path = revision_dir / "snapshot-manifest.json"
|
||||
fragment_path = revision_dir / "fragment.json"
|
||||
selector_bindings_path = revision_dir / "selector-bindings.json"
|
||||
part_skill_audit = _part_skill_audit(part_skills, request, generation_assumptions)
|
||||
generation_context = _generation_context(reference_ids, part_skill_audit)
|
||||
write_json(request_path, {"request": request, "created_at": now_iso()})
|
||||
if isinstance(feature_plan, dict):
|
||||
store.write_feature_plan(task["task_id"], feature_plan)
|
||||
write_json(references_path, {"reference_ids": reference_ids, "records": [item for item in reference_records or [] if isinstance(item, dict)]})
|
||||
write_json(part_skills_path, part_skill_audit)
|
||||
write_json(snapshot_manifest_path, {
|
||||
"schema_version": "1.0",
|
||||
"status": "unavailable",
|
||||
"reason": "Snapshot runner is not attached to this backend build",
|
||||
"snapshots": [],
|
||||
})
|
||||
def revision_record(status: str, *, error: str = "", engine_name: str = "") -> dict[str, Any]:
|
||||
record = {
|
||||
"revision_id": revision_id,
|
||||
"status": status,
|
||||
"created_at": now_iso(),
|
||||
"request_path": request_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"cdsl_path": cdsl_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"report_path": report_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"parameters_path": parameters_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"topology_path": topology_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"part_skills_path": part_skills_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"quality_path": quality_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"snapshot_manifest_path": snapshot_manifest_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"snapshot_status": "unavailable",
|
||||
"snapshot_paths": [snapshot_manifest_path.relative_to(store.task_dir(task["task_id"])).as_posix()],
|
||||
"part_skill_ids": list(part_skill_audit.get("skill_ids") or []),
|
||||
"generation_assumptions": list(part_skill_audit.get("assumptions") or []),
|
||||
"reference_ids": reference_ids,
|
||||
"summary": summary,
|
||||
"parent_revision_id": parent_revision_id or "",
|
||||
"operation": operation or {},
|
||||
"input_attachments": input_attachments or [],
|
||||
"repair_attempts": max(0, int(repair_attempts)),
|
||||
"branch_id": branch_id or "main",
|
||||
"visibility": visibility if visibility in {"checkpoint", "final", "superseded"} else "checkpoint",
|
||||
"node_id": node_id,
|
||||
"fragment_path": fragment_path.relative_to(store.task_dir(task["task_id"])).as_posix() if fragment else "",
|
||||
"selector_bindings_path": selector_bindings_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
}
|
||||
if status == "success":
|
||||
record.update({
|
||||
"step_path": step_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"glb_path": glb_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"selector_path": selector_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"edges_path": edges_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"topology_path": topology_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
|
||||
"engine": engine_name,
|
||||
})
|
||||
else:
|
||||
record["error"] = error
|
||||
return record
|
||||
|
||||
quality_report: dict[str, Any] | None = None
|
||||
quality_status = ""
|
||||
try:
|
||||
cdsl_copy = copy.deepcopy(cdsl)
|
||||
if not isinstance(cdsl_copy, dict):
|
||||
raise ValueError("CDSL must be a JSON object")
|
||||
cdsl_copy["part_id"] = task["task_id"]
|
||||
meta = cdsl_copy.setdefault("meta", {})
|
||||
if not isinstance(meta, dict):
|
||||
raise ValueError("CDSL meta must be an object when present")
|
||||
if not isinstance(meta.get("editable_parameters"), list) or not meta["editable_parameters"]:
|
||||
meta["editable_parameters"] = _derived_parameters(cdsl_copy)
|
||||
write_json(cdsl_path, cdsl_copy)
|
||||
if fragment is not None:
|
||||
write_json(fragment_path, fragment)
|
||||
write_json(parameters_path, parameter_contract(cdsl_copy))
|
||||
validate_cdsl(cdsl_copy, engine)
|
||||
# Product revisions are semantic CDSL artifacts. Do not route them
|
||||
# through the legacy rebuild entry point, which is allowed to use
|
||||
# compiler_context/translator compatibility fallbacks.
|
||||
engine_result = engine.run_cdsl_only(cdsl_copy, step_path)
|
||||
if engine_result.get("engine") != "cdsl_only" or not step_path.is_file() or step_path.stat().st_size == 0:
|
||||
raise RuntimeError("Engine did not produce a CDSL-only STEP artifact")
|
||||
preview = step_to_glb(step_path, glb_path)
|
||||
snapshot = topology_snapshot(engine_result, task_id=task["task_id"], revision_id=revision_id, preview=preview)
|
||||
write_json(topology_path, snapshot)
|
||||
write_json(selector_bindings_path, selector_bindings(
|
||||
engine_result,
|
||||
node_id=node_id,
|
||||
snapshot_id=str(snapshot.get("snapshot_id") or ""),
|
||||
))
|
||||
selector, edges = topology_sidecars(engine_result, preview, snapshot=snapshot)
|
||||
write_json(selector_path, selector)
|
||||
write_json(edges_path, edges)
|
||||
rules = validate_verification(verification, cdsl_copy)
|
||||
quality_report = evaluate_quality(rules, cdsl_copy, engine_result)
|
||||
quality_report["evaluated_at"] = now_iso()
|
||||
write_json(quality_path, quality_report)
|
||||
quality_status = (
|
||||
"accepted" if rules and quality_report["status"] == "passed"
|
||||
else "built_with_warnings" if quality_report["status"] == "passed"
|
||||
else "needs_repair"
|
||||
)
|
||||
if quality_report["status"] != "passed":
|
||||
raise QualityVerificationError(quality_report)
|
||||
report = {
|
||||
"engine_result": engine_result,
|
||||
"preview": preview,
|
||||
"generation_context": generation_context,
|
||||
"validated_at": now_iso(),
|
||||
}
|
||||
write_json(report_path, report)
|
||||
revision = revision_record("success", engine_name=str(engine_result["engine"]))
|
||||
revision["quality_status"] = quality_status or "accepted"
|
||||
revision["verification_summary"] = {
|
||||
"requested": bool(rules),
|
||||
"blocking_failures": len(quality_report.get("blocking_failures") or []),
|
||||
"warnings": len(quality_report.get("warnings") or []),
|
||||
}
|
||||
except Exception as error:
|
||||
if not isinstance(error, QualityVerificationError):
|
||||
# The failed candidate is retained in the conversation diagnostics,
|
||||
# not as a task revision. Runtime/schema failures must not create
|
||||
# an editable revision that looks like a model version.
|
||||
shutil.rmtree(revision_dir, ignore_errors=True)
|
||||
raise
|
||||
if not cdsl_path.is_file() and isinstance(cdsl, dict):
|
||||
write_json(cdsl_path, copy.deepcopy(cdsl))
|
||||
if quality_report is not None and not quality_path.is_file():
|
||||
write_json(quality_path, quality_report)
|
||||
write_json(report_path, {
|
||||
"error": str(error),
|
||||
"generation_context": generation_context,
|
||||
"quality": quality_report,
|
||||
"validated_at": now_iso(),
|
||||
})
|
||||
revision = revision_record("needs_repair" if quality_report is not None else "failed", error=str(error))
|
||||
revision["quality_status"] = quality_status or ("needs_repair" if quality_report else "failed")
|
||||
if quality_report is not None:
|
||||
revision["verification_summary"] = {
|
||||
"requested": bool(quality_report.get("verification_requested")),
|
||||
"blocking_failures": len(quality_report.get("blocking_failures") or []),
|
||||
"warnings": len(quality_report.get("warnings") or []),
|
||||
}
|
||||
store.update_task(task["task_id"], revision)
|
||||
error.task_id = task["task_id"]
|
||||
error.revision_id = revision_id
|
||||
raise
|
||||
store.update_task(task["task_id"], revision)
|
||||
return {"task_id": task["task_id"], **revision}
|
||||
|
||||
Reference in New Issue
Block a user