@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.
- package/package/corpus/agents/spec-writer.md +2 -2
- package/package/corpus/harnesses/copilot/instructions/application-profile.instructions.md +2 -2
- package/package/corpus/harnesses/copilot/instructions/glossary.instructions.md +2 -2
- package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md +7 -4
- package/package/corpus/harnesses/copilot/instructions/vertical-slices.instructions.md +22 -13
- package/package/corpus/product-sync.json +11 -10
- package/package/corpus/prompts/add-business-rule.prompt.md +1 -1
- package/package/corpus/rules/application-profile.md +2 -2
- package/package/corpus/rules/glossary.md +2 -2
- package/package/corpus/rules/specs.scenarios.csharp.md +7 -4
- package/package/corpus/rules/vertical-slices.md +22 -13
- package/package/corpus/skills/cratis-application-slice-conformance/SKILL.md +1 -1
- package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +4 -3
- package/package/corpus/skills/cratis-application-slice-specifications/references/query-and-decision-scenarios.md +12 -3
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +12 -7
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +11 -11
- package/package/corpus/skills/cratis-arc-command/SKILL.md +3 -2
- package/package/corpus/skills/cratis-arc-command/references/decisions-and-routing.md +95 -51
- package/package/corpus/skills/cratis-arc-command/references/fresh-identifiers.md +5 -2
- package/package/corpus/skills/cratis-arc-command/references/generated-values.md +27 -12
- package/package/corpus/skills/cratis-arc-command/references/read-model-injection.md +9 -5
- package/package/corpus/skills/cratis-arc-command/references/shop-generation.md +17 -6
- package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +99 -52
- package/package/corpus/skills/cratis-chronicle-decision-reads/SKILL.md +37 -15
- package/package/corpus/skills/cratis-chronicle-decision-reads/references/admission-and-ownership.md +3 -2
- package/package/corpus/skills/cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md +277 -138
- package/package/corpus/skills/cratis-chronicle-event-constraints/SKILL.md +4 -4
- package/package/corpus/skills/cratis-chronicle-event-modeling/SKILL.md +6 -4
- package/package/corpus/skills/cratis-chronicle-read-model/SKILL.md +2 -1
- package/package/corpus/skills/cratis-chronicle-reducer/SKILL.md +6 -3
- package/package/corpus/skills/cratis-code-review/SKILL.md +6 -3
- package/package/corpus/skills/cratis-fundamentals-concept/SKILL.md +10 -2
- package/package/corpus/skills/cratis-fundamentals-concept/references/shop-generation.md +49 -6
- package/package/corpus/skills/cratis-screenplay-automations-and-translations/SKILL.md +43 -41
- package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/cases-to-specify.md +11 -2
- package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/integration-interview.md +24 -0
- package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/public-events-and-direction.md +76 -10
- package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/realization-and-gap-fill.md +5 -0
- package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/todo-list-example.md +1 -1
- package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/translate-example.md +39 -23
- package/package/corpus/skills/cratis-screenplay-automations-and-translations/references/translation-patterns.md +35 -12
- package/package/corpus/skills/cratis-screenplay-captures-and-reactions/SKILL.md +51 -81
- package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/atomic-batches-and-source-mappings.md +83 -0
- package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/capture-map-and-example.md +14 -4
- package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/reaction-forms.md +59 -0
- package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/record-capture-specifications.md +60 -0
- package/package/corpus/skills/cratis-screenplay-captures-and-reactions/references/specifying-triggers-and-clock.md +5 -1
- package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +26 -28
- package/package/corpus/skills/cratis-screenplay-command-surface/references/described-handlers.md +82 -0
- package/package/corpus/skills/cratis-screenplay-command-surface/references/generated-values-and-responses.md +82 -28
- package/package/corpus/skills/cratis-screenplay-command-surface/references/protected-reads.md +113 -0
- package/package/corpus/skills/cratis-screenplay-command-surface/references/shop-generation.md +45 -12
- package/package/corpus/skills/cratis-screenplay-command-surface/references/text-and-match-rules.md +119 -0
- package/package/corpus/skills/cratis-screenplay-command-surface/references/validate-rules.md +14 -4
- package/package/corpus/skills/cratis-screenplay-command-surface/verification.json +10 -3
- package/package/corpus/skills/cratis-screenplay-event-modeling/SKILL.md +12 -12
- package/package/corpus/skills/cratis-screenplay-event-modeling/references/parsed-not-runnable.md +5 -3
- package/package/corpus/skills/cratis-screenplay-event-modeling/verification.json +5 -1
- package/package/corpus/skills/cratis-screenplay-legacy-extraction/SKILL.md +2 -2
- package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/evidence-table-template.md +1 -1
- package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/generated-recovery.md +51 -14
- package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/intent-example.md +5 -0
- package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/loss-report-template.md +2 -1
- package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/rule-modeling.md +6 -3
- package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/signal-to-intent.md +1 -1
- package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +22 -28
- package/package/corpus/skills/cratis-screenplay-model-authoring/references/choosing-generation.md +20 -7
- package/package/corpus/skills/cratis-screenplay-model-authoring/references/language-reference.md +12 -6
- package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-loop.md +9 -1
- package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-tools.md +58 -10
- package/package/corpus/skills/cratis-screenplay-model-authoring/verification.json +4 -2
- package/package/corpus/skills/cratis-screenplay-model-review/SKILL.md +2 -5
- package/package/corpus/skills/cratis-screenplay-model-review/references/anti-patterns.md +2 -2
- package/package/corpus/skills/cratis-screenplay-model-review/references/checklist.md +2 -2
- package/package/corpus/skills/cratis-screenplay-model-review/references/quick-self-check.md +2 -1
- package/package/corpus/skills/cratis-screenplay-model-review/references/review-questions.md +3 -2
- package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/principles.md +1 -1
- package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/vocabulary-map.md +1 -1
- package/package/corpus/skills/cratis-screenplay-read-surface/SKILL.md +2 -3
- package/package/corpus/skills/cratis-screenplay-read-surface/references/named-keys.md +3 -1
- package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/SKILL.md +8 -3
- package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/references/gap-fill-handoff.md +8 -0
- package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/references/renderability-gate.md +6 -4
- package/package/corpus/skills/cratis-screenplay-scenario-coverage/SKILL.md +36 -29
- package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/berth-reservations.md +6 -0
- package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/invoicing-dues-design.md +15 -4
- package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/invoicing-dues-example.md +25 -20
- package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/public-event-coverage.md +14 -3
- package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/scenario-examples.md +9 -3
- package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/spec-forms-by-mode.md +7 -3
- package/package/corpus/skills/cratis-screenplay-slice-design/SKILL.md +3 -1
- package/package/corpus/skills/cratis-screenplay-slice-design/references/command-inventory.md +4 -4
- package/package/corpus/skills/cratis-screenplay-slice-design/references/rule-layers.md +4 -2
- package/package/corpus/skills/cratis-screenplay-slice-design/references/slicing.md +4 -3
- package/package/corpus/skills/cratis-screenplay-slice-design/references/worked-example.md +8 -8
- package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +49 -47
- package/package/corpus/skills/cratis-screenplay-specifications/references/actions-beyond-commands.md +11 -1
- package/package/corpus/skills/cratis-screenplay-specifications/references/generated-fixtures.md +48 -17
- package/package/corpus/skills/cratis-screenplay-specifications/references/vocabulary.md +14 -1
- package/package/corpus/skills/cratis-screenplay-streams-and-consistency/SKILL.md +25 -21
- package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/boundaries.md +2 -2
- package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/consistency-and-concurrency.md +14 -6
- package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/multi-service-models.md +4 -3
- package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/streams-example.md +7 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +30 -22
- package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet-example.md +3 -1
- package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet.md +47 -2
- package/package/corpus/skills/cratis-screenplay-toolchain/references/diagnostic-arguments.md +60 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/diagnostics.md +65 -5
- package/package/corpus/skills/cratis-screenplay-toolchain/references/executable-subset.md +50 -22
- package/package/corpus/skills/cratis-screenplay-toolchain/references/generation-checks.md +93 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/generation-strategies-example.md +117 -35
- package/package/corpus/skills/cratis-screenplay-toolchain/references/language-admission.md +46 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/preferred-syntax.md +3 -1
- package/package/corpus/skills/cratis-screenplay-toolchain/references/renderable-subset.md +25 -10
- package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.json +1800 -50
- package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.version +1 -1
- package/package/corpus/skills/cratis-screenplay-toolchain/references/traps.md +5 -3
- package/package/corpus/skills/cratis-screenplay-toolchain/verification.json +6 -1
- package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +8 -53
- package/package/corpus/skills/cratis-screenplay-ui-composition/references/file-references.md +2 -2
- package/package/corpus/skills/cratis-screenplay-ui-composition/references/interaction-example.md +54 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/verification.json +6 -3
- package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +3 -2
- package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +1 -1
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +30 -29
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +36 -9
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/choosing-generation.md +28 -8
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/publication-and-recovery.md +31 -0
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/sandbox-and-specrunner.md +8 -0
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/verification.json +4 -1
- 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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
`Cratis.Arc.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
(
|
|
50
|
-
|
|
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.
|
|
63
|
-
`[Unprotected]`
|
|
64
|
-
migrations, changes to projection definitions
|
|
65
|
-
serialized append path.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
`
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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>()
|
|
102
|
-
|
|
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
|
|
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
|
|
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>`
|
|
42
|
-
a raw `Guid`, nullable concept or
|
|
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.
|
|
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.
|
|
58
|
-
|
|
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).
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
11
|
-
reads
|
|
12
|
-
`
|
|
13
|
-
|
|
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]`.
|
|
17
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
-
|
|
99
|
-
|
|
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
|
|
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** |
|
|
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
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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.
|
|
193
|
-
using Cratis.Arc.
|
|
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
|
|
200
|
-
/// <
|
|
201
|
-
|
|
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
|
|
249
|
-
|
|
250
|
-
|
|
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.**
|
|
277
|
-
moving it to `Handle()` or returning `Result<,>`
|
|
278
|
-
|
|
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
|
|