@cratis/pi 7.60.1 → 7.62.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/profiles.instructions.md +2 -0
- 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 +3 -2
- 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/issue-assignment.md +31 -0
- package/package/corpus/rules/profiles.md +2 -0
- 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-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/read-model-injection.md +9 -5
- 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-screenplay-streams-and-consistency/references/consistency-and-concurrency.md +5 -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.json +1 -1
|
@@ -89,14 +89,14 @@ public class and_all_information_is_valid : Specification
|
|
|
89
89
|
#endif
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
(`CommandScenario` event assertions are extension methods keyed by command + event type — `await _scenario.ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId[, predicate])`; seed prior state through `_scenario.Given.ForEventSource(id).Events(...)` or `_scenario.Services`. The mode matters: in legacy mode `Given` materializes an in-memory read-model dictionary only, so constraint preconditions go through `EventScenario`'s `Given`; in
|
|
92
|
+
(`CommandScenario` event assertions are extension methods keyed by command + event type — `await _scenario.ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId[, predicate])`; seed prior state through `_scenario.Given.ForEventSource(id).Events(...)` or `_scenario.Services`. The mode matters: in legacy mode `Given` materializes an in-memory read-model dictionary only, so constraint preconditions go through `EventScenario`'s `Given`; in decision mode (selected automatically from the command's read-protection mode and dependencies) `Given` writes to the scenario's real log and `EventScenario` is unavailable (accessing it throws), so seed everything through `Given`. Check which mode the spec's scenario uses; see `specs.scenarios.csharp.md` and `cratis-application-slice-specifications`.)
|
|
93
93
|
|
|
94
94
|
### What to specify
|
|
95
95
|
|
|
96
96
|
0. **Every contract specification first** — one spec each, named after it, using the contract's example values. Then add the code-derived cases below that the contract lacks and report them as proposals for the contract (the `.play` model when one covers the slice, otherwise the agreed outline); never present them as contract coverage. A `.play` specification maps to a plain-call spec for a pure decision, with a scenario added where the pipeline, validation, constraints or projections matter; never edit, skip or delete a spec derived from the contract to make code pass — change the code or return an edit request.
|
|
97
97
|
1. **Happy path** — succeeds, correct event(s) appended.
|
|
98
98
|
2. **Each validation failure** — assert **both** `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`. Do not assert on message strings by default; assert exact text only when that wording is the specified behavior, and name that behavior in the fact. Violate **only** the rule the spec is named after (build the command valid in every other respect), or a neighbouring rule makes it pass.
|
|
99
|
-
3. **Business-rule violations** — each `Result<,>` rejection / guarded decision condition. Plain injection is advisory; for `[ProtectedDecision]` or stream decisions use `
|
|
99
|
+
3. **Business-rule violations** — each `Result<,>` rejection / guarded decision condition. Plain injection under `Off` is advisory; for read protection `On`, `[ProtectedDecision]` or stream decisions use `new CommandScenario<T>()` with seeded events, `AppendConcurrently` and `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`, not pinned models and not `UseDecisionReads()`.
|
|
100
100
|
4. **Constraint violations** — `ShouldHaveConstraintViolationFor(name)` via `EventScenario`.
|
|
101
101
|
5. **Authorization** — `ShouldNotBeAuthorized()` (an unauthorized result has no validation errors).
|
|
102
102
|
6. **Repeat execution**, when a reactor invokes the command — the same command twice for the same source; specify whether the second is rejected, a no-op, or appends again.
|
|
@@ -32,7 +32,7 @@ Pick exactly one type per slice folder — determined by what the slice *does*.
|
|
|
32
32
|
|
|
33
33
|
| Type | What it does | Contents |
|
|
34
34
|
| --- | --- | --- |
|
|
35
|
-
| **State Change** | Accepts a command, appends events | Command + validator + event(s); optional `[Passive]` for a current read; a rule that must hold under concurrency needs a decision
|
|
35
|
+
| **State Change** | Accepts a command, appends events | Command + validator + event(s); optional `[Passive]` for a current read; a rule that must hold under concurrency needs read protection `On`, a stream decision or a constraint (see the `cratis-chronicle-decision-reads` skill, `references/choosing-how-a-rule-holds.md`) |
|
|
36
36
|
| **State View** | Builds a queryable read model from events | `[ReadModel]` + projection mappings or reducer code + static query method(s) on the record |
|
|
37
37
|
| **Automation** | Reacts to events, calls external systems / `ICommandPipeline`, or returns follow-up commands/events (including to another stream) | Reactor only |
|
|
38
38
|
| **Translation** | Takes data from outside our own facts (an external system or another service) and records it as our facts | An adapter at the boundary, often a reactor |
|
|
@@ -46,7 +46,7 @@ Commands are imperative intents (`Register`, `Create`); the slice folder is the
|
|
|
46
46
|
Tagged **[contract]** (framework-enforced) or **[convention]** (house default). Mechanics and examples live in `vertical-slices.md`.
|
|
47
47
|
|
|
48
48
|
1. **[contract] Model-bound — no controllers.** Commands are `[Command]` records with a public instance `Handle()` (Arc analyzers enforce this); queries are `static` methods on `[ReadModel]` records; projections/constraints/authorization use attributes. Arc generates the HTTP surface. For read-model style follow rule 11's ladder; for constraints use `IConstraint` when attributes cannot express the rule.
|
|
49
|
-
2. **[contract] Command validation & data flow.** Put command rejection in `CommandValidator<T>`, global value invariants in `ConceptValidator<T>`, and fetched/computed handler data in **`Provide()`** (runs after validation/authorization; may short-circuit with `ValidationResult.Error` / `Result<TProvided, ValidationResult>`). Keep `Handle()` focused on event construction. For a state-dependent rule that must hold **under concurrency**,
|
|
49
|
+
2. **[contract] Command validation & data flow.** Put command rejection in `CommandValidator<T>`, global value invariants in `ConceptValidator<T>`, and fetched/computed handler data in **`Provide()`** (runs after validation/authorization; may short-circuit with `ValidationResult.Error` / `Result<TProvided, ValidationResult>`). Keep `Handle()` focused on event construction. For a state-dependent rule that must hold **under concurrency**, start new code with read protection `On` (`<CratisReadProtection>On</CratisReadProtection>`): put the rule in a `CommandValidator<T>` with the read model as a constructor parameter and return the events from `Handle()`. Use a constraint for uniqueness, a per-stream read model or `IStreamReads`/`StreamDecision` for one stream, an aggregate for a rich lifecycle; `[ProtectedDecision]` with `DecisionRead<T>` is the older per-command opt-in. Under `Off` (the default when nothing is set) a validator over a plain read is a hint, not a guard. Return a typed rejection via `Result<TEvent, ValidationResult>` where needed; the result alone adds no guard. **Throwing from `Provide()`/`Handle()` is an exception (HTTP 500), not a validation rejection** — throw only for genuinely exceptional conditions, never for normal business rejection.
|
|
50
50
|
3. **[contract] Event-source id resolution:** `ICanProvideEventSourceId` first; else the first qualifying property in declaration order — an `EventSourceId`/`EventSourceId<T>`-derived value *or* a `[Key]` property (no priority between them; two candidates is `ARCCHR0002`); else Arc generates one. A value actually used as a Chronicle stream identity derives from `EventSourceId<T>` with the underlying `IComparable` primitive — never `ConceptAs<Guid>` for that stream identity. `NotSet`, `New()`, and primitive→derived-id operators are optional domain/API conveniences, not Chronicle requirements; typed empty/zero values are real specified stream IDs, not `EventSourceId.Unspecified`.
|
|
51
51
|
4. **[contract] Events never carry the event-source id** — it is implicit in the event context.
|
|
52
52
|
5. **[contract] `[Key]` / `[Subject]` are distinct.** `[Key]` is for event-source/read-model/projection key resolution; `[Subject]` is compliance identity only. Don't put either on an `EventSourceId<T>` value (it already is both); use them only for non-`EventSourceId<T>` values.
|
|
@@ -38,8 +38,8 @@ One precise line per load-bearing term, so the same word means the same thing ev
|
|
|
38
38
|
- **Observation command** — a command whose input comes from outside the model's own decisions (webhook, poll, sweep, retry, classifier). It compares with current state and appends nothing when nothing changed; looking is not a fact.
|
|
39
39
|
- **Signal event** — an event appended only to make a reactor do something (`…Requested`, `…Started`, `…Skipped`). Not a domain fact; use a job, state machine, command operation or `ICommandPipeline`.
|
|
40
40
|
- **Constraint** — `IConstraint`; an **append-time** invariant (uniqueness / concurrency) enforced at the event-store level.
|
|
41
|
-
- **DCB (Dynamic Consistency Boundary)** — enforcing a state-dependent rule under concurrency over explicitly enrolled event-state reads: stream decisions/exact scopes or protected, admitted projection decision reads. Plain injected models and `Result<TEvent, ValidationResult>` alone are advisory, not a guard.
|
|
42
|
-
- **Consistency boundary** — the scope within which an invariant holds atomically (an event source, a routed stream, or the event state enrolled by an admitted protected
|
|
41
|
+
- **DCB (Dynamic Consistency Boundary)** — enforcing a state-dependent rule under concurrency over explicitly enrolled event-state reads: read-model parameters under Arc read protection `On`, stream decisions/exact scopes, or protected, admitted projection decision reads. Plain injected models under `Off` (the default) and `Result<TEvent, ValidationResult>` alone are advisory, not a guard.
|
|
42
|
+
- **Consistency boundary** — the scope within which an invariant holds atomically (an event source, a routed stream, or the event state enrolled by an admitted protected read).
|
|
43
43
|
- **EventForEventSourceId** — self-describing wrapper to append an event to a **specific** (cross-stream) event source, from a command `Handle()` or from a reactor handler (reactor support has shipped since Chronicle 15.35). It carries the event stream type and id, source type, subject, occurred time, tags and causation, so it is also how a reactor sets those explicitly.
|
|
44
44
|
- **ReactorDelivery** — the identity of one delivery of one event to one reactor partition; declare it as a handler parameter and Chronicle passes it in. Stable across a replay and across recovering a failed partition, so a receipt kept under its `Id` is what makes a side effect survive re-delivery. An identity, not a guarantee — Chronicle does not know whether the effect ran.
|
|
45
45
|
|
|
@@ -92,6 +92,8 @@ profile includes neither Screenplay nor Stage.
|
|
|
92
92
|
| `cratis/engineering/typescript` | TypeScript engineering conventions | `cratis/engineering/core` |
|
|
93
93
|
| `cratis/engineering/react` | React engineering conventions | `cratis/engineering/core` |
|
|
94
94
|
|
|
95
|
+
The `cratis/engineering` profile also carries [issue-assignment.md](../../../rules/issue-assignment.md): when an agent starts work on a numbered issue in a known repository and nobody is assigned, it assigns the current `gh` user.
|
|
96
|
+
|
|
95
97
|
### Language Profiles
|
|
96
98
|
|
|
97
99
|
| Profile ID | Description |
|
package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md
CHANGED
|
@@ -117,10 +117,13 @@ arguments?, paging?, sorting?)`; `Observe(...)` returns `QueryObservation.Initia
|
|
|
117
117
|
and buffered `Next()`. Configure principal/policies in `Services`; schemes fail
|
|
118
118
|
closed without HTTP. Chronicle query `Given` can seed keyed models/events.
|
|
119
119
|
|
|
120
|
-
For guarded commands use `
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
For guarded commands use plain `new CommandScenario<T>()`: it selects the decision
|
|
121
|
+
mode from the command's read-protection mode and dependencies (`UseDecisionReads()`
|
|
122
|
+
is hidden and unnecessary). Given state, guarded reads and appends share one real
|
|
123
|
+
in-process log, unlike legacy mode above; an `Off` command with no protected
|
|
124
|
+
dependency stays in legacy mode. Pinned models cannot supply guards. Use `OnRoute(route)` and
|
|
125
|
+
`AppendConcurrently` with `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`
|
|
126
|
+
to prove conflicts (there is no `scenario.AppendConditions`); `ShouldHaveCompletedStream` proves
|
|
124
127
|
completion and `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)`
|
|
125
128
|
checks admission. See **cratis-application-slice-specifications**,
|
|
126
129
|
`references/query-and-decision-scenarios.md`, for sources and limitations.
|
|
@@ -89,7 +89,7 @@ The command carries input from the caller. `Handle()` is defined directly on the
|
|
|
89
89
|
| Reusable value invariant (length, format, range) | `ConceptValidator<T>` on the concept type |
|
|
90
90
|
| Command-input / cross-field / pre-handler rule (incl. injected read-model/service/identity checks) | `CommandValidator<TCommand>` with `RuleFor(...)` |
|
|
91
91
|
| Handler needs fetched/computed data before it can build the event | `Provide()` — fetches the data; may validate it and short-circuit |
|
|
92
|
-
| State-dependent rule that must hold **under concurrency** | `IStreamReads`/`StreamDecision` for one stream
|
|
92
|
+
| State-dependent rule that must hold **under concurrency** | Read protection `On`: the read model as a `CommandValidator<T>` constructor (or `Handle()`) parameter; a per-stream read model or `IStreamReads`/`StreamDecision` for one stream; an aggregate for a rich lifecycle. `[ProtectedDecision]` + `DecisionRead<T>` is the older per-command opt-in. Under `Off` (default) injection is a hint |
|
|
93
93
|
| Uniqueness | Chronicle `[Unique]` / `IConstraint` (race-safe) — not a read-model pre-check |
|
|
94
94
|
| Genuinely exceptional failure (bug, missing infra) | `throw` a domain exception |
|
|
95
95
|
|
|
@@ -183,13 +183,21 @@ Three attributes scope an append broad→narrow, and each one *also* (only with
|
|
|
183
183
|
|
|
184
184
|
Each attribute always tags the appended events (visible in `EventContext`). **Only when the command also carries `concurrency: true`** does each dimension join the server-side `ConcurrencyScope` (plus the event source id), with the optimistic strategy supplying the expected tail sequence number. A command **without** `concurrency: true` contributes no scope of its own, and the append falls to whatever concurrency strategy the event sequence is configured with — by default the optimistic one, scoped to the event source. So the attribute chooses *which dimensions* bound the check, not whether a check happens at all. Reach for it on high-contention state transitions where a stale read would let two commands both "win" — it narrows the boundary so appends that are genuinely independent stop colliding (concurrency failures surface on `CommandResult`/`AppendResult`). Adopt it deliberately per behavior, not as a blanket default.
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
`
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
186
|
+
New code sets `<CratisReadProtection>On</CratisReadProtection>` (Arc 22.106.0).
|
|
187
|
+
Under `On`, a read-model parameter of `Handle()` or of a `CommandValidator<T>`
|
|
188
|
+
constructor is read fresh and guards the append; the state rule goes in the
|
|
189
|
+
validator. Two entities: two parameters, `[LookUpBy(nameof(Cmd.Prop))]` on the
|
|
190
|
+
second, `IEnumerable<EventForEventSourceId>` returned. One stream inside a source:
|
|
191
|
+
a per-stream read model (`[ReadModel, EventStream<TSource>("Stream")]`) on a command
|
|
192
|
+
routed with `[EventSource<TSource>("Stream")]` and `[EventStreamId("{Prop}")]`, or
|
|
193
|
+
`IStreamReads.ForCommand()` in `Provide()` returning `StreamDecision.Append(...)`.
|
|
194
|
+
A deliberate advisory read is `[Unprotected("why")]`. Models must be admitted
|
|
195
|
+
(source-keyed projection with a string or Guid `Id`, or an unfiltered reducer
|
|
196
|
+
without constructor dependencies); joins, children and event-property keys are
|
|
197
|
+
refused. Under `Off` (the default) injection and keyed reads are advisory,
|
|
198
|
+
including in `Handle()`; `[ProtectedDecision]` + `DecisionRead<T>` is the older
|
|
199
|
+
per-command opt-in and still works. Exact returned scopes replace automatic
|
|
200
|
+
`concurrency: true` capture. Refusals: [choosing how a rule holds](../../../skills/cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md).
|
|
193
201
|
|
|
194
202
|
`[EventStreamId("{ReportingScopeId}:{Period}")]` resolves command property values
|
|
195
203
|
after validation; concepts supply their underlying values. An interface can
|
|
@@ -296,7 +304,7 @@ query reference for versioned behavior and composition trade-offs.
|
|
|
296
304
|
|
|
297
305
|
### Choosing read-model access in command-side code **[convention]**
|
|
298
306
|
|
|
299
|
-
Ladder, first that fits: (1)
|
|
307
|
+
Ladder, first that fits: (1) direct read-model injection — guarded under read protection `On`, advisory under `Off`; keyed by the command's own event-source id, or by `[LookUpBy]`; (2) a `[Passive]` projection; (3) `IReadModels.GetInstanceById<T>((EventSourceId)key)` for a different/derived key; (4) a materialized projection. A lookup *interface* is justified **only** for a genuine non-key search (e.g. find-by-email) — never to wrap a keyed `GetInstanceById`.
|
|
300
308
|
|
|
301
309
|
**[contract] Passive state is not sink state.** `IMongoCollection<T>` cannot
|
|
302
310
|
compute a passive read model; any leftover documents are not maintained state.
|
|
@@ -408,14 +416,15 @@ Redaction is a system-event request that removes payload availability from the e
|
|
|
408
416
|
|
|
409
417
|
## Cross-slice patterns **[convention]**
|
|
410
418
|
|
|
411
|
-
Cross-slice access is **read-only and goes through Chronicle** — inject another slice's read model (validator/`Provide()`/`Handle()`), read by explicit key with `IReadModels.GetInstanceById`, mark a `[Passive]` projection for a current read (a rule that must hold under concurrency needs a decision
|
|
419
|
+
Cross-slice access is **read-only and goes through Chronicle** — inject another slice's read model (validator/`Provide()`/`Handle()`), read by explicit key with `IReadModels.GetInstanceById`, mark a `[Passive]` projection for a current read (a rule that must hold under concurrency needs read protection `On`, a stream decision or a constraint, see the `cratis-chronicle-decision-reads` skill's `references/choosing-how-a-rule-holds.md`), or react to its events. **Never** instantiate or DI another slice's command/handler/service.
|
|
412
420
|
|
|
413
421
|
**DCB (Dynamic Consistency Boundary):** the boundary is the event state explicitly
|
|
414
422
|
enrolled in guarded decisions, not every model a command reads. Use stream
|
|
415
|
-
reads/exact scopes or
|
|
416
|
-
|
|
423
|
+
reads/exact scopes or read-model parameters under read protection `On`
|
|
424
|
+
(`[ProtectedDecision]` + `DecisionRead<T>` is the older per-command opt-in);
|
|
425
|
+
validator/`Provide()`/`Handle()` injection under `Off` is advisory. Stream-level
|
|
417
426
|
`concurrency: true` scopes are another explicit boundary, not protection for an
|
|
418
|
-
arbitrary snapshot. Specify conflicts with `
|
|
427
|
+
arbitrary snapshot. Specify conflicts with `AppendConcurrently` and actual seeded
|
|
419
428
|
events, not pinned models.
|
|
420
429
|
|
|
421
430
|
## Dialogs
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"cratis-arc-observable-query-http",
|
|
82
82
|
"cratis-arc-query-paging",
|
|
83
83
|
"cratis-arc-react-page",
|
|
84
|
+
"cratis-chronicle-decision-reads",
|
|
84
85
|
"cratis-chronicle-read-model",
|
|
85
86
|
"cratis-code-review",
|
|
86
87
|
"cratis-lens-browser-extension",
|
|
@@ -98,7 +99,7 @@
|
|
|
98
99
|
"vertical-slices.md"
|
|
99
100
|
]
|
|
100
101
|
},
|
|
101
|
-
"notes": "
|
|
102
|
+
"notes": "Whole-product floor unchanged at 22.100.0. Focused read-protection coverage, Arc 22.106.0-22.108.0: ReadProtectionMode Off, Measure and On (MSBuild property, assembly and command attributes, runtime MaximumMode cap), read-model parameters of Handle and validator constructors guarded under On, [LookUpBy], [Unprotected(\"why\")], per-stream read models on routed commands, Measure report, ReadChanged and ReadIncomplete refusals, and CommandScenario selecting decision mode automatically. the caller severity threshold (X-Allowed-Severity, [BlockOnValidationSeverity]) and the Error floor for commands declared On (22.108.0). [ProtectedDecision] and DecisionRead<T> retained as the per-command opt-in. The other 22.101.0-22.108.0 changes were not reviewed. Prior notes: scoped completion and routing validation, generated-target freshness, observable scenarios, asynchronous execution scopes, read-model diagnostics, ordered reactor refusal handling and ICommandExceptionMapper. No new runtime probe."
|
|
102
103
|
},
|
|
103
104
|
{
|
|
104
105
|
"id": "ai",
|
|
@@ -322,7 +323,7 @@
|
|
|
322
323
|
],
|
|
323
324
|
"rules": []
|
|
324
325
|
},
|
|
325
|
-
"notes": "
|
|
326
|
+
"notes": "Whole-product floor unchanged at 19.65.0. Focused read-protection coverage, Chronicle 19.68.0-19.71.x: admitted reducers (unfiltered, no constructor dependencies, 19.68.0), per-stream read models with [EventStream<TSource>], IUnitOfWork.EnforceReadProtection, AllowReads and GetAppendConditions, CHR0062-CHR0064. The other 19.66.0-19.71.x changes were not reviewed. Prior notes: explicit artifacts, collection observation, redelivery scenarios, webhook generation pins, enrollment contributors, seed ownership, reconnect readiness and hosting/storage fixes. No new kernel/transport probe."
|
|
326
327
|
},
|
|
327
328
|
{
|
|
328
329
|
"id": "arc-go",
|
|
@@ -16,7 +16,7 @@ Enforce a new rule on an existing command. Invoke the **cratis-arc-command-valid
|
|
|
16
16
|
- Reusable value invariant → `ConceptValidator<T>`.
|
|
17
17
|
- Command-input / cross-field / pre-handler rule → `CommandValidator<T>`.
|
|
18
18
|
- Handler needs fetched data first → `Provide()`.
|
|
19
|
-
- State rule that must hold **under concurrency** →
|
|
19
|
+
- State rule that must hold **under concurrency** → new code sets read protection `On` (`<CratisReadProtection>On</CratisReadProtection>`): the read model is a `CommandValidator<T>` constructor parameter and `Handle()` returns the events; add `[LookUpBy]` for a second entity, a per-stream read model or `IStreamReads`/`StreamDecision` for one stream. `[ProtectedDecision]` with `DecisionRead<T>` is the older per-command opt-in. Under `Off` (the default) a validator over a plain read is a hint, and `Result<TEvent, ValidationResult>` alone adds no guard. Specify the race with `AppendConcurrently` and seeded events, not pinned models.
|
|
20
20
|
- Uniqueness → `[Unique]` / `IConstraint`.
|
|
21
21
|
|
|
22
22
|
> **Never throw for normal business rejection** — a throw is HTTP 500, not a validation error. Recoverable rejection is a `ValidationResult` / `Result<,>`. Add a spec for the failure case (assert both not-successful and has-validation-errors). The skill carries the detail; don't duplicate it here.
|
|
@@ -31,7 +31,7 @@ Pick exactly one type per slice folder — determined by what the slice *does*.
|
|
|
31
31
|
|
|
32
32
|
| Type | What it does | Contents |
|
|
33
33
|
| --- | --- | --- |
|
|
34
|
-
| **State Change** | Accepts a command, appends events | Command + validator + event(s); optional `[Passive]` for a current read; a rule that must hold under concurrency needs a decision
|
|
34
|
+
| **State Change** | Accepts a command, appends events | Command + validator + event(s); optional `[Passive]` for a current read; a rule that must hold under concurrency needs read protection `On`, a stream decision or a constraint (see the `cratis-chronicle-decision-reads` skill, `references/choosing-how-a-rule-holds.md`) |
|
|
35
35
|
| **State View** | Builds a queryable read model from events | `[ReadModel]` + projection mappings or reducer code + static query method(s) on the record |
|
|
36
36
|
| **Automation** | Reacts to events, calls external systems / `ICommandPipeline`, or returns follow-up commands/events (including to another stream) | Reactor only |
|
|
37
37
|
| **Translation** | Takes data from outside our own facts (an external system or another service) and records it as our facts | An adapter at the boundary, often a reactor |
|
|
@@ -45,7 +45,7 @@ Commands are imperative intents (`Register`, `Create`); the slice folder is the
|
|
|
45
45
|
Tagged **[contract]** (framework-enforced) or **[convention]** (house default). Mechanics and examples live in `vertical-slices.md`.
|
|
46
46
|
|
|
47
47
|
1. **[contract] Model-bound — no controllers.** Commands are `[Command]` records with a public instance `Handle()` (Arc analyzers enforce this); queries are `static` methods on `[ReadModel]` records; projections/constraints/authorization use attributes. Arc generates the HTTP surface. For read-model style follow rule 11's ladder; for constraints use `IConstraint` when attributes cannot express the rule.
|
|
48
|
-
2. **[contract] Command validation & data flow.** Put command rejection in `CommandValidator<T>`, global value invariants in `ConceptValidator<T>`, and fetched/computed handler data in **`Provide()`** (runs after validation/authorization; may short-circuit with `ValidationResult.Error` / `Result<TProvided, ValidationResult>`). Keep `Handle()` focused on event construction. For a state-dependent rule that must hold **under concurrency**,
|
|
48
|
+
2. **[contract] Command validation & data flow.** Put command rejection in `CommandValidator<T>`, global value invariants in `ConceptValidator<T>`, and fetched/computed handler data in **`Provide()`** (runs after validation/authorization; may short-circuit with `ValidationResult.Error` / `Result<TProvided, ValidationResult>`). Keep `Handle()` focused on event construction. For a state-dependent rule that must hold **under concurrency**, start new code with read protection `On` (`<CratisReadProtection>On</CratisReadProtection>`): put the rule in a `CommandValidator<T>` with the read model as a constructor parameter and return the events from `Handle()`. Use a constraint for uniqueness, a per-stream read model or `IStreamReads`/`StreamDecision` for one stream, an aggregate for a rich lifecycle; `[ProtectedDecision]` with `DecisionRead<T>` is the older per-command opt-in. Under `Off` (the default when nothing is set) a validator over a plain read is a hint, not a guard. Return a typed rejection via `Result<TEvent, ValidationResult>` where needed; the result alone adds no guard. **Throwing from `Provide()`/`Handle()` is an exception (HTTP 500), not a validation rejection** — throw only for genuinely exceptional conditions, never for normal business rejection.
|
|
49
49
|
3. **[contract] Event-source id resolution:** `ICanProvideEventSourceId` first; else the first qualifying property in declaration order — an `EventSourceId`/`EventSourceId<T>`-derived value *or* a `[Key]` property (no priority between them; two candidates is `ARCCHR0002`); else Arc generates one. A value actually used as a Chronicle stream identity derives from `EventSourceId<T>` with the underlying `IComparable` primitive — never `ConceptAs<Guid>` for that stream identity. `NotSet`, `New()`, and primitive→derived-id operators are optional domain/API conveniences, not Chronicle requirements; typed empty/zero values are real specified stream IDs, not `EventSourceId.Unspecified`.
|
|
50
50
|
4. **[contract] Events never carry the event-source id** — it is implicit in the event context.
|
|
51
51
|
5. **[contract] `[Key]` / `[Subject]` are distinct.** `[Key]` is for event-source/read-model/projection key resolution; `[Subject]` is compliance identity only. Don't put either on an `EventSourceId<T>` value (it already is both); use them only for non-`EventSourceId<T>` values.
|
|
@@ -37,8 +37,8 @@ One precise line per load-bearing term, so the same word means the same thing ev
|
|
|
37
37
|
- **Observation command** — a command whose input comes from outside the model's own decisions (webhook, poll, sweep, retry, classifier). It compares with current state and appends nothing when nothing changed; looking is not a fact.
|
|
38
38
|
- **Signal event** — an event appended only to make a reactor do something (`…Requested`, `…Started`, `…Skipped`). Not a domain fact; use a job, state machine, command operation or `ICommandPipeline`.
|
|
39
39
|
- **Constraint** — `IConstraint`; an **append-time** invariant (uniqueness / concurrency) enforced at the event-store level.
|
|
40
|
-
- **DCB (Dynamic Consistency Boundary)** — enforcing a state-dependent rule under concurrency over explicitly enrolled event-state reads: stream decisions/exact scopes or protected, admitted projection decision reads. Plain injected models and `Result<TEvent, ValidationResult>` alone are advisory, not a guard.
|
|
41
|
-
- **Consistency boundary** — the scope within which an invariant holds atomically (an event source, a routed stream, or the event state enrolled by an admitted protected
|
|
40
|
+
- **DCB (Dynamic Consistency Boundary)** — enforcing a state-dependent rule under concurrency over explicitly enrolled event-state reads: read-model parameters under Arc read protection `On`, stream decisions/exact scopes, or protected, admitted projection decision reads. Plain injected models under `Off` (the default) and `Result<TEvent, ValidationResult>` alone are advisory, not a guard.
|
|
41
|
+
- **Consistency boundary** — the scope within which an invariant holds atomically (an event source, a routed stream, or the event state enrolled by an admitted protected read).
|
|
42
42
|
- **EventForEventSourceId** — self-describing wrapper to append an event to a **specific** (cross-stream) event source, from a command `Handle()` or from a reactor handler (reactor support has shipped since Chronicle 15.35). It carries the event stream type and id, source type, subject, occurred time, tags and causation, so it is also how a reactor sets those explicitly.
|
|
43
43
|
- **ReactorDelivery** — the identity of one delivery of one event to one reactor partition; declare it as a handler parameter and Chronicle passes it in. Stable across a replay and across recovering a failed partition, so a receipt kept under its `Id` is what makes a side effect survive re-delivery. An identity, not a guarantee — Chronicle does not know whether the effect ran.
|
|
44
44
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
profile: cratis/engineering
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Claim the issue you start work on
|
|
6
|
+
|
|
7
|
+
When an agent starts work on a GitHub issue, the issue says who is on it. Nobody can
|
|
8
|
+
tell from an unassigned issue that work has begun, so two people (or two agents) pick it
|
|
9
|
+
up, or it looks abandoned while it is being done.
|
|
10
|
+
|
|
11
|
+
- **Applies when** the work is tied to an issue with a number in a known repository: the
|
|
12
|
+
user named it (`#123`, `Cratis/Repo#123`, an issue URL), or the branch, task or handover
|
|
13
|
+
names it. Work with no issue, or an issue whose repository you cannot determine, is
|
|
14
|
+
outside this rule; never guess a number or a repository.
|
|
15
|
+
- **Read the assignees first**, in the issue's own repository:
|
|
16
|
+
`gh issue view <n> --repo <owner>/<repo> --json assignees,state`.
|
|
17
|
+
- **Assign only when nobody is assigned.** Run
|
|
18
|
+
`gh issue edit <n> --repo <owner>/<repo> --add-assignee @me` so the current `gh` user is
|
|
19
|
+
the assignee. `@me` resolves to whoever is authenticated, so it is the user the agent
|
|
20
|
+
works for, not a name you invent.
|
|
21
|
+
- **Never replace or add to an existing assignment.** Someone already on the issue means
|
|
22
|
+
it is claimed; leave it, and mention it if it affects the work.
|
|
23
|
+
- **Do it once, at the start,** before the first edit. Do not re-check on every turn.
|
|
24
|
+
- **Skip a closed issue** and say so; do not reopen or assign it.
|
|
25
|
+
- **If assigning fails** (no permission, `gh` not authenticated, the issue is in a
|
|
26
|
+
repository you cannot write to), say so in one line and continue with the work. A missing
|
|
27
|
+
assignee is never a reason to stop.
|
|
28
|
+
|
|
29
|
+
The user's request to work on the issue is the authority for this assignment; do not ask
|
|
30
|
+
before doing it. It authorizes assigning the issue and nothing else: commenting, labeling
|
|
31
|
+
or closing still follow [pull-requests.md](./pull-requests.md) and the user's request.
|
|
@@ -91,6 +91,8 @@ profile includes neither Screenplay nor Stage.
|
|
|
91
91
|
| `cratis/engineering/typescript` | TypeScript engineering conventions | `cratis/engineering/core` |
|
|
92
92
|
| `cratis/engineering/react` | React engineering conventions | `cratis/engineering/core` |
|
|
93
93
|
|
|
94
|
+
The `cratis/engineering` profile also carries [issue-assignment.md](./issue-assignment.md): when an agent starts work on a numbered issue in a known repository and nobody is assigned, it assigns the current `gh` user.
|
|
95
|
+
|
|
94
96
|
### Language Profiles
|
|
95
97
|
|
|
96
98
|
| Profile ID | Description |
|
|
@@ -116,10 +116,13 @@ arguments?, paging?, sorting?)`; `Observe(...)` returns `QueryObservation.Initia
|
|
|
116
116
|
and buffered `Next()`. Configure principal/policies in `Services`; schemes fail
|
|
117
117
|
closed without HTTP. Chronicle query `Given` can seed keyed models/events.
|
|
118
118
|
|
|
119
|
-
For guarded commands use `
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
For guarded commands use plain `new CommandScenario<T>()`: it selects the decision
|
|
120
|
+
mode from the command's read-protection mode and dependencies (`UseDecisionReads()`
|
|
121
|
+
is hidden and unnecessary). Given state, guarded reads and appends share one real
|
|
122
|
+
in-process log, unlike legacy mode above; an `Off` command with no protected
|
|
123
|
+
dependency stays in legacy mode. Pinned models cannot supply guards. Use `OnRoute(route)` and
|
|
124
|
+
`AppendConcurrently` with `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`
|
|
125
|
+
to prove conflicts (there is no `scenario.AppendConditions`); `ShouldHaveCompletedStream` proves
|
|
123
126
|
completion and `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)`
|
|
124
127
|
checks admission. See **cratis-application-slice-specifications**,
|
|
125
128
|
`references/query-and-decision-scenarios.md`, for sources and limitations.
|
|
@@ -88,7 +88,7 @@ The command carries input from the caller. `Handle()` is defined directly on the
|
|
|
88
88
|
| Reusable value invariant (length, format, range) | `ConceptValidator<T>` on the concept type |
|
|
89
89
|
| Command-input / cross-field / pre-handler rule (incl. injected read-model/service/identity checks) | `CommandValidator<TCommand>` with `RuleFor(...)` |
|
|
90
90
|
| Handler needs fetched/computed data before it can build the event | `Provide()` — fetches the data; may validate it and short-circuit |
|
|
91
|
-
| State-dependent rule that must hold **under concurrency** | `IStreamReads`/`StreamDecision` for one stream
|
|
91
|
+
| State-dependent rule that must hold **under concurrency** | Read protection `On`: the read model as a `CommandValidator<T>` constructor (or `Handle()`) parameter; a per-stream read model or `IStreamReads`/`StreamDecision` for one stream; an aggregate for a rich lifecycle. `[ProtectedDecision]` + `DecisionRead<T>` is the older per-command opt-in. Under `Off` (default) injection is a hint |
|
|
92
92
|
| Uniqueness | Chronicle `[Unique]` / `IConstraint` (race-safe) — not a read-model pre-check |
|
|
93
93
|
| Genuinely exceptional failure (bug, missing infra) | `throw` a domain exception |
|
|
94
94
|
|
|
@@ -182,13 +182,21 @@ Three attributes scope an append broad→narrow, and each one *also* (only with
|
|
|
182
182
|
|
|
183
183
|
Each attribute always tags the appended events (visible in `EventContext`). **Only when the command also carries `concurrency: true`** does each dimension join the server-side `ConcurrencyScope` (plus the event source id), with the optimistic strategy supplying the expected tail sequence number. A command **without** `concurrency: true` contributes no scope of its own, and the append falls to whatever concurrency strategy the event sequence is configured with — by default the optimistic one, scoped to the event source. So the attribute chooses *which dimensions* bound the check, not whether a check happens at all. Reach for it on high-contention state transitions where a stale read would let two commands both "win" — it narrows the boundary so appends that are genuinely independent stop colliding (concurrency failures surface on `CommandResult`/`AppendResult`). Adopt it deliberately per behavior, not as a blanket default.
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
`
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
185
|
+
New code sets `<CratisReadProtection>On</CratisReadProtection>` (Arc 22.106.0).
|
|
186
|
+
Under `On`, a read-model parameter of `Handle()` or of a `CommandValidator<T>`
|
|
187
|
+
constructor is read fresh and guards the append; the state rule goes in the
|
|
188
|
+
validator. Two entities: two parameters, `[LookUpBy(nameof(Cmd.Prop))]` on the
|
|
189
|
+
second, `IEnumerable<EventForEventSourceId>` returned. One stream inside a source:
|
|
190
|
+
a per-stream read model (`[ReadModel, EventStream<TSource>("Stream")]`) on a command
|
|
191
|
+
routed with `[EventSource<TSource>("Stream")]` and `[EventStreamId("{Prop}")]`, or
|
|
192
|
+
`IStreamReads.ForCommand()` in `Provide()` returning `StreamDecision.Append(...)`.
|
|
193
|
+
A deliberate advisory read is `[Unprotected("why")]`. Models must be admitted
|
|
194
|
+
(source-keyed projection with a string or Guid `Id`, or an unfiltered reducer
|
|
195
|
+
without constructor dependencies); joins, children and event-property keys are
|
|
196
|
+
refused. Under `Off` (the default) injection and keyed reads are advisory,
|
|
197
|
+
including in `Handle()`; `[ProtectedDecision]` + `DecisionRead<T>` is the older
|
|
198
|
+
per-command opt-in and still works. Exact returned scopes replace automatic
|
|
199
|
+
`concurrency: true` capture. Refusals: [choosing how a rule holds](../skills/cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md).
|
|
192
200
|
|
|
193
201
|
`[EventStreamId("{ReportingScopeId}:{Period}")]` resolves command property values
|
|
194
202
|
after validation; concepts supply their underlying values. An interface can
|
|
@@ -295,7 +303,7 @@ query reference for versioned behavior and composition trade-offs.
|
|
|
295
303
|
|
|
296
304
|
### Choosing read-model access in command-side code **[convention]**
|
|
297
305
|
|
|
298
|
-
Ladder, first that fits: (1)
|
|
306
|
+
Ladder, first that fits: (1) direct read-model injection — guarded under read protection `On`, advisory under `Off`; keyed by the command's own event-source id, or by `[LookUpBy]`; (2) a `[Passive]` projection; (3) `IReadModels.GetInstanceById<T>((EventSourceId)key)` for a different/derived key; (4) a materialized projection. A lookup *interface* is justified **only** for a genuine non-key search (e.g. find-by-email) — never to wrap a keyed `GetInstanceById`.
|
|
299
307
|
|
|
300
308
|
**[contract] Passive state is not sink state.** `IMongoCollection<T>` cannot
|
|
301
309
|
compute a passive read model; any leftover documents are not maintained state.
|
|
@@ -407,14 +415,15 @@ Redaction is a system-event request that removes payload availability from the e
|
|
|
407
415
|
|
|
408
416
|
## Cross-slice patterns **[convention]**
|
|
409
417
|
|
|
410
|
-
Cross-slice access is **read-only and goes through Chronicle** — inject another slice's read model (validator/`Provide()`/`Handle()`), read by explicit key with `IReadModels.GetInstanceById`, mark a `[Passive]` projection for a current read (a rule that must hold under concurrency needs a decision
|
|
418
|
+
Cross-slice access is **read-only and goes through Chronicle** — inject another slice's read model (validator/`Provide()`/`Handle()`), read by explicit key with `IReadModels.GetInstanceById`, mark a `[Passive]` projection for a current read (a rule that must hold under concurrency needs read protection `On`, a stream decision or a constraint, see the `cratis-chronicle-decision-reads` skill's `references/choosing-how-a-rule-holds.md`), or react to its events. **Never** instantiate or DI another slice's command/handler/service.
|
|
411
419
|
|
|
412
420
|
**DCB (Dynamic Consistency Boundary):** the boundary is the event state explicitly
|
|
413
421
|
enrolled in guarded decisions, not every model a command reads. Use stream
|
|
414
|
-
reads/exact scopes or
|
|
415
|
-
|
|
422
|
+
reads/exact scopes or read-model parameters under read protection `On`
|
|
423
|
+
(`[ProtectedDecision]` + `DecisionRead<T>` is the older per-command opt-in);
|
|
424
|
+
validator/`Provide()`/`Handle()` injection under `Off` is advisory. Stream-level
|
|
416
425
|
`concurrency: true` scopes are another explicit boundary, not protection for an
|
|
417
|
-
arbitrary snapshot. Specify conflicts with `
|
|
426
|
+
arbitrary snapshot. Specify conflicts with `AppendConcurrently` and actual seeded
|
|
418
427
|
events, not pinned models.
|
|
419
428
|
|
|
420
429
|
## Dialogs
|
|
@@ -201,9 +201,10 @@ produces. Pin an instance only when the events would be noise.
|
|
|
201
201
|
Both builder methods return `void`; there is nothing to await.
|
|
202
202
|
|
|
203
203
|
That is **legacy** read-model mode: seeded events populate an in-memory model,
|
|
204
|
-
not the append log.
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
not the append log. A command under read protection `On`, with a per-stream model or
|
|
205
|
+
with `[ProtectedDecision]` selects the decision mode itself (no `UseDecisionReads()`
|
|
206
|
+
call); actual events and decisions then share one in-process log. Pinned models
|
|
207
|
+
cannot supply decision guards. Read
|
|
207
208
|
[query and decision scenarios](references/query-and-decision-scenarios.md) when
|
|
208
209
|
specifying query pipelines, routed conflicts, projection admission or completion.
|
|
209
210
|
|
|
@@ -54,9 +54,18 @@ integration setup: scenario pins do not replace arbitrary stores.
|
|
|
54
54
|
|
|
55
55
|
## Guarded decisions
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
mode
|
|
59
|
-
|
|
57
|
+
Create `new CommandScenario<TCommand>()`. It selects Chronicle's in-process decision
|
|
58
|
+
mode from the command's read-protection mode and dependencies (for example under
|
|
59
|
+
`On`, or with a per-stream read model); do not call `UseDecisionReads()`, which is
|
|
60
|
+
hidden and unnecessary. This mode shares **one real in-process event log** among
|
|
61
|
+
given events, guarded reads, constraints and returned appends. Seed actual events
|
|
62
|
+
(`Given.ForEventSource(id).Events(...)`, or `.OnRoute(route).Events(...)`); protected
|
|
63
|
+
reads fold the log and ignore a pinned `.ReadModel(m)`, which only pins an advisory
|
|
64
|
+
(plain) read. Assert a lost race with
|
|
65
|
+
`result.ShouldHaveValidationErrorBecauseOf(ValidationResultReason.ConcurrencyViolation)`
|
|
66
|
+
after `scenario.AppendConcurrently(id, events...)`. There is no
|
|
67
|
+
`scenario.AppendConditions`. An `Off` command with no protected dependency stays in
|
|
68
|
+
legacy mode.
|
|
60
69
|
`Given.ForEventSource(id).OnRoute(route).Events(...)` preserves routing;
|
|
61
70
|
`scenario.EventRoutes.For<TSource>(stream).WithStreamId(...)` creates the route.
|
|
62
71
|
Queue interference using `AppendConcurrently(id, route, event)` and assert a
|
|
@@ -159,11 +159,15 @@ protect plain HTTP snapshots, waits and long polls; older versions leave them un
|
|
|
159
159
|
|
|
160
160
|
### Validators are not an authorization boundary
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
Authorization runs before validation, so a caller who may not act never reaches a
|
|
163
|
+
validator. Express who may act through policies, roles and `[Authorize]`, not a
|
|
164
|
+
validator. A state rule ("the order is still open") belongs in a
|
|
165
|
+
`CommandValidator<T>` with the read model as a constructor parameter; under read
|
|
166
|
+
protection `On` that read guards the append, under `Off` (the default) it is a
|
|
167
|
+
hint, and in an `[Unprotected]` command it is advisory. A state-dependent
|
|
168
|
+
permission or ownership rule (this member owns the order) may sit there too, with
|
|
169
|
+
the same limit. Under `[ProtectedDecision]` without `On`, only a parameterless
|
|
170
|
+
validator runs; see the command-validation and Chronicle guidance.
|
|
167
171
|
|
|
168
172
|
Read [authorization](references/authorization.md) for the guest rules, evaluation
|
|
169
173
|
order, the two baselines, default access under Arc's two hosting models, the
|
|
@@ -250,8 +254,9 @@ To run locally with an identity, supply the forwarded headers yourself — see
|
|
|
250
254
|
method directly.
|
|
251
255
|
- Observable queries that serve private or revocable data have an emission guard
|
|
252
256
|
backed by current session and membership state.
|
|
253
|
-
-
|
|
254
|
-
|
|
257
|
+
- State-dependent permission and ownership rules are guarded: read protection `On`
|
|
258
|
+
with the read model in the validator or handler, or `[ProtectedDecision]` commands
|
|
259
|
+
with the rule in `Provide()`/`Handle()`. Under `Off` they are hints.
|
|
255
260
|
- Discovery endpoints (`/.cratis/commands`, `/.cratis/queries`,
|
|
256
261
|
`/.cratis/identity-details/schema`, `/.cratis/users`, `/.cratis/tenants`) are
|
|
257
262
|
tested under Production settings; introspection is restricted or disabled where it
|
|
@@ -319,17 +319,17 @@ privileged.
|
|
|
319
319
|
|
|
320
320
|
## Protected decisions and validators
|
|
321
321
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
`[Unprotected]` command
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
322
|
+
Under read protection `On`, the read models in a convention-registered
|
|
323
|
+
`CommandValidator<T>` constructor are read fresh and guard the command's append, so
|
|
324
|
+
a state-dependent permission or ownership rule written there is decision-safe. Under
|
|
325
|
+
`Off` (the default) and `Measure` the same validator reads a plain model: a hint. In
|
|
326
|
+
an `[Unprotected]` command (or a model or method marked `[Unprotected("why")]`) the
|
|
327
|
+
read is advisory, so the rule is **not decision-safe**. A `[ProtectedDecision]`
|
|
328
|
+
command without `On` runs only certified, parameterless validators and refuses a
|
|
329
|
+
validator whose constructor takes dependencies; put such a rule in `Provide()` or
|
|
330
|
+
`Handle()` using `DecisionRead<T>` or `IDecisionReads`. Hand-registered scoped,
|
|
331
|
+
singleton, factory or instance validators are refused under `On`. Keep
|
|
332
|
+
static/service-locator state out of validators: a guard covers reads Arc can see.
|
|
333
333
|
Source: [protected validator boundary](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/chronicle/read-models/injecting-into-commands.md).
|
|
334
334
|
|
|
335
335
|
## Analyzer diagnostics
|
|
@@ -175,8 +175,9 @@ silently takes the first declared. Implement `ICanProvideEventSourceId`.
|
|
|
175
175
|
|
|
176
176
|
Read [decisions and routing](references/decisions-and-routing.md) when a command
|
|
177
177
|
needs guarded state, stream-id templates, definition-backed routes, event tags or
|
|
178
|
-
post-commit `CompleteStream`.
|
|
179
|
-
|
|
178
|
+
post-commit `CompleteStream`. A read-model parameter guards the append only under
|
|
179
|
+
read protection `On`; under `Off` (the default) it is advisory. `Result<,>` and
|
|
180
|
+
`concurrency: true` are not protection; see
|
|
180
181
|
[choosing how a rule holds](../cratis-chronicle-decision-reads/references/choosing-how-a-rule-holds.md).
|
|
181
182
|
A returned `EventForEventSourceId` route wins over command context.
|
|
182
183
|
|