@eventmodelers/cli 1.0.21 → 1.0.22

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -110,6 +110,7 @@ READMODEL // Query result / materialized view
110
110
  SCENARIO // GWT scenario
111
111
  LANE // Timeline row
112
112
  SLICE_BORDER // Slice boundary marker
113
+ MARKDOWN // Free-text markdown note — the content type a `feedback` lane accepts (see §2)
113
114
  ```
114
115
 
115
116
  ---
@@ -283,6 +284,33 @@ Drop a node into a timeline cell. Validates placement rules. If the node was alr
283
284
 
284
285
  ---
285
286
 
287
+ ### Feedback lanes and MARKDOWN nodes (free-text notes)
288
+
289
+ A chapter has no `feedback` lane by default — add one first via the lanes endpoint above (`{"type": "feedback", "label": "Notes"}`), which returns a `laneId`. This is a normal row in `meta.timelineData.rows` (`type: "feedback"`) alongside `actor`/`interaction`/`swimlane`/`spec`.
290
+
291
+ Place a free-text markdown note in that lane the same way any other node is placed — a plain `node:created` event through `POST .../nodes/events` (§3), **not** the cell-drop endpoint above. `cellId` is `"<feedbackRowId>-<columnId>"`, same convention as every other lane:
292
+
293
+ ```json
294
+ {
295
+ "id": "<event-uuid>",
296
+ "eventType": "node:created",
297
+ "nodeId": "<node-uuid>",
298
+ "boardId": "<boardId>",
299
+ "timestamp": 1234567890,
300
+ "chapterId": "<chapterId>",
301
+ "cellId": "<feedbackRowId>-<columnId>",
302
+ "meta": {
303
+ "type": "MARKDOWN",
304
+ "title": "Modeling Reasoning — <Chapter Name>",
305
+ "description": "# Heading\n\nFull markdown body here — headings, lists, bold, etc. all render."
306
+ }
307
+ }
308
+ ```
309
+
310
+ The node's content lives in `meta.description` (a plain string of markdown source) — **not** `meta.content`; that field is silently accepted and stored but never rendered, producing a visibly empty note. There is no `fields[]` array on this element type, and no separate render/sketch call is needed (unlike SCREEN/HTML_SCREEN). `node.type` comes back as `"markdown"` (lowercase) on read.
311
+
312
+ ---
313
+
286
314
  ## 3. Nodes
287
315
 
288
316
  **File**: `src/slices/change/api-nodes/routes.ts`
@@ -314,6 +342,7 @@ interface NodeChangeEvent {
314
342
  backgroundColor?: string
315
343
  title?: string
316
344
  url?: string
345
+ linkedTo?: string // origin node id — marks this node as a linked copy (see below)
317
346
  // ...other node data fields
318
347
  // Do NOT set a "type" here — the server derives the node's render type from
319
348
  // meta.type automatically. Setting one yourself risks it being read as the
@@ -363,12 +392,29 @@ Auto-connect a node to its timeline neighbors — mirrors the frontend's auto-co
363
392
 
364
393
  Incompatible or already-connected neighbors are reported in `skipped`, not an error. Returns an empty result for nodes not placed on any timeline, or not a connectable element type (e.g. SCENARIO/spec nodes are never auto-connected).
365
394
 
395
+ **Known gap**: the "own column already has a SCREEN" guard checks only for a SCREEN specifically — not an AUTOMATION. Placing an AUTOMATION in a COMMAND's own column, with a SCREEN sitting in the previous column, wires *both* into the COMMAND, leaving it with two issuers. A command is never issued by more than one thing — see `place-element` Step 7c for the check-and-fix.
396
+
397
+ **Connections (both auto-connect and `set_connection`) only ever pair nodes on the same timeline** — a node in Chapter A can never be wired directly to a node in Chapter B, even when the type pair is otherwise valid (e.g. EVENT→READMODEL). No direct cross-timeline connection is possible.
398
+
399
+ **The supported workaround is a linked copy**: place a copy of the source EVENT into its own swimlane on the *consuming* timeline, with `node.data.linkedTo` set to the origin node's id (see `linkedTo` in the `NodeChangeEvent` shape above; `eventmodeling-checking-completeness` documents how to recognize one when reading the board — it's an intentional copy, never a duplicate to clean up). Once the copy exists on the consuming timeline, it's a same-timeline node like any other and can be wired normally (e.g. linked-EVENT→READMODEL→SCREEN) to satisfy that context's local data need. Only fall back to documenting an integration gap when a linked copy genuinely isn't the right shape for the need (e.g. the consuming context needs live/aggregate data no single event copy can represent).
400
+
366
401
  **Response**:
367
402
  - `200` — `{ connected: [{edgeId, source, target, created}], skipped: [{nodeId, reason}] }`
368
403
  - `404` — node not found
369
404
 
370
405
  ---
371
406
 
407
+ ### POST `/api/org/:orgId/boards/:boardId/connections`
408
+ Create a single type-checked directed edge between two existing nodes — the REST fallback for `set_connection`.
409
+
410
+ **Request body**: `{ source: string, target: string }` (node ids)
411
+
412
+ **Response**: `200`/`201` — `{ edgeId, source, target }` on success · `400` — the pair is not one of the allowed type combinations · `404` — a node id doesn't exist
413
+
414
+ **`EVENT → READMODEL` is exempt from column ordering** — an event in a later column can connect to a read model in an earlier column, and vice versa. A read model is a continuously-listening projection, not a point-in-time action, so it can be fed by an event anywhere on its timeline. Every other pair (`SCREEN → COMMAND`, `COMMAND → EVENT`, `READMODEL → SCREEN`, `READMODEL → AUTOMATION`, `AUTOMATION → COMMAND`) is still forward-only. If a connection you expect to work gets rejected, retry once before concluding it's blocked — a transient rejection has been observed on an otherwise-valid pair.
415
+
416
+ ---
417
+
372
418
  ## 4. Images
373
419
 
374
420
  **File**: `src/slices/change/api-images/routes.ts`
@@ -19,6 +19,8 @@ Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect`
19
19
  artifact — only build one when the user's request explicitly asks for a "storyline", "walkthrough",
