@cratis/pi 7.30.0 → 7.31.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.
@@ -204,6 +204,20 @@ from `string` (`:158`) and a `Redacted` form for logging (`:152`).
204
204
 
205
205
  Read [references/hosting.md](references/hosting.md) when wiring Chronicle into a worker or ASP.NET Core host, or when `AddCratisChronicle` or a namespace resolver does not resolve.
206
206
 
207
+ ## Pin event generations for delivery
208
+
209
+ `ChronicleOptions.EventGenerationDelivery` defaults to `Compatibility`. Opt in
210
+ with `EventGenerationDelivery.Pinned` (`Cratis.Chronicle.Observation`) **before
211
+ connecting**. Reactors and reducers then receive their handler's CLR event
212
+ generation (Chronicle **19.41.0+**); typed materialized projections use each
213
+ clause's declared CLR event generation (**19.48.0+**). These additions are
214
+ verified separately against those release tags, not the baseline above.
215
+
216
+ Read [references/pinned-generation-delivery.md](references/pinned-generation-delivery.md)
217
+ for compiling standalone/host configuration, registration constraints, and the
218
+ Compatibility-only surfaces. For migration, PDL pin persistence, and integration
219
+ test requirements, use **cratis-chronicle-event-type-migration**.
220
+
207
221
  ## The client and the server check each other
208
222
 
209
223
  The client runs the server-side compatibility check automatically inside `Connect()`; a genuine mismatch throws `IncompatibleServerException`, while a transport error is logged and ignored by design.
