@cratis/pi 2.23.1 → 2.24.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 (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 +25 -9
  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 +241 -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
@@ -147,6 +147,8 @@ A claim is only as good as the signal behind it — a build result, a test run,
147
147
  | Writing a scan, allowlist or destructive pass that cannot pass vacuously | `guards-and-fuses.md` |
148
148
  | How the shared corpus is installed, updated and rolled back | `ai-distribution.md` |
149
149
  | Event modeling / schema migration / calling commands from code / paging / cross-cutting metadata / multi-tenancy | the matching skills |
150
+ | Designing an information system, business process or information flow as a **Screenplay** `.play` model | the **cratis-screenplay-event-modeling** skill, then the per-surface `cratis-screenplay-*` skills |
151
+ | Rendering a settled `.play` model into an application | the **cratis-stage-rendering-and-sandbox** skill |
150
152
  | Step-by-step recipes | `.cratis/ai/skills/` |
151
153
 
152
154
  ## Source-of-Truth Discipline
@@ -98,8 +98,25 @@ The complete list of available profiles is defined in [profile-catalog.json](../
98
98
  | `cratis/studio` | Studio MCP safety guidance |
99
99
  | `cratis/cli` | CLI operations |
100
100
  | `cratis/lens` | Lens browser extension |
101
- | `cratis/screenplay` | Screenplay event modeling |
101
+ | `cratis/screenplay` | Event modeling and information-system design with Screenplay — the method and the whole `.play` language |
102
102
  | `cratis/stage` | Stage rendering and sandbox |
103
+ | `cratis/modeling/screenplay-stage` | Screenplay + Stage together |
104
+
105
+ ### Event modeling with Screenplay
106
+
107
+ `cratis/screenplay` carries the **method** and the **language**, split one skill
108
+ per surface so only the relevant one loads:
109
+
110
+ | Skill | Covers |
111
+ | --- | --- |
112
+ | `cratis-screenplay-event-modeling` | Domain discovery, the nine-step workflow, the four slice types, model validation |
113
+ | `cratis-screenplay-command-surface` | `command`, `event`, `validate`, `authorize`, `produces`, `concurrency`, `constraint`, `concept`, `$context` |
114
+ | `cratis-screenplay-projections` | The Projection Declaration Language and the `reducer` escape hatch |
115
+ | `cratis-screenplay-read-surface` | `readmodel`, `query`, `screen`, name resolution |
116
+ | `cratis-screenplay-ui-composition` | `layout`, templates, `form`, `contribute`, `ui profile`, `theme`, `$strings`, `file` |
117
+ | `cratis-screenplay-captures-and-reactions` | The Change Data Capture Language, `reaction`, `trigger` |
118
+ | `cratis-screenplay-specifications` | Given/when/then and the reference execution |
119
+ | `cratis-screenplay-model-authoring` | The compiler, the CLI, diagnostics, and the parsed-versus-admitted boundary |
103
120
 
104
121
  ## How to Use Profiles
105
122
 
@@ -155,3 +155,11 @@ remain separately authorized; a ready model authorizes none of them.
155
155
  - Personal data has a decided subject, and the subject is person-level or the
156
156
  trade-off is surfaced.
157
157
  - The specification outline names the failures, not only the happy path.
158
+
159
+ ## Route near misses
160
+
161
+ - Modeling the same system as a **Screenplay `.play` document** rather than as a
162
+ brief for hand-written C#: `cratis-screenplay-event-modeling`. The method is the
163
+ same; the artifact is a file that compiles, and the four behavior types above
164
+ map onto its `StateChange`, `StateView`, `Automation` and `Translate` slices.
165
+ - Drawing a decided model as a diagram: `cratis-event-model-diagram`.
@@ -140,3 +140,10 @@ language. Include commands that emit no event.
140
140
  - The command-rules table covers every command, including those that emit
141
141
  nothing.
142
142
  - Cross-module flows are reflected in all three places.
143
+
144
+ ## Route near misses
145
+
146
+ - Deciding the model rather than drawing one: `cratis-chronicle-event-modeling`
147
+ for a hand-written Chronicle implementation, or
148
+ `cratis-screenplay-event-modeling` when the model is authored as a Screenplay
149
+ `.play` document.
@@ -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,229 @@
1
+ ---
2
+ name: cratis-screenplay-captures-and-reactions
3
+ description: Model the reactive edges of a Cratis Screenplay `.play` model — `capture` in the Change Data Capture Language for turning external data into events, and `reaction` plus `trigger` for work that runs when something happens, on a schedule, or on an integration signal. Use when integrating an external system, translating outside data into events, or declaring automatic follow-up work in a `.play` model. Do not use for commands, projections or screens.
4
+ license: MIT
5
+ ---
6
+
7
+ # Captures and reactions
8
+
9
+ The two slice types that run without anyone pressing a button. A `capture` in a
10
+ `Translate` slice turns outside data into events; a `reaction` in an `Automation`
11
+ slice runs when something happens.
12
+
13
+ ## Verified product sources
14
+
15
+ | Package | Version | Purpose |
16
+ | --- | --- | --- |
17
+ | `Cratis.Screenplay` | `4.12.1` | CDL parser, reaction and trigger parsers, diagnostics |
18
+
19
+ Read from the Screenplay repository at tag `v4.12.1` (commit `122eee8`), against
20
+ `Documentation/screenplay/{captures,captures/grammar,reactions,triggers}.md` and
21
+ `Source/DotNET/Screenplay/Parsing/`. Reverify before claiming another version
22
+ behaves the same.
23
+
24
+ ⚠️ **Neither slice type reaches the reference runtime.** `SemanticSliceKind` admits
25
+ only `StateChange` and `StateView`, so an `Automation` or `Translate` slice is
26
+ rejected by ESM v1 outright. Model them when the `.play` file is the deliverable;
27
+ do not promise they execute.
28
+
29
+ ## `capture` — the Change Data Capture Language
30
+
31
+ Captures live in `Translate` slices and are the anti-corruption layer: external
32
+ shapes in, domain events out.
33
+
34
+ ```screenplay
35
+ slice Translate LegacyInvoiceSync
36
+ capture LegacyInvoiceCapture
37
+ source api
38
+ api LegacyInvoicingApi
39
+ route /invoices
40
+ poll 5m
41
+ key id
42
+ map
43
+ status = status translate
44
+ "utkast" => draft
45
+ "sendt" => sent
46
+ "betalt" => paid
47
+ append InvoiceStatusChanged
48
+ tag legacy
49
+ when status
50
+ invoiceId = $.id
51
+ status = $.status
52
+ changedAt = $context.occurred
53
+ append InvoicePaidFromSent
54
+ when status from "sent" to "paid"
55
+ invoiceId = $.id
56
+ children lineItems identified by lineNumber
57
+ append InvoiceLineItemAdded
58
+ when added
59
+ invoiceId = $.id
60
+ lineNumber = $.lineNumber
61
+ ```
62
+
63
+ **`source <kind>`** with indented settings. The documented kinds and settings are
64
+ `api` (`api`, `route`, `poll`), `webhook` (`path`) and `message` (`topic`).
65
+ ⚠️ The grammar is **open** — the parser accepts any kind and any setting name as
66
+ free-form name/value pairs, so a typo is not caught. The three kinds are
67
+ convention, not enforcement.
68
+
69
+ **`key <property>`** names the source property identifying an instance.
70
+
71
+ **`map`** reshapes before events are appended: direct rename
72
+ (`productName = name`), a backtick template, `translate` with indented
73
+ `"source" => target` entries, and `split <source> by "<sep>"` with indented target
74
+ properties.
75
+
76
+ **Mapping sources:** `$.` for a value from the current source item, `$context.` for
77
+ capture context, `$env.` for an environment variable, plus literals and templates.
78
+
79
+ **`append <Event>`** with optional `tag` lines and one `when` clause:
80
+
81
+ | `when` form | Appends when |
82
+ | --- | --- |
83
+ | `when added` | an item appears in the source |
84
+ | `when removed` | an item disappears from the source |
85
+ | `when <Path>` | that property changes |
86
+ | `when <Path> from <v> to <v>` | that property makes that exact transition |
87
+ | `when <a> or <b> [or <c>]` | any of those properties change |
88
+ | `when <a> and <b> [and <c>]` | all of those properties change |
89
+ | ``when `<expression>` `` | a raw template expression is true (captured verbatim, unparsed) |
90
+
91
+ ⚠️ **`or` and `and` cannot be mixed in one clause** — `when a or b and c` is a
92
+ compile error. Split it into two `append` blocks.
93
+
94
+ **`children <collection> identified by <path>`** and **`nested <path>`** each take
95
+ an optional `map` and any number of `append` blocks.
96
+
97
+ ## `reaction`
98
+
99
+ ```screenplay
100
+ slice Automation NotifyOnBuildFailure
101
+ reaction NotifyOnFailure
102
+ description "Tells the owning team when a watched build fails"
103
+ when BuildFinished
104
+ repository
105
+ outcome
106
+ invokes SendFailureNotice
107
+ repository = repository
108
+ where outcome == "failed"
109
+ ```
110
+
111
+ A reaction declares **at least one trigger**; everything under a trigger is
112
+ optional.
113
+
114
+ ⚠️ **Indentation decides what is a trigger and what is an effect.** `produces`,
115
+ `invokes`, `file` and inline code belong **inside** the trigger block, indented
116
+ under `when`/`every`/`at`. Only `description` and `where` sit at reaction level.
117
+ Outdenting an effect gives *Expected a trigger in reaction body, got 'invokes …'*
118
+ (`PLAY0137`) — the compiler is looking for another trigger where the effect is.
119
+
120
+ ### What sets it off
121
+
122
+ | Form | Runs |
123
+ | --- | --- |
124
+ | `when <Name>` | on that event, declared trigger, or registered trigger |
125
+ | `every <n> seconds\|minutes\|hours\|days` | on that interval (`n` ≥ 1) |
126
+ | `at HH:mm` | every day at that time |
127
+ | `at HH:mm on <Weekday>` | every week on that day |
128
+ | `at HH:mm on day <n>` | every month on that day (`n` is 1–31) |
129
+
130
+ Time is strictly `HH:mm`. Weekday is the full English name.
131
+
132
+ ### The values a reaction takes
133
+
134
+ A bare name under a trigger says the reaction **uses** that value from the
135
+ occurrence. This is a **selection, not a declaration** — the shape belongs to the
136
+ event or trigger. Taking a value the occurrence does not carry is reported.
137
+
138
+ ### `where` — narrowing
139
+
140
+ `where` uses the same condition grammar as `produces … when` and `require`:
141
+ `==`, `!=`, the ordering operators, `contains` for a substring anywhere,
142
+ `starts with` for one at the beginning, combined with `and`, `or` and parentheses.
143
+
144
+ ⚠️ **`where` belongs to the reaction, not to one trigger** — it says which
145
+ occurrences are worth running for, whatever set them off. A second `where` on one
146
+ reaction is an error; combine with `and`/`or` instead.
147
+
148
+ ### Effects — `produces` vs `invokes`
149
+
150
+ ```screenplay
151
+ reaction Provisioner
152
+ when InvitationAccepted
153
+ workspaceId
154
+ produces WorkspaceProvisioned
155
+ for workspaceId
156
+ workspaceId = workspaceId
157
+ invokes SendWelcomeMail
158
+ workspaceId = workspaceId
159
+ ```
160
+
161
+ **The different word is the point.** `produces` appends a fact, and nothing can
162
+ refuse it. `invokes` asks for a command, which may still validate and reject.
163
+ Using `produces` for both would say those are the same kind of consequence.
164
+
165
+ Both are declarations of *what happens*, not of how — a trigger can state its
166
+ consequences **and** carry a `file` or inline block that implements them.
167
+
168
+ ## `trigger`
169
+
170
+ ```screenplay
171
+ trigger BuildFinished
172
+ description "CI reported a finished build on a watched repository"
173
+ repository Repository
174
+ outcome
175
+ ```
176
+
177
+ A trigger declares two things: **that the name exists**, and **what an occurrence
178
+ hands the reaction**. It deliberately declares nothing about what makes one occur —
179
+ that belongs to whatever produces it, and keeping it out is what lets the set stay
180
+ open. The type on a value is optional; a bare name is already a useful statement.
181
+
182
+ **Name resolution, in order:** events the document declares or imports → triggers
183
+ the document declares → triggers registered with the compiler, plus the built-in
184
+ host signals. A registration wins over a built-in of the same name. Only when all
185
+ three miss is the name reported unknown — as a **warning**, like every other
186
+ unresolved reference.
187
+
188
+ **Built-in triggers:** `Startup` and `Shutdown`. The clock is built in too, but
189
+ with its own syntax rather than a name.
190
+
191
+ ⚠️ A trigger body reserves `file`, so a trigger value named `file` is written
192
+ `@file`.
193
+
194
+ **Registration** (for a name only an integration knows) happens through the
195
+ compiler's language registry, either name-only or with a shape. ⚠️ Stating **no**
196
+ values and stating **none** are different claims: a definition with no value list
197
+ says the registration does not describe the shape, so what a reaction takes is left
198
+ alone; one with an **empty** list says an occurrence carries nothing, so taking
199
+ something is reported. `Startup` and `Shutdown` are registered the second way.
200
+
201
+ ## Guidance, verbatim
202
+
203
+ > - Reactions that only translate events into other events belong in `Translate`
204
+ > slices when driven by external data (captures); event-to-event automation stays
205
+ > in `Automation` slices.
206
+ > - Keep reaction logic small; anything substantial belongs in a `file` reference
207
+ > where it can be tested on its own.
208
+ > - Describe the reaction before you implement it — a document full of `file` lines
209
+ > and nothing else tells a reader nothing.
210
+ > - A name that only an integration knows belongs in a `trigger` declaration, so the
211
+ > document says what the reaction is handed rather than leaving the reader to guess.
212
+
213
+ ## Verify
214
+
215
+ - [ ] `screenplay <model> --warnaserror` reports zero errors and zero warnings.
216
+ - [ ] Each automation has all four components — occurrence, state consulted,
217
+ conditional logic, resulting command or event. If it always fires
218
+ unconditionally it is co-production, not an automation.
219
+ - [ ] Every reaction has a termination condition and cannot loop forever.
220
+ - [ ] `produces` is used for facts and `invokes` for intents, never interchangeably.
221
+ - [ ] No `when` clause mixes `or` and `and`.
222
+ - [ ] Cross-cutting infrastructure is **not** modeled as a `Translate` slice.
223
+ - [ ] Every name a reaction takes is carried by the event or trigger.
224
+
225
+ ## Route near misses
226
+
227
+ - Deciding whether a behavior is an automation at all: `cratis-screenplay-event-modeling`.
228
+ - The commands a reaction invokes: `cratis-screenplay-command-surface`.
229
+ - Hand-written C# Chronicle reactors: `cratis-chronicle-reactor`.
@@ -0,0 +1,10 @@
1
+ {
2
+ "skill": "cratis-screenplay-captures-and-reactions",
3
+ "input": "Translate external data into events and declare a reaction.",
4
+ "fixtures": [],
5
+ "assertions": [
6
+ { "kind": "skill-contains", "value": "cannot be mixed in one clause" },
7
+ { "kind": "skill-contains", "value": "`where` belongs to the reaction, not to one trigger" },
8
+ { "kind": "skill-contains", "value": "Startup` and `Shutdown" }
9
+ ]
10
+ }
@@ -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,270 @@
1
+ ---
2
+ name: cratis-screenplay-command-surface
3
+ description: Write the write side of a Cratis Screenplay `.play` model — the `command` block and its `identifier`, `reads`, `validate`, `authorize`, `produces`, `handler` and `concurrency` clauses, plus `event`, `constraint`, `policy`, `persona`, `concept`, `type` and `seed`. Use when declaring or changing a command, an event shape, a validation or authorization rule, an append-time constraint, or a strongly-typed value in Screenplay. Do not use for projections, queries, screens, captures or reactions.
4
+ license: MIT
5
+ ---
6
+
7
+ # The Screenplay write surface
8
+
9
+ Everything that changes the system: the `command` that expresses intent, the
10
+ `event` it appends, the rules that can refuse it, and the strongly-typed values
11
+ they are all built from.
12
+
13
+ ## Verified product sources
14
+
15
+ | Package | Version | Purpose |
16
+ | --- | --- | --- |
17
+ | `Cratis.Screenplay` | `4.12.1` | Parser, validator, diagnostics |
18
+
19
+ Read from the Screenplay repository at tag `v4.12.1` (commit `122eee8`), against
20
+ both the documentation and `Source/DotNET/Screenplay/Parsing/`. Reverify before
21
+ claiming another version behaves the same.
22
+
23
+ ## The command block
24
+
25
+ ```screenplay
26
+ command RegisterInvoice
27
+ description "Registers a new invoice with its lines and payment terms"
28
+ invoiceId InvoiceId identifier
29
+ invoiceNumber InvoiceNumber
30
+ lines InvoiceLine[]
31
+ note String?
32
+ reads InvoiceList by invoiceNumber
33
+ authorize CanManageInvoice
34
+ validate
35
+ invoiceNumber not empty message "Invoice number is required"
36
+ invoiceNumber matches "^INV-[0-9]{6}$" message "Must look like INV-000000"
37
+ produces InvoiceRegistered
38
+ invoiceId = invoiceId
39
+ registeredAt = $context.occurred
40
+ concurrency
41
+ eventSource
42
+ events InvoiceRegistered, InvoiceCancelled
43
+ ```
44
+
45
+ Type modifiers: `<Type>[]` for a collection, `<Type>?` for optional.
46
+
47
+ ⚠️ **The parser enforces no clause order.** The order above is the house
48
+ convention and the order a reader expects; the compiler accepts any. Keep to it.
49
+
50
+ ⚠️ **`produces` and `handler` are mutually exclusive** — declaring both is an
51
+ error. Everything else may repeat except `description` (one) and `concurrency`
52
+ (one).
53
+
54
+ ## `identifier` — the stream boundary decision
55
+
56
+ Exactly one command property may carry `identifier`. It names the value the
57
+ runtime resolves the event source id from, so **choosing it is choosing the stream
58
+ boundary** — the highest-consequence decision in the slice.
59
+
60
+ - A second `identifier` on the same command is an error: *only one property can be
61
+ the identifier*.
62
+ - `identifier` on an **event** property is an error: *an event never carries its
63
+ event source id*. It travels in the event context.
64
+
65
+ ## `reads` — state the command decides against
66
+
67
+ ```screenplay
68
+ reads InvoiceList by invoiceNumber
69
+ ```
70
+
71
+ `by <property>` names the command property the read model is looked up by; it is
72
+ optional. A command may read several read models, but **each one only once** —
73
+ a duplicate is an error.
74
+
75
+ This is a deliberate Cratis divergence from generic Event Modeling, which forbids
76
+ read-model-to-command edges. Use it for a genuine consistency boundary, and pair
77
+ it with `concurrency` so the decision is enforced at append time rather than
78
+ merely consulted. Do not use it to fetch data the caller could supply.
79
+
80
+ ## `validate` — the complete rule vocabulary
81
+
82
+ Fourteen rule kinds (`ValidationRuleKind`), each taking an optional
83
+ `message "<text>"`:
84
+
85
+ | Rule | Example |
86
+ | --- | --- |
87
+ | `not empty` | `name not empty` |
88
+ | `max <n>` / `min <n>` | `reason max 500`, `quantity min 1` |
89
+ | `> <v>` / `>= <v>` / `< <v>` / `<= <v>` | `quantity > 0`, `discountPct <= 100` |
90
+ | `== <v>` / `!= <v>` | `currency == "NOK"`, `status != "draft"` |
91
+ | `length == <n>` | `currency length == 3` |
92
+ | `matches <regex>` | `invoiceNumber matches "^INV-[0-9]{6}$"` |
93
+ | `all > <v>` / `all >= <v>` | `lines.quantity all > 0` |
94
+ | `rule <Name>` | `orgNumber rule BeAValidOrganizationNumber` |
95
+
96
+ `matches` accepts either a named pattern (`email matches email`) or a quoted
97
+ regex, which is why the reference lists it on two rows while the parser has one
98
+ kind for it.
99
+
100
+ A `rule <Name>` may carry an indented `file <path>` or an inline code block as its
101
+ body. A rule with no body is a complete statement that the rule exists.
102
+
103
+ **Whole-command rules** use `require <condition>` with an optional indented
104
+ `message`, sharing the condition grammar with `produces … when` and `where`.
105
+
106
+ **Put format rules on the `concept`, not the command.** A concept carries its own
107
+ `validate` block and every use inherits it — that is Screenplay's type system, and
108
+ a rule that travels is worth more than one that is repeated.
109
+
110
+ ## `authorize`
111
+
112
+ ```screenplay
113
+ authorize IsAccountant
114
+ or IsCustomerSelf
115
+
116
+ authorize (IsAccountant or IsFinance) and OwnsInvoice
117
+ ```
118
+
119
+ Policy names are PascalCase. `and` binds tighter than `or`; parentheses group.
120
+ A continuation line extends the clause.
121
+
122
+ ⚠️ **Two adjacent policies synthesize an implicit `and`.** `authorize A B` means
123
+ `A and B`. Write the operator explicitly so the reader does not have to know this.
124
+
125
+ ## `produces`
126
+
127
+ Every mapping source form: a command property (`= invoiceNumber`), a context value
128
+ (`= $context.occurred`, `= $context.identity.id`, `= $context.identity.claims.x`,
129
+ `= $context.causedBy.subject`, `= $context.causation.type`, `= $context.tenant`),
130
+ an environment variable (`= $env.REGION`), a literal (`= "draft"`, `= 0`), or a
131
+ template (`` = `${firstName} ${lastName}` ``).
132
+
133
+ - **`for <expression>`** says which event source the event lands on. **At most one
134
+ per `produces`** — *an event is appended to one event source*.
135
+ - **`tag`** lines apply tags to this append. Tags also exist on the `event`
136
+ declaration, where they apply to every append of that type.
137
+ - **Several unconditional `produces` blocks** are allowed — that is co-production,
138
+ and it is what an automation is *not*.
139
+ - **`produces when <condition>`** takes the event name on the next indented line:
140
+
141
+ ```screenplay
142
+ produces when isProForma == true
143
+ ProFormaInvoiceIssued
144
+ invoiceId = invoiceId
145
+ ```
146
+
147
+ ## `concurrency`
148
+
149
+ Five dimensions, each at most once, and at most one `concurrency` block per
150
+ command:
151
+
152
+ | Dimension | Scopes the check to |
153
+ | --- | --- |
154
+ | `eventSource` | the command's own event source id |
155
+ | `sourceType <Name>` | an event source type |
156
+ | `streamType <Name>` | an event stream type |
157
+ | `streamId <Name>` | an event stream id |
158
+ | `events <A>, <B>` | the listed event types |
159
+
160
+ An unknown dimension is an error naming all five.
161
+
162
+ ## `constraint` — append-time invariants
163
+
164
+ Exactly one of three forms per constraint:
165
+
166
+ ```screenplay
167
+ constraint UniqueInvoiceNumber
168
+ unique invoiceNumber on InvoiceRegistered
169
+
170
+ constraint OneRegistrationPerInvoice
171
+ unique event InvoiceRegistered
172
+
173
+ constraint InvoiceStatusTransition
174
+ file Constraints/InvoiceStatusTransition.cs
175
+ ```
176
+
177
+ A constraint with no body is an error naming the three forms. Use a constraint
178
+ when two concurrent appends must not both win — a `validate` rule cannot do that.
179
+
180
+ ## `concept` and `type`
181
+
182
+ ```screenplay
183
+ concept InvoiceId : Uuid
184
+ concept DiscountPercentage : Decimal
185
+ validate
186
+ >= 0 message "A discount cannot be negative"
187
+ <= 100 message "A discount cannot exceed 100 percent"
188
+ concept PersonName : String @pii
189
+ pii reason "Billing contact name; lawful basis: contract performance."
190
+ concept InvoiceStatus : Enum
191
+ draft
192
+ sent
193
+ paid
194
+ ```
195
+
196
+ The seven primitives are `Uuid`, `String`, `Int`, `Decimal`, `Bool`, `Date` and
197
+ `DateTime`. `Enum` is **not** one of them — it is a separate concept kind, which
198
+ is why the compiler says *expected … or Enum* rather than listing it among them.
199
+ Attributes `@pii` and `@sensitive`, each with at most one `reason`; a
200
+ reason for an attribute the concept does not declare is an error. **Compliance is
201
+ inherited** — a property typed with a `@pii` concept is PII everywhere.
202
+
203
+ ⚠️ **Enum trap.** A value literally named `validate` is read as an empty validate
204
+ block. Write `@validate` for the value; the compiler warns when it sees the
205
+ ambiguity.
206
+
207
+ Use `type <Name>` for a composite shape (several properties) that events and
208
+ commands reference; use `concept` for a single wrapped primitive.
209
+
210
+ ## `policy` and `persona`
211
+
212
+ ```screenplay
213
+ policy IsAuthenticated
214
+ require authenticated
215
+ policy CanManageInvoice
216
+ require role "InvoiceManager"
217
+ or role "Accountant"
218
+ policy OwnsInvoice
219
+ require claim "sub" matches subject
220
+
221
+ persona Accountant
222
+ description "Handles invoicing and collections"
223
+ policy IsAccountant
224
+ ```
225
+
226
+ Three condition forms: `authenticated`, `role "<name>"`, and
227
+ `claim "<name>" matches subject | <value>`. A policy must declare a `require`
228
+ condition or an inline code block.
229
+
230
+ ## `seed`
231
+
232
+ ```screenplay
233
+ seed
234
+ for "3fa85f64-5717-4562-b3fc-2c963f66afa6"
235
+ CustomerRegistered
236
+ name = "Acme Corp"
237
+ ```
238
+
239
+ Events append to that event source in declaration order, using the same mapping
240
+ expression grammar as `produces`. Several `seed` blocks accumulate.
241
+
242
+ ## `$context`
243
+
244
+ Four contexts, and **what each omits is load-bearing** — read
245
+ [context.md](references/context.md) for the full member lists, the declarative
246
+ `$context.` paths, and `$causedBy` / `$env` / `$strings`.
247
+
248
+ | Context | For | Deliberately omits |
249
+ | --- | --- | --- |
250
+ | Command | a command handler | — |
251
+ | Query | a query performer | — |
252
+ | Rule | a validation rule | **`Identity`** — validation does not see roles or claims |
253
+ | Policy | an authorization policy | **`CausedBy`, `Causation`** |
254
+
255
+ ## Verify
256
+
257
+ - [ ] `screenplay <model> --warnaserror` reports zero errors and zero warnings.
258
+ - [ ] Exactly one command property carries `identifier`, and no event property does.
259
+ - [ ] Format rules live on the `concept`; state-dependent rules are specifications.
260
+ - [ ] Every `authorize` combining policies writes `and`/`or` explicitly.
261
+ - [ ] Any `reads` that decides a rule is paired with a `concurrency` block.
262
+ - [ ] Personal data is `@pii` on the concept, with a reason.
263
+ - [ ] No event carries an optional property covering two situations.
264
+
265
+ ## Route near misses
266
+
267
+ - Deciding *which* commands and events exist: `cratis-screenplay-event-modeling`.
268
+ - Building read models from these events: `cratis-screenplay-projections`.
269
+ - Pinning the rejections: `cratis-screenplay-specifications`.
270
+ - The admitted-versus-parsed boundary: `cratis-screenplay-model-authoring`.