20
20
  or "narrative". Never generate a storyline as a side effect of ordinary scenario elaboration.
21
21
 
22
+ **Exception — the mandatory read-model scenario pass** (see the "Per read model" section of the Quality Checklist below): when the user's request already frames read-model coverage as open to either form — e.g. "add scenarios for the read models, GWT or storylines", or the orchestrating skill's Step 7 gate, which requires exactly that — the explicit-ask condition is satisfied at that request level, not per read model. Within that pass, decide GWT vs. storyline **per read model** based on domain fit (does this read model's row genuinely walk through multiple states worth narrating?), not by asking again for every individual read model and not by defaulting to GWT for all of them just because that's the baseline elsewhere in this skill. Outside that pass — an ad-hoc "write scenarios for X" with no mention of storylines — the strict on-demand rule above still applies unchanged.
23
+
22
24
  A GWT scenario asserts one isolated transition: a single precondition, a single action, a single
23
25
  resulting outcome. A storyline instead narrates one specific use case as an ordered sequence of
24
26
  **beats**, where the *same* element (usually a read model) is walked through multiple states across
@@ -804,11 +806,18 @@ After posting, tell the user:
804
806
 
805
807
  **No command has only 2 scenarios unless all other types were reviewed and found inapplicable.**
806
808
 
809
+ **Per read model — this is a separate, equally mandatory pass, not an afterthought of the command pass above:**
810
+ - [ ] **Every READMODEL on the board has at least one view scenario** — GWT (`given`: source EVENTs, `when`: empty, `then`: the READMODEL) or a storyline. A model with dozens of command scenarios and 0 read-model scenarios is not a complete Step 7 — it's easy to walk away thinking coverage is thorough because the command side looks exhaustive, so check the read-model side explicitly before reporting this step done.
811
+ - [ ] **Population scenario** — the view shows correct data after its source event(s)
812
+ - [ ] **Removal/update scenario, where applicable** — a row disappears or changes (`expectEmptyList: true` for list-type views) after an event that supersedes it (expiry, return, archival, withdrawal, status change, etc.). `EVENT → READMODEL` is exempt from column ordering (see `learn-eventmodelers-api` §3), so a later event connecting back to an earlier-placed read model is normal — add the connection if it's missing rather than assuming the scenario is impossible. Only skip this scenario, with a documented gap (TASK comment), when the superseding event genuinely lives in a different chapter.
813
+ - [ ] **GWT vs. storyline decided per read model, not applied uniformly** — reach for a storyline wherever the *same* read model row genuinely walks through multiple states worth narrating; the rest of the read models in the same model may be correctly GWT-only. Don't default to one format for every read model just because it worked for the first one, and don't judge "multiple states" by counting *distinct connected event types* — that undercounts real candidates. **A single event type recurring with different data is just as valid a storyline driver as several different event types**: `AccountFunded($40)` then `AccountFunded($70)` walking a balance read model from $40 to $110 is exactly as strong a storyline as a multi-event lifecycle. In practice this means almost every list/aggregate read model qualifies — a titles list growing from one row to two as the same `TitleAdded`-shaped event recurs, a dashboard's counters incrementing as the same `CopyAdded` event recurs, are both genuine storylines, not "just" GWT territory. Ask "does replaying this read model's *actually connected* event(s) more than once produce an interesting accumulated/changed state?" — not "how many different event types feed this."
814
+ - [ ] **No redundancy or contradiction between a read model's GWT scenarios and its storyline** — if both exist for the same read model, read the storyline's beats before finalizing the GWTs. A GWT that asserts the same state a beat already shows is redundant (delete it); a GWT written without tracing the same causal sequence the storyline encodes can end up asserting something the storyline's beats actually contradict (e.g. claiming two entities coexist in a view when the storyline correctly shows one superseding the other) — delete or fix it, never leave a contradiction on the board.
815
+ - [ ] **Cross-context read models handled honestly** — if a read model's true source events live in a different chapter, `given` can't reference them (same-timeline-only, like connections); write the scenario with an empty `given` and say so explicitly in the scenario title, rather than silently omitting the scenario or fabricating a same-timeline event that isn't the real source
816
+
807
817
  **Format and posting:**
808
818
  - [ ] State preconditions are explicit in Given (not just "Given an order")
809
819
  - [ ] Actions are clear in When
810
820
  - [ ] Outcomes are verifiable in Then (event produced or rejection with reason)
811
- - [ ] Every view has at least one update scenario
812
821
  - [ ] All scenarios posted to board spec cells via the `/scenarios` API
813
822
  - [ ] **Workshop facilitation approach documented**
814
823
  - [ ] **All stakeholder roles (PO, Dev, QA, Domain Expert) perspectives captured**
@@ -13,7 +13,7 @@ allowed-tools:
13
13
 
14
14
  Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect` skill) — the curl blocks below are the fallback for sessions without MCP connected.
15
15
 
16
- Coordinates the 10-step Event Modeling workflow. Each step delegates to a
16
+ Coordinates the 11-step Event Modeling workflow. Each step delegates to a
17
17
  specialized skill — this skill holds the sequence, transition conditions, and
18
18
  what to carry forward between steps.
19
19
 
@@ -101,6 +101,10 @@ Screens placed during Step 3 (Storyboarding) are provisional positions. Steps 4
101
101
  ### Column insertion
102
102
  Use `POST /timelines/:tl/columns` with `{"index": N}` to insert a column at a specific position (shifts existing columns right). Do not use `{}` (append) when placing read models or view screens — always target the correct position.
103
103
 
104
+ ### Documenting decisions inline, at any step
105
+
106
+ Separate from the Step 11 chapter-level reasoning note: at **any** step (1–10), if that step makes a decision or assumption important enough that a later reader could otherwise misread the model, add a small MARKDOWN note in the **column where that decision applies** (same feedback-lane + MARKDOWN mechanics as Step 11 — see there for the exact calls). Use sparingly — this is for a genuine "why is it like this" moment (an assumption that fills a gap the brief left open, a rejected alternative, a non-obvious constraint), not routine narration of what a step did.
107
+
104
108
  ---
105
109
 
106
110
  ## Interview Phase
@@ -166,7 +170,7 @@ Also update the Interview Trail table row for this step (Status → Done, Key Ou
166
170
 
167
171
  After writing the summary, run `/compact` to clear the accumulated context before loading the next skill. The summary written above is the handoff — the next skill reads it from the file, not from the conversation history.
168
172
 
169
- This keeps each step's context lean and prevents token bloat from accumulating across all 9 steps.
173
+ This keeps each step's context lean and prevents token bloat from accumulating across all 11 steps.
170
174
 
171
175
  ---
172
176
 
@@ -266,9 +270,14 @@ if Conway's Law boundaries are not relevant to the project.
266
270
  Invoke `eventmodeling-elaborating-scenarios`.
267
271
 
268
272
  **Input**: Commands and read models.
269
- **Output to carry forward**: Given-When-Then specifications for each command
270
- and view, posted to the board spec cells.
271
- **Gate**: Every command has scenarios covering **all applicable types** from the elaborating-scenarios workflow — not just happy path + one error case. See the gate checklist below.
273
+ **Output to carry forward**: Given-When-Then specifications (or storylines, for
274
+ walkthrough-style coverage) for every command **and every read model**, posted
275
+ to the board spec cells.
276
+ **Gate**: Every command has scenarios covering **all applicable types** from
277
+ the elaborating-scenarios workflow — not just happy path + one error case —
278
+ **and every READMODEL on the board has at least one view scenario**. See the
279
+ gate checklist below. A command-only pass is an incomplete Step 7, even if
280
+ every command's coverage looks exhaustive.
272
281
 
273
282
  > **Do not reduce scenarios to a simple good-case / bad-case pair.** The `eventmodeling-elaborating-scenarios` skill defines a structured scenario workshop covering seven scenario types per command. All applicable types must be written before this step is complete.
274
283
 
@@ -283,7 +292,9 @@ and view, posted to the board spec cells.
283
292
 
284
293
  For each type, ask the relevant question against the business case and write a scenario if the situation can occur. Do not decide based on brevity — decide based on the domain.
285
294
 
286
- > The `eventmodeling-elaborating-scenarios` skill designs scenarios **and** posts them to the board. It uses `GET /timelines/$TL/spec-info` to resolve node IDs, then `POST /timelines/$TL/columns/$COL/scenarios` with all scenarios for that column in one call (array body). The SCENARIO spec node is created automatically. Ensure the timeline and column IDs are resolved and passed to the skill before invoking it.
295
+ > **Read models need scenarios too easy to forget since the seven types above are command-shaped.** Every READMODEL needs at least one view scenario (GWT or storyline); a read model with zero scenarios is as incomplete as a command with zero. `eventmodeling-elaborating-scenarios`'s own checklist covers the details connectivity rules, GWT-vs-storyline judgment per read model, and avoiding redundancy between a storyline and its GWTs don't re-derive those here, just enforce the gate.
296
+
297
+ > The `eventmodeling-elaborating-scenarios` skill designs scenarios **and** posts them to the board. It uses `GET /timelines/$TL/spec-info` to resolve node IDs, then `POST /timelines/$TL/columns/$COL/scenarios` with all scenarios for that column in one call (array body) — this applies identically whether the column holds a COMMAND or a READMODEL. The SCENARIO spec node is created automatically. Ensure the timeline and column IDs are resolved and passed to the skill before invoking it.
287
298
 
288
299
  ---
289
300
 
@@ -331,6 +342,76 @@ already had one.
331
342
 
332
343
  ---
333
344
 
345
+ ### Step 11: Document Reasoning
346
+
347
+ Not delegated to a separate skill — performed directly by this orchestrating skill, since the reasoning being documented is the *orchestrator's own* accumulated context across all prior steps, not something a single-step skill has visibility into.
348
+
349
+ **Input**: The complete, sliced, validated model (Steps 1–10) plus this session's own record of decisions made along the way — assumptions added beyond the literal brief, sequencing corrections, business rules deliberately encoded as scenarios rather than events, read-model sharing choices, and any cross-context/integration gaps found (e.g. during Step 6 or Step 9).
350
+
351
+ **Output to carry forward**: One MARKDOWN node per chapter, placed in that chapter's first column, containing the full modeling reasoning for that bounded context in as much detail as the session actually has to give — not a boilerplate template filled in thinly.
352
+
353
+ **Gate**: Every chapter on the board has exactly one reasoning MARKDOWN node in its first column, non-empty, written after the model for that chapter was already complete (so it can describe the *finished* shape, not a plan).
354
+
355
+ **Mechanics** — a chapter has no `feedback` lane by default; add one first, then place a MARKDOWN node in it:
356
+
357
+ 1. **Add a feedback lane** (once per chapter, skip if one already exists — check `meta.timelineData.rows` for `type === "feedback"` first):
358
+
359
+ **Prefer MCP:**
360
+ ```
361
+ mcp__eventmodelers__add_lane { "boardId": "$BOARD_ID", "timelineId": "$CHAPTER_ID", "type": "feedback", "label": "Notes" }
362
+ ```
363
+
364
+ **Fallback (no MCP):**
365
+ ```bash
366
+ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/timelines/$CHAPTER_ID/lanes" \
367
+ -H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: orchestrator" \
368
+ -H "Content-Type: application/json" \
369
+ -d '{"type":"feedback","label":"Notes"}'
370
+ # → { laneId, type, label, index, totalLanes }
371
+ ```
372
+
373
+ 2. **Resolve the first column's ID** — the leftmost entry in `meta.timelineData.columns` (same chapter fetch used throughout this workflow for row/column lookups).
374
+
375
+ 3. **Create the MARKDOWN node**, `cellId = "<feedbackLaneId>-<firstColumnId>"`:
376
+
377
+ **Prefer MCP:**
378
+ ```
379
+ mcp__eventmodelers__submit_node_events {
380
+ "boardId": "$BOARD_ID",
381
+ "events": [{
382
+ "id": "<event-uuid>", "eventType": "node:created", "nodeId": "<node-uuid>",
383
+ "boardId": "$BOARD_ID", "timestamp": 1234567890,
384
+ "chapterId": "$CHAPTER_ID", "cellId": "<feedbackLaneId>-<firstColumnId>",
385
+ "meta": { "type": "MARKDOWN", "title": "Modeling Reasoning — <Chapter Name>", "description": "<full markdown body>" }
386
+ }]
387
+ }
388
+ ```
389
+
390
+ **Fallback (no MCP):**
391
+ ```bash
392
+ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
393
+ -H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: orchestrator" \
394
+ -H "Content-Type: application/json" \
395
+ -d '[{"id":"<event-uuid>","eventType":"node:created","nodeId":"<node-uuid>","boardId":"<BOARD_ID>",
396
+ "timestamp":1234567890,"chapterId":"<CHAPTER_ID>","cellId":"<feedbackLaneId>-<firstColumnId>",
397
+ "meta":{"type":"MARKDOWN","title":"Modeling Reasoning — <Chapter Name>","description":"<full markdown body>"}}]'
398
+ ```
399
+
400
+ The note's body lives in **`meta.description`** as plain markdown source — headings, lists, bold, code fences, tables all render. **Not `meta.content`** — that field is accepted and stored without error but never rendered by the board UI, producing a visibly empty note; this was caught by comparing against a note authored directly in the UI, so treat it as confirmed, not a guess. There is no separate render/sketch call (unlike SCREEN/HTML_SCREEN) and no `fields[]` array on this element type.
401
+
402
+ **What the note should actually contain** — write for the next person (or next session) who opens this board cold, not for whoever just built it:
403
+ - **Scope**: what business process this chapter covers, and its stream roots (identity keys).
404
+ - **Assumptions added beyond the literal brief** — anything invented to fill a gap the requirements left open, and why (e.g. adding a resolution event so a state isn't a one-way trap door).
405
+ - **Business rules deliberately encoded as scenarios, not new events** — so a reader doesn't mistake a missing event for an oversight.
406
+ - **Sequencing or design corrections made mid-workflow** — e.g. a column reorder because an event's original placement implied the wrong causality.
407
+ - **Read model design rationale** — especially where one read model deliberately serves several screens/automations, so it doesn't read as a missing 1:1 mapping.
408
+ - **Any cross-context or integration gaps found** (Step 6 Conway's Law, or discovered incidentally, e.g. a same-timeline connection constraint blocking a needed cross-chapter data dependency) — state the finding and the viable resolutions, matching whatever TASK/QUESTION comment was also posted on the affected node.
409
+ - **Closing summary**: element counts and the validation verdict for this chapter's slice of the model.
410
+
411
+ If a chapter's story is genuinely simple, say so briefly rather than padding — but for any chapter with real design decisions behind it, this note is the place those decisions survive past the session that made them.
412
+
413
+ ---
414
+
334
415
  ## Final Output
335
416
 
336
417
  A complete, sliced event model consisting of:
@@ -344,10 +425,11 @@ A complete, sliced event model consisting of:
344
425
  - Completeness verification
345
426
  - Validation report with readiness verdict
346
427
  - Slice definitions marking every independently deployable feature boundary
428
+ - A Modeling Reasoning MARKDOWN node in each chapter's first column, documenting the design decisions, assumptions, and any integration gaps behind that chapter's model
347
429
 
348
430
  ### Optional Follow-on Skills
349
431
 
350
- These skills are not part of the 10-step main path but extend the model for
432
+ These skills are not part of the 11-step main path but extend the model for
351
433
  specific needs:
352
434
 
353
435
  - **`eventmodeling-designing-event-models`** — Use when stream identity,
@@ -364,12 +446,14 @@ specific needs:
364
446
  ## Quality Checklist
365
447
 
366
448
  - [ ] No elements stranded at 0,0 — every EVENT, COMMAND, READMODEL, SCREEN, and AUTOMATION has a valid `cellId` in its chapter
367
- - [ ] All 10 modeling steps completed — no step skipped without explicit reason
449
+ - [ ] All 11 modeling steps completed — no step skipped without explicit reason
368
450
  - [ ] Every COMMAND, READMODEL, and AUTOMATION has a matching slice definition on the board
451
+ - [ ] Every chapter has a Modeling Reasoning MARKDOWN node in its first column, written after that chapter's model was complete
369
452
  - [ ] Role Catalog exists with named human roles and system processors
370
453
  - [ ] Every command is attributed to a specific role from the Role Catalog
371
454
  - [ ] Every read model satisfies at least one UI or processor query need
372
455
  - [ ] At least one Given-When-Then scenario exists per command
456
+ - [ ] At least one view scenario (GWT or storyline) exists per READMODEL — not just per command
373
457
  - [ ] Completeness check shows no unresolved field traceability gaps
374
458
  - [ ] Validation returns PASS or PASS WITH WARNINGS with all critical issues resolved
375
459
  - [ ] Interview trail in `.trogonai/` updated with status of each completed step
@@ -80,6 +80,7 @@ Verify each swimlane has:
80
80
  ### 2. Consistency Checks
81
81
 
82
82
  - [ ] **Event-Stream Mapping**: Every event belongs to exactly one lane
83
+ - [ ] **Single Command Issuer**: Every command is issued by exactly one SCREEN or AUTOMATION — never two. Check each COMMAND node's inbound edges; more than one SCREEN/AUTOMATION wired into the same command is a CRITICAL violation (commonly an auto-connect artifact — see `place-element` Step 7c)
83
84
  - [ ] **Command Outcomes**: Every command produces events OR documents rejection
84
85
  - [ ] **Deterministic Projections**: State can only be derived one way from events
85
86
  - [ ] **No Side Effects in Projections**: Pure state reconstruction logic
@@ -192,6 +193,7 @@ Format findings as comments:
192
193
  | Orphaned events | Events no one listens to | Link to projections or commands |
193
194
  | No read models | Commands reading query/read models for validation | Add separate query read models; keep command state minimal |
194
195
  | Circular dependencies | Projection A depends on B, B on A | Redesign stream boundaries |
196
+ | Command issued by multiple things | COMMAND node has 2+ inbound SCREEN/AUTOMATION edges | Keep the deliberate same-column issuer, remove the rest via `set_connection` (`action: "remove"`) — see `place-element` Step 7c |
195
197
 
196
198
  ## Key Principles for Event Sourcing
197
199
 
@@ -233,6 +235,7 @@ A model is **ready for code generation** if:
233
235
  - [ ] State projection is deterministic from events
234
236
  - [ ] Commands validate against current state only
235
237
  - [ ] Each command either produces events or rejects (no silent failures)
238
+ - [ ] **No command has more than one inbound SCREEN/AUTOMATION edge (a command is never issued by more than one thing)**
236
239
  - [ ] Event causality/command-event mapping is clear
237
240
  - [ ] State transitions are documented
238
241
  - [ ] No direct references between lanes
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: eventmodeling-validating-event-models-checklist
3
- description: "Validate event-sourced CQRS models against 16 architectural checks across 7 phases. Identifies anti-patterns and confirms compliance with event sourcing principles. Use when reviewing event models for production readiness or after completing event modeling steps. Do not use for: reviewing incomplete or in-progress models (use eventmodeling-validating-event-models), or for elaborating new scenarios (use eventmodeling-elaborating-scenarios)."
3
+ description: "Validate event-sourced CQRS models against 17 architectural checks across 7 phases. Identifies anti-patterns and confirms compliance with event sourcing principles. Use when reviewing event models for production readiness or after completing event modeling steps. Do not use for: reviewing incomplete or in-progress models (use eventmodeling-validating-event-models), or for elaborating new scenarios (use eventmodeling-elaborating-scenarios)."
4
4
  allowed-tools:
5
5
  - Write
6
6
  - Bash
@@ -12,7 +12,7 @@ allowed-tools:
12
12
 
13
13
  Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect` skill) — the curl blocks below are the fallback for sessions without MCP connected.
