@cratis/pi 7.113.0 → 7.114.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 (27) hide show
  1. package/package/corpus/harnesses/copilot/instructions/orleans.instructions.md +10 -2
  2. package/package/corpus/harnesses/copilot/instructions/vertical-slices.instructions.md +1 -1
  3. package/package/corpus/hooks/scripts/cratis-store-mutations.json +4 -0
  4. package/package/corpus/product-sync.json +12 -12
  5. package/package/corpus/rules/orleans.md +10 -2
  6. package/package/corpus/rules/vertical-slices.md +1 -1
  7. package/package/corpus/skills/cratis-application-slice-specifications/references/query-and-decision-scenarios.md +19 -0
  8. package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +8 -0
  9. package/package/corpus/skills/cratis-arc-command/references/proxy-generation.md +10 -1
  10. package/package/corpus/skills/cratis-arc-command-execution/SKILL.md +3 -2
  11. package/package/corpus/skills/cratis-arc-command-execution/references/occurrence-commands.md +7 -6
  12. package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +5 -0
  13. package/package/corpus/skills/cratis-arc-command-validation/references/input-binding.md +17 -0
  14. package/package/corpus/skills/cratis-arc-command-validation/references/protected-validator-construction.md +94 -0
  15. package/package/corpus/skills/cratis-arc-ef-core-migration/SKILL.md +18 -0
  16. package/package/corpus/skills/cratis-arc-query-paging/SKILL.md +11 -4
  17. package/package/corpus/skills/cratis-arc-query-paging/references/live-query-processing.md +71 -0
  18. package/package/corpus/skills/cratis-chronicle-cli-operations/SKILL.md +11 -2
  19. package/package/corpus/skills/cratis-chronicle-cli-operations/references/command-effects.md +7 -0
  20. package/package/corpus/skills/cratis-chronicle-cli-operations/references/project-tooling.md +91 -0
  21. package/package/corpus/skills/cratis-chronicle-cli-operations/references/recovery-and-comparison.md +68 -0
  22. package/package/corpus/skills/cratis-chronicle-read-model/references/queries.md +5 -3
  23. package/package/corpus/skills/cratis-cli-terminal-workbench/SKILL.md +5 -1
  24. package/package/corpus/skills/cratis-orleans-jobs/SKILL.md +39 -51
  25. package/package/corpus/skills/cratis-orleans-jobs/references/lifecycle-and-placement.md +52 -0
  26. package/package/corpus/skills/cratis-orleans-jobs/references/state-machines.md +3 -1
  27. package/package.json +1 -1
@@ -11,7 +11,7 @@ paths:
11
11
  > **⚠️ APPLIES ONLY TO PROJECTS USING MICROSOFT ORLEANS**
12
12
  > If your project does not reference `Microsoft.Orleans` or any Orleans packages, **ignore this entire file**. These rules are irrelevant outside of Orleans contexts.
13
13
 
14
- In the Cratis organization, Orleans is the Chronicle kernel's actor runtime (`Source/Kernel/`). The kernel conventions below are verified against `Cratis/Chronicle` **v19.50.0**. Shared hosting and jobs come from `Cratis/Orleans` **v1.16.3**; use its setup for other services that co-host a silo.
14
+ In the Cratis organization, Orleans is the Chronicle kernel's actor runtime (`Source/Kernel/`). The kernel conventions below are verified against `Cratis/Chronicle` **v19.50.0**. Shared hosting and jobs come from `Cratis/Orleans` **v1.30.0**; use its setup for other services that co-host a silo.
15
15
 
16
16
  ## General
17
17
 
@@ -50,7 +50,7 @@ framework/service guidance; they do not impose application slice architecture.
50
50
 
51
51
  For service authors, `Cratis.Orleans` supplies hosting, grain serializers, and the
52
52
  shared job system without requiring Chronicle. With `Cratis.Orleans` and
53
- `Cratis.Orleans.Storage.MongoDB` 1.16.3, this host uses durable MongoDB membership
53
+ `Cratis.Orleans.Storage.MongoDB` 1.30.0, this host uses durable MongoDB membership
54
54
  and jobs storage (a local MongoDB server is required):
55
55
 
56
56
  ```csharp
@@ -81,6 +81,14 @@ app.Run();
81
81
  - Register jobs storage separately: `AddCratisOrleansMongoDBJobsStorage`,
82
82
  `AddCratisOrleansSqlJobsStorage` (`Cratis.Orleans.Setup`), or
83
83
  `AddCratisOrleansInMemoryJobsStorage` (`Cratis.Orleans.Storage`).
84
+ - Use the local `IJobs` service to start/stop/delete typed jobs during rolling
85
+ upgrades; generic manager calls can land on a release that does not know the
86
+ type. Provide singleton `IJobsManagerKeys` on every silo for all host-owned
87
+ scope/namespace pairs; process-local discovery is not a recovery catalog.
88
+ - Controller and worker placement are separate opt-ins through `AddJobPlacement`
89
+ and `AddJobStepPlacement`. Enable them only after every silo supports the
90
+ strategies and register them identically. Worker throttling is not admission;
91
+ see **cratis-orleans-jobs** for drain-before-replacement and metadata limits.
84
92
  - Give grain implementations a unique `[GrainType("unique-name")]`. Orleans'
85
93
  default type name derives from the class name alone; a collision across
86
94
  assemblies prevents silo startup.
@@ -258,7 +258,7 @@ public record AuthorRegistered(AuthorName Name);
258
258
 
259
259
  ## Read Models and Queries
260
260
 
261
- **[contract]** `[ReadModel]` records expose eligible public and internal static methods as queries; internal visibility does not hide an endpoint. Dependencies are plain method parameters (no `[FromServices]`). For application code, start with model-bound mappings, use fluent when the definition needs structure, and choose reducer code when transitions read better as code. For step-by-step creation, invoke the **cratis-chronicle-read-model** skill.
261
+ **[contract]** `[ReadModel]` records expose eligible public static methods as queries. Requires Arc 22.121.0 for non-public methods to be excluded even with `[Query]` (ARC0029 warns); older versions also expose eligible internal statics. Keep helpers outside the read model when supporting those versions. Dependencies are plain method parameters (no `[FromServices]`). For application code, start with model-bound mappings, use fluent when the definition needs structure, and choose reducer code when transitions read better as code. For step-by-step creation, invoke the **cratis-chronicle-read-model** skill.
262
262
 
263
263
  Use `[ReadModel(RequireQueryAttribute = true)]` with `[Query]` on intended
264
264
  endpoints when folds exist (ARC0026/ARCCHR0019). `[Query]` alone does not hide
@@ -61,6 +61,7 @@
61
61
  { "command": "read-models occurrences", "reason": "lists a read model's replay history" },
62
62
  { "command": "read-models snapshots", "reason": "reads snapshots of one read model instance" },
63
63
  { "command": "recommendations list", "reason": "lists active recommendations" },
64
+ { "command": "streams closed list", "reason": "lists closed stream scopes and their closure metadata without changing them" },
64
65
  { "command": "subscriptions list", "reason": "lists event store subscriptions" },
65
66
  { "command": "users list", "reason": "lists registered users" }
66
67
  ],
@@ -68,13 +69,16 @@
68
69
  { "command": "observers replay", "effect": "replays an observer from the beginning of the event log, resetting all state it has built" },
69
70
  { "command": "observers replay-partition", "effect": "replays one partition of an observer from the beginning, resetting that partition's state" },
70
71
  { "command": "observers retry-partition", "effect": "retries a failed partition from the sequence number where it failed" },
72
+ { "command": "observers retry-failed-partitions", "effect": "starts bounded-batch recovery of every failed partition of an observer; acceptance does not mean recovery finished" },
71
73
  { "command": "observers clear-quarantine", "effect": "clears observer-level quarantine only, not a partition's quarantine" },
72
74
  { "command": "observers clear-partition-quarantine", "effect": "clears a failed partition's quarantine and resets its retry budget; retries the handler unless --no-retry is given" },
73
75
  { "command": "observers remove", "effect": "deletes an observer's definition, state, handled counts, failed partitions and projection definition across every namespace of the event store; leaves read model data" },
74
76
  { "command": "jobs stop", "effect": "stops a running background job" },
75
77
  { "command": "jobs resume", "effect": "resumes a stopped background job" },
78
+ { "command": "jobs delete", "effect": "requests irreversible deletion of a job and its retained state; acceptance does not confirm completed removal" },
76
79
  { "command": "recommendations perform", "effect": "executes a server recommendation, such as a schema or replay maintenance action" },
77
80
  { "command": "recommendations ignore", "effect": "marks a recommendation as ignored so it no longer appears" },
81
+ { "command": "streams closed reopen", "effect": "reopens one exact manual stream closure and records the authenticated actor and repair reason in a StreamScopeReopened system event; event-driven closures are refused" },
78
82
  { "command": "subscriptions add", "effect": "adds an event store subscription to a target event store" },
79
83
  { "command": "subscriptions remove", "effect": "removes an event store subscription from a target event store" },
80
84
  { "command": "users add", "effect": "adds a user to the server's user store" },
@@ -63,9 +63,9 @@
63
63
  }
64
64
  ],
65
65
  "synced": {
66
- "version": "22.117.0",
67
- "date": "2026-10-17",
68
- "by": "Cratis/AI#773"
66
+ "version": "22.139.0",
67
+ "date": "2026-10-25",
68
+ "by": "Cratis/AI#820"
69
69
  },
70
70
  "covers": {
71
71
  "skills": [
@@ -99,7 +99,7 @@
99
99
  "vertical-slices.md"
100
100
  ]
101
101
  },
102
- "notes": "Reviewed releases 22.100.1-22.117.0 at tags: occurrence command receipts and retry classification, tenant/purpose storage resolution, explicit-offset binding and authorization of malformed bodies, camelCase validation members, conflict event details, hub fallback authorization and snapshot guards, and React query-key isolation/resubscription. Existing read-protection and generated-value guidance retained. Query discovery includes internal statics; public-only discovery in Arc#3368 is unreleased. Screenplay-generator deltas classified for the owning lane, not changed here. No new runtime probe."
102
+ "notes": "Reviewed all 53 releases 22.117.1-22.139.0 at tagged sources. Corrected public-only query discovery, single observations, pre-transport occurrence append refusal, assignable pipeline responses, concept binding/annotations, protected validator construction and key metadata; added live query/calendar, upsert, scenario, authentication and proxy-generation guidance. Existing response validation, React lifetimes, tenant routing and storage resolution guidance retained after reviewing fixes; internal/dependency changes need no extra consumer action. Screenplay/Scene/Stage deltas belong to their owning lanes. No live runtime probe; prior floor established in Cratis/AI#773."
103
103
  },
104
104
  {
105
105
  "id": "ai",
@@ -125,9 +125,9 @@
125
125
  }
126
126
  ],
127
127
  "synced": {
128
- "version": "3.75.0",
129
- "date": "2026-10-17",
130
- "by": "Cratis/AI#752"
128
+ "version": "3.100.0",
129
+ "date": "2026-10-25",
130
+ "by": "Cratis/AI#820"
131
131
  },
132
132
  "covers": {
133
133
  "skills": [
@@ -158,7 +158,7 @@
158
158
  ],
159
159
  "rules": []
160
160
  },
161
- "notes": "Render bundle source-checked at v3.74.0 (release notes v3.63.0-v3.74.0 and Documentation/reference/screenplay.md): Screenplay 4.201.0 and Stage 4.135.0; CLI rendering admits ESM v1-v26 (v17/v19 from 3.63.0, v18 from 3.65.0, v20 from 3.66.0, v21-v26 from 3.74.0); Stage refuses unsupported constructs with typed STAGE-ESM codes (v22 STAGE-ESM-016, v26 date comparisons STAGE-ESM-046) and CLI-RENDER-004 blocks v27 and later. No render probe: the installed CLI is 3.65.0. Prior at v3.62.0: Screenplay 4.159.0 and Stage 4.99.0, ESM v1-v16. PATH render uses persisted root identities read-only, rejects root/name conflicts, checks destination event ids at every scope, and reports baseline warnings. CLI baseline admit/check and open-question validation are available. Other CLI operations retain prior evidence; no new render/browser probe."
161
+ "notes": "Reviewed all 35 releases 3.76.0-3.100.0 at tagged sources: refusal-aware job actions, observer-wide retry, exact manual stream repair, retained-job deletion, bounded namespace metadata comparison, release notes/cached templates, Pi adapters/profile MCP and isolated AI conformance/install-only checks. Updated CLI operations and terminal Workbench guidance. Multi-service context/orchestration unchanged. Reviewed bundled render/run changes through Stage 4.196.0 and Screenplay 4.240.2, ESM v29/v30 admission, reducer migration, executable target validation, model-root resolution and sample packs; Screenplay/Stage/Prologue skill edits reserved for their owning lanes. No live store, render, harness or browser probe. Prior floor established in Cratis/AI#752."
162
162
  },
163
163
  {
164
164
  "id": "screenplay",
@@ -229,9 +229,9 @@
229
229
  "repository": "Cratis/Orleans",
230
230
  "packages": [],
231
231
  "synced": {
232
- "version": "1.16.3",
233
- "date": "2026-10-12",
234
- "by": "Cratis/AI#620"
232
+ "version": "1.30.0",
233
+ "date": "2026-10-25",
234
+ "by": "Cratis/AI#820"
235
235
  },
236
236
  "covers": {
237
237
  "skills": [
@@ -243,7 +243,7 @@
243
243
  ]
244
244
  },
245
245
  "coverage": "tracked",
246
- "notes": "First synced in Cratis/AI#620 at 1.16.3."
246
+ "notes": "Reviewed all 33 releases 1.17.0-1.30.0 at tagged sources: local IJobs and recovery keys, activation-safe callbacks and drain/admission, bounded retries/test runs, durable plans and initialization hooks, finalized-stop/outcome storage and independent placement/metadata throttle; corrected jobs and Orleans hosting guidance. Internal fixes require no extra consumer action; no live silo probe. Prior floor established in Cratis/AI#620."
247
247
  },
248
248
  {
249
249
  "id": "keldakraft",
@@ -10,7 +10,7 @@ paths:
10
10
  > **⚠️ APPLIES ONLY TO PROJECTS USING MICROSOFT ORLEANS**
11
11
  > If your project does not reference `Microsoft.Orleans` or any Orleans packages, **ignore this entire file**. These rules are irrelevant outside of Orleans contexts.
12
12
 
13
- In the Cratis organization, Orleans is the Chronicle kernel's actor runtime (`Source/Kernel/`). The kernel conventions below are verified against `Cratis/Chronicle` **v19.50.0**. Shared hosting and jobs come from `Cratis/Orleans` **v1.16.3**; use its setup for other services that co-host a silo.
13
+ In the Cratis organization, Orleans is the Chronicle kernel's actor runtime (`Source/Kernel/`). The kernel conventions below are verified against `Cratis/Chronicle` **v19.50.0**. Shared hosting and jobs come from `Cratis/Orleans` **v1.30.0**; use its setup for other services that co-host a silo.
14
14
 
15
15
  ## General
16
16
 
@@ -49,7 +49,7 @@ framework/service guidance; they do not impose application slice architecture.
49
49
 
50
50
  For service authors, `Cratis.Orleans` supplies hosting, grain serializers, and the
51
51
  shared job system without requiring Chronicle. With `Cratis.Orleans` and
52
- `Cratis.Orleans.Storage.MongoDB` 1.16.3, this host uses durable MongoDB membership
52
+ `Cratis.Orleans.Storage.MongoDB` 1.30.0, this host uses durable MongoDB membership
53
53
  and jobs storage (a local MongoDB server is required):
54
54
 
55
55
  ```csharp
