@cratis/pi 6.2.0 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/SKILL.md +90 -15
  2. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/verification.json +5 -1
  3. package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +162 -64
  4. package/package/corpus/skills/cratis-screenplay-command-surface/references/context.md +20 -8
  5. package/package/corpus/skills/cratis-screenplay-command-surface/verification.json +33 -1
  6. package/package/corpus/skills/cratis-screenplay-event-modeling/SKILL.md +63 -31
  7. package/package/corpus/skills/cratis-screenplay-event-modeling/references/nine-steps.md +35 -13
  8. package/package/corpus/skills/cratis-screenplay-event-modeling/verification.json +12 -0
  9. package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +50 -9
  10. package/package/corpus/skills/cratis-screenplay-model-authoring/references/language-reference.md +78 -22
  11. package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-tools.md +21 -5
  12. package/package/corpus/skills/cratis-screenplay-model-authoring/verification.json +16 -0
  13. package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +155 -43
  14. package/package/corpus/skills/cratis-screenplay-projections/references/pdl-grammar.md +81 -37
  15. package/package/corpus/skills/cratis-screenplay-projections/verification.json +6 -1
  16. package/package/corpus/skills/cratis-screenplay-read-surface/SKILL.md +64 -17
  17. package/package/corpus/skills/cratis-screenplay-read-surface/verification.json +4 -1
  18. package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +201 -115
  19. package/package/corpus/skills/cratis-screenplay-specifications/verification.json +13 -1
  20. package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +133 -24
  21. package/package/corpus/skills/cratis-screenplay-ui-composition/verification.json +4 -1
  22. package/package.json +1 -1
@@ -25,31 +25,40 @@ source is the single flow model.
25
25
 
26
26
  | Package | Version | Purpose |
27
27
  | --- | --- | --- |
28
- | `Cratis.Screenplay` | `4.12.1` | PDL parser, validator, diagnostics |
28
+ | `Cratis.Screenplay` | `4.31.0` | PDL parser, validator, diagnostics, semantic binder |
29
29
 
30
- Read from the Screenplay repository at tag `v4.12.1` (commit `122eee8`), against
31
- `Documentation/screenplay/projections/` and
32
- `Source/DotNET/Screenplay/Parsing/ProjectionParser.cs`. Reverify before claiming
33
- another version behaves the same.
30
+ Checked against the Screenplay repository at tag `v4.31.0` (commit `355dffb`):
31
+ `Documentation/screenplay/projections/` (including `semantic-model.md`, `keys.md`
32
+ and `variants.md`), `readmodels.md`, `diagnostics.md` and decisions 0001 and 0002.
33
+ Every example below compiles with that version's compiler. Reverify before
34
+ claiming another version behaves the same.
35
+
36
+ **Chronicle decides what a projection means** (Screenplay decision 0001).
37
+ Screenplay owns the grammar; which instance an event affects, what a join may
38
+ create and what a removal removes follow Chronicle's lowering. Syntax that
39
+ compiles can still mean something other than it seems: read the warnings.
34
40
 
35
41
  ## The shape
36
42
 
43
+ Excerpt: the concepts and the three events (each carrying `invoiceId`) are
44
+ declared elsewhere in the model.
45
+
37
46
  ```screenplay
38
47
  readmodel InvoiceDetailsReadModel
48
+ invoiceId InvoiceId
39
49
  invoiceNumber InvoiceNumber
40
50
  status InvoiceStatus
41
51
  lastUpdatedAt DateTime
42
52
 
43
53
  projection InvoiceDetails => InvoiceDetailsReadModel
44
- key invoiceId
45
54
  every
46
55
  lastUpdatedAt = $eventContext.occurred
47
56
  exclude children
48
57
  from InvoiceRegistered key invoiceId
49
58
  status = "draft"
50
- from InvoiceSent
59
+ from InvoiceSent key invoiceId
51
60
  status = "sent"
52
- remove with InvoiceCancelled
61
+ remove with InvoiceCancelled key invoiceId
53
62
  ```
54
63
 
55
64
  **The arrow always points the same way.** A read model never declares what builds
@@ -60,14 +69,18 @@ projections, each building a **different** read model.
60
69
 
61
70
  ## Projection-level directives
62
71
 
