@cratis/pi 7.66.0 → 7.68.0

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 (51) hide show
  1. package/package/corpus/product-sync.json +3 -3
  2. package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/realization-and-gap-fill.md +1 -1
  3. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/atomic-batches-and-source-mappings.md +1 -1
  4. package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +4 -3
  5. package/package/corpus/skills/cratis-screenplay-command-surface/references/date-comparisons.md +13 -2
  6. package/package/corpus/skills/cratis-screenplay-model-authoring/references/language-reference.md +2 -2
  7. package/package/corpus/skills/cratis-screenplay-model-authoring/references/state-and-recovery.md +1 -0
  8. package/package/corpus/skills/cratis-screenplay-model-review/references/checklist.md +1 -1
  9. package/package/corpus/skills/cratis-screenplay-model-review/references/public-keys-and-routes.md +4 -2
  10. package/package/corpus/skills/cratis-screenplay-model-review/references/report-template.md +2 -2
  11. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/SKILL.md +1 -1
  12. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/phases.md +1 -1
  13. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/stop-or-assume.md +1 -1
  14. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/verdicts-and-modes.md +6 -1
  15. package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +8 -1
  16. package/package/corpus/skills/cratis-screenplay-projections/references/pdl-grammar.md +42 -2
  17. package/package/corpus/skills/cratis-screenplay-projections/references/reducer-identity.md +88 -0
  18. package/package/corpus/skills/cratis-screenplay-projections/verification.json +2 -1
  19. package/package/corpus/skills/cratis-screenplay-read-surface/SKILL.md +14 -7
  20. package/package/corpus/skills/cratis-screenplay-read-surface/references/equality-filters.md +1 -1
  21. package/package/corpus/skills/cratis-screenplay-read-surface/references/named-keys.md +168 -58
  22. package/package/corpus/skills/cratis-screenplay-read-surface/verification.json +2 -2
  23. package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/references/gap-fill-handoff.md +1 -1
  24. package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/references/renderability-gate.md +1 -1
  25. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/scenario-examples.md +2 -2
  26. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/spec-forms-by-mode.md +3 -3
  27. package/package/corpus/skills/cratis-screenplay-slice-design/SKILL.md +5 -7
  28. package/package/corpus/skills/cratis-screenplay-slice-design/references/public-boundaries-and-keys.md +5 -3
  29. package/package/corpus/skills/cratis-screenplay-slice-design/references/read-model-design.md +7 -3
  30. package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +3 -2
  31. package/package/corpus/skills/cratis-screenplay-specifications/references/query-rejections.md +4 -0
  32. package/package/corpus/skills/cratis-screenplay-specifications/references/vocabulary.md +17 -0
  33. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/SKILL.md +3 -1
  34. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/read-model-keys.md +46 -0
  35. package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +11 -5
  36. package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet.md +17 -2
  37. package/package/corpus/skills/cratis-screenplay-toolchain/references/diagnostics.md +22 -8
  38. package/package/corpus/skills/cratis-screenplay-toolchain/references/executable-subset.md +31 -9
  39. package/package/corpus/skills/cratis-screenplay-toolchain/references/language-admission.md +15 -5
  40. package/package/corpus/skills/cratis-screenplay-toolchain/references/production-routes-and-filters.md +1 -1
  41. package/package/corpus/skills/cratis-screenplay-toolchain/references/renderable-subset.md +3 -3
  42. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.json +1722 -76
  43. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.version +1 -1
  44. package/package/corpus/skills/cratis-screenplay-toolchain/references/traps.md +3 -2
  45. package/package/corpus/skills/cratis-screenplay-toolchain/references/verdicts.md +64 -3
  46. package/package/corpus/skills/cratis-screenplay-toolchain/verification.json +8 -2
  47. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +5 -4
  48. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +12 -7
  49. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/sandbox-and-specrunner.md +17 -0
  50. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/verification.json +1 -1
  51. package/package.json +1 -1
@@ -39,72 +39,188 @@ Chronicle's object keys are not ordered, encoded event stream ids.
39
39
  Choose a composite key when independent coordinates identify the business view,
40
40
  such as resource and reporting period. Prefer the natural single id when the view
41
41
  is one entity's state. Do not concatenate parts into an invented string id merely
42
- to pass binding; keep the truthful model and report the capability gap. Read-model
43
- keys do not supply projection routes: align each PDL `from ... key` mapping with
44
- the declared view identity (projection keys are a separate contract).
42
+ to pass binding; keep the truthful model. Read-model keys do not supply projection
43
+ routes: align each PDL `from ... key` block with the declared parts (several flat
44
+ parts take an untyped `key` block naming every one; a single key of composite type
45
+ is a different shape, and one read model has exactly one of them).
45
46
 
46
47
  ## Supply every part
47
48
 
