Skip to content

fix(analyzers): EVTC001 false positive on object payloads, harden type lookups - #581

Merged
alexeyzimarev merged 3 commits into
devfrom
fix/evtc001-false-positive
Aug 21, 2026
Merged

fix(analyzers): EVTC001 false positive on object payloads, harden type lookups#581
alexeyzimarev merged 3 commits into
devfrom
fix/evtc001-false-positive

Conversation

@alexeyzimarev

Copy link
Copy Markdown
Contributor

Closes #535

Problem

The EVTC001 analyzer flagged object itself as an unannotated domain event when a runtime-resolved payload (e.g. IEventReader.ReadEvents()StreamEvent.Payload) was passed to State<T>.When(), breaking builds on the documented replay pattern.

Reproducing it in tests surfaced two more latent defects, both previously hidden by the string-name fallbacks:

  • The functional-service builder lookups (CommandHandlerBuilder, IDefineExecution, ICommandHandlerBuilder, IDefineStoreOrExecution) used metadata names without the arity suffix, so GetTypeByMetadataName always returned null and only the string fallback matched. The symbol comparison also compared constructed generics against unbound definitions.
  • Act/ActAsync lambda arguments surface as IDelegateCreationOperation, which the analyzer didn't match — handler bodies were never traversed, so unannotated events created in ActAsync handlers produced no diagnostic at all.

Changes

  • IsConcreteEvent excludes System.Object: an object-typed value carries a runtime-resolved event type, so there is nothing to annotate at the call site
  • Builder metadata names carry the correct arity and IsFunctionalServiceAct compares ContainingType.OriginalDefinition
  • Act argument matching handles IDelegateCreationOperation (plain and conversion-wrapped)
  • All string-name fallbacks deleted; metadata names centralized in WellKnownTypeNames, resolved via GetTypesByMetadataName to handle the ambiguous-reference case where GetTypeByMetadataName returns null
  • IsAggregate/IsState/IsEventHandler collapsed into a shared DerivesFrom helper

Tests

  • Issue EVTC001 false positive when passing IEventReader.ReadEvents() payload to State<T>.When() #535 replay pattern folded through State<T>.When() must produce no diagnostic for object
  • Unannotated event created in an ActAsync handler must produce a diagnostic (pins the builder lookups behaviorally)
  • Event registered via TypeMap.Instance.AddType<T>() must be suppressed (pins the TypeMapper lookup)
  • Every name in WellKnownTypeNames.All must resolve against the real Eventuous assemblies, so a type rename that misses the analyzer fails CI in the same PR

Full solution builds with zero EVTC001 warnings; analyzer suite 5/5, core tests 29/29.

🤖 Generated with Claude Code

…e lookups

Fixes three defects in the EVTC001 event usage analyzer:

