@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
@@ -46,13 +46,13 @@ Select from these canonical rules in `.cratis/ai/rules/` only after applying the
46
46
 
47
47
  ## C# specs — start with the plain call
48
48
 
49
- Specify pure decisions with plain calls first: `Handle()`, `Handle(providedValue)`, reducers and reactor logic. Add the four in-process scenario helpers where the pipeline, validation, constraints or projections contribute proof; prefer them over out-of-process Chronicle host specs for those behaviors:
49
+ Specify pure `Handle()`, validator, reducer and reactor decisions with plain calls and supplied read-model state. Follow `.cratis/ai/rules/specs.scenarios.csharp.md`: scenarios add only pipeline/authorization wiring, constraints, declarative mapping or application-owned race-policy proof, not a platform guard matrix. Keep the single cheap assembly-level admission spec for the application's own decision-model shapes. Prefer in-process helpers over out-of-process Chronicle hosts for the applicable behaviors:
50
50
 
51
51
  | Tool | Use for |
52
52
  |---|---|
53
53
  | `CommandScenario<TCommand>` | **State Change** — runs authorization + validators + `Provide()` + `Handle()` + appended events |
54
54
  | `EventScenario` | constraint violations, raw append/sequencing semantics |
55
- | `ReadModelScenario<TReadModel>` | **State View** — projection/reducer state from a sequence of events |
55
+ | `ReadModelScenario<TReadModel>` | **State View** — one class per read model, many read-only assertions on shared setup |
56
56
  | `ReactorScenario<TReactor>` | **Automation / Translation** — reactor invocation + side effects |
57
57
 
58
58
  Reserve out-of-process integration specs for host/transport/infra boundaries the scenario helpers can't exercise.
@@ -90,14 +90,14 @@ public class and_all_information_is_valid : Specification
90
90
  #endif
91
91
  ```
92
92
 
93
- (`CommandScenario` event assertions are extension methods keyed by command + event type — `await _scenario.ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId[, predicate])`; seed prior state through `_scenario.Given.ForEventSource(id).Events(...)` or `_scenario.Services`. The mode matters: in legacy mode `Given` materializes an in-memory read-model dictionary only, so constraint preconditions go through `EventScenario`'s `Given`; in decision mode (selected automatically from the command's read-protection mode and dependencies) `Given` writes to the scenario's real log and `EventScenario` is unavailable (accessing it throws), so seed everything through `Given`. Check which mode the spec's scenario uses; see `specs.scenarios.csharp.md` and `cratis-application-slice-specifications`.)
93
+ `CommandScenario` event assertions are extension methods keyed by command + event type: `await _scenario.ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId[, predicate])`. Supply model state for business decisions; event seeding belongs to mapping/history/constraint proof, not read-protection plumbing. See `specs.scenarios.csharp.md` and `cratis-application-slice-specifications` for focused pipeline setup and advisory lookup pitfalls.
94
94
 
95
95
  ### What to specify
96
96
 
97
- 0. **Every contract specification first** — one spec each, named after it, using the contract's example values. Then add the code-derived cases below that the contract lacks and report them as proposals for the contract (the `.play` model when one covers the slice, otherwise the agreed outline); never present them as contract coverage. A `.play` specification maps to a plain-call spec for a pure decision, with a scenario added where the pipeline, validation, constraints or projections matter; never edit, skip or delete a spec derived from the contract to make code pass — change the code or return an edit request.
97
+ 0. **Every contract specification first** — one spec each, named after it, using the contract's example values. Then add the code-derived cases below that the contract lacks and report them as proposals for the contract (the `.play` model when one covers the slice, otherwise the agreed outline); never present them as contract coverage. A `.play` specification maps to a plain-call spec for a pure decision, with a scenario added only for the pipeline composition it proves (never a simulated race); never edit, skip or delete a spec derived from the contract to make code pass — change the code or return an edit request.
98
98
  1. **Happy path** — succeeds, correct event(s) appended.
99
- 2. **Each validation failure** — assert **both** `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`. Do not assert on message strings by default; assert exact text only when that wording is the specified behavior, and name that behavior in the fact. Violate **only** the rule the spec is named after (build the command valid in every other respect), or a neighbouring rule makes it pass.
100
- 3. **Business-rule violations** — each `Result<,>` rejection / guarded decision condition. Plain injection under `Off` is advisory; for read protection `On`, `[ProtectedDecision]` or stream decisions use `new CommandScenario<T>()` with seeded events, `AppendConcurrently` and `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`, not pinned models and not `UseDecisionReads()`.
99
+ 2. **Each validation failure** — call the validator directly with supplied state; pipeline validation specs assert **both** `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`. Do not assert on message strings by default; assert exact text only when that wording is the specified behavior, and name that behavior in the fact. Violate **only** the rule the spec is named after (build the command valid in every other respect), or a neighbouring rule makes it pass.
100
+ 3. **Business-rule violations** — call `Handle()` or the validator directly with the required model state and assert each rejection. Arc/Chronicle specs alone prove platform concurrency, conflict refusal and isolation. The application's only decision-read spec obligation is the single cheap shape-admission spec in `specs.scenarios.csharp.md`; do not repeat guard scenarios per decision.
101
101
  4. **Constraint violations** — `ShouldHaveConstraintViolationFor(name)` via `EventScenario`.
102
102
  5. **Authorization** — `ShouldNotBeAuthorized()` (an unauthorized result has no validation errors).
103
103
  6. **Repeat execution**, when a reactor invokes the command — the same command twice for the same source; specify whether the second is rejected, a no-op, or appends again.
@@ -74,7 +74,7 @@ Tagged **[contract]** (framework-enforced) or **[convention]** (house default).
74
74
  3. **Code level** — infrastructure (hosting, DI composition, identity wiring, CI, deployment), clients, Screenplay code attachments and handlers, adapters in the render's customization area, and scope Stage cannot render yet (the model remains the contract). Framework repositories and brownfield work that is not opted in also stay code-first. Only the entry-point session may propose a model, at most once per session and never for trivial, bug-fix, infrastructure, client, framework or brownfield-maintenance work; if declined, do not ask again. Confirm Module/Feature, slice name, slice type and domain requirements as before; for new behavior or unclear event vocabulary run **cratis-chronicle-event-modeling** first, and record the agreed outline (fields, events, rules, scenarios) where the team tracks work, if anywhere. If you write code first where a model exists, say the model now lags.
75
75
  4. **Trivial changes** keep the proportional-delegation policy; Phases 1–3 below apply to code you write at either level.
76
76
  - **Phase 1 — Backend.** Implement a coherent slice change. **Gate:** incremental Debug build of the affected project (regenerates proxies, compiles `#if DEBUG` spec code).
77
- - **Phase 2 — Specs.** Mandatory for every slice type. Start with plain-call specs for pure decisions; add in-process scenarios (`CommandScenario<T>`, `EventScenario`, `ReadModelScenario<T>`, `ReactorScenario<T>`) where pipelines, validation, constraints or projections contribute proof. **Gate:** tests pass.
77
+ - **Phase 2 — Specs.** Mandatory for every slice type. Start with plain calls for pure command/validator/reducer decisions with supplied read-model state; add scenarios only for pipeline/authorization wiring, constraints or declarative mapping. Follow [specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md) for shared setup and cost budgets. **Gate:** tests pass.
78
78
  - **Phase 3 — Frontend.** Build from the generated proxies, register in the composition page, wire routing. **Gate:** lint, conditional test, build.
79
79
 
80
80
  **Backend before frontend, always** — the frontend depends on proxies that exist only after a successful build. Generate proxies from the **Debug** build (house convention); verify Release without re-running the generator: `dotnet build -c Release -p:CratisProxiesOutputPath=`. Why that property, and what to do when nothing is generated: the **cratis-arc-command** skill's proxy-generation reference.
@@ -50,6 +50,7 @@ Repo conventions follow from this: `Source/DotNET` + `Source/JavaScript` for dua
50
50
  - **Backward compatibility is a contract.** A change to a public type, attribute, interface, or generated-proxy shape is a breaking change for every downstream app — label PRs by semver impact (`major`/`minor`/`patch`) and treat removals/renames of public surface as `major`.
51
51
  - **Convention discovery is built here.** `IInstancesOf<T>` / `IImplementationsOf<T>`, attribute-based discovery, and source generation are the mechanisms the framework *provides*; use them internally too rather than hand-registration where a convention fits. A Fundamentals project reference does not carry the type-discovery analyzer like its NuGet package does: reference `Fundamentals.TypeDiscovery.Generator` explicitly, or your project's types may be absent from the generated universe.
