@cratis/pi 2.23.1 → 2.24.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 (32) hide show
  1. package/package/corpus/rules/general.md +2 -0
  2. package/package/corpus/rules/profiles.md +18 -1
  3. package/package/corpus/skills/cratis-chronicle-event-modeling/SKILL.md +8 -0
  4. package/package/corpus/skills/cratis-event-model-diagram/SKILL.md +7 -0
  5. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/LICENSE +2 -0
  6. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/SKILL.md +229 -0
  7. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/verification.json +10 -0
  8. package/package/corpus/skills/cratis-screenplay-command-surface/LICENSE +2 -0
  9. package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +270 -0
  10. package/package/corpus/skills/cratis-screenplay-command-surface/references/context.md +111 -0
  11. package/package/corpus/skills/cratis-screenplay-command-surface/verification.json +23 -0
  12. package/package/corpus/skills/cratis-screenplay-event-modeling/LICENSE +2 -0
  13. package/package/corpus/skills/cratis-screenplay-event-modeling/SKILL.md +204 -0
  14. package/package/corpus/skills/cratis-screenplay-event-modeling/references/nine-steps.md +219 -0
  15. package/package/corpus/skills/cratis-screenplay-event-modeling/verification.json +23 -0
  16. package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +13 -1
  17. package/package/corpus/skills/cratis-screenplay-projections/LICENSE +2 -0
  18. package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +215 -0
  19. package/package/corpus/skills/cratis-screenplay-projections/references/pdl-grammar.md +239 -0
  20. package/package/corpus/skills/cratis-screenplay-projections/verification.json +11 -0
  21. package/package/corpus/skills/cratis-screenplay-read-surface/LICENSE +2 -0
  22. package/package/corpus/skills/cratis-screenplay-read-surface/SKILL.md +188 -0
  23. package/package/corpus/skills/cratis-screenplay-read-surface/verification.json +10 -0
  24. package/package/corpus/skills/cratis-screenplay-specifications/LICENSE +2 -0
  25. package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +199 -0
  26. package/package/corpus/skills/cratis-screenplay-specifications/verification.json +19 -0
  27. package/package/corpus/skills/cratis-screenplay-ui-composition/LICENSE +2 -0
  28. package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +231 -0
  29. package/package/corpus/skills/cratis-screenplay-ui-composition/verification.json +10 -0
  30. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +5 -2
  31. package/package/profile-catalog.json +8 -1
  32. package/package.json +1 -1
