@cratis/pi 7.123.0 → 7.125.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.
Files changed (42) hide show
  1. package/package/corpus/agents/spec-writer.md +6 -6
  2. package/package/corpus/harnesses/copilot/instructions/application-profile.instructions.md +1 -1
  3. package/package/corpus/harnesses/copilot/instructions/framework.instructions.md +1 -0
  4. package/package/corpus/harnesses/copilot/instructions/specs.csharp.instructions.md +2 -2
  5. package/package/corpus/harnesses/copilot/instructions/specs.instructions.md +1 -1
  6. package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md +16 -20
  7. package/package/corpus/harnesses/copilot/instructions/verification-discipline.instructions.md +1 -0
  8. package/package/corpus/harnesses/copilot/instructions/vertical-slices.instructions.md +5 -3
  9. package/package/corpus/harnesses/pi/agents/spec-writer.md +6 -6
  10. package/package/corpus/prompts/add-business-rule.prompt.md +2 -2
  11. package/package/corpus/rules/application-profile.md +1 -1
  12. package/package/corpus/rules/framework.md +1 -0
  13. package/package/corpus/rules/general.md +2 -1
  14. package/package/corpus/rules/specs.csharp.md +2 -2
  15. package/package/corpus/rules/specs.md +1 -1
  16. package/package/corpus/rules/specs.scenarios.csharp.md +16 -20
  17. package/package/corpus/rules/verification-discipline.md +1 -0
  18. package/package/corpus/rules/vertical-slices.md +5 -3
  19. package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +32 -21
  20. package/package/corpus/skills/cratis-application-slice-specifications/references/query-and-decision-scenarios.md +46 -44
  21. package/package/corpus/skills/cratis-arc-command/SKILL.md +5 -0
  22. package/package/corpus/skills/cratis-arc-command/references/decisions-and-routing.md +22 -14
  23. package/package/corpus/skills/cratis-arc-command/references/fresh-identifiers.md +13 -15
  24. package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +18 -12
  25. package/package/corpus/skills/cratis-arc-command-validation/references/protected-validator-construction.md +9 -5
  26. package/package/corpus/skills/cratis-chronicle-decision-reads/SKILL.md +12 -8
  27. package/package/corpus/skills/cratis-chronicle-decision-reads/references/admission-and-ownership.md +6 -2
  28. package/package/corpus/skills/cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md +23 -30
  29. package/package/corpus/skills/cratis-chronicle-event-specifications/SKILL.md +5 -4
  30. package/package/corpus/skills/cratis-chronicle-read-model-specifications/SKILL.md +14 -3
  31. package/package/corpus/skills/cratis-chronicle-read-model-specifications/references/view-lifecycle-checks.md +6 -3
  32. package/package/corpus/skills/cratis-ci-pipeline-performance/SKILL.md +8 -4
  33. package/package/corpus/skills/cratis-ci-pipeline-performance/references/test-setup-cost.md +15 -0
  34. package/package/corpus/skills/cratis-code-review/SKILL.md +10 -7
  35. package/package/corpus/skills/cratis-screenplay-command-surface/references/generated-values-and-responses.md +5 -1
  36. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/chronicle-verification.md +5 -3
  37. package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +19 -14
  38. package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +20 -19
  39. package/package/corpus/skills/cratis-specifications-csharp/references/running-specs.md +32 -1
  40. package/package/corpus/skills/cratis-specifications-csharp/references/shared-lifecycle.md +32 -13
  41. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +1 -1
  42. package/package.json +1 -1
@@ -10,8 +10,10 @@ metadata:
10
10
 
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
- services or harness. Add a scenario where the pipeline, validation,
14
- authorization, constraints or projections contribute proof. The scenario family
13
+ services or harness, including direct validator calls with supplied read-model state.
14
+ Add scenarios only for pipeline/authorization wiring, constraints, declarative
15
+ mapping; follow the
16
+ [scenario selection and cost rule](../../rules/specs.scenarios.csharp.md). The scenario family
15
17
  exercises the real Arc and Chronicle code paths in-process — no HTTP, no fixture,
16
18
  no server.
17
19
 
@@ -67,7 +69,8 @@ repository before claiming support for another version.
67
69
 
68
70
  When the slice has a contract — a `.play` slice with its `specification`
69
71
  blocks, or an agreed outline — its specifications are the **minimum list**: each
70
- one becomes a specification class here, named after it. Steps 3–7 then add what
72
+ gets concrete assertions. Decision outcomes use focused classes; declarative
73
+ read-model mappings share one class per model with many read-only facts. Steps 3–7 then add what
71
74
  the code reveals (each validator rule, each constraint). A rule found in the code
72
75
  with no contract specification is a proposal for the contract (the `.play` model when one covers the slice, otherwise the agreed outline), not silent coverage:
73
76
  list it in the report and, where a model exists, route it to
@@ -106,15 +109,16 @@ A behavior covering several subjects groups under
106
109
 