@@ -80,6 +80,14 @@ app.Run();
80
80
  - Register jobs storage separately: `AddCratisOrleansMongoDBJobsStorage`,
81
81
  `AddCratisOrleansSqlJobsStorage` (`Cratis.Orleans.Setup`), or
82
82
  `AddCratisOrleansInMemoryJobsStorage` (`Cratis.Orleans.Storage`).
83
+ - Use the local `IJobs` service to start/stop/delete typed jobs during rolling
84
+ upgrades; generic manager calls can land on a release that does not know the
85
+ type. Provide singleton `IJobsManagerKeys` on every silo for all host-owned
86
+ scope/namespace pairs; process-local discovery is not a recovery catalog.
87
+ - Controller and worker placement are separate opt-ins through `AddJobPlacement`
88
+ and `AddJobStepPlacement`. Enable them only after every silo supports the
89
+ strategies and register them identically. Worker throttling is not admission;
90
+ see **cratis-orleans-jobs** for drain-before-replacement and metadata limits.
83
91
  - Give grain implementations a unique `[GrainType("unique-name")]`. Orleans'
84
92
  default type name derives from the class name alone; a collision across
85
93
  assemblies prevents silo startup.
@@ -257,7 +257,7 @@ public record AuthorRegistered(AuthorName Name);
257
257
 
258
258
  ## Read Models and Queries
259
259
 
260
- **[contract]** `[ReadModel]` records expose eligible public and internal static methods as queries; internal visibility does not hide an endpoint. Dependencies are plain method parameters (no `[FromServices]`). For application code, start with model-bound mappings, use fluent when the definition needs structure, and choose reducer code when transitions read better as code. For step-by-step creation, invoke the **cratis-chronicle-read-model** skill.
260
+ **[contract]** `[ReadModel]` records expose eligible public static methods as queries. Requires Arc 22.121.0 for non-public methods to be excluded even with `[Query]` (ARC0029 warns); older versions also expose eligible internal statics. Keep helpers outside the read model when supporting those versions. Dependencies are plain method parameters (no `[FromServices]`). For application code, start with model-bound mappings, use fluent when the definition needs structure, and choose reducer code when transitions read better as code. For step-by-step creation, invoke the **cratis-chronicle-read-model** skill.
261
261
 
262
262
  Use `[ReadModel(RequireQueryAttribute = true)]` with `[Query]` on intended
263
263
  endpoints when folds exist (ARC0026/ARCCHR0019). `[Query]` alone does not hide
@@ -1,5 +1,11 @@
1
1
  # Query and decision scenarios
2
2
 
3
+ ## Contents
4
+
5
+ - Query pipeline
6
+ - Guarded decisions
7
+ - Sources
8
+
3
9
  <!-- Copyright (c) Cratis. All rights reserved. -->
4
10
  <!-- Licensed under the MIT license. See LICENSE file in the project root for full license information. -->
5
11
 
@@ -52,6 +58,13 @@ not a caller-supplied tenant filter. Compare whole returned items when the contr
52
58
  value; otherwise assert each named value. A query backed by a database still needs that provider's
53
59
  integration setup: scenario pins do not replace arbitrary stores.
54
60
 
61
+ `QueryScenario` owns and disposes its provider. Dispose the scenario (prefer
62
+ `DisposeAsync` for async-owned resources) as well as individual observations.
63
+ Register dependencies in `Services` before the first execution. Scenario
64
+ execution runs the query through Arc's hosted query pipeline and establishes context
65
+ before `Provide`/`Handle` on the command path. A direct static query call does not
66
+ cover those pipeline hooks.
67
+
55
68
  ## Guarded decisions
56
69
 
57
70
  Create `new CommandScenario<TCommand>()`. It selects Chronicle's in-process decision
@@ -87,6 +100,12 @@ read-model dictionary is the only backing are **legacy-mode** warnings, not a
87
100
  reason to fake protected decision readers. Keep non-key enumeration/provider
88
101
  boundaries explicit in either mode; decision mode is not a MongoDB substitute.
89
102
 
103
+ The default Chronicle-backed `CommandScenario` discovers constraints
104
+ automatically. Assert real constraint refusals instead of disabling them. Do not
105
+ subclass or substitute Chronicle's `EventScenario`; use its public
106
+ seeding/assertion surface or explicit `UseDecisionReads` mode, not a synthetic
107
+ EventScenario facade.
108
+
90
109
  ## Sources
91
110
 
92
111
  - [Query scenarios](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/testing/query-scenario.md)
@@ -179,6 +179,14 @@ filters, and system-execution.
179
179
  `Task<AuthenticationResult> HandleAuthentication(IHttpRequestContext context)`.
180
180
  Implement it and it is discovered — there is **no** `IArcBuilder` extension and
181
181
  **no** options type for authentication.
182
+ Requires Arc 22.139.0 for handlers to opt out of anonymous endpoints through
183
+ `AppliesToAnonymousEndpoints => false`. The default is `true`: existing handlers
184
+ keep running and may establish an identity even when no credential is required.
185
+ Opt out when a handler only rejects credentials and contributes no identity on
186
+ those endpoints. A handler that still runs can inspect the endpoint's anonymous
187
+ metadata and return `AuthenticationResult.Anonymous` instead of rejecting a
188
+ credential the endpoint never required. Specify anonymous and protected requests;
189
+ anonymous access does not bypass other authorization boundaries.
182
190
 
183
191
  Arc ships `MicrosoftIdentityPlatformAuthenticationHandler` in `Arc.Core`, which
184
192
  reads the forwarded principal. See
@@ -16,7 +16,7 @@ Reference the build package from the project that contains the commands,
16
16
  queries and read models, and set an output path:
17
17
 
