@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.
- package/README.md +43 -0
- package/package.json +24 -0
- package/skills/cratis-application-react-specifications/LICENSE +2 -0
- package/skills/cratis-application-react-specifications/SKILL.md +192 -0
- package/skills/cratis-application-slice-specifications/LICENSE +2 -0
- package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
- package/skills/cratis-arc-command/LICENSE +2 -0
- package/skills/cratis-arc-command/SKILL.md +286 -0
- package/skills/cratis-arc-command/references/command-result.md +111 -0
- package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
- package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
- package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
- package/skills/cratis-arc-command-execution/LICENSE +2 -0
- package/skills/cratis-arc-command-execution/SKILL.md +148 -0
- package/skills/cratis-arc-command-validation/LICENSE +2 -0
- package/skills/cratis-arc-command-validation/SKILL.md +230 -0
- package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
- package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
- package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
- package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
- package/skills/cratis-arc-query-paging/LICENSE +2 -0
- package/skills/cratis-arc-query-paging/SKILL.md +173 -0
- package/skills/cratis-arc-react-page/LICENSE +2 -0
- package/skills/cratis-arc-react-page/SKILL.md +291 -0
- package/skills/cratis-arc-react-page/references/data-page.md +125 -0
- package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
- package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
- package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
- package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
- package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
- package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
- package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
- package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
- package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
- package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
- package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
- package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
- package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
- package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
- package/skills/cratis-chronicle-compliance/LICENSE +2 -0
- package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
- package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
- package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
- package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
- package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
- package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
- package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
- package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
- package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
- package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
- package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
- package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
- package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
- package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
- package/skills/cratis-chronicle-projection/LICENSE +2 -0
- package/skills/cratis-chronicle-projection/SKILL.md +162 -0
- package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
- package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
- package/skills/cratis-chronicle-reactor/LICENSE +2 -0
- package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
- package/skills/cratis-chronicle-read-model/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
- package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
- package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
- package/skills/cratis-chronicle-reducer/LICENSE +2 -0
- package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
- package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
- package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
- package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
- package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
- package/skills/cratis-code-review/LICENSE +2 -0
- package/skills/cratis-code-review/SKILL.md +177 -0
- package/skills/cratis-components-accessibility/LICENSE +2 -0
- package/skills/cratis-components-accessibility/SKILL.md +176 -0
- package/skills/cratis-components-schema-editor/LICENSE +2 -0
- package/skills/cratis-components-schema-editor/SKILL.md +238 -0
- package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
- package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
- package/skills/cratis-components-styling/LICENSE +2 -0
- package/skills/cratis-components-styling/SKILL.md +221 -0
- package/skills/cratis-components-toolbar/LICENSE +2 -0
- package/skills/cratis-components-toolbar/SKILL.md +259 -0
- package/skills/cratis-documentation-writing/LICENSE +2 -0
- package/skills/cratis-documentation-writing/SKILL.md +121 -0
- package/skills/cratis-event-model-diagram/LICENSE +2 -0
- package/skills/cratis-event-model-diagram/SKILL.md +142 -0
- package/skills/cratis-fundamentals-concept/LICENSE +2 -0
- package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
- package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
- package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
- package/skills/cratis-governed-release-methodology/LICENSE +2 -0
- package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
- package/skills/cratis-lens-browser-extension/LICENSE +2 -0
- package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
- package/skills/cratis-performance-review/LICENSE +2 -0
- package/skills/cratis-performance-review/SKILL.md +139 -0
- package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
- package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
- package/skills/cratis-security-review/LICENSE +2 -0
- package/skills/cratis-security-review/SKILL.md +141 -0
- package/skills/cratis-specification-by-example/LICENSE +2 -0
- package/skills/cratis-specification-by-example/SKILL.md +188 -0
- package/skills/cratis-specifications-csharp/LICENSE +2 -0
- package/skills/cratis-specifications-csharp/SKILL.md +230 -0
- package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
- package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
- package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
- package/skills/cratis-specifications-typescript/LICENSE +2 -0
- package/skills/cratis-specifications-typescript/SKILL.md +202 -0
- package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
- package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
- package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
- package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
- package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
- 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,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,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.
|