- System.Object is no longer treated as a concrete event type, so
  runtime-resolved payloads (StreamEvent.Payload, IMessageConsumeContext.Message)
  passed to State<T>.When() no longer trigger a false positive (#535)
- The functional service builder type lookups used metadata names without
  the arity suffix (CommandHandlerBuilder vs CommandHandlerBuilder`2), so
  they never resolved and symbol comparison silently fell back to string
  matching; call sites also see constructed generics, so the comparison
  now uses OriginalDefinition
- Act/ActAsync lambda arguments surface as IDelegateCreationOperation,
  which the analyzer did not match, so handler bodies were never analyzed
  and unannotated events created in ActAsync handlers were missed

All string-name fallbacks are deleted. Metadata names now live in
WellKnownTypeNames as the single source of truth, resolved through
GetTypesByMetadataName to tolerate ambiguous references, and pinned by
tests that resolve every name against the real Eventuous assemblies so
a type rename fails CI in the same change.

Closes #535

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Fix EVTC001 false positives on object payloads; harden analyzer type resolution

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Prevent EVTC001 from flagging runtime-resolved object payloads passed to State.When().
• Replace string-based fallbacks with centralized, test-pinned well-known metadata type names.
• Ensure functional-service Act/ActAsync handlers are analyzed by handling delegate creation
 operations.
Diagram

graph TD
  A["EventUsageAnalyzer"] --> B["KnownTypeSymbols cache"] --> C["Roslyn Compilation"]
  B --> D["WellKnownTypeNames"]
  A --> E["Analyzer tests"] --> F["Analyzed.cs fixtures"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep string-name fallbacks for symbol matching
  • ➕ More tolerant of missing references in unusual compilation contexts
  • ➕ Less dependency on metadata-name accuracy
  • ➖ Silent false negatives/positives when metadata lookup fails
  • ➖ Not refactoring-safe; renames can degrade analyzer correctness without CI signal
  • ➖ Harder to reason about and test deterministically
2. Multi-target analyzers to reference runtime assemblies directly
  • ➕ Eliminates fragile metadata-name strings
  • ➕ Simplifies symbol resolution and reduces ambiguous lookup edge cases
  • ➖ Increases packaging/build complexity (analyzer target frameworks vs runtime TFMs)
  • ➖ May not be viable if runtime assemblies don’t support analyzer target TFMs

Recommendation: Proceed with the current approach: centralized metadata names + symbol-only matching + explicit tests that pin both behavioral outcomes and metadata-name resolvability. This keeps the analyzer deterministic and refactoring-safe while avoiding the packaging complexity of multi-targeting to reference runtime assemblies.

Files changed (5) +210 / -124

Bug fix (1) +68 / -118
EventUsageAnalyzer.csFix EVTC001 matching: robust type resolution, Act lambda traversal, and object exclusion +68/-118

Fix EVTC001 matching: robust type resolution, Act lambda traversal, and object exclusion

• Replaces string fallbacks with symbol-only comparisons using resolved well-known symbols, including a helper that handles ambiguous GetTypeByMetadataName cases. Extends Act/ActAsync argument detection to include delegate creation operations and excludes System.Object from concrete-event detection to avoid false positives on runtime-resolved payloads.

src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs

Refactor (1) +37 / -0
WellKnownTypeNames.csAdd centralized metadata-name catalog for analyzer type lookups +37/-0

Add centralized metadata-name catalog for analyzer type lookups

• Introduces WellKnownTypeNames as the single source of truth for fully-qualified metadata names (including correct generic arity) and exposes an All list for test pinning against referenced Eventuous assemblies.

src/Core/gen/Eventuous.Shared.Generators/WellKnownTypeNames.cs

Tests (2) +102 / -1
Analyzed.csExpand analyzer fixtures for replay, typemap registration, and functional ActAsync +44/-0

Expand analyzer fixtures for replay, typemap registration, and functional ActAsync

• Adds representative source patterns: replaying object-typed payloads through State.When, explicit TypeMap registration to suppress diagnostics, and a functional service using ActAsync that creates an unannotated event to ensure handler bodies are analyzed.

src/Core/test/Eventuous.Tests.Shared.Analyzers/Analyzed.cs

Analyzer_Ev001_Tests.csAdd regression tests for EVTC001 edge cases and pin WellKnownTypeNames resolution +58/-1

Add regression tests for EVTC001 edge cases and pin WellKnownTypeNames resolution

• Adds tests preventing object payload false positives, ensuring ActAsync-created events are diagnosed, and ensuring explicitly TypeMap-registered events are suppressed. Also adds a test that every metadata name in WellKnownTypeNames resolves against the current referenced assemblies, and extends compilation references to include needed runtime types.

src/Core/test/Eventuous.Tests.Shared.Analyzers/Analyzer_Ev001_Tests.cs

Documentation (1) +3 / -5
Constants.csClarify lookup-constant intent and point to WellKnownTypeNames +3/-5

Clarify lookup-constant intent and point to WellKnownTypeNames

• Updates XML documentation to describe the analyzer’s reliance on metadata-name lookups and the new centralized WellKnownTypeNames list pinned by tests.

src/Core/gen/Eventuous.Shared.Generators/Constants.cs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1d11f2545

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +180 to +182
IDelegateCreationOperation { Target: IAnonymousFunctionOperation anon } => anon,
IConversionOperation { Operand: IAnonymousFunctionOperation anon } => anon,
IConversionOperation { Operand: IDelegateCreationOperation { Target: IAnonymousFunctionOperation anon } } => anon,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict Act analysis to emitted event values

When an Act/ActAsync lambda builds its result with an intermediate allocation, such as var events = new List<object>();, this newly enabled traversal sends the entire lambda body to a scanner that treats every class or struct construction except object as a domain event. It therefore emits EVTC001 for List<object> and other helper objects that are never events; trace only values returned or added to NewEvents instead of scanning every creation in the body.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 88c22b8. IsConcreteEvent now excludes the entire System namespace, so List<object> and other BCL allocations inside handlers are no longer flagged (pinned by Should_not_warn_for_helper_allocations_in_act_handlers). User-defined non-event types constructed inside a handler (e.g. value objects) can still be flagged — full emitted-value tracing would trade that for false negatives on indirect flows like var e = new MyEvent(); return [e];, which this analyzer intentionally keeps catching.

_ => null
};

if (lambda != null) AnalyzeDelegateBodyForEventCreations(ctx, lambda.Body, knownTypes);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid reporting synchronous Act creations twice

For a synchronous handler such as Act(_ => new object[] { new UnannotatedEvent() }), this traversal reports the event construction, and the separately registered OperationKind.ObjectCreation callback analyzes the same node again. Because the lambda symbol returns NewEvents as IEnumerable<object>, ReturnsNewEvents accepts it and emits an identical diagnostic at the same location; reserve that fallback for method groups or otherwise deduplicate these reports.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 88c22b8. The object-creation safety net now skips creations located inside a lambda passed to a functional-service Act/ActAsync invocation — the invocation traversal owns those, so the safety net remains for method groups only. Pinned by Should_report_sync_act_event_exactly_once, which failed with 2 identical diagnostics before the change.

Comment on lines +70 to +77
foreach (var candidate in compilation.GetTypesByMetadataName(metadataName)) {
if (candidate.DeclaredAccessibility != Accessibility.Public
&& !SymbolEqualityComparer.Default.Equals(candidate.ContainingAssembly, compilation.Assembly)) continue;

if (best != null) return null;

best = candidate;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the explicit loop: the logic is 'return null when a second accessible candidate appears', and moving the filter into .Where(...) still leaves the two-match early exit in the loop body, so it doesn't simplify anything — it just splits one condition across two places.

Comment on lines +57 to +59
foreach (var payload in payloads) {
if (payload != null) state = state.When(payload);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping as is: this file is analyzed fixture source, and the loop deliberately mirrors the exact reproduction from #535 (if (evt.Payload != null) state = state.When(evt.Payload)). Rewriting it with LINQ would change the operation shapes the analyzer sees and diverge from the reported pattern the test exists to pin.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Registered events may warn 🐞 Bug ≡ Correctness
Description
If TypeMapper cannot be uniquely resolved (e.g., multiple accessible candidates),
KnownTypeSymbols.TypeMapper becomes null and GetExplicitRegistrations returns an empty set, so
IsExplicitlyRegistered will never suppress EVTC001 for TypeMap.Instance.AddType registrations. This
can reintroduce false positives for explicitly-registered events in projects with ambiguous
references.
Code

src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[R70-75]

+            foreach (var candidate in compilation.GetTypesByMetadataName(metadataName)) {
+                if (candidate.DeclaredAccessibility != Accessibility.Public
+                 && !SymbolEqualityComparer.Default.Equals(candidate.ContainingAssembly, compilation.Assembly)) continue;
+
+                if (best != null) return null;
+
Evidence
The analyzer’s suppression path now hard-depends on knownTypes.TypeMapper being non-null; but
GetBestTypeByMetadataName explicitly returns null when more than one accessible candidate exists,
which makes GetExplicitRegistrations return an empty set and IsExplicitlyRegistered always
false. That directly causes explicitly registered event types to be treated as unregistered for
diagnostic suppression.

src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[61-80]
src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[83-112]
src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[117-165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`GetBestTypeByMetadataName` intentionally returns `null` when more than one accessible candidate exists. For `TypeMapper`, that cascades into `GetExplicitRegistrations` returning an empty set, which disables the explicit-registration suppression and can produce EVTC001 for events that are actually registered via `TypeMap.Instance.AddType(...)`.
## Issue Context
This PR removed all string-name fallbacks and made explicit-registration detection depend on successfully resolving `Eventuous.TypeMapper`. In ambiguous-reference scenarios (or other resolution failures), suppression now breaks.
## Fix Focus Areas
- src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[61-107]
## Suggested fix approach
- Keep the symbol-first approach, but add a narrowly-scoped fallback for explicit registration detection only:
- If `knownTypes.TypeMapper == null`, treat invocations as registrations when:
- method name is `AddType`, and
- containing type metadata name matches `WellKnownTypeNames.TypeMapper` (or containing type name/namespace match), and
- the invocation target is reachable via `TypeMap.Instance` (optional).
- Alternatively: change `GetBestTypeByMetadataName` for `TypeMapper` to select a deterministic candidate instead of returning null when multiple accessible candidates exist (e.g., prefer the one whose containing assembly name matches the Eventuous assembly reference set, or prefer the one that also contains `EventTypeAttribute`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Silent analyzer disablement 🐞 Bug ☼ Reliability
Description
With all string fallbacks removed, any failure to resolve a well-known symbol (e.g.,
State/Aggregate/EventTypeAttribute) disables the corresponding checks entirely, producing false
negatives with no indication that the analyzer is effectively inactive. This makes diagnostics
unreliable in the presence of ambiguous references or partial type availability.
Code

src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[R270-275]

+    static bool DerivesFrom(INamedTypeSymbol? type, INamedTypeSymbol? baseDefinition) {
+        if (baseDefinition == null) return false;
-        // Walk base types to check if it derives from Eventuous.State<>
     for (var t = type; t != null; t = t.BaseType) {
-            // Prefer symbol comparison (refactoring-safe)
-            if (knownTypes.State != null) {
-                if (SymbolEqualityComparer.Default.Equals(t.OriginalDefinition, knownTypes.State)) {
-                    return true;
-                }
-            }
-            else {
-                // Fallback to string comparison
-                if (t is { Name: "State", Arity: 1 } && t.ContainingNamespace.ToDisplayString() == BaseNamespace) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    static bool IsEventHandler(INamedTypeSymbol? type, KnownTypeSymbols knownTypes) {
-        if (type == null) return false;
-
-        for (var t = type; t != null; t = t.BaseType) {
-            if (knownTypes.BaseEventHandler != null) {
-                if (SymbolEqualityComparer.Default.Equals(t.OriginalDefinition, knownTypes.BaseEventHandler)) {
-                    return true;
-                }
-            }
-            else {
-                if (t is { Name: "BaseEventHandler", Arity: 0 } && t.ContainingNamespace?.ToDisplayString() == "Eventuous.Subscriptions") {
-                    return true;
-                }
-            }
+            if (SymbolEqualityComparer.Default.Equals(t.OriginalDefinition, baseDefinition)) return true;
     }
Evidence
The new DerivesFrom returns false when baseDefinition is null, and HasEventTypeAttribute
returns false when knownTypes.EventTypeAttribute is null; both patterns disable checks rather than
failing loudly. Combined with the PR’s removal of all string-name fallbacks, resolution failures now
translate directly into skipped analysis.

src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[46-49]
src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[262-278]
src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[299-301]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The analyzer now exclusively uses resolved symbols and returns `false` when a base definition/attribute symbol is missing. This can silently disable large portions of the analyzer when symbols fail to resolve (including ambiguous-reference scenarios), leading to missing EVTC001 diagnostics without any signal.
## Issue Context
This PR intentionally removed string-name fallbacks and centralized metadata names in `WellKnownTypeNames`. While tests pin the names against Eventuous assemblies, real projects can still hit symbol-resolution failures (e.g., duplicate assemblies/ambiguous definitions).
## Fix Focus Areas
- src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[44-60]
- src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[259-302]
## Suggested fix approach
- Add a low-noise diagnostic or a single compilation-level warning (e.g., `EVTC001A` informational) when critical well-known types fail to resolve (State/Aggregate/EventTypeAttribute/TypeMapper), so users know analysis is degraded.
- Alternatively, restore a *minimal* string-based fallback only for determining State/Aggregate/EventHandler derivation, while keeping attribute checks symbol-based.
- Ensure any added diagnostic is suppressed by default if you don’t want it surfaced in normal builds, but is available for debugging misconfiguration.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +70 to +75
foreach (var candidate in compilation.GetTypesByMetadataName(metadataName)) {
if (candidate.DeclaredAccessibility != Accessibility.Public
&& !SymbolEqualityComparer.Default.Equals(candidate.ContainingAssembly, compilation.Assembly)) continue;

if (best != null) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Registered events may warn 🐞 Bug ≡ Correctness

If TypeMapper cannot be uniquely resolved (e.g., multiple accessible candidates),
KnownTypeSymbols.TypeMapper becomes null and GetExplicitRegistrations returns an empty set, so
IsExplicitlyRegistered will never suppress EVTC001 for TypeMap.Instance.AddType registrations. This
can reintroduce false positives for explicitly-registered events in projects with ambiguous
references.
Agent Prompt
## Issue description
`GetBestTypeByMetadataName` intentionally returns `null` when more than one accessible candidate exists. For `TypeMapper`, that cascades into `GetExplicitRegistrations` returning an empty set, which disables the explicit-registration suppression and can produce EVTC001 for events that are actually registered via `TypeMap.Instance.AddType(...)`.

## Issue Context
This PR removed all string-name fallbacks and made explicit-registration detection depend on successfully resolving `Eventuous.TypeMapper`. In ambiguous-reference scenarios (or other resolution failures), suppression now breaks.

## Fix Focus Areas
- src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs[61-107]

## Suggested fix approach
- Keep the symbol-first approach, but add a narrowly-scoped fallback for explicit registration detection only:
  - If `knownTypes.TypeMapper == null`, treat invocations as registrations when:
    - method name is `AddType`, and
    - containing type metadata name matches `WellKnownTypeNames.TypeMapper` (or containing type name/namespace match), and
    - the invocation target is reachable via `TypeMap.Instance` (optional).
- Alternatively: change `GetBestTypeByMetadataName` for `TypeMapper` to select a deterministic candidate instead of returning null when multiple accessible candidates exist (e.g., prefer the one whose containing assembly name matches the Eventuous assembly reference set, or prefer the one that also contains `EventTypeAttribute`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in f28b6bf, with a stronger invariant than a fallback: if either EventTypeAttribute or TypeMapper fails to resolve, the analyzer registers no actions at all — no diagnostic can be verified without them, so silence beats unverifiable warnings. Note the ambiguity scenario itself (two accessible Eventuous.TypeMapper definitions) means the user's own TypeMap.Instance.AddType calls fail with CS0433, so their build is broken regardless; the ILMerge-style case (one public + internalized copies) already resolves to the public candidate. Pinned by Should_stay_silent_when_event_type_attribute_cannot_be_resolved.

Comment thread src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown

Test Results

   44 files  + 21     44 suites  +21   13m 27s ⏱️ -15s
  554 tests  -  10    554 ✅  -   9  0 💤 ±0  0 ❌  - 1 
1 119 runs  +544  1 119 ✅ +545  0 💤 ±0  0 ❌  - 1 

Results for commit f28b6bf. ± Comparison against base commit a178a37.

This pull request removes 26 and adds 16 tests. Note that renamed tests count towards both.
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(08/21/2026 11:25:08 +00:00)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(08/21/2026 11:25:08)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(e7c7ecc7-b9a8-4dff-8071-39f696a285ed)
Eventuous.Tests.Azure.Storage.Blobs.BlobStorageProjectorTests ‑ AsyncContextAwareHandler_ExistingBlob_ShouldUpdateStateAndContext
Eventuous.Tests.Azure.Storage.Blobs.BlobStorageProjectorTests ‑ AsyncContextAwareHandler_NewBlob_ShouldUseContextAndStoreState
Eventuous.Tests.Azure.Storage.Blobs.BlobStorageProjectorTests ‑ AsyncStateHandler_ExistingBlob_ShouldUpdateState
Eventuous.Tests.Azure.Storage.Blobs.BlobStorageProjectorTests ‑ AsyncStateHandler_NewBlob_ShouldCreateAndStoreState
Eventuous.Tests.Azure.Storage.Blobs.BlobStorageProjectorTests ‑ ConcurrentAdditionOfNewBlob_ShouldReturnFailure
Eventuous.Tests.Azure.Storage.Blobs.BlobStorageProjectorTests ‑ ConcurrentModificationOfExistingBlob_ShouldReturnFailure
Eventuous.Tests.Azure.Storage.Blobs.BlobStorageProjectorTests ‑ CustomBlobId_ExistingBlob_ShouldUpdateWithEventId
…
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(08/21/2026 11:45:44 +00:00)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(08/21/2026 11:45:44)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(515ce62f-cdcc-47dc-822e-dc71ba9b2b5c)
Eventuous.Tests.Shared.Analyzers.Analyzer_Ev001_Tests ‑ Should_not_warn_for_event_registered_via_type_map
Eventuous.Tests.Shared.Analyzers.Analyzer_Ev001_Tests ‑ Should_not_warn_for_helper_allocations_in_act_handlers
Eventuous.Tests.Shared.Analyzers.Analyzer_Ev001_Tests ‑ Should_not_warn_for_object_typed_payload_passed_to_when
Eventuous.Tests.Shared.Analyzers.Analyzer_Ev001_Tests ‑ Should_report_sync_act_event_exactly_once
Eventuous.Tests.Shared.Analyzers.Analyzer_Ev001_Tests ‑ Should_resolve_all_well_known_type_names
Eventuous.Tests.Shared.Analyzers.Analyzer_Ev001_Tests ‑ Should_stay_silent_when_event_type_attribute_cannot_be_resolved
Eventuous.Tests.Shared.Analyzers.Analyzer_Ev001_Tests ‑ Should_warn_for_unannotated_event_created_in_functional_act
…

♻️ This comment has been updated with latest results.

alexeyzimarev and others added 2 commits August 21, 2026 13:37
…handlers

Addresses review findings on the Act/ActAsync analysis:

- Types in the System namespace (List<object>, DateTime, ...) are never
  domain events, so creations of those inside handlers are not flagged
- Creations inside lambdas passed to Act/ActAsync are reported only by
  the invocation traversal; the object-creation safety net skips them,
  so a sync handler no longer produces two identical diagnostics for
  the same creation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nnot resolve

Without these two symbols neither annotations nor explicit registrations
can be verified, so any EVTC001 diagnostic would be an unverifiable false
positive. Skip registering the analysis actions entirely in that case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexeyzimarev
alexeyzimarev merged commit 53bcd44 into dev Aug 21, 2026
16 checks passed
@alexeyzimarev
alexeyzimarev deleted the fix/evtc001-false-positive branch August 21, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EVTC001 false positive when passing IEventReader.ReadEvents() payload to State<T>.When()

1 participant