@cratis/pi 7.37.0 → 7.38.1

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 (77) hide show
  1. package/package/corpus/harnesses/copilot/instructions/ai-distribution.instructions.md +109 -0
  2. package/package/corpus/harnesses/copilot/instructions/application-profile.instructions.md +81 -0
  3. package/package/corpus/harnesses/copilot/instructions/capability-is-not-authority.instructions.md +45 -0
  4. package/package/corpus/harnesses/copilot/instructions/code-quality.csharp.instructions.md +92 -0
  5. package/package/corpus/harnesses/copilot/instructions/code-quality.instructions.md +83 -0
  6. package/package/corpus/harnesses/copilot/instructions/code-quality.typescript.instructions.md +90 -0
  7. package/package/corpus/harnesses/copilot/instructions/components.instructions.md +300 -0
  8. package/package/corpus/harnesses/copilot/instructions/concepts.instructions.md +138 -0
  9. package/package/corpus/harnesses/copilot/instructions/cratis-folder-is-configuration.instructions.md +29 -0
  10. package/package/corpus/harnesses/copilot/instructions/csharp.instructions.md +357 -0
  11. package/package/corpus/harnesses/copilot/instructions/dialogs.instructions.md +278 -0
  12. package/package/corpus/harnesses/copilot/instructions/documentation-structure-and-formatting.instructions.md +162 -0
  13. package/package/corpus/harnesses/copilot/instructions/documentation.instructions.md +90 -0
  14. package/package/corpus/harnesses/copilot/instructions/editing-cratis-docs.instructions.md +74 -0
  15. package/package/corpus/harnesses/copilot/instructions/efcore.instructions.md +84 -0
  16. package/package/corpus/harnesses/copilot/instructions/efcore.specs.instructions.md +45 -0
  17. package/package/corpus/harnesses/copilot/instructions/elixir.instructions.md +50 -0
  18. package/package/corpus/harnesses/copilot/instructions/exit-codes-and-wrappers.instructions.md +34 -0
  19. package/package/corpus/harnesses/copilot/instructions/framework.instructions.md +55 -0
  20. package/package/corpus/harnesses/copilot/instructions/frontend-quality.instructions.md +60 -0
  21. package/package/corpus/harnesses/copilot/instructions/frontend-testing.instructions.md +127 -0
  22. package/package/corpus/harnesses/copilot/instructions/git-commits.instructions.md +139 -0
  23. package/package/corpus/harnesses/copilot/instructions/github-actions.instructions.md +87 -0
  24. package/package/corpus/harnesses/copilot/instructions/glossary.instructions.md +63 -0
  25. package/package/corpus/harnesses/copilot/instructions/go-engineering.instructions.md +61 -0
  26. package/package/corpus/harnesses/copilot/instructions/go.instructions.md +116 -0
  27. package/package/corpus/harnesses/copilot/instructions/guards-and-fuses.instructions.md +61 -0
  28. package/package/corpus/harnesses/copilot/instructions/java.instructions.md +138 -0
  29. package/package/corpus/harnesses/copilot/instructions/kotlin.instructions.md +175 -0
  30. package/package/corpus/harnesses/copilot/instructions/local-work-artifacts.instructions.md +56 -0
  31. package/package/corpus/harnesses/copilot/instructions/new-repository-intake.instructions.md +35 -0
  32. package/package/corpus/harnesses/copilot/instructions/orleans.instructions.md +97 -0
  33. package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +403 -0
  34. package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +176 -0
  35. package/package/corpus/harnesses/copilot/instructions/react.instructions.md +203 -0
  36. package/package/corpus/harnesses/copilot/instructions/reactors.instructions.md +336 -0
  37. package/package/corpus/harnesses/copilot/instructions/rtk.instructions.md +48 -0
  38. package/package/corpus/harnesses/copilot/instructions/specs.csharp.instructions.md +147 -0
  39. package/package/corpus/harnesses/copilot/instructions/specs.instructions.md +133 -0
  40. package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md +281 -0
  41. package/package/corpus/harnesses/copilot/instructions/specs.typescript.instructions.md +141 -0
  42. package/package/corpus/harnesses/copilot/instructions/storybook.instructions.md +85 -0
  43. package/package/corpus/harnesses/copilot/instructions/typescript.instructions.md +151 -0
  44. package/package/corpus/harnesses/copilot/instructions/verification-discipline.instructions.md +27 -0
  45. package/package/corpus/harnesses/copilot/instructions/vertical-slices.instructions.md +414 -0
  46. package/package/corpus/harnesses/copilot/instructions/web-fetching.instructions.md +12 -0
  47. package/package/corpus/harnesses/copilot/instructions/writing-correct-examples.instructions.md +57 -0
  48. package/package/corpus/harnesses/copilot/instructions/writing-cratis-docs.instructions.md +72 -0
  49. package/package/corpus/product-sync.json +2 -2
  50. package/package/corpus/skills/cratis-screenplay-discovery/SKILL.md +11 -6
  51. package/package/corpus/skills/cratis-screenplay-discovery/references/facilitation.md +9 -5
  52. package/package/corpus/skills/cratis-screenplay-discovery/references/human-workshop.md +1 -1
  53. package/package/corpus/skills/cratis-screenplay-discovery/references/plotting-and-handoff.md +28 -4
  54. package/package/corpus/skills/cratis-screenplay-discovery/references/storming-loop.md +2 -1
  55. package/package/corpus/skills/cratis-screenplay-discovery/verification.json +5 -0
  56. package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +23 -29
  57. package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-loop.md +15 -1
  58. package/package/corpus/skills/cratis-screenplay-model-authoring/references/model-notes.md +208 -0
  59. package/package/corpus/skills/cratis-screenplay-model-authoring/verification.json +20 -1
  60. package/package/corpus/skills/cratis-screenplay-model-review/SKILL.md +19 -36
  61. package/package/corpus/skills/cratis-screenplay-model-review/references/report-template.md +26 -0
  62. package/package/corpus/skills/cratis-screenplay-model-review/verification.json +7 -0
  63. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/SKILL.md +14 -16
  64. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/core-rules-digest.md +8 -3
  65. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/handoff-template.md +5 -2
  66. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/intake.md +21 -0
  67. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/reasoning-notes.md +10 -6
  68. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/stop-or-assume.md +32 -1
  69. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/worked-example.md +5 -2
  70. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/verification.json +5 -0
  71. package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +4 -0
  72. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.json +1716 -183
  73. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.version +1 -1
  74. package/package/corpus/skills/cratis-screenplay-toolchain/references/versions.md +40 -4
  75. package/package/corpus/skills/cratis-screenplay-toolchain/verification.json +6 -0
  76. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +1 -1
  77. package/package.json +1 -1
