cratis-event-model-diagram · diff
git:20261012.ae9cd35 to git:20261013.dab149a
1 added, 1 removed. Audit A to A.
---
name: cratis-event-model-diagram
description: Create and maintain Mermaid eventmodeling diagrams for a Cratis module or feature, and keep them in sync with their authority (the accepted `.play` model where one covers the scope, otherwise the code). Use when adding, renaming, moving, or deleting a module, feature, behavior, command, event, read model, automation, translation, or cross-module flow. Do not use to decide the event vocabulary or stream boundaries - settle the model first, then render it here.
license: MIT
---
# Event model diagrams
Render an already-decided event model as a Mermaid `eventmodeling` diagram that
lives beside the code and is updated in the same change. If the event vocabulary
or the stream boundaries are not yet decided, model first; this skill draws a
chosen model, it does not choose one.
## Source authority
Diagrams use **Mermaid's native `eventmodeling` diagram type** (Mermaid v11.15
and later). The grammar is owned by Mermaid, and
<https://mermaid.js.org/syntax/eventmodeling.html> is the authority whenever this
file and Mermaid disagree. Confirm the Mermaid version available to the renderer
before relying on a construct.
The Cratis artifacts a diagram refers to were originally checked against `Cratis.Chronicle`
18.3.0 and re-read against Chronicle 19.50.0 and the Arc query and command surface it is used with
- (current pins: `cratis-screenplay-toolchain` `references/versions.md`). The Mermaid claim above is
+ (current pins: `cratis-screenplay-toolchain` `SKILL.md`). The Mermaid claim above is
external and was not re-verified here.
## What an event model is
It arranges a module's commands, events, read models, and automations on a
left-to-right business-flow timeline, answering *"what happens in this module,
and in what order?"*.
One file per module or feature, alongside the code, with the diagram in a fenced
`mermaid` block. A system-overview file at the source root shows only
cross-module flows.
## Grammar cheat sheet
**Frame prefix:** `tf` (timeframe — auto-connects to the previous frame) and
`rf` (resetframe — breaks the chain; start each independent flow with it). `rf`
*replaces* `tf`; `tf N rf Name` is invalid.
**Frame:** `<prefix> <number> <type> <EntityName>`. The number is unique;
declaration order does not matter because frames position by reference.
| Type | Swimlane | Represents |
| --- | --- | --- |
| `ui` | UI / Automation | the persona interacting — a persona name, never a screen name |
| `pcr` | UI / Automation | a reactor or automation processor |
| `cmd` | Command / Read Model | a command |
| `rmo` | Command / Read Model | a read model |
| `evt` | Events | an event type, using the exact self-describing type name |
**Multiple sources (`->>`).** A read model or fan-in reactor fed by several
frames references them by frame number:
```text
tf 10 rmo <ReadModelName> ->> 03 ->> 06 ->> 09
```
**Namespaces.** A `Module.` prefix creates a sub-swimlane. Use it for
cross-module entities and throughout the system overview.
**Comments.** `%% -- Section --`. A section header is a comment, never a frame.
## Behavior type to pattern
```text
%% -- State change: <CommandName> --------------------
rf 01 ui <Persona>
tf 02 cmd <CommandName>
tf 03 evt <EntityName><PastTenseVerb>
%% -- State view: <ReadModelName> (consumed by <Persona>) --
rf 04 rmo <ReadModelName> ->> 03
tf 05 ui <Persona>
%% -- Automation: <ReactorName> (reacts to our own event) --
rf 06 evt <EntityName><PastTenseVerb>
tf 07 pcr <ReactorName>
%% add the next two frames only when the automation invokes a command
tf 08 cmd <CommandName>
tf 09 evt <EntityName><PastTenseVerb>
%% -- Translation: <Outside>.<Fact> -> <Adapter> -> our event --
rf 10 evt <ExternalSource>.<FactName>
tf 11 pcr <AdapterName>
tf 12 evt <EntityName><PastTenseVerb>
```
- A state view's `rmo` references the event frames it projects from by number;
its consumer `ui` frame auto-chains after it. A passive read model has no
consumer UI — emit only the `rmo ... ->>` line with a `%% passive` comment.
- One definition of Translation everywhere: data from **outside** our own facts
becomes our own facts. Its flow starts at an outside-origin `evt` (an
`<ExternalSource>.` prefix marks it), passes through the adapter `pcr`, and
ends at an event we own. A `pcr` that reacts to **our own** event is an
**automation**, whether it causes an external side effect, appends a
follow-up event, or invokes a command (then draw the `cmd` and its resulting
`evt` after the `pcr`). Classify by where the data comes from, not by how the
reactor is built.
- Several consumers of one read model: declare each consumer `ui` as its own
`rf` frame with an explicit `->>` back to the `rmo`.
## Connections read forward
A diagram reads left to right, so every connection goes to a later frame, never back to an
earlier one. In practice:
- Number frames in story order and put elements that belong together next to each other: a
persona and the command it issues, a command and the events it produces, an event and the
read model that projects it. A read model's `->>` sources are frames that come **before** it.
- When a later event must change what an earlier read model shows, do not draw an arrow back to
that read model. Draw a new `rmo` frame after the later event, sourced from the events that now
matter (it is the same read model carried to a later state), and give it its own consumer
`ui` frame if a person looks at it. The earlier frame stays as it was: it correctly showed
that earlier point in the story. The model itself is unchanged; its projection simply
consumes the later event.
- If the natural place for a frame is already taken, insert it just before or after rather than
wiring across the gap.
Adapted from Martin Dilger and Nebulit GmbH's core rules (`references/provenance.md`); their
board mechanics (columns, cells, linked copies) do not apply to Mermaid frames.
## The command rules table
Mermaid's eventmodeling grammar has no shape for validation or guards. After the
diagram, add a `## Command rules` section: a table with **Command**, **Rules**,
and **Emits / result**, summarizing validator, value-invariant, provider,
concurrency, and authorization rules plus no-op and diff behavior, in human
language. Include commands that emit no event.
## Process
1. **Discover** the behaviors by scanning the module for the artifacts that
define them: command records for state changes, read models without a command
handler for state views, `IReactor` implementations that call out of the
system or return events or commands in response to our own events for
automations, and adapters that take outside data in as our own events for
translations. Choose the **authority** here: when an accepted model covers
the scope (or the repository is opted in, per `cratis-screenplay-modeling-lifecycle`), read the behaviors from
its slices (`StateChange`, `StateView`, `Automation`, `Translate`) and render
from the model; otherwise the code is the authority. Report code that
disagrees with the model as drift, separately; do not add unmodeled behavior
to the diagram. Use the exact type names.
2. **Order** frames by domain causality — what must happen before what. Put state
views after the events they project from. Use `rf` only between independent
flows, never between sibling events of one flow, or the diagram becomes a
tall tower one event wide.
3. **Write** the diagram and the command-rules table.
4. **Verify** it renders without a syntax-error banner, then reconcile it against
the authority chosen in step 1 (the model's slices, or the code's markers):
every behavior appears, classified by the type the authority gives it. A clean
render proves valid Mermaid, not completeness — close the gaps against the
authority, never from memory. List model/code drift in the report instead of
resolving it in the diagram.
## Common mistakes
- **A command that emits several events** chains them with consecutive
`tf ... evt ...` frames. Do not fight the auto-chain with `rf`.
- **The same event emitted from several commands** gets its own `tf evt` frame at
each emit point. Do not merge them into one.
- **Several consumers of one read model** — the first auto-chains; each
additional consumer needs an explicit `rf <n> ui <Persona> ->> <rmo-number>`.
- **A screen name in the `ui` lane.** The `ui` lane is the persona.
- **A section header written as a frame.** Section headers are `%%` comments.
- **A cross-module flow updated in one place.** It needs three: the overview
diagram, the source module's outputs, and the target module's inputs.
## Verify
- The diagram renders with no syntax-error banner.
- Every behavior in the module appears, with the type its authority (the accepted `.play` slice, otherwise the code) gives it; drift is reported separately.
- Every event frame uses the exact event type name from the source.
- Independent flows start with `rf`; sibling events within a flow do not.
- Passive read models have no consumer `ui` frame.
- The command-rules table covers every command, including those that emit
nothing.
- Cross-module flows are reflected in all three places.
## Route near misses
- An accepted model covers the scope (or the repository is opted in): it is the source for this
diagram; do not redraw from code and never change the model to match a
diagram. The diagram is a view of it.
- Deciding the model rather than drawing one: `cratis-chronicle-event-modeling`
for a hand-written Chronicle implementation, or
`cratis-screenplay-event-modeling` when the model is authored as a Screenplay
`.play` document.
Attribution for adapted method text: `references/provenance.md`.