# Evaluation

This document will define Masterbelt expression evaluation and compile-time evaluation behavior.

## Validation Rules

A master's `validation` rules run in the evaluator at [`masterbelt export`](../tooling/cli.md) time, over the post-filter records. No validation code is generated into any target language; validators are a build-time contract over the data. The surface form, scoping, and severity model are defined in [masterdata/validation.md](../masterdata/validation.md).

The validation evaluator executes each rule body as a statement block:

- An `assert` whose condition evaluates to `false` produces one diagnostic (`masterbelt.validation.assert_failed`) and block evaluation continues. A single rule body may therefore produce several diagnostics, one per failed `assert`.
- An `assert` whose condition evaluates to `true` produces no diagnostic.
- An evaluation error inside a rule body — an unbound reference, a runtime type error, a division by zero — is a hard error attributed to the rule, surfaced through the underlying evaluator diagnostic or wrapped as `masterbelt.validation.evaluation_failed`. Unlike a failed `assert`, a hard error stops the rule.

An `each` rule body runs once per post-filter record; an `all` rule body runs once over the whole post-filter record collection. An `all` rule binds `table` (and its alias `self`) to the master's post-filter [relation](#scope-evaluation); iterating it with `for row in table` yields the post-filter records, and the rule may apply the master's scopes to it.

## Scope Evaluation

A master's [scope](../masterdata/schema.md#scope-section) is a lazy query-plan builder, not an eager record scan. Evaluating a scope call constructs a relation value carrying the accumulated [query plan](ir.md#master-scopes) — predicates, orderings, skip, take — and returns it; no records are touched at the call. A scope chain (`self.adult().gendered(g)`) threads the relation through each stage, producing one combined plan.

Records are observed only when a terminal resolves the relation against the active dataset (in the build-time evaluator this is iteration of an `all` rule's `table`, or a `toList()` reach), and the observed records are always the master's **final, post-filter** records — a scope never runs against source/pre-filter data. The relation is immutable and copy-on-write: a base relation can seed independent chains without aliasing. The in-memory evaluator and the generated SQLite runtime share the same scope semantics; a scope builds a backend-independent relation plan, and the executor (in-memory closures or translated SQL) consumes it.
