Skip to content

Linter Rules

esdm lint checks a model in two stages. First, the parser and the resolver make sure the documents are well-formed: valid YAML, a known apiVersion, the shape the schema prescribes, and references that point at declared elements. Only when that stage reports no error do the rules run. A rule looks at the resolved model as a whole and throws when it finds something the schema cannot express: an Event that nothing publishes, a Feature that names a Command the model does not have, two domain types that share a name.

Every finding carries the ID of the rule that threw it, in the form esdm/<category>/<name>, and the address of its entry on this page, so you can follow it straight from the terminal or from the JSON output. This page has one entry per rule over the core schema: the severity it throws at, and what it checks and why. The rules of the extensions live next to the extension they belong to, on Given-When-Then: Linter Rules and Domain Storytelling: Linter Rules.

Severity

A rule throws either an error or a warning, and the severity is fixed per rule. An error means the model is inconsistent: something refers to what does not exist, or two declarations contradict each other. A run with at least one error exits with a non-zero status. A warning points at a gap the model can technically live with, such as an Aggregate without Commands. Warnings do not affect the exit code unless you pass --warnings-as-errors; the CLI reference has the details.

There is no configuration to disable a rule or to change its severity. Every model is held to the same standard, so a finding means the same thing in every project.

Categories

The category is the middle segment of the ID. structure rules throw when the model contradicts itself, for example when an identifiedBy names a field the state does not declare. modeling rules throw when the model is consistent but incomplete or misleading, for example when a Read Model has no Query reading from it.

Structure

esdm/structure/aggregate-identified-by-field

Severity: error

When the identifiedBy of an Aggregate uses source: state, the named field must be a property of the state of the Aggregate. Otherwise the identifier points at a value that does not exist.

esdm/structure/ambiguous-name

Severity: error

Within a Bounded Context, Aggregates, Dynamic Consistency Boundaries, Entities, Value Objects, and Domain Services share one namespace. They become the types of one code module, with nothing composed into their names, so two of them cannot share a name.

esdm/structure/duplicate-translation-language

Severity: error

A term has exactly one translation per language. Two translations into the same language contradict the one-term principle the ubiquitous language rests on.

esdm/structure/dynamic-consistency-boundary-identified-by-field

Severity: error

When an identifiedBy entry of a Dynamic Consistency Boundary uses source: command-payload, the named field must be a property of the data of every Command that triggers the boundary. Otherwise a decision cannot be tied to its instance.

esdm/structure/entity-identified-by-field

Severity: error

When the identifiedBy of an Entity uses source: schema, the named field must be a property of the schema of the Entity. Otherwise the identifier points at a value that does not exist.

esdm/structure/process-manager-correlated-by-field

Severity: error

When the correlatedBy of a Process Manager uses source: event-field, the named field must be a property of the data of every Event the Process Manager consumes. Otherwise an Event cannot be routed to its process instance.

esdm/structure/process-manager-timer-at-field

Severity: error

When a timer of a Process Manager uses the absolute at shape, the named field must be a property of the state of the Process Manager. Otherwise the timer has no point in time to fire at.

esdm/structure/translation-into-own-language

Severity: error

A translation into the language the Bounded Context itself is written in is a second term in that language, which the ubiquitous language does not allow.

Modeling

esdm/modeling/actor-without-type

Severity: warning

Every Actor must declare whether it is human or system; the type decides which other fields make sense on it. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/aggregate-without-commands

Severity: warning

An Aggregate should expose at least one Command. Without one, nothing can drive it from the outside, so it never changes state and never publishes an Event.

esdm/modeling/aggregate-without-events

Severity: warning

An Aggregate should have at least one Event. An Aggregate that records nothing is either unfinished or belongs to a different kind, such as a Read Model or a Value Object.

esdm/modeling/aggregate-without-identified-by

Severity: warning

Every Aggregate must declare how its instances are identified, via identifiedBy. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/aggregate-without-state

Severity: warning

Every Aggregate must declare a state schema. An empty schema (type: object) is fine and says explicitly that the Aggregate has no observable state. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/bounded-context-without-consistency-unit

Severity: warning

A Bounded Context should host at least one Aggregate or Dynamic Consistency Boundary. Without a consistency unit it holds no behavior and is a placeholder.