107
110
  Specify a pure decision with the plain `Specification` base first. A direct
108
111
  `Handle()` call does not run validation, authorization, `Provide()` or operations;
109
- keep a scenario for that wiring and for appended-event behavior.
112
+ add a scenario only where that wiring adds proof. Keep shared lifecycle enabled
113
+ and measure scenario cost as described in the linked rule.
110
114
 
111
115
  | Slice type | Scenario | Skill |
112
116
  | --- | --- | --- |
113
- | Pure command decision, reducer logic or reactor decision | Plain method call with `Specification` (default) | `cratis-specifications-csharp` |
117
+ | Pure command/validator decision, reducer logic or reactor decision | Plain method call with `Specification` and supplied read-model state (default) | `cratis-specifications-csharp` |
114
118
  | State Change — command pipeline and appended events | `CommandScenario<TCommand>` | this one |
115
119
  | Append semantics, a constraint | `EventScenario` | `cratis-chronicle-event-specifications` |
116
120
  | Query pipeline — arguments, authorization, validation, interceptors | `QueryScenario<TReadModel>` | [query and decision scenarios](references/query-and-decision-scenarios.md) |
117
- | State View — a projection or reducer | `ReadModelScenario<TReadModel>` | `cratis-chronicle-read-model-specifications` |
121
+ | State View — declarative mapping or reducer wiring | One `ReadModelScenario<TReadModel>` class per read model, many assertions on shared setup | `cratis-chronicle-read-model-specifications` |
118
122
  | Automation or Translation — a reactor | `ReactorScenario<TReactor>` | `cratis-specifications-csharp` |
119
123
  | A host, transport, or real-infrastructure boundary | Out-of-process integration | `cratis-specifications-csharp` |
120
124
 
@@ -131,9 +135,11 @@ exception diagnostics, synchronous handler throws and UTC context defaults.
131
135
 
132
136
  One specification class for **each** of:
133
137
 
134
- 1. **The happy path** — the command succeeds and the expected event is appended.
135
- 2. **Each validation failure** — one `and_` class per `CommandValidator` or
136
- `ConceptValidator` rule.
138
+ 1. **The happy path** — a plain call returns the expected event; add pipeline
139
+ coverage only for behavior that call cannot prove.
140
+ 2. **Each validation failure** — instantiate `CommandValidator` or
141
+ `ConceptValidator` with the required state and validate directly. Use a
142
+ scenario only to prove pipeline wiring.
137
143
  3. **Each business-rule rejection** — one `and_` class per condition in
138
144
  `Handle()` that inspects a read model.
139
145
  4. **Each constraint violation** — one class per constraint, written with
@@ -194,24 +200,27 @@ _scenario.Given.ForEventSource(_cartId).Events(new ItemAddedToCart(itemId));
194
200
  _scenario.Given.ForEventSource(_cartId).ReadModel(new CartSummary { ItemCount = 3 });
195
201
  ```
196
202
 
197
- Prefer seeding **events**: it exercises the projection that production relies on,
198
- so a specification cannot pass against a read-model shape the projection never
199
- produces. Pin an instance only when the events would be noise.
203
+ Supply read-model state for a business decision. Seed events only when the
204
+ history itself matters, such as projection mapping or append-time uniqueness;
205
+ do not replay a projection merely to feed a pure command decision.
200
206
 
201
207
  Both builder methods return `void`; there is nothing to await.
202
208
 
203
- That is **legacy** read-model mode: seeded events populate an in-memory model,
204
- not the append log. A command under read protection `On`, with a per-stream model or
205
- with `[ProtectedDecision]` selects the decision mode itself (no `UseDecisionReads()`
206
- call); actual events and decisions then share one in-process log. Pinned models
207
- cannot supply decision guards. Read
208
- [query and decision scenarios](references/query-and-decision-scenarios.md) when
209
- specifying query pipelines, routed conflicts, projection admission or completion.
209
+ Read protection is invisible to application spec authors. Never configure modes,
210
+ call `UseDecisionReads`, simulate competing appends (`AppendConcurrently`), seed
211
+ events solely for infrastructure, or re-prove the platform concurrency/guard
212
+ matrix: give state, assert the outcome. Keep one cheap assembly-level
213
+ `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` spec for the
214
+ application's own model/projection shapes: its only decision-read spec obligation.
215
+ If the harness cannot accept required state, report the upstream gap; never weaken
216
+ production guards for test speed. Read
217
+ [query and decision scenarios](references/query-and-decision-scenarios.md) for
218
+ query pipelines.
210
219
 
211
220
  Validator and `Provide()` dependencies are registered in `_scenario.Services`;
212
221
  the concrete validator itself is discovered automatically.
213
222
 
214
- ## Step 6 — Assert twice on every unhappy path
223
+ ## Step 6 — Assert twice on pipeline validation failures
215
224
 
216
225
  ```csharp
217
226
  [Fact] void should_not_succeed() => _result.ShouldNotBeSuccessful();