@@ -0,0 +1,414 @@
1
+ ---
2
+ applyTo: "**/*.cs, **/*.tsx"
3
+ profile: application
4
+ paths:
5
+ - "**/*.cs"
6
+ - "**/*.tsx"
7
+ ---
8
+ <!-- cratis-ai: generated Copilot instructions. Do not edit; rules/ is the source. -->
9
+
10
+ # Vertical Slice Architecture
11
+
12
+ > **Application profile.** This is the architecture for *applications built on Cratis*. It does **not** apply inside a Cratis framework repo (Arc/Chronicle/Fundamentals/Components) — those are libraries; see [framework.md](./framework.instructions.md).
13
+
14
+ A vertical slice owns a single behavior: the command or query, the events it produces, the projections that build read models, the React component that renders the UI, and the specs that verify it all works. Everything lives together because everything changes together. **The slice is the invariant unit** (command + events + projection + component + specs), created, renamed, and deleted as one.
15
+
16
+ This file is the reference for *what* goes in each part of a slice. Layout, slice types, workflow, and quality gates are in [general.md](../../../rules/general.md).
17
+
18
+ **Model-first check.** Before changing a slice, look for it in the accepted `.play` model (in the committed tree: `git ls-tree -r --name-only HEAD` lists a `.play` file there, narrowed to `-- <root>` when a root is configured; its HEAD version remains the contract until working-tree edits are committed) under the model root (the folder holding the project's `.play` files, or the root set by `mcpServers.screenplay.root` in `.cratis/ai.json`). A `.play` file outside the root or an untracked or uncommitted draft is not a contract. If an accepted model covers it, change the model and render or gap-fill from it; never edit Stage-managed files. See Phase 0 in [application-profile.md](./application-profile.instructions.md).
19
+
20
+ ## Technical stack
21
+
22
+ - .NET / C# (ASP.NET Core) — **Cratis Arc** for CQRS / model-bound commands and queries, **Cratis Chronicle** for event sourcing, MongoDB or EF Core for read models.
23
+ - React + TypeScript (Vite) — Cratis Components 4.x + Arc-generated proxies, MVVM. Vitest + Mocha/Chai/Sinon for frontend specs.
24
+ - xUnit + Cratis.Specifications + NSubstitute for C# specs (`Specification` plus a direct call by default for pure decisions; add the `*Scenario` family only where pipelines, validation, constraints, projections or wiring contribute proof — see [specs.md](./specs.instructions.md)).
25
+
26
+ ## Proxy generation — the build dependency
27
+
28
+ A `dotnet build` with `CratisProxiesOutputPath` set generates a typed TypeScript proxy per command and query next to the slice's `.cs` — the generator target has **no configuration condition**; running it from the Debug build is the house convention (one configuration owns the generated files, so Release never rewrites them differently). The generator reads PDB sequence points only to name output files after their source file, and only when `CratisProxiesUseSourceFileAsOutputFile` is on; Arc's own remark says which types retain that information *can differ* between Debug and Release, which is one more reason to let a single configuration generate. Until the backend compiles, **no proxy files exist** and frontend code cannot reference them. **Backend → build → frontend, always** — backend and frontend for the same slice cannot be done in parallel. When a subsequent Release build is only there to verify the app compiles in that configuration, skip proxy regeneration with `dotnet build -c Release -p:CratisProxiesOutputPath=` rather than letting it re-run the generator against a different compilation. Never edit a generated proxy (`// @generated by Cratis` header); fix the C# source and rebuild.
29
+
30
+ ## Model-bound — no controllers **[contract]**
31
+
32
+ Commands are `[Command]` records with a public instance `Handle()` (Arc analyzers enforce this); queries are `static` methods on `[ReadModel]` records; projections, constraints, and authorization use attributes. Arc generates the HTTP surface — never write an MVC/API controller. Drop to fluent (`IProjectionFor<T>`, `IConstraint`) only when model-bound cannot express the rule.
33
+
34
+ > **[convention]** A single `.cs` per slice and a fixed declaration order are house style for readability — Arc/Chronicle discover by attributes and static methods, not by file or source order. Split the file when a slice grows large or shared concepts move upward.
35
+
36
+ Declaration order (house style): concepts → command + validator (paired) → business rules → constraints → events → read models + queries → projections → reactors. StyleCop SA1201/SA1210 enforce that each `[EventType]` appears **after** the command/validator that constructs it (a [convention] surfaced as a compile warning, not a Chronicle [contract]).
37
+
38
+ A single `<Slice>.cs` therefore contains ALL of: `[Command]` records with `Handle()`, validators, constraints, `[EventType]` records, `[ReadModel]` records with static query methods, projections/reducers, reactors, and slice-specific concepts.
39
+
40
+ ```
41
+ <Slice>/
42
+ ├── <Slice>.cs ← ALL backend artifacts in one file
43
+ ├── <Component>.tsx ← React component(s) for the slice
44
+ └── when_<behavior>/ ← specs
45
+ └── and_<scenario>.cs
46
+ ```
47
+
48
+ **❌ Never split by artifact type** (`Commands/`, `Handlers/`, `Events/` folders) — that is the layered structure the slice replaces.
49
+
50
+ ## Concepts
51
+
52
+ Every domain identifier or named value is a typed record — raw `string`/`Guid`/`int` should not appear on commands, events, or read models for domain values. Primitives stay primitive only when the meaning *is* the base type (`bool`, a plain count, `DateOnly`).
53
+
54
+ - **[contract] Value concepts** derive from `ConceptAs<T>`. The base provides value equality and an implicit **concept → `T`** conversion. **Add your own `T` → concept operator** when call-site ergonomics need it — the base does *not* provide that direction.
55
+ - **[contract] Chronicle stream identities** derive from `EventSourceId<T>` with an `IComparable` underlying primitive. Add `NotSet`, `New()`, or a `T` → derived-id operator only when the domain API justifies it; typed empty/zero values are real specified stream IDs. The base conversion surface does not construct every derived domain record automatically.
56
+
57
+ ```csharp
58
+ public record AuthorId(Guid Value) : EventSourceId<Guid>(Value), IGeneratable<AuthorId>
59
+ {
60
+ public static readonly AuthorId NotSet = new(Guid.Empty);
61
+ public static AuthorId New() => new(GenerateValue.Uuid());
62
+ public static implicit operator AuthorId(Guid value) => new(value);
63
+ }
64
+
65
+ public record AuthorName(string Value) : ConceptAs<string>(Value)
66
+ {
67
+ public static implicit operator AuthorName(string value) => new(value);
68
+ }
69
+ ```
70
+
71
+ `IGeneratable<T>` and `GenerateValue` need Cratis.Fundamentals 7.23.0 or later. Arc and Chronicle can resolve an older version; add a direct reference or keep a plain `static New()` until then (see [concepts.md](./concepts.instructions.md)).
72
+
73
+ **[convention] Placement:** slice-specific concept → in the slice file; feature-shared → feature folder; module-shared → module folder; app-wide → `Common/`. Scan `Common/` and the feature folder before creating a new concept. See [concepts.md](./concepts.instructions.md) for full patterns, or invoke the **cratis-fundamentals-concept** skill.
74
+
75
+ ## Commands
76
+
77
+ The command carries input from the caller. `Handle()` is defined directly on the record — never a separate handler class. For step-by-step creation, invoke the **cratis-arc-command** skill.
78
+
79
+ > This application is event-sourced, so the guidance below assumes a command's `Handle()` returns event(s) that Arc+Chronicle appends. Arc itself is a standalone CQRS framework (see [general.md](../../../rules/general.md)) — a command may legitimately return a response or `void` and do its work through injected services when a slice isn't event-sourced; the return-shape and "never inject `IEventLog`" rules are the event-sourced default, not universal Arc laws.
80
+
81
+ **[convention]** Return immediate external work chosen by a command as a command operation rather than performing it inside `Handle()`; durable after-commit work belongs in reactors. Operations also work without Chronicle; direct service calls remain supported when the decision needs their result. Audit custom `ICommandExecutionScope`s first — every one must implement `ICommandOperationExecutionScope` or operation-bearing commands are rejected. See **cratis-arc-command-operation** (verified at Arc v22.48.1).
82
+
83
+ ### The decision matrix — where each rule lives **[contract]**
84
+
85
+ `Handle()` assumes the command is valid and constructs the event(s). Pick the mechanism by what the decision *is*:
86
+
87
+ | Decision | Use |
88
+ | --- | --- |
89
+ | Reusable value invariant (length, format, range) | `ConceptValidator<T>` on the concept type |
90
+ | Command-input / cross-field / pre-handler rule (incl. injected read-model/service/identity checks) | `CommandValidator<TCommand>` with `RuleFor(...)` |
91
+ | Handler needs fetched/computed data before it can build the event | `Provide()` — fetches the data; may validate it and short-circuit |
92
+ | State-dependent rule that must hold **under concurrency** | `IStreamReads`/`StreamDecision` for one stream, or `[ProtectedDecision]` + `DecisionRead<T>` for admitted projected state; plain injection is advisory |
93
+ | Uniqueness | Chronicle `[Unique]` / `IConstraint` (race-safe) — not a read-model pre-check |
94
+ | Genuinely exceptional failure (bug, missing infra) | `throw` a domain exception |
95
+
96
+ **Do not throw for normal business rejection.** A thrown exception (from `Provide()` or `Handle()`) surfaces as an exception/HTTP 500, *not* a validation result. Recoverable, user-facing rejections are validation: `ValidationResult.Error(...)` via a validator or `Result<,>`. (For step-by-step business-rule placement, invoke the **cratis-arc-command-validation** skill.)
97
+
98
+ ### Nested commands **[contract]**
99
+
100
+ With Arc + Chronicle, ordinary nested pipeline commands join the outer ambient
101
+ transaction even when the call creates a new DI scope. Child success is enrollment,
102
+ not final persistence. Do not promise partial success for an atomic returned batch.
103
+ Independent outcomes require separate top-level executions outside that transaction;
104
+ operation-capable commands reject nesting entirely. See **cratis-arc-command-execution**
105
+ for the versioned boundary and supported alternatives.
106
+
107
+ ### Causation — what a command records **[contract]**
108
+
109
+ A command's **property values** are recorded on the causation of every event it appends, alongside the command's name, so an event says not only which command produced it but what that command was asked to do. The causation is written into the event log and stays there for as long as the events do — a value recorded there cannot be taken back out by changing code.
110
+
111
+ Two markings keep a value off the chain (`[PII]` from Chronicle, `[NotAudited]` from Arc), and both are honored on the property, the declaring type, the positional record parameter, **and the property's type**:
112
+
113
+ | Marking | For | Also does |
114
+ | --- | --- | --- |
115
+ | `[PII]` | personal data | encrypts it in the event, enrolls it in erasure |
116
+ | `[NotAudited]` | a secret that is not personal data — a password or token given as a command input, an API key, a card number | nothing else; it only withholds. A credential (password, token) is never put on an event: store a hash or a reference. A secret the system must retain on an event is also `[Encrypted]` |
117
+
118
+ They are **not interchangeable**: `[PII]` on a password would encrypt it and enroll it in GDPR erasure, which is wrong; `[NotAudited]` on a name does nothing for an erasure request, which is also wrong. An operational secret the system retains on an event (a third-party API key, a company's bank account number) needs **both** `[Encrypted]` (`Cratis.Chronicle.ProtectedValues`, Chronicle 19.32.0+; at rest, no erasure) and `[NotAudited]` (off the chain); either alone leaves a gap. A credential used for authentication is never retained: the command marks it `[NotAudited]` and the event carries only a hash or a reference. Never combine `[PII]` with `[Encrypted]` (`CHR0053`), and neither goes on an event source id (`CHR0034`, `CHR0052`).
119
+
120
+ **Prefer marking the concept**, exactly as with `[PII]` — mark `ApiKey` once and every command taking one is covered:
121
+
122
+ ```csharp
123
+ [Encrypted]
124
+ [NotAudited]
125
+ public record ApiKey(string Value) : ConceptAs<string>(Value);
126
+ ```
127
+
128
+ `[NotAudited]` on the command type excludes every property at once, which is right when a command exists only to carry secrets. The command is still **named** on the chain either way — what is withheld is the values, never the fact that it ran.
129
+
130
+ `ARCCHR0009` warns on a command property whose *name* reads like a secret and is unmarked. It cannot see a secret whose name does not say so, so a clean build means "nothing obvious was missed", not "no secrets are recorded". For a false positive, **suppress the diagnostic** — marking it `[NotAudited]` would silence the warning by withholding a value you wanted recorded.
131
+
132
+ Values render camel-cased; concepts unwrap to the value they hold; a value that is not set is omitted; a long one is truncated (the causation rides on *every* event the command appends).
133
+
134
+ ### `Provide()` — data for `Handle()` **[contract]**
135
+
136
+ `Provide()` runs after authorization and validation, before `Handle()`. Its parameters resolve from DI (like `Handle()`'s); the command instance is `this`. Return one value, or a tuple (Arc binds tuple values to `Handle(...)` parameters by type). Each provided value should be consumed by a `Handle` parameter — an unused one raises the **`ARC0005`** analyzer warning. Use a **named record** when the provided values form a real domain/snapshot concept; a **tuple** when they are just separate inputs to event construction. It may be sync or async.
137
+
138
+ ```csharp
139
+ [Command]
140
+ public record PlaceOrder(OrderId OrderId, CustomerId CustomerId) : ICanProvideEventSourceId
141
+ {
142
+ public EventSourceId GetEventSourceId() => OrderId;
143
+
144
+ public async Task<Result<Customer, ValidationResult>> Provide(IReadModels readModels)
145
+ {
146
+ var customer = await readModels.GetInstanceById<Customer>((EventSourceId)CustomerId);
147
+ return customer is null ? ValidationResult.Error("Customer must exist.") : customer;
148
+ }
149
+
150
+ public OrderPlaced Handle(Customer customer) => new(customer.Name);
151
+ }
152
+ ```
153
+
154
+ Use `Provide()` for IO/fetched/computed data a valid command needs (explicit-key read-model lookups, external services, snapshots). It may short-circuit with `ValidationResult` / `AuthorizationResult` / `Result<TProvided, ValidationResult>` when the supplied data is missing or unusable. Keep IO in `Provide()` and the decision in `Handle()`. Do **not** write a pass-through `Provide()` that only wraps a read model `Handle(...)` could take directly, and do **not** duplicate the same rejection in both the validator and `Provide()` — pick one owner.
155
+
156
+ **[convention]** `Provide()` acquires decision inputs, not the writes moved out of `Handle()`; declare those as operations and let Arc execute them. Operations cannot return receipts into the decision — see **cratis-arc-command-operation**.
157
+
158
+ ### `Handle()` return shapes **[contract]**
159
+
160
+ Return the event(s) directly — Arc appends them; never inject `IEventLog` to append the primary event. If there is no `await`, return the value directly (no `Task<T>`/`Task.FromResult`).
161
+
162
+ | Return | When |
163
+ | --- | --- |
164
+ | `TEvent` | one event on the implicit stream |
165
+ | `(EventSourceId, TEvent)` | command decides the event source id (tuple order doesn't matter) |
166
+ | `(TResponse, TEvent)` | a response value plus an event |
167
+ | `IEnumerable<object>` | multiple events; `EventForEventSourceId(id, @event)` wrappers for cross-stream |
168
+ | `Result<TEvent, ValidationResult>` | success event or a typed validation error; does not itself guard concurrency |
169
+ | `ICommandOperation` / `CommandOperations` | immediate server-side work after event enrollment, never the client response |
170
+ | `void` | no event |
171
+
172
+ **How Arc picks the response vs. metadata:** for an `(A, B)` tuple, each element is checked — operations are server-consumed, and at most one ordinary element without a response-value handler becomes the `CommandResult<...>` response; if all are server-consumed, there is no response; **more than one un-handled element throws**. A `Result<TSuccess, TError>` has its inner value unwrapped and processed by these same rules (so `Result<(TId, TEvent), ValidationResult>` is processed as a tuple). A `(TEvent, Subject)` tuple's `Subject` is treated as **append metadata** (not a response) and overrides the resolved compliance subject. A `(TIdConcept, TEvent)` tuple opens a new stream — the id concept is the event source for that event.
173
+
174
+ ### Stream metadata & DCB concurrency **[contract]**
175
+
176
+ Three attributes scope an append broad→narrow, and each one *also* (only with `concurrency: true`) contributes its dimension to the server-side concurrency check:
177
+
178
+ | Attribute | Meaning |
179
+ | --- | --- |
180
+ | `[EventSourceType("...")]` | the overarching concept the event source *is* (`Account`, `Order`) |
181
+ | `[EventStreamType("...")]` | a concrete process **within** that source type (`Onboarding`, `Transactions`) |
182
+ | `[EventStreamId("...")]` | a marker separating **independent streams** within one stream type (`Monthly`, `2026-06`) |
183
+
184
+ Each attribute always tags the appended events (visible in `EventContext`). **Only when the command also carries `concurrency: true`** does each dimension join the server-side `ConcurrencyScope` (plus the event source id), with the optimistic strategy supplying the expected tail sequence number. A command **without** `concurrency: true` contributes no scope of its own, and the append falls to whatever concurrency strategy the event sequence is configured with — by default the optimistic one, scoped to the event source. So the attribute chooses *which dimensions* bound the check, not whether a check happens at all. Reach for it on high-contention state transitions where a stale read would let two commands both "win" — it narrows the boundary so appends that are genuinely independent stop colliding (concurrency failures surface on `CommandResult`/`AppendResult`). Adopt it deliberately per behavior, not as a blanket default.
185
+
186
+ At Arc v22.75.0, prefer `IStreamReads.ForCommand()` in `Provide()` and return
187
+ `StreamDecision.Append(...)` when deciding over one routed stream. For projected
188
+ state use `[ProtectedDecision]` + `DecisionRead<T>`; plain injection and keyed
189
+ reads are advisory, including in `Handle()`. Protected projections must be
190
+ Chronicle-admitted (direct source key, known events; not reducers, joins, children
191
+ or passive shapes); validators must be parameterless and certified by Arc.
192
+ Exact returned scopes replace automatic `concurrency: true` capture.
193
+
194
+ `[EventStreamId("{ReportingScopeId}:{Period}")]` resolves command property values
195
+ after validation; concepts supply their underlying values. An interface can
196
+ compute an id too. `AmbiguousEventStreamId` rejects any non-NotSet attribute
197
+ value combined with `ICanProvideEventStreamId`, including a template; pick one.
198
+ `[EventSource<TSource>(stream)]` selects a definition-backed route on commands and
199
+ aggregate roots. `EventRoute`/`IEventRoutes` reuse it; explicit wrapper routing
200
+ wins over command context. `[EventTag]`, `ICanProvideEventTags`, returned
201
+ `EventTags` and discovered `ICanProvideCommandEventTags` add tags to returned
202
+ events. `CompleteStream` runs after commit, not atomically with append, and closes
203
+ stream type/id across the log, not just one source. Read the Arc command skill's
204
+ `references/decisions-and-routing.md` for source-pinned contracts and limitations.
205
+
206
+ ### Event-source id resolution order **[contract]**
207
+
208
+ `ICanProvideEventSourceId.GetEventSourceId()` first; otherwise the **first property, in declaration order, that qualifies** — an `EventSourceId`/`EventSourceId<T>`-derived property *or* a `[Key]`-marked one (there is no priority between those two kinds; Arc reports two qualifying properties as `ARCCHR0002` and tells you to implement the interface); otherwise Arc generates a new id. When `ICanProvideEventSourceId` is implemented and returns `null` (or throws), the result is `EventSourceId.Unspecified`, **not** a generated id.
209
+
210
+ ⚠️ **Two attributes are spelled `[Key]`.** Chronicle resolves the key from `Cratis.Chronicle.Keys.KeyAttribute`; `System.ComponentModel.DataAnnotations.KeyAttribute` compiles, reads identically, and does **nothing** in an application that uses Chronicle — every command gets a fresh generated event source id and every read model keyed by it resolves to nothing. Arc's analyzer **`ARCCHR0008`** reports the wrong one; if a `using` pulls in the DataAnnotations namespace, check which `[Key]` you have.
211
+
212
+ ⚠️ **Injected read models resolve ONLY by the command's resolved event-source id** — not by the read-model type, and not by "the property that looks like its key." If the read model you need is keyed by a **different** value, direct injection silently hands you the **wrong** instance — or, when nothing exists for the command's id, `null` (or for a `[Passive]` projection a **default-valued** instance; see *Existence checks* below) — a correctness bug, not a compile error. The three failure shapes: (1) you need a *referenced other* entity (act on A, check B); (2) the command has **multiple** `EventSourceId<T>`-typed properties → ambiguous resolution (never rely on property order); (3) a create command whose id is generated inside `Handle()`. In all three, read by explicit key: `IReadModels.GetInstanceById<T>((EventSourceId)key)`.
213
+
214
+ ### Validators **[contract]**
215
+
216
+ `CommandValidator<TCommand>` (FluentValidation) sits directly below the `[Command]` it validates. **Omit it entirely when there are no rules** — delete an empty subclass. Single-property intrinsic rules belong on `ConceptValidator<T>`, not here. Constructor dependencies (read models, services, identity accessor) are injected and resolved per the command's event-source id.
217
+
218
+ ### Authorization **[contract]/[convention]**
219
+
220
+ Apply authorization attributes on the `[Command]` record or query method. **[convention]** never check roles inside `Handle()` with an `if` — express it as an attribute/policy/command-filter at the boundary; command-specific scope rejection belongs in the validator.
221
+
222
+ ## Events
223
+
224
+ `[EventType]` records are the schema-bound truth of the system.
225
+
226
+ - **[contract] No arguments for new events** — the type name is the identifier. Use `generation:`/id only when evolving an existing contract (invoke the **cratis-chronicle-event-type-migration** skill).
227
+ - **[contract] Past-tense, one-purpose names** (`AuthorRegistered`, not `Updated`/`FormSubmitted`) — Chronicle requires past-tense, single-purpose facts. Self-describing without slice context.
228
+ - **[contract] Avoid nullable properties** — Chronicle's analyzer warns on nullable event members; model optional facts as a separate event, or resolve a nullable command input to a non-null sentinel before constructing the event.
229
+ - **[contract] `Handle()` never returns a nullable event or `null` for "nothing to do"** — Arc treats a `null` result as success with nothing appended, so the caller is told a command succeeded that never happened. Missing state or a failed rule is a rejection: return `Result<TEvent, ValidationResult>` with `ValidationResult.Error(...)`, or reject in a validator or `Provide()`. Optional `ICommandOperation` returns are a separate contract and unaffected.
230
+ - **[contract] Never carry the event-source id** — it's implicit in the event context.
231
+ - **[convention] No `DateTimeOffset`/`DateTime` properties** that duplicate `EventContext.Occurred` — Chronicle records the wall-clock timestamp of every event there. Read it in projections/reducers via `[SetFromContext<T>]` or fluent `.Set(m => m.X).ToEventContextProperty(c => c.Occurred)`. Use `DateOnly` only for a business date the user explicitly supplies.
232
+ - **[convention] Every `[EventType]` has an XML `<summary>`** (Cratis C# doc convention) recording why the event exists and what each value means.
233
+ - **[contract] `[EventType]` lives in its owning slice file** — referenced from other slices via `using`.
234
+
235
+ **Event-design rules (what is and isn't a fact):**
236
+
237
+ - **Events are not read models** — never append events for totals, counts, statuses, search indexes, or dashboards. Project those from source events into read models.
238
+ - **Never duplicate one fact as an aggregate event *plus* a per-item fanout.** If a command fans out per item (one `RequestSentToPartner` per partner), don't also emit an aggregate event carrying the whole list. Keep the fanout as the single source of truth and project any aggregate from it; pick per-item vs. single-with-list event from how consumers read it — never both.
239
+ - **Command rejection normally appends no event.** Validation and business-rule rejections are command results, not facts. Add a rejection/failed event only when the business must audit or react to that failure later.
240
+ - **One event name = one meaning.** Don't reuse a vague event for several domain facts; if facts can occur, be absent, or have consumers independently, split them into separate event types.
241
+ - **Never persist clock-relative state.** "Overdue", "expired", "due in 3 days" are true *now* and false later, and a projection only moves when an event arrives — so a projected `IsOverdue` is wrong the moment the clock crosses the line with no event to flip it. Store the **deadline** (a `DateOnly`/`DateTimeOffset` the business supplied) and compute the comparison at the query or UI edge, refreshing when time alone changes the answer. Only when *crossing* the deadline is itself a business fact (a fee is charged, a reminder goes out) does a scheduled process append an event for it.
242
+
243
+ ```csharp
244
+ /// <summary>Emitted when an author is registered.</summary>
245
+ /// <param name="Name">The author's display name.</param>
246
+ [EventType]
247
+ public record AuthorRegistered(AuthorName Name);
248
+ ```
249
+
250
+ ## Read Models and Queries
251
+
252
+ **[contract]** `[ReadModel]` records expose queries as `public static` methods; dependencies are plain method parameters (no `[FromServices]`). Default to model-bound projection shape; drop to fluent only when needed. For step-by-step creation, invoke the **cratis-chronicle-read-model** skill.
253
+
254
+ Use `[ReadModel(RequireQueryAttribute = true)]` with `[Query]` on intended
255
+ endpoints when public folds/helpers exist (ARC0026/ARCCHR0019). `[Query]` alone
256
+ does not hide siblings; private/compiler-generated statics are never queries.
257
+ A `CancellationToken` is supplied by Arc; concept arguments come from the caller,
258
+ not DI. Bad GET/QUERY argument conversions return 400. Single-instance MongoDB/EF
259
+ observations emit `default` on absence/deletion/filter exit: guard `hasData` and
260
+ `isReady` in React. Verified at Arc v22.75.0; see the read-model query reference.
261
+
262
+ ### Query return shapes **[contract]**
263
+
264
+ Stay within analyzer-supported shapes: the read model, `T?`, a collection/array/`IQueryable<T>` of it, `Task<...>` of those, `IAsyncEnumerable<T>`, or `ISubject<...>` of read model/collection.
265
+
266
+ - **`IQueryable<TReadModel>`** → automatic server-side paging/sorting (frontend `useWithPaging` — see the **cratis-arc-query-paging** skill). Use it whenever a list can grow.
267
+ - **`ISubject<...>`** → live/observable queries. Return it directly — **never** `Task<ISubject<...>>`.
268
+ - **[contract] Custom paths use `[Path("...")]`** (`PathAttribute`), not ASP.NET `[Route]`. Reserve `[Route]` for controller endpoints (which this convention avoids).
269
+
270
+ ```csharp
271
+ [ReadModel]
272
+ [FromEvent<AuthorRegistered>]
273
+ public record Author(AuthorId Id, AuthorName Name)
274
+ {
275
+ public static IQueryable<Author> AllAuthors(IMongoCollection<Author> collection) => collection.AsQueryable();
276
+ public static Task<Author?> AuthorById(IReadModels readModels, AuthorId id) =>
277
+ readModels.GetInstanceById<Author>((EventSourceId)id);
278
+ }
279
+ ```
280
+
281
+ **[convention] No default values on `[ReadModel]` constructor parameters** — omit `= null`, `= default`, `= default!`, `= 0`, `= false`, `= ""`, `= []`. Chronicle writes the projected value on the first event, so a sentinel default is never correct and hides missing projection wiring. The only permitted defaults are enum variants that represent an explicit initial state and `[SetValue<T>]`-driven `bool` flags. Use `T?` for data genuinely absent before the relevant event fires.
282
+
283
+ **[convention] Read-model boundaries & naming.** Read models are shaped for consumers and command policies, not CRUD-style aggregate DTOs. Split one when it starts mixing unrelated lifecycles — the test is coupling (state that changes for different reasons), not size. The event log is already the canonical history/audit trail, so name a model `AuditLog`/`ActivityLog` only when it genuinely is an audit surface; for curated UI projections prefer names like `Timeline`, `WorkSurface`, `Summary`, `Detail`, or `Policy`. (This is "specialization over reuse" applied to read models.)
284
+
285
+ **[convention] Keep query cost bounded.** Prefer slim, purpose-built projections
286
+ for frequently read summaries rather than repeatedly joining broad collections.
287
+ This is not a one-collection framework restriction. Embed bounded children; keep
288
+ growing histories in separately paged timeline models. Evaluate variants when
289
+ lifecycle shapes diverge, and preserve reducer admission/ordering guards when
290
+ considering a projection conversion. Narrow observable inputs at the source.
291
+ Ordinary MongoDB `Observe` retains its initial set and applies changes; do not
292
+ claim it always re-queries the whole collection per event. Explicit recompute
293
+ callbacks have a different cost. See the **cratis-chronicle-read-model** skill's
294
+ query reference for versioned behavior and composition trade-offs.
295
+
296
+ ### Choosing read-model access in command-side code **[convention]**
297
+
298
+ Ladder, first that fits: (1) advisory direct read-model injection — only when keyed by the command's own event-source id; (2) a `[Passive]` projection; (3) `IReadModels.GetInstanceById<T>((EventSourceId)key)` for a different/derived key; (4) a materialized projection. A lookup *interface* is justified **only** for a genuine non-key search (e.g. find-by-email) — never to wrap a keyed `GetInstanceById`.
299
+
300
+ **[contract] Passive state is not sink state.** `IMongoCollection<T>` cannot
301
+ compute a passive read model; any leftover documents are not maintained state.
302
+ Read through Chronicle by key or supported injection. **[convention]** Never
303
+ materialize secret-bearing state just to make a collection query work: project
304
+ safe metadata separately. Count passive types only as inventory, not as a defect;
305
+ evaluate reconstruction frequency, history length, consistency, and security.
306
+
307
+ ### Existence checks — what an absent read model actually resolves to **[contract]**
308
+
309
+ `IReadModels.GetInstanceById<T>` is declared `Task<TReadModel>` (non-nullable) yet hands back `default!` when nothing exists, so the compiler gives callers no signal. What "nothing exists" resolves to depends on the **backing** — *not* on whether the model carries `[RemovedWith<T>]`:
310
+
311
+ | Backing | Never created — no matching event for the key | Removed by a `[RemovedWith<T>]` event |
312
+ | --- | --- | --- |
313
+ | Materialized projection or reducer (**the default**) | `null` | `null` — the sink document is deleted |
314
+ | `[Passive]` **projection** | ⚠️ a **default-valued instance**, never `null` | `null` — the computed state is blanked |
315
+ | `[Passive]` **reducer** | `null` | `null` |
316
+
317
+ `[RemovedWith<T>]` is therefore not the axis: a removed instance is `null` on **every** backing. The single non-`null` case is a **`[Passive]` projection that was never created** — Chronicle computes it on demand and seeds the initial state from the read model's schema, which fills every non-nullable, non-`string` property with its type default. (It answers `null` only when *no* property yields a schema default — all of them nullable, `string`/`object`, or an enum whose `0` is not a declared member — which is why the behavior looks inconsistent between models, not an escape hatch to design for.)
318
+
319
+ ⚠️ **This is invisible when a status enum's `0` is a real state** ("the state every stream starts in"): absent becomes byte-identical to freshly-created, so a command reads never-invited as already-invited. **Renumbering the enum from `1` is not the fix** — the value is then dropped from the payload and deserializes back to CLR `0` on the client anyway. Carry an explicit existence flag instead: `[SetValue<FirstEvent>(true)] bool Exists`, one `[SetValue<T>]` per event that can be the first for the stream. That is exactly the `[SetValue<T>]`-driven `bool` exception the no-defaults convention above allows for. An explicit `NotSet`/`Unknown` zero member also works, but only because the property stays present and readable; the flag is the robust form. ⚠️ **A spec suite will not catch this** — `CommandScenario`'s read-model harness answers `null` for an unseeded event-source id, which matches production for every row above *except* the passive projection. Cover the absent case by seeding a default-valued instance explicitly, not only `null`.
320
+
321
+ **Injected-parameter nullability is the required/optional switch:** a nullable `TReadModel?` parameter (on a validator, `Provide()`, or `Handle()`) gets `null` injected for a never-created/removed instance — check `is null`. A **non-nullable** `TReadModel` whose instance does not exist is **rejected, not crashed**: Arc raises `ReadModelDoesNotExistForCommand`, which surfaces as a validation failure (HTTP 400) with reason `DependencyUnavailable` (non-nullable = "required, must exist"). The server-fault path (`CannotResolveCommandDependency` / `CannotResolveValidatorDependency`, HTTP 500) is reached only when the dependency is *not* a registered read model or no usable key was resolved — the exact contract is in the **cratis-arc-command** skill's `read-model-injection` reference. ⚠️ Both switches ride on the same resolution, so on a **`[Passive]` projection** neither fires for a never-created instance: the nullable parameter receives a default-valued object and the `is null` guard never runs, and the non-nullable one resolves happily. Check the existence flag there, not nullability. Type by-id accessors you write yourself as `Task<T?>` so callers get the compiler signal Chronicle's own signature withholds.
322
+
323
+ ## Projections **[contract]**
324
+
325
+ Projections build read models from **events** (never from other read models). Use the ladder — for adding one to an existing model, invoke the **cratis-chronicle-projection** skill:
326
+
327
+ 1. **Model-bound attributes** on the `[ReadModel]`: `[FromEvent<T>]`, `[SetFrom<T>]`, `[SetFromContext<T>]`, `[ChildrenFrom<T>]`, `[RemovedWith<T>]`, `[Nested]`, `[ClearWith<T>]`, counters, `[FromAll]`.
328
+ 2. **Fluent `IProjectionFor<T>`** for joins, composite/constant keys, parent-key extraction, context mapping, `.NotRewindable()`, or supported conditional setters. Declarative metadata only — no DI side effects, no imperative `Define()` body, builder lambdas are member access only.
329
+ 3. **Reducer `IReducerFor<T>`** when the model is "current state + event → next state" — branching on prior state, loops, calculations. A valid style, **not** a failure mode; carry an inline justification comment naming the limitation that ruled out a projection.
330
+
331
+ - **[contract] AutoMap is on by default — never call `.AutoMap()`.** Match property names so AutoMap wires them; diverge with `[SetFrom<T>]` / `.Set().To()` only for genuine name differences. Re-enable `.AutoMap()` only inside a scope disabled with `.NoAutoMap()`.
332
+ - ⚠️ **AutoMap runs per *event* across every event the projection references — it does not defer to your explicit setters.** A property sourced with `[SetFrom<A>]` can still be silently overwritten when some *other* referenced event `B` happens to carry an identically named property (AutoMap wires `B`'s value on top). Fence the property with property-level **`[NoAutoMap]`** (which pairs with the `[SetFrom]`, not replaces it) so only your explicit setter writes it. `[SetFrom]` does **not** imply this — leaving AutoMap on is what allows the common create-then-rename pattern (a later event updating a property by name-match). No colliding event property name means no `[NoAutoMap]` needed. (An event subscribed only via an aggregate like `[Count]` doesn't auto-map its other properties, so it can't collide.) The analyzer **`CHR0025`** surfaces this collision as an *informational* heads-up (not a warning, because either resolution — add `[NoAutoMap]`, or accept the update — can be correct).
333
+ - **[contract] `[Nested]`** projects a single nullable child object; place `[FromEvent<T>]` on the nested type (or property-level `[SetFrom<T>]` when the parent shares the event — declaring class-level `[FromEvent<T>]` on both parent and nested type for the same event is a duplicate-registration startup crash).
334
+ - **[contract] Event-sequence source attributes:** class-level `[EventSequence("name")]`, `[EventLog]`, or `[EventStore("name")]` select where the projection reads from (a named sequence, the explicit default log, or another Chronicle event store / inbox). ⚠️ `[FromEventSequence]` is **removed** — use `[EventSequence("name")]`.
335
+
336
+ ## Constraints **[contract]**
337
+
338
+ Append-time invariants enforced by the Chronicle Kernel; Arc converts violations to validation errors on command appends.
339
+
340
+ - **`[Unique]`** on an event property (across event sources) or event class (one per source) — the default.
341
+ - **`[RemoveConstraint("name")]`** on a removal event to release a claimed value.
342
+ - **`IConstraint`** for multi-event, differing property names, case-insensitive (`.IgnoreCasing()`), or custom messages. Declarative metadata only — no DI side effects, member-access lambdas.
343
+
344
+ Group multi-event constraints under a shared name constant (`<Module>ConstraintNames`). Treat violations as validation results — never as thrown exceptions.
345
+
346
+ ## Reactors **[contract]**
347
+
348
+ `IReactor` is a marker interface; dispatch is by the first parameter type. Reactors live in Automation slices (reacting to events, calling external systems, returning follow-up commands/events) and in Translation slices that record outside data as our facts. See [reactors.md](./reactors.instructions.md) for full rules, or invoke the **cratis-chronicle-reactor** skill.
349
+
350
+ ```csharp
351
+ public Task AuthorRegistered(AuthorRegistered @event, EventContext context) => ...
352
+ ```
353
+
354
+ - **Signatures:** the first parameter is the event and drives dispatch; every parameter after it is resolved as a dependency — `EventContext`, a read model, a service, a `ReactorDelivery` — with no limit on how many. Return `Task`, `void`, or a synchronous side-effect return type. Prefer `Task`/async for real side effects, but sync returns are supported.
355
+ - **[convention] `[OnceOnly]`** on any non-idempotent side effect (emails, payments, external writes) — replay re-fires otherwise. It covers **replay only**; a recovered failed partition still re-delivers, so guard that with a `ReactorDelivery` receipt.
356
+ - **Side-effect events:** return them directly as event objects (single `TEvent`, `IEnumerable<object>`, or `Task<...>` thereof) — Chronicle appends them to the triggering event source. To target a different event source/stream, return `EventForEventSourceId(id, @event)` wrappers (the same cross-stream mechanism as a command — see [reactors.md](./reactors.instructions.md)); the wrapper is self-describing, so set the stream type, source type or `Subject` on it when you need full metadata control. Don't inject `IEventLog` for the normal path; reserve it for explicit append-result/wait/options/mixed-target workflows.
357
+ - **Commands from a reactor:** **return** the `[Command]` (or a collection of commands) from a `[OnceOnly]` handler — Arc executes it through the full validation/authorization/handler pipeline and a denied or invalid result fails the partition rather than disappearing. A reactor has no principal, so a `[Roles]`-gated command needs `[ExecuteCommandsAsSystem("<role>")]` on the reactor class. The imperative `ICommandPipeline.Execute(command)` remains available (`[OnceOnly]`, inspect the `CommandResult`, supply your own execution context — see the **cratis-arc-command-execution** skill). Don't reach for `IEventLog` to change state in another slice. Details and the analyzer: [reactors.md](./reactors.instructions.md).
358
+ - **[convention] Stateless, idempotent, read state via Chronicle read models** (`GetInstanceById`), not `IMongoCollection<T>`. Don't throw to "validate" malformed inbound events — that pauses the partition and can quarantine the observer; record an explicit failure/dead-letter event instead.
359
+
360
+ ## Compliance (PII)
361
+
362
+ **[contract]** Chronicle encrypts `[PII]`-marked values per subject; decryption is transparent to observers and queries (read models injected into a `CommandValidator<T>` or `Handle()` decrypt transparently under the command's resolved subject before validation/handler logic runs).
363
+
364
+ - Prefer **concept-level `[PII]`** (on the `ConceptAs<T>` type) so the marker travels everywhere automatically; use property-level `[PII]` only when a value is personal in one event context but not everywhere.
365
+ - `[PII]` also keeps the value **off the causation chain** when it is a command property — see [Causation](#causation--what-a-command-records-contract). A retained operational secret that is not personal data needs `[Encrypted]` plus `[NotAudited]` instead; `[PII]` is the wrong tool for it, and a password is never retained at all (store a hash).
366
+ - **`[PII]` is found at any nesting depth, and may mark a whole value object.** A `[PII]` concept inside a value object is encrypted where it sits (siblings stay readable). Marking the **value object type** (or a property typed as one) with `[PII]` classifies every value it holds — Chronicle pushes the marker down to the individual values, so the document keeps its shape and each value is separately encrypted rather than fused into one blob.
367
+ - Projection-backed read models inherit PII lineage automatically — don't add `[PII]` to their properties. Reducer-backed models: a `[PII]`-concept-typed property is detected automatically; add property-level `[PII]` only for a primitive/non-PII-concept property populated from PII source data.
368
+ - ⚠️ **`[PII]` cannot be applied to `EventSourceId`/`EventSourceId<T>`** — Chronicle throws `PIINotSupportedOnEventSourceId` at runtime because the identifier is the encryption-key lookup. If the identifier itself is sensitive, use a random `Guid`-backed surrogate as the event source id and store the sensitive value in a `[PII]` property.
369
+ - **PII inside a list/array element IS encrypted.** The compliance walk descends into nested objects *and* into array elements, so a `[PII]` concept in an `IReadOnlyList<T>` and a `[PII]` member of an element object are both encrypted per element. `[PII]` on the *list property itself* is different: it blob-encrypts the whole collection as one value, protecting it but making per-element values unqueryable — reach for that only when you never need to query inside the collection. For list-valued PII that must stay queryable, prefer a **person-scoped** read model joined at the query edge.
370
+ - ⚠️ **Do not declare a read-model property named `_subject`** — Chronicle reserves that field in MongoDB for internal key tracking. After right-to-erasure key deletion, `[PII]` properties return an **empty** value (no exception is thrown).
371
+
372
+ ### Subject resolution **[contract]**
373
+
374
+ The compliance **subject** is the encryption-key identity. It is resolved first-match-wins:
375
+
376
+ | Priority | Mechanism | Resolved by |
377
+ | --- | --- | --- |
378
+ | 1 | `Subject` as a tuple element returned from `Handle()` (computed in the handler) | Arc |
379
+ | 2 | `ICanProvideSubject.GetSubject()` on the command | Arc |
380
+ | 3 | a `Subject`-typed property, or a `[Subject]`-marked property, on the **command** (any other type is converted with `ToString()`) | Arc |
381
+ | 4 | a `[Subject]`-marked property on the **event** being appended | Chronicle client |
382
+ | 5 | otherwise the **event source id** of the append — for a command, whatever its event-source-id resolution produced (`ICanProvideEventSourceId` → `EventSourceId<T>` property → `[Key]`) | Chronicle kernel |
383
+
384
+ So a command whose identity is an `EventSourceId<T>` needs no attribute at all: with nothing marked, that identity is the subject. Mark `[Subject]` only when the person is *not* the stream — and never on the `EventSourceId<T>` itself (`CHR0026`).
385
+
386
+ `ICanProvideSubject` lives in `Cratis.Chronicle.Events` (alongside `ICanProvideEventSourceId`/`ICanProvideEventStreamId`). `[Subject]` is *not* aggregate identity. A managed read-model document has **one** subject — never mix multiple people's PII in one document; split into person-scoped models.
387
+
388
+ ### Redaction **[contract]**
389
+
390
+ Redaction is a system-event request that removes payload availability from the event log: it replaces the selected event payload in place, preserves the original sequence slot and event context, and records an **`EventRedacted`** marker. It is **not domain undo** — if business state must change, emit a domain event before/alongside the compliance operation. Rewind/rebuild replayable observers after redaction, and make reactor/projection code that consumes broad streams tolerate (filter or no-op on) `EventRedacted`.
391
+
392
+ ## Cross-slice patterns **[convention]**
393
+
394
+ Cross-slice access is **read-only and goes through Chronicle** — inject another slice's read model (validator/`Provide()`/`Handle()`), read by explicit key with `IReadModels.GetInstanceById`, mark a `[Passive]` projection for command-side decisions, or react to its events. **Never** instantiate or DI another slice's command/handler/service.
395
+
396
+ **DCB (Dynamic Consistency Boundary):** the boundary is the event state explicitly
397
+ enrolled in guarded decisions, not every model a command reads. Use stream
398
+ reads/exact scopes or `[ProtectedDecision]` + admitted `DecisionRead<T>`;
399
+ plain validator/`Provide()`/`Handle()` injection is advisory. Stream-level
400
+ `concurrency: true` scopes are another explicit boundary, not protection for an
401
+ arbitrary snapshot. Specify conflicts with `UseDecisionReads()` and actual seeded
402
+ events, not pinned models.
403
+
404
+ ## Dialogs
405
+
406
+ Frontend commands run through the Cratis dialogs — never a vendor or hand-rolled modal. Use `CommandDialog` (executes a command) from `@cratis/components/CommandDialog` or `Dialog` (data collection) from `@cratis/components/Dialogs`. See [dialogs.md](./dialogs.instructions.md).
407
+
408
+ ## See also
409
+
410
+ - [general.md](../../../rules/general.md) — layout, slice types, workflow, quality gates.
411
+ - [concepts.md](./concepts.instructions.md) — `ConceptAs<T>` / `EventSourceId<T>` full patterns.
412
+ - [reactors.md](./reactors.instructions.md) — reactor signatures and side-effect rules.
413
+ - [specs.md](./specs.instructions.md) — `CommandScenario` / `EventScenario` / `ReadModelScenario` / `ReactorScenario`.
414
+ - skills: **cratis-chronicle-event-modeling**, **cratis-arc-command**, **cratis-chronicle-read-model**, **cratis-fundamentals-concept**, **cratis-chronicle-projection**, **cratis-chronicle-reactor**, **cratis-arc-command-validation**, **cratis-chronicle-event-type-migration**, **cratis-arc-command-execution**, **cratis-arc-query-paging**, **cratis-chronicle-multi-tenancy**.
@@ -0,0 +1,12 @@
1
+ ---
2
+ applyTo: "**/*"
3
+ description: "Use when fetching data from the web, CI logs, artifact URLs, signed URLs, Azure Blob URLs, or simple API/text responses. Prefer curl in the terminal over webpage fetch tools for raw data retrieval."
4
+ ---
5
+ <!-- cratis-ai: generated Copilot instructions. Do not edit; rules/ is the source. -->
6
+
7
+ # Web Fetching
8
+
9
+ - Prefer `curl` in the terminal for raw remote content such as CI logs, artifact downloads, signed URLs, plain-text endpoints, and JSON APIs.
10
+ - Use webpage/content fetch tools only when the goal is to summarize or inspect rendered page content rather than retrieve the exact response body.
11
+ - For expiring, authenticated, or redirecting URLs, default to `curl -L -s` and then pipe to `head`, `grep`, `sed`, or `jq` as needed.
12
+ - When debugging remote responses, keep the raw output in the terminal path and filter locally instead of depending on fetch tools.
@@ -0,0 +1,57 @@
1
+ ---
2
+ applyTo: "**/Documentation/**/*.{md,mdx}"
3
+ paths:
4
+ - "**/Documentation/**/*.md"
5
+ - "**/Documentation/**/*.mdx"
6
+ ---
7
+ <!-- cratis-ai: generated Copilot instructions. Do not edit; rules/ is the source. -->
8
+
9
+ # Writing Correct Code Examples (Technical Docs)
10
+
11
+ Documentation code examples are **copied verbatim** by evaluators. A snippet that uses an API that doesn't exist is worse than no snippet — it breaks on first paste and loses trust. An audit of these docs found **~12 fabricated-API bugs** that had passed review and shipped. The discipline below is how you avoid adding the thirteenth.
12
+
13
+ ## The rule: verify every framework API against real source — before you write it
14
+
15
+ For each framework type, attribute, method, prop, hook, or import in an example, confirm it exists and has that exact shape in **source**, not in another doc page (the docs themselves had the bugs):
16
+
17
+ - **C# / backend** — grep real usage in a reference application (e.g. Cratis **Studio**) and the product `Source/` trees of the Cratis repos checked out alongside this one (`Arc/Source`, `Chronicle/Source`). For extension methods, find the `public static … (this <Type> …)` signature and note **which type it extends**.
18
+ - **React / Components** — the authoritative prop names are in the compiled type defs of the installed package (`node_modules/@cratis/components/dist/esm/**/*.d.ts`) or the `Components` source `dist`. Real usage: a reference app's `*.tsx`.
19
+ - **Invented *domain* names are fine** (event/concept/command names like `AuthorRegistered`, `BookId`). Only **framework APIs** must be real. Never invent a framework interface, attribute, prop, method, or import path.
20
+ - **Read the source at the version the reader runs, never at a checkout's `HEAD`.** A sibling clone's working tree is whatever someone last checked out; the package the reader installed is a tag. Read with `git show <tag>:<path>` and `git grep <pattern> <tag> -- <path>` — both work without touching the checkout, so a dirty or shared worktree is never a reason to skip the check. Name the repository and the tag in the claim ("`Arc v22.16.0`, `ParameterDependencyResolver.cs:44-62`") so the next reader can re-run it. Where the corpus states a version in a skill's *Verified product sources* table, that tag is the one to read; when a consumer's pin is newer, re-read at theirs. A release note is a reason to look, not evidence that a behavior changed or that a workaround can be retired — retire a workaround only after the original reproduction passes at the new tag.
21
+
22
+ ## Choose a maintained source for the example
23
+
24
+ For a long example or threaded tutorial, derive displayed snippets from a
25
+ compiling, tested sample or spec when tooling supports extraction; otherwise
26
+ compare each block to that source and check both together. For a short,
27
+ illustrative excerpt, write purpose-built code but verify its framework APIs
28
+ against the product source at the supported version. Do not forbid copying
29
+ from a runnable sample: copying *without a check that prevents drift* is the
30
+ problem. Multi-client pages use their client-owned snippet sources and checks;
31
+ never hand-translate an unsupported SDK. See the **cratis-technical-examples**
32
+ skill for the workflow.
33
+
34
+ ## Complete and correct
35
+
36
+ - No pseudo-code, no `// ...` elisions that leave the reader guessing, no props/members that don't exist.
37
+ - A standalone snippet should compile with its stated prerequisites. Label an excerpt as an excerpt and supply or link the domain types it assumes.
38
+ - Show the run command and an observable result for a substantial sample; a passing site build does not prove behavior.
39
+
40
+ ## Historical API pitfalls to recheck
41
+
42
+ These reminders are not versioned API evidence and must not be copied as an
43
+ unchecked contract. Before using one in a new example, resolve the reader's
44
+ package version and verify the exact receiver, signature and source path at
45
+ that tag. Recheck affected examples on product version changes; remove a
46
+ workaround only after its reproduction passes. The source check above, not
47
+ this list, establishes which shape the target version supports.
48
+
49
+ - Commands/queries are **model-bound**: a `[Command]` record with `Handle()` **on the record**, and `[ReadModel]` records with **static** query methods. The marker/handler interfaces `ICommand`, `ICommandHandler<T>`, `IQuery<T>`, `IQueryHandler<T,R>` **do not exist** — never use them.
50
+ - Bootstrap: `ArcApplication.CreateBuilder(args)` (not `ArcApplicationBuilder.CreateBuilder`). `builder.AddCratisArc()` on the builder (`WebApplicationBuilder`/`IHostBuilder`); `app.UseCratisArc()` on the built app and it takes **no args** (the listen URL comes from `ArcOptions.Hosting.ApplicationUrl`).
51
+ - Read the current user inside `Handle()` by injecting **`IHttpContextAccessor`** and reading `HttpContext?.User` (`ClaimsPrincipal`). There is no `CommandContext.User` and no `IUserAccessor` Arc type. In-`Handle` guards return **`Result<TEvent, ValidationResult>`** (success type first, error type second) + `ValidationResult.Error(...)` — there is no `CommandResult.Forbidden`/`Unauthorized` to return.
52
+ - Components: `DataPage` uses the **compound** `DataPage.Columns` / `DataPage.MenuItems`; the detail prop is **`detailsComponent`** (lowercase) — `detailsTitle`/`initialSizes` are **not** props. Import `DataTableForObservableQuery` from `@cratis/components/DataTables` (the root barrel only re-exports namespaces); `DataPage`/`MenuItem` from `@cratis/components/DataPage`. Required props like `emptyMessage`/`title` must be present.
53
+ - Chronicle model-bound projections use property attributes **`[SetFrom<T>]`** / **`[SetValue<T>]`** (and `[FromEvent<T>]` AutoMap) — **not** `static On(event)` methods (that shape does not exist). Retrieve a read model with `eventStore.ReadModels.GetInstanceById<T>(id)`. Assertion signatures depend on the extension receiver: the out-of-process `IChronicleSetupFixture` extension is `ShouldHaveAppendedEvent<TEvent>(sequenceNumber, eventSourceId, validator)`, while Arc's in-process `CommandScenario<TCommand>` extensions use `<TCommand, TEvent>` with the event-source id and optional predicate. Verify the receiver type and its exact extension signature before copying either shape.
54
+
55
+ ## Auditing at scale
56
+
57
+ Re-run a snippet-correctness audit periodically — it keeps finding bugs (the list above came from three rounds). Delegate the cross-checking to subagents that compare each snippet to source and report only confirmed discrepancies; **verify each finding against source yourself before fixing**. Consider adopting an automated example tester (**Doc Detective**, **Squidler** — from awesome-docs) that actually runs the snippets, so correctness is enforced by CI rather than by hand. The principle, from jvns's "write good examples by starting with real code": derive examples from working source, don't compose them from memory.
@@ -0,0 +1,72 @@
1
+ ---
2
+ applyTo: "**/Documentation/**/*.{md,mdx}"
3
+ paths:
4
+ - "**/Documentation/**/*.md"
5
+ - "**/Documentation/**/*.mdx"
6
+ ---
7
+ <!-- cratis-ai: generated Copilot instructions. Do not edit; rules/ is the source. -->
8
+
9
+ # Writing Cratis documentation — tour voice and Starlight authoring
10
+
11
+ The Cratis docs must **take the reader on a tour, like a teacher** — the way [Marten](https://martendb.io), [Wolverine](https://wolverinefx.net), and [aspire.dev](https://aspire.dev) docs do — **not** state facts like a reference dump. The differentiator is pedagogical structure, not decoration. Match it.
12
+
13
+ ## The bar
14
+
15
+ - **Pain → relief.** Open by naming the friction the reader feels, then reveal the feature as the relief.
16
+ - **Why before how.** A reader who understands the reasoning handles edge cases the docs do not cover.
17
+ - **Active voice, present tense, second person.** “You append the event,” not “the event is appended.”
18
+ - **Be honest about limits.** A “when this is the wrong fit” section builds more trust than omitting the limits.
19
+
20
+ ## One primary purpose per page
21
+
22
+ | Type | Reader is… | Reads like |
23
+ |---|---|---|
24
+ | **Tutorial** | learning by doing | a guided lesson — each step produces a visible result |
25
+ | **How-to** | solving a specific problem | a recipe — assume competence, no teaching |
26
+ | **Explanation** | trying to understand | a discussion — concepts, trade-offs, *why*, a diagram |
27
+ | **Reference** | looking something up | a dictionary — exhaustive, terse, tables/signatures |
28
+
29
+ Use the page's primary reader need to guide its structure, not to ban a brief prerequisite or explanation. A tutorial padded with a reference dump overwhelms; a how-to interrupted by a long conceptual detour stops being a recipe. Link out when the other material deserves sustained attention. Diátaxis does not imply a universal navigation bucket; bucket names are product-specific.
30
+
31
+ ## The tour-voice checklist
32
+
33
+ Apply this checklist to tutorials, getting-started pages, and explanations:
34
+
35
+ 1. **Open with a concrete scenario**, not a definition of the tool.
36
+ 2. **Name the friction first**, then the feature as its relief.
37
+ 3. **Make the first snippet the convention path.** Show the shape that works without registration or wiring a convention already handles (no manual `.AutoMap()`, no hand-registered handlers). A first example that configures what a convention does teaches the reader to distrust the convention.
38
+ 4. **Use chronological verbs** such as define → append → project → query.
39
+ 5. **After every code block, explain the invisible**: what happens under the hood and why it matters. Verify that explanation against the setup the reader actually built; a backend without Chronicle appends no events, so "the command appended an event" would be false there.
40
+ 6. **Recap before pivoting** to the next concept.
41
+ 7. **Anticipate a likely mistake** near the example it affects. Use a caution aside where the risk would otherwise be missed, such as a convention that fails silently (proxy generation, service lifetimes, AutoMap): show the working shape and a verified recovery path, and name affected versions only when the product source establishes them.
42
+ 8. **Show the result** — output, a resulting model, or another visible success signal.
43
+ 9. **Organize by workflow**, not alphabetically.
44
+ 10. **End each substantial section with the natural next step** when one exists.
45
+
46
+ Read a current, well-reviewed tutorial in the product or a closely related product before writing; do not assume one product's domain vocabulary fits every other product.
47
+
48
+ ## Use presentation to support the tour
49
+
50
+ Choose the simplest authoring surface that preserves the reading flow. Use steps for real procedures, tabs for genuine alternatives, asides for meaningful context or risk, and diagrams for non-trivial flows. Do not turn sequential cause-and-effect examples into tabs merely because they use different languages; hiding one side can make the explanation harder to follow.
51
+
52
+ Full-stack type safety is a differentiator, so show both the backend contract and generated frontend shape when both matter. Use `FullStackTabs` only when each pane remains understandable independently.
53
+
54
+ The raw Markdown mirror behind page actions such as “Copy Markdown” comes from synchronized Markdown/MDX rather than rendered HTML. Converter rewrites and normalized frontmatter are present, but component imports and JSX remain visible. Prefer plain Markdown unless a component adds real teaching value.
55
+
56
+ The exact Markdown/MDX boundary, aside semantics, component contracts, import paths, and rendering checks live in [Documentation Structure and Formatting](./documentation-structure-and-formatting.instructions.md). Do not duplicate or infer that rendering API here.
57
+
58
+ ## Two voices, connected products
59
+
60
+ - **Two voices per area:** the toured/educational layer and the terse, exhaustive reference. Narrative pages link *down* into the reference; the reference stays a dictionary.
61
+ - **Connect at the seams** rather than re-explaining. Show how neighboring products meet in the user's workflow and link to the glossary for shared terms.
62
+ - **Coming-from-X bridges** map new concepts to what the reader already knows without organizing the whole product around a competitor. Give each the same shape: the reader's current code, the Cratis equivalent beside it, then a short "what changed and why" list. Write one page per source technology. Keep the other technology's code accurate at a named version, or label it illustrative; state the trade-offs fairly and say when the reader's current approach remains the better fit.
63
+
64
+ ## Before you call a page done
65
+
66
+ - Verify every framework API in a code example against real source — see [Writing Correct Code Examples](./writing-correct-examples.instructions.md). Readers paste snippets verbatim.
67
+ - The owning repository's local documentation gate passes when one exists; when available, the sibling Documentation site's full check has zero hard lint errors and zero broken rendered links attributable to the change.
68
+ - For a visual page, preview it and inspect light **and** dark screenshots using the owning site's screenshot workflow.
69
+
70
+ Study the **aspire.dev** docs for strong Starlight information architecture and tour writing.
71
+
72
+ The edit/sync/verify loop and source ownership live in [Editing Cratis Documentation](./editing-cratis-docs.instructions.md).
@@ -166,9 +166,9 @@
166
166
  }
167
167
  ],
168
168
  "synced": {
169
- "version": "4.142.0",
169
+ "version": "4.144.0",
170
170
  "date": "2026-10-12",
171
- "by": "Cratis/AI#597"
171
+ "by": "Cratis/AI#627"
172
172
  },
173
173
  "covers": {
174
174
  "skills": [