Under the hood
The core track can skip this chapter; nothing later depends on it. It exists for the reader who wants to know why the guarantees hold — why a compiled model cannot dangle a reference, why runs are byte-reproducible, why a second-pass stream cannot read another. The machinery is not incidental to the language's promises: each promise is a property of a specific stage, and knowing which stage answers which question makes you faster at diagnosing anything.
The pipeline, stage by stage
A model goes from text to results through fixed stages, each with one job and one class of refusal:
- Lexing turns bytes into tokens. Refusals here are lexical: an unterminated string, an unclosed comment.
- Parsing turns tokens into a syntax tree. Refusals are structural: a malformed schedule, a stream name with one segment, a keyword where an expression belongs.
- Resolution builds the import graph and the symbol table, then resolves every name — entities, streams, phases, fields — to its declaration. Refusals: cycles, escapes from the model root, duplicates, references to nothing. After this stage, no name in the model is unbound, which is the property chapter 2's "reference to nobody" refusal was teaching.
- Validation checks model-level shape: exactly one time grid, schedules inside the horizon, required clauses present. With a pack in play, the pack's own validations run here too — a contract missing a required term is refused with the pack's vocabulary.
- Lowering and emission produce the IR — the intermediate representation. Contracts expand into the streams they imply, every schedule becomes an explicit set of dated occurrences, every identifier gets a stable ID, and the whole is written as one JSON document against a published schema.
- The engine evaluates the IR under a run configuration and emits results — the other published schema.
The division of labor explains a distinction you have already felt: everything structural fails before any number exists (stages 1–5), while everything numerical — a division by zero in some period, a prev read in period 0 — surfaces at evaluation. When something goes wrong, asking "which stage owns this?" is the fastest route to the fix, and the next chapter's diagnostic codes are organized along exactly this seam.
The IR: the model with nothing left implicit
The IR deserves a moment of respect rather than a skim. It is the model after every convenience is expanded — names resolved, schedules enumerated, contract sugar lowered, defaults applied — a complete description of what will be computed with no reference to how it was written. Three practical consequences:
It is the audit artifact. When a regulator, a counterparty, or a future you asks "what exactly did this model do," the IR is the answer, independent of source-file organization. Two differently-organized sources that mean the same model produce the same computation — chapter 13's split-is-pure-communication claim, made mechanical.
It is the interface. The playground, the command line, the Python bindings, and any future surface all run IR. A tool that consumes or produces it speaks the language without parsing it.
It is diffable at the semantic level. A source diff shows what you edited; an IR diff shows what changed in meaning — a reorganization produces an empty one, an assumption edit produces exactly the affected expressions. Reviewing consequential changes at both levels is a habit worth having for models that move money.
The model also carries a canonical hash — a fingerprint of the IR, recorded in results. "Which model produced this number?" has a cryptographic answer, which is what lets a committee memo cite a model the way it cites a document version.
Evaluation: order as a guarantee
The engine's period loop runs in a fixed order — rules, then events, then streams and waterfalls — and chapter 8's impossible-cycles argument is just this order read as a proof. Rules see the previous period's committed column; events see this period's rule outputs and may overwrite fields; streams and waterfalls read what was committed. Every read points backward in this sequence, so evaluation is well-founded by construction, and the language can refuse — at compile time — any expression that would need to look forward.
Reproducibility falls out of two further choices you have already met. Arithmetic is decimal with defined rounding — no platform float quirks — and every random draw comes from a counter-based generator keyed by (seed, assumption, trial). That keying is why each assumption owns an independent draw stream and why adding one assumption reshuffles nothing else: there is no shared random state anywhere to perturb. Determinism is not a test the engine passes; it is the absence of any mechanism for nondeterminism.
The two-pass rule for series reads
Chapter 9 promised the story behind series_sum's restriction. Here it is, in one breath.
Within a period, streams whose amounts touch no other stream's series are phase one: they evaluate first, in no particular order, because nothing they read can depend on their peers. Streams (and guards) that do read series — a fee on collections, a kicker on revenue — are phase two: they run after phase one has committed, so a series read inside the current period sees finished numbers.
The restriction follows immediately: a phase-two stream cannot read another phase-two stream. Allowing it would need an ordering among the readers — fee reads kicker reads fee — which is the circularity the language refuses everywhere else, one level up. When you hit this wall (a fee on a fee — rare in practice), the fix is the same staging discipline as chapter 8's: read the base quantity both fees share, or make the first fee's economics a rule field the second can read at close. The wall is not a limitation of the implementation; it is the two-pass structure being visible, and models that respect it — fees read bases, bases read no one — are models whose evaluation you can narrate.
Reading this back onto the promises
Every guarantee the course has leaned on now has an address. Nothing dangles — resolution. Nothing silently discarded — parsing refuses what it cannot represent. What compiles, runs — validation and lowering exhaust the structural checks before the engine starts. Same inputs, same bytes — decimal arithmetic, keyed draws, ordered evaluation. Cycles unwritable — the stage order, twice: rules-before-streams across constructs, two passes within streams. None of these is a slogan; each is a stage doing one job. That is what a small language buys: the machinery is short enough to actually know.
On your own
- Compile any model from this course with the command-line tool and read the IR it emits, end to end, once. Find your schedule as enumerated dates and your contract-free streams as they will be computed. Twenty minutes, and the word "compile" stops being abstract.
- Reorganize a multi-file model (move a stream between files) and diff the two IRs. Then change one assumption and diff again. The first diff's emptiness and the second's precision are the semantic-diff property, seen with your own eyes.
- Predict which phase each stream of your chapter-9 venue model runs in, then explain to a colleague why the percentage-rent kicker cannot ever race the revenue it reads. If you can narrate that, you own this chapter.