@cratis/pi 7.123.0 → 7.125.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/corpus/agents/spec-writer.md +6 -6
- package/package/corpus/harnesses/copilot/instructions/application-profile.instructions.md +1 -1
- package/package/corpus/harnesses/copilot/instructions/framework.instructions.md +1 -0
- package/package/corpus/harnesses/copilot/instructions/specs.csharp.instructions.md +2 -2
- package/package/corpus/harnesses/copilot/instructions/specs.instructions.md +1 -1
- package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md +16 -20
- package/package/corpus/harnesses/copilot/instructions/verification-discipline.instructions.md +1 -0
- package/package/corpus/harnesses/copilot/instructions/vertical-slices.instructions.md +5 -3
- package/package/corpus/harnesses/pi/agents/spec-writer.md +6 -6
- package/package/corpus/prompts/add-business-rule.prompt.md +2 -2
- package/package/corpus/rules/application-profile.md +1 -1
- package/package/corpus/rules/framework.md +1 -0
- package/package/corpus/rules/general.md +2 -1
- package/package/corpus/rules/specs.csharp.md +2 -2
- package/package/corpus/rules/specs.md +1 -1
- package/package/corpus/rules/specs.scenarios.csharp.md +16 -20
- package/package/corpus/rules/verification-discipline.md +1 -0
- package/package/corpus/rules/vertical-slices.md +5 -3
- package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +32 -21
- package/package/corpus/skills/cratis-application-slice-specifications/references/query-and-decision-scenarios.md +46 -44
- package/package/corpus/skills/cratis-arc-command/SKILL.md +5 -0
- package/package/corpus/skills/cratis-arc-command/references/decisions-and-routing.md +22 -14
- package/package/corpus/skills/cratis-arc-command/references/fresh-identifiers.md +13 -15
- package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +18 -12
- package/package/corpus/skills/cratis-arc-command-validation/references/protected-validator-construction.md +9 -5
- package/package/corpus/skills/cratis-chronicle-decision-reads/SKILL.md +12 -8
- package/package/corpus/skills/cratis-chronicle-decision-reads/references/admission-and-ownership.md +6 -2
- package/package/corpus/skills/cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md +23 -30
- package/package/corpus/skills/cratis-chronicle-event-specifications/SKILL.md +5 -4
- package/package/corpus/skills/cratis-chronicle-read-model-specifications/SKILL.md +14 -3
- package/package/corpus/skills/cratis-chronicle-read-model-specifications/references/view-lifecycle-checks.md +6 -3
- package/package/corpus/skills/cratis-ci-pipeline-performance/SKILL.md +8 -4
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/test-setup-cost.md +15 -0
- package/package/corpus/skills/cratis-code-review/SKILL.md +10 -7
- package/package/corpus/skills/cratis-screenplay-command-surface/references/generated-values-and-responses.md +5 -1
- package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/chronicle-verification.md +5 -3
- package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +19 -14
- package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +20 -19
- package/package/corpus/skills/cratis-specifications-csharp/references/running-specs.md +32 -1
- package/package/corpus/skills/cratis-specifications-csharp/references/shared-lifecycle.md +32 -13
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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** —
|
|
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
|
-
|
|
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
|
|
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** —
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md
CHANGED
|
@@ -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
|
-
|
|
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 |
|
|
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) |
|
|
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
|
-
- **
|
|
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. **
|
|
105
|
-
5. **
|
|
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
|
|
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
|
|
121
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
package/package/corpus/harnesses/copilot/instructions/verification-discipline.instructions.md
CHANGED
|
@@ -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
|
|
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
|
|
437
|
-
|
|
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
|
|
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** —
|
|
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
|
-
|
|
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
|
|
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** —
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 |
|
|
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) |
|
|
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
|
-
- **
|
|
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. **
|
|
104
|
-
5. **
|
|
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
|
|
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
|
|
120
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
436
|
-
|
|
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
|
|