@eventmodelers/cli 1.0.36 → 1.0.38
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/package.json +1 -1
- package/shared/skills/learn-eventmodelers-api/SKILL.md +22 -13
- package/stacks/modeling-kit/templates/.claude/skills/add-next-slice/SKILL.md +2 -23
- package/stacks/modeling-kit/templates/.claude/skills/add-next-slice/references/api-fallback.md +11 -0
- package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +6 -57
- package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/references/api-fallback.md +68 -0
- package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +4 -61
- package/stacks/modeling-kit/templates/.claude/skills/attributes/references/api-fallback.md +39 -0
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +9 -53
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/references/api-fallback.md +63 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-applying-conways-law/SKILL.md +12 -380
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-applying-conways-law/references/examples.md +329 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +45 -290
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/api-fallback.md +97 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/examples.md +35 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +15 -410
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/references/api-fallback.md +22 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/references/examples.md +397 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-core-rules/SKILL.md +182 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/SKILL.md +148 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/references/api-fallback.md +21 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-event-models/SKILL.md +48 -448
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-event-models/references/examples.md +167 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +31 -380
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/references/api-fallback.md +31 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/references/examples.md +216 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +33 -393
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/api-fallback.md +79 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/examples.md +282 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +54 -454
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/references/api-fallback.md +67 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/references/examples.md +273 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-interview-protocol/SKILL.md +31 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/SKILL.md +37 -184
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/domain-patterns.md +58 -99
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/patterns.md +82 -163
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +86 -78
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/api-fallback.md +51 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/SKILL.md +5 -5
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/references/api-fallback.md +10 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +22 -51
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/api-fallback.md +41 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/examples.md +12 -9
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/patterns.md +1 -10
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +29 -382
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/references/api-fallback.md +77 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/references/examples.md +271 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-translating-external-events/SKILL.md +12 -349
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-translating-external-events/references/examples.md +306 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +69 -107
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/references/api-fallback.md +14 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +75 -160
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/references/api-fallback.md +14 -0
- package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +3 -110
- package/stacks/modeling-kit/templates/.claude/skills/examples/references/api-fallback.md +118 -0
- package/stacks/modeling-kit/templates/.claude/skills/handle-comment/SKILL.md +5 -25
- package/stacks/modeling-kit/templates/.claude/skills/handle-comment/references/api-fallback.md +35 -0
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/SKILL.md +9 -44
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/references/api-fallback.md +51 -0
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +34 -180
- package/stacks/modeling-kit/templates/.claude/skills/place-element/references/api-fallback.md +203 -0
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +14 -81
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/references/api-fallback.md +74 -0
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +4 -45
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/references/api-fallback.md +44 -0
- package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +19 -88
- package/stacks/modeling-kit/templates/.claude/skills/timeline/references/api-fallback.md +91 -0
- package/stacks/modeling-kit/templates/.claude/skills/update-prompt-status/SKILL.md +1 -9
- package/stacks/modeling-kit/templates/.claude/skills/update-prompt-status/references/api-fallback.md +14 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-integrating-legacy-systems/SKILL.md +0 -674
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/snapshotting.md +0 -204
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md
CHANGED
|
@@ -11,14 +11,14 @@ allowed-tools:
|
|
|
11
11
|
|
|
12
12
|
> **Before doing anything else**, invoke the `connect` skill — if not already connected — to resolve `TOKEN`, `BOARD_ID`, `ORG_ID`, and `BASE_URL`. Do not proceed until it has completed. Consult `learn-eventmodelers-api` only if you need to look up a specific endpoint or field this file doesn't cover — don't load it eagerly.
|
|
13
13
|
|
|
14
|
+
This step applies the shared element rules in **`eventmodeling-core-rules`** — read it once per session if you haven't already; it defines what a COMMAND/EVENT/READMODEL/SCREEN/AUTOMATION is, how each is named, and the anti-patterns to reject, so this step doesn't restate them.
|
|
15
|
+
|
|
14
16
|
Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect` skill) — the curl blocks below are the fallback for sessions without MCP connected.
|
|
15
17
|
|
|
16
18
|
## Interview Phase (Optional)
|
|
17
19
|
|
|
18
20
|
**When to Interview**: Skip if the user has clearly identified: read model queries needed by UI, processor needs, and refresh patterns. Interview when unclear which data queries are critical or how frequently they're accessed.
|
|
19
21
|
|
|
20
|
-
**Interview Strategy**: Establish query patterns and identify any calculations before designing read models. The most common architecture error at this step is modeling recalculated state as an event — identifying calculated fields upfront prevents that anti-pattern.
|
|
21
|
-
|
|
22
22
|
### Critical Questions
|
|
23
23
|
|
|
24
24
|
1. **Query Patterns** (Impact: Determines which read models are needed and their update frequency)
|
|
@@ -31,58 +31,7 @@ Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect`
|
|
|
31
31
|
- Why it matters: Common mistake to model calculations as events; identifying them upfront prevents architecture errors
|
|
32
32
|
- Follow-up triggers: For each calculated field mentioned → confirm "This recalculates as source data changes, so it belongs in a read model projection — does that match your expectation?"
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
**Conditional Entry**:
|
|
37
|
-
```
|
|
38
|
-
If user has provided:
|
|
39
|
-
- UI screens with data needs mapped to event sources
|
|
40
|
-
- AND processor query needs documented
|
|
41
|
-
- AND calculated/aggregated fields identified as read models (not events)
|
|
42
|
-
|
|
43
|
-
Then: Skip interview, proceed directly to read model design
|
|
44
|
-
|
|
45
|
-
Else: Conduct interview
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
**Phase 1: Query Pattern Mapping** (Question 1)
|
|
49
|
-
- Identify which UI screens and processors need which data
|
|
50
|
-
- Typically every screen needs some kind of data, same for automations.
|
|
51
|
-
- Establish freshness requirements per consumer
|
|
52
|
-
- Determine if any queries require real-time consistency
|
|
53
|
-
|
|
54
|
-
**Phase 2: Calculation Detection** (Question 2)
|
|
55
|
-
- Surface any aggregated or computed values
|
|
56
|
-
- Confirm they are projections, not events
|
|
57
|
-
- Prevent the calculation-as-event anti-pattern before design begins
|
|
58
|
-
|
|
59
|
-
### Capturing Interview Findings
|
|
60
|
-
|
|
61
|
-
Append findings to the project's event modeling file:
|
|
62
|
-
|
|
63
|
-
**File**: `.trogonai/interviews/[project-name]/EVENTMODELING.md`
|
|
64
|
-
|
|
65
|
-
Use Write tool to add/update this section:
|
|
66
|
-
|
|
67
|
-
```markdown
|
|
68
|
-
## 5. Identifying Outputs (eventmodeling-identifying-outputs)
|
|
69
|
-
|
|
70
|
-
### Query Patterns
|
|
71
|
-
[From Q1: Which consumers need what freshness? Real-time vs. periodic?]
|
|
72
|
-
|
|
73
|
-
### Calculated Fields Identified
|
|
74
|
-
[From Q2: Which fields are aggregated/calculated? Confirmed as read models?]
|
|
75
|
-
|
|
76
|
-
### Read Model Summary
|
|
77
|
-
- Real-time read models: [list]
|
|
78
|
-
- Near-real-time read models: [list]
|
|
79
|
-
- Calculation-as-event anti-patterns caught: [list or "None"]
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Update Interview Trail:
|
|
83
|
-
```markdown
|
|
84
|
-
| 5 | eventmodeling-identifying-outputs | Done | Read model catalog, query patterns, calculation classification |
|
|
85
|
-
```
|
|
34
|
+
Follow **`eventmodeling-interview-protocol`** to run this interview and record its findings — label this step "**5. Identifying Outputs** (`eventmodeling-identifying-outputs`)". Findings should cover: query patterns (which consumers need what freshness), calculated/aggregated fields identified (and confirmed as read models, not events), and a read model summary by freshness tier.
|
|
86
35
|
|
|
87
36
|
---
|
|
88
37
|
|
|
@@ -174,181 +123,13 @@ InventoryLevelView
|
|
|
174
123
|
|
|
175
124
|
## Workflow
|
|
176
125
|
|
|
177
|
-
Given commands and events, identify all outputs:
|
|
178
|
-
|
|
179
|
-
### 1. Map Event Data to UI Screens
|
|
180
|
-
For each screen, identify source events:
|
|
181
|
-
|
|
182
|
-
```
|
|
183
|
-
Screen: Order Status View
|
|
184
|
-
Displays data from events:
|
|
185
|
-
orderId ← OrderCreated event
|
|
186
|
-
customerId ← OrderCreated event
|
|
187
|
-
items ← OrderCreated event
|
|
188
|
-
total ← OrderCreated event
|
|
189
|
-
status ← OrderConfirmed event (or OrderCancelled)
|
|
190
|
-
confirmedAt ← OrderConfirmed event
|
|
191
|
-
paymentId ← PaymentAuthorized event
|
|
192
|
-
shipmentId ← OrderShipped event
|
|
193
|
-
shippedAt ← OrderShipped event
|
|
194
|
-
|
|
195
|
-
This screen is a projection of these events:
|
|
196
|
-
- OrderCreated
|
|
197
|
-
- OrderConfirmed
|
|
198
|
-
- PaymentAuthorized
|
|
199
|
-
- OrderShipped
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### 2. Define Read Models
|
|
203
|
-
Create optimized views from event data:
|
|
204
|
-
|
|
205
|
-
```
|
|
206
|
-
ReadModel: OrderStatusView
|
|
207
|
-
Purpose: UI displays current order status
|
|
208
|
-
Events subscribed: OrderCreated, OrderConfirmed, PaymentAuthorized, OrderShipped, OrderCancelled
|
|
209
|
-
Data:
|
|
210
|
-
{
|
|
211
|
-
orderId: string (from OrderCreated)
|
|
212
|
-
customerId: string (from OrderCreated)
|
|
213
|
-
status: enum (from events: Draft → Confirmed → Authorized → Shipped → Delivered)
|
|
214
|
-
createdAt: Date (from OrderCreated)
|
|
215
|
-
confirmedAt: Date (from OrderConfirmed)
|
|
216
|
-
paymentId: string (from PaymentAuthorized)
|
|
217
|
-
shipmentId: string (from OrderShipped)
|
|
218
|
-
shippedAt: Date (from OrderShipped)
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### 3. Document Event → Data Mapping
|
|
223
|
-
Show exactly what data each event provides:
|
|
224
|
-
|
|
225
|
-
```
|
|
226
|
-
Event: OrderCreated
|
|
227
|
-
Provides to UI/Processors:
|
|
228
|
-
orderId
|
|
229
|
-
customerId
|
|
230
|
-
items[]
|
|
231
|
-
total
|
|
232
|
-
shippingAddress
|
|
233
|
-
createdAt
|
|
234
|
-
|
|
235
|
-
Event: OrderConfirmed
|
|
236
|
-
Provides to UI/Processors:
|
|
237
|
-
orderId (link to stream)
|
|
238
|
-
paymentMethod (user selected method)
|
|
239
|
-
confirmedAt (timestamp)
|
|
240
|
-
paymentId (payment system reference)
|
|
241
|
-
|
|
242
|
-
Event: PaymentAuthorized
|
|
243
|
-
Provides to UI/Processors:
|
|
244
|
-
orderId (link to stream)
|
|
245
|
-
paymentId
|
|
246
|
-
authCode
|
|
247
|
-
authorizedAt (timestamp)
|
|
248
|
-
amount (verified amount)
|
|
249
|
-
|
|
250
|
-
Event: OrderShipped
|
|
251
|
-
Provides to UI/Processors:
|
|
252
|
-
orderId (link to stream)
|
|
253
|
-
shipmentId
|
|
254
|
-
shippedAt (timestamp)
|
|
255
|
-
carrier (shipping company)
|
|
256
|
-
trackingNumber (for delivery tracking)
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### 4. Create Output Catalog
|
|
260
|
-
List all read models:
|
|
261
|
-
|
|
262
|
-
```
|
|
263
|
-
ReadModel Catalog: Order System
|
|
264
|
-
|
|
265
|
-
1. OrderStatusReadModel
|
|
266
|
-
Purpose: UI shows current order status
|
|
267
|
-
Events: OrderCreated, OrderConfirmed, PaymentAuthorized, OrderShipped, OrderCancelled
|
|
268
|
-
Data: orderId, status, createdAt, confirmedAt, paymentId, shipmentId
|
|
269
|
-
Consumed by:
|
|
270
|
-
- Order Status screen (UI)
|
|
271
|
-
- Customer Dashboard (UI)
|
|
272
|
-
- Order Processing Processor (decides if can ship)
|
|
273
|
-
|
|
274
|
-
2. OrderListReadModel
|
|
275
|
-
Purpose: UI lists all orders for a customer
|
|
276
|
-
Events: OrderCreated, OrderConfirmed, OrderCancelled
|
|
277
|
-
Data: orderId, customerId, total, status, createdAt
|
|
278
|
-
Consumed by:
|
|
279
|
-
- Customer Order History (UI)
|
|
280
|
-
- Order Search/Filter (UI)
|
|
281
|
-
|
|
282
|
-
3. PaymentStatusReadModel
|
|
283
|
-
Purpose: UI shows payment status
|
|
284
|
-
Events: OrderConfirmed, PaymentAuthorized, PaymentFailed
|
|
285
|
-
Data: orderId, paymentId, status, authCode, failureReason, timestamp
|
|
286
|
-
Consumed by:
|
|
287
|
-
- Payment Status screen (UI)
|
|
288
|
-
- Accounting Processor (reconciliation)
|
|
289
|
-
|
|
290
|
-
4. ShipmentTrackingReadModel
|
|
291
|
-
Purpose: UI shows tracking information
|
|
292
|
-
Events: OrderShipped, DeliveryConfirmed
|
|
293
|
-
Data: orderId, shipmentId, trackingNumber, carrier, shippedAt, estimatedDelivery
|
|
294
|
-
Consumed by:
|
|
295
|
-
- Order Tracking screen (UI)
|
|
296
|
-
- Customer notifications (Processor)
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### 5. Identify Missing Data
|
|
300
|
-
Check if all UI needs are covered:
|
|
301
|
-
|
|
302
|
-
```
|
|
303
|
-
Question: What if UI needs "estimated delivery date"?
|
|
304
|
-
Event: OrderShipped has carrier + trackingNumber
|
|
305
|
-
Action needed: Add estimatedDelivery to OrderShipped event
|
|
306
|
-
(or compute from carrier info)
|
|
307
|
-
|
|
308
|
-
Question: What if UI needs to show "payment method" on status?
|
|
309
|
-
Event: OrderConfirmed has paymentMethod
|
|
310
|
-
Action needed: Include paymentMethod in relevant read models
|
|
311
|
-
|
|
312
|
-
Question: What if UI needs "item descriptions"?
|
|
313
|
-
Event: OrderCreated has items[]
|
|
314
|
-
But: items[] only has productId
|
|
315
|
-
Action needed: Enrich with product descriptions from catalog
|
|
316
|
-
(via join with product service)
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### 6. Processor Outputs
|
|
320
|
-
Identify what processors consume:
|
|
321
|
-
|
|
322
|
-
```
|
|
323
|
-
Processor: Inventory System
|
|
324
|
-
Consumes from read models:
|
|
325
|
-
- Orders in "PaymentAuthorized" status
|
|
326
|
-
- Items and quantities needed
|
|
327
|
-
Produces commands:
|
|
328
|
-
- ReserveInventory
|
|
329
|
-
|
|
330
|
-
Processor: Fulfillment System
|
|
331
|
-
Consumes from read models:
|
|
332
|
-
- Orders in "InventoryReserved" status
|
|
333
|
-
- Items and quantities
|
|
334
|
-
- Shipping address
|
|
335
|
-
Produces commands:
|
|
336
|
-
- CreateShipment
|
|
337
|
-
|
|
338
|
-
Processor: Notification System
|
|
339
|
-
Consumes from read models:
|
|
340
|
-
- OrderCreated (sends confirmation)
|
|
341
|
-
- OrderConfirmed (sends receipt)
|
|
342
|
-
- OrderShipped (sends tracking)
|
|
343
|
-
- DeliveryConfirmed (sends thank you)
|
|
344
|
-
Does not produce commands (info-only)
|
|
345
|
-
```
|
|
126
|
+
Given commands and events, identify all outputs: for each screen, trace which events its data comes from; define a read model per component; document the event→data mapping; catalog the resulting read models; check for UI needs with no event source; and identify what processors consume. A full worked example of this reasoning (Order/Payment domain) is in `references/examples.md`.
|
|
346
127
|
|
|
347
128
|
## Output Format
|
|
348
129
|
|
|
349
|
-
Instead of writing a markdown document, **place each READMODEL (and any missing AUTOMATION) on the board** using the `node:created` API. Screens are typically already placed from Step 3 (storyboarding) as one plain screen per screen state — Step 3 does not pre-split anything, so working out how many components a screen actually has is this step's job (Step 5a below), not something to re-derive from storyboarding's output. Do not re-place an existing screen unless one is clearly missing. Automations are placed here
|
|
130
|
+
Instead of writing a markdown document, **place each READMODEL (and any missing AUTOMATION) on the board** using the `node:created` API. Screens are typically already placed from Step 3 (storyboarding) as one plain screen per screen state — Step 3 does not pre-split anything, so working out how many components a screen actually has is this step's job (Step 5a below), not something to re-derive from storyboarding's output. Do not re-place an existing screen unless one is clearly missing. Automations are placed here only in the rare case where analysis reveals a processor that reads state and issues commands but was missed entirely in Step 4 — if that happens, immediately apply `eventmodeling-designing-automation-chains`'s rules to it (todo-list read model, translation chain if externally triggered) before continuing, rather than leaving it for Step 5i's defensive re-check to catch.
|
|
350
131
|
|
|
351
|
-
This step proceeds as a sequence of lettered sub-steps: identify each screen's components (5a),
|
|
132
|
+
This step proceeds as a sequence of lettered sub-steps: identify each screen's components (5a), break multi-component screens apart into copies (5c), then design and place one read model per component (5d onward). Every automation's todo-list read model and any translation chain is designed in **Step 4b** (`eventmodeling-designing-automation-chains`), immediately after Step 4 — not here. This step only ever designs screen-facing read models; Step 5i below re-checks automations defensively, not as this step's primary job.
|
|
352
133
|
|
|
353
134
|
> **CRITICAL: Every READMODEL node MUST include `meta.fields` with a `mapping` on every field.** A read model without fields — or with fields that lack `mapping` — has no data lineage and cannot be traced back to its source events.
|
|
354
135
|
|
|
@@ -372,7 +153,7 @@ Automations follow:
|
|
|
372
153
|
READ MODEL → AUTOMATION → COMMAND → EVENT
|
|
373
154
|
```
|
|
374
155
|
|
|
375
|
-
Treat any screen or automation without an incoming read model as a gap. A screen may be exempt if it provably needs no prior state at all (e.g., a blank registration form) — an **automation is never exempt**: every automation gets a todo-list read model, even a trivial one that opens and closes within the same slice (
|
|
156
|
+
Treat any screen or automation without an incoming read model as a gap. A screen may be exempt if it provably needs no prior state at all (e.g., a blank registration form) — an **automation is never exempt**: every automation gets a todo-list read model, even a trivial one that opens and closes within the same slice. For automations, that read model was already designed in **Step 4b** (`eventmodeling-designing-automation-chains`); this step's job is limited to the defensive re-check in Step 5i below.
|
|
376
157
|
|
|
377
158
|
### Step 5a — Enumerate consumers and identify components
|
|
378
159
|
|
|
@@ -380,7 +161,7 @@ Treat any screen or automation without an incoming read model as a gap. A screen
|
|
|
380
161
|
|
|
381
162
|
Read models exist to serve the elements already on the board:
|
|
382
163
|
- Every **view screen** (output/read model screen) needs at least one read model to supply its data.
|
|
383
|
-
- Every **automation** needs at least one read model to read from — its todo list. This applies to every automation without exception, not only ones that visibly "decide" something;
|
|
164
|
+
- Every **automation** needs at least one read model to read from — its todo list. This applies to every automation without exception, not only ones that visibly "decide" something; this was already handled in Step 4b (`eventmodeling-designing-automation-chains`) for every automation placed in Step 4.
|
|
384
165
|
- Every **command/input screen** needs a read model unless it is a blank creation form with no prior state to display (this is the rare exception, not the rule).
|
|
385
166
|
|
|
386
167
|
For each SCREEN node, look at its rendered layout and its `meta.fields` and identify its components — groups of fields/UI elements a user would perceive as one area: a stats tile, a list below it, a summary card, a detail panel, a status column in a table, etc. **Most screens genuinely have exactly one component — do not force a split.** A screen has more than one component only when a user would point at two separate areas and describe them as different things.
|
|
@@ -391,42 +172,9 @@ Once a component's fields are genuinely homogeneous (every field needs the same
|
|
|
391
172
|
|
|
392
173
|
After this step is done, **every SCREEN and every AUTOMATION on the board must be connected to at least one read model** via a `READMODEL → SCREEN` or `READMODEL → AUTOMATION` connection, and every screen identified above as having 2+ components must have been broken apart per Step 5c before any read model is placed. If a screen or automation has no incoming read model connection, it is a gap — either a read model is missing or the connection arrow is missing.
|
|
393
174
|
|
|
394
|
-
> **Placement rule**: A read model must be placed
|
|
395
|
-
|
|
396
|
-
### Step 5b — Identify todo-list read models for automations
|
|
397
|
-
|
|
398
|
-
**Every AUTOMATION needs a todo-list read model — this is not optional, and there is no "pure relay" exemption.** An automation is a processor: it reacts to events, decides what work is outstanding, and issues commands to get that work done. The read model that tells it what's outstanding is its **todo list** — a queue of pending work items, not a snapshot of current entity state.
|
|
399
|
-
|
|
400
|
-
**The pattern**: one or more events *open* an item on the todo list (something now needs doing); one or more events *close* it (the work is done — remove the item). A todo list can be opened and closed by more than one event type on either side, and the set of opening events doesn't need to match the set of closing events in count or shape — whatever the domain calls for.
|
|
175
|
+
> **Placement rule**: A read model must be placed immediately upstream of the SCREEN or AUTOMATION it serves — sharing that column when possible (a SCREEN with a free interaction row), or one column to the left when not (any AUTOMATION; a SCREEN whose column is already occupied). Do not place a read model with no screen or automation in the very next column — doing so creates an orphaned read model that will never have a consumer.
|
|
401
176
|
|
|
402
|
-
**
|
|
403
|
-
- Todo list: **NotificationsToSend** — one row per pending notification.
|
|
404
|
-
- `CustomerRegistered` **opens** a row (a notification now needs sending).
|
|
405
|
-
- `NotificationSent` (the event this automation's own resulting command produces) **closes** that row (removes it — the list only ever shows outstanding work).
|
|
406
|
-
- The automation (`Send Welcome Notification`) reads `NotificationsToSend`, and for every open row issues `SendNotification`.
|
|
407
|
-
|
|
408
|
-
Even an automation that looks like a "pure signal relay" still has a todo list — model it anyway: it documents that the automation is idempotent/complete once its own event fires, and keeps every automation consistent with the same `READMODEL → AUTOMATION → COMMAND → EVENT` pattern instead of silently exempting some as "too simple to need one."
|
|
409
|
-
|
|
410
|
-
**There is no such thing as an invisible or informal "signal" — a trigger is always a real EVENT node, placed in a second swimlane when it belongs to another system.**
|
|
411
|
-
|
|
412
|
-
**An automation can only ever be directly triggered by an internal event — never by another system's event.** A "trigger" arriving from a second swimlane is not itself the thing that drives your domain's work; it first has to be *translated* into an internal event. Do not model this as one automation whose todo list is opened by the external EVENT and that also does the real work (e.g. an automation reading a todo list opened by `ReservationRequested` from another system's swimlane and directly issuing `ReserveCopy`) — that lets an external system trigger domain work with no translation step, which this model doesn't allow. **Apply this now, in Step 5** — Conway's Law (Step 6) only confirms the boundary, it doesn't introduce the chain. When an integration trigger comes from another team's system, model it as **two chained automations**, never one:
|
|
413
|
-
|
|
414
|
-
1. **Translation automation** — converts the external fact into an internal one; the other system's own decision logic is out of scope. Its todo list is opened by the external EVENT and closed by the internal EVENT its own command produces — the one and only place an external EVENT may open a todo list.
|
|
415
|
-
- **Three separate columns**, left to right: `[external EVENT] → [todo-list READMODEL] → [AUTOMATION + COMMAND + internal EVENT]`. Never crammed into one or two — the "one EVENT per column" rule applies here too.
|
|
416
|
-
- **Name the internal EVENT for its business meaning, not the transport** — usually the same name as the external EVENT (e.g. external `CopyReserved` → internal `CopyReserved`; the swimlane already shows which is which), never a mechanical `<X>SignalReceived`/`<X>RequestReceived` suffix. Same for the automation/command: `Record Reservation`/`RecordReservation`, not `Record Reservation Signal`/`RecordReservationSignal`.
|
|
417
|
-
- Its command and event carry no business decision — they only exist to produce the internal fact the next automation needs.
|
|
418
|
-
2. **Worker automation** — the one that does the actual work (the domain reaction the process is really about, e.g. `ReserveCopy`). Its todo list is opened **only** by the internal EVENT the translation automation produced (this chapter's own swimlane) — never by the external EVENT directly — and closed by whatever event marks that work done.
|
|
419
|
-
|
|
420
|
-
Wire the todo lists the standard way for each automation separately: for the translation automation, the external EVENT (second swimlane) **opens** the row and its own resulting internal EVENT **closes** it; for the worker automation, that same internal EVENT **opens** its row and its own resulting EVENT **closes** it. `EVENT → READMODEL` connections from both swimlanes are unaffected by which swimlane the event sits in.
|
|
421
|
-
|
|
422
|
-
**Fields**: a todo-list read model's fields describe the pending item — the identity it's about (e.g. `customerId`) plus enough context to act on it (e.g. `email`, `notificationType`). Do not add a `status` field to mark items done — a todo list's "open" state is *membership in the list itself* (the row exists at all), not a status flag on a row that never leaves. If the same underlying data is also useful with an explicit status column for a different consumer, that is a different read model, not this one.
|
|
423
|
-
|
|
424
|
-
**Connections and placement**: the todo-list read model goes in the interaction lane, same column as its automation (actor lane) — the same `READMODEL → AUTOMATION` placement covered in Step 5f below.
|
|
425
|
-
- Connect READMODEL → AUTOMATION **first** (the automation reads its own todo list) — the closing connection below is only accepted once this edge exists.
|
|
426
|
-
- Connect every **opening** EVENT → READMODEL.
|
|
427
|
-
- Connect every **closing** EVENT → READMODEL — including the automation's own resulting event, even though that event is produced by the command this same automation issues. This is not a backward arrow: `EVENT → READMODEL` connections are exempt from column ordering when the read model already feeds an AUTOMATION (see `learn-eventmodelers-api` §3) — this todo-list read model qualifies because of the `READMODEL → AUTOMATION` edge above. A read model in this shape is a live projection, not a frozen snapshot — a later event closing an earlier-opened item is the normal case, not an exception to reach for only when convenient.
|
|
428
|
-
|
|
429
|
-
After this step, every AUTOMATION on the board must have an incoming `READMODEL → AUTOMATION` connection to a todo-list read model — treat a connectionless automation exactly like a connectionless screen with no exemption: a gap to fix, not something to note and move past.
|
|
177
|
+
> **Automation todo-list read models are designed in Step 4b, not here.** The full pattern — every automation's todo-list read model, the "no invisible signal" rule, and the two-chained-automation translation requirement for externally-triggered automations — now lives in `eventmodeling-designing-automation-chains` (Step 4b), which runs immediately after Step 4, before this step. If Step 4b ran, every automation already on the board has its todo-list read model wired; Step 5i below only re-checks this defensively. The rare exception is an automation discovered only now, during output analysis (see "Output Format" above) — if that happens, apply `eventmodeling-designing-automation-chains`'s rules to it directly rather than re-deriving them here.
|
|
430
178
|
|
|
431
179
|
### Step 5c — Break apart multi-component screens into copies
|
|
432
180
|
|
|
@@ -435,7 +183,7 @@ After this step, every AUTOMATION on the board must have an incoming `READMODEL
|
|
|
435
183
|
- Each copy is the **same page**, so it **keeps the same screen name/title** — do not rename copies after their component (e.g. don't title one copy `"Librarian Dashboard — Statistics"` and another `"Librarian Dashboard — Recently Added"`; both stay `"Librarian Dashboard"`).
|
|
436
184
|
- Distinguish copies visually, not by name: **mark/highlight the one component each copy is about**, using the `html-screen` skill's native Marks feature — see that skill's "Marks" section for the full mechanism. In short: the component of interest gets `data-em-mark-id`/`em-mark em-mark-<colorhex>` baked onto it in the page HTML, paired with a matching `meta.marks` entry (`{id, color, pageIndex, blurOutside: true}`) that blurs every other top-level section. Never hand-roll this with inline `filter`/`outline`/`opacity` CSS — that does not match how the app itself renders a mark.
|
|
437
185
|
- Use the `html-screen` skill to produce each copy: pass it the original screen's markup, explicitly asking it to mark/highlight the one component to keep crisp and blur the rest (this satisfies `html-screen`'s "only when the user explicitly asks for one" condition for its Marks feature — the ask comes from this step). This is the skill's job — don't hand-roll the markup here.
|
|
438
|
-
- Place each copy in its own column,
|
|
186
|
+
- Place each copy in its own column, the **same column** as the read model that will feed it (insert a new column immediately before if that copy's column is already occupied) — this is normally a different column per copy, since each component typically has a different natural source event.
|
|
439
187
|
|
|
440
188
|
Why this matters, beyond tidiness:
|
|
441
189
|
- **It prevents backward arrows.** A single screen-wide read model is forced to aggregate from whatever events each of its components needs, which often means reaching back across many columns to events scattered throughout the timeline — and the read model can only sit in one column, so some of those connections end up spanning a wide gap or, worse, pushing the read model's column later than some of its screen's other consumers require. Splitting by component lets each narrower read model sit close to its own natural source event(s), keeping every `EVENT → READMODEL` arrow short and forward.
|
|
@@ -489,9 +237,9 @@ Every field must also set `"cardinality"` — use `"Single"` unless the field ge
|
|
|
489
237
|
}
|
|
490
238
|
```
|
|
491
239
|
|
|
492
|
-
Read models go in the `interaction` lane
|
|
240
|
+
Read models go in the `interaction` lane. For a **SCREEN**, the primary read model shares the SCREEN's column (READMODEL in interaction row, SCREEN in actor row of the same column) — unless that column's interaction row is already occupied (e.g. a command/input screen, where the COMMAND already sits there), in which case the read model goes one column to the left instead. For an **AUTOMATION**, the read model always goes one column to the left, never the same column — an automation's own column already holds the COMMAND it issues. If a consumer needs more than one read model, only the primary one gets this placement; every additional read model goes further left still.
|
|
493
241
|
|
|
494
|
-
> **Timeline alignment rule**: Place the read model in the same column as its consumer screen
|
|
242
|
+
> **Timeline alignment rule**: Place the read model in the same column as its consumer screen when that column is free, or one column before its consumer (screen or automation) when it isn't. Insert the new column immediately **before** the consumer's column (`{"index": consumerColumnIndex}`, shifting the consumer right) rather than after — the read model must sit upstream of (to the left of) the element it feeds. Do not append read model columns to the end of the timeline — doing so severs the visual left→right flow from data projection to UI consumption.
|
|
495
243
|
|
|
496
244
|
### Step 5f — Placing READMODEL and AUTOMATION nodes with `cellId` (Mandatory)
|
|
497
245
|
|
|
@@ -499,14 +247,16 @@ Read models go in the `interaction` lane — **in the same column as the SCREEN
|
|
|
499
247
|
|
|
500
248
|
**For a READMODEL** (interaction lane):
|
|
501
249
|
|
|
502
|
-
**Prefer MCP** — `place_element` collapses finding/creating the empty interaction cell
|
|
250
|
+
**Prefer MCP** — `place_element` collapses finding/creating the empty interaction cell and creating the node into one call, but it does **not** resolve an occupied target column for you: passing an already-occupied `columnIndex` fails outright rather than auto-inserting a column before it (it only auto-extends when `columnIndex` is *past* the current column count — a different situation). **Target column depends on the consumer type**:
|
|
251
|
+
- **SCREEN**: target the screen's own column index. First check whether that column's interaction row is already occupied (e.g. a command/input screen) — `get_node` with `projection: "cells"` on the chapter. If it is, insert a new column immediately before the screen first (`add_column` with `beforeNodeId` set to the screen's own node id), then pass *that* new column's index to `place_element` instead of the screen's original one.
|
|
252
|
+
- **AUTOMATION**: target `consumerColumnIndex - 1` (one column to the *left* of the automation) — never the automation's own column index, since it already holds the COMMAND the automation issues.
|
|
503
253
|
```
|
|
504
254
|
mcp__eventmodelers__place_element {
|
|
505
255
|
"boardId": "<BOARD_ID>",
|
|
506
256
|
"timelineId": "<CHAPTER_ID>",
|
|
507
257
|
"elementType": "READMODEL",
|
|
508
258
|
"title": "ActiveReservationView",
|
|
509
|
-
"columnIndex": <
|
|
259
|
+
"columnIndex": <consumerScreenColumnIndex> // or <automationColumnIndex - 1> for an AUTOMATION consumer
|
|
510
260
|
}
|
|
511
261
|
```
|
|
512
262
|
Then set `meta.fields` (with `mapping`/`generated`/`cardinality`) on the returned node id:
|
|
@@ -520,45 +270,17 @@ mcp__eventmodelers__submit_node_events {
|
|
|
520
270
|
}]
|
|
521
271
|
}
|
|
522
272
|
```
|
|
523
|
-
To determine the consumer's column index beforehand, or to check whether a specific interaction cell is already occupied (there is no `cellId` filter on `get_nodes` — see the note under "Wire connections" below), fetch the chapter and read its cell map:
|
|
273
|
+
To determine the consumer's column index beforehand, or to check whether a specific interaction cell is already occupied (there is no `cellId` filter on `get_nodes` — see the note under "Wire connections" below), fetch the chapter and read its cell map — `projection: "cells"` returns just `{rows, columns, cells}`, not the whole chapter node:
|
|
524
274
|
```
|
|
525
|
-
mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>" }
|
|
526
|
-
# →
|
|
275
|
+
mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>", "projection": "cells" }
|
|
276
|
+
# → rows (find "interaction"/"actor" rows) and cells (sparse; absent id = empty)
|
|
527
277
|
```
|
|
528
278
|
|
|
529
|
-
**Fallback (no MCP)
|
|
279
|
+
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5f — Placing a READMODEL node (full manual sequence)".
|
|
530
280
|
|
|
531
|
-
|
|
532
|
-
```bash
|
|
533
|
-
curl -s -H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" \
|
|
534
|
-
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID"
|
|
535
|
-
# → timelineData.rows — find the row where type === "interaction"
|
|
536
|
-
```
|
|
537
|
-
2. Check if the interaction cell is already occupied (existing COMMAND):
|
|
538
|
-
```bash
|
|
539
|
-
curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<interactionRowId>-<columnId>" \
|
|
540
|
-
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID"
|
|
541
|
-
```
|
|
542
|
-
If a COMMAND occupies that cell, insert a new column immediately after (`{"index": currentIndex + 1}`) and use that column's ID instead.
|
|
543
|
-
3. `cellId = interactionRow.id + "-" + columnId`
|
|
544
|
-
4. Create the READMODEL:
|
|
545
|
-
```bash
|
|
546
|
-
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
547
|
-
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: identifying-outputs" \
|
|
548
|
-
-H "Content-Type: application/json" \
|
|
549
|
-
-d '[{
|
|
550
|
-
"id": "<event-uuid>",
|
|
551
|
-
"eventType": "node:created",
|
|
552
|
-
"nodeId": "<node-uuid>",
|
|
553
|
-
"boardId": "<BOARD_ID>",
|
|
554
|
-
"timestamp": 1234567890,
|
|
555
|
-
"chapterId": "<CHAPTER_ID>",
|
|
556
|
-
"cellId": "<interactionRowId>-<columnId>",
|
|
557
|
-
"meta": {"type": "READMODEL", "title": "ActiveReservationView", "fields": [...]}
|
|
558
|
-
}]'
|
|
559
|
-
```
|
|
281
|
+
**Pass `autoConnect: false` on every `place_element` / `node:created` call in this step.** Output read-model columns are inserted right next to automation-chain and command columns whose events are *not* the read model's sources — the default auto-connect would wire the new READMODEL to the nearest event on its left, which is exactly the stray-edge cleanup this step has repeatedly generated. Suppress it here and wire every `EVENT → READMODEL` / `READMODEL → SCREEN` / `READMODEL → AUTOMATION` edge explicitly in Step 5h's `set_connections` batch. After the batch, run `validate_model` (`{boardId, chapterId}`) — its `backward-arrows` and `readmodel-sources` findings confirm the wiring is what you intended and no stray edge slipped in.
|
|
560
282
|
|
|
561
|
-
**For an AUTOMATION** (actor lane
|
|
283
|
+
**For an AUTOMATION** (actor lane) — its READMODEL always goes **one column to its left**, never the same column: the automation's own column already holds the COMMAND it issues (interaction row), so the read model can't also live there.
|
|
562
284
|
|
|
563
285
|
**Prefer MCP** — same `place_element` call with `"elementType": "AUTOMATION"`, then `submit_node_events` for fields, as above.
|
|
564
286
|
|
|
@@ -571,27 +293,26 @@ mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>"
|
|
|
571
293
|
|
|
572
294
|
### Step 5g — Preventing backward arrows (mandatory pre-placement check)
|
|
573
295
|
|
|
574
|
-
The timeline must always progress left-to-right. A `READMODEL → SCREEN` connection going right-to-left is a layout error.
|
|
296
|
+
The timeline must always progress left-to-right, or downward within the same column. A `READMODEL → SCREEN` connection going right-to-left is a layout error.
|
|
575
297
|
|
|
576
|
-
The correct layout is: **READMODEL
|
|
298
|
+
The correct layout is: **READMODEL and its (primary) view SCREEN share the same column** — READMODEL in the interaction row, SCREEN in the actor row, a downward connection. The read model only sits in a separate column, immediately **before** the screen's, when the screen's column is already unavailable to it (its interaction row is occupied by something else, e.g. a COMMAND on a command/input screen). If a screen displays more than one read model, only the primary read model shares the screen's column — every additional read model goes further left, never to the right. Before placing each read model, find the view screen it serves and verify the column order:
|
|
577
299
|
|
|
578
300
|
```
|
|
579
|
-
For each view screen S that queries this read model:
|
|
580
|
-
If column(S)
|
|
581
|
-
→
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
→ Gap between read model and screen. Move the read model to column(S) - 1, or move the screen to column(READMODEL) + 1.
|
|
301
|
+
For each view screen S that queries this read model as its primary read model:
|
|
302
|
+
If column(S)'s interaction row is free:
|
|
303
|
+
→ Place the READMODEL in column(S) itself (same column as S). No adjustment needed.
|
|
304
|
+
If column(S)'s interaction row is already occupied:
|
|
305
|
+
→ Insert a new column immediately before column(S) and place the READMODEL there instead.
|
|
306
|
+
mcp__eventmodelers__add_column { "boardId": "<BOARD_ID>", "timelineId": "<TL>", "beforeNodeId": "<S's node id>" }
|
|
307
|
+
(fallback, no MCP: POST /timelines/:tl/columns {"index": N} with N computed by hand),
|
|
308
|
+
then node:changed to update any node whose cell needs to move.
|
|
309
|
+
If the read model ends up more than one column away from S with nothing in between:
|
|
310
|
+
→ Gap between read model and screen. Close it: move the read model into column(S) (if free) or column(S) - 1.
|
|
590
311
|
```
|
|
591
312
|
|
|
592
|
-
**View screens
|
|
313
|
+
**View screens normally share the column of the (primary) read model they display** — either because they were placed there in Step 3, or because the read model was placed into the screen's own column just now (per the check above). A screen's own position is never moved to resolve a read model placement conflict — when the shared column isn't available, the **read model** gets a new column immediately before the screen's, not the other way around (this preserves the screen's narrative order from Step 3).
|
|
593
314
|
|
|
594
|
-
**The same rule applies to `EVENT → READMODEL`.** If a later event needs to update data a read model already feeds to a SCREEN, do not connect that later event back into the existing read model — the platform only accepts an `EVENT → READMODEL` backward connection when the read model already has a `READMODEL → AUTOMATION` edge (the todo-list pattern from Step
|
|
315
|
+
**The same rule applies to `EVENT → READMODEL`.** If a later event needs to update data a read model already feeds to a SCREEN, do not connect that later event back into the existing read model — the platform only accepts an `EVENT → READMODEL` backward connection when the read model already has a `READMODEL → AUTOMATION` edge (the todo-list pattern from Step 4b, `eventmodeling-designing-automation-chains`). For any read model feeding a SCREEN, resolve the update the same way Step 5c resolves multi-component screens: place a **new copy of the read model** in (or immediately after) the later event's column, connect the later event forward into that copy, and place a matching copy of the same screen there — same title, updated data, optionally re-marked/highlighted via `html-screen`'s Marks feature. Never link the new copy back to the earlier read model instance.
|
|
595
316
|
|
|
596
317
|
### Step 5h — Wire connections after placing each READMODEL (and its SCREEN)
|
|
597
318
|
|
|
@@ -599,43 +320,26 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
|
|
|
599
320
|
|
|
600
321
|
1. **EVENT → READMODEL** — find the primary source EVENT node in the swimlane row of the same column.
|
|
601
322
|
|
|
602
|
-
**Prefer MCP** — `get_nodes` has no `cellId` filter (only `type`); look up occupancy via the chapter's cell map instead, then connect with the type-checked edge tool (auto-corrects direction, skips duplicates):
|
|
323
|
+
**Prefer MCP** — `get_nodes` has no `cellId` filter (only `type`); look up occupancy via the chapter's cell map instead (`projection: "cells"`), then connect with the type-checked edge tool (auto-corrects direction, skips duplicates):
|
|
603
324
|
```
|
|
604
|
-
mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>" }
|
|
605
|
-
# → read
|
|
325
|
+
mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>", "projection": "cells" }
|
|
326
|
+
# → read cells["<swimlaneRowId>-<columnId>"] for the occupying node id
|
|
606
327
|
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<eventNodeId>", "target": "<readmodelNodeId>", "action": "connect" }
|
|
607
328
|
```
|
|
608
329
|
|
|
609
|
-
**Fallback (no MCP):**
|
|
610
|
-
```bash
|
|
611
|
-
curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<swimlaneRowId>-<columnId>" \
|
|
612
|
-
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: eventmodeling-identifying-outputs"
|
|
613
|
-
```
|
|
614
|
-
Connect it:
|
|
615
|
-
```bash
|
|
616
|
-
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/connections" \
|
|
617
|
-
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: eventmodeling-identifying-outputs" \
|
|
618
|
-
-H "Content-Type: application/json" \
|
|
619
|
-
-d '{"source":"<eventNodeId>","target":"<readmodelNodeId>"}'
|
|
620
|
-
```
|
|
330
|
+
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.1 — Wire EVENT → READMODEL".
|
|
621
331
|
|
|
622
|
-
2. **READMODEL → SCREEN** — connect to the existing SCREEN node in the actor row of the
|
|
332
|
+
2. **READMODEL → SCREEN** — connect to the existing SCREEN node in the actor row of the same column (or the column immediately after, only when that column wasn't available to the read model — screens are typically already placed from Step 3).
|
|
623
333
|
|
|
624
334
|
**Prefer MCP:**
|
|
625
335
|
```
|
|
626
336
|
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<readmodelNodeId>", "target": "<screenNodeId>", "action": "connect" }
|
|
627
337
|
```
|
|
628
338
|
|
|
629
|
-
**Fallback (no MCP):**
|
|
630
|
-
```bash
|
|
631
|
-
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/connections" \
|
|
632
|
-
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: eventmodeling-identifying-outputs" \
|
|
633
|
-
-H "Content-Type: application/json" \
|
|
634
|
-
-d '{"source":"<readmodelNodeId>","target":"<screenNodeId>"}'
|
|
635
|
-
```
|
|
339
|
+
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.2 — Wire READMODEL → SCREEN".
|
|
636
340
|
|
|
637
341
|
3. **READMODEL → AUTOMATION** — if the read model is consumed by an automatic process (scheduler, background job, external trigger), place the AUTOMATION node and connect it:
|
|
638
|
-
- Place the AUTOMATION in the automation lane, in the column immediately to the right of its read model
|
|
342
|
+
- Place the AUTOMATION in the automation lane, in the column immediately to the right of its read model — unlike a view screen, this is unconditional for automations: the automation's own column always holds the COMMAND it issues, so its read model never shares that column.
|
|
639
343
|
- Then connect:
|
|
640
344
|
|
|
641
345
|
**Prefer MCP:**
|
|
@@ -643,16 +347,12 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
|
|
|
643
347
|
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<readmodelNodeId>", "target": "<automationNodeId>", "action": "connect" }
|
|
644
348
|
```
|
|
645
349
|
|
|
646
|
-
**Fallback (no MCP):**
|
|
647
|
-
```bash
|
|
648
|
-
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/connections" \
|
|
649
|
-
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: eventmodeling-identifying-outputs" \
|
|
650
|
-
-H "Content-Type: application/json" \
|
|
651
|
-
-d '{"source":"<readmodelNodeId>","target":"<automationNodeId>"}'
|
|
652
|
-
```
|
|
350
|
+
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.3 — Wire READMODEL → AUTOMATION".
|
|
653
351
|
|
|
654
352
|
Skip a connection silently if the target cell is empty. Log each created arrow: `→ connected EVENT→READMODEL "OrderPlaced"→"OrderStatusView"`, `→ connected READMODEL→SCREEN "OrderStatusView"→"Order Status Screen"`, or `→ connected READMODEL→AUTOMATION "OrderStatusView"→"Fulfillment Processor"`.
|
|
655
353
|
|
|
354
|
+
If this step is processing more than one read model in the same pass, collect every connection resolved above (across all of them) into one `set_connections` call instead of one `set_connection` per pair — this was the single largest source of individual tool calls in this step. Pass `compact: true` on that call: with `autoConnect: false` set at placement, every edge here is one you're deliberately creating, so a `{connected, existed, removed, notFound, failed, errors}` tally is all you need back — not a row per edge.
|
|
355
|
+
|
|
656
356
|
4. **Document the reasoning for each connected event** — for every EVENT → READMODEL edge wired in step 1 (including any added later, e.g. via Step 5g's backward-connection exemption or a Step 5c/copy pattern), record why that event feeds this read model: which field(s) it sets or updates, and why. Use one MARKDOWN note per read model, in that read model's own column (same feedback-lane + MARKDOWN mechanics as `eventmodeling-orchestrating-event-modeling`'s "Documenting decisions inline, at any step" / Step 11 — add the chapter's feedback lane first if it doesn't already exist, then place the note at `cellId = "<feedbackLaneId>-<readModelColumnId>"`). Extend the existing note (don't create a second one) when the read model later gains another connected event.
|
|
657
357
|
|
|
658
358
|
Example body:
|
|
@@ -670,133 +370,33 @@ Do not declare Step 5 complete on the strength of the read models you happened t
|
|
|
670
370
|
|
|
671
371
|
1. Does it now have an incoming `READMODEL → SCREEN` or `READMODEL → AUTOMATION` connection?
|
|
672
372
|
2. If it's a SCREEN and not connected — is it a provably blank creation form with no prior state? State the reason in one line (e.g. `"Register Account" screen: blank form, no prior state — exempt`). This exemption applies to screens only.
|
|
673
|
-
3. If it's an AUTOMATION and not connected, it is **never** exempt —
|
|
373
|
+
3. If it's an AUTOMATION and not connected, it is **never** exempt — this should already be resolved by Step 4b (`eventmodeling-designing-automation-chains`); if it isn't, apply that skill's rules now to identify its todo-list read model.
|
|
674
374
|
4. If a SCREEN is neither connected nor exempt, it is an **unresolved gap**. Fix it now: design the missing read model (pulling from its `meta.fields`/`mapping` as above) and wire the connection. Do not move to Step 6 with an unresolved gap silently carried forward — either fix it or explicitly flag it to the user as accepted debt.
|
|
675
375
|
5. Does any screen still carry more than one component undivided (a Step 5a/5c miss)? If so, break it apart now per Step 5c before counting it as resolved.
|
|
676
376
|
6. **Re-fetch every READMODEL too** and run the >3-events heuristic (`eventmodeling-orchestrating-event-modeling`) on each one field by field — including read models a MARKDOWN note already justified as a roll-up. A prior note documents one field's irreducible fan-in; it does not exempt the rest of that node's fields from this check. The failure mode this catches: a wide-fan-in field (e.g. live per-copy availability) bundled together with a cheap, low-fan-in identity/fact field (e.g. a title set by 1-2 events) that has nothing to do with the roll-up — that pairing is always two read models, never one, no matter how the note reads.
|
|
677
377
|
7. **Every READMODEL has its event-reasoning MARKDOWN note (Step 5h.4)**, and that note accounts for *every* inbound `EVENT → READMODEL` edge on the node — not just the one from when it was first placed. If a read model gained a connected event later and the note wasn't extended, fix it now rather than carrying the gap forward.
|
|
678
378
|
|
|
379
|
+
Run `validate_model` (`{boardId, chapterId}`) once as the mechanical half of this pass — its `readmodel-sources` findings list every READMODEL with no inbound EVENT, `screen-collisions` flags any column that ended up with two screens, and `backward-arrows` catches a `READMODEL → SCREEN` that points left. Then do the judgement checks above (blank-form exemptions, the >3-events heuristic, note completeness) that the tool can't make.
|
|
380
|
+
|
|
679
381
|
List the result of this pass (connected / exempt / fixed) for every screen and automation checked — this list is the evidence the orchestrator's Step 5 gate ("every screen data need is satisfied by a read model") actually holds, not just an assumption.
|
|
680
382
|
|
|
681
383
|
After all read models, screens, automations, and connections are in place, present the Read Model Catalog summary as text to the user.
|
|
682
384
|
|
|
683
385
|
---
|
|
684
386
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
```markdown
|
|
688
|
-
# Outputs: [Domain Name]
|
|
689
|
-
|
|
690
|
-
## Read Models Summary
|
|
691
|
-
|
|
692
|
-
| ReadModel | Purpose | Events | Consumed By |
|
|
693
|
-
|-----------|---------|--------|-------------|
|
|
694
|
-
| OrderStatus | Show order state | OrderCreated, OrderConfirmed | UI, Processor |
|
|
695
|
-
| OrderList | List orders | OrderCreated, OrderCancelled | UI |
|
|
696
|
-
| PaymentStatus | Payment info | OrderConfirmed, PaymentAuthorized | UI, Accounting |
|
|
697
|
-
| Shipment Tracking | Track delivery | OrderShipped, DeliveryConfirmed | UI, Notifications |
|
|
698
|
-
|
|
699
|
-
---
|
|
700
|
-
|
|
701
|
-
## Detailed Read Models
|
|
702
|
-
|
|
703
|
-
### ReadModel: OrderStatusView
|
|
704
|
-
|
|
705
|
-
**Purpose**: Order Status screen displays current order state
|
|
706
|
-
|
|
707
|
-
**Events subscribed**:
|
|
708
|
-
- OrderCreated
|
|
709
|
-
- OrderConfirmed
|
|
710
|
-
- PaymentAuthorized
|
|
711
|
-
- OrderShipped
|
|
712
|
-
- OrderCancelled
|
|
713
|
-
- DeliveryConfirmed
|
|
714
|
-
|
|
715
|
-
**Data**:
|
|
716
|
-
```
|
|
717
|
-
{
|
|
718
|
-
orderId: string
|
|
719
|
-
customerId: string
|
|
720
|
-
status: 'Draft' | 'Confirmed' | 'Authorized' | 'Shipped' | 'Delivered' | 'Cancelled'
|
|
721
|
-
items: Array<{productId, quantity, unitPrice}>
|
|
722
|
-
total: number
|
|
723
|
-
shippingAddress: Address
|
|
724
|
-
|
|
725
|
-
createdAt: Date
|
|
726
|
-
confirmedAt: Date
|
|
727
|
-
paymentId: string
|
|
728
|
-
paymentMethod: 'card' | 'transfer'
|
|
729
|
-
authorizedAt: Date
|
|
730
|
-
|
|
731
|
-
shipmentId: string
|
|
732
|
-
carrier: string
|
|
733
|
-
trackingNumber: string
|
|
734
|
-
shippedAt: Date
|
|
735
|
-
estimatedDelivery: Date
|
|
736
|
-
}
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
**Update Logic**:
|
|
740
|
-
- OrderCreated: Insert with status='Draft'
|
|
741
|
-
- OrderConfirmed: Update status='Confirmed'
|
|
742
|
-
- PaymentAuthorized: Update status='Authorized', set paymentId
|
|
743
|
-
- OrderShipped: Update status='Shipped', set shipmentId, carrier, trackingNumber
|
|
744
|
-
- DeliveryConfirmed: Update status='Delivered'
|
|
745
|
-
- OrderCancelled: Update status='Cancelled'
|
|
746
|
-
|
|
747
|
-
**Consumed By**:
|
|
748
|
-
- Order Status Screen (displays)
|
|
749
|
-
- Order Processing Processor (checks status)
|
|
750
|
-
- Notification System (sends updates)
|
|
751
|
-
|
|
752
|
-
--- [Repeat for each read model]
|
|
753
|
-
|
|
754
|
-
---
|
|
755
|
-
|
|
756
|
-
## Data Completeness Check
|
|
757
|
-
|
|
758
|
-
### Events → UI Needs
|
|
759
|
-
|
|
760
|
-
Verify all UI needs have event sources:
|
|
761
|
-
|
|
762
|
-
| UI Need | Event Source | Status |
|
|
763
|
-
|---------|-------------|--------|
|
|
764
|
-
| Order status | OrderConfirmed, OrderShipped | |
|
|
765
|
-
| Tracking number | OrderShipped | |
|
|
766
|
-
| Order items | OrderCreated | |
|
|
767
|
-
| Estimated delivery | OrderShipped | |
|
|
768
|
-
| Cancellation reason | OrderCancelled | |
|
|
769
|
-
|
|
770
|
-
### Missing Data
|
|
771
|
-
|
|
772
|
-
Identify UI needs without event sources:
|
|
773
|
-
- None identified
|
|
774
|
-
|
|
775
|
-
---
|
|
776
|
-
|
|
777
|
-
## Processor Consumption
|
|
778
|
-
|
|
779
|
-
### Processors and their reads:
|
|
780
|
-
|
|
781
|
-
| Processor | Reads From | Writes Commands |
|
|
782
|
-
|-----------|-----------|-----------------|
|
|
783
|
-
| Inventory | OrderStatusView (Authorized) | ReserveInventory |
|
|
784
|
-
| Fulfillment | OrderStatusView (InventoryReserved) | CreateShipment |
|
|
785
|
-
| Notification | OrderStatusView (all) | None (info-only) |
|
|
786
|
-
| Accounting | PaymentStatusView | None (reporting) |
|
|
787
|
-
```
|
|
387
|
+
Older versions of this skill wrote the read model catalog as a markdown document rather than placing nodes on the board — that legacy template is kept in `references/examples.md` for reference only; it is not the actual output mechanism.
|
|
788
388
|
|
|
789
389
|
## Quality Checklist
|
|
790
390
|
|
|
791
391
|
### Read Model Design
|
|
792
392
|
- [ ] **Typical pattern applied**: most screens follow `READ MODEL → SCREEN → COMMAND → EVENT`
|
|
793
393
|
- [ ] **Every SCREEN from storyboarding is connected to at least one read model** (via `READMODEL → SCREEN`); only blank creation forms may be exempt — verified via the mandatory per-node pass above, not assumed
|
|
794
|
-
- [ ] **Every AUTOMATION from storyboarding is connected to at least one todo-list read model** (via `READMODEL → AUTOMATION`, Step
|
|
795
|
-
- [ ] **No automation's todo list is opened directly by another system's (second-swimlane) EVENT unless that automation is itself the translation automation** (Step
|
|
394
|
+
- [ ] **Every AUTOMATION from storyboarding is connected to at least one todo-list read model** (via `READMODEL → AUTOMATION`, Step 4b) — no exemption for automations, unlike screens; even a simple relay automation gets one
|
|
395
|
+
- [ ] **No automation's todo list is opened directly by another system's (second-swimlane) EVENT unless that automation is itself the translation automation** (Step 4b) — an automation doing the actual domain work is only ever opened by an internal event; a second-swimlane EVENT feeding straight into a work automation's todo list is a missing translation automation
|
|
796
396
|
- [ ] **No read model is placed without a connected SCREEN or AUTOMATION consumer**
|
|
797
397
|
- [ ] **No read model spans more than one component** — a screen with N distinct components gets N read models and N highlighted screen copies (same screen name, one component crisp per copy), not one screen-wide read model
|
|
798
398
|
- [ ] **Every multi-component screen was broken apart in Step 5c** — each copy keeps the original screen's name, differs only in which component is marked/highlighted
|
|
799
|
-
- [ ] **Every automation's todo-list read model identifies its opening and closing events** (Step
|
|
399
|
+
- [ ] **Every automation's todo-list read model identifies its opening and closing events** (Step 4b) — including the automation's own resulting event as a closing event where applicable, not just the triggering event
|
|
800
400
|
- [ ] **A field's genuinely irreducible wide fan-in is documented per-field** (inline MARKDOWN note, per "Documenting decisions inline") — and that note is never treated as clearing every other field on the same read model from the >3-events check; a cheap identity/fact field bundled alongside a wide roll-up field is always split out, never excused by the roll-up's own note
|
|
801
401
|
- [ ] **Every read model has an event-reasoning MARKDOWN note** (Step 5h.4) covering every connected event and which field(s) it sets or updates
|
|
802
402
|
- [ ] Every read model has clear purpose
|