18
18
  ```xml
19
- <PackageReference Include="Cratis.Arc.ProxyGenerator.Build" Version="22.75.0" />
19
+ <PackageReference Include="Cratis.Arc.ProxyGenerator.Build" Version="22.139.0" />
20
20
 
21
21
  <PropertyGroup>
22
22
  <CratisProxiesOutputPath>$(MSBuildThisFileDirectory)../<Web>/src/api</CratisProxiesOutputPath>
@@ -47,6 +47,8 @@ switches without a declared default are off when absent.
47
47
  | --- | --- | --- |
48
48
  | `CratisProxiesOutputPath` | empty | Where to write. Empty disables generation entirely |
49
49
  | `CratisProxiesInputAssembly` | empty | Overrides the assembly to read |
50
+ | `CratisProxiesIncremental` | `true` | Requires Arc 22.138.0; reuse unchanged owned output; set `false` to regenerate every build |
51
+ | `CratisProxiesFileHeader` | project setting | Requires Arc 22.123.0; otherwise uses `.editorconfig` `file_header_template`, or no header; `none` disables it |
50
52
  | `CratisProxiesSegmentsToSkip` | empty | Leading namespace segments to drop from the output path |
51
53
  | `CratisProxiesLibraryMode` | `false` | Generates for a library rather than an application |
52
54
  | `CratisProxiesSkipOutputDeletion` | **`true`** | Already the default — the generator does not wipe the output folder |
@@ -70,6 +72,13 @@ are optional. Sources:
70
72
  `Tools/ProxyGenerator.Build/build/Cratis.Arc.ProxyGenerator.Build.targets` under
71
73
  `Source/DotNET`.
72
74
 
75
+ Incremental generation fingerprints assemblies, generator/configuration, validation metadata
76
+ and owned output. A changed or missing generated file forces regeneration, as
77
+ does a previous warning so diagnostics remain visible. It does not make a
78
+ hand-edited generated file an input. Configure `file_header_template` explicitly
79
+ if you previously relied on the built-in Cratis MIT header; it is no longer
80
+ inserted without configuration.
81
+
73
82
  Item groups the target also forwards: `AssemblyToPackageMapping`,
74
83
  `ExcludeType`, `ExcludeNamespace`, `NamespaceRoot`, and `TypeToTsType`.
75
84
 
@@ -101,8 +101,9 @@ if (result.IsSuccess)
101
101
  }
102
102
  ```
103
103
 
104
- ⚠️ `Execute<TResult>` throws `InvalidCastException` when the handler returned a
105
- response of a different type. When the handler returns no response, or the
104
+ `Execute<TResult>` accepts an assignable response, such as an array requested
105
+ as `IEnumerable<T>` or a concrete value requested as an interface/base type.
106
+ ⚠️ An incompatible non-null response still throws `InvalidCastException`. When the handler returns no response, or the
106
107
  command failed for any reason, `Response` is `default` — a failed result never
107
108
  carries a response, because the pipeline clears it once the execution scopes
108
109
  have completed.
@@ -61,12 +61,13 @@ key where that service supports it; otherwise an external effect can repeat.
61
61
  Return events from the command, or use transactional appends. Do not complete a
62
62
  stream or commit an aggregate early inside the executor's command.
63
63
 
64
- An immediate append can commit **before** the executor detects it and reports
65
- `OccurrenceCommandAppendedImmediately`. No receipt is recorded for that failed
66
- attempt, so retry can duplicate the escaped append. The executor does not
67
- implicitly provide Chronicle's `RefuseImmediateAppends()` guard. Read
68
- **cratis-chronicle-client-dotnet**'s immediate-append refusal reference when
69
- building a custom boundary that needs prevention before transport.
64
+ Requires Arc 22.118.0 for the keyed executor to refuse immediate appends
65
+ **before transport**, with neither appended events nor an effect receipt. Arc
66
+ 22.117.x detected `OccurrenceCommandAppendedImmediately` only after an append
67
+ could escape; upgrade rather than treating that detection as retry safety.
68
+ Use `.Transactional` or returned events, and assert that a forbidden direct
69
+ append writes nothing. For a custom non-executor boundary, read
70
+ **cratis-chronicle-client-dotnet**'s immediate-append refusal reference.
70
71
 
71
72
  ## Verify
72
73
 
@@ -142,6 +142,11 @@ public class TransferFundsValidator : CommandValidator<TransferFunds>
142
142
  - A validator that itself throws does not become a 500 — Arc catches it and
143
143
  emits a validation error with reason `ValidatorFailed`, returning 400.
144
144
 
145
+ Read [protected validator construction](references/protected-validator-construction.md)
146
+ for plain read-model constructor inputs under `On`, the separate untrimmed-JIT
147
+ provider opt-in for decision tokens outside `On`, and generated composite-key
148
+ metadata. Do not conflate metadata coverage with validator certification.
149
+
145
150
  ## Step 4 — Data the handler needs: `Provide()`
146
151
 
147
152
  `Provide()` runs after authorization and validation and before `Handle()`. Its
@@ -15,6 +15,14 @@ with `DateTime command property '<member>' requires an explicit offset or Z.`
15
15
  `DateTimeOffset` retains the input offset; `DateTime` becomes UTC. The attribute
16
16
  is in `Cratis.Arc.Commands`.
17
17
 
18
+ Requires Arc 22.120.0 for concepts over `DateTime` or `DateTimeOffset` with
19
+ Arc's default JSON options. Concepts over `DateOnly` are refused as a
20
+ configuration error: calendar dates have no offset. The marker affects
21
+ JSON-bound properties, including nested collection elements, not query strings
22
+ or route scalars. Use reflection-based JSON metadata for these properties;
23
+ System.Text.Json source-generated metadata does not support derived
24
+ `JsonConverterAttribute` markers.
25
+
18
26
  ## Malformed bodies still authorize
19
27
 
20
28
  Requires Arc 22.110.0 for authorization-before-binding-failure handling on
@@ -31,6 +39,15 @@ policies can run; built-in role, claim, name and authenticated-user requirements
31
39
  are already resource-free. Do not mark a requirement resource-free if it reads
32
40
  the command.
33
41
 
42
+ ## Annotations on concepts
43
+
44
+ Requires Arc 22.121.2 for data annotations on command properties and model-bound
45
+ query parameters to validate a concept's wrapped value: `[Required]` rejects an
46
+ empty wrapped string, `[Range]` checks the primitive, and `[StringLength]` does
47
+ not throw on the wrapper. This uses generated validation metadata; types without
48
+ that metadata (including external assemblies) keep the previous behavior. Do
49
+ not assume annotations replace a concept's reusable validator.
50
+
34
51
  ## Validation members are field paths
35
52
 
36
53
  Requires Arc 22.115.0 for **all** `ValidationResult.Members` paths to be
@@ -0,0 +1,94 @@
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
+ # Protected validator construction and key metadata
5
+
6
+ ## Plain read models under On
7
+
8
+ Requires Arc 22.106.0 for plain Chronicle read-model dependencies in
9
+ `CommandValidator<T>` constructors under read protection `On`. Those reads share
10
+ the command's fold cache and append guard with `Provide`/`Handle`. Let convention
11
+ discovery register a transient validator. Scoped/singleton/factory/instance
12
+ registrations are refused; a plain transient self-binding is also allowed.
13
+ Ineligible construction fails closed; never fall back to unguarded reads. Use
14
+ `[LookUpBy(nameof(Command.Property))]` on each constructor read for multiple
15
+ keys. Another source/stream can instead be read explicitly in `Provide`.
16
+
17
+ ## Explicit decision tokens outside On
18
+
19
+ Requires Arc 22.126.0 for the opt-in construction of validators taking
20
+ `DecisionRead<T>` or command-aware `IDecisionReads` on `[ProtectedDecision]`
21
+ commands outside `On`. This is **untrimmed JIT only**, not trimmed JIT or
22
+ NativeAOT, and not a switch making every DI container trusted.
23
+
24
+ Install `ProtectedDecisionServiceProviderFactory` from
25
+ `Cratis.Arc.DependencyInjection` as the host's actual Microsoft DI factory
26
+ **after** `AddCratisArc`, passing the desired `ServiceProviderOptions` explicitly.
27
+ Without them both `ValidateScopes` and `ValidateOnBuild` default to false; host
28
+ Development settings are not inherited. `BuildProtectedDecisionServiceProvider`
29
+ is for independently owned service collections, not a second root inside the
30
+ running host. Execute in a native child scope (or the pipeline's scope-owning
31
+ overload), never directly on that root. Default `CommandScenario` does not opt in.
32
+
33
+ Register Chronicle through Arc's `WithChronicle`. Do not replace readers or
34
+ re-register tokens. Arc records origins at root construction; copied descriptors,
35
+ a wrapper provider, custom container or later collection mutation cannot acquire
36
+ that evidence.
37
+
38
+ Every validator/collaborator needs exactly one public constructor and a fully
39
+ inspectable transient graph. Arc constructs fresh instances for the invocation.
40
+ It refuses scoped/singleton validators or collaborators, application validator
41
+ bindings, factory/instance dependencies, cycles, keyed/open generic/deferred or
42
+ optional dependencies, provider/scope accessors and collections (including token
43
+ collections). Plain models and raw `IReadModels`, `IEventStore` or `IEventLog`
44
+ are refused on this path: use tokens or choose `On`.
45
+
46
+ Requires Arc 22.126.0 for
47
+ `services.AddProtectedDecisionValidator<TValidator>(configure)`. Use it instead
48
+ of an application DI factory when adding rules. The callback runs once on the
49
+ actual fresh instance before validation; thrown callbacks fail the command and
50
+ callback denials are not bypassed. It can replace one Arc convention binding,
51
+ not an explicit application registration. Extra explicit bindings refuse the
52
+ protected path. Ordinary commands still use normal constructor resolution.
53
+
54
+ Arc checks rule/component identities before invocation and refuses changed or
55
+ cross-scope/reused instances. Construction and cleanup failures block before
56
+ commit even with allowed-severity overrides. Arc disposes owned validators and
57
+ collaborators in reverse order; DI owns read leaves. This is not a sandbox for
58
+ trusted constructor/callback code, mutable captures, static reads or nested-child
59
+ rule changes. `Validate` stays advisory and creates a separate invocation; only
60
+ Execute enrolls its decision for append-time checking. Under `Off`, choose the
61
+ blocking severity needed for business denials.
62
+
63
+ ## Generated key metadata is not validator certification
64
+
65
+ `CommandReadModelKeys` registers command property/model-key metadata.
66
+ Requires Arc 22.125.0 for generated composite-key descriptors and
67
+ `ProtectedReadModelKeyAccessors`, which registers exact-type composite members.
68
+ These optimize matching; they do not authorize a read or certify a validator's
69
+ dependency graph.
70
+
71
+ Known concrete composite keys receive generated complete descriptors. For an
72
+ external/runtime key, register `ProtectedReadModelKeyAccessors.Register<T>` with
73
+ every public readable non-indexed instance property, including inherited ones,
74
+ before commands run. Generated metadata uses `DeclareKeyTypes` for startup
75
+ coverage checks; that is not a replacement for registering member accessors.
76
+ Duplicate registration fails. Do not omit a member or treat a
77
+ base descriptor as covering a derived runtime type. Missing declared metadata
78
+ fails with `ProtectedReadModelKeyAccessorMissing`.
79
+
80
+ Requires Arc 22.134.0 for `ReadProtection:RequireGeneratedMetadata` (false by
81
+ default). Enabling it rejects missing command/model metadata instead of using
82
+ reflection. `ProtectedReadModelKeyOptions.Strict` is also false by default;
83
+ strict composite matching or a restricted runtime requires exact accessors.
84
+ Untrimmed JIT can use the compatibility reflection path when strict mode is off.
85
+ ARC0030 is the composite coverage warning. Do not suppress it or promise
86
+ NativeAOT support for the unrelated dynamic validator opt-in.
87
+
88
+ ## Specify the boundary
89
+
90
+ Cover a fresh allowed/denied validator, callback-added denial and exception,
91
+ refused DI graphs and cleanup failure, and repeated validation/execution without
92
+ reusing an instance. For keys, cover inherited/flattened members, exact derived
93
+ types, missing metadata under strict settings and generated versus compatibility
94
+ matching. An advisory Validate success is not proof that Execute commits.
@@ -187,6 +187,24 @@ Mark the corresponding property `[Json]`.
187
187
  `migrationBuilder.GetDatabaseType()` is available when a migration genuinely has
