@cratis/pi 7.61.0 → 7.63.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 (132) hide show
  1. package/package/corpus/agents/spec-writer.md +2 -2
  2. package/package/corpus/harnesses/copilot/instructions/application-profile.instructions.md +2 -2
  3. package/package/corpus/harnesses/copilot/instructions/glossary.instructions.md +2 -2
  4. package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md +7 -4
  5. package/package/corpus/harnesses/copilot/instructions/vertical-slices.instructions.md +22 -13
  6. package/package/corpus/product-sync.json +11 -10
  7. package/package/corpus/prompts/add-business-rule.prompt.md +1 -1
  8. package/package/corpus/rules/application-profile.md +2 -2
  9. package/package/corpus/rules/glossary.md +2 -2
  10. package/package/corpus/rules/specs.scenarios.csharp.md +7 -4
  11. package/package/corpus/rules/vertical-slices.md +22 -13
  12. package/package/corpus/skills/cratis-application-slice-conformance/SKILL.md +1 -1
  13. package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +4 -3
  14. package/package/corpus/skills/cratis-application-slice-specifications/references/query-and-decision-scenarios.md +12 -3
  15. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +12 -7
  16. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +11 -11
  17. package/package/corpus/skills/cratis-arc-command/SKILL.md +3 -2
  18. package/package/corpus/skills/cratis-arc-command/references/decisions-and-routing.md +95 -51
  19. package/package/corpus/skills/cratis-arc-command/references/fresh-identifiers.md +5 -2
  20. package/package/corpus/skills/cratis-arc-command/references/generated-values.md +27 -12
  21. package/package/corpus/skills/cratis-arc-command/references/read-model-injection.md +9 -5
  22. package/package/corpus/skills/cratis-arc-command/references/shop-generation.md +17 -6
  23. package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +99 -52
  24. package/package/corpus/skills/cratis-chronicle-decision-reads/SKILL.md +37 -15
  25. package/package/corpus/skills/cratis-chronicle-decision-reads/references/admission-and-ownership.md +3 -2
  26. package/package/corpus/skills/cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md +277 -138
  27. package/package/corpus/skills/cratis-chronicle-event-constraints/SKILL.md +4 -4
  28. package/package/corpus/skills/cratis-chronicle-event-modeling/SKILL.md +6 -4
  29. package/package/corpus/skills/cratis-chronicle-read-model/SKILL.md +2 -1
  30. package/package/corpus/skills/cratis-chronicle-reducer/SKILL.md +6 -3
  31. package/package/corpus/skills/cratis-code-review/SKILL.md +6 -3
  32. package/package/corpus/skills/cratis-fundamentals-concept/SKILL.md +10 -2
  33. package/package/corpus/skills/cratis-fundamentals-concept/references/shop-generation.md +49 -6
  34. package/package/corpus/skills/cratis-screenplay-automations-and-translations/SKILL.md +43 -41
  35. package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/cases-to-specify.md +11 -2
  36. package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/integration-interview.md +24 -0
  37. package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/public-events-and-direction.md +76 -10
  38. package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/realization-and-gap-fill.md +5 -0
  39. package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/todo-list-example.md +1 -1
  40. package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/translate-example.md +39 -23
  41. package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/translation-patterns.md +35 -12
  42. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/SKILL.md +51 -81
  43. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/atomic-batches-and-source-mappings.md +83 -0
  44. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/capture-map-and-example.md +14 -4
  45. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/reaction-forms.md +59 -0
  46. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/record-capture-specifications.md +60 -0
  47. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/specifying-triggers-and-clock.md +5 -1
  48. package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +26 -28
  49. package/package/corpus/skills/cratis-screenplay-command-surface/references/described-handlers.md +82 -0
  50. package/package/corpus/skills/cratis-screenplay-command-surface/references/generated-values-and-responses.md +82 -28
  51. package/package/corpus/skills/cratis-screenplay-command-surface/references/protected-reads.md +113 -0
  52. package/package/corpus/skills/cratis-screenplay-command-surface/references/shop-generation.md +45 -12
  53. package/package/corpus/skills/cratis-screenplay-command-surface/references/text-and-match-rules.md +119 -0
  54. package/package/corpus/skills/cratis-screenplay-command-surface/references/validate-rules.md +14 -4
  55. package/package/corpus/skills/cratis-screenplay-command-surface/verification.json +10 -3
  56. package/package/corpus/skills/cratis-screenplay-event-modeling/SKILL.md +12 -12
  57. package/package/corpus/skills/cratis-screenplay-event-modeling/references/parsed-not-runnable.md +5 -3
  58. package/package/corpus/skills/cratis-screenplay-event-modeling/verification.json +5 -1
  59. package/package/corpus/skills/cratis-screenplay-legacy-extraction/SKILL.md +2 -2
  60. package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/evidence-table-template.md +1 -1
  61. package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/generated-recovery.md +51 -14
  62. package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/intent-example.md +5 -0
  63. package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/loss-report-template.md +2 -1
  64. package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/rule-modeling.md +6 -3
  65. package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/signal-to-intent.md +1 -1
  66. package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +22 -28
  67. package/package/corpus/skills/cratis-screenplay-model-authoring/references/choosing-generation.md +20 -7
  68. package/package/corpus/skills/cratis-screenplay-model-authoring/references/language-reference.md +12 -6
  69. package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-loop.md +9 -1
  70. package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-tools.md +58 -10
  71. package/package/corpus/skills/cratis-screenplay-model-authoring/verification.json +4 -2
  72. package/package/corpus/skills/cratis-screenplay-model-review/SKILL.md +2 -5
  73. package/package/corpus/skills/cratis-screenplay-model-review/references/anti-patterns.md +2 -2
  74. package/package/corpus/skills/cratis-screenplay-model-review/references/checklist.md +2 -2
  75. package/package/corpus/skills/cratis-screenplay-model-review/references/quick-self-check.md +2 -1
  76. package/package/corpus/skills/cratis-screenplay-model-review/references/review-questions.md +3 -2
  77. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/principles.md +1 -1
  78. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/vocabulary-map.md +1 -1
  79. package/package/corpus/skills/cratis-screenplay-read-surface/SKILL.md +2 -3
  80. package/package/corpus/skills/cratis-screenplay-read-surface/references/named-keys.md +3 -1
  81. package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/SKILL.md +8 -3
  82. package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/references/gap-fill-handoff.md +8 -0
  83. package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/references/renderability-gate.md +6 -4
  84. package/package/corpus/skills/cratis-screenplay-scenario-coverage/SKILL.md +36 -29
  85. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/berth-reservations.md +6 -0
  86. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/invoicing-dues-design.md +15 -4
  87. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/invoicing-dues-example.md +25 -20
  88. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/public-event-coverage.md +14 -3
  89. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/scenario-examples.md +9 -3
  90. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/spec-forms-by-mode.md +7 -3
  91. package/package/corpus/skills/cratis-screenplay-slice-design/SKILL.md +3 -1
  92. package/package/corpus/skills/cratis-screenplay-slice-design/references/command-inventory.md +4 -4
  93. package/package/corpus/skills/cratis-screenplay-slice-design/references/rule-layers.md +4 -2
  94. package/package/corpus/skills/cratis-screenplay-slice-design/references/slicing.md +4 -3
  95. package/package/corpus/skills/cratis-screenplay-slice-design/references/worked-example.md +8 -8
  96. package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +49 -47
  97. package/package/corpus/skills/cratis-screenplay-specifications/references/actions-beyond-commands.md +11 -1
  98. package/package/corpus/skills/cratis-screenplay-specifications/references/generated-fixtures.md +48 -17
  99. package/package/corpus/skills/cratis-screenplay-specifications/references/vocabulary.md +14 -1
  100. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/SKILL.md +25 -21
  101. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/boundaries.md +2 -2
  102. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/consistency-and-concurrency.md +14 -6
  103. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/multi-service-models.md +4 -3
  104. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/streams-example.md +7 -0
  105. package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +30 -22
  106. package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet-example.md +3 -1
  107. package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet.md +47 -2
  108. package/package/corpus/skills/cratis-screenplay-toolchain/references/diagnostic-arguments.md +60 -0
  109. package/package/corpus/skills/cratis-screenplay-toolchain/references/diagnostics.md +65 -5
  110. package/package/corpus/skills/cratis-screenplay-toolchain/references/executable-subset.md +50 -22
  111. package/package/corpus/skills/cratis-screenplay-toolchain/references/generation-checks.md +93 -0
  112. package/package/corpus/skills/cratis-screenplay-toolchain/references/generation-strategies-example.md +117 -35
  113. package/package/corpus/skills/cratis-screenplay-toolchain/references/language-admission.md +46 -0
  114. package/package/corpus/skills/cratis-screenplay-toolchain/references/preferred-syntax.md +3 -1
  115. package/package/corpus/skills/cratis-screenplay-toolchain/references/renderable-subset.md +25 -10
  116. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.json +1800 -50
  117. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.version +1 -1
  118. package/package/corpus/skills/cratis-screenplay-toolchain/references/traps.md +5 -3
  119. package/package/corpus/skills/cratis-screenplay-toolchain/verification.json +6 -1
  120. package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +8 -53
  121. package/package/corpus/skills/cratis-screenplay-ui-composition/references/file-references.md +2 -2
  122. package/package/corpus/skills/cratis-screenplay-ui-composition/references/interaction-example.md +54 -0
  123. package/package/corpus/skills/cratis-screenplay-ui-composition/verification.json +6 -3
  124. package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +3 -2
  125. package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +1 -1
  126. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +30 -29
  127. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +36 -9
  128. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/choosing-generation.md +28 -8
  129. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/publication-and-recovery.md +31 -0
  130. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/sandbox-and-specrunner.md +8 -0
  131. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/verification.json +4 -1
  132. package/package.json +1 -1
