feat: workflow graph system — blocks 1-20 complete checkpoint

Full graph-based workflow execution engine with:
- WorkflowGraphRuntime with two-phase dispatch (collect → fire Celery tasks)
- Node registry with contract validation, socket types, execution kinds
- WorkflowRuntimeServices: preflight, context resolution, shadow-mode A/B
- WorkflowRouter: CRUD + dispatch/preflight/run-history API endpoints
- OutputTypeContracts: workflow binding, rollout-mode resolution
- Admin router: output-type workflow binding endpoints
- Frontend: complete workflow editor with drag-drop canvas, node inspector,
  module bundles, reference bundles, preflight panel, validation banner,
  authoring guidance, blueprint templates, shadow/rollout gate UI
- Tests: comprehensive coverage for all workflow modules
- Docs: NODE_CONTRACT_AUDIT and VALIDATION_ERROR_INVENTORY

All 20 blocks from NEXT_20_BLOCK_BATCH_PLAN completed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 19:09:54 +02:00
co-authored by Claude Sonnet 4.6
parent c51dd8cd67
commit d2e4934cca
63 changed files with 7035 additions and 2140 deletions
@@ -142,6 +142,44 @@ _THUMBNAIL_TASK_KEYS = {
"transparent_bg",
}
_RESOLVE_TEMPLATE_RUNTIME_KEYS = {
"template_id_override",
"material_library_path",
"require_template",
"disable_materials",
"target_collection",
"material_replace_mode",
"lighting_only_mode",
"shadow_catcher_mode",
"camera_orbit_mode",
}
_MATERIAL_MAP_RUNTIME_KEYS = {
"material_override",
"disable_materials",
}
_AUTO_POPULATE_RUNTIME_KEYS = {
"persist_updates",
"refresh_material_source",
"include_populated_products",
}
_GLB_BBOX_RUNTIME_KEYS = {
"glb_path",
"source_preference",
}
_OUTPUT_SAVE_RUNTIME_KEYS = {
"expected_artifact_role",
"require_upstream_artifact",
}
_NOTIFY_RUNTIME_KEYS = {
"channel",
"require_armed_render",
}
_AUTHORITATIVE_RENDER_SETTING_KEYS = {
"render_engine",
"engine",
@@ -1135,16 +1173,25 @@ def _execute_glb_bbox(
node_params: dict[str, Any],
) -> tuple[dict[str, Any], str, str | None]:
del node
del session, workflow_context
if state.setup is None or state.setup.cad_file is None:
if state.setup is not None and state.setup.status == "skip":
return _serialize_setup_result(state.setup), "skipped", state.setup.reason
raise WorkflowGraphRuntimeError("glb_bbox requires a resolved cad_file")
cad_file: CadFile | None = None
glb_path = node_params.get("glb_path")
step_path = state.setup.cad_file.stored_path
if state.setup is not None and state.setup.cad_file is not None:
if state.setup.status == "skip":
return _serialize_setup_result(state.setup), "skipped", state.setup.reason
cad_file = state.setup.cad_file
else:
cad_file = _resolve_cad_file_context(session, workflow_context, state)
step_path = cad_file.stored_path
glb_path = node_params.get("glb_path")
source_preference = str(node_params.get("source_preference") or "auto")
if glb_path is None and source_preference != "step_only" and state.setup.glb_reuse_path is not None:
if (
glb_path is None
and source_preference != "step_only"
and state.setup is not None
and state.setup.glb_reuse_path is not None
):
glb_path = str(state.setup.glb_reuse_path)
elif glb_path is None and source_preference != "step_only":
step_file = Path(step_path)