188
188
  to branch per provider.
189
189
 
190
+ ## Tracked and native upserts
191
+
192
+ Requires Arc 22.135.0 for `DbSet<TEntity>.UpsertAsync(item, cancellationToken)`.
193
+ On supported SQL Server, PostgreSQL and SQLite mappings it executes an immediate,
194
+ parameterized native upsert matching the complete primary key, including composite
195
+ keys and value converters. Native key matching and writing are atomic. It uses
196
+ the context's connection/current transaction, bypasses tracking and does not
197
+ refresh generated values or already tracked instances. It adds no retry policy.
198
+
199
+ Use application-assigned keys and single-table scalar mappings. Unsupported
200
+ providers or mappings (including unset generated keys, SQL Server identity
201
+ columns, navigations, owned entities, shadow properties, inheritance and multiple
202
+ tables) fall back to tracked persistence and require `SaveChangesAsync`. The
203
+ existing `Upsert(item)` always uses tracked read-then-write persistence and also
204
+ requires saving; that path is not an atomic database upsert. Choose it explicitly
205
+ when deferred persistence is required. Do not rely on fallback for concurrency
206
+ protection or assume the native path flushes pending tracked changes.
207
+
190
208
  ## Observing an EF Core read model
191
209
 
192
210
  `DbSet<T>` gains `Observe(filter?, configure?)`, `ObserveSingle(filter?, configure?)`
@@ -56,10 +56,12 @@ Sources: `Arc.Core/Queries/QueryStringQueryRequestReader.cs`,
56
56
 
57
57
  ## Which return shapes page
58
58
 
59
- A query is an eligible public or internal static method on the `[ReadModel]`
60
- type, discovered by its return type. Internal visibility does not hide an
61
- endpoint; keep helpers outside that type. Read **cratis-chronicle-read-model**'s
62
- query reference for explicit `[Query]` discovery and older-version limits.
59
+ A query is an eligible public static method on the `[ReadModel]` type,
60
+ discovered by its return type.
61
+ Requires Arc 22.121.0 for non-public statics to be
62
+ excluded even with `[Query]` (ARC0029 warns); older Arc exposes internal methods
63
+ as endpoints too. Read **cratis-chronicle-read-model**'s query reference for
64
+ explicit `[Query]` discovery and older-version limits.
63
65
 
64
66
  | Return | Paged? | How |
65
67
  | --- | --- | --- |
@@ -174,6 +176,11 @@ For composed MongoDB/EF observations, pass `ignoreQueryContext: true` to
174
176
  auxiliary sources so only the primary consumes client paging/sorting and writes
175
177
  totals. Single-instance observations emit `default` when absent, deleted or
176
178
  outside the filter; guard `hasData`/`isReady` in React.
179
+ Requires Arc 22.117.1 for
180
+ `ObserveSingle`/`ObserveById` never to page or count; their retained
181
+ `ignoreQueryContext` argument has no effect. Use collection results for paging.
182
+ Read [live query processing](references/live-query-processing.md) for shared
183
+ MongoDB watches, emitted-value interception and calendar-bound answers.
177
184
  Source: [MongoDB observations](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/mongodb/observing-collections.md).
178
185
 
179
186
  ## Consume it from the frontend
@@ -0,0 +1,71 @@
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
+ # Live query processing
5
+
6
+ ## Shared MongoDB watches
7
+
8
+ Requires Arc 22.136.0. MongoDB `Observe()` uses a shared database-level watch keyed by deployment and
9
+ database rather than opening a collection watch for every observer. Each
10
+ observation keeps its filter, paging and tenant context.
11
+ Requires Arc 22.137.0 for observations of collections created by the caller's
12
+ own `MongoClient` to receive changes. The watcher derives the deployment key
13
+ from the database client instead of requiring prior registration. Joined observations coalesce refresh notifications into one pending
14
+ refresh and re-read every joined source. Snapshot read failures receive a
15
+ bounded retry burst; exhaustion waits for a later notification rather than
16
+ spinning or buffering unbounded work.
17
+
18
+ Watch reconnects resume from the token when possible and request a new snapshot
19
+ when the token cannot resume. The watch reopens before asking for snapshots so
20
+ writes during the read are still observed. Disposing one observer does not stop
21
+ the shared watch; disposing its subject or losing the last subscriber stops that
22
+ observation's refresh work and prevents a late emission. Shared watches do not
23
+ replace each subscription's cancellation and disposal.
24
+
25
+ ## Intercept the emitted model, not its wrapper
26
+
27
+ Implement `Cratis.Arc.Queries.IInterceptReadModel<T>` with
28
+ `Task<T> Intercept(T readModel)`. Return a copy when transforming data that another
29
+ subscriber could share. A non-abstract open generic `IInterceptReadModel<T>`
30
+ implementation must target exactly its own type parameter; closed
31
+ implementations also work.
32
+
33
+ Interception runs for supported direct/hub streaming emissions and observable
34
+ HTTP snapshot/wait results in the requesting subscriber's scope and identity. Interceptors run before emission guards; a thrown interceptor
35
+ fails the result rather than exposing the untransformed value. Ordinary model
36
+ queries and Arc-wrapped MVC GETs also intercept results; `[AspNetResult]` opts
37
+ out of that result processing. Interceptors are transformations, **not access
38
+ control**. Filter rows/fields and authorize independently on every exposed path,
39
+ and specify snapshot, SSE/WebSocket and hub paths separately.
40
+
41
+ ## Date-sensitive answers without data changes
42
+
43
+ Requires Arc 22.138.0 for `Cratis.Arc.Calendar`. Inject `IApplicationCalendar` and
44
+ pass an instant to `Read`; it does not read the clock. Zone precedence is an
45
+ operation-declared zone, then the first non-null `ICanProvideTenantCalendar`
46
+ answer, then `Cratis:Arc:Calendar:TimeZone` (default `UTC`). Headers and query
47
+ arguments cannot choose it. Configured and tenant-provider ids must be usable
48
+ IANA ids: unknown, Windows-only and nonzero fixed-offset ids fail with
49
+ `TimeZoneIsNotAnIanaZone`,
50
+ never a silent UTC fallback. Containers need time-zone data (`tzdata`).
51
+
52
+ A live "due today" answer needs candidates observed **without a clock-dependent
53
+ store filter**, or tomorrow's members will never enter the source. Use
54
+ `IClockBoundaries.Begin(tenant, operationZone, token)` to obtain a
55
+ `ClockBoundaryContext`, then `candidates.ReevaluateAtBoundaries(context,
56
+ (candidates, reading) => new ClockEvaluation<TAnswer>(answer, nextBoundary))`.
57
+ `LocalDateChanges.NextLocalDateChange(reading.Zone, reading.Instant)` handles
58
+ midnight, skipped dates and backwards date transitions. `reading.Today.AddDays`
59
+ steps calendar days; subtracting elapsed hours from the instant is different
60
+ across daylight-saving changes.
61
+
62
+ Every next boundary must be strictly after the evaluated instant or null;
63
+ otherwise the stream fails with `ClockBoundaryIsNotInTheFuture`. The first result
64
+ always emits; equal subsequent answers do not (collections compare in order).
65
+ Evaluation is serialized in the context's tenant. The zone is fixed at Begin;
66
+ application changes require restart and tenant-zone changes affect new
67
+ subscriptions. The operator connects on first subscription and disposes timer
68
+ and candidates on last unsubscribe, cancellation, error or completion. It does
69
+ not accept `OnNext`. Use the registered `TimeProvider` and a `FakeTimeProvider`
70
+ with `Advance` to specify boundary changes without sleeps. This is not a
71
+ continuously ticking `now` or an advanceable clock in query scenarios.
@@ -60,6 +60,10 @@ Two things worth knowing before running it:
60
60
  connection guidance in a repository-owned project context file; never patch
61
61
  packaged or generated bytes.
62
62
 
63
+ Read [project tooling](references/project-tooling.md) for embedded release notes,
64
+ cached templates, managed MCP registration and install-only versus harness AI
65
+ conformance checks. Updating instructions does not run those behavior checks.
66
+
63
67
  ## Reach the right server
64
68
 
65
69
  The connection string resolves in this exact order:
