# Master Data Schema

A master is a user-declared, named, externally-populated collection of records.

The master declaration is a top-level declaration of the language. Its surface form is defined together with the other declaration forms in [../language/syntax.md](../language/syntax.md); this document defines its semantics.

## Master Declarations

```mst
master Records {
  record {
    primary id: int,
    name: string,
  }

  source {
    csv "path/of/file.csv" {
      separator: ",",
    }

    csv "another/file.csv"
  }
}
```

A master declaration introduces a nominal type named by its identifier. The name lives in the same value-and-type identifier name space as a [type declaration](../language/syntax.md#type-expressions), so a master declaration and a type declaration with the same name within one file collide and are reported by [language/names](../language/names.md).

A master declaration is a [visible declaration](../language/syntax.md#declarations). The optional `pub` modifier makes the master visible outside its declaring file.

A master declaration may carry documentation comments. They attach to the master declaration as a whole, not to any section inside the body.

## Body Sections

The body of a master declaration is a brace-delimited list of sections. Seven section kinds are defined:

- `record { ... }` — the record section. Declares the element record type. Required.
- `source { ... }` — the source section. Declares the data sources used to populate the master at import time. Optional.
- `filter { ... }` — the filter section. Declares import-time row filters. Optional.
- `validation { ... }` — the validation section. Declares record- and collection-level validators run after filtering at export time. Optional. See [Validation Section](#validation-section).
- `static { ... }` — the static section. Declares constants and methods reachable through the master's surface name (`Master.X`). Optional.
- `select Name { ... }` — a select section. Declares a named projection that maps a subset of the record's fields onto a derived record type. Optional, repeatable. See [Select Section](#select-section).
- `master Name { ... }` — a nested master declaration. Reachable as `Parent.Name`. See [Nested Masters](#nested-masters).

The five single-kind sections (`record`, `source`, `filter`, `validation`, `static`) may each appear at most once. A second occurrence of the same kind is a syntax error reported on the later occurrence as `masterbelt.parser.master_section_duplicate`. Select sections and nested master declarations may appear any number of times.

A master declaration without a record section is a syntax error reported on the declaration as `masterbelt.parser.master_record_missing`.

Sections may appear in any order. Tooling-level conventions (formatter) may impose a canonical order; the language itself does not.

## Record Section

The record section declares the master's element record type. Its body is a [product type](../language/syntax.md#product-types-and-literals) body: a comma-separated list of fields and methods.

Field modifiers `readonly` and `writable` carry the same meaning as on any product type. The additional field modifier `primary` is described in [keys.md](keys.md).

Field names within the record section must be unique; duplicates are reported by the parser exactly as for any product type.

Methods declared inside the record section have the same semantics as product type methods. Method overload resolution and method dispatch are defined in [../language/types.md](../language/types.md).

The record section's body must declare at least one field; a record body that declares only methods, or that is empty, is a checker error.

## Source Section

The source section declares external data sources used to populate the master at import time. The body is a sequence of source entries.

```ebnf
source_entry  = source_kind string_literal [ source_options ] ;
source_kind   = identifier ;
source_options = "{" [ source_option { "," source_option } [ "," ] ] "}" ;
source_option = identifier ":" expression ;
```

A source entry begins with a source-kind identifier. The supported source kinds are:

- `csv` — see [import-csv.md](import-csv.md).

Additional kinds (for example `xlsx`) will be defined by extending this list. Until a kind appears in this list it is rejected as `masterbelt.checker.master_unknown_source_kind`.

The path string following the source kind names the resource to import. Resolution of this path against the project's working directory is defined by the importer specification and by [../tooling/configuration.md](../tooling/configuration.md).

An option list is optional. When written, it is a brace-delimited, comma-separated list of `name: value` pairs. A trailing comma after the last option is allowed. An empty option list `{}` is legal and equivalent to omitting the option list entirely.

Each option key is validated against the option schema declared by the source kind's importer specification:

- An option name not declared by the importer is reported as `masterbelt.checker.master_source_option_unknown`.
- An option value whose type does not match the importer's declared option type is reported as `masterbelt.checker.master_source_option_type_mismatch`.

Duplicate option names within one entry are reported by the parser on the later occurrence as `masterbelt.parser.master_source_option_duplicate`.

A source section with zero entries is legal at the language level; the driver treats such a master as having no automatic data source.

## Filter Section

The filter section declares row-level filters applied after a record has been read from a source. Each rule has a kind keyword (`include` or `exclude`), a reason string, and a brace-delimited body that evaluates a boolean expression against the candidate record:

```mst
master A {
  record { value: int }

  filter {
    include "non-negative" { return self.value >= 0 }
    exclude "outlier"      { return self.value > 100 }
  }
}
```

### Body Semantics

A filter rule body is a brace-delimited block. The block must `return` a value of type `bool`. The receiver of the body is the candidate record, available as the local `self` typed as the surrounding master's record type.

### Rule Application

Rules apply in source declaration order against each candidate record. The first rule that fails drops the record from the master; no later rule on the same record is evaluated.

- An `include` rule drops the record when its body returns `false`.
- An `exclude` rule drops the record when its body returns `true`.

Every dropped record produces a `masterbelt.importer.filter_excluded` diagnostic at hint severity. The diagnostic carries the rule's reason string so tooling can surface which rule caused the drop.

### Diagnostics

- A filter body whose return type is not assignable to `bool` is reported through the standard return-type-mismatch diagnostic.
- A filter section that contains no rules is legal at the language level.

## Validation Section

The validation section declares record- and collection-level data quality checks. It is distinct from the filter section:

- A `filter` rule **drops** a record from the master.
- A `validation` rule **keeps** every record and emits a diagnostic.

The two also run at different times: filters run during import while the record set is being assembled; validators run over the final, post-filter dataset. The full order is import → filter → validation → export-write, and validation runs at [`masterbelt export`](../tooling/cli.md) time before any artifact is written.

The section groups named validators by scope (`each` per record, `all` per collection) and asserts conditions over the data. The complete surface form, scoping, execution model, severity, and diagnostics are defined in [validation.md](validation.md).

## Static Section

The static section declares user-defined constants and methods that hang off the master's surface name as static members. The section is a brace-delimited list of [const declarations](../language/syntax.md#declarations) and [function declarations](../language/syntax.md#declarations); both forms accept the same modifiers and syntax they take at the top level (visibility, doc comments, effects, generics).

```mst
master Items {
  record { primary id: int, value: int }
  source { csv "data/items.csv" }
  static {
    pub const MaxId: int = 9999
    pub fn total(): int {
      let acc: int = 0
      for item in Items.toList() {
        acc = acc + item.value
      }
      return acc
    }
  }
}
```

The static section's members appear under the master's surface name as **static members**: a constant is reached as `Items.MaxId`; a method is reached as `Items.total()`. Visibility follows the regular `pub` rule and is independent from the master's own visibility — a `pub master` may expose private static members and vice versa.

### Scope and Resolution

A static member's body resolves names through the regular module scope: top-level constants, top-level functions, other masters' static members, and the master's own static members are all in scope. Module imports apply the same way they do for any other declaration.

The identifier `self` is **not** in scope inside a static section. The static section is detached from any record instance, so `self` would have no meaningful binding; references resolve through the master's surface name instead (`Items.toList()`, `Items.MaxId`). A use of `self` inside a static body is reported through the regular unbound-name diagnostic.

The master's own record fields are not exposed inside the static section either. A static body that needs to inspect records must iterate through `Master.toList()` (see [Iteration](#iteration)).

### Filter Interaction

A `filter` rule body MUST NOT call a master static method, because filter rules run during import while the master's record set is still being assembled. The checker rejects such calls with `masterbelt.checker.static_call_from_filter`. Static *constants* referenced from a filter body are permitted because they do not consult import state.

### Built-in Members

The static section's user-declared members coexist with the built-in static surface every master exposes (currently the `toList()` method described under [Iteration](#iteration)). Declaring a user member whose name collides with a built-in is reported through the regular duplicate-member diagnostic.

### Diagnostics

- A duplicate member name within one master's static section is reported as `masterbelt.checker.static_member_duplicate`.
- A member name that collides with a built-in master member is reported as `masterbelt.checker.static_member_reserved`.
- A static call from inside a filter rule body is reported as `masterbelt.checker.static_call_from_filter`.

## Select Section

A select section declares a named projection over the master's record. Each select section introduces one projected record type derived from a subset of the master's record fields, together with a query surface that filters, orders, and consumes records through the projected shape.

```mst
master Items {
  record {
    primary id: int,
    name: string,
    count: int,
  }

  select Summary {
    id: id,
    name: name,
  }
}
```

### Body Form

The body of a select section is a brace-delimited, comma-separated list of `target: source` field mappings. A trailing comma after the last mapping is allowed. The explicit `target: source` spelling is required even when the names match; the form is forward-compatible with future revisions that may permit renaming and limited type conversions.

```ebnf
master_select_section = "select" identifier "{" [ select_field { "," select_field } [ "," ] ] "}" ;
select_field          = identifier ":" identifier ;
```

### Constraints

The current revision allows only same-name same-type extraction. Each mapping must:

- Use the same identifier on both sides (`target == source`). A renaming mapping is reported as `masterbelt.checker.master_select_field_rename_unsupported`.
- Reference a field that exists on the master's record. An unknown source is reported as `masterbelt.checker.master_select_source_unknown`.
- Refer to a field with a primitive type (`bool`, numeric, `string`). A non-primitive field (a `ref<>`, `list<>`, `map<>`, or nested product) is rejected as `masterbelt.checker.master_select_unsupported_field_type`.
- Use a target identifier that is unique within the same projection. A duplicate target is reported as `masterbelt.checker.master_select_duplicate_field`.

Two select sections under the same master must have distinct names. A duplicate is reported as `masterbelt.checker.master_select_duplicate_name`. Cross-master name overlap is fine — the codegen identifier carries the master's name as a prefix.

A select section's body with zero mappings is legal at the language level but rejected by the checker through `masterbelt.checker.master_select_duplicate_field` when an empty body would otherwise be meaningless.

### Generated Names

Each `select Name { ... }` on `master Master { ... }` lowers to the following codegen-side identifiers:

| Codegen artifact | Spelling |
| --- | --- |
| Projected record type | `<Master><Name>Record` |
| Projected relation type | `<Master><Name>Relation` |
| Projected field builder | `<Master><Name>Fields` (Go / C#); per-target convention for TypeScript |
| Source-relation projection accessor | `Select<Name>()` (Go / C#) / `select<Name>()` (TypeScript) |

For `master Items { select Summary { id: id, name: name } }`, the Go target emits `ItemsSummaryRecord`, `ItemsSummaryRelation`, `ItemsSummaryFields`, and a `SelectSummary()` accessor on the source `ItemsRelation`.

### Query Surface

A projected relation exposes the same stage and terminal operations as the source relation (defined in [query.md](query.md)), parametrised on the projected record type. The terminals carry the same asyncable + cancellable + failable triplet the source terminals carry.

The accumulated source-side state (predicates, orderings, skip, take) flows from the source relation into the projected relation at `Select<Name>()` time, so the projection inherits any filters / orderings the user already chained. Predicates added through the projected relation are typed on the projected record, so authoring `Where(ItemsSummaryFields.Name.Eq("alpha"))` against an `ItemsSummaryRelation` is a compile-time match; passing a source-record predicate to the projected relation is a compile-time error.

### Semantics

A projection runs at terminal time, after the source records have been filtered, ordered, skipped, and taken according to the accumulated state. For each surviving source record the runtime copies the named fields into a fresh target record. The relation's underlying record set is never reshaped — projection is a view that materialises on each terminal call.

A projected relation does not re-import data or mutate the source relation. Two terminal calls observe the same record set under the same source-side state, matching the observational behaviour of [Iteration](#iteration) and the [Query API](#query-api).

## Scope Section

A scope section declares a named, parameterisable relation query that hangs off the master's [relation](#runtime-model) surface. Unlike the codegen-only `Where` / `OrderBy` operators, a scope is authored in Masterbelt source: it names a reusable query fragment that the source program — and, for `pub` scopes, the generated target API — can apply to a relation.

```mst
master Records {
  record {
    primary id: int,
    name: string,
    age: int,
    gender: int,
  }

  scope adult() {
    return self.where(fn(row) => row.age.ge(20))
  }

  scope gendered(gender: int) {
    return self.where(fn(row) => row.gender.eq(gender))
  }

  pub scope genderedAdult(gender: int) {
    return self.adult().gendered(gender)
  }
}
```

A scope is declared only inside a master body; there is no top-level or module-level scope. A [nested master](#nested-masters) may declare its own scopes, which surface on that nested master's relation. A scope always returns the declaring master's `Relation<M>`; it never returns a `list<Record>`, a nullable, a projected relation, or a joined relation.

### Surface Form

```ebnf
master_scope_declaration = [ visibility_modifier ] [ "indexed" ] "scope"
                           identifier "(" [ function_parameters ] ")"
                           ( function_block | scope_expression_body ) ;
scope_expression_body    = "=>" expression ;
```

A scope declaration carries an optional `pub` visibility modifier in the same position as every other declaration's visibility, an optional `indexed` modifier (see [Indexed Scopes](#indexed-scopes)), the `scope` keyword, a name, a parameter list, and a body. The `pub indexed scope` order is the only accepted spelling of the two modifiers; `indexed pub scope` and `scope indexed` are syntax errors. Both `scope` and `indexed` are context keywords (they remain usable as ordinary identifiers elsewhere); see [lexical.md](../language/lexical.md#keywords).

A scope never declares an explicit return type — the return type is always the declaring master's `Relation<M>`, so a return-type annotation is a syntax error — and a scope never carries type parameters.

### Parameters

Scope parameters use the same surface form as regular [function parameters](../language/syntax.md#declarations): the permitted parameter types, default values, optional / nullable parameters, and the call-site omission rules are identical to those of a regular function. Scope parameters are referenced from the body, including from inside query callbacks.

### Body

A scope body is either a function block or an arrow expression:

- A **block body** must `return` a `Relation<M>`. `let`, `if`, `for`, assignment, `break`, `continue`, and multiple / conditional `return`s are all allowed; a block body with no `return` is reported as `masterbelt.checker.scope_missing_return`.
- An **arrow body** (`=> expression`) requires its expression to evaluate to a `Relation<M>`.

In both forms a body expression whose type is not the declaring master's `Relation<M>` is reported as `masterbelt.checker.scope_return_type_mismatch`.

The body is **effect-free**: it builds and returns a relation plan and must not inherit `failable`, `cancellable`, or `asyncable`. A scope body — or a query callback inside it — that calls a user function, static, or const carrying any of those effects is reported as `masterbelt.checker.scope_forbidden_effect`. A scope is therefore never itself failable / cancellable / asyncable. Terminal relation operations (`toList`, `findBy`, …) carry those effects and so cannot be reached from a scope body; the constraint is enforced through the effect rule rather than by enumerating forbidden methods.

### `self`

Inside a scope body, `self` is the **receiver relation**: the `Relation<M>` the scope is applied to. `self` is a reserved implicit identifier in every context (see [lexical.md](../language/lexical.md#keywords)); a binding that tries to shadow it — as a function or scope parameter, a `let` / `const`, or a `for` / `match` binding — is rejected, and assigning to `self` is rejected. When a scope is reached through a [scope chain](#scope-chaining), its `self` denotes the relation produced by the stages applied earlier in the chain.

### The Relation Query DSL

A scope body reaches the relation query operators that are otherwise codegen-only. The operator vocabulary, the field-handle DSL (`row.age.ge(20)`, `row.name.asc()`), and the `and` / `or` / `not` combinators are defined in [query.md](query.md#source-level-relation-queries). A scope body uses `where`, `orderBy`, `thenBy`, `skip`, and `take`; it never introduces a new query surface such as `self.fields.*`.

### Calling and Chaining

A scope surfaces as a method on its declaring master's `Relation<M>` and **only** on that relation; it cannot be called on another master's relation, on a `Record<M>`, or on a `list<Record>`. A scope is reached from a relation receiver:

- The master's surface name is the base relation entrypoint, so `Records.gendered(1)` applies `gendered` to the master's base relation. An imported master's scopes are reachable through the import alias the same way.
- Within a scope body, `self.adult()` applies `adult` to the receiver relation.
- Scopes chain: `self.adult().gendered(gender)` and `Records.adult().gendered(1)` apply each scope in turn, threading the relation produced by one stage into the next scope's `self`.

Scope chaining does not depend on source order; a scope may chain a scope declared later in the same master. A scope that references itself directly or transitively is reported as `masterbelt.checker.cyclic_scope` — recursive scopes are forbidden. A scope call whose receiver does not expose the scope is reported as `masterbelt.checker.unknown_member`, and an argument-count / type mismatch through the general call diagnostics. A query callback referencing a field the record does not declare is reported as `masterbelt.checker.scope_unknown_field`.

### Visibility

A scope's `pub` modifier controls **generated-target API exposure only**. A `pub` scope is emitted as a method on the generated relation type in every codegen target; a non-`pub` scope is not emitted into the target API. Visibility does not restrict source-level use: a non-`pub` scope is still callable from Masterbelt source across module imports, exactly like a non-`pub` scope inside the declaring module. Generated method casing follows each target's relation-API convention — source `genderedAdult` is `GenderedAdult` on Go / C# and `genderedAdult` on TypeScript. The full per-target shape is defined in [codegen/golang.md](../codegen/golang.md), [codegen/typescript.md](../codegen/typescript.md), and [codegen/csharp.md](../codegen/csharp.md).

### Indexed Scopes

The `indexed` modifier marks a scope as a source for SQLite secondary-index inference. It carries no source-level semantics and does not change the generated non-SQLite API; non-SQLite targets ignore it without a diagnostic. When the SQLite backend is in use, the export pipeline infers secondary indexes from the `where` and order-by stages an indexed scope can produce. The inference rules, the generated DDL, deduplication, naming, and the partial-success / failure diagnostics are defined in [export-sqlite.md](export-sqlite.md#indexed-scope-secondary-indexes). `indexed` and `pub` are independent: `pub scope` is exposed but builds no index, `indexed scope` builds an index but is not exposed, and `pub indexed scope` does both.

### Naming and Resolution

A scope name is unique within one master; a duplicate is reported as `masterbelt.checker.duplicate_scope`, and scope overloading is not allowed. A scope name lives in the relation's method namespace: it must not collide with a built-in relation method (`where`, `orderBy`, `thenBy`, `skip`, `take`, terminals) or with the master's nested-master / static / function / const names, but it does **not** collide with a record field of the same name, because `Record<M>` and `Relation<M>` are distinct types with distinct namespaces. A collision is reported as `masterbelt.checker.scope_name_conflict`.

### Diagnostics

- A duplicate scope name within one master is reported as `masterbelt.checker.duplicate_scope`.
- A scope name that collides with a relation method, nested master, static, function, or const is reported as `masterbelt.checker.scope_name_conflict`.
- Declaring or assigning `self` is reported as `masterbelt.parser.reserved_identifier`, because `self` is lexically reserved (see [lexical.md](../language/lexical.md#keywords)).
- A scope body that does not return the declaring master's `Relation<M>` is reported as `masterbelt.checker.scope_return_type_mismatch`; a block body with no `return` as `masterbelt.checker.scope_missing_return`.
- A scope body or query callback that inherits a forbidden effect is reported as `masterbelt.checker.scope_forbidden_effect`.
- A direct or transitive self-reference is reported as `masterbelt.checker.cyclic_scope`.
- A scope call on a receiver that does not expose the scope is reported as `masterbelt.checker.unknown_member`; an argument mismatch through the general call diagnostics; an unknown field reference inside a callback as `masterbelt.checker.scope_unknown_field`.

## Nested Masters

A master declaration may contain other master declarations in its body. Each nested master is a full master declaration: it carries its own record, source, filter, static, and further-nested-master sections, and follows the same well-formedness rules as a top-level master.

```mst
master User {
  record { primary id: int, name: string }
  source { csv "data/users.csv" }

  master Friendships {
    record { primary id: int, owner: int, friend: int }
    source { csv "data/friendships.csv" }
  }
}
```

A nested master is reached through the parent's surface name with a dot: `User.Friendships`, `User.Friendships.toList()`, `User.Friendships.SomeStatic`. The dotted form behaves identically to a top-level master reference; nested masters are nominal types in the same name space as their parent's static members.

### Naming

Two nested masters under the same parent must differ in name. A duplicate is reported as `masterbelt.checker.nested_master_duplicate`.

A nested master's name must not collide with any of the parent's own static members (constants, methods, or other nested masters). A collision is reported as `masterbelt.checker.static_member_duplicate`.

### Visibility

A nested master may carry the `pub` modifier. Visibility is independent from the parent's: a `pub master` may contain a private nested master, and a private parent may contain a `pub` nested one. Cross-module references resolve through the dotted form (`OtherModule.Parent.Child`).

### Codegen

Each codegen target emits nested masters as siblings of top-level masters under a flattened identifier built by concatenating the parent's surface name and the nested master's surface name (`UserFriendships` for `master User { master Friendships { ... } }`). When the nesting depth is greater than one, the concatenation extends through every ancestor in declaration order (`UserFriendshipsArchive` for two levels of nesting).

The flattened identifier is the only name visible at the target level. Record and relation types follow the same naming policy as top-level masters — `UserFriendshipsRecord`, `UserFriendshipsRelation` — and the MasterData accessor for a nested master is flat (`data.UserFriendships`), not a chained property. Cross-references in the generated source see the same flattened identifier; the dotted Masterbelt-side path does not survive into the target.

### Limits

A nested master inherits no behavior from its parent: it carries its own record, sources, filters, statics, and iteration semantics. Records from the parent and records from a nested master are independent collections.

The body of a nested master may declare further-nested masters, with no fixed depth limit at the language level.

## Runtime Model

The Masterbelt surface form `master Foo { ... }` does not introduce a runtime singleton. Each master decomposes into two distinct runtime concepts:

- **Record** — the pure value type for one row, derived from the master's `record { ... }` section.
- **Relation** — the typed query / access surface over the master's records. Methods like iteration, primary-key lookup, and (in later revisions) richer queries hang off the relation.

A program reaches every relation through a **MasterData** value: the dataset entry that holds one materialised import. MasterData is not a singleton. A host application that needs to swap datasets per client (for example "Client v1 receives this content, Client v2 receives that") constructs and routes distinct MasterData values; the surface program never sees the choice.

The mapping from a Masterbelt-surface `master Foo` to its runtime parts is fixed:

| Surface | Runtime |
| --- | --- |
| Record type used in user code | `<Master>Record` |
| Query / access surface | `<Master>Relation` |
| Dataset entry | `MasterData`, with a flat accessor per master |
| Nested master `Parent.Child` | `<ParentChild>Record` / `<ParentChild>Relation`, reached through the same MasterData with a flat accessor (`data.parentChild` / `data.ParentChild` per target convention) |

The Masterbelt source program does not refer to MasterData. The implementation threads the active dataset through whatever mechanism the codegen target chooses (Go: through `context.Context`; TypeScript / C#: through a `data` parameter passed to terminals). A planner writes `Items.toList()` and the implementation rewrites the call against the active dataset.

The runtime model belongs to codegen targets. The Masterbelt source language never names `<Master>Record`, `<Master>Relation`, or `MasterData`: those identifiers are codegen-side only.

### Primary Key Lookup

Every relation exposes a built-in primary-key lookup operation that returns the record matching a given primary-key value, or a per-target "no match" sentinel when no record matches. The lookup is part of the runtime model; the Masterbelt source program never names it directly.

The method carries the `asyncable`, `cancellable`, and `failable` effects; see [Effect Inheritance](../language/types.md#effect-inheritance). Because the Masterbelt source program never names the lookup, no caller has to acknowledge the inheritance; the effect set is baked into each target's `FindBy` emission directly so backends that can fail (Phase 4 JSON loader, future SQLite, ...) surface that path on the generated signature.

#### Naming

The lookup method is always named `FindBy` (Go / C# PascalCase) or `findBy` (TypeScript camelCase), regardless of the master's primary-key field name or arity. Composite keys disambiguate through the positional argument list rather than through the method name; relations live on their own type, so there is no overload ambiguity even when several masters share a key field name.

A master rejected by the checker as `master_primary_missing` ([keys.md](keys.md)) has no primary key and therefore no `FindBy` method.

#### Signature

The method takes one positional parameter per primary-key field, named after the field and typed with the field's checked type, in source declaration order. There is no aggregate key struct.

The return shape follows each target's idiomatic "optional value" convention. The `failable` effect surfaces per the per-target rule (Go: trailing `error` return; TypeScript / C#: a thrown exception):

| Target | Return |
| --- | --- |
| Go | `(<Master>Record, bool, error)` — the second result is `true` when a record matched and `false` together with the record's zero value when no record matched. The third result is the backend failure: `nil` when the lookup completed (whether or not a match was found) and non-`nil` when the underlying read failed. |
| TypeScript | `Promise<<Master>Record \| undefined>` — `findBy` is declared `async` and returns `undefined` for no match. A backend failure surfaces as a thrown exception. |
| C# | `Task<<Master>Record?>` — `FindBy` is declared `async` and returns `null` for no match. A backend failure surfaces as a thrown exception. |

Targets that thread the active dataset through a `context.Context` (Go) or an analogous per-target slot keep the same threading convention for `FindBy` as for the other relation methods. The `cancellable` effect adds the threading slot (`ctx context.Context`, `signal: AbortSignal`, `CancellationToken cancellationToken`); the `asyncable` effect wraps the return in `Promise<R>` / `Task<R>` on TypeScript and C#.

#### Semantics

The lookup returns the first record whose primary-key fields are all equal to the supplied arguments using each target's structural equality on the primary-key field's checked type. Equality on primitive primary-key types follows the host language's natural equality (Go and C# `==`, TypeScript `===`). A master with an empty record set returns the per-target "no match" sentinel for every call.

The lookup is observational: it does not re-import data and does not mutate the relation. A subsequent call observes the same record set as the first call within one run, matching the behaviour of [Iteration](#iteration).

### Query API

Every relation is itself the chainable query surface: stage operations (`Where`, `OrderBy`, `ThenBy`, `Skip`, `Take`, projection, join) return a fresh relation, and terminal operations (`ToSlice` / `ToList` / `toArray`, `Iter` / `AsAsyncEnumerable`, `FindBy`, `FirstOrDefault`, `Count`, `Any`) execute the accumulated plan against the active dataset. The cross-target model, the operator vocabulary, and the runtime plan AST are defined in [query.md](query.md).

### Join Operator

A master whose record carries a `ref<Target>` field (see [Reference Fields](relations.md)) exposes a per-ref **join relation** on its query surface. Each such field automatically generates a parallel relation that walks the source records, resolves the ref's expanded primary-key fields against the target relation's `FindBy`, and yields a pair of `(left, right)` records per successful match. The join's full contract — pair record, pair field builder, joined relation type, INNER JOIN semantics, and how source-side state flows into the join — is defined in [query.md](query.md#joins).

## JSON Export Format

A project's imported master data is serialised to a single JSON document with the flat shape:

```json
{
  "items": [
    {"id": 1, "value": 10},
    {"id": 2, "value": 20}
  ],
  "userFriendships": [
    {"owner": 7, "friend": 9}
  ]
}
```

- Top-level keys are the masters' **flat camelCased identifiers**: the same names the per-target MasterData accessor uses (`items`, `userFriendships`). A nested master `master User { master Friendships { ... } }` appears under the flattened-then-camelCased key (`userFriendships`).
- Each value is an array of record objects. Records preserve the importer's row order; a master that declared no source section appears with an empty array.
- Per-record keys are the master's surface field names as written in source (`id`, `name`, `userId`). Records sort their keys lexicographically inside the file so the on-disk shape is deterministic.
- Primitive values map directly: `bool` to JSON `true`/`false`, `string` to JSON string, `null` to JSON `null`, integers whose absolute value fits in 2^53 to JSON number. Integers outside that range serialise as quoted strings so JavaScript-side consumers receive a lossless representation.
- Composite values nest naturally: `list<T>` becomes a JSON array, `map<K, V>` becomes a JSON object keyed by the entries' rendered keys, a nested product becomes a JSON object whose keys follow the same lexicographic sort rule as a top-level record.
- The `ref<T>` field expands to the target master's primary-key fields (`field_pk1`, `field_pk2`, ...) before serialisation; nothing in the JSON output reveals the original ref shape.

The format is the same regardless of codegen target. See [export-json.md](export-json.md) for the exporter contract and [codegen/golang.md](../codegen/golang.md#master-data), [codegen/typescript.md](../codegen/typescript.md#master-data), and [codegen/csharp.md](../codegen/csharp.md#master-data) for the per-target `LoadJSON` / `loadJSON` / `LoadJson` helper signatures.

## Iteration

A master declaration exposes the surface method `toList()` on the master's name. The call returns every imported record in import order; records dropped by the master's filter section ([Filter Section](#filter-section)) are not included in the result.

The method carries the `asyncable`, `cancellable`, and `failable` effects; any callable that transitively reaches `toList()` inherits the same effect set silently per [Effect Inheritance](../language/types.md#effect-inheritance). Each codegen target lifts the inherited effects on the surrounding callable's surface (Go: the `ctx` parameter plus an `error` second result; TypeScript: `async`, `AbortSignal`, `Promise<R>`; C#: `async`, `CancellationToken`, `Task<R>`). The Masterbelt source program never writes any of these threading slots; the planner just calls `Items.toList()`.

```mst
master Items {
  record { primary id: int, name: string }
  source { csv "data/items.csv" }
}

fn each() {
  for item in Items.toList() {
    use(item.id, item.name)
  }
}
```

`Items.toList()` is the surface form. At the target level it lowers to the chainable relation's list terminal (Go `Items.ToSlice(ctx)`; C# `Items.ToList(data, cancellationToken)`; TypeScript `items.toArray(data, signal)`); the planner never writes the dataset directly. The lowering is documented per target in [codegen/golang.md](../codegen/golang.md#master-data), [codegen/typescript.md](../codegen/typescript.md#master-data), and [codegen/csharp.md](../codegen/csharp.md#master-data).

A subsequent call to `toList()` observes the same record set as the first call within one run. Records are not re-imported on each call; the method is a view over the already-materialised collection.

`toList()` is a static member: it is reached through the master's declaration name (`Items.toList()`), not through an instance. The master type name itself does not denote a value; only the static method does.

See [query.md](query.md) for the cross-target query model.

## Reserved Keywords

The identifiers `master`, `record`, `source`, `filter`, `include`, `exclude`, `primary`, `static`, and `select` are reserved by the master data schema and cannot be used as identifiers in any position.

The [scope section](#scope-section) keywords `scope` and `indexed` are **context keywords**: they are matched only at the scope-declaration position inside a master body and remain usable as ordinary identifiers elsewhere. The implicit relation receiver `self` is fully reserved (see [lexical.md](../language/lexical.md#keywords)).

Source-kind identifiers (for example `csv`) are not reserved: they are matched only at the source-kind position inside a source section.
