@cratis/pi 2.22.0 → 2.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/package/corpus/agents/backend-developer.md +6 -5
  2. package/package/corpus/agents/code-reviewer.md +7 -7
  3. package/package/corpus/agents/coordinator.md +5 -4
  4. package/package/corpus/agents/frontend-developer.md +34 -40
  5. package/package/corpus/agents/orchestrator.md +5 -4
  6. package/package/corpus/agents/performance-reviewer.md +5 -4
  7. package/package/corpus/agents/planner.md +5 -4
  8. package/package/corpus/agents/repository-investigation-reviewer.md +2 -1
  9. package/package/corpus/agents/repository-investigator.md +2 -1
  10. package/package/corpus/agents/security-reviewer.md +5 -4
  11. package/package/corpus/agents/slice-implementer.md +8 -2
  12. package/package/corpus/agents/spec-writer.md +6 -4
  13. package/package/corpus/harnesses/pi/extensions/cratis-rules/index.ts +233 -10
  14. package/package/corpus/harnesses/pi/extensions/subagent/agents.ts +52 -7
  15. package/package/corpus/harnesses/pi/extensions/subagent/index.ts +14 -0
  16. package/package/corpus/hooks/README.md +29 -27
  17. package/package/corpus/hooks/agent-stop.md +1 -5
  18. package/package/corpus/hooks/scripts/cratis-patterns.json +1 -1
  19. package/package/corpus/hooks/scripts/validate-package-subpaths.sh +1 -2
  20. package/package/corpus/prompts/ship-changes.prompt.md +4 -0
  21. package/package/corpus/prompts/verify-ai-setup.prompt.md +17 -7
  22. package/package/corpus/rules/ai-distribution.md +60 -0
  23. package/package/corpus/rules/application-profile.md +73 -0
  24. package/package/corpus/rules/components.md +77 -44
  25. package/package/corpus/rules/dialogs.md +24 -20
  26. package/package/corpus/rules/efcore.md +35 -187
  27. package/package/corpus/rules/framework.md +1 -1
  28. package/package/corpus/rules/frontend-quality.md +1 -1
  29. package/package/corpus/rules/frontend-testing.md +4 -4
  30. package/package/corpus/rules/general.md +21 -162
  31. package/package/corpus/rules/guards-and-fuses.md +24 -10
  32. package/package/corpus/rules/local-work-artifacts.md +6 -0
  33. package/package/corpus/rules/new-repository-intake.md +34 -0
  34. package/package/corpus/rules/orleans.md +13 -27
  35. package/package/corpus/rules/profiles.md +6 -0
  36. package/package/corpus/rules/react.md +12 -12
  37. package/package/corpus/rules/reactors.md +28 -5
  38. package/package/corpus/rules/rtk.md +13 -3
  39. package/package/corpus/rules/specs.scenarios.csharp.md +18 -3
  40. package/package/corpus/rules/specs.typescript.md +1 -1
  41. package/package/corpus/rules/storybook.md +2 -2
  42. package/package/corpus/rules/typescript.md +1 -0
  43. package/package/corpus/rules/verification-discipline.md +7 -1
  44. package/package/corpus/rules/vertical-slices.md +20 -15
  45. package/package/corpus/rules/writing-correct-examples.md +1 -0
  46. package/package/corpus/skills/cratis-application-react-specifications/SKILL.md +4 -4
  47. package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +16 -8
  48. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +6 -4
  49. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +1 -1
  50. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +1 -1
  51. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +1 -1
  52. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +1 -1
  53. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +7 -4
  54. package/package/corpus/skills/cratis-arc-command/SKILL.md +9 -7
  55. package/package/corpus/skills/cratis-arc-command/references/command-result.md +1 -1
  56. package/package/corpus/skills/cratis-arc-command/references/handler-shapes.md +2 -2
  57. package/package/corpus/skills/cratis-arc-command/references/proxy-generation.md +6 -3
  58. package/package/corpus/skills/cratis-arc-command/references/read-model-injection.md +1 -1
  59. package/package/corpus/skills/cratis-arc-command-execution/SKILL.md +12 -2
  60. package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +5 -3
  61. package/package/corpus/skills/cratis-arc-ef-core-migration/SKILL.md +7 -4
  62. package/package/corpus/skills/cratis-arc-observable-query-http/SKILL.md +10 -3
  63. package/package/corpus/skills/cratis-arc-query-paging/SKILL.md +13 -8
  64. package/package/corpus/skills/cratis-arc-react-page/SKILL.md +27 -24
  65. package/package/corpus/skills/cratis-arc-react-page/references/data-page.md +15 -11
  66. package/package/corpus/skills/cratis-arc-react-page/references/data-tables.md +9 -2
  67. package/package/corpus/skills/cratis-arc-react-page/references/dialogs.md +11 -9
  68. package/package/corpus/skills/cratis-arc-react-page/references/queries-and-commands.md +26 -2
  69. package/package/corpus/skills/cratis-chronicle-cli-operations/SKILL.md +19 -13
  70. package/package/corpus/skills/cratis-chronicle-client-dotnet/SKILL.md +17 -15
  71. package/package/corpus/skills/cratis-chronicle-client-elixir/SKILL.md +24 -20
  72. package/package/corpus/skills/cratis-chronicle-client-kotlin/SKILL.md +26 -15
  73. package/package/corpus/skills/cratis-chronicle-client-typescript/SKILL.md +15 -11
  74. package/package/corpus/skills/cratis-chronicle-compliance/SKILL.md +14 -3
  75. package/package/corpus/skills/cratis-chronicle-event-constraints/SKILL.md +13 -6
  76. package/package/corpus/skills/cratis-chronicle-event-modeling/SKILL.md +2 -2
  77. package/package/corpus/skills/cratis-chronicle-event-specifications/SKILL.md +11 -5
  78. package/package/corpus/skills/cratis-chronicle-event-type-migration/SKILL.md +2 -2
  79. package/package/corpus/skills/cratis-chronicle-multi-tenancy/SKILL.md +2 -2
  80. package/package/corpus/skills/cratis-chronicle-projection/SKILL.md +23 -4
  81. package/package/corpus/skills/cratis-chronicle-projection/references/fluent-builder.md +1 -1
  82. package/package/corpus/skills/cratis-chronicle-projection/references/model-bound-attributes.md +2 -2
  83. package/package/corpus/skills/cratis-chronicle-reactor/SKILL.md +22 -8
  84. package/package/corpus/skills/cratis-chronicle-read-model/SKILL.md +1 -1
  85. package/package/corpus/skills/cratis-chronicle-read-model/references/queries.md +1 -1
  86. package/package/corpus/skills/cratis-chronicle-read-model-specifications/SKILL.md +3 -1
  87. package/package/corpus/skills/cratis-chronicle-reducer/SKILL.md +1 -1
  88. package/package/corpus/skills/cratis-chronicle-web-workbench/SKILL.md +19 -11
  89. package/package/corpus/skills/cratis-cli-terminal-workbench/SKILL.md +11 -6
  90. package/package/corpus/skills/cratis-code-review/SKILL.md +8 -6
  91. package/package/corpus/skills/cratis-components-accessibility/SKILL.md +112 -82
  92. package/package/corpus/skills/cratis-components-schema-editor/SKILL.md +12 -10
  93. package/package/corpus/skills/cratis-components-stepper-command-dialog/SKILL.md +11 -12
  94. package/package/corpus/skills/cratis-components-styling/SKILL.md +204 -170
  95. package/package/corpus/skills/cratis-components-toolbar/SKILL.md +32 -26
  96. package/package/corpus/skills/cratis-event-model-diagram/SKILL.md +1 -1
  97. package/package/corpus/skills/cratis-fundamentals-concept/SKILL.md +2 -2
  98. package/package/corpus/skills/cratis-fundamentals-type-discovery/SKILL.md +2 -2
  99. package/package/corpus/skills/cratis-lens-browser-extension/SKILL.md +4 -2
  100. package/package/corpus/skills/cratis-performance-review/SKILL.md +4 -2
  101. package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +15 -5
  102. package/package/corpus/skills/cratis-security-review/SKILL.md +20 -3
  103. package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +3 -3
  104. package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +12 -8
  105. package/package/corpus/skills/cratis-specifications-csharp/references/csharp-patterns.md +1 -1
  106. package/package/corpus/skills/cratis-specifications-csharp/references/integration-specs.md +7 -3
  107. package/package/corpus/skills/cratis-specifications-typescript/SKILL.md +6 -3
  108. package/package/corpus/skills/cratis-specifications-typescript/references/typescript-patterns.md +21 -4
  109. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +29 -17
  110. package/package.json +1 -1
  111. package/package/corpus/rules/terminal-commands.md +0 -19
