@eventmodelers/cli 1.0.149 → 1.0.151

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.
Files changed (38) hide show
  1. package/package.json +1 -1
  2. package/shared/skills/learn-eventmodelers-api/SKILL.md +8 -0
  3. package/stacks/axon/templates/.claude/skills/build-automation/SKILL.md +2 -0
  4. package/stacks/axon/templates/.claude/skills/build-state-change/SKILL.md +2 -0
  5. package/stacks/axon/templates/.claude/skills/build-state-view/SKILL.md +2 -0
  6. package/stacks/classic/templates/.claude/skills/build-automation/SKILL.md +2 -0
  7. package/stacks/classic/templates/.claude/skills/build-state-change/SKILL.md +2 -0
  8. package/stacks/classic/templates/.claude/skills/build-state-view/SKILL.md +2 -0
  9. package/stacks/kurrent/templates/.claude/skills/build-automation/SKILL.md +2 -0
  10. package/stacks/kurrent/templates/.claude/skills/build-state-change/SKILL.md +2 -0
  11. package/stacks/kurrent/templates/.claude/skills/build-state-view/SKILL.md +2 -0
  12. package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +3 -1
  13. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +1 -1
  14. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-core-rules/SKILL.md +3 -4
  15. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/SKILL.md +1 -1
  16. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +18 -0
  17. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +35 -40
  18. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/api-fallback.md +3 -3
  19. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +11 -25
  20. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-information-completeness/SKILL.md +65 -0
  21. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +15 -46
  22. package/stacks/modeling-kit/templates/.claude/skills/html-screen/SKILL.md +2 -11
  23. package/stacks/modeling-kit/templates/.claude/skills/html-screen/references/api-fallback.md +1 -1
  24. package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +1 -1
  25. package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +2 -2
  26. package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/references/api-fallback.md +1 -1
  27. package/stacks/modeling-kit/templates/kit/CLAUDE.md +3 -0
  28. package/stacks/node/templates/.claude/skills/build-automation/SKILL.md +2 -0
  29. package/stacks/node/templates/.claude/skills/build-state-change/SKILL.md +2 -0
  30. package/stacks/node/templates/.claude/skills/build-state-view/SKILL.md +2 -0
  31. package/stacks/opencqrs/templates/.claude/skills/build-automation/SKILL.md +2 -0
  32. package/stacks/opencqrs/templates/.claude/skills/build-state-change/SKILL.md +2 -0
  33. package/stacks/opencqrs/templates/.claude/skills/build-state-view/SKILL.md +2 -0
  34. package/stacks/supabase/templates/.claude/skills/build-state-change/SKILL.md +2 -0
  35. package/stacks/supabase/templates/.claude/skills/build-state-view/SKILL.md +2 -0
  36. package/stacks/umadb/templates/.claude/skills/build-automation/SKILL.md +2 -0
  37. package/stacks/umadb/templates/.claude/skills/build-state-change/SKILL.md +2 -0
  38. package/stacks/umadb/templates/.claude/skills/build-state-view/SKILL.md +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.149",
3
+ "version": "1.0.151",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -696,6 +696,8 @@ Append Given-When-Then scenario(s) to a column's spec node — the node is creat
696
696
  title: string
697
697
  vertical?: boolean
698
698
  examples?: unknown[]
699
+ context?: SpecStep[] // READMODELs the command decides against
700
+ contextNote?: string // markdown background — ONLY what events, commands and read models can't express
699
701
  given?: SpecStep[] // EVENTs from this timeline
700
702
  when?: SpecStep[] // at most one COMMAND, or one inline QUERY object; empty if then has a READMODEL
701
703
  then?: SpecStep[] // EVENTs only OR exactly one READMODEL (not mixed)
@@ -711,6 +713,7 @@ A step given as `{title, type}` is resolved server-side, so the names you alread
711
713
 
712
714
  **Validation rules**:
713
715
  - `context`: only READMODELs (the state the command decides against)
716
+ - `contextNote`: free markdown, last resort. State belongs in `given` events (or `context` read models), inputs in the `when` command's fields, outcomes in `then` — a note never replaces or repeats any of them. Use it only for background no element can carry: an external condition ("the payment provider is in maintenance"), a business assumption, the reason a case matters. Blank notes are dropped. Exported as `contextNote` on the scenario in slice data
714
717
  - `given`: only EVENTs from same timeline
715
718
  - `when`: max one COMMAND; must be empty when `then` contains a READMODEL (use an inline QUERY item for a state-view)
716
719
  - `then`: all EVENTs OR exactly one READMODEL — never mixed
@@ -765,8 +768,13 @@ Append one or more storylines to a column's spec node. The spec node is auto-cre
765
768
  examples?: unknown[]
766
769
  }>
767
770
  }
771
+
772
+ // A note beat — free markdown anywhere in the sequence, referencing no element:
773
+ { instanceId: 'uuid', refId: '', type: 'STORYLINE_NOTE', title: 'Note', note: 'markdown' }
768
774
  ```
769
775
 
776
+ A note beat is the storyline's `contextNote`: only for what the beats themselves can't express (an external condition, an assumption, a time gap like "two weeks later"). Never use one to stand in for an event, command or read model that should be a beat — model the element instead. It is exported as an element `{id: instanceId, type: 'STORYLINE_NOTE', description: note}`; an empty note is rejected (`STORYLINE_NOTE_EMPTY`).
777
+
770
778
  A beat is addressed either way, like a scenario step:
771
779
  ```typescript
772
780
  { refId: 'node-uuid' } // a board node — always wins, never re-resolved