52
52
  - **Source generators / analyzers** (Arc's proxy generator, Fundamentals) follow Roslyn conventions; their output is consumed verbatim by apps, so treat generated shape as public API.
53
+ - **Analyzers and generators are written for speed** (fast feedback is a product requirement): incremental generators only; a syntactic prefilter (name or kind) before any semantic bind; compute shared evidence once per compilation, never once per analyzer or per symbol; touch it only after cheap relevance checks; never rebind every call site of the compilation. Each expensive analyzer gets a performance spec.
53
54
  - **Orleans grains** in the Chronicle kernel follow `orleans.md`.
54
55
  - **An alternate implementation of an interface must match the primary one's *semantics*, not just its signature.** Chronicle ships several implementations of the same storage interfaces (MongoDB, SQL, in-memory) and the in-memory ones exist precisely so the real kernel code paths can run without infrastructure. When you touch one, **diff it against the persistent implementations** — the signature tells you nothing about the contract. The specific trap: query criteria carry **sentinels meaning "do not narrow"** (`EventSourceId.Unspecified`, `EventSourceType.Unspecified`/`Default`, `EventStreamType.All`, `EventStreamId.Default`, an empty event-type set). Callers asking for "everything" pass those sentinels, never `null`, so a plain `is not null` check narrows every row away and the read silently returns nothing. A divergence here does not fail — it makes specs pass vacuously, which is worse.
55
56
  - **A stub that silently succeeds is a bug, not a placeholder.** An unimplemented method returning `Task.CompletedTask`/an empty result lets a spec assert on work that never happened. Implement it, or make it fail loudly — never leave it quietly lying.
@@ -12,7 +12,7 @@ Extends the base [specs.md](./specs.instructions.md) (folder structure, naming,
12
12
 
13
13
  > **Which spec surface?**
14
14
  > - **Framework / library code** (Chronicle kernel, Arc pipeline, source generators, Fundamentals): the `Specification` base + NSubstitute in this file is the dominant mode — unit-test the classes under test in isolation. Reach for a `*Scenario` helper (in [specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md)) **only** when testing the very engine your repo provides — Arc → `CommandScenario`, Chronicle → the event/projection/reactor scenarios. The **cratis-application-slice-specifications** skill is application-oriented, not for general framework specs.
15
- > - **Event-sourced applications on Cratis** (commands, projections, reducers, reactors, constraints): use this base plus a direct call by default for pure decisions; add the in-process scenario family only where pipelines, validation, constraints, projections or wiring contribute proof — see **[specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md)** (`profile: application`) and the **cratis-application-slice-specifications** skill.
15
+ > - **Event-sourced applications on Cratis** (commands, projections, reducers, reactors, constraints): use this base plus a direct call by default for pure `Handle()`, validator and reducer decisions with supplied read-model state; add scenarios only for pipeline/authorization wiring, constraints or declarative projections — see **[specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md)** (`profile: application`) and the **cratis-application-slice-specifications** skill.
16
16
 
17
17
  `Cratis.Specifications` keeps the approach of Machine.Specifications (MSpec): `Establish → Because → should_` maps to "Given → When → Then" and keeps each spec focused on *one setup, one action, one set of assertions*.
18
18
 
@@ -48,7 +48,7 @@ public class when_combining_parts : Specification
48
48
  | `[Fact] void should_*()` | one assertion per fact | use the `ShouldXxx()` extension methods |
49
49
  | `void Destroy()` | teardown once per eligible class by default | after each fact when per-fact lifecycle is selected |
50
50
 
51
- The shared lifecycle is the default on xUnit v2 and v3 since 5.0; the package registers the framework, so delete any hand-written `[assembly: Xunit.TestFramework(...)]` (it fails with `CS0579`). Eligible classes derive from `Specification`, have one public constructor with no arguments or only framework-managed fixture arguments, and contain only plain facts. Construction, Establish/Because and Destroy run once; all facts share the instance and must not mutate it. `IDisposable` specifications also share the lifecycle since Cratis.Specifications 4.4.0: one construction and one `Dispose` after the last fact. Initialization failure fails every fact. Theories and `ITestOutputHelper` constructors keep per-fact execution; `[PerFactSpecificationLifecycle]` opts a class out. `CRSPEC0008` (a fact modifies the shared context) is an error by default: move the change into `Establish` or `Because`. See the **cratis-specifications-csharp** skill's shared-lifecycle reference.
51
+ The shared lifecycle is the default on xUnit v2 and v3 since 5.0; the package registers the framework, so delete any hand-written `[assembly: Xunit.TestFramework(...)]` (it fails with `CS0579`). Eligible classes derive from `Specification`, have one public constructor with no arguments or only framework-managed fixture arguments, and contain only plain facts. Construction, Establish/Because and Destroy run once; all facts share the instance and must not mutate it. `IDisposable` specifications also share the lifecycle since Cratis.Specifications 4.4.0: one construction and one `Dispose` after the last fact. Initialization failure fails every fact. Theories and `ITestOutputHelper` constructors keep per-fact execution. Keep shared lifecycle on; `[PerFactSpecificationLifecycle]` opts out only the class whose facts genuinely mutate shared state and cannot move the mutation into `Because`. `CRSPEC0008` (a fact modifies the shared context) is an error by default: move the change into `Establish` or `Because`. See the **cratis-specifications-csharp** skill's shared-lifecycle reference.
52
52
 
53
53
  Run specifications as the built executable, not `dotnet test`: restore and build once, then run `bin/<Config>/net10.0/<Project>.Specs`. In a solution that mixes Microsoft.Testing.Platform and VSTest projects, run each project on its own; `dotnet test` on the whole solution does not work there. Filters and the 4.x → 5.0 checklist are in the **cratis-specifications-csharp** skill's [running-specs reference](../../../skills/cratis-specifications-csharp/references/running-specs.md).
54
54
 
@@ -104,7 +104,7 @@ Each distinct outcome deserves its own spec file. This keeps specs small, focuse
104
104
 
105
105
  - When a behavior has multiple outcomes, create a `when_<behavior>/` folder with separate files for each outcome.
106
106
  - For simple behaviors with a single outcome, use a single file: `when_<behavior>`.
107
- - Never write a single file that tests an entire class.
107
+ - Never write a single file that tests an entire class's unrelated behaviors. **C# declarative read-model mappings are the exception:** use one scenario class per read model, one shared event sequence with captured intermediate states, and many read-only facts for fields and lifecycle outcomes. See [specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md) for scenario selection and cost budgets.
108
108
 
109
109
  ## Reusable Context
110
110
 
@@ -15,15 +15,19 @@ Specs are **mandatory for every slice type**, including reactors.
15
15
 
16
16
  ## Start with the plain call; add a scenario where it adds proof
17
17
 
18
- A command's `Handle()` is an ordinary method; with dependencies resolved by `Provide()`, its decision can be a pure function of its arguments. Reducer logic and a reactor's decision can be pure functions too. **Write the plain call as the default spec for a pure decision**, with no host, services or harness. Add a scenario helper where the framework's composition is part of what you must prove: the command pipeline, validation, authorization, constraints, and projections (declarative, so only a scenario can exercise them). The scenario helpers are additive; use both only when the slice needs both kinds of proof. For application behavior prefer the in-process helpers over full out-of-process Chronicle host specs.
18
+ **Judgment-shaped convention:** call pure `Handle()`, validators, reducers and reactor decisions directly with their inputs, including read-model state. Add a scenario only for pipeline/authorization wiring, constraints or declarative projection mapping. Do not repeat every decision through the pipeline; name the extra behavior each scenario proves.
19
+
20
+ **Application specs supply state, not decision-read infrastructure.** Call `Handle()` or the validator directly with supplied read-model state. Never call `UseDecisionReads`, configure protection modes, seed events solely for infrastructure, or re-prove the platform's concurrency, conflict-refusal or isolation matrix; Arc/Chronicle specs own it. The application's **only decision-read spec obligation** is one cheap assembly-level shape-admission spec: `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` (`Cratis.Arc.Chronicle.Testing.Commands`). It checks the application's own models/projections without executing commands or running Chronicle, not the platform guards. Never simulate competing appends either (`AppendConcurrently` or any hand-built interleaving): concurrency is a framework concern a developer should not think about in tests, and Arc and Chronicle specs prove it. A rule that must hold under races is expressed in production by taking the read model as a `Handle` parameter under `[ReadProtection(On)]`; the spec gives that read model's state and asserts the outcome. Report harness state gaps upstream; never weaken production guards for speed. `AppendConcurrently` is an advanced framework-verification API: use only for an explicit need. Direction, no release claim: Arc will obsolete `UseDecisionReads`.
21
+
22
+ **Share setup and budget cost (judgment-shaped).** Keep shared lifecycle on; use `[PerFactSpecificationLifecycle]` only on a class whose facts genuinely mutate shared state and cannot move that mutation into `Because`. Plain decision specs should take roughly milliseconds; measure scenario facts and justify seconds by the extra behavior they prove. Compare TRX durations and suite wall time with unchanged concurrency; see [running-specs.md](../../../skills/cratis-specifications-csharp/references/running-specs.md). Do not multiply scenario startup by splitting assertions into classes or opting the project out of shared lifecycle.
19
23
 
20
24
  | Tool | Tests | Use when |
21
25
  |---|---|---|
22
- | `Specification` (plain call) | `Handle()`, `Handle(providedValue)`, reducer logic or a reactor's decision as a method call; or an isolated class with mocked collaborators | **default** for a decision that is a pure function of its arguments |
23
- | `CommandScenario<TCommand>` | the real Arc command pipeline — authorization + validators + `Provide()` + `Handle()` + appended events | the pipeline, validation, authorization, or appended events matter (State Change slices) |
26
+ | `Specification` (plain call) | `Handle()`, `Handle(providedValue)`, validator/reducer logic or a reactor's decision as a method call; or an isolated class with mocked collaborators | **default** for a decision that is a pure function of its arguments |
27
+ | `CommandScenario<TCommand>` | the real Arc command pipeline — authorization + validators + `Provide()` + `Handle()` + appended events | pipeline wiring, authorization or constraints adds proof |
24
28
  | `QueryScenario<TReadModel>` | query pipeline: argument conversion, authorization, validation, interceptors; `Observe` buffers emissions | query composition matters; real MongoDB observations still need infrastructure |
25
29
  | `EventScenario` | Chronicle-level append semantics, no command pipeline | constraint violations, raw stream sequencing/concurrency |
26
- | `ReadModelScenario<TReadModel>` | projection/reducer state from a sequence of events (auto-detects model-bound / fluent / reducer) | **default for projections**, which are declarative (State View slices) |
30
+ | `ReadModelScenario<TReadModel>` | projection/reducer state from a sequence of events (auto-detects model-bound / fluent / reducer) | one class per read model, many read-only assertions on shared setup |
27
31
  | `ReactorScenario<TReactor>` | reactor handler invocation + side effects via mocked services | the reactor wiring and side effects (Automation / Translation slices) |
28
32
 
29
33
  > **Out-of-process Chronicle integration specs are an advanced case** — reserve them for host wiring, real infrastructure, serialization/transport, or end-to-end boundaries the scenario helpers can't exercise. They are not the default vertical-slice test shape.
@@ -76,7 +80,7 @@ public class and_all_information_is_valid : Specification
76
80
  - `CommandScenario<TCommand>` exposes `Services` (an `IServiceCollection`), `Context`, `Execute(command[, ct])`, `Validate(command)`, `LastResult`, `Operations` and the operation assertions `ShouldHaveExecutedOperation<T>()` / `ShouldHaveCompensatedOperation<T>()` / `ShouldHaveNoOperationInvocations()`; with the Chronicle integration it also gains `Given`, `EventScenario`, `EventLog`, `EventSequence` and `AppendedEvents` as extension members. Everything else is configured through `Services` and asserted with extension methods.
77
81
  - **Event assertions are extension methods on the scenario** (from the Chronicle testing package), keyed by **command + event** type: `await _scenario.ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId)` or the `(eventSourceId, Func<TEvent,bool> predicate)` overload; plus `await _scenario.ShouldHaveTailSequenceNumber<TCommand>(...)`. They return `Task`, so the fact is `async Task`.
78
82
  - **`CommandResult` assertions** (Arc extensions): `ShouldBeSuccessful()`, `ShouldNotBeSuccessful()`, `ShouldBeValid()`, `ShouldHaveValidationErrors()`, `ShouldHaveValidationErrorFor(message)`, `ShouldBeAuthorized()`, `ShouldNotBeAuthorized()`, `ShouldHaveExceptions()`/`ShouldNotHaveExceptions()`.
79
- - **Seed prior state through `_scenario.Given` or `_scenario.Services`.** `_scenario.Given.ForEventSource(id).Events(...)` materializes the seeded events into an **in-memory read-model dictionary** the harness answers keyed reads from — in **legacy mode** it never reaches the event log (see the false-greens list below). To populate advisory read models the validator/`Provide()`/`Handle()` inject, substitute `IReadModels` and register it (`_scenario.Services.Replace(new ServiceDescriptor(typeof(IReadModels), mock))`, mocking `GetInstanceById(...)`) or register projections with `_scenario.Services.AddReadModels(...)`.
83
+ - **Supply read-model state for business decisions.** Prefer a direct call with that state; for pipeline proof, use the harness's state setup. Seed events only when the event history itself is relevant, not to accommodate read-protection plumbing.
80
84
  - **Validator/`Provide()` dependencies:** register them in `_scenario.Services`; Arc testing discovers the concrete validator automatically. When several specs need different injected validator states, test rejected variants by instantiating the validator directly (per-scenario state can be order-sensitive under parallel xUnit).
81
85
  - **`Provide()`:** drive it through `CommandScenario` end-to-end; when the handler's decision is pure given provided data, test `Handle(providedValue)` directly as the default decision spec and keep the scenario for the wiring.
82
86
  - **Command operations:** use **cratis-arc-command-operation** for decision/adapter specs and real `CommandScenario` execution, forward failure, compensation, and Chronicle commit rejection; a direct `Handle()` does not execute operations.
@@ -101,8 +105,8 @@ Each of these passes while proving nothing. They are the reason the two-assertio
101
105
  1. **A discarded assertion `Task`.** The `IEventSequence` assertions `ShouldHaveAppendedEvent`/`ShouldHaveTailSequenceNumber` (Chronicle Testing) are genuinely `async`: in a `void [Fact]` that does not `await` them the exception lands on a `Task` nobody observes and the spec passes regardless of behavior, while the compiler's `CS4014` stays silent outside an `async` method. (The same-named `CommandScenario` overloads in Arc's Chronicle.Testing throw synchronously and only *return* a completed `Task`, so those fail even when discarded — but you cannot tell the two apart at the call site.) Make the fact `async Task` and `await` every `Task`-returning `Should*`. Chronicle's analyzer **`CHR0039` (`NonAwaitedAssertion`)** reports any discarded `Task`/`ValueTask`-returning `Should*` call from a Cratis testing package (Chronicle's and Arc's alike).
102
106
  2. **A dependency the spec forgot to seed.** When a validator's constructor, `Provide()` or `Handle()` asks for a read model that does not exist for the command's key, the validator is never built and no rule runs — yet the command is rejected (one validation result, reason **`DependencyUnavailable`**, HTTP 400), so `ShouldNotBeSuccessful()` passes and keeps passing with the rule it is named after deleted. `ShouldHaveValidationErrors()` **refuses** a result whose only errors carry that reason and tells you to seed the read model — or, when that rejection *is* the behavior under spec, assert it deliberately with `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.DependencyUnavailable)`. A missing *service* (not a read model) surfaces as an exception instead (`ShouldHaveExceptions()`), and `ShouldNotBeSuccessful()` alone cannot tell the two apart from a genuine rejection.
103
107
  3. **`.Validate()` authorizes first.** `Validate(command)` runs the command filters, and authorization filters are ordered before validation. A validate-only spec on a `[Roles]`/`[Authorize]` command with no identity registered is denied for authorization — the rule it is named after never ran. Register the identity, or assert `ShouldNotBeAuthorized()` and say so in the name.
104
- 4. **Legacy-mode constraint preconditions go to the event log, not `CommandScenario.Given`.** `_scenario.Given.ForEventSource(id).Events(...)` on a `CommandScenario` seeds an **in-memory read-model materialization only**; nothing reaches the event log a `[Unique]`/`IConstraint` is enforced against, so a "second registration with the same name" spec passes because the first one was never appended. Seed constraint preconditions through **`EventScenario`**'s `Given`, and assert the violation by constraint **name** (`ShouldHaveConstraintViolationFor`) — the same helper exists on `CommandResult` and on `IAppendResult`.
105
- 5. **Legacy `GetInstances<T>` is not harness-backed.** Under legacy `CommandScenario` the read-model harness answers keyed reads (`GetInstanceById`) from its seeded dictionary and forwards `GetInstances<T>`/`GetSnapshotsById`/`Watch` to the real client, which is not there. Code under spec that enumerates a read model needs a fake lookup registered in `Services`, not `Given`.
108
+ 4. **Constraint preconditions must exist at the append boundary.** Use **`EventScenario`**'s `Given` for a duplicate-name constraint, and assert the violation by name (`ShouldHaveConstraintViolationFor`). A pinned read model alone cannot prove append-time uniqueness.
109
+ 5. **Advisory `IReadModels` is not a database provider.** `CommandScenario` backs keyed `GetInstanceById(ReadModelKey)` reads, but forwards `GetInstances<T>`, `GetSnapshotsById` and `Watch` to the real client. For a focused pipeline spec whose handler injects advisory `IReadModels`, bridge keyed state from a `ReadModelScenario<T>` seeded with `Given.ForEventSourceId(id).ReadModel(instance)`, registering `_scenario.Services.AddSingleton(readModelScenario.ReadModels)` before execution. For non-key searches/filters, register a lookup double in `Services`; event seeding alone does not back them. This is advisory state setup, not decision-read protection plumbing; pure decisions still take state directly.
106
110
  6. **One child is not a children spec.** `[ChildrenFrom<T>]` without `key:` identifies children by the **event source id**, so every child event on one stream upserts the same child and a one-child spec stays green with the key wrong. Drive **two** children with distinct keys and assert the count.
107
111
  7. **Independently derived expectations.** An expected value computed in the spec with the same expression as the code under spec agrees with it by construction. Write the expected literal.
108
112
 
@@ -110,23 +114,15 @@ The `ReadModelScenario` and `EventScenario` equivalents of these live in the **c
110
114
 
111
115
  > **Historical validator-state order-sensitivity:** observed in the 22.16-era harness. Arc 22.23.2 stopped replacing the process-wide provider; later releases isolated scenario stores and event-source services. Reproduce at 22.75.0 before applying the old direct-validator/small `[Collection]` workaround. No current reproduction is verified here.
112
116
 
113
- #### Query and protected-decision modes (Arc v22.75.0)
117
+ #### Query pipeline
114
118
 
115
119
  `QueryScenario<TReadModel>` (`Cratis.Arc.Testing.Queries`) runs `Perform(nameof(Model.Method),
116
120
  arguments?, paging?, sorting?)`; `Observe(...)` returns `QueryObservation.Initial`
117
121
  and buffered `Next()`. Configure principal/policies in `Services`; schemes fail
118
122
  closed without HTTP. Chronicle query `Given` can seed keyed models/events.
119
123
 
120
- For guarded commands use plain `new CommandScenario<T>()`: it selects the decision
121
- mode from the command's read-protection mode and dependencies (`UseDecisionReads()`
122
- is hidden and unnecessary). Given state, guarded reads and appends share one real
123
- in-process log, unlike legacy mode above; an `Off` command with no protected
124
- dependency stays in legacy mode. Pinned models cannot supply guards. Use `OnRoute(route)` and
125
- `AppendConcurrently` with `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`
126
- to prove conflicts (there is no `scenario.AppendConditions`); `ShouldHaveCompletedStream` proves
127
- completion and `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)`
128
- checks admission. See **cratis-application-slice-specifications**,
129
- `references/query-and-decision-scenarios.md`, for sources and limitations.
124
+ For query setup, see **cratis-application-slice-specifications**,
125
+ `references/query-and-decision-scenarios.md`. Framework read-protection guards are not application spec obligations.
130
126
 