@@ -219,7 +228,9 @@ the concrete validator itself is discovered automatically.
219
228
  ```
220
229
 
221
230
  `ShouldNotBeSuccessful()` alone cannot distinguish a validation rejection from an
222
- unhandled exception, so both facts are required. **Do not assert on a message
231
+ unhandled exception, so both facts are required for pipeline validation specs.
232
+ For direct validator calls, assert the validation result and the specific rule.
233
+ **Do not assert on a message
223
234
  string by default** — it is presentation text; assert one only when that exact
224
235
  text is the specified behavior, and name that requirement in the fact. A `.play`
225
236
  specification may pin the message (`then error "<message>"`); prefer pinning the
@@ -3,7 +3,7 @@
3
3
  ## Contents
4
4
 
5
5
  - Query pipeline
6
- - Guarded decisions
6
+ - Decision specifications
7
7
  - Sources
8
8
 
9
9
  <!-- Copyright (c) Cratis. All rights reserved. -->
@@ -47,8 +47,10 @@ arbitrary stores.
47
47
  Requires Arc 22.87.0 and Chronicle 19.54.0 for scenario `IReadModels.Observe<T>(filter)` snapshots from given events or pinned models,
48
48
  including new emissions after scenario command appends. Pins need a readable key
49
49
  or throw `PinnedReadModelCannotBeObserved`.
50
- Requires Arc 22.91.1 and Chronicle 19.59.1 for default-mode children, joins and reducers to observe through scenario-owned storage (the initial Arc package was not published); older default mode cannot
51
- observe children, so use decision mode. Check both package versions.
50
+ Requires Arc 22.91.1 and Chronicle 19.59.1 for children, joins and reducers to
51
+ observe through scenario-owned storage (the initial Arc package was not published).
52
+ Check both package versions; upgrade or report a harness gap rather than configuring
53
+ decision-read modes in application specs.
52
54
 
53
55
  For a query with `by`, `filter` or `scoped to`, seed instances for two keys, two filter values
54
56
  or two tenants with distinct callers, as applicable, and assert that each call returns only its
@@ -65,50 +67,50 @@ execution runs the query through Arc's hosted query pipeline and establishes con
65
67
  before `Provide`/`Handle` on the command path. A direct static query call does not
66
68
  cover those pipeline hooks.
67
69
 
68
- ## Guarded decisions
69
-
70
- Create `new CommandScenario<TCommand>()`. It selects Chronicle's in-process decision
71
- mode from the command's read-protection mode and dependencies (for example under
72
- `On`, or with a per-stream read model); do not call `UseDecisionReads()`, which is
73
- hidden and unnecessary. This mode shares **one real in-process event log** among
74
- given events, guarded reads, constraints and returned appends. Seed actual events
75
- (`Given.ForEventSource(id).Events(...)`, or `.OnRoute(route).Events(...)`); protected
76
- reads fold the log and ignore a pinned `.ReadModel(m)`, which only pins an advisory
77
- (plain) read. Assert a lost race with
78
- `result.ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`
79
- after `scenario.AppendConcurrently(id, events...)`. There is no
80
- `scenario.AppendConditions`. An `Off` command with no protected dependency stays in
81
- legacy mode.
82
- `Given.ForEventSource(id).OnRoute(route).Events(...)` preserves routing;
83
- `scenario.EventRoutes.For<TSource>(stream).WithStreamId(...)` creates the route.
84
- Queue interference using `AppendConcurrently(id, route, event)` and assert a
85
- nonempty decision is rejected on the intended boundary. An empty identical-retry
86
- append deliberately returns no scope and cannot prove the conflict guard.
87
-
88
- Requires Arc 22.92.0 for `AppendConcurrently` without decision reads; older Arc
89
- needs decision mode. Its early interference does not make a plain read guarded;
90
- assert the actual scope/outcome, not merely that the competing append ran.
91
-
92
- `ShouldHaveCompletedStream(type, id)` proves successful completion;
93
- `ShouldHaveCompletedStream(ClosedStreamScope)` asserts the exact source and route.
94
- `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` checks projection
95
- admission in assembly-wide specs. These APIs are in
96
- `Cratis.Arc.Chronicle.Testing.Commands`. Routed seeding is refused in legacy mode.
97
-
98
- The old warnings that command `Given` never reaches the event log and that its
99
- read-model dictionary is the only backing are **legacy-mode** warnings, not a
100
- reason to fake protected decision readers. Keep non-key enumeration/provider
101
- boundaries explicit in either mode; decision mode is not a MongoDB substitute.
102
-
103
- The default Chronicle-backed `CommandScenario` discovers constraints
104
- automatically. Assert real constraint refusals instead of disabling them. Do not
105
- subclass or substitute Chronicle's `EventScenario`; use its public
106
- seeding/assertion surface or explicit `UseDecisionReads` mode, not a synthetic
107
- EventScenario facade.
70
+ ## Decision specifications
71
+
72
+ Start with a direct decision call supplied with read-model state. Follow the
73
+ [scenario rule](../../../rules/specs.scenarios.csharp.md): application authors do
74
+ not select read-protection modes, call `UseDecisionReads`, seed events only to
75
+ satisfy infrastructure, or prove the platform concurrency, conflict-refusal or
76
+ isolation matrix. Arc/Chronicle specs own it. The application's only decision-read
77
+ spec obligation is one cheap assembly-level check of its own model/projection
78
+ shapes, not one scenario per decision:
79
+
80
+ ```csharp
81
+ DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly);
82
+ ```
83
+
84
+ This API is in `Cratis.Arc.Chronicle.Testing.Commands`; it discovers declared reads
85
+ and checks admission without executing commands or running Chronicle. Keep the
86
+ spec's discovered projections representative of production; spec-only competing
87
+ definitions can make a model ambiguous. Explicit `IDecisionReads.Get` calls inside
88
+ method bodies are not statically discovered. If the harness cannot accept
89
+ required state for a business decision, report the upstream gap; never weaken
90
+ production guards or invent guard doubles.
91
+
92
+ Do not add a scenario to prove a race. A rule such as "two callers reserving the last
93
+ seat yield one reservation" is specified from state: supply a read model with no seats
94
+ left and assert the rejection, then supply one seat and assert the reservation.
95
+ Simulating competing appends is a framework concern a developer should not think
96
+ about in tests; Arc and Chronicle prove it. `AppendConcurrently` stays a public but
97
+ advanced framework-verification API (hidden from IntelliSense), outside application
98
+ testing guidance; use it only for a specific, explicit need. Production expresses
99
+ the guarantee with the read model as a `Handle` parameter under
100
+ `[ReadProtection(On)]`. Direction, not a release claim: Arc will obsolete
101
+ `UseDecisionReads`; do not adopt it.
102
+ `ShouldHaveCompletedStream(ClosedStreamScope)` may assert that a decision completed a
103
+ stream at the exact source and route (`Cratis.Arc.Chronicle.Testing.Commands`).
104
+ Use event history only when it is part of the domain example.
105
+
106
+ The Chronicle-backed `CommandScenario` discovers constraints automatically.
107
+ Assert domain constraint refusals instead of disabling them; use `EventScenario`
108
+ for append-time uniqueness examples. Do not subclass or substitute its engine.
109
+ Provider boundaries remain separate: state fixtures are not a MongoDB substitute.
108
110
 
109
111
  ## Sources
110
112
 
111
113
  - [Query scenarios](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/testing/query-scenario.md)
112
114
  - [Stream decisions and routed specs](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/chronicle/commands/stream-decisions.md)
113
115
  - [Decision-mode extension](https://github.com/Cratis/Arc/blob/main/Source/DotNET/Chronicle.Testing/Commands/CommandScenarioChronicleExtensions.cs)
114
- - [Admission assertion](https://github.com/Cratis/Arc/blob/main/Source/DotNET/Chronicle.Testing/Commands/DecisionReadAdmissions.cs)
116
+ - [Application-shape admission assertion](https://github.com/Cratis/Arc/blob/main/Source/DotNET/Chronicle.Testing/Commands/DecisionReadAdmissions.cs)
@@ -274,6 +274,11 @@ Components guidance, not to this skill.
274
274
 
275
275
  ## Verify
276
276
 
277
+ Start pure `Handle()` and validator decisions with direct calls and supplied
278
+ read-model state. Add scenarios only for pipeline/authorization wiring,
279
+ constraints, never read-protection plumbing or simulated concurrency;
280
+ follow the [scenario selection and cost rule](../../rules/specs.scenarios.csharp.md).
281
+
277
282
  - The command is a `record`, carries `[Command]`, and declares a public instance
278
283
  `Handle`.
279
284
  - The return shape matches what the command is supposed to do, and any
@@ -15,6 +15,13 @@ Source: Arc. These features require `Cratis.Arc.Chronicle`.
15
15
 
16
16
  ## Pick the guarded read
17
17
 
18
+ **Idiomatic default:** a state-dependent rule takes the read model as a `Handle()`
19
+ (or validator-constructor) parameter, with `[ReadProtection(On)]` where it must hold
20
+ under concurrent writers. Do not take `IDecisionReads` in `Handle()`: it is not
21
+ recommended with Arc and is not an investment target. A computed key stays in
22
+ `Provide()` until a read-model parameter covers it. Direction, not a release claim:
23
+ Arc will pin protected parameters so the idiomatic shape needs no further ceremony.
24
+
18
25
  Under read protection `On` (`<CratisReadProtection>On</CratisReadProtection>`, or
19
26
  `[ReadProtection(ReadProtectionMode.On)]` on a command), a read model that is a
20
27
  parameter of `Handle()` or of a `CommandValidator<T>` constructor is read fresh from
@@ -175,20 +182,21 @@ source id/type and stream route; reactor tuples/collections cannot mix it with e
175
182
 
176
183
  ## Decision specifications
177
184
 
178
- `new CommandScenario<T>()` selects the in-process decision log from the command's
179
- read-protection mode and dependencies (under `On`, with a per-stream model or
180
- `[ProtectedDecision]`); `UseDecisionReads()` is hidden and unnecessary. An `Off`
181
- command with no protected dependency stays in the legacy mode with pinned read
182
- models, which cannot prove a race. Seed with `Given.ForEventSource(id).Events(...)`,
183
- `.OnRoute(route).Events(...)`, queue a competing append with
184
- `AppendConcurrently(id, events...)` (or `AppendConcurrently(id, route, ...)`) and
185
- assert `result.ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`.
186
- There is no `scenario.AppendConditions`. Guarded state needs seeded events: protected reads ignore a pinned `.ReadModel(m)` (which only pins an advisory plain read) and `DecisionRead<T>` rejects it. Assert a
187
- real nonempty decision conflicts on the intended route, and that an independent
188
- route is not over-guarded. `ShouldHaveCompletedStream(type, id)` asserts completion;
189
- `ShouldHaveCompletedStream(ClosedStreamScope)` asserts the exact owner and route;
190
- `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` checks projection
191
- admission in assembly-level specs.
185
+ Call `Handle()` and validators directly with read-model state for pure business
186
+ decisions. Follow the [scenario rule](../../../rules/specs.scenarios.csharp.md):
187
+ read-protection plumbing is invisible to application spec authors. They never
188
+ call `UseDecisionReads`, configure modes, simulate competing appends
189
+ (`AppendConcurrently`), seed events solely for infrastructure, or re-prove the platform concurrency, conflict-refusal or isolation matrix;
190
+ Arc/Chronicle specs own it. The application's only decision-read spec obligation
191
+ is one cheap assembly-level shape-admission spec from that rule.
192
+ Never weaken production guards for test speed. Report a harness state-setup gap
193
+ upstream instead of working around it in application specs.
194
+
195
+ Add scenarios for pipeline/authorization wiring or constraints. Never simulate
196
+ competing appends: assert the business outcome from supplied state (for example, no
197
+ copies left rejects). Read
198
+ [query and decision scenarios](../../cratis-application-slice-specifications/references/query-and-decision-scenarios.md)
199
+ for the query and decision setup. Keep shared lifecycle on and measure the added cost.
192
200
 
193
201
  ## Sources
194
202
 
@@ -98,18 +98,16 @@ use an appropriate constraint or explicit concurrency scope for them.
98
98
 
99
99
  ## Specify the collision
100
100
 
101
- Use `new CommandScenario<TCommand>()`; it selects the in-process log from the
102
- command's read-protection mode and dependencies before seeding. If a seeded
103
- collision throws `GeneratedIdentifierCollisionRequiresDecisionReads`, the
104
- scenario stayed in legacy mode: call the compatibility switch `UseDecisionReads()`
105
- before seeding. Then `PinGeneratedValue(name, id)` and `Given.ForEventSource(id).Events(existingEvent)`.
106
- Assert the concurrency reason and collision detail, unchanged append tail and
107
- one handler execution. Also cover an empty source receiving two events through
108
- the chosen write API, an occupied source whose events belong to another stream,
109
- mixed returned/direct writes, and a nested rejection rolling back the outer unit.
110
-
111
- Requires Arc 22.95.1 for `GeneratedIdentifierCollisionRequiresDecisionReads`:
112
- default scenario mode fails fast when given events and a guarded pin share an id.
113
- Older default mode silently leaves the append log empty; it cannot prove occupancy.
114
- Run `dotnet build -c Debug` and the affected specification project with
115
- `dotnet test`; all collision and empty-source facts must pass.
101
+ The generated-value collision guard and its route, append and rollback matrix
102
+ belong in Arc/Chronicle specs, not every application. Follow the
103
+ [scenario rule](../../../rules/specs.scenarios.csharp.md): application authors
104
+ supply state and assert business decisions without configuring decision reads
105
+ or seeding events solely to satisfy infrastructure.
106
+
107
+ Add a scenario only for an application-owned collision/retry policy or generated
108
+ parameter pipeline wiring that a direct call cannot prove. `PinGeneratedValue(name, id)`
109
+ can make that domain example deterministic; assert its specified outcome, not
110
+ the platform guard matrix. If a harness rejects the state setup (for example
111
+ `GeneratedIdentifierCollisionRequiresDecisionReads`), report the upstream gap
112
+ rather than calling compatibility switches or weakening production guards.
113
+ Build the affected project and run its specifications with the project runner.
@@ -266,15 +266,19 @@ authorization and camelCase member paths are separate contracts.
266
266
 
267
267
  ## Step 6 — Specify the rejection
268
268
 
269
- Every rule gets a specification of its failure case, using
270
- `CommandScenario<TCommand>`. For guarded reads, `new CommandScenario<T>()`
271
- selects the in-process log from the command's mode and dependencies (do not call
272
- `UseDecisionReads()`). Seed real events with `Given.ForEventSource(id).Events(...)`
273
- (pinned read models cannot supply a guard), queue a competing
274
- `AppendConcurrently(id, events...)` and assert
275
- `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`.
276
- For the ordinary rejection, assert **both** that the command did not succeed
277
- and that it failed *as a validation*:
269
+ Every rule gets a failure specification: instantiate the validator with its
270
+ read-model state and validate directly, asserting the specific rejection. Add
271
+ `CommandScenario<TCommand>` only for pipeline/authorization wiring, constraints.
272
+ Follow the
273
+ [scenario rule](../../rules/specs.scenarios.csharp.md): application specs never
274
+ configure read-protection modes, call `UseDecisionReads`, simulate competing appends, seed events solely for
275
+ infrastructure, or re-prove platform guards. Keep the single cheap assembly-level
276
+ application-shape admission spec specified by that rule; it is the application's
277
+ only decision-read spec obligation. Report harness gaps upstream; never weaken
278
+ production guards for speed.
279
+
280
+ For a pipeline validation rejection, assert **both** that the command did not
281
+ succeed and that it failed *as a validation*:
278
282
 
279
283
  ```csharp