@@ -0,0 +1,92 @@
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
+ # Pin event generations before connecting
5
+
6
+ Requires Chronicle **19.41.0+** for client reactors/reducers and **19.48.0+** for
7
+ typed materialized projections. Upgrade every kernel silo before enabling the
8
+ option. `Compatibility` remains the default; adding a generation alone does not
9
+ opt consumers in.
10
+
11
+ ## Standalone client
12
+
13
+ This configuration excerpt uses `Cratis.Chronicle` **19.48.0** and a running
14
+ local development kernel. Keep the client alive for observation; supply logging
15
+ and a service provider when the application's observers need them (see the main
16
+ skill's standalone checklist).
17
+
18
+ ```csharp
19
+ using Cratis.Chronicle;
20
+ using Cratis.Chronicle.Connections;
21
+ using Cratis.Chronicle.Observation;
22
+
23
+ var options = new ChronicleOptions(ChronicleConnectionString.Development)
24
+ {
25
+ EventGenerationDelivery = EventGenerationDelivery.Pinned
26
+ };
27
+ using var client = new ChronicleClient(options);
28
+ var eventStore = await client.GetEventStore("People");
29
+ ```
30
+
31
+ ## Host integration
32
+
33
+ `AddCratisChronicle` extends `IHostApplicationBuilder` (an `IHostBuilder`
34
+ overload also exists), **not** `IServiceCollection`. Its `ChronicleClientOptions` inherits the delivery option.
35
+ This worker-host configuration excerpt uses `Cratis.Chronicle` **19.48.0** and
36
+ `Microsoft.Extensions.Hosting`; add the application's hosted worker and start
37
+ the host as usual.
38
+
39
+ ```csharp
40
+ using Cratis.Chronicle.Observation;
41
+ using Microsoft.Extensions.Hosting;
42
+
43
+ var builder = Host.CreateApplicationBuilder();
44
+ builder.AddCratisChronicle(options =>
45
+ {
46
+ options.EventStore = "People";
47
+ options.EventGenerationDelivery = EventGenerationDelivery.Pinned;
48
+ });
49
+ ```
50
+
51
+ ## Scope and registration
52
+
53
+ - Reactors and reducers pin the generation of the handler's CLR event type.
54
+ Typed materialized projections (fluent, model-bound, or explicitly registered)
55
+ pin each clause's declared CLR event generation. Live, catch-up, and replay
56
+ (and, for reactors and reducers, partition retry) use kernel generation
57
+ selection and schema-guided release.
58
+ - Projection pins cover `From`, `Join`, children, nested objects, removals,
59
+ event-content keys, and parent keys. Stored join/parent-event lookups select
60
+ and release through the same boundary as incoming events.
61
+ - **One generation per event identifier per observer/projection**, across every
62
+ handler or clause, including children and nested objects. Different
63
+ identifiers may pin different generations. Client validation throws
64
+ `Cratis.Chronicle.Observation.MultipleGenerationsOfEventTypeInObserver` for
65
+ conflicting pins; the kernel rejects them with
66
+ `ObserverPinsMultipleGenerationsOfEventType`. Kernel observer subscription
67
+ rejects an unregistered generation with
68
+ `EventTypeGenerationNotRegisteredForObserver`; projection schema resolution
69
+ can reject an unavailable pin with `PinnedEventTypeGenerationUnavailable`.
70
+ - Immediate/passive and wildcard (`all`/`FromAll`) projections stay
71
+ `Compatibility` when built through the .NET client. A definition explicitly
72
+ submitted as both wildcard and `Pinned` is unsupported and rejected by the
73
+ kernel with `EventGenerationDeliveryNotSupported`; the option does not make
74
+ wildcard pinning work.
75
+ - Ad-hoc queries/event reads are not opted in. Neither are webhooks, captures,
76
+ or event-store subscriptions. Do not generalize this option to every read
77
+ path, or to unreleased generation-delivery work.
78
+
79
+ For delivery-time migration, persisted PDL pins, and testing limits, read
80
+ [the migration reference](../../cratis-chronicle-event-type-migration/references/pinned-generations.md).
81
+
82
+ ## Product sources
83
+
84
+ Verified at `Cratis/Chronicle` **v19.48.0**:
85
+ `Source/Clients/DotNET/ChronicleOptions.cs`,
86
+ `Observation/EventGenerationDelivery.cs`,
87
+ `ChronicleHostApplicationBuilderExtensions.cs`,
88
+ `Projections/ProjectionGenerationDeliveryValidation.cs`, and
89
+ `Source/Kernel/Core/Projections/Engine/ProjectionFactory.cs`.
90
+ The option and reactor/reducer validation also exist at **v19.41.0**.
91
+
92
+ See [Chronicle's pinned observer delivery documentation](https://github.com/Cratis/Chronicle/blob/v19.48.0/Documentation/concepts/event-type-migrations.mdx#opt-in-to-pinned-observer-delivery).
@@ -51,8 +51,9 @@ public sealed class EventTypeAttribute(string id = "", uint generation = 1) : At
51
51
 
52
52
  `id` defaults to the CLR type name and `generation` starts at `1`. The attribute
53
53
  is not repeatable, so two generations are always two distinct CLR types.
54
- Chronicle routes a stored event through the migration chain before delivering it
55
- to observers:
54
+ Chronicle chains adjacent migrations to produce another generation. This is a
55
+ possible upcast path, **not a promise that every observer receives the latest
56
+ generation**; delivery policy is described below:
56
57
 
57
58
  ```text
58
59
  Generation 1 (stored) -> Migration 1->2 -> Migration 2->3 -> Current (Generation 3)
@@ -212,6 +213,23 @@ Migrations are discovered by convention — nothing is registered. They are
212
213
  activated through the service provider, so a migration may take constructor
213
214
  dependencies.
214
215
 
216
+ ## Choose the generation an observer receives
217
+
218
+ `Compatibility` remains the default. With
219
+ `ChronicleOptions.EventGenerationDelivery = EventGenerationDelivery.Pinned`,
220
+ reactors and reducers receive the generation of their handler's CLR event type
221
+ (verified at **19.41.0**). Typed materialized projection clauses receive the
222
+ generation they declare (verified at **19.48.0**), including joins, children,
223
+ removals, and event-content keys on live, catch-up, and replay.
224
+
225
+ Read [references/pinned-generations.md](references/pinned-generations.md) for
226
+ missing-generation migration, release and revision behavior, registration
227
+ rejections, persisted PDL pins, and testing limits. Configure the opt-in through
228
+ **cratis-chronicle-client-dotnet**; do not invent generation-qualified Screenplay
229
+ syntax. `ReadModelScenario` and `ReactorScenario` do **not** emulate the kernel's
230
+ pinned-generation migration and release; prove delivery with real-client
231
+ integration, not a scenario-only assertion.
232
+
215
233
  ## Do not model absence with a nullable property
216
234
 
217
235
  **CHR0012** (warning) flags a nullable property on an `[EventType]` record.
@@ -246,3 +264,7 @@ is expected, since the prior generation records history rather than new intent.
246
264
  outstanding.
247
265
  - Old-generation events replay into the current shape through the read-model
248
266
  specifications for the affected observers.
267
+ - With `EventGenerationDelivery.Pinned`, real-client integration also verifies
268
+ old-generation events on live delivery, catch-up, and replay into each
269
+ observer's pinned generation; in-process read-model/reactor scenarios alone
270
+ do not prove kernel migration and release.
@@ -0,0 +1,114 @@
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
+ # Migrations and pinned delivery
5
+
6
+ ## Contents
7
+
8
+ - What a pinned consumer receives
9
+ - PDL pins are resolved once, when saved
10
+ - Verify delivery through a real client
11
+ - Product sources
12
+
13
+ Verified against Chronicle **v19.48.0**. Reactor/reducer opt-in shipped in
14
+ **19.41.0**; typed materialized projection opt-in shipped in **19.48.0**.
15
+ Configure `ChronicleOptions.EventGenerationDelivery` before connecting; see
16
+ [standalone and host examples](../../cratis-chronicle-client-dotnet/references/pinned-generation-delivery.md).
17
+
18
+ ## What a pinned consumer receives
19
+
20
+ The kernel selects the pinned generation's content and hash, and releases
21
+ protected values using **that generation's schema**. A reactor or reducer pins
22
+ its handler's CLR event generation. A typed materialized projection uses the
23
+ CLR generation each clause declares, including joins, children, nested objects,
24
+ removals, event-content keys, and parent keys. Incoming live, catch-up, and replay
25
+ events and stored join/parent-event lookups use the same release boundary.
26
+
27
+ `EventContext.EventType.Generation` is the delivered generation;
28
+ `EventContext.AppendedGeneration` is the original append generation, or null for
29
+ legacy events where it is unknown. Do not infer the append generation from the
30
+ handler type.
31
+
32
+ If the pinned generation's content is missing from storage, Chronicle releases
33
+ the known appended generation (or the highest stored generation for legacy
34
+ events), then runs the adjacent `EventTypeMigration` upcast/downcast chain to the
35
+ pin **for this delivery only**. It neither persists that result nor caches
36
+ plaintext. The delivered hash is `EventHash.NotSet` because no durable hash
37
+ exists for that migrated content. Erased PII stays erased through migration.
38
+ An unavailable registered pin or an unreachable migration target fails with
39
+ `PinnedEventTypeGenerationUnavailable`, rather than silently substituting a
40
+ schema.
41
+
42
+ Revised events use their **latest revision**, migrating it to the pin when
43
+ needed. The revision's stored hash is retained only when its generation already
44
+ matches the pin; otherwise the delivered hash is `EventHash.NotSet`. SQL
45
+ revisions from before this release cannot be distinguished from original
46
+ content and retain existing generation-selection behavior until revised again.
47
+
48
+ Every observer/projection may pin **only one generation per event identifier**
49
+ across all handlers/clauses, including children and nested objects. Different
50
+ identifiers may pin different generations. See the client reference for the
51
+ client and kernel registration exception names. Changing a reactor/reducer's
52
+ pin or delivery mode does not itself trigger replay; request replay when prior
53
+ state needs rebuilding.
54
+
55
+ ## PDL pins are resolved once, when saved
56
+
57
+ For a PDL definition saved with `GenerationDelivery = Pinned`, a bare reference
58
+ such as `from PersonRegistered` resolves to the **highest registered generation
59
+ at registration time**. The definition persists that resolved pin. Join and
60
+ removal references, including children and nested references, follow the same
61
+ rule.
62
+
63
+ Registering a later generation does **not** advance an existing saved pin.
64
+ Re-saving the declaration resolves bare names again against the generations
65
+ registered at that time. Keep this distinction when deploying migrations: a
66
+ saved declaration is not a moving "latest" subscription.
67
+
68
+ PDL is owned by Screenplay and has **no generation-qualified event reference
69
+ syntax**. Never invent `PersonRegistered+2` or `Event+N`. When an explicit pin is
70
+ needed, use .NET fluent or model-bound builders with that generation's CLR event
71
+ type: for example, a `From<TEvent>` clause or `[FromEvent<TEvent>]` whose
72
+ `TEvent` carries the intended `[EventType]` or `[EventTypeGenerationFor<T>]`.
73
+ Do not mix two generations of the same identifier in different clauses.
74
+
75
+ Compatibility PDL retains bare-name generation **1** and identifier-only
76
+ dispatch. Immediate/passive and wildcard projections remain Compatibility;
77
+ ad-hoc queries are not opted in by this change. Do not use their results as
78
+ proof of pinned delivery.
79
+
80
+ ## Verify delivery through a real client
81
+
82
+ `ReadModelScenario` and `ReactorScenario` do **not** emulate kernel pinned
83
+ migration and protected-value release. The projection scenario preserves a
84
+ seeded event's generation, but that is not the kernel selecting or migrating
85
+ stored content. A scenario that feeds the desired CLR type only proves local
86
+ handling/mapping for that type.
87
+
88
+ Keep local decision/mapping specifications, and add **real-client integration**
89
+ against a matching kernel to prove generation delivery:
90
+
91
+ - Register both generations and their adjacent migration; use one pin per
92
+ identifier. Assert that conflicting pins are rejected.
93
+ - Append another generation and assert delivered content and
94
+ `EventContext.EventType.Generation` on reactors/reducers, plus projected state
95
+ for typed materialized projections. Check `AppendedGeneration` separately.
96
+ - Cover live delivery, catch-up, and replay; exercise contributing joins,
97
+ children, removals, and event-content/parent keys when the projection uses
98
+ them. Include missing stored generations and protected values when relevant.
99
+ - For saved PDL, verify that adding a generation preserves the old pin and that
100
+ re-saving resolves the bare reference anew.
101
+
102
+ Kernel specs provide product-level proof; in-process application scenarios are
103
+ not a replacement for real-client integration of this option.
104
+
105
+ ## Product sources
106
+
107
+ `Source/Kernel/Core/Events/EventGenerationRelease.cs` selects stored content,
108
+ releases with the selected schema, and migrates missing/revised generations.
109
+ `Source/Kernel/Core/Projections/ProjectionGenerationDeliveryValidation.cs`
110
+ validates all clauses; `Engine/DefinitionLanguage/ProjectionEventTypeReference.cs`
111
+ resolves PDL bare names. `Source/Clients/Testing/ReadModels/ProjectionReadModelProcessor.cs`
112
+ and `Reactors/ReactorScenario.cs` define the scenario boundary.
113
+
114
+ See [Chronicle's event type migration documentation](https://github.com/Cratis/Chronicle/blob/v19.48.0/Documentation/concepts/event-type-migrations.mdx#opt-in-to-pinned-observer-delivery).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/pi",
3
- "version": "7.30.0",
3
+ "version": "7.31.0",
4
4
  "description": "Configuration-aware Cratis AI integration for Pi",
5
5
  "type": "module",
6
6
  "files": [