The shape of the runtime

A layer-by-layer map of the Cruft runtime, from the CLI down through the host, the engine, the parser and compiler, to the built-in package manager. It shows where two boundaries, the engine/host line and the install/load seam, sit differently than Node's, and why that placement makes per-value isolation possible.

Node has a familiar layer map. At the bottom, V8: the language itself, 1 + "2", prototype chains, the promise queue. Beside it, libuv: the event loop and the IO. On top, the Node core you import, fs and http and path, half of it C++ and half of it JavaScript. Off to the side, npm: a separate program that fills node_modules before any of the above runs. Placing a stack trace or a bug means asking which layer owns it.

Cruft has the layers you'd expect. What is worth attention is that two of the boundaries fall in different places than Node's, and those two placements are the whole reason the previous page's subject, isolation and authority as things you construct, is possible.

Cruft, top to bottom
CLIcruft run / eval / check / REPL
Host environmentnode:* and web APIs, resolver, I/O polling, WASM bridge
JavaScript enginevalues, objects, intrinsics, interpreter, GC, jobs, modules, Intl, Temporal
Parser / compilersyntax, TypeScript erasure, bytecode, JIT (LeJIT)
Package managerinstall, lockfile, store, package graph

The engine and the host

In Node, the line between "the language" and "the environment" is a vendor line. V8 is the engine, and it is someone else's box. Array.prototype.sort, garbage collection, the microtask queue: all V8. fs.readFileSync actually touching the disk: that is Node's C++ on the other side of the V8 API. Authority cannot move across that line, because half of it isn't Node's.

Cruft draws the same line, and both sides are Cruft's. The engine owns language semantics: what 1 + "2" evaluates to, how prototype chains and promises work, what Array.prototype.sort does. It is a direct implementation of ECMA-262 (with Intl, Temporal, and RegExp), and it knows nothing about files, sockets, or processes. The host owns everything a program can observe about its environment: imports, globals, IO, process state, networking. When code calls fs.readFileSync, the engine dispatches an ordinary function, and the part that actually touches the disk is host territory, exactly as in Node.

The difference is what can be done at the line. In Cruft the host is assembled around an engine realm at startup through a single composition point, Cruft Core, the layer that wires the host onto the engine. It installs, in order: the portable cruft:* primitive surfaces, the Node-compatible module adapters, the Web platform globals (fetch, URL, streams, crypto, …), process and IPC hooks, the WebAssembly bridge, and the resolver that maps node:fs-style specifiers onto those modules.

Because all host authority flows through that one point, it can be granted, withheld, or recorded there. That governance is the architecture itself. --audit, --sealed, and the per-Compartment grants from the previous page are the same seam, read at different scopes. This is not buildable on top of V8, because the line is not yours to own.

Packages and loading

The second boundary Cruft places differently is between what was installed and what gets loaded. In Node these are two programs. npm (or pnpm, or yarn) resolves your graph and writes node_modules; then, separately, the runtime's loader reads node_modules back and resolves your imports. They agree by convention and file-system layout, not by design, which is why a package can do one thing at install time and another at import time with nothing connecting the two.

Cruft's package manager is built into the runtime itself. cruft install resolves the dependency graph, writes cruft-lock.json, and materializes packages from a content-addressed store, and the runtime's module loader consumes that same graph when resolving imports. Because both ends live in one binary, the join between installation and loading is a designed interface, and a package's declared capabilities can be enforced at the moment it loads. See Modules and packages.

The tier under the engine

The parser and compiler are opaque in V8. In Cruft they are visible. Source text (after TypeScript erasure, where the extension calls for it) is parsed to an AST and lowered to bytecode; the interpreter runs the bytecode; hot code is lowered again by LeJIT, a baseline JIT that hands a low-level IR to a Cranelift backend for native code. Each step sheds complexity it has proven it does not need. The next page walks a single function down through these tiers: How your code runs.

One binary, many doors

Everything above ships as one cruft executable. The CLI is the front door to all of it: running files and stdin, --eval, a syntax-check mode, a REPL, the package-manager subcommands, and the conformance and verification harnesses it ships with. There is no separate engine to install, no toolchain to assemble, no package manager to add. That, too, is a consequence of owning every layer.