14
14
 
15
- **Purpose**: Validate any event-sourced CQRS event model against 16 architectural checks across 7 phases. Identifies anti-patterns and confirms compliance with event sourcing principles.
15
+ **Purpose**: Validate any event-sourced CQRS event model against 17 architectural checks across 7 phases. Identifies anti-patterns and confirms compliance with event sourcing principles.
16
16
 
17
17
  **Applies To**: Any domain - e-commerce, banking, SaaS, marketplace, healthcare, etc.
18
18
 
@@ -24,7 +24,7 @@ Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect`
24
24
 
25
25
  **What It Does**:
26
26
  1. Reads current board state (EVENT, COMMAND, READMODEL nodes) as input
27
- 2. Systematically applies 16 validation checks across 7 phases
27
+ 2. Systematically applies 17 validation checks across 7 phases
28
28
  2. Identifies violations of event sourcing principles (domain-agnostic)
29
29
  3. Flags anti-patterns (calculations as events, non-entity streams, etc.)
30
30
  4. Verifies read model/event distinction
@@ -59,12 +59,13 @@ Use the board nodes as the model input. After the checklist, use `handle-comment
59
59
 
60
60
  ## Validation Phases (Domain-Agnostic)
61
61
 
62
- ### Phase 1: Event Stream & Command Handler State Validation (3 checks)
62
+ ### Phase 1: Event Stream & Command Handler State Validation (4 checks)
63
63
  - Check 1.1: Each event belongs to exactly one stream
