@cratis/pi 7.102.0 → 7.103.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 (140) hide show
  1. package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
  2. package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
  3. package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
  4. package/package/corpus/hooks/README.md +11 -1
  5. package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
  6. package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
  7. package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
  8. package/package/corpus/rules/profiles.md +10 -1
  9. package/package/corpus/rules/pull-requests.md +2 -0
  10. package/package/corpus/rules/secrets.md +4 -0
  11. package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
  12. package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
  13. package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
  14. package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
  15. package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
  16. package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
  17. package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
  18. package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
  19. package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
  20. package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
  21. package/package/corpus/skills/binlog-generation/LICENSE +23 -0
  22. package/package/corpus/skills/binlog-generation/NOTICE +22 -0
  23. package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
  24. package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
  25. package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
  26. package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
  27. package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
  28. package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
  29. package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
  30. package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
  31. package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
  32. package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
  33. package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
  34. package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
  35. package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
  36. package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
  37. package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
  38. package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
  39. package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
  40. package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
  41. package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
  42. package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
  43. package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
  44. package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
  45. package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
  46. package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
  47. package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
  48. package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
  49. package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
  50. package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
  51. package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
  52. package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
  53. package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
  54. package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
  55. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
  56. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
  57. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
  58. package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
  59. package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
  60. package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
  61. package/package/corpus/skills/dump-collect/LICENSE +23 -0
  62. package/package/corpus/skills/dump-collect/NOTICE +40 -0
  63. package/package/corpus/skills/dump-collect/SKILL.md +97 -0
  64. package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
  65. package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
  66. package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
  67. package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
  68. package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
  69. package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
  70. package/package/corpus/skills/extension-points/LICENSE +23 -0
  71. package/package/corpus/skills/extension-points/NOTICE +18 -0
  72. package/package/corpus/skills/extension-points/SKILL.md +248 -0
  73. package/package/corpus/skills/filter-syntax/LICENSE +23 -0
  74. package/package/corpus/skills/filter-syntax/NOTICE +17 -0
  75. package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
  76. package/package/corpus/skills/including-generated-files/LICENSE +23 -0
  77. package/package/corpus/skills/including-generated-files/NOTICE +17 -0
  78. package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
  79. package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
  80. package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
  81. package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
  82. package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
  83. package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
  84. package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
  85. package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
  86. package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
  87. package/package/corpus/skills/msbuild/LICENSE +23 -0
  88. package/package/corpus/skills/msbuild/NOTICE +55 -0
  89. package/package/corpus/skills/msbuild/SKILL.md +117 -0
  90. package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
  91. package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
  92. package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
  93. package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
  94. package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
  95. package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
  96. package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
  97. package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
  98. package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
  99. package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
  100. package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
  101. package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
  102. package/package/corpus/skills/orleans/LICENSE +21 -0
  103. package/package/corpus/skills/orleans/NOTICE +64 -0
  104. package/package/corpus/skills/orleans/SKILL.md +140 -0
  105. package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
  106. package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
  107. package/package/corpus/skills/orleans/references/examples.md +66 -0
  108. package/package/corpus/skills/orleans/references/grain-api.md +784 -0
  109. package/package/corpus/skills/orleans/references/grains.md +75 -0
  110. package/package/corpus/skills/orleans/references/hosting.md +71 -0
  111. package/package/corpus/skills/orleans/references/implementation.md +591 -0
  112. package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
  113. package/package/corpus/skills/orleans/references/patterns.md +811 -0
  114. package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
  115. package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
  116. package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
  117. package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
  118. package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
  119. package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
  120. package/package/corpus/skills/platform-detection/LICENSE +23 -0
  121. package/package/corpus/skills/platform-detection/NOTICE +17 -0
  122. package/package/corpus/skills/platform-detection/SKILL.md +230 -0
  123. package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
  124. package/package/corpus/skills/profiling/LICENSE +21 -0
  125. package/package/corpus/skills/profiling/NOTICE +50 -0
  126. package/package/corpus/skills/profiling/SKILL.md +144 -0
  127. package/package/corpus/skills/profiling/references/commands.md +439 -0
  128. package/package/corpus/skills/profiling/references/patterns.md +586 -0
  129. package/package/corpus/skills/profiling/references/profiling.md +190 -0
  130. package/package/corpus/skills/run-tests/LICENSE +23 -0
  131. package/package/corpus/skills/run-tests/NOTICE +19 -0
  132. package/package/corpus/skills/run-tests/SKILL.md +305 -0
  133. package/package/corpus/skills/target-authoring/LICENSE +23 -0
  134. package/package/corpus/skills/target-authoring/NOTICE +17 -0
  135. package/package/corpus/skills/target-authoring/SKILL.md +165 -0
  136. package/package/corpus/skills/vectorization/LICENSE +23 -0
  137. package/package/corpus/skills/vectorization/NOTICE +17 -0
  138. package/package/corpus/skills/vectorization/SKILL.md +189 -0
  139. package/package/profile-catalog.json +85 -1
  140. package/package.json +1 -1