esdm/modeling/command-without-data

Severity: warning

Every Command must declare a data schema. An empty schema is fine and says explicitly that the Command carries no payload. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/command-without-publishes

Severity: warning

Every Command must publish at least one Event. A Command that publishes nothing expresses an intent without any consequence the model can see. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/consumer-at-least-once-without-idempotency

Severity: warning

An Event Handler, Policy, or Process Manager with deliveryGuarantee: at-least-once must declare an idempotency strategy, because it will see the same Event more than once. The schema already requires this combination; the rule keeps the requirement in place independently of the schema.

esdm/modeling/cross-bounded-context-reference-without-mapping

Severity: warning

When a consumer reaches into more than one Bounded Context, for example a Read Model projecting Events from two contexts or a domain-scoped Policy handling Events across contexts, every pair of contexts it touches should be linked by a Context Mapping. The mapping is where the relationship between the two contexts is made explicit.

esdm/modeling/domain-service-without-functions

Severity: warning

Every Domain Service must declare at least one function; a Domain Service exists to offer operations. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/domain-without-bounded-context

Severity: warning

A Domain should own at least one Bounded Context. Without one, nothing in the model belongs to it and its presence is decorative.

esdm/modeling/dynamic-consistency-boundary-without-consults

Severity: warning

Every Dynamic Consistency Boundary must consult at least one Event; the consulted Events are what its decisions are based on. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/dynamic-consistency-boundary-without-identified-by

Severity: warning

Every Dynamic Consistency Boundary must declare at least one identifiedBy entry that says which Events belong to one decision. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/entity-without-identified-by

Severity: warning

Every Entity must declare how its instances are identified, via identifiedBy; identity is what distinguishes an Entity from a Value Object. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/entity-without-schema

Severity: warning

Every Entity must declare a schema field describing the shape of one instance. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/event-handler-without-handles

Severity: warning

Every Event Handler must declare at least one Event it handles. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/event-handler-without-side-effects

Severity: warning

Every Event Handler must declare at least one side effect; reacting to Events with further state changes instead is the job of a Process Manager. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/event-name-with-aggregate-prefix

Severity: warning

An Event bound to an Aggregate should not repeat the name of the Aggregate at the start of its own name. The scope already conveys the Aggregate, and ESDM composes the full name from Aggregate and Event wherever it is needed, so book-registered on the Aggregate book would read as BookBookRegistered. Events scoped to a Bounded Context are exempt, because no enclosing Aggregate provides the context.

esdm/modeling/event-without-consumer

Severity: warning

An Event should be consumed somewhere: by an Event Handler, a Policy, a Process Manager, a Read Model, or a Dynamic Consistency Boundary. An Event nobody consumes is usually a leftover from an earlier version of the model.

esdm/modeling/event-without-data

Severity: warning

Every Event must declare a data schema. An empty schema is fine and says explicitly that the Event carries no payload beyond the fact that it happened. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/event-without-publisher

Severity: warning

Every Event should be published by at least one Command. An Event without a publisher has no way to come into existence in a running system.

esdm/modeling/external-system-without-direction

Severity: warning

Every External System must declare its direction: inbound, outbound, or bidirectional. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/human-actor-with-backed-by

Severity: warning

A human Actor must not declare backedBy. The field names the External Systems that implement the channel of a system Actor and has no meaning for a person. The schema already forbids the combination; the rule keeps the restriction in place independently of the schema.

esdm/modeling/orphan-actor

Severity: warning

Every Actor should be named in the actors of at least one Command or Query. An Actor nothing references does nothing in the model.

esdm/modeling/orphan-context-mapping

Severity: warning

A Context Mapping between two Bounded Contexts should be backed by an actual reference across that boundary somewhere in the model. Otherwise it documents a relationship nothing in the model uses.

esdm/modeling/orphan-external-system

Severity: warning

Every External System should be referenced somewhere: from the backedBy of an Actor, from an external-call side effect of an Event Handler, or as the endpoint of a Context Mapping. Otherwise it has no role in the model.

esdm/modeling/policy-without-emits

Severity: warning

Every Policy must emit at least one Command; a Policy exists to turn Events into Commands, and a Policy that only observes is an Event Handler. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/policy-without-handles

Severity: warning