@@ -15,61 +15,100 @@ Source: Arc. These features require `Cratis.Arc.Chronicle`.
15
15
 
16
16
  ## Pick the guarded read
17
17
 
18
- Plain injected read models and `IReadModels.GetInstanceById` are advisory,
19
- materialized or passive. A `Result<TEvent, ValidationResult>` rejects a request
20
- but does not enroll a guard. Uniqueness and create-once belong to a Chronicle
21
- constraint, not to any read. For the routing steps, the matrix and the traps read
18
+ Under read protection `On` (`<CratisReadProtection>On</CratisReadProtection>`, or
19
+ `[ReadProtection(ReadProtectionMode.On)]` on a command), a read model that is a
20
+ parameter of `Handle()` or of a `CommandValidator<T>` constructor is read fresh from
21
+ the event log and guards the command's append. `Off` is the default when nothing is
22
+ set; under `Off` and `Measure` those reads are plain, a hint that guards nothing.
23
+ A `Result<TEvent, ValidationResult>` rejects a request but does not enroll a guard.
24
+ Uniqueness and create-once belong to a Chronicle constraint, not to any read. For the
25
+ routing by rule shape, the matrix and the traps read
22
26
  [choosing how a rule holds](../../cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md).
23
27
 
24
- - For one stream, inject `IStreamReads` into `Provide`, call `ForCommand()`, and
25
- return `StreamDecision.Append(...)` from `Handle`. It returns
26
- `EventsWithConcurrencyScopes`, retaining the read route and exact tail. A
27
- competing append conflicts, including two competing first appends.
28
- - For projected state, opt in with `[ProtectedDecision]` in
29
- `Cratis.Arc.Chronicle.ReadModels`, then inject `DecisionRead<T>` into
30
- `Provide`/`Handle` (`Exists`/`Instance`). Command-aware `IDecisionReads.Get<T>`
31
- enrolls reads of other keys. Only Chronicle-admitted direct event-source-keyed
32
- projections with a known event-type set qualify; reducers, joins, children,
33
- open-ended event types and incomplete definitions are refused. Arc certifies parameterless
34
- validators it constructs; constructor dependencies and uncertified instances
35
- are refused: a protected command's discoverable validator needs exactly one
36
- public parameterless constructor, otherwise the command fails with HTTP 500.
37
- The decision model needs an `Id` property, an event-source key and a flat
38
- projection; self-reducing models are refused like reducers. When the command has
39
- two or more key candidates, implement `ICanProvideEventSourceId`: `[Key]` does
40
- not silence `ARCCHR0002`, and Arc silently keys by the first eligible property
41
- in declaration order. Keep all decision state out of validators, including
42
- static members and service locators. `[Unprotected]` on the command opts out; never
43
- combine it with `[ProtectedDecision]`. A parameter-level marker acknowledges
44
- tooling, not a runtime opt-out. `Validate` is advisory, not a commit promise.
45
- - `concurrency: true` on the routing attributes is **not** a guard for the
46
- decision: Arc reads the narrowed tail after `Handle`, so it only widens or
47
- narrows the window the default check covers. Exact returned scopes replace
48
- automatic capture; remove the flags when returning `EventsWithConcurrencyScopes`
49
- (ARCCHR0016). The default check also reads the tail after `Handle`, and the first
50
- append into a scope is unchecked unless `CheckFirstAppendIntoAScope` is set.
28
+ Requires Arc 22.106.0 for read protection. Older Arc has only the per-command
29
+ `[ProtectedDecision]` path below.
30
+
31
+ - **One entity's state.** Put the rule in a validator with the read model as a
32
+ constructor parameter; `Handle()` returns the events. The key is
33
+ `[LookUpBy(nameof(Cmd.Prop))]` on the parameter (`Cratis.Arc.Commands`), else the
34
+ single command property whose type equals the model's `Id` type, else the
35
+ command's event source id. Several matching properties fail with
36
+ `AmbiguousReadModelLookup` (500): add `[LookUpBy]`.
37
+ - **Two entities.** Two read-model parameters, the second with `[LookUpBy]`;
38
+ `Handle()` returns `IEnumerable<EventForEventSourceId>`, one event per entity,
39
+ appended atomically. Use a distinct event type per entity.
40
+ - **One stream inside a source.** A per-stream read model
41
+ (`[ReadModel, EventStream<TSource>("Transactions")]`, `Cratis.Chronicle.ReadModels`)
42
+ as a plain parameter, on a command routed `[EventSource<TSource>("Transactions")]`
43
+ and `[EventStreamId("{Month}")]`; otherwise `ReadModelStreamNotOnCommandRoute`
44
+ (500, analyzer ARCCHR0037). It is folded from that one stream and key-only reads
45
+ are refused. For raw events of a short stream, inject `IStreamReads` into
46
+ `Provide`, call `ForCommand()`, and return `StreamDecision.Append(...)` from
47
+ `Handle` (`EventsWithConcurrencyScopes`); a competing append conflicts, including
48
+ two competing first appends.
49
+ - **Computed key or another stream or source.** Command-aware `IDecisionReads.Get<T>`
50
+ in `Provide()`, including the stream overloads.
51
+ - **Rich lifecycle.** An aggregate root, with `Commit()` returned last. Under `On`,
52
+ an aggregate and a protected read of the same source are refused
53
+ (`ReadAndAggregateOnSameSource`); a different source is fine.
54
+ - **Editing conflict.** Return `EventsWithConcurrencyScopes` built from the revision
55
+ the client sent. `concurrency: true` on the routing attributes is **not** a guard
56
+ for the decision: Arc reads the narrowed tail after `Handle`. Remove the flags
57
+ when returning exact scopes (ARCCHR0016). The default check also reads the tail
58
+ after `Handle`, and the first append into a scope is unchecked unless
59
+ `CheckFirstAppendIntoAScope` is set.
60
+ - **No guard needed.** A hint or a rare violation: mark a deliberate advisory read
61
+ `[Unprotected("why")]` (`Cratis.Arc.Chronicle.ReadModels`; on a method it covers
62
+ the body, not injected parameters), or repair with a reactor.
63
+
64
+ Under `On`, `IReadModels`, `IMongoCollection`, detached reads and immediate appends
65
+ through an injected `IEventLog` are refused; take the model as a parameter and
66
+ return the events. Admitted models: event-source-keyed model-bound or declarative
67
+ projections with a string or Guid `Id`, and unfiltered reducers without constructor
68
+ dependencies except `ILogger<T>` (self-reducing models are exempt). Joins and
69
+ children, event-property keys, Pinned delivery, filtered reducers, reducers with
70
+ dependencies and other event sequences fail with `ReadCannotBeProtected` (500)
71
+ unless marked `[Unprotected("why")]`. Validators with read models must be
72
+ convention-registered (transient).
73
+
74
+ When the command has two or more key candidates, implement `ICanProvideEventSourceId`:
75
+ `[Key]` does not silence `ARCCHR0002`, and Arc silently keys by the first eligible
76
+ property in declaration order.
77
+
78
+ A lost race is HTTP 400 with a validation result of reason `concurrencyViolation`,
79
+ `reasonDetail` `ReadChanged` and `state` `{ readModel, key }`. Arc and Chronicle do
80
+ not re-run the command; the client submits it again. Every other refusal is 500:
81
+ change the code.
82
+
83
+ ### Per-command opt-in: `[ProtectedDecision]`
84
+
85
+ `[ProtectedDecision]` (`Cratis.Arc.Chronicle.ReadModels`) with `DecisionRead<T>`
86
+ (`Exists`/`Instance`) is the older per-command opt-in. It still works and is not
87
+ obsolete; do not describe `On` as making it redundant. Use it for a command in a
88
+ project that is not on `On`. Without `On`, only a parameterless validator runs: a
89
+ discoverable validator needs exactly one public parameterless constructor,
90
+ otherwise the command fails with HTTP 500. Keep decision state out of validators
91
+ there, including static members and service locators. `[Unprotected]` on the
92
+ command opts out; never combine it with `[ProtectedDecision]`
93
+ (`CommandCannotBeBothProtectedAndUnprotected`).
51
94
 
