Skip to content

Code analysis rules

Arc’s Core Roslyn analyzers check command, query, validation, and concept declarations at compile time. They do not require Chronicle. The model-graph generator also reports ARC0030 for composite protected-read keys when Chronicle’s accessor registry is referenced, and ARC0031 for commands whose key properties cannot be generated. The optional Chronicle integration rules have a separate ARCCHR prefix.

These are the active ARC descriptors in the current source, all enabled by default in category Arc. Severity is the default before your project’s analyzer configuration. The release-tracking file currently lists ARC0001–ARC0031 under Unshipped; its Shipped table has no rule entries. This inventory describes the current source, not a claim that every older NuGet version contains every rule.

Rule ID Descriptor title Severity Analyzer
ARC0001 Incorrect Query method signature on ReadModel Error ReadModelAnalyzer
ARC0002 Missing [Command] attribute on command-like type Warning CommandAnalyzer
ARC0003 Handle() must be on [Command] type Error CommandAnalyzer
ARC0004 [Command] type must have public Handle() method Error CommandAnalyzer
ARC0005 Value produced by Provide is not consumed by Handle Warning CommandProvideAnalyzer
ARC0006 Command-scoped read model can be missing Warning InjectedReadModelAnalyzer
ARC0007 Command should be declared as a record Warning ModelBoundRecordAnalyzer
ARC0008 ReadModel should be declared as a record Warning ModelBoundRecordAnalyzer
ARC0009 Concept should be declared as a record Warning ConceptRecordAnalyzer
ARC0010 Command Handle() wraps a synchronous result in a Task Warning CommandHandleTaskWrappingAnalyzer
ARC0011 [Roles] argument should use nameof instead of a string literal Warning RolesLiteralAnalyzer
ARC0012 Arc artifact throws a built-in exception type Warning ArcArtifactBuiltInExceptionAnalyzer
ARC0013 Validator rule dereferences a possibly-null concept member Warning ValidatorConceptDereferenceAnalyzer
ARC0014 Generic query method on ReadModel Error ReadModelAnalyzer
ARC0015 Query parameter converted to a concept in the method body Warning QueryParameterConceptTypeAnalyzer
ARC0016 Invalid command operation method Error CommandOperationAnalyzer
ARC0017 Use CommandOperations for operation batches Error CommandOperationAnalyzer
ARC0018 Operation cannot have a generated invoker Error CommandOperationAnalyzer
ARC0019 [AllowAnonymous] conflicts with [Authorize] or [Roles] on the same declaration Warning AuthorizationAttributeAnalyzer
ARC0020 ASP.NET Core authorization attribute is not enforced without Arc’s ASP.NET Core integration Warning AuthorizationAttributeAnalyzer
ARC0021 Authentication scheme requires the ASP.NET Core host Warning AuthorizationAttributeAnalyzer
ARC0022 Invalid generated value parameter type Error GeneratedValueAnalyzer
ARC0023 Generated value declared outside a command Handle method Error GeneratedValueAnalyzer
ARC0024 Generated value names must be non-empty and unique Error GeneratedValueAnalyzer
ARC0025 Provide supplies a generated value type Warning GeneratedValueAnalyzer
ARC0026 Invalid explicit query declaration Error ReadModelAnalyzer and query metadata generation
ARC0027 Generated value implements both generation contracts Warning GeneratedValueAnalyzer
ARC0028 Move inline generation to a generated parameter Info InlineGeneratedValueAnalyzer
ARC0029 Non-public static method is not exposed as a query Warning NonPublicQueryAnalyzer
ARC0030 Composite key requires accessor registration Info ModelGraphWalkerGenerator
ARC0031 Command key properties are read through reflection Info ModelGraphWalkerGenerator

The individual rule pages contain deliberately invalid diagnostic examples, not runnable application checkpoints. Compile each alternative separately; duplicate domain type names are intentional.

Reports a non-record type marked [Command]. Declare the command as a record and retain its public instance Handle() method. This is a modeling warning, not a claim that the runtime rejects every class-based command.

Reports a non-record type marked [ReadModel]. Prefer a record to represent returned state with value equality. A standalone Arc read model need not be an event-sourced projection.

Reports a non-record class deriving from Cratis.Concepts.ConceptAs<T>, including indirect inheritance. Use a record. Since ConceptAs<T> is itself a record, an ordinary derived class also violates the C# record inheritance rules; this diagnostic can accompany a compiler error.

