Skip to content

Compile-Time Safety

Deepstaging ships 69 diagnostics across every module. These aren't suggestions — they're a compile-time safety net that catches structural mistakes, contract violations, and missing configuration before your code ever runs.

Every diagnostic listed here fires during dotnet build. Most errors block compilation. Warnings and info-level hints surface in the IDE and build output so nothing slips through quietly.

Diagnostics reference

For the full module-by-module diagnostic table, see the Diagnostics Reference. This page organizes the same checks by what they protect you from.


Structural Correctness

Source generators emit partial members on your types. If a type isn't declared correctly, the generator can't attach — and you'd get confusing errors downstream instead of a clear message upfront.

These checks enforce the modifiers that source generation requires:

What Required Diagnostic Severity
[EffectsModule] class partial DSEFX01 Error
[EffectsModule] class sealed DSEFX02 Warning
[Runtime] class partial DSRT01 Error
[DispatchModule] class partial DSDSP01 Error
[DispatchModule] class static DSDSP02 Error
[AuthPolicies] class static partial DSDSP08 Error
[ConfigRoot] class partial DSCFG01 Error
[ConfigRoot] class sealed DSCFG02 Warning
[ConfigModule] class partial DSCFG11 Error
[ConfigModule] class static DSCFG12 Error
[DataStore] class partial DSDS01 Error
[DataStore] class static DSDS02 Warning
[StoredEntity] class partial DSDS03 Error
[EventStore] class partial DSES01 Error
[EventStore] class static DSES02 Warning
[EventSourcedAggregate] class partial DSES03 Error
[HttpClient] class partial DSHTTP01 Error
HTTP verb method partial DSHTTP02 Error
[EventQueue] class partial DSEQ01 Error
[EventQueue] class static DSEQ02 Warning
[EventQueueHandler] class static DSEQ03 Error
[IntegrationEventQueue] class partial DSEQ07 Error
[IntegrationEventQueue] class static DSEQ08 Warning
[IntegrationEventHandler] class static DSEQ09 Error
[TypedId] struct partial DSID01 Error
[TypedId] struct readonly DSID02 Warning
[TableEntity] type partial DSAT01 Error
[TableStore] type partial DSAT10 Error
[TableStore] type static DSAT11 Warning
[CommandHandler] method static DSDSP03 Error
[QueryHandler] method static DSDSP04 Error
[JobHandler] method static DSJOB01 Error

Most of these have automated code fixes — your IDE will offer a one-click "Add partial modifier" or "Add sealed modifier" action.


Signature Contracts

Handlers must follow exact signatures so the generated dispatch, serialization, and DI wiring work correctly. These checks catch signature mismatches at compile time instead of letting them fail silently at runtime:

Contract Requirement Diagnostic Severity
[CommandHandler] Must return Eff<RT, T> with ≥1 parameter DSDSP05 Warning
[QueryHandler] Must return Eff<RT, T> with ≥1 parameter DSDSP06 Warning
[JobHandler] Single parameter, returns Eff<RT, Unit> DSJOB02 Error
[AuthPolicy] static bool with single ClaimsPrincipal DSDSP09 Error
ValidatorType Must have static Validate(T)Validation<Seq<FieldError>, T> DSDSP07 Warning

| HTTP verb method | Path must not be empty | DSHTTP04 | Error |

Why this matters

A [CommandHandler] that returns Task<Result> instead of Eff<RT, Result> would compile fine on its own — but the generated dispatcher wouldn't wire it. You'd discover the problem at runtime when the command fails to dispatch. These checks surface that mismatch immediately.


Referential Integrity

When one type references another — a foreign key pointing at an entity, a [Uses] targeting a module, a [ForeignKey<T>] referencing a stored entity — these checks verify the target actually exists and is the right kind:

Reference Requirement Diagnostic Severity
[Uses(typeof(T))] T must be a recognized module DSRT03 Error
[Uses] attribute Class must also have [Runtime] DSRT02 Error
[ForeignKey<T>] T must be a [StoredEntity] DSDS06 Error
[CompositeKey("Prop")] Referenced property must exist DSDS07 Error
[StoredEntity] Must have a [TypedId] property DSDS04 Error
[EventSourcedAggregate] Must have a [StreamId] property DSES06 Error
[StreamId] Must also have [TypedId] DSID03 Error
[StreamId] backing type Must be Guid or String DSID04 Warning
[EffectsModule] Exclude Named method must exist on target DSEFX05 Error
[EffectsModule] IncludeOnly Named method must exist on target DSEFX06 Error
[EffectsModule] duplicate Cannot target the same type twice DSEFX04 Error
[EffectsModule] target Should be an interface (not concrete) DSEFX03 Warning
[EffectsModule] target Must have at least one method DSEFX07 Warning
[EventQueueHandler] Must reference a known [EventQueue] DSEQ05 Error
[EventQueueHandler] Must contain at least one handler DSEQ04 Warning
[IntegrationEventHandler] Must reference a known [IntegrationEventQueue] DSEQ11 Error
[IntegrationEventHandler] Must contain at least one handler DSEQ10 Warning
[IntegrationEventHandler] Event types must have [IntegrationEvent] DSEQ06 Error
[BackgroundJob] Must have a matching [JobHandler] DSJOB03 Warning