52
95
  Requires Arc 22.81.0 for stream-scoped `IDecisionReads.Get<T>`,
53
96
  `GetDetached<T>` and `AdmitForStream<T>` to participate in command protection,
54
- caching and enrollment. Older Arc refuses these stream reads inside commands;
55
- check the Arc package version before choosing this path. `[Unprotected]` command
56
- reads are unguarded, not refused.
97
+ caching and enrollment. Older Arc refuses these stream reads inside commands.
57
98
 
58
99
  Do not manually append or commit to bypass the owner-capable unit of work.
59
100
  The guard detects every matching event appended after its boundary, whoever wrote
60
101
  it, but it protects only guarded decisions: an unguarded writer deciding on the
61
102
  same facts can still append on stale state, so the rule holds only when every
62
- deciding writer is guarded or the rule is a constraint. Protection does not guard an unguarded writer's own decision (a plain read,
63
- `[Unprotected]` or a direct append), external state, revisions, redactions,
64
- migrations, changes to projection definitions or writes that bypass the kernel's
65
- serialized append path. Protection is per command and
66
- opt-in; an unmarked command keeps its plain reads, and a conflict is returned as a
67
- `ConcurrencyViolation` validation result with no automatic retry. A protected command that
68
- stages no events commits an empty guarded batch: current kernels validate the
69
- enrolled reads (a scope with an actual boundary or expecting no matching event) and write nothing, while a kernel that rejects an empty batch
70
- raises `DecisionReadValidateOnlyNotSupported` (HTTP 500), so cover it with an
71
- integration specification. In contrast, `StreamDecision.Append([])`
72
- returns no scopes: use it only for an identical retry, not a rejected request.
103
+ deciding writer is guarded or the rule is a constraint. Outside the guard:
104
+ `[Unprotected]` reads, `Off` mode, external state, revisions and redactions,
105
+ migrations, changes to projection definitions and writes that bypass the kernel's
106
+ serialized append path. A protected command that stages no events commits an empty
107
+ guarded batch: current kernels validate the enrolled reads and write nothing, while
108
+ a kernel that rejects an empty batch raises `DecisionReadValidateOnlyNotSupported`
109
+ (HTTP 500), so cover it with an integration specification. In contrast,
110
+ `StreamDecision.Append([])` returns no scopes: use it only for an identical retry,
111
+ not a rejected request.
73
112
 