131
127
  ### `EventScenario` — constraints & append semantics
132
128
 
@@ -148,7 +144,7 @@ async Task Because() =>
148
144
 
149
145
  ### `ReadModelScenario<TReadModel>` — declarative projections and reducer wiring
150
146
 
151
- The default for declarative projections; pure reducer decisions start with a direct call. Add this scenario for reducer dispatch, filtering, metadata or read-model wiring. It drives events into the projection/reducer and asserts the resulting state. Use xUnit `Assert.*` on `_scenario.Instance`.
147
+ The default for declarative projections; pure reducer decisions start with a direct call. Use **one scenario class per read model**, not one per property, mapping attribute or lifecycle event. Apply its event sequence once, capture intermediate states in `Because` where needed, then assert fields, children, counters and lifecycle outcomes in many read-only facts. Add a reducer scenario only for dispatch, filtering, metadata or read-model wiring. Use xUnit `Assert.*` on `_scenario.Instance`.
152
148
 
153
149
  ```csharp
154
150
  ReadModelScenario<Author> _scenario = null!;
@@ -162,7 +158,7 @@ async Task Because() =>
162
158
 
163
159
  Use the named-parameter constructor when services/initial state are needed: `new ReadModelScenario<Author>(initialState: null, serviceProvider: services)`. Pre-seed a keyed read model with `Given.ForEventSourceId(id).ReadModel(instance)` for code that calls `IReadModels.GetInstanceById`. Cross-stream projections (`UsingKey`/`UsingParentKey`) are supported — seed each contributing stream with its own `Given.ForEventSource(...)`.
164
160
 
165
- **Bridging `IReadModels` into a `CommandScenario`** (current harness limitation): when a command handler injects `IReadModels` directly, build a focused `ReadModelScenario<T>`, seed it via `Given.ForEventSourceId(id).ReadModel(...)`, then register its read models into the command scenario — `_scenario.Services.AddSingleton(readModelScenario.ReadModels)`. Pass `scenario.ReadModels` into code under test for keyed `GetInstanceById` reads; for non-key searches/filters, register a fake lookup or an `IMongoCollection<T>` mock in the scenario services instead.
161
+ Do not build a projection scenario just to supply state to a pure command decision. Pass the read model directly, or substitute the application's lookup collaborator. Provider behavior belongs in provider integration specs.
166
162
 
167
163
  **Chronicle v19.50.0 scenario additions** (`Source/Clients/Testing/ReadModels/`):
168
164
  `WithProjection(Action<IProjectionBuilderFor<T>>)` overrides discovery, including
@@ -14,6 +14,7 @@ authored a file or preserve a chain of evidence about how it arrived.
14
14
  - Run one phase per command with its real exit status. Budget targeted checks around 120 seconds and build/test phases around 300 seconds; explain any longer budget first, and obtain explicit approval before a phase exceeds 600 seconds. Stop on a failure; do not blindly retry or repeat passing tests on unchanged inputs without a flaky-test hypothesis or required matrix.
15
15
  - Separate status/fetch from builds, Debug from Release, each test project, and frontend lint/compile/test/build. Do not combine these into an opaque command with a large outer timeout. Expose progress and cancellation; report the precise failed or timed-out phase and do not continue dependent phases after failure.
16
16
  - Never use `build/test | grep | sort/head/tail` as gate evidence. Capture the producer's exit status before summarising output; `pipefail` alone is insufficient with early-closing consumers such as `head`. Printed zero-error counts are not proof of success. Preserve needed full diagnostics in task-scoped `.ai-work/` logs rather than hiding them in `/tmp`.
17
+ - Fast feedback is a product requirement: a spec takes milliseconds, the suite seconds; specs share setup and run in parallel; analyzers must not halt builds. CI builds the spec path without analyzers and enforces them, warnings as errors, in a parallel required job; the pre-push gate runs them. A slowdown is a defect.
17
18
  - Add a focused specification for every behavior or rejection rule you change.
18
19
  - Keep each check deterministic and runnable locally and in CI.
19
20
  - Never replace a real behavior check with a checksum, inventory, generated
@@ -21,7 +21,7 @@ This file is the reference for *what* goes in each part of a slice. Layout, slic
21
21
 
22
22
  - .NET / C# (ASP.NET Core) — **Cratis Arc** for CQRS / model-bound commands and queries, **Cratis Chronicle** for event sourcing, MongoDB or EF Core for read models.
23
23
  - React + TypeScript (Vite) — Cratis Components 4.x + Arc-generated proxies, MVVM. Vitest + Mocha/Chai/Sinon for frontend specs.
24
- - xUnit + Cratis.Specifications + NSubstitute for C# specs (`Specification` plus a direct call by default for pure decisions; add the `*Scenario` family only where pipelines, validation, constraints, projections or wiring contribute proof — see [specs.md](./specs.instructions.md)).
24
+ - xUnit + Cratis.Specifications + NSubstitute for C# specs (`Specification` plus a direct call for pure command/validator/reducer decisions with supplied state; add scenarios only for pipeline/authorization wiring, constraints or declarative projections — see [specs.md](./specs.instructions.md)).
25
25
 
26
26
  ## Proxy generation — the build dependency
27
27
 
@@ -433,8 +433,10 @@ reads/exact scopes or read-model parameters under read protection `On`
433
433
  (`[ProtectedDecision]` + `DecisionRead<T>` is the older per-command opt-in);
434
434
  validator/`Provide()`/`Handle()` injection under `Off` is advisory. Stream-level
435
435
  `concurrency: true` scopes are another explicit boundary, not protection for an
436
- arbitrary snapshot. Specify conflicts with `AppendConcurrently` and actual seeded
437
- events, not pinned models.
436
+ arbitrary snapshot. Specify business decisions with direct `Handle()`/validator
437
+ calls and supplied model state. Follow [specs.scenarios.csharp.md](./specs.scenarios.csharp.instructions.md):
438
+ one cheap assembly-level application-shape admission spec; the platform guard
439
+ matrix belongs only in Arc/Chronicle specs.
438
440
 
439
441
  ## Dialogs
440
442
 
@@ -41,13 +41,13 @@ Select from these canonical rules in `.cratis/ai/rules/` only after applying the
41
41
 
42
42
  ## C# specs — start with the plain call
43
43
 
44
- Specify pure decisions with plain calls first: `Handle()`, `Handle(providedValue)`, reducers and reactor logic. Add the four in-process scenario helpers where the pipeline, validation, constraints or projections contribute proof; prefer them over out-of-process Chronicle host specs for those behaviors:
44
+ Specify pure `Handle()`, validator, reducer and reactor decisions with plain calls and supplied read-model state. Follow `.cratis/ai/rules/specs.scenarios.csharp.md`: scenarios add only pipeline/authorization wiring, constraints, declarative mapping or application-owned race-policy proof, not a platform guard matrix. Keep the single cheap assembly-level admission spec for the application's own decision-model shapes. Prefer in-process helpers over out-of-process Chronicle hosts for the applicable behaviors:
45
45
 
46
46
  | Tool | Use for |
47
47
  |---|---|
48
48
  | `CommandScenario<TCommand>` | **State Change** — runs authorization + validators + `Provide()` + `Handle()` + appended events |
49
49
  | `EventScenario` | constraint violations, raw append/sequencing semantics |
50
- | `ReadModelScenario<TReadModel>` | **State View** — projection/reducer state from a sequence of events |
50
+ | `ReadModelScenario<TReadModel>` | **State View** — one class per read model, many read-only assertions on shared setup |
51
51
  | `ReactorScenario<TReactor>` | **Automation / Translation** — reactor invocation + side effects |
52
52
 
53
53
  Reserve out-of-process integration specs for host/transport/infra boundaries the scenario helpers can't exercise.
@@ -85,14 +85,14 @@ public class and_all_information_is_valid : Specification
85
85
  #endif
86
86
  ```
