# NURL Roadmap

This roadmap describes where NURL is **today** and where it is going. It is
forward-looking and deliberately concise — the full, reverse-chronological
record of *what shipped when* lives in [`CHANGELOG.md`](CHANGELOG.md).
Anything marked done here has a regression test in
[`compiler/tests/`](compiler/tests/) and is covered by the bootstrap fixed
point.

_Last reviewed: 2026-10-27 · Current release: **0.79.1** · Language: **Grammar
v2.11** ([`spec/grammar.ebnf`](spec/grammar.ebnf))._

---

## Status at a glance

NURL is a small systems language with a regular prefix-arity grammar, a
self-hosted compiler written in NURL, and an LLVM backend. The compiler
bootstraps to a **byte-identical fixed point** on its own source (stage1 ≡
stage2). The only build dependency is clang / LLVM 15+.

The path to **1.0** is hardening, documentation precision, and external
validation rather than new language surface — see *Toward 1.0* below.

What is solid today:

### Language (Grammar v2.11)

Sum types (`|`) and product types (structs),
generics over structs and functions (incl. generics over option/result
types, closure types and trait objects), pattern matching with **match guards**, **or-patterns**, and
**N-ary payloads**, **trait bounds** on type parameters (`[A: Ord]`), **compile-time constant
folding** (`const_eval_int`), a full numeric type set (`i` = i64 and
`u` = byte/u8, plus sized `i8`/`i16`/`i32`, `u16`/`u32`/`u64`, `f` = f64
and `f32`), tail-call optimization, and
**variadic FFI** (the `printf` family callable directly). Since 0.40.0 the
language also spells the shapes a fast numeric kernel is written in:
**`v128`**, a first-class by-value SIMD vector type over ~31
`nurl_v128_*` primitives (§4.1b) that lowers to SSE2 / NEON, and to
wasm `simd128` where the module is built with it (a scalarised loop
otherwise), with no CPUID probe and no fallback path, and **wide
arithmetic** — `nurl_umulhi` (the high half of a 64×64 multiply) plus
`nurl_addc` / `nurl_subb` / `nurl_mac` for carry chains the backend
recognises. 0.46.0 adds the two that need a *runtime* decision:
**`v256`** (64-, 32- and 16-bit lanes) and the **`simd` declaration
prefix** (grammar v2.6, §3.3b) — nurlc emits one clone of the function
per instruction-set tier and dispatches on CPUID once per process, so a
kernel written once runs AVX2 where it exists and baseline where it does
not, with no `#ifdef` and no separate build. 0.49.0 adds the second
declaration prefix, **`inline`** (grammar v2.7, §3.3c): LLVM's
`alwaysinline`, for the case where the cost model is structurally wrong
— a helper whose arguments are constants at every call site, scored on
its whole body, called from a caller large enough to push the decision
the other way. 0.71.0 adds the third, **`unsafe`** (grammar v2.8,
§3.3d): the only functions whose body may touch raw memory. 0.73.0 adds
**`raw`** (grammar v2.9, §3.3e): a function whose soundness rests on its
caller, callable only from `unsafe` code, and a struct whose fields carry
such an invariant. The grammar
decision
for prefix-arity (no grouping delimiter) is formally locked, and since
0.37.0 the n-ary `&`/`|` arity trap it makes possible is a **hard error
by default** (`--no-strict-arity` demotes it to a warning) — the shape
compiled to working, wrong code before.

### Memory & safety

Single-owner memory with compiler-inserted auto-drop at
scope exit — no tracing GC, no hidden boxing. The **ownership rules are
always on** (`--strict-borrowck` adds audit checks) and reject
use-after-move, alias double-free, escaping closure captures,
interprocedural/return escape, loop-carried double-frees, a release
after storing into an owner, and iterator invalidation as hard errors without changing generated code.

Since 0.71.0 every program accepted without an `unsafe` function of its
own is **memory-safe and leak-free** ([`docs/MEMORY.md`](docs/MEMORY.md)
§6.2; 0.72.0 closed 0.71.0's one hole, a `Slice` of a `Vec` — views are
values — and the 37 its review found are closed since, each a probe in
`tools/fuzz/holes/` CI holds); `Rc` cycles are collected
and thread-shared cycles are rejected at compile time.

Since 0.44.0 **no rule depends on definition order**: every check that
consults a per-function summary parks what it cannot answer and
resolves it after the module, so where a helper is written can no
longer change a verdict.

Since 0.53.0 the coverage is measured from the
other direction: an **inverse-oracle fuzzer**
([`tools/fuzz/genreject.py`](tools/fuzz/genreject.py)) writes one
ownership violation into every context the language offers and demands
the diagnostic that violation deserves, not merely a failed compile. Its
first sweep found the two contexts where a *definite* double free still
compiled clean and segfaulted at run time — a `??` arm and a closure
body, the latter because the checker was switched off wholesale inside
one — and both are now analysed.

Since 0.55.0 a closure body's two
exits agree: one that falls off its end reclaims what it bound, where
only one that returned through `^` did, and `--no-borrowck` is a flag
the corpus runs rather than one nothing had ever exercised. 0.57.0
closed the other direction across that same boundary: the checker knew
what a closure body *frees*, but nothing tied a handle the enclosing
frame freed to a later call of a closure holding it, so every spelling
of that use-after-free compiled clean. **Invoking a closure is now a
read of everything it captured.**

### Concurrency