74
113
  ## Definition-backed routes and templates
75
114
 
@@ -125,12 +164,17 @@ source id/type and stream route; reactor tuples/collections cannot mix it with e
125
164
 
126
165
  ## Decision specifications
127
166
 
128
- Call `new CommandScenario<T>().UseDecisionReads()` before seeding events.
129
- `Given.ForEventSource(id).OnRoute(route).Events(...)` and
130
- `AppendConcurrently(id, route, ...)` share one real in-process log. Pinned models
131
- cannot supply guards. Assert a real nonempty decision conflicts on the intended
132
- route, and that an independent route is not over-guarded.
133
- `ShouldHaveCompletedStream(type, id)` asserts completion;
167
+ `new CommandScenario<T>()` selects the in-process decision log from the command's
168
+ read-protection mode and dependencies (under `On`, with a per-stream model or
169
+ `[ProtectedDecision]`); `UseDecisionReads()` is hidden and unnecessary. An `Off`
170
+ command with no protected dependency stays in the legacy mode with pinned read
171
+ models, which cannot prove a race. Seed with `Given.ForEventSource(id).Events(...)`,
172
+ `.OnRoute(route).Events(...)`, queue a competing append with
173
+ `AppendConcurrently(id, events...)` (or `AppendConcurrently(id, route, ...)`) and
174
+ assert `result.ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`.
175
+ There is no `scenario.AppendConditions`. Guarded state needs seeded events: protected reads ignore a pinned `.ReadModel(m)` (which only pins an advisory plain read) and `DecisionRead<T>` rejects it. Assert a
176
+ real nonempty decision conflicts on the intended route, and that an independent
177
+ route is not over-guarded. `ShouldHaveCompletedStream(type, id)` asserts completion;
134
178
  `ShouldHaveCompletedStream(ClosedStreamScope)` asserts the exact owner and route;
135
179
  `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` checks projection
136
180
  admission in assembly-level specs.
@@ -98,8 +98,11 @@ use an appropriate constraint or explicit concurrency scope for them.
98
98
 
99
99
  ## Specify the collision
100
100
 
101
- Use `new CommandScenario<TCommand>().UseDecisionReads()` **before seeding**, then
102
- `PinGeneratedValue(name, id)` and `Given.ForEventSource(id).Events(existingEvent)`.
101
+ Use `new CommandScenario<TCommand>()`; it selects the in-process log from the
102
+ command's read-protection mode and dependencies before seeding. If a seeded
103
+ collision throws `GeneratedIdentifierCollisionRequiresDecisionReads`, the
104
+ scenario stayed in legacy mode: call the compatibility switch `UseDecisionReads()`
105
+ before seeding. Then `PinGeneratedValue(name, id)` and `Given.ForEventSource(id).Events(existingEvent)`.
103
106
  Assert the concurrency reason and collision detail, unchanged append tail and
104
107
  one handler execution. Also cover an empty source receiving two events through
105
108
  the chosen write API, an occupied source whose events belong to another stream,
@@ -24,7 +24,10 @@ For the current threaded example, read [shop commands](shop-generation.md) and
24
24
  [shop concepts](../../cratis-fundamentals-concept/references/shop-generation.md).
25
25
  Those declarations show:
26
26
  CustomerId (`cus_` + ULID), context-aware TypeID OrderId, NanoID InviteCode and
27
- secret bytes ApiKey. They allocate responses, not events or persisted entities.
27
+ secret bytes ApiKey, readable InvoiceNumber (pattern/checksum) and Int64 pickup
28
+ TicketNumber (`Integer`). The latter helpers require Fundamentals 7.25.0; inspect
29
+ `dotnet list package --include-transitive` before use and retain existing factories
30
+ on older packages. They allocate responses, not events or persisted entities.
28
31
  For a real Chronicle stream identity use `EventSourceId<T>`; return `(id, event)`
29
32
  to append and return the stream ID, or use an explicit event-source wrapper.
