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/initonly, no methods) - Marker interfaces (
ICommand,IQuery,ISoftDeletable, etc.) - Support types (enums like
BackingType, records likeCursorResult)
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.