48
- A scalar query keeps `by name Type` on its own body line. A composite query has a
49
- bare `by` header and at least two distinct `name Type [from source]` children.
50
- Do not combine scalar and block forms. A single-instance lookup must supply every
51
- key part exactly once, with the declared types; collection queries may narrow by
52
- a subset but still have their separate executable-query limitations.
49
+ Requires Screenplay 4.204.0 (ESM v27; `screenplay --version`) to bind a read model with
50
+ two or more `key` marks; older binders refuse it with PLAY0268. A model with one `key`
51
+ mark keeps its earlier ESM version and bytes, so only models that use several parts
52
+ select v27.
53
+
54
+ A scalar query keeps `by name Type` on its own body line. Each lookup shape for a
55
+ composite-keyed read model is separate:
56
+
57
+ | Shape | Form | Result |
58
+ | --- | --- | --- |
59
+ | Optional single-instance query | bare `by` header, then every key part once as `name Type` | binds; the caller supplies every part |
60
+ | List query | equality `filter name Type [optional]` lines (one filter per narrowed part) | binds; a row matches when every supplied filter is equal |
61
+ | List query, one scalar `by` | `by name Type` on one line | binds as for a single key |
62
+ | List query with a `by` block | bare `by` header over a collection | PLAY0268: use filters |
63
+ | Non-optional single return, or a part with a `from` source | any `by` block | PLAY0268 |
64
+ | Missing part | any single-instance lookup | PLAY0661 |
65
+ | Unknown, optional, collection or ill-typed part; fewer than two parts | `by` block | PLAY0662 |
66
+
67
+ Do not combine scalar and block `by` forms. A caller-supplied part is never a trusted
68
+ tenant or subject: keep ownership at the runtime boundary.
69
+
70
+ Each `by` part runs its concept rules at execution, like a single `by` argument. A
71
+ specification argument that violates a rule still binds, so a Validation rejection
72
+ can be specified.
73
+
74
+ Command and trigger reads use a child `by` block of `part = source`, with required,
75
+ noncollection sources of compatible nominal types, not literals. Neither executes
76
+ over a composite view in the usual way:
77
+
78
+ - A command `reads … by` without the application-root `command reads protected`
79
+ preamble is PLAY0271. With the preamble it is PLAY0781 (reason ByParts), PLAY0782
80
+ first when the command has no `identifier`, and PLAY0273 when the key is not an
81
+ input property.
82
+ - A trigger `reads … by` binds when the reaction only invokes commands (information
83
+ PLAY0270); a reaction that produces events directly or runs code is PLAY0268.
53
84
 
54
- A command or reaction trigger read uses a child `by` block of `part = source`.
55
- Sources are required, noncollection property paths with compatible nominal types,
56
- not literals. Trigger reads map from the trigger's declared values. The historical
57
- single `reads View by value` form remains available for a single key.
85
+ Screen `data ... via query ... by value`, forms and navigation remain scalar
86
+ lookups: they cannot supply a composite single-instance key (PLAY0661). Do not
87
+ invent UI tuple syntax.
58
88
 
59
- This complete authoring-only example declares all lookup parts:
89
+ One executable example: a composite-keyed read model, a projection whose root `from`
90
+ and `remove with` name every part in an untyped `key` block, a list query with
91
+ equality filters, an optional single-instance query with a `by` block, and
92
+ specifications that run.
60
93
 
61
94
  ```screenplay
62
- concept ResourceId : Uuid
63
- concept Period : String
64
- module Reporting
65
- feature Months
95
+ concept InvoiceId : String
96
+ validate
97
+ min 3 message "Invoice id is too short"
98
+ concept PaymentMethod : Enum
99
+ card
100
+ cash
101
+
102
+ module Billing
103
+ feature Payments
66
104
  slice StateView Totals
67
- readmodel ResourceMonth
68
- resourceId ResourceId key
69
- period Period key
70
- hours Decimal
71
- query FindMonth => ResourceMonth optional
105
+ event PaymentReceived
106
+ invoice InvoiceId
107
+ method PaymentMethod
108
+ amount Int
109
+ event PaymentRemoved
110
+ invoice InvoiceId
111
+ method PaymentMethod
112
+ readmodel PaymentTotals
113
+ invoiceId InvoiceId key
114
+ method PaymentMethod key
115
+ total Int
116
+ query TotalsForInvoice => PaymentTotals[]
117
+ filter invoiceId InvoiceId
118
+ filter method PaymentMethod optional
119
+ query Total => PaymentTotals optional
72
120
  by
73
- resourceId ResourceId
74
- period Period
75
- slice StateChange CloseMonth
76
- command CloseMonth
77
- resourceId ResourceId identifier
78
- period Period
79
- reads ResourceMonth as totals
80
- by
81
- resourceId = resourceId
82
- period = period
83
- produces event MonthClosed
84
- for resourceId
85
- period Period = period
121
+ invoiceId InvoiceId
122
+ method PaymentMethod
123
+ projection PaymentTotals => PaymentTotals
124
+ no automap
125
+ from PaymentReceived
126
+ key
127
+ invoiceId = invoice
128
+ method = method
129
+ total = amount
130
+ remove with PaymentRemoved
131
+ key
132
+ invoiceId = invoice
133
+ method = method
134
+ specification TwoMethodsOneInvoice
135
+ given readmodel PaymentTotals
136
+ invoiceId = "INV-1"
137
+ method = "card"
138
+ total = 12
139
+ given readmodel PaymentTotals
140
+ invoiceId = "INV-1"
141
+ method = "cash"
142
+ total = 8
143
+ when query Total
144
+ invoiceId = "INV-1"
145
+ method = "cash"
146
+ then result
147
+ total = 8
148
+ specification FilterOnePart
149
+ given readmodel PaymentTotals
150
+ invoiceId = "INV-1"
151
+ method = "card"
152
+ total = 12
153
+ given readmodel PaymentTotals
154
+ invoiceId = "INV-2"
155
+ method = "card"
156
+ total = 3
157
+ when query TotalsForInvoice
158
+ invoiceId = "INV-1"
159
+ then result
160
+ invoiceId = "INV-1"
161
+ method = "card"
162
+ total = 12
163
+ specification RemoveOneMethod
164
+ given readmodel PaymentTotals
165
+ invoiceId = "INV-1"
166
+ method = "card"
167
+ total = 12
168
+ given readmodel PaymentTotals
169
+ invoiceId = "INV-1"
170
+ method = "cash"
171
+ total = 8
172
+ when append PaymentRemoved
173
+ invoice = "INV-1"
174
+ method = "card"
175
+ then no readmodel PaymentTotals for {"invoiceId":"INV-1","method":"card"}
176
+ then readmodel PaymentTotals
177
+ invoiceId = "INV-1"
178
+ method = "cash"
179
+ total = 8
180
+ specification RecreateRemovedInstance
181
+ given PaymentReceived
182
+ invoice = "INV-1"
183
+ method = "card"
184
+ amount = 12
185
+ given PaymentRemoved
186
+ invoice = "INV-1"
187
+ method = "card"
188
+ when append PaymentReceived
189
+ invoice = "INV-1"
190
+ method = "card"
191
+ amount = 20
192
+ then readmodel PaymentTotals
193
+ invoiceId = "INV-1"
194
+ method = "card"
195
+ total = 20
86
196
  ```