87
87
 
88
- (`CommandScenario` event assertions are extension methods keyed by command + event type — `await _scenario.ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId[, predicate])`; seed prior state through `_scenario.Given.ForEventSource(id).Events(...)` or `_scenario.Services`. The mode matters: in legacy mode `Given` materializes an in-memory read-model dictionary only, so constraint preconditions go through `EventScenario`'s `Given`; in decision mode (selected automatically from the command's read-protection mode and dependencies) `Given` writes to the scenario's real log and `EventScenario` is unavailable (accessing it throws), so seed everything through `Given`. Check which mode the spec's scenario uses; see `specs.scenarios.csharp.md` and `cratis-application-slice-specifications`.)
88
+ `CommandScenario` event assertions are extension methods keyed by command + event type: `await _scenario.ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId[, predicate])`. Supply model state for business decisions; event seeding belongs to mapping/history/constraint proof, not read-protection plumbing. See `specs.scenarios.csharp.md` and `cratis-application-slice-specifications` for focused pipeline setup and advisory lookup pitfalls.
89
89
 
90
90
  ### What to specify
91
91
 
92
- 0. **Every contract specification first** — one spec each, named after it, using the contract's example values. Then add the code-derived cases below that the contract lacks and report them as proposals for the contract (the `.play` model when one covers the slice, otherwise the agreed outline); never present them as contract coverage. A `.play` specification maps to a plain-call spec for a pure decision, with a scenario added where the pipeline, validation, constraints or projections matter; never edit, skip or delete a spec derived from the contract to make code pass — change the code or return an edit request.
92
+ 0. **Every contract specification first** — one spec each, named after it, using the contract's example values. Then add the code-derived cases below that the contract lacks and report them as proposals for the contract (the `.play` model when one covers the slice, otherwise the agreed outline); never present them as contract coverage. A `.play` specification maps to a plain-call spec for a pure decision, with a scenario added only for the pipeline composition it proves (never a simulated race); never edit, skip or delete a spec derived from the contract to make code pass — change the code or return an edit request.
93
93
  1. **Happy path** — succeeds, correct event(s) appended.