280
284
  [Fact] void should_not_succeed() => _result.ShouldNotBeSuccessful();
@@ -309,6 +313,8 @@ the fact. See
309
313
 
310
314
  `dotnet build` in Debug and in Release, zero warnings and zero errors — the
311
315
  analyzers above are how `ARC0005`, `ARC0006` and `ARC0013` surface. Then
312
- `dotnet test` with a specification per rule, each asserting both
313
- `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`. A rule with no
314
- red-first specification has not been shown to reject anything.
316
+ run the specification project using its runner, with a direct validation
317
+ specification per rule and focused pipeline specs where wiring matters. Pipeline
318
+ validation specs assert both `ShouldNotBeSuccessful()` and
319
+ `ShouldHaveValidationErrors()`. A rule with no red-first specification has not
320
+ been shown to reject anything.
@@ -87,8 +87,12 @@ NativeAOT support for the unrelated dynamic validator opt-in.
87
87
 
88
88
  ## Specify the boundary
89
89
 
90
- Cover a fresh allowed/denied validator, callback-added denial and exception,
91
- refused DI graphs and cleanup failure, and repeated validation/execution without
92
- reusing an instance. For keys, cover inherited/flattened members, exact derived
93
- types, missing metadata under strict settings and generated versus compatibility
94
- matching. An advisory Validate success is not proof that Execute commits.
90
+ In application specs, call the validator directly with supplied read-model state
91
+ and assert its business decision. Keep the one cheap assembly-level application-shape
92
+ admission spec in [specs.scenarios.csharp.md](../../../rules/specs.scenarios.csharp.md).
93
+
94
+ Arc/Chronicle specs own the construction/guard matrix: fresh instances, callback
95
+ failures, refused DI graphs, cleanup, key matching and repeated execution. Do not
96
+ re-prove that matrix per application validator. A focused application pipeline
97
+ spec may prove its own registration/wiring; advisory Validate success alone is
98
+ not proof that Execute commits.
@@ -188,12 +188,16 @@ deciding writer is guarded or the rule is a constraint.
188
188
 
