Skip to content

v0.9.8

Latest

Choose a tag to compare

@Hindurable Hindurable released this 15 Jun 20:23
· 31 commits to main since this release
d03d981

[0.9.8] — 2026-06-15

Added

  • NAT- and mobile-traversing distributed transport (§7.4). A complete
    pubkey-addressed overlay where a peer is a public key, not an address
    it reaches that key over a direct peer-to-peer path when it can and a relay
    when it must, and never drops to zero reachability. Built bottom-up:
    net/securedgram.nu (WireGuard-style encrypted UDP over Noise + a session
    AEAD with a sliding replay window, plus endpoint roaming so a peer survives
    a network change), net/stun.nu (RFC 8489 server-reflexive discovery),
    net/nat.nu (candidate gathering, NAT-type classification, UDP hole punch),
    net/relay.nu (DERP-style opaque forwarding plus group multicast
    broadcast to your own group), net/rendezvous.nu (signaling-only directory),
    net/transport.nu (the flat seam: transport_send/broadcast/recv,
    direct-when-possible/relay-when-forced with promote/demote), and SWIM
    membership (net/membership.nu) hardened by Lifeguard
    (std/lifeguard.nu) with a failure-detector control loop
    (net/failuredetector.nu). On top sit the sharding + replicated-state
    layers: a consistent-hash ring (dist/ring.nu), state-based CRDTs
    (dist/crdt.nu — PN-Counter, LWW-Register, OR-Set) and their gossip wiring
    (dist/replicator.nu, anti-entropy scoped to a key's replica set). Documented
    end to end in docs/DISTRIBUTED.md.

  • The Crown — distributed computation (§7.5). Turns the distributed state
    above into distributed work. dist/identity.nu gives each peer a replica
    id; dist/job.nu is the keystone — submit a task keyed by k, the ring owner
    executes it via a registered handler, the result is recorded idempotently, and
    a key that re-homes mid-flight is forwarded to the new owner so the job
    still completes. dist/lease.nu adds fencing tokens (epoch monotonicity +
    idempotency keys) so a side-effecting task fires at most once across a
    split-ownership window. Liveness under load is handled by SWIM
    self-refutation plus a heartbeat on a dedicated OS thread
    (dist/heartbeat.nu), so a 100%-CPU node is not falsely evicted. The whole
    story is verified by a deterministic chaos-simulation harness
    (dist/sim.nu): a virtual clock + in-process message bus with seeded fault
    injection (drop, latency/jitter→reorder, partition/heal) drives the real
    stdlib logic, with scenarios for converge-under-loss, keystone-across-
    partition, at-most-once-side-effect, and CPU-pinned-not-evicted — all
    byte-reproducible goldens, ASan-clean.

  • Push-To-Talk voice app — pttvoice/. A distributed PTT voice app on the
    overlay: captures the microphone, Opus-encodes it (48 kHz mono, 20 ms
    frames via a libopus FFI binding), and pushes a talkspurt either to one peer
    (unicast — p2p when punchable, relayed otherwise) or to the whole group
    (multicast); a receiver decodes and plays it. ALSA capture/playback
    (audio.nu), the codec (opus.nu), the voice wire frame (proto.nu), and
    the app (ptt.nu) live in a self-contained folder. Verified live over a
    loopback relay (group broadcast and peer unicast). build.sh now detects
    libopus and ALSA (dropping stdlib/runtime.{opus,asound} sentinels)
    and nurl.sh auto-links -lopus/-lasound when those FFI symbols appear.

  • Playground “🎙️ PTT Chat” demo with channels. A new /pptchat tab: a
    page with a microphone button and an embedded NURL→WebAssembly module
    (nurlapi/static/pptchat.nupptchat.wasm) that reads the mic through the
    audio FFI and paints a live VU meter + frequency spectrum, framing the
    distributed voice tech. Channels: no id → the shared public channel;
    + Create channel mints a random id and navigates to /pptchat/<id>;
    opening that URL joins the same channel (the URL is the invite, the future
    shared-secret/QR), with a Copy-link button.

  • Parallel sanitizer test suite. compiler/tests/run_san_tests.sh now runs
    AddressSanitizer/UBSan checks in parallel (NURL_SAN_JOBS, default = cores),
    cutting the sanitized CI leg from minutes to under one — matching the already-
    parallel functional runner.

  • HTTP client cookie jar — stdlib/ext/cookies.nu (critic B23). The
    server side writes Set-Cookie (ext/http_auth.nu); this is the missing
    client half. cookie_jar_set parses one Set-Cookie value (Domain,
    Path, Expires, Max-Age, Secure — Max-Age wins over Expires, an
    already-expired cookie deletes its stored match) defaulting Domain/Path
    from the request host/path; cookie_jar_header returns the Cookie:
    value for a request, applying RFC 6265 domain matching (§5.1.3,
    host-only vs subdomain), path matching (§5.1.4), Secure gating, and
    expiry, longest-path-first. Pure string-in/string-out — decoupled from
    the HTTP client types, so it round-trips a session over HTTP/1.1, h2,
    or any header source. now (unix seconds) is passed explicitly for
    deterministic expiry. Lock: compiler/tests/cookies_basic.nu
    (host-only vs Domain, path ordering, Secure, Max-Age/Expires expiry,
    replacement, Max-Age=0 deletion, malformed rejection); ASan+UBSan+LSan
    clean.

  • Benchmark harness — stdlib/std/bench.nu + nurlpkg bench (critic
    C4). std/bench.nu times a no-arg closure over many iterations and
    reports ns/op (via the monotonic clock) and allocations/op.
    bench_run name iters body runs a short untimed warmup then a timed
    loop; bench_auto name body auto-scales the iteration count until a
    pass clears ~50 ms, for stable numbers on sub-microsecond operations.
    bench_report prints one line; bench_result_* accessors expose the
    raw numbers. The allocation metric is backed by a new runtime hook,
    nurl_alloc_count (a relaxed-atomic counter on every
    nurl_alloc/nurl_zalloc — which is what stdlib vec/string/struct
    blocks route through), snapshotted around the timed loop so warmup is
    excluded. nurlpkg bench discovers benches/*.nu, compiles each at
    -O2, runs it, and streams its report (no goldens — wall time is
    machine-dependent; a bench fails only on a compile error or nonzero
    exit). Ships bench/stdlib_hotpath.nu (string build / vec push / sort
    micro-benches). Locks: compiler/tests/bench_basic.nu (deterministic
    surface — report formatting, the alloc counter on a vec cycle vs a
    no-op body, iteration bookkeeping) and
    compiler/tests/nurlpkg_bench_smoke.sh (runner discovery / streaming /
    summary / exit codes).

  • nurlpkg test — user-facing test runner (critic C3). Ships the
    compiler suite's per-test pattern as a tool: nurlpkg test discovers
    tests/*.nu, compiles and runs each, and reports PASS/FAIL with a
    summary (exit 0 iff every test passes). A test passes on exit 0; if a
    tests/outputs/<name>.txt golden exists, the program's stdout must
    match it byte-for-byte instead. Tests run in sorted order for
    determinism. The build driver is ./nurl.sh by default, overridable
    via $NURL_CC (a command taking <flags> <src> <outbin>) for an
    installed toolchain. Smoke-tested by
    compiler/tests/nurlpkg_test_smoke.sh (all four verdict paths +
    all-pass/any-fail exit codes + the empty-tree message).

  • REPL — tools/repl (nurl repl) (critic C1). An interactive
    read-eval-print loop on a process-per-eval model: top-level definitions
    (@ functions, & FFI, $ imports, and : types / enums / globals)
    accumulate into a persistent session, while every other line is spliced
    into a fresh main, compiled with ./nurl.sh -O0, and run — its stdout
    is echoed back. A new definition is validated by a fast build/nurlc
    frontend pass before it joins the session, so a typo never poisons later
    evaluations. Line editing + history come from std/term.nu (the B10
    work); on a non-tty (pipe / script) it falls back to plain buffered
    reads. All REPL chrome — prompts, acks, errors, :help — goes to
    stderr, so stdout carries only the evaluated program's output. Meta-
    commands: :help/:h, :quit/:q, :defs, :reset, :save FILE.
    Multi-line definitions are read until brackets balance. Build with
    ./tools/repl/build.sh; smoke-tested by compiler/tests/repl_smoke.sh
    (definitions + globals persist across lines, a bad definition is
    isolated, stdout stays clean). Note: process-per-eval re-initialises a
    : global on every evaluation — definitions and pure functions persist,
    but mutation does not accumulate across lines.

  • Bitset — stdlib/std/bitset.nu (critic B18, collections round-out).
    A fixed-size bit array over 64-bit limbs: bitset_set / bitset_clear
    / bitset_flip / bitset_test (all bounds-checked, so the unused high
    bits of the last limb stay clear), bitset_set_all / bitset_clear_all,
    a popcount-backed bitset_count, bitset_any / bitset_all /
    bitset_none, the in-place combiners bitset_and_with / bitset_or_with
    / bitset_xor_with, bitset_clone, and an ascending bitset_each_set.
    Storage is a flat nurl_zalloc word buffer peeked/poked by limb. NURL
    has no native XOR or NOT operator, so the module uses the exact,
    carry-free identities a ^ b = (a|b) - (a&b) and ~m = -1 - m. Lock:
    compiler/tests/bitset_basic.nu — cross-limb set/clear/flip, out-of-
    range no-ops, popcount, all() on a full set, the three combiners with
    an XOR-identity bit check, and clone independence; ASan+UBSan+LSan clean.

  • LRU cache — stdlib/std/lru.nu (critic B18, collections round-out).
    A fixed-capacity LruCache [V] over string keys, backed by a HashMap
    (key → slot) plus an intrusive doubly-linked recency list over
    preallocated slot arrays with a free list — so lru_get / lru_put /
    lru_contains / lru_remove are all O(1) and a cache at capacity does
    no further allocation. lru_get moves the key to MRU; lru_peek does
    not; lru_put returns the displaced value (replaced or evicted), owned;
    lru_each walks MRU→LRU; lru_free_with drops each value on teardown
    (the owned-element discipline the deque/heap work established). The map's
    hash/eq closures are non-capturing, so they allocate nothing per call.
    Lock: compiler/tests/lru_basic.nu — eviction order, get-as-touch
    survival, peek-without-reorder, replace-returns-old, remove, the MRU→LRU
    walk, and the owned-String free_with path; ASan+UBSan+LSan clean. With
    the B-tree and bitset, this closes critic B18.

  • B-tree ordered map — stdlib/std/btree.nu (critic B18). A
    BTree [K V] with O(log n) insert / lookup / delete, replacing the
    array-shift backing for large ordered maps. Classic CLRS proactive
    split-on-descent and borrow/merge-on-descent (minimum degree 8, so up
    to 15 keys per node) keep the tree balanced; each node also caches its
    subtree size, which makes btree_key_at / btree_val_at order-
    statistic queries (the i-th smallest key) O(log n) too. API:
    btree_new / btree_len / btree_is_empty / btree_get /
    btree_contains / btree_set / btree_remove / btree_min_key /
    btree_max_key / btree_key_at / btree_val_at / btree_each /
    btree_free / btree_free_with. Nodes are raw 6-slot blocks with
    typed-pointer access (the same generic-container pattern as
    std/set.nu). Lock: compiler/tests/btree_basic.nu — a 2000-key
    scrambled fill with replace, remove-every-third churn, order-statistic
    and ascending-iteration checks, and full drain; ASan+UBSan+LSan clean.

  • Terminal control — stdlib/std/term.nu (critic B10). The
    prerequisite for a REPL and TUI examples: POSIX termios raw mode
    (term_raw_enable / term_raw_disable, with struct termios sized
    via nurl_native_sizeof so the platform layout never leaks into NURL),
    term_is_tty, a full set of byte-exact ANSI builders (ansi_reset /
    ansi_sgr / ansi_fg / ansi_bg / ansi_clear / ansi_clear_line /
    ansi_cursor_to / ansi_cursor_up/down/right/left), and a
    minimal raw-mode line editor (term_read_line) with printable insert,
    backspace, ←/→, ↑/↓ history, Ctrl-A/E/K, and a clean not-a-tty None
    fallback. Adds TCSANOW / TCSAFLUSH to the runtime's
    nurl_native_constant table (POSIX-only; Win32/WASI return None from
    raw mode while the ANSI builders still work — Windows Terminal speaks
    VT). Lock: compiler/tests/term_basic.nu — the tty-independent surface
    (None on a file fd, byte-exact ANSI hex); ASan+UBSan+LSan clean.

  • ZIP archives — stdlib/ext/zip.nu (critic B15). A reader and
    writer for the ZIP format over the existing zlib FFI. Writer: zip_new
    / zip_add (raw-deflate, windowBits −15, falling back to store when
    deflate would not shrink the entry) / zip_add_stored / zip_finish,
    emitting local headers, the central directory, and the EOCD with a
    fixed DOS timestamp so archives are byte-deterministic. Reader:
    zip_open (backward EOCD scan over the comment window, with zip64
    rejected as unsupported) / zip_count / zip_name_at / zip_size_at
    / zip_extract / zip_extract_name / zip_close, every extraction
    CRC-32-validated. Lock: compiler/tests/zip_basic.nu — build (deflate

    • store + a compressible 5000-byte entry), re-open, CRC-checked extract
      of each entry, by-name extraction, missing-name and junk-archive
      rejection; cross-checked against system unzip -t; ASan+UBSan+LSan
      clean.
  • SMTP client — stdlib/ext/smtp.nu (critic B17). A mail-submission
    client over the runtime's client-side TCP/TLS connect: smtp_connect /
    smtp_connect_tls, EHLO capability discovery (smtp_ehlo /
    smtp_has_cap), smtp_starttls (RFC 3207 — upgrades the live
    plaintext fd to TLS and re-EHLOs), smtp_auth_plain / smtp_auth_login
    (RFC 4954), the smtp_mail_from / smtp_rcpt_to / smtp_data
    envelope (DATA dot-stuffs and terminates per RFC 5321 §4.5.2),
    smtp_quit / smtp_close, plus a minimal RFC 5322 MIME builder
    (mime_build), smtp_dotstuff, and smtp_date_now. STARTTLS needs to
    upgrade an already-open fd, which the existing connect primitives could
    not do, so this adds nurl_tcp_starttls to the runtime — the
    client-handshake half of nurl_tcp_connect_tls applied in place. Lock:
    compiler/tests/smtp_basic.nu — the offline surface (multiline reply
    scanner/parser, AUTH PLAIN/LOGIN base64 tokens, dot-stuffing, MIME),
    ASan+UBSan+LSan clean; examples/smtp_send.nu demonstrates the live
    STARTTLS submission flow.

  • Unix domain sockets — stdlib/std/unixsock.nu (critic B9). The
    local-IPC sibling of std/net.nu's TCP, same API shape but a
    filesystem path instead of host:port — for Postgres-over-socket,
    systemd-style services, container control planes. unix_listen /
    unix_accept / unix_connect / unix_socketpair / unix_read_chunk
    / unix_write_all / unix_write_str / unix_close_conn /
    unix_close_listener (which unlinks the socket file). Pure libc FFI
    (blocking SOCK_STREAM); unix_listen unlinks any stale path before
    binding. Adds AF_UNIX / SOCK_STREAM / EADDRINUSE to the runtime's
    nurl_native_constant table (POSIX-only; the module degrades to a
    clean UnixSocket error on Win32/WASI). Lock:
    compiler/tests/unixsock.nu — a deterministic socketpair round-trip
    (both directions + EOF-after-close) plus a thread-driven
    listen/accept/connect echo gated on NURL_NET_TESTS; ASan+UBSan+LSan
    clean on both paths.

  • CBOR (RFC 8949) — stdlib/ext/cbor.nu (critic B16). The
    IETF-standard binary serialization (COSE / WebAuthn / CTAP), sibling of
    MessagePack, over the shared Json value: cbor_encode Json → !( Vec u ) CborErr and cbor_decode ( Vec u ) → !Json CborErr. Encode
    is canonical-ish — integers and lengths use the shortest head, floats
    are float64 — so equal documents serialize to equal bytes. Decode is
    liberal: every definite-length head, signed/unsigned integers at all
    widths, and float16 / float32 / float64 (the half-float decoder
    handles zero / subnormal / normal / inf / NaN). Byte strings, tags,
    indefinite-length items, and exotic simple values are rejected as
    CborUnsupported; undefined (0xf7) → JNull. Lock:
    compiler/tests/cbor.nu — Json round-trip with canonical-byte check,
    the RFC 8949 Appendix A decode vectors (integer boundaries, negatives,
    nested array/map, all three float widths), and every documented
    rejection; ASan+UBSan+LSan clean (error paths free the partial tree).

  • Arbitrary-precision fixed-point decimal — stdlib/std/decimal.nu
    (critic B14, the last ROADMAP numeric gap). A Decimal is a BigInt
    coefficient × 10^-scale, so it is exact0.1 + 0.2 is 0.3, not
    the binary-float 0.30000000000000004 — and never overflows. Exact
    dec_add / dec_sub / dec_mul; dec_div a b scale with an explicit
    result scale and banker's rounding (round half-to-even, the
    financial default); scale-agnostic dec_cmp; dec_round / dec_rescale
    / dec_normalize; dec_from_string ("-12.340", ".5", "42") and
    dec_to_string. Builds on the bigint div/rem from PR #100. Lock:
    compiler/tests/decimal.nu (exact 0.1+0.2==0.3, the full
    half-to-even rounding table incl. negatives, division + div-by-zero,
    normalize, cross-scale compare; ASan+UBSan+LSan clean — every
    intermediate BigInt freed).

  • Playground: rendered stdlib API reference at /stdlib-docs
    (nurlapi). The nurldoc library is now wired into the playground
    server: /stdlib-docs is an auto-generated index of every stdlib
    module (grouped by core/std/ext/hal), and /stdlib-docs/<path>
    renders one module's signatures + doc comments through
    nurldoc_render → the existing md_to_html + dark-theme doc chrome
    (the same presentation as the README/spec pages). Append .md for the
    raw Markdown. Closes the loop the C2 nurldoc PR opened — the "broad
    stdlib" is now browsable, not just greppable. Listed in the OpenAPI
    spec; live-verified end-to-end (index + module HTML + raw .md,
    ASan-clean).

  • nurldoc — Markdown API-doc generator (critic C2). The stdlib's
    //-header + doc-comment discipline (90+ modules) was unrenderable;
    nurldoc extracts each module's header block, top-level declaration
    signatures (functions trimmed at their { body; types/enums/consts
    keep their full definition), and the doc comment above each, into
    Markdown. The render logic is an importable library
    (stdlib/ext/nurldoc.nu, nurldoc_render content title → String,
    brace-depth-aware so : locals inside bodies are never picked up);
    tools/nurldoc/main.nu is a thin CLI — nurldoc <file.nu> to stdout,
    or nurldoc <src-dir> <out-dir> to walk the tree with fs_glob and
    write one .md per module. Lock: compiler/tests/nurldoc.nu.

  • HTTP-date / RFC 2822 date parsing — stdlib/std/time.nu (critic
    B13). The server formatted HTTP dates (time_format_http) but could
    not parse them; http_date_parse now accepts all three forms RFC 7231
    §7.1.1.1 requires — IMF-fixdate (Sun, 06 Nov 1994 08:49:37 GMT),
    obsolete RFC 850 (Sunday, 06-Nov-94 08:49:37 GMT, 2-digit year via
    the POSIX <70 pivot), and asctime (Sun Nov 6 08:49:37 1994) — for
    If-Modified-Since / If-Unmodified-Since / cookie Expires.
    rfc2822_parse handles the email Date: form with numeric ±HHMM
    zones (Mon, 02 Jan 2006 15:04:05 -0700). Both return UTC seconds in
    the !i ParseErr shape (pair with time_from_unix), matching
    time_parse_iso. Lock: compiler/tests/http_date.nu — the three
    RFC 7231 spellings agree on the spec's own example (784111777), the
    RFC 2822 -0700 case equals Go's canonical reference instant, plus
    round-trip and rejects.

  • JWT bearer-auth middleware — stdlib/ext/http_jwt.nu (B5
    follow-through). with_jwt_hs256 secret inner / with_jwt_eddsa pubkey inner wrap a claims-aware handler
    (( @ HttpResponse HttpRequest Json )) and return the standard
    ( @ HttpResponse HttpRequest ) middleware shape, so they compose
    with with_access_log / with_cors_default / router_handle. A
    request runs the handler only with a valid Authorization: Bearer
    token; the verified payload claims are passed straight in (no
    re-parse, no header injection / spoofing surface, borrowed + freed by
    the middleware). Missing / invalid / expired / not-yet-valid tokens
    get a 401 with an RFC 6750 WWW-Authenticate: Bearer challenge whose
    error= / error_description= names the failure. Kept in its own
    module so the base ext/http_auth.nu stays free of the OpenSSL
    dependency ext/jwt.nu pulls in. Lock: compiler/tests/http_jwt.nu
    (valid/expired/tampered/wrong-key/missing × HS256 + EdDSA;
    ASan+UBSan+LSan clean).

  • Filesystem niceties + glob — stdlib/std/fs.nu (critic B6 + B7).
    fs_rename (libc rename), fs_copy_file (64 KiB-chunk streaming, so
    large files copy in bounded memory), fs_tempfile (libc mkstemp
    unique 0600 file, returns the path). fs_glob expands shell patterns
    against the tree: * / ? / [...] (with [a-z] ranges and
    [!...]/[^...] negation) within a segment, ** as a whole segment
    for recursive descent, the leading-dot rule (* never matches a
    dotfile; an explicit . does), absolute or relative patterns. Pure
    NURL over dir_list. Lock: compiler/tests/fs_glob.nu (every pattern
    class + rename/copy/tempfile against a built temp tree).

  • One URL parser — stdlib/std/url.nu (critic B12). RFC 3986
    scheme://[userinfo@]host[:port][/path][?query][#fragment] into an
    owned Url, with bracketed-IPv6 hosts, url_default_port /
    url_port_or_default (http/https/ws/wss/ftp/redis/postgres),
    url_request_target (path?query for the request line), a percent
    codec (url_percent_encode/_decode), and url_query_decode
    Vec[UrlParam] (form-urlencoded +→space, %xx). Lives in std/
    with core-only deps so ext/ layers on it without a cycle. Locked
    against RFC 3986 component-split vectors in
    compiler/tests/url_parse.nu.

  • JSON Web Tokens — stdlib/ext/jwt.nu (critic B5). HS256 (HMAC-
    SHA256) and EdDSA (Ed25519) sign + verify over the existing crypto
    block and base64url. jwt_hs256_sign/verify, jwt_eddsa_sign/verify,
    a …_verify_at core taking an explicit epoch now (deterministic;
    the wrapper uses the system clock), and jwt_decode_unverified.
    Validates exp/nbf time claims; the HS256 signature comparison is
    constant-time (std/subtle.nu). The HS256 path reproduces the
    canonical jwt.io reference token exactly and EdDSA is goldened against
    the RFC 8032 test key (Ed25519 signatures are deterministic) in
    compiler/tests/jwt_basic.nu. Adds b64_url_encode_vec /
    b64_url_decode_vec to std/encode.nu for binary, unpadded
    base64url (the signature segment).

Changed

  • ext/websocket.nu and ext/http2_client.nu delegate URL parsing to
    std/url.nu
    (critic B12 consolidation). Both hand-rolled
    scheme/host/port/path splitting; their __ws_parse_url /
    __h2_parse_url are now thin wrappers over url_parse that enforce
    the ws/wss and http/https schemes and map to the existing WsUrl /
    H2Url types — same public API, one parser underneath. The new parser
    also correctly stops the authority at ?/# (the old scanners folded
    a query into the host when no path was present) and keeps the query in
    the request target.

Fixed

  • CRDT replica ids must be globally stable (dist/crdt.nu,
    dist/replicator.nu, dist/identity.nu). The chaos-simulation harness
    exposed silent state corruption: PNCounter stored increments in a dense
    vector merged by position, while identity_of handed out ids in local
    first-seen order, so every node called itself replica 0 — two distinct
    replicas collided into one slot and the merge took max(1,1)=1 instead of
    summing to 2, converging to the wrong value with no error. Fixed deeply:
    identity_stable_id(pubkey) derives a globally consistent id from the pubkey
    (FNV-1a/64, no coordination); PNCounter is now sparse and keyed by
    replica id
    (merge aligns by identity, not position); the wire carries the
    id per slot and pncounter_encode emits slots in canonical ascending-id
    order so equal states encode identically (required by the digest anti-entropy).

  • Struct-pointer field access mis-resolved when the field name shadows a
    variable
    (compiler/nurlc.nu, gen_field). . p field on a struct
    pointer resolved field as a same-named local integer and emitted a pointer
    array-index instead of a field load — a silent miscompile only clang caught.
    A struct field now always wins over a same-named variable on the field-load
    path (the field-store = . p name val array-index form is unchanged and
    intentional). Lock: compiler/tests/ptr_field_name_shadow.nu.

  • Closure literals rejected parenthesised compound param/return types
    (compiler/nurlc.nu, gen_backslash_expr). \ ( Vec u ) p → ( Vec u ) { … }
    failed with “undefined identifier 'u'” because \ ( was only treated as a
    closure when the next token was @; a compound type head fell through to the
    try-expression path. \ ( is now a closure whenever the next token introduces
    a type (incl. the builtin Vec). Lock: compiler/tests/closure_compound_param.nu.

  • @ ?Enum { T Variant } emitted invalid IR (compiler/nurlc.nu,
    gen_agg_lit). Constructing Some(variant) of an option whose
    payload is a no-payload (C-style) enum inserted the variant's bare
    i64 tag into the option's %Enum aggregate slot, which clang
    rejected (insertvalue operand and field disagree in type). The
    Result form ! T E always worked because its payload slot is i64;
    only the option payload field carries the full %Enum type. The
    coercion now wraps the tag with insertvalue %Enum zeroinitializer, i64 tag, 0. Found while writing ext/jwt.nu. Lock:
    compiler/tests/option_enum_payload.nu.

  • Cryptography block — AEAD, signatures, key exchange, KDFs
    (stdlib/ext/crypto.nu, critic B1–B3). Binds libcrypto's EVP layer
    through pure-NURL & `openssl` @ FFI (no C bridge, same sentinel
    pattern as TLS/libpq → "install libssl-dev" at compile time):
    AES-256-GCM and ChaCha20-Poly1305 one-shot AEAD (tag appended;
    CryptoVerify on tampered input); Ed25519 keygen/sign/verify and
    X25519 keygen/derive via the EVP_PKEY raw-key API; HKDF-SHA256,
    PBKDF2-SHA256/512, and scrypt. Every primitive is locked against its
    published vector (NIST GCM, RFC 8439, RFC 8032 §7.1, RFC 7748 §6.1,
    RFC 5869 A.1, RFC 7914 §12) in compiler/tests/crypto_evp.nu.

  • std/subtle.nu — constant-time comparisons (critic B4).
    constant_time_eq / _eq_n / _eq_vec for secret material,
    promoted from the private bearer-token loop in ext/mcp_registry.nu
    (which now calls it). Length-leaking but content-timing-invariant,
    matching hmac.compare_digest / Go crypto/subtle.

Fixed

  • ext/crypto.nu HKDF: binary salt was silently zeroed before the
    module shipped — nurl_str_get is a NUL-bounded C-string read, so
    hex-encoding a Vec u salt through a # s cast truncated at the
    first 0x00 byte. Switched the binary→hex helper to the *u + . p k
    indexed load (the idiom encode.nu's __b64_emit already uses). The
    RFC 5869 test vector caught it; documented as a reuse hazard in
    critic.md B3.

  • nurlc --lint detects unused imports (compiler/nurlc.nu). A
    top-file $ import none of whose defined symbols (functions, FFI
    externs, types, constants — generic templates included) is referenced
    by the top file itself now warns unused import: no symbol from '<path>' is referenced in this file. References count from calls,
    identifier reads, and type positions; uses recorded while re-parsing
    generic template bodies during the instantiation flush are attributed
    to the template's defining file, so stdlib internals never mask a top
    file's dead import. Pure aggregator files (only $ directives, no
    decls of their own — e.g. stdlib/ext/http_full.nu) are exempt both
    as importer and as import target: re-exporting is their purpose. The
    LSP server (v0.6.0) now surfaces these straight from nurlc --lint
    and drops its former text-heuristic duplicate (~190 LOC removed) —
    one source of truth for the diagnostic.

  • Unknown callees are compile errors now (compiler/nurlc.nu). A
    call to a name with no registered return type — not an @-fn, FFI
    extern, builtin, impl method, or local closure — previously fell
    through as an assumed-i64 call to an undeclared symbol: invalid IR
    that clang rejected far from the source, or worse, code that linked
    by accident when the defining file happened to be imported later in
    the unit and the return type happened to be i64. Now dies at the
    call site: call to unknown function 'X' — … add the missing '$' import … or check the spelling. Same treatment for a generic call
    whose template is nowhere in the import closure (was: opaque
    expected '->' but found end of input inside the synthetic
    <generic …> source). En route this surfaced — and forced fixing —
    nine runtime builtins that were header-declared but missing from the
    compiler's symbol table (nurl_peek, nurl_init, nurl_memset,
    nurl_vec_drop, nurl_argc, nurl_argv_count, nurl_read_int,
    puts, printf), all silently riding the i64 default.

Fixed

  • 180 stale $ imports removed tree-wide (88 stdlib, 92
    tests/examples/tools). Several masked real latent bugs, now fixed
    with explicit imports: stdlib/ext/csv.nu used opt_unwrap_or
    without importing stdlib/core/option.nu (rode on sort.nu's own
    stale import), stdlib/ext/http2_hpack.nu called
    h2_default_header_table_size without importing
    stdlib/ext/http2_frame.nu, and stdlib/std/bufio.nu called
    nurl_file_open/nurl_file_close without importing
    stdlib/std/fs.nu — each compiled only when every consumer
    happened to import the missing file first. stdlib/std/set.nu no
    longer imports hashmap.nu for its callers' convenience; import it
    alongside (the stock hash_*/eq_* helpers live there).