87
197
 
88
- Screen `data ... via query ... by value`, forms and navigation remain scalar
89
- lookups: they cannot supply a composite single-instance key (PLAY0661). Do not
90
- invent UI tuple syntax. Context-derived query parts do not become caller inputs;
91
- keep tenant/subject ownership at the runtime boundary, not in editable UI filters.
198
+ Projection rules for the same shape are in the projections skill (`SKILL.md`, keys).
199
+ Verify with `screenplay <folder> --warnaserror` (0 errors, 0 warnings) and
200
+ `screenplay test <folder>` (all passed, none Unsupported).
92
201
 
93
202
  ## Specifications and executable boundary
94
203
 
95
- `given readmodel` and `then readmodel` state every key part (PLAY0351 if missing).
96
- A composite absence fixture uses an object with every named part. Query result
97
- assertions may omit keys when arguments already select the instance.
98
-
99
- Composite read-model keys, query `by` blocks and fixtures
100
- over those views remain authoring-only: binding reports PLAY0268 citing
101
- Screenplay#599 (still open). They do not reference-execute or render. This is not
102
- ESM v10 admission; v10 covers reaction identity, production routes and observer
103
- filters. Eligible command reads separately opt into v25 with `command reads protected`;
104
- legacy reads remain PLAY0271, and composite keys are still refused (PLAY0781 ByParts).
105
- See command-surface [protected reads](../../cratis-screenplay-command-surface/references/protected-reads.md).
106
- A single explicit key does bind; Stage admission still depends on the complete
107
- model and projection/query shape, not just the key modifier.
204
+ `given readmodel` and `then readmodel` state every key part (PLAY0351 if missing;
205
+ a repeated part is PLAY0519). `then no readmodel` takes an object naming every part
206
+ (missing PLAY0661, unknown PLAY0662, repeated member PLAY0294). Parts match one by
207
+ one, in any order; Uuid parts compare as ordinal text with no case folding. Query
208
+ arguments supply every `by` part (single) or the filters (list); a missing, unknown
209
+ or repeated argument is PLAY0468. A single-instance query result may omit parts the
210
+ arguments supply; list-query result rows state every part. A stated part that
211
+ differs from the argument makes the row fail to match at run time, not at binding.
212
+
213
+ Projections on a composite-keyed read model admit only root `from` and `remove with`.
214
+ Joins, `children`, `remove via join`, `every`, `all`, `nested`, `clear with`,
215
+ `variant` and reducers are PLAY0268. This is not ESM v10 admission.
216
+
217
+ Stage does not admit v27: it refuses the whole model with `STAGE-ESM-016`, so
218
+ `cratis render` cannot deliver it. Verify and reference-test the model with the
219
+ standalone compiler, then hand the delivery to gap-fill
220
+ (`cratis-screenplay-render-and-gap-fill`) with the model as the contract.
221
+ Eligible command reads separately opt into v25 with `command reads protected`;
222
+ a composite lookup there is still refused (PLAY0781 ByParts). See command-surface
223
+ [protected reads](../../cratis-screenplay-command-surface/references/protected-reads.md).
108
224
 
109
225
  ## Repairs and evidence
110
226
 
@@ -116,9 +232,3 @@ model and projection/query shape, not just the key modifier.
116
232
  - Read-model property rename remains unsupported until its mapping chain can be
117
233
  preserved. Use the model-authoring proposal/preview/apply contract, not a text
118
234
  shortcut around an identity-preserving refusal.
119
-
120
- Source: Screenplay `Documentation/screenplay/{readmodels,queries,commands,reactions,specifications}.md`,
121
- `fixtures/named-read-model-keys.play`, and
122
- `Source/DotNET/Screenplay/Semantics/SemanticModelBinder.ReadModels.cs` (identity inference).
123
- Its authoring delivery does not include
124
- an executable composite-key contract.
@@ -3,9 +3,9 @@
3
3
  "input": "Declare a Screenplay query and the screen that binds to it.",
4
4
  "fixtures": [],
5
5
  "assertions": [
6
- { "kind": "skill-contains", "value": "query FindMonth", "file": "references/named-keys.md" },
6
+ { "kind": "skill-contains", "value": "query Total => PaymentTotals optional", "file": "references/named-keys.md" },
7
7
  { "kind": "skill-contains", "value": "command reads only", "file": "references/named-keys.md" },
8
- { "kind": "skill-contains", "value": "remain authoring-only", "file": "references/named-keys.md" },
8
+ { "kind": "skill-contains", "value": "Requires Screenplay 4.204.0 (ESM v27", "file": "references/named-keys.md" },
9
9
  { "kind": "play-compiles" },
10
10
  { "kind": "skill-contains", "value": "Caller-supplied equality query filters (ESM v14)", "file": "references/equality-filters.md" },
11
11
  { "kind": "skill-contains", "value": "Supplied `null` is refused", "file": "references/equality-filters.md" },
@@ -108,7 +108,7 @@ reaction or drop an effect to dodge rendering.
108
108
  Preserve declared `reaction batches atomic` boundaries: each trigger occurrence's
109
109
  direct productions and each selected refusal branch are separate batches, with
110
110
  invoked commands independent. Stage refuses v22 (STAGE-ESM-016) and atomic mode
111
- in admitted v23–v25 (STAGE-ESM-043); the declaration is not external-effect deduplication.
111
+ in admitted v23–v26 (STAGE-ESM-043); the declaration is not external-effect deduplication.
112
112
  For described handlers, hand off only payload computation under the v23 result
113
113
  contract; Stage STAGE-IMPL-002 means no stubs, even Stub mode. Protected v25 reads
114
114
  refuse Cratis C# rendering with STAGE-ESM-044 despite version admission and need
@@ -31,7 +31,7 @@ ESM). Only the probe answers renderability. `PLAY0270` entries are information,
31
31
  | Class | Meaning | Typical examples (each verified in `admission.md`) | Action |