189
189
  ## Verify
190
190
 
191
- - The model is admitted and the read is protected and enrolled in the intended
192
- store, namespace and stream before staging events.
193
- - Refusal is surfaced, never replaced by a materialized/unprotected read.
194
- - One owner completes or rolls back; nested code does neither behind it.
195
- - Competing relevant events cause rejection; unrelated events outside the guard
196
- do not. Absence, empty checked append and stream isolation have specifications.
197
- - Scope conflicts, lifecycle violations and failed commit remain visible.
198
- - Real-client/kernel integration proves the concurrency boundary; a default
191
+ - Review production enrollment in the intended store, namespace and stream;
192
+ surface refusal and never replace it with an unprotected read. One owner completes.
193
+ - Application specs call `Handle()` or the validator directly with supplied state.
194
+ Their only decision-read spec obligation is one cheap assembly-level
195
+ `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` check of the
196
+ application's own model/projection shapes; see the
197
+ [scenario rule](../../rules/specs.scenarios.csharp.md).
198
+ - Arc/Chronicle specs alone prove platform conflict refusal, relevant/irrelevant
199
+ changes, absence guards, empty checked appends, isolation and commit lifecycle.
200
+ Their real-client/kernel integration proves the boundary; a default
199
201
  `EventScenario` scope strategy returning `None` is not concurrency evidence.