64
64
  - Check 1.2: Each command handler owns its own [CommandHandler]State class
65
65
  - Check 1.3: No hard dependencies between command handlers (orchestrated via events only)
66
+ - Check 1.4: Each command is issued by exactly one thing — no COMMAND node has more than one inbound SCREEN/AUTOMATION edge
66
67
 
67
- **Anti-pattern to catch**: Sharing state across handlers or treating state as persistent aggregate
68
+ **Anti-pattern to catch**: Sharing state across handlers or treating state as persistent aggregate; a command wired from two issuers (commonly a `place-element` auto-connect artifact where the command's own column holds an AUTOMATION and the previous column's SCREEN also gets wired in — see `learn-eventmodelers-api` §3 auto-connect "Known gap")
68
69
 
69
70
  ### Phase 2: Event Quality Validation (3 checks)
70
71
  - Check 2.1: Events represent domain facts, not calculations
@@ -204,6 +205,24 @@ Reconstruct [CommandHandler]State on-demand
204
205
 
205
206
  **Why**: State is derived from events, never stored. Events are source of truth. This enables consistent replay, audit trails, and time-travel debugging.
206
207
 
208
+ ### 5. Command With Multiple Issuers
209
+ ```
210
+ ANTI-PATTERN:
211
+ FlagLoanOverdue (COMMAND) has two inbound edges:
212
+ - "Flag Overdue Loans" (AUTOMATION, same column)
213
+ - "Adjust Due Date" (SCREEN, previous column)
214
+ - Result: unclear who/what actually triggers the command; validation and UI-vs-automation
215
+ authority checks (e.g. Role & Actor Attribution) can no longer be answered
216
+
217
+ CORRECT:
218
+ FlagLoanOverdue (COMMAND) has exactly one inbound edge, from the AUTOMATION that owns it.
219
+ If the SCREEN's user genuinely needs to trigger the same outcome, that's a second,
220
+ distinctly-named command (or the SCREEN issuing it directly, with the automation removed) —
221
+ not two issuers sharing one command.
222
+ ```
223
+
224
+ **Why**: A command is never issued by more than one thing. Each command represents one specific trigger's decision to act — collapsing two triggers onto one command node hides which actor is actually responsible, and usually means either a naming/slice-boundary mistake or a stray auto-connect edge (`learn-eventmodelers-api` §3, "Known gap"). Fix by removing the extra edge via `set_connection` (`action: "remove"`), not by keeping both.
225
+
207
226
  ---
