diff --git a/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs b/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs
index d0a63da1a274..9ead1b39167c 100644
--- a/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs
+++ b/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs
@@ -278,6 +278,10 @@ internal ProcessStepBuilderTyped(Type stepType, string id, ProcessBuilder? proce
: base(id, processBuilder)
{
Verify.NotNull(stepType);
+ if (!typeof(KernelProcessStep).IsAssignableFrom(stepType))
+ {
+ throw new ArgumentException($"Type '{stepType.FullName}' must be a subclass of KernelProcessStep.", nameof(stepType));
+ }
this._stepType = stepType;
this.FunctionsDict = this.GetFunctionMetadataMap();
diff --git a/dotnet/src/Experimental/Process.Runtime.Dapr.UnitTests/DaprStepInfoTests.cs b/dotnet/src/Experimental/Process.Runtime.Dapr.UnitTests/DaprStepInfoTests.cs
new file mode 100644
index 000000000000..60b81ab16428
--- /dev/null
+++ b/dotnet/src/Experimental/Process.Runtime.Dapr.UnitTests/DaprStepInfoTests.cs
@@ -0,0 +1,86 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using Microsoft.SemanticKernel;
+using Xunit;
+
+namespace SemanticKernel.Process.Dapr.Runtime.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public class DaprStepInfoTests
+{
+ ///
+ /// Tests that ToKernelProcessStepInfo throws when InnerStepDotnetType is not a KernelProcessStep subclass.
+ ///
+ [Fact]
+ public void ToKernelProcessStepInfoThrowsForInvalidStepType()
+ {
+ // Arrange
+ var stepInfo = new DaprStepInfo
+ {
+ InnerStepDotnetType = typeof(string).AssemblyQualifiedName!,
+ State = new KernelProcessStepState("TestStep", version: "v1"),
+ Edges = new Dictionary>()
+ };
+
+ // Act & Assert
+ var ex = Assert.Throws(() => stepInfo.ToKernelProcessStepInfo());
+ Assert.Contains("is not a valid KernelProcessStep type", ex.Message);
+ }
+
+ ///
+ /// Tests that ToKernelProcessStepInfo throws when InnerStepDotnetType cannot be resolved.
+ ///
+ [Fact]
+ public void ToKernelProcessStepInfoThrowsForUnresolvableType()
+ {
+ // Arrange
+ var stepInfo = new DaprStepInfo
+ {
+ InnerStepDotnetType = "NonExistent.Type, NonExistent.Assembly",
+ State = new KernelProcessStepState("TestStep", version: "v1"),
+ Edges = new Dictionary>()
+ };
+
+ // Act & Assert
+ Assert.Throws(() => stepInfo.ToKernelProcessStepInfo());
+ }
+
+ ///
+ /// Tests that ToKernelProcessStepInfo succeeds for a valid KernelProcessStep subclass.
+ ///
+ [Fact]
+ public void ToKernelProcessStepInfoSucceedsForValidStepType()
+ {
+ // Arrange
+ var stepInfo = new DaprStepInfo
+ {
+ InnerStepDotnetType = typeof(ValidTestStep).AssemblyQualifiedName!,
+ State = new KernelProcessStepState("TestStep", version: "v1"),
+ Edges = new Dictionary>()
+ };
+
+ // Act
+ var result = stepInfo.ToKernelProcessStepInfo();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(typeof(ValidTestStep), result.InnerStepType);
+ }
+
+ ///
+ /// A valid test step for type validation testing.
+ ///
+ public sealed class ValidTestStep : KernelProcessStep
+ {
+ ///
+ /// A test function.
+ ///
+ [KernelFunction]
+ public void TestFunction()
+ {
+ }
+ }
+}
diff --git a/dotnet/src/Experimental/Process.Runtime.Dapr.UnitTests/TypeInfoTests.cs b/dotnet/src/Experimental/Process.Runtime.Dapr.UnitTests/TypeInfoTests.cs
new file mode 100644
index 000000000000..24e532b412f7
--- /dev/null
+++ b/dotnet/src/Experimental/Process.Runtime.Dapr.UnitTests/TypeInfoTests.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Process.Serialization;
+using Xunit;
+
+namespace SemanticKernel.Process.Dapr.Runtime.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public class TypeInfoTests
+{
+ ///
+ /// Tests that ConvertValue deserializes a JsonElement to the correct type.
+ ///
+ [Fact]
+ public void ConvertValueDeserializesJsonElement()
+ {
+ // Arrange
+ var json = JsonSerializer.SerializeToElement(42);
+ var typeName = typeof(int).AssemblyQualifiedName;
+
+ // Act
+ var result = TypeInfo.ConvertValue(typeName, json);
+
+ // Assert
+ Assert.Equal(42, result);
+ }
+
+ ///
+ /// Tests that ConvertValue throws when the type name cannot be resolved.
+ ///
+ [Fact]
+ public void ConvertValueThrowsForUnresolvableType()
+ {
+ // Arrange
+ var json = JsonSerializer.SerializeToElement(42);
+
+ // Act & Assert
+ Assert.Throws(() =>
+ TypeInfo.ConvertValue("NonExistent.Type, NonExistent.Assembly", json));
+ }
+
+ ///
+ /// Tests that ConvertValue returns non-JsonElement values unchanged.
+ ///
+ [Fact]
+ public void ConvertValueReturnsNonJsonElementUnchanged()
+ {
+ // Arrange
+ var value = "plain string";
+
+ // Act
+ var result = TypeInfo.ConvertValue(typeof(string).AssemblyQualifiedName, value);
+
+ // Assert
+ Assert.Equal("plain string", result);
+ }
+
+ ///
+ /// Tests that ConvertValue returns null when value is null.
+ ///
+ [Fact]
+ public void ConvertValueReturnsNullWhenValueIsNull()
+ {
+ // Act
+ var result = TypeInfo.ConvertValue(typeof(string).AssemblyQualifiedName, null);
+
+ // Assert
+ Assert.Null(result);
+ }
+}
diff --git a/dotnet/src/Experimental/Process.Runtime.Dapr/Actors/StepActor.cs b/dotnet/src/Experimental/Process.Runtime.Dapr/Actors/StepActor.cs
index 27bda37176fb..ff8545efb73b 100644
--- a/dotnet/src/Experimental/Process.Runtime.Dapr/Actors/StepActor.cs
+++ b/dotnet/src/Experimental/Process.Runtime.Dapr/Actors/StepActor.cs
@@ -102,6 +102,11 @@ private void InitializeStep(DaprStepInfo stepInfo, string? parentProcessId, stri
throw new KernelException($"Could not load the inner step type '{stepInfo.InnerStepDotnetType}'.").Log(this._logger);
}
+ if (!typeof(KernelProcessStep).IsAssignableFrom(this._innerStepType))
+ {
+ throw new KernelException($"Type '{stepInfo.InnerStepDotnetType}' is not a valid KernelProcessStep type.").Log(this._logger);
+ }
+
this.ParentProcessId = parentProcessId;
this._stepInfo = stepInfo;
this._stepState = this._stepInfo.State;
@@ -373,8 +378,18 @@ protected virtual async ValueTask ActivateStepAsync()
if (stepStateType.HasValue)
{
stateType = Type.GetType(stepStateType.Value);
+ if (stateType is null)
+ {
+ throw new KernelException($"Type '{stepStateType.Value}' could not be resolved to a valid KernelProcessStepState type.").Log(this._logger);
+ }
+
+ if (!typeof(KernelProcessStepState).IsAssignableFrom(stateType))
+ {
+ throw new KernelException($"Type '{stepStateType.Value}' is not a valid KernelProcessStepState type.").Log(this._logger);
+ }
+
var stateObjectJson = await this.StateManager.GetStateAsync(ActorStateKeys.StepStateJson).ConfigureAwait(false);
- stateObject = JsonSerializer.Deserialize(stateObjectJson, stateType!) as KernelProcessStepState;
+ stateObject = JsonSerializer.Deserialize(stateObjectJson, stateType) as KernelProcessStepState;
}
else
{
diff --git a/dotnet/src/Experimental/Process.Runtime.Dapr/DaprStepInfo.cs b/dotnet/src/Experimental/Process.Runtime.Dapr/DaprStepInfo.cs
index 15b72d76744d..93a30df4008a 100644
--- a/dotnet/src/Experimental/Process.Runtime.Dapr/DaprStepInfo.cs
+++ b/dotnet/src/Experimental/Process.Runtime.Dapr/DaprStepInfo.cs
@@ -50,6 +50,11 @@ public KernelProcessStepInfo ToKernelProcessStepInfo()
throw new KernelException($"Unable to create inner step type from assembly qualified name `{this.InnerStepDotnetType}`");
}
+ if (!typeof(KernelProcessStep).IsAssignableFrom(innerStepType))
+ {
+ throw new KernelException($"Type '{this.InnerStepDotnetType}' is not a valid KernelProcessStep type.");
+ }
+
return new KernelProcessStepInfo(innerStepType, this.State, this.Edges);
}
diff --git a/dotnet/src/Experimental/Process.Runtime.Dapr/Serialization/TypeInfo.cs b/dotnet/src/Experimental/Process.Runtime.Dapr/Serialization/TypeInfo.cs
index ad64a1e1a53c..9cbca889783b 100644
--- a/dotnet/src/Experimental/Process.Runtime.Dapr/Serialization/TypeInfo.cs
+++ b/dotnet/src/Experimental/Process.Runtime.Dapr/Serialization/TypeInfo.cs
@@ -39,6 +39,11 @@ internal static class TypeInfo
}
Type? valueType = Type.GetType(assemblyQualifiedTypeName);
- return ((JsonElement)value).Deserialize(valueType!);
+ if (valueType is null)
+ {
+ throw new KernelException($"Could not load type '{assemblyQualifiedTypeName}'.");
+ }
+
+ return ((JsonElement)value).Deserialize(valueType);
}
}
diff --git a/dotnet/src/Experimental/Process.UnitTests/Core/ProcessBuilderTests.cs b/dotnet/src/Experimental/Process.UnitTests/Core/ProcessBuilderTests.cs
index 26c2fc9b0e7e..8d8ea820513b 100644
--- a/dotnet/src/Experimental/Process.UnitTests/Core/ProcessBuilderTests.cs
+++ b/dotnet/src/Experimental/Process.UnitTests/Core/ProcessBuilderTests.cs
@@ -167,6 +167,37 @@ public void OnFunctionErrorCreatesEdgeBuilder()
Assert.EndsWith("Global.OnError", edgeBuilder.EventData.EventId);
}
+ ///
+ /// Tests that AddStepFromType(Type) throws ArgumentException for non-KernelProcessStep types.
+ ///
+ [Fact]
+ public void AddStepFromTypeWithInvalidTypeThrowsArgumentException()
+ {
+ // Arrange
+ var processBuilder = new ProcessBuilder(ProcessName);
+
+ // Act & Assert
+ var ex = Assert.Throws(() => processBuilder.AddStepFromType(typeof(string)));
+ Assert.Contains("must be a subclass of KernelProcessStep", ex.Message);
+ }
+
+ ///
+ /// Tests that AddStepFromType(Type) succeeds for valid KernelProcessStep types.
+ ///
+ [Fact]
+ public void AddStepFromTypeWithValidTypeAddsStep()
+ {
+ // Arrange
+ var processBuilder = new ProcessBuilder(ProcessName);
+
+ // Act
+ var stepBuilder = processBuilder.AddStepFromType(typeof(TestStep), StepName);
+
+ // Assert
+ Assert.Single(processBuilder.Steps);
+ Assert.Equal(StepName, stepBuilder.Name);
+ }
+
///
/// A class that represents a step for testing.
///