202
+ - Application specs never simulate competing appends or repeat that matrix.
203
+ Never weaken production guards for spec speed.
@@ -28,8 +28,12 @@ an adapter for arbitrary read-model storage:
28
28
  Whole-source key admission supports string/Guid schemas, with canonical lowercase
29
29
  D-form Guid source ids assumed. A renamed or custom-converted key is not made
30
30
  safe by a cast in the caller. Runtime definition comparison is a diagnostic, not
31
- proof of equality with an executing projection. Keep integration specifications
32
- for the actual kernel pair and model shape.
31
+ proof of equality with an executing projection. Arc/Chronicle own kernel-pair
32
+ integration and the platform guard matrix. Application specs have just one cheap
33
+ assembly-level shape-admission check,
34
+ `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)`; see the
35
+ [scenario rule](../../../rules/specs.scenarios.csharp.md). It checks the
36
+ application's own discovered models/projections without running Chronicle.
33
37
 
34
38
  Do not silently replace a refused decision read with a materialized sink read.
35
39
  Redesign the decision model as a focused admitted projection only when the
@@ -12,7 +12,7 @@
12
12
  - Arc traps
13
13
  - Chronicle-only traps
14
14
  - Roll out with Measure
15
- - Test the race
15
+ - Specify the business decision
16
16
  - Costs
