mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-08 20:58:45 +02:00
fix: make sub-workflow resolution tolerant of missing references
Extract TryResolveSubWorkflowDefinition that returns null instead of throwing when a referenced workflow is missing from the library. Use the tolerant variant in all graph-walking helpers that build agent indexes, collect agent nodes, and find sub-workflow nodes. These operations are used for activity UI enrichment and agent identity resolution — they should degrade gracefully rather than crash the turn. The throwing ResolveSubWorkflowDefinition is now only used by WorkflowRunner.CreateNodeRoute, which is the actual execution path and correctly fails with a clear error if a reference is truly missing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -350,7 +350,12 @@ internal static class AgentIdentityResolver
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkflowDefinitionDto subWorkflow = node.ResolveSubWorkflowDefinition(workflowLibrary);
|
WorkflowDefinitionDto? subWorkflow = node.TryResolveSubWorkflowDefinition(workflowLibrary);
|
||||||
|
if (subWorkflow is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
CollectAgentSubworkflowContexts(
|
CollectAgentSubworkflowContexts(
|
||||||
subWorkflow,
|
subWorkflow,
|
||||||
workflowLibrary,
|
workflowLibrary,
|
||||||
|
|||||||
@@ -66,6 +66,15 @@ internal static class WorkflowDefinitionExtensions
|
|||||||
public static WorkflowDefinitionDto ResolveSubWorkflowDefinition(
|
public static WorkflowDefinitionDto ResolveSubWorkflowDefinition(
|
||||||
this WorkflowNodeDto node,
|
this WorkflowNodeDto node,
|
||||||
IReadOnlyDictionary<string, WorkflowDefinitionDto>? workflowLibrary)
|
IReadOnlyDictionary<string, WorkflowDefinitionDto>? workflowLibrary)
|
||||||
|
{
|
||||||
|
return node.TryResolveSubWorkflowDefinition(workflowLibrary)
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
$"Sub-workflow node \"{node.Id}\" references unknown workflow \"{node.Config.WorkflowId}\".");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WorkflowDefinitionDto? TryResolveSubWorkflowDefinition(
|
||||||
|
this WorkflowNodeDto node,
|
||||||
|
IReadOnlyDictionary<string, WorkflowDefinitionDto>? workflowLibrary)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(node);
|
ArgumentNullException.ThrowIfNull(node);
|
||||||
|
|
||||||
@@ -81,8 +90,7 @@ internal static class WorkflowDefinitionExtensions
|
|||||||
return workflow;
|
return workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidOperationException(
|
return null;
|
||||||
$"Sub-workflow node \"{node.Id}\" references unknown workflow \"{node.Config.WorkflowId}\".");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsOrchestrationMode(this WorkflowDefinitionDto workflow, string mode)
|
public static bool IsOrchestrationMode(this WorkflowDefinitionDto workflow, string mode)
|
||||||
@@ -190,8 +198,11 @@ internal static class WorkflowDefinitionExtensions
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkflowDefinitionDto subWorkflow = node.ResolveSubWorkflowDefinition(workflowLibrary);
|
WorkflowDefinitionDto? subWorkflow = node.TryResolveSubWorkflowDefinition(workflowLibrary);
|
||||||
CollectAgentNodes(subWorkflow, workflowLibrary, agentNodes, visitedWorkflowIds, visitedAnonymousWorkflows);
|
if (subWorkflow is not null)
|
||||||
|
{
|
||||||
|
CollectAgentNodes(subWorkflow, workflowLibrary, agentNodes, visitedWorkflowIds, visitedAnonymousWorkflows);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +238,12 @@ internal static class WorkflowDefinitionExtensions
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkflowDefinitionDto subWorkflow = node.ResolveSubWorkflowDefinition(workflowLibrary);
|
WorkflowDefinitionDto? subWorkflow = node.TryResolveSubWorkflowDefinition(workflowLibrary);
|
||||||
|
if (subWorkflow is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
WorkflowNodeDto? match = FindSubWorkflowNode(
|
WorkflowNodeDto? match = FindSubWorkflowNode(
|
||||||
subWorkflow,
|
subWorkflow,
|
||||||
nodeId,
|
nodeId,
|
||||||
|
|||||||
@@ -174,6 +174,23 @@ public sealed class AgentIdentityResolverTests
|
|||||||
Assert.Equal("Inner Review", subworkflow.SubworkflowName);
|
Assert.Equal("Inner Review", subworkflow.SubworkflowName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildAgentSubworkflowIndex_SkipsUnresolvableSubWorkflowReferences()
|
||||||
|
{
|
||||||
|
WorkflowDefinitionDto workflow = CreateWorkflow(
|
||||||
|
"parent-workflow",
|
||||||
|
[
|
||||||
|
CreateAgent("agent-top-level", "Top Level"),
|
||||||
|
CreateSubworkflow("subworkflow-missing", "Missing Pipeline", workflowId: "nonexistent-workflow"),
|
||||||
|
],
|
||||||
|
orchestrationMode: "concurrent");
|
||||||
|
|
||||||
|
IReadOnlyDictionary<string, SubworkflowContext> index =
|
||||||
|
AgentIdentityResolver.BuildAgentSubworkflowIndex(workflow);
|
||||||
|
|
||||||
|
Assert.Empty(index);
|
||||||
|
}
|
||||||
|
|
||||||
private static WorkflowDefinitionDto CreateWorkflow(
|
private static WorkflowDefinitionDto CreateWorkflow(
|
||||||
IReadOnlyList<WorkflowNodeDto> nodes,
|
IReadOnlyList<WorkflowNodeDto> nodes,
|
||||||
string orchestrationMode = "concurrent")
|
string orchestrationMode = "concurrent")
|
||||||
|
|||||||
Reference in New Issue
Block a user