#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 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.
The validation evaluator executes each rule body as a statement block:
- An
assertwhose condition evaluates tofalseproduces one diagnostic (masterbelt.validation.assert_failed) and block evaluation continues. A single rule body may therefore produce several diagnostics, one per failedassert. - An
assertwhose condition evaluates totrueproduces 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 failedassert, 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; 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 is a lazy query-plan builder, not an eager record scan. Evaluating a scope call constructs a relation value carrying the accumulated query plan — 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.