17
17
  - Checklist for generated code
18
18
 
@@ -82,7 +82,7 @@ published scenario catalogue.
82
82
  of the same source in one command are refused.
83
83
  11. **Moving an existing app to `On`** (S12): `Measure` first.
84
84
  12. **A command failed under protection** (S13): the [refusals](#refusals).
85
- 13. **Prove it** (S14): [test the race](#test-the-race).
85
+ 13. **Prove it** (S14): [specify the business decision](#specify-the-business-decision).
86
86
 
87
87
  ## The matrix
88
88
 
@@ -256,7 +256,7 @@ return `Commit()` last), `ReadOfSourceWithStagedEvents`, `ExplicitScopeDoesNotCo
256
256
  | Injected `IEventLog.Append` in `Handle` | immediate write that survives a conflict rollback | return events |
257
257
  | Aggregate `Commit()` whose result is ignored, or `Failed()` without `Commit()` | staged events still commit | return `await aggregate.Commit()` |
258
258
  | `[Unprotected]` command taking `DecisionRead<T>` | resolves to an unguarded read | remove `[Unprotected]` |
259
- | Tests without `AppendConcurrently`, or a scenario left in legacy mode | the race is never exercised | [test the race](#test-the-race) |
259
+ | Per-decision specs rebuild the platform guard matrix | repeated infrastructure proof, not business coverage | [specify the business decision](#specify-the-business-decision) |
260
260
 
261
261
  ## Chronicle-only traps
262
262
 
@@ -304,31 +304,23 @@ then set `On` per project. Behaviour for callers does not change under `Measure`
304
304
  or annotate `[Unprotected("why")]`. Under `On`, `ARCCHR0011` no longer applies;
305
305
  fix `ARCCHR0024`, `0028` and `0033`.
306
306
 
307
- ## Test the race
308
-
309
- `new CommandScenario<T>()` selects the in-process decision log from the command's
310
- read-protection mode and dependencies (for example under `On`); do not call
311
- `UseDecisionReads()` (it is hidden and unnecessary). An `Off` command with no
312
- protected dependency stays in legacy mode with pinned read models, which cannot
313
- prove a race. Seed with
314
- `scenario.Given.ForEventSource(id).Events(...)` (for a routed stream
315
- `.OnRoute(route).Events(...)`). Guarded state needs events: protected reads fold the
316
- event log and ignore a pinned model, and `DecisionRead<T>` parameters reject one.
317
- `.ReadModel(m)` only pins an advisory (plain) read. Then queue a competing event with
318
- `scenario.AppendConcurrently(id, events...)` and execute:
319
-
320
- ```csharp
321
- await using var scenario = new CommandScenario<BorrowBook>();
322
- scenario.Given.ForEventSource(isbn).Events(new BookAdded(Copies: 1));
323
- scenario.AppendConcurrently(isbn, new BookBorrowed(other));
324
- var result = await scenario.Execute(new BorrowBook(isbn, me));
325
- result.ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation);
326
- ```
327
-
328
- There is no `scenario.AppendConditions`. A pure decision test builds the command
329
- and the validator with a hand-made read model. Chronicle-direct code asserts
330
- `unitOfWork.GetAppendConditions()`. A default `EventScenario` scope strategy
331
- returning `None` is not concurrency evidence.
307
+ ## Specify the business decision
308
+
309
+ Start with a plain `Handle()` or validator call supplied with read-model state.
310
+ Follow the [scenario rule](../../../rules/specs.scenarios.csharp.md): read-protection
311
+ plumbing is invisible to application spec authors. They never configure modes,
312
+ call `UseDecisionReads`, seed events solely for infrastructure or re-prove platform
313
+ concurrency, conflict-refusal or isolation guards. Arc/Chronicle specs own that
314
+ matrix. The application's only decision-read spec obligation is one cheap
315
+ assembly-level `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)`
316
+ check of its own models/projections, not per-decision scenarios. Production
317
+ protection stays unchanged regardless of spec cost.
318
+
319
+ Do not simulate competing appends in an application spec, even for a rule such as
320
+ two borrowers competing for the last copy. Give the state (no copies left) and assert
321
+ the domain outcome (rejected); the guarantee that only one wins is proven by Arc and
322
+ Chronicle. If the harness cannot accept required state, report the upstream gap
323
+ rather than adding infrastructure setup to application specs.
332
324
 
333
325
  ## Costs
334
326
 
@@ -349,7 +341,8 @@ append. Decision reads exist in the .NET and Elixir clients.
349
341
  an explicit scope, enrolled **before** the events are staged.
350
342
  - The model is admitted (source-keyed projection or admitted reducer, `Id`).
351
343
  - Cross-key rules append one event per source atomically, with distinct event types.
352
- - A specification interleaves a competing append and asserts
353
- `ConcurrencyViolation`.
344
+ - Business decisions have direct `Handle()`/validator specs with supplied state;
345
+ one cheap assembly-level spec checks application-shape admission. Arc/Chronicle,
346
+ not application specs, interleave appends to prove the platform guard matrix.
354
347
  - What stays unguarded (an unguarded writer's own decision, external state,
355
348
  revisions, `[Unprotected]` reads, `Off` mode) is stated.
@@ -52,10 +52,11 @@ version. Never translate an assertion name from memory.
52
52
  (`When`, or `EventLog.Append` without a `concurrencyScope`) can never report
53
53
  `HasConcurrencyViolations`. Only an append that passes an explicit scope on
54
54
  `scenario.EventLog.Append(...)` reaches the in-process validator at all — and
55
- that is a spec of the scope you hand-built, not of the application's strategy. Specify concurrency against
56
- the real kernel with an out-of-process integration specification, and do not
57
- write a scenario specification that looks like it covers it — it would pass
58
- vacuously, which is worse than not having one.
55
+ that is a spec of the scope you hand-built, not of the application's strategy. The platform concurrency/guard matrix belongs only in Arc/Chronicle specs,
56
+ with real-kernel integration where required. Application decisions use direct
57
+ `Handle()`/validator calls with supplied state plus the one cheap assembly-level
58
+ shape-admission spec in [specs.scenarios.csharp.md](../../rules/specs.scenarios.csharp.md).
59
+ A default scenario must not claim concurrency coverage — it would pass vacuously.
59
60
 
60
61
  ## Step 1 — Create the scenario
61
62
 
@@ -15,6 +15,11 @@ default, with the event and current state supplied explicitly (for a self model,
15
15
  `Model.Reduce(@event, current)`). Add
16
16
  `ReadModelScenario<TReadModel>` only where declarative projection behavior,
17
17
  event dispatch, filtering, metadata or read-model wiring contribute proof.
18
+ Follow the [scenario selection and cost rule](../../rules/specs.scenarios.csharp.md):
19
+ use one scenario class per read model, many read-only assertions on one shared
20
+ setup, and keep shared lifecycle enabled. Do not create a class per property,
21
+ mapping attribute or lifecycle event. Measure scenario cost with TRX durations
22
+ using [running-specs.md](../cratis-specifications-csharp/references/running-specs.md).
18
23
 
19
24
  For a read-model scenario specification the **`Given` *is* the act**. There
20
25
  is no separate `When`: seeding the events is what makes the projection run.
@@ -69,7 +74,8 @@ version.
69
74
 
70
75
  ## Step 1 — Create the scenario
71
76
 
72
- A new scenario per specification, never shared state.
77
+ Create one scenario for the read model's specification class. Share setup among
78
+ its read-only facts, never across specification classes.
73
79
 
74
80
  ```csharp
