fix(inspect): skip a workflow node whose type is a list or object

The overwritten-PromptManager walk now skips such a node, on the server
and in the browser port, instead of marking the whole file malformed.
A prompt that resolves still resolves; the other nodes still feed the
check.
This commit is contained in:
Vito Sansevero
2026-10-22 19:04:38 -07:00
parent e5dba0466b
commit bf30048bb4
7 changed files with 45 additions and 48 deletions
File diff suppressed because one or more lines are too long
+5 -4
View File
@@ -131,11 +131,12 @@ test('committed server answers equal the local evidence', () => {
assert.ok(compared > 280, `compared ${compared}`);
});
test('a list or object workflow node type is malformed evidence, as on the server (#286)', () => {
test('a list or object workflow node type is skipped, as on the server (#286)', () => {
const graph = base({'6': {class_type: 'PromptManager', inputs: {text: 'a fox', clip: ['4', 1]}}});
for (const type of [['PromptManager'], {name: 'PromptManager'}]) {
const evidence = resolver.resolveLocalEvidence({prompt: JSON.stringify(graph), workflow: JSON.stringify({nodes: [{id: 6, type}]})}, semantics);
assert.deepEqual(evidence.resolution, {state: 'unresolved', sampler_id: null, reason: 'Prompt evidence is malformed', reason_code: 'malformed', node: null});
assert.equal(evidence.prompts.negative.message, 'Prompt metadata in this file is malformed.');
const evidence = resolver.resolveLocalEvidence({prompt: JSON.stringify(graph), workflow: JSON.stringify({nodes: [{id: 6, type, widgets_values: ['x']}]})}, semantics);
assert.equal(evidence.resolution.state, 'resolved');
assert.equal(evidence.prompts.positive.status, 'resolved');
assert.equal(evidence.prompts.positive.text, 'a fox');
}
});
+15 -2
View File
@@ -834,8 +834,8 @@ def golden_cases():
_graph(["6", 0], ["7", 0], overwritten),
workflow="{bad",
)
# A list or object node ``type`` is malformed evidence (#286), also inside a
# subgraph and after a node the walk skips; one with an invalid ID is skipped.
# A list or object node ``type`` is skipped like a non-dict node (#286), also
# inside a subgraph; a skipped node never hides the rest of the walk.
for label, kind in (("list", ["PromptManager"]), ("dict", {"a": 1})):
add(
f"workflow-type-{label}",
@@ -856,6 +856,19 @@ def golden_cases():
}
),
)
add(
"workflow-type-skipped-keeps-overwritten",
_graph(["6", 0], ["7", 0], overwritten),
workflow=json.dumps(
{
"nodes": [
workflow["nodes"][0],
{"id": 8, "type": ["PromptManager"]},
workflow["nodes"][1],
]
}
),
)
add(
"workflow-type-without-id",
_graph(["6", 0], ["7", 0], overwritten),
+9 -17
View File
@@ -1643,11 +1643,13 @@ class TestPromptInspector(ImageAPITestCase):
str(path), False, report_ambiguity=True
)
async def test_unhashable_workflow_node_type_answers_malformed(self):
"""A UI workflow node whose ``type`` is a list or dict is malformed (#286).
async def test_unhashable_workflow_node_type_is_skipped(self):
"""A UI workflow node whose ``type`` is a list or dict is skipped (#286).
The R1 check looks each node's ``type`` up among subgraph IDs; an
unhashable ``type`` used to raise there and the route answered 500.
Such a node can be neither a subgraph instance nor a PromptManager
node, so the walk skips it and a prompt that resolves still resolves.
"""
source = graph()
source["1"]["class_type"] = "PromptManager"
@@ -1663,21 +1665,11 @@ class TestPromptInspector(ImageAPITestCase):
)
response, result = await self.inspect()
self.assertEqual(response.status, 200)
self.assertEqual(
result["resolution"],
{
"state": "unresolved",
"sampler_id": None,
"reason": "Prompt evidence is malformed",
"reason_code": "malformed",
"node": None,
},
)
for side in result["prompts"].values():
self.assertEqual(side["status"], "unresolved")
self.assertEqual(
side["message"], "Prompt metadata in this file is malformed."
)
self.assertEqual(result["resolution"]["state"], "resolved")
self.assertEqual(result["resolution"]["sampler_id"], "3")
self.assertEqual(result["prompts"]["positive"]["status"], "resolved")
self.assertEqual(result["prompts"]["positive"]["text"], "portrait")
self.assertEqual(result["prompts"]["negative"]["text"], "blurry")
self.assertNotIn("unhashable", json.dumps(result))
async def test_unhashable_graph_class_type_is_not_a_preview_output(self):