@@ -0,0 +1,111 @@
1
+ # Contexts and context expressions
2
+
3
+ Four contexts, one per job. What each **omits** is as deliberate as what it
4
+ carries: a validation rule cannot see the caller's roles, and a policy cannot see
5
+ the causation chain.
6
+
7
+ ## The four contexts
8
+
9
+ ```csharp
10
+ public record CommandContext(
11
+ dynamic Command, TenantId Tenant, Identity Identity,
12
+ CausedBy CausedBy, Causation Causation, DateTimeOffset Occurred);
13
+
14
+ public record QueryContext(
15
+ dynamic Arguments, TenantId Tenant, Identity Identity,
16
+ CausedBy CausedBy, Causation Causation, DateTimeOffset Occurred);
17
+
18
+ public record RuleContext(
19
+ dynamic Artifact, dynamic Value, string Property,
20
+ TenantId Tenant, CausedBy CausedBy, DateTimeOffset Occurred);
21
+
22
+ public record PolicyContext(
23
+ dynamic Artifact, string Subject, Identity Identity,
24
+ TenantId Tenant, DateTimeOffset Occurred);
25
+ ```
26
+
27
+ | Context | In scope for | Omits | Why |
28
+ | --- | --- | --- | --- |
29
+ | **Command** | a command `handler` | — | the full picture |
30
+ | **Query** | a query `performer` | — | same, with `Arguments` instead of `Command` |
31
+ | **Rule** | a `validate` rule body | **`Identity`** | a rule *may* reject based on who sent it — "you may not approve your own request" is validation, and `CausedBy` carries the caller's identifier for it. Inspecting **roles or claims** is authorization and belongs in a `policy`; leaving those out is what keeps the two apart |
32
+ | **Policy** | a `policy` code block | **`CausedBy`, `Causation`** | a decision about the caller, not an audit record |
33
+
34
+ `RuleContext` carries `Artifact` (the whole thing under validation), `Value` (the
35
+ value the rule is declared on) and `Property` (where it sits — empty for a
36
+ whole-command `require`).
37
+
38
+ ## The values they carry
39
+
40
+ | Type | Members |
41
+ | --- | --- |
42
+ | `Identity` | `Id`, `Name`, `UserName`, `IsAuthenticated`, `Roles`, `Claims` |
43
+ | `Claim` | `Name`, `Value` |
44
+ | `CausedBy` | `Subject`, `Name`, `UserName` |
45
+ | `Causation` | `Type`, `Occurred`, `Properties` |
46
+
47
+ `Identity` is the **authorization** view of the caller — what a policy decides on.
48
+ `CausedBy` is the **audit** view of the same caller — the three values that travel
49
+ with an appended event.
50
+
51
+ ## Reaching the context declaratively
52
+
53
+ Usable wherever a mapping source is — `produces` mappings, `seed` values, capture
54
+ `append` mappings, query `from` parameters:
55
+
56
+ | Path | Yields |
57
+ | --- | --- |
58
+ | `$context.occurred` | when the command or query was received |
59
+ | `$context.tenant` | the tenant |
60
+ | `$context.command.<property>` | a command property (in commands) |
61
+ | `$context.arguments.<name>` | a query argument (in queries) |
62
+ | `$context.identity.id` | the caller's identifier |
63
+ | `$context.identity.name` / `.userName` | display name / user name |
64
+ | `$context.identity.isAuthenticated` | whether the caller is authenticated |
65
+ | `$context.identity.roles` | the caller's roles |
66
+ | `$context.identity.claims.<name>` | one claim value |
67
+ | `$context.causedBy.subject` / `.name` / `.userName` | the audit view |
68
+ | `$context.causation.type` | the causation type |
69
+
70
+ An unknown `$context.` path is reported, so a typo does not silently become null.
71
+
72
+ ## The other expression roots
73
+
74
+ | Root | Where | Yields |
75
+ | --- | --- | --- |
76
+ | `$env.<VAR_NAME>` | mapping sources | an environment variable |
77
+ | `$eventContext.<property>` | **projections only** | `occurred`, `sequenceNumber`, `correlationId`, `eventSourceId` |
78
+ | `$eventSourceId` | **projections only** | shorthand for `$eventContext.eventSourceId` |
79
+ | `$causedBy.<property>` | **projections only** | `subject`, `name`, `userName` — an unknown one is an error naming all three |
80
+ | `$.` | **captures only** | a value from the current source item |
81
+ | `$strings.<dotted.key>` | labels, titles, messages | a localized string from a `.strings` file |
82
+
83
+ ## Templates and literals
84
+
85
+ A template is backticked with `${}` substitutions:
86
+
87
+ ```screenplay
88
+ fullName = `${firstName} ${lastName}`
89
+ ```
90
+
91
+ Literals are `true` / `false`, `"quoted text"`, numbers (`42`, `-3.14`), and
92
+ `null`. In a projection, `literal <value>` forces a value to be read as a literal
93
+ rather than a property path — that is how a constant key is written:
94
+
95
+ ```screenplay
96
+ from UserLoggedIn key literal "site-stats"
97
+ count TotalLogins
98
+ ```
99
+
100
+ ⚠️ A template expression is **not allowed in a composite key**, and a composite
101
+ key with no parts is an error.
102
+
103
+ ## Escaping
104
+
105
+ A reserved word used as a property name is escaped with a leading `@` —
106
+ `@with`, `@file`, `@validate`. This matters most in three places:
107
+
108
+ - a `trigger` body reserves `file`, so a trigger value named `file` is `@file`;
109
+ - an `Enum` concept value named `validate` is `@validate`, or it reads as an
110
+ empty validate block;
111
+ - a projection clearing a property named `with` is `clear @with`.
@@ -0,0 +1,23 @@
1
+ {
2
+ "skill": "cratis-screenplay-command-surface",
3
+ "input": "Declare a Screenplay command with validation, authorization and concurrency.",
4
+ "fixtures": [],
5
+ "assertions": [
6
+ {
7
+ "kind": "skill-contains",
8
+ "value": "A second `identifier` on the same command is an error"
9
+ },
10
+ {
11
+ "kind": "skill-contains",
12
+ "value": "`produces` and `handler` are mutually exclusive"
13
+ },
14
+ {
15
+ "kind": "skill-contains",
16
+ "value": "Two adjacent policies synthesize an implicit `and`"
17
+ },
18
+ {
19
+ "kind": "skill-contains",
20
+ "value": "`identifier` on an **event** property is an error"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,2 @@
1
+ Copyright (c) Cratis. All rights reserved.
2
+ Licensed under the MIT license. See LICENSE file in the project root for full license information.
@@ -0,0 +1,204 @@
1
+ ---
2
+ name: cratis-screenplay-event-modeling
3
+ description: Facilitate an Event Modeling session and land the result as Cratis Screenplay `.play` source — domain discovery, the nine-step workflow per behavior, the four slice types, information completeness, and the model-validation gate. Use when designing an information system, mapping a business process or information flow, deciding the event vocabulary or stream boundaries, or turning a whiteboard model into `.play`. Do not use for the language and compiler mechanics alone, and do not use to render a settled model into an application.
4
+ license: MIT
5
+ ---
6
+
7
+ # Event modeling with Screenplay
8
+
9
+ Event Modeling is the **method**: walk a business process left to right and write
10
+ down every behavior as a command that changes the system, a view that reads it,
11
+ an automation that runs off it, or a translation of outside data. Screenplay is
12
+ the **artifact**: one language that holds that whole model — concepts, commands,
13
+ events, read models, queries, screens, specifications — in files that compile.
14
+
15
+ You are a **facilitator, not a stenographer.** Ask probing questions. Challenge
16
+ assumptions. Keep asking *"and then what happens?"* after every event, every
17
+ command, every answer. Use business language. Do not discuss databases, APIs, or
18
+ frameworks during modeling.
19
+
20
+ ## Verified product sources
21
+
22
+ | Package | Version | Purpose |
23
+ | --- | --- | --- |
24
+ | `Cratis.Screenplay` | `4.12.1` | Compiler: parser, validator, diagnostics, folder merge, semantic binder |
25
+ | `Cratis.Screenplay.Tool` | `4.12.1` | The `screenplay` dotnet tool |
26
+
27
+ Read from the Screenplay repository at tag `v4.12.1` (commit `122eee8`). Reverify
28
+ before claiming another version behaves the same.
29
+
30
+ > **Method lineage.** The two-phase process, the nine steps, the four patterns and
31
+ > the GWT discipline follow **Event Modeling** (Adam Dymitruk; Martin Dilger,
32
+ > *Understanding Eventsourcing*), as structured in
33
+ > [jwilger/agent-skills `event-modeling`](https://github.com/jwilger/agent-skills/tree/main/skills/event-modeling).
34
+ > Screenplay adopts that vocabulary directly: *"Slices are the atom — everything
35
+ > lives inside a typed slice aligned with Event Modeling's vocabulary."*
36
+ > Where Cratis deliberately diverges, this skill says so.
37
+
38
+ ## Two phases — discovery, then design
39
+
40
+ **Never jump into detailed workflow design without broad domain understanding.**
41
+ Phase 1 maps the territory; Phase 2 explores each region.
42
+
43
+ **Phase 1 — Domain discovery.** What does the business do? Who are the actors?
44
+ What major processes exist? What external systems integrate? Which workflow is
45
+ most critical? *Ask the user; do not assume answers.* Lands as the `module` set,
46
+ `persona` declarations, and the `import` list.
47
+
48
+ **Phase 2 — Workflow design.** One workflow at a time, all nine steps, before
49
+ starting the next. Read **[nine-steps.md](references/nine-steps.md)** — it carries
50
+ each step's activities, its Screenplay output, and the facilitation questions.
51
+
52
+ ## The prime directive: do not lose information
53
+
54
+ Store what happened (events), not just current state. Events are immutable
55
+ past-tense facts in business language. **Every read-model field must trace back to
56
+ an event.** If a field has no source event, something is missing from the model —
57
+ it is not an optional column.
58
+
59
+ ## The four patterns → the four slice types
60
+
61
+ Every behavior is exactly one. An unknown slice type is a compile error:
62
+ *Unknown slice type '<x>' - expected StateChange, StateView, Automation or Translate*.
63
+
64
+ | Pattern | Slice type | Screenplay constructs |
65
+ | --- | --- | --- |
66
+ | Command → Event | `StateChange` | `command` → `produces` → `event`, plus `validate`, `authorize`, `constraint` |
67
+ | Events → Read Model | `StateView` | `readmodel`, `projection` or `reducer`, `query`, `screen` |
68
+ | Event → decision → Command/Event | `Automation` | `reaction` |
69
+ | External data → Event | `Translate` | `capture` |
70
+
71
+ **A slice is one behavior, not one artifact of each kind.** Every construct may
72
+ appear as many times as the behavior needs. Only `description` is limited to one.
73
+
74
+ **Automation has four required components** — a triggering occurrence, the state
75
+ it consults, conditional logic, and a resulting command or event. If the events
76
+ are always unconditionally co-produced, it is **not** an automation: model it as
77
+ one `StateChange` slice with several `produces` blocks.
78
+
79
+ **Translation is workflow-specific anti-corruption.** Generic infrastructure every
80
+ workflow needs (event persistence, message transport) is not a `Translate` slice.
81
+
82
+ ## Where Cratis diverges from the generic method
83
+
84
+ Two divergences matter, and getting them wrong produces a model that will not
85
+ compile or will not be safe. Both are deliberate.
86
+
87
+ - **A command may read state.** The generic method forbids `ReadModel → Command`
88
+ edges. Screenplay ships `reads <ReadModel> [by <property>]` on a command
89
+ precisely so a state-dependent rule can be decided under concurrency. Use it for
90
+ genuine consistency boundaries, and pair it with `concurrency` so the decision is
91
+ enforced at append time rather than merely consulted. Do not use it to fetch
92
+ data the command could carry as input.
93
+ - **Some validation *does* belong in the model.** The generic method routes format
94
+ rules to the type system. Screenplay's type system *is* the `concept`, and a
95
+ concept carries its own `validate` block — so a format rule lives on the concept
96
+ and travels with every use, while a state-dependent rule stays a specification.
97
+ The split is the same; the place is different.
98
+
99
+ ## Naming the primitives is the highest-value work
100
+
101
+ - **Concepts before events.** `concept InvoiceId : Uuid` once, and every construct
102
+ using it is typed end to end. The seven primitives are `Uuid`, `String`, `Int`,
103
+ `Decimal`, `Bool`, `Date` and `DateTime`; `Enum` is a separate concept kind,
104
+ with its values indented beneath.
105
+ - **Classify personal data at the concept**, with a reason:
106
+ `concept PersonName : String @pii` plus an indented `pii reason "..."`. Every
107
+ usage inherits it; a reason for an attribute the concept does not declare is an
108
+ error. Decide this before fixing event shapes — erasure follows the subject and
109
+ the subject follows the stream.
110
+ - **Events are past tense and self-describing** — `InvoiceRegistered`, never
111
+ `Created`. One purpose per event; an event needing an optional property to cover
112
+ two situations is two events.
113
+ - **The event-source identity is never an event property.** The command binds it
114
+ with `identifier` on exactly one property; marking an *event* property
115
+ `identifier` is an error: *an event never carries its event source id*.
116
+ - **Domain facts, not runtime context.** Test: would this field have the same value
117
+ if the event were replayed on a different machine? If not, it does not belong.
118
+
119
+ ## Model validation — the gate before implementation
120
+
121
+ Run this after the GWT specifications are written. **Do not proceed with gaps.**
122
+ When one is found, ask the user to clarify, create the missing element, re-validate.
123
+
124
+ 1. Every `readmodel` property traces to an `event` (**backward trace**).
125
+ 2. Every `event` feeds a projection, reaction, or capture target (**forward trace**).
126
+ 3. Every `command` has documented rejection conditions.
127
+ 4. Every `reaction` has a termination condition and cannot loop forever.
128
+ 5. No `given`/`when`/`then` clause references an undefined element.
129
+ 6. Every behavior is exactly one slice type.
130
+ 7. Read-model fields use collection types where the domain allows concurrent
131
+ instances — ask *"can there be more than one of these at once?"* for each field.
132
+ 8. No cross-cutting infrastructure is modeled as a `Translate` slice.
133
+
134
+ ## One file, or a folder
135
+
136
+ Start with one file, read top to bottom. Reach for a folder when it stops being
137
+ navigable — the invoicing sample is 981 lines with **one** module.
138
+
139
+ The folder layout mirrors the language, one folder per level: `application.play`
140
+ at the root (`domain`, `import`, `concept`, `type`, `policy`, `persona`,
141
+ `authentication`, `seed`), then `<Module>/<Module>.play`,
142
+ `<Module>/…/<Feature>/<Feature>.play`, and `<Module>/…/<Slice>/<Slice>.play` for
143
+ one slice, whole. A slice file restates its `module` and `feature`; nothing is
144
+ written twice.
145
+
146
+ ⚠️ **Compile the folder as one application.** `screenplay <folder>` merges every
147
+ `.play` beneath the root *before* resolving, so an event declared in one file and
148
+ produced in another resolves. Compiling files individually reports unknown types,
149
+ events and policies (`PLAY0165`, `PLAY0166`, `PLAY0167`) that are not missing.
150
+ Duplicates *across* files are real errors naming both ends (`PLAY0172`, `PLAY0173`).
151
+
152
+ Round-tripping a folder does not preserve **declaration order** — modules, features
153
+ and slices come back sorted by name. Never encode meaning in order.
154
+
155
+ ## Verify
156
+
157
+ ```shell
158
+ screenplay path/to/model --warnaserror
159
+ ```
160
+
161
+ - [ ] Zero errors **and zero warnings**. An unrecognized construct inside a slice
162
+ is only a warning (`PLAY0029`) and its block is **silently dropped** — a typo
163
+ can delete a whole projection while the exit code stays `0`.
164
+ - [ ] Every behavior is exactly one slice type, and the whole model validates
165
+ against the eight checks above.
166
+ - [ ] Every event is past tense, single-purpose, and carries no event-source id.
167
+ - [ ] Personal data is classified on the `concept`, with a reason.
168
+ - [ ] Specifications name the rejections, not only the happy path.
169
+ - [ ] If the model must reach a runtime, every slice is `StateChange` or
170
+ `StateView` and stays inside the admitted vertical (below).
171
+
172
+ ## Parsed is not runnable
173
+
174
+ The compiler accepts far more than anything executes. Between the syntax tree and
175
+ any runtime sits the **executable semantic model (ESM v1)**, and it fails closed.
176
+ `SemanticSliceKind` has exactly three members — `Unknown = -1`, `StateChange`,
177
+ `StateView` — so an `Automation` or `Translate` slice is rejected outright:
178
+ *Slice '<name>' of type '<type>' is not admitted by ESM v1.*
179
+
180
+ **Exactly 40 constructs** bind to `UnsupportedSemanticSyntax` (`PLAY0268`) rather
181
+ than to a weaker model — imports, policies, personas, triggers, `authorize`, event
182
+ and produced-event tags, conditional `produces … when`, code-backed validation,
183
+ query filters/scope/performers, `@pii` concepts, projection parent keys, reducers,
184
+ reactions, captures and constraints among them.
185
+
186
+ Model the wider language freely when the `.play` file **is** the deliverable —
187
+ documentation, review, a shared description of a system. Stay inside the admitted
188
+ vertical when it must reach a runtime. Never read a clean `screenplay` run as
189
+ evidence a construct works downstream.
190
+
191
+ ## Route near misses
192
+
193
+ | Need | Skill |
194
+ | --- | --- |
195
+ | Commands, validation, authorization, `produces`, concurrency, `$context` | `cratis-screenplay-command-surface` |
196
+ | Projections — PDL keys, joins, children, removal, arithmetic | `cratis-screenplay-projections` |
197
+ | Read models, queries, screens | `cratis-screenplay-read-surface` |
198
+ | Layouts, templates, forms, contributions, themes, i18n | `cratis-screenplay-ui-composition` |
199
+ | Captures (CDL), reactions, triggers | `cratis-screenplay-captures-and-reactions` |
200
+ | Given/when/then specifications | `cratis-screenplay-specifications` |
201
+ | Language mechanics, the compiler, the admitted set | `cratis-screenplay-model-authoring` |
202
+ | Rendering a settled model into an application | `cratis-stage-rendering-and-sandbox` |
203
+ | Drawing the model as a Mermaid diagram | `cratis-event-model-diagram` |
204
+ | Modeling against hand-written Chronicle C# | `cratis-chronicle-event-modeling` |
@@ -0,0 +1,219 @@
1
+ # The nine-step workflow design process
2
+
3
+ Follow all nine steps for each workflow. Do not skip steps, do not combine them,
4
+ and do not start workflow 2 while workflow 1 is incomplete — **discovery is
5
+ design**. Each step has a defined Screenplay output; produce it.
6
+
7
+ ## Step 1 — Identify the user goal
8
+
9
+ Ask until the goal is unambiguous: *"What exactly is the user trying to
10
+ accomplish? What does success look like? What would make this fail?"*
11
+
12
+ **Output:** the `feature` name and its `description`.
13
+
14
+ ```screenplay
15
+ module Invoicing
16
+ feature InvoiceManagement
17
+ description "Registering and managing the lifecycle of invoices"
18
+ ```
19
+
20
+ `description` is the optional **first body line** of a module, feature, slice,
21
+ persona or command; at most one. Use a fenced ``` block when one line is not enough.
22
+
23
+ ## Step 2 — Brainstorm events
24
+
25
+ Sticky-note style, no ordering yet. *"What facts need recording? What happened
26
+ that we care about? What would an auditor want to know?"* Keep asking *"what
27
+ else?"*
28
+
29
+ Past tense, business language, facts: `InvoiceRegistered`, `PaymentReceived`.
30
+
31
+ **Domain facts vs runtime context.** Events must be true regardless of which
32
+ machine replays them. A working directory, PID or hostname is runtime context and
33
+ does not belong in an event.
34
+
35
+ **Output:** a list of `event` names. Shapes come later.
36
+
37
+ ## Step 3 — Order events chronologically
38
+
39
+ Arrange into the timeline — the plot. *"What happens first? And then what
40
+ happens?"* Identify the happy path **and** the alternative and error paths.
41
+
42
+ **Output:** the order the slices will be written in. Note that folder round-trip
43
+ does not preserve declaration order, so the timeline is documentation, not
44
+ structure.
45
+
46
+ ## Step 4 — Create wireframes
47
+
48
+ These need not be the real UI. Their purpose is a complete accounting of what a
49
+ user can **see** and what they can **do** at each interaction point.
50
+
51
+ ```
52
+ +-------------------------------+
53
+ | Register Invoice |
54
+ +-------------------------------+
55
+ | Customer: [dropdown] |
56
+ | Lines: [list] |
57
+ | Total: $XX.XX |
58
+ | |
59
+ | [Register] |
60
+ +-------------------------------+
61
+ ```
62
+
63
+ Every field traces to an **event field** (something displayed) or a **command
64
+ input** (something provided). If you cannot trace a field, something is missing.
65
+
66
+ **Concurrency check:** *"Can there be more than one of these in progress at the
67
+ same time?"* If yes, the wireframe shows a list or table, not a single-item view.
68
+
69
+ **Output:** the `screen` declarations, at Level 1 (intent) for now —
70
+ `data <ReadModel> via query <Query>` plus `action <Command>`.
71
+
72
+ ## Step 5 — Identify commands
73
+
74
+ For each event: *"What triggered this? Who issued that command? What information
75
+ did they provide? Under what circumstances would this NOT happen?"*
76
+
77
+ Commands are imperative and present tense: `RegisterInvoice`, `ProcessPayment`.
78
+ **Commands can fail; events cannot.**
79
+
80
+ **Output:** the `command` declaration with its properties, exactly one
81
+ `identifier` property, `authorize`, and `produces`.
82
+
83
+ ```screenplay
84
+ slice StateChange RegisterInvoice
85
+ command RegisterInvoice
86
+ invoiceId InvoiceId identifier
87
+ invoiceNumber InvoiceNumber
88
+ authorize CanManageInvoice
89
+ validate
90
+ invoiceNumber not empty message "Invoice number is required"
91
+ produces InvoiceRegistered
92
+ invoiceId = invoiceId
93
+ invoiceNumber = invoiceNumber
94
+ registeredAt = $context.occurred
95
+ ```
96
+
97
+ The "under what circumstances would this NOT happen" answers become `validate`
98
+ rules, `authorize` policies, `constraint` declarations, and the rejection
99
+ specifications.
100
+
101
+ ## Step 6 — Design read models
102
+
103
+ Read models exist to support what wireframes display and what automations need.
104
+ For each actor at each point, and for each automation: *"What does this person
105
+ need to see? What information do they need to decide? What does this automation
106
+ need to determine its next action?"*
107
+
108
+ Verify **every** field traces back to an event:
109
+
110
+ ```
111
+ InvoiceListReadModel:
112
+ invoiceId <- InvoiceRegistered.invoiceId
113
+ invoiceNumber <- InvoiceRegistered.invoiceNumber
114
+ status <- InvoiceRegistered, InvoiceSent, InvoicePaid
115
+ ```
116
+
117
+ **Concurrency check per field:** if the domain supports concurrent instances, use
118
+ a collection type, not a singular value.
119
+
120
+ **Output:** the `readmodel` shape and the one `projection` or `reducer` that
121
+ builds it. **Exactly one thing may build a read model** — two builders is a
122
+ compile error (`PLAY0191`).
123
+
124
+ ```screenplay
125
+ slice StateView InvoiceList
126
+ readmodel InvoiceListReadModel
127
+ invoiceNumber InvoiceNumber
128
+ status InvoiceStatus
129
+ projection InvoiceList => InvoiceListReadModel
130
+ from InvoiceRegistered key invoiceId
131
+ status = "draft"
132
+ from InvoiceSent
133
+ status = "sent"
134
+ query ListInvoices => InvoiceListReadModel[]
135
+ ```
136
+
137
+ **Do not model infrastructure preconditions as read models.** "Does the directory
138
+ exist?" is not domain state.
139
+
140
+ ## Step 7 — Find automations
141
+
142
+ *"Does anything happen automatically after this event? What business rules trigger
143
+ other processes? Does the system need to check anything before acting?"*
144
+
145
+ **All four components are required for a true automation:** a triggering
146
+ occurrence, state it consults, conditional logic, and a resulting command or
147
+ event. **Test:** *"Can this automatic response ever be skipped or vary based on
148
+ system state?"* If no, it is co-production — one `StateChange` slice with several
149
+ `produces` blocks, not an `Automation` slice.
150
+
151
+ **Output:** the `reaction`, in an `Automation` slice.
152
+
153
+ ```screenplay
154
+ slice Automation ChaseOverdueInvoices
155
+ reaction OverdueChaser
156
+ when InvoiceRegistered
157
+ invoiceId
158
+ dueDate
159
+ invokes MarkInvoiceOverdue
160
+ invoiceId = invoiceId
161
+ where dueDate < today
162
+ ```
163
+
164
+ `produces` and `invokes` are indented **inside** the trigger; only `description`
165
+ and `where` sit at reaction level. Outdenting an effect gives `PLAY0137`.
166
+
167
+ `produces` appends a fact nothing can refuse. `invokes` asks for a command, which
168
+ may still validate and reject. The words are different on purpose.
169
+
170
+ Every automation needs a **termination condition**. Watch for infinite loops.
171
+
172
+ ## Step 8 — Map external integrations
173
+
174
+ *"Does this workflow receive data from outside? Does it send data outside?"* Note
175
+ names and purposes only — no APIs, webhooks or protocols yet.
176
+
177
+ **Ask:** *"Is this integration specific to THIS workflow, or would every workflow
178
+ need it?"* If every workflow needs it, it is cross-cutting infrastructure, **not**
179
+ a `Translate` slice.
180
+
181
+ **Output:** the `capture` in a `Translate` slice, and any `trigger` declaration
182
+ for a name only an integration knows.
183
+
184
+ ```screenplay
185
+ trigger BuildFinished
186
+ repository
187
+ outcome
188
+ ```
189
+
190
+ A `trigger` declares that the name exists and what an occurrence hands the
191
+ reaction — deliberately **not** what makes one occur.
192
+
193
+ ## Step 9 — Decompose into vertical slices
194
+
195
+ List every slice grouped by type. A good slice is a complete interaction,
196
+ independently valuable, testable in isolation, small enough for 1–2 days.
197
+
198
+ Bad slices: *"Set up database"* (technical, no user value), *"Implement invoicing"*
199
+ (too broad), *"Create Invoice table"* (implementation detail).
200
+
201
+ **Slice independence.** Slices sharing an event schema are **independent** —
202
+ connected by the event contract, not by execution order. A `StateChange` slice is
203
+ specified by asserting on produced events; a `StateView` slice is specified with
204
+ synthetic `given` events. Neither needs the other implemented first. Do not build
205
+ artificial dependency chains.
206
+
207
+ **Output:** the complete `feature` → `slice` tree, ready for the specifications.
208
+
209
+ ## Facilitation questions quick reference
210
+
211
+ | Topic | Questions |
212
+ | --- | --- |
213
+ | Domain discovery | What does the business do? Who are the actors? What are the major processes? What external systems exist? Which workflow is most critical? |
214
+ | Events | What facts need recording? What happened here? Would the business need to know this? |
215
+ | Timeline | What happens first? And then? Can these happen in parallel? |
216
+ | Commands | Who initiates this? User-triggered or automatic? What intent does this represent? |
217
+ | Read models | What does this actor need to see? What queries do users run? Can multiple instances be active at once? |
218
+ | Automations | Does anything happen automatically? What business rules apply? Can this response ever be skipped? |
219
+ | Edge cases | What if this fails? What if the user cancels? What if the external system is down? |
@@ -0,0 +1,23 @@
1
+ {
2
+ "skill": "cratis-screenplay-event-modeling",
3
+ "input": "Facilitate an event modeling session and land the model as .play source.",
4
+ "fixtures": [],
5
+ "assertions": [
6
+ {
7
+ "kind": "skill-contains",
8
+ "value": "expected StateChange, StateView, Automation or Translate"
9
+ },
10
+ {
11
+ "kind": "skill-contains",
12
+ "value": "**Every read-model field must trace back to"
13
+ },
14
+ {
15
+ "kind": "skill-contains",
16
+ "value": "references/nine-steps.md"
17
+ },
18
+ {
19
+ "kind": "skill-contains",
20
+ "value": "is not admitted by ESM v1"
21
+ }
22
+ ]
23
+ }
@@ -92,7 +92,7 @@ Translate`:
92
92
 
93
93
  **Inside any slice:** `description`, `event`, `command`, `query`, `projection`,
94
94
  `capture`, `reaction`, `screen`, `constraint`, `specification`, `readmodel`,
95
- `reducer`. An unrecognised word here is a **warning**, not an error, and its
95
+ `reducer`. An unrecognized word here is a **warning**, not an error, and its
96
96
  block is skipped — a typo can silently drop a whole construct, so treat slice
97
97
  warnings as failures.
98
98
 
@@ -232,6 +232,18 @@ pull request as evidence the language service still compiles.
232
232
 
233
233
  ## Route near misses
234
234
 
235
+ This skill is the language and compiler reference. For the constructs themselves:
236
+
237
+ | Need | Skill |
238
+ | --- | --- |
239
+ | Facilitating the model and the nine-step method | `cratis-screenplay-event-modeling` |
240
+ | `command`, `event`, `validate`, `authorize`, `produces`, `concurrency`, `$context` | `cratis-screenplay-command-surface` |
241
+ | `projection` and the PDL, `reducer` | `cratis-screenplay-projections` |
242
+ | `readmodel`, `query`, `screen` | `cratis-screenplay-read-surface` |
243
+ | `layout`, templates, `form`, `contribute`, `theme`, `$strings` | `cratis-screenplay-ui-composition` |
244
+ | `capture` and the CDL, `reaction`, `trigger` | `cratis-screenplay-captures-and-reactions` |
245
+ | `specification` | `cratis-screenplay-specifications` |
246
+
235
247
  - Turning a model into an application, or the runtime sandbox:
236
248
  `cratis-stage-rendering-and-sandbox`.
237
249
  - Designing an event model as a diagram rather than as a `.play` file:
@@ -0,0 +1,2 @@
1
+ Copyright (c) Cratis. All rights reserved.
2
+ Licensed under the MIT license. See LICENSE file in the project root for full license information.