30
33
 
@@ -32,14 +35,19 @@ Keep existing/client-chosen identifiers and references on command properties.
32
35
  Services and read models remain ordinary dependencies, not generated values.
33
36
  UUID generation does not establish authorization, secrecy, idempotency, storage
34
37
  freshness, or gapless/sequential numbering. Use the domain's authoritative
35
- allocation mechanism for invoice numbers, not a random UUID strategy.
38
+ allocation mechanism for gapless/sequential invoice numbers, not a random strategy.
39
+ A pattern supplies a readable reference, not an allocator or secret; check digits
40
+ catch typos, not forgery. The concept validator can call
41
+ `GenerateValue.HasValidChecksum(value, Checksum.Luhn)` alongside shape checks;
42
+ pinning bypasses generation, never validation. Keep short-code uniqueness separate.
36
43
 
37
44
  ## Resolution, validation and captures
38
45
 
39
46
  `GeneratedValueAttribute` targets parameters only. Use it on a command's public
40
47
  instance `Handle()`, not a property, constructor or `Provide()` parameter.
41
- The type must be non-nullable and implement `IGeneratable<TSelf>` for itself;
42
- a raw `Guid`, nullable concept or by-reference parameter is invalid. Use domain
48
+ The type must be non-nullable and implement `IGeneratable<TSelf>` or
49
+ `IGeneratableInContext<TSelf>` for itself; a raw `Guid`, nullable concept or
50
+ by-reference parameter is invalid. Use domain
43
51
  concepts; the runtime's type check is the self-generatable interface contract.
44
52
 
45
53
  `[GeneratedValue]` uses the parameter name. `[GeneratedValue("customerId")]` keeps the
@@ -52,10 +60,11 @@ invoking `Handle()`. Neither a provided value nor a DI registration overrides a
52
60
  generated parameter. Unmarked parameters retain their normal resolution.
53
61
 
54
62
  Discoverable concept validators run with the command's scoped provider and
55
- cancellation token. Errors produce `GeneratedValueRejected`, an exception
63
+ cancellation token. A failed rule produces `GeneratedValueRejected`, an exception
56
64
  outcome in `CommandResult`: a server-generation defect, not a request-validation
57
- 400. Warnings/information allow execution. Fix the strategy or invariant; do not
58
- turn the defect into a user rejection or disable validation to make it pass.
65
+ 400. Requires Arc 22.104.0 for rejection at every severity, including Warning and
66
+ Information; older runtimes let those severities execute. Fix the strategy or
67
+ invariant; do not turn the defect into a user rejection or disable validation.
59
68
 
60
69
  Validated values enter `CommandContext.GeneratedValues` in parameter-resolution
61
70
  order. Each capture has zero-based `Order`, `Name`, declared `Type` and `Value`;
@@ -73,7 +82,7 @@ handler-construction checks remain for declarations compiled without analyzers.
73
82
 
74
83
  | Diagnostic | Severity | Fix |
75
84
  | --- | --- | --- |
76
- | ARC0022 | Error | Use a non-nullable type implementing `IGeneratable<TSelf>` for itself; no raw `Guid` or by-reference parameter. |
85
+ | ARC0022 | Error | Use a non-nullable type implementing `IGeneratable<TSelf>` or `IGeneratableInContext<TSelf>` for itself; no raw `Guid` or by-reference parameter. |
77
86
  | ARC0023 | Error | Move the attribute to a non-static command `Handle()` parameter, not `Provide()`, a read model or controller. |
78
87
  | ARC0024 | Error | Give each generated parameter a non-empty unique name; comparison is case-sensitive. |
79
88
  | ARC0025 | Warning | Remove a redundant provided value of the generated type, or remove the attribute if `Provide()` should supply it. An unmarked parameter can still consume that provided type. |
@@ -126,7 +135,13 @@ Source: `Source/DotNET/Arc.Core/Commands/ModelBound/GeneratedValueAttribute.cs`,
126
135
  `Source/DotNET/Testing/Commands/CommandScenarioGeneratedValueExtensions.cs`.
127
136
 
128
137
  Contextual generation (received time and tenant) and Fundamentals' additional strategies need a newer Arc; [sources and Clock/Tenant
129
- setup](shop-generation.md). Arc does not supply a native freshness/collision
130
- allocator or a pattern contract. Screenplay separately admits concept `generate`
131
- strategies and freshness; see `cratis-screenplay-command-surface/references/generated-values-and-responses.md`
132
- for that language contract and Stage's explicit scopes, not native Arc freshness support.
138
+ setup](shop-generation.md). Generated parameter resolution is not a gapless or
139
+ short-code collision allocator. Fundamentals owns pattern/checksum factories;
140
+ Arc resolves these through the concept's generation interface, not strategy-specific
141
+ `[GeneratedValue]` options. Requires Arc.Screenplay 22.107.0 for pattern/random,
142
+ checksum and self-contained String-rule recovery; older adapters keep them in code.
143
+ Requires Stage 4.118.0 for checksum validation and 4.119.0 for pattern/random
144
+ rendering; older renderers refuse them. `generate rule` remains STAGE-ESM-028.
145
+ Screenplay separately admits concept `generate` strategies and freshness; see `cratis-screenplay-command-surface/references/generated-values-and-responses.md`
146
+ for that language contract and Stage's explicit scopes. Native Arc freshness is a
147
+ separate policy; a factory alone is not a storage guard.
@@ -7,10 +7,12 @@
7
7
  - Which provider owns a read model type
8
8
 
9
9
  Source: [read models in commands](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/chronicle/read-models/injecting-into-commands.md).
10
- Plain models and keyed reads are advisory, not concurrency guards. For protected
11
- reads use [decisions and routing](decisions-and-routing.md): opt into
12
- `[ProtectedDecision]` before taking `DecisionRead<T>`; constructor-injected
13
- validators are refused there.
10
+ Under read protection `Off` (the default) and `Measure`, injected models and keyed
11
+ reads are plain reads, not concurrency guards. Under `On`, a read-model parameter of
12
+ `Handle()` or a validator constructor is guarded; `IReadModels.GetInstanceById` in a
13
+ command is refused, so read another entity's model with a second parameter and
14
+ `[LookUpBy(nameof(Cmd.Prop))]`. See [decisions and routing](decisions-and-routing.md)
15
+ and, for per-command opt-in, `[ProtectedDecision]` with `DecisionRead<T>`.
14
16
 