Reports a public instance Handle() on a [Command] that returns Task or Task<T> and either:

  • is async but has no own await, or
  • returns only Task.FromResult(...) / Task.CompletedTask wrappers.

Return the synchronous value (or void) directly. Forwarding a genuine asynchronous operation is not the same as wrapping a synchronous value. Ordinary response DTOs do not need Chronicle. The editor offers Unwrap to synchronous Handle(); an async example without await may also produce compiler warning CS1998.

Reports direct string-literal arguments to Arc’s [Roles] attribute. Prefer nameof(ApplicationRole.Administrator) when that enum member defines the role’s actual wire name. Do not change an externally assigned role string merely to silence the analyzer.

The editor offers Use nameof for role only when it finds a matching enum member. The current fix searches by member name and inserts an unqualified enum type name; review the selected enum and its namespace, especially when several enums share a member name. It does not prove that your identity provider issues that role.

Reports explicit throw new ... of an exception in System or a System.* namespace from:

  • a [Command] type’s Handle() method,
  • a CommandValidator<T> or ConceptValidator<T> type,
  • a Chronicle IReactor implementation, if the optional integration is present.

Use validation rejection for expected invalid input. For genuinely exceptional failures, use a domain-named exception. Simply renaming an exception does not turn it into a validation result. For expected invalid input, return an explicit validation result rather than throwing an ordinary domain exception.

ARC0013: Concept dereferences in validators

Section titled “ARC0013: Concept dereferences in validators”

Reports a FluentValidation RuleFor selector that dereferences a member of a concept property, such as RuleFor(command => command.Name.Value). Input can contain a null concept even when its declaration is non-nullable. Prefer validating the concept itself and placing its invariant in a ConceptValidator<T>; use a null guard when checking an optional concept’s member.

Current limitation: detection is syntactic after identifying the concept type. It does not analyze a surrounding .When(...), rule ordering, or cascade settings. A correctly guarded member selector can still be reported. Review the guard before applying a narrowly scoped suppression; do not remove required validation to obtain a clean build.

Reports public static generic methods on a [ReadModel] whose return type has an accepted query shape. Methods with a parameter of their own read-model type, nullable or not, are excluded; generic self-parameter helpers are not query candidates. Query invocation cannot close a genuine query’s type parameters. Make the query non-generic, move a generic composition helper off the read model, or opt into explicit query selection and leave the helper unmarked. A valid return type alone does not make a generic method invocable.

Reports string, Guid, or nullable Guid parameters converted to a ConceptAs<T>-derived type inside a public static non-void method on a [ReadModel]. Self-parameter methods are never queries and are ignored. In explicit mode only selected queries are analyzed; unmarked helpers are ignored. The analyzer recognizes conversion operations (including implicit conversions), not every possible manual construction or data-flow pattern.

Declare the parameter as the concept when you want Arc’s query pipeline to validate that concept before invoking the method. Preserve null-aware handling for omitted input. For free-text search whose accepted values intentionally differ from an identifier concept’s rules, review the model or suppress this warning locally with a reason. Direct static calls still bypass the query pipeline.

Reports an ICommandOperation implementation without exactly one valid public instance Execute() or with an invalid optional Compensate(). Both methods must be nongeneric and return void, Task, or ValueTask. Unsupported shapes include async void, value-returning tasks, static or overloaded methods, by-reference arguments, optional service parameters, and service locators. CommandOperationFailure can be requested only by Compensate().

Use the operation declaration contract. Forward service calls directly; do not add a separate executor type or application try/catch merely to satisfy the convention.

Reports a command return shape containing a bare collection of operation values, including arrays and typed enumerables inside supported wrappers. Return CommandOperations instead. The explicit immutable batch distinguishes server execution from an ordinary collection response and supports collection expressions such as [].

See zero-to-many operations. Do not work around the diagnostic by erasing operation types to object; use a meaningful declared return contract.

Reports a concrete operation whose type cannot be referenced by its generated invoker. Use a public or internal nongeneric operation in accessible nongeneric containing types. File-local, private nested, or generic declarations do not provide the supported generated invocation shape. A file-local type cannot be referenced from the separate generated source file.

Runtime validation remains necessary when declarations are loaded without the source generator. Generated operation invokers do not establish NativeAOT support for every other Arc execution path.

Reports [AllowAnonymous] declared on the same class or method as [Authorize] or [Roles]. One declaration that both opens itself to anonymous callers and restricts itself to authenticated or role-holding ones has no defensible reading. Arc refuses to guess: at run time it rejects the call with AmbiguousAuthorizationLevel, whichever attribute families are involved. The analyzer reports the contradiction at build time instead of on the first request. Keep one of the two.