63
- `key <expr>`, `no automap`, `sequence <name>`, `file <path>`. A second `key` is
64
- error `PLAY0059`.
72
+ `no automap`, `sequence <name>`, `file <path>`. `sequence` is a realization
73
+ concern and does not bind to the executable model.
74
+
75
+ ⚠️ **Do not write `key` on the projection.** It parses, but neither Chronicle nor
76
+ the executable model routes any event by it, and it is not a default for the
77
+ `from` blocks (warning `PLAY0381`). Key each `from` instead.
65
78
 
66
79
  ## Keys — eight ways, and the default
67
80
 
68
81
  | Form | Example |
69
82
  | --- | --- |
70
- | Projection-level | `key invoiceId` |
83
+ | Projection-level | `key invoiceId` — **routes nothing** (`PLAY0381`); do not use |
71
84
  | From-level inline | `from InvoiceRegistered key invoiceId` |
72
85
  | From-level block | indented `key invoiceId` inside the `from` |
73
86
  | Per event, several events | `from A key idA, B key idB, C` |
@@ -76,6 +89,12 @@ error `PLAY0059`.
76
89
  | Children identity | `children lines identified by lineNumber` |
77
90
  | **Default** | **the event source id** — `from X` ≡ `from X key $eventSourceId` |
78
91
 
92
+ The key resolves from the event's inline key, then the `from` block's key, then
93
+ the event source id. A `from` without a key does **not** inherit another
94
+ `from`'s key: when one event is keyed by `invoiceId` and another is unkeyed, they
95
+ update the same instance only if the event source id happens to equal
96
+ `invoiceId`.
97
+
79
98
  A second key on one `from` is error `PLAY0060`. A template expression in a
80
99
  composite key is error `PLAY0073`; an empty composite key is `PLAY0074`.
81
100
 
@@ -84,11 +103,16 @@ composite key is error `PLAY0073`; an empty composite key is `PLAY0074`.
84
103
  | Block | Fires for | Use for |
85
104
  | --- | --- | --- |
86
105
  | `from <Event>` | that event type only | the actual mapping work |
87
- | `every` | **only** the types this projection subscribes to via `from` | common fields across your own events |
88
- | `all` | **every event type in the system** | system-wide audit logs, global counters |
106
+ | `every` | **only** the event types its level names in `from` and `join` blocks | common fields across your own events |
107
+ | `all` | **every event type in the system**; types no `from` names are keyed by their event source id | activity timestamps and counters per event source |
89
108
 
90
109
  ⚠️ `all` and `every` are not synonyms, and confusing them is the classic PDL
91
- mistake. `every` is scoped to your `from` blocks; `all` is not scoped at all.
110
+ mistake. `every` is scoped to your level's `from` and `join` events — a joined
111
+ event also runs the `every` mappings, so a counter in `every` counts joined
112
+ events too. `all` is not scoped at all. Only a projection's own level can
113
+ subscribe to every event type: `all` inside `children` or `nested` behaves as
114
+ `every` (warning `PLAY0380`). An `all` block beside removals, `children` or
115
+ `nested` binds, but the reference execution plan refuses it.
92
116
 
93
117
  `every` accepts `exclude children`, so child events do not bump the parent's
94
118
  `lastUpdatedAt`. The `every` block **inside** a `children` block does not accept
@@ -98,14 +122,25 @@ it — it is already in a children context.
98
122
 
99
123
  ⚠️ **AutoMap is on by default.** Matching property names are copied before your
100
124
  explicit mappings run, and explicit mappings win. Turn it off with `no automap` at
101
- **projection**, `every`/`all`, `join … with`, `children` or `nested` level — it
102
- **cannot** be toggled inside an individual `from` block.
125
+ **projection**, `every`/`all`, `join`, `children` or `nested` level — it
126
+ **cannot** be toggled inside an individual `from` block. An `automap`/`no automap`
127
+ under a joined event (`with …`) parses but Chronicle replaces it with the auto-map
128
+ of the level the join sits in (`PLAY0380`).
103
129
 
104
130
  Mapping sources: a property path (`name`, `contactInfo.email`), a literal
