@eventmodelers/cli 1.0.149 → 1.0.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +3 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-core-rules/SKILL.md +3 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/SKILL.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +35 -40
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/api-fallback.md +3 -3
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +11 -25
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-information-completeness/SKILL.md +65 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +15 -46
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/SKILL.md +2 -11
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/references/api-fallback.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/references/api-fallback.md +1 -1
- package/stacks/modeling-kit/templates/kit/CLAUDE.md +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.150",
|
|
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": {
|
|
@@ -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
|
```
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
100
|
+
### Field data lineage — `mapping` and `generated`
|
|
88
101
|
|
|
89
|
-
|
|
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
|
|
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", "
|
|
121
|
-
{"name": "bikeId", "type": "String", "example": "bike-17"
|
|
122
|
-
{"name": "stationId", "type": "String", "example": "stn-03"
|
|
123
|
-
{"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z"
|
|
124
|
-
{"name": "endTime", "type": "DateTime", "example": "2026-06-01T17:00:00Z", "mapping": "
|
|
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` →
|
|
131
|
-
- `customerId` → `ReserveBike
|
|
132
|
-
- `
|
|
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", "
|
|
171
|
-
{"name": "bikeId", "type": "String", "example": "bike-17"
|
|
172
|
-
{"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z"
|
|
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
|
|
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", "
|
|
44
|
-
{"name": "bikeId", "type": "String", "example": "bike-17"
|
|
45
|
-
{"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z"
|
|
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
|
}]'
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
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"
|
|
233
|
-
{"name": "customerId", "type": "String", "example": "cust-42"
|
|
234
|
-
{"name": "bikeId", "type": "String", "example": "bike-17"
|
|
235
|
-
{"name": "stationId", "type": "String", "example": "stn-03"
|
|
236
|
-
{"name": "expiresAt", "type": "DateTime", "example": "2026-06-01T09:30:00Z", "mapping": "ReservationConfirmed
|
|
237
|
-
{"name": "status", "type": "String", "example": "confirmed", "mapping": "
|
|
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
|
```
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-information-completeness/SKILL.md
ADDED
|
@@ -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.
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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", "
|
|
171
|
-
{"name": "stationId", "type": "String", "example": "stn-03", "
|
|
172
|
-
{"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z", "
|
|
173
|
-
{"name": "
|
|
174
|
-
{"name": "bikeCategory","type": "String", "example": "City Bike", "mapping": "AvailableBikeView
|
|
175
|
-
{"name": "dailyRate", "type": "Decimal", "example": "0.10", "mapping": "
|
|
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
|
|
188
|
-
{"name": "bikeName", "type": "String", "example": "City Bike — Gazelle", "mapping": "ActiveReservationView
|
|
189
|
-
{"name": "stationName", "type": "String", "example": "Central Park East", "mapping": "ActiveReservationView
|
|
190
|
-
{"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z", "mapping": "ActiveReservationView
|
|
191
|
-
{"name": "expiresAt", "type": "DateTime", "example": "2026-06-01T09:30:00Z", "mapping": "ActiveReservationView
|
|
192
|
-
{"name": "estimatedCost", "type": "Decimal", "example": "48.00", "
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
127
|
+
{"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView", "cardinality": "Single"}
|
|
128
128
|
] }
|
|
129
129
|
}],
|
|
130
130
|
"compact": true
|
package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/references/api-fallback.md
CHANGED
|
@@ -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
|
|
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,
|