Bounded Context Boundaries

Each assembly represents a bounded context. These checks enforce that boundary at the container level:

Constraint Diagnostic Severity
Only one [DataStore] per assembly DSDS05 Error
Only one [EventStore] per assembly DSES04 Error

If you need multiple data stores or event stores, split them into separate projects. This isn't a limitation — it enforces the DDD principle that each bounded context owns its own persistence.


Secret Safety

Configuration properties that look like they hold secrets are flagged automatically, and the toolchain ensures user secrets infrastructure is in place:

Check Diagnostic Severity
Property name matches secret patterns (Password, ApiKey, Token, ConnectionString, etc.) DSCFG05 Warning
[Secret] properties exist but assembly has no <UserSecretsId> DSCFG07 Error
User secrets can be synced via deepstaging.secrets-update.sh DSCFG09 Info

How secret detection works

DSCFG05 uses pattern matching on property names. If a property is named DatabasePassword, StripeApiKey, or JwtToken, the analyzer flags it with a suggestion to add [Secret]. This prevents accidental exposure in appsettings.json — secrets annotated with [Secret] are only bound from user secrets or environment variables, never from checked-in config files.


Configuration Completeness

These checks ensure your configuration layer is fully wired — no orphaned types, no empty sections, no inferrable-name mistakes:

Check Diagnostic Severity
Section name cannot be inferred (no ConfigRoot suffix, no explicit Section) DSCFG03 Error
Exposed type has no public properties DSCFG04 Warning
A [ConfigSection] from a referenced package is available but not exposed DSCFG10 Info

The info-level diagnostics act as discoverability hints — when you add a NuGet package that ships config sections (e.g., Deepstaging.EventStore.Marten), the analyzer tells you the section is available and offers a code fix to wire it in.


Infrastructure Compatibility

When you choose a persistence backend, additional analyzers activate to enforce backend-specific requirements:

PostgreSQL / EF Core

Check Diagnostic Severity
[TypedId] used in a [StoredEntity] must include IdConverters.EfCoreValueConverter DSPG01 Error

Without the EF Core value converter, Entity Framework can't map your strongly-typed ID to a database column. This check fires the moment you add a [TypedId] property to a [StoredEntity], and the code fix adds the converter for you.

Marten (Event Store)

Check Diagnostic Severity
[StreamId] backing type should be Guid (Marten defaults to Guid stream identity) DSMRT01 Warning

Marten's default stream identity is Guid. If you use a String-backed [StreamId], Marten requires explicit StreamIdentity.AsString configuration. This warning reminds you to either switch to Guid or add the configuration.

Azure Table Storage

Check Diagnostic Severity
[TableEntity] must have a [PartitionKey] property DSAT02 Error
[TableEntity] must have a [RowKey] property DSAT03 Error
Property type not natively supported — will be JSON-serialized DSAT04 Info

Azure Table Storage requires explicit partition and row keys. DSAT04 is informational — it lets you know when a property of a complex type will be automatically serialized to JSON, since Azure Table Storage only natively supports primitives.


Orphan Detection

These info-level diagnostics catch declarations that are structurally valid but disconnected — things that compile fine but won't actually do anything:

Check Diagnostic Severity
[StoredEntity] exists but no [DataStore] in the assembly DSDS08 Info
[EventSourcedAggregate] exists but no [EventStore] in the assembly DSES05 Info
An effects module is available but no [Runtime] references it via [Uses] DSRT04 Info

These won't block your build, but they surface work that's half-done — an entity without a store, an aggregate without an event store, a module that nothing uses.


Abstractions Purity

The Deepstaging.Abstractions package (what consumers add to their .csproj) contains only marker attributes and marker interfaces — zero behavior, zero logic, zero reflection. All 89+ files are either:

  • Marker attributes with configuration properties (get/init only, no methods)
  • Marker interfaces (ICommand, IQuery, ISoftDeletable, etc.)
  • Support types (enums like BackingType, records like CursorResult)

All attribute interpretation lives in Deepstaging.Projection (compile-time only, not shipped to consumers). This deliberate split means:

  • The consumer-facing NuGet package is tiny and has no transitive dependencies beyond .NET
  • Attributes never break at runtime because they contain no behavior to break
  • Changing how an attribute is interpreted (in Projection) doesn't require consumers to update their package reference

Summary

Category Errors Warnings Info Total
Structural Correctness 19 7 26
Signature Contracts 4 3 7
Referential Integrity 8 5 13
Bounded Context Boundaries 2 2
Secret Safety 1 1 1 3
Configuration Completeness 1 1 1 3
Infrastructure Compatibility 1 1 1 3
Orphan Detection 3 3
Total 36 18 6 60

Testing diagnostics

The testing analyzer (DSEFX07 on [TestRuntime]) is intentionally excluded from this page — it's a development-time convenience hint, not a production safety check. See Testing for details.

Suppressing diagnostics

Any diagnostic can be suppressed via <NoWarn>, .editorconfig, or #pragma warning disable. See MSBuild Integration for details.