Skip to content

ARC0006: Command-scoped read model can be missing

A non-nullable read model parameter makes state a required dependency. Make the parameter nullable when the command handles absence as a normal outcome; retain a required parameter when absence should reject the command. Default severity: Warning.

The analyzer checks:

  • constructors of CommandValidator<TCommand> types,
  • public instance Provide() methods on [Command] types,
  • public instance Handle() methods on [Command] types.

It recognizes types marked [ReadModel] and, when Chronicle is referenced, types used by IProjectionFor<TReadModel> implementations in the current compilation. This compile-time recognition does not register a runtime provider or prove that an instance exists.

This standalone Arc declaration deliberately produces ARC0006. Compile it in a project referencing Cratis.Arc.Core with nullable reference types enabled. It is not a runnable lookup: a real application must configure the read model provider and its key resolution.

#nullable enable
using Cratis.Arc.Commands;
using Cratis.Arc.Commands.ModelBound;
using Cratis.Arc.Queries.ModelBound;
using FluentValidation;
[Command]
public record SubmitOrder(string OrderId)
{
public string Handle() => OrderId;
}
[ReadModel]
public record OrderState(bool Ready);
public class SubmitOrderValidator : CommandValidator<SubmitOrder>
{
public SubmitOrderValidator(OrderState order)
{
RuleFor(_ => order.Ready)
.Equal(true)
.WithMessage("Order must be ready for submission");
}
}

The command has a public handler and the read model is a record, so neither ARC0004 nor ARC0008 obscures the intended warning. The string identifier does not, by itself, configure command-scoped lookup.

Replace the validator above with this nullable version when you want the validator itself to report absence:

public class SubmitOrderValidator : CommandValidator<SubmitOrder>
{
public SubmitOrderValidator(OrderState? order)
{
RuleFor(_ => order)
.NotNull()
.WithMessage("Order does not exist");
When(_ => order is not null, () =>
{
RuleFor(_ => order!.Ready)
.Equal(true)
.WithMessage("Order must be ready for submission");
});
}
}

This is a replacement fragment using the first block’s types and imports. The guard prevents dereferencing an absent instance. For creation commands, absence might instead be the desired condition; choose rules for your use case, not merely to silence the warning.

Retain the non-nullable parameter when missing state should fail before the validator is constructed. Review or narrowly suppress the warning to record that deliberate choice. Nullable annotations do not fix provider registration or guarantee lookup success.

For a registered command-scoped read model provider:

Resolution outcomeCurrent behavior
Usable key, instance absent, nullable parameterReceives null; application logic decides what absence means
Usable key, instance absent, required parameterReadModelDoesNotExistForCommand, a validation failure mapped to HTTP 400
Provider cannot resolve a usable keyUnableToResolveReadModelFromCommandContext, a validation failure mapped to HTTP 400, even for a nullable parameter
Required dependency is not registeredCannotResolveCommandDependency or CannotResolveValidatorDependency, an exception outcome rather than a normal missing-state rejection

An unregistered nullable dependency can receive null without any lookup. Check registration before treating that as evidence that no state exists. The missing-state/key failures carry ValidationResultReason.DependencyUnavailable; testing assertions distinguish them from rules that actually ran.

With the Chronicle integration, an event-source identifier or [Key] selects a projected instance but does not prove it exists. It may never have been created, may have been removed, or may be unavailable during a rebuild. A Chronicle command without a declared identity gets a generated key; that is different from an unusable resolved key.

Arc alone does not require projected state. For Chronicle-backed invariants that must hold under concurrency, decide whether projected state is sufficient or whether you need source-of-truth state instead.