94
- 2. **Each validation failure** — assert **both** `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`. Do not assert on message strings by default; assert exact text only when that wording is the specified behavior, and name that behavior in the fact. Violate **only** the rule the spec is named after (build the command valid in every other respect), or a neighbouring rule makes it pass.
95
- 3. **Business-rule violations** — each `Result<,>` rejection / guarded decision condition. Plain injection under `Off` is advisory; for read protection `On`, `[ProtectedDecision]` or stream decisions use `new CommandScenario<T>()` with seeded events, `AppendConcurrently` and `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`, not pinned models and not `UseDecisionReads()`.
94
+ 2. **Each validation failure** — call the validator directly with supplied state; pipeline validation specs assert **both** `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`. Do not assert on message strings by default; assert exact text only when that wording is the specified behavior, and name that behavior in the fact. Violate **only** the rule the spec is named after (build the command valid in every other respect), or a neighbouring rule makes it pass.
95
+ 3. **Business-rule violations** — call `Handle()` or the validator directly with the required model state and assert each rejection. Arc/Chronicle specs alone prove platform concurrency, conflict refusal and isolation. The application's only decision-read spec obligation is the single cheap shape-admission spec in `specs.scenarios.csharp.md`; do not repeat guard scenarios per decision.
96
96
  4. **Constraint violations** — `ShouldHaveConstraintViolationFor(name)` via `EventScenario`.
97
97
  5. **Authorization** — `ShouldNotBeAuthorized()` (an unauthorized result has no validation errors).
98
98
  6. **Repeat execution**, when a reactor invokes the command — the same command twice for the same source; specify whether the second is rejected, a no-op, or appends again.
@@ -16,7 +16,7 @@ Enforce a new rule on an existing command. Invoke the **cratis-arc-command-valid
16
16
  - Reusable value invariant → `ConceptValidator<T>`.
17
17
  - Command-input / cross-field / pre-handler rule → `CommandValidator<T>`.
18
18
  - Handler needs fetched data first → `Provide()`.
19
- - State rule that must hold **under concurrency** → new code sets read protection `On` (`<CratisReadProtection>On</CratisReadProtection>`): the read model is a `CommandValidator<T>` constructor parameter and `Handle()` returns the events; add `[LookUpBy]` for a second entity, a per-stream read model or `IStreamReads`/`StreamDecision` for one stream. `[ProtectedDecision]` with `DecisionRead<T>` is the older per-command opt-in. Under `Off` (the default) a validator over a plain read is a hint, and `Result<TEvent, ValidationResult>` alone adds no guard. Specify the race with `AppendConcurrently` and seeded events, not pinned models.
19
+ - State rule that must hold **under concurrency** → new code sets read protection `On` (`<CratisReadProtection>On</CratisReadProtection>`): the read model is a `CommandValidator<T>` constructor parameter and `Handle()` returns the events; add `[LookUpBy]` for a second entity, a per-stream read model or `IStreamReads`/`StreamDecision` for one stream. `[ProtectedDecision]` with `DecisionRead<T>` is the older per-command opt-in. Under `Off` (the default) a validator over a plain read is a hint, and `Result<TEvent, ValidationResult>` alone adds no guard. Specify the business decision by calling `Handle()` or the validator directly with supplied read-model state. Follow `.cratis/ai/rules/specs.scenarios.csharp.md`: keep one cheap assembly-level shape-admission spec; the platform's concurrency/guard matrix belongs only in Arc/Chronicle specs.
20
20
  - Uniqueness → `[Unique]` / `IConstraint`.
21
21
 
22
- > **Never throw for normal business rejection** — a throw is HTTP 500, not a validation error. Recoverable rejection is a `ValidationResult` / `Result<,>`. Add a spec for the failure case (assert both not-successful and has-validation-errors). The skill carries the detail; don't duplicate it here.
22
+ > **Never throw for normal business rejection** — a throw is HTTP 500, not a validation error. Recoverable rejection is a `ValidationResult` / `Result<,>`. Add a direct-call failure spec; a pipeline validation spec asserts both not-successful and has-validation-errors. The skill carries the detail; don't duplicate it here.
@@ -73,7 +73,7 @@ Tagged **[contract]** (framework-enforced) or **[convention]** (house default).
73
73
  3. **Code level** — infrastructure (hosting, DI composition, identity wiring, CI, deployment), clients, Screenplay code attachments and handlers, adapters in the render's customization area, and scope Stage cannot render yet (the model remains the contract). Framework repositories and brownfield work that is not opted in also stay code-first. Only the entry-point session may propose a model, at most once per session and never for trivial, bug-fix, infrastructure, client, framework or brownfield-maintenance work; if declined, do not ask again. Confirm Module/Feature, slice name, slice type and domain requirements as before; for new behavior or unclear event vocabulary run **cratis-chronicle-event-modeling** first, and record the agreed outline (fields, events, rules, scenarios) where the team tracks work, if anywhere. If you write code first where a model exists, say the model now lags.
74
74
  4. **Trivial changes** keep the proportional-delegation policy; Phases 1–3 below apply to code you write at either level.
75
75
  - **Phase 1 — Backend.** Implement a coherent slice change. **Gate:** incremental Debug build of the affected project (regenerates proxies, compiles `#if DEBUG` spec code).
76
- - **Phase 2 — Specs.** Mandatory for every slice type. Start with plain-call specs for pure decisions; add in-process scenarios (`CommandScenario<T>`, `EventScenario`, `ReadModelScenario<T>`, `ReactorScenario<T>`) where pipelines, validation, constraints or projections contribute proof. **Gate:** tests pass.
76
+ - **Phase 2 — Specs.** Mandatory for every slice type. Start with plain calls for pure command/validator/reducer decisions with supplied read-model state; add scenarios only for pipeline/authorization wiring, constraints or declarative mapping. Follow [specs.scenarios.csharp.md](./specs.scenarios.csharp.md) for shared setup and cost budgets. **Gate:** tests pass.
77
77
  - **Phase 3 — Frontend.** Build from the generated proxies, register in the composition page, wire routing. **Gate:** lint, conditional test, build.
78
78
 
79
79
  **Backend before frontend, always** — the frontend depends on proxies that exist only after a successful build. Generate proxies from the **Debug** build (house convention); verify Release without re-running the generator: `dotnet build -c Release -p:CratisProxiesOutputPath=`. Why that property, and what to do when nothing is generated: the **cratis-arc-command** skill's proxy-generation reference.
@@ -49,6 +49,7 @@ Repo conventions follow from this: `Source/DotNET` + `Source/JavaScript` for dua
49
49
  - **Backward compatibility is a contract.** A change to a public type, attribute, interface, or generated-proxy shape is a breaking change for every downstream app — label PRs by semver impact (`major`/`minor`/`patch`) and treat removals/renames of public surface as `major`.
50
50
  - **Convention discovery is built here.** `IInstancesOf<T>` / `IImplementationsOf<T>`, attribute-based discovery, and source generation are the mechanisms the framework *provides*; use them internally too rather than hand-registration where a convention fits. A Fundamentals project reference does not carry the type-discovery analyzer like its NuGet package does: reference `Fundamentals.TypeDiscovery.Generator` explicitly, or your project's types may be absent from the generated universe.