Every Policy must declare at least one Event it handles. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/process-manager-without-ends-when

Severity: warning

Every Process Manager must declare at least one termination condition in endsWhen; a process that never ends is a design smell the model should not hide. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/process-manager-without-event-reactions

Severity: warning

A Process Manager whose reactions are all timer-based has no event-driven behavior beyond its starting Event. That is usually a gap: a process that only waits is not coordinating anything.

esdm/modeling/process-manager-without-starts-when

Severity: warning

Every Process Manager must declare at least one starting Event in startsWhen. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/query-without-read-model

Severity: warning

Every Query must name the Read Model it reads from. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/query-without-result

Severity: warning

Every Query must declare a result schema. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/read-model-without-projections

Severity: warning

A Read Model should have at least one projection; the projections are what fill it. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/read-model-without-query

Severity: warning

A Read Model should have at least one Query reading from it. A Read Model nobody reads has no purpose.

esdm/modeling/read-model-without-schema

Severity: warning

Every Read Model must declare a schema field describing what it materializes. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

esdm/modeling/subdomain-without-bounded-context

Severity: warning

A subdomain should list at least one Bounded Context; a subdomain without one names a part of the Domain that nothing in the model fills. The schema already requires this; the rule keeps the requirement in place independently of the schema.

esdm/modeling/value-object-without-schema

Severity: warning

Every Value Object must declare a schema field describing the shape of one instance. The schema already requires the field; the rule keeps the requirement in place independently of the schema.

Pipeline Diagnostics

The parser, the resolver, and the runner report their findings in the same shape as the rules, but they are not rules of the catalog. They throw before any rule runs, and an error from the parser or the resolver stops the rules from running at all: a model that does not parse or resolve cannot be checked for its meaning. Their IDs use the structure category for problems in the model and the system category for problems around it.

esdm/structure/yaml-syntax-error

Severity: error

The file is not valid YAML. The message carries the YAML parser's own description of the problem. Parsing stops at the first syntax error, so fix it and lint again to see what follows.

esdm/structure/unknown-api-version

Severity: error

The document has no apiVersion, or names one this version of ESDM does not know. The message lists the versions it does know. Without a known apiVersion there is no schema to validate against, so the rest of the document is skipped.

esdm/structure/missing-required-field

Severity: error

The document leaves out a field the schema requires, for example an Aggregate without identifiedBy. The message names the field, and the finding points at the object that should hold it.

esdm/structure/unknown-field

Severity: error

The document carries a field the schema does not know at that position. Unknown fields are rejected rather than ignored, because a typo in a field name would otherwise silently drop the field's meaning. A common cause is a field that belongs to a different kind, or one that sits a level too high or too low.

esdm/structure/type-mismatch

Severity: error

A value has a different type than the schema expects, for example a string where a list is required. The message names the expected and the actual type.

esdm/structure/constraint-violation

Severity: error

A value breaks a constraint of the schema that none of the findings above covers: a value outside an enumeration, a name that does not match the kebab-case pattern, a list shorter than its minimum length, a combination of fields the schema forbids. When the value is close to an allowed enumeration entry, the finding suggests it.

esdm/structure/duplicate-name

Severity: error

Two documents of the same kind declare the same name in the same scope, for example two Commands named place on the same Aggregate. The finding points at the second declaration and notes where the first one is. The same name in different scopes is fine; a name shared between kinds is the concern of esdm/structure/ambiguous-name.

esdm/structure/unresolved-reference

Severity: error

A document refers to an element the model does not declare: a Command publishes an Event that does not exist, a scope names an Aggregate its Bounded Context does not have, a Context Mapping names an unknown endpoint. The message names the missing element, and when a declared name is close, the finding suggests it.

esdm/system/rule-panic

Severity: error

A rule crashed while checking the model. This is a defect in ESDM, not in your model. The message names the rule and the reason; please report it together with the model that triggered it. The other rules keep running, so the rest of the output is still valid.

esdm/system/schemas-directory-drift

Severity: error

The project's local schemas/ directory, created by esdm add-schema for editor support, does not match the schemas embedded in this version of ESDM. The linter stops before reading any model file, because its results would disagree with what your editor validates. Run esdm update-schema to refresh the directory, or update ESDM if the directory holds the newer revision.