@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
@@ -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). Write one specification per contract specification, using its example values, and
20
- add the cases below only where the contract states the behavior; an uncontracted rule found in the
21
- projection is a proposal for the contract, not silent coverage.
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 once and reuse the output; run analyzers in one
148
- parallel lint job instead of in every build, and let that job fail early on compile
149
- and analyzer errors before long test legs. Pitfall: dropping analyzers from the only
150
- build that consumers enforce. Read `references/test-setup-cost.md`.
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, `[Unprotected("why")]` carries a real reason, and a conflict spec
88
- (`AppendConcurrently` plus `ConcurrencyViolation`) exists.
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 specifications
155
- (`Specification` plus a direct call); scenarios are added where pipelines,
156
- validation, constraints, projections or wiring contribute proof.
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 specification asserting **both**
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 Arc delivers the same guard.
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**: never-created absence, removal then recreation, a relevant concurrent
11
- change, an irrelevant one, rejected or no-fact outcomes, refusal of an incompatible
12
- projection or key.
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, routed conflicts or protected projection reads.
17
- Seed actual events for a guarded command; pinned models cannot supply a guard.
18
- `new CommandScenario<T>()` selects the in-process decision log from the command's
19
- read-protection mode and dependencies, so `UseDecisionReads()` is not called. `Perform`/`Observe` need configured services/principal, and
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 logic or reactor decision (default) | Plain method call with `Specification` |
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
- | Chronicle append semantics, constraints, concurrency | `EventScenario` |
85
- | Projection or reducer state from a sequence of events | `ReadModelScenario<TReadModel>` |
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 and add the scenario helpers where the pipeline, validation,
93
- constraints or declarative projections contribute proof; read
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 — specify concurrency against the
103
- real kernel with an out-of-process integration specification instead.
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, opting a
228
- class, assembly or project out with `[PerFactSpecificationLifecycle]`, and
229
- fixing `CRSPEC0008`.
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
 
@@ -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. Use them for event-sourced *application* behavior where the pipeline,
13
- validation, constraints or projections matter; a pure decision (`Handle()`,
14
- `Handle(providedValue)`, reducer or reactor logic) is specified first as a plain
15
- call. A library or framework repository reaches for a scenario only to test the
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 or guarded command scenarios.
20
- Guarded decisions need actual seeded events, not pinned models; their given
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 | State Change behavior |
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 | Constraints, raw sequencing, concurrency |
28
- | `ReadModelScenario<TReadModel>` | Projection or reducer state from a sequence of events | State View behavior |
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`. Prefer seeding events — it
87
- exercises the projection production depends on, so the specification cannot
88
- pass against a read-model shape the projection never produces.
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
- The helper auto-detects a model-bound projection, a fluent projection, or a
237
- reducer. Use the named-parameter constructor when services or initial state are
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
- To bridge read models into a command scenario, build a focused
245
- `ReadModelScenario<T>`, seed it, then register its read models into the command
246
- scenario with `_scenario.Services.AddSingleton(readModelScenario.ReadModels)`.
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 when the fact genuinely needs a fresh instance.
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]` when a fresh instance per fact is needed.
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. Move actual mutations
63
- into `Establish` or `Because`:
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. When the repository must lower it, set it in
78
- `.editorconfig` with `dotnet_diagnostic.CRSPEC0008.severity = warning`.
79
-
80
- ## Keep per-fact execution
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
- Use the narrowest scope; the reason is a fact that genuinely needs a fresh instance.
100
+ ## Keep shared execution on
83
101
 
84
- | Scope | How |
85
- | --- | --- |
86
- | One class | `[PerFactSpecificationLifecycle]` on the class |
87
- | The assembly | `[assembly: PerFactSpecificationLifecycle]` |
88
- | The project | `<CratisSpecificationsSharedLifecycle>false</CratisSpecificationsSharedLifecycle>` |
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 specifications run in decision mode); `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).
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/pi",
3
- "version": "7.123.0",
3
+ "version": "7.125.0",
4
4
  "description": "Configuration-aware Cratis AI integration for Pi",
5
5
  "type": "module",
6
6
  "files": [