105
- (`true`, `"Pending"`, `42`, `null`), a template (`` `${first} ${last}` ``),
106
- `$eventSourceId`, `$eventContext.<occurred|sequenceNumber|correlationId|eventSourceId>`,
107
- and `$causedBy.<subject|name|userName>` (an unknown one is an error naming all
108
- three).
131
+ (`true`, `"Pending"`, `42`, `null`), `$eventSourceId`, and
132
+ `$eventContext.<path>` from the event-context catalog (for example `occurred`,
133
+ `sequenceNumber`, `correlationId`, `causedBy.subject`; an unknown path warns with
134
+ `PLAY0295`). Templates (`` `${first} ${last}` ``) and `$causedBy.<…>` parse but
135
+ do not bind to the executable model; write `$eventContext.causedBy.subject`
136
+ instead of `$causedBy.subject`.
137
+
138
+ ⚠️ Only the event source identity (`$eventSourceId`,
139
+ `$eventContext.eventSourceId`) runs in the reference runner. Any other
140
+ `$eventContext` path in a mapping or key — `occurred` in the shape above,
141
+ `sequenceNumber`, `causedBy.subject` — binds, but the reference execution plan
142
+ refuses it, so no specification in that model runs there. Use it when the target
143
+ needs it, and report those specifications as needing a target.
109
144
 
110
145
  `clear <property>` removes a value and is equivalent to `= null`; prefer `clear`.
111
146
  It takes a dotted path (`clear Owner.Note`) and escapes reserved names
@@ -126,23 +161,33 @@ must be numeric.
126
161
 
127
162
  ## Joins
128
163
 
164
+ Excerpt, inside a `projection` whose read model has `customerId` and
165
+ `customerName`:
166
+
129
167
  ```screenplay
130
- join Customer on CustomerId
168
+ join customer on customerId
131
169
  with CustomerCreated
132
- no automap
133
- CustomerName = name
170
+ customerName = name
134
171
  with CustomerUpdated
135
- CustomerName = name
172
+ customerName = name
136
173
  ```
137
174
 
138
- `join <property> on <key>` (error `PLAY0063` otherwise), then one or more
139
- `with <EventType>` blocks (error `PLAY0064` otherwise), each able to toggle
140
- `automap` / `no automap` for itself. Joins work at projection level and inside
141
- `children`. A join cannot declare its own key or trigger removal — that is
175
+ `join <label> on <property>` (error `PLAY0063` otherwise), then one or more
176
+ `with <EventType>` blocks (error `PLAY0064` otherwise). **A join never creates an
177
+ instance**: a joined event updates every existing instance whose `on` property
178
+ equals the joined event's event source id. Chronicle discards the label, but
179
+ inside `children` and `nested` the compiler's completeness check (`PLAY0284`)
180
+ counts the label as the field the join fills, so name it after that field there.
181
+ Joins work at a projection's own level and inside `children`. Inside `nested`,
182
+ Chronicle's engine does not wire a join, a `children` block or a
183
+ `remove via join`; they bind, but the reference execution plan refuses them.
184
+ A join cannot declare its own key or trigger removal — that is
142
185
  `remove via join on`.
143
186
 
144
187
  ## Children and nested
145
188
 
189
+ Excerpt, inside a `projection`:
190
+
146
191
  ```screenplay
147
192
  children lineItems identified by lineNumber
148
193
  from InvoiceLineItemAdded key lineNumber
@@ -167,6 +212,8 @@ nested billingContact
167
212
 
168
213
  ## Removal
169
214
 
215
+ Excerpt, inside a `projection`:
216
+
170
217
  ```screenplay
171
218
  remove with InvoiceCancelled key invoiceId
172
219
  remove via join on CustomerAccountClosed
@@ -174,48 +221,113 @@ remove via join on CustomerAccountClosed
174
221
 
175
222
  `remove with <Event> [key <expr>]` removes the instance the event identifies.
176
223
  `remove via join on <Event> [key <expr>]` removes instances reached through a
177
- join. Inside `children`, both take an indented `parent <expr>` — and **only**
224
+ join. On a projection's own level, as in the second line above, it has no
225
+ verified meaning — Chronicle's engine wires it as a child removal — so it binds
226
+ but the reference execution plan refuses it. Use it inside `children`. Inside
227
+ `children`, both take an indented `parent <expr>` — and **only**
178
228
  `parent`; anything else is error `PLAY0069`. Several removal conditions may
179
229
  coexist.
180
230
 
181
- ## When PDL is not enough — the reducer
231
+ ## Variants — one identity, mutually exclusive read models
232
+
233
+ Excerpt: the events and the three read models (each with a keyed query) are
234
+ declared elsewhere. Every event about an issue is appended with the issue's
235
+ identifier as its event source.
182
236
 
