The Event Model canvas
At http://localhost:5526/event-model: the system drawn as an Event Model — slices on a timeline, grouped into chapters, with the specifications bound to each one and the run evidence that says whether they pass.
Where the model comes from
Nobody draws it. A descriptor is assembled from producers that each see part of the system, and merged.
| Producer | What it knows |
|---|---|
| A running Wolverine application | Its handler chains and HTTP routes — commands, handlers, emitted events, messages, read models. |
| The store | One View slice per registered projection, named after the document type, carrying its real apply set. |
| A Bobcat spec assembly | The slices its scenarios declare (@slice:, @domain:, @chapter:, Triggered by …) and the specifications bound to each. |
A curated *.emodel.yaml | Slices declared ahead of any code — the Declared rung. |
Each pushes what it can see, under a source name, and PUT /api/event-model/{source} replaces only that source. GET serves the merge.
Merging is by model name, which is why [assembly: EventModelName("…")] matters: the Wolverine-derived model is named for the service, so a spec assembly called MyApp.Tests can never fold into the chains it describes unless it declares the service's name. Slices merge by slice name within that — so a spec-declared View slice folds into the store's only if @slice: names the document type exactly.
Getting a model in
From a running application, whose host exports its own descriptor:
dotnet run --project src/MyApp -- event-model --url http://localhost:5525/api/event-modelLive, as you edit — the watch command redraws the canvas on each rebuild:
stoat watch-event-modelFrom a file, curated or an eventmodelers.ai board export, using Bobcat's free tool:
bobcat import-event-model Wallet.emodel.yaml --url http://localhost:5525A board export is segmented into slices and written out as a curated file to review first — the segmentation is a set of reported guesses, and a wrong guess should be a one-line diff in that file rather than a re-import.
From a spec run: a Bobcat run that attaches to the console publishes its spec assemblies' descriptors itself, under a source named for the assembly, under the same never-slow-never-fail invariant as the event pump.
What the canvas draws
Rendering is @jasperfx/event-model-vue, the same package CritterWatch uses — which is what makes "the same descriptor renders identically in both viewers" true by construction rather than by convention. Layout is a pure synchronous function of the descriptor, pinned on exact coordinates by its own test gate.
- Lanes for the Event Modeling grammar: triggers, commands, handlers, aggregates, events, projections, read models, external systems.
- Edges within a slice, computed on read from the typed roles — Trigger→Command→Handler→ {Aggregate, Event, Message}, Event→Projection→ReadModel, ReadModel→Trigger for a pure view slice. Only roles are ever stamped; the graph is computed, so there is one opinion about it.
- Chapter bands above the lanes, one per contiguous run of same-chapter slices. The canvas never reorders slices, so an interleaved chapter draws one band per run with the same name.
- Hotspots — a pending specification, or a
SourceDisagreementwhere two producers claim the same slice differently. A View slice whose scenarios between them arrange fewer events than the projection applies is a disagreement, and the right reading is that the view is under-specified. - Slice colouring from run evidence, by spec identity, with a drill-down drawer showing each bound spec's step results and flagging touched types the model does not declare.
Cross-slice links and region zoom are designed but not built — the reasoning is in the canvas design notes.
Identity
A specification's identity is {Feature}/{Scenario} — the same string Bobcat's MTP node mapping produces and scenario_finished carries. Design-time and run evidence therefore join with no mapping table, which is the whole reason the binding is by identity plus observed evidence rather than hand-typed anywhere.
See also
- The run board — where the evidence comes from
- Canvas design notes — cross-slice links, zoom, and what is decided
slice_coveragein the MCP tool reference