@@ -0,0 +1,396 @@
1
+ # Streaming, Broadcast Channels, and Observers API
2
+
3
+ ## Contents
4
+
5
+ - Orleans Streams
6
+ - Broadcast Channels
7
+ - Observers
8
+ - Stream Providers
9
+ - Streams Programming API Details
10
+ - Direct Calls vs Streams vs Broadcast vs Observers vs IAsyncEnumerable
11
+
12
+ Detailed streaming and communication patterns from official Orleans documentation.
13
+
14
+ ## Orleans Streams
15
+
16
+ Streams are virtual, always exist, never fail, identified by `StreamId` (namespace + key). Decouple data generation from processing in time and space.
17
+
18
+ ### Configuration
19
+
20
+ ```csharp
21
+ // Silo — memory streams (dev only)
22
+ siloBuilder.AddMemoryStreams("StreamProvider")
23
+ .AddMemoryGrainStorage("PubSubStore");
24
+
25
+ // Silo — Azure Event Hubs
26
+ siloBuilder.AddEventHubStreams("EventHubProvider", options =>
27
+ {
28
+ options.ConfigureEventHub(eh => eh.Configure(o =>
29
+ {
30
+ o.ConnectionString = connectionString;
31
+ o.ConsumerGroup = "$Default";
32
+ o.Path = "my-hub";
33
+ }));
34
+ options.UseAzureTableCheckpointer(c => c.ConfigureTableServiceClient(storageConnection));
35
+ });
36
+
37
+ // Client
38
+ clientBuilder.AddMemoryStreams("StreamProvider");
39
+ ```
40
+
41
+ ### Producing Events
42
+
43
+ ```csharp
44
+ var streamProvider = this.GetStreamProvider("StreamProvider");
45
+ var streamId = StreamId.Create("SensorData", this.GetPrimaryKeyString());
46
+ var stream = streamProvider.GetStream<SensorReading>(streamId);
47
+ await stream.OnNextAsync(new SensorReading { Value = 42.0 });
48
+ ```
49
+
50
+ ### Consuming Events — Implicit Subscription
51
+
52
+ ```csharp
53
+ [ImplicitStreamSubscription("SensorData")]
54
+ public class AggregatorGrain : Grain, IAggregatorGrain
55
+ {
56
+ public override async Task OnActivateAsync(CancellationToken ct)
57
+ {
58
+ var streamProvider = this.GetStreamProvider("StreamProvider");
59
+ var stream = streamProvider.GetStream<SensorReading>(
60
+ StreamId.Create("SensorData", this.GetPrimaryKeyString()));
61
+
62
+ await stream.SubscribeAsync(
63
+ (reading, token) =>
64
+ {
65
+ // Process reading
66
+ return Task.CompletedTask;
67
+ });
68
+ await base.OnActivateAsync(ct);
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### Consuming Events — Explicit Subscription
74
+
75
+ ```csharp
76
+ var stream = streamProvider.GetStream<Order>(StreamId.Create("Orders", "incoming"));
77
+
78
+ StreamSubscriptionHandle<Order> handle = await stream.SubscribeAsync(
79
+ onNextAsync: (item, token) => ProcessAsync(item),
80
+ onErrorAsync: ex => HandleError(ex),
81
+ onCompletedAsync: () => Task.CompletedTask,
82
+ token: lastKnownToken); // resume from position
83
+
84
+ // Unsubscribe
85
+ await handle.UnsubscribeAsync();
86
+ ```
87
+
88
+ ### Delivery Semantics
89
+
90
+ | Provider | Delivery | Ordering |
91
+ |---|---|---|
92
+ | Memory | Best-effort | FIFO per stream |
93
+ | Azure Queue | At-least-once | FIFO per queue |
94
+ | Event Hubs | At-least-once | Per partition |
95
+ | Broadcast Channel | At-most-once | No guarantee |
96
+
97
+ Rewindable streams support subscribing from arbitrary point using `StreamSequenceToken`.
98
+
99
+ ### IAsyncEnumerable (Orleans 7+)
100
+
101
+ Request-response scoped streaming, not pub-sub:
102
+
103
+ ```csharp
104
+ // Interface
105
+ public interface IDataGrain : IGrainWithStringKey
106
+ {
107
+ IAsyncEnumerable<DataItem> GetItems(
108
+ int count,
109
+ [EnumeratorCancellation] CancellationToken ct = default);
110
+ }
111
+
112
+ // Implementation
113
+ public class DataGrain : Grain, IDataGrain
114
+ {
115
+ public async IAsyncEnumerable<DataItem> GetItems(
116
+ int count,
117
+ [EnumeratorCancellation] CancellationToken ct = default)
118
+ {
119
+ for (int i = 0; i < count && !ct.IsCancellationRequested; i++)
120
+ {
121
+ yield return await FetchItem(i);
122
+ }
123
+ }
124
+ }
125
+
126
+ // Consumer
127
+ await foreach (var item in grain.GetItems(100).WithCancellation(cts.Token))
128
+ {
129
+ Process(item);
130
+ }
131
+ ```
132
+
133
+ Default batch size: 100. Configure with `.WithBatchSize(50)`.
134
+
135
+ ## Broadcast Channels
136
+
137
+ Fire-and-forget broadcast to all implicitly subscribed grains. No persistence, no delivery guarantees.
138
+
139
+ ### Configuration
140
+
141
+ ```csharp
142
+ siloBuilder.AddBroadcastChannel("announcements");
143
+ ```
144
+
145
+ ### Consumer
146
+
147
+ ```csharp
148
+ [ImplicitChannelSubscription]
149
+ public class NotificationGrain : Grain, INotificationGrain, IOnBroadcastChannelSubscribed
150
+ {
151
+ public Task OnSubscribed(IBroadcastChannelSubscription subscription)
152
+ {
153
+ subscription.Attach<Announcement>(
154
+ item => OnAnnouncement(item),
155
+ ex => OnError(ex));
156
+ return Task.CompletedTask;
157
+ }
158
+
159
+ private Task OnAnnouncement(Announcement item)
160
+ {
161
+ // Process broadcast message
162
+ return Task.CompletedTask;
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### Producer
168
+
169
+ ```csharp
170
+ var provider = client.GetBroadcastChannelProvider("announcements");
171
+ var channelId = ChannelId.Create("system", Guid.Empty);
172
+ var writer = provider.GetChannelWriter<Announcement>(channelId);
173
+ await writer.Publish(new Announcement { Message = "System update" });
174
+ ```
175
+
176
+ ## Observers
177
+
178
+ One-way async push notifications from grains to clients or other grains.
179
+
180
+ ### Observer Interface
181
+
182
+ ```csharp
183
+ public interface IChatObserver : IGrainObserver
184
+ {
185
+ [OneWay] // fire-and-forget
186
+ Task OnMessage(string user, string message);
187
+
188
+ Task OnUserJoined(string user);
189
+ }
190
+ ```
191
+
192
+ ### Server-Side (ObserverManager)
193
+
194
+ ```csharp
195
+ public class ChatRoomGrain : Grain, IChatRoomGrain
196
+ {
197
+ private readonly ObserverManager<IChatObserver> _observers;
198
+
199
+ public ChatRoomGrain(ILogger<ChatRoomGrain> logger)
200
+ {
201
+ _observers = new ObserverManager<IChatObserver>(
202
+ TimeSpan.FromMinutes(5), logger);
203
+ }
204
+
205
+ public Task Subscribe(IChatObserver observer)
206
+ {
207
+ _observers.Subscribe(observer, observer);
208
+ return Task.CompletedTask;
209
+ }
210
+
211
+ public Task Unsubscribe(IChatObserver observer)
212
+ {
213
+ _observers.Unsubscribe(observer);
214
+ return Task.CompletedTask;
215
+ }
216
+
217
+ public Task SendMessage(string user, string message)
218
+ {
219
+ _observers.Notify(o => o.OnMessage(user, message));
220
+ return Task.CompletedTask;
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### Client-Side
226
+
227
+ ```csharp
228
+ // Create observer
229
+ var observerInstance = new ChatObserver();
230
+ var observerRef = grainFactory.CreateObjectReference<IChatObserver>(observerInstance);
231
+
232
+ // Subscribe
233
+ var chatRoom = client.GetGrain<IChatRoomGrain>("lobby");
234
+ await chatRoom.Subscribe(observerRef);
235
+
236
+ // Cleanup — must call to avoid memory leaks (held as WeakReference)
237
+ grainFactory.DeleteObjectReference<IChatObserver>(observerRef);
238
+ ```
239
+
240
+ ### Grain-as-Observer
241
+
242
+ ```csharp
243
+ public class MonitorGrain : Grain, IMonitorGrain, IChatObserver
244
+ {
245
+ public async Task StartMonitoring(string room)
246
+ {
247
+ var chatRoom = GrainFactory.GetGrain<IChatRoomGrain>(room);
248
+ await chatRoom.Subscribe(this.AsReference<IChatObserver>());
249
+ }
250
+
251
+ public Task OnMessage(string user, string message)
252
+ {
253
+ // Handle notification
254
+ return Task.CompletedTask;
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Observer Execution Model
260
+
261
+ - Non-reentrant, single-threaded per observer reference
262
+ - `[Reentrant]` / `[AlwaysInterleave]` attributes don't affect observers
263
+ - `CancellationToken` support added in Orleans 9.0
264
+
265
+ ## Stream Providers
266
+
267
+ ### Azure Event Hubs
268
+
269
+ NuGet: `Microsoft.Orleans.Streaming.EventHubs`. Real-time ingestion, rewindable, supports replay from arbitrary point.
270
+
271
+ ```csharp
272
+ siloBuilder.AddEventHubStreams("EventHubProvider", options =>
273
+ {
274
+ options.ConfigureEventHub(eh => eh.Configure(o =>
275
+ {
276
+ o.ConnectionString = connectionString;
277
+ o.ConsumerGroup = "$Default";
278
+ o.Path = "my-hub";
279
+ }));
280
+ options.UseAzureTableCheckpointer(c =>
281
+ c.ConfigureTableServiceClient(storageConnection));
282
+ });
283
+ ```
284
+
285
+ ### Azure Queue (AQ)
286
+
287
+ NuGet: `Microsoft.Orleans.Streaming.AzureStorage`. Uses pulling agents inside silos. Not rewindable. Does not guarantee FIFO on failures.
288
+
289
+ ```csharp
290
+ siloBuilder.AddAzureQueueStreams("AQProvider", optionsBuilder =>
291
+ optionsBuilder.ConfigureAzureQueue(options =>
292
+ options.Configure(opt =>
293
+ opt.QueueServiceClient = new QueueServiceClient(endpoint, credential))));
294
+ ```
295
+
296
+ ### Aspire Streaming Integration
297
+
298
+ ```csharp
299
+ // Azure Queue with Aspire
300
+ var storage = builder.AddAzureStorage("storage");
301
+ var queues = storage.AddQueues("streaming");
302
+ var orleans = builder.AddOrleans("cluster")
303
+ .WithStreaming("AQProvider", queues);
304
+
305
+ // In-memory streaming
306
+ orleans.WithMemoryStreaming("MemoryProvider");
307
+
308
+ // Broadcast channels
309
+ orleans.WithBroadcastChannel("BroadcastChannel");
310
+ ```
311
+
312
+ ### Pub-Sub Management
313
+
314
+ Managed by `PubSubRendezvousGrain`, persisted via `"PubSubStore"`:
315
+
316
+ ```csharp
317
+ siloBuilder.AddAzureTableGrainStorage("PubSubStore",
318
+ options => options.TableServiceClient = new TableServiceClient(endpoint, credential));
319
+ ```
320
+
321
+ ## Streams Programming API Details
322
+
323
+ ### Core Interfaces
324
+
325
+ ```csharp
326
+ public interface IAsyncObserver<in T>
327
+ {
328
+ Task OnNextAsync(T item, StreamSequenceToken token = null);
329
+ Task OnCompletedAsync();
330
+ Task OnErrorAsync(Exception ex);
331
+ }
332
+
333
+ public interface IAsyncObservable<T>
334
+ {
335
+ Task<StreamSubscriptionHandle<T>> SubscribeAsync(IAsyncObserver<T> observer);
336
+ }
337
+ ```
338
+
339
+ ### Failure Recovery
340
+
341
+ Consumer must re-attach processing logic in `OnActivateAsync`:
342
+
343
+ ```csharp
344
+ public override async Task OnActivateAsync(CancellationToken ct)
345
+ {
346
+ var stream = this.GetStreamProvider("SP").GetStream<string>(streamId);
347
+ var handles = await stream.GetAllSubscriptionHandles();
348
+ foreach (var handle in handles)
349
+ await handle.ResumeAsync(this);
350
+ }
351
+ ```
352
+
353
+ ### Implicit Subscriptions with IStreamSubscriptionObserver
354
+
355
+ ```csharp
356
+ [ImplicitStreamSubscription("MyNamespace")]
357
+ public class MyGrain : Grain, IMyGrain, IStreamSubscriptionObserver
358
+ {
359
+ public async Task OnSubscribed(IStreamSubscriptionHandleFactory handleFactory)
360
+ {
361
+ var handle = handleFactory.Create<string>();
362
+ await handle.ResumeAsync(this);
363
+ }
364
+ }
365
+ ```
366
+
367
+ ### Key Characteristics
368
+
369
+ - Subscriptions are per-grain (not per-activation) — durable across activations
370
+ - Multiple producers and consumers per stream
371
+ - Subscribing X times delivers event X times
372
+ - Rewindable streams (Event Hubs) support replay from `StreamSequenceToken`
373
+
374
+ ### Why Orleans Streams?
375
+
376
+ Existing stream systems (Kafka, Storm, Spark) are for uniform dataflow graphs. Orleans Streams target:
377
+ 1. **Flexible processing logic** — imperative, functional, Rx, stateful, with side effects
378
+ 2. **Dynamic topologies** — add/remove nodes at runtime
379
+ 3. **Fine-grained granularity** — each stream link is an independent entity
380
+ 4. **Distribution** — scalable, elastic, reliable
381
+
382
+ Typical use: per-user streams with different processing logic per user, subscriptions changing dynamically.
383
+
384
+ ## Direct Calls vs Streams vs Broadcast vs Observers vs IAsyncEnumerable
385
+
386
+ | Feature | Direct call | Streams | Broadcast | Observers | IAsyncEnumerable |
387
+ |---|---|---|---|---|---|
388
+ | Purpose | Command/query a known owner | Decoupled event flow | Loss-tolerant fan-out | Live callback to a connected subscriber | Progressive response to one caller |
389
+ | Persistence | No message persistence | Provider-dependent | No | No | No |
390
+ | Delivery | At-most-once by default; retries can duplicate | Provider-dependent | Best-effort | Best-effort for clients | Bound to the request |
391
+ | Subscription | None | Implicit or explicit | Implicit | Manual and renewable | None |
392
+ | Replay/history | No | Provider-dependent | No | No | No |
393
+ | Lifetime | One request/response | Long-lived logical subscription | Active implicit subscriber mapping | Client/object reference lifetime | One enumeration/call |
394
+ | Primary use | Typed result, completion, error | Dynamic pub/sub and queue-backed processing | Cache invalidation, latest-value hints, announcements | UI/client notifications | Large/progressive result sets with backpressure |
395
+
396
+ Use a one-way request only as an advanced direct-call variant when the caller explicitly accepts no completion, error, result, or receipt guarantee. Do not treat one-way calls, broadcast channels, or observers as durable messaging.
@@ -0,0 +1,210 @@
1
+ # Orleans Testing Patterns
2
+
3
+ ## Contents
4
+
5
+ - Choose The Harness First
6
+ - Runtime-Only Orleans Tests
7
+ - Shared AppHost Fixture For Real Topology Tests
8
+ - Mix AppHost Infra With `WebApplicationFactory`
9
+ - TUnit Per-Session Sharing
10
+ - SignalR And Browser Flows
11
+ - Failure Diagnostics
12
+ - Anti-Patterns
13
+
14
+ Use this reference when the task is about testing Orleans grains together with real hosting, HTTP/SignalR surfaces, Aspire resources, or browser flows.
15
+
16
+ These patterns are grounded in working test harnesses used in `AIBase` and `WA.Storied.Agents`: one shared AppHost fixture for the distributed topology, an optional `WebApplicationFactory` layer for direct Host DI/grain access, and separate browser contexts per UI test.
17
+
18
+ Sharing the AppHost reduces startup cost and does not make the suite sequential. Keep tests parallel, make fixture helpers concurrency-safe, and use unique grain IDs, tenant IDs, storage prefixes, database keys, connections, and browser contexts per test. Constrain only the smallest keyed group that performs destructive changes to the exact same shared state.
19
+
20
+ ## Choose The Harness First
21
+
22
+ | Need | Best Harness | Why |
23
+ |---|---|---|
24
+ | Grain runtime behavior only | `InProcessTestCluster` | Fastest Orleans-focused harness without HTTP/UI layers |
25
+ | Real API, SignalR, SSE, or AppHost wiring | Shared Aspire/AppHost fixture | Exercises the actual distributed topology and resource graph |
26
+ | Direct Host DI services or `IGrainFactory` from the hosted app | Shared Aspire/AppHost fixture + `WebApplicationFactory<TEntryPoint>` | Keeps real infra while letting tests resolve services from the Host container |
27
+ | Browser automation against a co-hosted Orleans app | Shared Aspire/AppHost fixture + Playwright | Reuses one browser process and one AppHost boot, but isolates browser state per test |
28
+
29
+ ## Runtime-Only Orleans Tests
30
+
31
+ When the test is purely about grain scheduling, persistence, reentrancy, or state transitions, stay close to Orleans and use `InProcessTestCluster`:
32
+
33
+ ```csharp
34
+ var builder = new InProcessTestClusterBuilder();
35
+ builder.ConfigureSilo(siloBuilder =>
36
+ {
37
+ siloBuilder.AddMemoryGrainStorageAsDefault();
38
+ });
39
+
40
+ await using var cluster = await builder.BuildAndStartAsync();
41
+ var grain = cluster.GrainFactory.GetGrain<IOrderGrain>(Guid.NewGuid());
42
+
43
+ await grain.SubmitAsync(new SubmitOrder("PO-42"));
44
+ var state = await grain.GetStateAsync();
45
+ ```
46
+
47
+ Use this harness when HTTP, SignalR, and AppHost resources are irrelevant to the assertion.
48
+
49
+ ## Shared AppHost Fixture For Real Topology Tests
50
+
51
+ Use Aspire when the assertion depends on the actual Orleans host plus the real surrounding resources:
52
+
53
+ ```csharp
54
+ public sealed class AspireTestFixture : IAsyncDisposable
55
+ {
56
+ private DistributedApplication? _app;
57
+
58
+ public DistributedApplication App =>
59
+ _app ?? throw new InvalidOperationException("App not initialized.");
60
+
61
+ public string HostUrl { get; private set; } = string.Empty;
62
+
63
+ public async Task InitializeAsync()
64
+ {
65
+ var builder = await DistributedApplicationTestingBuilder.CreateAsync<Projects.My_AppHost>();
66
+
67
+ builder.Services.AddLogging(logging =>
68
+ {
69
+ logging.ClearProviders();
70
+ logging.AddConsole();
71
+ logging.SetMinimumLevel(LogLevel.Warning);
72
+ logging.AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning);
73
+ logging.AddFilter("Aspire.Hosting.Backchannel", LogLevel.Critical);
74
+ });
75
+
76
+ _app = await builder.BuildAsync();
77
+ await _app.StartAsync();
78
+
79
+ using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));
80
+ await _app.ResourceNotifications.WaitForResourceHealthyAsync("host", cts.Token);
81
+
82
+ HostUrl = _app.CreateHttpClient("host", "http").BaseAddress?.ToString()
83
+ ?? throw new InvalidOperationException("Host URL was not resolved.");
84
+ }
85
+
86
+ public HttpClient CreateApiClient() => App.CreateHttpClient("host", "http");
87
+ }
88
+ ```
89
+
90
+ This is the right shape for:
91
+
92
+ - HTTP endpoint tests
93
+ - SignalR/SSE/OpenResponses tests
94
+ - admin or operator UI tests
95
+ - Orleans flows that require the real host pipeline, auth, or middleware
96
+
97
+ ## Mix AppHost Infra With `WebApplicationFactory`
98
+
99
+ This is the production-style pattern for co-hosted Orleans apps: boot infrastructure in Aspire once, then create a `WebApplicationFactory` over the Host/API assembly for direct DI and grain access.
100
+
101
+ ```csharp
102
+ public sealed class TestApplication
103
+ : WebApplicationFactory<HostEntryPointMarker>, IAsyncDisposable
104
+ {
105
+ private static readonly AspireTestFixture SharedFixture = new();
106
+ private readonly Dictionary<string, string?> _overrides = new(StringComparer.OrdinalIgnoreCase);
107
+
108
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
109
+ {
110
+ builder.UseEnvironment(Environments.Development);
111
+ builder.ConfigureAppConfiguration((_, config) => config.AddInMemoryCollection(_overrides));
112
+ }
113
+
114
+ public async Task InitializeAsync()
115
+ {
116
+ await SharedFixture.InitializeAsync();
117
+
118
+ var tables = await SharedFixture.App.GetConnectionStringAsync("tables");
119
+ var blobs = await SharedFixture.App.GetConnectionStringAsync("blobs");
120
+
121
+ _overrides["ConnectionStrings:Tables"] = tables;
122
+ _overrides["ConnectionStrings:Blobs"] = blobs;
123
+
124
+ CreateClient();
125
+ }
126
+
127
+ public AsyncServiceScope CreateScope() => Services.CreateAsyncScope();
128
+
129
+ public HttpClient CreateApiClient()
130
+ {
131
+ var client = CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
132
+ client.Timeout = TimeSpan.FromMinutes(5);
133
+ return client;
134
+ }
135
+ }
136
+ ```
137
+
138
+ Use this when the test needs:
139
+
140
+ - `IGrainFactory`
141
+ - repositories or managers from DI
142
+ - direct access to runtime services
143
+ - real HTTP clients and hub connections against the same host
144
+ - whichever async fixture contract the test framework expects: xUnit, TUnit, or a repo-local wrapper
145
+
146
+ ## TUnit Per-Session Sharing
147
+
148
+ For TUnit-based Orleans suites, keep the AppHost boot outside individual tests:
149
+
150
+ ```csharp
151
+ [ClassDataSource<TestApplication>(Shared = SharedType.PerTestSession)]
152
+ public sealed class OrderGrainIntegrationTests(TestApplication app)
153
+ {
154
+ [Test]
155
+ public async Task Order_grain_persists_state_through_real_host()
156
+ {
157
+ await using var scope = app.CreateScope();
158
+ var grainFactory = scope.ServiceProvider.GetRequiredService<IGrainFactory>();
159
+
160
+ var grain = grainFactory.GetGrain<IOrderGrain>(Guid.NewGuid());
161
+ await grain.SubmitAsync(new SubmitOrder("PO-42"));
162
+
163
+ var state = await grain.GetStateAsync();
164
+ await Assert.That(state.Number).IsEqualTo("PO-42");
165
+ }
166
+ }
167
+ ```
168
+
169
+ The same pattern works for API-only tests with `ClassDataSource<AspireTestFixture>(Shared = SharedType.PerTestSession)`.
170
+
171
+ Do not add `[NotInParallel]` to the class or force one test module/worker merely because the fixture is shared. If a maintenance test drops a shared schema or resets the whole cluster, give only those destructive tests the same keyed non-parallel constraint.
172
+
173
+ ## SignalR And Browser Flows
174
+
175
+ For SignalR or UI flows, keep connection and browser helpers on the shared fixture:
176
+
177
+ ```csharp
178
+ public HubConnection CreateAgentHubConnection(string token)
179
+ {
180
+ var hubUrl = new Uri(new Uri(HostUrl), "/agenthub");
181
+
182
+ return new HubConnectionBuilder()
183
+ .WithUrl(hubUrl, options =>
184
+ {
185
+ options.AccessTokenProvider = () => Task.FromResult(token)!;
186
+ options.Headers["Authorization"] = $"Bearer {token}";
187
+ options.Transports = HttpTransportType.LongPolling;
188
+ })
189
+ .WithAutomaticReconnect()
190
+ .Build();
191
+ }
192
+ ```
193
+
194
+ For Playwright:
195
+
196
+ - initialize Playwright once in the shared fixture
197
+ - create a new browser context per test
198
+ - set `BaseURL`, viewport, and `IgnoreHTTPSErrors` in the helper, not inline in every test
199
+
200
+ ## Failure Diagnostics
201
+
202
+ Follow the `SKILL.md` Diagnostic Output Budget: keep progress/ANSI visible, collect Warning/Error/Critical logs in a size-bounded collector, and show only the exact test/resource failure, root exception, and relevant stack frames (at most 80 lines / 8 KiB per response). Never print the full collector on HTTP 500, startup failures, or crashes. Link Playwright screenshots/HTML and inspect only bounded resource-error excerpts when health waits fail. Preserve the original exception and runner exit code.
203
+
204
+ ## Anti-Patterns
205
+
206
+ - Creating `DistributedApplicationTestingBuilder.CreateAsync<...>()` inside each test method
207
+ - Booting a second Orleans client inside a co-hosted Host test when the app already uses `UseOrleans`
208
+ - Copy-pasting local connection strings into tests instead of resolving them from `SharedFixture.App`
209
+ - Using in-memory substitutes for persistence-sensitive or streaming-sensitive integration tests
210
+ - Sharing a Playwright page or browser context across tests instead of sharing only the browser process
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) .NET Foundation and Contributors
4
+
5
+ All rights reserved.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ Test Platform and Framework Detection
2
+ Copyright (c) .NET Foundation and Contributors
3
+ Licensed under the MIT License. See LICENSE.
4
+
5
+ Upstream source: https://github.com/dotnet/skills/blob/a6bf43ab61967c30919334986515163ab24d7635/plugins/dotnet-test/skills/platform-detection/SKILL.md
6
+
7
+ Changes made by Cratis:
8
+
9
+ LICENSE:
10
+ - Copy upstream MIT license and copyright verbatim.
11
+
12
+ SKILL.md:
13
+ - Normalize frontmatter serialization, add MIT license where absent; preserve name and description.
14
+ - Add marked modification/source comment and applicability/Cratis precedence line with the skill-specific safety, ownership or compatibility caveat.
15
+ - Rewrite the description to the Cratis shape (what it does, Use when, Not for) and drop references to skills that are not part of this corpus.
16
+
17
+ NOTICE: Add this attribution, change list, and any download/execution disclosure.