A stackful M:N work-stealing async runtime with **no
`async`/`await` colouring** — ordinary code runs unchanged under the
scheduler — plus threads/mutex/cond, typed channels, and Go-style `??`
channel **select**. Since 0.43.0, **`Send` / `Sync`** marker traits
([`stdlib/core/marker.nu`](stdlib/core/marker.nu)) are *derived* over a
type's whole graph and checked wherever a value crosses a thread
boundary — `thread_spawn`, `spawn`, `chan_send`, and an `Arc`'s payload
— so an `Rc` or a `Cell` reaching a worker is a compile error however
it is spelled, and `% NotSend` / `% Send` mark the cases a structural
derivation cannot see either way. Since 0.71.0 a thread closure moves
its captures and only share handles (`Channel`, `Mutex`, `Arc`,
`Locked`) cross threads; since 0.73.0 an `Arc`'s payload is frozen once
made and state threads change lives in a `Locked [T]` whose own lock its
operations hold, so data races are inside the guarantee with no lock
counted; marker assertions are the trusted part
([`docs/MEMORY.md`](docs/MEMORY.md) §3, §6.5). Since 0.75.0 threads
share an `Atomic` word, an `RwLocked [T]` whose read guards many
threads hold at once and lend frozen (`% Frozen`) views, and a `PFile`
read and written at offsets; `locked_await` waits on a `Locked` value
until a condition holds. Since 0.58.0 the Linux I/O path has no reactor thread:
an **idle worker becomes the poller** and blocks in `epoll_wait`
itself, so a ready fiber resumes on that worker with no queue hop, no
cross-thread wake and no migration (the Go netpoller / tokio driver
shape). Sockets register once per fd lifetime, edge-triggered, and
readiness that arrives while the fiber computes is banked so the next
wait skips the park entirely. The surplus a saturated poller cannot
keep spills into a **fair global FIFO** taken in batches, which is
what keeps the tail flat under load. Elsewhere a reactor thread polls:
`poll(2)` on the other POSIX systems, `WSAPoll` on Windows (busy
workers polling there too is agora #137). The contract — a wait may only follow an
`EAGAIN` — is in [`docs/ASYNC.md`](docs/ASYNC.md).

### Standard library

A broad pure-NURL stdlib (see the inventory below)
spanning collections, hashing, serialization, a full HTTP/1.1+2 + WebSocket
stack, database clients, distributed systems (p2p overlay, CRDTs), MCP, and the Anthropic Claude API.

### Targets

Linux x86_64 (primary, CI-tested), Windows x86_64 (CI-tested:
bootstrap fixed point + the Windows golden corpus on every push and PR,
plus a Linux-side mingw-w64 cross-link gate, since the Windows runner's
two toolchains are both UCRT and the cross one is not),
macOS ARM64 (CI-tested on Apple Silicon: bootstrap fixed point + the
full corpus against the same goldens as Linux, on every push and PR;
needs Homebrew LLVM, no prebuilt toolchain) and macOS x86_64
(cross-compiled Mach-O, no CI), `wasm32-wasi`, static Linux ARM64 / RISC-V64
(musl), and **bootable unikernel images** — a NURL program as its own
kernel on x86_64, AArch64 and RISC-V64, no host OS and no libc.
Tier definitions: [`docs/PLATFORMS.md`](docs/PLATFORMS.md).

### Tooling

`nurlc` (compiler), `nurlfmt` (canonical formatter), `nurl-lsp`
(language server), `nurlpkg` (package manager + test/bench runner), `nurldoc`
(API-doc generator), `tools/repl`, DWARF debug info (`--g`), a
VS Code extension, and `nurlapi` — a compiler-as-a-service container that
powers the public playground and MCP endpoint.

---

## Shipped

A high-level map of what exists. Dates and per-feature detail are in
[`CHANGELOG.md`](CHANGELOG.md).

### Compiler & language

- Self-hosted compiler (`compiler/nurlc.nu`) with a deterministic, byte-identical
  bootstrap; stage-0 links the committed `nurlc_lastgood.ll` snapshot (no
  Python in the toolchain).
- Grammar evolved v0.1 → **v2.11** (snapshots through v2.2 in [`spec/`](spec/)). v2.x added:
  visibility (`pub`) enforcement across functions, types, consts, and enum
  variants; trait bounds; match guards + or-patterns; const folding; channel
  select; **dynamic trait objects** (`%Trait` + `( dyn Trait v )`, v2.3);
  **`break` / `continue`** as reserved identifiers (v2.4); the **`v128`**
  SIMD lane type (v2.5); the **`simd` CPU-dispatch prefix** on `@`
  declarations (v2.6); the **`inline` always-inline prefix** (v2.7); the
  **`unsafe` raw-memory prefix** (v2.8); the **`raw` caller-vouched
  prefix** (v2.9); **FFI parameter markers** (`in`, `sync`, `nocapture`)
  and a foreach over a field (v2.10); **`unsafe % Send` / `unsafe % Sync`**
  and the **`v512`** lane type of the AVX-512 VAES crypto tier (v2.11); and locked the prefix-arity
  grouping decision.
- Type system: strong, static, inferred, algebraic; no subtyping, no implicit
  conversions. Sized integer/float types with **signedness carried in the
  type representation itself** (`u`/`u16`/`u32`/`u64` distinct from the
  signed types end to end — no flag side-channels); explicit `#` casts with
  correct `sext`/`zext`/`trunc`/`fpext`/`fptrunc`. Since 0.58.0 every value
  boundary — return and every argument path, including generic
  instantiations, trait-method dispatch, closure/fn-pointer invocation
  and `%Trait` dynamic dispatch — ends in a **total-agreement
  backstop**: after the sanctioned coercions the value's lowered type
  must equal the declared one, so a pair nobody enumerated is a
  diagnostic rather than a `call` clang assembles and the callee reads
  as garbage ([`docs/spec.md` §4.1](docs/spec.md)).
- Trait contracts are resolved across the complete import graph before body
  emission: declarations may follow their impls, associated types and defaults
  are order-independent, and explicit/default methods share argument-type and
  `inout`/`sink` signature registration.
- Generics: monomorphised generic structs and functions (signedness-aware
  monomorphs, including behind `*`/`?` prefixes), generic nesting
  (`Channel[A]`, `Vec[Thread]`), generics over `?T` / `!T E`, and closure
  types and trait objects as type arguments (`( Vec ( @ v i ) )`,
  `( HashMap i ( @ i i ) )`, `( Vec %Show )`, 0.74.0).
- Memory model v1.0 (0.67.0): `String`, `Vec`, owning structs, closure
  environments, handle enums (`Json`, `TomlValue`) and library handles
  (`HashMap`, `Set`, `Deque`, `BTree`, `Box`, `Rc`, `Arc`) are dropped by
  the compiler through per-binding drop flags, on panic paths too;
  containers drop their elements; recursive `Drop` for boxed enum/struct
  payloads, `% Drop` user destructors, move/borrow analysis (incl.
  interprocedural and loop-carried escape detection, consume after store).
  Since 0.68.0 the whole test corpus runs leak-clean under LeakSanitizer
  as a CI gate, `--lint` reports release calls the model made redundant
  (`[redundant-free]`), and the panic journal belongs to the fiber, so
  recover extents of interleaving fibers stay apart. Since 0.69.0 opaque
  library state is self-releasing handles and a raw pointer or closure
  outliving the value it borrows is a compile error; since 0.73.0 nothing
  a program keeps is released by hand — no package, example or bench
  kernel frees or claims memory itself, and the standard library's
  remaining such calls sit inside the owners that implement ownership. Since 0.70.0 ownership of a returned value is decided per call
  (a function that lends on one path and gives on another no longer leaks
  or double-frees), a borrowed value of a type that cannot be copied is
  never silently duplicated (a compile error says where to clone or move),
  and `--debug` builds at the default -O2. Since 0.71.0 the rules are a
  guarantee — every program accepted without an `unsafe` function of its
  own is memory-safe and leak-free (MEMORY.md §6.2; every hole found,
  through probe h388, is closed and held by a probe; the rules for
  single-threaded code are proven sound in Lean 4, `formal/` phases A1–A2,
  0.74.0) — `Rc` cycles
  are collected by a cycle
  collector, `Weak` and `ArcWeak` exist, and a cycle of thread-shared
  handles is a compile error. Model, guarantee and trusted base:
  [`docs/MEMORY.md`](docs/MEMORY.md).
- Front-end is diagnostic-first: malformed prefix-arity programs, undefined
  identifiers, call-arity mismatches, unbalanced braces / stray top-level
  tokens, and visibility violations are hard errors with source locations —
  nothing malformed reaches the backend silently. Since 0.39.0 that claim
  is measured corpus-wide: a mutation probe (one realistic mistake injected
  into each of the ~790 test programs, ~5 400 mutants) produces **zero**
  compiler hangs, zero crashes, and zero broken programs reaching the LLVM
  verifier or linker; every return path is type-checked (implicit fall-off
  and closure tails included), and errors inside generic/trait re-parses
  point at the template's real file:line with the instantiation named.
- Diagnostics are *measured*, not asserted, and since 0.64.0 measured **in
  CI**: `check_diag_coverage.sh` reports which of the compiler's 364
  `die`/`warn` sites a test has ever made it print (~85% provably, with the
  18 never-fired ones baselined so a new silent diagnostic fails the build);
  `check_diag_anchor.sh` gates that every baselined diagnostic points at the
  mistake rather than at the token after it; `diag_mutate.py` injects one
  realistic error into a working program and reads the answer. Between them
  they have found messages that were false, messages that were unreachable,
  and programs the compiler accepted and miscompiled.
- The v1-hardening sweep asks one question of every language surface: is this
  construct reached by a path that skips a check the compiler already performs
  in another spelling? Round after round it says yes, and the ledger of what
  was closed and what remains open is
  [`docs/dev/V1_HARDENING.md`](docs/dev/V1_HARDENING.md) — silent
  reinterprets, invented error payloads, calls that jump into a constant, IR
  only `llvm-as` rejects. Two oracles keep it honest: a token-deletion
  mutation sweep (`tools/fuzz/mutate_delete.py --clang`) that asks clang
  whether a surviving mutant's module is even valid, and `tools/tree_sweep.sh`,
  which requires byte-identical diagnostics over every tracked first-party
  `.nu` file so a new rejection rejects nothing that was valid.
- Emission: since 0.71.0 a library function (stdlib/, deps/) nothing in
  the program reaches is not compiled at all — about 40% fewer compile
  instructions across the corpus. Only the functions `main` can reach the
  `.ll` (`--no-dce` to emit everything). Reachability is computed over the
  finished IR, so closures, monomorphs, drop glue and dyn vtable thunks
  need no special casing — worth 30–40% of the clang step on a
  stdlib-heavy program. What survives is then emitted as several
  independent modules (`--split=N`) that the driver lowers concurrently
  and links with ThinLTO, taking the clang step on the compiler's own
  3.2 MB of IR from 11.3 s to 2.0 s — at a measured 3.4% of the built
  program's own speed (before 0.73.0 started copying small functions into
  every part that calls them, so they inline across parts), which is why `nurl.sh` splits your program and
  `build.sh` does not split the compiler it installs (`NURL_SPLIT=0`
  opts out). Since 0.40.0 that link is also **cached** — per-module
  ThinLTO backend codegen, the pre-link object keyed by content hash of
  the emitted IR, and the driver's toolchain probes, all under
  `~/.cache/nurl` — so an empty program links in 99 ms instead of 305
  and a warm rebuild costs less than the equivalent C compile
  (`NURL_CACHE=0` opts out; `NURL_LTO=full` links one full-LTO module,
  which on Linux inlines less than the default: the runtime is ThinLTO
  bitcode). Both passes:
  [`docs/BUILDING.md`](docs/BUILDING.md).
