# `StatifierBlocks.Predicates.TruthTable`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/predicates/truth_table.ex#L1)

A table spec plus rows in, a struct with per-cell outcomes, first-match-wins
selection and expectation status out.

## Why `expected` is compared against selection, not raw truth

`core.branch` (`lib/statifier_blocks/core/branch.ex`, ADR-0002 decision 10)
stores an ordered `arms` list and tries them in order: the first arm whose
condition holds runs, and every later arm is skipped even if its own
condition would also hold. That ordering is a config-level fact, not an
implementation detail - the branch's moduledoc says arms are tried in order
and `slots/1` returns them in that order followed by `otherwise`.

So a truth table over a branch has to model the same thing a branch does:
which arm is *taken*, not which arms are individually true. A cell here
carries both readings. `outcome` is the raw `{:ok, boolean()} | {:error,
reason}` from `Predicates.evaluate/2` - what the expression alone says.
`selected?` is `true | false | :undecidable`, from one ordered
first-match-wins pass over the row's columns, mirroring what `core.branch`
would actually run. `expected`, a row's declared answer for one column, is
compared against `selected?` rather than `outcome`, because "does this
fixture behave like the branch it describes" is a question about which arm
wins, not about which conditions are independently true. Two columns can
both raw-evaluate `true` in the same row; at most one of them is selected.

## The `otherwise` column

At most one column may carry `source: nil` - the `otherwise` column - and
it is never evaluated as an expression (`outcome: nil`). It must be the
last column if present, because it is the branch's fallback arm and a
fallback that could still be overridden by a later real arm would not be a
fallback. `build/2` rejects a spec that breaks either rule before touching
any row.

## The five-value `status`

`status` is deliberately wider than a `true | false | unset` tri-state,
because "the row's expectation was contradicted" and "this cell's answer is
unknowable" are different findings that a renderer, and an author reading
one, need to tell apart:

  * `:match` - `selected?` is a boolean and equals `expected`.
  * `:mismatch` - `selected?` is a boolean and differs from `expected`.
  * `:unchecked` - `selected?` is a boolean but the row declares no
    `expected` value for this column.
  * `:error` - this column's own `outcome` is an error; nothing about
    selection can be said until the expression itself is fixed.
  * `:undecidable` - this column's own `outcome` is fine, but an *earlier*
    column in the row errored, so whether this column would even have been
    reached is unknown. Reporting `false` here would claim "no arm before
    this one matched", which is a guess this package does not make.

A row's bindings can also fail to build a context at all (an undefined
variable, a binding conflict). Then the whole row carries `error: reason`
and `cells: []` rather than guessing at any column - a renderer checks
`row.error` first.

# `build_error`

```elixir
@type build_error() ::
  {:duplicate_column, String.t()}
  | {:otherwise_not_last, String.t()}
  | {:invalid_source, String.t()}
```

# `t`

```elixir
@type t() :: %StatifierBlocks.Predicates.TruthTable{
  columns: [StatifierBlocks.Predicates.TruthTable.Column.t()],
  description: String.t() | nil,
  name: String.t(),
  paths: [String.t()],
  rows: [StatifierBlocks.Predicates.TruthTable.Row.t()]
}
```

# `build`

```elixir
@spec build(spec :: map(), rows :: [map()]) :: {:ok, t()} | {:error, build_error()}
```

Builds a `%TruthTable{}` from a table `spec` and a list of row `spec`s.

Validates the spec first - column keys must be unique
(`{:error, {:duplicate_column, key}}`), at most one column may have
`source: nil` and it must be last (`{:error, {:otherwise_not_last, key}}`),
and every column's `source` must be a binary or `nil`
(`{:error, {:invalid_source, key}}`). A malformed spec is an error return;
a failing *cell* is data, because showing which cells fail is the point of
the table - rows still build and render around a bad cell or a bad row.

# `statuses`

```elixir
@spec statuses(t()) :: [StatifierBlocks.Predicates.TruthTable.Cell.status()]
```

Flattens every cell's `status` across every row, in row-then-column order,
so a caller can assert a whole table's shape in one pattern match.

---

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