Add Roslyn analyzers (DE001-DE004) for Amazon.Lambda.DurableExecution#2443
Open
GarrettBeatty wants to merge 1 commit into
Open
Add Roslyn analyzers (DE001-DE004) for Amazon.Lambda.DurableExecution#2443GarrettBeatty wants to merge 1 commit into
GarrettBeatty wants to merge 1 commit into
Conversation
Ports the JavaScript SDK's ESLint plugin to Roslyn and adds a .NET-specific rule, bundled into the Amazon.Lambda.DurableExecution package so they activate automatically for consumers (analyzers/dotnet/cs). - DE001 (Warning): non-deterministic API (DateTime.Now, Guid.NewGuid, Random, Stopwatch, Environment.TickCount, crypto RNG) used outside a step + code fix. - DE002 (Warning): durable operation invoked inside a step-wrapped delegate via the captured outer IDurableContext. - DE003 (Warning): mutation of a captured outer-scope variable inside a durable-operation delegate. - DE004 (Info): Task.WhenAll/WhenAny over durable tasks; prefer ParallelAsync/MapAsync + code fix. Detection is semantic (matches IDurableContext by symbol), not name-based like the JS plugin. A shared per-compilation symbol cache bails out when the SDK is not referenced, so the analyzers are zero-cost on non-durable projects. 34 unit tests via Microsoft.CodeAnalysis.*.Testing; verified end-to-end against a packed consumer.
philasmar
approved these changes
Jun 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Roslyn analyzer package (
Amazon.Lambda.DurableExecution.Analyzers) implementing DE001–DE004, the .NET counterpart of the JavaScript SDK's@aws/durable-execution-sdk-js-eslint-plugin. This is the P1 follow-up described inDocs/durable-execution-design.md§"Roslyn Analyzers".The analyzer assembly is bundled inside the
Amazon.Lambda.DurableExecutionNuGet package (analyzers/dotnet/cs), so it activates automatically for any project that references the package — no separate package or extra reference. Detection is semantic (durable operations are matched by theAmazon.Lambda.DurableExecution.IDurableContextsymbol), which avoids the method-name false positives the AST-only JS plugin documents.Why this exists
A durable workflow has no persisted program counter. On every invocation — including every replay after a wait, retry, or failure — the handler runs again from the top, and each durable call either replays its checkpointed result or runs fresh. For the cached results to line up with the code, the workflow must run the same operations, in the same order, every time. These analyzers catch the four most common ways that contract gets broken, at author time instead of as a confusing runtime replay failure.
Diagnostics
IDurableContextTask.WhenAll/Task.WhenAnyover durable tasks; preferParallelAsync/MapAsyncDE001 — Non-deterministic call outside a step
Flags
DateTime.Now/UtcNow/Today,DateTimeOffset.Now/UtcNow,Guid.NewGuid(),new Random()/Random.Shared,Stopwatch.GetTimestamp()/StartNew()/Elapsed*,Environment.TickCount/TickCount64,RandomNumberGenerator/RNGCryptoServiceProvider, andPath.GetTempFileName()/GetRandomFileName()when used in workflow code outside a step. The value would differ between the original run and replays. (A seedednew Random(42)is deterministic and is not flagged.)DE002 — Nested durable operation inside a step body
Flags any durable operation (
StepAsync,WaitAsync,ParallelAsync,MapAsync,InvokeAsync,RunInChildContextAsync,CreateCallbackAsync,WaitForCallbackAsync) invoked inside a step-wrapped delegate — aStepAsyncbody, aWaitForCallbackAsyncsubmitter, or aWaitForConditionAsynccheck — by capturing the outerIDurableContext. Step bodies are leaf operations; group durable operations withRunInChildContextAsyncinstead.DE003 — Mutable variable captured and modified inside a durable operation
Flags assignment, compound assignment, or increment/decrement of a variable captured from an outer scope inside a durable-operation delegate. On replay the body is skipped and the cached result returned, so the write never happens and the captured variable holds stale state. Reading a captured variable is safe and is not flagged.
DE004 —
Task.WhenAll/Task.WhenAnyover durable tasksAdvisory (Info).
Task.WhenAll/Task.WhenAnywork correctly with durable tasks — operation IDs are allocated deterministically viaInterlocked.Increment— but they bypass completion policies, concurrency limits, branch naming, and structuredIBatchResultoutput. PreferParallelAsync/MapAsync.validated they diagnostics show up in visual studio
Tests / verification
Microsoft.CodeAnalysis.CSharp.Analyzer/CodeFix.Testing.XUnit1.1.2) covering the JS-rule scenarios plus .NET-specific cases (step-wrapped suppression, child-context legality, shadowing, Parallel branches). All pass.dotnet packverified: analyzer lands inanalyzers/dotnet/cs, notlib/..editorconfigoverride path confirmed.