U — Honest Feature Matrix

Every major feature, with a plain verdict on its state and a pointer to the test that proves the claim. The point of this document is that it does not flatter the project. A feature is marked Works only if a test compiles and runs it; Simplified means the shape is real but a production system would want more; Placeholder means the interface exists but the hard part is explicitly not implemented, and is labeled as such in the code.

● Works — tested end-to-end (compiles, runs, correct result) ● Simplified — real shape, deliberately reduced scope ● Placeholder — interface real, core marked unimplemented

Test counts at the time of writing: 119 parser · 345 linter · 594 codegen · 6 optimize = 1064 passing. Codegen tests compile the generated C with a real compiler and run it, so a passing codegen test means the full pipeline (U → C → binary → correct output) works for that case.

Supply-chain security (post-release layers)

The five layers that guard dynamic plugin loading. Built as module-level analyses over capability sets, fingerprints, and signatures — the mechanism the plan specifies, without new surface syntax.

FeatureStatusProof
Layer 1: dynamic_grantable whitelist (rogue cap → error)Works test_parser: “supply-chain” — rogue load requesting an ungranted cap is rejected
Non-escalation: loader can only grant caps it holdsWorks test_parser: same — granting an unheld cap is an error
Layer 2: u.lock fingerprints (tampered module → error)Works test_parser: build/verify lockfile, mismatch on tamper
Layer 4: Merkle root over the dep graphWorks test_parser: root determinism + flips on any change
Layer 3: M-of-N threshold DERIVED from the cap setWorks test_parser: logger 1-of-2, auth 2-of-3, crypto 3-of-5; inflation raises M
Transparency log (hash-chained, tamper-evident)Works test_parser: chain verifies, breaks on edited history
a o("name", {caps}) as parsed surface syntaxSimplified The enforcement runs as a source-level analysis (regex over the load site); a fully parsed AST node for the dynamic-load form is not wired. The checks are real; the syntax integration is the simplification.
Signatures in publish = real cryptographic signaturesWorks A cosign is now a real asymmetric signature over the module's content hash, verified against the reviewer's public key before it counts toward M — a bare reviewer name can no longer stand in for a signature, and a forged or mutated signature is rejected (guarded by a test with tampered-message, wrong-key, mutated-signature, zero-signature, and cross-key forgery cases, plus verify_signatures() re-checking every stored cosign). Honest scope: the build-tooling signer (u2c/pubsign.py) is a compact pure-Python EdDSA-style Edwards-curve scheme using only hashlib (no third-party dep), forgery-resistant and deterministic, but NOT validated bit-exact against the RFC 8032 Ed25519 vectors — so it is deliberately not called "Ed25519." For production provenance the signing delegates to the verified SPHINCS+ in the C runtime (byte-identical to pyspx). The M-of-N logic and transparency log remain real.

Core language

FeatureStatusProof
Arithmetic, precedence, function callsWorks test_codegen: “arithmetic + function call”, “operator precedence”
Ternary cond ? then ! else, chainedWorks test_codegen: “ternary chain (?!)”
Inheritance + virtual dispatchWorks test_codegen: “inheritance + virtual dispatch” — a subclass upcast to its base dispatches through the vtable to the overridden method (verified in C: s->vt->area(s) resolves to the subclass override). Scope (audit finding): this is the C-level mechanism. Passing a subclass instance to a U-level function whose parameter is the base type (f describe(sh: Shape) called with a Shape.Circle) is currently a compile error: class-typed parameters emit as value types rather than pointers and method calls on them are static, not vtable dispatch, so the linter rejects the substitution rather than emit C that would neither compile nor dispatch. U-level base-typed-parameter polymorphism is therefore not yet supported; the vtable dispatch it would rest on works, but the parameter-passing path does not. Named here rather than left implied by the broader "virtual dispatch" claim.
Null coalesce ??, guard |Works test_codegen: “null coalesce”, “expression-position guard”
Typed errors: throw x Err(...), catch x.on()Works test_codegen: “exception floor”, “INTEGRATION”
Ranges [1..n], [1...n], descendingWorks test_codegen: three “range:” tests matching the spec’s examples
1-based list indexingWorks test_codegen: “1-based list indexing”
Maps {K:V}, sets {T}, empty-literal typingWorks test_codegen: “maps:” tests; linter empty-literal unification
S(n) string-length types with boundary checkWorks test_codegen: “S(n) string length type”
Generic-class construction + field read-back (compiles & runs)Works test_codegen: "G3: generic class monomorphization — Box[I] and Box[S] COMPILE and RUN". A type-parameter field is type-erased to void*; a scalar argument is boxed at construction (UBox_<T>) and unboxed on read, for multi-letter parameters (ElemType, Key, Val) as well as T. Both construction forms — Box({...}) and Box[I]({...}) — agree on representation. Verified by compiling the emitted C with gcc and running it: Box[I] round-trips 42 and Box[S] round-trips a string. Prior state (corrected): the field type item: ElemType was emitted verbatim as an undefined C type, so the generated C did not compile at all — the earlier "works" row only exercised linting and the single-letter-T construction path. Remaining scope: this is type-erasure (one void* representation), not full monomorphization — a distinct specialized struct per instantiation is not emitted, so a generic field of class or collection type still relies on the erased representation rather than a concrete specialized layout. Scalar and pointer type arguments work end-to-end.

Concurrency & async

