build log · 18 entries · roadmap complete — M0–M7 all shipped with committed evidence: a capability-gated runtime that survives its own drill and its own million-request load
jsrt: the build log
A purpose-built JavaScript host in Rust — V8 through deno_core, where the engine is borrowed and everything that matters is ours: the capabilities, the lifecycle, the honest errors, the ops. Built in the open, one proven milestone at a time.
| Milestone | Name | Entries | State |
|---|---|---|---|
M0 |
scaffold | 1 | done |
M1 |
execute | 3 | done |
M2 |
async host | 3 | done |
M3 |
capabilities | 2 | done |
M4 |
supervise | 2 | done |
M5 |
stdlib wave 1 | 2 | done |
M6 |
operate a real app | 2 | done |
M7 |
load and ship | 3 | done |
Latest · M7 · 7.3: the hour soak
tests per entry — 4 at 0.1 → 84 at 7.3
0.1 ███ 4 1.1 ████████ 15 1.2 ███████████ 21 1.3 ███████████ 21 2.1 ██████████████ 27 2.2 ████████████████ 32 2.3 █████████████████ 34 3.1 █████████████████████████ 51 3.2 █████████████████████████████ 59 4.1+4.2███████████████████████████████ 64 4.3 ███████████████████████████████ 64 5.1 ██████████████████████████████████ 70 5.2 ██████████████████████████████████████ 77 6.1 █████████████████████████████████████████ 83 6.2 █████████████████████████████████████████ 84 7.1 █████████████████████████████████████████ 84 7.2 █████████████████████████████████████████ 84 7.3 █████████████████████████████████████████ 84
M0 · scaffold
Workspace, pinned deno_core + V8, the rt.* namespace, an error taxonomy where every way a run ends has its own exit code, and an isolate watchdog that can actually kill a CPU-bound script.
0.1 |
Borrow the engineA JavaScript runtime is two projects pretending to be one — an engine and a host. We borrow the engine (V8, through Deno's own embedding crate) and write everything else ourselves. |
4 |
M1 · execute
The run lifecycle proven end to end (every exit class reproducible, including uncaught CapabilityError → 7 and watchdog kill → 6); real module semantics — imports between files, honest load errors; the op boundary measured (cold-start p50/p95, 10k op calls) with numbers recorded, never oversold.
1.1 |
Every exit classFifteen integration tests drive the real binary through every documented exit class — and caught two real bugs on the way: a parked event loop the watchdog couldn't reach, and op errors that arrived in JS with the wrong class. |
15 | |
1.2 |
Module semanticsMulti-file imports run, missing and malformed specifiers exit 3 naming the specifier, a syntax error in a dependency stays a syntax error — and circular imports evaluate in both entry directions. |
21 | |
1.3 |
Op boundaryCold start p50 9.2ms, an rt.now() call costs ~55ns, and a 1ms sleep actually lands at 2.3ms — the first committed numbers, with the machine attached and the caveats attached harder. |
21 |
M2 · async host
setTimeout/clearTimeout/setInterval over op_sleep with real cancellation; bounded fetch — stream-capped body, per-op deadline, classified stalls; and teardown that proves nothing outlives its runtime (promises, timers, handles).
2.1 |
TimerssetTimeout/setInterval/clearTimeout/clearInterval/unref under rt.*, built on deno_core's own timer machinery — a pending ref'd timer holds the loop until it fires, an unref'd one doesn't, and a thrown callback classifies as a plain exception. |
27 | |
2.2 |
Bounded fetchA per-op deadline, a streamed 4MiB body cap, and a destination allowlist — five tests against a real TCP stub pin every fetch outcome class, including the one people get wrong: a 500 is a result, not a crash. |
32 | |
2.3 |
Retained resourcesA PendingCounts snapshot taken the moment before the isolate drops — abandoned timers, in-flight ops and open resources are counted, reported, and then destroyed with the runtime. Leaks are seen, never silently dropped. |
34 |
M3 · capabilities
The manifest is the law — and now the loader is inside the fence too: JsrtModuleLoader gates every import at resolve()+load() through declared read paths plus the entry's own dir; then the escape suite found a real hole (the authority parser and reqwest disagreed on \@), fixed so the checked host is the connected host.
3.1 |
The loader gatesThe module loader used to resolve any import to any file on disk — ops checked may_read but the module graph walked straight past it. Now JsrtModuleLoader gates every non-entry specifier at resolve() and load(), and a refusal is a real CapabilityError. |
51 | |
3.2 |
The escape suiteThe escape suite found a real hole before it ran a single test: url_authority ended the authority only at '/', so http://evil.com\@granted.com/ checked 'granted.com' while reqwest connected to evil.com. Authority now ends at / \ ? # and both sides normalize before compare. |
59 |
M4 · supervise
jsrt supervise: the runtime runs in a child and the parent enforces what it can't enforce on itself — CPU seconds by pre_exec rlimit, resident memory by footprint polling (RLIMIT_AS rejected, V8's pointer cage makes it unbootable), a wall backstop, and JSONL diagnostics that rotate and name their dropped count.
4.1+4.2 |
The process boundaryjsrt supervise runs the host in a child and enforces what a child cannot enforce on itself — CPU seconds by rlimit the child can't decline, resident memory by footprint polling, a wall backstop, and a JSON report of exactly how it ended. |
64 | |
4.3 |
Bounded diagnosticsPer-run JSONL diagnostics under a byte cap — when the next record would overflow, the file rotates and the fresh generation opens by naming how many records it displaced. Bounded output, visible drops. |
64 |
M5 · stdlib wave 1
First-party APIs as ops: rt.kv — an embedded, namespaced, quota'd key-value store; rt.media — streamed content-addressed uploads to Bunny.net or local disk, where a mid-upload failure reports 'uncertain', never 'uploaded'.
5.1 |
rt.kvrt.kv — embedded SQLite behind a declared manifest line. The declared file is the namespace, the quota is the file's own bytes, and no grant means every call throws CapabilityError. |
70 | |
5.2 |
rt.mediart.media — streamed, content-addressed uploads into a declared store. 64 KiB chunks, sha256 computed in flight, a mid-upload failure removes its partial, and 'uncertain' is never relabeled 'uploaded'. |
77 |
M6 · operate a real app
rt.serve — a capability-gated HTTP listener; then the guide's ship gate verbatim: a small webhook processor deployed on jsrt, updated, broken, inspected, rolled back — every step committed evidence.
6.1 |
rt.servert.serve — a capability-gated HTTP listener. Pull-model dispatch: hyper owns the wire, the script's own event loop drives dispatch, and a crashing handler is just a JS exception in a loop the host owns. |
83 | |
6.2 |
the webhook drillThe webhook app ran the ship gate verbatim — deploy, update, break, inspect, rollback — and step one exposed a real lifecycle bug: killing the supervisor orphaned the worker, which kept holding the port. |
84 |
M7 · load and ship
The workspace load standard applied to the runtime itself: cold start on 1,000 scripts, 1,000,000 op calls, 1,000,000+ HTTP requests through rt.serve, a one-hour soak — committed results with machine and commit; misses kept, laptop numbers stay lab numbers.
7.1 |
cold start ×10001,000 release-binary spawns, wall clock from exec to exit — p50 10.8ms, p95 14.4ms, p99 28.5ms, one 138ms outlier kept in the record, zero failures. |
84 | |
7.2 |
1M through the boundaryThe workspace load standard applied to jsrt itself: 1,000,000 op calls and 1,000,000 HTTP requests through rt.serve — 90k rps, p99 under a millisecond, zero errors — plus the saturation probe that found the real knee. |
84 | |
7.3 |
the hour soakThe soak: 1.56 million timer/fetch iterations over one hour — pending handles identical at minute 1 and hour 1, 389,730 fetches with zero errors, and an RSS shape that says allocator, not leak. |
84 |