15
17
  A read model can be injected into a `CommandValidator<T>` constructor, into
16
18
  `Provide()`, or into `Handle()`. Arc resolves it through an
@@ -26,7 +28,9 @@ error. The three shapes it takes:
26
28
  ambiguous;
27
29
  3. the id is generated inside `Handle()` and does not exist yet.
28
30
 
29
- In all three, read by explicit key in `Provide()`:
31
+ In all three, under `Off` read by explicit key in `Provide()`; under `On` use a
32
+ second parameter with `[LookUpBy]`, or `IDecisionReads.Get` in `Provide()` for a
33
+ key computed there:
30
34
 
31
35
  ```csharp
32
36
  var other = await readModels.GetInstanceById<<Other>>((EventSourceId)<OtherId>);
@@ -13,8 +13,10 @@
13
13
 
14
14
  Use the [same shop concepts](../../cratis-fundamentals-concept/references/shop-generation.md):
15
15
  CustomerId is `cus_` + ULID; OrderId is context-aware TypeID; InviteCode is NanoID;
16
- ApiKey is secret `Bytes(32)` with `[NotAudited]`. The concept owns the factory,
17
- not a command property or a per-call random implementation.
16
+ ApiKey is secret `Bytes(32)` with `[NotAudited]`. InvoiceNumber is a date/checksum
17
+ pattern and TicketNumber an inclusive random Int64 pickup ticket. The concept owns
18
+ the factory, not a command property or a per-call random implementation.
19
+ Requires Fundamentals 7.25.0 for Pattern/Integer factories; check `dotnet list package --include-transitive`, upgrade or retain existing factories on older versions.
18
20
 
19
21
  The following complete command declarations depend on those Shop concepts and
20
22
  `Cratis.Arc.Core` / `Cratis.Arc.Chronicle`. They allocate response values only, not persisted entities or events;
@@ -87,16 +89,25 @@ and captured-time semantics, never one exact random suffix.
87
89
  - Existing/client-owned IDs stay command inputs; services/read models remain
88
90
  ordinary dependencies. Generation is not authorization or tenant isolation.
89
91
  - Randomness is not idempotency, storage freshness or a gapless invoice allocator.
90
- Arc#3129 freshness is not delivered by this parameter API; Stage's v15 explicit
91
- scopes are a separate realization.
92
+ Stage's explicit whole-source scopes protect generated destinations, not
93
+ every generated payload code; enforce payload uniqueness separately.
92
94
  - ApiKey is a credential, never a Chronicle stream ID: event-source IDs are plaintext
93
95
  metadata and cannot be encrypted. Identify the key with a separate `ApiKeyId` and keep
94
96
  ApiKey a secret value property. `[NotAudited]` protects
95
97
  causation only. Never log captures, return secrets on public endpoints, or
96
98
  append plaintext authentication credentials. Retain a hash; for retained
97
99
  operational secrets use `[Encrypted]` plus `[NotAudited]`.
98
- - Screenplay's generated context and secret execution remain unavailable. A
99
- compiled Arc factory does not imply that it can recover or render as `.play`.
100
+ - Readable patterns are not gapless/sequential invoice allocators or secrets;
101
+ check digits catch typos, not forgery. Short codes need uniqueness handling;
102
+ generation alone does not reserve a code.
103
+ - Screenplay admits attached generation rules with Now/Tenant context, but its
104
+ reference evaluator only consumes pinned results. Generated secrets remain
105
+ refused. A compiled Arc factory does not imply that it can recover or render
106
+ as `.play`. Read [generated-value recovery](../../cratis-screenplay-legacy-extraction/references/generated-recovery.md)
107
+ for Arc.Screenplay prerequisites and refusal boundaries, and
108
+ [Stage admission](../../cratis-stage-rendering-and-sandbox/references/admission.md)
109
+ for checksum and pattern/random rendering prerequisites. `generate rule`
110
+ remains STAGE-ESM-028.
100
111
 
101
112
  ## Sources
102
113
 
@@ -59,14 +59,15 @@ another version.
59
59
  Which mechanism makes a rule hold under concurrent writers (constraint, protected
60
60
  decision, stream decision, aggregate, scope, reactor repair) is routed in
61
61
  [choosing how a rule holds](../cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md).
62
- A validator is never that mechanism.
62
+ A validator holds a state rule only when the project is on read protection `On`;
63
+ under `Off` (the default) it is a hint, never the mechanism.
63
64
 
64
65
  | The rule is… | Put it in |
65
66
  | --- | --- |
66
67
  | A reusable value invariant — length, format, range | `ConceptValidator<T>` on the concept type |
67
68
  | Command input, cross-field, or a pre-handler check | `CommandValidator<TCommand>` with `RuleFor(...)` |
68
69
  | A dependency on data the handler must fetch first | `Provide()` — fetch, and short-circuit if unusable |
69
- | State-dependent and must hold **under concurrency** | Guard the read with `IStreamReads`/`StreamDecision`, or `[ProtectedDecision]` + `DecisionRead<T>`; return events through Arc |
70
+ | State-dependent and must hold **under concurrency** | Under `On`, a `CommandValidator<T>` with the read model as a constructor parameter (Step 5); or `IStreamReads`/`StreamDecision`; return events through Arc. `[ProtectedDecision]` + `DecisionRead<T>` is the older per-command opt-in |
70
71
  | Uniqueness | A Chronicle constraint, never a command-side read |
71
72
  | A genuine defect or missing infrastructure | `throw` a domain exception |
72
73
 
@@ -119,11 +120,27 @@ public class TransferFundsValidator : CommandValidator<TransferFunds>
119
120
  - The validator is discovered by type — no registration.
120
121
  - Constructor dependencies (read models, services) are injected. A read model
121
122
  injected here resolves the same way as one injected into `Handle()`; see
122
- Step 5 for what "the same way" actually means. A validator that reads state is
123
- **advisory only**: it shows the state at validation time and guards nothing, so
124
- use it for a friendly early message, never as the rule. A `[ProtectedDecision]`
125
- command refuses a discoverable validator unless it has exactly one public
126
- parameterless constructor (otherwise HTTP 500), so keep state rules out of it.
123
+ Step 5. **State rules ("may this proceed") belong here**, with the read model as a
124
+ constructor parameter. Under read protection `On` that read is fresh from the log
125
+ and guards the append: if an event of the model's types for that key arrives
126
+ after the read, nothing from the command is written. Under `Off` (the default
127
+ when nothing is set) and `Measure` the same validator reads a plain model: a hint
128
+ with a friendly message that guards nothing. Under `On` the validator must be
129
+ convention-registered (transient); hand-registered scoped, singleton, factory or
130
+ instance validators are refused. Under `[ProtectedDecision]` without `On`, a
131
+ discoverable validator needs exactly one public parameterless constructor
132
+ (otherwise HTTP 500).
133
+ - **The caller can lower the severity threshold.** Model-bound commands accept
134
+ `X-Allowed-Severity` (`allowedSeverity`). Under `Off` and `Measure`, a caller
135
+ sending `3` (Error) removes Error results from validators, command filters and
136
+ `Provide()`, so `Handle()` runs and a validator's state rule can be skipped
137
+ unless the command declares
138
+ `[BlockOnValidationSeverity(ValidationResultSeverity.Error)]`
139
+ (`Cratis.Arc.Commands.ModelBound`). Requires Arc 22.108.0 for the `On` floor: a
140
+ command declared `On` blocks `Error` and `Unknown` whatever the caller sends; the
141
+ caller can relax only `Warning` and `Information`. Lowering
142
+ `ReadProtection:MaximumMode` at runtime does not remove the floor, and an explicit
143
+ attribute wins. Severity is not authorization.
127
144
  - **Omit the validator entirely when there are no rules.** An empty subclass is
128
145
  noise the reader has to check.
129
146
  - Single-property intrinsic rules belong on `ConceptValidator<T>` instead, so
@@ -159,53 +176,77 @@ public async Task<Result<Customer, ValidationResult>> Provide(IReadModels readMo
159
176
 
160
177
  ## Step 5 — Guard a concurrency-sensitive decision
161
178
 
162
- Plain read-model injection or `IReadModels.GetInstanceById` is **advisory**, even
163
- inside `Handle()` returning `Result<,>`: it enrolls no guard against a competing
164
- append. Choose the first boundary that fits:
165
-
166
- 1. One routed stream: `Provide(IStreamReads reads) => reads.ForCommand()` returns
167
- `Task<StreamDecision>`; `Handle(StreamDecision decision)` returns
168
- `decision.Append(...)` (`EventsWithConcurrencyScopes`), guarding the read tail.
169
- 2. Projected state: mark the command `[ProtectedDecision]` and take
170
- `DecisionRead<T>` (`.Exists`/`.Instance`) or command-aware `IDecisionReads`.
171
- Only Chronicle-admitted, direct event-source-keyed projections with known
172
- event types qualify; reducers (including self-reducing models), joins, children and open-ended event
173
- types are refused. The model needs an `Id` property.
174
- Only parameterless validators constructed/certified by Arc run. Constructor
175
- dependencies are refused; static/service-locator decision state is still your
176
- responsibility. Command-level `[Unprotected]` opts out, not into safety.
177
- 3. Stream-level automatic scopes: `concurrency: true` on routing attributes
178
- (`EventStreamId`, `EventStreamType`, `EventSourceType`). It is **not** a guard
179
- for the decision: like the default check it reads the tail after `Handle`.
180
- Exact returned scopes take precedence; do not combine automatic capture with
181
- exact scopes. A guard detects every matching event after its boundary, whoever
182
- wrote it, but it protects only guarded decisions, so every writer that decides
183
- on the rule needs one, or the rule must be a constraint.
184
-
185
- `ARCCHR0011/0012` flag unprotected reads and immediate append bypasses. Return
186
- events, never append them directly. Read
179
+ Set read protection `On` for new code: `<CratisReadProtection>On</CratisReadProtection>`
180
+ in the project (or `[ReadProtection(ReadProtectionMode.On)]` on one command). Under
181
+ `On`, the read-model parameters of `Handle()` and of the validator constructor are
182
+ the guard, so the state rule is a validator and `Handle()` returns the events.
183
+ Requires Arc 22.106.0. Under `Off` (the default) a plain read guards nothing, even
184
+ inside `Handle()` returning `Result<,>`. Choose the first boundary that fits:
185
+
186
+ 1. A unique value or create-once: a Chronicle constraint, not a read.
187
+ 2. One entity's state: a read-model parameter in the validator (this step's example).
188
+ Several entities: one parameter each, with `[LookUpBy(nameof(Command.Prop))]` on
189
+ a parameter whose key is not the command's event source id, and `Handle()`
190
+ returning `IEnumerable<EventForEventSourceId>` with a distinct event type per
191
+ entity.
192
+ 3. A limit inside one stream: a per-stream read model (`[EventStream<TSource>("…")]`)
193
+ on a command routed `[EventSource<TSource>("…")]` + `[EventStreamId("{Prop}")]`,
194
+ or `Provide(IStreamReads reads) => reads.ForCommand()` returning `StreamDecision`.
195
+ 4. An editing conflict: exact returned scopes from the revision the client sent.
196
+ `concurrency: true` on routing attributes is **not** a guard: like the default
197
+ check it reads the tail after `Handle`.
198
+ 5. Rich lifecycle: an aggregate; do not also read the same source.
199
+
200
+ A read under `On` is refused, with HTTP 500, when it cannot be guarded exactly:
201
+ `IReadModels`, `IMongoCollection`, detached reads, an injected `IEventLog.Append`,
202
+ and models that are joins, event-property keyed, filtered or dependency-bearing
203
+ reducers, Pinned or on another sequence (`ReadCannotBeProtected`; mark a deliberate
204
+ advisory read `[Unprotected("why")]`). Several command properties with the model's
205
+ `Id` type are `AmbiguousReadModelLookup`: add `[LookUpBy]`. A lost race is HTTP 400
206
+ `concurrencyViolation` (`ReadChanged`): the client resubmits; Arc does not re-run
207
+ the command. `[ProtectedDecision]` with `DecisionRead<T>` is the older per-command
208
+ opt-in; it still works and is not obsolete. A guard protects only guarded
209
+ decisions: every writer that decides on the rule needs one, or the rule must be a
210
+ constraint.
211
+
212
+ `ARCCHR0011` (Info, `Off` only) and `ARCCHR0012` flag plain reads and immediate
213
+ append bypasses. Return events, never append them directly. Read
187
214
  [protected and stream decisions](../cratis-arc-command/references/decisions-and-routing.md)
188
- when choosing the guard. This handler excerpt assumes `CartSummary` is an
189
- admitted projection; domain types are the slice's existing types.
215
+ and
216
+ [choosing how a rule holds](../cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md)
217
+ when choosing the guard. The example assumes `CartSummary` is a model-bound
218
+ projection with a `CartId Id`; domain types are the slice's existing types.
190
219
 
191
220
  ```csharp