75
81
  // Default, empty initial state
@@ -214,7 +220,11 @@ public class and_two_items_are_priced : Specification
214
220
 
215
221
  Wrap files in `#if DEBUG … #endif` only when they compile into the application
216
222
  assembly, so specification code ships only in Debug. Dedicated spec projects
217
- need no wrapper. Keep one outcome per `should_` fact.
223
+ need no wrapper. Keep one outcome per `should_` fact, not one scenario class per
224
+ assertion. Apply the event sequence once; capture intermediate states in `Because`
225
+ for create/update/delete assertions so facts remain read-only. Capture values or
226
+ immutable copies if later events mutate the same instance. Multiple event-source
227
+ keys may share this setup for isolation and child-key assertions.
218
228
 
219
229
  ## Step 6 — Cover the read model, not just one event
220
230
 
@@ -224,7 +234,8 @@ One summation example proves one event. Take the inventory from the contract
224
234
  (the `.play` read model and its specifications, or the agreed outline); without
225
235
  one, derive it from the projection and say so. Then check each line:
226
236
 
227
- - **Every property is asserted** in at least one specification.
237
+ - **Every property is asserted** in a read-only fact in the model's shared
238
+ scenario class; do not multiply scenario startup for each field.
228
239
  - **Every subscribed event has a specification.** Reconcile three lists in both
229
240
  directions: the contract's event types, the projection's actual subscriptions,
230
241
  and the events your specifications seed. Report any event in one list missing