@@ -396,6 +396,8 @@ class {AutomationName}ProcessorTest {
396
396
 
397
397
  ### Storyline-Derived Tests (Optional)
398
398
 
399
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
400
+
399
401
  `slice.json` may also carry a `storylines[]` array — narrated walkthroughs with an ordered
400
402
  `elements[]` "beats" sequence (EVENT/COMMAND/READMODEL/...). This is a secondary, supplementary
401
403
  source; `specifications[]` above stays the primary and default source of test cases. Most slices
@@ -405,6 +405,8 @@ see [references/axon-test-fixture-patterns.md](references/axon-test-fixture-patt
405
405
 
406
406
  ## Step 7b: Storyline-Derived Tests (Optional)
407
407
 
408
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
409
+
408
410
  `slice.json` may carry a `storylines[]` array alongside `specifications[]` — narrated walkthroughs
409
411
  where an ordered sequence of `elements[]` "beats" (EVENT/COMMAND/READMODEL/...) shows one use case
410
412
  end to end. This is a secondary, supplementary source — `specifications[]` (Step 7) stays the
@@ -210,6 +210,8 @@ Do not design your own test cases unless specifically instructed to do so.
210
210
 
211
211
  ## Step 4b: Storyline-Derived Tests (Optional)
212
212
 
213
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
214
+
213
215
  `slice.json` may also carry a `storylines[]` array — narrated walkthroughs where the *same* read
214
216
  model appears as multiple ordered "beats" across one flow (see `elements[]` on each storyline).
215
217
  This is a secondary, supplementary source: `specifications[]` (Step 4) remains the primary and
@@ -39,6 +39,8 @@ Follow `/build-state-change` for this slice's command and events — all of its
39
39
 
40
40
  ### Storyline-derived tests
41
41
 
42
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
43
+
42
44
  If `storylines[]` includes a beat sequence running through this automation's trigger → this slice's command, the command test for that segment is already covered by build-state-change's own "Storyline-derived tests" step.
43
45
 
44
46
  ## Step 3 — Find the trigger point
@@ -77,6 +77,8 @@ Name each test after the specification title so it can be traced back to the boa
77
77
 
78
78
  ### Storyline-derived tests (optional)
79
79
 
80
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
81
+
80
82
  `storylines[]` in slice.json (optional — only present when the board author built an explicit walkthrough) can supply additional tests. A storyline embedded in this slice's slice.json already belongs entirely to this slice — no need to match beats against `commands[]` by id/title. Scan each storyline's ordered `elements` for a `type: COMMAND` beat followed by its EVENT beat(s):
81
83
 
82
84
  - `given` — the cumulative ordered events from the start of the storyline up to (not including) the command beat, set up as state as above
@@ -80,6 +80,8 @@ Name each test after the specification title.
80
80
 
81
81
  ### Storyline-derived tests (optional)
82
82
 
83
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
84
+
83
85
  `storylines[]` in slice.json is optional — present only when the board author explicitly built a walkthrough for this flow. When present, mine it for **additional** tests, on top of (never instead of) the specification tests.
84
86
 
85
87
  A storyline is `{ id, title, elements: [...] }`, where `elements` is an ordered list of beats — the same element can repeat to show its state at different points in the flow. A storyline embedded in this slice's slice.json already belongs entirely to this slice — no need to match beats against `readmodels[]` by id/title. Scan for a pair of adjacent beats that are both `type: READMODEL`, with only EVENT beat(s) between them and no COMMAND beat in that run. That pair is one self-contained test:
@@ -384,6 +384,8 @@ class {AutomationName}HandlingTest {
384
384
 
385
385
  ### Storyline-Derived Tests (Optional)
386
386
 
387
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
388
+
387
389
  `slice.json` may also carry a `storylines[]` array — narrated walkthroughs with an ordered
388
390
  `elements[]` "beats" sequence (EVENT/COMMAND/READMODEL/...). This is a secondary, supplementary
389
391
  source; `specifications[]` above stays the primary and default source of test cases. Most slices
@@ -376,6 +376,8 @@ the same reconstruction path production uses.
376
376
 
377
377
  ## Step 6b: Storyline-Derived Tests (Optional)
378
378
 
379
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
380
+
379
381
  `slice.json` may carry a `storylines[]` array alongside `specifications[]` — narrated walkthroughs
380
382
  where an ordered sequence of `elements[]` "beats" (EVENT/COMMAND/READMODEL/...) shows one use case
381
383
  end to end. This is a secondary, supplementary source — `specifications[]` (Step 7) stays the
@@ -341,6 +341,8 @@ package — which it already does, following this stack's convention (`{SliceNam
341
341
 
342
342
  ## Step 5b: Storyline-Derived Tests (Optional)
343
343
 
344
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
345
+
344
346
  `slice.json` may also carry a `storylines[]` array — narrated walkthroughs where the *same* read
345
347
  model appears as multiple ordered "beats" across one flow (see `elements[]` on each storyline).
346
348
  This is a secondary, supplementary source: `specifications[]` (Step 4) remains the primary and
@@ -138,7 +138,7 @@ For each node, compute (don't write yet):
138
138
 
139
139
  ### If operation is `add`:
140
140
  - Check if a field with that name already exists in `meta.fields` — if so, skip this node (log it).
141
- - Otherwise append a new field:
141
+ - Otherwise append a new field, with its source set per `eventmodeling-information-completeness`. Along the chain, the same name carries the value from hop to hop. The **source element** is where it starts (`generated: true`), unless one of its own inbound neighbours already carries it, in which case the chain stops too early: extend it to the real origin.
142
142
  ```json
143
143
  {
144
144
  "name": "<attributeName>",
@@ -157,6 +157,7 @@ For each node, compute (don't write yet):
157
157
  ### If operation is `rename`:
158
158
  - Find the field where `name === oldName` (case-insensitive). If not found in this node, skip it (log it).
159
159
  - Update only the `name` property to `newName`. Leave all other field properties unchanged.
160
+ - Then fix the downstream fields outside the chain that lost their source through the rename (see `eventmodeling-information-completeness` → "Renames move the lineage too"), in the same batch.
160
161
 
161
162
  Collect one `node:changed` event per affected node, then send them together.
162
163
 
@@ -196,6 +197,7 @@ Tell the user:
196
197
  - **Chain**: list each element in order (type + title + cell)
197
198
  - **Updated**: which nodes were changed
198
199
  - **Skipped**: which nodes were skipped and why (field already exists / field not found / slice not `Created` — asked to confirm)
200
+ - **Lineage**: where the attribute starts (`generated` on which element) and any `mapping` you set for a renamed hop
199
201
 
200
202
  Example output:
201
203
  ```
@@ -127,7 +127,7 @@ Older versions of this skill wrote the completeness check as a markdown report r
127
127
  ## Completeness Criteria
128
128
 
129
129
  The model is **complete** when:
130
- Every event field has a source (command or system or is marked as generated)
130
+ Every field on every element has a source per `eventmodeling-information-completeness`
131
131
  Every command input becomes event/state data
132
132
  Every read model field has event source
133
133
  All state transitions are covered
@@ -56,6 +56,7 @@ Slices are not placed directly — they're **derived** from a completed model. E
56
56
  ### COMMAND
57
57
  - Represents business intent — what an actor wants to do.
58
58
  - Can be rejected; only succeeds if its documented preconditions hold.
59
+ - Carries every piece of information its validation needs that isn't in the stream's prior events. That includes context such as the current time or date, the acting user and the tenant. The decision never reads a clock or a session on its own. See `eventmodeling-identifying-inputs` → "Command input is self-contained".
59
60
  - Naming: imperative, business language.
60
61
  - Valid: `PlaceOrder`, `ConfirmPayment`, `CancelSubscription`
61
62
  - Invalid: `LoadOrders`, `FetchData`, `OpenDialog` — these are queries or UI-only actions, not business intent
@@ -100,11 +101,9 @@ Every event model needs one, established before brainstorming events (it's the f
100
101
 
101
102
  See `eventmodeling-brainstorming-events` for how the catalog is built, `eventmodeling-identifying-inputs` for attributing commands to it, and `eventmodeling-validating-event-models` for re-checking it holds once the model is complete.
102
103
 
103
- ## Field Lineage
104
+ ## Field Lineage (Information Completeness)
104
105
 
105
- Every field on a COMMAND or READMODEL must carry a `mapping` that traces it back to where its value actually comes from — user input, the session, a prior event's field, a derived/computed value, or an external webhook payload. **A field with no mapping is a gap, not a detail to fill in later.**
106
-
107
- See `eventmodeling-identifying-inputs` for the full COMMAND mapping vocabulary and `eventmodeling-identifying-outputs` for the READMODEL equivalent — this rule only states that every field must have one, not the mapping syntax itself.
106
+ Every field on every element has a known source: a same-named field on a connected upstream element, a `mapping` to a differently named field or a connected event, or `generated: true` where the information starts. Check it each time you add or rename a field. **A field with no source is a gap, not a detail to fill in later.** The full rule, per element type, is in `eventmodeling-information-completeness`.
108
107
 
109
108
  ## Connections Read Forward
110
109
 
@@ -69,7 +69,7 @@ When a worker automation *is* warranted, wire the todo lists differently for eac
69
69
 
70
70
  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.
71
71
 
72
- Every field must set a `mapping` per `eventmodeling-identifying-outputs`'s field data lineage rules (`"<EventTitle>.<fieldName>"`, `"latest:..."`, `"aggregate:..."`, `"derived:..."`), and `"cardinality"` (`"Single"` unless the field genuinely holds a list).
72
+ Every field needs a known source per `eventmodeling-information-completeness`, and `"cardinality"` (`"Single"` unless the field genuinely holds a list).
73
73
 
74
74
  A todo-list read model is always list-shaped — it's a queue of pending items, never a single record — so it always gets **`meta.listElement: true`** on the node itself (see `eventmodeling-core-rules`'s READMODEL section). This is a node-level flag separate from any field's own `cardinality`.
75
75
 
@@ -50,6 +50,21 @@ narrated lifecycle, GWT covers everything else about that read model.
50
50
  A storyline threads all three beats into one walkthrough; three separate GWTs could each assert one
51
51
  transition but not the lifecycle.
52
52
 
53
+ ## Notes — Last Resort Only
54
+
55
+ A scenario can carry a `contextNote` (markdown), and a storyline can hold note beats
56
+ (`{type: "STORYLINE_NOTE", refId: "", note: "…"}`). **Notes are only for what cannot be expressed
57
+ with events and the other elements.** Before writing one, ask: could this be a `given` event, a
58
+ `context` read model, a field example on the command, or a `then` outcome? If yes, model it that
59
+ way — a scenario that tells its story in a note instead of in events is incomplete, and code
60
+ generated from it can't test what's only in prose.
61
+
62
+ Legit uses: a condition outside the system ("the payment provider is in maintenance"), a business
63
+ assumption or regulation behind the case, why the case matters, a time gap between storyline beats
64
+ ("14 days later"). Not legit: "the customer has already ordered twice" (→ `given` events), "the
65
+ cart total is 120 €" (→ field examples), "an email is sent" (→ a `then` event), restating the
66
+ title. Most scenarios need no note; keep any note to a few lines.
67
+
53
68
  ### Storyline data shape
54
69
 
55
70
  ```json
@@ -77,6 +92,8 @@ transition but not the lifecycle.
77
92
  READMODEL. Beats are only READMODEL or EVENT — no COMMAND, SCREEN or other types. Every `refId`
78
93
  must be on the same timeline.
79
94
  - `isError` marks a beat as an alternate/error branch off the previous beat.
95
+ - A note beat (`type: "STORYLINE_NOTE"`, `refId: ""`, markdown in `note`) may sit anywhere and is
96
+ skipped by the order rules — see "Notes — Last Resort Only" above before adding one.
80
97
  - `fields`, `expectEmptyList`, `exampleMode`, `examples` mirror the same fields GWT scenario steps
81
98
  use, letting a beat show concrete example data the same way a "then" readmodel step does.
82
99
 
@@ -494,6 +511,7 @@ After posting, tell the user:
494
511
  **No command has only 2 scenarios unless all other types were reviewed and found inapplicable.**
495
512
 
496
513
  **Per read model — this is a separate, equally mandatory pass, not an afterthought of the command pass above:**
514
+ - [ ] **Notes carry nothing an element could** — every `contextNote` and note beat holds only background no event, command field or read model can express; anything else is moved into `given`/`when`/`then`/`context` and the note removed
497
515
  - [ ] **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.
498
516
  - [ ] **Population scenario** — the view shows correct data after its source event(s)
499
517
  - [ ] **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 **only when the read model already feeds an AUTOMATION** (`READMODEL → AUTOMATION` edge; see `learn-eventmodelers-api` §3 and `eventmodeling-orchestrating-event-modeling`'s "No backward arrows") — a later event connecting back to an earlier-placed todo-list read model is normal for that accumulator shape, so add the connection if it's missing rather than assuming the scenario is impossible. If the read model has no automation to feed, the backward connection is rejected — by Step 5 this should already have been modeled the forward way (see `eventmodeling-identifying-outputs` Step 5g's copy pattern: a new read model + screen copy in the later event's column, never a link back). If it wasn't, write the scenario against that forward-placed copy rather than the original. Only skip this scenario, with a documented gap (TASK comment), when the superseding event genuinely lives in a different chapter.
@@ -67,6 +67,21 @@ Show which commands come from automation vs. user actions: catalog every command
67
67
  ### 3. Document Command Specifics
68
68
  For each command, define structure: its source, its typed input fields, the validation rules it enforces, the preconditions it checks against stream state, the success event it produces, and each distinct failure outcome. A full worked example (ConfirmOrder) is in `references/examples.md`.
69
69
 
70
+ ### 3b. Command input is self-contained
71
+
72
+ A command must carry **everything needed to validate it**. For each validation rule and precondition, list every value the rule reads. Each value is either:
73
+ - **a field on the command**, or
74
+ - **stream state**: data from prior events of this entity (step 6).
75
+
76
+ Nothing else. Watch for **contextual information** that is easy to overlook because "the system just knows it":
77
+ - the current time or date (`now`, `today`), for deadlines, expiry, "must be in the future", opening hours, cut-off dates
78
+ - the acting user, their role, and the tenant or organization, for "only the owner may…", limits per customer
79
+ - the timezone or locale, when a rule depends on the local date ("before midnight", "on a business day")
80
+
81
+ Each one becomes a command field, `generated: true` (it enters the model at the command, supplied when the command is issued, per `eventmodeling-information-completeness`). The decision then never reads a clock or session on its own, and the same command always gets the same outcome. That is also what lets a GWT scenario state it: *when `ReserveBike` with `requestedAt = 09:00`, then rejected, because `startTime` is in the past*.
82
+
83
+ A timestamp the event records (`reservedAt`) comes from that command field, not from a clock read when the event is stored.
84
+
70
85
  ### 4. Create Command Catalog
71
86
  List all commands the system accepts, grouped into UI-issued and processor-issued sections, each with its source, input fields, and the event it produces. A full worked example is in `references/examples.md`.
72
87
 
@@ -80,60 +95,39 @@ Document what comes from stream state beyond the command's explicit input: the p
80
95
 
81
96
  Instead of writing a markdown document, **place each COMMAND on the board** using the `node:created` API.
82
97
 
83
- > **CRITICAL: Every COMMAND node MUST include `meta.fields` with a `mapping` on every field.** A command without fields — or with fields that lack `mapping` — is an incomplete model that cannot be traced, validated, or turned into code.
84
-
85
- ### Field data lineage — the `mapping` attribute
98
+ > **CRITICAL: Every COMMAND node MUST include `meta.fields`, and every field must have a known source.** A command without fields — or with fields whose origin is unknown — is an incomplete model that cannot be traced, validated, or turned into code.
86
99
 
87
- Every field on a COMMAND must carry a `mapping` that says exactly where its value comes from. Use one of these forms:
100
+ ### Field data lineage — `mapping` and `generated`
88
101
 
89
- | `mapping` format | Meaning | `generated` | Example |
90
- |---|---|---|---|
91
- | `"user-input"` | User types or selects this value directly | `false` | `startTime` picked from a date picker |
92
- | `"session:<fieldName>"` | Read from the authenticated session | `false` | `session:customerId` |
93
- | `"<EventTitle>.<fieldName>"` | Taken from a previously emitted event | `false` | `BikeReserved.bikeId` |
94
- | `"derived:<expression>"` | Computed by the system | `true` | `derived:uuid4()`, `derived:now()` |
95
- | `"webhook:<payloadField>"` | Comes from an external webhook payload | `false` | `webhook:gateway_transaction_id` |
96
-
97
- Set the field's `generated` property according to this table. Fields typed by the user (typically from a screen form) are never generated — the user supplies the value. Fields with a `derived:` mapping are always generated — the system produces the value automatically.
98
-
99
- The `mapping` traces the value all the way back to its origin. **If you cannot write a mapping for a field, that field has an unknown origin — treat it as a gap and resolve it.**
102
+ Apply `eventmodeling-information-completeness` to every COMMAND field and every EVENT field as you add it. A command's upstream is the SCREEN or AUTOMATION issuing it. An event's upstream is the COMMAND producing it. User input is `generated` on the screen that captures it, not on the command. Contextual values the validation reads (current time, acting user, tenant) are `generated` on the command (see 3b). Only an id the system creates when recording the fact is `generated` on the event. **A field whose source you can't name is a gap. Resolve it before you place the field.**
100
103
 
101
104
  Every field must also set `"cardinality"` — use `"Single"` unless the field genuinely holds a list of values (e.g. multi-select), in which case use `"List"`. Default to `"Single"` when unsure. Only add fields the command actually needs to do its job — do not add speculative fields; enrich later via `/attributes`.
102
105
 
103
106
  ### Field traceability rule for events
104
107
 
105
- Every field in the resulting EVENT must either:
106
- - Appear in the command's `fields` (direct mapping — same name or documented rename), or
107
- - Be derivable from command fields + system state
108
-
109
- Example: `BikeReserved.reservedAt` is not in `ReserveBike` but derives from `derived:now()`. That is acceptable — document it. `BikeReserved.reservationId` is not in `ReserveBike` but derives from `derived:uuid4()`. Also acceptable.
110
-
111
- If an event field has **no** corresponding command field and **no** derivation rule, that is a gap — add it to the command or note it as a system-generated field.
112
-
113
- > **Connected-element rule**: A field `mapping` may only reference elements that are connected to this COMMAND via board arrows (`SCREEN → COMMAND`). If a field's value would come from something not yet connected, flag it as a gap. **If a mapping cannot be defined at all, it hints at missing data in the model or a modeling error** — a missing event field, a missing screen field, or a broken connection. Investigate before proceeding.
108
+ Every EVENT field comes from its COMMAND (same name, or `mapping` for a rename) or is created by the system when the event is recorded (`generated: true`). Anything else means the command, or the screen behind it, is missing that field.
114
109
 
115
110
  ```json
116
111
  {
117
112
  "type": "COMMAND",
118
113
  "title": "ReserveBike",
119
114
  "fields": [
120
- {"name": "customerId", "type": "String", "example": "cust-42", "mapping": "session:customerId", "generated": false},
121
- {"name": "bikeId", "type": "String", "example": "bike-17", "mapping": "user-input", "generated": false},
122
- {"name": "stationId", "type": "String", "example": "stn-03", "mapping": "user-input", "generated": false},
123
- {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z", "mapping": "user-input", "generated": false},
124
- {"name": "endTime", "type": "DateTime", "example": "2026-06-01T17:00:00Z", "mapping": "user-input", "generated": false}
115
+ {"name": "customerId", "type": "String", "example": "cust-42", "generated": true},
116
+ {"name": "bikeId", "type": "String", "example": "bike-17"},
117
+ {"name": "stationId", "type": "String", "example": "stn-03"},
118
+ {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z"},
119
+ {"name": "endTime", "type": "DateTime", "example": "2026-06-01T17:00:00Z", "mapping": "returnTime"},
120
+ {"name": "requestedAt", "type": "DateTime", "example": "2026-06-01T08:45:00Z", "generated": true}
125
121
  ]
126
122
  }
127
123
  ```
128
124
 
125
+ `customerId` (from the session) and `requestedAt` (the current time) are contextual: the rules "a customer may hold one active reservation" and "`startTime` must be after `requestedAt`" read them, so they are on the command, `generated: true`. `bikeId`, `stationId` and `startTime` come from same-named fields on the `Reserve a Bike` screen. `endTime` is called `returnTime` on that screen.
126
+
129
127
  Event fields produced by this command:
130
- - `reservationId` → `derived:uuid4()` → `generated: true`
131
- - `customerId` → `ReserveBike.customerId` → `generated: false`
132
- - `bikeId` → `ReserveBike.bikeId` → `generated: false`
133
- - `stationId` → `ReserveBike.stationId` → `generated: false`
134
- - `startTime` → `ReserveBike.startTime` → `generated: false`
135
- - `endTime` → `ReserveBike.endTime` → `generated: false`
136
- - `reservedAt` → `derived:now()` → `generated: true`
128
+ - `reservationId` → created by the system → `generated: true`
129
+ - `customerId`, `bikeId`, `stationId`, `startTime`, `endTime` → same name on `ReserveBike` → no mapping
130
+ - `reservedAt` → `mapping: "requestedAt"`, the command's current time, never a clock read when the event is stored
137
131
 
138
132
  Commands go in the `interaction` lane — same column as their resulting event.
139
133
 
@@ -167,9 +161,9 @@ mcp__eventmodelers__submit_node_events {
167
161
  "type": "COMMAND",
168
162
  "title": "ReserveBike",
169
163
  "fields": [
170
- {"name": "customerId", "type": "String", "example": "cust-42", "mapping": "session:customerId"},
171
- {"name": "bikeId", "type": "String", "example": "bike-17", "mapping": "user-input"},
172
- {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z","mapping": "user-input"}
164
+ {"name": "customerId", "type": "String", "example": "cust-42", "generated": true},
165
+ {"name": "bikeId", "type": "String", "example": "bike-17"},
166
+ {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z"}
173
167
  ]
174
168
  }
175
169
  }],
@@ -251,6 +245,7 @@ Older versions of this skill wrote the command catalog as a markdown document ra
251
245
  - [ ] Preconditions from stream state are explicit
252
246
  - [ ] Success and failure outcomes documented
253
247
  - [ ] Implicit context from stream state is identified
248
+ - [ ] **Every value a validation rule reads is a command field or stream state, including contextual information (current time/date, acting user, tenant)**
254
249
  - [ ] No undocumented commands exist
255
250
  - [ ] Command naming is consistent and clear
256
251
  - [ ] Processor triggers are explicit
@@ -262,7 +257,7 @@ Older versions of this skill wrote the command catalog as a markdown document ra
262
257
  ## Key Principles
263
258
 
264
259
  1. **Source Clarity**: Every command comes from UI or Processor
265
- 2. **Input Completeness**: All needed data captured
260
+ 2. **Input Completeness**: All data needed to validate is on the command, including contextual information like the current time
266
261
  3. **Validation Explicit**: All rules documented
267
262
  4. **State Awareness**: Preconditions from stream state clear
268
263
  5. **Event Mapping**: Every input becomes event data
@@ -40,9 +40,9 @@ curl -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
40
40
  "type": "COMMAND",
41
41
  "title": "ReserveBike",
42
42
  "fields": [
43
- {"name": "customerId", "type": "String", "example": "cust-42", "mapping": "session:customerId"},
44
- {"name": "bikeId", "type": "String", "example": "bike-17", "mapping": "user-input"},
45
- {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z","mapping": "user-input"}
43
+ {"name": "customerId", "type": "String", "example": "cust-42", "generated": true},
44
+ {"name": "bikeId", "type": "String", "example": "bike-17"},
45
+ {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z"}
46
46
  ]
47
47
  }
48
48
  }]'
@@ -131,7 +131,7 @@ Instead of writing a markdown document, **place each READMODEL (and any missing
131
131
 
132
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.
133
133
 
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.
134
+ > **CRITICAL: Every READMODEL node MUST include `meta.fields`, and every field must trace back to a connected source event (or be `generated`).** A read model without fields, or with fields of unknown origin, has no data lineage and cannot be traced back to its source events.
135
135
 
136
136
  ### The typical slice pattern
137
137
 
@@ -195,30 +195,15 @@ Before finalizing any read model, ask: "does this screen contain more than one c
195
195
 
196
196
  **Do not re-derive read model needs from a screen's title or description alone, and do not rely on the orchestrator's phase-summary handoff for this** — if you arrived here via `eventmodeling-orchestrating-event-modeling`, the handoff after Step 3 is a short hand-written prose summary (`.eventmodelers/interviews/.../EVENTMODELING.md`), not the actual field data. It will not reliably carry the per-field mappings forward. Go back to the board itself:
197
197
 
198
- For every SCREEN node, fetch it directly (`get_node`/`get_nodes`, never from memory) and read its `meta.fields` — a full read on purpose (`projection: "line"` carries field names only, not the `mapping` this step groups by); scope it to the chapter, e.g. `get_nodes { "boardId": "<BOARD_ID>", "type": "SCREEN", "chapterId": "<CHAPTER_ID>" }`, rather than board-wide. Step 3 already required every field to carry a `mapping`, and for view fields that mapping is already in the exact form `"<ReadModelTitle>.<fieldName>"` — recorded specifically so this step doesn't have to re-guess it.
198
+ For every SCREEN node, fetch it directly (`get_node`/`get_nodes`, never from memory) and read its `meta.fields` — a full read on purpose (`projection: "line"` carries field names only, not the `mapping` this step groups by); scope it to the chapter, e.g. `get_nodes { "boardId": "<BOARD_ID>", "type": "SCREEN", "chapterId": "<CHAPTER_ID>" }`, rather than board-wide. Step 3 already required every view field to name its read model, as `mapping: "<ReadModelTitle>"`. It was recorded so this step doesn't have to guess it again.
199
199
 
200
200
  - **Group the screen's fields by the `<ReadModelTitle>` already named in their `mapping`.** That grouping — not a fresh read of the screen's visuals — is the read model's title and field list. Build the READMODEL node from it directly.
201
- - If a field's `mapping` names a read model that isn't `"<CommandTitle>.<fieldName>"` or `"session:..."` or `"derived:..."`, it is a read-model reference — treat it as a requirement, not a suggestion.
201
+ - A field whose `mapping` is a title that isn't on the board yet is a read-model reference. Treat it as a requirement, not a suggestion. (Fields marked `generated: true` are input or display-only values. They need no read model.)
202
202
  - A screen with no fields, or with fields that carry no read-model-shaped mapping, is **not** evidence that it needs no read model. Re-check it against the three rules above (view screen / automation / command screen showing prior state) before concluding it's the rare blank-form exception — and say explicitly why it qualifies.
203
203
 
204
204
  ### Step 5e — Field data lineage — the `mapping` attribute on READMODEL fields
205
205
 
206
- Every field on a READMODEL must carry a `mapping` that says exactly which event (or command) field it is projected from. Use one of these forms:
207
-
208
- | `mapping` format | Meaning | `generated` | Example |
209
- |---|---|---|---|
210
- | `"<EventTitle>.<fieldName>"` | Projected directly from an event field | `false` | `"BikeReserved.customerId"` |
211
- | `"latest:<EventTitle>.<fieldName>"` | Latest value from the most recent event of this type | `false` | `"latest:BikeStatusChanged.toStatus"` |
212
- | `"aggregate:<EventTitle>.<fieldName>"` | Aggregated across multiple events of this type | `true` | `"aggregate:RentalEnded.durationMinutes"` |
213
- | `"derived:<expression>"` | Calculated from other read model fields | `true` | `"derived:sum(lineItems.amount)"` |
214
-
215
- Set the field's `generated` property according to this table. Fields projected directly from events are not generated — they carry a real domain value. Fields that are aggregated or calculated by the system are generated.
216
-
217
- **Field traceability rule**: Every field in a read model must trace back to at least one source event. If a field cannot be mapped to any event in the timeline, it is either:
218
- - A calculated/derived field — document the derivation expression, or
219
- - A missing event field — add it to the source event before proceeding.
220
-
221
- > **Connected-element rule**: A read model's field `mapping` may only reference EVENTs that are connected to this READMODEL via a board `EVENT → READMODEL` arrow. If a field needs data from an event that is not connected, either add the connection or flag it as a gap. **If a mapping cannot be defined for a field, it signals missing data in the model, a missing event, or a modeling error.** Do not leave unmapped fields without a note.
206
+ Apply `eventmodeling-information-completeness` to every READMODEL field as you add it. A read model's upstream is the EVENTs connected to it by `EVENT → READMODEL` arrows. A value the projection calculates itself (a count, a sum, a duration) is `generated: true`. A field no connected event supplies is a missing event field or a missing connection: add it before proceeding.
222
207
 
223
208
  Every field must also set `"cardinality"` — use `"Single"` unless the field genuinely holds a list of values (e.g. line items, a collection projected from multiple events), in which case use `"List"`. Default to `"Single"` when unsure. Include only the fields the consuming SCREEN or AUTOMATION actually displays or needs — do not add speculative fields; enrich later via `/attributes`.
224
209
 
@@ -229,12 +214,13 @@ If the READMODEL itself represents a set of rows rather than a single record —
229
214
  "type": "READMODEL",
230
215
  "title": "ActiveReservationView",
231
216
  "fields": [
232
- {"name": "reservationId", "type": "String", "example": "res-001", "mapping": "BikeReserved.reservationId", "generated": false},
233
- {"name": "customerId", "type": "String", "example": "cust-42", "mapping": "BikeReserved.customerId", "generated": false},
234
- {"name": "bikeId", "type": "String", "example": "bike-17", "mapping": "BikeReserved.bikeId", "generated": false},
235
- {"name": "stationId", "type": "String", "example": "stn-03", "mapping": "BikeReserved.stationId", "generated": false},
236
- {"name": "expiresAt", "type": "DateTime", "example": "2026-06-01T09:30:00Z", "mapping": "ReservationConfirmed.expiresAt", "generated": false},
237
- {"name": "status", "type": "String", "example": "confirmed", "mapping": "latest:ReservationConfirmed.status", "generated": false}
217
+ {"name": "reservationId", "type": "String", "example": "res-001"},
218
+ {"name": "customerId", "type": "String", "example": "cust-42"},
219
+ {"name": "bikeId", "type": "String", "example": "bike-17"},
220
+ {"name": "stationId", "type": "String", "example": "stn-03"},
221
+ {"name": "expiresAt", "type": "DateTime", "example": "2026-06-01T09:30:00Z", "mapping": "ReservationConfirmed"},
222
+ {"name": "status", "type": "String", "example": "confirmed", "mapping": "toStatus"},
223
+ {"name": "minutesLeft", "type": "Integer", "example": "25", "generated": true}
238
224
  ]
239
225
  }
240
226
  ```
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: eventmodeling-information-completeness
3
+ description: The rule for where every field's value comes from. Use it whenever a field is added, renamed or reviewed on any element (SCREEN, HTML_SCREEN, COMMAND, EVENT, READMODEL, AUTOMATION), whether in a full modeling run, a one-off /attributes or /place-element call, or a fix. Each field either matches a same-named field on a connected upstream element, maps to a differently named field or a connected event, or is marked generated where the information starts. Anything else is a gap to close. Not a step of its own. Other skills reference it instead of restating it.
4
+ ---
5
+
6
+ # Information Completeness
7
+
8
+ Information doesn't appear out of nowhere. Every field on every element either comes from an element **connected upstream** of it, or starts at this element. Check this **each time you add or rename a field**, before you write it. Don't leave it for a later completeness pass.
9
+
10
+ ## The four valid states of a field
11
+
12
+ | Where the value comes from | What to write | Example |
13
+ |---|---|---|
14
+ | A connected upstream element has a field **with the same name** | nothing: no `mapping`, the same name *is* the mapping | `BikeReserved.bikeId` ← `ReserveBike.bikeId` |
15
+ | A connected upstream element has it **under a different name** | `mapping: "<sourceFieldName>"` (a sub-field as `"<field>.<subfield>"`) | `ReserveBike.endTime` with `mapping: "returnTime"` |
16
+ | It comes from one specific connected **event** (or other connected element), e.g. because several upstream elements carry the field | `mapping: "<EventTitle>"`, the element's title on its own | `ReservationView.expiresAt` with `mapping: "ReservationConfirmed"` |
17
+ | **Nothing upstream** supplies it: manual input typed into a screen, a session value, a generated id or timestamp, a value computed at this element | `generated: true` | `HTML_SCREEN "Reserve a Bike".startTime`, `BikeReserved.reservationId` |
18
+
19
+ These are the forms the board resolves. The field picker offers the connected fields and the event titles, and the Data Trail follows them. Write the field name or the element title **bare**. Combined forms like `"BikeReserved.bikeId"`, `"user-input"`, `"session:x"` or `"derived:..."` resolve to nothing.
20
+
21
+ ## Upstream means inbound, per element type
22
+
23
+ Lineage always points to where the value comes **from**. It never points downstream.
24
+
25
+ | Element | Its upstream (where its fields can come from) |
26
+ |---|---|
27
+ | SCREEN / HTML_SCREEN | the READMODEL it displays. The COMMAND it submits is **downstream**: never map a screen field to it. |
28
+ | COMMAND | the SCREEN or AUTOMATION issuing it |
29
+ | EVENT | the COMMAND producing it |
30
+ | READMODEL | the EVENTs connected to it (`EVENT → READMODEL`) |
31
+ | AUTOMATION | the READMODEL (todo list) it reads |
32
+
33
+ So information typed into a form is `generated: true` **on the screen**. The command, event and read model then pick it up by the same name, or by a `mapping` where it's renamed. Contextual values a command's validation reads (current time or date, acting user, tenant) are `generated: true` on the **command**. Commands carry everything they need to validate (`eventmodeling-identifying-inputs` → "Command input is self-contained"), and the event takes a recorded time from there. Only an id the system creates when recording the fact is `generated: true` on the event. A count, sum or duration the projection calculates is `generated: true` on the read model.
34
+
35
+ ## How to check a field
36
+
37
+ 1. Look at the element's inbound neighbours, from the edges (`get_connected_nodes` `direction: "inbound"`, `includeFields: true`), or from the layout when the chapter is unwired.
38
+ 2. Pick the row above that matches. One neighbour with the same name, and nothing to write, is the common case.
39
+ 3. **No row fits → it's a gap.** The field is missing upstream, the connection is missing, or the element is missing. Close it: add the field upstream (and carry it through the chain the same way), add the connection, or add the element. Only when the source genuinely can't be decided from the model, apply the questioning rule.
40
+
41
+ `generated: true` means "this information starts here". It is never a way to say "I don't know where this comes from". Don't mark a field `generated` to make a gap go away, and don't invent a `generated` origin in the middle of a flow.
42
+
43
+ ## Planned sources that aren't on the board yet
44
+
45
+ While storyboarding, a displayed screen field is often meant to come from a read model that hasn't been designed yet. Write `mapping: "<ReadModelTitle>"` anyway and flag it as a gap. `eventmodeling-identifying-outputs` groups screen fields by that title to design the read model. Once the read model is connected, a same-named field needs no mapping, and a differently named one gets `mapping: "<readModelFieldName>"`. A displayed field is never marked `generated` just because its read model is missing.
46
+
47
+ ## Renames move the lineage too
48
+
49
+ Renaming a field breaks every downstream field that matched it by name, or that has `mapping: "<oldName>"`. In the same write, either rename those too or set their `mapping` to the new name. `/attributes` does this for a chain.
50
+
51
+ ## Worked example — reserving a bike
52
+
53
+ ```
54
+ HTML_SCREEN "Reserve a Bike" bikeId (generated), startTime (generated), returnTime (generated),
55
+ customerId (generated, from session), bikeCategory (mapping "AvailableBikeView")
56
+ COMMAND ReserveBike bikeId, startTime, customerId (same names, no mapping), endTime (mapping "returnTime"),
57
+ requestedAt (generated: current time, read by "startTime must be in the future")
58
+ EVENT BikeReserved bikeId, startTime, endTime, customerId (same names), reservationId (generated),
59
+ reservedAt (mapping "requestedAt")
60
+ READMODEL ActiveReservation reservationId, bikeId, startTime (same names), expiresAt (mapping "ReservationConfirmed"),
61
+ minutesLeft (generated)
62
+ HTML_SCREEN "Reservation" reservationId, startTime, expiresAt (same names, read model connected)
63
+ ```
64
+
65
+ Every field on the way traces back to a `generated` origin. Nothing appears without a source.
@@ -135,30 +135,9 @@ The result is a `{ roleName: actorRowId }` map, covering human roles only, used
135
135
 
136
136
  ## Mandatory Field Definitions on Screen Nodes
137
137
 
138
- > **CRITICAL: Every screen node MUST include `meta.fields` with a `mapping` on every field.** A screen without fields cannot show data lineage — it becomes impossible to verify that all displayed data has a source event or command. This applies regardless of which content type the screen renders as (HTML_SCREEN by default, or SCREEN when a sketch was explicitly requested).
138
+ > **CRITICAL: Every screen node MUST include `meta.fields`, and every field must have a known source.** A screen without fields cannot show data lineage — it becomes impossible to verify that all displayed data has a source event or command. This applies regardless of which content type the screen renders as (HTML_SCREEN by default, or SCREEN when a sketch was explicitly requested).
139
139
 
140
- There are two types of screens, and each type has a different `mapping` source:
141
-
142
- ### Command screen (input/action screen)
143
-
144
- An **input screen** captures data that will be sent as a command. Its fields map to the command they feed into.
145
-
146
- | Field type | `mapping` | `generated` | Example |
147
- |---|---|---|---|
148
- | User types a value | `"<CommandTitle>.<fieldName>"` | `false` | `"ReserveBike.bikeId"` |
149
- | Read from session | `"session:<fieldName>"` | `false` | `"session:customerId"` |
150
- | Pre-populated from a previous event | `"<EventTitle>.<fieldName>"` | `false` | `"BikeReserved.stationId"` |
151
- | Calculated for display only (not sent to command) | `"derived:<expression>"` | `true` | `"derived:sum(items.price)"` |
152
-
153
- ### View screen (output/read model screen)
154
-
155
- A **view screen** displays data read from a Read Model. Its fields map to the read model that serves the data. View screens are placed adjacent to their read model in Step 5 (Identifying Outputs) — during storyboarding, record the intended read model name as the source even if the read model hasn't been designed yet.
156
-
157
- | Field type | `mapping` | `generated` | Example |
158
- |---|---|---|---|
159
- | Displayed from a read model | `"<ReadModelTitle>.<fieldName>"` | `false` | `"ActiveReservationView.status"` |
160
- | Displayed from a direct event (no read model yet) | `"<EventTitle>.<fieldName>"` | `false` | `"BikeReserved.startTime"` |
161
- | Calculated/formatted on the client | `"derived:<expression>"` | `true` | `"derived:formatDuration(durationMinutes)"` |
140
+ Apply `eventmodeling-information-completeness` to every screen field as you add it. In short: input the user enters, session values and display-only calculations are `generated: true`. Displayed data names its read model (`mapping: "<ReadModelTitle>"`), even when that read model isn't designed yet, which Step 5 then groups by. A screen field never maps to the command it submits.
162
141
 
163
142
  ### Example: command screen fields
164
143
 
@@ -167,12 +146,12 @@ A **view screen** displays data read from a Read Model. Its fields map to the re
167
146
  "type": "HTML_SCREEN",
168
147
  "title": "Reserve a Bike",
169
148
  "fields": [
170
- {"name": "bikeId", "type": "String", "example": "bike-17", "mapping": "ReserveBike.bikeId"},
171
- {"name": "stationId", "type": "String", "example": "stn-03", "mapping": "ReserveBike.stationId"},
172
- {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z", "mapping": "ReserveBike.startTime"},
173
- {"name": "endTime", "type": "DateTime", "example": "2026-06-01T17:00:00Z", "mapping": "ReserveBike.endTime"},
174
- {"name": "bikeCategory","type": "String", "example": "City Bike", "mapping": "AvailableBikeView.category"},
175
- {"name": "dailyRate", "type": "Decimal", "example": "0.10", "mapping": "AvailableBikeView.ratePerMinute"}
149
+ {"name": "bikeId", "type": "String", "example": "bike-17", "generated": true},
150
+ {"name": "stationId", "type": "String", "example": "stn-03", "generated": true},
151
+ {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z", "generated": true},
152
+ {"name": "returnTime", "type": "DateTime", "example": "2026-06-01T17:00:00Z", "generated": true},
153
+ {"name": "bikeCategory","type": "String", "example": "City Bike", "mapping": "AvailableBikeView"},
154
+ {"name": "dailyRate", "type": "Decimal", "example": "0.10", "mapping": "ratePerMinute"}
176
155
  ]
177
156
  }
178
157
  ```
@@ -184,27 +163,17 @@ A **view screen** displays data read from a Read Model. Its fields map to the re
184
163
  "type": "HTML_SCREEN",
185
164
  "title": "Reservation Confirmed",
186
165
  "fields": [
187
- {"name": "reservationId", "type": "String", "example": "res-001", "mapping": "ActiveReservationView.reservationId"},
188
- {"name": "bikeName", "type": "String", "example": "City Bike — Gazelle", "mapping": "ActiveReservationView.bikeName"},
189
- {"name": "stationName", "type": "String", "example": "Central Park East", "mapping": "ActiveReservationView.stationName"},
190
- {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z", "mapping": "ActiveReservationView.startTime"},
191
- {"name": "expiresAt", "type": "DateTime", "example": "2026-06-01T09:30:00Z", "mapping": "ActiveReservationView.expiresAt"},
192
- {"name": "estimatedCost", "type": "Decimal", "example": "48.00", "mapping": "derived:durationHours × AvailableBikeView.ratePerMinute × 60"}
166
+ {"name": "reservationId", "type": "String", "example": "res-001", "mapping": "ActiveReservationView"},
167
+ {"name": "bikeName", "type": "String", "example": "City Bike — Gazelle", "mapping": "ActiveReservationView"},
168
+ {"name": "stationName", "type": "String", "example": "Central Park East", "mapping": "ActiveReservationView"},
169
+ {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z", "mapping": "ActiveReservationView"},
170
+ {"name": "expiresAt", "type": "DateTime", "example": "2026-06-01T09:30:00Z", "mapping": "ActiveReservationView"},
171
+ {"name": "estimatedCost", "type": "Decimal", "example": "48.00", "generated": true}
193
172
  ]
194
173
  }
195
174
  ```
196
175
 
197
- ### Connected-element rule (critical constraint)
198
-
199
- > **A field `mapping` may only reference an element that is connected to this SCREEN via a board connection arrow.**
200
- >
201
- > - A command screen may only map to: the COMMAND it submits, the SCREEN's own input (user-input), or a READ MODEL whose `READMODEL → SCREEN` arrow points to this screen.
202
- > - A view screen may only map to: the READ MODEL connected to it via a `READMODEL → SCREEN` arrow, or derived expressions.
203
- > - If a mapping references an element that is not yet connected — for example, `AvailableBikeView.category` when no such read model exists on the board — that is a **gap**. It signals either:
204
- > 1. A read model is missing and must be created in Step 5 (Identifying Outputs), or
205
- > 2. The connection arrow is missing and must be added.
206
- >
207
- > **When you encounter a mapping that cannot resolve to a connected element, write the mapping as-is but flag it as a gap in the completeness notes.** Do not invent connections that don't exist.
176
+ A read model named in a `mapping` but not on the board (or not connected to this screen) is a **gap** for Step 5 (Identifying Outputs) to close. Flag it in the completeness notes, and don't invent connections that don't exist.
208
177
 
209
178
  Every field must also set `"cardinality"` — use `"Single"` unless the field genuinely displays/collects a list of values (e.g. a repeated line-item row), in which case use `"List"`. Default to `"Single"` when unsure. Only add the fields the screen actually shows or captures — do not add speculative fields; enrich later via `/attributes`.
210
179
 
@@ -161,19 +161,10 @@ Expect `204 No Content` on success from either curl call. A `HTML_SCREEN_PAGE_TO
161
161
 
162
162
  ## Step 5 — Define field data lineage (mandatory)
163
163
 
164
- Every screen — new or updated — needs `meta.fields`: one entry per piece of data the screen displays or captures, each with a `mapping` naming where that data comes from. A screen with only a title and no fields is an empty placeholder from a data-lineage standpoint, even if the mockup itself looks complete.
164
+ Every screen, new or updated, needs `meta.fields`: one entry per piece of data the screen displays or captures, each with a known source per `eventmodeling-information-completeness`. Input, session values and display-only calculations are `generated: true`. Displayed data names its read model (`mapping: "<ReadModelTitle>"`), even if that read model isn't on the board yet. This skill doesn't create read models or connections, but naming the source is not optional. A screen with only a title and no fields is an empty placeholder from a data-lineage standpoint, even if the mockup itself looks complete.
165
165
 
166
166
  **For a brand-new screen** (Step 4's `create_screen` call), pass `fields` directly on that call instead of a separate `node:changed` here — one round trip instead of two. This step's separate `submit_node_events` call is still how you set/update fields on an **existing** screen (`render_screen` doesn't take `fields` either).
167
167
 
168
- | Field type | `mapping` | Example |
169
- |---|---|---|
170
- | User types a value, sent as a command | `"<CommandTitle>.<fieldName>"` | `"ReserveBike.bikeId"` |
171
- | Read from session | `"session:<fieldName>"` | `"session:customerId"` |
172
- | Displayed data, sourced from a read model | `"<ReadModelTitle>.<fieldName>"` | `"ActiveReservationView.status"` |
173
- | Calculated/formatted only for display | `"derived:<expression>"` | `"derived:formatDuration(durationMinutes)"` |
174
-
175
- Name the read model even if it doesn't exist as a board node yet — this skill only renders the screen, it does not create READMODEL nodes or connections (that's `eventmodeling-identifying-outputs` or `place-element`, if the model is taken that far). But naming the source is **not optional**: a screen displaying data should almost never have a field with no mapping. If you can't say which read model a displayed field comes from, that's a sign the model is missing something — not a reason to skip the field.
176
-
177
168
  If this node is one of several sharing the same visual base with different marks/highlights (see "Marked screens and field scoping" above), only list the fields that fall inside *this* node's highlighted area — not every field the shared screen shows.
178
169
 
179
170
  Set `cardinality` too (`"Single"` unless the field is a repeated/list value), then push the fields onto the node:
@@ -186,7 +177,7 @@ mcp__eventmodelers__submit_node_events {
186
177
  "id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
187
178
  "changedAttributes": ["meta.fields"],
188
179
  "meta": { "type": "HTML_SCREEN", "fields": [
189
- {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
180
+ {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView", "cardinality": "Single"}
190
181
  ] }
191
182
  }],
192
183
  "compact": true
@@ -43,7 +43,7 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
43
43
  "id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
44
44
  "changedAttributes": ["meta.fields"],
45
45
  "meta": { "type": "HTML_SCREEN", "fields": [
46
- {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
46
+ {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView", "cardinality": "Single"}
47
47
  ] }
48
48
  }]'
49
49
  ```
@@ -55,7 +55,7 @@ mcp__eventmodelers__place_element {
55
55
  }
56
56
  ```
57
57
 
58
- Pass `fields` whenever the element's attributes are already known — they are written by this same call, so don't follow a placement with a `submit_node_events` just to set them. Placing **more than one** element is more entries in the same `elements` array, applied in order so each entry sees the columns the previous one added — one call for a whole slice's or column run's worth instead of one per element. **Order the array by narrative, left to right, one slice after another** (screen → command → event → the read model/view screen that event feeds → next slice) — never grouped by `elementType` (all commands, then all read models): by-type order makes every read model land in an inserted column after the fact and breaks the visual story.
58
+ Pass `fields` whenever the element's attributes are already known — they are written by this same call, so don't follow a placement with a `submit_node_events` just to set them. Every field you pass needs a known source per `eventmodeling-information-completeness`. A field without one is a gap to close upstream, not a field to place as is. Placing **more than one** element is more entries in the same `elements` array, applied in order so each entry sees the columns the previous one added — one call for a whole slice's or column run's worth instead of one per element. **Order the array by narrative, left to right, one slice after another** (screen → command → event → the read model/view screen that event feeds → next slice) — never grouped by `elementType` (all commands, then all read models): by-type order makes every read model land in an inserted column after the fact and breaks the visual story.
59
59
 
60
60
  This tool finds or creates an empty cell in the correct lane and places the node in one call — it collapses the "resolve timeline → fetch columns → determine lane → check occupancy → create node" sequence (Steps 2–3, 4, 6, 7b below) into a single round trip. A `columnIndex` past the timeline's current column count is handled automatically (columns are added to reach it) — no need to pre-check the column count or catch an out-of-range error yourself. If `timelineId` is unknown, resolve it first via Step 2's MCP call. Pass `compact: true` for a smaller `{nodeId, cellName, columnIndex}` response (plus `connectedCount` if auto-connect wired an edge) when you don't need the full `lane`/`elementType`/`title`/`autoConnected` detail back. Go straight to Step 8 once it returns.
61
61
 
@@ -110,7 +110,7 @@ mcp__eventmodelers__render_screen {
110
110
 
111
111
  ## Step 5 — Define field data lineage (mandatory)
112
112
 
113
- Every screen — new or updated — needs `meta.fields`: one entry per piece of data the screen displays or captures, each with a `mapping` naming where that data comes from. A screen with only a title and no fields is an empty placeholder from a data-lineage standpoint, even once the wireframe is rendered. See the `html-screen` skill's "Step 5 — Define field data lineage" for the full `mapping`-format table (command/session/read-model/derived forms).
113
+ Every screen — new or updated — needs `meta.fields`: one entry per piece of data the screen displays or captures, each with a known source per `eventmodeling-information-completeness`. A screen with only a title and no fields is an empty placeholder from a data-lineage standpoint, even once the wireframe is rendered.
114
114
 
115
115
  Name the read model even if it doesn't exist as a board node yet — this skill only renders the screen, it does not create READMODEL nodes or connections. But naming the source is **not optional**: a screen displaying data should almost never have a field with no mapping. If you can't say which read model a displayed field comes from, that's a sign the model is missing something — not a reason to skip the field.
116
116
 
@@ -124,7 +124,7 @@ mcp__eventmodelers__submit_node_events {
124
124
  "id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
125
125
  "changedAttributes": ["meta.fields"],
126
126
  "meta": { "type": "SCREEN", "fields": [
127
- {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
127
+ {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView", "cardinality": "Single"}
128
128
  ] }
129
129
  }],
130
130
  "compact": true
@@ -35,7 +35,7 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
35
35
  "id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
36
36
  "changedAttributes": ["meta.fields"],
37
37
  "meta": { "type": "SCREEN", "fields": [
38
- {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
38
+ {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView", "cardinality": "Single"}
39
39
  ] }
40
40
  }]'
41
41
  ```
@@ -100,6 +100,9 @@ exactly those elements — nothing else about the rule loosens. **No skill overr
100
100
  when there is no chat to ask in (a prompt that did not come from one, a standalone turn): then change
101
101
  nothing and comment which status blocked it. Where there is a chat, never comment — ask with the snippet.
102
102
 
103
+ **Every field you add or rename must have a known source. This holds in every skill and every turn, not only in a full modeling run.**
104
+ Follow `eventmodeling-information-completeness` before writing any field. It defines the three valid sources: a same-named upstream field, a `mapping`, or `generated: true`. A field with none of these is a gap to close upstream, never one to place unmapped.
105
+
103
106
  **One board read, shared by the whole turn.** Orientation first — `get_board_outline`, or `get_nodes` with
104
107
  `projection: "line"` — to establish where the work actually is, and only for the chapter this turn is about.
105
108
  In a `standalone=on` session the `SESSION_START` warm-up gave you the chapter list but no chapter's contents,
@@ -64,6 +64,8 @@ Refer to the build-state-change skill for the full command handler structure.
64
64
 
65
65
  ### Storyline-derived tests
66
66
 
67
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
68
+
67
69
  If `storylines[]` includes a beat sequence running through this automation's trigger event → this slice's command, the command-handler test for that segment is already covered by build-state-change's own "Storyline-derived tests" step — it runs as part of following that skill above. This skill has no separate reactor-test format: the processor's event-to-command field mapping is exercised only indirectly, through that command-handler test, never by a dedicated `processor.test.ts`.
68
70
 
69
71
  ---
@@ -230,6 +230,8 @@ Add one test per specification in the slice.json. If the spec has no preconditio
230
230
 
231
231
  ### Storyline-derived tests (optional)
232
232
 
233
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
234
+
233
235
  `storylines[]` in slice.json (optional — only present when the board author built an explicit walkthrough) can also supply command-handler tests, in addition to the specifications-derived ones above. A storyline embedded in this slice's slice.json already belongs entirely to this slice — no need to match beats against `commands[]` by id/title. Just scan each storyline's ordered `elements` for a `type: COMMAND` beat, followed by its emitted EVENT beat(s):
234
236
 
235
237
  - `given` — the cumulative ordered events from the start of the storyline up to (not including) the command beat
@@ -327,6 +327,8 @@ Write one `it` block per specification in the slice.json. Use `given([events]).w
327
327
 
328
328
  ### Storyline-derived tests (optional)
329
329
 
330
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
331
+
330
332
  `storylines[]` in slice.json is optional — present only when the board author explicitly built a walkthrough for this flow; most slices have none. When present, mine it for **additional** read-model-chain tests, on top of (never instead of) the specifications-derived tests above.
331
333
 
332
334
  A storyline is `{ id, title, elements: [...] }`, where `elements` is an ordered list of beats — the same element can repeat to show its state at different points in the flow. A storyline embedded in this slice's slice.json already belongs entirely to this slice — no need to match beats against `readmodels[]` by id/title. Just scan for a pair of adjacent beats that are both `type: READMODEL`, with only EVENT beat(s) between them and no COMMAND beat in that run. That pair is one self-contained projection test:
@@ -394,6 +394,8 @@ class {AutomationName}ProcessorTest {
394
394
 
395
395
  ### Storyline-Derived Tests (Optional)
396
396
 
397
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
398
+
397
399
  `slice.json` may also carry a `storylines[]` array — narrated walkthroughs with an ordered
398
400
  `elements[]` "beats" sequence (EVENT/COMMAND/READMODEL/...). This is a secondary, supplementary
399
401
  source; `specifications[]` above stays the primary and default source of test cases. Most slices
@@ -368,6 +368,8 @@ Full fluent-API cheat sheet: see
368
368
 
369
369
  ## Step 7b: Storyline-Derived Tests (Optional)
370
370
 
371
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
372
+
371
373
  `slice.json` may carry a `storylines[]` array alongside `specifications[]` — narrated walkthroughs
372
374
  where an ordered sequence of `elements[]` "beats" (EVENT/COMMAND/READMODEL/...) shows one use case
373
375
  end to end. This is a secondary, supplementary source — `specifications[]` (Step 7) stays the
@@ -300,6 +300,8 @@ class {SliceName}ProjectorTest {
300
300
 
301
301
  ## Step 4b: Storyline-Derived Tests (Optional)
302
302
 
303
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
304
+
303
305
  `slice.json` may also carry a `storylines[]` array — narrated walkthroughs where the *same* read
304
306
  model appears as multiple ordered "beats" across one flow (see `elements[]` on each storyline).
305
307
  This is a secondary, supplementary source: `specifications[]` (Step 3) remains the primary and
@@ -232,6 +232,8 @@ Add one test per specification in the slice.json. If the spec has no preconditio
232
232
 
233
233
  ## Step 4b — Storyline-derived tests (optional)
234
234
 
235
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
236
+
235
237
  Some slices also have a `storylines[]` array in slice.json — narrated walkthroughs of one use case as an ordered sequence of beats (`elements[]`). Most slices have no storylines; skip this step silently when `storylines[]` is empty or absent.
236
238
 
237
239
  A storyline embedded in this slice's slice.json already belongs entirely to this slice — no need to match beats against `commands[]` by id/title. For each storyline, find a `type: COMMAND` beat directly followed by its EVENT beat(s). That pair is a command-handler test — the same shape as the specifications-derived tests above:
@@ -309,6 +309,8 @@ Write one `it` block per specification in the slice.json. Use `given([events]).w
309
309
 
310
310
  ## Step 5b — Storyline-derived tests (optional)
311
311
 
312
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
313
+
312
314
  Some slices also have a `storylines[]` array in slice.json — narrated walkthroughs where the *same* read model repeats across several beats in one ordered `elements[]` list, showing its state at different points in a single flow. Most slices have no storylines; skip this step silently when `storylines[]` is empty or absent.
313
315
 
314
316
  A storyline embedded in this slice's slice.json already belongs entirely to this slice — no need to match beats against `readmodels[]` by id/title. For each storyline, scan `elements[]` for pairs of adjacent `type: 'READMODEL'` beats with only EVENT beat(s) in between. Each such pair is a clean, self-contained projection test — the same shape as the specifications-derived tests above:
@@ -282,6 +282,8 @@ verifyNoMoreInteractions(targetCommandHandler);
282
282
 
283
283
  ### Storyline-Derived Tests (Optional)
284
284
 
285
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
286
+
285
287
  `slice.json` may also carry a `storylines[]` array — narrated walkthroughs with an ordered
286
288
  `elements[]` "beats" sequence (EVENT/COMMAND/READMODEL/...). This is a secondary, supplementary
287
289
  source; `specifications[]` above stays the primary and default source of test cases. Most slices
@@ -344,6 +344,8 @@ behaviour, why `subscribe()` isn't faked: see
344
344
 
345
345
  ## Step 7b: Storyline-Derived Tests (Optional)
346
346
 
347
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
348
+
347
349
  `slice.json` may carry a `storylines[]` array alongside `specifications[]` — narrated walkthroughs
348
350
  where an ordered sequence of `elements[]` "beats" (EVENT/COMMAND/READMODEL/...) shows one use case
349
351
  end to end. This is a secondary, supplementary source — `specifications[]` (Step 7) stays the
@@ -286,6 +286,8 @@ Add a `com.h2database:h2` test-scope dependency once, project-wide, if it isn't
286
286
 
287
287
  ## Step 4b: Storyline-Derived Tests (Optional)
288
288
 
289
+ > **Note beats**: an entry with `type: STORYLINE_NOTE` is a note the board author placed in the walkthrough — its `description` is markdown context (an assumption, an external condition, a time gap), and its `id` is not a board node. Read it as background for the beats around it, but it is never a step: skip it when looking for COMMAND/EVENT/READMODEL beats or judging which beats are adjacent, and never turn it into test setup or an assertion on its own.
290
+
289
291
  `slice.json` may also carry a `storylines[]` array — narrated walkthroughs where the *same* read
290
292
  model appears as multiple ordered "beats" across one flow. This is a secondary, supplementary
291
293
  source: `specifications[]` (Step 4) remains the primary and default source of test cases. Most