208
227
 
209
228
  ## Questions to Ask During Validation
@@ -233,7 +252,7 @@ Reconstruct [CommandHandler]State on-demand
233
252
  ## Success Criteria
234
253
 
235
254
  **Model is validated when**:
236
- - All 16 checks pass (or have documented workarounds)
255
+ - All 17 checks pass (or have documented workarounds)
237
256
  - No critical anti-patterns identified
238
257
  - All 3 final questions answer YES
239
258
  - Event sourcing principles clearly upheld
@@ -306,7 +325,7 @@ Running the checklist after Step 2 prevents wasting time on later steps if core
306
325
 
307
326
  ## Checklist Questions by Domain
308
327
 
309
- The skill applies the same 16 checks regardless of domain. Here's how to think about it in different contexts:
328
+ The skill applies the same 17 checks regardless of domain. Here's how to think about it in different contexts:
310
329
 
311
330
  **E-commerce domain**:
312
331
  - Events: OrderCreated, OrderConfirmed, PaymentAuthorized, OrderShipped
@@ -338,7 +357,7 @@ The principle is the same across all domains: **immutable facts as events, calcu
338
357
 
339
358
  ## Quality Checklist
340
359
 
341
- - [ ] All 16 checks evaluated — no check skipped without documented justification
360
+ - [ ] All 17 checks evaluated — no check skipped without documented justification
342
361
  - [ ] Every FAIL result includes the specific event, handler, or stream that violated the check
