mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-24 05:28:46 +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;
|
||||
}
|
||||
|
||||
WorkflowDefinitionDto subWorkflow = node.ResolveSubWorkflowDefinition(workflowLibrary);
|
||||
WorkflowDefinitionDto? subWorkflow = node.TryResolveSubWorkflowDefinition(workflowLibrary);
|
||||
if (subWorkflow is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CollectAgentSubworkflowContexts(
|
||||
subWorkflow,
|
||||
workflowLibrary,
|
||||
|
||||
@@ -66,6 +66,15 @@ internal static class WorkflowDefinitionExtensions
|
||||
public static WorkflowDefinitionDto ResolveSubWorkflowDefinition(
|
||||
this WorkflowNodeDto node,
|
||||
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);
|
||||
|
||||
@@ -81,8 +90,7 @@ internal static class WorkflowDefinitionExtensions
|
||||
return workflow;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Sub-workflow node \"{node.Id}\" references unknown workflow \"{node.Config.WorkflowId}\".");
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsOrchestrationMode(this WorkflowDefinitionDto workflow, string mode)
|
||||
@@ -190,8 +198,11 @@ internal static class WorkflowDefinitionExtensions
|
||||
continue;
|
||||
}
|
||||
|
||||
WorkflowDefinitionDto subWorkflow = node.ResolveSubWorkflowDefinition(workflowLibrary);
|
||||
CollectAgentNodes(subWorkflow, workflowLibrary, agentNodes, visitedWorkflowIds, visitedAnonymousWorkflows);
|
||||
WorkflowDefinitionDto? subWorkflow = node.TryResolveSubWorkflowDefinition(workflowLibrary);
|
||||
if (subWorkflow is not null)
|
||||
{
|
||||
CollectAgentNodes(subWorkflow, workflowLibrary, agentNodes, visitedWorkflowIds, visitedAnonymousWorkflows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +238,12 @@ internal static class WorkflowDefinitionExtensions
|
||||
return node;
|
||||
}
|
||||
|
||||
WorkflowDefinitionDto subWorkflow = node.ResolveSubWorkflowDefinition(workflowLibrary);
|
||||
WorkflowDefinitionDto? subWorkflow = node.TryResolveSubWorkflowDefinition(workflowLibrary);
|
||||
if (subWorkflow is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
WorkflowNodeDto? match = FindSubWorkflowNode(
|
||||
subWorkflow,
|
||||
nodeId,
|
||||
|
||||
@@ -174,6 +174,23 @@ public sealed class AgentIdentityResolverTests
|
||||
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(
|
||||
IReadOnlyList<WorkflowNodeDto> nodes,
|
||||
string orchestrationMode = "concurrent")
|
||||
|
||||
Reference in New Issue
Block a user