32
32
  | --- | --- | --- | --- |
33
33
  | **Model fix (in mode)** | the model is in, or the user chose, renderable mode and breaks its own subset in a way that has a renderable equivalent with the same meaning | a `produces` whose `for` is not the command identifier; two projections for one read model (`-007`); a policy Stage cannot express portably (`-015`; role-only and opaque pure policies render) | edit request to the modeler session with address, change and rationale |
34
- | **Capability gap** | the intended meaning needs a construct Stage does not render yet | DateTime equality filters and required parameters after optional filters (`-010`); context/single-result filters, opaque `scoped to`/`performer` and composite `by` remain PLAY0268; Translate, clocks, captures, non-event triggers and opaque reaction bodies (`-024`); claim-reading/opaque `runs as` gates (`-034`), legacy `from` (`-035`), mixed effects in one handler (`-037`, retries can duplicate events; keep it whole), unsupported reaction mappings (`-039`); unsupported generated types/result combinations (`-028`/`-038`); `then no readmodel` (`-027`); evolved events (`STAGE-ESM-026`); unsupported expression/enum production conditions (`-006`; supported conditional productions render); legacy reads (`PLAY0271`), protected-read rendering (STAGE-ESM-044 despite v25 admission; semantic Host/executor support is separate); code validation (`-005`); protected identities/enums (`-040`) or unadmitted explicit subject lineage (`PLAY0268`); attached command handlers (PLAY0268), described handlers (v23, STAGE-IMPL-002 even Stub mode), atomic mode (STAGE-ESM-016 for v22, STAGE-ESM-043 in admitted v23–v25) | keep the model; fallback record entry; options a to c in `SKILL.md`; link the Stage issue |
34
+ | **Capability gap** | the intended meaning needs a construct Stage does not render yet | DateTime equality filters and required parameters after optional filters (`-010`); context/single-result filters, opaque `scoped to`/`performer` and a list-query `by` block remain PLAY0268; a read model with several key parts (v27) refuses `STAGE-ESM-016`; Translate, clocks, captures, non-event triggers and opaque reaction bodies (`-024`); claim-reading/opaque `runs as` gates (`-034`), legacy `from` (`-035`), mixed effects in one handler (`-037`, retries can duplicate events; keep it whole), unsupported reaction mappings (`-039`); unsupported generated types/result combinations (`-028`/`-038`); `then no readmodel` (`-027`); evolved events (`STAGE-ESM-026`); unsupported expression/enum production conditions (`-006`; supported conditional productions render); legacy reads (`PLAY0271`), protected-read rendering (STAGE-ESM-044 despite v25 admission; semantic Host/executor support is separate); code validation (`-005`); protected identities/enums (`-040`) or unadmitted explicit subject lineage (`PLAY0268`); attached command handlers (PLAY0268), described handlers (v23, STAGE-IMPL-002 even Stub mode), atomic mode (STAGE-ESM-016 for v22, STAGE-ESM-043 in admitted v23–v26) | keep the model; fallback record entry; options a to c in `SKILL.md`; link the Stage issue |
35
35
  | **Tool skew** | the bundled compiler disagrees with documented semantics | the false `PLAY0285` on reaction cascades from an older bundle; codes the standalone compiler does not raise | record in the session state and the fallback record; never "fix" a correct model |
36
36
  | **Environment** | not the model | restore failures, missing SDK, a destination that is not writable | fix the environment or report blocked |
37
37
 
