Amended 2026-10-19 by await-is-the-block-run-here: in the closed set,
service-handleis replaced by core’sworker <id>, which re-acquires the handle of any worker by its id and admits the value it settles with at theawaitsite.
Amended 2026-10-13.
Fixingsis session state, not one per unit: one table per session keyed by(UnitId, var), shared by the session’s threads and snapshotted into a fork, so a unit’s sites still share their fixings (ty).
A boundary is checked against its solved type
A door through which a value of a shape the program did not decide enters typed code admits that value against the type the checker solved at its call. It admits; it never converts. What fails is the decode, naming the field and the line that needed it.
What was decided
- The set is closed. The decoders of undecided shape (
from-json,from-jsonl, and the newfrom-json-at),use,service-handle, every tag ofexarch-agents,exarch-pinsandexarch-transcript, and_ed-state, whose cell persists across sessions. ABuiltinEntrymarks itself withBuiltinEntry::boundary, and its body is aBuiltinBody::Boundary, which is handed the site.from-csvdecides its shape ([Map String]) and is not one; nor isdetach, whose receipt has one shape; nor$ENV, typedMap String. - The site is on the call.
Execcarriessite: Option<Arc<Site>>, filled byannotatefor a boundary head. There is noCheckframe: the door already knows the value, and a frame sees it only after the door has let it go.serial.rsships the site with the IR, so an engine child admits too. The checker builds the site (typecheck/site.rs); the runtime walks it (types/admit.rs). - A
Siteis the solved type frozen as a graph, one node per variable root, so cycles close and sharing survives; it keeps the span that imposed each structure and each free variable’s kind.Site::admitwalks the value and the graph together. Its rules:- a ground scalar position needs that scalar’s head, and a JSON
3is anIntand2.5aFloatat a ground site as under a variable: a door never reads3as3.0, andfloatat the use is the one word that accepts both; - a free variable is checked against its kind alone, at every occurrence; the
one exception is a
comparablevariable, fixed at its first scalar to numbers or to text, because ordering a number against text is the one operation that fails on two admissible values of different types (Fixings, one per unit, shared by all its sites); - an open row checks the fields it names and its tail’s deep bit the rest; a closed row requires the exact field set, so a decoded value joined with a closed record literal refuses an extra field rather than dropping it;
- a variant needs a tag of its row, and a decoder produces none;
- a block position needs a closure, and
Site::admit_moduleholds auseexport to its scheme in one scratch unifier over the whole record.
- a ground scalar position needs that scalar’s head, and a JSON
- The report is the pointer. A mismatch names an RFC 6901 pointer into the value, what was found, what the script uses it as, and the line of the use that imposed it, with a secondary caret on that use.
- A boundary used as a value is η-expanded.
$from-jsonbecomes{ from-json }, so the block holds a saturated call that carries a site; so does an under-applied call, wherever it stands, a block tail included. - Sessions. A residual weak variable stays weak across units. A data
binding that enters a unit with residuals is admitted again, against the
type the unit solved, before the unit runs (
Toplevel.admits); a thunk’s results do not exist yet and are admitted by the sites in its own IR. from-json-at tokenswalks reference tokens (an object member by exact key, an array element by canonical decimal), so a path needs no escaping and a miss names the pointer prefix and the keys or the length.- The perimeter is a test. No builtin outside the boundary set and the
divergent
fail/exit/quithas a variable only its result mentions, and no storedDefinescheme quantifies what a boundary decides.detach’s∀αis listed as the one known exception until it is typed.
Why
HM cannot know a decoded value, but the checker does know what the program
will do with it. Checking at the door turns “fails later, elsewhere” into
“fails here, naming the field and the line that needed it”. For exarch that
matters most: $r[reply][verdict] on a child’s reply fails at the `read,
saying /reply/verdict is missing.
Rejected
Declaring the casts, which leaves them unsound. A JSON sum type, which is
verbose and is the reason from-json-at exists. A Check frame wrapping the
call: the door knows what the frame has to be told. Reading a JSON integer as a
Float where the type says so: a decoder that converts by type is a second
decoder. Fixing every free variable at its first scalar: it refused
for $xs { |x| echo $x } over [1, "a"], which is parametric.
Builds on a-let-generalises-what-is-not-weak, whose weak variables are what make the type recorded at a call the one every use of the value fixes; uses kinds for the free-variable check.