@cratis/pi 7.56.2 → 7.57.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 (31) hide show
  1. package/package/corpus/harnesses/copilot/instructions/elixir.instructions.md +2 -2
  2. package/package/corpus/product-sync.json +11 -9
  3. package/package/corpus/rules/elixir.md +2 -2
  4. package/package/corpus/skills/cratis-arc-command/SKILL.md +1 -1
  5. package/package/corpus/skills/cratis-arc-command/references/command-result.md +29 -0
  6. package/package/corpus/skills/cratis-arc-command/references/fresh-identifiers.md +65 -5
  7. package/package/corpus/skills/cratis-chronicle-client-elixir/SKILL.md +64 -87
  8. package/package/corpus/skills/cratis-chronicle-client-elixir/references/append-api.md +10 -0
  9. package/package/corpus/skills/cratis-chronicle-client-elixir/references/captures.md +43 -0
  10. package/package/corpus/skills/cratis-chronicle-client-elixir/references/client-options.md +6 -0
  11. package/package/corpus/skills/cratis-chronicle-client-elixir/references/connections.md +1 -1
  12. package/package/corpus/skills/cratis-chronicle-client-elixir/references/constraints.md +63 -0
  13. package/package/corpus/skills/cratis-chronicle-client-elixir/references/decision-reads.md +65 -0
  14. package/package/corpus/skills/cratis-chronicle-client-elixir/references/discovery-and-generations.md +56 -0
  15. package/package/corpus/skills/cratis-chronicle-client-elixir/references/event-sources.md +1 -1
  16. package/package/corpus/skills/cratis-chronicle-client-elixir/references/idioms-and-inspection.md +2 -2
  17. package/package/corpus/skills/cratis-chronicle-client-elixir/references/observability.md +37 -0
  18. package/package/corpus/skills/cratis-chronicle-client-elixir/references/patterns.md +22 -0
  19. package/package/corpus/skills/cratis-chronicle-client-elixir/references/pitfalls.md +9 -1
  20. package/package/corpus/skills/cratis-chronicle-client-elixir/references/projections.md +28 -0
  21. package/package/corpus/skills/cratis-chronicle-client-elixir/references/read-models.md +41 -0
  22. package/package/corpus/skills/cratis-chronicle-client-elixir/references/testing.md +63 -0
  23. package/package/corpus/skills/cratis-chronicle-client-elixir/references/upgrading.md +18 -0
  24. package/package/corpus/skills/cratis-chronicle-client-elixir/verification.json +17 -4
  25. package/package/corpus/skills/cratis-chronicle-decision-reads/SKILL.md +11 -0
  26. package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/SKILL.md +2 -2
  27. package/package/corpus/skills/cratis-screenplay-scenario-coverage/references/scenario-catalogue.md +1 -1
  28. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +6 -5
  29. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/behavior-walk.md +2 -2
  30. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/sandbox-and-specrunner.md +34 -2
  31. package/package.json +1 -1
@@ -12,13 +12,13 @@ paths:
12
12
  # Elixir Conventions
13
13
 
14
14
  Use Elixir and OTP idioms, not translated C# or JVM shapes. These conventions
15
- are grounded in `Cratis/Chronicle.Elixir v3.10.2` (`Source/chronicle/mix.exs`,
15
+ are grounded in `Cratis/Chronicle.Elixir v3.13.0` (`Source/chronicle/mix.exs`,
16
16
  `.formatter.exs`, and `lib/chronicle/`). Client-specific APIs belong in the
17
17
  **cratis-chronicle-client-elixir** skill.
18
18
 
19
19
  ## Build and format
20
20
 
21
- - For Chronicle 3.10.x use Elixir 1.18 or later (`~> 1.18`).
21
+ - For Chronicle 3.13.x use Elixir 1.18 or later (`~> 1.18`).
22
22
  - Run `mix compile --warnings-as-errors`, `mix test`, and
23
23
  `mix format --check-formatted` with the project's formatter inputs.
24
24
  - Use two-space indentation and let `mix format` own layout.
@@ -63,9 +63,9 @@
63
63
  }
64
64
  ],
65
65
  "synced": {
66
- "version": "22.97.0",
67
- "date": "2026-10-14",
68
- "by": "Cratis/AI#695"
66
+ "version": "22.100.0",
67
+ "date": "2026-10-15",
68
+ "by": "Cratis/AI#710"
69
69
  },
70
70
  "covers": {
71
71
  "skills": [
@@ -98,7 +98,7 @@
98
98
  "vertical-slices.md"
99
99
  ]
100
100
  },
101
- "notes": "Release-tag delta through 22.97.0: scoped completion and routing validation, generated-target freshness, observable scenarios, asynchronous execution scopes, read-model diagnostics and ordered reactor refusal handling. Existing context generation/self-reduction guidance retained; Screenplay extraction deltas handed to its owning lane. No new runtime probe."
101
+ "notes": "Release-tag delta through 22.100.0: scoped completion and routing validation, generated-target freshness (including direct unit-of-work writes and nested commands), observable scenarios, asynchronous execution scopes, read-model diagnostics, ordered reactor refusal handling and ICommandExceptionMapper. Existing context generation/self-reduction guidance retained; Screenplay extraction deltas handed to its owning lane. No new runtime probe."
102
102
  },
103
103
  {
104
104
  "id": "ai",
@@ -579,17 +579,19 @@
579
579
  }
580
580
  ],
581
581
  "synced": {
582
- "version": "3.10.2",
583
- "date": "2026-10-12",
584
- "by": "Cratis/AI#620"
582
+ "version": "3.13.0",
583
+ "date": "2026-10-15",
584
+ "by": "Cratis/AI#719"
585
585
  },
