Fields are reached by name
A record field is reached only by naming it in a row. Selection names it, a map pattern names it, and a builtin taking a record names its fields in the builtin’s own row (row-types). No operation reads a field the type does not mention.
This is a rule about what may be added, not a description of a coincidence.
keys, has, and fields are typed over Map<α> rather than over a record
row (core/src/typecheck/builtins.rs), and that is load-bearing: a
to-json :: [ρ] → F Str, a record-typed keys, or any structural printer over
an open row would read fields no row names, and would thereby reopen
an-open-spread-must-come-last —
whose cost argument is that the entries behind an open spread are unreachable
under every sound typing. Such a builtin is a language decision, not a
convenience.
One operation detects what it cannot name: structural equality (== / !=)
compares a record’s key–value pairs whole (docs/SPEC.md §4.8). Its operands
have one data type, with no blocks or handles at any depth. Detection
is not reading — it yields no field’s value, so the unreachability argument
stands — and it is what makes the weaker typing of a merge unsound rather than
merely useless: a closed row describing a value with unnamed extra fields is
observably wrong (record-concatenation).
The rule is also why optionality cannot hide in a record’s width: a field that may or may not be there must be named to be read, and naming it in a result’s row forces every caller to supply it. Absence therefore travels as a variant (optionality-via-variants).
The rule holds across the harness wire too. A family tag’s record — exarch-agents `start’s spec, exarch-context `evict, exarch-transcript `grep — crosses to the
desk by field name and is decoded through Datum (core/src/first_order/datum.rs):
record! (or a hand-written decode over exact_keys) reads each named field and refuses
any other, so the complete list of names the tag reads is
the struct’s own: `exarch-transcript `grep`: unknown field `turn — did you mean `turns?.
An open shell-side row cannot
state an optional field’s absence, so without this a misspelt optional field
would be read by nobody and silently change the call’s meaning — a grep over
the whole transcript where a narrowing was meant.