fix: workflow blueprints freely editable after creation (preserve_user_graph)

canonicalize_workflow_config was silently rebuilding any workflow with
ui.blueprint or ui.preset == still_graph from the canonical template on
every save, preflight, and read — making blueprint-based workflows
effectively read-only.

Add preserve_user_graph=True flag that skips the rebuild blocks. Only
create_workflow still uses preserve_user_graph=False so blueprints are
correctly expanded at creation time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 11:17:32 +02:00
co-authored by Claude Sonnet 4.6
parent 4ea6c4ee9c
commit 53b19f2ba1
3 changed files with 33 additions and 28 deletions
@@ -512,7 +512,7 @@ def _canonicalize_legacy_custom_config(raw: dict[str, Any]) -> dict[str, Any]:
return canonical
def canonicalize_workflow_config(raw: dict[str, Any]) -> dict[str, Any]:
def canonicalize_workflow_config(raw: dict[str, Any], *, preserve_user_graph: bool = False) -> dict[str, Any]:
if not isinstance(raw, dict):
raise ValueError("Workflow config must be a JSON object")
@@ -524,32 +524,34 @@ def canonicalize_workflow_config(raw: dict[str, Any]) -> dict[str, Any]:
ui = {}
normalized["ui"] = dict(ui)
normalized["ui"].setdefault("execution_mode", "legacy")
preset = normalized["ui"].get("preset")
blueprint = normalized["ui"].get("blueprint")
if preset == "still_graph":
merged_ui = dict(normalized["ui"])
canonical = build_preset_workflow_config(
"still_graph",
_extract_render_params_from_nodes(normalized.get("nodes") or [], StepName.BLENDER_STILL),
)
merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"])
canonical["ui"].update(merged_ui)
return canonical
if not preserve_user_graph:
preset = normalized["ui"].get("preset")
blueprint = normalized["ui"].get("blueprint")
if blueprint in _WORKFLOW_BLUEPRINTS:
merged_ui = dict(normalized["ui"])
canonical = build_workflow_blueprint_config(blueprint)
merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"])
canonical["ui"].update(merged_ui)
return canonical
if preset == "still_graph":
merged_ui = dict(normalized["ui"])
canonical = build_preset_workflow_config(
"still_graph",
_extract_render_params_from_nodes(normalized.get("nodes") or [], StepName.BLENDER_STILL),
)
merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"])
canonical["ui"].update(merged_ui)
return canonical
if blueprint in _WORKFLOW_STARTER_BLUEPRINTS:
merged_ui = dict(normalized["ui"])
canonical = build_starter_workflow_config(_WORKFLOW_STARTER_BLUEPRINTS[blueprint])
merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"])
canonical["ui"].update(merged_ui)
return canonical
if blueprint in _WORKFLOW_BLUEPRINTS:
merged_ui = dict(normalized["ui"])
canonical = build_workflow_blueprint_config(blueprint)
merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"])
canonical["ui"].update(merged_ui)
return canonical
if blueprint in _WORKFLOW_STARTER_BLUEPRINTS:
merged_ui = dict(normalized["ui"])
canonical = build_starter_workflow_config(_WORKFLOW_STARTER_BLUEPRINTS[blueprint])
merged_ui.setdefault("execution_mode", canonical["ui"]["execution_mode"])
canonical["ui"].update(merged_ui)
return canonical
return normalized