Skip to content

When read model resolution fails

Resolving a read model for a command can fail in a small number of well-defined ways. Each one is a distinct type, and each one is deliberately surfaced as a validation failure (HTTP 400) rather than a server error, because every one of them is caused by the request rather than by the server.

FailureCauseClient sees
UnableToResolveReadModelFromCommandContextThe command carries no usable keyHTTP 400 — “The command is missing the identifier required to load its current state.”
ReadModelDoesNotExistForCommandValid key, but a required (non-nullable) read model does not existHTTP 400 — “The command targets an entity that does not exist.”
ReadModelValidatorRequiresCommandPipelineThe validator ran through MVC model binding, before a command context existedRequest fails
CannotResolveCommandDependency / CannotResolveValidatorDependencyA required non-nullable dependency could not be resolvedDepends on the dependency

In every case the detailed message — which names the read model type — goes to the server log only. The client sees a generic message, so the type never leaks over the wire.

UnableToResolveReadModelFromCommandContext

Section titled “UnableToResolveReadModelFromCommandContext”

The command carried no usable key, so there is nothing to resolve a read model by. With Chronicle that means the resolved identity is EventSourceId.Unspecified; without it, that no rule recognized a key on the command.

// No key: nothing marks one, and the command composes none —
// so there is nothing to resolve a read model by.
[Command]
public record InvalidCommand(string SomeProperty)
{
public SomethingHappened Handle(Customer customer) => new();
}

This is not “the entity does not exist” — it is a command that could never resolve one, for a nullable and a non-nullable parameter alike. Making the parameter nullable does not suppress it.

Fix: give the command a key. What counts as one depends on whether the application has Chronicle:

SetupDeclare the key by
With Chroniclemarking a property with Cratis.Chronicle.Keys.KeyAttribute, using a property whose type converts to EventSourceId (typically a ConceptAs<Guid> with an implicit operator EventSourceId), or implementing ICanProvideEventSourceId — see Resolving EventSourceId
Without Chroniclemarking a property with System.ComponentModel.DataAnnotations.KeyAttribute, or implementing ICanProvideKeyForCommand — see Declaring the key without Chronicle

A command that marks the data annotations attribute in a Chronicle application fails this way while looking correct — ARCCHR0008 reports it at build time.

The command carried a valid key, but no read model exists for it — and the dependency was declared non-nullable, so Arc cannot inject anything.

This is the runtime counterpart of the choice ARC0006 asks you to make. Arc treats it as a rejected command rather than a server fault, because “you asked me to act on an entity that isn’t there” is invalid input.

Fix — pick the one that matches your intent:

  • Absence is a business condition. Make the parameter nullable and write the rule around null:

    public class RemoveContactValidator : CommandValidator<RemoveContact>
    {
    public RemoveContactValidator(Customer? customer) =>
    RuleFor(_ => customer)
    .NotNull()
    .WithMessage("Customer is not registered");
    }

    You get a specific message instead of the generic one, which is almost always the better experience.

  • The projection really is required. Leave it non-nullable — the HTTP 400 is the intended behavior, and nothing needs to change.

If neither fits, the read model may not have caught up yet: it is eventually consistent, so a command issued immediately after the event that creates the projection can arrive first. For an invariant that must hold regardless, use a Chronicle constraint instead of projected state.

A CommandValidator<TCommand> that depends on a read model was constructed through the MVC controller model-validation path. MVC runs validation during model binding — before the command context, and therefore before the event source id, exists. The validator cannot be constructed, so the request fails.

This affects MVC controllers only. Minimal-API command endpoints (the Arc default) and direct ICommandPipeline execution both establish the command context first and work correctly.

Fix: expose the command through a minimal-API command endpoint, or move the read-model based check out of the validator and into the command’s Handle() method.

CannotResolveCommandDependency and CannotResolveValidatorDependency

Section titled “CannotResolveCommandDependency and CannotResolveValidatorDependency”

The general case: Arc needed to invoke Provide(), Handle(), or construct a discoverable validator, and a required non-nullable parameter could not be resolved or resolved to null.

For a registered read model with a valid event source id, Arc classifies the failure as ReadModelDoesNotExistForCommand instead — so if you are seeing these types for a read model parameter, the cause is usually one of:

  • The read model has no Chronicle backing artifact. [ReadModel] alone does not register a type for command-scope injection. Add an IProjectionFor<T>, a model-bound projection, or an IReducerFor<T>. See what makes a read model injectable.
  • The parameter is not a read model at all — an ordinary service that is not registered.

Arc deliberately leaves these as server errors rather than masking them as validation failures, so genuine misconfiguration is not hidden behind an HTTP 400.