343
362
  - [ ] Anti-patterns identified by name with the exact model element that triggered the flag
344
363
  - [ ] Final verdict is one of: PASS / PASS WITH WARNINGS / FAIL — no ambiguous outcomes
@@ -359,6 +378,6 @@ The principle is the same across all domains: **immutable facts as events, calcu
359
378
 
360
379
  ## Validation Checklist Reference
361
380
 
362
- The 16-point checklist is defined in the **Validation Phases** section above.
381
+ The 17-point checklist is defined in the **Validation Phases** section above.
363
382
  Each check includes the anti-pattern to catch and questions to ask when evaluating your model.
364
383
 
@@ -446,6 +446,34 @@ Response: `{ "hashes": { "<event-uuid>": "<hash>" } }`
446
446
 
447
447
  ---
448
448
 
449
+ ## Step 7c — Verify the command has exactly one issuer
450
+
451
+ **A command is never issued by more than one thing.** Run this check whenever `elementType` is `SCREEN`, `AUTOMATION`, or `COMMAND` — placing any of these can trigger the server's fire-and-forget auto-connect (`learn-eventmodelers-api` §3), which wires `SCREEN→COMMAND` and `AUTOMATION→COMMAND` edges to type-compatible neighbors in the node's own column and the previous column.
452
+
453
+ **Why this can go wrong**: auto-connect only skips the previous column's SCREEN when the COMMAND's own column already has a SCREEN — it does not check for an AUTOMATION there. So a COMMAND whose own column holds an AUTOMATION, with a SCREEN sitting in the previous column, ends up wired from *both* — the automation (same column) and the screen (previous column) — and now looks issued by two things.
454
+
455
+ After placing, resolve the relevant COMMAND node (the one just placed, or the one in the same/adjacent column as the SCREEN/AUTOMATION just placed) and inspect its edges:
456
+
457
+ **Prefer MCP:**
458
+ ```
459
+ mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<COMMAND_NODE_ID>" }
460
+ ```
461
+
462
+ Count inbound edges where `target === COMMAND_NODE_ID` and the source node is type `SCREEN` or `AUTOMATION`.
463
+
464
+ - **0 or 1 such edge** → fine, nothing to do.
465
+ - **2 or more** → keep the edge whose source sits in the COMMAND's own column (the deliberate, same-slice issuer) and remove every other one:
466
+
467
+ ```
468
+ mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<extra-issuer-node-id>", "target": "<COMMAND_NODE_ID>", "action": "remove" }
469
+ ```
470
+
471
+ If it's not clear which edge is the deliberate one (e.g. neither source sits in the COMMAND's own column), do not guess — leave both edges and post a `QUESTION` comment on the COMMAND node via `handle-comment` instead, describing the ambiguity.
472
+
473
+ **Fallback (no MCP)**: there is no documented single-purpose REST endpoint for edge removal outside `/nodes/events`. Connect MCP via the `connect` skill first; if that's genuinely not possible, skip the auto-fix and post a `QUESTION` comment on the COMMAND node flagging the double issuer for manual resolution instead of fabricating a payload.
474
+
475
+ ---
476
+
449
477
  ## Step 8 — Report back
450
478
 
451
479
  Tell the user: