@cratis/pi 7.34.1 → 7.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/package/corpus/agents/security-reviewer.md +1 -0
  2. package/package/corpus/agents/spec-writer.md +1 -1
  3. package/package/corpus/harnesses/pi/extensions/shared/rules.ts +3 -1
  4. package/package/corpus/hooks/scripts/cratis-nuget-pins.txt +7 -6
  5. package/package/corpus/hooks/scripts/cratis-release-notes-reviewed.cjs +7 -6
  6. package/package/corpus/hooks/scripts/cratis-store-mutations.json +7 -3
  7. package/package/corpus/product-sync.json +896 -0
  8. package/package/corpus/prompts/add-business-rule.prompt.md +1 -1
  9. package/package/corpus/prompts/ship-changes.prompt.md +2 -2
  10. package/package/corpus/rules/ai-distribution.md +31 -3
  11. package/package/corpus/rules/application-profile.md +2 -1
  12. package/package/corpus/rules/components.md +66 -7
  13. package/package/corpus/rules/concepts.md +11 -3
  14. package/package/corpus/rules/csharp.md +3 -1
  15. package/package/corpus/rules/dialogs.md +9 -0
  16. package/package/corpus/rules/efcore.md +1 -1
  17. package/package/corpus/rules/elixir.md +49 -0
  18. package/package/corpus/rules/framework.md +5 -3
  19. package/package/corpus/rules/general.md +1 -0
  20. package/package/corpus/rules/glossary.md +4 -4
  21. package/package/corpus/rules/go-engineering.md +60 -0
  22. package/package/corpus/rules/go.md +115 -0
  23. package/package/corpus/rules/java.md +5 -2
  24. package/package/corpus/rules/kotlin.md +7 -5
  25. package/package/corpus/rules/orleans.md +65 -9
  26. package/package/corpus/rules/profiles.md +43 -6
  27. package/package/corpus/rules/pull-requests.md +3 -3
  28. package/package/corpus/rules/react.md +8 -1
  29. package/package/corpus/rules/reactors.md +27 -3
  30. package/package/corpus/rules/specs.csharp.md +11 -4
  31. package/package/corpus/rules/specs.scenarios.csharp.md +35 -4
  32. package/package/corpus/rules/specs.typescript.md +2 -1
  33. package/package/corpus/rules/storybook.md +1 -1
  34. package/package/corpus/rules/typescript.md +1 -1
  35. package/package/corpus/rules/vertical-slices.md +37 -5
  36. package/package/corpus/skills/cratis-application-react-specifications/SKILL.md +4 -2
  37. package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +18 -8
  38. package/package/corpus/skills/cratis-application-slice-specifications/references/query-and-decision-scenarios.md +65 -0
  39. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +53 -57
  40. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +38 -42
  41. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +18 -7
  42. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authproxy.md +30 -0
  43. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +12 -9
  44. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +9 -9
  45. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/observable-emission-guards.md +8 -5
  46. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +20 -4
  47. package/package/corpus/skills/cratis-arc-command/SKILL.md +32 -10
  48. package/package/corpus/skills/cratis-arc-command/references/command-result.md +12 -5
  49. package/package/corpus/skills/cratis-arc-command/references/decisions-and-routing.md +95 -0
  50. package/package/corpus/skills/cratis-arc-command/references/generated-values.md +3 -2
  51. package/package/corpus/skills/cratis-arc-command/references/handler-shapes.md +8 -8
  52. package/package/corpus/skills/cratis-arc-command/references/proxy-generation.md +22 -3
  53. package/package/corpus/skills/cratis-arc-command/references/read-model-injection.md +13 -1
  54. package/package/corpus/skills/cratis-arc-command-execution/SKILL.md +19 -9
  55. package/package/corpus/skills/cratis-arc-command-kotlin/SKILL.md +44 -18
  56. package/package/corpus/skills/cratis-arc-command-kotlin/references/read-model-naming.md +29 -0
  57. package/package/corpus/skills/cratis-arc-command-operation/SKILL.md +10 -9
  58. package/package/corpus/skills/cratis-arc-command-operation/references/execution-and-recovery.md +2 -2
  59. package/package/corpus/skills/cratis-arc-command-operation/references/testing.md +4 -3
  60. package/package/corpus/skills/cratis-arc-command-typescript/SKILL.md +42 -22
  61. package/package/corpus/skills/cratis-arc-command-typescript/references/chronicle-events.md +36 -5
  62. package/package/corpus/skills/cratis-arc-command-typescript/references/command-operations.md +114 -0
  63. package/package/corpus/skills/cratis-arc-command-typescript/references/hosting-and-storage.md +37 -16
  64. package/package/corpus/skills/cratis-arc-command-typescript/references/identity-and-introspection.md +88 -0
  65. package/package/corpus/skills/cratis-arc-command-typescript/references/observability-and-host-scopes.md +25 -0
  66. package/package/corpus/skills/cratis-arc-command-typescript/references/proxy-generation.md +35 -4
  67. package/package/corpus/skills/cratis-arc-command-typescript/references/slice-layout.md +11 -0
  68. package/package/corpus/skills/cratis-arc-command-typescript/verification.json +8 -1
  69. package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +49 -19
  70. package/package/corpus/skills/cratis-arc-ef-core-migration/SKILL.md +12 -6
  71. package/package/corpus/skills/cratis-arc-go/SKILL.md +148 -0
  72. package/package/corpus/skills/cratis-arc-go/references/queries.md +80 -0
  73. package/package/corpus/skills/cratis-arc-hosting/SKILL.md +96 -0
  74. package/package/corpus/skills/cratis-arc-hosting/references/integrations-and-publish.md +80 -0
  75. package/package/corpus/skills/cratis-arc-identity-and-tenancy-kotlin/SKILL.md +137 -0
  76. package/package/corpus/skills/cratis-arc-observable-query-http/SKILL.md +20 -2
  77. package/package/corpus/skills/cratis-arc-query-kotlin/SKILL.md +26 -6
  78. package/package/corpus/skills/cratis-arc-query-paging/SKILL.md +25 -20
  79. package/package/corpus/skills/cratis-arc-query-typescript/SKILL.md +32 -6
  80. package/package/corpus/skills/cratis-arc-query-typescript/references/storage-observation-and-compliance.md +66 -0
  81. package/package/corpus/skills/cratis-arc-query-typescript/verification.json +5 -1
  82. package/package/corpus/skills/cratis-arc-react-page/SKILL.md +24 -13
  83. package/package/corpus/skills/cratis-arc-react-page/references/data-page.md +20 -10
  84. package/package/corpus/skills/cratis-arc-react-page/references/data-tables.md +17 -3
  85. package/package/corpus/skills/cratis-arc-react-page/references/dialogs.md +14 -3
  86. package/package/corpus/skills/cratis-arc-react-page/references/from-screenplay.md +6 -1
  87. package/package/corpus/skills/cratis-arc-react-page/references/provenance.md +1 -1
  88. package/package/corpus/skills/cratis-arc-react-page/references/queries-and-commands.md +11 -1
  89. package/package/corpus/skills/cratis-arc-react-page/verification.json +6 -1
  90. package/package/corpus/skills/cratis-arc-spring-data-kotlin/SKILL.md +163 -0
  91. package/package/corpus/skills/cratis-arc-testing-kotlin/SKILL.md +131 -0
  92. package/package/corpus/skills/cratis-arc-validation-kotlin/SKILL.md +44 -11
  93. package/package/corpus/skills/cratis-arc-validation-typescript/SKILL.md +5 -3
  94. package/package/corpus/skills/cratis-chronicle-cli-operations/SKILL.md +79 -25
  95. package/package/corpus/skills/cratis-chronicle-cli-operations/references/command-effects.md +57 -0
  96. package/package/corpus/skills/cratis-chronicle-client-dotnet/SKILL.md +87 -99
  97. package/package/corpus/skills/cratis-chronicle-client-dotnet/references/additional-surfaces.md +65 -0
  98. package/package/corpus/skills/cratis-chronicle-client-dotnet/references/client-construction.md +42 -0
  99. package/package/corpus/skills/cratis-chronicle-client-dotnet/references/compatibility-check.md +12 -0
  100. package/package/corpus/skills/cratis-chronicle-client-dotnet/references/discovery.md +43 -19
  101. package/package/corpus/skills/cratis-chronicle-client-dotnet/references/hosting.md +29 -1
  102. package/package/corpus/skills/cratis-chronicle-client-elixir/SKILL.md +158 -162
  103. package/package/corpus/skills/cratis-chronicle-client-elixir/references/append-api.md +36 -15
  104. package/package/corpus/skills/cratis-chronicle-client-elixir/references/compliance.md +32 -0
  105. package/package/corpus/skills/cratis-chronicle-client-elixir/references/connections.md +56 -0
  106. package/package/corpus/skills/cratis-chronicle-client-elixir/references/event-sources.md +64 -0
  107. package/package/corpus/skills/cratis-chronicle-client-elixir/references/pitfalls.md +25 -0
  108. package/package/corpus/skills/cratis-chronicle-client-elixir/references/projections.md +48 -0
  109. package/package/corpus/skills/cratis-chronicle-client-elixir/references/upgrading.md +34 -0
  110. package/package/corpus/skills/cratis-chronicle-client-elixir/references/variants.md +13 -5
  111. package/package/corpus/skills/cratis-chronicle-client-elixir/verification.json +23 -0
  112. package/package/corpus/skills/cratis-chronicle-client-go/SKILL.md +162 -0
  113. package/package/corpus/skills/cratis-chronicle-client-go/references/projections.md +73 -0
  114. package/package/corpus/skills/cratis-chronicle-client-kotlin/SKILL.md +87 -88
  115. package/package/corpus/skills/cratis-chronicle-client-kotlin/references/connection-lifecycle.md +13 -0
  116. package/package/corpus/skills/cratis-chronicle-client-kotlin/references/discovery-and-registration.md +14 -4
  117. package/package/corpus/skills/cratis-chronicle-client-kotlin/references/event-sources.md +65 -0
  118. package/package/corpus/skills/cratis-chronicle-client-kotlin/references/options.md +29 -0
  119. package/package/corpus/skills/cratis-chronicle-client-kotlin/references/spring-boot.md +13 -0
  120. package/package/corpus/skills/cratis-chronicle-client-kotlin/references/variants.md +5 -4
  121. package/package/corpus/skills/cratis-chronicle-client-typescript/SKILL.md +165 -156
  122. package/package/corpus/skills/cratis-chronicle-client-typescript/references/read-models-and-projections.md +54 -11
  123. package/package/corpus/skills/cratis-chronicle-client-typescript/references/testing.md +126 -0
  124. package/package/corpus/skills/cratis-chronicle-client-typescript/references/variants.md +3 -5
  125. package/package/corpus/skills/cratis-chronicle-client-typescript/verification.json +19 -0
  126. package/package/corpus/skills/cratis-chronicle-compliance/SKILL.md +36 -13
  127. package/package/corpus/skills/cratis-chronicle-decision-reads/SKILL.md +110 -0
  128. package/package/corpus/skills/cratis-chronicle-decision-reads/references/admission-and-ownership.md +57 -0
  129. package/package/corpus/skills/cratis-chronicle-decision-reads/verification.json +12 -0
  130. package/package/corpus/skills/cratis-chronicle-event-constraints/SKILL.md +58 -19
  131. package/package/corpus/skills/cratis-chronicle-event-constraints/references/closing-streams.md +73 -0
  132. package/package/corpus/skills/cratis-chronicle-event-modeling/SKILL.md +32 -3
  133. package/package/corpus/skills/cratis-chronicle-event-specifications/SKILL.md +18 -5
  134. package/package/corpus/skills/cratis-chronicle-event-type-migration/SKILL.md +32 -2
  135. package/package/corpus/skills/cratis-chronicle-hosting/SKILL.md +134 -0
  136. package/package/corpus/skills/cratis-chronicle-hosting/references/operations.md +89 -0
  137. package/package/corpus/skills/cratis-chronicle-hosting/verification.json +14 -0
  138. package/package/corpus/skills/cratis-chronicle-mcp-inspection/SKILL.md +93 -66
  139. package/package/corpus/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +21 -7
  140. package/package/corpus/skills/cratis-chronicle-mcp-inspection/references/deployment.md +77 -0
  141. package/package/corpus/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +46 -4
  142. package/package/corpus/skills/cratis-chronicle-multi-tenancy/SKILL.md +15 -1
  143. package/package/corpus/skills/cratis-chronicle-projection/SKILL.md +44 -10
  144. package/package/corpus/skills/cratis-chronicle-projection/references/fluent-builder.md +7 -1
  145. package/package/corpus/skills/cratis-chronicle-projection/references/model-bound-attributes.md +8 -0
  146. package/package/corpus/skills/cratis-chronicle-reactor/SKILL.md +85 -105
  147. package/package/corpus/skills/cratis-chronicle-reactor/references/specifications.md +20 -1
  148. package/package/corpus/skills/cratis-chronicle-read-model/SKILL.md +2 -2
  149. package/package/corpus/skills/cratis-chronicle-read-model/references/queries.md +35 -16
  150. package/package/corpus/skills/cratis-chronicle-read-model-specifications/SKILL.md +15 -3
  151. package/package/corpus/skills/cratis-chronicle-reducer/SKILL.md +23 -4
  152. package/package/corpus/skills/cratis-chronicle-web-workbench/SKILL.md +40 -15
  153. package/package/corpus/skills/cratis-cli-terminal-workbench/SKILL.md +20 -6
  154. package/package/corpus/skills/cratis-code-review/SKILL.md +25 -3
  155. package/package/corpus/skills/cratis-components-accessibility/SKILL.md +20 -4
  156. package/package/corpus/skills/cratis-components-schema-editor/SKILL.md +40 -28
  157. package/package/corpus/skills/cratis-components-schema-editor/references/tree-layout.md +62 -0
  158. package/package/corpus/skills/cratis-components-schema-editor/verification.json +14 -0
  159. package/package/corpus/skills/cratis-components-stepper-command-dialog/SKILL.md +16 -8
  160. package/package/corpus/skills/cratis-components-styling/SKILL.md +57 -20
  161. package/package/corpus/skills/cratis-components-styling/verification.json +13 -0
  162. package/package/corpus/skills/cratis-components-toolbar/SKILL.md +37 -7
  163. package/package/corpus/skills/cratis-components-toolbar/verification.json +12 -0
  164. package/package/corpus/skills/cratis-engineering-csharp-conventions/SKILL.md +6 -0
  165. package/package/corpus/skills/cratis-engineering-csharp-conventions/references/exceptions-logging-and-di.md +5 -3
  166. package/package/corpus/skills/cratis-fundamentals-concept/SKILL.md +19 -6
  167. package/package/corpus/skills/cratis-fundamentals-concept/references/typescript-concepts.md +97 -0
  168. package/package/corpus/skills/cratis-fundamentals-concept/verification.json +4 -1
  169. package/package/corpus/skills/cratis-fundamentals-concept-go/SKILL.md +112 -0
  170. package/package/corpus/skills/cratis-fundamentals-type-discovery/SKILL.md +54 -17
  171. package/package/corpus/skills/cratis-fundamentals-type-discovery/verification.json +14 -0
  172. package/package/corpus/skills/cratis-lens-browser-extension/SKILL.md +55 -28
  173. package/package/corpus/skills/cratis-performance-review/SKILL.md +5 -2
  174. package/package/corpus/skills/cratis-release-notes/SKILL.md +3 -1
  175. package/package/corpus/skills/cratis-screenplay-automations-and-translations/SKILL.md +5 -4
  176. package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +5 -6
  177. package/package/corpus/skills/cratis-screenplay-command-surface/references/compliance.md +24 -6
  178. package/package/corpus/skills/cratis-screenplay-command-surface/references/enum-numbers.md +4 -2
  179. package/package/corpus/skills/cratis-screenplay-command-surface/references/generated-values-and-responses.md +4 -1
  180. package/package/corpus/skills/cratis-screenplay-event-modeling/SKILL.md +3 -2
  181. package/package/corpus/skills/cratis-screenplay-event-modeling/references/parsed-not-runnable.md +2 -2
  182. package/package/corpus/skills/cratis-screenplay-legacy-extraction/SKILL.md +1 -0
  183. package/package/corpus/skills/cratis-screenplay-legacy-extraction/references/generate-notes.md +34 -4
  184. package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +5 -3
  185. package/package/corpus/skills/cratis-screenplay-model-authoring/references/language-reference.md +1 -1
  186. package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-tools.md +4 -2
  187. package/package/corpus/skills/cratis-screenplay-model-authoring/references/property-links.md +42 -3
  188. package/package/corpus/skills/cratis-screenplay-model-authoring/verification.json +3 -1
  189. package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/references/gap-fill-handoff.md +3 -1
  190. package/package/corpus/skills/cratis-screenplay-scenario-coverage/SKILL.md +3 -1
  191. package/package/corpus/skills/cratis-screenplay-specifications/references/examples-and-routes.md +48 -0
  192. package/package/corpus/skills/cratis-screenplay-specifications/verification.json +3 -1
  193. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/SKILL.md +4 -2
  194. package/package/corpus/skills/cratis-screenplay-streams-and-consistency/references/streams-example.md +5 -4
  195. package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +8 -6
  196. package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet-example.md +2 -2
  197. package/package/corpus/skills/cratis-screenplay-toolchain/references/diagnostics.md +4 -2
  198. package/package/corpus/skills/cratis-screenplay-toolchain/references/doc-contradictions.md +1 -1
  199. package/package/corpus/skills/cratis-screenplay-toolchain/references/executable-subset.md +16 -11
  200. package/package/corpus/skills/cratis-screenplay-toolchain/references/renderable-subset.md +5 -2
  201. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.json +1329 -32
  202. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.version +1 -1
  203. package/package/corpus/skills/cratis-screenplay-toolchain/references/verdicts.md +2 -2
  204. package/package/corpus/skills/cratis-screenplay-toolchain/references/versions.md +23 -7
  205. package/package/corpus/skills/cratis-screenplay-toolchain/verification.json +1 -1
  206. package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +2 -0
  207. package/package/corpus/skills/cratis-screenplay-ui-composition/references/screen-composition-contract.md +27 -9
  208. package/package/corpus/skills/cratis-security-review/SKILL.md +9 -3
  209. package/package/corpus/skills/cratis-security-review/references/arc-boundaries.md +59 -0
  210. package/package/corpus/skills/cratis-specification-by-example/SKILL.md +7 -0
  211. package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +43 -11
  212. package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +16 -4
  213. package/package/corpus/skills/cratis-specifications-csharp/references/csharp-patterns.md +18 -5
  214. package/package/corpus/skills/cratis-specifications-csharp/references/integration-specs.md +21 -0
  215. package/package/corpus/skills/cratis-specifications-csharp/references/shared-lifecycle.md +34 -0
  216. package/package/corpus/skills/cratis-specifications-csharp/verification.json +13 -0
  217. package/package/corpus/skills/cratis-specifications-typescript/SKILL.md +14 -5
  218. package/package/corpus/skills/cratis-specifications-typescript/verification.json +12 -0
  219. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +8 -2
  220. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +6 -3
  221. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/rendered-application.md +10 -1
  222. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/scene-runtime.md +75 -0
  223. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/verification.json +5 -0
  224. package/package/corpus/skills/cratis-studio-mcp-safety-guidance/SKILL.md +14 -1
  225. package/package/profile-catalog.json +68 -1
  226. package/package.json +1 -1
  227. package/src/index.ts +3 -1
@@ -0,0 +1,126 @@
1
+ <!-- Copyright (c) Cratis. All rights reserved. -->
2
+ <!-- Licensed under the MIT license. See LICENSE file in the project root for full license information. -->
3
+
4
+ # Testing without a kernel
5
+
6
+ ## Contents
7
+
8
+ - Appends — EventScenario
9
+ - Deliveries — ReactorScenario
10
+ - Materialization — ReadModelScenario
11
+ - Use kernel-backed tests outside the boundary
12
+
13
+ Verified at `Cratis/Chronicle.TypeScript` **v6.51.0**:
14
+ `Source/testing/index.ts`, `EventScenario.ts`, `ReactorScenario.ts`,
15
+ `ReadModelScenario.ts`, and `Documentation/testing.md`.
16
+ Import the three scenarios from `@cratis/chronicle/testing`. They share
17
+ production dispatch/definition code within a **fixture-bounded** evaluator;
18
+ a green scenario is not proof of kernel storage, scheduling or compliance.
19
+
20
+ The examples below share these standard-decorator artifacts:
21
+
22
+ ```typescript
23
+ // Copyright (c) Cratis. All rights reserved.
24
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
25
+
26
+ import 'reflect-metadata';
27
+ import { field } from '@cratis/fundamentals';
28
+ import { eventType, fromEvent, reactor } from '@cratis/chronicle';
29
+ import { EventScenario, ReactorScenario, ReadModelScenario } from '@cratis/chronicle/testing';
30
+
31
+ @eventType()
32
+ class MessageRecorded {
33
+ @field(String) text: string;
34
+ constructor(text: string) { this.text = text; }
35
+ }
36
+
37
+ @fromEvent(MessageRecorded)
38
+ class Message {
39
+ @field(String) id = '';
40
+ @field(String) text = '';
41
+ }
42
+
43
+ @eventType()
44
+ class MessageCopied {
45
+ @field(String) text: string;
46
+ constructor(text: string) { this.text = text; }
47
+ }
48
+
49
+ @reactor()
50
+ class CopyMessage {
51
+ messageRecorded(event: MessageRecorded): MessageCopied {
52
+ return new MessageCopied(event.text);
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Appends — EventScenario
58
+
59
+ ```typescript
60
+ const events = new EventScenario({
61
+ artifacts: { eventTypes: [MessageRecorded] }, constraints: 'disabled'
62
+ });
63
+ await events.given.forEventSource('message-1').events(new MessageRecorded('seed'));
64
+ const result = await events.when.forEventSource('message-2').event(new MessageRecorded('act'));
65
+ if (!result.isSuccess) throw new Error('Expected the append to succeed');
66
+ ```
67
+
68
+ `given...events` seeds sequential single appends; `when.event` is a single
69
+ append and `when.events` an atomic batch. `forEventSource(id, options?)` accepts
70
+ fixture-backed routing. Omitted routes resolve to `Default`/`All`/`Default`.
71
+ `constraints: 'disabled'` is explicit opt-out, never a silent fallback.
72
+ Selected `@unique`/fluent constraints support fixture-proven key domains,
73
+ removal cycles and scopes; unproven shapes throw.
74
+
75
+ ## Deliveries — ReactorScenario
76
+
77
+ ```typescript
78
+ const deliveries = new ReactorScenario(CopyMessage, {
79
+ artifacts: { eventTypes: [MessageRecorded, MessageCopied] }, constraints: 'disabled'
80
+ });
81
+ await deliveries.when.forEventSource('message-3').events(new MessageRecorded('copy'));
82
+ deliveries.shouldHaveProduced(MessageCopied, event => event.text === 'copy');
83
+ ```
84
+
85
+ Returned events are **recorded, not appended or recursively delivered**. A
86
+ production reactor returning its own subscribed type may loop; the scenario
87
+ does not test termination. `produced`, `sideEffects` and `results`
88
+ expose observations. Failed deliveries reject the call and prevent subsequent
89
+ deliveries; retries/checkpoints are not simulated. Content is client-serialized
90
+ JSON, not necessarily kernel-normalized content. Explicit non-subscribed
91
+ service appends enter history but are not delivered to observers.
92
+
93
+ ## Materialization — ReadModelScenario
94
+
95
+ ```typescript
96
+ const models = new ReadModelScenario(Message);
97
+ models.given.forEventSource('message-4').events(new MessageRecorded('read'));
98
+ const message = await models.instanceForEventSourceId('message-4');
99
+ if (message?.text !== 'read') throw new Error('Expected the materialized message');
100
+ ```
101
+
102
+ `instance` requires a sole model; `instanceForEventSourceId` returns null when
103
+ absent, and `wasDeletedForEventSourceId` distinguishes deletion. For composition,
104
+ use `new ReadModelScenario(Message).observe(events)` (or a ReactorScenario).
105
+ Every read replays the source's accepted history; returned reactor effects do
106
+ not reach it. Do not mix observing and seeding or observe twice.
107
+
108
+ Reducers and fixture-backed projections work: scalar root mappings/AutoMap,
109
+ removal/recreation, bounded root arithmetic, one level of typed string-keyed
110
+ children, and one string-keyed root join. The **whole** definition is validated
111
+ before replay, even for events you never seed.
112
+
113
+ ## Use kernel-backed tests outside the boundary
114
+
115
+ `UnsupportedEventSequenceOperation`, `UnsupportedReactorOperation`, and
116
+ `UnsupportedProjectionOperation` name the operation/artifact and require a
117
+ kernel-backed test. Do not catch them and call the scenario green.
118
+
119
+ Unsupported examples include non-fixture-backed constraints, concurrency,
120
+ transactions, compliance, named tags on input appends, tag-filtered reactors,
121
+ replay/redelivery, nested children, child/nested joins, `removedWithJoin`,
122
+ variants and custom/composite keys. Named tags on returned reactor effects can
123
+ be recorded, but are not proof of persisted named-tag behavior. Nondefault
124
+ routes cannot be evaluated by ReadModelScenario. Read the tagged
125
+ `Documentation/testing.md` capability tables before claiming an advanced shape
126
+ is covered; use a live kernel for storage and observer fidelity.
@@ -1,8 +1,8 @@
1
1
  ## Variants — mutually exclusive read models for one entity's lifecycle
2
2
 
3
- > Requires `@cratis/chronicle` `6.2.0` or later — newer than this skill's
4
- > `5.1.0` baseline (`Source/projections/VariantReclassifier.ts` and siblings).
5
- > Reverify before claiming support; take the version from npm.
3
+ > Available since `@cratis/chronicle` 6.2.0; verified at **v6.51.0**
4
+ > (`Source/projections/VariantReclassifier.ts` and model-bound decorators).
5
+ > Read models are inferred; use `static readModelId` for a custom id.
6
6
 
7
7
  Some entities do not have one shape for their whole lifetime — a work item is a
8
8
  backlog entry until a pull request exists for it, then it is a pull request
@@ -16,7 +16,6 @@ class WorkItem {} // anchors the group; not itself a read model
16
16
  @variantOf(WorkItem, 'id')
17
17
  @entersOn(IssueCreated)
18
18
  @fromEvent(IssueCreated)
19
- @readModel()
20
19
  class BacklogItem {
21
20
  id = '';
22
21
  @setFrom(IssueCreated, 'title') title = '';
@@ -26,7 +25,6 @@ class BacklogItem {
26
25
  @entersOn(PullRequestCreated)
27
26
  @fromEvent(PullRequestCreated)
28
27
  @fromEvent(BuildCompleted) // not the entering event -> update-only join
29
- @readModel()
30
28
  class PullRequestItem {
31
29
  id = '';
32
30
  @setFrom(PullRequestCreated, 'pullRequestUrl') pullRequestUrl = '';
@@ -0,0 +1,19 @@
1
+ {
2
+ "skill": "cratis-chronicle-client-typescript",
3
+ "input": "Append a routed event and specify a TypeScript Chronicle reactor without a kernel.",
4
+ "fixtures": [],
5
+ "assertions": [
6
+ { "kind": "skill-contains", "value": "tag **`v6.51.0`**" },
7
+ { "kind": "skill-contains", "value": "**Single `append()` supports routing and subject options.**" },
8
+ { "kind": "skill-contains", "value": "Standard TC39 decorators" },
9
+ { "kind": "skill-contains", "value": "**A compiled-JS entry defaults to `[]`.**" },
10
+ { "kind": "skill-contains", "value": "IncompatibleChronicleServer" },
11
+ { "kind": "skill-contains", "value": "installed package version" },
12
+ { "kind": "skill-contains", "value": "**Reactors replay by default since 6.9.0.**" },
13
+ { "kind": "skill-contains", "value": "@handles(EventType)" },
14
+ { "kind": "skill-contains", "value": "Returns **null when absent**", "file": "references/read-models-and-projections.md" },
15
+ { "kind": "skill-contains", "value": "`@unique()`", "file": "references/read-models-and-projections.md" },
16
+ { "kind": "skill-contains", "value": "**recorded, not appended or recursively delivered**", "file": "references/testing.md" },
17
+ { "kind": "skill-contains", "value": "UnsupportedProjectionOperation", "file": "references/testing.md" }
18
+ ]
19
+ }
@@ -17,8 +17,8 @@ This skill is verified against this exact public release:
17
17
 
18
18
  | Package | Version | Purpose |
19
19
  | --- | --- | --- |
20
- | `Cratis.Chronicle` | `18.3.0` | `Cratis.Chronicle.Compliance.GDPR`, `Cratis.Chronicle.SubjectAttribute`, `IEventSequence.Redact`, `IReadModels.Release` |
21
- | `Cratis.Chronicle.CodeAnalysis` | `18.3.0` | `CHR0026`, `CHR0034`, `CHR0035`, `CHR0043`, `CHR0046` |
20
+ | `Cratis.Chronicle` | `19.50.1` | `Cratis.Chronicle.Compliance.GDPR`, `Cratis.Chronicle.SubjectAttribute`, `IEventSequence.Redact`, `IReadModels.Release` |
21
+ | `Cratis.Chronicle.CodeAnalysis` | `19.50.1` | `CHR0026`, `CHR0034`, `CHR0035`, `CHR0043`, `CHR0046` |
22
22
 
23
23
  Reverify product sources before claiming support for another version.
24
24
 
@@ -58,10 +58,10 @@ it.** The rationale that actually reaches the event schema comes from
58
58
  marker and `[ComplianceDetails("...")]` for the reason; do not write
59
59
  `[PII("reason")]` and expect the reason to be recorded.
60
60
 
61
- `PII` is the only compliance category Chronicle defines — there is exactly one
62
- well-known `ComplianceMetadataType`. Chronicle has no `[Redact]`, `[Erase]`,
63
- `[Anonymize]`, `[Encrypt]`, or `[NotAudited]` attribute; do not write one into
64
- an example. Redaction is an API call, not an annotation.
61
+ `PII` is the only compliance category Chronicle defines. The protection markers
62
+ are `[PII]` for personal data and `[Encrypted]` for retained operational secrets;
63
+ there is no `[Redact]`, `[Erase]`, `[Anonymize]`, or `[Encrypt]` annotation.
64
+ Redaction is an API call. `[NotAudited]` belongs to Arc, not Chronicle.
65
65
 
66
66
  `[Encrypted]` (`Cratis.Chronicle.ProtectedValues`, Chronicle 19.32.0+) is a
67
67
  security marker, not a compliance category: it encrypts an operational secret
@@ -70,6 +70,11 @@ and no erasure. Never put it on a value that is also `[PII]` (analyzer
70
70
  **CHR0053**, which `[PII]` alone already covers), nor on an event source id
71
71
  (**CHR0052**). Arc's `[NotAudited]` is not a Chronicle attribute either; a secret
72
72
  that is also a command property takes both `[Encrypted]` and `[NotAudited]`.
73
+ `EncryptedAttribute.Scope` supports `EncryptionScope.Subject` (default),
74
+ `Namespace`, and `Global`; these are security-key scopes, not PII erasure.
75
+ `PIIManager` refuses to delete security keys. Custom security metadata providers
76
+ live in `Cratis.Chronicle.Confidentiality` (`ICanProvideSecurityMetadataForType`
77
+ and `ICanProvideSecurityMetadataForProperty`).
73
78
 
74
79
  Mark only genuinely personal values. Business metadata, identifiers of
75
80
  non-persons, and amounts stay unmarked — every marked value costs an encryption
@@ -152,9 +157,11 @@ What this does and does not do:
152
157
  recorded before any key is deleted.
153
158
  - The events stay in the log. Their `[PII]` values remain as unreadable
154
159
  ciphertext — this is crypto-shredding, not deletion.
155
- - Reading an erased value yields an **empty string** rather than an exception,
156
- so queries and read models over an erased subject keep working. An erased
157
- container value comes back as an empty object or array.
160
+ - Released values depend on the schema: string → empty string; non-nullable
161
+ number/boolean → `0`/`false`; nullable number/boolean → `null`; collection →
162
+ empty collection; object/value object → empty object. An erased `0`, `false`
163
+ or empty string is indistinguishable from a genuinely recorded one.
164
+ Source at v19.50.0: `Documentation/compliance/erasing-a-subject.mdx`.
158
165
  - Appending a new `[PII]` value for an erased subject **fails** with
159
166
  `EncryptionKeyErased`. It does not quietly mint a new key. Call
160
167
  `AllowNewEncryptionKeyFor` deliberately if the subject must be re-enrolled.
@@ -162,9 +169,10 @@ What this does and does not do:
162
169
  carries the individual failures. Treat it as unfinished and retry — the fence
163
170
  is already in place, so retrying is safe.
164
171
 
165
- **Erasure is scoped to one namespace.** There is no cross-namespace erasure. In
166
- a multi-tenant deployment, an erasure request that spans tenants is one call per
167
- tenant namespace, and completing only some of them is an incomplete erasure.
172
+ **Erasure covers every event store in one namespace**, not just the store from
173
+ which `PII` was resolved: forwarding copies subject keys within that namespace.
174
+ There is no cross-namespace erasure. A request spanning tenants is one call per
175
+ tenant namespace; completing only some is an incomplete erasure.
168
176
 
169
177
  ## Redact a stored event
170
178
 
@@ -184,6 +192,21 @@ Use redaction for content that should never have been appended. Use key erasure
184
192
  for a subject exercising a right to erasure. They are different mechanisms and
185
193
  one does not imply the other.
186
194
 
195
+ Since 19.11.0, redaction also drops retained causation property values, revision
196
+ history and content hashes. It does not reach backups, oplogs or events already
197
+ forwarded elsewhere. Subject erasure does **not** erase causation values: these
198
+ are not encrypted under the subject key. Kernel configuration
199
+ `events.causationPropertyRetention = Omit`
200
+ (`Cratis__Chronicle__Events__CausationPropertyRetention`) prevents **new** retention
201
+ on appends, forwarding, revisions and redactions; it does not rewrite history.
202
+ Source at v19.50.0: `Documentation/hosting/configuration/events.md` and
203
+ `Documentation/compliance/erasing-a-subject.mdx`.
204
+
205
+ Adding `[PII]` / `[Encrypted]` to an existing generation is accepted with a warning
206
+ and protects **later events only**. Revision and migration fail rather than
207
+ persist empty substitutes when key lookup/decryption fails; an erased subject
208
+ migrates as erased. Do not confuse that fail-closed write path with read release.
209
+
187
210
  ## Read personal data back
188
211
 
189
212
  The kernel decrypts on the paths it owns, so projections and observers see plain
@@ -211,7 +234,7 @@ compliance setup — `AddCompliance()` is already called for you, and the raw
211
234
  client path discovers the metadata providers by convention. The developer-facing
212
235
  requirement is only to annotate the types.
213
236
 
214
- Key storage is a hosting decision, not an application one. Chronicle ships
237
+ Key storage is per namespace and is a hosting decision, not an application one. Chronicle ships
215
238
  MongoDB (the default), SQL, in-memory, HashiCorp Vault, and Azure Key Vault key
216
239
  storage, configured under `Cratis:Chronicle:Compliance:Encryption`.
217
240
 
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: cratis-chronicle-decision-reads
3
+ description: Guard a state-dependent Chronicle decision with IDecisionReads, projection admission, ambient unit-of-work enrollment and commit conflict handling. Use when an append must remain valid for the facts read, when choosing whole-source versus stream-scoped reads, or diagnosing DecisionReadRefused and scope conflicts. Do not use for ordinary query reads, append-time uniqueness alone, or defining an Arc command's ProtectedDecision API.
4
+ license: MIT
5
+ ---
6
+
7
+ <!-- Copyright (c) Cratis. All rights reserved. -->
8
+ <!-- Licensed under the MIT license. See LICENSE file in the project root for full license information. -->
9
+
10
+ # Guard the facts a decision used
11
+
12
+ A fresh read is not enough: another writer can change its facts between reading
13
+ and appending. A decision read folds an admitted projection from events and
14
+ carries a guard to commit. The append is rejected when a later event inside that
15
+ guard could change the decision. This is the Chronicle side of a dynamic
16
+ consistency boundary (DCB), not authorization or a replacement for unique constraints.
17
+
18
+ ## Verified product sources
19
+
20
+ | Product | Version | Sources |
21
+ | --- | --- | --- |
22
+ | `Cratis.Chronicle` | `19.50.1` | `Source/Clients/DotNET/ReadModels/{IDecisionReads,DecisionReads,DecisionRead,DecisionReadRefusalReason}.cs` |
23
+ | `Cratis.Chronicle` transactions | `19.50.1` | `Source/Clients/DotNET/Transactions/{IUnitOfWork,UnitOfWork,DecisionReadCommitOwner,UnitOfWorkLifecyclePolicy}.cs` |
24
+ | `Cratis.Chronicle` scope resolution | `19.50.1` | `Source/Clients/DotNET/EventSequences/Concurrency/DefinitionConcurrencyScopeResolver.cs` |
25
+
26
+ The client surface began in 19.9.0; stream-scoped reads require the newer kernel
27
+ surface added in 19.39.0. Reverify a client/kernel pair rather than disabling
28
+ compatibility checking to force a read through.
29
+
30
+ ## Route the work
31
+
32
+ - For Arc `[ProtectedDecision]`, Arc command/handler shapes and Arc `IStreamReads`,
33
+ use **cratis-arc-command** and **cratis-arc-command-validation**; do not recreate
34
+ their unit-of-work ownership in a handler. The Chronicle `DecisionRead<T>`
35
+ below is not a license to invent an Arc wrapper shape.
36
+ - For a unique value claimed atomically or a closed stream, use
37
+ **cratis-chronicle-event-constraints**.
38
+ - For read-model declarations, use **cratis-chronicle-read-model** and
39
+ **cratis-chronicle-projection**. Ordinary `IReadModels` reads do not enroll guards.
40
+ - In model-first scope, settle the rule and its refusal/concurrency specifications
41
+ in the accepted model using **cratis-screenplay-event-modeling** before gap-fill.
42
+ Never broaden a projection or bypass model protection just to make admission pass.
43
+
44
+ ## Check admission, then read
45
+
46
+ `IDecisionReads` is in `Cratis.Chronicle.ReadModels`; obtain the reader with
47
+ `eventStore.GetDecisionReads()` (`ReadModels/DecisionReadExtensions.cs`).
48
+ `Admit<T>()` / `AdmitForStream<T>()` check shape **without I/O**, returning
49
+ `DecisionReadAdmission(IsAdmitted, Reason)`. A successful shape check is not
50
+ runtime read success: definitions, key conversion and fold completion are also
51
+ checked while reading.
52
+
53
+ `Get<T>(ReadModelKey key, cancellationToken)` requires an ambient unit of work
54
+ and enrolls the protected read into it. Without one it throws
55
+ `DecisionReadRequiresUnitOfWork`. `GetDetached<T>` reads without enrollment:
56
+ explicitly add it with `IUnitOfWork.AddDecisionRead(read)` or carry it to a guarded
57
+ append; dropping its guard makes the decision unprotected.
58
+
59
+ `DecisionRead<T>` exposes `Instance` (nullable), `Exists`, `Key`, `IsProtected`,
60
+ and store/namespace/sequence/stream routing. An absent instance is still a fact
61
+ to guard, not permission to return a fabricated default model. Decide the
62
+ business rejection or creation behavior explicitly.
63
+
64
+ Read [admission and ownership](references/admission-and-ownership.md) when a
65
+ shape is refused, a read crosses a stream boundary, or nested work tries to
66
+ complete a protected transaction.
67
+
68
+ ## Read one stream instead of the whole source
69
+
70
+ The stream overload takes `eventSourceId`, `eventStreamType`, `eventStreamId`,
71
+ optional `eventSourceType`, and cancellation; it has both `Get<T>` and
72
+ `GetDetached<T>` forms. It folds only that stream and guards it independently of
73
+ the model's key. It still admits **projections only**, not reducers or arbitrary
74
+ queries. Select one stream type, not `EventStreamType.All`; a stream-keyed
75
+ projection also needs an explicit stream id, not the default wildcard.
76
+ `AdmitForStream<T>()` checks this shape; unsupported readers/kernels refuse with
77
+ `StreamScopeNotSupported`. Do not fall back to an unguarded query on refusal.
78
+
79
+ ## Commit and inspect conflicts
80
+
81
+ Enroll the read **before** staging its decision's events through
82
+ `EventLog.Transactional` and let the owning unit of work commit. Inspect
83
+ `IsSuccess`, append errors/constraint violations/concurrency violations and
84
+ `GetDecisionConflicts()`; the latter reports decision conflicts without exposing
85
+ sequence numbers. A checked empty append validates its scope without writing
86
+ an event. Do not mistake "no event" for "no consistency check".
87
+
88
+ Do not attach a separate explicit concurrency scope to a source already guarded
89
+ by a decision read: `DecisionReadConflictsWithConcurrencyScope` rejects that
90
+ combination. A batch needing incompatible scopes for one event source throws
91
+ `IncompatibleConcurrencyScopesForEventSource`. These are configuration/enrollment
92
+ errors, not an ordinary business rejection. Choose one coherent scope; do not
93
+ catch them and append without protection.
94
+
95
+ On a real conflict, reread and **re-decide** with the same authorization and
96
+ business rule; never resend stale events as if a retry fixed the decision. A
97
+ DCB guard protects event facts, not external side effects. Durable follow-up
98
+ belongs in a reactor/outbox; use **cratis-chronicle-reactor**.
99
+
100
+ ## Verify
101
+
102
+ - The model is admitted and the read is protected and enrolled in the intended
103
+ store, namespace and stream before staging events.
104
+ - Refusal is surfaced, never replaced by a materialized/unprotected read.
105
+ - One owner completes or rolls back; nested code does neither behind it.
106
+ - Competing relevant events cause rejection; unrelated events outside the guard
107
+ do not. Absence, empty checked append and stream isolation have specifications.
108
+ - Scope conflicts, lifecycle violations and failed commit remain visible.
109
+ - Real-client/kernel integration proves the concurrency boundary; a default
110
+ `EventScenario` scope strategy returning `None` is not concurrency evidence.
@@ -0,0 +1,57 @@
1
+ <!-- Copyright (c) Cratis. All rights reserved. -->
2
+ <!-- Licensed under the MIT license. See LICENSE file in the project root for full license information. -->
3
+
4
+ # Admission and commit ownership
5
+
6
+ Verified at **Cratis/Chronicle v19.50.0**:
7
+ `Source/Clients/DotNET/ReadModels/DecisionReads.cs`, `DecisionReadRefusalReason.cs`,
8
+ `Source/Clients/DotNET/Transactions/UnitOfWork.cs`, `DecisionReadCommitOwner.cs`,
9
+ and `UnitOfWorkLifecyclePolicy.cs`.
10
+
11
+ ## Why a shape is refused
12
+
13
+ Admission requires exactly one supported projection on the event log. It is not
14
+ an adapter for arbitrary read-model storage:
15
+
16
+ | Reason | Shape to inspect |
17
+ | --- | --- |
18
+ | Reducer / AmbiguousProjection / NotEventLog | reduced model, several projections, or another sequence |
19
+ | Join / Hierarchy | joins, children or nested projections |
20
+ | OpenEndedEventTypes / Derivatives / FromEventProperty | all-events, FromEvery, or event-property routing |
21
+ | NotEventSourceKeyed / KeyConversion | keys not convertible to the admitted raw source key |
22
+ | NoEventTypes / UnsupportedEventTypeId | no finite guarded type set or unsupported encoded type ids |
23
+ | InvalidKey / FoldIncomplete / DefinitionMismatch | runtime key, fold or client/kernel definition disagreement |
24
+ | StreamKeyRequiresStreamScope | stream-keyed model read without a stream scope |
25
+ | StreamScopeNotSupported / AllStreamsNotSupported / StreamKeyRequiresExplicitStreamId | unsupported stream reader or incomplete stream selection |
26
+
27
+ Whole-source key admission supports string/Guid schemas, with canonical lowercase
28
+ D-form Guid source ids assumed. A renamed or custom-converted key is not made
29
+ safe by a cast in the caller. Runtime definition comparison is a diagnostic, not
30
+ proof of equality with an executing projection. Keep integration specifications
31
+ for the actual kernel pair and model shape.
32
+
33
+ Do not silently replace a refused decision read with a materialized sink read.
34
+ Redesign the decision model as a focused admitted projection only when the
35
+ contract permits it, or surface the missing capability.
36
+
37
+ ## Completion is owned
38
+
39
+ `IUnitOfWork.HasEnrolledDecisionReads` is sticky: it remains true after commit,
40
+ rollback or disposal. It does not mean the transaction is still open.
41
+ `UnitOfWork.ClaimDecisionReadCommitOwnership()` returns the opaque
42
+ `DecisionReadCommitOwner` used by `CommitAsOwner` and `RollbackAsOwner`; these are concrete ownership capabilities, not methods on
43
+ `IUnitOfWork`. Once ownership is claimed, nested work must not complete that
44
+ protected transaction using ordinary commit/rollback. Arc's ownership wrapper
45
+ belongs in **cratis-arc-command** / **cratis-arc-command-validation**, not custom
46
+ application plumbing.
47
+
48
+ `ChronicleOptions.UnitOfWorkLifecyclePolicy` defaults to `Compatibility` for major
49
+ 19; `Strict` opts in and is planned as the next-major default. Compatibility
50
+ retains legacy late staging only for unprotected work and logs it. Strict rejects
51
+ all late staging. Protected work and owner completion checks remain enforced;
52
+ `UnitOfWorkIsCompleting` must not be caught and interpreted as success. An
53
+ in-flight commit cannot be undone by a competing rollback.
54
+
55
+ Keep read, decision, enrollment and completion inside one ambient async context.
56
+ If a commit rejects the read, start a new read/decision attempt rather than
57
+ reusing the stale protected instance or appending its events without the guard.
@@ -0,0 +1,12 @@
1
+ {
2
+ "skill": "cratis-chronicle-decision-reads",
3
+ "input": "Guard a standalone Chronicle decision through its owning commit.",
4
+ "fixtures": [],
5
+ "assertions": [
6
+ { "kind": "skill-contains", "value": "DecisionReadRequiresUnitOfWork" },
7
+ { "kind": "skill-contains", "value": "AdmitForStream<T>()" },
8
+ { "kind": "skill-contains", "value": "DecisionReadConflictsWithConcurrencyScope" },
9
+ { "kind": "skill-contains", "value": "GetDecisionConflicts()" },
10
+ { "kind": "skill-contains", "value": "reread and **re-decide**" }
11
+ ]
12
+ }
@@ -17,11 +17,12 @@ Constraints are discovered automatically. There is no registration call.
17
17
 
18
18
  | Package | Version | Purpose |
19
19
  | --- | --- | --- |
20
- | `Cratis.Chronicle` (client) | `18.3.0` | `[Unique]`, `[RemoveConstraint]`, `IConstraint`, `IConstraintBuilder`, `IUniqueConstraintBuilder`, `ConstraintViolation`, `IAppendResult` |
21
- | `Cratis.Chronicle` code analysis | `18.3.0` | `CHR0017`, `CHR0018`, `CHR0020` |
22
- | `Cratis.Chronicle.Testing` | `18.3.0` | `EventScenario` and the `IAppendResult` `Should*` assertions |
20
+ | `Cratis.Chronicle` (client) | `19.50.1` | uniqueness, stream closing, constraint builders and append results |
21
+ | `Cratis.Chronicle` code analysis | `19.50.1` | `CHR0017`, `CHR0018`, `CHR0020` |
22
+ | `Cratis.Chronicle.Testing` | `19.50.1` | `EventScenario` and the `IAppendResult` `Should*` assertions |
23
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.
24
+ Sources: `Source/Clients/DotNET/Events/Constraints/`,
25
+ `EventSequences/IEventSequence.cs` and `Source/Clients/Testing/` at v19.50.0.
25
26
 
26
27
  Reverify against the Chronicle repository before claiming support for another
27
28
  version. Never translate an attribute argument order or an assertion name from
@@ -31,8 +32,9 @@ memory.
31
32
 
32
33
  - The rule rejects **command input** — a range, a format, a cross-field
33
34
  condition: use `cratis-arc-command-validation`.
34
- - The rule is state-dependent and belongs in the handler under concurrency (the
35
- DCB pattern): also `cratis-arc-command-validation`.
35
+ - The rule is state-dependent and needs concurrency protection: use enrolled
36
+ stream/projection decision reads with `cratis-arc-command-validation`.
37
+ Plain injected models and `Result<,>` alone remain advisory.
36
38
  - You are creating or changing a read model that the rule reads: use
37
39
  `cratis-chronicle-read-model`.
38
40
  - You are writing the specification and want the `EventScenario` mechanics in
@@ -58,12 +60,12 @@ memory.
58
60
  | The same value must be unique across **several** event types | `IConstraint` with one `[Unique(name: …)]` name, or a fluent `Unique(…)` |
59
61
  | Case-insensitive matching, composite keys, or a scoped constraint | `IConstraint` |
60
62
 
61
- **Per-event-source versus across-event-sources is the constraint *kind*, not a
62
- setting.** Class-level `[Unique]` (and the generic `Unique<TEvent>()` builder
63
- call) means "at most one such event per event source id". Property-level
64
- `[Unique]` (and `Unique(unique => unique.On<T>(…))`) means "this value is unique
65
- across event sources". Choosing the wrong one gives a constraint that never
66
- fires rather than a compile error.
63
+ Class-level `[Unique]` (and `Unique<TEvent>()`) means "at most one such event
64
+ per event source id". Property-level `[Unique]` (and `Unique(unique => unique.On<T>(…))`)
65
+ means "this value is unique across event sources". `Mode` tunes **property-value
66
+ retention**, not that distinction: `PerEventSource` (default) releases the old
67
+ value when the source claims a new one; `PerValue` keeps every value reserved
68
+ until a removal releases it. Reclaiming one's own value succeeds in either mode.
67
69
 
68
70
  ## Step 2 — `[Unique]` — the default
69
71
 
@@ -94,8 +96,16 @@ public record UserRemoved;
94
96
  several on one removal event to release several claimed values at once.
95
97
  - To enforce the same property across **multiple** event types, give each its
96
98
  `[Unique(name: "UniqueEmail")]` with the **same name**.
97
- - Several removal events may declare the same name; **any one of them releases
98
- the claim on its own**.
99
+ - Several removal events may declare the same name; **any one releases the claim**.
100
+ - `Mode = UniqueConstraintMode.PerValue` retains all values. A removal can use
101
+ `Properties = [nameof(RemovalEvent.Value)]` to release one value (composite
102
+ properties in constraint order); empty `Properties` releases all values held
103
+ by the source in the scope.
104
+ - `EventSequences = [EventSequenceId.LogId]` limits validation/indexing to the
105
+ event log. Empty means every sequence; when declarations share a name, their
106
+ sequence sets combine and an unrestricted declaration wins.
107
+ - Null/missing property values do not claim `""`; clearing a default-mode value
108
+ releases its claim. `IgnoreCasing` is honored by the kernel.
99
109
 
100
110
  ## Step 3 — `IConstraint` — the advanced form
101
111
 
@@ -116,7 +126,7 @@ public class UniqueProjectName : IConstraint
116
126
  ```
117
127
 
118
128
  `IUniqueConstraintBuilder` also carries `WithName(name)`, `WithMessage(message)`,
119
- and an `On(EventType, string[])` overload. `On<TEvent>` is **variadic** —
129
+ `WithMode(UniqueConstraintMode)`, and an `On(EventType, string[])` overload. `On<TEvent>` is **variadic** —
120
130
  `On<T>(e => e.Tenant, e => e.Email)` constrains the composite of both
121
131
  properties, not two independent values.
122
132
 
@@ -130,6 +140,8 @@ Always pass them by name.
130
140
 
131
141
  `ConstraintScope` is a record of three optional dimensions, not an enum, and you
132
142
  never construct it — the three `Per…` calls select which dimensions participate.
143
+ `ForEventSequences(...)` / `ForEventLog()` select the sequences to validate and
144
+ index. Keep these separate from scope dimensions.
133
145
 
134
146
  ## Step 4 — Keep `Define` declarative
135
147
 
@@ -212,10 +224,37 @@ Use per-test unique values (`$"{Guid.NewGuid():N}"`) so specifications are not
212
224
  order-dependent. For a release, append the removal event and then assert the
213
225
  value can be claimed again.
214
226
 
215
- ⚠️ **`EventScenario` disables concurrency checking.** It wires a no-op
216
- concurrency-scope strategy, so `ShouldHaveConcurrencyViolations()` can never
217
- become true through the in-process scenario. Constraint violations are fully
218
- exercisable there; concurrency violations need the real kernel.
227
+ ⚠️ **The default `EventScenario` client scope strategy answers `ConcurrencyScope.None`.**
228
+ A scenario-only concurrency assertion without a checked scope is vacuous.
229
+ The scenario runs the kernel grain/validator, but explicit-scope violations
230
+ have not been proven end to end here; use real-client integration for that proof.
231
+ Constraints are fully exercisable. Dispose the scenario in `Destroy()`.
232
+
233
+ ## Close a stream at its business end
234
+
235
+ Choose the lifetime with **cratis-chronicle-event-modeling**, then enforce it at
236
+ append time. `IEventSequence.CompleteStream(ClosedStreamScope,
237
+ expectedTailSequenceNumber?)` returns `Result<EventSequenceNumber, CompleteStreamError>`:
238
+ `EmptyScope`, `AlreadyCompleted`, or `ExpectedTailMismatch` are not successful
239
+ new closures. An append matching a persisted closed scope is refused with
240
+ `StreamClosed`. Unspecified dimensions are wildcards, so name the intended
241
+ source/type/stream dimensions precisely.
242
+
243
+ Prefer an event-owned declaration when closing is a business fact:
244
+ `[ClosesStream]` / `IConstraintBuilder.ClosesStreamOn<TEvent>()`. The attribute
245
+ has `Dimensions`, `EventStreamIdFrom`, `ReopenedBy`, `Name` and `EventSequences`;
246
+ default dimensions are source id, stream type and stream id. Closing happens in
247
+ the **same grain turn** as append, **not crash-atomically** with it. Registering
248
+ the declaration is not retroactive; reindex rebuilds event-owned closures.
249
+ Reopening events remove only their constraint's exact closure, never a manual
250
+ operator closure.
251
+
252
+ Read [closing scope and recovery](references/closing-streams.md) when implementing
253
+ "close the books", choosing a scope, specifying the refusal, or repairing a
254
+ mistaken manual close. `IsStreamCompleted` and `GetClosedStreams` inspect;
255
+ Workbench's **Closed streams** page is read-only. Kernel `ReopenStreamScope`
256
+ requires an authorized operator and records `StreamScopeReopened` before
257
+ removing a manual closure. Closing does not erase history or prevent replay.
219
258
 
220
259
  ## What breaks
221
260