@@ -180,6 +184,10 @@ needs Chronicle 19.29.0 or later, resets the retry budget and retries unless `--
180
184
  is given; handler side effects may repeat. `retry-partition` exits non-zero
181
185
  when quarantined or not failed. `jobs resume` resumes Stopped jobs, not Failed.
182
186
 
187
+ Read [recovery outcomes and namespace comparison](references/recovery-and-comparison.md)
188
+ for refusal-aware job actions, retained-job deletion, observer-wide failed-partition
189
+ recovery, exact manual stream reopening and bounded metadata comparison.
190
+
183
191
  ## Before you change anything
184
192
 
185
193
  Default to read-only inspection. A request to diagnose a live store does not
@@ -188,12 +196,13 @@ authorize replay, retry, removal, quarantine clearing, or another mutation.
188
196
  These commands mutate the running store:
189
197
 
190
198
  - `chronicle observers replay`, `replay-partition`, `retry-partition`,
191
- `clear-quarantine`
199
+ `retry-failed-partitions`, `clear-quarantine`
192
200
  - `chronicle observers remove` — destructive across every namespace of the event
193
201
  store: definition, state, handled counts, failures and projection definition;
194
202
  read model data stays. Refused while running or subscribed, no force flag.
195
203
  - `chronicle observers clear-partition-quarantine` — may retry the handler
196
- - `chronicle jobs stop`, `chronicle jobs resume`
204
+ - `chronicle jobs stop`, `chronicle jobs resume`, `chronicle jobs delete`
205
+ - `chronicle streams closed reopen` — exact manual scope repair with a reason
197
206
  - `chronicle recommendations perform`, `chronicle recommendations ignore`
198
207
  - `chronicle subscriptions add`, `chronicle subscriptions remove`
199
208
  - `chronicle users add`, `chronicle users remove`
@@ -20,6 +20,7 @@ non-read-only commands.
20
20
  | `workbench` | destructive | false (its actions have their own dialogs) |
21
21
  | `applications add` | mutating | false |
22
22
  | `applications remove` | destructive | true |
23
+ | `jobs delete` | destructive | true |
23
24
  | `jobs resume` | mutating | true |
24
25
  | `jobs stop` | mutating | true |
25
26
  | `observers clear-partition-quarantine` | mutating | true |
@@ -27,14 +28,20 @@ non-read-only commands.
27
28
  | `observers remove` | destructive | true |
28
29
  | `observers replay` | destructive | true |
29
30
  | `observers replay-partition` | destructive | true |
31
+ | `observers retry-failed-partitions` | mutating | true |
30
32
  | `observers retry-partition` | mutating | true |
31
33
  | `recommendations ignore` | mutating | true |
32
34
  | `recommendations perform` | destructive | true |
35
+ | `streams closed reopen` | mutating | true |
33
36
  | `subscriptions add` | mutating | false |
34
37
  | `subscriptions remove` | destructive | true |
35
38
  | `users add` | mutating | false |
36
39
  | `users remove` | destructive | true |
37
40
 
41
+ See [recovery outcomes](recovery-and-comparison.md) for CLI/server floors and
42
+ acceptance versus completed work. `jobs delete` exits nonzero on declined/unavailable
43
+ confirmation; success means deletion requested, not completed removal.
44
+
38
45
  The remaining Chronicle commands are cataloged read-only, including
39
46
  `report-error`; the guard deliberately blocks that command because it opens an
40
47
  external browser issue form. `chronicle version` is not a command in this
@@ -0,0 +1,91 @@
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
+ # CLI updates and project tooling
5
+
6
+ This complements live-store operations; it does not grant permission to install
7
+ servers, change a repository or run quota-consuming agent sessions. Inspect the
8
+ installed version with `cratis --version` and its catalog before using a flag.
9
+
10
+ ## Inspect the upgrade, not a remembered bundle
11
+
12
+ Requires CLI 3.87.0 for offline embedded release notes:
13
+
14
+ ```bash
15
+ cratis release-notes --since <version> -o json
16
+ cratis release-notes --version <version>
17
+ ```
18
+
19
+ `cratis update` displays the skipped releases after its update results. Requires
20
+ 3.89.0 for `cratis new` to use the template set cached at `~/.cratis/templates`:
21
+ first use downloads from NuGet; offline use can reuse a complete cached set.
22
+ `cratis new cratis --language csharp --version <version>` selects a version for
23
+ one run rather than replacing the installed choice. `cratis update` refreshes
24
+ compatible templates without downgrading or failing the CLI update when refresh
25
+ fails; `--no-templates` skips it. CLI and template versions are separate. Do not
26
+ silently enable scaffold scripts; preserve the template's `--allow-scripts`
27
+ choice. Multi-service orchestration still needs explicit per-store settings.
28
+
29
+ For `cratis render` / `run`, read **cratis-screenplay-render-and-gap-fill** and
30
+ **cratis-stage-rendering-and-sandbox**. Their bundled renderer determines support;
31
+ a CLI upgrade can change admission, generated output and the default container
32
+ without changing the model. Never infer support from the ESM version alone or
33
+ edit generated output to make an older CLI accept it.
34
+
35
+ ## Managed AI installation and MCP
36
+
37
+ Requires CLI 3.77.1 for `.pi/agents` to link to Pi-native corpus adapters when
38
+ available, falling back to canonical agents only for older corpora. Do not
39
+ hand-edit generated adapters to restore missing tools; update the CLI/corpus.
40
+
41
+ Requires 3.90.0 for profile-selected servers in the corpus's `mcp-servers.json` to
42
+ be registered by `cratis ai install` / `update` in Claude Code, Copilot, Cursor,
43
+ Codex and OpenCode. Installation registers configuration; it does not start the
44
+ servers. Deselecting/uninstalling removes only unchanged owned entries. Foreign
45
+ or modified entries remain conflicts, even with force. Resolve a conflict by
46
+ intentionally renaming/removing your entry or disabling that server with
47
+ `mcpServers.<id>.enabled = false` in the project configuration; do not overwrite
48
+ it speculatively. Pi supports only Screenplay through its native extension and
49
+ reports other servers unsupported. An invalid selected descriptor is reported
50
+ and skipped; successful guidance installation does not prove MCP availability.
51
+ Installing a server does not authorize its tools or commands.
52
+
53
+ ## Deterministic AI checks versus harness behavior
54
+
55
+ Requires CLI 3.98.0 for opt-in `cratis ai test`. Each fixture × harness uses a
56
+ fresh temporary workspace with the fixture's selected profiles/languages; the
57
+ invoking repository is unchanged. It is **not** a security sandbox: fixture
58
+ commands and harnesses still have host tools/authentication. Trust the source,
59
+ request the intended session/budget, and keep transcripts off unless needed.
60
+ The default fixture root is `Evaluations/conformance`; `--fixtures` overrides
61
+ it, `--fixture` selects names, and `--harnesses claude,codex` narrows adapters.
62
+ `--output json` is this command's structured format, not proof of a passing run.
63
+
64
+ Requires 3.100.0 for install-only CI without a harness, credentials or model quota:
65
+
66
+ ```bash
67
+ cratis ai test --source <corpus> --install-only --output json
68
+ ```
69
+
70
+ This runs install-phase checks for declared harness ids (or the configured ids
71
+ when a fixture declares none), narrowed by `--harnesses`. Result-phase checks
72
+ stay `not_run`; no install checks means skipped, not behavioral conformance.
73
+ Normal mode rejects missing/modified installations unless corpus-source mode is
74
+ selected. `--source`/`CRATIS_AI_SOURCE` selects a trusted local corpus.
75
+
76
+ Fixtures require a non-optional result check even in install-only mode. File
77
+ checks choose one path/glob; an empty glob match fails. Install checks cannot be
78
+ commands. A result command uses `run: ["executable", "argument"]` with no implicit
79
+ shell and cannot also declare file path/glob/pattern fields. Invalid check shapes
80
+ are errors. Paths and links cannot escape the workspace.
81
+
82
+ A timeout kills the process tree; output is capped. Requested `--timeout` and
83
+ `--max-budget-usd` can lower, not raise, fixture limits. Claude enforces a spending
84
+ limit; Codex reports that bound `not_enforced`. Missing harnesses/credentials are
85
+ skipped with a reason, never passed. Exit 0 can mean all skipped or no selected
86
+ runs: inspect counts and `no fixtures ran successfully`. Exit 1 means fixture,
87
+ harness or check failures/errors; exit 2 means usage/install errors. Installed
88
+ file hashes, observed tool reads and check results are separate evidence levels;
89
+ unknown reads remain unknown. `--keep-transcripts` retains best-effort-redacted
90
+ temporary output; inspect it for secrets before sharing. Passing install checks
91
+ is not proof that an agent read or obeyed the guidance.
@@ -0,0 +1,68 @@
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
+ # Recovery outcomes and namespace comparison
5
+
6
+ Check the installed `cratis llm-context` and server version before using these
7
+ commands. Confirm server, event store, namespace and exact targets. A prompt or
8
+ `--yes` is not authority to mutate a live store.
9
+
10
+ ## Jobs: acceptance is not completed removal
11
+
12
+ Requires CLI 3.79.0 for `chronicle jobs stop` / `resume` and the terminal
13
+ Workbench to report kernel refusals as failures, with reasons in text/JSON and a
14
+ nonzero exit. Do not parse an old CLI's success message as proof of progress.
15
+ Inspect the job again; resume can be refused for missing, concluded, unpersisted
16
+ or otherwise unresumable work. Do not repeat it without understanding the reason.
17
+
18
+ Requires CLI 3.99.0 for `cratis chronicle jobs delete <JOB_ID>`. It is destructive
19
+ and irreversible: retained job state is removed. Before confirmation it names
20
+ the store, namespace and job id. An invalid GUID, declined/unavailable prompt,
21
+ server refusal, authorization failure or server error is nonzero. `--yes` skips
22
+ only the local prompt; the server still authorizes and decides. Exit zero and
23
+ "deletion requested" are **not** proof of completed removal. Capture the relevant
24
+ pre-state first and inspect again after an authorized deletion. This is not a
25
+ terminal Workbench key or permission to delete every retained job.
26
+
27
+ ## Retry all failed partitions
28
+
29
+ Requires CLI 3.79.0 and Chronicle 19.80.0 for
30
+ `cratis chronicle observers retry-failed-partitions <OBSERVER_ID>`. It starts
31
+ server-owned bounded-batch recovery and reports how many partitions were failed;
32
+ it does not wait for all handlers to succeed. Inspect failures first, fix their
33
+ cause, authorize the observer-wide target, then re-read state after the request.
34
+ Do not mistake this for a client loop of one-partition requests or a guarantee
35
+ that quarantines were cleared.
36
+
37
+ ## Inspect or repair a closed scope
38
+
39
+ Requires CLI 3.80.0 and Chronicle 19.39.0 for
40
+ `cratis chronicle streams closed list`. It is read-only and lists exact scope
41
+ dimensions, origin, owner constraint, sequence number and closing time. Filter by
42
+ event source id/type and event stream type/id; do not select a repair from a
43
+ partial dimension match.
44
+
45
+ `cratis chronicle streams closed reopen --reason <text>` is a confirmed,
46
+ mutating repair of **one exact** mistaken manual close. Supply the scope flags
47
+ shown by the installed help/catalog and the reason. The kernel records actor
48
+ and reason in `StreamScopeReopened` and refuses a closure owned by a closing
49
+ event. `--yes` skips confirmation, not that ownership check. Never turn this into
50
+ a way around a modeled closure or reopen broad scopes speculatively.
51
+
52
+ ## Compare a model with live metadata
53
+
54
+ Requires CLI 3.85.0 for
55
+ `cratis screenplay conform-namespace <model> --event-store <store> --namespace <ns>`.
56
+ This is a read-only comparison, not model authoring, rendering or state repair.
57
+ It compares registered event types, producing commands/reactors and observers
58
+ with the model, scanning metadata up to the tail captured at start. The default
59
+ `--max-events` is `100000`; `--from`/`--to` bound a range. A partial scan stays
60
+ partial; without `--fail-on-drift` a drift report need not fail the command.
61
+
62
+ Requires 3.89.1 for authorization failures to exit 4 (`authentication_error`),
63
+ Ctrl+C between pages to exit 8 (`inconclusive`), and event attribution only when
64
+ the causation is a command. Chronicle's metadata API still transmits event source
65
+ ids and subjects; the CLI drops them without persisting them. Do not claim they
66
+ never cross the connection. It does not request payloads or command values.
67
+ Redact the report before sharing it, and do not treat an incomplete or cancelled
68
+ comparison as evidence of conformance.
@@ -32,9 +32,11 @@ Chronicle extensions — Chronicle declares no `Observe` on `IMongoCollection<T>
32
32
 
33
33
  ## Discovery rules
