diff --git a/.github/workflows/dep-version-validation.yml b/.github/workflows/dep-version-validation.yml
new file mode 100644
index 000000000..e7623d0f6
--- /dev/null
+++ b/.github/workflows/dep-version-validation.yml
@@ -0,0 +1,117 @@
+# Validates that dependency version changes do not break the DTFx NuGet packages.
+# Packs DurableTask.Core and DurableTask.Emulator from source, builds a console
+# app using the local packages, runs a HelloCities orchestration with the
+# Emulator backend, and validates orchestration output.
+name: Dependency Version Validation
+
+on:
+ push:
+ branches: [ main ]
+ paths:
+ - 'Directory.Packages.props'
+ - 'tools/DurableTask.props'
+ - 'Test/SmokeTests/**'
+ pull_request:
+ paths:
+ - 'Directory.Packages.props'
+ - 'tools/DurableTask.props'
+ - 'Test/SmokeTests/**'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ dep-validation-smoke-test:
+ name: 'Emulator Orchestration Smoke Test (NuGet Packages)'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ with: { dotnet-version: '8.0.x' }
+
+ - name: Parse DTFx package versions
+ id: version
+ run: |
+ set -e
+ get_version() {
+ local v
+ v=$(dotnet msbuild "$1" -nologo -getProperty:PackageVersion 2>/dev/null | tail -n 1 | tr -d '\r')
+ [ -z "$v" ] && v=$(dotnet msbuild "$1" -nologo -getProperty:Version 2>/dev/null | tail -n 1 | tr -d '\r')
+ [ -z "$v" ] && { echo "FAIL: Cannot resolve version for $1" >&2; exit 1; }
+ echo "$v"
+ }
+ CORE_VERSION=$(get_version "src/DurableTask.Core/DurableTask.Core.csproj")
+ EMULATOR_VERSION=$(get_version "src/DurableTask.Emulator/DurableTask.Emulator.csproj")
+ echo "Core: $CORE_VERSION, Emulator: $EMULATOR_VERSION"
+ echo "core_version=${CORE_VERSION}" >> "$GITHUB_OUTPUT"
+ echo "emulator_version=${EMULATOR_VERSION}" >> "$GITHUB_OUTPUT"
+
+ - name: Pack DTFx packages
+ run: |
+ set -e
+ PKG=Test/SmokeTests/EmulatorSmokeTest/local-packages; mkdir -p "$PKG"
+ dotnet build src/DurableTask.Core/DurableTask.Core.csproj -c Release
+ dotnet build src/DurableTask.Emulator/DurableTask.Emulator.csproj -c Release
+ dotnet pack src/DurableTask.Core/DurableTask.Core.csproj -c Release --no-build --output "$PKG"
+ dotnet pack src/DurableTask.Emulator/DurableTask.Emulator.csproj -c Release --no-build --output "$PKG"
+ ls -la "$PKG"
+
+ - name: Verify packed packages
+ env:
+ CORE_VERSION: ${{ steps.version.outputs.core_version }}
+ EMULATOR_VERSION: ${{ steps.version.outputs.emulator_version }}
+ run: |
+ set -e
+ PKG=Test/SmokeTests/EmulatorSmokeTest/local-packages
+ [ -f "$PKG/Microsoft.Azure.DurableTask.Core.${CORE_VERSION}.nupkg" ] && echo "OK: Core ${CORE_VERSION}" || { echo "FAIL: Core missing"; ls -1 "$PKG"; exit 1; }
+ [ -f "$PKG/Microsoft.Azure.DurableTask.Emulator.${EMULATOR_VERSION}.nupkg" ] && echo "OK: Emulator ${EMULATOR_VERSION}" || { echo "FAIL: Emulator missing"; ls -1 "$PKG"; exit 1; }
+
+ - name: Generate NuGet.config
+ run: |
+ cat > Test/SmokeTests/EmulatorSmokeTest/NuGet.config << 'EOF'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EOF
+
+ - name: Build smoke test app
+ env:
+ CORE_VERSION: ${{ steps.version.outputs.core_version }}
+ EMULATOR_VERSION: ${{ steps.version.outputs.emulator_version }}
+ run: |
+ dotnet build Test/SmokeTests/EmulatorSmokeTest/EmulatorSmokeTest.csproj -c Release \
+ -p:SmokeTestCoreVersion=$CORE_VERSION \
+ -p:SmokeTestEmulatorVersion=$EMULATOR_VERSION
+
+ - name: Verify package versions in assets
+ env:
+ CORE_VERSION: ${{ steps.version.outputs.core_version }}
+ EMULATOR_VERSION: ${{ steps.version.outputs.emulator_version }}
+ run: |
+ set -e
+ ASSETS=Test/SmokeTests/EmulatorSmokeTest/obj/project.assets.json
+ grep -qi "Microsoft.Azure.DurableTask.Core/${CORE_VERSION}" "$ASSETS" && echo "OK: Core ${CORE_VERSION}" || { echo "FAIL: Core not found"; exit 1; }
+ grep -qi "Microsoft.Azure.DurableTask.Emulator/${EMULATOR_VERSION}" "$ASSETS" && echo "OK: Emulator ${EMULATOR_VERSION}" || { echo "FAIL: Emulator not found"; exit 1; }
+
+ - name: Run smoke test
+ env:
+ CORE_VERSION: ${{ steps.version.outputs.core_version }}
+ EMULATOR_VERSION: ${{ steps.version.outputs.emulator_version }}
+ run: |
+ dotnet run --project Test/SmokeTests/EmulatorSmokeTest/EmulatorSmokeTest.csproj \
+ -c Release --no-build \
+ -p:SmokeTestCoreVersion=$CORE_VERSION \
+ -p:SmokeTestEmulatorVersion=$EMULATOR_VERSION
diff --git a/Test/SmokeTests/EmulatorSmokeTest/.gitignore b/Test/SmokeTests/EmulatorSmokeTest/.gitignore
new file mode 100644
index 000000000..0272d976a
--- /dev/null
+++ b/Test/SmokeTests/EmulatorSmokeTest/.gitignore
@@ -0,0 +1,2 @@
+local-packages/
+NuGet.config
diff --git a/Test/SmokeTests/EmulatorSmokeTest/EmulatorSmokeTest.csproj b/Test/SmokeTests/EmulatorSmokeTest/EmulatorSmokeTest.csproj
new file mode 100644
index 000000000..15b18255e
--- /dev/null
+++ b/Test/SmokeTests/EmulatorSmokeTest/EmulatorSmokeTest.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ false
+ false
+ false
+
+ 3.7.1
+ 2.6.0
+
+
+
+
+
+
+
+
+
+
diff --git a/Test/SmokeTests/EmulatorSmokeTest/Program.cs b/Test/SmokeTests/EmulatorSmokeTest/Program.cs
new file mode 100644
index 000000000..3e76ee27f
--- /dev/null
+++ b/Test/SmokeTests/EmulatorSmokeTest/Program.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Reflection;
+using DurableTask.Core;
+using DurableTask.Emulator;
+using Microsoft.Extensions.Logging;
+
+Console.WriteLine("=== DurableTask Emulator Smoke Test ===");
+Console.WriteLine();
+
+using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
+{
+ builder.AddConsole();
+ builder.SetMinimumLevel(LogLevel.Warning);
+});
+
+// Create the in-memory orchestration service
+var orchestrationService = new LocalOrchestrationService();
+
+// Start the worker with our orchestration and activities
+var worker = new TaskHubWorker(orchestrationService, loggerFactory);
+await worker
+ .AddTaskOrchestrations(typeof(HelloCitiesOrchestration))
+ .AddTaskActivities(typeof(SayHelloActivity))
+ .StartAsync();
+
+// Create a client and start the orchestration
+var client = new TaskHubClient(orchestrationService, loggerFactory: loggerFactory);
+OrchestrationInstance instance = await client.CreateOrchestrationInstanceAsync(
+ typeof(HelloCitiesOrchestration), input: null);
+
+Console.WriteLine($"Started orchestration: {instance.InstanceId}");
+
+// Wait for completion
+OrchestrationState result = await client.WaitForOrchestrationAsync(
+ instance, TimeSpan.FromSeconds(30), CancellationToken.None);
+
+Console.WriteLine($"Orchestration status: {result.OrchestrationStatus}");
+Console.WriteLine($"Orchestration output: {result.Output}");
+
+await worker.StopAsync(true);
+
+// Print loaded DTFx assembly versions (after usage to ensure all assemblies are loaded)
+Console.WriteLine();
+Console.WriteLine("Loaded DTFx assembly versions:");
+foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
+{
+ string? name = asm.GetName().Name;
+ if (name != null && name.StartsWith("DurableTask.", StringComparison.OrdinalIgnoreCase))
+ {
+ string? infoVersion = asm
+ .GetCustomAttribute()
+ ?.InformationalVersion;
+ if (infoVersion != null && infoVersion.Contains('+'))
+ {
+ infoVersion = infoVersion[..infoVersion.IndexOf('+')];
+ }
+ Console.WriteLine($" {name} = {infoVersion ?? asm.GetName().Version?.ToString() ?? "unknown"}");
+ }
+}
+
+// Validate result
+if (result.OrchestrationStatus != OrchestrationStatus.Completed)
+{
+ Console.WriteLine("FAIL: Orchestration did not complete successfully.");
+ return 1;
+}
+
+if (result.Output == null ||
+ !result.Output.Contains("Hello, Tokyo!") ||
+ !result.Output.Contains("Hello, London!") ||
+ !result.Output.Contains("Hello, Seattle!"))
+{
+ Console.WriteLine("FAIL: Orchestration output did not contain expected greetings.");
+ return 1;
+}
+
+Console.WriteLine();
+Console.WriteLine("PASS: HelloCities orchestration completed with expected output.");
+return 0;
+
+// ---- Orchestration ----
+
+///
+/// A simple function chaining orchestration that calls SayHello for three cities.
+///
+public class HelloCitiesOrchestration : TaskOrchestration
+{
+ public override async Task RunTask(OrchestrationContext context, object input)
+ {
+ string result = "";
+ result += await context.ScheduleTask(typeof(SayHelloActivity), "Tokyo") + " ";
+ result += await context.ScheduleTask(typeof(SayHelloActivity), "London") + " ";
+ result += await context.ScheduleTask(typeof(SayHelloActivity), "Seattle");
+ return result;
+ }
+}
+
+// ---- Activity ----
+
+///
+/// A simple activity that returns a greeting for the given city name.
+///
+public class SayHelloActivity : TaskActivity
+{
+ protected override string Execute(TaskContext context, string cityName)
+ {
+ return $"Hello, {cityName}!";
+ }
+}