51
51
  - **Source generators / analyzers** (Arc's proxy generator, Fundamentals) follow Roslyn conventions; their output is consumed verbatim by apps, so treat generated shape as public API.
52
+ - **Analyzers and generators are written for speed** (fast feedback is a product requirement): incremental generators only; a syntactic prefilter (name or kind) before any semantic bind; compute shared evidence once per compilation, never once per analyzer or per symbol; touch it only after cheap relevance checks; never rebind every call site of the compilation. Each expensive analyzer gets a performance spec.
52
53
  - **Orleans grains** in the Chronicle kernel follow `orleans.md`.
53
54
  - **An alternate implementation of an interface must match the primary one's *semantics*, not just its signature.** Chronicle ships several implementations of the same storage interfaces (MongoDB, SQL, in-memory) and the in-memory ones exist precisely so the real kernel code paths can run without infrastructure. When you touch one, **diff it against the persistent implementations** — the signature tells you nothing about the contract. The specific trap: query criteria carry **sentinels meaning "do not narrow"** (`EventSourceId.Unspecified`, `EventSourceType.Unspecified`/`Default`, `EventStreamType.All`, `EventStreamId.Default`, an empty event-type set). Callers asking for "everything" pass those sentinels, never `null`, so a plain `is not null` check narrows every row away and the read silently returns nothing. A divergence here does not fail — it makes specs pass vacuously, which is worse.
54
55
  - **A stub that silently succeeds is a bug, not a placeholder.** An unimplemented method returning `Task.CompletedTask`/an empty result lets a spec assert on work that never happened. Implement it, or make it fail loudly — never leave it quietly lying.
@@ -13,7 +13,7 @@ Profile-specific rules declare a **`profile:`** in their frontmatter (`applicati
13
13
 
14
14
  > **Arc is a standalone CQRS framework — not bound to event sourcing.** Even within the application profile, Arc provides model-bound commands/queries, validation, authorization, and full-stack proxy generation, and works **without** Chronicle (Arc.Core does not depend on Chronicle). A `[Command]` `Handle()` does not *have* to append events — it can return a response, return `void`, or work through injected services. The event-sourcing behavior (a returned event gets appended; `EventForEventSourceId`; "never inject `IEventLog`") comes from the **Arc + Chronicle** integration. This application is event-sourced, so the slice guidance assumes event-sourced commands — read the event-centric rules as the *house default for this app*, not universal Arc laws.
15
15
 
16
- The framework is convention-over-configuration. **Idiomatic Cratis is the goal — not custom abstractions over it.** When something is unclear, prefer the Cratis convention; do not invent. The rules and skills under `.cratis/ai/` are the authoritative answer — if your question is not answered there, ask rather than inferring framework behavior from package internals.
16
+ The framework is convention-over-configuration. **Idiomatic Cratis is the goal — not custom abstractions over it.** Always recommend the idiomatic way unless explicitly needed otherwise. When something is unclear, prefer the Cratis convention; do not invent. The rules and skills under `.cratis/ai/` are the authoritative answer — if your question is not answered there, ask rather than inferring framework behavior from package internals.
17
17
 
18
18
  ## Project Philosophy
19
19
 
@@ -52,6 +52,7 @@ obtaining credentials, and other local conventions ("Product policy" above).
52
52
  - Project context may explain which approved secret mechanism or local setup to
53
53
  use, but it must never contain credential values, tokens, keys, passwords, or
54
54
  other secrets.
55
+ - A repository's accepted decision records (`decisions/`) and project rules bind AI work: read those matching your paths, follow them, never work around one.
55
56
  - Project-specific guidance wins when it deliberately narrows shared behavior,
56
57
  but it may not weaken organization security, authorization, or required
57
58
  quality gates.
@@ -11,7 +11,7 @@ Extends the base [specs.md](./specs.md) (folder structure, naming, philosophy) w
11
11
 
12
12
  > **Which spec surface?**
13
13
  > - **Framework / library code** (Chronicle kernel, Arc pipeline, source generators, Fundamentals): the `Specification` base + NSubstitute in this file is the dominant mode — unit-test the classes under test in isolation. Reach for a `*Scenario` helper (in [specs.scenarios.csharp.md](./specs.scenarios.csharp.md)) **only** when testing the very engine your repo provides — Arc → `CommandScenario`, Chronicle → the event/projection/reactor scenarios. The **cratis-application-slice-specifications** skill is application-oriented, not for general framework specs.
14
- > - **Event-sourced applications on Cratis** (commands, projections, reducers, reactors, constraints): use this base plus a direct call by default for pure decisions; add the in-process scenario family only where pipelines, validation, constraints, projections or wiring contribute proof — see **[specs.scenarios.csharp.md](./specs.scenarios.csharp.md)** (`profile: application`) and the **cratis-application-slice-specifications** skill.
14
+ > - **Event-sourced applications on Cratis** (commands, projections, reducers, reactors, constraints): use this base plus a direct call by default for pure `Handle()`, validator and reducer decisions with supplied read-model state; add scenarios only for pipeline/authorization wiring, constraints or declarative projections — see **[specs.scenarios.csharp.md](./specs.scenarios.csharp.md)** (`profile: application`) and the **cratis-application-slice-specifications** skill.
15
15
 
16
16
  `Cratis.Specifications` keeps the approach of Machine.Specifications (MSpec): `Establish → Because → should_` maps to "Given → When → Then" and keeps each spec focused on *one setup, one action, one set of assertions*.
17
17
 
@@ -47,7 +47,7 @@ public class when_combining_parts : Specification
47
47
  | `[Fact] void should_*()` | one assertion per fact | use the `ShouldXxx()` extension methods |
48
48
  | `void Destroy()` | teardown once per eligible class by default | after each fact when per-fact lifecycle is selected |
49
49
 
50
- The shared lifecycle is the default on xUnit v2 and v3 since 5.0; the package registers the framework, so delete any hand-written `[assembly: Xunit.TestFramework(...)]` (it fails with `CS0579`). Eligible classes derive from `Specification`, have one public constructor with no arguments or only framework-managed fixture arguments, and contain only plain facts. Construction, Establish/Because and Destroy run once; all facts share the instance and must not mutate it. `IDisposable` specifications also share the lifecycle since Cratis.Specifications 4.4.0: one construction and one `Dispose` after the last fact. Initialization failure fails every fact. Theories and `ITestOutputHelper` constructors keep per-fact execution; `[PerFactSpecificationLifecycle]` opts a class out. `CRSPEC0008` (a fact modifies the shared context) is an error by default: move the change into `Establish` or `Because`. See the **cratis-specifications-csharp** skill's shared-lifecycle reference.
50
+ The shared lifecycle is the default on xUnit v2 and v3 since 5.0; the package registers the framework, so delete any hand-written `[assembly: Xunit.TestFramework(...)]` (it fails with `CS0579`). Eligible classes derive from `Specification`, have one public constructor with no arguments or only framework-managed fixture arguments, and contain only plain facts. Construction, Establish/Because and Destroy run once; all facts share the instance and must not mutate it. `IDisposable` specifications also share the lifecycle since Cratis.Specifications 4.4.0: one construction and one `Dispose` after the last fact. Initialization failure fails every fact. Theories and `ITestOutputHelper` constructors keep per-fact execution. Keep shared lifecycle on; `[PerFactSpecificationLifecycle]` opts out only the class whose facts genuinely mutate shared state and cannot move the mutation into `Because`. `CRSPEC0008` (a fact modifies the shared context) is an error by default: move the change into `Establish` or `Because`. See the **cratis-specifications-csharp** skill's shared-lifecycle reference.
51
51
 
52
52
  Run specifications as the built executable, not `dotnet test`: restore and build once, then run `bin/<Config>/net10.0/<Project>.Specs`. In a solution that mixes Microsoft.Testing.Platform and VSTest projects, run each project on its own; `dotnet test` on the whole solution does not work there. Filters and the 4.x → 5.0 checklist are in the **cratis-specifications-csharp** skill's [running-specs reference](../skills/cratis-specifications-csharp/references/running-specs.md).
53
53
 
@@ -103,7 +103,7 @@ Each distinct outcome deserves its own spec file. This keeps specs small, focuse
103
103
 
104
104
  - When a behavior has multiple outcomes, create a `when_<behavior>/` folder with separate files for each outcome.
105
105
  - For simple behaviors with a single outcome, use a single file: `when_<behavior>`.
106
- - Never write a single file that tests an entire class.
106
+ - Never write a single file that tests an entire class's unrelated behaviors. **C# declarative read-model mappings are the exception:** use one scenario class per read model, one shared event sequence with captured intermediate states, and many read-only facts for fields and lifecycle outcomes. See [specs.scenarios.csharp.md](./specs.scenarios.csharp.md) for scenario selection and cost budgets.
107
107
 
108
108
  ## Reusable Context
109
109
 
@@ -14,15 +14,19 @@ Specs are **mandatory for every slice type**, including reactors.
14
14
 
15
15
  ## Start with the plain call; add a scenario where it adds proof
16
16
 
17
- A command's `Handle()` is an ordinary method; with dependencies resolved by `Provide()`, its decision can be a pure function of its arguments. Reducer logic and a reactor's decision can be pure functions too. **Write the plain call as the default spec for a pure decision**, with no host, services or harness. Add a scenario helper where the framework's composition is part of what you must prove: the command pipeline, validation, authorization, constraints, and projections (declarative, so only a scenario can exercise them). The scenario helpers are additive; use both only when the slice needs both kinds of proof. For application behavior prefer the in-process helpers over full out-of-process Chronicle host specs.
17
+ **Judgment-shaped convention:** call pure `Handle()`, validators, reducers and reactor decisions directly with their inputs, including read-model state. Add a scenario only for pipeline/authorization wiring, constraints or declarative projection mapping. Do not repeat every decision through the pipeline; name the extra behavior each scenario proves.
18
+
19
+ **Application specs supply state, not decision-read infrastructure.** Call `Handle()` or the validator directly with supplied read-model state. Never call `UseDecisionReads`, configure protection modes, seed events solely for infrastructure, or re-prove the platform's concurrency, conflict-refusal or isolation matrix; Arc/Chronicle specs own it. The application's **only decision-read spec obligation** is one cheap assembly-level shape-admission spec: `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` (`Cratis.Arc.Chronicle.Testing.Commands`). It checks the application's own models/projections without executing commands or running Chronicle, not the platform guards. Never simulate competing appends either (`AppendConcurrently` or any hand-built interleaving): concurrency is a framework concern a developer should not think about in tests, and Arc and Chronicle specs prove it. A rule that must hold under races is expressed in production by taking the read model as a `Handle` parameter under `[ReadProtection(On)]`; the spec gives that read model's state and asserts the outcome. Report harness state gaps upstream; never weaken production guards for speed. `AppendConcurrently` is an advanced framework-verification API: use only for an explicit need. Direction, no release claim: Arc will obsolete `UseDecisionReads`.
20
+
21
+ **Share setup and budget cost (judgment-shaped).** Keep shared lifecycle on; use `[PerFactSpecificationLifecycle]` only on a class whose facts genuinely mutate shared state and cannot move that mutation into `Because`. Plain decision specs should take roughly milliseconds; measure scenario facts and justify seconds by the extra behavior they prove. Compare TRX durations and suite wall time with unchanged concurrency; see [running-specs.md](../skills/cratis-specifications-csharp/references/running-specs.md). Do not multiply scenario startup by splitting assertions into classes or opting the project out of shared lifecycle.
18
22
 
19
23
  | Tool | Tests | Use when |
20
24
  |---|---|---|
21
- | `Specification` (plain call) | `Handle()`, `Handle(providedValue)`, reducer logic or a reactor's decision as a method call; or an isolated class with mocked collaborators | **default** for a decision that is a pure function of its arguments |
22
- | `CommandScenario<TCommand>` | the real Arc command pipeline — authorization + validators + `Provide()` + `Handle()` + appended events | the pipeline, validation, authorization, or appended events matter (State Change slices) |
25
+ | `Specification` (plain call) | `Handle()`, `Handle(providedValue)`, validator/reducer logic or a reactor's decision as a method call; or an isolated class with mocked collaborators | **default** for a decision that is a pure function of its arguments |
26
+ | `CommandScenario<TCommand>` | the real Arc command pipeline — authorization + validators + `Provide()` + `Handle()` + appended events | pipeline wiring, authorization or constraints adds proof |
23
27
  | `QueryScenario<TReadModel>` | query pipeline: argument conversion, authorization, validation, interceptors; `Observe` buffers emissions | query composition matters; real MongoDB observations still need infrastructure |
24
28
  | `EventScenario` | Chronicle-level append semantics, no command pipeline | constraint violations, raw stream sequencing/concurrency |
25
- | `ReadModelScenario<TReadModel>` | projection/reducer state from a sequence of events (auto-detects model-bound / fluent / reducer) | **default for projections**, which are declarative (State View slices) |
29
+ | `ReadModelScenario<TReadModel>` | projection/reducer state from a sequence of events (auto-detects model-bound / fluent / reducer) | one class per read model, many read-only assertions on shared setup |
26
30
  | `ReactorScenario<TReactor>` | reactor handler invocation + side effects via mocked services | the reactor wiring and side effects (Automation / Translation slices) |
27
31
 
28
32
  > **Out-of-process Chronicle integration specs are an advanced case** — reserve them for host wiring, real infrastructure, serialization/transport, or end-to-end boundaries the scenario helpers can't exercise. They are not the default vertical-slice test shape.
@@ -75,7 +79,7 @@ public class and_all_information_is_valid : Specification
75
79
  - `CommandScenario<TCommand>` exposes `Services` (an `IServiceCollection`), `Context`, `Execute(command[, ct])`, `Validate(command)`, `LastResult`, `Operations` and the operation assertions `ShouldHaveExecutedOperation<T>()` / `ShouldHaveCompensatedOperation<T>()` / `ShouldHaveNoOperationInvocations()`; with the Chronicle integration it also gains `Given`, `EventScenario`, `EventLog`, `EventSequence` and `AppendedEvents` as extension members. Everything else is configured through `Services` and asserted with extension methods.
76
80
  - **Event assertions are extension methods on the scenario** (from the Chronicle testing package), keyed by **command + event** type: `await _scenario.ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId)` or the `(eventSourceId, Func<TEvent,bool> predicate)` overload; plus `await _scenario.ShouldHaveTailSequenceNumber<TCommand>(...)`. They return `Task`, so the fact is `async Task`.
77
81
  - **`CommandResult` assertions** (Arc extensions): `ShouldBeSuccessful()`, `ShouldNotBeSuccessful()`, `ShouldBeValid()`, `ShouldHaveValidationErrors()`, `ShouldHaveValidationErrorFor(message)`, `ShouldBeAuthorized()`, `ShouldNotBeAuthorized()`, `ShouldHaveExceptions()`/`ShouldNotHaveExceptions()`.
78
- - **Seed prior state through `_scenario.Given` or `_scenario.Services`.** `_scenario.Given.ForEventSource(id).Events(...)` materializes the seeded events into an **in-memory read-model dictionary** the harness answers keyed reads from — in **legacy mode** it never reaches the event log (see the false-greens list below). To populate advisory read models the validator/`Provide()`/`Handle()` inject, substitute `IReadModels` and register it (`_scenario.Services.Replace(new ServiceDescriptor(typeof(IReadModels), mock))`, mocking `GetInstanceById(...)`) or register projections with `_scenario.Services.AddReadModels(...)`.
82
+ - **Supply read-model state for business decisions.** Prefer a direct call with that state; for pipeline proof, use the harness's state setup. Seed events only when the event history itself is relevant, not to accommodate read-protection plumbing.
79
83
  - **Validator/`Provide()` dependencies:** register them in `_scenario.Services`; Arc testing discovers the concrete validator automatically. When several specs need different injected validator states, test rejected variants by instantiating the validator directly (per-scenario state can be order-sensitive under parallel xUnit).
80
84
  - **`Provide()`:** drive it through `CommandScenario` end-to-end; when the handler's decision is pure given provided data, test `Handle(providedValue)` directly as the default decision spec and keep the scenario for the wiring.
81
85
  - **Command operations:** use **cratis-arc-command-operation** for decision/adapter specs and real `CommandScenario` execution, forward failure, compensation, and Chronicle commit rejection; a direct `Handle()` does not execute operations.
@@ -100,8 +104,8 @@ Each of these passes while proving nothing. They are the reason the two-assertio
100
104
  1. **A discarded assertion `Task`.** The `IEventSequence` assertions `ShouldHaveAppendedEvent`/`ShouldHaveTailSequenceNumber` (Chronicle Testing) are genuinely `async`: in a `void [Fact]` that does not `await` them the exception lands on a `Task` nobody observes and the spec passes regardless of behavior, while the compiler's `CS4014` stays silent outside an `async` method. (The same-named `CommandScenario` overloads in Arc's Chronicle.Testing throw synchronously and only *return* a completed `Task`, so those fail even when discarded — but you cannot tell the two apart at the call site.) Make the fact `async Task` and `await` every `Task`-returning `Should*`. Chronicle's analyzer **`CHR0039` (`NonAwaitedAssertion`)** reports any discarded `Task`/`ValueTask`-returning `Should*` call from a Cratis testing package (Chronicle's and Arc's alike).
101
105
  2. **A dependency the spec forgot to seed.** When a validator's constructor, `Provide()` or `Handle()` asks for a read model that does not exist for the command's key, the validator is never built and no rule runs — yet the command is rejected (one validation result, reason **`DependencyUnavailable`**, HTTP 400), so `ShouldNotBeSuccessful()` passes and keeps passing with the rule it is named after deleted. `ShouldHaveValidationErrors()` **refuses** a result whose only errors carry that reason and tells you to seed the read model — or, when that rejection *is* the behavior under spec, assert it deliberately with `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.DependencyUnavailable)`. A missing *service* (not a read model) surfaces as an exception instead (`ShouldHaveExceptions()`), and `ShouldNotBeSuccessful()` alone cannot tell the two apart from a genuine rejection.
102
106
  3. **`.Validate()` authorizes first.** `Validate(command)` runs the command filters, and authorization filters are ordered before validation. A validate-only spec on a `[Roles]`/`[Authorize]` command with no identity registered is denied for authorization — the rule it is named after never ran. Register the identity, or assert `ShouldNotBeAuthorized()` and say so in the name.
103
- 4. **Legacy-mode constraint preconditions go to the event log, not `CommandScenario.Given`.** `_scenario.Given.ForEventSource(id).Events(...)` on a `CommandScenario` seeds an **in-memory read-model materialization only**; nothing reaches the event log a `[Unique]`/`IConstraint` is enforced against, so a "second registration with the same name" spec passes because the first one was never appended. Seed constraint preconditions through **`EventScenario`**'s `Given`, and assert the violation by constraint **name** (`ShouldHaveConstraintViolationFor`) — the same helper exists on `CommandResult` and on `IAppendResult`.
104
- 5. **Legacy `GetInstances<T>` is not harness-backed.** Under legacy `CommandScenario` the read-model harness answers keyed reads (`GetInstanceById`) from its seeded dictionary and forwards `GetInstances<T>`/`GetSnapshotsById`/`Watch` to the real client, which is not there. Code under spec that enumerates a read model needs a fake lookup registered in `Services`, not `Given`.
107
+ 4. **Constraint preconditions must exist at the append boundary.** Use **`EventScenario`**'s `Given` for a duplicate-name constraint, and assert the violation by name (`ShouldHaveConstraintViolationFor`). A pinned read model alone cannot prove append-time uniqueness.
108
+ 5. **Advisory `IReadModels` is not a database provider.** `CommandScenario` backs keyed `GetInstanceById(ReadModelKey)` reads, but forwards `GetInstances<T>`, `GetSnapshotsById` and `Watch` to the real client. For a focused pipeline spec whose handler injects advisory `IReadModels`, bridge keyed state from a `ReadModelScenario<T>` seeded with `Given.ForEventSourceId(id).ReadModel(instance)`, registering `_scenario.Services.AddSingleton(readModelScenario.ReadModels)` before execution. For non-key searches/filters, register a lookup double in `Services`; event seeding alone does not back them. This is advisory state setup, not decision-read protection plumbing; pure decisions still take state directly.
105
109
  6. **One child is not a children spec.** `[ChildrenFrom<T>]` without `key:` identifies children by the **event source id**, so every child event on one stream upserts the same child and a one-child spec stays green with the key wrong. Drive **two** children with distinct keys and assert the count.
106
110
  7. **Independently derived expectations.** An expected value computed in the spec with the same expression as the code under spec agrees with it by construction. Write the expected literal.
107
111
 
@@ -109,23 +113,15 @@ The `ReadModelScenario` and `EventScenario` equivalents of these live in the **c
109
113
 
110
114
  > **Historical validator-state order-sensitivity:** observed in the 22.16-era harness. Arc 22.23.2 stopped replacing the process-wide provider; later releases isolated scenario stores and event-source services. Reproduce at 22.75.0 before applying the old direct-validator/small `[Collection]` workaround. No current reproduction is verified here.
111
115
 
112
- #### Query and protected-decision modes (Arc v22.75.0)
116
+ #### Query pipeline
113
117
 
114
118
  `QueryScenario<TReadModel>` (`Cratis.Arc.Testing.Queries`) runs `Perform(nameof(Model.Method),
115
119
  arguments?, paging?, sorting?)`; `Observe(...)` returns `QueryObservation.Initial`
116
120
  and buffered `Next()`. Configure principal/policies in `Services`; schemes fail
117
121
  closed without HTTP. Chronicle query `Given` can seed keyed models/events.
118
122
 
119
- For guarded commands use plain `new CommandScenario<T>()`: it selects the decision
120
- mode from the command's read-protection mode and dependencies (`UseDecisionReads()`
121
- is hidden and unnecessary). Given state, guarded reads and appends share one real
122
- in-process log, unlike legacy mode above; an `Off` command with no protected
123
- dependency stays in legacy mode. Pinned models cannot supply guards. Use `OnRoute(route)` and
124
- `AppendConcurrently` with `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`
125
- to prove conflicts (there is no `scenario.AppendConditions`); `ShouldHaveCompletedStream` proves
126
- completion and `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)`
127
- checks admission. See **cratis-application-slice-specifications**,
128
- `references/query-and-decision-scenarios.md`, for sources and limitations.
123
+ For query setup, see **cratis-application-slice-specifications**,
124
+ `references/query-and-decision-scenarios.md`. Framework read-protection guards are not application spec obligations.
129
125
 
130
126
  ### `EventScenario` — constraints & append semantics
131
127
 
@@ -147,7 +143,7 @@ async Task Because() =>
147
143
 
148
144
  ### `ReadModelScenario<TReadModel>` — declarative projections and reducer wiring
149
145
 
150
- The default for declarative projections; pure reducer decisions start with a direct call. Add this scenario for reducer dispatch, filtering, metadata or read-model wiring. It drives events into the projection/reducer and asserts the resulting state. Use xUnit `Assert.*` on `_scenario.Instance`.
146
+ The default for declarative projections; pure reducer decisions start with a direct call. Use **one scenario class per read model**, not one per property, mapping attribute or lifecycle event. Apply its event sequence once, capture intermediate states in `Because` where needed, then assert fields, children, counters and lifecycle outcomes in many read-only facts. Add a reducer scenario only for dispatch, filtering, metadata or read-model wiring. Use xUnit `Assert.*` on `_scenario.Instance`.
151
147
 
152
148
  ```csharp