34
34
 
35
- Arc discovers eligible **public and internal static** methods on `[ReadModel]`
36
- types. Internal visibility is not an endpoint privacy boundary. Keep helpers
37
- outside the read model, regardless of their visibility. Use
35
+ Arc discovers eligible **public static** methods on `[ReadModel]` types.
36
+ Requires Arc 22.121.0: non-public methods are excluded even with `[Query]`, and
37
+ ARC0029 warns on a non-public query-shaped method. A public query on an internal
38
+ read-model type still works. On older Arc, internal statics are exposed too;
39
+ keep helpers outside the read model when supporting those versions. Use
38
40
  `[ReadModel(RequireQueryAttribute = true)]` and `[Query]` on intended endpoints
39
41
  when the type also has folds (`Create`/`Reduce`); `[Query]` alone does not hide
40
42
  siblings. Requires Arc 22.27.1 for exclusion of private, protected, special-name
@@ -138,7 +138,11 @@ Several views bind a key to an operation that changes the running server:
138
138
  | Jobs | `S` / `U` | stop / resume the job |
139
139
  | Recommendations | `A` / `I` | perform / ignore the recommendation |
140
140
 
141
- Each has a bulk form that applies to every checked row.
141
+ Each has a bulk form that applies to every checked row. Requires CLI 3.79.0 for
142
+ job stop/resume to display kernel refusals as failures rather than success.
143
+ Inspect the reason and re-read the job; acknowledgement is not completed work.
144
+ Retained-job deletion is a separate CLI command (3.99.0), not a Workbench key;
145
+ read **cratis-chronicle-cli-operations** before requesting it.
142
146
 
143
147
  Every one of these opens a centered confirmation modal that states the action
144
148
  cannot be undone. **Only an explicit `Y` (or the Confirm button) confirms;
@@ -11,32 +11,22 @@ license: MIT
11
11
 
12
12
  For services and kernel contributors using Orleans. This is library-consumer guidance, not the application vertical-slice architecture. For live Chronicle operations use **cratis-chronicle-cli-operations** instead. Co-hosting conventions are in [orleans.md](../../rules/orleans.md).
13
13
 
14
- ## Verified product sources
15
-
16
- All Orleans claims below come from `Cratis/Orleans` source.
17
-
18
- | Packages | Sources |
19
- | --- | --- |
20
- | `Cratis.Orleans`, `Cratis.Orleans.Storage` | `Source/Orleans/Jobs/{Job.cs,Job.Steps.cs,Job.Stages.cs,JobStep.cs,IJobsManager.cs,JobsOptions.cs}`; `Source/Orleans.Storage/Jobs/*` |
21
- | `Cratis.Orleans.Storage.MongoDB`, `Cratis.Orleans.Storage.Sql` | `Source/Orleans.Storage.MongoDB/{MongoDBJobsStorageOptions.cs,DatabaseNames.cs}`; `Source/Orleans.Storage.Sql/{SqlJobsStorage.cs,SqlJobsStorageOptions.cs,Jobs/Migrations/v1_0_0.cs}` |
22
- | `Cratis.Orleans.Chronicle` | `Source/Orleans.Chronicle/StateMachines/EventSourcedStateMachine.cs`; [state-machine reference](references/state-machines.md) |
23
-
24
- The API snippets cite their exact files and lines below. Product docs `Documentation/concepts/{jobs,storage-scopes,state-machines,event-sourced-state-machines}.md` supply the task vocabulary; source resolves contradictions.
25
-
26
14
  ## 1. Choose a job, not a detached task
27
15
 
28
- A job plans recoverable work; each step is a grain and does the work. Use it for work that must outlive a request. It is **at-least-once**, not exactly-once: a step interrupted before a durable checkpoint can perform the same work again. Protect external effects with receiver-owned idempotency/deduplication; a checkpoint does not atomically commit an external write (`JobStep.cs`, `JobsOptions.cs`).
16
+ A job plans recoverable work; each step is a grain and does the work. Use it for work that must outlive a request. It is **at-least-once**, not exactly-once: a step interrupted before a durable checkpoint can perform the same work again. Protect external effects with receiver-owned idempotency/deduplication; a checkpoint does not atomically commit an external write.
17
+
18
+ - Job: derive `Job<TRequest, TJobState>`; the request is a class implementing `IJobRequest`, the state derives `JobState`. Implement `PrepareSteps(TRequest)` returning `Task<IImmutableList<JobStepDetails>>`.
19
+ - Step: derive `JobStep<TRequest, TResult, TState>`; state derives `JobStepState`. Its constructor receives `[PersistentState(nameof(JobStepState), WellKnownGrainStorageProviders.JobSteps)] IPersistentState<TState>`, `IJobStepThrottle`, and `ILogger<JobStep<TRequest, TResult, TState>>`.
20
+ - Implement `PrepareStep(TRequest)` → `Task<Result<PrepareJobStepError>>`; `InitializeState(TRequest)` → `ValueTask`, called after preparation succeeds; and `PerformStep(TState, CancellationToken)` → `Task<Catch<JobStepResult>>`. `TResult` is the step's result type; the work method returns the framework's `JobStepResult` envelope, not `TResult` directly.
21
+ - Give the concrete job and step unique `[GrainType("product-specific-name")]` names. Use serializable request/state/result types. Do not reuse a class-derived grain name across assemblies.
29
22
 
30
- - Job: derive `Job<TRequest, TJobState>`; the request is a class implementing `IJobRequest`, the state derives `JobState` (`Job.cs`). Implement `PrepareSteps(TRequest)` returning `Task<IImmutableList<JobStepDetails>>` (`Job.Steps.cs`).
31
- - Step: derive `JobStep<TRequest, TResult, TState>`; state derives `JobStepState`. Its constructor receives `[PersistentState(nameof(JobStepState), WellKnownGrainStorageProviders.JobSteps)] IPersistentState<TState>`, `IJobStepThrottle`, and `ILogger<JobStep<TRequest, TResult, TState>>` (`JobStep.cs`).
32
- - Implement `PrepareStep(TRequest)` → `Task<Result<PrepareJobStepError>>`; `InitializeState(TRequest)` → `ValueTask`, called after preparation succeeds; and `PerformStep(TState, CancellationToken)` → `Task<Catch<JobStepResult>>` (`JobStep.cs`). `TResult` is the step's result type; the work method returns the framework's `JobStepResult` envelope, not `TResult` directly.
33
- - Give the concrete job and step unique `[GrainType("product-specific-name")]` names. Use serializable request/state/result types. Do not reuse a class-derived grain name across assemblies (`Jobs/JobsManager.cs`, `Documentation/how-to/host-the-silo.md`, and `orleans.md`).
23
+ `PerformStep` runs **outside the activation context** with a deep-copied state snapshot. Mutating that snapshot does not persist it; do not access/write grain `State` directly from background work. A specification host such as Orleans.TestKit must register `DeepCopier<TState>`.
34
24
 
35
- `PerformStep` runs **outside the activation context** with a state snapshot obtained through the grain reference. Mutating that snapshot does not persist it; do not access/write grain `State` directly from background work (`JobStep.cs`). Keep progress/state coordination on grain calls. Requires Cratis.Orleans 1.16.2 for framework-owned stop reporting; there, do not report cancellation again from a `PerformWork` override (`Documentation/concepts/jobs.md`).
25
+ Requires Cratis.Orleans 1.19.0 for `TryInvokeOnActivation` to coordinate state/checkpoints on the current activation, including during deactivation. Call it only from the run's background work. Keep callbacks local: capture copied inputs, call remote systems outside the callback, then commit in a second callback. Never call another grain, nest and await another callback, or start unawaited work inside it. Inspect `ActivationCallbackRejected` (`NoRun`, `StaleRun`, `Closed`, `Cancelled`); exceptions propagate. The optional `queueCancellationToken` cancels waiting callbacks only; started callbacks finish. Stop/deactivation waits for started callbacks and work to settle; `JobsOptions.StepStopTimeout` defaults to 10 seconds and `JobStepError.StopTimedOut` is not proof of quiescence. Requires 1.16.2 for framework-owned stop reporting: do not report cancellation again from a `PerformWork` override.
36
26
 
37
27
  ## 2. Plan dependencies as stages
38
28
 
39
- Default steps all use `JobStepStage.First`. Prefer independent fan-out. Use stages only when correctness requires earlier work to finish (`Job.Stages.cs`).
29
+ Default steps all use `JobStepStage.First`. Prefer independent fan-out. Use stages only when correctness requires earlier work to finish.
40
30
 
41
31
  Planning excerpt inside an existing `Job<RecoveryRequest, RecoveryState>`. It assumes `RecoveryRequest : IJobRequest`, `RecoveryState : JobState`, and the two existing grain interfaces `IRepairIndex` and `IRebuildCatalog`, each deriving a typed `IJobStep<,,>`:
42
32
 
@@ -52,64 +42,62 @@ protected override Task<IImmutableList<JobStepDetails>> PrepareSteps(RecoveryReq
52
42
  ]);
