# `Chronicle.ReadModels.DecisionReads`
[🔗](https://github.com/Cratis/Chronicle.Elixir/blob/main/lib/chronicle/read_models/decision_reads.ex#L4)

Client-side decision-consistent reads: fold a read model, carry forward the exact log
position it was folded at, and let `Chronicle.Transactions.UnitOfWork` guard a subsequent
append with it — the Dynamic Consistency Boundary (DCB) pattern.

    Chronicle.decide(fn _unit_of_work ->
      {:ok, %{instance: seat}} = Chronicle.ReadModels.DecisionReads.get(MyApp.ReadModels.Seat, "seat-5")

      if seat == nil do
        Chronicle.EventSequences.EventLog.append("seat-5", %SeatBooked{by: "alice"})
      end
    end)

If another process books the same seat between the read and the commit, the kernel rejects
the whole batch and `Chronicle.decide/2` returns `{:error, %Chronicle.ReadModels.DecisionConflict{}}`
instead of appending anything.

Call `get/2` (or its stream variant, `get/4`) from inside a unit of work — it enrolls the read
as a decision dependency automatically. Call `get_detached/2` (or `get_detached/4`) to fold the
same way without an ambient unit of work, when you only need the guarded read's current value
and are not about to commit anything against it.

## What can be guarded

Not every read model shape can be protected: `admit/2` and `admit_for_stream/2` run the same
checks `get/2`/`get_detached/2` do, without any I/O, so a caller can check eligibility ahead of
time. See `Chronicle.ReadModels.DecisionReadRefused` for every refusal reason.

## Passive reducers (Elixir-only)

The .NET client refuses every reducer-backed read model outright. A read model
produced by a **passive** reducer (`use Chronicle.Reducers.Reducer, active: false`) can be
decision-read here instead: because a passive reducer is never kept warm by an observer, its
current value is always folded fresh from `Chronicle.EventSequences.EventLog.get_for_event_source/2`,
which makes it trivial to capture the exact boundary the fold stopped at. An **active**
reducer is still refused with `:reducer` — only the client that asked for the fold knows it
reached a consistent point, and an active reducer never asks.

# `refusal_reason`

```elixir
@type refusal_reason() :: Chronicle.ReadModels.DecisionReadRefused.reason()
```

# `admit`

```elixir
@spec admit(module(), keyword()) :: {:ok, [module()]} | {:error, refusal_reason()}
```

Checks, without any I/O, whether `model_module` can back a model-keyed decision read.

Returns `{:ok, event_types}` when admitted, or `{:error, reason}` — see
`Chronicle.ReadModels.DecisionReadRefused` for every `reason`.

# `admit_for_stream`

```elixir
@spec admit_for_stream(module(), keyword()) ::
  {:ok, [module()]} | {:error, refusal_reason()}
```

Checks, without any I/O, whether `model_module` can back a stream-scoped decision read.

Elixir admits the same shapes for a stream-scoped read as for a model-keyed one; unlike the
.NET client, it does not additionally admit a projection keyed by the event stream id itself.

# `get`

```elixir
@spec get(module(), String.t(), keyword()) ::
  {:ok, Chronicle.ReadModels.DecisionRead.t()}
  | {:error, :requires_unit_of_work | term()}
```

Folds `model_module` at `key` and enrolls the read as a decision dependency of the current
unit of work, so `Chronicle.Transactions.UnitOfWork.commit/1` (and `Chronicle.decide/2`)
guards the eventual append against anything else changing it first.

Returns `{:error, :requires_unit_of_work}` when called outside `Chronicle.decide/2` or
`Chronicle.Transactions.UnitOfWork.with_unit_of_work/2`. Raises
`Chronicle.ReadModels.DecisionReadRefused` when the model's shape cannot be guarded.

# `get`

```elixir
@spec get(module(), String.t(), String.t(), String.t(), keyword()) ::
  {:ok, Chronicle.ReadModels.DecisionRead.t()}
  | {:error, :requires_unit_of_work | term()}
```

Stream-scoped variant of `get/3`. See `get_detached/5` for the stream-scoping rules.

# `get_detached`

```elixir
@spec get_detached(module(), String.t(), keyword()) ::
  {:ok, Chronicle.ReadModels.DecisionRead.t()} | {:error, term()}
```

Folds `model_module` at `key` in a fresh, guarded session, independently of any unit of work.

Returns `{:ok, %Chronicle.ReadModels.DecisionRead{}}`, or `{:error, reason}` for a transport
failure. Raises `Chronicle.ReadModels.DecisionReadRefused` when the model's shape cannot be
guarded at all.

## Options

Same as `Chronicle.ReadModels.get_instance_by_id/3`.

# `get_detached`

```elixir
@spec get_detached(module(), String.t(), String.t(), String.t(), keyword()) ::
  {:ok, Chronicle.ReadModels.DecisionRead.t()} | {:error, term()}
```

Folds `model_module` guarding one event stream directly, independently of the model's own key.

`event_source_id` is used both as the model's lookup key and as the target of the guard.
Pass `:event_source_type` in `opts` to additionally narrow the event source type.

Refused with `:all_streams_not_supported` when `event_stream_type` is empty or `"All"` — a
stream-scoped guard needs a specific stream.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
