@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.
- package/package/corpus/agents/spec-writer.md +6 -6
- package/package/corpus/harnesses/copilot/instructions/application-profile.instructions.md +1 -1
- package/package/corpus/harnesses/copilot/instructions/framework.instructions.md +1 -0
- package/package/corpus/harnesses/copilot/instructions/specs.csharp.instructions.md +2 -2
- package/package/corpus/harnesses/copilot/instructions/specs.instructions.md +1 -1
- package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md +16 -20
- package/package/corpus/harnesses/copilot/instructions/verification-discipline.instructions.md +1 -0
- package/package/corpus/harnesses/copilot/instructions/vertical-slices.instructions.md +5 -3
- package/package/corpus/harnesses/pi/agents/spec-writer.md +6 -6
- package/package/corpus/prompts/add-business-rule.prompt.md +2 -2
- package/package/corpus/rules/application-profile.md +1 -1
- package/package/corpus/rules/framework.md +1 -0
- package/package/corpus/rules/general.md +2 -1
- package/package/corpus/rules/specs.csharp.md +2 -2
- package/package/corpus/rules/specs.md +1 -1
- package/package/corpus/rules/specs.scenarios.csharp.md +16 -20
- package/package/corpus/rules/verification-discipline.md +1 -0
- package/package/corpus/rules/vertical-slices.md +5 -3
- package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +32 -21
- package/package/corpus/skills/cratis-application-slice-specifications/references/query-and-decision-scenarios.md +46 -44
- package/package/corpus/skills/cratis-arc-command/SKILL.md +5 -0
- package/package/corpus/skills/cratis-arc-command/references/decisions-and-routing.md +22 -14
- package/package/corpus/skills/cratis-arc-command/references/fresh-identifiers.md +13 -15
- package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +18 -12
- package/package/corpus/skills/cratis-arc-command-validation/references/protected-validator-construction.md +9 -5
- package/package/corpus/skills/cratis-chronicle-decision-reads/SKILL.md +12 -8
- package/package/corpus/skills/cratis-chronicle-decision-reads/references/admission-and-ownership.md +6 -2
- package/package/corpus/skills/cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md +23 -30
- package/package/corpus/skills/cratis-chronicle-event-specifications/SKILL.md +5 -4
- package/package/corpus/skills/cratis-chronicle-read-model-specifications/SKILL.md +14 -3
- package/package/corpus/skills/cratis-chronicle-read-model-specifications/references/view-lifecycle-checks.md +6 -3
- package/package/corpus/skills/cratis-ci-pipeline-performance/SKILL.md +8 -4
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/test-setup-cost.md +15 -0
- package/package/corpus/skills/cratis-code-review/SKILL.md +10 -7
- package/package/corpus/skills/cratis-screenplay-command-surface/references/generated-values-and-responses.md +5 -1
- package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/chronicle-verification.md +5 -3
- package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +19 -14
- package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +20 -19
- package/package/corpus/skills/cratis-specifications-csharp/references/running-specs.md +32 -1
- package/package/corpus/skills/cratis-specifications-csharp/references/shared-lifecycle.md +32 -13
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +1 -1
- package/package.json +1 -1
|
@@ -16,9 +16,12 @@ Martin Dilger and Nebulit GmbH's build-state-view (see `provenance.md`), with th
|
|
|
16
16
|
migrations and projection specification harness replaced by `ReadModelScenario<TReadModel>`.
|
|
17
17
|
|
|
18
18
|
Take the contract first (the `.play` read model, its projection and its specifications, or the
|
|
19
|
-
agreed outline).
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
agreed outline). Give each contract specification assertions using its example
|
|
20
|
+
values in **one shared scenario class per read model**, not a class per property
|
|
21
|
+
or lifecycle event. Capture intermediate values or immutable snapshots in `Because`
|
|
22
|
+
as the event sequence advances; facts only inspect them. Add the cases below only
|
|
23
|
+
where the contract states the behavior; an uncontracted rule found in the projection
|
|
24
|
+
is a proposal for the contract, not silent coverage.
|
|
22
25
|
|
|
23
26
|
## How to use this list
|
|
24
27
|
For each read model, list its properties and its events in a small table (property, events that
|
|
@@ -144,10 +144,14 @@ handle it. Pitfall: without `merge_group` the required check never reports in th
|
|
|
144
144
|
queue. Read `references/required-checks-and-path-filters.md`.
|
|
145
145
|
|
|
146
146
|
**Compile once, analyze once.** Fits any workflow where one push compiles the same
|
|
147
|
-
code several times. Default: build
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
code several times. Default: build the spec path without analyzers
|
|
148
|
+
(`-p:RunAnalyzers=false`) and reuse the output; enforce every analyzer with warnings
|
|
149
|
+
as errors in one parallel **required** job that fails early on compile and analyzer
|
|
150
|
+
errors, while the local fast loop may skip analyzers and the pre-push gate runs them.
|
|
151
|
+
Pitfall: dropping analyzers from the only build that consumers enforce, or leaving
|
|
152
|
+
the analyzer job out of the required checks. Measure analyzer cost per rule before
|
|
153
|
+
and after (binlog analyzer timings), and fix a slow analyzer at its source rather
|
|
154
|
+
than switching it off. Read `references/test-setup-cost.md`.
|
|
151
155
|
|
|
152
156
|
**Cores per job before job count.** Fits uniformly slow jobs. Default: check `nproc`
|
|
153
157
|
and the runner's resources before sharding; undersized runners make every job slow.
|
|
@@ -11,6 +11,7 @@ a suite re-tests a dependency, or when compile errors surface late.
|
|
|
11
11
|
- Test each guarantee where it can break
|
|
12
12
|
- Surface compile errors early
|
|
13
13
|
- Count compiles per push
|
|
14
|
+
- Analyzer cost
|
|
14
15
|
|
|
15
16
|
## Time hidden outside per-test timing
|
|
16
17
|
|
|
@@ -80,3 +81,17 @@ Each extra compile is avoidable: build once, reuse the output (see
|
|
|
80
81
|
`build-once-and-shard.md`). Analyzers can roughly double compile time; run them once
|
|
81
82
|
in a parallel lint job and disable them in the shared build, while keeping the
|
|
82
83
|
strictest configuration that consumers enforce somewhere on the required path.
|
|
84
|
+
|
|
85
|
+
## Analyzer cost
|
|
86
|
+
|
|
87
|
+
Analyzers that run in the spec path stall every build. Take per-analyzer timings
|
|
88
|
+
from a binlog of the slow project, with the same inputs and concurrency before and
|
|
89
|
+
after. Rank by cumulative time per rule, not by assembly. Three results are
|
|
90
|
+
typical: rules that cannot apply to the repository (switch them off; a bulk
|
|
91
|
+
severity setting can run an analyzer whose own rules are `none`), an analyzer that
|
|
92
|
+
rebinds the whole compilation (fix it upstream: syntactic prefilter, evidence
|
|
93
|
+
computed once per compilation, touched only after cheap relevance checks), and
|
|
94
|
+
generators, which are rarely the cost. In one measured application, compiling with
|
|
95
|
+
analyzers took 395 s, 180 s after removing inapplicable rules, and 90 s with
|
|
96
|
+
analyzers off. Pass means the spec-path build skips analyzers, the parallel
|
|
97
|
+
required job still fails on any analyzer warning, and both timings are recorded.
|
|
@@ -84,8 +84,11 @@ there is nothing to review under a red build.
|
|
|
84
84
|
- Read protection is `Off` by default; new code sets `<CratisReadProtection>On</CratisReadProtection>`.
|
|
85
85
|
Under `Off` an injected model or validator read is advisory, not a concurrency guard.
|
|
86
86
|
Under `On`, review that state rules sit in validators or handlers with the model as
|
|
87
|
-
a parameter
|
|
88
|
-
(
|
|
87
|
+
a parameter and `[Unprotected("why")]` carries a real reason. Apply
|
|
88
|
+
[specs.scenarios.csharp.md](../../rules/specs.scenarios.csharp.md): business
|
|
89
|
+
decisions use direct `Handle()`/validator calls with supplied state. Require
|
|
90
|
+
one cheap assembly-level admission spec for the application's own shapes,
|
|
91
|
+
not per-decision concurrency/conflict/isolation tests; Arc/Chronicle own that matrix.
|
|
89
92
|
- Use `Cratis.Arc.Core.CodeAnalysis` and `Cratis.Arc.Chronicle.CodeAnalysis`.
|
|
90
93
|
ARCCHR0011 (Info, `Off` only) and 0012 flag unprotected reads/immediate
|
|
91
94
|
bypasses; 0013 (Info) recommends definitions; 0014 checks declared observer
|
|
@@ -151,12 +154,12 @@ degradations a reviewer can see without measuring:
|
|
|
151
154
|
|
|
152
155
|
## Step 6 — Specification coverage
|
|
153
156
|
|
|
154
|
-
- Pure command, reducer and reactor decisions have plain-call
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
- Pure command, validator, reducer and reactor decisions have plain-call specs
|
|
158
|
+
with supplied read-model state; scenarios add only pipeline/authorization
|
|
159
|
+
wiring, constraints, declarative mapping or application-owned race-policy proof.
|
|
157
160
|
- Every State Change command has a happy-path specification.
|
|
158
|
-
- Every validation rule has a failure
|
|
159
|
-
`ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`.
|
|
161
|
+
- Every validation rule has a direct-call failure spec. Pipeline validation
|
|
162
|
+
specs assert **both** `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`.
|
|
160
163
|
- Every business-rule rejection has a specification.
|
|
161
164
|
- Every constraint has an `EventScenario` specification asserting the constraint
|
|
162
165
|
**name** by default.
|
|
@@ -259,7 +259,11 @@ Stage emits strategy-specific `IGeneratable<T>.New()` factories over Fundamental
|
|
|
259
259
|
checks atomically at append: a batch's scopes are checked once against the state before
|
|
260
260
|
the batch, all or nothing, and a storage retry that renumbers the append checks them again. Generated rejection specs assert
|
|
261
261
|
`ValidationResultReason.ConcurrencyViolation`, not Arc's message text. This is
|
|
262
|
-
Stage's explicit realization, not a claim that
|
|
262
|
+
Stage's explicit realization and renderer-owned smoke coverage, not a claim that
|
|
263
|
+
Arc delivers the same guard or an instruction to hand-write an application guard
|
|
264
|
+
matrix. Application decisions use direct `Handle()`/validator calls with supplied
|
|
265
|
+
state; the one cheap application-shape admission spec is their only decision-read
|
|
266
|
+
obligation. See [specs.scenarios.csharp.md](../../../rules/specs.scenarios.csharp.md).
|
|
263
267
|
Time-based factories use the command's receipt time through `IGeneratableInContext<T>`
|
|
264
268
|
and `GenerationContext`, the same instant as `$context.occurred`.
|
|
265
269
|
|
|
@@ -7,9 +7,11 @@ or `question` cells).
|
|
|
7
7
|
|
|
8
8
|
- **Claims**: different-source collision, same-source reclaim, change or release, a legitimate
|
|
9
9
|
repeat.
|
|
10
|
-
- **Decision reads**:
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
- **Decision reads**: application specs call `Handle()` or the validator directly
|
|
11
|
+
with supplied state and assert business outcomes. Their only decision-read
|
|
12
|
+
obligation is one cheap assembly-level admission spec for the application's
|
|
13
|
+
own models/projections; see [specs.scenarios.csharp.md](../../../rules/specs.scenarios.csharp.md).
|
|
14
|
+
The concurrency, conflict-refusal and isolation matrix belongs only to Arc/Chronicle.
|
|
13
15
|
- **Namespaces**: identical business keys in different tenants, background target selection,
|
|
14
16
|
independent observer progress, every namespace covered by erasure.
|
|
15
17
|
- **Compliance**: subject resolution, unreadable values after erasure, fenced new writes,
|
|
@@ -13,10 +13,10 @@ of contents, and each file states one setup, one action, and one or more
|
|
|
13
13
|
focused assertions.
|
|
14
14
|
|
|
15
15
|
Read [query and decision scenarios](../cratis-application-slice-specifications/references/query-and-decision-scenarios.md)
|
|
16
|
-
when specifying query emissions
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
read-protection
|
|
16
|
+
when specifying query emissions.
|
|
17
|
+
Follow the [scenario selection and cost rule](../../rules/specs.scenarios.csharp.md):
|
|
18
|
+
application specs supply read-model state and test business decisions, never
|
|
19
|
+
read-protection plumbing. `Perform`/`Observe` need configured services/principal;
|
|
20
20
|
MongoDB observations still need real infrastructure.
|
|
21
21
|
|
|
22
22
|
## Verified product sources
|
|
@@ -77,20 +77,21 @@ before changing runner or package family.
|
|
|
77
77
|
|
|
78
78
|
| Situation | Surface |
|
|
79
79
|
| --- | --- |
|
|
80
|
-
| Pure `Handle()` / `Handle(providedValue)`, reducer
|
|
80
|
+
| Pure `Handle()` / `Handle(providedValue)`, validator, reducer or reactor decision (default) | Plain method call with `Specification`, supplying read-model state |
|
|
81
81
|
| Isolated class, collaborators can be substituted | `Specification` + NSubstitute |
|
|
82
82
|
| Arc command pipeline (validators, `Provide()`, `Handle()`, appended events) | `CommandScenario<TCommand>` |
|
|
83
83
|
| Arc query conversion, validation, authorization and interceptors | `QueryScenario<TReadModel>` (`Cratis.Arc.Testing.Queries`) |
|
|
84
|
-
|
|
|
85
|
-
|
|
|
84
|
+
| Append-time domain constraints | `EventScenario` |
|
|
85
|
+
| Declarative mapping or reducer wiring | One `ReadModelScenario<TReadModel>` class per read model, many read-only assertions on shared setup |
|
|
86
86
|
| Reactor invocation and its side effects | `ReactorScenario<TReactor>` |
|
|
87
87
|
| Host, transport, or real-infrastructure boundary | Out-of-process Chronicle integration specification |
|
|
88
88
|
| Setup shared by many specifications | Reusable context under `given/` |
|
|
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
|
|
93
|
-
constraints or declarative
|
|
92
|
+
plain call, including direct validator calls, and add scenarios only for
|
|
93
|
+
pipeline/authorization wiring, constraints, or declarative mapping. Keep shared lifecycle on; measure scenario cost using
|
|
94
|
+
[running-specs.md](references/running-specs.md). Read
|
|
94
95
|
[application-scenarios.md](references/application-scenarios.md) before using
|
|
95
96
|
one. Out-of-process integration specifications are an advanced case reserved for
|
|
96
97
|
boundaries the scenario helpers cannot reach — see
|
|
@@ -99,8 +100,12 @@ boundaries the scenario helpers cannot reach — see
|
|
|
99
100
|
⚠️ `EventScenario` wires a strategy that always answers `ConcurrencyScope.None`,
|
|
100
101
|
which the validator skips, so an append that lets the strategy choose the scope
|
|
101
102
|
never produces a concurrency violation. `ShouldHaveConcurrencyViolations()`
|
|
102
|
-
cannot pass for such an append and its negation passes vacuously
|
|
103
|
-
|
|
103
|
+
cannot pass for such an append and its negation passes vacuously. Arc/Chronicle
|
|
104
|
+
alone prove the platform concurrency, conflict-refusal and isolation matrix,
|
|
105
|
+
with real-kernel integration where required. The application's only decision-read
|
|
106
|
+
spec obligation is one cheap assembly-level shape-admission check from the
|
|
107
|
+
[scenario rule](../../rules/specs.scenarios.csharp.md); business decisions take
|
|
108
|
+
supplied state in direct `Handle()`/validator calls.
|
|
104
109
|
|
|
105
110
|
For a class-based `ReactorScenario<TReactor>` on a recent Chronicle (Requires Chronicle 19.46.0), seed each
|
|
106
111
|
handler read-model parameter with `Given.ForEventSourceId(id).ReadModel(instance)`
|
|
@@ -224,9 +229,9 @@ convention — there are no attributes on them.
|
|
|
224
229
|
The shared lifecycle is the default: one instance and one Establish/Because
|
|
225
230
|
execution per specification class. Facts must not mutate that shared context;
|
|
226
231
|
`CRSPEC0008` is an error when one does. Read
|
|
227
|
-
[shared-lifecycle.md](references/shared-lifecycle.md) for eligibility,
|
|
228
|
-
|
|
229
|
-
|
|
232
|
+
[shared-lifecycle.md](references/shared-lifecycle.md) for eligibility, auditing
|
|
233
|
+
mutation, class-only `[PerFactSpecificationLifecycle]` exceptions and fixing
|
|
234
|
+
`CRSPEC0008`. Do not opt the assembly or project out.
|
|
230
235
|
|
|
231
236
|
## Step 4 — Extract a reusable context
|
|
232
237
|
|
package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md
CHANGED
|
@@ -9,23 +9,22 @@
|
|
|
9
9
|
- Application conventions
|
|
10
10
|
|
|
11
11
|
The four scenario helpers exercise the real Arc and Chronicle pipelines
|
|
12
|
-
in-process.
|
|
13
|
-
|
|
14
|
-
`Handle(
|
|
15
|
-
|
|
12
|
+
in-process. Follow the [scenario selection and cost rule](../../../rules/specs.scenarios.csharp.md):
|
|
13
|
+
use them for pipeline/authorization wiring, constraints, or declarative mapping;
|
|
14
|
+
specify pure `Handle()`, validator, reducer and
|
|
15
|
+
reactor decisions first as plain calls with supplied read-model state. A library or framework repository reaches for a scenario only to test the
|
|
16
16
|
very engine it provides.
|
|
17
17
|
|
|
18
18
|
Read [query and decision scenarios](../../cratis-application-slice-specifications/references/query-and-decision-scenarios.md)
|
|
19
|
-
when using `QueryScenario<T>`, buffered emissions
|
|
20
|
-
|
|
21
|
-
state and appends share one in-process log, unlike legacy dictionary seeding.
|
|
19
|
+
when using `QueryScenario<T>`, or buffered emissions.
|
|
20
|
+
Read-protection plumbing is the harness's responsibility, never application setup.
|
|
22
21
|
|
|
23
22
|
| Helper | Exercises | Use for |
|
|
24
23
|
| --- | --- | --- |
|
|
25
|
-
| `CommandScenario<TCommand>` | Authorization, validators, `Provide()`, `Handle()`, appended events |
|
|
24
|
+
| `CommandScenario<TCommand>` | Authorization, validators, `Provide()`, `Handle()`, appended events | Pipeline wiring, authorization, domain constraints |
|
|
26
25
|
| `QueryScenario<TReadModel>` | Argument conversion, authorization, validation, interceptors and buffered observations | Query pipeline behavior |
|
|
27
|
-
| `EventScenario` | Chronicle append semantics without the command pipeline |
|
|
28
|
-
| `ReadModelScenario<TReadModel>` | Projection or reducer state from a sequence of events |
|
|
26
|
+
| `EventScenario` | Chronicle append semantics without the command pipeline | Domain constraints; raw engine guards belong in framework specs |
|
|
27
|
+
| `ReadModelScenario<TReadModel>` | Projection or reducer state from a sequence of events | Declarative mapping/wiring, one class per model |
|
|
29
28
|
| `ReactorScenario<TReactor>` | Reactor invocation and its side effects | Automation and Translation behavior |
|
|
30
29
|
|
|
31
30
|
Wrap scenario specification files in `#if DEBUG … #endif` only when they compile
|
|
@@ -83,9 +82,9 @@ from `Cratis.Arc.Chronicle.Testing`.
|
|
|
83
82
|
`AppendedEvents`. `Given.ForEventSource(id).Events(…)` seeds the events an
|
|
84
83
|
injected read model is then materialized from by its own projection or
|
|
85
84
|
reducer; `Given.ForEventSource(id).ReadModel(instance)` pins a specific
|
|
86
|
-
read-model value instead. Both return `void`.
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
read-model value instead. Both return `void`. Supply state for business
|
|
86
|
+
decisions; seed events only when history/mapping is part of the example, never
|
|
87
|
+
solely to satisfy read-protection infrastructure.
|
|
89
88
|
- **Validator and `Provide()` dependencies** are registered in
|
|
90
89
|
`_scenario.Services`; the concrete validator is discovered automatically.
|
|
91
90
|
- **Caller setup (Requires Arc 22.75.1):** register a substituted
|
|
@@ -233,18 +232,20 @@ async Task Because() =>
|
|
|
233
232
|
Assert.True(_scenario.Instance!.<Property>);
|
|
234
233
|
```
|
|
235
234
|
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
Use one scenario class per read model, many read-only facts and shared lifecycle.
|
|
236
|
+
Apply its event sequence once; capture intermediate states in `Because` for
|
|
237
|
+
lifecycle assertions. Do not split startup per property, attribute or event.
|
|
238
|
+
Pure reducer decisions remain plain calls. The helper auto-detects a model-bound
|
|
239
|
+
projection, a fluent projection, or a reducer. Use the named-parameter constructor when services or initial state are
|
|
238
240
|
needed: `new ReadModelScenario<<ReadModelType>>(initialState: null,
|
|
239
241
|
serviceProvider: services)`. Pre-seed a keyed read model with
|
|
240
242
|
`Given.ForEventSourceId(id).ReadModel(instance)` for code that calls
|
|
241
243
|
`IReadModels.GetInstanceById`. Cross-stream projections are supported — seed each
|
|
242
244
|
contributing stream with its own `Given.ForEventSource(…)`.
|
|
243
245
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
For non-key searches, register a lookup double in the scenario services instead.
|
|
246
|
+
Do not build a projection scenario merely to feed a pure command decision.
|
|
247
|
+
Pass state directly or substitute the application's lookup collaborator; provider
|
|
248
|
+
behavior belongs in provider integration specs.
|
|
248
249
|
|
|
249
250
|
Do not pre-emptively skip a read-model assertion. Assume scalar concept, enum,
|
|
250
251
|
and identifier properties populate; if one does not, investigate the projection.
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
# Running specifications and migrating to 5.0
|
|
5
5
|
|
|
6
|
+
## Contents
|
|
7
|
+
|
|
8
|
+
- Project shape
|
|
9
|
+
- Local loop
|
|
10
|
+
- Measure specification cost
|
|
11
|
+
- Filters
|
|
12
|
+
- Migrate from 4.x to 5.0
|
|
13
|
+
|
|
6
14
|
Requires `Cratis.Specifications.XUnit.v3` 5.0 on `net10.0`. Check the
|
|
7
15
|
`PackageReference` versions in the specification project; 4.x projects follow the
|
|
8
16
|
checklist below before the executable loop applies.
|
|
@@ -50,6 +58,28 @@ passed count.
|
|
|
50
58
|
Where a solution mixes Microsoft.Testing.Platform and VSTest projects, run each
|
|
51
59
|
project on its own; `dotnet test` on the whole solution does not work there.
|
|
52
60
|
|
|
61
|
+
## Measure specification cost
|
|
62
|
+
|
|
63
|
+
Apply the [scenario cost budget](../../../rules/specs.scenarios.csharp.md): plain
|
|
64
|
+
decision specs should take roughly milliseconds, not seconds. Measure scenario
|
|
65
|
+
facts and justify their cost by pipeline/authorization wiring, constraints,
|
|
66
|
+
or declarative mapping that a plain call cannot
|
|
67
|
+
prove. Prefer fewer startups, not fewer assertions or weaker production guards.
|
|
68
|
+
|
|
69
|
+
Capture TRX for the affected project before and after the change. With VSTest,
|
|
70
|
+
use `dotnet test <project> --no-build --configuration Release --logger "trx;LogFileName=specs.trx" --results-directory <results>`.
|
|
71
|
+
For the built xUnit v3 `mtp-off` executable, use `-trx <results>/specs.trx`;
|
|
72
|
+
check `<executable> -h` for other runner flavors rather than mixing switches.
|
|
73
|
+
Use separate result paths for each run, under `.ai-work/` for local work.
|
|
74
|
+
|
|
75
|
+
Inspect `UnitTestResult` entries: rank `duration` by `testName`, group by spec
|
|
76
|
+
class, and compare pure calls, command scenarios and read-model scenarios. Shared
|
|
77
|
+
setup may be charged to one fact, so also compare class totals and suite wall
|
|
78
|
+
time; an individual fact's duration is not its isolated startup cost. Keep package
|
|
79
|
+
versions, configuration, filters, concurrency/shards and machine fixed; preserve
|
|
80
|
+
discovered/passed/failed/skipped counts and investigate any change. Do not count
|
|
81
|
+
restore/build time as test execution or repeat unchanged runs merely for reassurance.
|
|
82
|
+
|
|
53
83
|
## Filters
|
|
54
84
|
|
|
55
85
|
The executable accepts the filters of the xUnit v3 runner flavor the project
|
|
@@ -81,7 +111,8 @@ and record the spec count and outcomes first.
|
|
|
81
111
|
4. Delete any `[assembly: Xunit.TestFramework(...)]` that registers Cratis
|
|
82
112
|
Specifications.
|
|
83
113
|
5. Fix each `CRSPEC0008` error by moving the change into `Establish` or `Because`; use
|
|
84
|
-
`[PerFactSpecificationLifecycle]` only
|
|
114
|
+
`[PerFactSpecificationLifecycle]` only on a class whose facts genuinely mutate shared
|
|
115
|
+
state and cannot move that mutation into `Because`.
|
|
85
116
|
6. Change assertion delegates typed as `Predicate<T>` to `Func<T, bool>`; for
|
|
86
117
|
NUnit 4 comparisons that relied on unordered `ShouldEqual(params)`, use
|
|
87
118
|
`ShouldEqualInAnyOrder`. Null actual values now fail, including nullable
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
# Shared specification lifecycle
|
|
5
5
|
|
|
6
|
+
## Contents
|
|
7
|
+
|
|
8
|
+
- Eligibility
|
|
9
|
+
- Native runner teardown outcomes
|
|
10
|
+
- CRSPEC0008
|
|
11
|
+
- Keep shared execution on
|
|
12
|
+
|
|
6
13
|
Requires `Cratis.Specifications.XUnit.v3` or `.XUnit` 5.0. Before 5.0 the shared
|
|
7
14
|
lifecycle was an opt-in on xUnit and `CRSPEC0008` was informational; check the package
|
|
8
15
|
version before relying on either. NUnit, TUnit and the native runner always share the
|
|
@@ -35,7 +42,8 @@ the lifecycle; earlier versions use per-fact construction. With shared lifecycle
|
|
|
35
42
|
construction happens once, and `Destroy` runs followed by
|
|
36
43
|
one `Dispose` after the last fact, even when `Destroy` fails. Class/collection fixtures remain owned by xUnit,
|
|
37
44
|
with assembly fixtures also supported on v3. Use
|
|
38
|
-
`[PerFactSpecificationLifecycle]`
|
|
45
|
+
`[PerFactSpecificationLifecycle]` only on the class whose facts genuinely mutate
|
|
46
|
+
shared state and cannot move the mutation into `Because`; state the reason locally.
|
|
39
47
|
|
|
40
48
|
`Specification.InitializeAsync()` is idempotent for a single instance. It does not make
|
|
41
49
|
an ineligible class shared or cache instances across classes.
|
|
@@ -59,8 +67,18 @@ Requires Specifications 5.0.2 for Received.InOrder verification recognition; che
|
|
|
59
67
|
Calls inside NSubstitute `Received.InOrder(...)` delegates are recognized as
|
|
60
68
|
verification, but assignments and increments there still mutate state and are
|
|
61
69
|
reported. Theories, per-fact classes/assemblies, immutable collection calls and
|
|
62
|
-
collection calls whose return value is used are excluded.
|
|
63
|
-
|
|
70
|
+
collection calls whose return value is used are excluded.
|
|
71
|
+
|
|
72
|
+
Since Specifications 5.2.0, `CRSPEC0008` also reports indirect changes it can prove:
|
|
73
|
+
writes through nested members and indexers (also `?.`), `Dispose`/`DisposeAsync` on a
|
|
74
|
+
shared field, reconfiguring a shared NSubstitute substitute (`Returns`, `Throws`,
|
|
75
|
+
`When`, `ClearReceivedCalls`), and calls to a method in the same file that does any of
|
|
76
|
+
these. The warning `CRSPEC0012` reports calls that usually change state, judged by
|
|
77
|
+
name: `Execute`/`Handle`, destructive reads (`Take`, `Dequeue`, `Pop`, `MoveNext`) and
|
|
78
|
+
`Push`, `Enqueue`, `TryAdd`, `Reset`, or `Add`/`Remove`/`Clear` on a collection reached
|
|
79
|
+
through a field. Helpers in other files (such as a `given` context) and Moq `Setup`
|
|
80
|
+
are not followed, so audit those classes by hand before turning the shared lifecycle
|
|
81
|
+
back on. Move actual mutations into `Establish` or `Because`:
|
|
64
82
|
|
|
65
83
|
```csharp
|
|
66
84
|
public class when_registering : Specification
|
|
@@ -74,18 +92,19 @@ public class when_registering : Specification
|
|
|
74
92
|
}
|
|
75
93
|
```
|
|
76
94
|
|
|
77
|
-
Do not lower the severity to pass the gate.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
Do not lower the severity to pass the gate. The analyzer is not a complete mutation
|
|
96
|
+
audit: also inspect helper calls, nested/indexer writes, command execution,
|
|
97
|
+
destructive reads, disposal and substitute reconfiguration inside facts. Awaiting
|
|
98
|
+
a read-only assertion is not itself a mutation.
|
|
81
99
|
|
|
82
|
-
|
|
100
|
+
## Keep shared execution on
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
Do not opt the assembly or project out for a few mutating facts. Remove blanket
|
|
103
|
+
opt-outs only after auditing fact mutations; move actions into `Because` where
|
|
104
|
+
possible and put `[PerFactSpecificationLifecycle]` on the remaining concrete
|
|
105
|
+
classes, with a reason. Compare discovered/passed/failed/skipped counts and TRX
|
|
106
|
+
durations before and after with unchanged concurrency. A clean analyzer run alone
|
|
107
|
+
does not prove that all facts are read-only.
|
|
89
108
|
|
|
90
109
|
Read [running-specs.md](./running-specs.md) for the executable loop and the 4.x → 5.0
|
|
91
110
|
checklist.
|
|
@@ -40,7 +40,7 @@ Requires Stage 4.136.0 to render `generate rule`: the attached C# body becomes t
|
|
|
40
40
|
opaque/unsupported rules refuse -005 before pin evaluation. Independent execution
|
|
41
41
|
always needs pins.
|
|
42
42
|
Requires Stage 4.124.0 for v24 text-rule admission (`cratis --version` identifies the CLI bundle); older renderers refuse v24 with STAGE-ESM-016.
|
|
43
|
-
Requires Stage 4.127.0 for v25 version admission; older renderers refuse v25 with STAGE-ESM-016. Requires Stage 4.138.0 for the Cratis renderer to render protected reads as Arc read protection (`[ReadProtection(On)]`, a nullable read-model `Handle` parameter per read with `[LookUpBy]`, the `<View> '<key>' does not exist.` rejection, requirements evaluated in `Handle` after the reads; generated
|
|
43
|
+
Requires Stage 4.127.0 for v25 version admission; older renderers refuse v25 with STAGE-ESM-016. Requires Stage 4.138.0 for the Cratis renderer to render protected reads as Arc read protection (`[ReadProtection(On)]`, a nullable read-model `Handle` parameter per read with `[LookUpBy]`, the `<View> '<key>' does not exist.` rejection, requirements evaluated in `Handle` after the reads; the renderer owns generated specification plumbing; application spec authors do not select decision modes); `cratis render` gets it from the first CLI that bundles Stage 4.138.0, and earlier bundles (CLI 3.75.0 bundles 4.137.0) refuse every protected read with STAGE-ESM-044. Reads it cannot preserve faithfully stay STAGE-ESM-044 and name the reason: constrained events, read paths in `produces when`, mappings to optional, collection, composite or mistyped event properties, generated-value freshness scopes. See [protected reads](../../cratis-screenplay-command-surface/references/protected-reads.md). For hand-written application coverage, use direct `Handle()`/validator calls with supplied state and the single cheap assembly-level shape-admission spec in [specs.scenarios.csharp.md](../../../rules/specs.scenarios.csharp.md); do not reproduce Arc/Chronicle's platform guard matrix.
|
|
44
44
|
Requires Stage 4.129.0 for semantic Host/executor protected reads; older engines refuse STAGE-ESM-044. Required absence rejects before requirements or generation; read-backed requirements and produced-event mappings execute. Non-Error protected requirements refuse STAGE-ESM-045, including reaction invocations. See [sandbox boundaries](sandbox-and-specrunner.md#protected-command-reads) for ordering and outside-append commit boundaries.
|
|
45
45
|
Described handlers still refuse STAGE-IMPL-002, even in Stub mode. Query
|
|
46
46
|
performers have a separate implementation-gap contract; gaps are not completed
|