586
586
  "covers": {
587
587
  "skills": [
588
588
  "cratis-chronicle-client-elixir"
589
589
  ],
590
- "rules": []
590
+ "rules": [
591
+ "elixir.md"
592
+ ]
591
593
  },
592
- "notes": "Initial historical floor recorded on 2026-10-12 by Cratis/AI#619; date records this inventory, not a new product verification."
594
+ "notes": "Synced to the v3.13.0 tag: decision reads, Chronicle.Testing, captures, discovery drift, standalone constraints, children collections, concurrency scopes and observability."
593
595
  },
594
596
  {
595
597
  "id": "authproxy",
@@ -11,13 +11,13 @@ paths:
11
11
  # Elixir Conventions
12
12
 
13
13
  Use Elixir and OTP idioms, not translated C# or JVM shapes. These conventions
14
- are grounded in `Cratis/Chronicle.Elixir v3.10.2` (`Source/chronicle/mix.exs`,
14
+ are grounded in `Cratis/Chronicle.Elixir v3.13.0` (`Source/chronicle/mix.exs`,
15
15
  `.formatter.exs`, and `lib/chronicle/`). Client-specific APIs belong in the
16
16
  **cratis-chronicle-client-elixir** skill.
17
17
 
18
18
  ## Build and format
19
19
 
20
- - For Chronicle 3.10.x use Elixir 1.18 or later (`~> 1.18`).
20
+ - For Chronicle 3.13.x use Elixir 1.18 or later (`~> 1.18`).
21
21
  - Run `mix compile --warnings-as-errors`, `mix test`, and
22
22
  `mix format --check-formatted` with the project's formatter inputs.
23
23
  - Use two-space indentation and let `mix format` own layout.
@@ -246,7 +246,7 @@ whatever renders it:
246
246
 
247
247
  Read [references/client-contract.md](references/client-contract.md) for the generated proxy members.
248
248
 
249
- Read `references/command-result.md` when branching on a command result or matching validation failures to fields. Branch on the specific flag, not only on `isSuccess`. The exact `CommandResult`
249
+ Read `references/command-result.md` when branching on a command result, matching validation failures to fields, or mapping integration exceptions. Branch on the specific flag, not only on `isSuccess`. The exact `CommandResult`
250
250
  and `ValidationResult` shapes are in
251
251
  [command result](references/command-result.md) — in particular, a validation
252
252
  failure carries `members: string[]` (camelCased) and a numeric `severity`, not a
@@ -3,6 +3,7 @@
3
3
  ## Contents
4
4
 
5
5
  - Server side
6
+ - Map integration exceptions
6
7
  - Severity filtering
7
8
  - Client side
8
9
  - Executing with a severity
@@ -40,6 +41,34 @@ every execution scope has completed and the result is not successful, so a
40
41
  transaction that rolls back after `Handle` returned a value does not leak that
41
42
  value into the error body.
42
43
 
44
+ ## Map integration exceptions
45
+
46
+ Requires Arc 22.100.0 or later for `Cratis.Arc.Commands.ICommandExceptionMapper`.
47
+ Check `dotnet list package --include-transitive`; older Arc has no mapper hook
48
+ and uses `CommandResult.FromException` for unhandled execution exceptions.
49
+
50
+ Implement the interface for an integration's typed failures, not ordinary
51
+ user-correctable rejections (return validation results for those):
52
+
53
+ ```csharp
54
+ bool TryMap(CommandContext context, Exception exception,
55
+ [NotNullWhen(true)] out CommandResult? result);
56
+ ```
57
+
58
+ `NotNullWhen` is from `System.Diagnostics.CodeAnalysis`. Return `true` with a
59
+ non-null **failure** preserving `context.CorrelationId`, or `false` with `null`
60
+ to decline. Match exception types/properties rather than parsing messages.
61
+ Implementations are convention-discovered through `IInstancesOf<ICommandExceptionMapper>`
62
+ (`Cratis.Types`) and resolved from the command's service scope. The first accepting
63
+ mapper wins. Mapping runs for exceptions escaping command execution or response
64
+ processing, before execution scopes complete, so a mapped failure still rolls
65
+ back pending work. If no mapper accepts, the pipeline falls back to
66
+ `CommandResult.FromException`; a throwing mapper produces `CommandResult.Error`
67
+ and scopes still complete with
68
+ failure. This hook does not replace validation-only or scope-completion exception
69
+ handling. Specify accepting, declining and throwing cases and assert rollback
70
+ and correlation preservation, not only the message.
71
+
43
72
  ## Severity filtering
44
73
 
45
74
  `ValidationResultSeverity`: `Unknown = 0`, `Information = 1`, `Warning = 2`,
@@ -3,6 +3,12 @@
3
3
 
4
4
  # Fresh generated append targets
5
5
 
6
+ ## Contents
7
+
8
+ - Direct writes and nested commands
9
+ - Opt out deliberately
10
+ - Specify the collision
11
+
6
12
  Requires Arc 22.95.0 and Chronicle client 19.59.4 for guarded generated targets; deploy a Chronicle kernel 19.40.1 or later for retry-time emptiness checks. Check package references and the deployed kernel version before relying on the guard; older Arc needs an explicit whole-source `BeforeFirst` scope.
7
13
 
8
14
  A `[GeneratedValue]` parameter defaults to `GeneratedValueFreshness.Guarded`.
@@ -10,9 +16,12 @@ When an Arc-owned append targets a validated value generated in that invocation,
10
16
  it requires the **whole event source** to be empty, including other streams and
11
17
  event types. Payload-only and response-only generated values add no scope.
12
18
  Returned events, tuples, wrappers, `EventsWithConcurrencyScopes` and aggregates
13
- acquired for that generated identifier are covered. Direct `IUnitOfWork.AddEvent`,
14
- `AddEvents` and `eventLog.Transactional.Append` calls are not: return events
15
- through Arc or supply the exact emptiness scope explicitly.
19
+ acquired for that generated identifier are covered.
20
+
21
+ Requires Arc 22.100.0 or later and Chronicle client 19.65.2 or later for direct
22
+ unit-of-work writes and redacted enrollment-conflict translation. Check resolved
23
+ versions with `dotnet list package --include-transitive`; older Arc needs returned
24
+ events or an explicit whole-source `BeforeFirst` scope for direct writes.
16
25
 
17
26
  An occupied destination returns `ValidationResultReason.ConcurrencyViolation`
18
27
  with `ReasonDetail` `GeneratedIdentifierCollision`. Arc does not rerun `Handle()`.
@@ -25,6 +34,56 @@ explicit scope, including `ConcurrencyScope.None` or a narrowed scope, fails wit
25
34
  generated source's returned events contiguous; an interleaved shape that would
26
35
  split its guard fails with `GeneratedIdentifierEventsCannotBeSplit`.
27
36
 
37
+ ## Direct writes and nested commands
38
+
39
+ The same guard covers these writes on the **command's actual unit of work**:
40
+
41
+ - `IUnitOfWork.AddEvent`, `AddEventWithNamedTags` and `AddEvents`.
42
+ - `eventLog.Transactional.Append`, `AppendMany`, `AppendWithNamedTags` and
43
+ `AppendManyWithNamedTags`.
44
+
45
+ Returned, aggregate and direct events share one whole-source expectation. An
46
+ occupied generated destination rejects the whole pending unit, including events
47
+ for other sources, with the collision result above. Arbitrary new units and
48
+ external stores are outside this protection.
49
+
50
+ Nested pipeline invocations share the outer unit and leave completion to its
51
+ owner. Guarded captures from every active invocation participate, so a nested
52
+ handler's attribute-derived scope cannot narrow an outer generated guard.
53
+ Propagate nested rejection so the outer command rolls back earlier events.
54
+ An explicit aggregate `Commit()` can still complete the shared unit early.
55
+
56
+ After enrollment, the required scope survives disposal of the invocation's
57
+ contributor; later unscoped writes inherit it. An incompatible supplied scope
58
+ against that recorded generated guard maps to `ConflictingConcurrencyScopes`,
59
+ even if Chronicle rejects it before invoking contributors. Neither collision nor
60
+ scope-conflict messages expose the identifier or scope values. Arc uses typed
61
+ scope properties and recorded guard provenance, not message parsing; unrelated
62
+ enrollment conflicts retain Chronicle's redacted `ConflictingEnrollmentScope`.
63
+ Scope-only `AddEvents` entries preserve existing requirements but do not invoke
64
+ contributors or establish a new generated guard. A completed nested invocation's
65
+ untargeted capture leaves no enrolled requirement.
66
+
67
+ Custom units must support enrollment contributors. A guarded generated parameter
68
+ fails closed with `FreshIdentifierGuardRequiresEnrollmentContributors` if the unit
69
+ cannot register one; no generated parameters or only `Unguarded` parameters do not
70
+ require that support.
71
+
72
+ `AddEvents` checks all destinations before staging that call; a scope conflict
73
+ stages none of its events. Transactional `AppendMany` and
74
+ `AppendManyWithNamedTags` stage per event, so a later rejection can leave earlier
75
+ events pending. Let the exception escape `Handle()` to roll back the unit;
76
+ catching it and succeeding can commit those earlier events.
77
+
78
+ Immediate `eventLog.Append`, `AppendMany` and their named-tag variants bypass
79
+ enrollment, cannot be rolled back and receive **no automatic freshness guard**.
80
+ Prefer returning events. An integration needing direct writes must use the
81
+ command's transactional unit; an immediate caller needs an explicit whole-source
82
+ `BeforeFirst` scope and must inspect the append result, without automatic
83
+ `GeneratedIdentifierCollision` classification.
84
+
85
+ ## Opt out deliberately
86
+
28
87
  Only when appending to an existing generated destination is intentional, use this
29
88
  parameter attribute fragment (imports `Cratis.Arc.Commands.ModelBound` and
30
89
  `Cratis.Arc.Commands`):
@@ -42,8 +101,9 @@ use an appropriate constraint or explicit concurrency scope for them.
42
101
  Use `new CommandScenario<TCommand>().UseDecisionReads()` **before seeding**, then
43
102
  `PinGeneratedValue(name, id)` and `Given.ForEventSource(id).Events(existingEvent)`.
44
103
  Assert the concurrency reason and collision detail, unchanged append tail and
45
- one handler execution. Also cover an empty source receiving two events and an
46
- occupied source whose events belong to another stream.
104
+ one handler execution. Also cover an empty source receiving two events through
105
+ the chosen write API, an occupied source whose events belong to another stream,
106
+ mixed returned/direct writes, and a nested rejection rolling back the outer unit.
47
107
 
48
108
  Requires Arc 22.95.1 for `GeneratedIdentifierCollisionRequiresDecisionReads`:
49
109
  default scenario mode fails fast when given events and a guarded pin share an id.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: cratis-chronicle-client-elixir
3
- description: Talk to a Chronicle server from an Elixir application with the cratis_chronicle Hex package - Chronicle.Client supervision and connection lifecycle, EventType structs, append errors, event sources and streams, Chronicle.Concept, PII and encrypted secrets, reactors, read models/projections with composite keys, variants and GlobalHandler, and observer inspection. Use when an OTP application appends to or observes a Chronicle event store. Do not use for the .NET, TypeScript, or Kotlin clients.
3
+ description: Talk to a Chronicle server from an Elixir application with the cratis_chronicle Hex package - Chronicle.Client supervision and connection lifecycle, EventType structs, append errors, event sources and streams, Chronicle.Concept, PII and encrypted secrets, reactors, read models/projections with composite keys, children, variants and GlobalHandler, decision reads, in-memory testing, captures, constraints, discovery drift and observer inspection. Use when an OTP application appends to or observes a Chronicle event store. Do not use for the .NET, TypeScript, or Kotlin clients.
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -17,23 +17,11 @@ to yours, and everything after that is ordinary OTP.
17
17
 
18
18
  Source: `Cratis/Chronicle.Elixir` (Hex package `cratis_chronicle`). Source paths below are relative to `Source/chronicle/`.
19
19
 
20
- Read [references/upgrading.md](references/upgrading.md) when moving an application from an older client; wire compatibility and corrected behavior both matter.
20
+ Read [references/upgrading.md](references/upgrading.md) when moving an application from an older client. Requires Elixir `~> 1.18`; read [references/product-sources.md](references/product-sources.md) for the package and contracts dependency facts.
21
21
 
22
- Requires Elixir `~> 1.18`. Read [references/product-sources.md](references/product-sources.md) for the package and contracts dependency facts.
22
+ > **A git tag is not a Hex release.** `mix deps.get` fetches only published versions, and the repository's `VERSION` file is a build-time placeholder: never quote it. Use namespaced modules (`Chronicle.Events.EventType`, not `Chronicle.EventType`); older READMEs got this wrong. Maintained snippets live in `Documentation/client-snippets/`.
23
23
 
24
- > **A git tag is not a Hex release.** `mix deps.get` fetches only published versions. The `Chronicle.Concept` macro
25
- > (`use Chronicle.Concept, type: :string`, a single-value concept that carries its
26
- > `pii` classification once, mirroring `ConceptAs<T>`) is the preferred way to
27
- > classify a personal value; a client without it annotates fields with `pii/1,2`.
28
-
29
- Requires `cratis_chronicle` 3.0.0 for `Chronicle.Concept`.
30
-
31
- > **The repository's `VERSION` file is a build-time placeholder** overwritten by the publish workflow. Never quote it.
32
-
33
- > Use namespaced modules (`Chronicle.Events.EventType`, not `Chronicle.EventType`); older READMEs got this wrong. Maintained
34
- > snippets live in `Documentation/client-snippets/`.
35
-
36
- Select the published version from hex.pm rather than treating a guide's range as a pin.
24
+ `Chronicle.Concept` (`use Chronicle.Concept, type: :string`) is the preferred way to classify a personal value once, mirroring `ConceptAs<T>`; it requires `cratis_chronicle` 3.0.0, and an older client annotates fields with `pii/1,2`.
37
25
 
38
26
  ## Adding it
39
27
 
@@ -41,7 +29,7 @@ Select the published version from hex.pm rather than treating a guide's range as
41
29
  # mix.exs
42
30
  defp deps do
43
31
  [
44
- {:cratis_chronicle, "~> 3.10"}
32
+ {:cratis_chronicle, "~> 3.13"}
45
33
  ]
46
34
  end
47
35
  ```
@@ -104,17 +92,14 @@ case Lifecycle.wait_until(Lifecycle.name_for(Chronicle.Client), :registered, 30_
104
92
  end
105
93
  ```
106
94
 
107
- `wait_until(lifecycle, target_phase, timeout \\ 30_000)` is the blocking wait.
108
- `subscribe/1` returns `{:ok, phase, connection_id}` **and** sends it as a message,
109
- which closes the subscribe-after-transition race; `phase/1` is the plain
110
- read.
95
+ `wait_until(lifecycle, target_phase, timeout \\ 30_000)` is the blocking wait;
96
+ `subscribe/1` returns `{:ok, phase, connection_id}` **and** sends it as a message
97
+ (closing the subscribe-after-transition race); `phase/1` is the plain read.
111
98
 
112
- Reconnect never gives up: exponential backoff starts at 1000 ms and caps at
113
- 10000 ms, re-resolving addresses each time. `:retry_attempts` is ignored.
114
- Registration failures leave the client in `:connected`, log
99
+ Reconnect never gives up (backoff 1000 to 10000 ms; `:retry_attempts` is
100
+ ignored). Registration failures leave the client in `:connected`, log
115
101
  `Chronicle registration failed`, and retry every five seconds. Appends before a
116
- channel exists return `{:error, :not_connected}`; read model queries wait up to
117
- 30 seconds for `:registered`.
102
+ channel exists return `{:error, :not_connected}`.
118
103
 
119
104
  ## Authentication and TLS
120
105
 
@@ -154,24 +139,23 @@ defaults: `nil` registers as string, numbers as number, booleans as boolean;
154
139
  list element schemas use the first default element. Nested structs and concepts
155
140
  are descended into (`schemas/json_schema_generator.ex`).
156
141
 
157
- The macro imports constraint macros into your module:
158
- `unique/1`, `unique/2`, `unique_event_type/0`, `unique_event_type/1`,
159
- `remove_constraint/1`, plus `Chronicle.Compliance.pii/1,2` and
160
- `Chronicle.Confidentiality.encrypted/1,2,3`. The
161
- equivalent accumulating module attributes `@unique`, `@unique_event_type`,
162
- `@remove_constraint` also work.
142
+ The macro imports `unique/1,2`, `unique_event_type/0,1`, `remove_constraint/1,2`,
143
+ `closes_stream/1`, `Chronicle.Compliance.pii/1,2` and
144
+ `Chronicle.Confidentiality.encrypted/1,2,3`; the accumulating attributes
145
+ `@unique`, `@unique_event_type`, `@remove_constraint` also work. Read
146
+ [references/constraints.md](references/constraints.md) for their options.
163
147
 
164
148
  **Only top-level event keys are camelCased.** `encode_event/1` transforms them
165
149
  before `Jason.encode!` (`event_sequences/event_log.ex`); nested keys are
166
150
  not transformed. Event structs get no `Jason.Encoder` implementation;
167
- `Chronicle.Concept` does, encoding its wrapped value rather than a wrapper.
151
+ `Chronicle.Concept` does, encoding its wrapped value.
168
152
 
169
153
  Read [references/compliance.md](references/compliance.md) when defining personal data, data subjects, concepts or encrypted operational secrets. `pii` and `encrypted` are not interchangeable and cannot mark the same leaf.
170
154
 
171
155
  Schema evolution is
172
156
  `use Chronicle.Events.Migration, from: {Mod, generation: n}, to: {Mod, generation: n + 1}`
173
- with `upcast/1` and `downcast/1`; the generations must be exactly one apart or it
174
- raises at compile time.
157
+ with `upcast/1` and `downcast/1`; the generations must be exactly one apart. Mark
158
+ the older module with `generation_for:`, not a second `:id`.
175
159
 
176
160
  ## Appending
177
161
 
@@ -183,40 +167,30 @@ case Chronicle.append("account-1", %MyApp.Events.FundsDeposited{amount: 50}) do
183
167
  end
184
168
  ```
185
169
 
186
- `Chronicle.append/3` delegates to `Chronicle.EventSequences.EventLog`
187
- (`lib/chronicle.ex`):
188
-
189
- ```elixir
190
- @spec append(String.t(), struct(), keyword()) :: :ok | {:error, term()}
191
- ```
192
-
170
+ `Chronicle.append/3` delegates to `Chronicle.EventSequences.EventLog`.
193
171
  **The event source id comes first, the event second.** The return is a bare
194
- `:ok`, not a result struct — this client's `append/3` deliberately carries no
195
- sequence number, and `append_and_wait_for_completion/3` exists for when you need
196
- the outcome.
172
+ `:ok`, carrying no sequence number; use `append_with_result/3` or
173
+ `append_and_wait_for_completion/3` when you need the outcome.
197
174
 
198
175
  Read [references/append-api.md](references/append-api.md) when you need an append variant, a read-back function, or an append option such as `:event_stream_type`, `:concurrency_scope` or `:tags`.
199
176
 
200
177
  Requires `cratis_chronicle` 3.2.0 for rejected appends to return `{:error, reason}`; an older client could report a rejection as `:ok`.
201
178
 
202
- Failed appends return `{:error, reason}`. Reasons include `{:constraint_violations, violations}`,
179
+ Failed appends return `{:error, reason}`: `{:constraint_violations, violations}`,
203
180
  `{:concurrency_violations, violations}`, `{:append_errors, errors}`,
204
- `:not_connected`, and `{:incompatible_server, response}` from the first append's
205
- compatibility preflight per channel. Pair the kernel and installed contracts;
206
- do not blindly retry a timed-out write.
207
-
208
- **Match on them.** A bare `:ok = Chronicle.append(...)` raises a `MatchError` on
209
- a constraint violation, which is a fine choice in a script and the wrong one in a
210
- GenServer.
181
+ `{:unknown_event_type, module}`, `:not_connected`, and `{:incompatible_server, response}`
182
+ from the first append's compatibility preflight per channel. Pair the kernel and
183
+ installed contracts; do not blindly retry a timed-out write. **Match on them**: a
184
+ bare `:ok = Chronicle.append(...)` raises a `MatchError` on a violation, fine in a
185
+ script and wrong in a GenServer.
211
186
 
212
187
  Read [references/event-sources.md](references/event-sources.md) when declaring named event sources/streams, deriving concurrency scopes or routing mixed-source batches; definitions have client and kernel requirements stated there.
213
188
 
214
- A unit of work is a process: `Chronicle.begin_unit_of_work/1` starts an `Agent`,
215
- and while one is current for the calling process `append`/`append_many` buffer
216
- instead of sending, until `UnitOfWork.commit/1` or `rollback/1`.
217
- `commit/1` returns `:ok | {:error, reason}`. `Chronicle.with_unit_of_work(fun, opts)`
218
- passes the unit to an arity-one function, commits on return, and raises on commit
219
- failure. Source/stream options apply to buffered appends too.
189
+ A unit of work is a process: `Chronicle.begin_unit_of_work/1` starts an `Agent`;
190
+ while one is current for the calling process, `append`/`append_many` buffer until
191
+ `UnitOfWork.commit/1` (`:ok | {:error, reason}`) or `rollback/1`.
192
+ `Chronicle.with_unit_of_work(fun, opts)` commits on return and raises on commit
193
+ failure.
220
194
 
221
195
  ## Observing
222
196
 
@@ -224,11 +198,8 @@ failure. Source/stream options apply to buffered appends too.
224
198
 
225
199
  Read [references/reactor-example.md](references/reactor-example.md) when you need a worked reactor module.
226
200
 
227
- This follows `Documentation/client-snippets/get-started/common/reactor.md`.
228
-
229
201
  - **`@handles` is an accumulating module attribute** declaring the subscription;
230
- dispatch is then ordinary Elixir pattern matching in `handle/2`. This is the
231
- Elixir answer to the other clients' method-name or parameter-type conventions.
202
+ dispatch is then ordinary Elixir pattern matching in `handle/2`.
232
203
  - The callback is
233
204
  `handle(event :: struct(), context :: map()) :: :ok | {:error, term()} | {:ok, struct() | [struct()]}`.
234
205
  - **Returning `{:ok, event_or_events}` appends those events as a side effect** —
@@ -239,28 +210,27 @@ This follows `Documentation/client-snippets/get-started/common/reactor.md`.
239
210
  - Optional replay callbacks: `on_replay_begin/0`, `on_replay_end/0`,
240
211
  `on_partition_replay_begin/1`, `on_partition_replay_end/1`.
241
212
 
242
- **The reactor context has six keys**: `event_source_id`, `sequence_number`,
243
- `occurred`, `event_source`, `event_store`, `namespace`
244
- (`lib/chronicle/reactors/handler.ex`). `event_source` is the registered
245
- source name or `nil` for legacy appends. The moduledoc mentions `:correlation_id`,
246
- but **it is not in the map**. Reducer context differs: `event_source_id`,
247
- `sequence_number`, `occurred`, `event_source`, `observation_state`
248
- (`reducers/handler.ex`).
213
+ **The context is a `Chronicle.Events.EventContext` struct** (requires 3.11.0; a
214
+ bare map before). Pattern match on keys or use `context.caused_by`. It carries
215
+ `event_source_id`, `sequence_number`, `occurred`, `event_source` (the registered
216
+ source name or `nil`), `event_store`, `namespace`, `correlation_id`, `caused_by`,
217
+ `causation`, `tags`, `subject`, `appended_generation`, `replaying?` and, for
218
+ reactors only, `delivery_id` (stable across replay and failed-partition
219
+ recovery; key a receipt on it). Reducers get the same struct with
220
+ `observation_state` set (`lib/chronicle/events/event_context.ex`).
249
221
 
250
222
  ### Read models and projections
251
223
 
252
- Read [references/reactor-example.md](references/reactor-example.md#read-model-example) for a worked model-bound read model.
253
-
254
- This is the model-bound shape; use typed defaults for its registered schema.
224
+ Read [references/reactor-example.md](references/reactor-example.md#read-model-example) for a worked model-bound read model with typed defaults.
255
225
 
256
- The DSL macros are `from/1`, `from/2`, `join/2`, `removed_with/2`, `from_every/1`.
226
+ The DSL macros are `from/1,2`, `join/2`, `removed_with/2`, `from_every/1`, `children/2,3`.
257
227
  `from/2` options: `:key` (defaults to `"$eventSourceId"`), `:parent_key`, `:set`,
258
228
  `:add`, `:subtract`, `:count`. `join/2` requires `:on`.
259
229
 
260
- Read [references/projections.md](references/projections.md) when choosing `passive`, `not_rewindable`, `event_sequence`, `no_auto_map`, composite/event-context keys or dictionary counters. Snake_case field atoms now map correctly; no camelCase-string workaround is needed.
230
+ Read [references/projections.md](references/projections.md) when choosing `passive`, `not_rewindable`, `event_sequence`, `no_auto_map`, composite/event-context keys, dictionary counters or `children/2,3` collections. Snake_case field atoms now map correctly; no camelCase-string workaround is needed.
261
231
 
262
232
  > **The read model id sent to the kernel is the last module segment** —
263
- > `Module.split() |> List.last()` at `lib/chronicle/read_models/read_model.ex`.
233
+ > `Module.split() |> List.last()` at `read_models/read_model.ex`.
264
234
  > `MyApp.ReadModels.Account` registers as `"Account"`, so two read models with the
265
235
  > same final segment collide. Pass `id:` to disambiguate.
266
236
 
@@ -283,10 +253,8 @@ Querying (`lib/chronicle/read_models.ex`):
283
253
  {:ok, account} = Chronicle.read_model(MyApp.ReadModels.AccountBalance, "account-1")
284
254
  ```
285
255
 
286
- `Chronicle.read_model/3` delegates to `ReadModels.get/3` (`lib/chronicle.ex`,
287
- `read_models.ex`), returning `{:ok, struct() | nil} | {:error, term()}`.
288
- `Chronicle.all/2` delegates to `ReadModels.all/2`
289
- (`lib/chronicle.ex`, `read_models.ex`).
256
+ `Chronicle.read_model/3` returns `{:ok, struct() | nil} | {:error, term()}`; `nil`
257
+ means absent **or not projected yet**.
290
258
 
291
259
  Use `Chronicle.ReadModels.query(MyApp.ReadModels.AccountBalance, page: 0)` for paging:
292
260
  `:page` is zero-based (default `0`), `:page_size` defaults to `50`, and
@@ -294,18 +262,26 @@ Use `Chronicle.ReadModels.query(MyApp.ReadModels.AccountBalance, page: 0)` for p
294
262
  `occurrences/2`, and `definitions/1` expose history, replay containers, and
295
263
  registered definitions.
296
264
 
297
- **Live updates arrive as messages, not as a stream.** `ReadModels.watch/2`
298
- (`read_models.ex`) sends
299
- `{:chronicle_read_model_changed, module, %Changeset{}}` and
300
- `{:chronicle_read_model_watch_error, module, reason}` to the calling process. The receiving process needs a `handle_info` for both.
265
+ **Live updates arrive as messages, not as a stream.** `ReadModels.watch/2` and `subscribe/2` send `{:chronicle_read_model_changed, module, %Changeset{}}` and error messages to the calling process, which needs a `handle_info` for each. Read [references/read-models.md](references/read-models.md) when subscribing, filtering, observing collections, previewing or waiting for a projection.
266
+
267
+ ## Guarding decisions, testing and more
268
+
269
+ Read the reference that matches the task:
270
+
271
+ - [references/decision-reads.md](references/decision-reads.md) when an append depends on state a concurrent append could change: `Chronicle.decide/2`, `DecisionReads`, `ConcurrencyScope` builders, the optimistic default.
272
+ - [references/testing.md](references/testing.md) when writing specs: `Chronicle.Testing.Case`, assertions, the in-memory kernel's limits.
273
+ - [references/constraints.md](references/constraints.md) when declaring uniqueness, standalone constraint modules, `remove_constraint/2`, violation details or closing events.
274
+ - [references/discovery-and-generations.md](references/discovery-and-generations.md) when pinning ids, setting `:drift_policy`, running `mix chronicle.discover`, or handling older event generations.
275
+ - [references/captures.md](references/captures.md) when turning external API, webhook or other-store data into events.
276
+ - [references/observability.md](references/observability.md) and [references/patterns.md](references/patterns.md) for OpenTelemetry/`:telemetry` and `Chronicle.Patterns`.
301
277
 
302
278
  ## Idioms and operational inspection
303
279
 
304
- Read [references/idioms-and-inspection.md](references/idioms-and-inspection.md) when carrying correlation or identity across a `Task`, formatting the DSL, supervising `ReadModels.watch/2`, or removing observers (obtain explicit authority first).
280
+ Read [references/idioms-and-inspection.md](references/idioms-and-inspection.md) when carrying correlation or identity across a `Task`, formatting the DSL, supervising watches, or removing observers (obtain explicit authority first).
305
281
 
306
282
  ## Common pitfalls
307
283
 
308
- Read [references/pitfalls.md](references/pitfalls.md) when troubleshooting discovery, defaults, process context, variant registration or older-version examples.
284
+ Read [references/pitfalls.md](references/pitfalls.md) when troubleshooting discovery, defaults, process context, variants or older-version examples.
309
285
 
310
286
  ## Verify
311
287
 
@@ -319,10 +295,11 @@ Read [references/pitfalls.md](references/pitfalls.md) when troubleshooting disco
319
295
  - Every event type module passes an explicit, stable `id:`.
320
296
  - Every `Chronicle.append/3` call site handles `{:error, _}` as well as `:ok`.
321
297
  - Every reactor's `@handles` list matches the clauses of its `handle/2`.
298
+ - Reactors, reducers and projections that must survive a rename pin their `:id`.
322
299
  - Read model module names have distinct final segments, or explicit ids.
323
300
  - Every variant group has at least one `enters_on` per variant, and every
324
301
  `GlobalHandler` member exists on every variant it targets.
325
302
  - Production logging filters credential-bearing gRPC/Mint crash reports; see
326
303
  the connections reference for limits.
327
304
  - Examples match the APIs and schema defaults at the installed tag.
328
- - `mix compile --warnings-as-errors` and `mix test` are clean.
305
+ - `mix compile --warnings-as-errors` and `mix test` are clean; `mix chronicle.discover --otp-app my_app` lists every expected artifact.
@@ -38,3 +38,13 @@ Read [event-sources.md](event-sources.md) for definitions, stream routing and de
38
38
  `append_response.ex` returns `{:error, {:constraint_violations, violations}}`, `{:error, {:concurrency_violations, violations}}` or `{:error, {:append_errors, errors}}` on rejection. Channel/preflight errors include `{:error, :not_connected}` and `{:error, {:incompatible_server, response}}`. A transport timeout does not prove that no event was stored.
39
39
 
40
40
  `append_and_wait_for_completion/3` has **no many-event variant**. `{:ok, result}` does not imply every observer succeeded: inspect `result.success` and `failed_partitions`. Its wait happens **after** the append (`event_log.ex`), so `{:error, reason}` can follow a stored event if waiting fails. Do not retry the write blindly; reconcile the stored outcome first.
41
+
42
+ ## Structured results, prepared events and unknown types
43
+
44
+ Requires `cratis_chronicle` 3.13.0 for everything in this section.
45
+
46
+ - `append_with_result/3` and `append_many_with_result/3` return `{:ok, %AppendResult{sequence_number: _}}` (batch: every assigned number, in order) or `{:error, %AppendResult{}}` carrying `:constraint_violations` (each with `:constraint_name`, `:message`, `:event_type_id`, `:sequence_number`, `:details`), `:concurrency_violations` (`:event_source_id`, `:expected_sequence_number`, `:actual_sequence_number`) and `:errors`. Use them when you need the assigned sequence number or the structured reason; transport failures stay plain `{:error, reason}`. `AppendResult.wait_for_completion/2` waits for observers.
47
+ - `EventLog.prepare/2` serializes an event once into a `PreparedEvent` bound to the client, namespace and event sequence that prepared it; reuse it with `EventLog.append_prepared/3` and `Streams.verify_content/3`. Appending it through another client, namespace or sequence returns `{:error, :prepared_event_belongs_to_another_sequence}`. Single use is not enforced.
48
+ - **A struct that does not `use Chronicle.Events.EventType` returns `{:error, {:unknown_event_type, module}}`** from every append path, `prepare/2` and `Streams.verify_content/3`; it does not raise. Match it like any other error.
49
+ - A payload over 4 MiB returns `{:error, {:payload_too_large, size, limit}}` before sending. Raise or disable the guard with `config :cratis_chronicle, max_payload_bytes: ...` (or `:infinity`) only when the kernel is configured for larger messages.
50
+ - Reads: `get_for_event_source/3` and `get_from_sequence_number/2` return raw wire events; pass `decode: true` for `Chronicle.Events.AppendedEvent` structs (`event: nil` and `:raw` for an unknown type, `redacted?: true` for a redacted event).
@@ -0,0 +1,43 @@
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
+ # Captures
5
+
6
+ Source: `Cratis/Chronicle.Elixir`: `Source/chronicle/lib/chronicle/captures.ex`, `captures/capture.ex`, `captures/registrar.ex`, and `Documentation/captures.md`.
7
+
8
+ Requires `cratis_chronicle` 3.13.0.
9
+
10
+ A capture turns data that arrives from outside the event log (an external API, an inbound webhook, another event store's public events) into events appended to this event store. The DSL compiles at build time to Capture Declaration Language (CDL), the text the kernel's `SaveCapture` accepts. Use a capture instead of a hand-written poller when the source data should become facts you own.
11
+
12
+ ```elixir
13
+ # Copyright (c) Cratis. All rights reserved.
14
+ # Licensed under the MIT license. See LICENSE file in the project root for full license information.
15
+
16
+ defmodule MyApp.Captures.InvoiceCapture do
17
+ use Chronicle.Captures.Capture, name: "InvoiceCapture"
18
+
19
+ api_source("InvoicingApi", route: "/invoices", poll: "10m")
20
+
21
+ key("id")
22
+
23
+ append MyApp.Events.InvoiceStatusChanged, when: "status" do
24
+ set(:status, "$.status")
25
+ end
26
+ end
27
+ ```
28
+
29
+ - Exactly one source: `api_source/1,2` (polls a named external service), `webhook_source/1`, or `events_source/1` (a list of registered event type modules from another event store's public events).
30
+ - `key/1` names the property path that identifies and diffs an item; use `"$eventSourceId"` for an events source.
31
+ - `:id` defaults to a UUIDv5 of the module name, so redeploying updates the same kernel-side capture. `:name` defaults to the last module segment. `start: true` makes the registrar start it once saved.
32
+ - `when:` takes a property name, `{:any, [...]}`, `{:all, [...]}`, `{:transition, property, from, to}`, `:added`, `:removed`, or `{:expression, "template"}`. `set/2` maps a target field from `"$.path"` (payload) or `"$context.<property>"`.
33
+ - The DSL also has `nested` and `children` scopes, and a `map do` block with `rename`, `template`, `translate` and `split`.
34
+
35
+ **An appended event type's id must start with an uppercase letter and contain only letters, digits and underscores.** The kernel's CDL parser rejects hyphens, so `use Chronicle.Events.EventType, id: "InvoiceStatusChanged"` works as an `append` target and `id: "invoice-status-changed"` raises `ArgumentError` at compile time. This restriction does not apply to `events_source/1`.
36
+
37
+ **The kernel's capture engine runs a subset of the language**: only `api` and `events` sources, and only root-level `append` rules. `map`, `nested` and `children` compile and validate but are not applied at runtime, and webhook sources cannot carry authentication.
38
+
39
+ ## Registering and managing
40
+
41
+ Captures are discovered with `otp_app:` or listed in the client's `:captures` option. `Chronicle.Captures.Registrar` saves them at `:registered` and again on every reconnect. It skips a capture whose declaration already matches, and warns before saving over a **started** capture whose declaration changed, because saving stops it on the kernel.
42
+
43
+ Manage directly with `Chronicle.Captures.register/1`, `start/1`, `stop/1`, `delete/1`, `list/0`, `validate/1` (a module or raw CDL text) and `captured_events/1`. `start`, `stop` and `delete` also accept a raw capture id; `delete` is destructive, so obtain authority for the specific capture first.
@@ -16,6 +16,12 @@ your app must read its own configuration and pass it in (`lib/chronicle/client.e
16
16
  | `:discover` | `true` |
17
17
  | `:otp_app` | none; falls back to scanning loaded modules |
18
18
  | `:event_types`, `:event_sources`, `:migrations`, `:reactors`, `:reducers`, `:read_models`, `:projections`, `:seeders`, `:webhooks`, `:event_store_subscriptions`, `:global_handlers` | `[]`, merged with discovered |
19
+ | `:constraints`, `:captures`, `:read_model_reactors` | `[]`, merged with discovered where discoverable |
20
+ | `:drift_policy` | `:warn`; `:ignore` or `:fail`. See [discovery-and-generations.md](discovery-and-generations.md) |
21
+ | `:strict_validation` | `false`; `true` raises on event type and migration mistakes instead of logging |
22
+ | `:warn_defaulted_ids` | `true` |
23
+ | `:event_generation_delivery` | `:compatibility`; `:pinned` delivers events at their appended generation |
24
+ | `:concurrency` | none; `[default: :optimistic, check_first_append: bool]`. See [decision-reads.md](decision-reads.md) |
19
25
  | `:default_sink_type_id` | `:mongodb`; accepts `:mongodb`, `:sql`, `:in_memory`, `:none` or a raw string |
20
26
  | `:server_address` | alternative `"host:port"` address (connection option) |
21
27
  | `:skip_tls_validation`, `:load_balancer`, `:grpc_options`, `:reconnect_base_delay`, `:reconnect_max_delay` | forwarded to the connection | — |
@@ -45,7 +45,7 @@ The client uses grpc 1.x and Mint 1.11+, owns its transport, and needs no `GRPC.
45
45
 
46
46
  ## Credential-safe logging is still necessary
47
47
 
48
- Chronicle's own process status redacts credentials, but dependency crash reports can expose gRPC headers, bearer tokens, API keys and Mint HPACK tables. Before starting the client, install the primary Logger filter from the [client crash-report guide](https://github.com/Cratis/Chronicle.Elixir/blob/main/Documentation/connections/crash-reports.md). It matches `Elixir.GRPC.Client`, `Elixir.GRPC.Channel`, and `Elixir.Mint.` report content and replaces reports with a notice.
48
+ Chronicle's own process status redacts credentials, but dependency crash reports can expose gRPC headers, bearer tokens, API keys and Mint HPACK tables. The client itself installs a primary Logger filter (`Chronicle.Connections.ResponseCrashRedaction`) when a connection starts, replacing the Mint response-process termination report, the one shape it can intercept generically, with a one-line notice (requires `cratis_chronicle` 3.13.0). The `ConnectionProcess` and `GRPC.Client.Connection` state can still print credentials, so before starting the client, install the primary Logger filter from the [client crash-report guide](https://github.com/Cratis/Chronicle.Elixir/blob/main/Documentation/connections/crash-reports.md). It matches `Elixir.GRPC.Client`, `Elixir.GRPC.Channel`, and `Elixir.Mint.` report content and replaces reports with a notice.
49
49
 
50
50
  Keep `handle_sasl_reports: false`. This is a safety net, not a guarantee: plain warning strings carrying exit reasons are not covered, and filters do not survive a Logger restart. Restrict production log access and rotate any credential already exposed. Never log connection strings.
51
51