- Debugging: DWARF emission (`nurlc --g`) with `ptype`/`print` over structs.

### Standard library

Organised as `core/` (language essentials), `std/` (general-purpose), and
`ext/` (external-format / network / service bindings). All pure NURL except a
small C runtime (`stdlib/runtime.c`) for the bootstrap surface and a few
platform-specific shims.

- **core** — `string`, `vec`, `option`, `result`, `errors`, `char`, `slice`,
  `pair`, `box`, `cell`, `mem`, `io` (`write_bytes`, `read_all_stdin_bytes`), `symtab`, `posix`.
- **std/collections & algorithms** — `hashmap`, `set`, `deque`, `heap`,
  `ordmap`, `btree`, `lru`, `bitset`, `iter`, `sort`, `cmp`, `bytes`, `bufio`, `fmt`, `int`, `float`,
  `bigint` (arbitrary-precision integers), `decimal` (exact fixed-point).
- **std/runtime services** — `async`, `thread` (incl. `Locked [T]`,
  shared state threads change, and `locked_await`), `atomic` (`Atomic`),
  `rwlock` (`RwLocked [T]`, frozen read guards), `par` (`par_map_index`), `channel`, `arc`,
  `rc`, `arena`, `mmap` (a read-only file mapping lent as a checked
  view), `signal`, `panic`/`recover`, `process`, `unixsock` (local IPC),
  `log` (text + JSON), `time` (incl. local time via the platform's zone,
  timezone/DST, HTTP/RFC 2822 dates), `args` (CLI parser),
  `term` (POSIX termios, ANSI), `sysinfo` (`uname` fields, host name,
  processor count).
