@cratis/pi 0.0.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 (125) hide show
  1. package/README.md +43 -0
  2. package/package.json +24 -0
  3. package/skills/cratis-application-react-specifications/LICENSE +2 -0
  4. package/skills/cratis-application-react-specifications/SKILL.md +192 -0
  5. package/skills/cratis-application-slice-specifications/LICENSE +2 -0
  6. package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
  7. package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
  8. package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
  9. package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
  10. package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
  11. package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
  12. package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
  13. package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
  14. package/skills/cratis-arc-command/LICENSE +2 -0
  15. package/skills/cratis-arc-command/SKILL.md +286 -0
  16. package/skills/cratis-arc-command/references/command-result.md +111 -0
  17. package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
  18. package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
  19. package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
  20. package/skills/cratis-arc-command-execution/LICENSE +2 -0
  21. package/skills/cratis-arc-command-execution/SKILL.md +148 -0
  22. package/skills/cratis-arc-command-validation/LICENSE +2 -0
  23. package/skills/cratis-arc-command-validation/SKILL.md +230 -0
  24. package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
  25. package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
  26. package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
  27. package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
  28. package/skills/cratis-arc-query-paging/LICENSE +2 -0
  29. package/skills/cratis-arc-query-paging/SKILL.md +173 -0
  30. package/skills/cratis-arc-react-page/LICENSE +2 -0
  31. package/skills/cratis-arc-react-page/SKILL.md +291 -0
  32. package/skills/cratis-arc-react-page/references/data-page.md +125 -0
  33. package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
  34. package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
  35. package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
  36. package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
  37. package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
  38. package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
  39. package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
  40. package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
  41. package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
  42. package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
  43. package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
  44. package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
  45. package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
  46. package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
  47. package/skills/cratis-chronicle-compliance/LICENSE +2 -0
  48. package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
  49. package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
  50. package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
  51. package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
  52. package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
  53. package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
  54. package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
  55. package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
  56. package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
  57. package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
  58. package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
  59. package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
  60. package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
  61. package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
  62. package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
  63. package/skills/cratis-chronicle-projection/LICENSE +2 -0
  64. package/skills/cratis-chronicle-projection/SKILL.md +162 -0
  65. package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
  66. package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
  67. package/skills/cratis-chronicle-reactor/LICENSE +2 -0
  68. package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
  69. package/skills/cratis-chronicle-read-model/LICENSE +2 -0
  70. package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
  71. package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
  72. package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
  73. package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
  74. package/skills/cratis-chronicle-reducer/LICENSE +2 -0
  75. package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
  76. package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
  77. package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
  78. package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
  79. package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
  80. package/skills/cratis-code-review/LICENSE +2 -0
  81. package/skills/cratis-code-review/SKILL.md +177 -0
  82. package/skills/cratis-components-accessibility/LICENSE +2 -0
  83. package/skills/cratis-components-accessibility/SKILL.md +176 -0
  84. package/skills/cratis-components-schema-editor/LICENSE +2 -0
  85. package/skills/cratis-components-schema-editor/SKILL.md +238 -0
  86. package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
  87. package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
  88. package/skills/cratis-components-styling/LICENSE +2 -0
  89. package/skills/cratis-components-styling/SKILL.md +221 -0
  90. package/skills/cratis-components-toolbar/LICENSE +2 -0
  91. package/skills/cratis-components-toolbar/SKILL.md +259 -0
  92. package/skills/cratis-documentation-writing/LICENSE +2 -0
  93. package/skills/cratis-documentation-writing/SKILL.md +121 -0
  94. package/skills/cratis-event-model-diagram/LICENSE +2 -0
  95. package/skills/cratis-event-model-diagram/SKILL.md +142 -0
  96. package/skills/cratis-fundamentals-concept/LICENSE +2 -0
  97. package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
  98. package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
  99. package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
  100. package/skills/cratis-governed-release-methodology/LICENSE +2 -0
  101. package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
  102. package/skills/cratis-lens-browser-extension/LICENSE +2 -0
  103. package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
  104. package/skills/cratis-performance-review/LICENSE +2 -0
  105. package/skills/cratis-performance-review/SKILL.md +139 -0
  106. package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
  107. package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
  108. package/skills/cratis-security-review/LICENSE +2 -0
  109. package/skills/cratis-security-review/SKILL.md +141 -0
  110. package/skills/cratis-specification-by-example/LICENSE +2 -0
  111. package/skills/cratis-specification-by-example/SKILL.md +188 -0
  112. package/skills/cratis-specifications-csharp/LICENSE +2 -0
  113. package/skills/cratis-specifications-csharp/SKILL.md +230 -0
  114. package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
  115. package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
  116. package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
  117. package/skills/cratis-specifications-typescript/LICENSE +2 -0
  118. package/skills/cratis-specifications-typescript/SKILL.md +202 -0
  119. package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
  120. package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
  121. package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
  122. package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
  123. package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
  124. package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
  125. package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: cratis-security-review
