Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions Documentation/constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,27 @@ Constraints are shared Chronicle behavior. The shared docs explain the consisten

## Kernel-free scenario coverage

`EventScenario` and its `ReactorScenario` input use the production constraint compiler. When a scenario selects event types but leaves constraints to default discovery, it compiles the union of globally discovered and selected event constructors (once per constructor), with globally discovered fluent constraints, before keeping definitions referencing a selected event type (including removal events). A selected constructor still contributes its decorators after the discovery registry is cleared. If discovery and selection contain different constructors for the same event type ID and either carries constraint decorators, or a kept definition references an event outside the selected catalog or uses unsupported behavior, the scenario rejects with `UnsupportedEventSequenceOperation` rather than partially simulating it. Constraint-free shadowed IDs use the selected constructor. The pinned-kernel `constraints.json` fixture proves unscoped, case-sensitive unique values for a **single string property** (nonempty ASCII letters and spaces), and one covered unique event type per constraint (decorated or fluent `uniqueFor`). The isolated `constraints-key-domain.json` fixture extends single-property keys to the string and boolean domains below, with a shared definition across event types. It checks same-source reclaims, conflicts across sources and event types, case and whitespace distinctions, default property messages for in-batch and post-reclaim conflicts, configured-message resolution from raw kernel wire violations, and atomic rollback. It proves violation order across different events in a batch, not multiple definitions on the same event; overlapping definitions reject at scenario construction. `constraints-property-lifecycle.json` and `constraints-property-covered-removal.json` additionally prove owner-based replacement and release, two removal event types, a fieldless removal event, and removal events that also claim a key. Include **all** claiming and removal types in the selected catalog; unknown removal names reject instead of silently doing nothing. Leave constraints enabled when testing these behaviors: `new EventScenario({ artifacts: { eventTypes: [Registered] } })`. `constraints-event-type-siblings.json` and `constraints-event-type-cycles.json` prove two unique-event-type removal cycles, each installed as the only definition: two covered types with two removers, and three covered types with three removers where one covered type is also a remover. See [Testing](./testing.md) for the cycle table. `constraints-composite.json` proves fluent composite keys of two and three string properties: the kernel joins the values with `-` in declared order, so `['a-b', 'c']` and `['a', 'b-c']` collide, and a collision reports one violation per property. `constraints-ignore-casing.json` proves fluent `.ignoreCasing()` for string keys in the ASCII key domain: the joined key is lowercased before comparison, and violation details keep the original casing. Non-ASCII keys under `ignoreCasing`, scoped, other composite, unique-event-type shapes and other unproven definitions reject with `UnsupportedEventSequenceOperation`; use a kernel-backed test. `ReactorScenario` uses the same input boundary, so rejected events do not reach its handlers. See [Testing](./testing.md) for the exact supported boundary and example.
`EventScenario` and its `ReactorScenario` input use the production constraint compiler. When a scenario selects event types but leaves constraints to default discovery, it compiles the union of globally discovered and selected event constructors (once per constructor), with globally discovered fluent constraints, before keeping definitions referencing a selected event type (including removal events). A selected constructor still contributes its decorators after the discovery registry is cleared. If discovery and selection contain different constructors for the same event type ID and either carries constraint decorators, or a kept definition references an event outside the selected catalog or uses unsupported behavior, the scenario rejects with `UnsupportedEventSequenceOperation` rather than partially simulating it. Constraint-free shadowed IDs use the selected constructor. The pinned-kernel `constraints.json` fixture proves unscoped, case-sensitive unique values for a **single string property** (nonempty ASCII letters and spaces), and one covered unique event type per constraint (decorated or fluent `uniqueFor`). The isolated `constraints-key-domain.json` fixture extends single-property keys to the string and boolean domains below, with a shared definition across event types. It checks same-source reclaims, conflicts across sources and event types, case and whitespace distinctions, default property messages for in-batch and post-reclaim conflicts, configured-message resolution from raw kernel wire violations, and atomic rollback. It proves violation order across different events in a batch, not multiple definitions on the same event; overlapping definitions reject at scenario construction. `constraints-property-lifecycle.json` and `constraints-property-covered-removal.json` additionally prove owner-based replacement and release, two removal event types, a fieldless removal event, and removal events that also claim a key. Include **all** claiming and removal types in the selected catalog; unknown removal names reject instead of silently doing nothing. Leave constraints enabled when testing these behaviors: `new EventScenario({ artifacts: { eventTypes: [Registered] } })`. `constraints-event-type-siblings.json` and `constraints-event-type-cycles.json` prove two unique-event-type removal cycles, each installed as the only definition: two covered types with two removers, and three covered types with three removers where one covered type is also a remover. See [Testing](./testing.md) for the cycle table. `constraints-composite.json` proves fluent composite keys of two and three string properties: the kernel joins the values with `-` in declared order, so `['a-b', 'c']` and `['a', 'b-c']` collide, and a collision reports one violation per property. `constraints-ignore-casing.json` proves fluent `.ignoreCasing()` for string keys in the ASCII key domain: the joined key is lowercased before comparison, and violation details keep the original casing. `constraints-scopes.json` proves all seven scope combinations for case-sensitive single-string property keys, singleton event-type constraints and two-type/two-remover cycles, with one definition installed at a time. Scope-local ownership, replacement/removal, default and custom single/batch routes, case-sensitive matching and atomic rollback are compared through the packaged client and TypeScript protobuf paths. Non-ASCII keys under `ignoreCasing`, unproven scoped, composite or unique-event-type shapes and other unproven definitions reject with `UnsupportedEventSequenceOperation`; use a kernel-backed test. `ReactorScenario` uses the same input boundary, so rejected events do not reach its handlers. See [Testing](./testing.md) for the exact supported boundary and example.

