The language
How documents, identities, references, fields, and flow work.
The model
A model contains domain catalogs (lists of shared definitions), workshop notation, and workflows. Catalogs define contexts, actors, ownership, Events, aggregates, and field types. Workflows describe behavior. Chapters and hotspots keep workshop context.
Specification v0.4.0 defines a model as one .em.hcl file or a folder of member files. A folder includes only direct .em.hcl files whose names do not start with a dot. It does not include subfolders.
Each file holds whole top-level blocks and must parse on its own. References resolve across member files without changing ID scopes. Each bounded context and its domain contracts stay in one file. See Folder models.
After validation, downstream tools use a normalized typed model that has derived titles and source-to-target edges.
Identities and derived titles
The identity is add_pet, and its derived display title is Add Pet. Labels use lower_snake_case. Write title only for different wording:
The formatter leaves derived titles implicit and formats one file at a time. A one-file model uses source order, so keep workflows in business-time order.
With two or more member files, model order is bytewise file-name order, then source order. Chapters must stay in one file. Chapter order, then each chapter's workflows list, sets workflow order. Workflows outside chapters follow in model order and produce EM407. A folder with one member file keeps the one-file rules.
Typed references
Quoted values are data. Unquoted traversals are references. For example, to = [event.pet_management.pet_added] resolves a relationship. If you quote that traversal, it becomes an invalid string.
| Target | Form |
|---|---|
| Bounded context | bounded_context.clinic |
| Actor | actor.clinic_staff |
| Team | team.clinic_team |
| System | system.partner |
| Workflow | workflow.add_pet |
| Event | event.pet_management.pet_added |
| Aggregate | aggregate.pet_management.pet |
| Local aggregate, inside owning context | aggregate.pet |
| Field type | field_type.pet_management.pet_id |
| Local field type, inside owning context | field_type.pet_id |
| Local workflow element | command.add_pet |
| Workflow-qualified element, for a hotspot | command.add_pet.add_pet_command |
Bounded contexts own domain contracts
Events, aggregates, and field types belong to a bounded context:
Inside the owning context, references are local, for example aggregate.pet and field_type.pet_id. Outside the context, use the qualified form: aggregate.pet_management.pet, field_type.pet_management.pet_id, event.pet_management.pet_added.
Aggregates
An aggregate is a consistency boundary that a context owns:
Use aggregate_dependencies when behavior spans another consistency boundary.
Reusable field types
Use a field_type when the same business term appears in several contracts:
For a one-off field, write a built-in type inline, for example field "request_id" { type = "UUID" }. The reference lists the available types and flags.
Field shorthand
A plain field can infer the field_type with the same name. A fields list adds reusable fields if you need no per-field override:
The shorthand applies only to field blocks. A field_type must still declare an explicit built-in type.
Lists and structured data
A custom reusable field type can carry nested subfields:
Examples are native HCL literals. The validator checks that examples match the effective type.
One edge, one canonical declaration
Each edge has one canonical spelling.
Rule 1, workflow-local source: write to on the source.
Rule 2, catalog Event source: write from on the receiving workflow element.
Here is what happens if a Command reverses the first rule. The Command puts from on itself instead of to on the Screen:
The validator rejects the reverse form:
To check an edge, find the source. Use to on a workflow source. Use from on the receiver if the source is a catalog Event.