153
149
  ReadModelScenario<Author> _scenario = null!;
@@ -161,7 +157,7 @@ async Task Because() =>
161
157
 
162
158
  Use the named-parameter constructor when services/initial state are needed: `new ReadModelScenario<Author>(initialState: null, serviceProvider: services)`. Pre-seed a keyed read model with `Given.ForEventSourceId(id).ReadModel(instance)` for code that calls `IReadModels.GetInstanceById`. Cross-stream projections (`UsingKey`/`UsingParentKey`) are supported — seed each contributing stream with its own `Given.ForEventSource(...)`.
163
159
 
164
- **Bridging `IReadModels` into a `CommandScenario`** (current harness limitation): when a command handler injects `IReadModels` directly, build a focused `ReadModelScenario<T>`, seed it via `Given.ForEventSourceId(id).ReadModel(...)`, then register its read models into the command scenario — `_scenario.Services.AddSingleton(readModelScenario.ReadModels)`. Pass `scenario.ReadModels` into code under test for keyed `GetInstanceById` reads; for non-key searches/filters, register a fake lookup or an `IMongoCollection<T>` mock in the scenario services instead.
160
+ Do not build a projection scenario just to supply state to a pure command decision. Pass the read model directly, or substitute the application's lookup collaborator. Provider behavior belongs in provider integration specs.
165
161
 