[Command]
[Authorize]
[AllowAnonymous] // ARC0019
public record OpenAccount(string Name)
{
public void Handle() { }
}

Overriding a class across two declarations is a different, supported thing and is not reported: an [Authorize] read model can mark one query method [AllowAnonymous], which is how a protected read model exposes a login-screen query. Both Arc’s and ASP.NET Core’s attributes are recognized, but a declaration using only ASP.NET Core’s attributes on a type that is not an Arc artifact, such as an MVC controller, is left to ASP.NET Core.

ARC0020: ASP.NET Core authorization attributes

Section titled “ARC0020: ASP.NET Core authorization attributes”

Reports [Authorize] or [AllowAnonymous] from Microsoft.AspNetCore.Authorization on a [Command] or [ReadModel] type or method, in a project that does not reference Arc’s ASP.NET Core integration (Cratis.Arc). That integration is what enforces the ASP.NET Core attributes on model-bound artifacts. Without it - on Arc Core’s own HTTP host, for example - nothing reads them, and a command marked with one stays open to every caller.

using Microsoft.AspNetCore.Authorization;
[Command]
[Authorize] // ARC0020 in a project without Cratis.Arc: this command is not protected
public record CloseAccount(string Id)
{
public void Handle() { }
}

Use the Arc attribute of the same name, Cratis.Arc.Authorization.AuthorizeAttribute or AllowAnonymousAttribute, which every Arc host enforces. When a file imports both namespaces, qualify the attribute or alias it. Controllers are not reported; ASP.NET Core MVC enforces its own attributes there.

Arc v18.2.0 through v22.20.0 did not enforce the ASP.NET Core attributes on model-bound artifacts even with Cratis.Arc, because its evaluators resolved Arc’s same-named attributes instead (#2719).

ARC0021: Unevaluated authorization settings

Section titled “ARC0021: Unevaluated authorization settings”

Reports AuthenticationSchemes on an authorization attribute of a [Command] or [ReadModel] type or method when the project does not reference Arc’s ASP.NET Core integration. Native named policies are evaluated asynchronously on either Arc host. Explicit schemes require ASP.NET Core’s authentication service; the standalone Core host fails startup rather than silently admitting a caller.

[Command]
[Authorize(AuthenticationSchemes = "Bearer")] // ARC0021 without Cratis.Arc
public record UpdateProfile(string Name)
{
public void Handle() { }
}

Use ASP.NET Core integration and register the scheme, or omit AuthenticationSchemes and use a host-neutral named policy. Missing or ambiguous policy registrations also fail startup. Controllers are not reported; ASP.NET Core MVC evaluates their policies and schemes independently.

Only these Core diagnostics currently have code-fix providers. The other rules require manual changes.

Rule Editor action Provider
ARC0010 Unwrap to synchronous Handle() UnwrapCommandHandleTaskCodeFixProvider
ARC0011 Use nameof for role UseNameofForRolesCodeFixProvider
ARC0028 Move to a [GeneratedValue] parameter MoveToGeneratedValueCodeFixProvider

ARC0010 and ARC0011 expose Roslyn’s batch Fix All support; ARC0028 deliberately fixes one expression at a time. Review and compile the result, particularly the enum lookup for ARC0011.

The Cratis.Arc.Core NuGet package depends on Cratis.Arc.Core.CodeAnalysis. Install Core normally; no Chronicle package is needed:

Terminal window
dotnet add package Cratis.Arc.Core

The analyzer package places Cratis.Arc.Core.CodeAnalysis.dll and Cratis.Arc.Core.CodeAnalysis.CodeFixes.dll under analyzers/dotnet/cs. Compiler analysis and workspace-based editor fixes live in separate assemblies. Arc Core’s source generators are separate assets; ModelGraphWalkerGenerator supplies ARC0030 for the optional Chronicle accessor registry. Editor support and the installed package version determine which fixes you see.

For source contributors, the inventory is defined by Source/DotNET/Arc.Core.CodeAnalysis/DiagnosticDescriptors.cs, each analyzer’s SupportedDiagnostics, AnalyzerReleases.*.md, and Arc.Core.CodeAnalysis.Package/Arc.Core.CodeAnalysis.Package.csproj. Check all four when adding a rule; a descriptor alone does not demonstrate that a diagnostic runs or is packaged.