Learn · 05 of 5

Quality: smells, errors, and warnings

The validator checks structure. Business clarity still requires judgment.

The four anti-patterns

These shapes are warnings. Investigate them. The validator does not reject them automatically.

Left chair

1 Command → many Events. Check whether several business capabilities are mixed together.

Right chair

many Events → 1 Read Model. Check whether the Read Model answers more than one question.

Bed

1 Screen → many Commands. Split any second action into another Screen state, even when the intents are related.

Shelf

1 workflow → many scenarios, others → none. Check which workflows still rely on implicit rules.

ShapeValidator warning thresholdMethodology review threshold
BedEM401: more than one Command from a ScreenAny second Command is an anti-pattern.
Left chairEM402: more than one Event from a CommandMore than two Events is a candidate for review.
Right chairEM403: more than one source Event for a Read ModelMore than three Events prompts field-by-field review.
ShelfEM405: at least two scenarios, all in one workflow, with another workflow presentNoticeably more scenarios than neighboring workflows. No fixed count.

Chair and shelf counts start a review, not an automatic split. Inspect the actual rules and fields. Explain justified fan-in or fan-out.

Use plain business language with stakeholders. The shape nicknames are internal shorthand.

What the validator rejects outright

The default profile rejects these problems: invalid HCL and unknown syntax, missing required semantic attributes, wrong literal types, invalid examples, duplicate identities, unresolved references, and references of the wrong kind. It also rejects non-canonical flow directions, invalid pattern-specific scenario structure, non-contiguous chapters in a one-file model, and incorrect Automation/Translation externality. In a folder model, it also rejects chapters in several files and a workflow in two chapters. Diagnostics use stable EMxxx codes. EM0xx codes report structure, EM1xx references, EM2xx flow, EM3xx scenarios, and EM4xx modeling judgment.

What it leaves to human judgment

Some checks need judgment, not hard rejection. These are a Command with no incoming flow, API endpoint, or trigger (EM404), the four anti-patterns above, an open hotspot (EM406), and a workflow in no chapter in a folder model (EM407). Three profiles decide how strict the checks are:

ProfileJudgment diagnostics (EM404, EM406, EM407, the four smells)
workshopInformational. Incomplete discovery stays easy to work with.
valid (default)Warnings
strictEM404, EM406, and EM407 become errors. The four smells stay non-blocking judgment signals.

This Command has no incoming flow, API endpoint, or trigger:

bounded_context "appointments" { event "appointment_added" { } } state_change "schedule_appointment" { command "add_appointment" { to = [event.appointments.appointment_added] } }

Under the default valid profile, this is a warning and the file still validates:

em404-test.em.hcl:6:3: Warning EM404: Every command has a reason: command "add_appointment" has no incoming flow, api_endpoint, or external_trigger. em404-test.em.hcl valid

Under --profile strict, the same diagnostic becomes a hard error, and the validator reports nothing as valid:

em404-test.em.hcl:6:3: Error EM404: Every command has a reason: command "add_appointment" has no incoming flow, api_endpoint, or external_trigger.

A shipped example also produces a right-chair warning:

pet-management-detailed.em.hcl:459:12: Warning EM403: Right chair anti-pattern: readmodel "owner_details" connects to several events; review whether the workflow contains more than one business capability. pet-management-detailed.em.hcl valid

To make an agent-ready model more explicit, use strictness. Do not use permissiveness to hide uncertainty. Put unknown business facts in hotspot blocks, not in silence.

Review information completeness

Walk the full business journey, including cancellation, failure, and compensation. Inspect the current fields, connections, and scenarios rather than relying on memory.

  • Trace each Event field to Command input or a documented domain calculation or generated value.
  • Trace each displayed Read Model field to connected Events and explain derived values.
  • Trace each required Command input to its human or machine issuer. Explain inputs that do not reach resulting business state.
  • Review consumer needs, identity, and ownership across context boundaries. Inspect existing sources before declaring data missing.

A passing validator does not prove that these sources or business rules are complete. Keep genuine unknowns as attached hotspot blocks. Do not invent answers to pass strict validation.

Review structure and business meaning separately. Record findings before making changes. A model with a material open question is not implementation-ready.

Respect workflow ownership

HCL workflow statuses are created, planned, assigned, in_progress, review, blocked, done, and informational.

Under the methodology, edit freely only when status is absent or created. Every other status requires explicit permission for the named workflow and proposed changes.

A field rename across a chain can touch locked work. Do not treat permission for one workflow as permission for its neighbors. Editing done work reopens it.

These edit locks are collaboration rules, not a claim that the HCL validator enforces permission.

The methodology quality review and governance guidance provide the full checklists.

Validity-spotting drill

For each item, decide which label applies: invalid (a validator-level problem), valid but suspicious (a warning or smell), or valid.

A event "order_placed" { title = "Order Placed" }
Answer

Invalid Events are contracts that a bounded context owns. You must declare them inside a bounded_context.

B to = ["event.orders.order_placed"]
Answer

Invalid Relationships are typed, unquoted HCL traversals. This is a string.

C readmodel "order_summary" { title = "Order Summary" }
Answer

Invalid A readmodel block exists only inside a state_view, automation, or translation workflow. Even if you declare it correctly, it needs a concrete question.

D One command -> five semantically unrelated events
Answer

Valid but suspicious This is a left-chair smell. Investigate whether one Command hides several capabilities.

E A translation consumes only event.orders.order_placed, where "orders" is an internal bounded context.
Answer

Invalid A Translation must consume at least one Event from an external bounded context. The validator checks this externality rule directly.

F A State Change scenario contains two "when" blocks.
Answer

Invalid A State Change scenario requires one when block that targets a Command.

Keep going

The full guide includes more exercises, facilitation notes, and a final project.