fix: preserve real artifact node definitions
This commit is contained in:
@@ -259,18 +259,19 @@ def _seed_remote_node_defs(
|
|||||||
requirement.output_schema_snapshot,
|
requirement.output_schema_snapshot,
|
||||||
output_fields,
|
output_fields,
|
||||||
)
|
)
|
||||||
|
if name in builder.seeded_node_defs:
|
||||||
|
# A contract explicitly carried by the plan is authoritative even
|
||||||
|
# when the server omitted dependency snapshots for this capability.
|
||||||
|
continue
|
||||||
if not isinstance(requirement.input_schema_snapshot, dict) or not isinstance(
|
if not isinstance(requirement.input_schema_snapshot, dict) or not isinstance(
|
||||||
requirement.output_schema_snapshot, dict
|
requirement.output_schema_snapshot, dict
|
||||||
):
|
):
|
||||||
permissive_names.add(name)
|
permissive_names.add(name)
|
||||||
builder.seeded_node_defs.setdefault(
|
builder.seeded_node_defs[name] = NodeDef(
|
||||||
name,
|
name=name,
|
||||||
NodeDef(
|
input_schema=SchemaRef.model_validate(input_schema),
|
||||||
name=name,
|
output_schema=SchemaRef.model_validate(output_schema),
|
||||||
input_schema=SchemaRef.model_validate(input_schema),
|
outcomes=outcomes_by_node.get(name, ["ok"]),
|
||||||
output_schema=SchemaRef.model_validate(output_schema),
|
|
||||||
outcomes=outcomes_by_node.get(name, ["ok"]),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return permissive_names
|
return permissive_names
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,22 @@ def remote_plan_without_schema_snapshots(version: int = 1) -> dict[str, Any]:
|
|||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def remote_plan_with_real_node_def(version: int = 1) -> dict[str, Any]:
|
||||||
|
payload = remote_plan_without_schema_snapshots(version)
|
||||||
|
payload["plan"]["node_defs"] = [
|
||||||
|
{
|
||||||
|
"name": "app.default.remote",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {"real": {"type": "string"}},
|
||||||
|
},
|
||||||
|
"output_schema": {"type": "object", "properties": {}},
|
||||||
|
"outcomes": ["ok"],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_validate_stops_before_remote_call_when_local_graph_is_invalid() -> None:
|
async def test_validate_stops_before_remote_call_when_local_graph_is_invalid() -> None:
|
||||||
port = FakePort()
|
port = FakePort()
|
||||||
@@ -206,3 +222,29 @@ async def test_snapshotless_remote_node_upgrades_to_real_capability_contract() -
|
|||||||
)
|
)
|
||||||
with pytest.raises(ValueError, match="incompatible duplicate"):
|
with pytest.raises(ValueError, match="incompatible duplicate"):
|
||||||
graph.use(incompatible, id="incompatible", input=[], output=[])
|
graph.use(incompatible, id="incompatible", input=[], output=[])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_real_plan_node_def_remains_authoritative_without_snapshots() -> None:
|
||||||
|
port = FakePort()
|
||||||
|
port.inspect_artifact_result = remote_plan_with_real_node_def(version=1)
|
||||||
|
graph = await App._from_port(cast(WorkflowClientPort, port)).edit_workflow(
|
||||||
|
"report", version=1
|
||||||
|
)
|
||||||
|
incompatible = RemoteCapability(
|
||||||
|
_port=cast(WorkflowClientPort, port),
|
||||||
|
ref=CapabilityRef.parse("app.default.remote"),
|
||||||
|
qualified_name="app.default.remote",
|
||||||
|
description=None,
|
||||||
|
input_schema={"type": "object", "properties": {"other": {"type": "string"}}},
|
||||||
|
output_schema={"type": "object", "properties": {}},
|
||||||
|
outcomes=("ok",),
|
||||||
|
is_async=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="incompatible duplicate"):
|
||||||
|
graph.use(incompatible, id="incompatible", input=[], output=[])
|
||||||
|
|
||||||
|
assert graph.seeded_node_defs["app.default.remote"].input_schema.properties == {
|
||||||
|
"real": {"type": "string"}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user