166
162
  **Chronicle v19.50.0 scenario additions** (`Source/Clients/Testing/ReadModels/`):
167
163
  `WithProjection(Action<IProjectionBuilderFor<T>>)` overrides discovery, including
@@ -13,6 +13,7 @@ authored a file or preserve a chain of evidence about how it arrived.
13
13
  - Run one phase per command with its real exit status. Budget targeted checks around 120 seconds and build/test phases around 300 seconds; explain any longer budget first, and obtain explicit approval before a phase exceeds 600 seconds. Stop on a failure; do not blindly retry or repeat passing tests on unchanged inputs without a flaky-test hypothesis or required matrix.
14
14
  - Separate status/fetch from builds, Debug from Release, each test project, and frontend lint/compile/test/build. Do not combine these into an opaque command with a large outer timeout. Expose progress and cancellation; report the precise failed or timed-out phase and do not continue dependent phases after failure.
15
15
  - Never use `build/test | grep | sort/head/tail` as gate evidence. Capture the producer's exit status before summarising output; `pipefail` alone is insufficient with early-closing consumers such as `head`. Printed zero-error counts are not proof of success. Preserve needed full diagnostics in task-scoped `.ai-work/` logs rather than hiding them in `/tmp`.
16
+ - Fast feedback is a product requirement: a spec takes milliseconds, the suite seconds; specs share setup and run in parallel; analyzers must not halt builds. CI builds the spec path without analyzers and enforces them, warnings as errors, in a parallel required job; the pre-push gate runs them. A slowdown is a defect.
16
17
  - Add a focused specification for every behavior or rejection rule you change.
17
18
  - Keep each check deterministic and runnable locally and in CI.
18
19
  - Never replace a real behavior check with a checksum, inventory, generated
@@ -20,7 +20,7 @@ This file is the reference for *what* goes in each part of a slice. Layout, slic
20
20
 
21
21
  - .NET / C# (ASP.NET Core) — **Cratis Arc** for CQRS / model-bound commands and queries, **Cratis Chronicle** for event sourcing, MongoDB or EF Core for read models.
22
22
  - React + TypeScript (Vite) — Cratis Components 4.x + Arc-generated proxies, MVVM. Vitest + Mocha/Chai/Sinon for frontend specs.
23
- - xUnit + Cratis.Specifications + NSubstitute for C# specs (`Specification` plus a direct call by default for pure decisions; add the `*Scenario` family only where pipelines, validation, constraints, projections or wiring contribute proof — see [specs.md](./specs.md)).
23
+ - xUnit + Cratis.Specifications + NSubstitute for C# specs (`Specification` plus a direct call for pure command/validator/reducer decisions with supplied state; add scenarios only for pipeline/authorization wiring, constraints or declarative projections — see [specs.md](./specs.md)).
24
24
 
25
25
  ## Proxy generation — the build dependency
26
26
 
@@ -432,8 +432,10 @@ reads/exact scopes or read-model parameters under read protection `On`
432
432
  (`[ProtectedDecision]` + `DecisionRead<T>` is the older per-command opt-in);
433
433
  validator/`Provide()`/`Handle()` injection under `Off` is advisory. Stream-level
434
434
  `concurrency: true` scopes are another explicit boundary, not protection for an
435
- arbitrary snapshot. Specify conflicts with `AppendConcurrently` and actual seeded
436
- events, not pinned models.
435
+ arbitrary snapshot. Specify business decisions with direct `Handle()`/validator
436
+ calls and supplied model state. Follow [specs.scenarios.csharp.md](./specs.scenarios.csharp.md):
437
+ one cheap assembly-level application-shape admission spec; the platform guard
438
+ matrix belongs only in Arc/Chronicle specs.
437
439
 
438
440
  ## Dialogs
439
441