192
- using Cratis.Monads;
193
- using Cratis.Arc.Chronicle.ReadModels;
221
+ using Cratis.Arc.Commands;
222
+ using Cratis.Arc.Commands.ModelBound;
223
+ using FluentValidation;
194
224
 
195
225
  [Command]
196
- [ProtectedDecision]
197
226
  public record AddItemToCart(CartId CartId, ItemId ItemId)
198
227
  {
199
- /// <summary>Adds the item; rejects when the cart already holds the maximum.</summary>
200
- /// <param name="cart">The current cart summary, resolved by the command's event-source id.</param>
201
- /// <returns>The event on success, or a validation error.</returns>
202
- public Result<ItemAddedToCart, ValidationResult> Handle(DecisionRead<CartSummary> cart) =>
203
- !cart.Exists
204
- ? ValidationResult.Error("Cart must exist.")
205
- : cart.Instance.ItemCount >= 3
206
- ? ValidationResult.Error("A cart can hold at most 3 items.")
207
- : new ItemAddedToCart(ItemId);
228
+ /// <summary>Adds the item.</summary>
229
+ /// <returns>The event.</returns>
230
+ public ItemAddedToCart Handle() => new(ItemId);
208
231
  }
232
+
233
+ public class AddItemToCartValidator : CommandValidator<AddItemToCart>
234
+ {
235
+ public AddItemToCartValidator(CartSummary? cart) =>
236
+ RuleFor(c => c.CartId)
237
+ .Must(_ => cart is { ItemCount: < 3 })
238
+ .WithMessage("The cart must exist and hold fewer than 3 items.");
239
+ }
240
+ ```
241
+
242
+ Use `Result<TEvent, ValidationResult>` from `Handle()` only when the rule needs a value
243
+ computed there:
244
+
245
+ ```csharp
246
+ public Result<ItemAddedToCart, ValidationResult> Handle(CartSummary? cart) =>
247
+ cart is not { ItemCount: < 3 }
248
+ ? ValidationResult.Error("A cart can hold at most 3 items.")
249
+ : new ItemAddedToCart(ItemId);
209
250
  ```
210
251
 
211
252
  - `Result<TSuccess, TError>` is `Cratis.Monads.Result<,>` from Cratis
@@ -214,6 +255,8 @@ public record AddItemToCart(CartId CartId, ItemId ItemId)
214
255
  statements above compile without wrapping.
215
256
  - **Return the error; never throw it.**
216
257
  - One parameter per logical read model; several reads mean several parameters.
258
+ - Keep the fetch for a *computed* key in `Provide()` with `IDecisionReads.Get`;
259
+ under `On`, `IReadModels` there is refused.
217
260
 
218
261
  ⚠️ **Two things the resolution does *not* do.** It does not key off the
219
262
  `[ReadModel]` attribute — a read model becomes injectable because a Chronicle
@@ -245,9 +288,13 @@ where absence is *not* `null`.
245
288
  ## Step 6 — Specify the rejection
246
289
 
247
290
  Every rule gets a specification of its failure case, using
248
- `CommandScenario<TCommand>`. For protected reads, call `UseDecisionReads()`
249
- before seeding actual events; pinned read models cannot supply a guard. Queue a
250
- competing append on the relevant route and assert the conflict. Assert **both** that the command did not succeed
291
+ `CommandScenario<TCommand>`. For guarded reads, `new CommandScenario<T>()`
292
+ selects the in-process log from the command's mode and dependencies (do not call
293
+ `UseDecisionReads()`). Seed real events with `Given.ForEventSource(id).Events(...)`
294
+ (pinned read models cannot supply a guard), queue a competing
295
+ `AppendConcurrently(id, events...)` and assert
296
+ `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`.
297
+ For the ordinary rejection, assert **both** that the command did not succeed
251
298
  and that it failed *as a validation*:
252
299
 
253
300
  ```csharp
@@ -273,9 +320,9 @@ the fact. See
273
320
  - **The rule never fires.** The read-model type has no backing Chronicle
274
321
  projection, reducer, `DbSet<T>`, or MongoDB collection, so nothing registered
275
322
  it for command-side resolution.
276
- - **Two requests both win.** A plain read was mistaken for a protected decision;
277
- moving it to `Handle()` or returning `Result<,>` does not guard it. Enroll a
278
- stream/projection decision read, or use an append-time uniqueness constraint.
323
+ - **Two requests both win.** The project is on `Off` (the default) or `Measure`, so
324
+ the validator read is a hint; moving it to `Handle()` or returning `Result<,>`
325
+ does not guard it. Set `On`, or use a stream decision or a constraint.
279
326
  - **`ARC0005` on build.** `Provide()` returns a value no `Handle()` parameter
280
327
  consumes.
281
328