53
43
  ```
54
44
 
55
- Import `System.Collections.Immutable` and `Cratis.Orleans.Jobs` in that job file. `CreateStep<TStep>` takes the **grain interface**, not the implementation (`Job.Steps.cs`). Its stage overload accepts `JobStepStage`, with implicit integer conversion (`Source/Orleans.Storage/Jobs/Stages/JobStepStage.cs`).
45
+ Import `System.Collections.Immutable` and `Cratis.Orleans.Jobs` in that job file. `CreateStep<TStep>` takes the **grain interface**, not the implementation. Its stage overload accepts `JobStepStage`, with implicit integer conversion.
56
46
 
57
- Every step in a stage reaches an outcome before later stages start; steps within a stage run in parallel. All steps are prepared up front, so later-stage preparation cannot read the effects of earlier-stage execution. Read such dependencies in `PerformStep` (`Job.Stages.cs`).
47
+ Every step in a stage reaches an outcome before later stages start; steps within a stage run in parallel. All steps are prepared up front, so later-stage preparation cannot read the effects of earlier-stage execution. Read such dependencies in `PerformStep`.
58
48
 
59
- A failed stage stops the pipeline by default: later steps become `Unreachable`, and the job completes with failures. A best-effort stage may override `GetFailureBehaviorFor(JobStepStage)` to return `JobStageFailureBehavior.ContinueWithNextStage` (`Job.Stages.cs`, namespace `Cratis.Orleans.Jobs.Stages`). Do not silently continue when a later stage relies on the failed output.
49
+ A failed stage stops the pipeline by default: later steps become `Unreachable`, and the job completes with failures. A best-effort stage may override `GetFailureBehaviorFor(JobStepStage)` to return `JobStageFailureBehavior.ContinueWithNextStage` (namespace `Cratis.Orleans.Jobs.Stages`). Do not silently continue when a later stage relies on the failed output.
60
50
 
61
- ## 3. Start and resume through the scoped manager
51
+ Requires 1.25.0 for durable step plans: preparation requests must round-trip through the host's JSON options. Invalid plans return `StartJobError.InvalidStepPlan`. Recovery reuses identities and requests, prepares only unsaved steps, and never calls `PrepareSteps` again. Put retry-safe initialization needed before start **and** resume in `OnBeforeDispatchingJobSteps(JobStepDispatchContext)`, after the respective start/resume hook. `InitializationMayBeIncomplete` flags recovery from preparing/starting, not exactly-once initialization. A failed hook dispatches nothing; recovery never replays the start hook. A legacy preparing/starting job without a saved plan returns `CannotResumeJobError.MissingStepPlan` rather than inventing identities.
62
52
 
63
- `IGrainFactory.GetJobsManager(scope, namespace)` resolves the manager (`JobsManagerExtensions.cs`). `IJobsManager.Start<TJob,TRequest>(request)` returns `Result<JobId, StartJobError>`; inspect its error before telling a caller that a job started (`IJobsManager.cs`). Start success is not completed work; observe the job's status/outcomes.
53
+ ## 3. Start locally and recover every scope
64
54
 
65
- `IJobsManager.Resume(JobId)` returns **`Task<bool>`**. False means nothing took the job; do not wait for a completion callback as though resumption succeeded. True can mean resumed, already running, or finalized as completed (`IJobsManager.cs`). `Rehydrate()` completes after loading/preparing interrupted jobs, **before** its bounded background drain has resumed them all.
55
+ Requires 1.18.0 for the singleton local service `Cratis.Orleans.Jobs.IJobs`, registered by `AddCratisOrleans`. Prefer `Start<TJob,TRequest>(new JobKey(scope, namespace), request)` (or its explicit job-id overload), `Stop(key, jobId)` and `Delete(key, jobId)` during rolling upgrades. Generic `IJobsManager.Start`/`GetJobsOfType` send types through a manager that may run an older release and cannot decode them.
66
56
 
67
- `StartStepsInBackground` defaults to false. Overriding it to true makes Start return once the job is durable, moving step preparation/start to the grain's next turn. Later startup errors belong to the job, not the Start response (`Job.cs`).
57
+ Inspect `Result<JobId, StartJobError>` before telling a caller that work started. `FailedStartingSomeJobSteps` means remaining work is still running, not that nothing started; `AllJobStepsFailedStarting` means no step started. Start success is not completion. Match `StopJobResult`/`DeleteJobResult`: accepted, `NotFound`, `UnknownJobType`, or `Refused` with the job's error. Unknown types require a release that knows the type; exceptions propagate. Deletion acceptance is not completed removal. Third-party storage should override `IJobStorage.GetJobType` with a metadata-only read; its fallback reads the request too.
68
58
 
69
- ## 4. Know what storage retains
59
+ `IGrainFactory.GetJobsManager(scope, namespace)` remains available. `IJobsManager.Resume(JobId)` returns **`Task<bool>`**; false means nothing took the job: do not wait for a completion callback as though resumption succeeded. True may also mean already running or finalized. `IJobsManager.Stop` returns `Result<StopJobError>`; preserve a persistence refusal. `Rehydrate()` returns after loading/preparing interrupted jobs, before its bounded background drain resumes them all. `StartStepsInBackground = true` moves step preparation/start to the next turn after durability; later failures belong to the job, not Start.
70
60
 
71
- Successful jobs clear themselves by default; override `KeepAfterCompleted` to retain job state. Jobs completed with failures default to `KeepAfterCompletedWithFailures = true`, but retention cleanup still applies (`Job.cs`, `JobsOptions.cs`).
61
+ Requires 1.17.0 for automatic recovery on silo activation/member death. Provide singleton `IJobsManagerKeys` implementations on **every silo**, enumerating all host-owned scope/namespace pairs through `GetKeys(CancellationToken)`. The default remembers only keys used in this process: it is not a durable catalog. Providers run outside request scope; never inject a current tenant/user or scoped database. Requires 1.23.3 (1.24.2 for provider construction failures) for automatic recovery retries of failed providers/managers, from 1 second doubling to 60 seconds until success or silo shutdown. There is no periodic sweep by default. `AddCratisOrleans` registers recovery; separately configured silos need `AddCratisOrleansJobsRecovery()` (`Cratis.Orleans.Hosting`).
72
62
 
73
- **Keeping a job does not keep successful step state.** The step provider deletes successful/removing state, moves failed state to failed-step storage, and saves stopped/scheduled/running/unreachable state (`JobStepGrainStorageProvider.cs`). SQL keeps failed step rows with a failure status rather than a separate collection (`Source/Orleans.Storage.Sql/Jobs/JobStepStorage.cs`, `MoveToFailed`). Copy results the job must retain through `OnStepCompletedOrStopped(JobStepId, JobStepResult)` into job state (`Job.Steps.cs`); do not read deleted successful step state later.
63
+ Read [lifecycle and placement](references/lifecycle-and-placement.md) for drain/admission, bounded attempts, placement, custom storage compatibility and step test runs.
74
64
 
75
- Deliver external callbacks to a method on the **job's grain interface**, update job state there and persist it with `WriteState()`. Preserve the returned `Catch` failure; do not acknowledge input whose write failed (`Job.State.cs`, `WriteState`; `Documentation/concepts/jobs.md`). Step state is not a callback inbox: a concurrent callback can race the final write, and successful completion deletes it. A running step's snapshot will not see later job/step writes automatically.
65
+ ## 4. Know what storage retains
76
66
 
77
- ## 5. Register storage and budget concurrency
67
+ Successful jobs clear themselves by default; override `KeepAfterCompleted` to retain state. Jobs completed with failures default to `KeepAfterCompletedWithFailures = true`, but retention cleanup still applies.
68
+
69
+ Keeping a job does not keep successful step state: successful **steps delete their state**. Failed step state is retained. A durable callback receipt, deduplication set or result must live on the **job** (and be persisted), not only on a successful step. Deliver external callbacks to a method on the job's grain interface and preserve `WriteState()`'s `Catch` failure: do not acknowledge a failed write. Step state is not a callback inbox, and a running snapshot does not see later writes automatically.
78
70
 
79
- `IJobsStorage.GetFor(scope, namespace)` returns `JobsStorage`. Scope and namespace are opaque engine coordinates carried in grain keys; the host/provider owns their mapping (`Source/Orleans.Storage/Jobs/IJobsStorage.cs`, `Job.cs`). They are storage boundaries, not authorization checks.
71
+ ## 5. Register storage and budget concurrency
80
72
 
81
- Choose one storage registration alongside `AddCratisOrleans`:
73
+ `IJobsStorage.GetFor(scope, namespace)` returns `JobsStorage`. Scope and namespace are opaque host-owned storage coordinates, not authorization checks. Choose one storage registration alongside `AddCratisOrleans`:
82
74
 
83
75
  | Registration | Package / namespace | Use |
84
76
  | --- | --- | --- |
85
- | `AddCratisOrleansMongoDBJobsStorage` | `.Storage.MongoDB` / `Cratis.Orleans.Setup` | Durable MongoDB; database-name resolver can override the default `{scope}+jobs[+{namespace}]` |
86
- | `AddCratisOrleansSqlJobsStorage` | `.Storage.Sql` / `Cratis.Orleans.Setup` | Durable EF Core; `OptionsResolver(scope, namespace)` selects typed `DbContextOptions<JobsDbContext>` |
87
- | `AddCratisOrleansInMemoryJobsStorage` | `.Storage` / `Cratis.Orleans.Storage` | Specs/development only; process restart loses state |
88
-
89
- Evidence: each package's `*JobsStorageServiceCollectionExtensions.cs`; MongoDB `DatabaseNames.cs` and `MongoDBJobsStorageOptions.cs`; SQL `SqlJobsStorageOptions.cs`; in-memory `InMemory/InMemoryJobsStorage.cs`.
90
-
91
- ### SQL schema creation
77
+ | `AddCratisOrleansMongoDBJobsStorage` | `.Storage.MongoDB` / `Cratis.Orleans.Setup` | Durable MongoDB; default database `{scope}+jobs[+{namespace}]`, overridable |
78
+ | `AddCratisOrleansSqlJobsStorage` | `.Storage.Sql` / `Cratis.Orleans.Setup` | Durable EF Core; `OptionsResolver(scope, namespace)` selects `DbContextOptions<JobsDbContext>` |
79
+ | `AddCratisOrleansInMemoryJobsStorage` | `.Storage` / `Cratis.Orleans.Storage` | Specs/development; restart loses state |
92
80
 
93
- The product's SQL setup docs say to create the schema yourself. **the provider applies migrations automatically**: `SqlJobsStorage.GetFor` calls `EnsureSchema`, which calls `dbContext.Database.Migrate()` before constructing storage (`Source/Orleans.Storage.Sql/SqlJobsStorage.cs`). The migration is `Jobs/Migrations/v1_0_0.cs`; do not substitute `EnsureCreated` or hand-create those tables. The connection needs permissions to apply the migration, and scope-to-database mapping must be deliberate. Storage is cached per scope/namespace; `IJobsStorage.Reset()` clears the cache and the next resolution migrates again (`SqlJobsStorage.cs`). This resolves the documentation conflict from source, not from a release-note inference.
81
+ The SQL provider calls `dbContext.Database.Migrate()` automatically before constructing storage. Do not substitute `EnsureCreated` or hand-create tables; grant migration permissions and choose scope-to-database mapping deliberately. `IJobsStorage.Reset()` clears the cache; the next resolution migrates again. There is no MySQL/MariaDB jobs provider.
94
82
 
95
- Configure `JobsOptions` through the host's options registration. Its properties are **init-only**: supply a constructed instance via the options mechanism rather than assigning them in a `Configure(options => options.Property = …)` callback (`JobsOptions.cs`).
83
+ `JobsOptions` properties are **init-only**: supply a constructed instance through the host's options mechanism, not assignment in `Configure(options => …)`.
96
84
 
97
85
  | Option | Default | Purpose |
98
86
  | --- | --- | --- |
99
- | `MaxParallelSteps` | `max(1, ProcessorCount - 1)` when unset | Actual execution throttle |
100
- | `MaxConcurrentStepStarts` | `16` | Startup/resume calls per job, not the execution limit |
87
+ | `MaxParallelSteps` | `max(1, ProcessorCount - 1)` when unset | Execution throttle per silo |
88
+ | `MaxConcurrentStepStarts` | `16` | Startup/resume calls per job, not execution limit |
101
89
  | `MaxConcurrentRehydration` | `8` | Jobs resumed concurrently by a manager |
102
90
  | `MaxConcurrentCleanup` | `4` | Dead-job deletions |
103
- | `StepCheckpointBatchInterval` | `100` | Batches between durable progress checkpoints |
104
- | `StepCheckpointFlushInterval` | `5 seconds` | Timed checkpoint flush; nonpositive disables it |
105
- | `DeadJobThreshold`, `CleanupCadence` | `1 hour` each | Preparation-with-no-steps scavenging |
106
- | `TerminalJobRetention` | `7 days` | Terminal history retention; `Timeout.InfiniteTimeSpan` retains indefinitely |
91
+ | `StepCheckpointBatchInterval` | `100` | Batches between durable checkpoints |
92
+ | `StepCheckpointFlushInterval` | `5 seconds` | Timed flush; nonpositive disables it |
93
+ | `DeadJobThreshold`, `CleanupCadence` | `1 hour` each | Preparation-with-no-steps scavenging; saved plans are excluded |
94
+ | `TerminalJobRetention` | `7 days` | Terminal history; `Timeout.InfiniteTimeSpan` retains indefinitely |
107
95
  | `MaxTerminalJobsPerCleanup` | `100` | Bounded oldest-first cleanup page |
108
96
 
109
- Source: `JobsOptions.cs`. The three concurrency options for starts/rehydration/cleanup treat values below one as one. Retention does not delete preparing/running/stopped/removing jobs. Larger checkpoint intervals mean more post-crash redelivery, not higher delivery guarantees.
97
+ Concurrency values below one become one. Retention does not delete preparing/running/stopped/removing jobs. Larger checkpoint intervals increase post-crash redelivery, not delivery guarantees.
110
98
 
111
99
  ## State machines and verification
112
100
 
113
- Use [state-machines.md](references/state-machines.md) for `StateMachine<TStoredState>` and `EventSourcedStateMachine<TModel>`: jobs are recoverable work, state machines are lifecycle behavior. Neither makes external effects exactly-once.
101
+ Use [state-machines.md](references/state-machines.md) for `StateMachine<TStoredState>` and `EventSourcedStateMachine<TModel>`. Neither makes external effects exactly-once.
114
102
 
115
- Verify a new job with specs for planning, idempotent redelivery, stop/resume (including false), failed barriers/Unreachable, retention and persistence failures. Exercise the selected durable provider for restart recovery and SQL migration permissions. A build alone cannot establish crash recovery. Do not operate a live store merely to validate a code example.
103
+ Verify planning, invalid/missing saved plans, idempotent redelivery, stop/resume/refusal, failed barriers/Unreachable, retention and persistence failures. Exercise durable storage restart recovery and SQL migration permissions. A build alone cannot establish crash recovery. Do not operate a live store merely to validate an example.
@@ -0,0 +1,52 @@
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
+ # Lifecycle, recovery and placement
5
+
6
+ ## Stop, drain and admission
7
+
8
+ Requires Cratis.Orleans 1.20.0 for `IJobs.StopAndDrain(key, jobId, timeout)` and the grain protocols `IJobDrain` / `IJobStepDrain`. Unlike ordinary stop acceptance, `Drained` means no step code can run, final outcomes/checkpoints are durable and completion/stop hooks finished. Other outcomes are `TimedOut` (call again), `Completed`, `NotFound` and `UnknownJobType`. `TimeSpan.Zero` polls. Add the method to hand-written `IJobs` implementations. Enable only after every silo supports it. A pending drain survives reactivation and refuses resume; a timeout is not proof of quiescence. Hooks can repeat or be missed after a crash, so keep them idempotent rather than collecting results only there.
9
+
10
+ Requires 1.21.0 for owner admission: implement `IJobAdmission.MayRun(JobAdmissionRequest, CancellationToken)` and register with `siloBuilder.AddJobAdmission<T>()` on every silo that hosts those job types. Every admission must allow; none means allow. It runs before start, every resume and each later stage. Refusals return `StartJobError.NotAdmitted` / `CannotResumeJobError.NotAdmitted`; manager resume returns false. A refused running job is stopped/drained, and a refused later stage stops the job. Exceptions/timeouts are failures, not refusals; a later stage retries an unanswered admission on a timer.
11
+
12
+ `MayRun` runs in the job's turn: never call the job, its steps, or a non-interleaving grain that could be waiting on this start/resume. To change what existing work may do, the owner calls `StopAndDrain` **before** changing the admission answer. Older silos do not ask, so admission guarantees require every silo to run the supporting version. Admission does not by itself serialize arbitrary host-owned mutual-exclusion rules.
13
+
14
+ ## Bounded delivery and retries
15
+
16
+ Requires 1.23.0 for these opt-in execution controls. Defaults preserve unbounded dispatch, one execution attempt and no lease timeout:
17
+
18
+ | Option | Default / meaning |
19
+ | --- | --- |
20
+ | `MaxActiveStepsPerJob` | `null` (all); a positive limit fills a dispatch window within a stage |
21
+ | `MaxStepAttempts` | `1`, including first attempt; failed work can retry when increased |
22
+ | `StepRetryBackoff`, `MaxStepRetryBackoff` | `5 seconds`, `5 minutes`; capped exponential delay |
23
+ | `JobWatchdogInterval` | `1 minute`; probes stalled delivery/progress |
24
+ | `StepLeaseTimeout` | `TimeSpan.Zero` disables heartbeat lease enforcement |
25
+ | `StepDeliveryMaxAttempts` | `10` |
26
+ | `StepDeliveryRetryDelay`, `MaxStepDeliveryRetryDelay` | `1 second`, `30 seconds` |
27
+
28
+ The watchdog restarts a leased step only after its owner silo left the cluster **and** its lease expired. Steps saved by older releases get one lease period. `Job.ShouldRetryStep` decides which failures retry, and `Job.MaxActiveSteps` can override the window. Persist optional `Attempt` and `Lease` on custom step state; old records default to attempt 1 and no lease. `IJobStepStorage.CountByStatus(jobId)` has a default implementation. A step persists its terminal state before retrying outcome delivery; exhausted delivery does not turn that completed step into a new failed execution. The watchdog reconciles stored outcomes if reports never arrive. Keep work idempotent: leases and delivery deduplication do not make external effects exactly-once.
29
+
30
+ `IJobsManager.RetryFailedSteps(jobId)` is opt-in through `Job.CanRetryFailedSteps` and runs failed steps of a job completed with failures again. Ordinary resume is unchanged. It is not a general rerun of a successful job.
31
+
32
+ ## Step test runs
33
+
34
+ Requires 1.22.0 for `step.BeginTestRun()` and `JobStepTestRun<TState>.Perform(state)` in a specification host. `Perform` runs `PerformStep` off activation with the supplied state and returns its `Catch<JobStepResult>`; `TryInvokeOnActivation` callbacks run serially on a stand-in scheduler. `IsOnActivation` tests that state changes happen in callbacks. `Stop()` cancels work while admission remains open; `CloseAdmission()` refuses callbacks. A run performs once and records/delivers nothing to a job. Cover `JobStepAlreadyHasALiveRun` and `JobStepTestRunWasSuperseded` misuse. This does not isolate external effects: use safe substitutes, not a live job/store.
35
+
36
+ ## Custom storage and rolling upgrades
37
+
38
+ Persist the complete `JobState`, including `StopFinalized`, `StoppedStepsCounted` and `CompletedStepsCounted`. Missing lists read as empty for old records; do not project these fields away when storing state. The completion list tracks outcomes in executions begun by resume/retry, not jobs never resumed, and warns at 100,000 entries. Cross-version code that does not persist the list can lose duplicate protection, so keep mixed-version windows short.
39
+
40
+ A completed stop sets `StopFinalized` after all required notifications have succeeded. Unfinalized stops are retried; finalized stops are excluded from recovery through `IJobStorage.GetJobsExcludingFinalizedStops`. Custom providers can override its default in-memory filtering to filter in storage. MongoDB and SQL filter there; MongoDB adds `status_stopFinalized`. SQLite, PostgreSQL and SQL Server jobs storage are supported; MySQL/MariaDB is not. Removing jobs likewise retries unacknowledged notifications; acceptance of removal is not quiescence or immediate physical deletion.
41
+
42
+ ## Independent controller and worker placement
43
+
44
+ Requires 1.30.0 for `Cratis.Orleans.Hosting` extensions on `ISiloBuilder`:
45
+
46
+ - `AddJobPlacement()` or `AddJobPlacement<TDirector>()` opts controllers into `JobsPlacementStrategy`.
47
+ - `AddJobStepPlacement()` or `AddJobStepPlacement<TDirector>()` opts workers into `JobStepsPlacementStrategy`, including recovered steps.
48
+ - Directors implement Orleans `IPlacementDirector`. Host directors decide eligibility; throw `NoEligibleJobWorker` when no silo is eligible. Orleans does not retry that exception; the job's failure handling applies.
49
+ - Without an opt-in, controller placement remains resource-optimized and workers use the cluster default. The built-in directors preserve those defaults when an opt-in has no custom director.
50
+ - **Wait until every silo runs the supporting version**, then register each strategy identically on every silo. Do not introduce the strategy during a mixed-version rollout.
51
+
52
+ `UseJobStepParallelismFromSiloMetadata()` independently sizes each silo's throttle from the positive integer `cratis.jobs.max-parallel-steps` metadata value (or the supplied metadata key). Missing, nonnumeric or nonpositive values fall back to `JobsOptions.MaxParallelSteps` and its processor-count default. Publish with Orleans `UseSiloMetadata`. This is a startup value, not live resizing, authorization or admission. Keep controller placement, worker eligibility, execution throttling and job admission as separate choices.
@@ -44,7 +44,9 @@ public class Finished : State<ProcessState>;
44
44
 
45
45
  Split types into their house-style files in the consumer; the block collects declarations to show the API. Do not copy the `processes` provider name into Chronicle's kernel; use its registered constants.
46
46
 
47
- A refused transition is a **no-op**, not an exception or a successful change. An unknown target throws `UnknownStateTypeInStateMachine`. The machine runs leaving hooks, `OnLeave`, entering hooks, `OnEnter`, then persistence (`StateMachine.cs`). Transitions requested while entering are scheduled; transitions while leaving throw `TransitioningDuringOnLeaveIsNotSupported`.
47
+ A refused transition is a **no-op**, not an exception or a successful change. An unknown target throws `UnknownStateTypeInStateMachine`. The machine runs leaving hooks, `OnLeave`, entering hooks, `OnEnter`, then persistence (`StateMachine.cs`).
48
+
49
+ Requires 1.22.3 for transitions admitted during entry to queue FIFO rather than be dropped. Their request tasks complete on admission, **before** the queued transition executes. If another state has been entered, the target is rechecked and silently skipped when refused; a failed drain discards pending targets and fails its owning task. Transitions while leaving still throw `TransitioningDuringOnLeaveIsNotSupported`.
48
50
 
49
51
  When stored state derives `StateMachineState`, `CurrentState` stores the state's full type name; activation resolves it, enters it again, and can repeat enter effects. Renames need a stored-value migration; an unknown recorded name throws `UnknownCurrentState` (`StateMachine.cs`). A self-transition with the **same state instance** skips persistence: return a new stored-state instance when a self-transition must write.
50
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/pi",
3
- "version": "7.113.0",
3
+ "version": "7.114.0",
4
4
  "description": "Configuration-aware Cratis AI integration for Pi",
5
5
  "type": "module",
6
6
  "files": [