| Unique-property key | In-process support |
| --- | --- |
| Case-sensitive strings | Empty, spaces and padded strings; ASCII letters/digits and `.`, `@`, `_`, `-`, `:`, `\|`, `{`, `}`, `$` (including email-like strings); plus `é` (U+00E9). Spaces are significant. Other valid event-content punctuation is not necessarily a supported constraint key. |
| Schema-backed booleans | `true` and `false` become kernel strings `True` and `False`; boolean `true` conflicts with string `"True"`, not with `"true"`. Violation details retain the kernel spelling. |
| Ownership and release | A successful claim replaces the same source's previous value, releasing the old value to other sources. `@removeConstraint('Name')` or fluent `removedWith(RemovalEvent)` releases **that source's** claim, even if the removal event has no fields; removing an absent claim does nothing. A covered event that also removes must pass uniqueness validation first, then releases instead of saving. |
| Atomic batches | Validation checks the pre-batch index and earlier claims, without releasing property claims mid-batch. `[remove A, B claims A's key]` and `[A replaces its key, B claims A's old key]` still fail and commit nothing. A successful batch of two replacements by A commits both events but only the last value remains owned afterward. Separate successful appends can release and reclaim the old key. |
| Still rejected | Numeric-valued or null/missing keys, unproven punctuation, Unicode or escaped strings, arrays, objects, dates, concepts, schema/value mismatches, non-ASCII or boolean keys under `ignoreCasing`, composites with non-string, repeated or more than three properties, and scopes. Use a kernel-backed test. |
| Still rejected | Numeric-valued or null/missing keys, unproven punctuation, Unicode or escaped strings, arrays, objects, dates, concepts, schema/value mismatches, non-ASCII or boolean keys under `ignoreCasing`, composites with non-string, repeated or more than three properties, and scoped composites, case-insensitive or boolean keys. Scoped fieldless or covered-and-removal events and scoped definitions alongside other definitions also require a kernel-backed test. |

Non-ASCII keys under `ignoreCasing` stay rejected by design: the scenario maintains no Unicode case mapping, so it never approximates the kernel's lowercasing. Use a kernel-backed test for them.

Fieldless schemas are fixture-backed only for unique-property removal-only events. These are constrained-key rules, not a relaxation of the scenario's general event-content domain. The oracle captures accepted event content/hashes as well as raw violations and mapped results; failed single and batch operations leave history and the next sequence unchanged.
Fieldless schemas are fixture-backed only for unscoped unique-property removal-only events. These are constrained-key rules, not a relaxation of the scenario's general event-content domain. The oracle captures accepted event content/hashes as well as raw violations and mapped results; failed single and batch operations leave history and the next sequence unchanged.

### Scope matching in scenarios

Use fluent `perEventSourceType()`, `perEventStreamType()` or `perEventStreamId()` before `unique(...)` or `uniqueFor(...)`. Combine them to require all selected dimensions to match. Source ID is the owner or cycle identity, not an additional scope flag. `@unique` is unscoped and cannot merge with a same-named scoped definition; `@removeConstraint` can name a scoped definition.

Direct `EventScenario.append` and both `appendMany` overloads accept routing options. Omitted or empty routes resolve to `Default` / `All` / `Default`, which are exact constraint-scope values, **not** wildcard-like read filters. Route identifiers remain restricted to ASCII letters, digits, `_` and `-`. Property scope keys flatten unescaped dimensions; event-type scopes compare a tuple. Delimiter-alias fixtures are guards, not supported route values. See the [scoped testing example](./testing.md#scoped-constraints-and-append-routing) for per-stream-ID and combined scopes.

Given/when builders, including `ReactorScenario` input, still use default routes. No routed-builder overload is available. A constraint rejection prevents reactor delivery and records no returned effects for the rejected action.

## Concurrent appends

Expand Down
Loading