@cratis/pi 7.125.2 → 7.126.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/corpus/agents/spec-writer.md +2 -2
- package/package/corpus/harnesses/copilot/instructions/application-profile.instructions.md +1 -1
- package/package/corpus/harnesses/copilot/instructions/fast-feedback.instructions.md +37 -0
- package/package/corpus/harnesses/copilot/instructions/specs.csharp.instructions.md +1 -1
- package/package/corpus/harnesses/copilot/instructions/specs.instructions.md +7 -1
- package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md +40 -3
- package/package/corpus/harnesses/copilot/instructions/vertical-slices.instructions.md +1 -1
- package/package/corpus/harnesses/pi/agents/spec-writer.md +2 -2
- package/package/corpus/rules/application-profile.md +1 -1
- package/package/corpus/rules/fast-feedback.md +36 -0
- package/package/corpus/rules/general.md +1 -0
- package/package/corpus/rules/specs.csharp.md +1 -1
- package/package/corpus/rules/specs.md +7 -1
- package/package/corpus/rules/specs.scenarios.csharp.md +40 -3
- package/package/corpus/rules/vertical-slices.md +1 -1
- package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +15 -10
- package/package/corpus/skills/cratis-arc-command/SKILL.md +7 -4
- package/package/corpus/skills/cratis-chronicle-event-specifications/SKILL.md +5 -0
- package/package/corpus/skills/cratis-chronicle-reactor/references/specifications.md +23 -2
- package/package/corpus/skills/cratis-chronicle-read-model-specifications/SKILL.md +6 -3
- package/package/corpus/skills/cratis-chronicle-reducer/SKILL.md +2 -2
- package/package/corpus/skills/cratis-code-review/SKILL.md +4 -2
- package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +2 -2
- package/package.json +1 -1
|
@@ -46,13 +46,13 @@ Select from these canonical rules in `.cratis/ai/rules/` only after applying the
|
|
|
46
46
|
|
|
47
47
|
## C# specs — start with the plain call
|
|
48
48
|
|
|
49
|
-
Specify pure `Handle()`, validator, reducer and reactor decisions with plain calls and supplied read-model state. Follow `.cratis/ai/rules/specs.scenarios.csharp.md`: scenarios
|
|
49
|
+
Specify pure `Handle()`, validator, reducer and reactor decisions with plain calls and supplied read-model state. Follow `.cratis/ai/rules/specs.scenarios.csharp.md`: add exactly the scenarios its per-artifact decision table obligates (pipeline and authorization, constraints, projection and reducer dispatch, reactor dispatch and routing) plus application-owned race-policy proof, not a platform guard matrix. Keep the single cheap assembly-level admission spec for the application's own decision-model shapes. Prefer in-process helpers over out-of-process Chronicle hosts for the applicable behaviors:
|
|
50
50
|
|
|
51
51
|
| Tool | Use for |
|
|
52
52
|
|---|---|
|
|
53
53
|
| `CommandScenario<TCommand>` | **State Change** — runs authorization + validators + `Provide()` + `Handle()` + appended events |
|
|
54
54
|
| `EventScenario` | constraint violations, raw append/sequencing semantics |
|
|
55
|
-
| `ReadModelScenario<TReadModel>` | **State View** — one class per read model, many read-only assertions on shared setup |
|
|
55
|
+
| `ReadModelScenario<TReadModel>` | **State View** — one class per read model by default (another only for a sequence that cannot share it), many read-only assertions on shared setup |
|
|
56
56
|
| `ReactorScenario<TReactor>` | **Automation / Translation** — reactor invocation + side effects |
|
|
57
57
|
|
|
58
58
|
Reserve out-of-process integration specs for host/transport/infra boundaries the scenario helpers can't exercise.
|
|
@@ -74,7 +74,7 @@ Tagged **[contract]** (framework-enforced) or **[convention]** (house default).
|
|
|
74
74
|
3. **Code level** — infrastructure (hosting, DI composition, identity wiring, CI, deployment), clients, Screenplay code attachments and handlers, adapters in the render's customization area, and scope Stage cannot render yet (the model remains the contract). Framework repositories and brownfield work that is not opted in also stay code-first. Only the entry-point session may propose a model, at most once per session and never for trivial, bug-fix, infrastructure, client, framework or brownfield-maintenance work; if declined, do not ask again. Confirm Module/Feature, slice name, slice type and domain requirements as before; for new behavior or unclear event vocabulary run **cratis-chronicle-event-modeling** first, and record the agreed outline (fields, events, rules, scenarios) where the team tracks work, if anywhere. If you write code first where a model exists, say the model now lags.
|
|
75
75
|
4. **Trivial changes** keep the proportional-delegation policy; Phases 1–3 below apply to code you write at either level.
|
|
76
76
|
- **Phase 1 — Backend.** Implement a coherent slice change. **Gate:** incremental Debug build of the affected project (regenerates proxies, compiles `#if DEBUG` spec code).
|
|
77
|
-
- **Phase 2 — Specs.** Mandatory for every slice type. Start with plain calls for pure command/validator/reducer decisions with supplied read-model state; add scenarios
|
|
77
|
+
- **Phase 2 — Specs.** Mandatory for every slice type. Start with plain calls for pure command/validator/reducer decisions with supplied read-model state; add the scenarios the decision table in [specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md) obligates, and follow it for shared setup and cost budgets. **Gate:** tests pass.
|
|
78
78
|
- **Phase 3 — Frontend.** Build from the generated proxies, register in the composition page, wire routing. **Gate:** lint, conditional test, build.
|
|
79
79
|
|
|
80
80
|
**Backend before frontend, always** — the frontend depends on proxies that exist only after a successful build. Generate proxies from the **Debug** build (house convention); verify Release without re-running the generator: `dotnet build -c Release -p:CratisProxiesOutputPath=`. Why that property, and what to do when nothing is generated: the **cratis-arc-command** skill's proxy-generation reference.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/for_*/**,**/when_*/**,**/*.csproj,**/Directory.Build.props,**/Directory.Build.targets,**/*.Specs/**,**/.github/workflows/**"
|
|
3
|
+
paths:
|
|
4
|
+
- "**/for_*/**"
|
|
5
|
+
- "**/when_*/**"
|
|
6
|
+
- "**/*.csproj"
|
|
7
|
+
- "**/Directory.Build.props"
|
|
8
|
+
- "**/Directory.Build.targets"
|
|
9
|
+
- "**/*.Specs/**"
|
|
10
|
+
- "**/.github/workflows/**"
|
|
11
|
+
---
|
|
12
|
+
<!-- cratis-ai: generated Copilot instructions. Do not edit; rules/ is the source. -->
|
|
13
|
+
|
|
14
|
+
# Fast feedback is a product requirement
|
|
15
|
+
|
|
16
|
+
Applies to every Cratis repository and every application built on Cratis. A build and a spec run are
|
|
17
|
+
measured in seconds. Each line is tagged **[contract]** (binding) or **[convention]** (the house
|
|
18
|
+
default) per [`general.md`](../../../rules/general.md).
|
|
19
|
+
|
|
20
|
+
- **[contract] Building and running specs is as fast as possible, locally and in CI.** A change
|
|
21
|
+
that makes a build or a spec run slower is a regression: measure it (cold full-suite wall time
|
|
22
|
+
plus a focused single-folder loop) and fix it in the same change.
|
|
23
|
+
- **[contract] Every spec is safe to run in parallel with every other spec.** No shared mutable
|
|
24
|
+
static state, no ordering assumptions, no fixed ports, paths or names that two specs could both
|
|
25
|
+
claim. Never serialize a suite or opt out of shared setup to hide an isolation bug.
|
|
26
|
+
- **[contract] No per-spec startup cost for convenience.** Setup is built once and shared
|
|
27
|
+
(shared lifecycle stays on). Slow setup in a framework harness is fixed in the framework and
|
|
28
|
+
reported upstream; do not work around it by merging unrelated assertions or skipping specs.
|
|
29
|
+
- **[contract] Analyzers and generators are cheap or they are not added.** A slow one is an
|
|
30
|
+
upstream issue; see [`framework.md`](./framework.instructions.md) for how analyzers are written for speed.
|
|
31
|
+
- **[convention] Specify with the cheapest proof that is real.** Call a pure decision directly;
|
|
32
|
+
use a scenario for what only the framework pipeline or engine proves. The per-artifact table is in
|
|
33
|
+
[`specs.scenarios.csharp.md`](./specs.scenarios.csharp.instructions.md); the universal base is in
|
|
34
|
+
[`specs.md`](./specs.instructions.md).
|
|
35
|
+
|
|
36
|
+
A cold full-suite run under a minute is the target for a repository's spec gate. It is a budget to
|
|
37
|
+
protect, not a reason to delete coverage.
|
|
@@ -12,7 +12,7 @@ Extends the base [specs.md](./specs.instructions.md) (folder structure, naming,
|
|
|
12
12
|
|
|
13
13
|
> **Which spec surface?**
|
|
14
14
|
> - **Framework / library code** (Chronicle kernel, Arc pipeline, source generators, Fundamentals): the `Specification` base + NSubstitute in this file is the dominant mode — unit-test the classes under test in isolation. Reach for a `*Scenario` helper (in [specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md)) **only** when testing the very engine your repo provides — Arc → `CommandScenario`, Chronicle → the event/projection/reactor scenarios. The **cratis-application-slice-specifications** skill is application-oriented, not for general framework specs.
|
|
15
|
-
> - **Event-sourced applications on Cratis** (commands, projections, reducers, reactors, constraints): use this base plus a direct call by default for pure `Handle()`, validator and reducer decisions with supplied read-model state; add scenarios
|
|
15
|
+
> - **Event-sourced applications on Cratis** (commands, projections, reducers, reactors, constraints): use this base plus a direct call by default for pure `Handle()`, validator and reducer decisions with supplied read-model state; add the scenarios its per-artifact decision table obligates — see **[specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md)** (`profile: application`) and the **cratis-application-slice-specifications** skill.
|
|
16
16
|
|
|
17
17
|
`Cratis.Specifications` keeps the approach of Machine.Specifications (MSpec): `Establish → Because → should_` maps to "Given → When → Then" and keeps each spec focused on *one setup, one action, one set of assertions*.
|
|
18
18
|
|
|
@@ -98,13 +98,19 @@ The goal is to cover *decisions and transformations* — code where bugs hide. S
|
|
|
98
98
|
|
|
99
99
|
**Avoid file names starting with:** `when_getting_*`, `when_returning_*` — if a spec name starts with "getting" or "returning", it's probably testing a simple getter, which is not worth specifying.
|
|
100
100
|
|
|
101
|
+
## Choosing the harness
|
|
102
|
+
|
|
103
|
+
Call the code. A function whose result depends only on its arguments is specified by calling it with those arguments and asserting the result: no host, container or harness. Use a harness (scenario, fixture, in-memory engine) for behavior that exists only in the framework: discovery, attribute- or convention-driven wiring, authorization, persistence-time rules and declarative mappings. Name each such spec after the wiring it proves, and do not repeat through the harness a decision a plain call already proves.
|
|
104
|
+
|
|
105
|
+
Substitute collaborators (a service, a clock, a sender) freely in a plain call. Never substitute the event store, a read-model store or the pipeline as proof of persistence, projection or pipeline behavior: either make the code a pure function of its inputs, or keep the harness. Harness coverage is an obligation per behavior the framework owns, not a quota. Fast, parallel-safe harnesses are required by [fast-feedback.md](./fast-feedback.instructions.md).
|
|
106
|
+
|
|
101
107
|
## Multiple Outcomes
|
|
102
108
|
|
|
103
109
|
Each distinct outcome deserves its own spec file. This keeps specs small, focused, and independently verifiable. When a spec fails, you immediately know *which* outcome broke — no debugging through a multi-assertion file.
|
|
104
110
|
|
|
105
111
|
- When a behavior has multiple outcomes, create a `when_<behavior>/` folder with separate files for each outcome.
|
|
106
112
|
- For simple behaviors with a single outcome, use a single file: `when_<behavior>`.
|
|
107
|
-
- Never write a single file that tests an entire class's unrelated behaviors. **C# declarative read-model mappings are the exception:** use one scenario class per read model, one shared event sequence with captured intermediate states, and many read-only facts for fields and lifecycle outcomes. See [specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md) for scenario selection and cost budgets.
|
|
113
|
+
- Never write a single file that tests an entire class's unrelated behaviors. **C# declarative read-model mappings are the exception:** use one scenario class per read model by default (another only for a sequence that cannot share it, such as a removal or another key), one shared event sequence with captured intermediate states, and many read-only facts for fields and lifecycle outcomes. See [specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md) for scenario selection and cost budgets.
|
|
108
114
|
|
|
109
115
|
## Reusable Context
|
|
110
116
|
|
package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md
CHANGED
|
@@ -15,7 +15,44 @@ Specs are **mandatory for every slice type**, including reactors.
|
|
|
15
15
|
|
|
16
16
|
## Start with the plain call; add a scenario where it adds proof
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Call pure `Handle()`, validators, reducers and reactor decisions directly with their inputs, including read-model state. Add a scenario only for the wiring the decision table below obligates (pipeline and authorization, constraints, projection and reducer dispatch, reactor dispatch and routing). Do not repeat every decision through the pipeline; name the extra behavior each scenario proves.
|
|
19
|
+
|
|
20
|
+
**Decision table — apply it per artifact, without judgment.** A plain call proves the decision; a scenario is a coverage obligation for the wiring only the framework proves. These are obligations, not quotas: add a scenario for each behavior listed, and none beyond it.
|
|
21
|
+
|
|
22
|
+
| Artifact | Plain call (`Specification`) | Scenario obligation |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Command with a pure `Handle(…)` | every outcome and rejection branch, with read-model state supplied | the pipeline path once (`CommandScenario`: discovery, DI, event source, appended events), plus a **negative proof for each wiring that can refuse before `Handle`**: authorization denies (`ShouldNotBeAuthorized`), validation rejects, `Provide()` short-circuits |
|
|
25
|
+
| `Handle` that needs `IEventStore`, `IReadModels` or services | none: do not substitute the store | keep the scenario; when touched, take the read model as a `Handle` parameter and use the row above |
|
|
26
|
+
| `CommandValidator`, `ConceptValidator`, `Provide()` logic | each rule, built with its state | the command's pipeline scenarios prove the wiring |
|
|
27
|
+
| Constraint (`[Unique]`, `IConstraint`) | none | an `EventScenario` per constraint: the violation and the nearest non-violation |
|
|
28
|
+
| Projection or read model | none | `ReadModelScenario` against the real engine, covering **each event, key, join, children and removal the projection declares**: one class per read model with one shared event sequence by default; add a class only for a sequence that cannot share it (a removal, another key) |
|
|
29
|
+
| Reducer | each transition: `Model.Reduce(event, current[, context])` | a `ReadModelScenario` for dispatch, filters and stream routing |
|
|
30
|
+
| Reactor | each handler decision and side effect, called directly with substitute collaborators | a `ReactorScenario`: dispatch, filtering, read-model parameters, routing of produced events to the right event source, `[OnceOnly]`/replay |
|
|
31
|
+
| Query | extracted filter, sort or composition logic | `QueryScenario` for arguments, authorization and paging; provider behavior is integration |
|
|
32
|
+
| Cross-slice flow | each link | out-of-process integration, outside the fast loop |
|
|
33
|
+
|
|
34
|
+
Name a scenario after the wiring it proves (`and_the_caller_is_not_a_maintainer`). A plain `Handle()` spec never takes a validator, authorization or constraint name: it did not run them. Merge scenario classes only with an assertion map that keeps every assertion. Never use a substitute for the event store or a read-model store as proof of persistence or projection. Cost and parallel-safety requirements are in [fast-feedback.md](./fast-feedback.instructions.md).
|
|
35
|
+
|
|
36
|
+
#### Negative authorization proof
|
|
37
|
+
|
|
38
|
+
```csharp
|
|
39
|
+
#if DEBUG
|
|
40
|
+
namespace MyApp.Authors.Registration.when_registering_an_author;
|
|
41
|
+
|
|
42
|
+
public class and_the_caller_is_not_a_maintainer : Specification
|
|
43
|
+
{
|
|
44
|
+
readonly CommandScenario<RegisterAuthor> _scenario = new(); // no identity registered
|
|
45
|
+
CommandResult _result;
|
|
46
|
+
|
|
47
|
+
async Task Because() => _result = await _scenario.Execute(new RegisterAuthor(AuthorId.New(), "Jane Austen"));
|
|
48
|
+
|
|
49
|
+
[Fact] void should_be_denied() => _result.ShouldNotBeAuthorized();
|
|
50
|
+
[Fact] async Task should_append_nothing() => await _scenario.ShouldNotHaveAppendedEvents();
|
|
51
|
+
}
|
|
52
|
+
#endif
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
An unauthenticated scenario against an `[Authorize]`/`[Roles]` command is denied before `Handle`; pair it with the happy-path scenario that registers an allowed identity in `_scenario.Services`.
|
|
19
56
|
|
|
20
57
|
**Application specs supply state, not decision-read infrastructure.** Call `Handle()` or the validator directly with supplied read-model state. Never call `UseDecisionReads`, configure protection modes, seed events solely for infrastructure, or re-prove the platform's concurrency, conflict-refusal or isolation matrix; Arc/Chronicle specs own it. The application's **only decision-read spec obligation** is one cheap assembly-level shape-admission spec: `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` (`Cratis.Arc.Chronicle.Testing.Commands`). It checks the application's own models/projections without executing commands or running Chronicle, not the platform guards. Never simulate competing appends either (`AppendConcurrently` or any hand-built interleaving): concurrency is a framework concern a developer should not think about in tests, and Arc and Chronicle specs prove it. A rule that must hold under races is expressed in production by taking the read model as a `Handle` parameter under `[ReadProtection(On)]`; the spec gives that read model's state and asserts the outcome. Report harness state gaps upstream; never weaken production guards for speed. `AppendConcurrently` is an advanced framework-verification API: use only for an explicit need. Direction, no release claim: Arc will obsolete `UseDecisionReads`.
|
|
21
58
|
|
|
@@ -27,7 +64,7 @@ Specs are **mandatory for every slice type**, including reactors.
|
|
|
27
64
|
| `CommandScenario<TCommand>` | the real Arc command pipeline — authorization + validators + `Provide()` + `Handle()` + appended events | pipeline wiring, authorization or constraints adds proof |
|
|
28
65
|
| `QueryScenario<TReadModel>` | query pipeline: argument conversion, authorization, validation, interceptors; `Observe` buffers emissions | query composition matters; real MongoDB observations still need infrastructure |
|
|
29
66
|
| `EventScenario` | Chronicle-level append semantics, no command pipeline | constraint violations, raw stream sequencing/concurrency |
|
|
30
|
-
| `ReadModelScenario<TReadModel>` | projection/reducer state from a sequence of events (auto-detects model-bound / fluent / reducer) | one class per read model, many read-only assertions on shared setup |
|
|
67
|
+
| `ReadModelScenario<TReadModel>` | projection/reducer state from a sequence of events (auto-detects model-bound / fluent / reducer) | one class per read model by default, many read-only assertions on shared setup (see the decision table for when to add another) |
|
|
31
68
|
| `ReactorScenario<TReactor>` | reactor handler invocation + side effects via mocked services | the reactor wiring and side effects (Automation / Translation slices) |
|
|
32
69
|
|
|
33
70
|
> **Out-of-process Chronicle integration specs are an advanced case** — reserve them for host wiring, real infrastructure, serialization/transport, or end-to-end boundaries the scenario helpers can't exercise. They are not the default vertical-slice test shape.
|
|
@@ -144,7 +181,7 @@ async Task Because() =>
|
|
|
144
181
|
|
|
145
182
|
### `ReadModelScenario<TReadModel>` — declarative projections and reducer wiring
|
|
146
183
|
|
|
147
|
-
The default for declarative projections; pure reducer decisions start with a direct call. Use **one scenario class per read model**, not one per property, mapping attribute or lifecycle event. Apply its event sequence once, capture intermediate states in `Because` where needed, then assert fields, children, counters and lifecycle outcomes in many read-only facts. Add a reducer scenario only for dispatch, filtering, metadata or read-model wiring. Use xUnit `Assert.*` on `_scenario.Instance`.
|
|
184
|
+
The default for declarative projections; pure reducer decisions start with a direct call. Use **one scenario class per read model**, not one per property, mapping attribute or lifecycle event; add another class only for an event sequence that cannot share the first (a removal, another key), as the decision table says. Apply its event sequence once, capture intermediate states in `Because` where needed, then assert fields, children, counters and lifecycle outcomes in many read-only facts. Add a reducer scenario only for dispatch, filtering, metadata or read-model wiring. Use xUnit `Assert.*` on `_scenario.Instance`.
|
|
148
185
|
|
|
149
186
|
```csharp
|
|
150
187
|
ReadModelScenario<Author> _scenario = null!;
|
|
@@ -21,7 +21,7 @@ This file is the reference for *what* goes in each part of a slice. Layout, slic
|
|
|
21
21
|
|
|
22
22
|
- .NET / C# (ASP.NET Core) — **Cratis Arc** for CQRS / model-bound commands and queries, **Cratis Chronicle** for event sourcing, MongoDB or EF Core for read models.
|
|
23
23
|
- React + TypeScript (Vite) — Cratis Components 4.x + Arc-generated proxies, MVVM. Vitest + Mocha/Chai/Sinon for frontend specs.
|
|
24
|
-
- xUnit + Cratis.Specifications + NSubstitute for C# specs (`Specification` plus a direct call for pure command/validator/reducer decisions with supplied state; add scenarios
|
|
24
|
+
- xUnit + Cratis.Specifications + NSubstitute for C# specs (`Specification` plus a direct call for pure command/validator/reducer decisions with supplied state; add the scenarios the per-artifact decision table in [specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md) obligates).
|
|
25
25
|
|
|
26
26
|
## Proxy generation — the build dependency
|
|
27
27
|
|
|
@@ -41,13 +41,13 @@ Select from these canonical rules in `.cratis/ai/rules/` only after applying the
|
|
|
41
41
|
|
|
42
42
|
## C# specs — start with the plain call
|
|
43
43
|
|
|
44
|
-
Specify pure `Handle()`, validator, reducer and reactor decisions with plain calls and supplied read-model state. Follow `.cratis/ai/rules/specs.scenarios.csharp.md`: scenarios
|
|
44
|
+
Specify pure `Handle()`, validator, reducer and reactor decisions with plain calls and supplied read-model state. Follow `.cratis/ai/rules/specs.scenarios.csharp.md`: add exactly the scenarios its per-artifact decision table obligates (pipeline and authorization, constraints, projection and reducer dispatch, reactor dispatch and routing) plus application-owned race-policy proof, not a platform guard matrix. Keep the single cheap assembly-level admission spec for the application's own decision-model shapes. Prefer in-process helpers over out-of-process Chronicle hosts for the applicable behaviors:
|
|
45
45
|
|
|
46
46
|
| Tool | Use for |
|
|
47
47
|
|---|---|
|
|
48
48
|
| `CommandScenario<TCommand>` | **State Change** — runs authorization + validators + `Provide()` + `Handle()` + appended events |
|
|
49
49
|
| `EventScenario` | constraint violations, raw append/sequencing semantics |
|
|
50
|
-
| `ReadModelScenario<TReadModel>` | **State View** — one class per read model, many read-only assertions on shared setup |
|
|
50
|
+
| `ReadModelScenario<TReadModel>` | **State View** — one class per read model by default (another only for a sequence that cannot share it), many read-only assertions on shared setup |
|
|
51
51
|
| `ReactorScenario<TReactor>` | **Automation / Translation** — reactor invocation + side effects |
|
|
52
52
|
|
|
53
53
|
Reserve out-of-process integration specs for host/transport/infra boundaries the scenario helpers can't exercise.
|
|
@@ -73,7 +73,7 @@ Tagged **[contract]** (framework-enforced) or **[convention]** (house default).
|
|
|
73
73
|
3. **Code level** — infrastructure (hosting, DI composition, identity wiring, CI, deployment), clients, Screenplay code attachments and handlers, adapters in the render's customization area, and scope Stage cannot render yet (the model remains the contract). Framework repositories and brownfield work that is not opted in also stay code-first. Only the entry-point session may propose a model, at most once per session and never for trivial, bug-fix, infrastructure, client, framework or brownfield-maintenance work; if declined, do not ask again. Confirm Module/Feature, slice name, slice type and domain requirements as before; for new behavior or unclear event vocabulary run **cratis-chronicle-event-modeling** first, and record the agreed outline (fields, events, rules, scenarios) where the team tracks work, if anywhere. If you write code first where a model exists, say the model now lags.
|
|
74
74
|
4. **Trivial changes** keep the proportional-delegation policy; Phases 1–3 below apply to code you write at either level.
|
|
75
75
|
- **Phase 1 — Backend.** Implement a coherent slice change. **Gate:** incremental Debug build of the affected project (regenerates proxies, compiles `#if DEBUG` spec code).
|
|
76
|
-
- **Phase 2 — Specs.** Mandatory for every slice type. Start with plain calls for pure command/validator/reducer decisions with supplied read-model state; add scenarios
|
|
76
|
+
- **Phase 2 — Specs.** Mandatory for every slice type. Start with plain calls for pure command/validator/reducer decisions with supplied read-model state; add the scenarios the decision table in [specs.scenarios.csharp.md](./specs.scenarios.csharp.md) obligates, and follow it for shared setup and cost budgets. **Gate:** tests pass.
|
|
77
77
|
- **Phase 3 — Frontend.** Build from the generated proxies, register in the composition page, wire routing. **Gate:** lint, conditional test, build.
|
|
78
78
|
|
|
79
79
|
**Backend before frontend, always** — the frontend depends on proxies that exist only after a successful build. Generate proxies from the **Debug** build (house convention); verify Release without re-running the generator: `dotnet build -c Release -p:CratisProxiesOutputPath=`. Why that property, and what to do when nothing is generated: the **cratis-arc-command** skill's proxy-generation reference.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/for_*/**,**/when_*/**,**/*.csproj,**/Directory.Build.props,**/Directory.Build.targets,**/*.Specs/**,**/.github/workflows/**"
|
|
3
|
+
paths:
|
|
4
|
+
- "**/for_*/**"
|
|
5
|
+
- "**/when_*/**"
|
|
6
|
+
- "**/*.csproj"
|
|
7
|
+
- "**/Directory.Build.props"
|
|
8
|
+
- "**/Directory.Build.targets"
|
|
9
|
+
- "**/*.Specs/**"
|
|
10
|
+
- "**/.github/workflows/**"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Fast feedback is a product requirement
|
|
14
|
+
|
|
15
|
+
Applies to every Cratis repository and every application built on Cratis. A build and a spec run are
|
|
16
|
+
measured in seconds. Each line is tagged **[contract]** (binding) or **[convention]** (the house
|
|
17
|
+
default) per [`general.md`](./general.md).
|
|
18
|
+
|
|
19
|
+
- **[contract] Building and running specs is as fast as possible, locally and in CI.** A change
|
|
20
|
+
that makes a build or a spec run slower is a regression: measure it (cold full-suite wall time
|
|
21
|
+
plus a focused single-folder loop) and fix it in the same change.
|
|
22
|
+
- **[contract] Every spec is safe to run in parallel with every other spec.** No shared mutable
|
|
23
|
+
static state, no ordering assumptions, no fixed ports, paths or names that two specs could both
|
|
24
|
+
claim. Never serialize a suite or opt out of shared setup to hide an isolation bug.
|
|
25
|
+
- **[contract] No per-spec startup cost for convenience.** Setup is built once and shared
|
|
26
|
+
(shared lifecycle stays on). Slow setup in a framework harness is fixed in the framework and
|
|
27
|
+
reported upstream; do not work around it by merging unrelated assertions or skipping specs.
|
|
28
|
+
- **[contract] Analyzers and generators are cheap or they are not added.** A slow one is an
|
|
29
|
+
upstream issue; see [`framework.md`](./framework.md) for how analyzers are written for speed.
|
|
30
|
+
- **[convention] Specify with the cheapest proof that is real.** Call a pure decision directly;
|
|
31
|
+
use a scenario for what only the framework pipeline or engine proves. The per-artifact table is in
|
|
32
|
+
[`specs.scenarios.csharp.md`](./specs.scenarios.csharp.md); the universal base is in
|
|
33
|
+
[`specs.md`](./specs.md).
|
|
34
|
+
|
|
35
|
+
A cold full-suite run under a minute is the target for a repository's spec gate. It is a budget to
|
|
36
|
+
protect, not a reason to delete coverage.
|
|
@@ -139,6 +139,7 @@ A claim is only as good as the signal behind it — a build result, a test run,
|
|
|
139
139
|
| React + Arc + Cratis Components + MVVM + dialogs | `react.md`, `components.md`, `dialogs.md` |
|
|
140
140
|
| Frontend engineering quality & testing | `frontend-quality.md`, `frontend-testing.md`, `storybook.md` |
|
|
141
141
|
| Spec patterns — universal `Specification` base (both profiles) | `specs.md`, `specs.csharp.md`, `specs.typescript.md` |
|
|
142
|
+
| Build and spec-run speed: parallel-safe specs, no per-spec startup, cheap analyzers | `fast-feedback.md` |
|
|
142
143
|
| Spec patterns — the four `*Scenario` helpers (application only) | `specs.scenarios.csharp.md` |
|
|
143
144
|
| Strongly-typed values (`ConceptAs<T>`, `EventSourceId<T>`) | `concepts.md` |
|
|
144
145
|
| Shared term definitions (event, projection, reducer, reactor, observer, DCB, …) | `glossary.md` |
|
|
@@ -11,7 +11,7 @@ Extends the base [specs.md](./specs.md) (folder structure, naming, philosophy) w
|
|
|
11
11
|
|
|
12
12
|
> **Which spec surface?**
|
|
13
13
|
> - **Framework / library code** (Chronicle kernel, Arc pipeline, source generators, Fundamentals): the `Specification` base + NSubstitute in this file is the dominant mode — unit-test the classes under test in isolation. Reach for a `*Scenario` helper (in [specs.scenarios.csharp.md](./specs.scenarios.csharp.md)) **only** when testing the very engine your repo provides — Arc → `CommandScenario`, Chronicle → the event/projection/reactor scenarios. The **cratis-application-slice-specifications** skill is application-oriented, not for general framework specs.
|
|
14
|
-
> - **Event-sourced applications on Cratis** (commands, projections, reducers, reactors, constraints): use this base plus a direct call by default for pure `Handle()`, validator and reducer decisions with supplied read-model state; add scenarios
|
|
14
|
+
> - **Event-sourced applications on Cratis** (commands, projections, reducers, reactors, constraints): use this base plus a direct call by default for pure `Handle()`, validator and reducer decisions with supplied read-model state; add the scenarios its per-artifact decision table obligates — see **[specs.scenarios.csharp.md](./specs.scenarios.csharp.md)** (`profile: application`) and the **cratis-application-slice-specifications** skill.
|
|
15
15
|
|
|
16
16
|
`Cratis.Specifications` keeps the approach of Machine.Specifications (MSpec): `Establish → Because → should_` maps to "Given → When → Then" and keeps each spec focused on *one setup, one action, one set of assertions*.
|
|
17
17
|
|
|
@@ -97,13 +97,19 @@ The goal is to cover *decisions and transformations* — code where bugs hide. S
|
|
|
97
97
|
|
|
98
98
|
**Avoid file names starting with:** `when_getting_*`, `when_returning_*` — if a spec name starts with "getting" or "returning", it's probably testing a simple getter, which is not worth specifying.
|
|
99
99
|
|
|
100
|
+
## Choosing the harness
|
|
101
|
+
|
|
102
|
+
Call the code. A function whose result depends only on its arguments is specified by calling it with those arguments and asserting the result: no host, container or harness. Use a harness (scenario, fixture, in-memory engine) for behavior that exists only in the framework: discovery, attribute- or convention-driven wiring, authorization, persistence-time rules and declarative mappings. Name each such spec after the wiring it proves, and do not repeat through the harness a decision a plain call already proves.
|
|
103
|
+
|
|
104
|
+
Substitute collaborators (a service, a clock, a sender) freely in a plain call. Never substitute the event store, a read-model store or the pipeline as proof of persistence, projection or pipeline behavior: either make the code a pure function of its inputs, or keep the harness. Harness coverage is an obligation per behavior the framework owns, not a quota. Fast, parallel-safe harnesses are required by [fast-feedback.md](./fast-feedback.md).
|
|
105
|
+
|
|
100
106
|
## Multiple Outcomes
|
|
101
107
|
|
|
102
108
|
Each distinct outcome deserves its own spec file. This keeps specs small, focused, and independently verifiable. When a spec fails, you immediately know *which* outcome broke — no debugging through a multi-assertion file.
|
|
103
109
|
|
|
104
110
|
- When a behavior has multiple outcomes, create a `when_<behavior>/` folder with separate files for each outcome.
|
|
105
111
|
- For simple behaviors with a single outcome, use a single file: `when_<behavior>`.
|
|
106
|
-
- Never write a single file that tests an entire class's unrelated behaviors. **C# declarative read-model mappings are the exception:** use one scenario class per read model, one shared event sequence with captured intermediate states, and many read-only facts for fields and lifecycle outcomes. See [specs.scenarios.csharp.md](./specs.scenarios.csharp.md) for scenario selection and cost budgets.
|
|
112
|
+
- Never write a single file that tests an entire class's unrelated behaviors. **C# declarative read-model mappings are the exception:** use one scenario class per read model by default (another only for a sequence that cannot share it, such as a removal or another key), one shared event sequence with captured intermediate states, and many read-only facts for fields and lifecycle outcomes. See [specs.scenarios.csharp.md](./specs.scenarios.csharp.md) for scenario selection and cost budgets.
|
|
107
113
|
|
|
108
114
|
## Reusable Context
|
|
109
115
|
|
|
@@ -14,7 +14,44 @@ Specs are **mandatory for every slice type**, including reactors.
|
|
|
14
14
|
|
|
15
15
|
## Start with the plain call; add a scenario where it adds proof
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Call pure `Handle()`, validators, reducers and reactor decisions directly with their inputs, including read-model state. Add a scenario only for the wiring the decision table below obligates (pipeline and authorization, constraints, projection and reducer dispatch, reactor dispatch and routing). Do not repeat every decision through the pipeline; name the extra behavior each scenario proves.
|
|
18
|
+
|
|
19
|
+
**Decision table — apply it per artifact, without judgment.** A plain call proves the decision; a scenario is a coverage obligation for the wiring only the framework proves. These are obligations, not quotas: add a scenario for each behavior listed, and none beyond it.
|
|
20
|
+
|
|
21
|
+
| Artifact | Plain call (`Specification`) | Scenario obligation |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Command with a pure `Handle(…)` | every outcome and rejection branch, with read-model state supplied | the pipeline path once (`CommandScenario`: discovery, DI, event source, appended events), plus a **negative proof for each wiring that can refuse before `Handle`**: authorization denies (`ShouldNotBeAuthorized`), validation rejects, `Provide()` short-circuits |
|
|
24
|
+
| `Handle` that needs `IEventStore`, `IReadModels` or services | none: do not substitute the store | keep the scenario; when touched, take the read model as a `Handle` parameter and use the row above |
|
|
25
|
+
| `CommandValidator`, `ConceptValidator`, `Provide()` logic | each rule, built with its state | the command's pipeline scenarios prove the wiring |
|
|
26
|
+
| Constraint (`[Unique]`, `IConstraint`) | none | an `EventScenario` per constraint: the violation and the nearest non-violation |
|
|
27
|
+
| Projection or read model | none | `ReadModelScenario` against the real engine, covering **each event, key, join, children and removal the projection declares**: one class per read model with one shared event sequence by default; add a class only for a sequence that cannot share it (a removal, another key) |
|
|
28
|
+
| Reducer | each transition: `Model.Reduce(event, current[, context])` | a `ReadModelScenario` for dispatch, filters and stream routing |
|
|
29
|
+
| Reactor | each handler decision and side effect, called directly with substitute collaborators | a `ReactorScenario`: dispatch, filtering, read-model parameters, routing of produced events to the right event source, `[OnceOnly]`/replay |
|
|
30
|
+
| Query | extracted filter, sort or composition logic | `QueryScenario` for arguments, authorization and paging; provider behavior is integration |
|
|
31
|
+
| Cross-slice flow | each link | out-of-process integration, outside the fast loop |
|
|
32
|
+
|
|
33
|
+
Name a scenario after the wiring it proves (`and_the_caller_is_not_a_maintainer`). A plain `Handle()` spec never takes a validator, authorization or constraint name: it did not run them. Merge scenario classes only with an assertion map that keeps every assertion. Never use a substitute for the event store or a read-model store as proof of persistence or projection. Cost and parallel-safety requirements are in [fast-feedback.md](./fast-feedback.md).
|
|
34
|
+
|
|
35
|
+
#### Negative authorization proof
|
|
36
|
+
|
|
37
|
+
```csharp
|
|
38
|
+
#if DEBUG
|
|
39
|
+
namespace MyApp.Authors.Registration.when_registering_an_author;
|
|
40
|
+
|
|
41
|
+
public class and_the_caller_is_not_a_maintainer : Specification
|
|
42
|
+
{
|
|
43
|
+
readonly CommandScenario<RegisterAuthor> _scenario = new(); // no identity registered
|
|
44
|
+
CommandResult _result;
|
|
45
|
+
|
|
46
|
+
async Task Because() => _result = await _scenario.Execute(new RegisterAuthor(AuthorId.New(), "Jane Austen"));
|
|
47
|
+
|
|
48
|
+
[Fact] void should_be_denied() => _result.ShouldNotBeAuthorized();
|
|
49
|
+
[Fact] async Task should_append_nothing() => await _scenario.ShouldNotHaveAppendedEvents();
|
|
50
|
+
}
|
|
51
|
+
#endif
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
An unauthenticated scenario against an `[Authorize]`/`[Roles]` command is denied before `Handle`; pair it with the happy-path scenario that registers an allowed identity in `_scenario.Services`.
|
|
18
55
|
|
|
19
56
|
**Application specs supply state, not decision-read infrastructure.** Call `Handle()` or the validator directly with supplied read-model state. Never call `UseDecisionReads`, configure protection modes, seed events solely for infrastructure, or re-prove the platform's concurrency, conflict-refusal or isolation matrix; Arc/Chronicle specs own it. The application's **only decision-read spec obligation** is one cheap assembly-level shape-admission spec: `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` (`Cratis.Arc.Chronicle.Testing.Commands`). It checks the application's own models/projections without executing commands or running Chronicle, not the platform guards. Never simulate competing appends either (`AppendConcurrently` or any hand-built interleaving): concurrency is a framework concern a developer should not think about in tests, and Arc and Chronicle specs prove it. A rule that must hold under races is expressed in production by taking the read model as a `Handle` parameter under `[ReadProtection(On)]`; the spec gives that read model's state and asserts the outcome. Report harness state gaps upstream; never weaken production guards for speed. `AppendConcurrently` is an advanced framework-verification API: use only for an explicit need. Direction, no release claim: Arc will obsolete `UseDecisionReads`.
|
|
20
57
|
|
|
@@ -26,7 +63,7 @@ Specs are **mandatory for every slice type**, including reactors.
|
|
|
26
63
|
| `CommandScenario<TCommand>` | the real Arc command pipeline — authorization + validators + `Provide()` + `Handle()` + appended events | pipeline wiring, authorization or constraints adds proof |
|
|
27
64
|
| `QueryScenario<TReadModel>` | query pipeline: argument conversion, authorization, validation, interceptors; `Observe` buffers emissions | query composition matters; real MongoDB observations still need infrastructure |
|
|
28
65
|
| `EventScenario` | Chronicle-level append semantics, no command pipeline | constraint violations, raw stream sequencing/concurrency |
|
|
29
|
-
| `ReadModelScenario<TReadModel>` | projection/reducer state from a sequence of events (auto-detects model-bound / fluent / reducer) | one class per read model, many read-only assertions on shared setup |
|
|
66
|
+
| `ReadModelScenario<TReadModel>` | projection/reducer state from a sequence of events (auto-detects model-bound / fluent / reducer) | one class per read model by default, many read-only assertions on shared setup (see the decision table for when to add another) |
|
|
30
67
|
| `ReactorScenario<TReactor>` | reactor handler invocation + side effects via mocked services | the reactor wiring and side effects (Automation / Translation slices) |
|
|
31
68
|
|
|
32
69
|
> **Out-of-process Chronicle integration specs are an advanced case** — reserve them for host wiring, real infrastructure, serialization/transport, or end-to-end boundaries the scenario helpers can't exercise. They are not the default vertical-slice test shape.
|
|
@@ -143,7 +180,7 @@ async Task Because() =>
|
|
|
143
180
|
|
|
144
181
|
### `ReadModelScenario<TReadModel>` — declarative projections and reducer wiring
|
|
145
182
|
|
|
146
|
-
The default for declarative projections; pure reducer decisions start with a direct call. Use **one scenario class per read model**, not one per property, mapping attribute or lifecycle event. Apply its event sequence once, capture intermediate states in `Because` where needed, then assert fields, children, counters and lifecycle outcomes in many read-only facts. Add a reducer scenario only for dispatch, filtering, metadata or read-model wiring. Use xUnit `Assert.*` on `_scenario.Instance`.
|
|
183
|
+
The default for declarative projections; pure reducer decisions start with a direct call. Use **one scenario class per read model**, not one per property, mapping attribute or lifecycle event; add another class only for an event sequence that cannot share the first (a removal, another key), as the decision table says. Apply its event sequence once, capture intermediate states in `Because` where needed, then assert fields, children, counters and lifecycle outcomes in many read-only facts. Add a reducer scenario only for dispatch, filtering, metadata or read-model wiring. Use xUnit `Assert.*` on `_scenario.Instance`.
|
|
147
184
|
|
|
148
185
|
```csharp
|
|
149
186
|
ReadModelScenario<Author> _scenario = null!;
|
|
@@ -20,7 +20,7 @@ This file is the reference for *what* goes in each part of a slice. Layout, slic
|
|
|
20
20
|
|
|
21
21
|
- .NET / C# (ASP.NET Core) — **Cratis Arc** for CQRS / model-bound commands and queries, **Cratis Chronicle** for event sourcing, MongoDB or EF Core for read models.
|
|
22
22
|
- React + TypeScript (Vite) — Cratis Components 4.x + Arc-generated proxies, MVVM. Vitest + Mocha/Chai/Sinon for frontend specs.
|
|
23
|
-
- xUnit + Cratis.Specifications + NSubstitute for C# specs (`Specification` plus a direct call for pure command/validator/reducer decisions with supplied state; add scenarios
|
|
23
|
+
- xUnit + Cratis.Specifications + NSubstitute for C# specs (`Specification` plus a direct call for pure command/validator/reducer decisions with supplied state; add the scenarios the per-artifact decision table in [specs.scenarios.csharp.md](./specs.scenarios.csharp.md) obligates).
|
|
24
24
|
|
|
25
25
|
## Proxy generation — the build dependency
|
|
26
26
|
|
|
@@ -11,9 +11,10 @@ metadata:
|
|
|
11
11
|
Start a pure decision with a plain method call: `Handle()`,
|
|
12
12
|
`Handle(providedValue)`, reducer logic or a reactor's decision needs no host,
|
|
13
13
|
services or harness, including direct validator calls with supplied read-model state.
|
|
14
|
-
Add scenarios
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Add exactly the scenarios the per-artifact decision table in the
|
|
15
|
+
[scenario selection and cost rule](../../rules/specs.scenarios.csharp.md) obligates
|
|
16
|
+
(pipeline and authorization, constraints, projection and reducer dispatch,
|
|
17
|
+
reactor dispatch and routing). The scenario family
|
|
17
18
|
exercises the real Arc and Chronicle code paths in-process — no HTTP, no fixture,
|
|
18
19
|
no server.
|
|
19
20
|
|
|
@@ -108,9 +109,12 @@ A behavior covering several subjects groups under
|
|
|
108
109
|
## Step 2 — Start with the decision; add the scenario by slice type
|
|
109
110
|
|
|
110
111
|
Specify a pure decision with the plain `Specification` base first. A direct
|
|
111
|
-
`Handle()` call does not run validation, authorization, `Provide()` or operations
|
|
112
|
-
|
|
113
|
-
and
|
|
112
|
+
`Handle()` call does not run validation, authorization, `Provide()` or operations,
|
|
113
|
+
so each wiring that can refuse before `Handle` needs its own negative scenario.
|
|
114
|
+
The per-artifact decision table and its coverage obligations are in the
|
|
115
|
+
[scenario rule](../../rules/specs.scenarios.csharp.md); apply it as written.
|
|
116
|
+
A `Handle` that takes `IEventStore`, `IReadModels` or services is not a plain
|
|
117
|
+
call: keep the scenario, and when touched take read models as parameters.
|
|
114
118
|
|
|
115
119
|
| Slice type | Scenario | Skill |
|
|
116
120
|
| --- | --- | --- |
|
|
@@ -135,11 +139,12 @@ exception diagnostics, synchronous handler throws and UTC context defaults.
|
|
|
135
139
|
|
|
136
140
|
One specification class for **each** of:
|
|
137
141
|
|
|
138
|
-
1. **The happy path** — a plain call returns the expected event
|
|
139
|
-
|
|
142
|
+
1. **The happy path** — a plain call returns the expected event, plus one
|
|
143
|
+
`CommandScenario` happy path for the pipeline, and a negative scenario for
|
|
144
|
+
each authorization policy and `Provide()` short-circuit.
|
|
140
145
|
2. **Each validation failure** — instantiate `CommandValidator` or
|
|
141
|
-
`ConceptValidator` with the required state and validate directly
|
|
142
|
-
|
|
146
|
+
`ConceptValidator` with the required state and validate directly; the
|
|
147
|
+
command's pipeline scenario proves the wiring.
|
|
143
148
|
3. **Each business-rule rejection** — one `and_` class per condition in
|
|
144
149
|
`Handle()` that inspects a read model.
|
|
145
150
|
4. **Each constraint violation** — one class per constraint, written with
|
|
@@ -274,10 +274,13 @@ Components guidance, not to this skill.
|
|
|
274
274
|
|
|
275
275
|
## Verify
|
|
276
276
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
Keep `Handle` a pure function of the command, its read-model parameters and
|
|
278
|
+
provided values: that makes it specifiable by a plain call. Start `Handle()` and
|
|
279
|
+
validator decisions with direct calls and supplied read-model state. Add the
|
|
280
|
+
pipeline scenario plus a negative proof per authorization policy, validator
|
|
281
|
+
rejection and `Provide()` short-circuit, and a constraint scenario; never
|
|
282
|
+
read-protection plumbing or simulated concurrency. Follow the
|
|
283
|
+
[decision table](../../rules/specs.scenarios.csharp.md).
|
|
281
284
|
|
|
282
285
|
- The command is a `record`, carries `[Command]`, and declares a public instance
|
|
283
286
|
`Handle`.
|
|
@@ -10,6 +10,11 @@ license: MIT
|
|
|
10
10
|
database, no network — an empty event log at sequence number zero, with the
|
|
11
11
|
application's constraints discovered and active.
|
|
12
12
|
|
|
13
|
+
Specify every constraint with an `EventScenario`: one class for the violation
|
|
14
|
+
and one for the nearest non-violation. A constraint is enforced at append, so no
|
|
15
|
+
plain call or substituted event store proves it. Pure decisions around it are
|
|
16
|
+
plain calls; see the [decision table](../../rules/specs.scenarios.csharp.md).
|
|
17
|
+
|
|
13
18
|
## Verified product sources
|
|
14
19
|
|
|
15
20
|
| Package | Purpose |
|
|
@@ -12,8 +12,29 @@
|
|
|
12
12
|
|
|
13
13
|
Start with `Specification` plus a direct call for pure reactor decisions,
|
|
14
14
|
passing the trigger and supplied values explicitly and asserting the returned
|
|
15
|
-
command or event. Add `ReactorScenario<TReactor>`
|
|
16
|
-
|
|
15
|
+
command or event. Add one `ReactorScenario<TReactor>` per reactor for what only
|
|
16
|
+
Chronicle proves: dispatch, filtering, read-model parameters and routing of
|
|
17
|
+
produced events to the right event source (the
|
|
18
|
+
[decision table](../../../rules/specs.scenarios.csharp.md)).
|
|
19
|
+
Substitute the reactor's collaborators in either style; never substitute the
|
|
20
|
+
event store as proof of routing.
|
|
21
|
+
|
|
22
|
+
```csharp
|
|
23
|
+
public class when_an_author_is_registered : Specification
|
|
24
|
+
{
|
|
25
|
+
readonly INotifier _notifier = Substitute.For<INotifier>();
|
|
26
|
+
readonly AuthorId _id = AuthorId.New();
|
|
27
|
+
|
|
28
|
+
async Task Because() =>
|
|
29
|
+
await new AuthorNotifications(_notifier).Registered(
|
|
30
|
+
new AuthorRegistered("Jane Austen"),
|
|
31
|
+
EventContext.EmptyWithEventSourceId(_id));
|
|
32
|
+
|
|
33
|
+
[Fact] void should_notify_about_the_author() => _notifier.Received(1).Notify("Jane Austen");
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
In reactor scenarios, drive
|
|
17
38
|
events with a service provider of substitutes and assert on the substitutes for
|
|
18
39
|
non-event side effects; for handlers that return events, assert the resulting
|
|
19
40
|
appends through the scenario's event store. Cover the replay path separately
|
|
@@ -16,9 +16,12 @@ default, with the event and current state supplied explicitly (for a self model,
|
|
|
16
16
|
`ReadModelScenario<TReadModel>` only where declarative projection behavior,
|
|
17
17
|
event dispatch, filtering, metadata or read-model wiring contribute proof.
|
|
18
18
|
Follow the [scenario selection and cost rule](../../rules/specs.scenarios.csharp.md):
|
|
19
|
-
use one scenario class per read model
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
use one scenario class per read model and per distinct event sequence the
|
|
20
|
+
projection declares (lifecycle, join, children with two keys, removal), with many
|
|
21
|
+
read-only assertions on one shared setup, and keep shared lifecycle enabled. Cover
|
|
22
|
+
each declared sequence; do not create a class per property or mapping attribute.
|
|
23
|
+
Never replace the engine with a substitute store as projection proof. Reducer
|
|
24
|
+
transitions are plain calls; the scenario proves dispatch and filters. Measure scenario cost with TRX durations
|
|
22
25
|
using [running-specs.md](../cratis-specifications-csharp/references/running-specs.md).
|
|
23
26
|
|
|
24
27
|
For a read-model scenario specification the **`Given` *is* the act**. There
|
|
@@ -271,8 +271,8 @@ Read [references/filtering.md](references/filtering.md) when restricting which e
|
|
|
271
271
|
|
|
272
272
|
Start with `Specification` plus a direct call for pure reducer decisions:
|
|
273
273
|
pass the event and current state explicitly and assert the returned next state.
|
|
274
|
-
Add `ReadModelScenario<TReadModel>`
|
|
275
|
-
metadata
|
|
274
|
+
Add one `ReadModelScenario<TReadModel>` per reducer for event dispatch, filtering,
|
|
275
|
+
metadata and read-model wiring (the [decision table](../../rules/specs.scenarios.csharp.md)); drive the event sequence and
|
|
276
276
|
assert on the resulting instance there. For the self example:
|
|
277
277
|
|
|
278
278
|
Read [references/specification-example.md](references/specification-example.md) when you need a worked direct-call and `ReadModelScenario` spec.
|
|
@@ -155,8 +155,10 @@ degradations a reviewer can see without measuring:
|
|
|
155
155
|
## Step 6 — Specification coverage
|
|
156
156
|
|
|
157
157
|
- Pure command, validator, reducer and reactor decisions have plain-call specs
|
|
158
|
-
with supplied read-model state; scenarios
|
|
159
|
-
|
|
158
|
+
with supplied read-model state; scenarios cover exactly the obligations in the
|
|
159
|
+
per-artifact decision table of `specs.scenarios.csharp.md` (pipeline and
|
|
160
|
+
authorization, constraints, projection and reducer dispatch, reactor dispatch
|
|
161
|
+
and routing) plus application-owned race-policy proof.
|
|
160
162
|
- Every State Change command has a happy-path specification.
|
|
161
163
|
- Every validation rule has a direct-call failure spec. Pipeline validation
|
|
162
164
|
specs assert **both** `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`.
|
|
@@ -89,8 +89,8 @@ before changing runner or package family.
|
|
|
89
89
|
|
|
90
90
|
The plain `Specification` base is the universal foundation and the dominant mode
|
|
91
91
|
in library and framework code. In applications, start pure decisions with a
|
|
92
|
-
plain call, including direct validator calls, and add scenarios
|
|
93
|
-
|
|
92
|
+
plain call, including direct validator calls, and add the scenarios the
|
|
93
|
+
per-artifact decision table in `specs.scenarios.csharp.md` obligates. Keep shared lifecycle on; measure scenario cost using
|
|
94
94
|
[running-specs.md](references/running-specs.md). Read
|
|
95
95
|
[application-scenarios.md](references/application-scenarios.md) before using
|
|
96
96
|
one. Out-of-process integration specifications are an advanced case reserved for
|