Skip to content

Commit a86d032

Browse files
mattmilleraiclaude
andcommitted
fix(workflow): omit source_address for an unselected dynamic-combo sub-widget
A promoted dynamic-combo sub-widget (model.max_images) passed the declaration check whenever its selector was a dynamic combo, even when the saved selection does not expose it. Check the sub-widget against the interior node's value-aware widget order before publishing the address. The terminal-node walk is shared with promoted_source_port. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 14aca0c commit a86d032

2 files changed

Lines changed: 51 additions & 9 deletions

File tree

‎comfy_cli/cql/engine.py‎

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4766,28 +4766,44 @@ class the catalog does not know is trusted as saved)."""
47664766
head = widget.split(".", 1)[0]
47674767
if head == widget or not any(p.name == head and p.is_dynamic_combo for p in m.inputs):
47684768
return None
4769+
# …and only the SELECTED option's sub-widgets exist on the node.
4770+
node = _promoted_source_node(sg, path, defs)
4771+
values = _widgets_as_positional(node.get("widgets_values") if node else None, graph, class_type)
4772+
if widget not in graph.widget_order_for_node(class_type, values):
4773+
return None
47694774
return f"{_SUBGRAPH_PATH_SEP.join([instance_id, *path])}.{widget}"
47704775

47714776

4772-
def promoted_source_port(sg: dict, pi: Any, defs: dict[str, dict], graph: Graph) -> tuple[str, Port | None]:
4773-
"""``(interior class, Port)`` of the concrete widget behind promoted input
4774-
``pi`` — the schema a host value is validated against."""
4777+
def _promoted_source_node(sg: dict, path: list[str], defs: dict[str, dict]) -> dict | None:
4778+
"""The terminal interior node at the end of a promoted source ``path``,
4779+
descending into nested subgraph definitions; ``None`` if any hop is missing."""
47754780
from comfy_cli.cql import promoted as _promoted
47764781

4777-
target = _promoted.deepest_source(sg, pi, defs)
4778-
if target is None:
4779-
return "", None
4780-
path, widget = target
47814782
current_sg = sg
47824783
node: dict | None = None
47834784
for seg in path:
47844785
node = _promoted._inner_node(current_sg, seg)
47854786
if node is None:
4786-
return "", None
4787+
return None
47874788
nxt = defs.get(str(node.get("type", "")))
47884789
if nxt is not None:
47894790
current_sg = nxt
4790-
class_type = str(node.get("type", "")) if node else ""
4791+
return node
4792+
4793+
4794+
def promoted_source_port(sg: dict, pi: Any, defs: dict[str, dict], graph: Graph) -> tuple[str, Port | None]:
4795+
"""``(interior class, Port)`` of the concrete widget behind promoted input
4796+
``pi`` — the schema a host value is validated against."""
4797+
from comfy_cli.cql import promoted as _promoted
4798+
4799+
target = _promoted.deepest_source(sg, pi, defs)
4800+
if target is None:
4801+
return "", None
4802+
path, widget = target
4803+
node = _promoted_source_node(sg, path, defs)
4804+
if node is None:
4805+
return "", None
4806+
class_type = str(node.get("type", ""))
47914807
m = graph.node(class_type)
47924808
if m is None:
47934809
return class_type, None

‎tests/comfy_cli/command/test_workflow_slots.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,3 +1223,29 @@ def test_null_interior_node_does_not_crash_the_chain(self, patched_subgraph_grap
12231223
assert env["ok"] is True
12241224
by_addr = {s["address"]: s for s in env["data"]["slots"]}
12251225
assert by_addr["10.value"]["source_address"] == "10/3/7.value"
1226+
1227+
@pytest.mark.parametrize(
1228+
("selection", "widget", "expected"),
1229+
[
1230+
("seedream 5.0 pro", "model.size_preset", "50/1.model.size_preset"),
1231+
# max_images exists only on the lite option: the saved pro
1232+
# selection has no such widget, so no dangling address.
1233+
("seedream 5.0 pro", "model.max_images", None),
1234+
("seedream 5.0 lite", "model.max_images", "50/1.model.max_images"),
1235+
("seedream 5.0 pro", "model.bogus", None),
1236+
],
1237+
)
1238+
def test_dynamic_combo_sub_widget_follows_saved_selection(self, selection, widget, expected):
1239+
from comfy_cli.cql import engine as cql_engine
1240+
from comfy_cli.cql.promoted import PromotedInput
1241+
1242+
inner = _seedream_workflow()["nodes"][0]
1243+
if selection == "seedream 5.0 lite":
1244+
# lite inlines size_preset, width, height, max_images, fail_on_partial.
1245+
inner["widgets_values"] = ["p", selection, "(1K) 1024x1024 (1:1)", 2048, 2048, 3, False, 0, "fixed", False]
1246+
else:
1247+
assert inner["widgets_values"][1] == selection
1248+
pi = PromotedInput(
1249+
name=widget, type="INT", index=0, value_index=0, source_node="1", source_input=widget, source_widget=widget
1250+
)
1251+
assert cql_engine._promoted_source_address("50", {"nodes": [inner]}, pi, {}, _seedream_graph()) == expected

0 commit comments

Comments
 (0)