# Declarative data model

The [Rules vocabulary](/docs/sdk-reference/statemachine-api/rules) is sugar over these types. You rarely
construct them by hand, but they are what a declarative machine records and what the IR carries, so this is
the reference for reading an export or writing a generator.

## Rule

A guard or a field constraint, as data. `abstract record Rule` with these cases:

| Case | Fields | Holds when |
| --- | --- | --- |
| `Present` | `Source`, `Field` | the field exists and is not null |
| `Absent` | `Source`, `Field` | the field is missing or null |
| `OfType` | `Source`, `Field`, `Type` | the field is present and of `Type` |
| `NonEmpty` | `Source`, `Field` | a non-empty string or array |
| `OneOf` | `Source`, `Field`, `Values` | a string equal to one of `Values` |
| `Compare` | `Source`, `Field`, `Op`, `Value` (`double`) | a number in the `Op` relation to `Value` |
| `Count` | `Source`, `Field`, `Op`, `Value` (`int`) | an array whose length is in the `Op` relation to `Value` |
| `Length` | `Source`, `Field`, `Op`, `Value` (`int`) | a string whose length is in the `Op` relation to `Value` |
| `BoolEquals` | `Source`, `Field`, `Value` (`bool`) | a boolean field equals `Value` |
| `ArrayOf` | `Source`, `Field`, `ElementType` | an array whose every element is of `ElementType` |
| `All` | `Rules` | every sub-rule holds (empty is true) |
| `Any` | `Rules` | at least one sub-rule holds (empty is false) |
| `Custom` | `Name` | the handler bound with `CustomGuard(name, fn)` resolves it (the escape hatch) |

Evaluation is total: `RuleEvaluator` returns `false` for a missing or wrong-typed field, never an exception.
A `Custom` name with no handler in the map is also `false`, but a machine built with `MachineBuilder` cannot
reach that case: `Build()` refuses a custom name that has no handler bound.

## Reduction

How a transition computes the destination context, as data. `abstract record Reduction`:

| Case | Fields | Produces |
| --- | --- | --- |
| `Keep` | | the current context, unchanged |
| `Clear` | | an empty context |
| `Reset` | | the machine's initial context |
| `Set` | `Steps` | a clone of the context with each `SetStep` applied |
| `Custom` | `Name` | the result of the handler bound with `CustomReducer(name, fn)` |

`ReductionEvaluator` always returns a fresh `JsonObject`; it never mutates the inputs.

### SetStep and ValueSource

A `Set` carries one or more `SetStep(string Field, ValueSource Source)`. `ValueSource` is where the value
comes from:

| Case | Fields | Value |
| --- | --- | --- |
| `FromInput` | `Field` | the named field copied from the trigger input |
| `Constant` | `Value` (`JsonNode?`) | a literal |

## Context schema

Reflected from a context record and carried in the IR.

| Type | Fields | Meaning |
| --- | --- | --- |
| `ContextSchema` | `Fields` | the state's fields, sorted by key (ordinal) to match the canonical wire; `ContextSchema.Empty` is a state with no context |
| `FieldSchema` | `Name`, `Type`, `Nullable`, `Constraints` | one field: its JSON key, JSON type, whether it may be null, and any `Rule` constraints (e.g. non-empty from `[MinLength(1)]`) |

## Enums

| Enum | Values |
| --- | --- |
| `RuleSource` | `Context`, `Input` |
| `JsonFieldType` | `String`, `Number`, `Boolean`, `Array`, `Object` |
| `CompareOp` | `GreaterThan`, `GreaterOrEqual`, `LessThan`, `LessOrEqual`, `EqualTo` |
