@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.
- package/package/corpus/rules/general.md +2 -0
- package/package/corpus/rules/profiles.md +18 -1
- package/package/corpus/skills/cratis-chronicle-event-modeling/SKILL.md +8 -0
- package/package/corpus/skills/cratis-event-model-diagram/SKILL.md +7 -0
- package/package/corpus/skills/cratis-screenplay-captures-and-reactions/LICENSE +2 -0
- package/package/corpus/skills/cratis-screenplay-captures-and-reactions/SKILL.md +229 -0
- package/package/corpus/skills/cratis-screenplay-captures-and-reactions/verification.json +10 -0
- package/package/corpus/skills/cratis-screenplay-command-surface/LICENSE +2 -0
- package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +270 -0
- package/package/corpus/skills/cratis-screenplay-command-surface/references/context.md +111 -0
- package/package/corpus/skills/cratis-screenplay-command-surface/verification.json +23 -0
- package/package/corpus/skills/cratis-screenplay-event-modeling/LICENSE +2 -0
- package/package/corpus/skills/cratis-screenplay-event-modeling/SKILL.md +204 -0
- package/package/corpus/skills/cratis-screenplay-event-modeling/references/nine-steps.md +219 -0
- package/package/corpus/skills/cratis-screenplay-event-modeling/verification.json +23 -0
- package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +25 -9
- package/package/corpus/skills/cratis-screenplay-projections/LICENSE +2 -0
- package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +215 -0
- package/package/corpus/skills/cratis-screenplay-projections/references/pdl-grammar.md +241 -0
- package/package/corpus/skills/cratis-screenplay-projections/verification.json +11 -0
- package/package/corpus/skills/cratis-screenplay-read-surface/LICENSE +2 -0
- package/package/corpus/skills/cratis-screenplay-read-surface/SKILL.md +188 -0
- package/package/corpus/skills/cratis-screenplay-read-surface/verification.json +10 -0
- package/package/corpus/skills/cratis-screenplay-specifications/LICENSE +2 -0
- package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +199 -0
- package/package/corpus/skills/cratis-screenplay-specifications/verification.json +19 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/LICENSE +2 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +231 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/verification.json +10 -0
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +5 -2
- package/package/profile-catalog.json +8 -1
- 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,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
|
|
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
|
|
|
@@ -207,16 +207,20 @@ The language service ships two ways: the Monaco package
|
|
|
207
207
|
(marketplace id `cratis.screenplay`), which is the same service plus a `.play`
|
|
208
208
|
file icon.
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
`contribute`, `dialog` and `
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
210
|
+
Its keyword list had drifted from the parser: `capture`, `projection`, `reducer`,
|
|
211
|
+
`contribute`, `dialog`, `form`, `theme` and `ui` were constructs the compiler
|
|
212
|
+
accepts with no keyword entry, so they got no highlighting and no completion.
|
|
213
|
+
Fixed in `Cratis/Screenplay#199`, which also added a specification deriving the
|
|
214
|
+
dispatched set from the parsers so the two cannot drift apart again silently.
|
|
215
|
+
|
|
216
|
+
⚠️ The general lesson stands regardless: **absent highlighting is not evidence
|
|
217
|
+
that a construct is wrong** — check `screenplay` rather than the editor.
|
|
216
218
|
|
|
217
219
|
The repository's TypeScript workspaces are **not** built, linted or tested by any
|
|
218
|
-
pull-request workflow;
|
|
219
|
-
pull request as evidence the language service still compiles
|
|
220
|
+
pull-request workflow; only `dotnet-build.yml` runs tests on a pull request. Do
|
|
221
|
+
not read a green pull request as evidence the language service still compiles —
|
|
222
|
+
and put any guard on the language service in the C# suite, which is the one that
|
|
223
|
+
actually runs.
|
|
220
224
|
|
|
221
225
|
## Verify
|
|
222
226
|
|
|
@@ -232,6 +236,18 @@ pull request as evidence the language service still compiles.
|
|
|
232
236
|
|
|
233
237
|
## Route near misses
|
|
234
238
|
|
|
239
|
+
This skill is the language and compiler reference. For the constructs themselves:
|
|
240
|
+
|
|
241
|
+
| Need | Skill |
|
|
242
|
+
| --- | --- |
|
|
243
|
+
| Facilitating the model and the nine-step method | `cratis-screenplay-event-modeling` |
|
|
244
|
+
| `command`, `event`, `validate`, `authorize`, `produces`, `concurrency`, `$context` | `cratis-screenplay-command-surface` |
|
|
245
|
+
| `projection` and the PDL, `reducer` | `cratis-screenplay-projections` |
|
|
246
|
+
| `readmodel`, `query`, `screen` | `cratis-screenplay-read-surface` |
|
|
247
|
+
| `layout`, templates, `form`, `contribute`, `theme`, `$strings` | `cratis-screenplay-ui-composition` |
|
|
248
|
+
| `capture` and the CDL, `reaction`, `trigger` | `cratis-screenplay-captures-and-reactions` |
|
|
249
|
+
| `specification` | `cratis-screenplay-specifications` |
|
|
250
|
+
|
|
235
251
|
- Turning a model into an application, or the runtime sandbox:
|
|
236
252
|
`cratis-stage-rendering-and-sandbox`.
|
|
237
253
|
- Designing an event model as a diagram rather than as a `.play` file:
|