@@ -159,6 +159,23 @@ public Task<IEnumerable<EventForEventSourceId>> Handle(AnEvent @event, EventCont
159
159
 
160
160
  > **Chronicle version note:** reactor side-effect handling of `EventForEventSourceId` wrappers has shipped since Chronicle 15.35.
161
161
 
162
+ ### Return a command (Arc + Chronicle)
163
+
164
+ When the follow-up belongs to **another slice**, return its `[Command]` instead of an event — the same idiom as returning events, applied to intent. Arc's Chronicle integration executes a returned command through the full pipeline (validation, authorization, `Handle()`) in its own service scope; a collection is executed in order when **every** element is a command (events and commands do not mix in one return). An unauthorized, invalid or throwing result is a **side-effect failure** — it fails the partition like a failed append, so it is never silently dropped.
165
+
166
+ ```csharp
167
+ public class StockKeeping : IReactor
168
+ {
169
+ [OnceOnly]
170
+ public Task<DecreaseStock> BookReserved(BookReserved @event, EventContext context) =>
171
+ Task.FromResult(new DecreaseStock(@event.Isbn, @event.Quantity));
172
+ }
173
+ ```
174
+
175
+ - **A reactor runs with no principal.** It is not an HTTP request, so a returned command gated by `[Roles]`/`[Authorize]` is denied. Opt in on the reactor **class** with `[ExecuteCommandsAsSystem("<role>", …)]` (`Cratis.Arc.Chronicle.Reactors`), naming only the roles its returned commands need. The attribute covers **returned** commands only.
176
+ - Mark the handler `[OnceOnly]` — a replay would otherwise execute the command again. Replay exclusion is not exactly-once: recovery re-delivers, so the command itself must be safe to repeat.
177
+ - The imperative alternative — inject `ICommandPipeline` and call `Execute(command)` — remains supported (see the **cratis-arc-command-execution** skill). It needs `[OnceOnly]` (`ARCCHR0006` warns otherwise), its `CommandResult` must be inspected (a denied or invalid result is otherwise discarded), and it needs its own execution context: `[ExecuteCommandsAsSystem]` does not apply to it.
178
+
162
179
  ## External event stores (outbox / inbox)
163
180
 
164
181
  Cross-service events route through Chronicle's outbox/inbox rather than a shared log:
@@ -176,11 +193,12 @@ The default is fire-and-forget. When a caller's correctness depends on all obser
176
193
 
177
194
  1. **Idempotent** — Reactors may be called more than once for the same event (e.g. during replay or recovery). Design accordingly. For a side effect that must **not** repeat on replay (emails, payments, external writes), mark the handler method `[OnceOnly]` — Chronicle then skips that handler for every event arriving as part of a **replay** (observer rewind, redaction, revision). That is the whole of it: `[OnceOnly]` is replay-exclusion, **not** exactly-once and **not** a per-event-source counter. Recovering a failed partition re-delivers the event as an ordinary observation, so the handler runs again — which is the point of a retry. When the side effect must survive that retry too, take a `ReactorDelivery` parameter alongside the attribute and keep a receipt under its identity, which is stable across the failure and the recovery.
178
195
  2. **Use event data directly** — Never query the read model back inside a reactor. The event contains all the information you need.
179
- 3. **Return events instead of injecting IEventLog** — If the reactor needs to produce new events, return them directly as `Task<TEvent>`, `Task<EventForEventSourceId>`, or a collection thereof. For commands in other slices, inject `ICommandPipeline` and execute a command. Avoid injecting `IEventLog` directly into a reactor.
196
+ 3. **Return events and commands instead of injecting the plumbing** — If the reactor needs to produce new events, return them directly as `Task<TEvent>`, `Task<EventForEventSourceId>`, or a collection thereof. For intent in another slice, return the `[Command]` (Arc executes it; a denied or invalid result fails the partition) — or inject `ICommandPipeline` and inspect the result. Either way the handler is `[OnceOnly]`. Avoid injecting `IEventLog` directly into a reactor.
180
197
  4. **Single responsibility** — Each reactor class should have a focused purpose. Multiple handler methods in one reactor are fine if they serve the same automation concern.
181
198
  5. **Failure behavior** — If a reactor throws, *or* a returned side-effect event fails to append (constraint violation, concurrency violation, or error), the failing event-source partition pauses until the issue is resolved. Repeated failures can **quarantine** the observer: once `QuarantineOnFailedPartitionCount`/`QuarantineOnFailedPartitionPercentage` (under `Observers`) is crossed, the observer enters the `Quarantined` state — reminders cancelled, retries stopped, automatic recovery suppressed. **A quarantined observer does NOT auto-resume on reconnect** — an operator must call `ClearObserverQuarantine()`. A periodic watchdog (default 60s, `WatchdogInterval`) re-routes stuck/dead observers but does not rescue quarantined ones. Design for resilience.
182
199
  6. **Don't throw to validate malformed inbound events** — reactors are not data-quality validators; invalid payloads must be rejected at the command/append site. When a malformed cross-service fact reaches a consumer, throwing just to reject it pauses the partition and can quarantine the whole observer. Instead append a clear failure/dead-letter event (e.g. `ProvisioningFailed`) or surface it via the operational failure path, and skip partial side effects.
183
200
  7. **No state** — Reactors should be stateless. Inject dependencies via primary constructor, but do not store mutable state on the class.
201
+ 8. **An unattended, irreversible pass reads the log, not the sink — and carries a fuse.** A reactor that deletes, revokes, bills or notifies *because a read model says something is absent* is trusting a materialized sink that is only as current as its observer: a paused partition, a replay in progress or a lagging projection makes "absent" true of the sink and false of the world. Decide from a **`[Passive]`** read model (computed on demand from the events at the moment the reactor runs) or from the events themselves. And the code shape cannot tell an **empty subject set** ("nothing to consider" — fail-safe) from an **empty qualifying set** ("nothing survived the filter" — fail-destructive: every subject is about to be acted on), so an unattended pass states its expected population, refuses when the count is implausible, and caps how many subjects one run may touch — the same discipline as [guards-and-fuses.md](./guards-and-fuses.md), applied to a reactor.
184
202
 
185
203
  ## Slice Types That Use Reactors
186
204
 
@@ -207,18 +225,23 @@ public class ProjectRegisteredNotifier(INotificationService notifications) : IRe
207
225
  ### Translation Example
208
226
 
209
227
  ```csharp
210
- public class StockKeeping(IStockKeeper stockKeeper, ICommandPipeline commandPipeline) : IReactor
228
+ [ExecuteCommandsAsSystem("inventory")]
229
+ public class StockKeeping : IReactor
211
230
  {
212
231
  /// <summary>
213
- /// Reacts to a book reservation by decreasing stock.
232
+ /// Reacts to a book reservation by decreasing stock in the inventory slice.
214
233
  /// </summary>
215
234
  /// <param name="event">The event.</param>
216
235
  /// <param name="context">The event context.</param>
217
- public async Task BookReserved(BookReserved @event, EventContext context) =>
218
- await commandPipeline.Execute(new DecreaseStock(@event.Isbn, await stockKeeper.GetStock(@event.Isbn)));
236
+ /// <returns>The command Arc executes as this reactor's side effect.</returns>
237
+ [OnceOnly]
238
+ public Task<DecreaseStock> BookReserved(BookReserved @event, EventContext context) =>
239
+ Task.FromResult(new DecreaseStock(@event.Isbn, @event.Quantity));
219
240
  }
220
241
  ```
221
242
 
243
+ The command is returned, not executed: Arc runs it through validation and authorization under the roles the class opts into, and a rejected command fails the partition instead of vanishing. `[OnceOnly]` keeps a replay from decreasing stock twice.
244
+
222
245
  ## Testing Reactors
223
246
 
224
247
  Use `ReactorScenario<TReactor>` (from `Cratis.Chronicle.Testing.Reactors`) — construct it with an `IServiceProvider` of NSubstitute mocks, fire events through `Given`, and assert on the mocks:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  applyTo: "**/*"
3
- description: "Use when running any shell command, reading files, or searching code. Route every command rtk supports through rtk (a hook auto-rewrites Bash commands); call rtk read/grep/find directly because the built-in Read/Grep/Glob tools bypass that hook."
3
+ description: "Use when running any shell command, reading files, or searching code. Route commands through rtk for human-readable output (a hook auto-rewrites Bash commands; prefix rtk yourself where it does not); use raw output when the result is parsed, compared exactly, or drives another operation."
4
4
  ---
5
5
 
6
6
  # Using rtk (Token-Optimized Commands)
@@ -9,11 +9,21 @@ description: "Use when running any shell command, reading files, or searching co
9
9
 
10
10
  ## How it works — let the hook do its job
11
11
 
12
- A `PreToolUse` hook **auto-rewrites Bash commands** to their `rtk` equivalent transparently and at zero token overhead (`git status` → `rtk git status`). For any supported command you do **nothing special** — run it normally and the hook wraps it.
12
+ A `PreToolUse` hook **auto-rewrites Bash commands** to their `rtk` equivalent transparently and at zero token overhead (`git status` → `rtk git status`). For any supported command you do **nothing special** — run it normally and the hook wraps it. Where no hook is configured, prefix the command yourself — including each command in an `&&` chain: `rtk git add . && rtk git commit -m "msg"`.
13
13
 
14
- - **Never bypass it.** Don't disable the hook, and reserve `rtk proxy <cmd>` for the rare case where you genuinely need the raw, unfiltered output (e.g. debugging what a filter dropped).
15
14
  - **Audit coverage** with `rtk gain` (savings so far) and `rtk discover` (commands that slipped past rtk — missed opportunities to close).
16
15
 
16
+ ## When to use raw output instead
17
+
18
+ rtk output is a summary for a reader. It is never evidence of exact file contents, paths, Git state, or command semantics, and a filter can drop the line you needed. Use the raw command — `rtk proxy <cmd>`, or the plain command where nothing rewrites it — whenever:
19
+
20
+ - the output is parsed by a script, compared exactly, or fed to another command;
21
+ - the output is the evidence for a claim about content, paths, or Git state;
22
+ - the command's exit code or exact diagnostics decide what happens next (security checks, structured JSON, machine-readable diagnostics);
23
+ - a wrapped command failed. Read the actual error and retry through `rtk proxy` before concluding the tool, path, or repository is missing.
24
+
25
+ Do not add `rtk` to scripts, hooks, or CI steps that deliberately use native commands; their raw output is the point. Exit codes are never traded for token savings — see [`exit-codes-and-wrappers.md`](./exit-codes-and-wrappers.md).
26
+
17
27
  ## What rtk supports (route these through rtk)
18
28
 
19
29
  - **Files** — `ls`, `tree`, `read`, `find`, `grep`, `diff`
@@ -51,10 +51,10 @@ public class and_all_information_is_valid : Specification
51
51
  #endif
52
52
  ```
53
53
 
54
- - `CommandScenario<TCommand>` exposes `Services` (an `IServiceCollection`), `Context`, `Execute(command)`, and `Validate(command)` — and nothing else; everything else is configured through `Services` and asserted with extension methods.
54
+ - `CommandScenario<TCommand>` exposes `Services` (an `IServiceCollection`), `Context`, `Execute(command[, ct])`, `Validate(command)`, `LastResult`, `Operations` and the operation assertions `ShouldHaveExecutedOperation<T>()` / `ShouldHaveCompensatedOperation<T>()` / `ShouldHaveNoOperationInvocations()`; with the Chronicle integration it also gains `Given`, `EventScenario`, `EventLog`, `EventSequence` and `AppendedEvents` as extension members. Everything else is configured through `Services` and asserted with extension methods.
55
55
  - **Event assertions are extension methods on the scenario** (from the Chronicle testing package), keyed by **command + event** type: `await _scenario.ShouldHaveAppendedEvent<TCommand, TEvent>(eventSourceId)` or the `(eventSourceId, Func<TEvent,bool> predicate)` overload; plus `await _scenario.ShouldHaveTailSequenceNumber<TCommand>(...)`. They return `Task`, so the fact is `async Task`.
56
56
  - **`CommandResult` assertions** (Arc extensions): `ShouldBeSuccessful()`, `ShouldNotBeSuccessful()`, `ShouldBeValid()`, `ShouldHaveValidationErrors()`, `ShouldHaveValidationErrorFor(message)`, `ShouldBeAuthorized()`, `ShouldNotBeAuthorized()`, `ShouldHaveExceptions()`/`ShouldNotHaveExceptions()`.
57
- - **Seed prior state through `_scenario.Services`** — there is no `Given`/`Events` on `CommandScenario`. To populate the DCB read models the validator/`Provide()`/`Handle()` inject, substitute `IReadModels` and register it (`_scenario.Services.Replace(new ServiceDescriptor(typeof(IReadModels), mock))`, mocking `GetInstanceById(...)`) or register projections with `_scenario.Services.AddReadModels(...)`.
57
+ - **Seed prior state through `_scenario.Given` or `_scenario.Services`.** `_scenario.Given.ForEventSource(id).Events(...)` materializes the seeded events into an **in-memory read-model dictionary** the harness answers keyed reads from — it never reaches the event log (see the false-greens list below). To populate the DCB read models the validator/`Provide()`/`Handle()` inject, substitute `IReadModels` and register it (`_scenario.Services.Replace(new ServiceDescriptor(typeof(IReadModels), mock))`, mocking `GetInstanceById(...)`) or register projections with `_scenario.Services.AddReadModels(...)`.
58
58
  - **Validator/`Provide()` dependencies:** register them in `_scenario.Services`; Arc testing discovers the concrete validator automatically. When several specs need different injected validator states, test rejected variants by instantiating the validator directly (per-scenario state can be order-sensitive under parallel xUnit).
59
59
  - **`Provide()`:** drive it through `CommandScenario` end-to-end; when the handler's decision is pure given provided data, also test `Handle(providedValue)` directly.
60
60
 
@@ -69,7 +69,21 @@ Every unhappy-path spec asserts **both**:
69
69
 
70
70
  `ShouldNotBeSuccessful()` alone can't tell a validation rejection from an unhandled exception. **Never assert on message strings** — they're presentation text. Authorization failures use `ShouldNotBeAuthorized()` (an unauthorized result has *no* validation errors, so `ShouldHaveValidationErrors()` would silently flip). To exercise a command that carries authorization attributes, register the identity the authorization evaluator reads into `_scenario.Services` (substitute the identity provider your app uses). **Adding `[Roles]` to an existing command breaks its happy-path AND validation-failure `.Execute()` specs** — an unauthorized result is not successful and carries no validation errors; switch those assertions to `ShouldNotBeAuthorized()`.
71
71
 
72
- `CommandResult` assertions (from `Cratis.Arc.Testing.Commands`; failures throw `CommandResultAssertionException`): `ShouldBeSuccessful()` (`isAuthorized && isValid && !hasExceptions`), `ShouldNotBeSuccessful()`, `ShouldBeValid()` (no validation errors — does *not* check authz/exceptions), `ShouldHaveValidationErrors()`, `ShouldHaveValidationErrorFor(message)`, `ShouldBeAuthorized()`, `ShouldNotBeAuthorized()`, `ShouldHaveExceptions()`.
72
+ `CommandResult` assertions (from `Cratis.Arc.Testing.Commands`; failures throw `CommandResultAssertionException`): `ShouldBeSuccessful()` (`isAuthorized && isValid && !hasExceptions`), `ShouldNotBeSuccessful()`, `ShouldBeValid()` (no validation errors — does *not* check authz/exceptions), `ShouldHaveValidationErrors()`, `ShouldHaveValidationErrorFor(message)`, `ShouldHaveValidationErrorBecauseOf(ValidationResultReason)`, `ShouldHaveConstraintViolationFor(name)`, `ShouldBeAuthorized()`, `ShouldNotBeAuthorized()`, `ShouldHaveExceptions()`.
73
+
74
+ #### False greens the harness lets through
75
+
76
+ Each of these passes while proving nothing. They are the reason the two-assertion rule above exists.
77
+
78
+ 1. **A discarded assertion `Task`.** The `IEventSequence` assertions `ShouldHaveAppendedEvent`/`ShouldHaveTailSequenceNumber` (Chronicle Testing) are genuinely `async`: in a `void [Fact]` that does not `await` them the exception lands on a `Task` nobody observes and the spec passes regardless of behavior, while the compiler's `CS4014` stays silent outside an `async` method. (The same-named `CommandScenario` overloads in Arc's Chronicle.Testing throw synchronously and only *return* a completed `Task`, so those fail even when discarded — but you cannot tell the two apart at the call site.) Make the fact `async Task` and `await` every `Task`-returning `Should*`. Chronicle's analyzer **`CHR0039` (`NonAwaitedAssertion`)** reports any discarded `Task`/`ValueTask`-returning `Should*` call from a Cratis testing package (Chronicle's and Arc's alike).
79
+ 2. **A dependency the spec forgot to seed.** When a validator's constructor, `Provide()` or `Handle()` asks for a read model that does not exist for the command's key, the validator is never built and no rule runs — yet the command is rejected (one validation result, reason **`DependencyUnavailable`**, HTTP 400), so `ShouldNotBeSuccessful()` passes and keeps passing with the rule it is named after deleted. `ShouldHaveValidationErrors()` **refuses** a result whose only errors carry that reason and tells you to seed the read model — or, when that rejection *is* the behavior under spec, assert it deliberately with `ShouldHaveValidationErrorBecauseOf(ValidationResultReason.DependencyUnavailable)`. A missing *service* (not a read model) surfaces as an exception instead (`ShouldHaveExceptions()`), and `ShouldNotBeSuccessful()` alone cannot tell the two apart from a genuine rejection.
80
+ 3. **`.Validate()` authorizes first.** `Validate(command)` runs the command filters, and authorization filters are ordered before validation. A validate-only spec on a `[Roles]`/`[Authorize]` command with no identity registered is denied for authorization — the rule it is named after never ran. Register the identity, or assert `ShouldNotBeAuthorized()` and say so in the name.
81
+ 4. **Constraint preconditions go to the event log, not `CommandScenario.Given`.** `_scenario.Given.ForEventSource(id).Events(...)` on a `CommandScenario` seeds an **in-memory read-model materialization only**; nothing reaches the event log a `[Unique]`/`IConstraint` is enforced against, so a "second registration with the same name" spec passes because the first one was never appended. Seed constraint preconditions through **`EventScenario`**'s `Given`, and assert the violation by constraint **name** (`ShouldHaveConstraintViolationFor`) — the same helper exists on `CommandResult` and on `IAppendResult`.
82
+ 5. **`GetInstances<T>` is not harness-backed.** Under `CommandScenario` the read-model harness answers keyed reads (`GetInstanceById`) from its seeded dictionary and forwards `GetInstances<T>`/`GetSnapshotsById`/`Watch` to the real client, which is not there. Code under spec that enumerates a read model needs a fake lookup registered in `Services`, not `Given`.
83
+ 6. **One child is not a children spec.** `[ChildrenFrom<T>]` without `key:` identifies children by the **event source id**, so every child event on one stream upserts the same child and a one-child spec stays green with the key wrong. Drive **two** children with distinct keys and assert the count.
84
+ 7. **Independently derived expectations.** An expected value computed in the spec with the same expression as the code under spec agrees with it by construction. Write the expected literal.
85
+
86
+ The `ReadModelScenario` and `EventScenario` equivalents of these live in the **cratis-chronicle-read-model-specifications** and **cratis-chronicle-event-specifications** skills.
73
87
 
74
88
  > **Validator-state order-sensitivity:** the command-scenario pipeline can cache enough state to make injected-validator branches order-sensitive when xUnit runs classes in parallel. Use `CommandScenario` for the valid path, test rejected state-variants by **instantiating the validator directly**, and — only for that validator-state case — put the command's specs in a **small xUnit `[Collection]`**. (This is distinct from the unique-value-collision rule below, where `[Collection]` is the *wrong* fix.)
75
89
 
@@ -151,6 +165,7 @@ public class and_name_already_exists(context context) : Given<context>(context)
151
165
  }
152
166
  ```
153
167
 
168
+ - `ChronicleCollection` is a project-defined `[CollectionDefinition]` class over `ChronicleOutOfProcessFixture` (Chronicle ships the fixture, not the collection).
154
169
  - `ExecuteCommand<TCommand>(url, cmd)` returns `CommandResult<object>?`; `ExecuteCommand<TCommand, TResult>(url, cmd)` returns `CommandResult<TResult>?`.
155
170
  - Helpers: `Context.ShouldHaveTailSequenceNumber(n)` (First = 0), `Context.ShouldHaveAppendedEvent<TEvent>(seq, eventSourceId, validator)`.
156
171
 
@@ -40,7 +40,7 @@ for_EventsCommandResponseValueHandler/
40
40
 
41
41
  ## BDD Pattern with `given()` Helper
42
42
 
43
- The `given()` function is the TypeScript equivalent of the C# `Specification` base class. It instantiates a context class (the "given"), passes it to the test suite, and ensures setup runs before assertions. This keeps the Establish/Because/should pattern consistent across both stacks.
43
+ The `given()` function is the TypeScript counterpart of the C# `Specification` base class. It instantiates a context class (the "given") **once when the `describe` is registered**, passes that instance to the suite callback, and leaves the per-behavior action to a `beforeEach` you write. Because the context is shared by every `it()` in the `describe`, keep one action per `describe` and let `it()` blocks only assert — that is what keeps Establish/Because/should consistent across both stacks.
44
44
 
45
45
  ```typescript
46
46
  import { an_events_command_response_value_handler } from '../given/an_events_command_response_value_handler';
@@ -17,7 +17,7 @@ Good candidates: a presentational card/list-item/form-field with multiple visual
17
17
 
18
18
  ## Preview infrastructure (what you get for free)
19
19
 
20
- A configured `.storybook/preview` should wrap every story in `CratisComponentsProvider` (from `@cratis/components/Common`) and import `@cratis/components/styles` so PrimeReact-based components render correctly. With that in place:
20
+ A configured `.storybook/preview` should wrap every story in `CratisComponentsProvider` (from `@cratis/components`) and import `@cratis/components/tokens`, `@cratis/components/styles` and (for the baseline look) `@cratis/components/theme` — the same three entries the app imports — so components render styled. With that in place:
21
21
 
22
22
  - **Auto prop tables** via `react-docgen-typescript` — a component's `interface` + per-prop JSDoc renders as a Docs prop table, and union props become `select` controls automatically. Keep a JSDoc comment on every prop. **`argTypes` is an override layer** (change control type, group under `table.category`, disable a control) — don't re-list every prop.
23
23
  - **Accessibility panel** (`@storybook/addon-a11y`) scans each story with axe-core. Treat new violations as defects.
@@ -67,7 +67,7 @@ export const Interactive: Story = {
67
67
  };
68
68
  ```
69
69
 
70
- - **Stateful only** — presentational atoms get no `play:`. Thin PrimeReact wrappers (overlay-portal components) are excluded — their behavior is the framework's.
70
+ - **Stateful only** — presentational atoms get no `play:`. Components' own overlay components (dialogs, dropdowns, toasts) are excluded — their behavior is the library's, not yours.
71
71
  - Prefer role/structure queries over localized text. `play:` runs in the Interactions panel (documentation/QA); the enforced behavioral gate is the Vitest spec.
72
72
 
73
73
  ## Router-dependent components
@@ -121,6 +121,7 @@ All identifiers, comments, JSDoc, and string literals must use **American Englis
121
121
 
122
122
  ## Folder Structure
123
123
 
124
+ - **Never give a `.tsx` the same basename as a `.ts` in the same folder.** The Arc proxy generator emits `<Slice>.ts` beside the slice's C#; a component named `<Slice>.tsx` next to it is **silently dropped from the program**: for one basename TypeScript keeps only the `.ts`, so the component's type errors are never reported and `tsc -b` stays green while the file is broken (reproduced on TypeScript 6.0.3 and 7.0.2 with `include: ["src/**/*"]`). Name the component for what it renders (`Listing.tsx`, `RegisterAuthor.tsx`), never for the slice, and consider a repository spec that fails on any `.tsx` with a sibling `.ts` of the same name.
124
125
  - Do not prefix a file, component, type, or symbol with the name of its containing folder or the concept it belongs to. Instead, use folder structure to provide that context.
125
126
  - Favor functional folder structure over technical folder structure.
126
127
  - Group files by the feature or concept they belong to, not by their technical role.
@@ -14,4 +14,10 @@ authored a file or preserve a chain of evidence about how it arrived.
14
14
  - Keep each check deterministic and runnable locally and in CI.
15
15
  - Never replace a real behavior check with a checksum, inventory, generated
16
16
  receipt, or provenance record.
17
- - Report failures and skipped checks honestly.
17
+ - Documentary evidence — a receipt, ledger, attestation, hash inventory,
18
+ snapshot, escrow copy, or ad-hoc `verify-*` script — is not verification and
19
+ is not produced unless a repository workflow (such as a governed release) or
20
+ the user asks for it. The check's own output is the evidence.
21
+ - Report the conclusion and any real uncertainty in a line or two, not the
22
+ audit trail. Show the output when asked, when a claim is contested, or when
23
+ the check failed. Report failures and skipped checks honestly.
@@ -17,12 +17,12 @@ This file is the reference for *what* goes in each part of a slice. Layout, slic
17
17
  ## Technical stack
18
18
 
19
19
  - .NET / C# (ASP.NET Core) — **Cratis Arc** for CQRS / model-bound commands and queries, **Cratis Chronicle** for event sourcing, MongoDB or EF Core for read models.
20
- - React + TypeScript (Vite) — Cratis Components (PrimeReact-based) + Arc-generated proxies, MVVM. Vitest + Mocha/Chai/Sinon for frontend specs.
20
+ - React + TypeScript (Vite) — Cratis Components 4.x + Arc-generated proxies, MVVM. Vitest + Mocha/Chai/Sinon for frontend specs.
21
21
  - xUnit + Cratis.Specifications + NSubstitute for C# specs (the `*Scenario` family — see [specs.md](./specs.md)).
22
22
 
23
23
  ## Proxy generation — the build dependency
24
24
 
25
- A Debug `dotnet build` generates a typed TypeScript proxy per command and query next to the slice's `.cs` — Debug is the canonical trigger because it carries the fullest, most reliably-emitted PDB debug information the generator uses to place generated files. Until the backend compiles, **no proxy files exist** and frontend code cannot reference them. **Backend → build → frontend, always** — backend and frontend for the same slice cannot be done in parallel. When a subsequent Release build is only there to verify the app compiles in that configuration, skip proxy regeneration with `dotnet build -c Release -p:CratisProxiesOutputPath=` rather than letting it re-run the generator against a different compilation. Never edit a generated proxy (`// @generated by Cratis` header); fix the C# source and rebuild.
25
+ A `dotnet build` with `CratisProxiesOutputPath` set generates a typed TypeScript proxy per command and query next to the slice's `.cs` — the generator target has **no configuration condition**; running it from the Debug build is the house convention (one configuration owns the generated files, so Release never rewrites them differently). The generator reads PDB sequence points only to name output files after their source file, and only when `CratisProxiesUseSourceFileAsOutputFile` is on; Arc's own remark says which types retain that information *can differ* between Debug and Release, which is one more reason to let a single configuration generate. Until the backend compiles, **no proxy files exist** and frontend code cannot reference them. **Backend → build → frontend, always** — backend and frontend for the same slice cannot be done in parallel. When a subsequent Release build is only there to verify the app compiles in that configuration, skip proxy regeneration with `dotnet build -c Release -p:CratisProxiesOutputPath=` rather than letting it re-run the generator against a different compilation. Never edit a generated proxy (`// @generated by Cratis` header); fix the C# source and rebuild.
26
26
 
27
27
  ## Model-bound — no controllers **[contract]**
28
28
 
@@ -116,7 +116,7 @@ Values render camel-cased; concepts unwrap to the value they hold; a value that
116
116
 
117
117
  ### `Provide()` — data for `Handle()` **[contract]**
118
118
 
119
- `Provide()` runs after authorization and validation, before `Handle()`. Its parameters resolve from DI (like `Handle()`'s); the command instance is `this`. Return one value, or a tuple (Arc binds tuple values to `Handle(...)` parameters by type). Each provided value must be consumed by a `Handle` parameter — an unused one is the **`ARC0005`** analyzer error. Use a **named record** when the provided values form a real domain/snapshot concept; a **tuple** when they are just separate inputs to event construction. It may be sync or async.
119
+ `Provide()` runs after authorization and validation, before `Handle()`. Its parameters resolve from DI (like `Handle()`'s); the command instance is `this`. Return one value, or a tuple (Arc binds tuple values to `Handle(...)` parameters by type). Each provided value should be consumed by a `Handle` parameter — an unused one raises the **`ARC0005`** analyzer warning. Use a **named record** when the provided values form a real domain/snapshot concept; a **tuple** when they are just separate inputs to event construction. It may be sync or async.
120
120
 
121
121
  ```csharp
122
122
  [Command]
@@ -163,11 +163,13 @@ Three attributes scope an append broad→narrow, and each one *also* (only with
163
163
 
164
164
  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.
165
165
 
166
- When the stream id is known only at runtime, implement `ICanProvideEventStreamId.GetEventStreamId()` instead of `[EventStreamId]`. ⚠️ Combining `ICanProvideEventStreamId` with a non-null `[EventStreamId]` attribute on the same command throws `AmbiguousEventStreamId` at startup — pick one (or set the attribute value to `null` to defer to the interface).
166
+ When the stream id is known only at runtime, implement `ICanProvideEventStreamId.GetEventStreamId()` instead of `[EventStreamId]`. ⚠️ Combining `ICanProvideEventStreamId` with a non-null `[EventStreamId]` attribute on the same command throws `AmbiguousEventStreamId` when that command is executed (the check runs while Arc builds the command context, not at startup) — pick one (or set the attribute value to `null` to defer to the interface).
167
167
 
168
168
  ### Event-source id resolution order **[contract]**
169
169
 
170
- `ICanProvideEventSourceId.GetEventSourceId()` → a single `EventSourceId`/`EventSourceId<T>`-derived property → a `[Key]` property → else Arc/Chronicle generates one.
170
+ `ICanProvideEventSourceId.GetEventSourceId()` first; otherwise the **first property, in declaration order, that qualifies** — an `EventSourceId`/`EventSourceId<T>`-derived property *or* a `[Key]`-marked one (there is no priority between those two kinds; Arc reports two qualifying properties as `ARCCHR0002` and tells you to implement the interface); otherwise Arc generates a new id. When `ICanProvideEventSourceId` is implemented and returns `null` (or throws), the result is `EventSourceId.Unspecified`, **not** a generated id.
171
+
172
+ ⚠️ **Two attributes are spelled `[Key]`.** Chronicle resolves the key from `Cratis.Chronicle.Keys.KeyAttribute`; `System.ComponentModel.DataAnnotations.KeyAttribute` compiles, reads identically, and does **nothing** in an application that uses Chronicle — every command gets a fresh generated event source id and every read model keyed by it resolves to nothing. Arc's analyzer **`ARCCHR0008`** reports the wrong one; if a `using` pulls in the DataAnnotations namespace, check which `[Key]` you have.
171
173
 
172
174
  ⚠️ **Injected read models resolve ONLY by the command's resolved event-source id** — not by the read-model type, and not by "the property that looks like its key." If the read model you need is keyed by a **different** value, direct injection silently hands you the **wrong** instance — or, when nothing exists for the command's id, `null` (or for a `[Passive]` projection a **default-valued** instance; see *Existence checks* below) — a correctness bug, not a compile error. The three failure shapes: (1) you need a *referenced other* entity (act on A, check B); (2) the command has **multiple** `EventSourceId<T>`-typed properties → ambiguous resolution (never rely on property order); (3) a create command whose id is generated inside `Handle()`. In all three, read by explicit key: `IReadModels.GetInstanceById<T>((EventSourceId)key)`.
173
175
 
@@ -197,6 +199,7 @@ Apply authorization attributes on the `[Command]` record or query method. **[con
197
199
  - **Never duplicate one fact as an aggregate event *plus* a per-item fanout.** If a command fans out per item (one `RequestSentToPartner` per partner), don't also emit an aggregate event carrying the whole list. Keep the fanout as the single source of truth and project any aggregate from it; pick per-item vs. single-with-list event from how consumers read it — never both.
198
200
  - **Command rejection normally appends no event.** Validation and business-rule rejections are command results, not facts. Add a rejection/failed event only when the business must audit or react to that failure later.
199
201
  - **One event name = one meaning.** Don't reuse a vague event for several domain facts; if facts can occur, be absent, or have consumers independently, split them into separate event types.
202
+ - **Never persist clock-relative state.** "Overdue", "expired", "due in 3 days" are true *now* and false later, and a projection only moves when an event arrives — so a projected `IsOverdue` is wrong the moment the clock crosses the line with no event to flip it. Store the **deadline** (a `DateOnly`/`DateTimeOffset` the business supplied) and compute the comparison at the query or UI edge, refreshing when time alone changes the answer. Only when *crossing* the deadline is itself a business fact (a fee is charged, a reminder goes out) does a scheduled process append an event for it.
200
203
 
201
204
  ```csharp
202
205
  /// <summary>Emitted when an author is registered.</summary>
@@ -250,7 +253,7 @@ Ladder, first that fits: (1) direct read-model (DCB) injection — only when key
250
253
 
251
254
  ⚠️ **This is invisible when a status enum's `0` is a real state** ("the state every stream starts in"): absent becomes byte-identical to freshly-created, so a command reads never-invited as already-invited. **Renumbering the enum from `1` is not the fix** — the value is then dropped from the payload and deserializes back to CLR `0` on the client anyway. Carry an explicit existence flag instead: `[SetValue<FirstEvent>(true)] bool Exists`, one `[SetValue<T>]` per event that can be the first for the stream. That is exactly the `[SetValue<T>]`-driven `bool` exception the no-defaults convention above allows for. An explicit `NotSet`/`Unknown` zero member also works, but only because the property stays present and readable; the flag is the robust form. ⚠️ **A spec suite will not catch this** — `CommandScenario`'s read-model harness answers `null` for an unseeded event-source id, which matches production for every row above *except* the passive projection. Cover the absent case by seeding a default-valued instance explicitly, not only `null`.
252
255
 
253
- **Injected-parameter nullability is the required/optional switch:** a nullable `TReadModel?` parameter (on a validator, `Provide()`, or `Handle()`) gets `null` injected for a never-created/removed instance — check `is null`. A **non-nullable** `TReadModel` whose instance does not exist throws **`CannotResolveValidatorDependency`** (non-nullable = "required, must exist"). ⚠️ Both switches ride on the same resolution, so on a **`[Passive]` projection** neither fires for a never-created instance: the nullable parameter receives a default-valued object and the `is null` guard never runs, and the non-nullable one resolves happily. Check the existence flag there, not nullability. Type by-id accessors you write yourself as `Task<T?>` so callers get the compiler signal Chronicle's own signature withholds.
256
+ **Injected-parameter nullability is the required/optional switch:** a nullable `TReadModel?` parameter (on a validator, `Provide()`, or `Handle()`) gets `null` injected for a never-created/removed instance — check `is null`. A **non-nullable** `TReadModel` whose instance does not exist is **rejected, not crashed**: Arc raises `ReadModelDoesNotExistForCommand`, which surfaces as a validation failure (HTTP 400) with reason `DependencyUnavailable` (non-nullable = "required, must exist"). The server-fault path (`CannotResolveCommandDependency` / `CannotResolveValidatorDependency`, HTTP 500) is reached only when the dependency is *not* a registered read model or no usable key was resolved — the exact contract is in the **cratis-arc-command** skill's `read-model-injection` reference. ⚠️ Both switches ride on the same resolution, so on a **`[Passive]` projection** neither fires for a never-created instance: the nullable parameter receives a default-valued object and the `is null` guard never runs, and the non-nullable one resolves happily. Check the existence flag there, not nullability. Type by-id accessors you write yourself as `Task<T?>` so callers get the compiler signal Chronicle's own signature withholds.
254
257
 
255
258
  ## Projections **[contract]**
256
259
 
@@ -286,7 +289,7 @@ public Task AuthorRegistered(AuthorRegistered @event, EventContext context) => .
286
289
  - **Signatures:** the first parameter is the event and drives dispatch; every parameter after it is resolved as a dependency — `EventContext`, a read model, a service, a `ReactorDelivery` — with no limit on how many. Return `Task`, `void`, or a synchronous side-effect return type. Prefer `Task`/async for real side effects, but sync returns are supported.
287
290
  - **[convention] `[OnceOnly]`** on any non-idempotent side effect (emails, payments, external writes) — replay re-fires otherwise. It covers **replay only**; a recovered failed partition still re-delivers, so guard that with a `ReactorDelivery` receipt.
288
291
  - **Side-effect events:** return them directly as event objects (single `TEvent`, `IEnumerable<object>`, or `Task<...>` thereof) — Chronicle appends them to the triggering event source. To target a different event source/stream, return `EventForEventSourceId(id, @event)` wrappers (the same cross-stream mechanism as a command — see [reactors.md](./reactors.md)); the wrapper is self-describing, so set the stream type, source type or `Subject` on it when you need full metadata control. Don't inject `IEventLog` for the normal path; reserve it for explicit append-result/wait/options/mixed-target workflows.
289
- - **Commands from a reactor:** inject `ICommandPipeline` and `Execute(command)` (scopeless overload) — runs the full validation/authorization/handler pipeline. Don't reach for `IEventLog` to change state in another slice.
292
+ - **Commands from a reactor:** **return** the `[Command]` (or a collection of commands) from a `[OnceOnly]` handler — Arc executes it through the full validation/authorization/handler pipeline and a denied or invalid result fails the partition rather than disappearing. A reactor has no principal, so a `[Roles]`-gated command needs `[ExecuteCommandsAsSystem("<role>")]` on the reactor class. The imperative `ICommandPipeline.Execute(command)` remains available (`[OnceOnly]`, inspect the `CommandResult`, supply your own execution context — see the **cratis-arc-command-execution** skill). Don't reach for `IEventLog` to change state in another slice. Details and the analyzer: [reactors.md](./reactors.md).
290
293
  - **[convention] Stateless, idempotent, read state via Chronicle read models** (`GetInstanceById`), not `IMongoCollection<T>`. Don't throw to "validate" malformed inbound events — that pauses the partition and can quarantine the observer; record an explicit failure/dead-letter event instead.
291
294
 
292
295
  ## Compliance (PII)
@@ -305,13 +308,15 @@ public Task AuthorRegistered(AuthorRegistered @event, EventContext context) => .
305
308
 
306
309
  The compliance **subject** is the encryption-key identity. It is resolved first-match-wins:
307
310
 
308
- | Priority | Mechanism |
309
- | --- | --- |
310
- | 1 | `Subject` as the second tuple element returned from `Handle()` (computed in the handler) |
311
- | 2 | `ICanProvideSubject.GetSubject()` on the command |
312
- | 3 | a `Subject`-typed property on the command |
313
- | 4 | an `EventSourceId<T>` property (the typed identity is the subject — no attribute) |
314
- | 5 | `[Subject]` on a **non-`EventSourceId<T>`** property (Arc converts via `ToString()`) |
311
+ | Priority | Mechanism | Resolved by |
312
+ | --- | --- | --- |
313
+ | 1 | `Subject` as a tuple element returned from `Handle()` (computed in the handler) | Arc |
314
+ | 2 | `ICanProvideSubject.GetSubject()` on the command | Arc |
315
+ | 3 | a `Subject`-typed property, or a `[Subject]`-marked property, on the **command** (any other type is converted with `ToString()`) | Arc |
316
+ | 4 | a `[Subject]`-marked property on the **event** being appended | Chronicle client |
317
+ | 5 | otherwise the **event source id** of the append — for a command, whatever its event-source-id resolution produced (`ICanProvideEventSourceId` → `EventSourceId<T>` property → `[Key]`) | Chronicle kernel |
318
+
319
+ So a command whose identity is an `EventSourceId<T>` needs no attribute at all: with nothing marked, that identity is the subject. Mark `[Subject]` only when the person is *not* the stream — and never on the `EventSourceId<T>` itself (`CHR0026`).
315
320
 
316
321
  `ICanProvideSubject` lives in `Cratis.Chronicle.Events` (alongside `ICanProvideEventSourceId`/`ICanProvideEventStreamId`). `[Subject]` is *not* aggregate identity. A managed read-model document has **one** subject — never mix multiple people's PII in one document; split into person-scoped models.
317
322
 
@@ -327,7 +332,7 @@ Cross-slice access is **read-only and goes through Chronicle** — inject anothe
327
332
 
328
333
  ## Dialogs
329
334
 
330
- Frontend commands run through Cratis dialog wrappers — **never** import `Dialog` from `primereact/dialog`. Use `CommandDialog` (executes a command) or `Dialog` (data collection) from `@cratis/components`. See [dialogs.md](./dialogs.md).
335
+ Frontend commands run through the Cratis dialogs — never a vendor or hand-rolled modal. Use `CommandDialog` (executes a command) from `@cratis/components/CommandDialog` or `Dialog` (data collection) from `@cratis/components/Dialogs`. See [dialogs.md](./dialogs.md).
331
336
 
332
337
  ## See also
333
338
 
@@ -16,6 +16,7 @@ For each framework type, attribute, method, prop, hook, or import in an example,
16
16
  - **C# / backend** — grep real usage in a reference application (e.g. Cratis **Studio**) and the product `Source/` trees of the Cratis repos checked out alongside this one (`Arc/Source`, `Chronicle/Source`). For extension methods, find the `public static … (this <Type> …)` signature and note **which type it extends**.
17
17
  - **React / Components** — the authoritative prop names are in the compiled type defs of the installed package (`node_modules/@cratis/components/dist/esm/**/*.d.ts`) or the `Components` source `dist`. Real usage: a reference app's `*.tsx`.
18
18
  - **Invented *domain* names are fine** (event/concept/command names like `AuthorRegistered`, `BookId`). Only **framework APIs** must be real. Never invent a framework interface, attribute, prop, method, or import path.
19
+ - **Read the source at the version the reader runs, never at a checkout's `HEAD`.** A sibling clone's working tree is whatever someone last checked out; the package the reader installed is a tag. Read with `git show <tag>:<path>` and `git grep <pattern> <tag> -- <path>` — both work without touching the checkout, so a dirty or shared worktree is never a reason to skip the check. Name the repository and the tag in the claim ("`Arc v22.16.0`, `ParameterDependencyResolver.cs:44-62`") so the next reader can re-run it. Where the corpus states a version in a skill's *Verified product sources* table, that tag is the one to read; when a consumer's pin is newer, re-read at theirs. A release note is a reason to look, not evidence that a behavior changed or that a workaround can be retired — retire a workaround only after the original reproduction passes at the new tag.
19
20
 
20
21
  ## Complete and correct
21
22
 
@@ -57,7 +57,7 @@ Behavior that can regress without TypeScript catching it:
57
57
  - **Component rendering** only when the markup, a disabled state, or wrapper
58
58
  behavior is genuinely the point.
59
59
 
60
- Do **not** specify generated Cratis proxies, PrimeReact or framework internals,
60
+ Do **not** specify generated Cratis proxies, Cratis Components or framework internals,
61
61
  CSS pixel-perfection, snapshots, or trivial presentational pass-throughs.
62
62
 
63
63
  ## View-model specification — the default shape
@@ -109,9 +109,9 @@ import React from 'react';
109
109
  import { renderToStaticMarkup } from 'react-dom/server';
110
110
  import { describe, beforeEach, it, vi } from 'vitest';
111
111
 
112
- vi.mock('primereact/dialog', () => ({
113
- Dialog: (props: { footer?: React.ReactNode; children?: React.ReactNode }) =>
114
- React.createElement('div', null, props.footer, props.children),
112
+ vi.mock('@cratis/components/Dialogs', () => ({
113
+ Dialog: (props: { buttons?: React.ReactNode; children?: React.ReactNode }) =>
114
+ React.createElement('div', null, props.children, props.buttons),
115
115
  }));
116
116
 
117
117
  describe('when rendered while busy', () => {
@@ -19,11 +19,13 @@ This skill decides **which scenario** and **what to cover**. The C# mechanics
19
19
 
20
20
  | Package | Version | Purpose |
21
21
  | --- | --- | --- |
22
- | `Cratis.Arc.Testing` | `22.10.4` | `CommandScenario<TCommand>` and the `CommandResult` assertions |
23
- | `Cratis.Arc.Chronicle.Testing` | `22.10.4` | `Given`, `EventLog`, `AppendedEvents` and the appended-event assertions on a command scenario |
24
- | `Cratis.Chronicle.Testing` | `16.45.2` | `EventScenario`, `ReadModelScenario<T>`, `ReactorScenario<T>` |
22
+ | `Cratis.Arc.Testing` | `22.16.0` | `CommandScenario<TCommand>` and the `CommandResult` assertions |
23
+ | `Cratis.Arc.Chronicle.Testing` | `22.16.0` | `Given`, `EventLog`, `AppendedEvents` and the appended-event assertions on a command scenario |
24
+ | `Cratis.Chronicle.Testing` | `18.3.0` | `EventScenario`, `ReadModelScenario<T>`, `ReactorScenario<T>` |
25
25
  | `Cratis.Specifications.XUnit` | `4.x` | The `Specification` base and the `ShouldXxx` assertions |
26
26
 
27
+ > Re-verified at the versions above by **symbol and signature**: every type, attribute and member this skill names exists at that tag, and the public surface it describes is unchanged since the previous verification (Chronicle 16.45.x / Arc 22.10.4 — the Chronicle 16→18 client diff is converters, options and doc comments; no type was removed or renamed). Behavior claims were verified at the earlier tag unless a section says otherwise.
28
+
27
29
  `Cratis.Testing` is the meta-package that brings `Cratis.Arc.Testing` and
28
30
  `Cratis.Arc.Chronicle.Testing` together. Reverify against the owning product
29
31
  repository before claiming support for another version.
@@ -109,9 +111,13 @@ public class and_name_is_unique : Specification
109
111
  #endif
110
112
  ```
111
113
 
112
- The scenario's own surface is `Services`, `Context`, `Execute(command)` and
113
- `Validate(command)` — `Validate` runs the filters without the handler.
114
- Everything else is an extension method.
114
+ The scenario's own surface is `Services`, `Context`, `Execute(command[, ct])`,
115
+ `Validate(command)` (runs the filters without the handler), `LastResult`,
116
+ `Operations` and the three operation assertions
117
+ (`ShouldHaveExecutedOperation<T>()`, `ShouldHaveCompensatedOperation<T>()`,
118
+ `ShouldHaveNoOperationInvocations()`); it is `IDisposable`/`IAsyncDisposable`.
119
+ Seeding (`Given`), the event log and the event assertions arrive as extension
120
+ members from the Chronicle integration.
115
121
 
116
122
  ## Step 5 — Seed prior state through `Given`
117
123
 
@@ -186,8 +192,10 @@ keyed by **command and event type**. They return `Task`, so the fact is
186
192
 
187
193
  ⚠️ On a command scenario the second argument is a **predicate**
188
194
  (`Func<TEvent, bool>`), not an assertion callback. The `Action<TEvent>` validator
189
- overloads exist only on the `IEventSequence` extensions in
190
- `Cratis.Chronicle.Testing`, which is a different receiver.
195
+ shape lives on other receivers — the `IEventSequence` extensions in
196
+ `Cratis.Chronicle.Testing`, `AppendedEventWithResult.ShouldHaveEvent<TEvent>(Action<TEvent>?)`,
197
+ and the `IChronicleSetupFixture` extension in `Cratis.Chronicle.XUnit.Integration`
198
+ — never on `CommandScenario`.
191
199
 
192
200
  Sequence numbers are **zero-based**: the first event is `0`, and the tail after a
193
201
  single append is `0`, never `1`.
@@ -15,10 +15,12 @@ told about them. Each has its own extension point.
15
15
 
16
16
  | Package | Version | Purpose |
17
17
  | --- | --- | --- |
18
- | `Cratis.Arc.Core` | `22.10.4` | `Cratis.Arc.Identity`, `Cratis.Arc.Authorization`, `Cratis.Arc.Authentication`, `Cratis.Arc.Tenancy` |
19
- | `Cratis.Arc` | `22.10.4` | ASP.NET Core hosting, `AddMicrosoftIdentityPlatformIdentityAuthentication` |
20
- | `@cratis/arc` | `22.10.4` | `IdentityProvider`, `IIdentity` |
21
- | `@cratis/arc.react` | `22.10.4` | `IdentityProvider` component, `useIdentity`, `RequireRole` |
18
+ | `Cratis.Arc.Core` | `22.16.0` | `Cratis.Arc.Identity`, `Cratis.Arc.Authorization`, `Cratis.Arc.Authentication`, `Cratis.Arc.Tenancy` |
19
+ | `Cratis.Arc` | `22.16.0` | ASP.NET Core hosting, `AddMicrosoftIdentityPlatformIdentityAuthentication` |
20
+ | `@cratis/arc` | `22.16.0` | `IdentityProvider`, `IIdentity` |
21
+ | `@cratis/arc.react` | `22.16.0` | `IdentityProvider` component, `useIdentity`, `RequireRole` |
22
+
23
+ > Re-verified at the versions above by **symbol and signature**: every type, attribute and member this skill names exists at that tag, and the public surface it describes is unchanged since the previous verification (Chronicle 16.45.x / Arc 22.10.4 — the Chronicle 16→18 client diff is converters, options and doc comments; no type was removed or renamed). Behavior claims were verified at the earlier tag unless a section says otherwise.
22
24
 
23
25
  Reverify before claiming support for another version.
24
26
 
@@ -1,6 +1,6 @@
1
1
  # Authentication
2
2
 
3
- Verified against `Cratis.Arc.Core` and `Cratis.Arc` `22.10.4`. Types are in
3
+ Verified against `Cratis.Arc.Core` and `Cratis.Arc` `22.16.0`. Types are in
4
4
  `Cratis.Arc.Authentication` and `Cratis.Arc.Identity`.
5
5
 
6
6
  ## The contract
@@ -1,6 +1,6 @@
1
1
  # Authorization
2
2
 
3
- Verified against `Cratis.Arc.Core` and `Cratis.Arc` `22.10.4`. Everything below
3
+ Verified against `Cratis.Arc.Core` and `Cratis.Arc` `22.16.0`. Everything below
4
4
  is in `Cratis.Arc.Authorization` unless stated.
5
5
 
6
6
  ## Default access differs by hosting model
@@ -1,6 +1,6 @@
1
1
  # Frontend identity
2
2
 
3
- Verified against `@cratis/arc` and `@cratis/arc.react` `22.10.4`.
3
+ Verified against `@cratis/arc` and `@cratis/arc.react` `22.16.0`.
4
4
 
5
5
  ## `@cratis/arc/identity`
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Running with an identity locally
2
2
 
3
- Verified against `Cratis.Arc.Core` and `Cratis.Arc` `22.10.4`.
3
+ Verified against `Cratis.Arc.Core` and `Cratis.Arc` `22.16.0`.
4
4
 
5
5
  ## What Arc does not give you
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Tenancy
2
2
 
3
- Verified against `Cratis.Arc.Core` `22.10.4`. Types are in `Cratis.Arc.Tenancy`.
3
+ Verified against `Cratis.Arc.Core` `22.16.0`. Types are in `Cratis.Arc.Tenancy`.
4
4
 
5
5
  Arc tenancy is **request-scoped tenant resolution**. It answers "which tenant is
6
6
  this request for". It is not Chronicle's tenant namespace isolation, and it does
@@ -61,9 +61,12 @@ the configured value.
61
61
 
62
62
  `ITenantIdAccessor.Current` gives the current `TenantId`, cached into an
63
63
  `AsyncLocal`. An empty resolution is `TenantId.NotSet`, whose value is the
64
- literal string `"[NotSet]"`. `TenantId.Default` is `"Default"` and `IsDefault`
65
- tests for it. `CurrentTenantIdIsNotSet.ThrowIfNotSet(...)` is the guard for code
66
- that requires a tenant.
64
+ literal string `"[NotSet]"`. `TenantId.Default` is `"Default"`. ⚠️ `IsDefault`
65
+ is `this == NotSet || this == Default` — it is true for an **unresolved** tenant
66
+ as well, so it cannot tell "single-tenant default" from "nothing resolved".
67
+ Compare against `TenantId.NotSet` explicitly, or use
68
+ `CurrentTenantIdIsNotSet.ThrowIfNotSet(...)`, wherever an unresolved tenant must
69
+ be refused.
67
70
 
68
71
  ⚠️ `NotSet` is a real string, not `null`. A check like
69
72
  `string.IsNullOrEmpty(tenantId)` never fires for an unresolved tenant. Compare
@@ -14,12 +14,14 @@ whatever `Handle()` returns into appended events, a response, or both.
14
14
 
15
15
  | Package | Version | Purpose |
16
16
  | --- | --- | --- |
17
- | `Cratis.Arc.Core` | `22.10.4` | `Cratis.Arc.Commands.ModelBound.CommandAttribute`, `CommandResult`, `CommandValidator<T>` |
18
- | `Cratis.Arc.Chronicle` | `22.10.4` | event append handling, `Cratis.Arc.Chronicle.Commands.NotAuditedAttribute` |
19
- | `Cratis.Arc.ProxyGenerator.Build` | `22.10.4` | `CratisProxiesOutputPath` MSBuild integration |
20
- | `Cratis.Fundamentals` | `7.18.2` | `Cratis.Monads.Result<TResult, TError>` |
21
- | `Cratis.Chronicle` | `16.39.1` | `EventTypeAttribute`, `EventForEventSourceId`, `ICanProvideEventSourceId` |
22
- | `@cratis/arc` | `22.10.4` | `ICommand`, `CommandResult`, `ValidationResult` |
17
+ | `Cratis.Arc.Core` | `22.16.0` | `Cratis.Arc.Commands.ModelBound.CommandAttribute`, `CommandResult`, `CommandValidator<T>` |
18
+ | `Cratis.Arc.Chronicle` | `22.16.0` | event append handling, `Cratis.Arc.Chronicle.Commands.NotAuditedAttribute` |
19
+ | `Cratis.Arc.ProxyGenerator.Build` | `22.16.0` | `CratisProxiesOutputPath` MSBuild integration |
20
+ | `Cratis.Fundamentals` | `7.19.2` | `Cratis.Monads.Result<TResult, TError>` |
21
+ | `Cratis.Chronicle` | `18.3.0` | `EventTypeAttribute`, `EventForEventSourceId`, `ICanProvideEventSourceId` |
22
+ | `@cratis/arc` | `22.16.0` | `ICommand`, `CommandResult`, `ValidationResult` |
23
+
24
+ > Re-verified at the versions above by **symbol and signature**: every type, attribute and member this skill names exists at that tag, and the public surface it describes is unchanged since the previous verification (Chronicle 16.45.x / Arc 22.10.4 — the Chronicle 16→18 client diff is converters, options and doc comments; no type was removed or renamed). Behavior claims were verified at the earlier tag unless a section says otherwise.
23
25
 
24
26
  Reverify before claiming support for another version. Arc without Chronicle is a
25
27
  supported setup; everything on this page that appends events needs Chronicle.
@@ -214,7 +216,7 @@ either way, so the reading is all a reviewer has to go on.
214
216
  ## Generate the TypeScript proxy
215
217
 
216
218
  ```xml
217
- <PackageReference Include="Cratis.Arc.ProxyGenerator.Build" Version="22.10.4" />
219
+ <PackageReference Include="Cratis.Arc.ProxyGenerator.Build" Version="22.16.0" />
218
220
 
219
221
  <PropertyGroup>
220
222
  <CratisProxiesOutputPath>$(MSBuildThisFileDirectory)../<Web>/src/api</CratisProxiesOutputPath>
@@ -1,6 +1,6 @@
1
1
  # CommandResult and ValidationResult
2
2
 
3
- Verified against `Cratis.Arc.Core` `22.10.4` and `@cratis/arc` `22.10.4`.
3
+ Verified against `Cratis.Arc.Core` `22.16.0` and `@cratis/arc` `22.16.0`.
4
4
 
5
5
  ## Server side
6
6
 
@@ -1,7 +1,7 @@
1
1
  # Handler shapes and how Arc dispatches them
2
2
 
3
- Verified against `Cratis.Arc.Core` and `Cratis.Arc.Chronicle` `22.10.4` with
4
- `Cratis.Chronicle` `16.39.1`.
3
+ Verified against `Cratis.Arc.Core` and `Cratis.Arc.Chronicle` `22.16.0` with
4
+ `Cratis.Chronicle` `18.3.0`.
5
5
 
6
6
  ## The pipeline around `Handle`
7
7
 
@@ -1,6 +1,6 @@
1
1
  # Proxy generation
2
2
 
3
- Verified against `Cratis.Arc.ProxyGenerator.Build` `22.10.4`.
3
+ Verified against `Cratis.Arc.ProxyGenerator.Build` `22.16.0`.
4
4
 
5
5
  ## Wiring
6
6
 
@@ -8,7 +8,7 @@ Reference the build package from the project that contains the commands,
8
8
  queries and read models, and set an output path:
9
9
 
10
10
  ```xml
11
- <PackageReference Include="Cratis.Arc.ProxyGenerator.Build" Version="22.10.4" />
11
+ <PackageReference Include="Cratis.Arc.ProxyGenerator.Build" Version="22.16.0" />
12
12
 
13
13
  <PropertyGroup>
14
14
  <CratisProxiesOutputPath>$(MSBuildThisFileDirectory)../<Web>/src/api</CratisProxiesOutputPath>
@@ -27,7 +27,7 @@ Output folders mirror the C# **namespace**, not the file path. A command in
27
27
 
28
28
  ## MSBuild properties
29
29
 
30
- Every property below is declared by the package with the default shown.
30
+ The first nine properties below are declared with the default shown in the package's `.props`; the last three are read by the `.targets` only when set to `true` (absent means off).
31
31
 
32
32
  | Property | Default | Effect |
33
33
  | --- | --- | --- |
@@ -40,6 +40,9 @@ Every property below is declared by the package with the default shown.
40
40
  | `CratisProxiesSkipQueryNameInRoute` | `false` | Leaves the query name out of the generated route |
41
41
  | `CratisProxiesApiPrefix` | `api` | Route prefix |
42
42
  | `CratisProxiesSkipFileIndexTracking` | `false` | Disables tracking which files the generator owns |
43
+ | `CratisProxiesSkipIndexGeneration` | `false` | Does not write the per-folder `index.ts` barrels |
44
+ | `CratisProxiesUseSourceFileAsOutputFile` | `false` | Names the generated file after the C# source file rather than the type |
45
+ | `CratisProxiesEmitInterfaces` | off | **Arc ≥ 22.15.0** (the underlying `--emit-interfaces` flag exists from 22.14.0; the MSBuild property from 22.15.0). Emits plain TypeScript `interface`s instead of `@field`-decorated classes — no `@cratis/fundamentals` import in the output. Only for consumers that never reconstruct instances from JSON; command and query proxies need the classes |
43
46
 
44
47
  ⚠️ `CratisProxiesSkipOutputDeletion` defaults to `true`. Guidance that tells you
45
48
  to set it to `true` "so the generator does not delete your hand-written files"
@@ -1,6 +1,6 @@
1
1
  # Injecting a read model into a command
2
2
 
3
- Verified against `Cratis.Arc.Core` `22.10.4` and `Cratis.Chronicle` `16.39.1`.
3
+ Verified against `Cratis.Arc.Core` `22.16.0` and `Cratis.Chronicle` `18.3.0`.
4
4
 
5
5
  A read model can be injected into a `CommandValidator<T>` constructor, into
6
6
  `Provide()`, or into `Handle()`. Arc resolves it through an