3
+ description: Perform a focused security review of changed code in a Cratis application — injection, authentication and authorization, data exposure, secrets, event-sourcing-specific exposure, and the frontend — and report findings by risk. Use when asked for a security review or audit. Do not use to implement authentication and do not report a policy preference as a framework contract.
4
+ license: MIT
5
+ ---
6
+
7
+ # Cratis security review
8
+
9
+ Event sourcing changes the shape of a security review. A mistake in an ordinary
10
+ application is a bug you fix; a secret written into an append is a fact that
11
+ lives in the log forever and cannot be edited out. The event-sourcing section
12
+ below is the one that is specific to this stack, and it is the one worth reading
13
+ first.
14
+
15
+ ## Verified product sources
16
+
17
+ | Package | Version | Purpose |
18
+ | --- | --- | --- |
19
+ | `Cratis.Arc.Chronicle` | `22.10.4` | `[NotAudited]` in `Cratis.Arc.Chronicle.Commands`; the `ARCCHR0009` analyzer |
20
+ | `Cratis.Chronicle` | `16.45.2` | `[PII]`, `[Subject]`, redaction, namespace isolation |
21
+ | `Cratis.Arc.Core` | `22.10.4` | Authorization filters, `CommandResult.Unauthorized` |
22
+
23
+ Reverify against the owning product repository before asserting a framework
24
+ guarantee this file does not already state.
25
+
26
+ ## Route near misses
27
+
28
+ - General correctness and maintainability: use `cratis-code-review`.
29
+ - Scalability and resource use: use `cratis-performance-review`.
30
+ - Implementing authentication, authorization or identity: use
31
+ `cratis-arc-authentication-authorization-and-identity`. This skill reviews;
32
+ it does not build.
33
+ - Compliance mechanics — `[PII]`, subject resolution, erasure, redaction: use
34
+ `cratis-chronicle-compliance`.
35
+
36
+ ## Step 1 — Event sourcing: the permanent-record checks
37
+
38
+ - **Every `[Command]` property holding a secret is marked `[NotAudited]`.** A
39
+ command's property values are written to the causation of every event it
40
+ appends, and causation is as permanent as the events. Prefer the marking on
41
+ the concept type so it travels everywhere the value appears.
42
+ `ARCCHR0009` catches properties whose *names* read as secrets — so read the
43
+ properties whose names do not say what they hold, because the analyzer cannot.
44
+ - **Personal data is `[PII]`, not `[NotAudited]`.** They are different
45
+ mechanisms with different consequences: `[NotAudited]` withholds a value from
46
+ the causation chain; `[PII]` enrolls it in per-subject encryption and erasure.
47
+ A password is `[NotAudited]`. An email address is `[PII]`. Neither substitutes
48
+ for the other.
49
+ - No secret, token, API key or password in an event property or a read model.
50
+ - Event-source ids are generated server-side, never accepted from an untrusted
51
+ client where the id grants access to a stream.
52
+ - Upcasting and event-type migration logic cannot introduce a property the
53
+ original contract did not carry.
54
+ - Uniqueness cannot be bypassed by concurrent writes — it is enforced by a
55
+ Chronicle constraint, not by a read-model pre-check.
56
+ - Cross-tenant writes cannot bypass a constraint that is scoped per namespace.
57
+
58
+ ## Step 2 — Input validation and injection
59
+
60
+ - Every command property is validated before use — null, empty, range, format.
61
+ - No raw SQL concatenation; parameterized queries or EF Core only.
62
+ - No user-supplied value reaches `Path.Combine`, a `File.*` call, a shell
63
+ command, or process arguments.
64
+ - No user-supplied value becomes an event-store key without sanitization.
65
+
66
+ ## Step 3 — Authentication and authorization
67
+
68
+ - Every exposed endpoint is either authorized or explicitly anonymous with a
69
+ stated reason.
70
+ - Authorization is expressed at the boundary — an attribute, a policy, a command
71
+ filter — never as an `if` on roles inside `Handle()`.
72
+ - Tenant isolation holds: no cross-namespace data is reachable without
73
+ authorization.
74
+ - Claims are verified before acting on identity-dependent command data. A client
75
+ must not be able to assert who it is through a command property.
76
+ - Note that an unauthorized command result maps to HTTP **403**, not 401 — a
77
+ reviewer reading logs for 401s will miss authorization failures.
78
+
79
+ ## Step 4 — Data exposure
80
+
81
+ - No personal data is returned to a caller that did not supply it.
82
+ - Query results are scoped to the requesting tenant and user. A query that can
83
+ return all-tenant data is a finding even when no current caller reaches it.
84
+ - Response payloads carry only fields the client uses. Over-fetching is an
85
+ exposure surface, not only a performance one.
86
+ - A managed read-model document holds one subject's personal data. Mixing
87
+ several people's data in one document breaks erasure.
88
+
89
+ ## Step 5 — Secrets and configuration
90
+
91
+ - No secret in source, in a configuration file, or in a specification fixture.
92
+ - Secrets come from environment variables or a secrets manager.
93
+ - No hard-coded connection string outside test code.
94
+
95
+ ## Step 6 — Frontend
96
+
97
+ - No user-supplied value in `dangerouslySetInnerHTML`.
98
+ - No token or secret in `localStorage` — use an `httpOnly` cookie or in-memory
99
+ state.
100
+ - Command payloads carry only the minimum required fields.
101
+ - No client-side access control that is not also enforced server-side. A
102
+ disabled button is a usability affordance, never a control.
103
+
104
+ ## Step 7 — Report
105
+
106
+ Open with one line:
107
+
108
+ > **Security review: No issues / Low-risk findings / Blocking issues found**
109
+
110
+ Group findings by the section that produced them, and classify each:
111
+
112
+ | Risk | Meaning |
113
+ | --- | --- |
114
+ | **Critical** | Must be fixed before merge |
115
+ | **Medium** | Should be fixed soon; state what makes it not-critical |
116
+ | **Low** | Fix when convenient |
117
+
118
+ Close with a per-section summary table, and **name what you did not review** —
119
+ the paths, the surfaces, and the axes out of scope. A security report listing
120
+ only findings reads as a clean bill of health for everything it never opened.
121
+
122
+ ## What breaks
123
+
124
+ - **A finding is a policy preference in framework clothing.** Which roles exist,
125
+ which data is sensitive, and which retention applies are the product's calls,
126
+ not the framework's. State them as policy questions for the owner, not as
127
+ contracts.
128
+ - **`[NotAudited]` is used for personal data.** The value stays out of causation
129
+ but is never encrypted and never enrolled in erasure — the opposite of what a
130
+ subject-rights request needs.
131
+ - **The review assumes the analyzer covered the secrets.** `ARCCHR0009` matches
132
+ names. A property called `Value` holding an API key passes it silently.
133
+ - **A missing check is reported as "verified".** Unknown is not pass. If a
134
+ surface could not be reached, say so as `indeterminate` rather than omitting
135
+ it.
136
+
137
+ ## How it is proven
138
+
139
+ The build and specifications are green before the report is written; each
140
+ finding cites the file and line; and the report states explicitly which
141
+ surfaces were and were not examined.
@@ -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,188 @@
1
+ ---
2
+ name: cratis-specification-by-example
3
+ description: Structure and name executable specifications the Cratis way — one behavior per specification, a for_/when_/and_ path that reads as an English sentence, layered given/ contexts, and an explicit decision about what is not worth specifying. Use when deciding how to organize, name, or scope specifications in any language. Do not use for language mechanics; route to the C# or TypeScript specification skill for those.
4
+ license: MIT
5
+ ---
6
+
7
+ # Cratis specification by example
8
+
9
+ Cratis calls automated tests **specifications**. That is not a vocabulary
10
+ preference — it changes what you write. A test asks "does this code still do
11
+ what it did yesterday?". A specification states what the software promises, in
12
+ the language of the domain, in a form a machine can check. The folder tree is
13
+ the table of contents; the file names are the sentences; the assertions are the
14
+ promises.
15
+
16
+ This skill is the language-agnostic layer. It settles the questions that are the
17
+ same in C#, TypeScript, and anything else: what to specify, how to name it,
18
+ where to put it, and when to stop.
19
+
20
+ ## Verified product sources
21
+
22
+ | Source | Version | What it grounds |
23
+ | --- | --- | --- |
24
+ | `Cratis.Specifications` analyzers | `CRSPEC0001`–`CRSPEC0007` | The naming and structure rules below are machine-enforced in C#, not taste |
25
+
26
+ The seven diagnostics are declared in `Cratis.Specifications.CodeAnalysis.DiagnosticIds`:
27
+
28
+ | Id | Rule |
29
+ | --- | --- |
30
+ | `CRSPEC0001` | A test method inside a specification must be named `should_*` |
31
+ | `CRSPEC0002` | A file declares at most one specification |
32
+ | `CRSPEC0003` | A test method must not sit on a reusable `given/` context |
33
+ | `CRSPEC0004` | A `should_*` method without a test attribute never runs |
34
+ | `CRSPEC0005` | A lifecycle method must not call its base implementation |
35
+ | `CRSPEC0006` | A specification declaring test methods must be public so the runner finds it |
36
+ | `CRSPEC0007` | The action under test must not sit on a reusable `given/` context |
37
+
38
+ In a language without those analyzers the same rules hold; the reviewer enforces
39
+ them instead of the compiler. Reverify against the owning product repository
40
+ before claiming behavior for another version.
41
+
42
+ ## Route near misses
43
+
44
+ - Writing the C# mechanics — the `Specification` base, `Establish`/`Because`,
45
+ substitutes, assertions: use `cratis-specifications-csharp`.
46
+ - Writing the TypeScript mechanics — `describe`/`it`, Sinon, the Chai `should`
47
+ interface: use `cratis-specifications-typescript`.
48
+ - Specifying an event-sourced application slice with the in-process scenario
49
+ family: use `cratis-application-slice-specifications`.
50
+ - Deciding what a command, projection, reducer, or reactor *should do*: that is
51
+ a modeling question. Settle the behavior first; a specification records a
52
+ decision, it does not make one.
53
+
54
+ ## Step 1 — State the behavior as a sentence
55
+
56
+ Before creating a file, say the specification out loud as one English sentence:
57
+
58
+ > **for** the changeset, **when** adding changes, **and** there are differences,
59
+ > it **should** record them.
60
+
61
+ Every clause becomes one level of the path. If the sentence does not survive
62
+ being spoken, the specification is not focused enough yet — that is the signal
63
+ to split it, not to write a longer name.
64
+
65
+ ## Step 2 — Build the path from the sentence
66
+
67
+ ```
68
+ for_<SubjectUnderTest>/
69
+ ├── given/
70
+ │ ├── all_dependencies ← substitutes every collaborator
71
+ │ └── a_<subject> ← builds the subject, layered on the above
72
+ ├── when_<behavior>/ ← a behavior with several outcomes
73
+ │ ├── and_<condition>
74
+ │ ├── with_<state>
75
+ │ └── without_<requirement>
76
+ └── when_<simple_behavior> ← a single outcome is a single file
77
+ ```
78
+
79
+ - **`for_<Subject>`** names the thing being specified.
80
+ - **`when_<behavior>`** names the action. This is the only place the word
81
+ **`when`** may appear.
82
+ - **Outcome names** use one of five prepositions: `and_`, `with_`, `without_`,
83
+ `having_`, `given_`.
84
+
85
+ **Two `when`s in one path is always wrong.** A file named
86
+ `with_a_registered_migration_when_appending_a_generation_1_event` is two
87
+ sentences pretending to be one. Fold the context into the `when_` folder and let
88
+ the outcomes be flat files:
89
+
90
+ ```
91
+ # Wrong
92
+ when_appending_event_with_migrations/
93
+ └── with_a_registered_migration_when_appending_a_generation_1_event
94
+
95
+ # Also wrong — a folder level that holds a single file
96
+ when_appending_event_with_migrations/
97
+ └── and_event_is_generation_1/
98
+ └── with_a_registered_migration
99
+
100
+ # Correct
101
+ when_appending_event_with_registered_migration/
102
+ ├── and_event_is_generation_1
103
+ ├── and_event_is_generation_2
104
+ └── and_event_has_default_value
105
+ ```
106
+
107
+ Add a sub-folder under `when_` only when that condition has its own several
108
+ outcomes. One outcome is one flat file.
109
+
110
+ ## Step 3 — One behavior, one specification
111
+
112
+ Each distinct outcome is its own file. When a specification fails you should
113
+ know from its name alone which promise broke, without reading the diff.
114
+
115
+ - A file that specifies a whole class is not a specification; it is a test suite
116
+ wearing the name.
117
+ - Assertions inside one file all describe the *same* outcome from different
118
+ angles. A second setup means a second file.
119
+ - Optimize for readability over removing duplication. Repeating three lines of
120
+ setup so a specification is self-contained is a good trade; a shared helper
121
+ the reader must go and open is not.
122
+
123
+ ## Step 4 — Put the world in `given/`, the action in the specification
124
+
125
+ A context captures the world *before* the action. Layer them:
126
+ `all_dependencies` substitutes the collaborators, `a_<subject>` builds the
127
+ subject on top of it, and the concrete specification adds only what is unique to
128
+ its case.
129
+
130
+ - Name a context `a_` or `an_` so it reads as "given an observer, when
131
+ handling".
132
+ - **The action under test never appears in a context** (`CRSPEC0007`) and
133
+ **assertions never appear in a context** (`CRSPEC0003`). A context that acts is
134
+ a specification that several files silently share, and the failure it produces
135
+ names the wrong subject.
136
+ - Contexts build in layers: `all_dependencies` → `a_reactor_handler` →
137
+ `when_handling`.
138
+
139
+ ## Step 5 — Decide what not to specify
140
+
141
+ Specify decisions, transformations, branching rules, and coordination between
142
+ collaborators — the places defects live. Leave alone:
143
+
144
+ - Auto-properties and properties that return a constructor parameter.
145
+ - Simple delegation that forwards to a collaborator and adds nothing.
146
+ - Logging. It is fragile to specify and worth nothing when it passes.
147
+ - Trivial null checks the type system already enforces.
148
+
149
+ A name beginning `when_getting_` or `when_returning_` is the tell: that is a
150
+ getter, not a behavior. Delete it rather than maintaining it.
151
+
152
+ ## Step 6 — Make the outcome observable
153
+
154
+ A specification is only as good as the signal it reads.
155
+
156
+ - **Assert on the outcome, not the message.** A presentation string is copy;
157
+ it changes for reasons that have nothing to do with the behavior. Assert on
158
+ the identity of what failed — an error code, a constraint name, a typed
159
+ result — never on the sentence shown to a user.
160
+ - **Never wait on the clock.** A sleep before an assertion passes because the
161
+ machine happened to be fast, and writes today's latency into the suite. Await
162
+ a completion signal, under a deadline. A timeout turns a hang into a named
163
+ failure; a sleep turns a race into a coin flip.
164
+ - **Nothing ambient.** The real clock, a random value, the network, shared
165
+ storage, or an ambient culture makes the outcome depend on something the
166
+ specification never stated. Inject it.
167
+ - **A specification that cannot fail proves nothing.** If you cannot describe
168
+ the change that would make it red, it is not specifying anything yet.
169
+
170
+ ## Step 7 — Say what the specification did not cover
171
+
172
+ Finishing a behavior means naming its unspecified edges, not claiming the
173
+ behavior is proven. A suite that lists only what passed reads as if everything
174
+ was checked. Record the cases you deliberately left out and why — an untested
175
+ edge someone chose is a different thing from one nobody saw.
176
+
177
+ ## Verify
178
+
179
+ - Every path reads as one English sentence, and `when` appears only in a
180
+ `when_<behavior>` folder name.
181
+ - Every outcome file name starts with `and_`, `with_`, `without_`, `having_`, or
182
+ `given_`.
183
+ - One specification per file; one outcome per specification.
184
+ - No action and no assertion sits on a `given/` context.
185
+ - Nothing trivial, delegated, or compiler-verified is specified.
186
+ - No assertion reads a presentation message string.
187
+ - No sleep, bare delay, or poll loop stands in for a completion signal.
188
+ - The suite runs green, and the report names what was not covered.
@@ -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,230 @@
1
+ ---
2
+ name: cratis-specifications-csharp
3
+ description: Write C# specifications with Cratis.Specifications using the Establish/Because/should_ pattern and the for_/when_/and_ folder hierarchy. Use when adding or restructuring C# specs in any Cratis repository, choosing between an isolated unit spec and an in-process scenario spec, or building reusable given/ contexts. Do not use for TypeScript or React specs, and do not use it to decide what a command, projection, or reactor should do.
4
+ license: MIT
5
+ ---
6
+
7
+ # Cratis C# specifications
8
+
9
+ Specifications are executable documentation. The folder tree reads like a table
10
+ of contents, and each file states one setup, one action, and one or more
11
+ focused assertions.
12
+
13
+ ## Verified product sources
14
+
15
+ This skill is verified against these exact public releases:
16
+
17
+ | Package | Version | Purpose |
18
+ | --- | --- | --- |
19
+ | `Cratis.Specifications` | `4.1.0` | `Specification` base, `Catch.Exception`, `ShouldXxx` assertions |
20
+ | `Cratis.Arc.Testing` | `22.10.4` | `CommandScenario<TCommand>` and `CommandResult` assertions |
21
+ | `Cratis.Chronicle.Testing` | `17.0.0` | `EventScenario`, `ReadModelScenario<T>`, `ReactorScenario<T>` |
22
+
23
+ Reverify against the owning product repository before claiming support for
24
+ another version. Never translate an assertion or helper name from memory.
25
+
26
+ ## Route near misses
27
+
28
+ - The specification is TypeScript, React, or a view model: use
29
+ `cratis-specifications-typescript` instead.
30
+ - The question is what a command, projection, reducer, or reactor *should do*:
31
+ resolve the behavior first; this skill only specifies already decided
32
+ behavior.
33
+ - The repository builds a Cratis library rather than an application: stay on the
34
+ plain `Specification` base in this file and do not reach for the scenario
35
+ family except to test the very engine that repository provides.
36
+
37
+ ## Step 1 — Choose the specification surface
38
+
39
+ | Situation | Surface |
40
+ | --- | --- |
41
+ | Isolated class, collaborators can be substituted | `Specification` + NSubstitute |
42
+ | Arc command pipeline (validators, `Provide()`, `Handle()`, appended events) | `CommandScenario<TCommand>` |
43
+ | Chronicle append semantics, constraints, concurrency | `EventScenario` |
44
+ | Projection or reducer state from a sequence of events | `ReadModelScenario<TReadModel>` |
45
+ | Reactor invocation and its side effects | `ReactorScenario<TReactor>` |
46
+ | Host, transport, or real-infrastructure boundary | Out-of-process Chronicle integration specification |
47
+ | Setup shared by many specifications | Reusable context under `given/` |
48
+
49
+ The plain `Specification` base is the universal foundation and the dominant mode
50
+ in library and framework code. The four scenario helpers are the default for
51
+ event-sourced *application* behavior; read
52
+ [application-scenarios.md](references/application-scenarios.md) before using
53
+ one. Out-of-process integration specifications are an advanced case reserved for
54
+ boundaries the scenario helpers cannot reach — see
55
+ [integration-specs.md](references/integration-specs.md).
56
+
57
+ Specification projects are named `<Source>.Specs` and run on xUnit.
58
+
59
+ ## Step 2 — Create the folder structure
60
+
61
+ ```
62
+ for_<ClassName>/
63
+ ├── given/
64
+ │ ├── all_dependencies.cs ← substitutes every collaborator
65
+ │ └── a_<system_under_test>.cs ← builds the SUT, inherits all_dependencies
66
+ ├── when_<behavior>/ ← a behavior with multiple outcomes
67
+ │ ├── and_<condition>.cs
68
+ │ └── with_<state>.cs
69
+ └── when_<simple_behavior>.cs ← a single outcome is a single file
70
+ ```
71
+
72
+ Paths read as English sentences: `for_AuthorService / when_registering /
73
+ and_name_already_exists`. Allowed outcome prefixes are `and_`, `with_`,
74
+ `without_`, `having_`, and `given_`.
75
+
76
+ **`when` belongs only in a `when_<behavior>` folder name.** A specification file,
77
+ class, or non-`when_` folder must never contain the word `when`. Two "whens" in
78
+ one path is always wrong — fold the context into the `when_` folder name and use
79
+ preposition files for the outcomes.
80
+
81
+ ```
82
+ # Wrong — two whens in the sentence path
83
+ when_appending_event_with_migrations/
84
+ └── with_a_registered_migration_when_appending_a_generation_1_event.cs
85
+
86
+ # Correct — context in the folder, outcomes are flat files
87
+ when_appending_event_with_registered_migration/
88
+ ├── and_event_is_generation_1.cs
89
+ └── and_event_is_generation_2.cs
90
+ ```
91
+
92
+ Add a sub-folder under `when_` only when that condition has its own multiple
93
+ outcomes.
94
+
95
+ ## Step 3 — Write the specification
96
+
97
+ ```csharp
98
+ // Copyright (c) Cratis. All rights reserved.
99
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
100
+
101
+ namespace <RootNamespace>.for_<ClassName>;
102
+
103
+ public class when_<behavior> : Specification
104
+ {
105
+ <CollaboratorType> _<collaborator>;
106
+ <ResultType> _result;
107
+
108
+ void Establish() => _<collaborator> = <setup>;
109
+
110
+ void Because() => _result = <the single action under test>;
111
+
112
+ [Fact] void should_<expected_outcome>() => _result.ShouldEqual(<expected>);
113
+ [Fact] void should_<other_expected_outcome>() => _result.ShouldNotBeEmpty();
114
+ }
115
+ ```
116
+
117
+ - `void Establish()` sets up the world. Each class in an inheritance chain has
118
+ its own and they run base-first. Never call `base.Establish()`.
119
+ - `void Because()` performs **one** action. It belongs only in a concrete
120
+ specification, never in a reusable context.
121
+ - `[Fact] void should_*()` carries one assertion. Use expression-body form and
122
+ leave no blank line between `should_` methods.
123
+ - Fields are `private` in a concrete specification and `protected` in a `given/`
124
+ context, named `_camelCase`.
125
+ - Any phase may be `async Task`. `void Destroy()` is the optional teardown.
126
+
127
+ `Cratis.Specifications` discovers `Establish`, `Because`, and `Destroy` by
128
+ convention — there are no attributes on them.
129
+
130
+ ## Step 4 — Extract a reusable context
131
+
132
+ When several specifications share setup, layer it under `given/`:
133
+ `all_dependencies` substitutes the collaborators, the next context builds the
134
+ system under test, and each specification adds only what is unique.
135
+
136
+ ```csharp
137
+ // for_<ClassName>/given/all_dependencies.cs
138
+ namespace <RootNamespace>.for_<ClassName>.given;
139
+
140
+ public class all_dependencies : Specification
141
+ {
142
+ protected <CollaboratorType> _<collaborator>;
143
+
144
+ void Establish() => _<collaborator> = Substitute.For<<CollaboratorType>>();
145
+ }
146
+ ```
147
+
148
+ ```csharp
149
+ // for_<ClassName>/given/a_<system_under_test>.cs
150
+ namespace <RootNamespace>.for_<ClassName>.given;
151
+
152
+ public class a_<system_under_test> : all_dependencies
153
+ {
154
+ protected <ClassName> _<sut>;
155
+
156
+ void Establish() => _<sut> = new(_<collaborator>);
157
+ }
158
+ ```
159
+
160
+ Name a context `a_` or `an_` so it reads as "given an observer, when handling".
161
+ Full substitution, assertion, and exception-catching patterns are in
162
+ [csharp-patterns.md](references/csharp-patterns.md).
163
+
164
+ ## Step 5 — Never wait on the clock
165
+
166
+ A specification never sleeps to let the system under test catch up.
167
+ `Thread.Sleep`, a bare `Task.Delay`, or a `SpinWait` before an assertion passes
168
+ because the machine happened to be fast enough and writes today's latency into
169
+ the suite. Await a signal instead:
170
+
171
+ | Waiting for | Await |
172
+ | --- | --- |
173
+ | Observers to catch up with an append | `appendResult.WaitForCompletion()` |
174
+ | Client artifacts registered with the kernel | `eventStore.WaitForRegistration()` |
175
+ | An observer's state or position | `WaitTillActive`, `WaitTillSubscribed`, `WaitTillReachesEventSequenceNumber`, `WaitForState` |
176
+ | Anything without a helper | A `SemaphoreSlim` or `TaskCompletionSource` released by the code that observes the event, awaited under a timeout |
177
+
178
+ A **deadline** is not a sleep: every helper takes a timeout, and a timeout turns
179
+ a hang into a named failure. Sleeping *between* re-checks is a sleep — a poll
180
+ loop is a completion signal that has not been built yet.
181
+
182
+ Three delays are not waits and stay allowed. Say which one it is in a comment: a
183
+ test double that is slow on purpose so the specification can observe it
184
+ mid-flight, an infrastructure readiness backoff between connect retries, and a
185
+ `Task.Delay(1)` or `Task.Yield()` that widens an interleaving window in a
186
+ concurrency specification.
187
+
188
+ ## Step 6 — Apply the C# conventions
189
+
190
+ - Common usings come from `GlobalUsings.Specs.cs` (`Xunit`, `NSubstitute`,
191
+ `Cratis.Specifications`). Do not duplicate them and do not add a using for the
192
+ namespace of the system under test.
193
+ - Order usings with non-aliased namespaces first, a blank line, then
194
+ `using <alias> = …` sorted by alias name. Alias a type whose short name
195
+ collides with a namespace segment, using a domain-meaningful alias rather than
196
+ a technical `Command`/`Event` suffix.
197
+ - Prefer a concept's own sentinel — `NotSet`, `Empty`, `New()` — over a raw
198
+ `string.Empty`, `Guid.Empty`, or `0` that implicitly converts. It states intent
199
+ and survives a sentinel change. Reserve raw primitives for genuinely
200
+ non-concept values.
201
+ - Every file carries the repository license header.
202
+
203
+ ## What not to specify
204
+
205
+ - Simple auto-properties and properties that return a constructor parameter.
206
+ - Simple delegation such as `public IEnumerable<Author> All => _list;`.
207
+ - Logging calls and trivial null checks.
208
+ - Anything a specification name starting with `when_getting_` or
209
+ `when_returning_` would describe — that is a getter, not a behavior.
210
+
211
+ Specify decisions, transformations, branching business rules, and coordination
212
+ between collaborators. That is where defects hide.
213
+
214
+ ## Verify
215
+
216
+ - Every specification file states one `Establish`, one `Because`, and one or
217
+ more `should_` facts.
218
+ - `Because()` appears only in concrete specifications, never in a `given/`
219
+ context.
220
+ - No path contains `when` outside a `when_<behavior>` folder name.
221
+ - Outcome files use an allowed preposition prefix.
222
+ - Field access modifiers and `_camelCase` naming match the surface.
223
+ - No `Thread.Sleep`, bare `Task.Delay`, or poll loop stands in for a signal; any
224
+ remaining delay carries a comment naming which allowed case it is.
225
+ - Assertions use the `ShouldXxx` extension methods and never assert on a
226
+ presentation message string.
227
+ - Nothing trivial or compiler-verified is specified.
228
+ - The file carries the repository license header.
229
+ - The specification project builds and its specifications pass against the
230
+ verified package versions.