- **std/crypto & encoding** — **Post-Quantum Cryptography**: `mlkem` (ML-KEM-512/768/1024, FIPS 203),
  `mldsa` (ML-DSA-44/65/87 & HashML-DSA, FIPS 204), `slhdsa` (SLH-DSA, FIPS 205, SHAKE family),
  `hash_sha3` (SHA-3 & SHAKE128/256, FIPS 202). **Classical & symmetric**: `x25519`, `ed25519`,
  `ecdsa_p256` (P-256 + P-384 constant-time), `rsa` (PKCS#1 v1.5 + PSS), `aes_gcm`, `chacha20poly1305`,
  `hash` (SHA-1/224/256/384/512, SHA-512/224, SHA-512/256, MD5, HMAC), `hash_blake3`, `hash_xxh64` (XXH64),
  `hkdf`, `pbkdf2`, `scrypt`, `encode` (hex, base64, base32), `random` (OS CSPRNG `rand_bytes`), `rng` (xoshiro256\*\*).
- **std/PKI & certificates** — `x509` (DER parser + chain/host validation, ML-DSA & SLH-DSA OIDs), `x509_gen` (self-signed P-256 & ML-DSA certificates, PKCS#8), `csr` (PKCS#10 RFC 2986 parser, generator, self-signature verifier, PEM round-trip, CA issuance from CSR), `pkey` (PEM private-key loading: SEC1/PKCS#8 P-256, RSA, and ML-DSA). A complete private CA built on them — classical or post-quantum, with enrollment, CSR signing, revocation and CRLs — is the `pki-server` registry package.
- **std/compression** — `zstd` (pure-NURL RFC 8878 Zstandard decompressor & compressor up to level 19 optimal parser, no libzstd), `deflate` (RFC 1951 + table-driven CRC-32).
- **std/IO & net** — `fs` (incl. streaming, `stat`/`lstat`/`fstat` + `statvfs`,
  glob matching, `readlink`, `file_sync`, `dir_sync`,
  `file_truncate`, `set_permissions`, `set_times`), `pfile` (positional
  reads and writes shared by threads), `path` (typed),
  `net` (TCP/TLS), `udp`, `dns`, `dos`.
- **ext/serialization** — `json`, `toml`, `csv`, `msgpack`, `cbor`, `xml`, `yaml`,
  `protobuf` (checked Protocol Buffers wire readers/writers,
  [`docs/stdlib/protobuf.md`](docs/stdlib/protobuf.md)), `serde`, `regex` (a
  Pike VM, with capture groups and back references), `unicat` (Unicode
  general categories for tokenizers).
- **ext/web stack** — full HTTP/1.1 server (keep-alive, pipelining, static,
  auth, JWT bearer-auth with HS256/EdDSA/**ES256**, cookies, forms, multipart, router, middleware, access log + Prometheus
  metrics, DoS caps, graceful shutdown (`server_drain`: idle keep-alive closed, in-flight finished with `Connection: close` / HTTP/2+3 GOAWAY, deadline), per-request timeouts, panic recovery),
  HTTP client (with cookie jar), **Post-Quantum TLS 1.3** (client & server: `X25519MLKEM768` hybrid & pure ML-KEM key exchange, ML-DSA certificate support, client SNI + ALPN; per-hostname server certificates, mTLS and live cert reload are not yet supported on the pure-NURL server, [`docs/NETWORKING.md`](docs/NETWORKING.md)), **HTTP/2**
  (RFC 9113 + HPACK, **server and client** — served by every HttpServer /
  HttpApp listener, ALPN `h2` over TLS and prior knowledge on cleartext,
  h2spec-gated in CI), **HTTP/3 over a pure-NURL QUIC** (RFC 9000/9001/9002 +
  9114 + QPACK, served beside every HttpApp TLS listener with Alt-Svc,
  h3spec-gated in CI), **WebSocket** (RFC 6455, **server
  and client**, with **permessage-deflate** compression — RFC 7692),
  reverse proxy with binary-safe streaming. Since 0.46.0 the serve path's
  scaling mode is **fiber-per-connection** (`http_app_async n`): TLS record
  I/O parks on the reactor instead of blocking a worker pthread, and the TLS
  handshake runs on the connection's own fiber, so handshakes overlap rather
  than serialise on the accept loop. The stack has had a
  dedicated security-hardening pass (path-traversal, SSRF, request-smuggling,
  HTTP/2 CONTINUATION-flood + stream-accounting, and clean cross-thread
  listener shutdown) with regression tests, and its serve path is
  peer-benchmarked against Rust hyper and Node
  ([`bench/HTTP_RESULTS.md`](bench/HTTP_RESULTS.md): within 2.5% of
  hyper's plaintext throughput from c=10 up and well ahead of it at c=1,
  an HTTP request served in 2 syscalls).
- **ext/data services** — `sqlite` (production-hardened), `mqtt` 5.0 client,
  `smtp` (mail submission). Postgres and Redis clients live in the registry
  packages `psql` and `redis` (pure NURL — no libpq, no hiredis), and
  `nurldb` is a multi-threaded relational database in safe NURL.
- **ext/AI & agents** — `mcp` (+ `server` — the one server framework, on
  which every MCP server in the tree is built since 0.60.0, with its wire
  frozen by `compiler/tests/mcp_server_contract.nu`; `client`, `http`,
  `session`, `stdio`, `search`, since 0.40.0 `tasks` — the
  `io.modelcontextprotocol/tasks` extension, so a long-running
  `tools/call` returns a pollable task handle instead of holding a
  JSON-RPC response open — and since 0.61.0 `auth`, the OAuth
  resource-server half of the MCP Authorization spec: RFC 9728 protected
  resource metadata and the 401 challenge that points at it) and
  `anthropic` (Claude Messages API incl.
  streaming SSE + tool-use deltas).
- **ext/packaging** — `semver`, `manifest`, `lockfile`, `resolver`,
  `registry_index`, `pkg_fetch`, `pkg_publish` (the `nurlpkg` backend).
- **ext/misc** — `compress` (pure zlib/zstd/gzip), `zip` (archives), `tar`, `uuid` (v4/v7),
  `credentials`, `env`.
- **dist/distributed systems** — secure pubkey-addressed p2p overlay, STUN,
  NAT traversal, DERP relay, SWIM membership, state-based CRDTs (PN-Counter,
  LWW-Register, OR-Set), gossip replicator, consistent-hash ring, distributed
  computation (Crown).

### Targets & tooling

- Native: Linux x86_64 (CI-tested), Windows x86_64 (CI-tested), macOS
  ARM64 (CI-tested on Apple Silicon; needs Homebrew LLVM, no prebuilt
  toolchain), macOS x86_64 (cross-compiled, not CI-tested).
- WebAssembly `wasm32-wasi` (WASI SDK), including the compiler itself running
  in the browser playground, and **whole neural networks running client-side
  in the browser** — the pure-NURL ONNX runtime compiled to wasm, executing
  on the CPU (precompiled kernels) or on the visitor's **GPU via WebGPU** (the
  CUDA-C kernels translated to WGSL compute shaders; the `gpu` package's third
  backend). Live YOLOE segmentation and tiny-YOLOv2 detection run in a tab
  with no server inference (`packages/yoloe-demo`, the playground objdet
  demo).
- A **WebAssembly runtime written in pure NURL** (`packages/nwasm`) that
  decodes and executes real `wasm32-wasi` modules (full int/float instruction
  set, linear/bulk memory, tables + `call_indirect`, threads + atomics,
  sockets, WASI + `--dir` file ops), with no external runtime — and with
  two JIT tiers on top over guard-page linear memory: a **register-allocating
  JIT** (tier 8, the default on x86-64) and the template JIT it falls back
  to. Over `bench/wasmbench.sh`'s corpus it runs every one of the 60
  modules faster than the reference Cranelift JIT's wall clock (nwasm
  2.4.0, 2026-10-09: geometric mean 0.58× for the NURL modules) — and the
  control columns say this is not a NURL-shaped fast path: the same corpus
  emitted by **clang and rustc** runs at 0.62× and 0.56×. The compiler
  **self-hosts on wasm**: `nurlc` compiled to `wasm32-wasi` recompiles
  `nurlc.nu` to byte-identical IR, both under the external reference
  `wasmtime` and under this pure-NURL runtime — and identically under its
  interpreter and both JIT tiers.
- Static cross-compiles: Linux ARM64 / RISC-V64 (musl). Milk-V Duo (RISC-V
  C906) validated on-device.
- **Unikernel: a NURL program boots as its own kernel** — no host OS, no
  libc, no interpreter — on three architectures: x86_64 (QEMU microvm,
  and the same PVH image boots under Firecracker and cloud-hypervisor),
  AArch64 and RISC-V64 (QEMU virt; on AArch64 a flat `Image` wrapper
  covers Firecracker/cloud-hypervisor). Since 0.40.0 the x86_64 image
  also carries a Multiboot2 header beside its PVH note and packages into
  a hybrid BIOS+UEFI disk, so it **boots on real PC hardware off a USB
  stick** — with screen output (EGA text or a UEFI framebuffer) where a
  microvm had a serial port, and a PIT-calibrated TSC where no firmware
  states the frequency. Per-arch QEMU gates run the hosted corpus
  against the same goldens (19–20 each, including the SIMD corpus),
  with virtio net/rng/blk drivers, TLS handshakes in the guest, native
  fiber switches on all three ISAs, and CI booting every architecture on
  every commit.
  Since 0.48.0 a guest can also **keep state**: `--disk` links in a
  virtio-blk driver and a read/write FAT12/16/32 filesystem, so the
  ordinary `std/fs.nu` calls reach a volume any host can read — checked
  by `fsck.vfat` and by an independent reader, across reboots. The same
  release closed the endpoint milestone (a guest that boots straight
  into a **swarm compute appliance**, answering expression and wasm
  tasks) and ran the compiler itself inside the machine: `nurlc.wasm`
  compiles in the guest to output byte-identical to native, and takes
  the bootstrap fixed point there.
  The playground builds and boots these images (`POST /build_unikernel`
  + target dropdown), and agents do the same over MCP
  (`nurl_build_unikernel`).
- **A userland in one binary** — [`packages/nurlbox`](packages/nurlbox/README.md)
  is a busybox-shaped multi-call executable: 100 of the everyday UNIX
  utilities, `sh` among them, in pure NURL over the shipped stdlib with
  nothing to link beyond libc. It builds for Linux, macOS, Windows and
  `wasm32-wasi`, and boots as its own kernel on the unikernel target. 348
  differential cases hold it to the original: the same command line
  through nurlbox and through the system busybox, agreeing on stdout, on
  the bytes, and on the exit status.
- `nurlfmt` canonical formatter (idempotent, IR-preserving), `nurl-lsp`
  (completion, references, unused-symbol lint), `nurlpkg` package manager,
  DWARF debugging, VS Code extension, and the `nurlapi` compiler-as-a-service
  container (playground + cross-compile endpoints + public MCP server).
- Showcase programs: a Game Boy emulator (with sound), a C64 demo and ESP32
  targets in [`examples/`](examples/), Milk-V Duo programs in
  [`duo/`](duo/), and a Push-To-Talk distributed voice app (`pttvoice/`).
- **Language models run locally, in pure NURL** (`nurllama` + `gguf`):
  pull a GGUF model from HuggingFace (resumable, content-addressed store),
  then `run`, `chat`, or `serve` an **ollama-compatible API** that existing
  clients speak unchanged. A hostile-input GGUF parser, a tokenizer read
  from the model's own metadata, and a llama forward pass whose matvec
  kernels **decode quantised blocks inside the matmul** — Q4_0…Q8_0 and the
  K-quants (Q4_K/Q5_K/Q6_K) — so weights never expand to f32 on the device,
  and the same kernel sources run on the CPU backend byte-identically.
  Verified against independent references at every layer: token IDs vs a
  SentencePiece implementation, logits and greedy text vs a numpy forward
  pass, dequantisation bit-identical to an independent decoder. Architectures
  span llama / qwen2 / gemma3 / phi3 and, since 0.21.0, **diffusion** language
  models: LLaDA2.x, a Mixture-of-Experts `llada2` model that converts from its
  Hugging Face checkpoint (`nurllama convert`, streaming a model larger than
  RAM to GGUF at constant memory) and generates by block denoising — parallel
  commits with token editing, not left-to-right — its logits matched to a
  numpy forward and its decoded ids to a reference-faithful loop.
- **Speech recognition, in pure NURL** (`whisper` + `audio` + `safetensor` +
  `tokenizer`): a WAV goes in and text comes out — *word for word what
  Hugging Face transformers produces from the same model*, on whisper-tiny
  and on distil-large-v3, on the GPU or the CPU. Every stage is verified
  against an independent implementation rather than against our own
  understanding of it: the resampler against scipy, the log-mel against HF's
  own feature extractor (r = 1.00000000, on both 80 and 128 bands), the
  400-point FFT against numpy (Bluestein, because 400 is not a power of two
  and padding it computes a *different* transform), the weights bit-exact,
  the vocabulary against HF's tokenizer, the encoder against HF's encoder,
  and the transcription against HF's. Whisper is not the llama shape —
  LayerNorm rather than RMSNorm, error-function GELU rather than tanh, two
  conv1d layers, cross-attention, and the ecosystem's first **non-causal**
  attention.
- **Speech synthesis, in pure NURL** (`f5tts` + `audio` + `safetensor` +
  `torchpt` + `gpukit`): a reference recording and its transcript go in and
  the same voice comes out saying something it never said — F5-TTS, the
  flow-matching model, from the checkpoints Hugging Face ships, with no
  Python and no ONNX export. A 22-block diffusion transformer with adaLN-zero
  modulation, conditional flow matching with classifier-free guidance and
  sway sampling, and the vocos vocoder. Verified against the reference with
  fixed inputs at every stage: the text front-end id for id, the text encoder
  at 2.6e-6, the whole forward at 2.3e-6, 32 guided ODE steps at 7.4e-6, the
  waveform at 2.9e-4. What the model is *not* is the whole program — the
  duration is guessed linearly, the reference is loudness-normalised and
  scaled back, and long text is chunked and cross-faded, none of which is
  learned and all of which changes what comes out. The service answers wav,
  mp3 or raw PCM; the **MP3 encoder** is `audio`'s own (MPEG-1/2/2.5 Layer
  III, all nine sample rates, no ffmpeg and no codec library), because a
  program that can only hand back WAV cannot answer a request for an mp3.
- **Package ecosystem** on the live registry (`reg.nurl-lang.org`,
  `nurlpkg install`): GPU compute (`gpu` — CUDA driver + NVRTC, a CPU
  fallback backend, a static-kernel backend, and a **WebGPU / WGSL backend**;
  `gpukit`, `tensor`), pure-NURL vision and ML (`image` PNG/JPEG codecs,
  `onnx` runtime, `objdet`, `yoloe`, `iforest`, `anomaly`, and `mlp` — the
  first *trainable* package, a deterministic sklearn-faithful MLP regressor),
  local LLMs (`nurllama`, `gguf`, `tokenizer`, `safetensor`), speech
  recognition and synthesis (`whisper`, `f5tts`, `audio` — WAV, resampling,
  mel spectrograms and MP3 encoding), agent coordination (`agora` — channels,
  mail, a task board and notes over one SQLite file, as MCP and REST),
  distributed compute (`swarm`, `swarm-mcp`), web
  (`template` HTML templating, `http`, `http-client` — one client facade
  over HTTP/1.1, HTTP/2 and HTTP/3 — `gRPC`, a native client and server over
  that HTTP/2, and `oauth`, an OpenID Connect relying party and
  resource-server guard in pure NURL), PKI (`pki-server` — a private CA
  with a classical *or* post-quantum signing key), database clients
  (`psql`, `redis`
  — pure NURL), a relational database (`nurldb` — SQL, MVCC, a write-ahead
  log and B+tree indexes, multi-threaded, no `unsafe` function), and application scaffolding (`cli`, `cas`, `wasmbuilder`,
  `nurl-mcp`).
  Installed tools carry runtime data via the manifest's `[install] assets`
  mechanism (staged into `<prefix>/share/<name>/`).
- **The registry itself is a NURL program** (`packages/registry`): a
  self-hostable server speaking the exact `nurlpkg` wire protocol —
  bearer-authenticated publish with server-side checksums, name ownership
  and version immutability, yank/revoke, search, and a server-rendered
  catalog that renders each package's README straight from its published
  tarball. SQLite as the single source of truth; built on the `http`,
  `template` and `md2html` packages and proven end-to-end against the real
  client. (The public reg.nurl-lang.org cutover from the Cloudflare Worker
  is pending deployment.)
- **Signed packages, verified in pure NURL.** The registry signs every
  published tarball with a project Ed25519 key; `nurlpkg` pins the public key
  and verifies the detached minisign signature — using a pure-NURL BLAKE2b +
  minisign implementation (`std/hash_blake2b`, `std/minisign`) — before
  unpacking, **mandatory and fail-closed**. Release archives are signed too;
  the installers always verify the checksum fail-closed and verify the
  minisign signature against a pinned key when `minisign` is available.
  Since 0.41.0 the packer also excludes compiler output by extension, so a
  given revision packs to the same bytes from any clean checkout — a
  checksum identifies the source, not the machine that happened to build it.

---

## Toward 1.0

The remaining work to declare a stable 1.0 is mostly precision and proof, not
new language features.

### Brand: Logo, Images

- Switch from generated imagery to a human-designed one.

### Documentation precision (safety & soundness)

- [x] **State the safety contract exactly** — which bug classes the
  ownership rules reject, with no implied Rust-equivalence
  ([`docs/MEMORY.md`](docs/MEMORY.md), [`docs/LIMITATIONS.md`](docs/LIMITATIONS.md)).
  *(Resolved in 0.71.0: safe programs are guaranteed memory-safe and
  leak-free, MEMORY.md §6; the one `Slice` exception was closed in 0.72.0,
  below.)*
- [x] **Write the soundness story** — decide and document whether
  interprocedural escape analysis and `*T` raw-pointer flows are on the
  roadmap or out of scope by design. *(Resolved: interprocedural escape and
  return-escape implemented; in 0.71.0 raw pointers, casts, raw-memory
  primitives and user FFI are confined to `unsafe` functions, spec §3.3d,
  `nurlc --unsafe-report`.)*
- [x] **Document the known auto-drop leaks** (nested owned-struct fields,
  arm-local fall-through bindings, allocations inside a `recover` scope). *(Resolved: leaks fixed.)*
- [x] **Close the `Slice` hole** — a `Slice` built from a `Vec`
  (`slice_from_vec`, `slice_sub`, `slice_from_raw`, protobuf's
  `ProtoReader`) was not tracked as a view of it, the one known exception to
  the 0.71.0 guarantee (hole probe `tools/fuzz/holes/h32`). *(Resolved in
  0.72.0: views are values in the borrow walk — tracked through structs,
  Options, containers, closures, globals and function results — with 109
  more probes (h33–h141) closed on the way: sealed representations,
  exclusive calls, closure effects, accessors and field paths, checked
  allocation sizes, temporaries, ownership summaries, the raw foreign
  surface, null strings, method calls asked of their impls (static and
  `dyn`), strings that own their buffer, and raw strings in values
  (MEMORY.md §2.13); docs/HARDENING_PLAN.md.)*
- [x] **Close the holes the 0.72.0 review found** — 37 safe programs the
  compiler accepted and that faulted under the sanitizers: raw strings
  handed to `string_adopt` or a `sink s`, views leaving through a helper's
  return value or into a container reached indirectly, `mem_forget` /
  `mem_take` / `rcbox` callable from safe code, destructors, closure calls
  with a fresh string, and trust by `/stdlib/` path. All closed, each at
  its root, and held by a probe in `tools/fuzz/holes/` (h142–h179; h179 a
  view stored into a field of a struct that outlives it, found on the way).
  In safe code only a declared `sink` takes a raw string over and its
  callee releases it — a caller hands over its own string or a copy — so
  nothing is released by hand.
- [x] **Close the holes found since** — h180–h273 while closing the 0.72.0
  review's (0.73.0), h274–h287 (0.74.0): ten found by modelling
  closures for the proof below — a closure that changes a capture, run
  where something else holds a view of it — and four by making closure
  types generic arguments (an option or result of a closure that owned
  nothing, a closure read out of a field); and h288–h298 (0.75.0), found
  building a multi-threaded database (`nurldb`): frozen views, guards in
  structs and argument temporaries on the panic path, closures replaced
  through `inout`; and h299–h315 (after 0.76.0), found probing what the
  library's doc comments still asked of callers: closures that outlived
  what they read — a closure a call returned over its argument, the
  library's iterators, a by-value capture whose binding was given a new
  value — and lends returned through a join (`lru_get`), h311–h315;
  h316–h321 (0.77.0–0.78.0); and h322–h370 (0.79.0): fields that cannot
  be copied, field borrows, a call's temporaries on the panic path, the
  MCP task store, and, making threads sound, a trait object's Send, a
  handle's copies left behind at a crossing (checked at run time), the
  journal's drop flags, a `dyn` method's effects and a borrowed `inout`
  argument replaced; h371–h377 (0.79.1): an assignment's value
  dropped by the `?` statement it ended, a binding given its own value
  through a join, and closure envs a panic skipped; and h378–h383: a view
  of a call's temporary argument kept past its statement (rejected), and
  a `UdpSocket` used after `udp_close` or never closed (a handle with a
  drop now); and h384–h388: a closure returned over a parameter in a
  spelling its function's summary missed (bound first, joined, given a
  new value, over a view). None is open.
- [ ] **Mechanized soundness proof** ([`formal/`](formal/README.md)) —
  phases A1 (the core calculus) and A2 (structs, enums, closures and trait
  objects, panics, the drop journal, leak freedom) are proven in Lean 4
  as of 0.74.0 (`A2.memory_safe`, `A2.no_leak_end`). Open: A3 (threads,
  data-race freedom), B (the standard library's `unsafe` base), C (nurlc
  implements the rules, by certificate) and D (code generation).

### Evidence for the "LLM-native" thesis

These convert a hypothesis into measured results.

- [x] **Tokenizer-level token-count study** — measured real BPE tokens (not
  characters) for NURL vs Python/Rust/JS across 8 matched programs
  ([`bench/TOKEN_EFFICIENCY.md`](bench/TOKEN_EFFICIENCY.md)). *Result: the
  raw token-count claim did not survive measurement — on today's tokenisers
  NURL is ~1.7× Python's tokens (median), losing to out-of-distribution
  glyph fragmentation. The claim was retired; the defensible arguments are
  grammar regularity and first-pass compile success.*
- [x] **Controlled generation-accuracy comparison** — first-pass compile +
  correctness, NURL vs Python/Rust, across four models (Sonnet 4.6 / Opus
  4.8 / Haiku 4.5 / mercury-2 diffusion), primed with NURL's one-page
  reference since the training corpus contains zero NURL
  ([`bench/genacc/`](bench/genacc/), results in
  [`bench/genacc/RESULTS.md`](bench/genacc/RESULTS.md)). *Result: from a
  single page a model reaches the Python/Rust ballpark on several tasks but
  not parity first-try; failures are out-of-distribution habits (imports,
  then grouping-parens, then mutability) that targeted primer cues fix in
  turn. The follow-up measured the agentic half of the claim: with ONE
  round of compiler-diagnostic feedback (`bench/genacc/repair.py` — the
  model sees only its program and the compiler's stderr, never the
  expected output), Sonnet and Opus reach **exact Python/Rust parity —
  100% compile, 100% correct** — Haiku reaches 100% compile, and the
  diffusion model quadruples its score. The diagnostic-first compiler is
  the load-bearing artifact, and its value is now measured, not asserted.*
- [ ] **Separate the language claim from the MCP-integration claim** in
  project copy — "an agent can drive the toolchain over MCP" is a tooling
  win, not evidence the *language* is better for LLMs.

### Project health

- [ ] **Reduce bus factor** — recruit at least one additional reviewer /
  maintainer and publish a short governance note (license stays MIT OR
  Apache-2.0).

---

## Planned (post-1.0 direction)

Not blocking 1.0; ordered roughly by likely value.

- **Mobile & embedded targets** — Android (NDK), iOS, and a `no_std`-style
  embedded profile. The RISC-V / ARM64 static cross-compiles already prove the
  shape; these extend it.
- ~~**Runtime split (organisational)**~~ — *done.* `stdlib/runtime.c` is
  split into `stdlib/runtime_core.c` (bootstrap core) and
  `stdlib/runtime_ffi.c` (stdlib FFI shims), stitched by a thin aggregator
  so the single `runtime.o` build is unchanged. The core compiles
  standalone and defines the symbol set the `no_std` profile links.

---

## Research / exploratory

Ideas under consideration, no committed timeline.

- **Compiler-embedded LLM** — LLM-assisted, self-correcting compile-error
  suggestions, leaning on the regular grammar and local error semantics.
- **Additional backends** — JVM bytecode and .NET CIL are conceivable given
  the simple IR, but are not on the near-term path.

---

## Non-goals

Deliberate exclusions, to set expectations:

- **GC or reference-counting by default.** Single-owner + auto-drop is the
  model; `rc`/`arc` are opt-in library types.
- **`async`/`await` function colouring.** Concurrency is via stackful fibers;
  ordinary code is scheduler-agnostic.
- **Infix operators / operator precedence.** Prefix-arity is the grammar's
  whole point.
- **Implicit numeric conversions / subtyping.** Casts are always explicit
  (`#`).
- **Catching hardware faults** (SIGSEGV/SIGFPE/SIGBUS) via `recover` — only
  explicit `panic` is recoverable; faults remain process aborts.

---

## How to read progress

- **Shipped, with proof** → it has a test in `compiler/tests/` and survives
  the bootstrap fixed point; details in [`CHANGELOG.md`](CHANGELOG.md).
- **Toward 1.0 / Planned** → unchecked boxes here.
- **Language reference** → [`docs/spec.md`](docs/spec.md) (normative) and
  [`spec/grammar.ebnf`](spec/grammar.ebnf) (authoritative grammar).
