cratis-event-model-diagram · git:20261013.dab149a · 2026-10-13 · sha256 994cc7e038c2f57f
cratis-event-model-diagram git:20261013.dab149aA
Immutable. This exact content never changes and is served at /api/v1/blob/994cc7e038c2f57f.
--- 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` `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`.