FeatureStatusProof
Fibers: suspend/resume, locals surviveWorks test_codegen: “fiber: two suspend points”
Fork/await, concurrent forks, self-recursion via forkWorks test_codegen: four “fork:” tests
[T+A].all() / .any() join/raceWorks test_codegen: “[T+A].all()”, “[T+A].any()”
MVCC patch <<, 8-thread contentionWorks test_codegen: “MVCC: read-modify-write survives 8-thread contention”
-M propagation: params are read-only viewsWorks test_linter: 9 cases. A -M binding (the default for parameters and locals) makes everything reachable through it read-only — field writes, index writes, mutating methods (.push() etc), and << patches are all compile errors through a -M reference, even when the field or element is declared +M in its class. Adding +M to the parameter allows mutation. t (self) is exempt — the class’s own methods are the owner. This is transitive const, applied uniformly.
Database.Query codegen: builder chain → u_dbq_* C runtimeWorks test_codegen: 1 case. U source that chains .select().where().orderBy().limit().getSQL() transpiles to C containing u_dbq_new, u_dbq_select, u_dbq_where, u_dbq_order_by, u_dbq_limit, u_dbq_get_sql. Runtime (u_db.h) builds dialect-aware SQL ($N for Postgres, ? for MySQL), binds parameters separately, and executes through the existing wire-protocol clients. The query builder chain returns UDbQuery* for fluent chaining; terminal operations (.fetchAll, .fetchRow, .execute) call u_db_exec underneath.
HTTP client codegen: verbs + builder chain → u_httpc_* C runtimeWorks test_codegen: 1 case (4 sub-assertions). HTTP.get(url) → u_httpc_get, HTTP.post(url,body) → u_httpc_post, HTTP.request(url).method().header().bearer().send() → u_httpc_request/u_httpc_req_method/u_httpc_req_header/u_httpc_req_bearer/u_httpc_req_send, Response.status → u_httpc_resp_status. Runtime (u_http.h) adds URL parsing, high-level client API, request builder, response accessors, and URL encode/decode — all on top of the existing async HTTP/1.1 state machine.
Deadlines: resolve-before / timeout / deadlock-panicWorks test_codegen: “deadline runtime”
Pull streams (demand down, values up)Works test_codegen: “pull streams: right-associative chain”
Time — monotonic + realtime clocks, calendar, ISO-8601, durationsWorks Two clocks kept separate BY CONSTRUCTION (u_time.h): MONOTONIC for durations and deadlines (never jumps — immune to NTP, DST, and the user setting the clock) and REALTIME for human-facing timestamps and calendar work. Conflating them is the classic timer bug, so they are different functions. Calendar components (UTC and local), ISO-8601 format and parse with a verified round-trip, strftime-style formatting, and duration humanising (1500ms → 1.50s, 125000ms → 2m 5s). Portable: clock_gettime is POSIX and provided by wasi-libc, so identical source works natively and on WASM/WASI. All verified by a running test.
Environment / OS — env vars, args, exit, cwd, pidWorks The clearest one-interface / three-backend case (u_env.h). get/has/or/set/get_int for environment, process arguments, exit, cwd, pid, hostname. Natively everything works; on WASI the host supplies env and argv through wasi-libc; in a BROWSER there is no process environment at all, so getenv returns empty and cwd/pid degrade rather than fail — portable code keeps running instead of breaking. Verified running: set/get, typed get_int with fallback, args, cwd, pid.
Filesystem — read/write/append, stat, dirs, pathsWorks POSIX-shaped because that is what every backend can express (u_fs.h): whole-file read_all/write_all/append (the 90% case, with a size cap so a surprise 10GB file cannot take the process down and a NUL terminator so text use is safe), stat (size/mtime/is_dir/is_file), mkdir/mkdirs (mkdir -p)/remove/rename, directory listing, and path helpers (basename, extension, join). Backends: native libc; WASI via wasi-libc, where the host must PRE-OPEN the directories the module may touch (capability-based — no ambient authority over the disk); browser via Emscripten MEMFS/NODEFS. Verified running end to end.
HTTP client — asynchronous, over U's own socketsWorks Deliberately not a libcurl binding (u_http.h). Writing HTTP/1.1 directly on u_net.h is what PROVES the socket layer is a sufficient transport — if a real protocol client could not be written against it, the transport was not done. It can. A request is a state machine (connect → send → read) driven by the fiber scheduler exactly like the GPU readback: each step advances as far as the socket allows, then returns SUSPENDED so the epoll reactor sleeps until more bytes arrive. No thread, no spin. Verified against a real loopback server: GET 200 with correct body, POST with a body echoed back, 404 handled as an ordinary response — and critically the run recorded 17 suspensions with a concurrent fiber interleaving 3 steps, so it genuinely took the async path rather than a lucky fast path. Parses the status line and Content-Length, reads to close when length is absent. Scope: no TLS yet (so http, not https), no chunked transfer-encoding, no redirect following, no connection reuse (Connection: close), no cookie jar. Browser target uses the platform fetch() behind the same interface, since raw sockets do not exist there.
TLS — https, mutual TLS, session ticketsWorks Verified against real servers (u_tls.h): TLS 1.3 handshake (TLS_AES_256_GCM_SHA384), encrypted GET returning 200. TLS WRAPS a transport, so HTTP/MySQL/Postgres inherit encryption. Handshake is non-blocking (WANT_READ/WANT_WRITE) so a fiber suspends to the reactor. Verification and hostname checking are ON by default — OpenSSL does not check hostnames for you — and the test asserts the property that matters: a self-signed certificate is REJECTED when verifying. Mutual TLS works against a CERT_REQUIRED server behind a real CA: a client certificate is accepted, its absence is rejected, and a mismatched cert/key pair is caught at load time rather than failing silently at handshake. A bug this testing caught: client certs were being set on the SSL_CTX, but SSL_new has already copied the context by then — so the certificate was never sent, and under TLS 1.3 (where the client cert goes out after the client thinks the handshake finished) this looked like a successful handshake followed by zero bytes. Now set on the SSL itself. Session tickets: the NewSessionTicket callback fires and the saved session is accepted by a new connection; an actually-resumed handshake is NOT demonstrated — against the test server OpenSSL reports the session non-resumable and SSL_session_reused() stays 0, so the API is in place but resumption itself is unverified. Client side only. On loopback the handshake never returned WANT_*, so the suspend path is implemented but unexercised.
DNS — hostname resolutionWorks Sockets took IP literals only; u_dns.h is what makes connecting to a hostname possible. Uses getaddrinfo, so IPv4, IPv6, /etc/hosts and the system resolver config all work without reimplementing any of it. Returns EVERY address rather than just the first, which is what lets a caller do happy-eyeballs or fail over when one address is unreachable. Verified running. Browsers resolve implicitly inside fetch(), so there is no separate DNS step on that target.
Encoding — base64, hex, percent, UTF-8Works Small, dependency-free, and used constantly (HTTP headers, URLs, JSON, crypto output). base64 with the URL-safe variant, hex, percent-encoding, and UTF-8 encode/decode/validate. Everything writes into a caller-supplied buffer and returns a length, so no allocation and no truncation surprises. Malformed input is rejected rather than guessed at: base64 refuses junk characters, hex refuses odd lengths, and UTF-8 validation rejects overlong forms and surrogates — accepting those is how mojibake and security bugs get in. Verified against known vectors (TWFu, YQ==, deadbeef).
Random — CSPRNG and seedable PRNG, UUIDv4Works Two generators, deliberately separate and named so they cannot be confused: u_rand_secure_* (OS CSPRNG — keys, tokens, session ids, salts) and u_rng_* (xoshiro256**, fast, seedable, REPRODUCIBLE — simulations, shuffling, tests). Reaching for the wrong one is the classic security bug, so the secure one has the obvious name. Both bounded generators use rejection sampling to avoid modulo bias. A CSPRNG failure is never silently downgraded to weak randomness. UUIDv4 always draws from the CSPRNG. Verified: secure bytes differ across calls, the seedable PRNG is bit-identical from the same seed, doubles land in [0,1), and UUIDs carry the right version/variant nibbles.
Regex — POSIX extended regular expressionsWorks Wraps <regex.h>, which is in libc AND wasi-libc, so this needs no third-party dependency and works on both targets. Compile (with ignore-case and multiline), search with capture groups, test, count, and replace-all. Zero-width matches advance by a byte so replace cannot spin. Named honestly: this is POSIX ERE, not PCRE — no lookaround, no lazy quantifiers, no \d shorthand — though it also has none of the catastrophic-backtracking surprises those features bring. Verified: capture groups extracted, counts correct, replace-all correct, and invalid patterns rejected with a readable message.
Compression — gzip, zlib, raw deflateWorks Via zlib (u_compress.h). HTTP effectively assumes this: every client sends Accept-Encoding: gzip, so a client without it either lies in its headers or wastes bandwidth. All three container formats are exposed because they are NOT interchangeable — gzip for HTTP and files, zlib for PNG and protocols, raw deflate when the container comes from elsewhere. Plus crc32. Verified by round-trip and against the standard crc32 test vector (0xCBF43926).
HTTP cookie jarWorks Parses Set-Cookie (Domain, Path, Expires, Max-Age, Secure, HttpOnly), stores, expires, and serialises the correct subset back into a Cookie header. The matching rules are the whole point, so they are tested as security properties, not features: a Secure cookie is never sent over plain http; a host-only cookie (no Domain attribute) does NOT leak to subdomains; and the classic suffix attack is blocked — a cookie scoped to example.com is NOT sent to evilexample.com, because the domain match requires an explicit dot boundary. Max-Age=0 deletes. Verified running.
Structured loggingWorks Log lines are DATA, not prose (u_log.h): typed key/value fields that can be filtered and aggregated, rather than values glued into a sentence. Level filtering, a service name on every line, and two sinks from the same call — human-readable text for a terminal, JSON for anything that ingests logs. JSON output is correctly TYPED (strings quoted, numbers and booleans not) and properly escaped (quotes, newlines, control characters). Logs go to stderr, leaving stdout for program output. Verified running.
Subprocess spawnWorks fork/exec with an explicit argv (u_proc.h), capturing stdout, stderr and the exit code, plus a PATH lookup. There is deliberately no "run this command line" entry pointsystem() hands the string to a shell, so any interpolated value becomes an injection vector. Verified running: output captured, a non-zero exit reported rather than swallowed, a missing program failing cleanly as 127. NATIVE ONLY: neither WASI nor the browser has a process model, and this is one of the few places a target genuinely cannot participate.
Test frameworkWorks Deliberately small (u_test.h): a runner, a summary with a CI-readable exit code, and assertions that report WHAT differed — got 3, want 4 rather than "assertion failed". A framework that only says "failed" makes you add printfs, which is the thing a test framework exists to prevent. Typed assertions for ints, strings and floats-with-tolerance, plus skip. Dogfooded: it is what runs the logging and subprocess tests.
Scheduler — clock, timers, intervals, cron; unified timer+IO event loopWorks U's answer to cron + a stream_select loop, verified by execution (u2c/runtime/u_sched.h). Clocks: u_now_mono_ms (MONOTONIC — for deadlines, immune to NTP/DST jumps) and u_now_wall_ms/u_now_unix_sec (REALTIME — for timestamps and cron matching); mixing these up is the classic timer bug, so they are separate by construction. Timers: u_sched_after (one-shot), u_sched_every (interval), u_sched_cron (calendar), plus cancel and fire-counts. Cron supports the 5 standard fields with *, numbers, lists (0,30), ranges (9-17), and steps (*/15), Sunday as 0 or 7 — 13 matcher cases verified correct. THE KEY INTEGRATION: timers and I/O share ONE wait. u_sched_next_timeout_ms() computes the next deadline and it is passed as the reactor's epoll timeout, so the process sleeps until EITHER a socket is ready OR a timer is due — never spinning, never overshooting a deadline. Proven by a running test: with a listener idle, the loop woke 6 times for timers AND still saw the socket become readable through the same u_net_select. Scope: single-threaded cooperative (matches U's fiber model); no persistence of schedules across restarts; cron is minute-granularity (no seconds field).
Multiplexing — u_net_select (stream_select equivalent), fd-value-agnosticWorks Reports which of N streams are ready, the way PHP's stream_select does — but deliberately NOT select(2)-shaped, for a portability reason learned from the PHP-on-WASM port: select() uses fd_set BITSETS indexed by fd value and cannot see any fd ≥ FD_SETSIZE (1024), while WASI RANDOMIZES fds across [3, 231) — so a bitset-based select silently drops most sockets, and for (fd = 0; fd <= max_fd; fd++) loops become billions of iterations. U's API therefore takes an explicit array of fds and returns an explicit array of ready-masks: no bitsets, no 0..max_fd loops, correct whether fds are 3,4,5 (POSIX) or 918273645 (WASI). Backend is epoll natively; the same surface maps onto poll()/wasi:io/poll for a WASI build. Verified running: detects listener readability and composes with the scheduler's timeout for the unified event loop.
Sockets — TCP / UDP with epoll reactor, integrated with +ASimplified Real POSIX sockets, verified by execution. u2c/runtime/u_net.h: TCP listen/connect/accept/read/write and UDP bind/sendto/recv, all non-blocking, all returning PENDING FRAMES (T +A values with the UGenericFrame prefix) driven by U's fiber scheduler. The architecturally new piece is the REACTOR: a bare ready-queue cannot drive a socket read — the frame would spin, since nothing says when the fd becomes readable — so an epoll set now provides readiness wakeups and the process SLEEPS instead of busy-polling. Verified two ways: (1) a full TCP echo (HELLO-U client→server→echo→client) plus a UDP datagram, driven end to end by the scheduler; (2) a forced suspend/wake test proving the reactor genuinely works — an accept with no client SUSPENDS 5/5 steps, epoll reports 0 ready before connect and 1 after, and the frame completes on wake (not a lucky fast path). Errors map to a UNetStatus enum shaped for U's catchable ! NetError returns. Scope: NATIVE target (POSIX/Linux epoll) — browsers forbid raw TCP, so the WASM backend for these interfaces is WebSockets/fetch and a raw-socket program is a native program (same one-interface/two-backends pattern as GPU). Not yet done: TLS, DNS resolution (IP literals only), async connect/write frames (write is retry-loop), kqueue/IOCP for BSD/Windows, and the wire-protocol clients (MySQL/Postgres/HTTP) that would sit ON this layer.
MySQL / MariaDB — wire-protocol clientWorks Companion to the Postgres client: same shape, different wire (u_mysql.h). v10 handshake, mysql_native_password authentication built on the runtime's own SHA-1 (SHA1(pw) XOR SHA1(salt + SHA1(SHA1(pw)))), the text query protocol, length-encoded integers, column definitions, and row parsing — no libmysqlclient. Verified against a live MariaDB 10.11: auth, SELECT with named and ordered columns, INSERT reporting affected rows, SQL NULL distinguished from the empty string, and a server error reported with the connection still usable. MySQL frames every message as a 3-byte length plus a sequence number, and getting that sequence wrong is the classic first bug, so it is threaded explicitly and reset per command rather than assumed. The test seeds its own fixture and starts its own server. Scope: text protocol only — no prepared statements or binary parameters, no caching_sha2_password (MySQL 8's default; the server's auth-switch request is detected and reported rather than mishandled), no TLS-wrapped connection, no compression.
Prepared statements — real parameter binding (PostgreSQL)Works The extended query protocol (Parse / Bind / Describe / Execute / Sync) sends the STATEMENT and the VALUES as separate wire messages, so a parameter can never be parsed as SQL no matter what it contains. That is the actual fix for injection — escaping is not. Verified against a live server: prepared INSERT and SELECT with bound parameters, a NULL parameter becoming SQL NULL rather than the string "NULL", and the case that matters — the payload greg'; DROP TABLE pp; -- is bound as a VALUE, matches nothing, and the table survives with all its rows. Statements are named and reusable. Scope: text-format parameters (format code 0); binary format is wired in the protocol but not yet exercised per-type, and MySQL prepared statements (COM_STMT_PREPARE, binary result rows) are not implemented.
HPACK — HTTP/2 header compression (RFC 7541)Works HTTP/2 does not send header names as text: it sends INDICES into a table both peers maintain identically, so :method: GET is one byte. That shared mutable state is the whole difficulty — encoder and decoder must evict in lockstep, and a single disagreement desynchronises the connection permanently and silently, since HTTP/2 has no resynchronisation point. So this is verified against the RFC's own test vectors: C.1 integer encoding (including 1337 in a 5-bit prefix → 1f9a0a), C.2.1 literal-with-indexing byte-for-byte, C.2.4 indexed fields, and C.3.1 — decoding a real first request 8286 8441 0f77… into :method: GET, :scheme: http, :path: /, :authority: www.example.com with the dynamic table ending at exactly 57 bytes. Implements the 61-entry static table, the dynamic table with FIFO eviction and the RFC's size accounting (name + value + 32), prefix-coded integers, length-prefixed strings, all four field representations and dynamic-table size updates. Malformed input is refused rather than guessed: truncated continuations, overlong integers that would wrap, and out-of-range indices. Scope: Huffman-coded strings are DETECTED and reported (-2) rather than decoded — the H bit is optional to emit, so this interoperates, but a peer that Huffman-codes its headers is not yet readable.
HTTP/2 framing (RFC 7540)Works The binary frame layer that lets many requests share one connection: a 9-byte header (24-bit length, type, flags, reserved bit + 31-bit stream id) plus payload. The stream id is what makes multiplexing work — frames for different requests interleave and are reassembled per stream, with stream 0 reserved for the connection itself. Encodes and decodes frame headers, the exact 24-byte connection preface (deliberately invalid HTTP/1.1 so a 1.1 server cannot misread what follows), and SETTINGS as 6-byte id/value pairs. The reserved high bit of the stream id is MASKED rather than trusted — forgetting that is a classic interop bug, and the test sets it to prove it is ignored. Truncated headers are refused. Scope: framing and HPACK only — no stream state machine, no flow control (WINDOW_UPDATE), no server push, and nothing yet drives it over a TLS connection with ALPN h2.
FTP client (RFC 959)Works FTP is unusual in using TWO connections: a text CONTROL channel and a separate DATA connection per transfer. Passive mode throughout, because active mode asks the server to dial back to the client and no NAT in twenty years permits that. Multiline replies are consumed whole — a hyphen after the code (220-) means more lines follow, and reading only the first is the classic bug that makes every subsequent reply arrive off by one. Verified against a real server: login through a multiline banner, passive LIST, RETR returning exact file bytes, a STOR round-trip read back byte-identical, MKD/CWD/DELE, and a bad password refused rather than assumed. TYPE I is set on connect because ASCII mode mangles binary. PASV's port is split across two bytes (p1*256+p2), which is the part implementations get backwards.
WebSocket — RFC 6455 clientWorks Not layered on HTTP/2. A WebSocket is an HTTP/1.1 Upgrade handshake, after which the connection stops being HTTP entirely and becomes its own framed protocol over TCP; TLS just wraps it (wss://). RFC 8441 added WS-over-HTTP/2 later as a patchily-supported extension. That is why this was small — it needed a TCP socket, an HTTP/1.1 handshake, SHA-1 and base64 for Sec-WebSocket-Accept, and secure random for masking keys, all of which U already had. Verified against a real server: handshake with the accept hash actually VERIFIED (skipping that check means trusting any 101 from any server), masked client frames (servers drop unmasked ones — the classic first bug), extended payload lengths past 125 bytes, automatic PONG replies, clean close, and rejection of a server that does not upgrade.
Unix domain socketsWorks Same API shape as TCP, different address family (u_net.h). Worth having for real reasons rather than completeness: local database connections use a unix socket by default, it skips the TCP stack entirely, and the filesystem permissions on the socket path ARE the access control — nothing is exposed to the network. Verified connecting over Postgres's own socket path. A latent bug this surfaced: u_net.h's include guard closed at line 387 while the file ran to 439, so the multiplexing section appended earlier sat OUTSIDE the guard and redefined itself whenever the header was included twice — which is exactly what happened once two modules both included it. Same bug class as the runtime's vectorization section. Guard moved to EOF; double-inclusion now tested.
PostgreSQL — wire-protocol client with SCRAM-SHA-256Works The first wire-protocol client, and the proof the transport layer is real (u_pgsql.h): PostgreSQL v3 protocol written against U's own u_net.h sockets and U's own crypto — no libpq. SCRAM-SHA-256 authentication is built from primitives the runtime already had (SHA-256, HMAC, PBKDF2), which is the only reason a genuinely authenticating client was possible here. Verified against a live PostgreSQL 16: SCRAM handshake, SELECT returning named columns and ordered rows, INSERT with its command tag, SQL NULL distinguished from the empty string (a distinction a careless client loses), and a server error reported with the connection still usable afterwards. The test seeds its own fixture rather than depending on state from a previous run. Scope: the simple query protocol — no prepared statements or binary parameter binding yet, no connection pooling, no TLS-wrapped connections (the pieces exist; they are not wired together here), and no LISTEN/NOTIFY or COPY.
SQLite — embedded database bindingsWorks Complete SQLite bindings, verified by execution (u2c/runtime/u_sqlite.h, real libsqlite3 3.45): open/close, exec, prepare/bind/step/finalize/reset, typed column reads (int/real/text/null + names and count), transactions (begin/commit/rollback), last_insert_id, changes. Tested by a full CRUD cycle that RUNS: create table, three parameterized inserts, a bound-parameter SELECT returning the right 2 rows in the right order, and a transaction rollback restoring the row count. Every value goes through sqlite3_bind_* — never string concatenation, which is the runtime half of the guarantee the linter already enforces statically (structural-position interpolation is a compile error, value-position is parameterized). SQLite is the right embedded DB for U because it is in-process (no socket) and compiles to WASM (sql.js), so it works on BOTH targets — unlike a network DB, which is served by the socket layer instead. Shapes match the runtime's existing Database.Query[T]/Row types. Scope: the C binding surface; no U-level ORM/query-builder sugar yet, and BLOB binding is not exposed.
Case sensitivity — with near-miss hintsWorks U is case-SENSITIVE for classes, functions and methods, and stays that way deliberately: case already carries meaning (types are Point and HTML, values are userName, type parameters must be multi-letter except T), and Type reflection needs one canonical spelling per name. Naming CONVENTIONS are not enforced — d point and f GetName() both compile — so the rule is about identity, not style. The ergonomic cost of case sensitivity is the pure case slip, so that is now named directly: an unknown identifier that differs from a known class, function or alias only by case reports "did you mean 'HTML'? names are case-sensitive". That recovers the forgiveness of a case-insensitive language without giving up the signal, and unlike PHP it applies uniformly rather than splitting the rules between names and variables.
SQL[Conn] — the connection rides in brackets, and picks the dialectWorks Brackets rather than a first argument, for a reason that is not aesthetic: the connection selects the DIALECT, and the dialect decides the placeholder syntax the statement is compiled with — PostgreSQL writes $1, MySQL writes ?. That is a COMPILE-TIME decision, so a runtime first argument arrives too late: by the time you hold a connection value the statement text is already built. Brackets are also already U's compile-time parameter syntax (Box[I]) and already how the spec selects a connection — o Database[Database.Connection.Replica] { get, find }, which parses and lints clean. Verified by emission: no connection or SQL[Database.Connection.Postgres] gives $1 … $2, SQL[Database.Connection.MySQL] gives ? … ?, and the dialect is inherited through a subclass OR a type alias (d AppDb : Database.Connection.MySQL), so it is declared once instead of repeated at every call site. Values remain bound parameters in every dialect — the bracket changes the placeholder spelling, never whether a value is parameterised. Ordinary list indexing is not mistaken for a connection bracket. Scope: the bracket names a connection TYPE (dialect + eventually schema); binding a runtime connection INSTANCE so the statement auto-executes is still the remaining step.
Database relations — FK graph resolved at compile timeWorks A port of Qbix's Db_Relation::compile() (u2c/relation.py), moved to BUILD time because every input is static. Qbix finds the ROOT store (the unique table no foreign key points out of), rejects more than one root, rejects zero (a cycle), and BFS-levels the graph — and the levels ARE the join order: "the order they should appear in JOIN statements, for the statements to make sense". The join order is therefore DERIVED FROM THE KEYS rather than written by hand. Doing it at compile time means Qbix's two runtime exceptions become build errors that name the tables involved, and the join sequence is emitted once instead of recomputed per query. Verified: users ← posts ← comments levels to [[posts],[comments]] and emits the joins in dependency order; two roots is rejected as "would produce a cross product"; a cycle is rejected as "no table to start the joins from"; and the same pair joined by two different keys is reported as ambiguous, which Qbix cannot detect at all. Scope: the graph and its checks are implemented in Python and callable; they are not yet reachable from U source as z f hooks.
Schema from SQL DDL (--from-sql)Works A model can be read from checked-in CREATE TABLE statements instead of scanning a live database (parse_sql_ddl). Usually the better build input: it is versioned so generated classes move with the code, it needs no server so CI can generate without credentials, it is the SAME artifact a migration applies so the two cannot drift, and its FOREIGN KEY declarations give the relation graph for free — the join order a query needs is already written in the DDL, so nobody restates it as hasOne/hasMany. Extracts columns with lengths and nullability (varchar(64), NOT NULL), maps SQL types to kinds (decimal(10,2)→float), and reads PRIMARY/UNIQUE/KEY including multi-column keys in order, which is what the leftmost-prefix index rule needs. A bug caught in the writing: matching the table body with a non-greedy \(.*?\) stops at the first ) — the one inside varchar(64) — silently truncating the column list; the body is now extracted by balancing parentheses, and the test asserts the LAST column survived. Unrecognised clauses (engine options, collations, CHECK) are skipped rather than failing the file.
Derived joins verified on SQLite, PostgreSQL and MariaDBWorks The joins are never hand-written — they are derived at compile time from the DDL's FOREIGN KEY declarations, and this test runs the SAME generated SQL against three real engines and requires identical results. From posts→users and comments→posts it derives root users, order [posts, comments], and emits LEFT JOIN posts ON posts.userId = users.id then LEFT JOIN comments ON comments.postId = posts.id. All three engines return identical rows, including the LEFT JOIN NULL for a user whose post has no comments. A useful thing this exposed: the DDL is dialect-specific — SQLite rejects MySQL's inline KEY name (cols) and Postgres wants it separate — but the relation graph derived from it is not, because FOREIGN KEY … REFERENCES is spelled the same everywhere. That is precisely the portability the layer exists for, demonstrated rather than asserted. Skips cleanly when an engine is unavailable.
Grouping, locking and portable upsertWorks An earlier pass cut these by applying the checkability rule too quickly; each in fact carries a real compile-time check. groupBy validates columns AND the select-list consistency rule — every selected column must be grouped or aggregated. Postgres enforces that always, MySQL only under ONLY_FULL_GROUP_BY, so the same query can pass locally and fail in production; catching it makes the query portable by construction. having is only meaningful over groups, and referencing a plain column there is either an error or a silent scan that where() would have indexed — both refused. lock spells differently per engine (FOR UPDATE vs LOCK IN SHARE MODE) and SQLite has no row-level locking at all, which is reported rather than silently emitted. upsert is the operation that most justifies a structured form: Postgres and SQLite REQUIRE a conflict target that MySQL FORBIDS, so writing it portably by hand is why people give up and special-case per engine. Here the conflict columns are DERIVED from the model's unique index, and one description emits ON DUPLICATE KEY UPDATE, ON CONFLICT (…) DO UPDATE SET … EXCLUDED.v and the SQLite excluded.v form correctly. It also carries a check no string can: an upsert against a table with no unique constraint has an unreachable update branch, and one where every supplied column is part of the key would change nothing — both refused. REPLACE INTO is refused on Postgres with the reason (it DELETES the old row, firing triggers and clearing unsupplied columns). Verified by execution: insert-then-conflict yields one updated row on SQLite, PostgreSQL 16 and MariaDB 10.11.
HTTP client surface — fluent, protocol-namedWorks Node groups these as http and url; U uses classes, so the namespace is the protocol. https is deliberately NOT separate — it is HTTP with a certificate, so it is a URL scheme rather than a different API. Nothing new was needed for chaining: U's +A already IS the promise, so the scheduler drives the wait and the syntax stays direct rather than needing .then(). One-call verbs (HTTP.get/post/put/patch/delete/head) for the common case, and a fluent builder for the rest: HTTP.request(url).header(…).bearer(tok).json(data).timeout(5000).cookies(jar).followRedirects(3).send().json(). Auth helpers (bearer, basic) exist so nobody hand-builds an Authorization header. Responses decode with ok(), text(), json() (to a Tree), bytes() and header() — the decode is where the await naturally lands. Client-side cookies are a CookieJar passed to a request, backed by the jar whose domain-suffix and Secure rules are already tested. URL helpers live on HTTP (encodeURL, decodeURL, encodeQuery) rather than on URL, because URL is a template TAG and giving it static methods shadowed it — two tests caught that. Scope: the surface is bound, type-checked, and codegen lowers to u_http.h. The underlying client still lacks redirect-following, keep-alive and automatic gzip decoding.
Db EXECUTES — connection registry and executorWorks The missing link between "the compiler builds a statement" and "a program runs a query" (u_db.h). Everything above it already existed — the compiler emits a USqlStmt with placeholders and parameters apart, and the wire clients speak the protocols — but nothing turned a NAME from config into a live connection and pushed a statement through it. The registry is by name rather than by object, because main resolves to a DIALECT at compile time (which is how the placeholders were chosen) and to a SOCKET at run time; keeping those apart is what lets a build happen on a machine with no credentials. Connections open lazily, so a program that never queries never pays for a socket. Verified by execution against live PostgreSQL 16 and MariaDB 10.11: statements built exactly as SQL[main]`…` lowers them are prepared and executed, rows come back, and an unknown connection name is reported rather than guessed. The decisive case — the payload x'); DROP TABLE runq; -- is stored as a VALUE and the table survives with both rows, on both engines. Postgres uses the real extended protocol (Parse/Bind/Execute); MySQL substitutes at the DRIVER with proper quoting, because its prepared statements use a binary result protocol that is not implemented — the statement still travels as text plus a separate parameter list, and only that last hop merges them.
Db layer BOUND to U — Database.Query / Database.RowWorks Until this, the whole Db layer lived in the COMPILER — query.py, relation.py, schema.py, about 40KB — and no U source could reach any of it. Now the surface is bound and type-checked: Database.Query gets select, where, orderBy, limit, offset, join, plus the terminals fetchAll, fetchRow, execute and getSQL; Database.Row gets the Qbix lifecycle — save, remove, retrieve, retrieveOrSave, set, get, toArray, wasRetrieved/wasModified/wasInserted, modifiedFields, getPrimaryKey, getTable, find. A generated model inherits the entire lifecycle, which is the integration that makes any of it usable. Everything that reaches the wire is +A, so a query suspends on the same fiber scheduler as every other wait instead of blocking. getSQL returns a string, deliberately — not a SQL value. The SQL type's guarantee is that it can only be built from a LITERAL: the text is fixed at compile time and only slots vary. A function that minted one at run time would hand back a value whose text is arbitrary, which is exactly the injection vector the type exists to exclude — the guarantee would be worthless. Turning strings into template literals at run time is not a pit of success, so getSQL is for INSPECTION (logging, EXPLAIN) and executing the query is how you run it. find was also dropped from the row instance: starting a query is not something an individual record does. Scope creep is refused and tested: groupBy, having, onDuplicateKeyUpdate, replace and lock exist in Db_Query and are deliberately NOT bound, because structure buys the compiler nothing for them — they belong in the template, which is already parameterised. Binding them is exactly how Db_Query reached 3,719 lines. Scope: the surface is bound, TYPE-CHECKED, and codegen LOWERS these calls to the u_dbq_* runtime query builder. The connection registry hands live connections to the wire clients (Postgres, MySQL, SQLite). groupBy, having, upsert and lock are also bound with compile-time checks (see next row).
Structured query builder — scoped by checkabilityWorks The builder exists to be legible to the compiler, not as a nicer way to write SQL: it and the SQL`…` template lower to the SAME artifact (statement + params) and neither is less safe. So an operation belongs here only if the compiler gains something from its being structured — select, where, orderBy, limit earn their place (column existence, index coverage); joins are DERIVED from the foreign-key graph rather than being a method; and aggregates, GROUP BY, CTEs and window functions are deliberately excluded, because structure buys nothing there. That is a principled boundary rather than an arbitrary one, and it is why the builder is ~250 lines rather than Qbix's 3,719 — sharding, caching, chunking and transactions stay runtime concerns. The headline check: leftmost-prefix index coverage. KEY (userId,id) serves a filter on userId and does nothing for one on title, so filtering by title reports "no index covers that prefix — this will scan the whole table" — the warning Qbix's signalMissingIndex only produces AFTER the query has run. Also caught before execution: unknown columns in where/select/orderBy, unsupported operators, and an INSERT missing a NOT NULL column with no default. IN () becomes 1 = 0 rather than a syntax error, because silently dropping the clause would WIDEN the query instead of narrowing it. Each method returns a new query, so the predicate accumulates as a value — which is what makes any of it checkable. Verified by execution: select/where/orderBy/IN and get() return identical rows on SQLite, PostgreSQL 16 and MariaDB 10.11.
Db relations — root, cycles and join order at compile timeWorks A port of Qbix's Db_Relation::compile(), moved to BUILD time because every input is static (u2c/relation.py). Qbix does something worth stealing: it does not ask for a join ORDER, it DERIVES one — find the root store (referenced but never referencing), then level breadth-first, and the levels are the join order, so every JOIN references a store already in scope. Two structural mistakes fall out as errors rather than runtime exceptions: more than one root (joining them is a cross product, not one result shape) and no root at all (a cycle — nothing to start from). Both name the offending stores. validate() additionally reports non-fatal ambiguity: the same pair joined by two different keys, and a self-referencing store that needs an alias. Verified: posts→users, comments→posts yields root users, levels [[posts],[comments]], and emits LEFT JOIN posts ON posts.userId = users.id before the comments join. Qbix recomputes this per query; here it is computed once and costs nothing at run time.
Schema from checked-in SQL DDLWorks A model can come from versioned DDL instead of a live database (parse_sql_ddl): no server needed, it is the same artifact a migration applies, and its FOREIGN KEY declarations give the relation graph for free — so join order comes from the schema rather than being hand-written. Parses columns with type and length, nullability (NOT NULL wins; a bare column is nullable), AUTO_INCREMENT, PRIMARY KEY, UNIQUE KEY, plain KEY, and FOREIGN KEY … REFERENCES. The body is split on commas at paren depth zero rather than by regex, because a naive split breaks on decimal(10,2) and on KEY x (a,b) and silently truncates the column list — the test asserts the LAST column survives for exactly that reason. Multi-column index order is preserved, which is what leftmost-prefix index checking depends on. decimal(10,2) maps to float; type names are normalised across engines.
Database.* types registered (Row, Query, Range, Relation, Connection)Works The Qbix Db surface now exists as U types: Database.Row, Database.Query, Database.Range, Database.Relation, Database.Connection, Database.Expression and Row. Engine-agnostic by design, exactly as Db_Row is — a model must be the same type whether it talks to MySQL or Postgres, or the portability the layer exists for is lost; the engine appears only at the interface boundary (the connection and the SQL tag). Verified that a class GENERATED by schema pulld Base.Streams.Stream : Database.Row with store, key, typed columns and indexes: [S] +G = ["id", "publisherId,name"] — now lints clean, so the schema→class→linter path connects end to end. Note the composite index is kept as a comma-joined string, which is the form leftmost-prefix checking reads. Scope: indexes and columns are declared by the generated subclass rather than on the builtins, because a list or map field on a builtin gets dragged into every structural pass that walks builtin fields (two tests caught this).
Inheritance from a generic parentWorks d Crate[T] : Box[T] used to attach no parent at all: the parser never consumed the parent's [T], so the type arguments fell out of the declaration as loose expression statements and the class inherited nothing. Worse, it was silent — reading an inherited field still type-checked, because member access is permissive on a class with no recorded fields, so nothing ever reported the loss. Fixed: the parent's type arguments are consumed and recorded (parent_type_args), the parent attaches, and fields merge. Verified by asserting the INHERITANCE rather than that a read passes — Crate now reports [count, extra, item]. Non-generic (d Circle : Shape) and dotted (d A.Base.Sub : A.Base) parents keep working, and d X : Y with NO body is still correctly a type ALIAS with an empty field map of its own. Scope: type-argument SUBSTITUTION is not applied — an inherited field declared item: T is recorded as T, not resolved to the instantiated argument.
u2c config check — lockfile and .local manifestWorks Three jobs, separated by which config layers each may read. Resolve every Config[T]`path` binding against the checked-in layers. Lock the result in a committed u.lock.json, so a config change that alters the SQL the compiler emits appears as a DIFF rather than silently — the message names the consequence: "engine 'postgres' → 'mysql' — this changes the SQL that gets emitted". That is what makes config-driven engine selection safe instead of action at a distance, and it is the same principle as committing generated Base classes. Report which values .local/ still owes, derived from each bound type's fields without defaults — the only step that consults secrets at all. Verified end to end on a real project: correctly names db.main.password and db.main.user as missing while excluding port (has a default) and host (supplied). --write-lock records; --strict makes a missing value an error for deploy while a build only warns, so CI can typecheck a repo it has no secrets for. A bug this caught: the manifest first came back empty because d X : Y with no body is an ALIAS, and the lookup read its own (empty) field map instead of resolving through it — reporting success on an incomplete config, a green result meaning "I could not see anything".
Config[T]`path` — typed config bindingSimplified Config supplies TWO things at TWO times, and the mechanism now keeps them apart. The checked-in layers (framework → plugins → config/app.json) give the TYPE — which engine, which keys exist — at BUILD time. .local/app.json gives the VALUES at RUN time. A build deliberately stops before .local/: it is gitignored, so a type inferred from it would differ between a laptop and CI, and compiling against secrets risks baking them into the binary. load_build_config and load_runtime_config now make that split explicit. Config[Database.Connection.MySQL]`db.main` validates at compile time that the path exists in the checked-in config (a missing path is an ERROR, never a silent empty — an empty config would make every later lookup succeed), and cross-checks the config's own engine string against the declared type. The type is the single source of truth; the JSON string is a cross-check — letting the string pick the type would mean editing a JSON file silently changes which SQL a program emits with no diff in the source. The binding produces its bracket type, so SQL[main] resolves the dialect through it: verified that a MySQL-bound name emits ? and a Postgres-bound name emits $1. A tag bracket naming a variable is resolved to that variable's declared type at lint time, so nothing downstream needs a special case. Not yet: the required-keys manifest is derivable (required_keys_for works, excluding fields with defaults) but no u2c config check command emits local.sample/app.json or reports what .local is missing; and no runtime registry yet hands a populated connection to u_pg_connect/u_my_connect.
SQL[dialect] — the engine as a type argumentWorks The compile-time input a SQL template needs is the DIALECT, not the connection. Placeholder syntax, quoting and type spelling are static properties of the engine; the live connection is only needed at runtime to actually execute. So the engine arrives as a type argument on the tag — SQL[Database.Connection.MySQL]`…` — and the two stages separate cleanly: compile takes the dialect and produces statement text plus param slots; runtime takes the connection and produces rows. Verified by emission: MySQL yields WHERE name = ? AND age > ?, Postgres yields WHERE name = $1 AND age > $2, and an application alias inherits its dialect through the parent chain, so d AppDb : Database.Connection.MySQL gives ? without restating it at every call site. Values remain parameters in every dialect. Both spellings work: SQL[Conn]`…` for the template and SQL[Conn](`…`) for the invoke sugar. This generalises beyond SQL — CSV delimiters/quoting, date formats and wire-format versions are the same shape: a static parameter that selects the encoder while the I/O stays deferred.
SQL lowering — statement and parameters kept apartWorks This is the change that makes the injection guarantee true rather than aspirational. SQL`…` used to emit u_str_concat(), splicing values straight into the statement — so the linter's "value position is parameterised" message was false, and the structural-position check was guarding the front door while the side door concatenated. Now each {{slot}} becomes a PLACEHOLDER ($1, $2, …) in the statement text and the value travels beside it in a params array, all the way to the wire. Verified by compiling and running: the payload greg'; DROP TABLE users; -- appears as params[0] and the statement text contains no value at all — only WHERE name = $1 AND age > $2. SQL therefore no longer maps to char*; its C type is USqlStmt, which is exactly the shape u_pg_prepare/u_pg_execute already consume. Scope: the statement is built but not yet auto-executed — binding a connection to a call site is the remaining step.
HTML per-position slot types (__check__)Works What a slot may be now depends on WHERE it lands, which is the spec's __check__ design ("pos.attr == 'style' ? Templates.CSS; pos.attr == 'href' ? Templates.URL"). Implemented: href/src/action want a URL, style wants CSS, and — the important one — a slot inside a <script> element is refused for any type but JS, because a value there is parsed as CODE and HTML escaping is simply the wrong tool. A plain S is still accepted in an attribute (the adapter can escape it) and in content. A DEFERRED slot has no known type at the template site, so it is not judged there — only the invoked form, where the value is bound, is checked. Composition-time </script> escaping lives in u_js_for_script(). Scope: the rules are a built-in table rather than a user-implementable z f __check__ hook.
Template invoke sugar and the Handlebars tagWorks Both spellings of a template now carry distinct meaning, which is the settled model: Tag`…` is the TEMPLATE — its {{slots}} are parameters supplied by the caller's trailing {} bag, so they need not exist anywhere — while Tag(`…`) is SUGAR for calling it with the slot names in scope, and reports a missing name as an error. Verified both ways, for HTML and SQL, with every safety rule (on*, SQL structural) surviving the sugar. Handlebars is registered as the 15th tag: a SIBLING of HTML rather than a subtype, since substituting an unrendered template where markup is expected would print {{fieldA}} onto the page. Both produce markup; they differ only in who fills the slots and when — U server-side, the browser client-side — which is exactly Qbix's addTemplate pattern of one artifact usable from either side. Its slots are deferred, so they need no U binding at all. Scope: __check__ is still not consulted per position, and the invoked form does not yet lower to an actual call — codegen still emits the capture form.
JS template tag — values into JavaScriptWorks Interpolates U values into JavaScript by JSON-encoding each slot, so the result is a valid right-hand side (u_js.h, slot union S | I | N | L | Tree | JSON). The point is that JSON encoding alone is NOT sufficient, and all three gaps are closed and tested by execution: (1) the </script> break-out, which is a COMPOSITION hazard rather than a JavaScript one — < is unremarkable in JS, but the HTML parser scans for </script regardless of JavaScript string context, so the escaping lives in a separate u_js_for_script() applied when a JS value is embedded in HTML, and plain JS bound for a .js file is not over-escaped. (2) U+2028/U+2029 — legal inside a JSON string but historically illegal as literal characters in a JS string literal, so both are escaped. (3) Position — a JSON value is safe on the right-hand side of an expression but not inside a JS string literal or a JS template literal, where ${…} re-opens interpolation and turns data back into code; those positions are a compile error rather than an escaping problem, because no encoding makes them safe. Escaped quotes inside a string do not read as closers. Also re-escapes an existing JSON document while preserving its structure, and refuses rather than truncates when a buffer is too small. Scope: __validate__ is not called, so the JavaScript itself is not parsed; and since a U template literal is backtick-delimited, a JS template literal inside JS`…` needs its backticks escaped (interpolating into one is refused anyway).
Template tags — HTML / SQL / CSS with injection rulesWorks The syntax is UPPERCASE with BACKTICKS — HTML`<p>{{nm}}</p>`, SQL`SELECT ...`, CSS`...` — and each tag produces its OWN type rather than a bare string, which is what makes an HTML value safe to nest into an HTML slot without re-escaping. Two injection rules are enforced at compile time, both from the spec's own adapter design: (1) SQL structural positionsSELECT * FROM {{table}} is refused because a table or column name cannot be parameterised, while WHERE age > {{age}} is fine because a value can; (2) HTML event-handler attributes — an interpolation inside onclick=, onmouseover= or any on* attribute is a compile error, because that value is parsed as JavaScript and no amount of HTML escaping makes it safe (the spec's TemplateLang example raises CompileError("inline JS not allowed") for exactly this). Ordinary attributes (href, class) and content slots stay allowed. Scope: the per-attribute TYPE rules from the spec (href should demand a URL type, style a CSS type) are not yet enforced — only the on* prohibition is; and a nested template inside a single-brace expression slot ({xs.map(ii => HTML`...`)}) does not yet resolve.
Compile-time reflection — Phases 1, 2 and 3 (z f)Simplified Phase 1 (model): Type/Field/Method built from existing linter state (u2c/reflect.py), handling unions (modifiers after a union bind to every alternative; splitting respects nesting) and generics (declaration PARAMS vs use-site ARGS, nested). Dumpable via u2c --reflect file.u. Phase 2 (evaluation): a z f now READS the compiler's own symbol table and walks it with ordinary U code — fieldsOf(nm).map(ff => ff.name).join(",") and fieldsOf(nm).filter(ff => ff.type.name == "N").len both evaluate at compile time. No macro DSL: same language, different stage. Reflection builtins (typeNamed, typeOf, allTypes, fieldsOf) and the reflection types (Type, Field, Method, Diagnostic, Position) are registered so z f bodies type-check. Member access, lambdas, collection pipelines, string ops and guard chains are supported, under a 100k-step budget so a runaway compile-time loop is a compile error rather than a hung build. Phase 3 (type feedback): a z f declared -> Type does not HAVE type Type at its call site — it PRODUCES one, and the compiler installs the result as that expression's static type. This is the keystone the adapter hooks need: it is how __output__ will give a query its row type. A mismatch is reported against the EVALUATED type ('val' expects Point, got Row), a guard chain can select a different type per branch, and re-entrant evaluation is reported as a compile-time type cycle rather than hanging. Scope: the evaluable subset is literals, arithmetic, comparison, member access, lambdas, collection pipelines and guard chains over reflected data — not arbitrary U. Adapters are now UNBLOCKED but not yet written; __schema__ (compile-time I/O to load an external schema) is not implemented, so the SQL adapters still cannot check real column names.
Math standard library (sqrt, pow, trig, log/exp, rounding, hypot, atan2, sign, constants)Works A real math stdlib, emitted as direct libm calls — portable by construction (libm ships natively AND in Emscripten, so these lower to WASM unchanged). Registered as builtins so sqrt(x), pow(x,y), sin/cos/tan/asin/acos/atan/sinh/cosh/tanh, exp/ln/log2/log10, floor/ceil/round/trunc, fabs/hypot/atan2/fmod/fmin/fmax/copysign/cbrt/sign, and constants pi()/e()/tau() all type-check and compute correctly (compile+run tested: sqrt(2)=1.4142, pow(2,10)=1024, hypot(3,4)=5, ln(exp 1)=1, cbrt(27)=3, …). A user function of the same name OVERRIDES the builtin (the user's decl wins), verified by test. N is U's double. Scope: the real-valued double-domain functions; the Math. namespace-qualified spelling (Math.sqrt) is not yet wired (bare names work) — needs the namespace resolver to treat Math as built-in, a small follow-up.
+V vectorizationWorks Real vendor-agnostic SIMD, now running by default. A [I +V] list's .map(*k)/.map(+k)/muladd and .sum() lower to runtime kernels written with GCC/Clang vector extensions (v8i32 __attribute__((vector_size(32)))), which the C compiler lowers to the target ISA: SSE/AVX on x86, NEON on ARM, and — answering the WASM question — WebAssembly SIMD 128 under emcc -msimd128. Vendor-agnostic by construction (the compiler picks the ISA), verified real (the map kernel emits pmuludq/paddd/movdqa in the x86 assembly) and correct (compile-LINK-run tests: map *3 → 108, sum → 36). Correction from a prior audit: this was mislabelled "hints only." The SIMD was real but sat behind an off-by-default U_VEC_ENABLE, so emitted calls never LINKED and the old tests only checked the call string appeared — never ran it. The SIMD tier is now always compiled; the multi-core fork-join tier stays opt-in behind U_VEC_ENABLE (needs pthread), with a serial fallback so single-core still gets real SIMD. Fixed a latent include-guard bug exposed by this (the vec section sat outside #endif U_RUNTIME_H, double-defining on re-include). Also emits #pragma GCC ivdep on inline .on()/map/filter +V handlers, and enforces the spec's "+V non-lane-representable handler is a compile error" rule. Remaining scope: 128/256-bit fixed width (not AVX-512/SVE); GPU warps (+R(GPU)) emit an OpenCL kernel-string builder but no dispatch/execution backend — real vendor-agnostic GPU (SPIR-V/WGSL) is a separate backend, named out of scope, not faked.
+R(GPU) — WebGPU/WGSL compute kernels (map, reduce, tiled, +A async)Works Full path runs, including the +A async lowering. A U data.map(...) on a [I] +R(GPU) list routes via transpile_gpu() to a real WGSL compute shader + host dispatch (u2c/codegen/wgsl.py, u2c/runtime/u_gpu_async.h). Everything is verified by EXECUTION against real wgpu-native (bundled with wgpu-py), not just compilation: (1) WGSL kernels validated AND executed on a real device (map i32/f32, atomic sum); the full loop U source → compiler → WGSL → device → correct is pinned. (2) The complete host dispatch — upload, pipeline, dispatch, async mapAsync readback — compiles, LINKS against libwgpu_native, and RUNS to correct output. (3) Workgroup-memory tiling (the data-reuse tier): a var<workgroup> tree reduction emitted and RUN correctly (128 elems → 8256). (4) +A async, now fully lowered onto U's fiber scheduler: a GPU dispatch returns a PENDING FIBER FRAME (matching the UGenericFrame prefix) that U's real ready-queue scheduler drives cooperatively — u_gpu_map_step polls the device once per scheduler step and returns U_FIBER_SUSPENDED until the readback lands, then U_FIBER_DONE. Verified by execution: the frame is SUSPENDED right after dispatch (non-blocking), a concurrent fiber interleaves before the GPU completes, and the result is correct — the readback SUSPENDS A COROUTINE instead of blocking a poll loop, exactly like fetch(). This closes the last-turn caveat. Handler lowering enforces lane-representability (rejects outer capture / impure calls → CPU); default transpile() keeps the CPU fallback (no webgpu.h), so ordinary output still builds with plain gcc. Honest remaining scope: browser/WASM execution is unverified here (the API is identical via Emscripten --use-port=emdawnwebgpu, but there is no emcc in CI — native execution is what runs); a full tiled MATMUL (vs the tiled reduction that runs) is not yet emitted; and the +A frame is driven by the scheduler slice, with the compiler emitting the dispatch glue (emit_gpu_async_glue_c) — the auto-await INSERTION at the U call site reuses the same fork/await path as fetch() but is exercised via the runtime glue rather than a full end-to-end U-source async program. Named precisely.
+F — Function Domain Modifier (bare, typed, nullary; +R/+A/+N oracle combinations)Simplified Core forms now work (compile + run), up from Placeholder. A +F type is a callable producing the base T, emitted as a real C function pointer: I +F(I)int32_t (*)(int32_t), Config +F()Config* (*)(void). All three spec forms run end to end: bare T +F and typed T +F(S) parameters (pass a function, call it through the param → 42), and the nullary T +F() thunk (producing a class, field read back → 42). A function name in +F-argument position resolves to its mangled symbol; +F-parameter functions are excluded from inlining. Note: (I, I) -> S and S +F(I, I) denote the same type (identical C representation), but only the +F spelling parses in parameter position today. Not yet done: the compiler-oracle modifier combinations — +F +R (bound closures capturing self), +F +A (auto-await), +F +N (null-check-before-call: compiles but returns garbage) — and [T +F] collections (fails "unknown type"). Core call-through works; the oracle combinations and heterogeneous collections do not. Named, not silent.

Serialization

FeatureStatusProof
Tree: build, get/set, deep copy, merge, diffWorks test_codegen: “Tree.merge directives”, “Tree.diff”
__pack__ / __unpack__ (class ↔ Tree)Works test_codegen: “INTEGRATION: class → pack → JSON → decode → unpack”
JSON encode/decode round-tripWorks test_codegen: “JSON decode + full round-trip”
All integer widths (incl. unsigned) through TreeWorks test_codegen: “every integer width round-trips through Tree”
Streaming JSON parser (bounded memory)Works test_codegen: “JSON.stream: streaming parser hands fragments”

Schema & ORM — the thesis

FeatureStatusProof
Config load order + namespace warningsWorks test_parser: config load order; test_linter: namespace warnings
u2c schema pull (schema → Base classes)Works test_parser: generated Base classes parse
Migrations: discovery, version ordering, filteringWorks test_parser: “migration discovery + ordering + filter”
Three-way verification (schema ↔ class drift)Works test_parser: “three-way verification: missing/sync/tampered”
Lifecycle hooks (+E), compile-time dispatchWorks test_codegen: “lifecycle hooks: __run_hook dispatches +E hooks by phase”
Database.Row: change tracking, deep-copy snapshotWorks test_codegen: “Database.Row: new/save/patch/changes”
Database.Query[T]: single-predicate builderWorks test_codegen: “Database.Query: .where/.sort REPLACE (no two WHEREs)”
Relations (hasOne/hasMany) from foreign keysWorks test_parser: “relations: hasOne/hasMany from FK”
Adapter capabilities: NotSupported → compile errorWorks test_parser: “adapter capabilities: unimplemented → error”
THE INDEX CHECK: table-scan → compile errorWorks test_linter INDEX_TESTS: 6 cases incl. composite leftmost-prefix

Platform

FeatureStatusProof
Text i18n: resolve + {{interpolation}}Works test_codegen: “Text.resolve: key lookup + interpolation”
Locale completeness (missing key/placeholder → build error)Works test_parser: “text completeness: missing key + typo → error”
Handler chains wired at compile time (Q::event gone)Works test_codegen: “handler chains: __before_X__ → X → __after_X__”
Override explicit (+O), both directionsWorks test_linter: silent-override / explicit / nothing-to-override
Module registration: capability grant whitelistWorks test_codegen: “module registration: capability grant check”
z f purity (no I/O at compile time)Works test_linter PURITY_TESTS: 4 cases

Combinators

FeatureStatusProof
memo / debounce / batchWorks test_codegen: “combinators: memo caches, debounce suppresses, batch flushes”
retry / getter (fuses cache+throttle+batch)Works test_codegen: “combinators 2: retry re-attempts, getter fuses”
Rx: .or / .latest / .debounce / .delayWorks test_codegen: “Rx operators … (pull stages)”

Cryptographic canonicalization — read this carefully

The hashing, canonicalization, AND signatures are all real. The signatures are genuine ECDSA over the standard curves — P-256 for ES256/OCP, secp256k1 for EIP-712/Ethereum — with the elliptic-curve arithmetic verified against the RFC 6979 test vector and known curve points. What was a marked placeholder is now real elliptic-curve cryptography.

FeatureStatusProof
JCS canonicalization (RFC 8785, deterministic)Works test_codegen: “JCS canonicalization: sorted keys, deterministic”
SHA-256 (FIPS 180-4)Works test_codegen: verified against FIPS vectors (empty, “abc”, 56-byte)
keccak256 (Ethereum padding)Works test_codegen: matches Ethereum vectors c5d24601…, 4e03657a…
EIP-712 digest (0x1901 ‖ domainSep ‖ hashStruct)Works test_codegen: typeHash determinism + domain separation
OCP pipeline shape (canonicalize→hash→sign→verify)Works test_codegen: round-trip + all four failure modes rejected
OCP signature = real ES256 (ECDSA P-256)Works test_codegen: real ECDSA over P-256; u_ocp_sign produces a genuine (r,s) signature, u_ocp_verify checks it against the public key. The EC arithmetic is verified against the RFC 6979 P-256 vector (r matches exactly) and known secp256k1 points.
EIP-712 signature = real secp256k1 ECDSAWorks test_codegen: real ECDSA over secp256k1 — u_eip712_sign produces (r,s) plus the recovery id v, u_eip712_verify checks it. The same EC engine verified against the RFC 6979 vector; 2·G on secp256k1 matches the known point.

Cryptographic standard library (SubtleCrypto-shaped)

The Crypto, Aes, Kdf, Encoding and Random modules expose a Web-Crypto-style surface. Every primitive is verified in the runtime against its published RFC / NIST / FIPS test vector — not self-consistency, the actual standard vector.

FeatureStatusProof
SHA-1 / SHA-384 / SHA-512 (FIPS 180-4)Works test_codegen: “crypto stdlib” — a9993e36…, cb00753f…, ddaf35a1…
HMAC (RFC 4231)Works test_codegen: HMAC-SHA256 TC1 b0344c61…
HKDF (RFC 5869)Works test_codegen: TC1 OKM 3cb25f25…
PBKDF2 (RFC 8018)Works test_codegen: c=1 120fb6cf…
AES-128/256, GCM / CBC / CTR (FIPS 197, NIST GCM)Works test_codegen: FIPS-197 block + GCM TC3 ciphertext & tag; forged tag rejected
ECDH (P-256, secp256k1)Works test_codegen: both parties derive the same shared secret
base64 (RFC 4648)Works test_codegen: round-trip SGVsbG8sIFdvcmxkIQ==
Constant-time / side-channel hardeningSimplified The comparison leak — the classic and most exploitable one — is now closed: ctcrypto.ct_equal compares in time that depends only on length, not on content or the position of the first difference (test measures a <3× first-vs-last-byte timing ratio, where a naive early-exit compare shows ~160×), and signature verify() uses it for the final point check. A branchless u_ct_equal is in the C runtime too, where — unlike CPython — it is genuinely constant-time on the target. Exact remaining scope: the underlying field/scalar arithmetic (_mul, _add) is not yet constant-time (secret-dependent scalar loops), and CPython itself has an interpreter timing floor these helpers cannot remove — full constant-time execution belongs in the compiled C runtime. So: comparison hardened and auditable, field arithmetic not yet. Named, not hidden.

Post-quantum signatures

SPHINCS+ (SLH-DSA) is a stateless hash-based signature scheme standardized by NIST in FIPS 205. Its security rests only on the hash function, so it resists the quantum attacks that break ECDSA. This is not a shape or a stub: the implementation is byte-identical to the SPHINCS+ reference.

FeatureStatusProof
SHAKE128 / SHAKE256 XOF (FIPS 202)Works test_codegen: SHAKE256(“abc”) 48336660…; built on the existing Keccak-f[1600]
SPHINCS+-SHAKE-128f-simple keygenWorks test_codegen: public-key root 2f94dbe8… is byte-identical to the reference C compiled from the SPHINCS+ source (same seed)
SPHINCS+ sign (deterministic)Works test_codegen: the full 17088-byte signature is byte-identical to the reference (matched checksum); the official reference verifier accepts our signature
SPHINCS+ verify (round-trip + forgery rejection)Works test_codegen: valid signature verifies; tampered message, tampered signature, and wrong public key all rejected
Kyber / Dilithium (ML-KEM / ML-DSA)Placeholder Named in the Crypto namespace surface but not yet implemented. These are lattice schemes (NTT over Z3329 / Z8380417), a separate build from the hash-based SPHINCS+. Declared honestly as not-yet-built rather than stubbed to look real.

Tooling

FeatureStatusProof
u2c init scaffolds a buildable projectWorks test_codegen: “u2c init: default Main.u and all --example templates compile and run”
u2c init --example (hello/fib/json/errors)Works same test; examples/ on disk asserted to match templates
u2c <file> -o out.c transpileWorks examples/build.sh drives it end-to-end from a clean unzip
u2c build <file.u> -o <exe> (transpile + wrap main + link in one step)Works test_codegen: “u2c build: compiles a .u entry to a native executable end-to-end” — drives the real subcommand per example, runs the binary, asserts its exit code; a missing C compiler fails cleanly (exit 1)
pip install u2cu2c console commandWorks clean-venv install verified: u2c --version and u2c build run from a non-repo directory; the C runtime header ships as package data
u2c --versionWorks prints the packaged version string

Template metaprogramming & injection safety (TemplateLang)

U's template tags — SQL`...`, HTML`...`, Regex`...`, URL`...`, etc. — are the spec's metaprogramming facility: each tag is a TemplateLang with compile-time hooks (__check__ for interpolated expressions, __validate__ for the parsed template, __output__ for the result type). This row states exactly how much is enforced today, because the enforcement — not the syntax — is the security claim.

CapabilityStatusDetail
Tags parse, get a distinct type, flags lex as a suffixWorks Regex`\s+`g etc.; unknown tags warned "not injection-safe".
Interpolations are full expressions, scope- and type-checked (__check__)Works An undefined or ill-typed {{expr}} is a compile error, not silent text.
Regex injection guard: raw S rejected, only Regex sub-patterns composeWorks test_codegen: "a raw S cannot be interpolated into Regex`...`" + the permissive composition half.
SQL / HTML accept S in value position (parameterized / escaped)Works test_codegen: "html/sql/url DO accept an S". The sql module parameterizes; html escapes.
Grammar-aware __validate__: SQL structural-position injection (SELECT * FROM {{table}})Works The spec's SELECT * FROM {{table}} ✗ is now caught: a raw S interpolated into a SQL structural position (after FROM/JOIN/INTO/UPDATE, or a qualified-name dot) is a compile error, because the sql module parameterizes values but cannot parameterize a table/column identifier. A raw S in a value position (WHERE col = {{x}}) stays accepted and parameterized, and a composed SQL sub-template in a structural slot is fine. test_codegen: "Regex + SQL structural-position injection are enforced; value-position S is parameterized" (checks FROM/JOIN/INTO/UPDATE rejection, value-position + HTML acceptance, and sub-template composition). Exact scope: the position analysis is lexical — it inspects the SQL clause keyword immediately before each interpolation — not a full SQL parser, so it catches the common structural slots the spec names rather than every conceivable grammar position; HTML escaping and other tags' deep grammar validation remain trust-the-module. The headline injection case the type system advertises is now enforced.

Web playground — translate other languages TO U

A browser playground translates JavaScript, TypeScript, and Python to U (the compiler direction is TO C; this is the front-of-house complement). Every bundled example is graded by the real u2c type-checker — full compile, not just parse — and the result is pinned by a regression test in the codegen suite.

LanguageExamples compiling through real u2cStatus
JavaScript21 / 22Works
TypeScript19 / 20Works
Python20 / 22Works
Total60 / 64Works

The four that do not compile are language-boundary cases, not translator bugs, and are named rather than hidden:

The backend half (the real Python u2c running in-browser via Pyodide, byte-identical to native) is covered under Tooling / Platform above.

The rule this document follows

The same discipline that produced the index check produced this matrix: measure, don’t assume. A “Works” row exists only because a test compiles and runs the thing. Twice during development a claimed limitation dissolved on inspection — the Rx operators did not need concurrency, and an empty-map literal already unified correctly — and both were downgraded from “hard problem” to “done” only after the test proved it. For a long time the two crypto signature rows were honest Placeholders: rather than claim ECDSA that was really a keyed hash, the gap was named. Then the gap was closed — real elliptic-curve arithmetic (verified against RFC 6979) now backs both, and the rows are Works. That is the honest progression: name the gap while it exists, and only promote the row when a test proves the real thing. A matrix that cannot say “not yet” is not honest; one that never gets to erase a “not yet” is not finished.