@@ -219,6 +219,6 @@ empty-list intent is a separate claim from a keyed row being absent (`view-and-s
219
219
  queries are executable too (`ListOpenInvoices` in the backing example); caller-supplied equality filters on collections bind as ESM v14.
220
220
  Pass `by` and filters in query arguments; PLAY0468 reports unknown, repeated or
221
221
  missing required arguments. Optional filters may be omitted, supplied null is refused.
222
- Context-sourced/single-result filters, opaque `scoped to`/`performer`, and composite
223
- `by` remain PLAY0268. Compiled coverage and renderer ceilings:
222
+ Context-sourced/single-result filters, opaque `scoped to`/`performer`, and a list-query
223
+ `by` block remain PLAY0268. Compiled coverage and renderer ceilings:
224
224
  read-surface [equality filters](../../cratis-screenplay-read-surface/references/equality-filters.md).
@@ -18,7 +18,7 @@ Typed example/override forms: `cratis-screenplay-specifications`
18
18
  | `then readmodel` / `then query` (keyed `ById`, `arguments`, `result`) | yes | yes | yes |
19
19
  | `then query` on an unfiltered list or a list narrowed by one `by` key (observable too) | yes | yes: no `arguments` for an unkeyed list, one `result` per row stating the read-model identifier, no `result` = empty list | snapshot and observable lists render |
20
20
  | Collection query with caller-supplied equality `filter`, including observable | yes | yes, ESM v14: same-named scalar property/type, AND with `by`; optional omission allowed, supplied null refused; PLAY0468 checks arguments; row order does not matter | yes on supported snapshot/observable collections; DateTime/filter parameter order refuse -010; independent Stage executor still refuses filtered assertions (-010) |
21
- | Context `from`, single-result filters, `scoped to`, `performer` or composite `by` | yes | PLAY0268: query context, collection-only filtering, opaque scope/implementation or multipart lookup are unadmitted | no |
21
+ | Context `from`, single-result filters, `scoped to`, `performer` or a list-query `by` block | yes | PLAY0268: query context, collection-only filtering, opaque scope/implementation or a multipart list lookup are unadmitted | no |
22
22
  | `then no readmodel <RM> for "<key>"` | yes | yes | no |
23
23
  | `when query` -> `then result` / `then no result` | yes | yes | same as `then query`: only the query-only cases below |
24
24
  | `given clock` / `when clock`, `when trigger`, record-source `given`/`when capture` | yes | yes (standalone and any cratis whose compiler admits v6; an older bundle does not, see toolchain `references/executable-subset.md`) | no |
@@ -34,12 +34,12 @@ Typed example/override forms: `cratis-screenplay-specifications`
34
34
  | Refusal branches, `$refusal` | yes | ESM v19; first matching branch wins | Stage supported subset; -039 for unfaithful refusal details/trigger groups, -011 for specification limits |
35
35
  | Query `then error` | yes | ESM v18; one query/one error, no result/denial | requires target v18 admission; a v19-capable renderer does not necessarily admit v18 |
36
36
  | Composite (JSON) values in specs | yes | yes | no |
37
- | Same-primitive `Date`/`DateTime` comparisons in `require`, `produces when` and command or property validation rules (v26; Requires Screenplay 4.199.0; see [date comparisons](../../cratis-screenplay-command-surface/references/date-comparisons.md)) | spec them | binds and executes; a `DateTime` input without an offset is a Validation rejection | v26 STAGE-ESM-016: gap-fill |
37
+ | Same-primitive `Date`/`DateTime` comparisons in `require`, `produces when` and command or property validation rules (v26; Requires Screenplay 4.199.0; see [date comparisons](../../cratis-screenplay-command-surface/references/date-comparisons.md)) | spec them | binds and executes; a `DateTime` input without an offset is a Validation rejection | Host/executor run it (Requires Stage 4.135.0); renderer STAGE-ESM-046: gap-fill |
38
38
  | Date rules using `today`, `$context`, mixed `Date`/`DateTime`, concept or reaction-`where` positions; nested paths | spec them in design | rule does not bind: `recorded` | `recorded` |
39
39
  | Bodied rules, code policies | spec them | spec reported **unsupported**, never passed | no |
40
40
  | Pending described command handlers (v23) | specify intent | binds; denial/validation execute, body-reaching specs Unsupported ImplementationBody; all-Unsupported is not green | STAGE-IMPL-002 even Stub mode |
41
41
  | Attached command handlers, `file` constraints, query `performer`s | spec them in design | blocked: PLAY0268 at binding, no spec runs (V3 blocked) | no |
42
- | Atomic reaction mode (v22) | declare for new models | failed batch adds no facts or view changes; earlier batches/commands remain; `then error` alone is insufficient | v22 STAGE-ESM-016; mode in admitted v23–v25 STAGE-ESM-043 |
42
+ | Atomic reaction mode (v22) | declare for new models | failed batch adds no facts or view changes; earlier batches/commands remain; `then error` alone is insufficient | v22 STAGE-ESM-016; mode in admitted v23–v26 STAGE-ESM-043 |
43
43
  | Protected command reads (v25) | declare opt-in for eligible reads | absence Validation; matching intervening facts Conflict; race vectors are not a new spec construct | Cratis C# rendering STAGE-ESM-044; semantic Host/executor execute admitted reads, non-Error protected requirements STAGE-ESM-045; v25 pair admitted |
44
44
 
45
45
  Requires Screenplay 4.105.0 or later (`screenplay --version`) for list-query specifications, and 4.143.0 or later for collection equality filters (ESM v14).
@@ -38,8 +38,7 @@ Ask the batch below only for what the request leaves open: (1) origin of each ac
38
38
  ## Procedure
39
39
 
40
40
  1. **Fix the mode** for the scope (`cratis-screenplay-modeling-lifecycle`): design, executable
41
- or renderable. It decides which shapes you may use (steps 6, 9). Read the existing model
42
- before adding to it.
41
+ or renderable. It decides which shapes you may use (steps 6, 9). Read the existing model first.
43
42
  2. **Find each event's origin.** For every event ask who or what makes it happen and through
44
43
  which path: a persona's screen `action` or form, a reaction's `invokes`, a capture. The
45
44
  origin must be **reachable** (some screen, form, reaction or integration leads to the
@@ -54,8 +53,7 @@ Ask the batch below only for what the request leaves open: (1) origin of each ac
54
53
  separate reasons change these values, then split (`references/slicing.md` *Generic edits*).
55
54
  One input per thing the caller decides or supplies; `identifier` on the property naming the
56
55
  instance (omit only for allocation); inline `produces event <Name>` (no `for`) when the command owns the event; plain `produces <Name>` needs `for <identifier>`. Trace each input
57
- (`references/field-lineage.md`) and list the values each rule reads: input, state or context
58
- (`references/command-inventory.md`). A conditional input is a granularity question: two
56
+ (`references/field-lineage.md`) and list the values each rule reads: input, state or context. A conditional input is a granularity question: two
59
57
  decisions are two commands; one decision with a dependent rule is an implication
60
58
  (`require a == false or b > c`). Never a boolean attestation input (`confirmsX == true`)
61
59
  standing in for a rule. Place each rule in its layer (`references/rule-layers.md`). A rule
@@ -89,7 +87,7 @@ Ask the batch below only for what the request leaves open: (1) origin of each ac
89
87
  6. **Design the read side** (`references/read-model-design.md`). Enumerate every screen and
90
88
  automation first: one read model per screen component or automation need; the typical
91
89
  slice is `read model -> screen -> command -> event`. Pick the builder (declarative projection first, reducer when
92
- guards cannot be expressed). The query follows the business view: a list where people scan a
90
+ guards cannot be expressed; a lookup `by` another identity type warns `PLAY0690`). The query follows the business view: a list where people scan a
93
91
  list, a keyed lookup where they open one thing. For **executable or renderable scope**, every
94
92
  read model needs one unambiguous identity: explicit trailing `key` first. Read `references/read-model-design.md` ("Executable identity of a read model") for the fallback order, the key decision table and the Stage rule.
95
93
 
@@ -122,7 +120,7 @@ Ask the batch below only for what the request leaves open: (1) origin of each ac
122
120
 
123
121
  - At most one `identifier` per command; never on an event property or read-model property (use `key`). At most one builder per read model (`PLAY0191`). Read `references/read-model-design.md` ("Identifier and key contract") for the exact rules.
124
122
 
125
- - A query's `by` goes on its own body line; `from`, single-result filters, scope, performer and composite `by` remain `PLAY0268`; equality filters on collections bind as ESM v14. Read `references/read-model-design.md` ("Query binding") for the binding rules.
123
+ - A query's `by` goes on its own body line; `from`, single-result filters, scope, performer and a list-query `by` block remain `PLAY0268`; equality filters on collections bind as ESM v14. Read `references/read-model-design.md` ("Query binding") for the binding rules.
126
124
 
127
125
  - A `unique` constraint names a property the event declares directly; the constraint name is
128
126
  its identity (renaming starts an empty index).
@@ -146,7 +144,7 @@ collections, invariants, who sees which rows); report a finding only with a doma
146
144
 
147
145
  ## Public boundaries and view identity
148
146
 
149
- Translate crosses an application boundary (inbound consumes foreign public events; outbound folds private facts into exactly one local public event type); commands never publish public events. Mark view identity with trailing `key`; composite view keys remain unadmitted (`PLAY0268`) - keep the true identity and report the gap. Read [references/public-boundaries-and-keys.md](references/public-boundaries-and-keys.md) when a slice crosses an application boundary or a view needs a composite identity.
147
+ Translate crosses an application boundary (inbound consumes foreign public events; outbound folds private facts into exactly one local public event type); commands never publish public events. Mark view identity with trailing `key`; flat composite view keys bind as ESM v27 (Requires Screenplay 4.204.0) - keep the true identity. Read [references/public-boundaries-and-keys.md](references/public-boundaries-and-keys.md) when a slice crosses an application boundary or a view needs a composite identity.
150
148
 
151
149
  ## Identity-affecting edits on an existing model
152
150
 
@@ -11,6 +11,8 @@ the slice outline and trace every exported field; read automations-and-translati
11
11
  Mark view identity with trailing `key`. Use a composite key for independent view
12
12
  coordinates (resource + period), a natural single id for one entity's state.
13
13
  Supply every named part in single-instance queries and command/trigger reads;
14
- see read-surface `references/named-keys.md`. Composite view keys remain
15
- unadmitted (PLAY0268), unlike admitted composite stream routes.
16
- Keep the true identity and report the gap rather than inventing a concatenated id.
14
+ see read-surface `references/named-keys.md`. Flat composite view keys bind as ESM v27
15
+ (older binders refuse with PLAY0268; compiler requirement in named-keys), as do composite
16
+ stream routes. Command and trigger reads by parts stay refused, and Stage does not admit
17
+ v27. Keep the true identity rather than inventing a concatenated id; choosing between
18
+ shapes: streams-and-consistency `references/read-model-keys.md`.
@@ -130,7 +130,10 @@ Observable delivery does not make a materialized projection decision-consistent;
130
130
  decision needs a guarded read (`rule-layers.md`, `cratis-screenplay-streams-and-consistency`). Prefer a
131
131
  declarative projection (mappings, event joins, counters, bounded children, removals); a join
132
132
  consumes event facts, not another read model. Use a reducer when prior-state, attempt or
133
- ordering guards cannot be expressed declaratively. Review every writer of a property: AutoMap
133
+ ordering guards cannot be expressed declaratively, and the view is only ever looked up by the
134
+ event-source identity: a reducer cannot be re-keyed, so a query `by` another identity type warns
135
+ `PLAY0690`; aggregate by a payload value with a projection keyed on each `from`, or a join
136
+ (projections [reducer identity](../../cratis-screenplay-projections/references/reducer-identity.md)). Review every writer of a property: AutoMap
134
137
  can introduce updates from other events. Use variants only when lifecycle stages have different
135
138
  shapes. Keep growing histories in a separate view; never combine several people's PII under
136
139
  one read-model instance (compose queries across person-scoped views instead).
@@ -327,8 +330,9 @@ module Berths
327
330
  - A query's `by` goes on its own body line. Optional keyed and collection queries
328
331
  bind, including observable delivery. Caller-supplied equality filters on collections
329
332
  bind as ESM v14; `by` ANDs with supplied filters. `from`, single-result
330
- filters, scope, performer and composite `by` remain PLAY0268 for their respective
331
- context/cardinality/opaque/multipart gaps. Read read-surface
333
+ filters, scope, performer and a `by` block on a list query remain PLAY0268 for their
334
+ respective context/cardinality/opaque/multipart gaps. A composite-keyed optional
335
+ single-instance query takes a `by` block naming every part (ESM v27). Read read-surface
332
336
  [equality filters](../../cratis-screenplay-read-surface/references/equality-filters.md).
333
337
 
334
338
  ## Executable identity of a read model
@@ -175,8 +175,9 @@ Use `when append <PrivateEvent>` then `then <PublicEvent>`, not `then readmodel`
175
175
  follow-up events are exhaustive and ordered. Read
176
176
  [publication and fold cases](../cratis-screenplay-automations-and-translations/references/public-events-and-direction.md)
177
177
  when specifying outbound facts or event-source captures: it owns the minimum
178
- compiler, PLAY0700/0701, key folding and Unsupported boundaries. Composite view
179
- fixtures remain unadmitted; see read-surface `references/named-keys.md`.
178
+ compiler, PLAY0700/0701, key folding and Unsupported boundaries. Composite-keyed
179
+ view fixtures state every key part (Requires Screenplay 4.204.0, ESM v27); see
180
+ `references/vocabulary.md` and read-surface `references/named-keys.md`.
180
181
 
181
182
  ## Reusable fixtures, routes and recovery intent
182
183
 
@@ -10,6 +10,10 @@ through their concept's `validate` rules in every ESM version. An omitted option
10
10
  filter is not validated. A violation is a Validation rejection carrying the rule's
11
11
  message; a valid key with no matching instance is absence.
12
12
 
13
+ On a composite-keyed read model every `by` part of a single-instance query is validated
14
+ the same way; an argument that breaks a part's concept rule binds and rejects with
15
+ Validation (Requires Screenplay 4.204.0, ESM v27).
16
+
13
17
  Use exactly one query request and one `then error`, bare or with a message. No
14
18
  command/append/reaction action, result, `then no result` or `then denied` may
15
19
  accompany it (PLAY0465). An actionless `then query` with `arguments` and no `result`
@@ -35,6 +35,23 @@ This reference holds the given/when/then construct table and the enumeration and
35
35
  | `for <value>` | the event source of a `given`/`then` event, or of the `when` command or appended event |
36
36
  | `<property> = <value>` | a literal, `null`, or a one-line JSON-shaped object or list: `lines = [{"sku":"A-1","quantity":2}]` |
37
37
 
38
+ ## Composite-keyed read models
39
+
40
+ Requires Screenplay 4.204.0 (ESM v27; `screenplay --version`); older binders refuse a
41
+ read model with two or more `key` marks, and every specification over it, with PLAY0268.
42
+
43
+ - `given readmodel` and `then readmodel` state every key part (missing part PLAY0351,
44
+ repeated part PLAY0519).
45
+ - `then no readmodel <Type> for {"part":"value",...}` names every part (missing PLAY0661,
46
+ unknown PLAY0662, repeated member PLAY0294).
47
+ - `when query` or `then query … arguments` supplies every `by` part of a single-instance
48
+ query, or the equality filters of a list query (missing, unknown or repeated PLAY0468).
49
+ - A single-instance `then result` may omit parts the arguments supply; list rows state
50
+ every part. A stated part that disagrees with the argument fails to match at run time.
51
+ - Parts match one by one in any order; Uuid parts compare as ordinal text.
52
+
53
+ The complete model with these specifications is in read-surface `references/named-keys.md`.
54
+
38
55
  ## Parse and binding diagnostics
39
56
 
40
57
  PLAY0358 is a syntax error; PLAY0388 also occurs at parse time for extra text
@@ -138,6 +138,8 @@ Requires Chronicle 19.59.6 or later (19.59.3 for the InMemory provider) for the
138
138
 
139
139
  **Modeling defaults** (deviate with a recorded reason)
140
140
  - One business identity per stream; cross-stream references are payload properties.
141
+ - A read model's key is not a stream identity. Read [references/read-model-keys.md](references/read-model-keys.md)
142
+ when choosing one id, flat composite key parts (v27) or a composite-typed key.
141
143
  - Copy the source id into the payload only when a consumer needs it as a value.
142
144
  - Give each invariant an authoritative atomic decision point and inspect every path that could
143
145
  violate it. A constraint violation rejects that command's whole append set; a reaction cascade
@@ -145,7 +147,7 @@ Requires Chronicle 19.59.6 or later (19.59.3 for the InMemory provider) for the
145
147
  - New models declare `reaction batches atomic`: direct productions per trigger
146
148
  occurrence and each selected refusal branch are separate batches; invoked commands
147
149
  stay independent. Legacy absence retains per-fact appends. Stage refuses v22
148
- with STAGE-ESM-016 and atomic mode in admitted v23–v25 with STAGE-ESM-043. Read
150
+ with STAGE-ESM-016 and atomic mode in admitted v23–v26 with STAGE-ESM-043. Read
149
151
  [atomic boundaries](../cratis-screenplay-captures-and-reactions/references/atomic-batches-and-source-mappings.md)
150
152
  when defining failure rollback; batching is not redelivery deduplication.
151
153
  - Match duplicate protection to scope. A property constraint lets a source re-claim its own
@@ -0,0 +1,46 @@
1
+ <!-- Copyright (c) Cratis. All rights reserved. -->
2
+ <!-- Licensed under the MIT license. See LICENSE file in the project root for full license information. -->
3
+
4
+ # Read-model keys: one id, flat parts or a composite-typed key
5
+
6
+ Requires Screenplay 4.204.0 (ESM v27; `screenplay --version`) for a read model with two or
7
+ more `key` marks; older binders refuse it with PLAY0268. A read model with one `key` mark
8
+ keeps its earlier version and bytes.
9
+
10
+ A read model's key says which instance a lookup or projection reaches. It is not a stream
11
+ identity and not an atomic decision point: choose it after the streams, and keep invariants
12
+ in constraints and commands.
13
+
14
+ ## Choose the shape
15
+
16
+ | Business shape | Key | Why |
17
+ | --- | --- | --- |
18
+ | The view is one entity's state | One natural `key` property (default) | Binds on every supported version, and the event source usually supplies it |
19
+ | Independent coordinates identify the view (resource and period, plant and field), read by filtering on some parts or by all parts | Several flat `key` properties (v27) | Each part keeps its name and type; a list narrows by any part with equality filters |
20
+ | One value object already identifies the entity everywhere (it is also a command or event value) | One `key` property whose type is a composite `type` | Executes on earlier versions; projections use a typed `key TypeName` block |
21
+
22
+ A read model has exactly one of the two composite shapes: mixing flat parts with a
23
+ composite-typed key is PLAY0660, though one application may hold read models of both.
24
+ Do not concatenate parts into a string id to avoid a composite key, and do not invent a
25
+ composite type only to carry flat parts: both hide the identity the model should state.
26
+
27
+ ## What flat parts do and do not do
28
+
29
+ - **Equality is per part**, in any order. Uuid parts compare as ordinal text with no case
30
+ folding, so write one spelling of each uuid in events, fixtures and query arguments.
31
+ - **A projection names every part** in an untyped `key` block on each root `from` and
32
+ `remove with`. The parts come from the event payload; they need not match the event
33
+ source. `remove with` deletes one instance and a later `from` recreates it.
34
+ - **A composite key does not enforce uniqueness.** Two events can still claim the same
35
+ part values; use a `unique` constraint over the same properties when the store must
36
+ refuse the duplicate (`references/consistency-and-concurrency.md`).
37
+ - **Lookups.** An optional single-instance query names every part in a `by` block; a list
38
+ uses equality filters. A command or reaction that must read the view by parts is refused
39
+ (PLAY0271, PLAY0781 ByParts, or PLAY0268 for a producing reaction), so decide on a stream
40
+ the command owns instead, or restructure the read. Do not drop the read to bind.
41
+ - **Limits.** Joins, `children`, `nested`, `every`, `all`, `variant` and reducers on the
42
+ composite-keyed view are PLAY0268. Stage refuses v27 with STAGE-ESM-016, so deliver it
43
+ through gap-fill with the model as the contract.
44
+
45
+ Read read-surface `references/named-keys.md` for one complete executable example and the
46
+ projections skill `references/pdl-grammar.md` for the key block syntax.
@@ -39,6 +39,10 @@ superseded statement needs context; exceptions do not change the contract.
39
39
  2. **Check the folder, warnings as errors.** `screenplay <folder> --warnaserror` or
40
40
  `cratis screenplay validate <folder> --warnings-as-errors`. Warnings hide defects:
41
41
  PLAY0029 (a misspelt keyword or a `type` inside a slice) drops a construct silently.
42
+ A committed `screenplay.json` at the model root declares the repository's gate (checks, warnings
43
+ as errors, scope, open questions): run without repeating those flags (confirm with
44
+ `--print-gate` before dropping `--warnaserror`), read it with
45
+ `--print-gate`, and treat a refused file as "could not run" (`references/verdicts.md`).
42
46
  3. **Pick the compiler deliberately and name it.** Standalone `screenplay` for
43
47
  language checks and reference specifications; `cratis` for rendering
44
48
  and generation. Report both tool and version, rather than transferring a verdict across
@@ -63,6 +67,7 @@ superseded statement needs context; exceptions do not change the contract.
63
67
  | --- | --- | --- |
64
68
  | Check root | `screenplay <root> --warnaserror --no-color` | `cratis screenplay validate <root> --warnings-as-errors -o json-compact` |
65
69
  | Scope/checks | `--scope <address>`, `--check <names>`; a scoped pass is not whole-application success | `validate --executable` (bundled binder, no write), `--check <names>`, `--scope`; `render --scope`/`--check`; `cratis screenplay conform` |
70
+ | Declared gate | `screenplay <root> --print-gate`, `--format json`, `--no-gate-file` | `validate <root> --print-gate`, `--no-gate-file`; the same `screenplay.json` |
66
71
  | Reference execution | `screenplay test <root>` or MCP `run-specifications` | Discover bundled MCP capability; target tests are not V4 |
67
72
  | MCP | `screenplay mcp <root>`; discover schemas | `cratis screenplay mcp [root]`; registered by `cratis ai install` |
68
73
  | Render boundary | Binder admission is not Stage admission | The CLI renders only ESM versions it knows; a newer bound model gets `CLI-RENDER-004` |
@@ -101,7 +106,7 @@ changes with the root folder name.
101
106
  - **Executable** (binds, V3): `references/executable-subset.md`. Optional keyed lookups, unfiltered
102
107
  lists and lists narrowed by one `by` key (observable too) bind; caller-supplied equality
103
108
  filters on collections bind as ESM v14. Context `from`, single-result
104
- filters, opaque `scoped to`/`performer` and composite `by` remain PLAY0268. Read
109
+ filters, opaque `scoped to`/`performer` and a `by` block on a list query remain PLAY0268. Read
105
110
  read-surface [equality filters](../cratis-screenplay-read-surface/references/equality-filters.md).
106
111
  Pending handlers beside unconditional productions bind as described commands (v23),
107
112
  but reached bodies are Unsupported; attached handlers remain PLAY0268.
@@ -120,9 +125,10 @@ changes with the root folder name.
120
125
  - **Public contracts and routes (v9/v10/v11):** public contracts and direction select v9;
121
126
  reaction identity, production overrides and observer filters select v10;
122
127
  qualifying retained non-route Double literals select v11.
123
- Composite view keys remain authoring-only (PLAY0268), while one explicit
124
- `key` binds. Read `references/production-routes-and-filters.md` and read-surface
125
- `references/named-keys.md`.
128
+ Flat composite read-model keys select v27 (Requires Screenplay 4.204.0; older binders
129
+ report PLAY0268); one explicit `key` keeps its earlier version. Read
130
+ `references/production-routes-and-filters.md`, [language admission](references/language-admission.md)
131
+ and read-surface `references/named-keys.md`.
126
132
  - **Enum numbers (v12):** read command-surface `references/enum-numbers.md` for
127
133
  pins, stored numbers and Stage output; `references/diagnostics.md` owns PLAY0667–0671.
128
134
  - **Concept strategies (v15):** read
@@ -189,7 +195,7 @@ Read [references/diagnostics.md](references/diagnostics.md) when a PLAY, STAGE o
189
195
  7. Mark read-model identity with trailing `key`, not `identifier`. Otherwise single-instance
190
196
  queries must agree on `by`; only with none and an admitted list query can `*Id`
191
197
  inference run. Queryless views need `key` (read-surface `references/named-keys.md`).
192
- Align projection keys. Multipart keys remain PLAY0268.
198
+ Align projection keys; several parts need an untyped `key` block (v27).
193
199
  8. Projection `key` routes only on `from`; joins never create; `all` is per source.
194
200
  9. `null` only for optional read-model properties in specifications.
195
201
  10. `then` events are exhaustive and ordered; after `when append` they list only what followed.