183
237
  ```screenplay
238
+ projection WorkItem
239
+ from TitleChanged
240
+ title = title
241
+ variant BacklogItem
242
+ enters on IssueCreated
243
+ variant DevelopmentItem
244
+ enters on IssueStarted
245
+ variant PullRequestItem
246
+ enters on PullRequestCreated
247
+ ```
248
+
249
+ Each variant is its own read model; only its `enters on` events create it.
250
+ Everything else, including a projection-level shared handler, is update-only: it
251
+ becomes a join on the variant's identifier, matched against the handler's key
252
+ (here the event source).
253
+
254
+ ⚠️ **Mutual exclusion works through the event source identity.** Entering a
255
+ variant removes the entity from its siblings, and that removal is always keyed by
256
+ the entering event's source identity, whatever key the `enters on` line declares.
257
+ Keep the event source equal to the entity's identifier, as above. An
258
+ `enters on IssueStarted key issueId` whose `issueId` differs from the event
259
+ source creates the development item under `issueId` but removes the backlog item
260
+ keyed by the event source instead, so the issue stays in both variants.
261
+
262
+ Diagnostics: no `enters on` (`PLAY0382`), a shared mapping a variant's read
263
+ model lacks (`PLAY0383`), duplicate variant names (`PLAY0384`), an entering
264
+ event claimed twice (`PLAY0385`). The executable model binds variants the way Chronicle's client SDK
265
+ reclassifies them; Chronicle's hosted declaration language does not lower them
266
+ yet (Cratis/Chronicle#4109). Use variants for genuinely different shapes, not for
267
+ a single `status` field.
268
+
269
+ ## When PDL is not enough — the reducer
270
+
271
+ Excerpt: the events and the `AccountBalance` read model are declared elsewhere.
272
+
273
+ ````screenplay
184
274
  reducer Balance => AccountBalance
185
275
  on AmountDeposited
186
- csharp
187
- ```
276
+ ```csharp
188
277
  return context.State is null
189
278
  ? new(context.Event.amount, 1)
190
279
  : context.State with { balance = context.State.balance + context.Event.amount };
191
280
  ```
192
281
  on AmountWithdrawn
193
282
  file Reducers/Withdrawn.cs
194
- ```
195
-
196
- Reach for a reducer only when the next state depends on the current one — a
197
- running balance, a state machine. `context.State` is **null for the first event**
198
- and is the only nullable member; `Event`, `Key`, `Tenant`, `Occurred`,
199
- `SequenceNumber` and `IsFirst` are always there.
283
+ ````
284
+
285
+ Inline code uses a tagged fence (` ```csharp `); a `csharp` line above a bare
286
+ fence still parses but warns (`PLAY0397`). Reach for a reducer only when the next
287
+ state depends on the current one — a running balance, a state machine.
288
+ `context.State` is **null for the first event** and is the only nullable member;
289
+ `Event`, `Key`, `Tenant`, `Occurred`, `SequenceNumber` and `IsFirst` are always
290
+ there.
291
+
292
+ What the executable model does with it (ESM v3):
293
+
294
+ - Every `on` rule needs a body, inline or `file`. Mixing bodied and body-less
295
+ rules is `PLAY0398`; two rules for one event is `PLAY0399`; a reducer with no
296
+ bodies is rejected with a hint to write a projection.
297
+ - The key is **always the event source id** — a reducer cannot route by an event
298
+ property. State starts at null; returning null deletes the instance.
299
+ - The body is an opaque attachment identified by a content hash, never compiled
300
+ or run by Screenplay. The reference runner cannot compute reducer state, so a
301
+ specification that reads a reducer-built read model reports unsupported and
302
+ never passes. A target must supply the transition.
200
303
 
201
304
  **Prefer a projection where one will do.** A reducer is code, and code is the part
202
- of a document a reader cannot check at a glance. A reducer binds to
203
- `UnsupportedSemanticSyntax` in ESM v1, so it never reaches the reference runtime.
305
+ of a document a reader cannot check at a glance.
204
306
 
205
- Read [pdl-grammar.md](references/pdl-grammar.md) for the complete EBNF, every
206
- diagnostic code, and the full worked examples.
307
+ Read [pdl-grammar.md](references/pdl-grammar.md) for the syntax grammar, the
308
+ projection diagnostic codes, and worked examples.
207
309
 
208
310
  ## Verify
209
311
 
210
312
  - [ ] `screenplay <model> --warnaserror` reports zero errors and zero warnings.
211
313
  - [ ] Each read model has **exactly one** builder.
314
+ - [ ] No projection-level `key`; every `from` that must address the same
315
+ instance is keyed on the same identity.
212
316
  - [ ] Every read-model property traces back to an event that carries it.
213
317
  - [ ] `every` was intended where `every` is written — not `all`.
214
318
  - [ ] AutoMap's default-on behavior is intended, or `no automap` is declared at the
215
319
  right level.
216
320
  - [ ] Every `children` block's `from` declares `parent`.
217
321
  - [ ] Every `nested` block contains at least one `from`.
218
- - [ ] A reducer is present only because a projection genuinely could not express it.
322
+ - [ ] No `$causedBy`, template or `sequence` where the model must run.
323
+ - [ ] Where specifications must run in the reference runner: no `$eventContext`
324
+ path other than `eventSourceId`, no projection-level `remove via join`, no
325
+ `all` beside removals, `children` or `nested`, and no `join`, `children` or
326
+ `remove via join` inside `nested`.
327
+ - [ ] Variant entering events use the event source identity as the entity's
328
+ identifier.
329
+ - [ ] A reducer is present only because a projection genuinely could not express it,
330
+ and every rule has a body in a tagged fence or a `file`.
219
331
 
220
332
  ## Route near misses
221
333
 
@@ -1,19 +1,14 @@
1
- # PDL — complete grammar and diagnostics
1
+ # PDL — syntax grammar, meaning and diagnostics
2
2
 
3
- Verified against `Documentation/screenplay/projections/grammar.md` and
4
- `Source/DotNET/Screenplay/Parsing/ProjectionParser.cs` at commit `122eee8`.
3
+ Checked against `Documentation/screenplay/projections/{grammar,semantic-model,keys,variants}.md`
4
+ and `Source/DotNET/Screenplay/Parsing/ProjectionParser.cs` at Screenplay tag
5
+ `v4.31.0` (commit `355dffb`).
5
6
 
6
- Three things below were once looser than the published EBNF, because the parser
7
- accepts more than that EBNF described. `Cratis/Screenplay#199` corrected the
8
- reference, so the two now agree:
9
-
10
- - `EveryBlock` accepts a bare `automap` as well as `no automap`.
11
- - The braces on a composite key are **optional** — a composite key with and
12
- without them both compile clean.
13
- - `$causedBy` is a valid expression root.
14
-
15
- Against a Screenplay older than that fix the published grammar is the stricter of
16
- the two, and this file is the accurate one.
7
+ The grammar says what **parses**. It does not say what an event does at runtime:
8
+ Chronicle's lowering decides that (Screenplay decision 0001), and the
9
+ [meaning table](#syntax-that-parses-but-means-something-else) lists the places
10
+ where the two differ. The braces on a composite key are optional, and
11
+ `EveryBlock` accepts a bare `automap` as well as `no automap`.
17
12
 
18
13
  ## Grammar
19
14
 
@@ -28,7 +23,14 @@ ProjDirective = "no", "automap", NL
28
23
  | CompositeKeyDecl ;
29
24
 
30
25
  Block = EveryBlock | FromAllBlock | FromEventBlock | JoinBlock
31
- | ChildrenBlock | NestedBlock | RemoveWithBlock | RemoveWithJoinBlock ;
26
+ | ChildrenBlock | NestedBlock | RemoveWithBlock | RemoveWithJoinBlock
27
+ | VariantBlock ;
28
+
29
+ VariantBlock = "variant", Ident, NL,
30
+ INDENT, EntersOnDecl, { EntersOnDecl }, { ProjDirective | Block }, DEDENT ;
31
+ EntersOnDecl = "enters", "on", TypeRef, [ "key", Expr ], NL ;
32
+
33
+ (* A projection-level KeyDecl parses but routes no event (PLAY0381). *)
32
34
 
33
35
  EveryBlock = "every", NL, INDENT,
34
36
  [ "automap" | "no", "automap", NL ], { MappingLine },
@@ -93,6 +95,9 @@ LiteralKeyword = "literal", " ", Literal ; (* parsed at the Expr level *)
93
95
  | Nested | `^nested\s+(@?[\w.]+)$` |
94
96
  | Remove with | `^remove\s+with\s+(@?[\w.]+)(?:\s+key\s+(.+))?$` |
95
97
  | Remove via join | `^remove\s+via\s+join\s+on\s+(@?[\w.]+)(?:\s+key\s+(.+))?$` |
98
+ | Clear with | `^clear\s+with\s+(@?[\w.]+)$` |
99
+ | Variant | `^variant\s+(@?[\w.]+)\s*$` |
100
+ | Enters on | `^enters\s+on\s+(@?[\w.]+)(?:\s+key\s+(.+))?$` |
96
101
  | Counters | `^(increment\|decrement\|count\|clear)\s+(@?[$\w.]+)$` |
97
102
  | Arithmetic | `^(add\|subtract)\s+(@?[$\w.]+)\s+by\s+(.+)$` |
98
103
  | Assignment | `^(@?[$\w.@]+)\s*=(?!=\|>)\s*(.+)$` |
@@ -130,20 +135,53 @@ LiteralKeyword = "literal", " ", Literal ; (* parsed at the Expr level *)
130
135
  | `PLAY0190` | A reducer rule body opens with an unknown word |
131
136
  | `PLAY0191` | A read model is built by more than one projection or reducer |
132
137
  | `PLAY0192` | A read model is declared more than once |
138
+ | `PLAY0295`–`PLAY0299` | An `$eventContext` path the event-context catalog does not have |
139
+ | `PLAY0380` | Chronicle drops part of a construct: `all` below the top level, or `automap` on a joined event |
140
+ | `PLAY0381` | A projection-level `key` routes no event |
141
+ | `PLAY0382`–`PLAY0385` | Variant errors: no `enters on`, shared mapping missing on a variant, duplicate variant name, entering event claimed twice |
142
+ | `PLAY0397` | Inline code uses a language line instead of a tagged fence |
143
+ | `PLAY0398` / `PLAY0399` | A reducer mixes bodied and body-less rules / observes one event twice |
144
+
145
+ ## Syntax that parses but means something else
146
+
147
+ | Syntax | What Chronicle and the executable model do |
148
+ | --- | --- |
149
+ | `key` on the projection | Nothing: events route by the `from` key, then the event source id (`PLAY0381`) |
150
+ | `from X` with no key | Routes by the event source id, never by another `from`'s key |
151
+ | `join … on …` | Updates existing instances only; never creates one; the name after `join` is discarded |
152
+ | `automap` under a joined event | Replaced by the auto-map of the join's level (`PLAY0380`) |
153
+ | `all` inside `children`/`nested` | Behaves as `every` (`PLAY0380`) |
154
+ | `$causedBy.<x>` | Does not bind; use `$eventContext.causedBy.<x>` |
155
+ | Templates in mappings or keys | Do not bind |
156
+ | A number or Boolean literal key | Read as a property path; write `key literal "…"` |
157
+ | `parent` outside `children` | Ignored by Chronicle; does not bind |
158
+ | `sequence` | Realization concern; does not bind |
159
+ | Reducer rules | Opaque ESM v3 code keyed by the event source id; not computed by the reference runner |
160
+ | `$eventContext.<path>` other than `eventSourceId` in a mapping or key | Binds; the reference execution plan refuses it, so no specification in the model runs there |
161
+ | `remove via join` on a projection's own level | Chronicle's engine wires it as a child removal; binds, but the reference execution plan refuses it |
162
+ | `all` beside removals, `children` or `nested` | Binds, but the reference execution plan refuses it |
163
+ | `join`, `children` or `remove via join` inside `nested` | Not wired by Chronicle's engine; binds, but the reference execution plan refuses it |
133
164
 
134
165
  ## Worked examples
135
166
 
167
+ Each example is an excerpt: the events and read models it names are declared
168
+ elsewhere in the slice or model. Read-model properties in a `.play` model are
169
+ camelCase (a PascalCase property line is `PLAY0016`), so mapping targets are too.
170
+ Several examples map `$eventContext.occurred`, `sequenceNumber` or
171
+ `causedBy.subject`. Those bind, but the reference runner cannot execute a model
172
+ that contains them; a target must run their specifications.
173
+
136
174
  ### Composite key with event context
137
175
 
138
176
  ```screenplay
139
177
  projection LineItems => LineItemReadModel
140
178
  from LineItemAdded
141
179
  key LineItemKey
142
- OrderId = orderId
143
- LineNumber = lineNumber
144
- SequenceNumber = $eventContext.sequenceNumber
145
- CreatedBy = $causedBy.subject
146
- Product = productName
180
+ orderId = orderId
181
+ lineNumber = lineNumber
182
+ sequenceNumber = $eventContext.sequenceNumber
183
+ createdBy = $eventContext.causedBy.subject
184
+ product = productName
147
185
  ```
148
186
 
149
187
  ### Global counter with a literal key
@@ -151,7 +189,7 @@ projection LineItems => LineItemReadModel
151
189
  ```screenplay
152
190
  projection SiteStats => SiteStatsReadModel
153
191
  from UserLoggedIn key literal "site-stats"
154
- count TotalLogins
192
+ count totalLogins
155
193
  lastLogin = $eventContext.occurred
156
194
  ```
157
195
 
@@ -168,23 +206,24 @@ projection ActivityFeed => ActivityFeedModel
168
206
  recentUsers = name
169
207
  ```
170
208
 
171
- `totalSystemEvents` counts **every** event in the system; `recentUsers` only fills
172
- on `UserRegistered`.
209
+ `all` subscribes to **every** event type in the system; `recentUsers` only fills
210
+ on `UserRegistered`. Chronicle keys the event types that only `all`
211
+ reaches by their **event source id**, so each event source gets its own instance
212
+ and its own count; `all` takes no key of its own.
173
213
 
174
214
  ### Children with a join and scoped removal
175
215
 
176
216
  ```screenplay
177
217
  projection Group => GroupReadModel
178
218
  from GroupCreated
179
- Name = name
219
+ name = name
180
220
  children members identified by userId
181
221
  from UserAddedToGroup key userId
182
222
  parent groupId
183
- Role = role
184
- join User on UserId
223
+ role = role
224
+ join userName on userId
185
225
  with UserCreated
186
- no automap
187
- UserName = name
226
+ userName = name
188
227
  remove with UserRemovedFromGroup key userId
189
228
  parent groupId
190
229
  ```
@@ -194,29 +233,30 @@ projection Group => GroupReadModel
194
233
  ```screenplay
195
234
  projection Group => GroupReadModel
196
235
  every
197
- LastActivity = $eventContext.occurred
236
+ lastActivity = $eventContext.occurred
198
237
  exclude children
199
238
  from GroupCreated
200
- Name = name
239
+ name = name
201
240
  children members identified by userId
202
- from UserAdded
203
- Name = userName
241
+ from UserAdded key userId
242
+ parent groupId
243
+ name = userName
204
244
  ```
205
245
 
206
- Group-level events bump `LastActivity`; member events do not.
246
+ Group-level events bump `lastActivity`; member events do not.
207
247
 
208
248
  ### Nested nullable object
209
249
 
210
250
  ```screenplay
211
251
  projection Slice => SliceReadModel
212
252
  from SliceCreated
213
- Name = name
253
+ name = name
214
254
  nested command
215
255
  from CommandSetForSlice
216
- Name = commandName
217
- Schema = schema
256
+ name = commandName
257
+ schema = schema
218
258
  from CommandRenamed
219
- Name = newName
259
+ name = newName
220
260
  clear with CommandClearedForSlice
221
261
  ```
222
262
 
@@ -227,6 +267,10 @@ projection Slice => SliceReadModel
227
267
 
228
268
  ```screenplay
229
269
  slice StateView CustomerPortalReport
270
+ readmodel PortalReportReadModel
271
+ invitedAt DateTime
272
+ readmodel RevokedPortalTokenReadModel
273
+ revokedAt DateTime
230
274
  query GetPortalReport => PortalReportReadModel
231
275
  by customerId CustomerId
232
276
  projection PortalReport => PortalReportReadModel
@@ -8,6 +8,11 @@
8
8
  { "kind": "skill-contains", "value": "AutoMap is on by default" },
9
9
  { "kind": "skill-contains", "value": "PLAY0191" },
10
10
  { "kind": "skill-contains", "value": "every event type in the system" },
11
- { "kind": "skill-contains", "value": "references/pdl-grammar.md" }
11
+ { "kind": "skill-contains", "value": "references/pdl-grammar.md" },
12
+ { "kind": "skill-contains", "value": "Do not write `key` on the projection." },
13
+ { "kind": "skill-contains", "value": "**routes nothing** (`PLAY0381`)" },
14
+ { "kind": "skill-contains", "value": "The key is **always the event source id**" },
15
+ { "kind": "skill-contains", "value": "**A join never creates an" },
16
+ { "kind": "skill-contains", "value": "Inline code uses a tagged fence" }
12
17
  ]
13
18
  }