@milehimikey/em 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/event-modeling/SKILL.md +40 -43
- package/.claude/skills/event-modeling/reference/em-dsl.md +58 -53
- package/.claude/skills/event-modeling/reference/extract.md +12 -10
- package/.claude/skills/event-modeling/reference/methodology.md +40 -33
- package/.claude/skills/event-modeling/templates/model-readme.md +4 -4
- package/.claude/skills/event-modeling/templates/slice.md +3 -3
- package/.claude/skills/event-modeling/templates/state.md +1 -1
- package/dist/catalog/classify.d.ts +7 -8
- package/dist/catalog/classify.js +7 -8
- package/dist/catalog/classify.js.map +1 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/emit/dot.js +46 -5
- package/dist/emit/dot.js.map +1 -1
- package/dist/model/edges.js +17 -17
- package/dist/model/edges.js.map +1 -1
- package/dist/model/rules.d.ts +9 -8
- package/dist/model/rules.js +9 -8
- package/dist/model/rules.js.map +1 -1
- package/dist/model/validate.js +42 -25
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/lexer.d.ts +17 -2
- package/dist/parser/lexer.js +119 -13
- package/dist/parser/lexer.js.map +1 -1
- package/dist/parser/parser.js +52 -23
- package/dist/parser/parser.js.map +1 -1
- package/dist/render/sliceDiagram.js +33 -35
- package/dist/render/sliceDiagram.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling
|
|
3
|
-
em-version: 1.7.
|
|
3
|
+
em-version: 1.7.1
|
|
4
4
|
description: >-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
a ratified model for drift against the codebase that implements it. Works in resumable phases
|
|
12
|
-
via an argument: `discover` (steps 1-4, greenfield), `extract` (current-state model of an
|
|
13
|
-
existing system), `model` (steps 5-7), `slice` (deep slice specs), `implement` (build one
|
|
14
|
-
ratified slice into code, following the bundled agent guide), `conform` (drift check
|
|
15
|
-
against the codebase), plus `watch`, `review` (stakeholder walkthrough), and `validate`.
|
|
16
|
-
With no argument it resumes from the saved state file.
|
|
5
|
+
Use when the user wants to event-model a business process or system: building, editing,
|
|
6
|
+
updating, or maintaining a model — extending or detailing an existing one into slices,
|
|
7
|
+
extracting a current-state model from an existing codebase, implementing a ratified slice, or
|
|
8
|
+
checking a model for drift against the code. Drives the `em` CLI through resumable phases —
|
|
9
|
+
discover, extract, model, slice, implement, conform, watch, review, validate — or resumes
|
|
10
|
+
automatically with no argument.
|
|
17
11
|
---
|
|
18
12
|
|
|
19
13
|
# Event Modeling with `em`
|
|
@@ -59,23 +53,24 @@ vendored alongside this skill.
|
|
|
59
53
|
keep a sub-flow that detours into another context together, not parked at the end of the model).
|
|
60
54
|
Declare every instance after the first with
|
|
61
55
|
**`view X again from "..."`** — `again` instances are exempt from the duplicate-name warning even
|
|
62
|
-
when referenced, and each reference resolves to the right instance.
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
when referenced, and each reference resolves to the right instance. A reaction's command is
|
|
57
|
+
wired by same-slice presence, exactly like `ui → command` — no positional/adjacency rule to get
|
|
58
|
+
right (see `em-dsl.md`).
|
|
65
59
|
See `reference/methodology.md` (State View) and `reference/em-dsl.md`.
|
|
66
60
|
- **Never connect two instances of one read model.** The repeat is a timeline device, not a flow:
|
|
67
61
|
continuity is implied by the shared name, and the events arriving at each instance are what show
|
|
68
62
|
it changing. An arrow between instances says the read model feeds itself, and is an error.
|
|
69
63
|
- **Every slice is a COMPLETE pattern, never a half-slice.** A State Change is
|
|
70
64
|
`ui → command → event`; a State View is `event → read model → ui` (or `→ reaction`). So: a
|
|
71
|
-
command needs something that **triggers** it (the `ui` it's issued from, or the reaction
|
|
72
|
-
|
|
73
|
-
own. A command nothing points at is a write nobody can start;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
command needs something that **triggers** it (the `ui` it's issued from, or the reaction that
|
|
66
|
+
triggers it, also in this slice), its event needs a **reader**, and that read model needs a
|
|
67
|
+
**consumer** of its own. A command nothing points at is a write nobody can start; a reaction
|
|
68
|
+
that triggers no command is a decision the system never acts on; an event nothing projects is
|
|
69
|
+
a write nobody can see; a read model nothing displays is information dropped on the floor.
|
|
70
|
+
Write the whole chain together rather than sweeping up dangling ends later. Reactions don't
|
|
71
|
+
count as readers of *events* — they read views. **Every instance** of a repeated read model
|
|
72
|
+
needs its own consumer: if you repeat a view next to an event just to keep the arrow short,
|
|
73
|
+
bring its screen along, or don't add the instance.
|
|
79
74
|
- **Only six connections are legal:** `ui → command`, `command → event`, `event → read model`,
|
|
80
75
|
`read model → ui`, `read model → reaction`, `reaction → command`. Reaching for anything else —
|
|
81
76
|
above all `command → read model` (the CQRS violation) or `read model → command` — means the model
|
|
@@ -150,8 +145,9 @@ phase applies to field tables, below.
|
|
|
150
145
|
3. **Inputs (step 3).** For each event, find the **command** that causes it (imperative name).
|
|
151
146
|
Form `command → event` slices (State Change pattern). Every command needs a **trigger** in the
|
|
152
147
|
same breath: the screen the user issues it from (a `ui` in the slice), or — for the Automation
|
|
153
|
-
and Translation patterns — the reaction in
|
|
154
|
-
a write nobody can start. *"Who does this, and where are they when they
|
|
148
|
+
and Translation patterns — the reaction that triggers it, also in this slice. A command
|
|
149
|
+
nothing points at is a write nobody can start. *"Who does this, and where are they when they
|
|
150
|
+
do it?"*
|
|
155
151
|
4. **Outputs (step 4).** Identify the **read models / views** consumers need and wire them with
|
|
156
152
|
`from "Event"` (State View pattern), each with the screen (or reaction) that consumes it — a
|
|
157
153
|
read model nothing displays is information dropped on the floor. In a **headless/API** model
|
|
@@ -194,23 +190,23 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
|
|
|
194
190
|
|
|
195
191
|
1. **Swimlanes & patterns (step 5).** Group events into **contexts** (bounded contexts /
|
|
196
192
|
aggregates) — ask which events share a consistency boundary and who owns them. Classify each
|
|
197
|
-
slice as one of the 4 patterns. **
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
it **triggers a command and never records an event
|
|
201
|
-
external inputs (externally triggered: external →
|
|
202
|
-
|
|
203
|
-
event).
|
|
193
|
+
slice as one of the 4 patterns. **Share the slice between every automation or translation and
|
|
194
|
+
the command it triggers** (the reaction — processor/translation — together with the command
|
|
195
|
+
and event it produces; the read model it watches, if any, stays in the slice before). A
|
|
196
|
+
translation or automation is a reaction: it **triggers a command and never records an event
|
|
197
|
+
directly**. Add `translation` slices for external inputs (externally triggered: external →
|
|
198
|
+
translation → command → event, no slice before) and for the system reacting to its own state
|
|
199
|
+
(internally triggered: read model in the slice before → translation → command → event).
|
|
204
200
|
2. **Elaborate scenarios — first pass (step 6).** For each slice, capture the happy-path
|
|
205
201
|
Given/When/Then and the obvious invariants as short notes. (The full spec is the `slice` phase.)
|
|
206
202
|
3. **Evaluate completeness (step 7).** Walk the model: every slice is a **complete** pattern, not a
|
|
207
203
|
half-slice — **every command has something that triggers it**, every command emits an event,
|
|
208
204
|
**every event is read by a read model**, **every read model has a consumer**, every view has a
|
|
209
205
|
source, every UI is reachable, every connection is
|
|
210
|
-
one of the six legal pairs, and
|
|
211
|
-
|
|
212
|
-
and warnings —
|
|
213
|
-
|
|
206
|
+
one of the six legal pairs, and every automation **and** translation triggers a command (none
|
|
207
|
+
wired straight to an event, none with no command at all). Run `em validate` and resolve all
|
|
208
|
+
errors and warnings — it catches a reaction wired straight to an event too now (see
|
|
209
|
+
`reference/em-dsl.md`), so a clean run covers this case.
|
|
214
210
|
For an unread event, don't just bolt on a view to silence the warning: ask the user who looks at
|
|
215
211
|
this fact and what they do with it. The honest answers are "here's the read model we missed" or
|
|
216
212
|
"nobody — so why are we recording it", and both improve the model.
|
|
@@ -368,8 +364,8 @@ rule (see `reference/em-dsl.md`) and proposing the fix. Apply fixes on agreement
|
|
|
368
364
|
|
|
369
365
|
| Diagnostic | Fix |
|
|
370
366
|
|---|---|
|
|
371
|
-
|
|
|
372
|
-
| **command has nothing that triggers it** | Add the `ui` it's issued from — or, if the system issues it, the reaction in
|
|
367
|
+
| reaction triggers no command | Add the command it issues, in this same slice, or an explicit `arrow` to one elsewhere |
|
|
368
|
+
| **command has nothing that triggers it** | Add the `ui` it's issued from — or, if the system issues it, the reaction that triggers it, also in this slice. Ask *"who does this, and where are they when they do it?"* |
|
|
373
369
|
| **read model has no consumer** | Add the screen that displays it, or the reaction that watches it. If it's a repeat added only to shorten an arrow and nothing looks at it there, drop the instance |
|
|
374
370
|
| read model has no source | Add the missing `from "Event"` |
|
|
375
371
|
| command produces no event | Give the command its event, or drop the command |
|
|
@@ -383,10 +379,11 @@ relevant element over a `# TBD` comment — it shows up as a red marker on the r
|
|
|
383
379
|
`em validate` tracks it as an open-issue warning, so it isn't lost once rendered. `em validate
|
|
384
380
|
--list-issues` gives a quick sweep of everything still open.
|
|
385
381
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
382
|
+
`em validate` now catches a reaction wired straight to an event too — any reaction with no
|
|
383
|
+
`command` in its own slice, and no explicit arrow to one, warns "triggers no command". A
|
|
384
|
+
`translation`/`processor`/`automation` should always trigger a `command` in its own slice
|
|
385
|
+
(`reaction → command → event`); if one instead records an event directly, route it through a
|
|
386
|
+
command instead.
|
|
390
387
|
|
|
391
388
|
---
|
|
392
389
|
|
|
@@ -153,6 +153,11 @@ type Name { field: Type, ... } # named structured type, reusable from any f
|
|
|
153
153
|
declared `type` (see Named types below), in which case it resolves to a structured
|
|
154
154
|
reference. Keep these light; full field specs with rules live in the slice doc.
|
|
155
155
|
- **Comments:** `# ...` anywhere outside quotes (full-line or trailing).
|
|
156
|
+
- **Quoted strings:** everything between a `"` and its matching `"` is literal — `{`, `}`, `#`
|
|
157
|
+
included, never re-interpreted as a field block or comment — so REST path-template
|
|
158
|
+
placeholders are safe as-is: `issue "PUT v3/widgets/{widgetId}/suspend"`. Escape a literal
|
|
159
|
+
`"` or `\` inside a string with `\"`/`\\`; no other backslash sequence is special. An
|
|
160
|
+
unterminated string (missing closing `"`) is a parse error naming the clause it broke in.
|
|
156
161
|
- **`source "url"` on a slice header** (the only slice-level clause — everything above is
|
|
157
162
|
per-element) links the slice back to the ticket/conversation it traces to, e.g.
|
|
158
163
|
`slice "Checkout" source "https://linear.app/team/issue/MIL-60" { ... }`. Exports as
|
|
@@ -203,12 +208,12 @@ slice "Open Orders" {
|
|
|
203
208
|
ui Order List @Customer
|
|
204
209
|
}
|
|
205
210
|
|
|
206
|
-
# 3. Automation:
|
|
211
|
+
# 3. Automation: read model in the slice before, reaction + command + event together
|
|
207
212
|
slice "Orders To Fulfill" {
|
|
208
213
|
view Orders To Fulfill from "Order Placed"
|
|
209
|
-
processor Fulfillment Service
|
|
210
214
|
}
|
|
211
|
-
slice "Ship Order" { #
|
|
215
|
+
slice "Ship Order" { # reaction, command, and event all share this one slice
|
|
216
|
+
processor Fulfillment Service from "Orders To Fulfill"
|
|
212
217
|
command Ship Order
|
|
213
218
|
event Order Shipped @Shipping
|
|
214
219
|
}
|
|
@@ -218,10 +223,8 @@ slice "Open Orders — shipped" {
|
|
|
218
223
|
}
|
|
219
224
|
|
|
220
225
|
# 4a. Translation (external trigger, no durable artifact): external call -> translation -> command -> event
|
|
221
|
-
slice "
|
|
226
|
+
slice "Confirm Delivery" {
|
|
222
227
|
translation Carrier Adapter # inbound from outside the model; no internal `from`
|
|
223
|
-
}
|
|
224
|
-
slice "Confirm Delivery" { # the triggered command goes in the NEXT slice
|
|
225
228
|
command Confirm Delivery
|
|
226
229
|
event Delivery Confirmed @Shipping
|
|
227
230
|
}
|
|
@@ -238,9 +241,9 @@ slice "Receive Carrier Event" { # ingest: persist the raw webhook befo
|
|
|
238
241
|
}
|
|
239
242
|
slice "Inbound Carrier Events" {
|
|
240
243
|
view Inbound Carrier Events from "Carrier Event Received" # the persisted inbound queue
|
|
241
|
-
translation Carrier Adapter
|
|
242
244
|
}
|
|
243
245
|
slice "Acknowledge Carrier Event" {
|
|
246
|
+
translation Carrier Queue Adapter from "Inbound Carrier Events" # different producer than 4a's Carrier Adapter, so a distinct name
|
|
244
247
|
command Acknowledge Carrier Event
|
|
245
248
|
event Carrier Event Acknowledged @Shipping
|
|
246
249
|
}
|
|
@@ -251,15 +254,15 @@ slice "Inbound Carrier Events — processed" {
|
|
|
251
254
|
|
|
252
255
|
# 4c. Translation (internal trigger, durable artifact): read model -> translation -> command -> event
|
|
253
256
|
slice "Accept Quote" {
|
|
254
|
-
ui Quote Screen @Customer # every command needs a trigger: a ui, or a reaction
|
|
257
|
+
ui Quote Screen @Customer # every command needs a trigger: a ui, or a reaction, in this slice
|
|
255
258
|
command Accept Quote
|
|
256
259
|
event Quote Accepted @Quote
|
|
257
260
|
}
|
|
258
261
|
slice "Quotes To Sync" {
|
|
259
262
|
view Accepted Quotes from "Quote Accepted"
|
|
260
|
-
translation CRM Sync # reacts to our own state via the read model in this slice
|
|
261
263
|
}
|
|
262
264
|
slice "Record Sync" {
|
|
265
|
+
translation CRM Sync from "Accepted Quotes" # reacts to our own state via the read model
|
|
263
266
|
command Record Crm Sync
|
|
264
267
|
event Quote Synced @Quote
|
|
265
268
|
}
|
|
@@ -278,8 +281,11 @@ the context/lane whose fact it represents (e.g. `Carrier Event Received @Shippin
|
|
|
278
281
|
Anti-Corruption-Layer event in DDD) instead of a technical artifact leaking foreign vocabulary
|
|
279
282
|
into the model.
|
|
280
283
|
|
|
281
|
-
A `translation` (like a `processor`) is a **reaction**: it triggers a command
|
|
282
|
-
|
|
284
|
+
A `translation` (like a `processor`) is a **reaction**: it triggers a command in the same slice —
|
|
285
|
+
same shape as the Automation pattern above, and the same shape `ui` already uses in State Change.
|
|
286
|
+
The reaction never records the event directly (there's no `reaction -> event` edge); the command
|
|
287
|
+
it triggers is what records it, and that event happens to live in the reaction's own slice now
|
|
288
|
+
because the command does.
|
|
283
289
|
|
|
284
290
|
Note the read slice closing each pattern: **every event must be read by some read model**
|
|
285
291
|
(warning 4 below). A command slice is not finished until the slice that projects its event
|
|
@@ -314,8 +320,9 @@ slice "Read Quote — created" {
|
|
|
314
320
|
request/response API call — that's Pattern 1 (State Change) with an API persona, exactly like the
|
|
315
321
|
`Create Quote` slice above.
|
|
316
322
|
- **Internal-only commands and views (no public route) carry no `ui` at all.** They follow the
|
|
317
|
-
ordinary Automation pattern already documented: the reaction
|
|
318
|
-
internal-only read model is consumed by that reaction, not by a screen
|
|
323
|
+
ordinary Automation pattern already documented: the reaction shares the command's slice, and an
|
|
324
|
+
internal-only read model is consumed by that reaction (from the slice before), not by a screen
|
|
325
|
+
or an API query.
|
|
319
326
|
- **Repeat the read model** in every slice where it's read so the timeline flows left-to-right, and
|
|
320
327
|
**prefer `view X again`** for every instance after the first (see Clauses) — **each instance
|
|
321
328
|
carries its own consumer** (the `ui` that reads it, or a reaction), not just the last one. `again`
|
|
@@ -337,23 +344,19 @@ slice "Read Quote — created" {
|
|
|
337
344
|
Keep the read model adjacent to its event, and keep a sub-flow that detours into another context
|
|
338
345
|
together rather than parking it at the end of the model.
|
|
339
346
|
|
|
340
|
-
###
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
- A `ui` never triggers a reaction — no pattern has a `ui` wired to `processor`/`automation`/
|
|
354
|
-
`saga`/`translation`, only to `command`. A `ui` left in the reaction's own slice (instead of the
|
|
355
|
-
read-model or command slice) renders with no outgoing edge, disconnected, and `em validate` now
|
|
356
|
-
warns on it.
|
|
347
|
+
### Reaction wiring — no positional gotcha
|
|
348
|
+
|
|
349
|
+
A reaction's command is inferred from **same-slice presence**, exactly like `ui -> command`: put
|
|
350
|
+
`processor`/`translation` in the same slice as the command it triggers, in either order, and `em`
|
|
351
|
+
wires it correctly regardless of what any other slice contains or where it sits in the file. There's
|
|
352
|
+
no adjacency to get right and no reordering hazard — the read model it watches (if any) is named
|
|
353
|
+
explicitly via `from "View"`, resolved by name to the nearest instance at or before its own slice,
|
|
354
|
+
not by position either.
|
|
355
|
+
|
|
356
|
+
The one thing that still doesn't wire on its own: a `ui` never triggers a reaction — no pattern has
|
|
357
|
+
a `ui` wired to `processor`/`automation`/`saga`/`translation`, only to `command`. A `ui` left in a
|
|
358
|
+
reaction's slice renders with no outgoing edge, disconnected, and `em validate` warns on it — move
|
|
359
|
+
it to the slice that displays the read model, or drop it.
|
|
357
360
|
|
|
358
361
|
---
|
|
359
362
|
|
|
@@ -388,15 +391,16 @@ slice "Read Quote — created" {
|
|
|
388
391
|
real split/merge (see the four `lineage-*` codes in the table below).
|
|
389
392
|
|
|
390
393
|
**Warnings (should fix):**
|
|
391
|
-
1. **
|
|
392
|
-
|
|
393
|
-
|
|
394
|
+
1. **Reaction with no command** — a `processor`/`automation`/`saga`/`translation` that triggers
|
|
395
|
+
nothing. It never records an event itself; add the command it issues in this same slice, or an
|
|
396
|
+
explicit `arrow` to one elsewhere.
|
|
397
|
+
2. **`ui` shares slice with a reaction** — a `ui` only ever wires to a `command` a person issues; no
|
|
394
398
|
pattern has a `ui` triggering an automation/processor/saga/translation. Left in the reaction's
|
|
395
|
-
own slice it renders disconnected, with no edge
|
|
396
|
-
slice
|
|
397
|
-
3. **Command with no trigger** — nothing issues it. A command needs a `ui` in its slice, or
|
|
398
|
-
reaction (automation/processor/saga/translation)
|
|
399
|
-
of (5): a command nothing points at is a write nobody can start.
|
|
399
|
+
own slice it renders disconnected, with no edge, whether or not that slice also has the
|
|
400
|
+
reaction's own command. Move it to the slice that displays the read model, or drop it.
|
|
401
|
+
3. **Command with no trigger** — nothing issues it. A command needs a `ui` in its slice, or the
|
|
402
|
+
reaction (automation/processor/saga/translation) that triggers it, *also* in this slice. The
|
|
403
|
+
input-side mirror of (5): a command nothing points at is a write nobody can start.
|
|
400
404
|
4. **Command without event** — every command should record at least one event.
|
|
401
405
|
5. **Event nobody reads** — the mirror of (4): recording an event no read model projects is a
|
|
402
406
|
write with no reader. Follow every write slice with the read slice that consumes its event.
|
|
@@ -450,12 +454,12 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
450
454
|
| `arrow-backward` | error | Backward arrow | Restructure so the target comes later. |
|
|
451
455
|
| `arrow-unresolved-source` | error | Arrow source unresolved | Fix the arrow's source name. |
|
|
452
456
|
| `arrow-unresolved-target` | error | Arrow target unresolved | Fix the arrow's target name. |
|
|
453
|
-
| `automation-shares-slice-with-command` | warning | Automation shares slice with its command | Put the triggered command in the next slice. |
|
|
454
457
|
| `binding-missing-file` | warning | Doc binding points at a missing file | Create the slice doc, or fix the `note` path. |
|
|
455
458
|
| `both-ends-of-a-flow/command-no-event` | warning | Command without event | Add the event this command records. |
|
|
456
|
-
| `both-ends-of-a-flow/command-untriggered` | warning | Command with no trigger | Add a `ui`
|
|
459
|
+
| `both-ends-of-a-flow/command-untriggered` | warning | Command with no trigger | Add a `ui` or the reaction that issues it, both in this slice. |
|
|
457
460
|
| `both-ends-of-a-flow/event-unproduced` | warning | Event with no producing command | Add the command that records it, or an explicit arrow from one. |
|
|
458
461
|
| `both-ends-of-a-flow/event-unread` | warning | Event nobody reads | Project it into a view, or reconsider recording it. |
|
|
462
|
+
| `both-ends-of-a-flow/reaction-no-command` | warning | Reaction with no command | Add the command it triggers, in this slice, or an explicit arrow to one. |
|
|
459
463
|
| `both-ends-of-a-flow/ui-unbacked` | warning | `ui` with no read model or command | Add a `view` it displays, or the command it triggers. |
|
|
460
464
|
| `both-ends-of-a-flow/view-unconsumed` | warning | Read model with no consumer | Add a `ui` or reaction that consumes it, or drop this instance. |
|
|
461
465
|
| `connection-legality/illegal-pair` | error | Illegal connection | Only ui→command→event→view→ui and view→reaction→command are legal — the message names the missing step. |
|
|
@@ -478,7 +482,7 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
478
482
|
| `reaction-from-unresolved` | error | Unknown read-model source | Project the event into a view first, or fix the `from` reference. |
|
|
479
483
|
| `translation-name-collision` | warning | Translation name reused for different producers | Use a distinct name per producer to avoid confusion. |
|
|
480
484
|
| `type-cycle` | error | Cyclic type reference | Break the cycle, or route the self/mutual reference through an array. |
|
|
481
|
-
| `ui-shares-slice-with-automation` | warning | `ui` shares slice with a reaction
|
|
485
|
+
| `ui-shares-slice-with-automation` | warning | `ui` shares slice with a reaction | Move the `ui` to the slice that displays the read model, or drop it. |
|
|
482
486
|
| `view-again-without-earlier` | error | `again` without an earlier declaration | Declare the view plainly the first time it appears. |
|
|
483
487
|
| `view-from-future-event` | error | Backward timeline (view reads a future event) | Move the source to a later `view X again` instance. |
|
|
484
488
|
| `view-from-unresolved` | error | Unknown event source | Fix the `from` reference to name an existing event. |
|
|
@@ -488,8 +492,8 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
488
492
|
**Design rules that keep models valid:**
|
|
489
493
|
- One element per band per slice (multiple personas/contexts are fine — they're different rows).
|
|
490
494
|
- Every `command` slice includes its `event`. Every `view` has a `from` source.
|
|
491
|
-
- **Every `command` has a trigger.** A `ui` in its slice, or
|
|
492
|
-
A command nothing points at is a write nobody can start.
|
|
495
|
+
- **Every `command` has a trigger.** A `ui` in its slice, or the reaction that triggers it, also in
|
|
496
|
+
its slice. A command nothing points at is a write nobody can start.
|
|
493
497
|
- **Every `event` has a reader.** A command slice isn't finished until the read slice that projects
|
|
494
498
|
its event exists. Reactions don't count — they read views, not events. Pair each write slice with
|
|
495
499
|
its read slice as you go rather than sweeping up dangling events at the end.
|
|
@@ -502,16 +506,17 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
502
506
|
`command → view` (the CQRS violation: an event has to sit between them) and `view → command`
|
|
503
507
|
(a reaction has to sit between them). If you reach for an arrow the patterns don't allow, the
|
|
504
508
|
model is missing an element, not an arrow.
|
|
505
|
-
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
509
|
+
- A reaction (`processor`/`automation`/`saga`/`translation`) shares its slice with the `command` it
|
|
510
|
+
triggers and that command's `event` — the same shape `ui` already uses in State Change. If it's
|
|
511
|
+
internally triggered, the **view** (read model) it watches lives in the slice before, named via
|
|
512
|
+
`from "X"` (which must resolve to a view, never an event directly). Externally-triggered reactions
|
|
513
|
+
start from outside the model — no `from` at all, and no slice before them.
|
|
510
514
|
- Name everything uniquely and consistently.
|
|
511
515
|
- Events are past tense; commands are imperative; views name the thing shown.
|
|
512
516
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
517
|
+
A reaction that emits an event with no command anywhere between them — `translation T` and
|
|
518
|
+
`event E` in the same slice, nothing issuing it — is exactly what
|
|
519
|
+
`both-ends-of-a-flow/reaction-no-command` (above) catches: any reaction with no `command` in its
|
|
520
|
+
own slice, and no explicit arrow to one, warns. Still route every reaction through a real command
|
|
521
|
+
by construction (`reaction → command → event`) rather than an explicit `arrow "Reaction" -> "Event"`
|
|
522
|
+
— that shape is its own `connection-legality` error (a reaction never records an event itself).
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Read this **before doing any extract work**. It governs the `extract` phase and **overrides
|
|
4
4
|
`methodology.md` step 1's stance** for the duration of extraction: you are capturing how the
|
|
5
5
|
system behaves **today**, not modeling what the business needs. Everything else in the
|
|
6
|
-
methodology — the 4 patterns, the "is it an event?" test, the Socratic stance, the
|
|
7
|
-
reaction
|
|
6
|
+
methodology — the 4 patterns, the "is it an event?" test, the Socratic stance, the
|
|
7
|
+
reaction-shares-a-slice-with-its-command shape — still applies.
|
|
8
8
|
|
|
9
9
|
## Stance override — current-state-only
|
|
10
10
|
|
|
@@ -81,7 +81,7 @@ it is fine to finish a round with `# TBD`s still parked.
|
|
|
81
81
|
`em render` here.**
|
|
82
82
|
- **R3 — Commands / inputs.** For each event, the request that actually produces it today
|
|
83
83
|
(imperative name), *and what triggers it* — the real screen, or the scheduler/listener that
|
|
84
|
-
fires it (model that as a reaction
|
|
84
|
+
fires it (model that as a reaction sharing this same slice, per R5). Unclear trigger → `# TBD`,
|
|
85
85
|
not a plausible guess; expect a *command has nothing that triggers it* warning until R5 fills
|
|
86
86
|
it in, and don't invent a screen to silence it.
|
|
87
87
|
- **R4 — Read models / outputs.** The projections/queries the system actually serves; wire
|
|
@@ -91,16 +91,18 @@ it is fine to finish a round with `# TBD`s still parked.
|
|
|
91
91
|
a **finding about the system**, not a modelling mistake — never invent a reader to silence it
|
|
92
92
|
(that would be desired state). Park it in the Decisions log and carry the warning.
|
|
93
93
|
- **R5 — Boundaries & reactions.** Current automations and integrations as-is: schedulers,
|
|
94
|
-
listeners, outbound syncs, inbound webhooks. Model each as a reaction
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
listeners, outbound syncs, inbound webhooks. Model each as a reaction sharing its slice with
|
|
95
|
+
the `command` + `event` it triggers (`reaction → command → event`, per `em-dsl.md`) — the
|
|
96
|
+
read model it watches, if any, lives in the slice before, named via the reaction's own
|
|
97
|
+
`from`. This matches how the current code usually already reads (scheduler/listener → one
|
|
98
|
+
procedural step); note any real as-built difference in the slice's `# TBD`/Decisions.
|
|
98
99
|
- **R6 — Gap & TBD reconciliation.** Walk every parked `# TBD` with the user. Resolve only
|
|
99
100
|
what they can confirm **as a current-state fact**; everything else stays parked. Explicitly
|
|
100
101
|
refuse to resolve a TBD by inventing desired behavior.
|
|
101
|
-
- **R7 — Convergence.** Final `em render` + `em validate` (fix errors and warnings
|
|
102
|
-
|
|
103
|
-
faithful picture of today. Mark extraction complete in the state file
|
|
102
|
+
- **R7 — Convergence.** Final `em render` + `em validate` (fix errors and warnings — this now
|
|
103
|
+
catches a reaction wired straight to an event too, via *reaction triggers no command*). User
|
|
104
|
+
confirms the model is a faithful picture of today. Mark extraction complete in the state file
|
|
105
|
+
and hand off.
|
|
104
106
|
|
|
105
107
|
Seven rounds is the default shape, not a quota — collapse rounds for a small system, repeat
|
|
106
108
|
one for a sprawling one. Record any deviation in the Decisions log.
|
|
@@ -25,16 +25,16 @@ tempting shortcuts:
|
|
|
25
25
|
- **`command → read model`** — the CQRS violation. A write is only ever visible to a reader
|
|
26
26
|
through the event it recorded. Put the event between them.
|
|
27
27
|
- **`read model → command`** — reads never drive a write directly. A processor or translation
|
|
28
|
-
watches the read model and issues the command
|
|
28
|
+
watches the read model and issues the command that sits in the same slice.
|
|
29
29
|
|
|
30
30
|
If you find yourself wanting an arrow the patterns don't allow, the model is missing an
|
|
31
31
|
**element**, not an arrow.
|
|
32
32
|
|
|
33
33
|
**Automations and translations are not exceptions to rule 1.** They are *reactions* — a processor
|
|
34
34
|
or an adapter that wakes up, decides something must change, and **issues a command** to do it. They
|
|
35
|
-
never record an event themselves. A reaction box always points at a **command
|
|
36
|
-
|
|
37
|
-
straight to an event, the model is wrong.
|
|
35
|
+
never record an event themselves. A reaction box always points at a **command in its own slice** —
|
|
36
|
+
the same shape a `ui` already uses in State Change — and that command produces the event. If you
|
|
37
|
+
ever see a translation or automation wired straight to an event, the model is wrong.
|
|
38
38
|
|
|
39
39
|
Events are **immutable past-tense facts** ("Order Placed", "Payment Captured"). They are the
|
|
40
40
|
spine of the model. Everything else hangs off the events.
|
|
@@ -52,8 +52,8 @@ Every slice is exactly one of these. In `em`, each maps to a specific shape (see
|
|
|
52
52
|
**UI → Command → Event.** A user (or automation) submits a command; the system validates it
|
|
53
53
|
against invariants and records one or more events.
|
|
54
54
|
- **A State Change never travels alone, and it's joined at both ends.** Something must *trigger*
|
|
55
|
-
the command — the `ui` it's issued from, or (patterns 3 and 4) the reaction
|
|
56
|
-
|
|
55
|
+
the command — the `ui` it's issued from, or (patterns 3 and 4) the reaction that triggers it,
|
|
56
|
+
also in this slice — and its event needs a read model that projects it. So the unit of work is a State Change
|
|
57
57
|
slice *plus* the State View slice that reads its event. Write them together; a command nothing
|
|
58
58
|
points at is a write nobody can start, an unread event is a write nobody can see, and
|
|
59
59
|
`em validate` warns on each.
|
|
@@ -114,16 +114,17 @@ displays. Read-only; changes no state.
|
|
|
114
114
|
expectation?"*
|
|
115
115
|
|
|
116
116
|
### 3. Automation (Processor pattern)
|
|
117
|
-
**Read Model → Processor →
|
|
118
|
-
processor watches a read model (a "to-do list" of work) and **issues a
|
|
119
|
-
records an event directly.
|
|
120
|
-
- `em` shape:
|
|
117
|
+
**Read Model → Processor → Command → Event, the last three together in one slice.** The system
|
|
118
|
+
reacts on its own: a processor watches a read model (a "to-do list" of work) and **issues a
|
|
119
|
+
command** — it never records an event directly.
|
|
120
|
+
- `em` shape: the read model in the slice before, then the reaction with its command and event —
|
|
121
|
+
plus the read slice that consumes the event — three slices in all:
|
|
121
122
|
```em
|
|
122
123
|
slice "Todo" {
|
|
123
124
|
view Todo from "..."
|
|
124
|
-
processor P
|
|
125
125
|
}
|
|
126
|
-
slice "Do It" { #
|
|
126
|
+
slice "Do It" { # processor, command, and event all share this slice
|
|
127
|
+
processor P from "Todo"
|
|
127
128
|
command C
|
|
128
129
|
event E @Context
|
|
129
130
|
}
|
|
@@ -132,9 +133,10 @@ records an event directly.
|
|
|
132
133
|
ui Work Board @Persona # ...and every read model needs a consumer
|
|
133
134
|
}
|
|
134
135
|
```
|
|
135
|
-
The processor
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
The processor never records the event directly — the command it triggers does, and that event
|
|
137
|
+
happens to live in the processor's own slice now because the command does. A processor with no
|
|
138
|
+
command in its slice (and no explicit arrow to one) is a validation warning: a decision the
|
|
139
|
+
system never acts on. Forgetting the third slice leaves `E` unread, which is also a warning.
|
|
138
140
|
- **Naming:** name the read model after the pending work — `Payments To Process`,
|
|
139
141
|
`Orders To Fulfill`, `Pending Approvals` — never after the triggering event. The event is what
|
|
140
142
|
happened; the view is what's left to do. Reusing the event's name also collides in the shared
|
|
@@ -144,10 +146,11 @@ records an event directly.
|
|
|
144
146
|
list does the processor watch? What command does it fire? What if it fails or retries?"*
|
|
145
147
|
|
|
146
148
|
### 4. Translation
|
|
147
|
-
**Boundary crossing → command → event
|
|
148
|
-
system, or another bounded context) into
|
|
149
|
-
translation is a *reaction*: it **triggers a
|
|
150
|
-
independent questions shape which form it takes —
|
|
149
|
+
**Boundary crossing → command → event, the reaction and what it triggers together in one slice.**
|
|
150
|
+
An adapter translates data across a boundary (an external system, or another bounded context) into
|
|
151
|
+
the model's own language. Like an automation, a translation is a *reaction*: it **triggers a
|
|
152
|
+
command**, never records an event directly. Two independent questions shape which form it takes —
|
|
153
|
+
don't conflate them:
|
|
151
154
|
- **Trigger source:** does the input come from outside the model, or from the model's own state
|
|
152
155
|
pushed back out?
|
|
153
156
|
- **Durable artifact:** is there a queryable, persisted thing behind the trigger (a queue, topic,
|
|
@@ -167,13 +170,14 @@ The `em` shape only tracks the second question — trigger source doesn't change
|
|
|
167
170
|
it's scoped to the context/lane whose fact it represents, not by who committed it — the same
|
|
168
171
|
move as an Anti-Corruption-Layer boundary event in DDD — and stays legitimate as long as it
|
|
169
172
|
doesn't leak into the model's own domain vocabulary.
|
|
170
|
-
- `em` shape:
|
|
173
|
+
- `em` shape: exactly like an automation — the read model (durable-artifact form only) in the
|
|
174
|
+
slice before, then the reaction with its command and event, plus the read slice for the event:
|
|
171
175
|
```em
|
|
172
176
|
slice "Boundary" {
|
|
173
177
|
view Source from "..." # durable-artifact form only; omit if there's nothing persisted
|
|
174
|
-
translation T from "Source"
|
|
175
178
|
}
|
|
176
|
-
slice "Record It" {
|
|
179
|
+
slice "Record It" { # translation, command, and event all share this slice
|
|
180
|
+
translation T from "Source" # omit `from` entirely for the no-durable-artifact form
|
|
177
181
|
command C
|
|
178
182
|
event E @Context
|
|
179
183
|
}
|
|
@@ -182,7 +186,7 @@ The `em` shape only tracks the second question — trigger source doesn't change
|
|
|
182
186
|
ui Status Screen @Persona # ...and every read model needs a consumer
|
|
183
187
|
}
|
|
184
188
|
```
|
|
185
|
-
The translation
|
|
189
|
+
The translation never records the event directly — the command it triggers does.
|
|
186
190
|
- Socratic prompts: *"What boundary are we crossing, and which way? What outside system or context
|
|
187
191
|
feeds us (or do we feed)? In what format? How do we know its data is trustworthy/complete? What
|
|
188
192
|
internal **command** does it trigger, and what event does that command record?"*
|
|
@@ -237,9 +241,9 @@ each view to the events that feed it.
|
|
|
237
241
|
### Step 5 — Swimlanes & Apply the Patterns *(model)*
|
|
238
242
|
Organize elements into swimlanes: one **persona** row per actor, one **context** (bounded
|
|
239
243
|
context / aggregate) row per event family. Classify every slice as one of the **4 patterns** and
|
|
240
|
-
wire them correctly — especially **
|
|
241
|
-
(the
|
|
242
|
-
an event directly; it triggers a command.
|
|
244
|
+
wire them correctly — especially **share the slice between every automation or translation and the
|
|
245
|
+
command it triggers** (the read model it watches, if any, stays in the slice before). A translation
|
|
246
|
+
or automation never records an event directly; it triggers a command.
|
|
243
247
|
- Prompts: *"Which events belong together as one consistency boundary (aggregate)? Who owns this
|
|
244
248
|
data? Is this slice a state change, a view, an automation, or a translation?"*
|
|
245
249
|
- Output: a structurally complete model with swimlanes and pattern-correct slices.
|
|
@@ -258,8 +262,9 @@ dedicated `slice` phase writes the full rich spec (see `templates/slice.md`).
|
|
|
258
262
|
|
|
259
263
|
### Step 7 — Evaluate Completeness *(model)*
|
|
260
264
|
Walk the whole model with stakeholders and check for loose ends:
|
|
261
|
-
- Every **command** has something that **triggers** it — a `ui` in its slice, or
|
|
262
|
-
|
|
265
|
+
- Every **command** has something that **triggers** it — a `ui` in its slice, or the reaction that
|
|
266
|
+
triggers it, also in its slice. A command nothing points at is a write nobody can start.
|
|
267
|
+
`em validate` warns.
|
|
263
268
|
- Every **command** produces at least one **event**.
|
|
264
269
|
- Every **view** has at least one source event, **and a consumer** — a `ui` (headless: the `ui`
|
|
265
270
|
tagged to the API-caller persona), or a reaction watching it. Every *instance* of a repeated
|
|
@@ -273,11 +278,13 @@ Walk the whole model with stakeholders and check for loose ends:
|
|
|
273
278
|
- Every connection is one of the six legal pairs (`ui → command`, `command → event`,
|
|
274
279
|
`event → read model`, `read model → ui`, `read model → reaction`, `reaction → command`). No
|
|
275
280
|
arrow between two instances of one read model.
|
|
276
|
-
- Automations **and** translations are
|
|
277
|
-
reaction wired straight to an event
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
- Automations **and** translations are wired correctly (reaction → command → event, all in one
|
|
282
|
+
slice — never a reaction wired straight to an event, and never a reaction with no command at
|
|
283
|
+
all); translations cover every external input.
|
|
284
|
+
- Run `em validate` and resolve all errors and warnings. It catches illegal `arrow` kind pairs
|
|
285
|
+
(error), unread events (warning), and a reaction with no command in its slice (warning) — a
|
|
286
|
+
reaction wired straight to an event with no command anywhere is exactly what that last check
|
|
287
|
+
catches, so a clean `em validate` run does cover this case.
|
|
281
288
|
- Prompts: *"Is there any event nobody reads — and if the business genuinely never looks at it, why
|
|
282
289
|
are we recording it? Any command that just happens, with nobody and nothing asking for it? Any
|
|
283
290
|
screen with no way in or out? Any command that records nothing? Any
|
|
@@ -18,14 +18,14 @@ Static render: `em render {{model-name}}.em -o {{model-name}}.svg`
|
|
|
18
18
|
## Patterns legend
|
|
19
19
|
- **State Change** — UI → Command → Event
|
|
20
20
|
- **State View** — Event(s) → Read Model → UI
|
|
21
|
-
- **Automation** — Read Model
|
|
22
|
-
- **Translation** — External input (or Read Model) → Translation
|
|
21
|
+
- **Automation** — Read Model (slice before) → Processor + Command → Event, together
|
|
22
|
+
- **Translation** — External input (or Read Model, slice before) → Translation + Command → Event, together
|
|
23
23
|
|
|
24
24
|
Between them these are the only legal connections: `ui → command`, `command → event`,
|
|
25
25
|
`event → read model`, `read model → ui`, `read model → reaction`, `reaction → command`. A command
|
|
26
26
|
never reaches a read model directly — the event goes between them. Every slice is joined up at
|
|
27
|
-
both ends: something triggers each command (the screen it's issued from, or the reaction
|
|
28
|
-
|
|
27
|
+
both ends: something triggers each command (the screen it's issued from, or the reaction that
|
|
28
|
+
triggers it, also in this slice), and every event a command records is read by some read model — so each State
|
|
29
29
|
Change slice is paired with the State View slice that projects its event. A read model repeated
|
|
30
30
|
along the timeline (`view X again`) shows the same projection at a later point; the instances are
|
|
31
31
|
never connected to one another.
|