Quartz v5.25

Quartz Roadmap

Version: v5.28.0-alpha | Target: v6.0.0 (Launch) Last updated: April 18, 2026 (night — https://mattkelly.io/ now served by the Quartz unikernel, Caddy LE TLS termination, HTTP/2 + HTTP/3 advertised, 2077 connections served pre-handoff) Status: 2,026 functions (default features) | 523 spec files | 6,500+ tests | Fixpoint verified | 7.81 GB peak RSS on macOS (was 25.2 GB; -69%, wall 35.6s → 18.3s, -49%)

Language syntax frozen (Mar 19, 2026). Self-hosted compiler is sole compiler. All ship-blocking items resolved. Pattern exhaustiveness upgraded to world-class Maranget algorithm Apr 16 evening. Open compiler/parser holes are tracked below.


Priority Philosophy (read this before picking work)

The compiler comes first. Everything else is last-mile.

Quartz’s highest-weight priority is compiler completeness, unification, spec pass rate, robustness, and usability — making the language actually real, not approachable-but-broken. The anti-toy-language mindset: every feature documented must work; every spec must pass; every known hole must be filled. We do NOT put lipstick on a pig.

Order of operations:

  1. Close all open compiler/parser holes (Open compiler issues section + IMPL-TRAIT-RPITIT/TAIT/STRUCTURAL + cache-pattern miscompile)
  2. Make every spec pass (8 remaining failing specs — json SIGSEGV, separate_compilation, file_helpers, http2_frame, route_groups, semaphore, tls_async, actor Linux rebuild)
  3. Complete WASM backend (TGT.3 — 3 failing wasm specs close out multi-target story)
  4. Language feature gaps (Slice, re-exports, user-defined macros, effect bounds)
  5. Performance/infrastructure follow-ups (compiler memory, arena allocator, async lock hardening)
  6. Last mile ceremony (docs, package manager, demos, VS Code extension, blog post, community) — ONLY AFTER 1-5 are done

What this means: Tier 1 (Packaging & Launch) and the package-manager / doc-generator / demo-programs items in Tier 4 are DEFERRED until the language itself is complete. See the re-ranked view below.

Two major initiatives run in parallel with the above, starting now (Apr 17, 2026):

  • Operation Piezoelectric Effects — algebraic effects for Quartz, Koka-style evidence-passing, 5 phases, ~5-8 quartz-weeks. Phase 3 migrates async onto effects. Unifying mechanism for throws / io / state / async / alloc. Named after the piezoelectric effect (quartz crystal → electric charge under stress, Curie 1880) — same metaphor: user code → observable effects. Phase 0 design COMPLETE 2026-04-18. Commit tag [piezo].
  • Kernel Epic — Bare-Metal & Unikernel — systems-language infrastructure buildout (SYS.1-5) + unikernel (KERN.1-4), ~6-10 quartz-weeks. Dogfoods Quartz as a legitimate kernel-capable systems language. Kernel scheduler = Async effect handler; allocator = Alloc effect. Effects-independent SYS epics start immediately.

These two tracks are coupled: kernel scheduler waits for effects Phase 3, kernel allocator waits for effects Phase 2. Effects-independent parts of both can start today in parallel with compiler completeness work.

This is the single source of truth for Quartz roadmap work. Historical roadmaps and handoffs live in docs/archive/.


Where We Are

The language is feature-complete for launch. All ship-blocking compiler bugs are resolved. What remains is packaging, documentation, ecosystem, and a tail of compiler holes that the April 2026 dogfooding sprint surfaced.

Complete: Self-hosting compiler, H-M type inference, generics, bounded generics, union/intersection/record types, pattern matching, borrow checker, move semantics, Drop/RAII, generators, async/await, goroutines, channels, select, actors, race detector, structured concurrency, HTTP/2 server (deployed on VPS), JSON/TOML/CSV parsers, Unicode support, unified Map<K,V>, formatter, linter (24 rules), REPL, LSP, DWARF debugger, WASM backend, playground, postfix ?, puts auto-coercion, string/map iteration, negative indexing, char literals, destructuring, comprehensions, implicit it (documented surface; parser support is partial — see CMP-PARSER-IMPLICIT-IT below), safe navigation, pipeline operator, triple-quoted strings, abstract trait methods, trait auto-satisfaction (Swift/Go model), Container trait, Show/Eq/Serializable stdlib impls, Open UFCS compiler migration, multi-line function signatures, trailing commas, CLI style library (composable Style API, terminal capability detection, color degradation), String ergonomics (String + Int auto-coerce, String * Int repetition, Bool.to_s()"true"/"false"), Map<Int,V> iteration (intmap_keys/values via occupancy bitmap, for-in typed loop vars), Float hashability rejection (tc_struct_is_hashable walks fields), unqualified variant payload types, Option ergonomics (is keyword, postfix ! force-unwrap, if let/elsif let, panicking m[key] subscript via map_fetch), compiler smoke tests (guard:source clean-room build + brainfuck.qz + expr_eval.qz), resolver full scope tracking (local vars/for/lambda/destructure shadow module names in UFCS), @x sigil (implicit self in impl/extend — verified: nested blocks, lambdas, closures), raw pthread_mtx intrinsics (internal lock/unlock without Mutex value slot).

Apr 15-16 sprint: pthread_mtx intrinsics (raw lock/unlock without value slot, 9 call sites swapped, imtx 64→72→64 bytes), resolver full scope tracking (Tier 3 #11 — local vars/for-loops/lambda params shadow module names in UFCS), @x sigil verified in all contexts (Tier 3 #16), async lock wait-list audit (15a: no leak, 2 hardening items filed), concurrency stress sweep (15b: actor crash+restart+stop task loss, root-caused to stale pipe bytes + EV_ONESHOT + io_map race FIXED Apr 16 — actual root cause was cascade-stop race, not pipe bytes).

Apr 16 handoff session (11 commits, ~100+ tests unblocked): SEND-RECV-SHADOW fixed (deleted dead extern "C" send/recv — unblocked 6+ concurrency specs), async $poll extern collision fixed (cg_extern_var_index $-strip matched __Future_*$poll → libc poll()), .size wart fixed at compiler level (typechecker auto-rewrites .size on Int-typed values to vec_size()), impl Option/Result infinite recursion fixed (method bodies delegated to same-named free functions — inlined match logic), collection stubs complete (reversed() + sorted() added to prelude — 21/21), generic_ufcs_dispatch_spec fixed (test string concatenation bug, not compiler bug — 8/8 green), move semantics holes verified already-enforced (s25_low 15/15 + s25_safety 13/13), modules_spec fixed (stale expected error string — 35/35 with QUARTZ_FIXTURES). Retest sweep: ~100 specs tested, ~90% pass rate. Stale roadmap entries closed en masse.

Apr 18 — Quartz unikernel goes public (7 commits, zero compiler source touched): KERN.4 deploy + J.1 live telemetry + J.2 HTTP routing + J.3 Joy-demo page + multi-segment TCP + J.4 handoff + DEF-A 16-slot multi-connection TCP + HTTPS flip at root. https://mattkelly.io/ now served by a Quartz-authored unikernel through self-authored virtio-net + Ethernet + IPv4 + TCP + HTTP/1.1, with Caddy fronting Let’s Encrypt TLS. 58 KiB ELF, 2,077 connections served pre-handoff, PMM flat at 138 pages, 0 leak, 16 concurrent verified. See docs/handoff/kern4-to-joy-demo-handoff.md + Tier 6 below. Commits f287b68c…53e80796.

Apr 16 late evening — Maranget pattern exhaustiveness (3 commits, +35 functions): Replaced the 290-line flat ad-hoc tc_check_exhaustiveness with the full Maranget (2007) pattern matrix algorithm — same as Rust’s rustc_pattern_analysis, OCaml’s match compiler, and GHC. Shipped in 3ddaa9fa (core algorithm, phases 0-6, 18 new tests), 13610c95 (bare TYPE_RESULT/TYPE_OPTION subjects — wildcard_pattern_spec 9/9), and 463fab4c (parser chain loop for triple-qualified patterns Mod::Enum::Variant(v) + deleted 303 lines of legacy checker). Witness generation shows missing patterns in errors (“missing pattern ‘West’”). QZ0108 unreachable arm warnings for closed types. Handles enum/Bool/Int/String/Option/Result/union/or-patterns/guards (conservative). Single canonical tc_check_exhaustiveness function, no legacy fallback.

Flagship demos: Self-hosted compiler (2,559 functions), Soul of Quartz demo (50K tasks/sec + instant HTTP), Quartz unikernel at https://mattkelly.io/ serving the Joy-of-Quartz live-telemetry page over HTTPS (Apr 18, 2026 — self-authored virtio-net + Ethernet + IPv4 + TCP + multi-segment + 16-slot multi-connection + HTTP/1.1

  • dark-mode landing + JSON API polled from the browser, zero libc, zero Linux in the request path, unikernel running as a QEMU -M microvm guest under systemd, Caddy-terminated TLS via Let’s Encrypt).

Tier 1 — Packaging & Launch ⏸ DEFERRED — last mile

Do NOT start these until Tier 0 (compiler completeness), Tier 2 (cleanup), Tier 3 (language feature gaps), and WASM backend are complete. These are ceremony, not substance. Shipping these on a buggy compiler would be putting lipstick on a pig.

#ItemEffortNotes
1Publish VS Code extension3-4h⏸ Last mile. Defer until language fully crystallized and spec-complete.
2Stdlib narrative guide2-3d⏸ Last mile. “How do I…” guide — writing docs for a compiler with open holes means rewriting them later.
3Testing/FFI/Error/Debug guides3-4d⏸ Last mile. Same reasoning. Write docs ONCE, after the compiler is real.
4Launch blog post1d⏸ Last mile. HN narrative — one-shot attention. Must have a working, bug-free language to point at.
5Community infrastructure1d⏸ Last mile. Discord/Matrix, issue templates. Sets up for traffic we don’t want to invite yet.

Tier 2 — Cleanup & Hardening

High-value polish. Do before or alongside launch.

#ItemEffortNotes
6Scheduler park/wake refactor2-3dCOMPLETE Apr 15, 2026 — all five follow-ups landed, zero open items. (1) __qz_sched_wake rewritten as parking_lot-style CAS retry loop (codegen_runtime.qz:2480-2530, commit ab9a7829). Pre-fix: 6 hangs + 2 crashes / 1500 runs (0.4% hang). Post-full-sprint: 0 hangs + 0 crashes / 8000+ runs. Test 4 of sched_park_spec.qz un-pended. (2) QZ0210 narrow rejection at MIR-lower time (commit 737b0a4e) — direct calls from main() rejected with actionable hint. (3) Dead spin-park codegen in cg_intrinsic_conc_sched.qz:2988-3086 replaced with call @abort() (~95 lines of CAS+spin IR deleted); wk_try_spin branch dropped from __qz_sched_wake since no code writes state 3 anymore (commit d1c58715). (4) QZ0210 widened from “main only” to “any non-async-target function” via new PASS 0.98 in mir_lower.qz that pre-scans all AST stores for NODE_ASYNC_CALL / NODE_GO targets, populating _async_func_names before PASS 1 (commit d1c58715). Regression-locked by 4 tests in qz0210_sched_park_compile_error_spec.qz. (5) SCHED-WAKE-SIGSEGV resolved — root-caused from the macOS crash report (~/Library/Logs/DiagnosticReports/spk*.ips) to a TOCTOU race in the multi_park_worker test helper (sched_park_spec.qz:57-85), not a compiler bug. The parker did atomic_add(g_frame_count, 1) BEFORE store(base, idx, frame), so the waker could see g_frame_count >= n and read a still-zero slot (malloc via alloc(4) returns uninitialized memory that happens to be zero-paged from fresh mmap). sched_wake(0) then crashed at __qz_sched_wake + 16 (the load atomic frame[5] — far=40 in the crash report). All 21 archived crash reports had the same signature (test_park_n_wake_all → __Future_multi_wake_worker$poll → __qz_sched_wake). Fix: waker waits per-slot until load(base, i) != 0 before calling sched_wake. park_state_spec.qz’s mw_park_task was already safe (stores before counting). Post-fix: 8000/8000 clean. The pre-fix Phase 1 “0.17% crash residue” I attributed to the fix itself was actually this test bug all along, unmasked by the hang fix running the waker faster.
7Resolve CMP-MAP-1..4 + CMP-PARSER-IMPLICIT-IT-1/2 + CMP-VARIANT-PATTERN + CMP-POSTFIX-TRY3-7d✅ ALL FIXED (Apr 2026). CMP-MAP-1..4: Cluster A (commit 02db857e). CMP-PARSER-IMPLICIT-IT-1/2 + FAIL-FAST: Cluster B (commit 0981c238). CMP-VARIANT-PATTERN: Cluster C-1 (commit f4ccbbfe). CMP-POSTFIX-TRY: Cluster C-2 via Path A (commit 5e2fd98d). CMP-MAP-FOLLOWUP: phases 1-3 (commits 3aa38ba0, b67c1914). Follow-up session (Apr 12): int-key iteration, for-in loop var typing, Float hashability, float-zext codegen, FFI dedup, Request retyping, unqualified variant payload resolution, dogfood 22 specs.
8Fold in stale handoff/recovery state✅ DONE — docs/archive/ consolidated; all HANDOFF_*.md, OVERNIGHT_PLAN.md, VISION.md, prior docs/Roadmap/archive/ content moved to single location.
8aextend / impl unification — delete impl keyword~1 dayPhilosophy-drift cleanup. self-hosted/frontend/parser.qz:6552-6553 confirms extend and impl (inherent) produce the same NODE_IMPL_BLOCK — two syntactic forms for the same intent. Unify under extend (Swift-style). New grammar: extend Type ... end (inherent), extend Type with Trait ... end (trait impl, replaces impl Trait for Type), extend Type without Trait ... end (negative impl, replaces impl !Trait for Type), extend<T> Vec<T> with Trait where ... end (generic trait impl). impl keyword deleted entirely — Directive 7, zero-user world, migrate all internal uses in the same commit that removes the keyword. Parser: ps_parse_impl deleted; ps_parse_extend extended with with/without clause and type-parameter/where-clause support. Lexer: retire TOK_IMPL. Typecheck + MIR: no change (same AST node). Migration scope: self-hosted/**/*.qz, std/**/*.qz, examples/**/*.qz, all QSpec files, docs/QUARTZ_REFERENCE.md. Rationale: one verb, one form, philosophy-aligned. Swift precedent (extension Type: Protocol { }) proves the pattern works.
8bMacro system audit — is it world-class?~1 quartz-dayQuartz has a fuller macro story than the Tier 4 #25 entry suggests: self-hosted/frontend/macro_expand.qz implements both builtin macros ($try, $unwrap, $assert, $debug) and user-defined macros (macro name(args) ... end with full registry, variadic params, quote/template variants), plus separate derive-macro machinery in self-hosted/frontend/derive.qz. The infrastructure is there; the audit question is whether it’s production-grade. Check: (1) hygiene — does expansion rewrite identifiers to prevent capture across macro call sites? (2) pattern matching on AST shapes — can a macro destructure its argument AST beyond positional params? (3) error quality — what does the user see when a macro is misused? (4) documentationdocs/MACROS.md doesn’t exist; zero user-facing docs. (5) test coverage — how much QSpec exercises macro edge cases? Output: a gap list + prioritized fixes, or a “it’s already world-class, just needs docs” closure. Report determines whether Tier 4 #25 (user-defined macros, Large) gets downgraded to “document + polish” or stays as an open feature sprint.
8cCG.1: defuse cg_extern_var_index extern-demangling landmine~1 quartz-dayFIXED Apr 19 2026. Root cause was not “resolver prefixes extern callees” but “the parser translates module::fn into the single identifier module$fn at parse time, which then arrives at codegen as the call’s func_name.” Rewrite at resolver/MIR lowering would have been correct but invasive. Actual fix: in cg_collect_extern_info, store the resolver entry’s entry.name (bare for bare registrations, prefixed for per-module re-registrations) as the call-site lookup key and keep the AST’s str1 (always the bare C symbol) as a parallel extern_var_c_names[i] for LLVM link-time emit. cg_extern_var_index is now a simple exact-match lookup — both the __ prefix guard and the cg_find_function user-function guard deleted. The tag=10 double-registration the resolver already does (bare + module$name) is what makes the table cover every call-site spelling without content-agnostic demangling. Verified: UFCS-EXTERN-SHADOW repro (FileHandle$close still resolves to itself, NOT libc close), __Future_*$poll original burn (12/12 async_spill_regression_spec), multi-module ffi_mod::abs (20/20 extern_def_spec), 15/15 ffi_spec, fixpoint (2142 functions) + smoke (style_demo/brainfuck/expr_eval).

Tier 3 — Language Features

Post-launch or alongside. Ordered by design-readiness.

#ItemEffortNotes
9Option ergonomics Phase 4: smart narrowing2-3d✅ COMPLETE (Apr 12, 2026). Flow-sensitive narrowing at MIR level: MirContext tracks narrowed_var_names/narrowed_variant_names. if opt is Some narrows opt in then-block; if opt is None narrows to Some in else-block. mir_lower_match_expr detects narrowed subjects and emits payload extraction directly (no tag check). Works for Option (Some/None) and Result (Ok/Err). Also supports is-with-bindings (if opt is Some(v)). 7 tests in option_narrowing_spec.qz.
10Option ergonomics Phase 6: @value Option3-5d✅ PARTIAL (Apr 12, 2026). Option::Some(v) and Option::None enum constructors now use MIR_ALLOC_STACK(2) instead of MIR_ALLOC. Escape analysis heap-promotes when returned/stored to heap. Fixed 2 escape analysis bugs: (1) multi-origin variable overwrite didn’t mark old origin escaped, (2) block ordering caused LOAD_VAR to miss variable origins (added second pass). Safe navigation ?. also uses alloc_stack. Codegen inline helpers (hashmap_get, vec_pop, etc.) still use malloc.
11Resolver full scope tracking1-2dCOMPLETE Apr 15, 2026. _ufcs_current_params now tracks all in-scope bindings: function params + local var/const bindings (NODE_LET, NODE_CONST_DECL, NODE_GLOBAL_VAR) + destructured bindings (tuple/struct/vec) + for-loop iter vars + lambda params. NODE_BLOCK walks children sequentially, pushing bindings after each statement; NODE_FOR/FOR_AWAIT push loop var before body; NODE_LAMBDA pushes params before body (was entirely missing). Verified discrimination: OLD binary returns wrong result on definitive collision test (calls module$method() with 0 args), NEW binary returns correct result via UFCS (calls module$method(value) with 1 arg). Remaining: match arm pattern captures + comprehension bindings not yet tracked (follow-up). 6 tests in resolver_local_shadow_spec.qz + fixture module scope_test_mod.qz. All 7 existing UFCS specs green.
12Rust-style pattern matrix exhaustiveness3-5dCOMPLETE Apr 16, 2026. Maranget (2007) pattern matrix algorithm — same as Rust/OCaml/GHC. tc_check_exhaustiveness in typecheck_match.qz: MxPatStore (flat parallel-vector storage), MxMatrix (row vectors), mx_is_useful (recursive usefulness), mx_find_witness (witness generation). Handles enum/Bool/Int/String/Option/Result/union types + or-patterns + guards (conservative). QZ0108 unreachable arm warnings for closed types. Witness patterns in error messages (“missing pattern ‘West’”). Parser fixed to handle triple-qualified patterns (Mod::Enum::Variant) via chaining loop in ps_parse_pattern. 303-line legacy checker deleted. 18 new tests + 13 existing + 9 wildcard tests green. 2026 functions, fixpoint verified.
13Scheduler park/wake refactor2-3dCOMPLETE Apr 15, 2026 — zero open items (see Tier 2 #6 for the full writeup). Five pieces shipped: wake CAS retry loop, narrow QZ0210, dead spin-park codegen deletion, widened QZ0210 via PASS 0.98 async-target pre-scan, and SCHED-WAKE-SIGSEGV root-caused from crash report to a TOCTOU race in the multi_park_worker test helper (not a compiler bug). Failure rate 0.53%→0% across 8000 stress runs. Unblocks #15.
14Re-exports (pub import)MediumClean public API surfaces. import json gives you everything.
15bActor crash+restart+stop hang — scheduler task lossMRESOLVED (Apr 16, 2026). Root cause was NOT the pipe/io_map race hypothesized in the handoff — actor inboxes don’t use pipes (they use channel_park_recv + direct __qz_sched_wake). The actual bug was a cascade-stop race: when a.stop() stops Pinger, Pinger’s stop handler sends reply THEN sends LINK_STOPPED (tag -4) to Crasher. The reply unblocks the main thread, which immediately calls b.stop(). Meanwhile, the LINK_STOPPED races with b.stop()’s message. If LINK_STOPPED wins (~25%), Crasher cascade-stops and closes its inbox — b.stop()’s reply_ch never gets a response → main hangs forever. Fix (two parts): (1) Close inbox FIRST in both stop handler and cascade_stop, before notifications and drain. This makes the channel_closed check in the stop proxy see the actor as dead immediately. (2) Replace the blocking recv(reply_ch) in the stop proxy with a try_recv_or_closed poll loop that checks channel_closed(inbox) as a fallback — if the actor cascade-stopped before processing the stop message, the proxy detects the closed inbox and returns instead of blocking. Stress: 0/500 hangs (was 28/500). actor_link_spec 10/10 green. Fixpoint verified at 1989 functions.
15aAsync lock hardening follow-ups (filed Apr 15)S-MItem (1) DONE Apr 19 2026 (commit TBD): async_mutex_free and async_rwlock_free now abort with the diagnostic async lock freed while wait list non-empty (UAF imminent) when slot-2 wait_head is non-zero. Helper qz_async_lock_drain_panic emitted by codegen_runtime.qz next to the other panic helpers; drain-check prologue inlined at both free intrinsic handlers in cg_intrinsic_conc_async.qz. New spec async_lock_drain_on_free_spec.qz (2 tests, both via assert_run_panics). Fixpoint 2144 functions. Item (2) STILL OPEN: async_rwlock_write_unlock only wakes ONE waiter. If a chain of readers is queued behind a writer, only the head reader wakes on write-unlock; the next reader waits for that reader’s unlock (state 1→0 triggers another wake). Readers serialize where they should parallelize — throughput hit under reader contention, not a liveness bug. Fix requires expanding wait-node layout from alloc(3)alloc(4) to carry a reader/writer mode flag (the [398] layout comment already promises this slot — reserved but never wired), then in the wake path, if popped node mode==0 (reader), keep popping readers until mode==1 or list empty and wake them as a batch. Touches read-acquire + write-acquire + write-unlock in mir_lower_expr_handlers.qz + cg_intrinsic_conc_async.qz. Likely M, not S — budget a full session for wait-node layout change + batch-wake loop + reader-contention throughput test.
15Async Mutex/RwLock2-3dCOMPLETE Apr 15, 2026. ASYNC-MUTEX-MISSED-WAKEUP root-caused to TWO independent bugs that interacted. Bug A (heap corruption): async_mutex_new and async_rwlock_new allocated 64 bytes for their internal imtx pthread_mutex_t, but mir_emit_async_mutex_lock/_rwlock_read/_rwlock_write call the mutex_lock(imtx) intrinsic which treats imtx as a Quartz Mutex struct and reads/writes byte 64 as a “protected value” slot. Every contended lock OOB-read 8 bytes past the end, and every unlock OOB-wrote 8 zero bytes past the end — corrupting adjacent heap allocations (task frames, wait nodes, other mutexes). The Quartz mutex_new intrinsic in cg_intrinsic_conc_sched.qz:11 allocates 72 bytes precisely because of this — it’s the canonical Mutex layout. Fix: grow both imtx allocations from 64 → 72 bytes to match the canonical layout. Bug B (missed wakeup): Classic parking_lot-style race in the contend paths — between the fast-path CAS failure and the internal-mutex acquire, the holder could complete its entire unlock path, observe an empty wait list, and return, leaving the acquirer appended and parked forever. Fix: after acquiring the internal wait-list mutex, re-run the fast-path CAS. If it succeeds (holder released in the window), release the internal mutex and jump to the acquired path. Shipped in mir_emit_async_mutex_lock, mir_emit_async_rwlock_read, and mir_emit_async_rwlock_write. Stress results: baseline 49 hangs + 9 crashes / 600 (9.7% fail) for async_mutex_spec; 1 hang + 1 other / 300 for async_rwlock_spec. Post-fix async_mutex: 0 / 2000 (sequential). Post-fix async_rwlock: 0 / 1500. Why my earlier re-check attempt failed: Bug A’s heap corruption was the dominant failure mode — fixing the protocol alone didn’t matter when memory was being trashed underneath. The 128-byte experiment isolated Bug A (crashes 9→0, hangs 49→29), and adding the re-check on top closed the remaining race. Neither fix alone is sufficient; both are load-bearing.
16@x sigil (implicit self)Small-MedCOMPLETE Apr 15, 2026. Verified working in all contexts: direct impl/extend methods, nested if/while/for blocks, lambdas/closures within methods (parser in_extend_method flag stays active through nested scopes). 13 tests in at_sigil_spec.qz (9 original + 4 new edge cases: nested-if, for-loop, lambda-passed, lambda-captured). No code changes needed — the implementation was already complete, just under-tested. Remaining design items (match-arm captures for @x, @self shorthand, directive namespace conflicts) are polish, not blockers.
17Effect bounds on function types3-5dFn(Int): Int @pure syntax.
18usize type aliasTrivialSemantic clarity. Low value since everything is i64.
19Compiler memory optimization1-2w✅ PHASES 1+2+3a+3b.next+3c DONE — 25.2 GB → 7.81 GB peak RSS on macOS (-69%, wall 35.6s → 18.3s, -49%). Phase 1: eliminated builtin UFCS mangled names + tc_mangle cache. Phase 2: mimalloc + 33 mangle cache sites (mimalloc later disabled on macOS due to dyld TLS race; Linux still benefits). Phase 3a (889a758d): O(n³) parser blowup fixed via PascalCase gate on speculative generic-init walk in ps_parse_ident_expr (25.2→15.1 GB). Phase 3b (c4086eea): investigation only — discovered tc_free’s covered Vecs only hold ~1.4 MB; the actual 6.3 GB leak lives inside tc_function’s body walk. Phase 3c (c1eb4fd4): gated egraph and lint modules behind @cfg(feature: "egraph") / @cfg(feature: "lint") (15.1→12.47 GB; function count 2289→1985). Phase 3b.next (this session): bisected tc_stmt/tc_expr by node kind → NODE_CALL dominant → drilled into tc_expr_call → localized to arity-check suffix fallback. Root cause: 9 sites in typecheck_registry.qz used str_byte_slice(name, len-suf, len).eq(suffix) to check “ends with suffix” — allocating ~2100 substrings per fallback call. For module-internal calls (bare tc_parse_type inside typecheck.qz → suffix-matches typecheck$tc_parse_type), the fallback fires on ~10% of NODE_CALLs × 67k calls = 4.6 GB leaked. Fix: replace all 9 with zero-alloc name.ends_with(suffix) (runtime memcmp). Typecheck 5401→737 MB (-86%), peak 12.47→7.81 GB (-37%), wall 21.0→18.3s (-13%). Also shipped: tc.parse_type_cache field for future groundwork and a first-arg type cache in tc_expr_call to avoid redundant UFCS receiver re-walks (~90 MB). Further gains available from remaining allocations in tc_expr_call’s post-arg-walk section (borrow summary, lambda validation, trait bounds, container type propagation) if needed to push below 6 GB.
20Arena-based compiler allocator2-3wLong-term architecture: per-phase arenas using mmap-backed regions that drop via munmap at phase boundaries. Higher priority after Phase 3b investigation: macOS libc’s malloc never returns freed pages to OS (verified empirically — even madvise(MADV_FREE_REUSABLE) on malloc-allocated memory doesn’t drop RSS). The only reliable RSS drop on macOS is mmap → munmap. Evaluate after 3b.next ships; if peak still >8 GB the arena rewrite becomes the unblocker.
20aStructured concurrency ergonomics — gap auditResearch: ~0.5 dayQuartz has two well-defined concurrency endpoints: fire-and-forget (go do -> end, invisible) and full structured supervisor trees (rich-wielded). Audit question: is there a middle-ground user intent (“I want some structure — cancel children if parent exits, no zombies — but don’t want to write a full scope block”) that currently has no clean form? Research-only item. Execute AFTER effects Phase 3 (async-as-effect migration), because Phase 3 itself changes the concurrency ergonomics — go becomes an Async handler-op and with scope do -> ... end may become natural, potentially closing the gap for free. Deliverable: memo — “the gap exists, here’s what to build” or “gap is closed by Phase 3, no action.”

Tier 4 — Ecosystem

Build the flywheel. Each item unlocks the next. Items 21-23 are last-mile (defer until compiler complete); items 24-25 are language features (work on alongside Tier 3).

#ItemEffortNotes
21Package manager2-3w⏸ Last mile. Quartz.toml, quake add, Git-based registry, lock file. Was previously called “THE post-launch priority” — reclassified as ceremony until language completeness is achieved.
22Doc generator (static HTML)1w⏸ Last mile. quartz doc → searchable, cross-linked API docs from ## comments. Needs API to stop moving first.
23Demo programs (15 designed)Multi-sprint⏸ Last mile (mostly). See Demo Programs below. qz-http ✅ already shipped — further demos are marketing surface, build after compiler is bug-free.
24Slice<T> typeLargeLanguage feature (keep in priority queue). {ptr, len} fat pointer + range indexing v[2..5]. Real systems language table stakes.
25User-defined macrosLargeLanguage feature (keep in priority queue). Design phase first (Rust/Elixir/Zig study).

Tier 5 — Moonshots

Uncommitted until design sessions happen. Full thinking is in docs/archive/VISION.md.

#ItemNotes
26Refinement typesSMT solver (Z3). World-class or nothing.
27GPU compute@gpu → NVPTX backend.
28LLM directives@ai("prompt") annotations.
29Stream abstractionChannels + generators.
30libLLVM integrationIn-process backend: JIT/REPL, optimization control, faster compilation.

Tier 6 — Kernel Epic (Bare-Metal & Unikernel)

Status: 🔥🔥🔥 The whole stack is LIVE on the public internet at https://mattkelly.io/. (Apr 18, 2026.) KERN.1, KERN.3a–d, KERN.4, J.1, J.2, J.3, J.4, and DEF-A — all shipped in a single day. The root domain is now served by a 58 KiB x86_64 ELF written in Quartz, running as a QEMU microvm guest, speaking its own Ethernet/IPv4/TCP/HTTP with zero libc and zero Linux in the request path. Caddy fronts TLS via Let’s Encrypt; 16-slot per-connection state tables handle async concurrency; a browser-side 500 ms poll against /api/stats.json drives the live counter cards. 2,077 connections served pre-handoff, PMM flat at 138 pages — zero leak. This is the moment Quartz stops being “a language” and becomes “a language that can host itself all the way to your browser’s URL bar.” Next: deferred items (IRQ-driven RX, HTTP/2 port, M:N scheduler), most of which are multi-session epics. Full plan: docs/KERNEL_EPIC.md. Companion: docs/research/EFFECTS_IMPLEMENTATION_PLAN.md. Handoff for deploy session: docs/handoff/kern3-to-deploy-handoff.md.

🏁 Apr 18 2026 (night) — https://mattkelly.io/ served by the unikernel

The user visited https://mattkelly.io/ in a browser and saw the Quartz-authored dark-mode Joy-of-Quartz page render. The entire request path from TLS handshake termination onward ran code written in Quartz. User quote on the landing page:

“I just visited my own website. It is served by a completely custom stack on our own custom kernel, running our own custom web server, running our own custom web page in our own self-hosted language.”

What that actually means end-to-end:

browser
  ↓ TLS 1.3 (HTTP/2)
Caddy (:443 on Linux VPS, Let's Encrypt cert, auto-renewed)
  ↓ HTTP/1.1 cleartext on 127.0.0.1:8080
QEMU hostfwd (user-mode SLIRP, -M microvm)
  ↓ virtio-mmio v1 (legacy)
QUARTZ UNIKERNEL (58 KiB ELF, PVH boot, 64 MiB PMM, LAPIC scheduler)
  ↓ self-authored virtio-net driver (Quartz)
  ↓ self-authored Ethernet parse + IPv4 + TCP state machine (Quartz)
  ↓ 16-slot per-connection state table (Quartz)
  ↓ multi-segment TCP TX (Quartz)
  ↓ HTTP/1.1 request parser + router (Quartz)
  ↓ zero-alloc buf_write_* response builder with live kernel counters (Quartz)
  → response

No libc. No Linux kernel in the request path. No C anywhere in the network stack. The only non-Quartz bytes from the VPS-host-port-80 line onward are QEMU itself (bootloader handoff + virtio-mmio device emulation) and Caddy (TLS termination — a future KERN.5 stretch moves that inside the unikernel too).

Why this is unusual: self-hosted compilers are well-documented (rustc, Zig, Go, OCaml, …). Language-specific unikernels are documented (MirageOS in OCaml, HermitCore in Rust). What’s rare is the combination: the same language hosting the same compiler hosting the same runtime hosting the same TCP/IP stack hosting the same HTTP server hosting the same personal-site page at the same author’s root domain, with no third-party systems code on the critical path. Off the top: MirageOS gets close (OCaml-all-the-way), but MirageOS isn’t “the compiler team’s own compiler serves its own public website.” LISP-machine people in the 1980s had something philosophically similar at the OS level but not on the public internet. The set “self-hosted systems language + own kernel + own TCP/IP + own HTTP + your personal domain” is a very short list.

Pre-handoff scoreboard: 2,077 connections served, PMM flat at 138 pages, 0 leak, 16 concurrent verified.

🎯 Apr 18 2026 — The Unikernel HTTP Session

Holy shit moment. In one sitting, Quartz went from “we have a context-switching scheduler” to “our own self-hosted language is serving a full styled HTML page through its own self-authored network stack on its own unikernel.” No Linux kernel in the request path. No libc. Every byte from the NIC driver to the HTTP response body written in Quartz.

Pipeline of control, all Quartz:

virtio-mmio ISR → rx ring dequeue → Ethernet frame parse →
  IPv4 header + checksum → TCP state machine (LISTEN → SYN_RCVD →
  ESTABLISHED → CLOSE_WAIT → LAST_ACK) → HTTP/1.1 response builder →
  tx ring enqueue → virtio-mmio kick → wire

End-to-end demo:

$ curl -v http://127.0.0.1:8094/
< HTTP/1.1 200 OK
< Server: Quartz-unikernel
{739 bytes of styled HTML}

This is the moment Quartz stops being “an educational language with cute demos” and becomes a language that can credibly front-line a real, on-the-wire, kernel-resident workload authored entirely in itself. Self-hosted compiler + self-authored TCP/IP + self-authored HTTP server = no excuses, no “but for the runtime.” The only non-Quartz bytes are the QEMU bootloader handoff. Everything else is ours.

Compiler bugs surfaced during the session: PSQ-9 (extern param named from OOMs typechecker) + PSQ-10 (and/or codegen mallocs per iteration — matters in tight kernel loops). Filed, not worked around in kernel source. Four bare-metal regression tests remain green (verify_hello_{aarch64,x86_64}, qemu_boot_x86_64, qemu_http). No compiler source touched; fixpoint at 2138 functions unchanged.

Write a unikernel — kernel + the Quartz web server linked as one bare-metal binary, booting on QEMU and eventually on the user’s VPS with no Linux underneath. The unikernel is the forcing function; the real deliverable is the systems-language infrastructure buildout (SYS epics) that makes Quartz a legitimate kernel-capable systems language. Once built, hypervisor / embedded / RTOS / driver work all become downstream.

Audit finding (Apr 17): Quartz is ~70% kernel-ready. Already present: atomics (all 5 orderings), volatile load/store, @[naked], @[repr(C)], @[packed], extern "C" definitions, @[section(...)] parsing, inline asm via @c("..."), aarch64-unknown-none + x86_64-unknown-none-elf freestanding targets, and a working bare-metal hello world at tools/baremetal/hello.qz that boots on QEMU aarch64-virt.

Committed design decisions:

  • Allocator = Alloc effect (Koka alloc⟨h⟩ model), not Zig-style explicit params. Handler swap = arena swap.
  • Interrupt calling convention = type-level (extern "x86-interrupt" def ...), not attribute. Rust / Zig prior art.
  • Stdlib split = three-tier (std/core/, std/alloc/, std/std/). Rust model; industry standard.

Systems-infrastructure epics (SYS — effects-independent, can start now)

#ItemBlockingEstimateStatus
SYS.1Bare-metal completeness~1 weekDONE (2026-04-18). Atomic RMW, extern "x86-interrupt"/preserve_{all,most} cconvs, target knobs, @weak, FS/GS + TPIDR TLS, @panic_handler, field @align(N), wrmsr/rdmsr. Fifteen commits across two sessions.
SYS.2Stdlib three-tier scaffolding~2-3 daysNot started. Low priority — std/core/* + std/alloc/* modules can be reorganized any time; Vec/Map/String already work in freestanding so the hard problem (no-alloc vs. alloc boundary) is effectively solved via libc_stubs.c.
SYS.3Alloc effect proposal for effects Phase 0~0.5 dayNot started. Design note.
SYS.4Alloc effect impl + stdlib API shapeEffects Phase 2~3-5 daysBlocked on effects.
SYS.5x86_64-unknown-none parity with aarch64-virtSYS.1~2-3 daysDONE (2026-04-18). PVH ELF note + MB2 header + freestanding codegen complete. baremetal:verify_hello_x86_64 + baremetal:qemu_boot_x86_64 green.

Kernel / unikernel epics (KERN)

Top-level goal: curl https://mattkelly.io/ served by a Quartz unikernel, no Linux in the request path. Estimate: ~3-6 quartz-weeks from today. TCP/IP stack is the long pole; everything else is bounded.

#ItemBlockingEstimateStatus
KERN.1Unikernel synchronous partsSYS.1, SYS.5~1.5-2 weeksDONE (2026-04-18). Context switch + APIC + RAM probe all landed. See detail below.
KERN.2Kernel scheduler as Async effect handlerKERN.1, Effects Phase 3~1-2 weeksToy cooperative scheduler already landed. Real form waits on effects Phase 3 — but not on the critical path for KERN.3/KERN.4.
KERN.3Virtio-net + TCP/IP + HTTP server portKERN.1 finish (not KERN.2)~2-4 weeks3a-3d DONE (Apr 18 2026). 3e/3f pending. See phased breakdown below.
KERN.4VPS deployKERN.3~2-3 daysDONE (Apr 18 2026). Unikernel lives at http://195.35.36.247:8080/ on mattkelly.io VPS as a QEMU -M microvm guest under systemd. Caddy fronts TLS (auto-LE). Joy-demo dark landing page + /api/stats.json JS poll at 500 ms + /health. See detail below.
KERN.5TLS in unikernelKERN.3~2-3 weeksStretch. Port rustls-equivalent or hand-write. Cert provisioning via Let’s Encrypt.
KERN.6Static filesystem + content embeddingKERN.3~3-5 daysStretch. @[section(...)] + include_bytes!-equivalent for build-time content embed. Later: virtio-blk + minimal ext2 reader.
KERN.7Multiple concurrent connections via Async handlerKERN.2 + KERN.3~1 weekStretch. Drops out naturally once KERN.2 ships.
KERN.8SMP / multi-coreKERN.1 + APIC~2-4 weeksFar stretch. Needs MP tables / ACPI, per-CPU state, cache coherency discipline.
KERN.9Virtio-blk + ext2 / simple FSKERN.1 + SYS.4~2-4 weeksFar stretch. Alternative to KERN.6 if real persistence matters.

KERN.1 — Unikernel Synchronous Parts ✅ DONE

All three pieces landed Apr 18 2026 in commits 6bcacfba, 721fc42c, 2af80240.

PieceScopeOutcome
Context-switching schedulerswitch_to(from_slot, to_slot) asm helper + per-task 4 KiB stacks + seeded fake-suspended-frame via task_init_stack. Two tasks cooperatively yield.✅ QEMU prints A1 B1 A2 B2 A3 B3 A4 B4 A5 B5 — real per-task state survives context switches.
RAM probe + 1 GiB identity mapPMM pool bumped 1 MiB → 64 MiB in .bss (16384 pgs). 512-entry PD fill loop covers first 1 GiB with 2 MiB huge pages. 2 MiB/step write-pattern probe from 96 MiB up finds the RAM ceiling.RAM: 128 MiB, PMM pool: 64 MiB (16384 pgs) on QEMU default. Multiboot2/PVH boot-info parsing queued as follow-up.
APIC + LAPIC timerDedicated pd_apic PD at PDPT[3] identity-maps the single 2 MiB page containing 0xFEE00000. apic_init enables globally via MSR 0x1B bit 11, configures Spurious Vector, LVT Timer (periodic, vector 0x20), Divide (/16), Initial Count (10M). pic_disable masks both 8259As. timer_isr EOIs via MMIO write. PIT + serial RX ISR deleted.APIC enabled @ 0xFEE00000 + sched done (ticks=3) after a hlt-park loop proves the LAPIC timer actually fires.

Compiler bug surfaced + filed: PSQ-9 — naming an extern parameter from OOM-loops the type checker (worked around with from_slot).

KERN.3 — Virtio-net + TCP/IP + HTTP (phased)

The long pole. Each phase has a clean standalone deliverable.

PhaseScopeDeliverableEst
3a: virtio-net driverMMIO device discovery + feature negotiation + RX/TX queue setup + polling RX. Legacy virtio-mmio v1 (QEMU -M microvm offers no v2). Broadcast ARP request on TX elicits a reply from SLIRP which RX hex-dumps.DONE Apr 18 2026 — full ARP roundtrip through Quartz-authored virtio-net. Commits 3a.1–3a.3 (f277509b, 0f236ea6, 2dec6567).3-5 days
3b: ARP + IPv4 + ICMP pingEthernet / IPv4 / ICMP parsing + outbound checksum (RFC 1071) + generalized RX/TX (multi-frame). Host-side ping 10.0.2.15 still needs an IOAPIC / tap network (SLIRP is NAT-only).DONE Apr 18 2026 — guest-initiated ping 10.0.2.2 roundtrip through SLIRP. Commit 9f31e987.2-4 days
3c: TCP state machineSingle-connection RFC 793 subset — LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED, pseudo-header checksum, proper FIN accounting. No retransmits / TIME_WAIT (echo server doesn’t need them).DONE Apr 18 2026nc 127.0.0.1 8090 via SLIRP hostfwd round-trips cleanly. Commit 1a2e38d4.5-10 days
3d: HTTP/1.1 serverFixed-response server (doesn’t parse request path yet — any request gets the same landing page). Status line + headers + HTML body. FIN after response per Connection: close.DONE Apr 18 2026curl http://127.0.0.1:8094/ returns a styled HTML5 page. Commit 944cc774.1-2 days
3e: Embed “joy of Quartz” + marketing contentStatic HTML + CSS + (optional) JS embedded via @[section(...)]. Build-time asset bundling.Page looks good in a real browser.1-2 days
3f: Port existing web serverIf the hosted HTTP/2 server’s socket API abstracts over reasonable enough, swap libc syscalls for the kernel’s socket shim. Otherwise, treat 3d as the new server and upstream features from there.Same code path in kernel as in userspace.1-5 days

KERN.4 — VPS Deploy ✅ DONE (Apr 18 2026)

Shipped. Unikernel runs on mattkelly.io (195.35.36.247) as a QEMU -M microvm guest under a systemd unit. Five commits across the deploy session (f287b68c through 2d30eed4):

PhaseShippedNotes
Deploybaremetal:build_elf Quake task + scp pipeline + systemd unit + Caddy frontingVPS was already running Caddy with reverse_proxy localhost:8080 in the Caddyfile — unikernel’s hostfwd=tcp::8080-:80 plugs straight in. Port 80/443 stay on Caddy (auto-LE cert on first hit).
J.1 — Live telemetryLanding page embeds scheduler ticks + connections served + PMM pages used/total + virtio-net MAC via zero-alloc buf_write_* helpers. Caught + fixed two per-request PMM leaks (tcp_send + response buffer); serve loop converted from “bounded + exit on first conn” to perpetual.PMM stays flat across sequential requests.
J.2 — RoutingGET / (landing), GET /api/stats.json (counters as JSON + CORS), GET /health (text “ok”), everything else → 404. http_request_matches is deliberately exact-match + space-terminated (distinguishes / from every path).
J.3 — Joy-demo + multi-seg TCPDark-mode grid of 4 live counter cards, inline fetch() loop polling /api/stats.json every 500 ms. TCP ESTAB handler now splits responses > 1400 bytes into multiple segments (PSH on last); unblocked a 3391-byte styled page.
DEF-A — Multi-connection TCP16-slot per-connection table in one PMM page (state / rcv_nxt / snd_nxt / snd_una / remote_port / peer_mac / peer_ip). tcp_handle_frame looks up slot by (peer_ip, src_port), dispatches on per-slot state. tcp_send takes slot as first param. tcp_active_conns exposed via /api/stats.json + landing page “Active” card.16 concurrent curls over public internet all returned 200, connections_active peaked at 7 simultaneous.
HTTPS flipUser pointed mattkelly.io root at the VPS. Caddy config: single block mattkelly.io { reverse_proxy localhost:8080 } + HSTS header. Let’s Encrypt cert landed in <10 s. 308 redirect from http:// to https://. HTTP/2 upstream-to-browser; HTTP/3 advertised via alt-svc.https://mattkelly.io/ is live. 2,077 connections served pre-handoff with PMM flat at 138 pages — zero leak under real traffic.

Strategy actually used (vs. original plan): Caddy replaced the speculative nginx+certbot path — it was already on the VPS doing auto-LE, so we rode it. No DNS changes needed (mattkelly.io root still points to user’s personal fly.io site; unikernel reachable by IP for now). Subdomain selection pending user decision.

Live demo (public internet):

$ curl http://195.35.36.247:8080/            # styled HTML, 3391 bytes
$ curl http://195.35.36.247:8080/api/stats.json
{"version":1,"ticks":1234,"connections_served":42,"pmm_pages_used":137,...}
$ curl http://195.35.36.247:8080/health       # "ok"

Request path: public IP → port 8080 on Linux VPS → SLIRP hostfwd → QEMU microvm guest → Quartz-authored virtio-net driver → Ethernet parse → IPv4/TCP state machine → HTTP router → response builder → wire. Zero Linux bytes in the request path.

DEF-A shipped in same session (d8a84d83). 16-slot per-connection TCP table in one PMM page. tcp_handle_frame dispatches on per-slot state; tcp_send takes slot as first param. Active-count exposed via /api/stats.json + landing page. 16 concurrent curls on production VPS all returned 200 OK, connections_active peaked at 7 simultaneous.

Remaining menu (see handoff doc):

  • HTTPS flip — pending user DNS decision. Add A record for a subdomain → VPS IP, append Caddyfile block, systemctl reload caddy. Caddy grabs LE cert on first HTTPS hit. ~5-15 min ops.
  • DEF-B — IOAPIC + IRQ-driven RX (replaces rx_wait polling print-hack). ~5-8 quartz-hours. HIGH brick risk — remote recovery requires redeploy.
  • DEF-C — Fix PSQ-10 (and/or malloc per eval) in compiler. Requires quake guard + fix-specific golden. ~3-5 quartz-hours.
  • DEF-D — HTTP/2 (h2c) in unikernel — multi-session epic. KERN.3f.
  • DEF-E — M:N scheduler in unikernel — multi-session epic. KERN.2 + KERN.7. Blocked on effects Phase 3.

KERN.5 — TLS in unikernel (stretch)

Options (ranked by practicality):

  1. Hand-write minimal TLS 1.3 in Quartz. ~3000 LoC. Proves systems-language chops.
  2. Port rustls to Quartz. Translate the state machines; reuse the same cryptographic primitives.
  3. Link a C TLS lib (BearSSL, mbedTLS) via FFI. Fastest; dodges the “all Quartz” claim.

Needs: Let’s Encrypt ACME client (~500 LoC), cert storage (tie into KERN.6), SNI routing.

KERN.6 — Static FS + content embedding (stretch / optional)

Two paths:

  • Cheap: @[section(".content")] attribute + build-time asset injection. Embedded content is just bytes in the ELF. ~50 LoC kernel, ~100 LoC Quake task.
  • Real: virtio-blk driver + minimal ext2 / FAT32 reader. ~2 weeks. Only worth it if we need runtime mutability (user uploads, logs persisted across reboots).

For the “joy of Quartz” site, cheap is almost certainly right.

KERN.7+ — SMP / effects-backed concurrency / etc. (far stretch)

All well-scoped but not on the MVP path. Sequence after KERN.4 lands.

Joy-of-Quartz Unikernel Epic — ⏸ PAUSED at clean phase boundary (Apr 18, 2026). All of KERN.4 + J.1–4 + DEF-A shipped; https://mattkelly.io/ served by the unikernel end-to-end. Four-phase plan (K / S / H / D, ~6–10 quartz-weeks across 10–15 sessions) preserved verbatim in docs/handoff/joy-of-quartz-unikernel-epic.md; resumes at Phase K.1 (slab allocator on top of the bump PMM). Endgame unchanged: live htop-style dashboard + “spawn 10,000 tasks” button on mattkelly.io demonstrating Elixir-level concurrency on a ~60 KB ELF.

Next step (compiler track): Operation Piezoelectric Effects — Phase 1 Milestone A IN PROGRESS (Apr 19, 2026) (commit tag [piezo]). Phase 0 design closed Apr 18; lexer + parser surface syntax landed in Milestone A. What shipped: TOK_CAN/TOK_EFFECT/TOK_WITH/TOK_REIFY wired as keywords; TOK_HANDLE/TOK_THROW/TOK_RESUME reserved as token constants only; ps_parse_effect_decl accepts effect Name<T> ... end (discards to top-level no-op); ps_parse_can_row accepts can Row function-sig suffix (parsed + discarded); ps_parse_handler_block accepts with H do -> body end (handler discarded, body evaluated eagerly); ps_parse_reify accepts reify { expr }; ps_parse_try_catch extended to emit ast_try_expr when no catch follows (new propagation-prefix form); $try macro + QZ0509 deleted; 11 std/iter.qz + 4 spec + 1 example $try(...) call sites migrated to try .... 5 new QSpec parse specs filed.

Deferred inside Milestone A (not blockers; file follow-up milestones):

  • Wire handle/throw/resume as hard keywords. Blocks on: handle has ~45 identifier-collision sites across self-hosted + std + specs (struct fields in Arena, Task<T>; locals in pipe_open callers; fn params in ast_await, _wasm_find_block_idx). throw/resume need to survive as method names inside effect bodies — requires either contextual-keyword handling in ps_parse_function or a contextless-name relaxation in effect-op declarations. Pick when Milestone C/D wires operation-kind bodies.
  • can Row currently parsed + discarded. Milestone B wires it into TcRegistry.rows for effect-row inference.
  • effect Name<T> ... end currently emits ast_import("", "") at top level and discards all method signatures. Milestone B introduces NODE_EFFECT_DECL + a parallel side-table on TcRegistry for effect ops.
  • with H do -> body end evaluates body as a plain block; handler is parsed-and-discarded. Milestone D introduces NODE_WITH with evidence-chain linkage.
  • reify { expr } returns the inner expression directly. Milestone D wraps with a prelude handler that converts throws to Err(e) and returns to Ok(v).

Phase 1 remaining (per docs/research/EFFECTS_LLVM_COMPILATION_MEMO.md § 7): Milestone B (type-system rows + inference, ~2 quartz-days), Milestone C (MIR effect primitives + kind classification, ~1 quartz-day), Milestone D (codegen evidence threading + handler structs, ~1.5 quartz-days), Milestones E/F/G/H (stdlib pilots, error messages, fixpoint, docs). Phase 1 total estimate: 5-7 quartz-days · 7-10 sessions.


Demo Programs

Purpose: 15 impressive programs that prove Quartz is a world-class systems language. Not toy examples — real tools that only a systems language could build well.

What we’re proving: Speed. Portability. Syntactic sweetness. Practical power.

The compiler itself is Demo #0: 20K+ lines, self-hosted, ring buffers, arena allocators, e-graph optimization, 3,239 tests, fixpoint-verified. Everything below builds on that foundation.

Planning Process

Every demo program follows this process before a single line of code is written.

  1. Research — Study what the community and world loves best. Find out what the big players are already doing. Identify the most beloved, simple, unified APIs. Document findings in a research brief.
  2. Design Interview — Iterative planning session — interview until consensus. Present API options with trade-offs. Compare against best-in-class implementations. Synthesize the best of all approaches into our design.
  3. Approval — Write implementation plan with full API surface. Get explicit user approval before proceeding. No implementation begins without a signed-off plan.
  4. Implementation — Build to the plan, following prime directives. Any gap discovered during implementation → fill it or backlog it. Fixpoint verification after stdlib changes.
  5. Showcase — Write a showcase section for the README. Benchmark against comparable implementations in other languages. Document what the demo proves about Quartz.

Dependency Graph

graph TD
    HTTP["1. qz-http<br/>HTTP Server"] --> JSON["2. qz-json<br/>JSON Parser"]
    HTTP --> ARG["3. qz-arg<br/>CLI Args"]
    JSON --> DB["11. qz-db<br/>Key-Value Store"]
    ARG --> GREP["9. qz-grep<br/>File Search"]
    ARG --> FMT["8. qz-fmt<br/>Code Formatter"]
    ARG --> TEST["10. qz-test<br/>Test Runner"]
    ARG --> COMPRESS["7. qz-compress<br/>Compressor"]
    HTTP --> WASM["14. qz-wasm<br/>WASM Backend"]
    WASM --> RENDER["12. qz-render<br/>3D Renderer"]
    RENDER --> CANVAS["★ Canvas Site<br/>WASM + 3D"]

    HASH["5. qz-hash<br/>SHA-256"] -.-> COMPRESS
    ALLOC["4. qz-alloc<br/>Allocator Bench"]
    REGEX["6. qz-regex<br/>Regex Engine"] -.-> GREP
    LISP["13. qz-lisp<br/>Lisp Interpreter"]
    SHELL["15. qz-shell<br/>Interactive Shell"]

    style HTTP fill:#ff6b6b,color:#fff
    style JSON fill:#ff6b6b,color:#fff
    style ARG fill:#ff6b6b,color:#fff
    style CANVAS fill:#ffd93d,color:#000

Compiler Prerequisites for Demo Programs

These compiler gaps were discovered during qz-http implementation. Most are now resolved.

A. extern "C" def Type Checker Support ✅ RESOLVED

extern "C" def declarations parse correctly and codegen emits proper LLVM declare statements, but the type checker doesn’t register them as callable functions.

Already fully implemented. typecheck_walk.qz:169 registers NODE_EXTERN_FN in Phase 2 via tc_register_extern_fn_signature() (typecheck.qz:3584-3624). Full pipeline working: parser → AST → resolver (tag=10, dedup) → type checker → MIR (skip) → codegen. 25+ tests across extern_def_spec.qz, ffi_spec.qz, cimport_spec.qz. Real usage in std/ffi/tls.qz (50+ OpenSSL bindings), self-hosted/shared/repl.qz. This roadmap entry was a fossil from an earlier codebase state — the fix was part of the April 2026 source fossil resolution.

B. Socket Intrinsics (Nice-to-Have)

Add POSIX socket functions (socket, bind, listen, accept, tcp_recv, tcp_send, close_fd, htons, setsockopt) as compiler intrinsics, following the qz_malloc/qz_free pattern in codegen.qz.

Note: With A resolved, this is optional — extern "C" def declarations work and can be used instead. Intrinsics provide better DX (no boilerplate declarations) but are not a blocker.

C. Hex Literal Parser Support 🟡 DISCOVERED

The self-hosted parser does not support hex literals (0xFFFF, 0x0004), producing Parse error: Expected ')'. Decimal workarounds exist but hex is important for bit manipulation constants.

Tier 1: Foundation

1. qz-http — HTTP/1.1 + HTTP/2 Server ✅ SHIPPED — deployed on mattkelly.io

What it proves: Quartz can build real networked services from raw TCP.

Implementation: std/net/http_server.qz (3821 LOC, HTTP/1.1 router + middleware), std/net/http2.qz (765 LOC, HPACK + Huffman). Demo at examples/qz-http/main.qz exercises the full router/middleware/route_param API. HTTP/2 variant deployed on mattkelly.io VPS (2 vCPU / 8 GB / x86_64 linux-gnu). See docs/PLATFORMS.md for cross-compilation notes.

AspectDetail
ScopeMinimal, zero-dependency HTTP/1.1 server with routing, middleware, static file serving
API StyleGo-style single handler: Fn(Request): Response
MiddlewareRack-style composition via pipeline: handler | with_logging | with_cors
ConcurrencyThread-per-connection (v1), event-loop upgrade path (v2)
BenchmarkHello-world req/s vs Go net/http, Node.js http, Python http.server
Depends onExisting networking stack (std/net/tcp.qz), string handling
EnablesDemo platform for all other projects, WASM canvas site, dogfooding vision

Core API:

# Minimal server — trailing block syntax
http_serve(8080) do req: Request ->
  Response { status: 200, body: "Hello, Quartz!" }
end

# With router
var r = router_new()
r.get("/") do req -> text_response("Hello!") end
r.get("/users/:id") do req -> json_response(get_user(req.param("id"))) end

# Middleware composition via pipeline
http_serve(8080, r.handler() | with_logging | with_cors | with_auth)

Why this is a flex: Pipeline middleware is better than Go (no pipelines), better than Rack (no class-per-middleware), and better than Express (explicit app.use() ordering). One function type, composed left-to-right.

2. qz-json — JSON Parser/Serializer

What it proves: Quartz’s enums, pattern matching, and recursive data structures are production-ready.

AspectDetail
ScopeFull JSON spec (RFC 8259) — parse, serialize, pretty-print, path queries
ResearchRust serde_json, Go encoding/json, simdjson (for perf ideas), jq (for query API)
API targetsjson_parse(str) → Result<JsonValue, JsonError>, json_stringify(val), val["key"] access
BenchmarkParse speed vs Go encoding/json, Python json on a 1MB file
Depends onString handling, Result<T, E>, enums with payloads
EnablesConfig files for other demos, HTTP request/response bodies, qz-db storage format

NOTE: We already have std/json.qz and std/json/parser.qz — this builds on that foundation, hardening it to full spec compliance and benchmarkable performance.

3. qz-arg — CLI Argument Parser

What it proves: Quartz’s struct ergonomics and builder patterns make declarative APIs elegant.

AspectDetail
ScopeDeclarative CLI parsing with subcommands, flags, help generation, error messages
ResearchRust clap, Go cobra/flag, Python argparse/click, Zig std.process
API targetsBuilder pattern, auto-generated --help, type-safe flag values
Depends onString handling, Vec<String>, struct builders
EnablesEvery subsequent demo uses this for CLI interface

NOTE: We already have std/argparse.qz (TS.10) — this either hardens it or replaces it with a world-class implementation modeled after the best in class.

Tier 2: Systems Flexes

4. qz-alloc — Custom Allocator Benchmark Suite

What it proves: Quartz has language-level memory control that garbage-collected languages can’t touch.

AspectDetail
ScopeArena vs pool vs slab vs malloc benchmarks with visualization
Researchjemalloc, mimalloc, Zig allocator interface, Rust GlobalAlloc
BenchmarkAllocation throughput, fragmentation, cache behavior across patterns
Depends onExisting arena/pool/slab allocators, timing intrinsics

Why this is a flex: We already HAVE arenas, pools, and slab allocators implemented in Quartz. Nobody else has this at the language level in a 47-day-old language.

5. qz-hash — SHA-256 / BLAKE3

What it proves: Quartz’s bitwise ops and tight loops compete with C.

AspectDetail
ScopePure Quartz SHA-256 and BLAKE3. Hash files from CLI, streaming API
ResearchReference C implementations, Rust sha2/blake3 crates
API targetssha256(data) → String, sha256_file(path) → String, streaming Hasher struct
BenchmarkMB/s vs C openssl, Rust sha2. Within 2x of C = headline material
Depends onFixed-width integers (U32, U64), bitwise ops, byte-level buffer access

6. qz-regex — Regex Engine

What it proves: Quartz can build complex state machines with clean, readable code.

AspectDetail
ScopeNFA-based regex matcher via Thompson construction. Character classes, quantifiers, groups
ResearchRuss Cox’s RE2 articles, Thompson NFA, Pike VM, Rust regex crate architecture
API targetsregex_compile(pattern) → Regex, regex.match(str) → Option<Match>
BenchmarkPathological cases (where backtracking engines explode), basic throughput
Depends onEnums, dynamic arrays, state machine patterns

7. qz-compress — LZ77/Deflate Compressor

What it proves: Byte-level manipulation, ring buffers, and performance-critical code.

AspectDetail
ScopeLZ77 sliding window compression + Huffman coding. Compress/decompress files
ResearchRFC 1951 (DEFLATE), zlib, LZ4, zstd (for API ideas)
API targetscompress(data) → Vec<U8>, decompress(data) → Vec<U8>, file CLI
BenchmarkCompression ratio and speed vs gzip, LZ4
Depends onRing buffers (already have), bit-level I/O, fixed-width integers

Why this is a flex: We already have ring buffers in the compiler. Now we use them for what they were designed for.

Tier 3: Developer Tools

8. qz-fmt — Code Formatter

What it proves: Quartz is mature enough to have its own tooling ecosystem.

AspectDetail
ScopeAuto-format Quartz source code. Parse → AST → pretty-print
Researchgofmt, rustfmt, Prettier, Black (Python). Philosophy: one true style, no config
API targetsquartz fmt file.qz, quartz fmt --check, stdin/stdout mode
DogfoodFormat the compiler source itself. Diff should be zero after initial formatting
Depends onParser (reuse compiler’s parser), AST traversal

What it proves: Quartz can build the kind of tool ripgrep is — fast, practical, real.

AspectDetail
ScopeRecursive grep with regex, colored output, .gitignore awareness, file type filters
Researchripgrep (architecture blog posts), grep, ag (The Silver Searcher)
API targetsqz-grep PATTERN [PATH], --ignore, --type, --count, colored output
BenchmarkSearch speed vs grep on a large codebase
Depends onqz-regex (or built-in regex), filesystem traversal, qz-arg

10. qz-test — Test Runner Framework

What it proves: Quartz’s macro system and reflection capabilities enable meta-programming.

AspectDetail
ScopeDiscover test functions, run them, report pass/fail with colors, assertion helpers
ResearchGo testing, Rust #[test], Jest, pytest, Zig test blocks
API targets@test attribute on functions, assert_eq(a, b), assert(cond), CLI runner
DogfoodRun on the compiler’s own test utilities
Depends onMacro system (TS.20-21), qz-arg, colored terminal output

Tier 4: Jaw-Droppers

11. qz-db — Embedded Key-Value Store

What it proves: Quartz can build infrastructure — not just tools that use infrastructure.

AspectDetail
ScopeB-tree or LSM-tree backed KV store. put, get, delete, scan. File-backed with WAL
ResearchLevelDB, RocksDB (LSM), SQLite (B-tree), BoltDB (Go), sled (Rust)
API targetsdb_open(path) → DB, db.put(key, value), db.get(key) → Option<String>
BenchmarkOps/sec vs SQLite, BoltDB on sequential and random workloads
Depends onFile I/O, qz-json (for metadata), binary serialization

12. qz-render — Software Rasterizer / Ray Tracer

What it proves: Quartz handles compute-heavy math, SIMD, and produces visual output.

AspectDetail
ScopeRay tracer outputting PPM/BMP images. Spheres, planes, reflections, shadows
Research”Ray Tracing in One Weekend” (Peter Shirley), PBRT, smallpt
API targetsScene description → rendered image. CLI: qz-render scene.json -o output.ppm
BenchmarkRender time vs C ray tracer. SIMD acceleration via F32x4
Extends toWASM + Canvas: Compile to WASM, render directly to HTML5 Canvas in browser. Real-time 3D in the browser, rendered by Quartz — not the DOM, not WebGL shaders, but our own rasterizer spitting pixels into a canvas. Like a game engine.
Depends onF32/F64 math, SIMD (F32x4), qz-json (scene files)

THIS IS THE CENTERPIECE OF THE WASM VISION. The Quartz marketing site could ultimately be rendered by this engine running in the browser via WebAssembly — no HTML, no CSS, no DOM. Just Quartz code drawing pixels to a canvas. The most unhinged flex possible.

13. qz-lisp — Lisp Interpreter

What it proves: Meta-circular flex — a language written in a language that wrote itself.

AspectDetail
ScopeS-expression parser → tree-walking interpreter. Closures, tail-call optimization, GC
ResearchSICP, MAL (Make-A-Lisp), Peter Norvig’s lis.py
API targetsREPL mode + file execution. (define fib (lambda (n) ...))
Depends onString parsing, recursive data structures, enums

14. qz-wasm — WebAssembly Compiler Backend

What it proves: Quartz can target the browser — compile code → WASM → run in browser.

AspectDetail
ScopeCompile a simple expression language (or subset of Quartz) → .wasm binary
ResearchWASM spec, wat2wasm, Binaryen, AssemblyScript compiler
API targetsqz-wasm compile input.qz -o output.wasm
Extends toCombined with qz-http, serve a web page that runs Quartz-compiled WASM
Depends onBinary serialization, WASM binary format knowledge

15. qz-shell — Interactive Shell / REPL

What it proves: Quartz is a real systems language — you can implement an OS-level tool.

AspectDetail
ScopeLine editing, history, pipes, job control. Parse commands, fork/exec, wait
Researchbash source, fish shell, Oil shell, Rust nushell
API targetsInteractive REPL with prompt, history (up/down), tab completion
Depends onFFI (fork, exec, waitpid, pipe, dup2), terminal I/O, qz-arg

Implementation Order

PhaseProjectsRationale
Sprint 1qz-http, qz-jsonFoundation — demo platform + data format. Enables everything
Sprint 2qz-arg, qz-hashCLI infrastructure + first systems flex with benchmarks
Sprint 3qz-alloc, qz-fmtMemory flex + first dev tool (dogfood on compiler)
Sprint 4qz-regex, qz-grepState machines + practical tool with benchmarks
Sprint 5qz-test, qz-compressMeta-programming + byte-level manipulation
Sprint 6qz-render, qz-lispJaw-dropper demos — visual output + meta-circular
Sprint 7qz-db, qz-wasm, qz-shellInfrastructure + browser target + OS-level tool
Sprint 8Canvas SiteWASM renderer in browser — the ultimate demo

Demo Success Metrics

MetricTarget
All 15 demos compile and run
Each demo has benchmarks vs comparable implNumbers published
qz-hash within 2x of CHeadline benchmark
qz-http handles 10K+ req/sCompetitive with Go
qz-render produces 1080p imageVisual proof
WASM canvas renders in browserThe ultimate flex
README showcases all demosFirst impression
Style guide reflects real idiomsAuthentic, not prescriptive

Known Bugs

BugImpactStatus
UFCS module name collisionvalue.to_s() breaks if module named value✅ FIXED (Apr 7) — Resolver checks param names before module rewrite. 3 tests. Follow-up: full scope tracking (Tier 3).
UTF-8 double-encodingAll Unicode symbols display as mojibake✅ FIXED (Apr 7) — New sb_append_byte intrinsic + 6 lexer paths fixed. 68→0 corrupted strings.
Multi-line function signaturesParser error✅ FIXED (Apr 7) — ps_skip_newlines() after ), :. 6 tests.
Circular import detectionNo error✅ FIXED — QZ0550, resolver cycle detection.
MIR heap corruptionCrash (SIGABRT)✅ FIXED — Explicit init_intrinsic_registry() before pipeline (commit 1d23b1dd).
Async cross-suspend SSA dominance"prefix " + "#{await h}" → LLVM rejects IR with “Instruction does not dominate all uses”✅ FIXED (Apr 11) — 3440903f binary-op handler spills LHS to generator frame slot when RHS contains await or suspending intrinsic (recv, sched_sleep, etc).
MIR_AWAIT double-use UAFwhile count < await h segfaults on second iteration: pthread_join + free + re-read = UAF✅ FIXED (Apr 11) — e2f829fd writes state=-1 after join, drops free; subsequent awaits dispatch through cached-future path.
Closure capture walker skips async nodesx -> x + await h emitted load ptr %h against undefined alloca✅ FIXED (Apr 11) — 73a14d56 NODE_AWAIT / NODE_GO / NODE_ASYNC_CALL cases walk left child so free vars are captured.
try_send / channel_close null iomap crashtry_send on a ready channel from sync code crashed: @__qz_sched[12] (iomap base) is zero when the scheduler never initialized, but codegen indexed it unconditionally✅ FIXED (Apr 11) — Added null guard on .iomap.i in both try_send and channel_close codegen. Unlocked concurrency_spec (57 tests) and the select-send smoke path.
iomap dangling pointer after sched_shutdownsched_shutdown freed the iomap but left @__qz_sched[12] pointing at freed memory; channel_close afterwards chased the dangling pointer✅ FIXED (Apr 11) — sched_shutdown now stores 0 to slot 12 after freeing. Unlocked channel_result_spec (6 tests).
completion_map/task_locals dangling after sched_shutdownpost-shutdown spawn + await hung forever: @__qz_completion_map[3] (the mutex proxy for “scheduler active”) was freed but not nulled, so the await walked into the dangling completion-pipe path on a dead scheduler✅ FIXED (Apr 11) — 73dd0479 zeros slots 0–3 of both @__qz_completion_map and @__qz_task_locals after their respective frees in __qz_sched_shutdown. Regression test rs_spawn_after_shutdown in async_spill_regression_spec.
Channel capacity silently rounded to next power of 2channel_new(10) actually held 16 items; channel_pressure(ch) returned 31 (5/16) instead of 50 (5/10); channel_remaining reported 11 instead of 5✅ FIXED (Apr 11) — 020e8194 removes the bit-smearing round-up in channel_new and switches ring-buffer indexing from and count, (cap-1) to urem count, cap at all 8 send/recv/try_send/try_recv sites. Unlocked backpressure_spec (7/7 passing, was 1/7).

| Int-key map iteration (3 chained bugs) | Silent wrong results for for k in map over Map<Int,V> | ✅ FIXED (Apr 12) — 524abbfc: (1) TC ptype→base normalization in NODE_FOR.extra, (2) MIR map_key_types tracking threads elem_type to codegen, (3) intmap_keys/values rewritten to use occupancy bitmap instead of delegating to hashmap_keys which treated key values 0/1 as sentinels. | | For-in loop variable typing | Loop var always typed as Int, even for Map<String,V> keys | ✅ FIXED (Apr 12) — 0b4e6fc5: TC binds loop var with container’s key/element type derived from ptype. | | Float map keys accepted by tc_type_is_hashable | F64/F32 keys allowed despite NaN!=NaN and -0.0==+0.0 breaking hash invariants | ✅ FIXED (Apr 12) — 85152035: removed Float types from hashable list; tc_struct_is_hashable now walks field types. | | MIR_INDEX float zext codegen | v[i] on Vec produced invalid zext float to i64 | ✅ FIXED (Apr 12) — 8435d663: 4 MIR_INDEX* handlers now branch on is_float (fpext+bitcast for read, bitcast+fptrunc for write). | | FFI declaration dedup missed define | Functions both defined and declared as extern emitted duplicate symbols, rejected by LLVM | ✅ FIXED (Apr 12) — e8efa41a: dedup scans for both declare and define in context window. | | Request.params/query/context typed as Int | UFCS form.get() dispatched to wrong function, SIGSEGV | ✅ FIXED (Apr 12) — ba4e4bd2: retyped to Map<String, String>. | | Unqualified variant payload types wrong | Ok(data) in match resolved against first-registered enum (builtin Result<T,E>) instead of the subject’s actual type | ✅ FIXED (Apr 12) — 1c68b67c: match handler derives enum name from subject expression’s function return annotation. | | Linter SIGSEGV on all lint invocations | quartz lint crashed on every file — 6 spec files, 58 tests blocked | ✅ FIXED (Apr 12) — 885f80c9: Three .size field accesses on Int-typed Vec handles read capacity instead of size (offset 0 vs 8). Fixed to vec_size(). Also fixed parser consuming significant newline after ) which silently disabled T2 lint rules. | | Extern declaration dedup false positive | extern "C" def abs(...) missing from IR when test string constants contained declare i64 @abs(i64) | ✅ FIXED (Apr 12) — 646c4c07: Dedup searched 80 bytes before @name( for “declare”/“define”, false-positived on string constants. Fix: find line start, check line begins with declare/define. | | MIR guard node check off-by-one | arm_guard_node > 0 would skip guard at AST handle 0 | ✅ FIXED (Apr 12) — 4c1ed05d: Changed to >= 0. |

Pre-existing spec failures still open (updated Apr 16 evening):

SpecModeImpactInvestigation
json_spec.qzSIGSEGV (exit 139)Runtime null-deref in JSON parser✅ FIXED (Apr 16) — Sprint 1: Object(fields: Int) payload erased the Map<String, JsonValue> type at destructure. keys[i] from map_keys(Int-typed receiver) typed as Int → map_get(fields, key) dispatched to intmap_get on a hashmap → SIGSEGV. Stdlib workaround: helper _json_key_to_string casts the i64 slot back to String. The “right” fix (typed enum payloads Vec<JsonValue>/Map<String, JsonValue>) is blocked on OPEN-UFCS-HASH-SENSITIVITY (see new entry below). 18/18 json_spec passes.
separate_compilation_spec.qz5/6 FAIL:linkSeparate compilation linker gaps — missing symbolsInfrastructure gap, not a quick fix.
file_helpers_spec.qz2 tests failFile handle opsFIXED Apr 19, 2026 (UFCS-EXTERN-SHADOW). Not a runtime bug and not a typecheck bug — the typechecker correctly rewrote fh.close()FileHandle$close(fh), and MIR preserved it. The collision was in codegen: cg_extern_var_index (codegen_util.qz:424-443) has a $-strip fallback for cross-module extern calls (ffi_mod$absabs) that was matching FileHandle$closeclose → the POSIX extern "C" def close(fd: CInt) from ffi/socket.qz. Prior fix (5abfe9b5) added a __-prefix guard for compiler-generated names; this extends that with a cg_find_function(state, name) != 0 guard so any fully-prefixed user function (including all impl methods) shadows the fallback. Three-line fix; the whole class of impl-method-shadowed-by-libc-extern bugs dies with it. 8/8 file_helpers_spec green, fixpoint verified at 2142 functions.
http2_frame_spec.qzLink errorMissing library dependencyFIXED Apr 19, 2026 (d2680e3b). Not a missing library — the QSpec runner’s OpenSSL auto-detection only matched three hardcoded symbol names (SSL_CTX_new, SSL_connect, OPENSSL_init_ssl) and missed ERR_clear_error / SSL_read / etc. that HTTP/2 uses. Broadened to prefix checks (@SSL_, @ERR_, @OPENSSL_) across Quakefile qspec/qspec_file runners and std/qspec/subprocess.qz. 11/11 green.
route_groups_spec.qzLink errorSame — needs HTTP module link setupFIXED Apr 19, 2026 (d2680e3b). Same root cause as http2_frame_spec. 6/6 green.
semaphore_spec.qzTimeout in PTYScheduler-based test hangs in Claude Code PTY contextWorks in native terminal (needs verification).
tls_async_spec.qz0/6 passingTLS networking integration (depends on OpenSSL + real sockets); not a compiler bugPre-existing — reproduces on pre-fix golden
actor_spec.qzLinux rebuild only__Future_<pointer>$name non-determinism truncates IR when compiler is rebuilt on LinuxHappens only when rebuilding the compiler on Linux; the 38c88277-era golden is deterministic and passes 21/21. See docs/archive/HANDOFF_SESSION_4.md §“What I learned” point 4.
Incremental cache Tier 2 skip-without-fragment (a.k.a. %push in socket$pipe_create)~~Warm-cache direct `quartzllcproduces invalid IR with undefined%push` local~~FIXED Apr 19 2026. Three-part fix in self-hosted/quartz.qz + self-hosted/shared/dep_graph.qz: (1) replace “any-fragment-exists” coarse gate with per-module fragment check — unchanged modules whose short-name .ll fragment is missing are added to changed_modules before depgraph_invalidate_with_cutoff; (2) normalize recompile_set to short names after invalidation so downstream consumers line up with function-mangling / fragment-filename convention; (3) TC-skip gate now resolves the owning module via depgraph_name_for_ast_store(entry.ast_store) rather than parsing the function-name prefix, correctly handling impl methods whose prefix is a struct name (Progress$spinner_only) rather than a module name. 4-compile repro reports 0 bad %push loads across 6 consecutive rounds; cached vs --no-cache compiled binaries produce identical test output; fixpoint verified at 2142 functions. See docs/handoff/pipe-create-push-cache-bug.md for the diagnosis writeup. Follow-up: audit the 10+ --no-cache sites in Quakefile.qz now that the cache path is sound.

Apr 13 PM: triage tail audit — most entries below were stale.

The “Apr 12 triage tail” section that follows lists 6 single-test failures and 4 systemic issues. An audit on Apr 13 (commit forthcoming) found that 9 of the 10 entries were already fixed by intervening compiler work, and one was actually a typo in the spec source. Specifically:

  • Result$unwrap extracts wrong field (line 475): ✅ FIXED — r.unwrap() on Result::Ok(55) correctly returns 55. Verified directly. option_narrowing_spec 7/7.
  • Parser defer count += 1 (line 476): ✅ FIXED — cross_defer_safety_spec 8/8 with QUARTZ_COMPILER set.
  • Parser extern "C" def with body (line 477): ✅ FIXED — ffi_spec 15/15, extern_def_spec 20/20.
  • never_type_spec “never type in let binding” (line 483): ✅ FIXED — 11/11.
  • stress_type_system_spec “enum variant with named payload” (line 484): ✅ FIXED — 43/43.
  • error_codes_spec “QZ1215 triple-quoted indentation” (line 485): ✅ FIXED — 35/35.
  • arenas_advanced_spec “Arena$drop QZ0551” (line 486): ✅ FIXED — 28/28.
  • stress_pattern_matching_spec “generic unwrap function” (line 488): ✅ FIXED.
  • stress_pattern_matching_spec “match on Option” (line 487): ✅ FIXED via test typo correction — the test source had MyOption<T> with variants Some/None but the match arms referenced MySome/MyNone. The compiler emitted bad IR (which llc rejected) instead of producing a typecheck error. The typo is now consistent on both sides; the underlying compiler bug (unknown enum variants in match should be QZ02xx, not bad IR) is real but separate and not yet filed.

The .size field access on Int-typed Vec handles audit (line 474) is the only systemic issue I did NOT verify in this audit. If new sites have crept in since Apr 12, the bug is still present. Worth a fresh grep before claiming closed.

Remaining spec failures after Apr 12 triage sprint

After the Apr 12 triage that fixed ~165 tests across 17 spec files, the following are known-failing and deliberately left open. Each has a root cause identified, so none are mysteries — just honest work that needs a real session.

Systemic issues discovered, not yet fixed (world-class fix required):

IssueImpactRoot causeFix shape
.size field access on Int-typed Vec handles reads capacity, not sizeSILENT wrong valueTypechecker falls through to raw field access for .size on TYPE_INT values with no struct name.RESOLVED (Apr 16, 2026). When .size falls through to the “no struct name” path and object_type is TYPE_INT, the typechecker now rewrites to vec_size() call. Verified: Int-typed Vec handle returns correct size (3, not capacity). Fixpoint verified.
Result$unwrap extracts wrong field for Result::Ok payloadsr.unwrap() on Result::Ok(55) returns 0 instead of 55.option_narrowing_spec (2/7 tests fail).RESOLVED (Apr 16, 2026 retest). option_narrowing_spec 7/7 green.
Parser defer only accepts expressions, not statementsBlocks cross_defer_safety_spec.ps_parse_defer calls ps_parse_expr.RESOLVED (Apr 16, 2026 retest). cross_defer_safety_spec 8/8 green.
Parser cannot handle extern "C" def with body at top levelffi_spec and extern_def_spec each lose 1 test.ps_parse_extern_fn stops at ).RESOLVED (Apr 16, 2026 retest). ffi_spec 15/15, extern_def_spec 20/20 green.

Single-test failures (deep codegen bugs):

SpecTestSymptomCategory
never_type_spec”never type in let binding context”Exit 176RESOLVED (Apr 16, 2026 retest). 11/11 green.
stress_type_system_spec”enum variant with named payload”Returns 0RESOLVED (Apr 16, 2026 retest). 43/43 green.
error_codes_spec”QZ1215: cannot implement both Copy and Drop”Parser errorRESOLVED (Apr 16, 2026 retest). 35/35 green.
arenas_advanced_spec”Arena$drop called automatically on scope exit”QZ0551 empty nameRESOLVED (Apr 16, 2026 retest). 28/28 green.
stress_pattern_matching_spec”match on Optionllc failedRESOLVED (Apr 16, 2026 retest). 28/28 green with QUARTZ_COMPILER set.
stress_pattern_matching_spec”generic unwrap function”Exit 80 instead of 42RESOLVED (Apr 16, 2026 retest). Was stale roadmap data.

.size wart — FIXED at compiler level (Apr 16, 2026):

Not yet audited. RESOLVED: Typechecker now auto-rewrites .size on Int-typed values (no struct name) to vec_size(). Previously-broken sites (lint.qz, codegen.qz, toml_spec, unicode specs, extern_def_spec) were manually fixed; the compiler-level fix prevents future occurrences.

Missing stdlib functions (feature gap):

collection_stubs_spec tests reversed, sorted, unique, flatten, enumerate, zip, partition, group_by. None exist in the stdlib. RESOLVED (Apr 16, 2026). reversed and sorted added to prelude (using vec_clone + vec_reverse/vec_sort builtins). The other 6 functions already existed. 21/21 green.

Move semantics enforcement (S2.5 holes, 3 specs):

s25_low_holes_spec (10/13 failing), s25_safety_holes_spec (10/13 failing). RESOLVED (Apr 16, 2026). All 6 holes (#4 while-loop, #5 list-comp, #8 return-position, #10 match-subject, #11 NLL-interaction, #12 transitive-borrow) are wired up and enforced. s25_low_holes_spec 15/15, s25_safety_holes_spec 13/13 — all green. The handoff’s claim “infrastructure exists, checks missing” was outdated; the wiring was already in place.

WASM backend gaps (3 specs):

  • wasm_core_spec: exit codes, variables, functions, control flow, recursion — core backend missing
  • wasm_data_spec: closures, bitwise ops, string ops (str_concat, to_str, str_eq, str_starts_with, str_from_char, str_is_empty), vec ops
  • wasm_extended_spec: char predicates (is_alnum, is_whitespace, is_upper/lower), str_trim, str_downcase/upcase, str_repeat, str_cmp

This is TGT.3 Direct WASM Backend — 10 phases, not yet started per user memory.

Generic Iterator bounded dispatch (generic_ufcs_dispatch_spec, 8/8 failing 8/8 green):

<T: Iterator> bounded generic functions with for-in body emit invalid code. RESOLVED (Apr 16, 2026). The underlying compiler feature works correctly — sum, count, break, continue, multiple Iterator types, empty iterator, and adapter chains all pass. The enddef parse error was a test infrastructure bug: Quartz triple-quoted strings strip trailing newlines, so gud_counter_src() + """...""" concatenated end directly with def on the same line. Fix: append "\n" to helper functions. 8/8 tests green.

Module path resolution (2 specs):

  • modules_spec: needs spec/qspec/fixtures on include path. RESOLVED (Apr 16, 2026). The spec requires -I spec/qspec/fixtures for direct compilation and QUARTZ_FIXTURES env var for subprocess tests. With those set, 35/35 pass (one test had a stale expected error string: “Undefined function” → “has no function”). Not a compiler bug.
  • accept_worker_spec: multi-level path std/net/http_server doesn’t resolve. NOT A BUG (Apr 16, 2026). Hierarchical import std/net/http_server resolves correctly via resolver.qz step 1 (base_path + module_name + ".qz"). Compiles and links fine.

Scheduler/async (pre-existing, roadmap items):

  • backpressure_spec: BLOCKED — send/recv shadow bug UNBLOCKED (Apr 16, 2026 — SEND-RECV-SHADOW fix)
  • semaphore_spec: BLOCKED — same UNBLOCKED (Apr 16, 2026)
  • graceful_shutdown_spec: BLOCKED — same UNBLOCKED (Apr 16, 2026)
  • compiler_bugs_p30_spec: SIGSEGV in subprocess FIXED (Apr 16 retest) — 9/9 green, no SIGSEGV
  • map_index_spec internals — FIXED Apr 12

Retested Apr 16 after #13 park/wake fix + 15b cascade-stop fix. compiler_bugs_p30_spec now passes. The other three can’t compile because send/recv channel builtins are shadowed. All three unblocked (Apr 16 — SEND-RECV-SHADOW fix deleted dead externs). All 6 specs now compile.

Other known-failing but deliberate:

  • ufcs_complete_specm.delete() causes runtime SIGSEGV RESOLVED (Apr 13, commit 06b936d2). The spec was using m.del(1) (a phantom verb), not m.delete(). Fixed in place; 21/21 tests green including the deletes via UFCS case. The underlying map_delete intrinsic was always sound — the spec just had a typo from an earlier iteration.

Open compiler / bootstrap issues

These are gaps discovered during the April 2026 recovery and dogfooding sprint. Each one has a clear reproducer and a sketched fix.

Batch A + Batch B sprint summary (Apr 14, 2026)

Nine items from the April 13 overnight-sprint handoff landed across two sessions. Fixpoint 2281 functions byte-identical throughout. Every commit passed quake guard + quake smoke + per-item regression sweep.

#CommitItemOutcome
A1a16b8c19macOS mimalloc gateFixed (one-line link-flag change; diagnosis corrected from IOAccelerator to dyld TLS-slot race)
A225032709OPTION-CTOR-IN-IMPL-BODYAlready fixed on trunk — locked in with 5 regression tests, root cause was stale .quartz-cache
A394f80b64Stale typecheck_registry.qz.bakDeleted
A4daaefd88quake launcher stderr surfacingFixed (Ninja-style sh_buffered helper; llc not found false positive eliminated)
A525df7bf8Lambda capture walker intrinsic-name shadowFixed (1-line check reorder in mir_collect_captures_walk; first/sum/map-named locals now capture correctly)
B16e46491eIMPL-TRAIT-SEND-AUTO coinductive solverFixed (~30 lines plumbing existing structural walker into tc_check_trait_bound)
B2b61da0f4Scheduler state machine + free auditFixed (atomic @__qz_sched_state word, 7 zero-after-frees, 5 missing frees → ~4.5 MB/cycle leak closed, 7-test lifecycle spec)
B3dbe2bab7PSQ-4 Vec element type lossAlready fixed on trunk — locked in with regression spec; adjacent B3-DIRECT-INDEX-FIELD bug filed
B4258468f5step! in while loop miscompileNot fixed RESOLVED — see B4-UNWRAP-IN-LOOP below (Batch C sprint C5, 73185be8)

Follow-ups filed during the sprint: QUAKE-STDERR-AUDIT, POST-RESOLVE-IDENT-ASSERTION, IMPL-NOT-SEND, B3-DIRECT-INDEX-FIELD, B4-UNWRAP-IN-LOOP.

New regression spec files: enum_ctor_in_impl_spec.qz (5), closure_intrinsic_shadow_spec.qz (5), send_auto_trait_spec.qz (5), vec_element_type_spec.qz (4+1 pending), unwrap_in_loop_spec.qz (3+1 pending), sched_lifecycle_spec.qz (7) = 29 new tests + 2 pending markers.

Bootstrap & infrastructure

IssueImpactInvestigation
Permanent source fossilsSource referenced functions/constants never defined in source✅ ALL RESOLVED (Apr 12, 2026). All 11 fossils now have real definitions in source: ast_func_is_cconv_c (stub at ast.qz:1489), TYPE_MAP (type_constants.qz:90), NODE_IS_CHECK (node_constants.qz:257), tc_type_is_hashable/tc_struct_is_hashable/tc_hashable_rejection_reason/tc_struct_hashable_rejection (full impls in typecheck_helpers.qz), mir_register_poll_callee/mir_func_set_cconv_c/mir_func_is_cconv_c (mir.qz), mir_block_set_switch/mir_block_get_switch_* (mir.qz), cg_emit_struct_hash_eq_functions/cg_emit_custom_map_functions (codegen_runtime.qz). Source-only build verified: gen1 (1,679,869 lines) == gen2 byte-identical. Standing patch set in .quartz-guard/ retired.
SEND-RECV-SHADOW: send/recv channel builtins shadowed by POSIX socket externsextern "C" def send/recv in std/ffi/socket.qz shadows 2-arg channel builtins.RESOLVED (Apr 16, 2026). Deleted the dead extern "C" def send/recv declarations entirely — zero callers, socket_send/socket_recv wrappers already use sendto/recvfrom. Unblocked 6+ specs: concurrency_spec, channel_handoff_spec, unbounded_channel_spec, backpressure_spec, semaphore_spec, graceful_shutdown_spec. Fixpoint verified (1989 functions).
Cache-pattern miscompile in 54eb4965 onwardThe mangle/suffix caching pattern (as_int(string) → intmap → as_string(int) round-trip used in resolver.qz and typecheck_registry.qz) miscompiles when the cache grows large (~9.7 GB into a self-compile): cross-module struct type lookups silently fail. Recovery workaround was to revert the cache calls to raw "#{prefix}$#{suffix}" interpolation.PARTIAL (Sprint 1, Apr 16): root cause confirmed and first sub-bug fixed (253b6edb) — __map_get_raw was missing from the str2-propagation guard in mir_lower_expr_handlers.qz:2261, so Int-keyed __map_get_raw(intmap_handle, int_key) silently routed to __hashmap_get_sentinel codegen, calling qz_str_hash on the Int key. Two more sub-bugs block re-enabling the dedup cache: (a) __intmap_get_raw codegen missingcg_intrinsic_map.qz:116 maps __map_get_rawintmap_get for Int-keyed maps, but intmap_get returns Option<V>, not a raw sentinel. The hashmap path has __hashmap_get_sentinel (raw value or 0); the intmap path needs the same. (b) compiler hangs on match map_get(...)/Some/None do...end pattern in mangle — switching to the Option-wrapped path triggered an infinite loop in gen1 when self-compiling. Likely a typechecker recursion on Option from a recursive code path. Repro: re-add the cache to string_intern.qz mangle/mangle_int using match map_get(...); gen1 hangs at 100% CPU with no output. Next steps: (1) implement __intmap_get_raw codegen (~150-line variant of intmap_get returning raw value or 0). (2) After (a), the cache can use __map_get_raw directly without the Option/match wrap that triggered the hang. The cache value would be intern_id (small Int, dense, stable — no UAF possible). See string_intern.qz:255-301 for the exact form to restore. ~2 GB peak RSS recovery expected.
__Future_<pointer>$<name> non-determinismActor codegen emits future helper functions whose names contain a literal pointer value✅ VERIFIED FIXED (Apr 12, 2026). All Future symbols now use stable string identifiers (__Future_Counter$__poll$new, __Future_Accumulator$__poll$new, etc.). Two back-to-back compilations of actor_spec.qz produce byte-identical IR. The old pointer-based naming was replaced with function-name-based naming in mir_lower_gen.qz and mir_lower.qz.
Free-without-zero pattern in __qz_sched_shutdownRecurring shape: global slots get free()’d without being zeroed, leaving dangling pointers that codegen treats as “scheduler still alive” via ptr != 0 proxy checks.RESOLVED (Apr 14, 2026 Batch A+B sprint B2). Introduced @__qz_sched_state atomic state word (UNINIT=0, RUNNING=1, STOPPING=2, STOPPED=3) as the authoritative scheduler-lifecycle signal. sched_init CAS-transitions UNINIT→RUNNING, sched_shutdown CAS-transitions RUNNING→STOPPING→UNINIT, and the sched_is_active() intrinsic was rewritten to load atomic seq_cst the state word and compare with RUNNING — replacing the completion_map[3] != 0 pointer-proxy check that broke under free-without-zero. Also closed the remaining unzeroed slots (0 worker_array, 1 global_queue, 6 mutex, 7 condvar, 17 worker_data, @__qz_timer_peers, @__qz_pidfd_set) and — more importantly — added the missing free() calls for priority queue slots 20/24/28 (3×524 KB), priority table slot 32 (524 KB), and task_registry slot 0/3 (2 MB+64 B). Previously every shutdown leaked ~4.5 MB of priority + registry memory. Design follows Go runtime’s scheduler state machine, Tokio’s worker State atomic, and libuv’s loop_alive predicate — all converged on “single atomic state variable, explicit transitions, zero on free” as the pattern to replace pointer-proxy checks. Locked in by spec/qspec/sched_lifecycle_spec.qz (7 tests: init→active→shutdown→inactive, double-init, double-shutdown, shutdown-without-init, re-init cycle). Scheduler regression sweep verified: sched_lifecycle_spec 7/7, sched_idle_hook_spec 5/5, scheduler_spec 4/4, spawn_await_spec 3/3, proc_suspend_spec 3/3, process_async_spec 5/5, colorblind_async_spec 13/13, await_nonasync_spec 6/6, async_channel_spec 6/6. Fixpoint 2281 functions, byte-identical.
quake guard:source clean-room verificationquake guard:source implemented (Apr 12). Full clean-room pipeline: gen0→gen1→gen2 fixpoint + 3 smoke tests (brainfuck, expr_eval, style_demo). Not yet wired into CI.Phase 2 (guard:forward per-PR) and Phase 3 (guard:history weekly sweep) remain. See docs/QUARTZ_GUARD.md.
macOS mimalloc/IOAccelerator collisionThe committed self-hosted/bin/quartz segfaults during self-compile on macOS because mimalloc on Apple Silicon crashes during dyld init.RESOLVED (Apr 14, 2026). Fixed in the Batch A+B sprint by dropping -lmimalloc from the macOS link path in Quakefile.qz:1327 (_mimalloc_flags() returns "" on macOS). Revised root cause per microsoft/mimalloc#343: the crash is a dyld TLS-slot bootstrap race — ImageLoader calls malloc before tpidr_el0 points at a valid TCB, so mi_tls_slot() null-derefs. The earlier “IOAccelerator region walk” theory was wrong; the fix is unchanged. Linux builds still link mimalloc and keep the 49% RSS win. Performance loss on macOS self-compile is acceptable. See docs/PLATFORMS.md allocator note and docs/BOOTSTRAP_RECOVERY.md §“The macOS mimalloc/IOAccelerator collision”.
MULTI-CLAUSE-1: any multi-clause def in a user program corrupts prelude typecheckTwo Match arm type mismatch errors with misleading file attribution.RESOLVED (Apr 13, 2026). The prior session’s investigation went down the wrong path — this was never about multi-clause desugaring. It was two entangled bugs, both of which also affected non-multi-clause code: (1) Scope-shadowing bug in tc_expr_call: a user-level function named f was shadowing parameters named f (e.g. prelude’s result_and_then(f: Fn(T): Result<U,E>) saw the user’s def f(n: Int): Int in the registry instead of the parameter). Fix: short-circuit to indirect call when a NODE_IDENT callee resolves to a scope-bound Fn-typed variable, mirroring the existing FIELD_ACCESS indirect path (typecheck_expr_handlers.qz). A plain def f(n: Int): Int = n * 2 in the user file (no multi-clause at all) triggered the same error. (2) Guard sentinel bug in MIR match lowering (mir_lower_expr_handlers.qz:3196): if arm_guard_node >= 0 used >= 0 instead of > 0. Since node 0 is the AST null sentinel, every guardless enum/value-pattern arm got a phantom guard that lowered to 0 (false), making the arm unreachable. That’s why fib(0) and fib(1) recursed into the default case and blew the stack. Both bugs landed pre-existing the file-attribution regression. With both fixed, examples/expr_eval.qz runs 22/22 end-to-end; smoke baseline regenerated. Separately, the file-attribution bug (the user-file-with-prelude-line-numbers thing) was also fixed via depgraph_path_for_ast_store lookup.
MAP-NEW-DECLARE-MISSING: map_new() inside a stdlib function emits a call without an @hashmap_new/@intmap_new declaration, llc failsllc: use of undefined value '@hashmap_new'RESOLVED (Apr 13, 2026). Misdiagnosed in the handoff — the bug was NOT in codegen.qz FFI emission. The call i64 @hashmap_new() came from a separate broken group_by builtin intrinsic in cg_intrinsic_collection.qz, not from the user’s map_new(). That intrinsic was internally inconsistent: it called @hashmap_new() to create the map but then used @intmap_has / @intmap_set / @intmap_get to operate on it — and it always used hashmap regardless of key type. Because it was registered as a TC builtin, it shadowed any user-space def group_by in prelude. Fix: deleted the intrinsic entirely (237-line handler block in cg_intrinsic_collection.qz + entries in intrinsic_registry.qz + typecheck_builtins.qz). The user-space def group_by in std/prelude.qz now handles it correctly via map_new() (which dispatches to @intmap_new for Int keys). collection_stubs_spec now runs 21/21 green including the previously it_pending group_by test.
OPTION-CTOR-IN-IMPL-BODY: bare Some(v) / None inside trait impl method body emits %Some undef / %None undef IRreturn None or return Some(v) inside a trait impl method silently compiled through the typechecker and produced LLVM IR with %None undef / %Some undef references. llc rejected the IR with “use of undefined value”, surfacing as a confusing build error. The workaround was to use qualified forms Option::None / Option::Some(v) or to import traits first.RESOLVED / NOT REPRODUCIBLE (Apr 14, 2026). Regression hunt on current trunk: the bug does not reproduce after a clean .quartz-cache rebuild. All four originally-broken forms (return None, return Some(v), same with @field shorthand, same with import traits) now behave correctly — bare forms are typecheck errors (QZ0403/QZ0401), qualified forms compile and run. The earlier “silent undef” behavior observed during the impl Trait sprint was caused by stale .quartz-cache state from a previous session, not a current compiler defect. Most likely fixed indirectly by commit 39a984ca (UNKNOWN-VARIANT-MATCH variant validation) and/or commit 1c68b67c (unqualified variant payload type fix), both landed April 13. Locked in via regression spec spec/qspec/enum_ctor_in_impl_spec.qz with 5 tests. A follow-up guardrail — post-resolve NODE_IDENT assertion promoting silent-undef to a hard internal-compiler-error — is filed as POST-RESOLVE-IDENT-ASSERTION below for future consideration but deliberately not implemented now (no failing test to justify speculative infrastructure per Directive 5).
POST-RESOLVE-IDENT-ASSERTIONFuture guardrail: promote any NODE_IDENT that reaches MIR lowering without a resolved binding to a hard internal-compiler-error.RESOLVED (Apr 14, 2026 Batch C sprint C4). Implementation narrowed from the original plan: the guardrail fires only for PascalCase identifiers, not all unresolved NODE_IDENTs. Rationale: during development the unconditional check false-positived 61 times in a single self-compile on lowercase names like v, e, idx — match pattern bindings and closure captures that have legitimate late-bound resolution paths in codegen (not MIR lowering). Quartz convention reserves PascalCase for types, enums, and variant constructors; any bare PascalCase ident that fails local-var / function / arity-mangled / global lookups at MIR entry is almost certainly an unresolved enum variant leaking past a resolver scope-push omission (the OPTION-CTOR-IN-IMPL-BODY class). Lowercase names are deliberately excluded from the check. Implementation: new helper mir_ident_has_global_resolution in mir_lower.qz checks mir_ctx_is_global + mir_find_global_name fallback; new helper mir_ident_is_pascal_case gates the check by first-character case; the NODE_IDENT branch in mir_lower.qz:1279 fires QZ9501 and exit(1) on match with a filename/line/col + remediation hint. 5 canary tests in spec/qspec/post_resolve_assertion_spec.qz — all common PascalCase patterns (qualified/unqualified variants, bare Some/None in impl bodies, struct constructors, user enum variants) compile cleanly. The assertion is a prophylactic guardrail: it has no direct trigger on current trunk because all known resolver holes have been patched, but any FUTURE resolver regression that leaks a PascalCase ident will fail loudly at MIR entry instead of producing %<name> undef IR that llc rejects with cryptic “use of undefined value” errors. Fixpoint verified at 2285 functions; B-sprint regression sweep (16 specs) + post_resolve_assertion_spec all green. Design reference: rustc’s Res::Err invariant — MIR lowering refuses to lower unresolved names.
QUAKE-STDERR-AUDITRemaining 2>/dev/null sites in Quakefile.qz beyond the A4-fixed build/link chain in tools/quake.qz.RESOLVED (Apr 14, 2026 Batch C sprint C1). Migrated 13 build/link sites in Quakefile.qz from sh("... 2>/dev/null") / sh("... 2>>tmp/build.err") / system("... 2>/dev/null") to sh_buffered(cmd, step_name) with explicit fail() wrappers. Sites covered: Gen1 build (llc-gen1 / link-gen1), guard task (llc-guard-gen1 / link-guard-gen1), guard:source task (llc-source-gen1 / link-source-gen1), build_compiler in-live branch (opt-build / llc-build-opt / llc-build / link-build-release / link-build-debug), and _compile_qz_tool helper (compile-tool / llc-tool / link-tool). Each site now surfaces real tool stderr on failure via the sh_buffered red-header + captured-stderr dump — success remains silent, so Live UI cursor tracking is unaffected. Fixpoint verified at 2281 functions; smoke 4/4 + 22/22 green. Remaining sites (qspec runner, fuzz task, smoke task test-runs) intentionally untouched — those are non-compile-critical paths where the current sh_maybe/system forms are appropriate.
B3-DIRECT-INDEX-FIELDDirect vec[i].field access (no intermediate var binding) produces QZ0301: Unknown struct: Struct, Int when the Vec element is a user struct.RESOLVED (Apr 14, 2026 Batch C sprint C2). Root cause: tc_type_name_full in typecheck.qz:2310 was rendering Vec ptypes as Vec<Struct, Int> by treating ptype_arg2 as a second type id. But for TYPE_VEC, arg2 holds the struct_idx metadata (used to deduplicate Vec<Point> vs Vec<Triple> in the ptype table — see typecheck.qz:1144, 1172), not a type id. Rendering Vec<VA> with struct_idx=1 therefore produced "Vec<Struct, Int>" — bare "Struct" from tc_type_name(TYPE_STRUCT) plus "Int" from coercing struct_idx=1 into tc_type_name(TYPE_INT). That corrupted annotation then propagated into var va: Vec<VA> = vec_new() bindings via the init_type_kind >= PTYPE_BASE reconstruction branch at typecheck_walk.qz:1707, and the NODE_INDEX type-annotation walker at typecheck_generics.qz:278-290 stripped the outer Vec<> to produce "Struct, Int" as the inferred element type — which then hit tc_check_field_access and failed with QZ0301: Unknown struct: Struct, Int. Why the workaround (var a = va[0]; a.y) worked: the intermediate binding’s struct_name was set via a different code path (NODE_INDEX-aware str2 annotation at typecheck_walk.qz:1655-1659) that reads ast_get_str2 rather than reconstructing from the ptype. Fix: special-case TYPE_VEC in tc_type_name_full — for Vec ptypes, ignore arg2, prefer the cached ptype_name slot (set by tc_ptype_set_name for struct elements), and fall back to tc_type_name_full(arg1) for primitive elements. Locked in by 3 new tests in spec/qspec/vec_element_type_spec.qz: direct va[0].y access, nested va[i].inner.field chain, and arithmetic combination va[0].y + va[1].y. Commit <next>. Fixpoint verified at 2281 functions; B-sprint regression sweep (15 specs) all green.
B4-UNWRAP-IN-LOOPstep! (macro $unwrap) inside a while loop body produces a silent miscompile when step is reassigned each iteration.RESOLVED (Apr 14, 2026 Batch C sprint C5). Root cause was NOT in the match lowering path (where both Session 2 fix attempts looked) — it was in expand_node at self-hosted/frontend/macro_expand.qz:219-233. The NODE_ASSIGN / NODE_INDEX_ASSIGN / NODE_FIELD_ASSIGN branch unconditionally read ast_get_right(h) for the “value” slot, but the actual slot layouts diverge: NODE_ASSIGN stores value in left, NODE_INDEX_ASSIGN in extra, NODE_FIELD_ASSIGN in extra. The wrong-slot read meant the macro expander silently SKIPPED the RHS of every compound assignment — so sum += step! (which parser desugars to sum = sum + step!) carried a raw NODE_MACRO_CALL("unwrap") straight through into typecheck and MIR lowering. Downstream phases interpret unresolved macro calls as zero-valued, producing the observed %v11 = add i64 %v10, %v0 where %v0 = add i64 0, 0. The two Session 2 fix attempts (payloads sentinel, subject hoist) didn’t help because the macro was never running in the first place — they were patching code that wasn’t executing. Why step! outside the loop worked: var a = step! is NODE_LET, not NODE_ASSIGN, and the NODE_LET branch of expand_node reads left (the correct slot). Why the explicit match in loop worked: no macro call to expand — the parser already produced a NODE_MATCH, which expand_node walks through correctly. Fix: split the collapsed if kind == 26 or 27 or 28 branch into three explicit per-kind branches reading the correct slots. Also updated the $unwrap macro (expand_builtin_unwrap) to desugar to an if x is Some(payload) block using battle-tested narrowing instead of a 3-arm match — defense-in-depth so any future hole in match-in-loop-with-reassignment gets caught by the narrowing path. Locked in by 3 new tests in spec/qspec/unwrap_in_loop_spec.qz: while-with-reassignment (the original repro), for-with-reassignment, and nested-while. Fixpoint verified at 2285 functions; B+C regression sweep (17 specs) all green. Lessons learned: when two fix attempts both no-op, suspect the fix site isn’t reached at all. A targeted eputs in the macro body would have surfaced this in 5 minutes.
UNKNOWN-VARIANT-MATCH: match arms with a typo’d variant name silently compile to bad IRmatch x; NotAReal(v) => ...; AlsoFake => ... end with x: MyEnum silently typechecks, then llc rejects with use of undefined value '%AlsoFake'. Users got confusing downstream failures instead of a clear “unknown variant” error.RESOLVED (Apr 13, 2026). Two holes, one in each pattern shape: (1) tc_bind_pattern_variables at typecheck_expr_handlers.qz silently fell through when a NODE_ENUM_ACCESS pattern referenced a variant that didn’t exist in the resolved enum — no error, no binding, arm body typechecked against a bogus type. (2) NODE_IDENT patterns were always bound as capture variables regardless of name shape; a typo’d variant like AlsoFake then got bound as a scope variable named AlsoFake, while MIR lowering separately tried to treat it as an enum variant tag comparison (mir_lower_expr_handlers.qz:3103-3123), emitting load i64, ptr %AlsoFake from an alloca that was never declared. Fix: validate NODE_ENUM_ACCESS variants against tc_enum_has_variant; classify NODE_IDENT patterns by first-character case — PascalCase goes through the variant-lookup path (error if not in any enum, error if in a different enum than the subject), lowercase binds as a capture. Regression spec at spec/qspec/match_unknown_variant_spec.qz covers unqualified variants, variants with payloads, and fully qualified patterns. Self-compile handles the compiler’s own Found(v) => ... NotFound => ... patterns via the module-prefix suffix-match fallback.

Map type checker holes (dogfooding sprint)

IssueImpactInvestigation
CMP-MAP-1: untyped map literals lose type infom = {"hello" => 42}; m.get("hello") fails.FIXED (Apr 2026, Cluster A): NODE_MAP_LIT now returns TYPE_MAP (not TYPE_INT); NODE_MAP_COMP typechecks key/value exprs and returns Map<K,V>; new tc_try_resolve_map_key_value_types infers K/V from the first constraining map_set/map_insert call (mirrors Vec inference).
CMP-MAP-2: Option .some?/.none? predicates fail on let-bound Map.get() resultresult = m.get(k); assert(result.some?) fails to typecheck.FIXED (Apr 2026, Cluster A): map_get result is now wrapped in Option<V> at the result-type assignment site, so the let binding records Option<V> and predicate narrowing finds the proper Option.
CMP-MAP-3: Map.del() short alias missingm.del(k) is undefined; only m.delete(k) works.WONTFIX (per API_GUIDELINES.md §“Vocabulary Rules”). Test removed from spec/qspec/map_ufcs_spec.qz.
CMP-MAP-4: is Some / is None on let-bound Map.get() result causes runaway memory in type inferenceReproducer consumed 33+ GB RSS.FIXED (Apr 2026, Cluster A): root cause was the same as CMP-MAP-2 — let binding recorded Int instead of Option<V>, so is Some narrowing recursed through unbound type vars. Fixing the Option<V> wrap at the source eliminates the runaway. Regression test: spec/qspec/map_int_spec.qz line 40-46.
CMP-MAP-FOLLOWUP: unify map_* intrinsics across LLVM and WASM backends, delete hashmap_*/intmap_* user-facing namesThe Cluster A typechecker fix unblocks all Map-using surface syntax, but the legacy hashmap_*/intmap_* intrinsic names are still registered as user-callable builtins.FIXED (Apr 2026, two commits): (1) WASM backend gained a translate-at-entry layer in wasm_runtime.qz (_wasm_resolve_map_name mirroring cg_intrinsic_map._map_to_hashmap_name/_map_to_intmap_name) so codegen_wasm.qz reads slot2 and resolves map_* before downstream processing — verified with quartz check --feature wasm since the WASM target is @cfg(feature: "wasm")-gated. (2) TC dispatch rewrite at typecheck_expr_handlers.qz lines 1487-1571 replaced with a 6-line annotation pass that sets slot2 = "Int" for int-keyed maps, the struct name for struct-keyed maps, or empty (string-default) — runs in both the direct-call site and the UFCS rewrite block. (3) Inliner fix in egraph_opt.qz eg_inline_call: cloned MIR_CALL/MIR_INTRINSIC instructions now copy slot2 so inlined map_* calls keep their elem_type annotation (found via global_vars_spec where gv_init_imap() was inlined into qz_main and lost its int-key annotation). (4) Deleted hashmap_* and intmap_* builtin registrations from typecheck_builtins.qz, deleted __hashmap_get_raw and __hashmap_get_sentinel definitions from str_compat.qz, migrated all backend callers (egraph.qz, domtree.qz, mir_lower_async_registry.qz) to __map_get_raw / map_*. (5) benchmarks/hash_map_bench.qz migrated to unified map_*. After this commit, user code calling hashmap_new() or intmap_new() produces Undefined function errors. The internal codegen still uses hashmap_*/intmap_* as runtime helper symbol names — these are not user-visible.

Parser holes (dogfooding sprint)

IssueImpactInvestigation
CMP-PARSER-IMPLICIT-IT-1: trailing-block .method() { it ... } syntax not supportedBoth items.map { it * 2 } and items.map() { it * 2 } errored.FIXED (Apr 2026, Cluster B). Parser changes in parser.qz: (1) ps_parse_do_end_block_if_present now treats every {...} in trailing-block position as a lambda body — if no explicit -> header is present, an it parameter is synthesized; (2) the dot-method branch in ps_parse_postfix recognizes paren-free .method { ... } form via the new helper ps_paren_free_block_is_implicit_it which disambiguates from struct/map literals (which require IDENT : or IDENT => as the first body token). Both .map { it * 2 } and .map() { it * 2 } now work, including multi-statement bodies like .filter { var t = 12; it > t }.
CMP-PARSER-IMPLICIT-IT-2: standalone { it ... } block-as-lambda not supportedvar f = { it * 2 } errored.FIXED (Apr 2026, Cluster B). Primary expression { dispatcher in ps_parse_postfix’s ps_parse_primary recognizes { it ... (where it is followed by a non-header token like an operator) as a single-arg implicit-it lambda and elaborates to it -> body. Set literals are still recognized when the first body token isn’t it.
CMP-PARSER-FAIL-FAST: implicit-it parse failure runs away to multi-GB RSSenumerable_spec consumed 18+ GB RSS.FIXED (Apr 2026, Cluster B). Added call_rewrite_depth: Int field to TypecheckState (initialized to 0) and a depth guard in tc_expr_call’s UFCS rewrite path (typecheck_expr_handlers.qz line 2143 area). Cap = 32, chosen as 8x the legitimate maximum (1-3 levels) and half of Rust’s general-purpose recursion_limit (128). Any future method-resolution loop now fast-fails with QZ0205: method resolution exceeded recursion depth 32 instead of consuming multi-GB of RAM. Note: the RUNAWAY itself is also fixed by resolving the underlying parser issue (CMP-PARSER-IMPLICIT-IT-1) — but the depth guard remains as a defense in depth.
CMP-POSTFIX-TRY: postfix ? on let-binding RHS not parsedvar val = opt? errors with “Undefined variable: opt?”.RESOLVED via Path A (Apr 2026, Cluster C-2): postfix ? was deliberately removed in v5.12.27 in favor of the $try() macro because the lexer rule that glues trailing ? into identifiers (which makes .some?/.none?/.empty? predicate methods work) is incompatible with a standalone postfix operator. The bug was documentation drift, not a missing feature. Fix: CLAUDE.md anti-hallucination table updated to point at $try(expr); docs/QUARTZ_REFERENCE.md “Postfix ?” section retitled to $try() macro with the rationale; spec/qspec/postfix_try_spec.qz rewritten to use $try(opt) (9/9 tests passing). The alternative Path B.3 (rename predicate methods to free ? for postfix-try) was offered to the user as a follow-up but Path A was chosen for this session.
CMP-VARIANT-PATTERN: unqualified user-enum constructor patterns not supported in match armsErr(e) => ... errors with “Expected expression / Expected pattern”.FIXED (Apr 2026, Cluster C-1): the bug was actually in match-arm body parsing, not pattern parsing. After parsing the body of arm N (e.g. Ok(v) => v), the parser scanned for “next arm” tokens but missed the IDENT ( form — Err(e) was being parsed as a function-call continuation of the previous body. Fix: in ps_parse_match_arm’s extra-statement loop, treat a PascalCase identifier followed by ( as an arm boundary (Quartz convention reserves PascalCase for types/variants). Also generalized tc_bind_pattern_variables to scan all registered enums when the unqualified variant has no enum_name, so payload types resolve correctly for user enums (mirrors the existing built-in Option/Result handling).
CMP-IS-KEYWORD: is keyword not recognized by parserx is Some fails with “Expected newline”FIXED (Apr 12, 2026). Root cause: is had no token constant, no lexer keyword mapping, no parser rule. All downstream (typecheck, MIR, codegen) already worked. Fix: added TOK_IS = 126 to token_constants.qz, "is" => TOK_IS to lexer.qz, ast_is_check() to ast.qz, TOK_IS branch in ps_parse_equality() in parser.qz (equality precedence, with optional binding form). 21/21 tests passing (is_keyword_spec 12/12, is_binding_spec 9/9).
IMPL-TRAIT-SEND-AUTO: Send/Sync auto-trait does not propagate through impl Trait args without an explicit impl Send for Xtc_validate_trait_boundstc_check_trait_bound only consults tc_lookup_impl.RESOLVED (Apr 14, 2026 Batch A+B sprint B1, commit 6e46491e). tc_check_trait_bound now falls through to the structural field-recursive tc_type_is_send / tc_type_is_sync helpers when the bound is Send/Sync and no explicit impl exists. The existing helpers already had coinductive cycle detection via g_send_checking / g_sync_checking visited stacks, so recursive types like struct Node { next: Option<Box<Node>> } correctly prove Send via structural induction. Follows RFC 0458 (“structural, not nominal”) and rustc’s auto-trait solver design. 5 regression tests in spec/qspec/send_auto_trait_spec.qz. A follow-up IMPL-NOT-SEND (explicit impl !Send for X opt-out) is filed below for a future sprint — this commit handles the positive auto-derivation direction only.
IMPL-NOT-SENDExplicit impl !Send for X / impl !Sync for X opt-out syntaxRESOLVED (Apr 14, 2026 Batch C sprint C3). Parser accepts impl !TraitName for TypeName end (optionally with leading impl<T> type params). Implementation: parser consumes an optional TOK_BANG after type params in ps_parse_impl, records the flag in NODE_IMPL_BLOCK.int_val, and errors if the negative impl lacks a for clause (there is no such thing as a negative inherent impl). tc_register_impl_block short-circuits on the negative flag — writes the (trait, for_type) pair into new parallel registry vecs neg_impl_trait_names / neg_impl_for_types and returns without registering any method bodies. Three consultation sites: tc_type_is_send, tc_type_is_sync, and the Send/Sync branch of tc_check_trait_bound all consult tc_lookup_neg_impl FIRST, returning 0 on match — before the positive tc_lookup_impl path and before the structural walker. tc_lookup_neg_impl strips <...> on both the query type and each registered type so impl !Send for X rejects both X and X<Int>. Locked in by spec/qspec/impl_not_send_spec.qz (5 tests: basic reject, positive regression, Sync analogue, all-primitive-fields still rejected, unrelated positive impl does not mask). Fixpoint verified at 2283 functions (+2 from 2281: new tc_register_neg_impl and tc_lookup_neg_impl helpers). B-sprint regression sweep (16 specs incl. send_auto_trait) all green. Design reference: Rust RFC 0019 (ImplPolarity::Negative).
BOUNDED-STRUCT-FIELD-HANG: struct W<C: Counter> { inner: C } hangs the parserCanonical reproducer infinite-looped in ps_parse_optional_type_params on the :ps_expect(TOK_COMMA) recorded an error without advancing the cursor, so the outer while ps_check(TOK_GT) == 0 kept seeing the same token.FIXED (Apr 13 2026, impl-Trait sprint Phase 5). ps_parse_optional_type_params now consumes :Trait and +Trait bounds and preserves them inline in the returned bracketed source form (e.g. "<C: Counter>"). Downstream: tc_strip_struct_tp_bound and tc_extract_struct_tp_bound helpers split the bound from the name at the registry boundary; tc_infer_struct_type_args and tc_substitute_field_type strip the bound before name-matching against field annotations; new tc_check_struct_type_param_bounds validates each concrete type arg at struct-init time via the existing tc_check_trait_bound helper. Positive/negative tests in spec/qspec/impl_trait_spec.qz.
STRUCT-GENERIC-ARG-INFERENCE: implicit type-arg inference drops concrete struct namesvar w = Wrapper { inner: Cell { ... } } (no explicit Wrapper<Cell>) causes tc_infer_struct_type_argstc_type_name(TYPE_STRUCT) to return the generic string "Struct" instead of the actual struct name. Downstream field access through w.inner.val then fails with QZ0301: Unknown struct: Struct.FIXED (Apr 13 2026, impl-Trait sprint follow-up). Root cause confirmed as hypothesized: tc_find_field_type_for_param returned only the integer type ID, which the caller then ran through tc_type_name, collapsing every struct field to the generic kind name “Struct”. Fix: tc_infer_struct_type_args now takes the field initializer AST nodes, and the new helper tc_find_field_type_name_for_param calls the existing tc_infer_expr_type_annotation walker on each initializer to recover the source-level struct name (handles NODE_STRUCT_INIT, NODE_IDENT, NODE_CALL, NODE_FIELD_ACCESS, etc.). Falls back to tc_type_name on the runtime type ID when the AST walker can’t infer an annotation. Two files touched (typecheck_generics.qz, typecheck_expr_handlers.qz); regression test in spec/qspec/impl_trait_spec.qzITLift { source: ITBase { val: 41 } } now binds with type ITLift<ITBase> and l.source.val resolves to 41. Unblocks Phase 6 (std/iter.qz) writing adapter constructors like MapIter { source: src, f: f } without explicit type args.
NESTED-MONO-UFCS-TYPE-PARAM: UFCS dispatch through type-parameter receiver in bounded-generic body dispatches through the wrong implInside def sink<I: Iter>(src: I): Int the call it.i_next() should re-dispatch through the caller’s concrete type for I at each monomorphization. Currently the dispatch either (a) reuses the outer caller’s concrete type directly (collapsing adapter wrappers), or (b) stamps the wrong impl into the call site. End-to-end prototype sink(map_doubler(range)) returns garbage even though each isolated layer compiles.FIXED (Apr 13, 2026, impl-Trait sprint Phase 6 unblock). Root cause was two-fold. (1) When the MIR lowered var d = f(...) for a bounded generic f returning Foo<T>, it recorded the raw declared return Foo<T> as d’s tracked struct type. That leaked the type parameter into downstream monomorphization: calling sink(d) from main produced the spec name sink$1$MapDoubler$I (not $RangeSrc) and a degenerate spec_param_types mapping of I → MapDoubler<I>. (2) The A8 trait-rewriting block in mir_lower_expr_handlers.qz only consulted spec_param_types for the UFCS receiver — it never looked at the variable’s tracked struct type, and it concatenated spec_current_type literally without stripping the <...> generic suffix, so bodies like var it = src; it.method() failed to build a valid Type$method symbol and fell through to whatever method the earlier fallback dispatch happened to pick. Fix (three edits across two files): added mir_subst_type_with_map and mir_subst_return_type_for_call helpers in self-hosted/backend/mir.qz that substitute type parameters in the declared return type using the caller’s inferred argument types; self-hosted/backend/mir_lower_stmt_handlers.qz now uses the substituted return type when marking the call-result variable’s struct type; self-hosted/backend/mir_lower_expr_handlers.qz A8 block now falls back to mir_ctx_get_var_type for local-variable receivers and strips the generic suffix off both the resolved type and spec_current_type before building the mangled method name. End-to-end sink(map_doubler(range)) now returns 12 as expected. 44/44 in spec/qspec/impl_trait_spec.qz (was 42/42, added two regression tests — nmutp_direct_struct_init and nmutp_wrapped_generic_call), fixpoint verified at 2278 functions, smoke + regression sweep clean (iterable_trait, abstract_trait, traits, hybrid_ufcs_traits, collection_stubs, arity_overload, match_unknown_variant). Unblocks Phase 6 (std/iter.qz rewrite) and the bounded-generic RPITIT case below.
IMPL-TRAIT-OPACITY: callers can still name the concrete type returned by an impl Trait functionvar x: Hello = make_greeter() compiles even though make_greeter(): impl Greeter is supposed to hide the concrete type. Phase 7 of the impl-Trait sprint was scoped for opacity enforcement but the scope binding currently unifies struct_name (for dispatch) and type_ann (for source-level matching) into a single slot, so splitting them is bigger than the 2-4h budget estimated in the sprint handoff.FIXED (Apr 13, 2026, impl-Trait sprint Phase 7). Added a parallel binding_display_types: Vec<String> slot to TcScope (typecheck_util.qz) wired through a new tc_scope_bind_full_with_display and a matching tc_scope_lookup_display_type reader. tc_lookup_impl_return_opaque and tc_is_opaque_marker were added to typecheck_registry.qz; tc_infer_expr_display_annotation was added to typecheck_generics.qz to return the opaque marker "impl <Trait>@<func_name>" for impl Trait-returning calls (NODE_BLOCK unwrap to last expression handles if c then make() ... end). The NODE_LET handler in typecheck_walk.qz stamps the opaque marker into the binding’s display slot when the call target returns impl Trait, propagates it through var b = a IDENT bindings, and emits QZ0167 when the user provides an explicit annotation that names the concrete type behind the opaque return. The expression-context NODE_IF branch unifier emits QZ0168 when both branches return values whose display annotations are different opaque markers. The dispatch_type slot (existing binding_type_annotations) keeps holding the resolved concrete struct name, so UFCS method lookup and bounded-generic bound resolution still work — verified by 4 positive opacity tests including a refactor-safety case (changing ITHelloITWorld in a helper does not break callers). 39/39 in spec/qspec/impl_trait_spec.qz (was 33/33), fixpoint verified at 2276 functions, smoke + regression sweep clean.
OPEN-UFCS-HASH-SENSITIVITY: edits to one stdlib file silently miscompile method dispatch in anothersb.append("m") in std/style.qz:_style_render lowers to %v112 = load i64, ptr %append, align 8 (closure-call path) referencing an undefined %append SSA value. Triggers when ANY top-level def is added to std/json.qz (or its body changes meaningfully) — purely line-count / hash-bucket sensitive. The open-UFCS rewrite of sb.append(...) falls through to the closure-call path instead of finding sb_append as a free function. Reproducer: add a single def _foo() = 0 to the end of std/json.qz, then compile spec/qspec/json_pretty_spec.qz. Affects 4 specs in baseline (json_pretty_spec, json_float_spec, json_edge_cases_spec, json_ufcs_spec) — these were ALL pre-existing failures, not caused by the json fix. Discovered Apr 16 during Sprint 1. Investigation: walk the open-UFCS rewrite path in typecheck_expr_handlers.qz — find why method-name lookup for append against StringBuilder fails non-deterministically. Likely a stale resolution cache or an iteration-order-dependent dispatch.Sprint 1 follow-up. Blocks the typed-payload fix for JsonValue::Object/Array (the right answer for the json crash).
JSON-OBJECT-PAYLOAD-INT: enum payloads typed as Int lose Map<K,V> / Vec<T> info on destructureenum JsonValue { ... Object(fields: Int) ... } — when matched, fields has type Int. map_keys(fields)Vec<Int> (default fallback). keys[i] types as Int. map_get(fields, key) then dispatches to intmap_get even though fields was created via map_new() (string-keyed hashmap) → SIGSEGV at runtime. The right fix is Object(fields: Map<String, JsonValue>) (typed payload), but that triggers OPEN-UFCS-HASH-SENSITIVITY above and breaks style.qz. Current workaround: helper _json_key_to_string(k: Int): String = as_string(k) reinterprets the Int back to its String pointer (no-op at runtime, hashmap key slots ARE string pointers stored as i64).Sprint 1 (Apr 16) — fix landed. Underlying compiler bug (Int-typed receiver dispatching map_get to intmap based on key arg type when receiver IS a hashmap) remains. The deeper fix requires either typed-payload migration (blocked) or smarter dispatch fallback (when key type is Int but receiver was created via map_new() with no annotation, prefer hashmap path).
IMPL-TRAIT-RPITIT: impl Trait in trait method return typesPARTIALLY LANDED (Apr 13, 2026, impl-Trait sprint Phases 11 + 4). Phase 11 added per-impl concrete-return tracking for trait methods declared as def m(self): impl OtherTrait, with direct-receiver dispatch routing through the impl-specific concrete via the impl-return registry. Phase 4 wired trait Iterable<T> { def iter(self): impl Iterator<T> } into std/traits.qz and added impl Iterable<Int> for X end (auto-satisfaction) for all six stdlib collections (Stack, Queue, Deque, LinkedList, PriorityQueue, SortedMap). 6/6 direct-receiver tests in spec/qspec/iterable_trait_spec.qz, 42/42 in spec/qspec/impl_trait_spec.qz (incl. 3 RPITIT cases), fixpoint verified at 2276 functions. Still missing: bounded-generic dispatch — def sum_all<C: Iterable<Int>>(c: C): Int = ... does NOT yet route c.iter() through the per-monomorphization concrete; that path is blocked on NESTED-MONO-UFCS-TYPE-PARAM above.
IMPL-TRAIT-TAIT: type alias impl Traittype Foo = impl Iterator<Int> — lets you name an opaque type once and reuse it in struct fields, static items, and recursive contexts. Follow-up for a future sprint after the core impl Trait + opacity + RPITIT work lands.Filed per impl-Trait sprint handoff. See Rust RFC 2515 for the design reference.
IMPL-TRAIT-STRUCTURAL: structural opaque return typesdef pair(): (impl Iter, impl Iter) — tuples/arrays of opaque types. Follow-up for a future sprint.Filed per impl-Trait sprint handoff. See Swift SE-0328 for the design reference.

Completed (Recent)

ItemCommitNotes
Fix impl Option/Result infinite recursionb984ab71impl Option and impl Result method bodies delegated to same-named free functions (def is_err() = is_err(self)), causing infinite recursion. Method name shadows the free function inside impl scope. Fix: inline match logic directly. Unblocked result_helpers_spec (16/16), csv_spec (9/9), option_result_spec (17/17), lookup_result_dogfood_spec (8/8), and any code using .is_some()/.is_none()/.is_ok()/.is_err()/.unwrap()/.unwrap_or() method syntax.
Fix .size wartb6432169Typechecker auto-rewrites .size on Int-typed values (no struct name) to vec_size() call. Previously read capacity (offset 0) instead of size for untyped Vec handles.
Fix async $poll extern name collision5abfe9b5cg_extern_var_index fallback stripped __Future_*$poll via last-$ lookup → matched libc poll(). Fix: skip $ fallback for __-prefixed compiler-generated names. async_spill_regression_spec 12/12.
Add reversed() and sorted() to prelude65ab0ed9Two missing eager collection operations via vec_clone + vec_reverse/vec_sort. collection_stubs_spec 21/21.
Fix SEND-RECV-SHADOW965bfbaaDeleted dead extern "C" def send/recv from std/ffi/socket.qz. Zero callers — wrappers use sendto/recvfrom. Unblocked 6+ concurrency specs.
proc_suspend(pid) intrinsic — pidfd / EVFILT_PROC child exitab443188Two new scheduler intrinsics. proc_suspend(pid) user-facing, MIR-lowered via new @__qz_io_pending_is_proc TLS flag that worker task_io_real reads to dispatch to register_proc instead of register_io. sched_register_proc(pid, task) raw form: Linux pidfd_open + epoll + @__qz_pidfd_set cleanup; macOS kevent EVFILT_PROC + NOTE_EXIT with task in udata, atomic register+check via paired changelist/eventlist call. I/O poller extended for both platforms (macOS filter dispatch, Linux pidfd close-after-fire). 3-test QSpec using extern fork/usleep/_exit/waitpid. Replaces read==0 EOF heuristic for child exit detection — fixes daemonizing-child and grandchild-piped failure modes. Stdlib integration (rewrite std/process.qz on fork+exec) is the P1 follow-up.
7 P0 progress sprint quirks filed as compiler bugs766296c9docs/bugs/PROGRESS_SPRINT_QUIRKS.md catalogues PSQ-1..PSQ-7 with status, root-cause hypothesis, fix direction, and existing workaround. Top priority: PSQ-4 (Vec<T> element type loss → silent wrong-struct field reads — hit twice in 1a60633c). Closely related: PSQ-6 (Vec.size reads 0 from poller-thread context).
std/process.qz async-aware subprocess + Live render mutex1a60633cNew std/process.qz with sh_run_async/sh_capture_async/sh_stream_lines_async using io_suspend(fd) on EAGAIN. Quakefile sh_with_progress rewritten to use go + await, drives quake build’s 8-phase compile bar through cooperative async. Live _render_mtx field guards _render_force against poller/main thread interleaving. 5-test process_async_spec.
Scheduler idle hook intrinsic + Live integration945cc265sched_idle_hook_set/clear intrinsic — fires registered Fn(): Void once per I/O poll iteration in __qz_sched_io_poller. Slot 35 of @__qz_sched, tagged-pointer dispatch (closure/plain). Replaces the polling go-task ticker that the previous commit shipped. End-to-end verification: quake build TTY mode renders 441 frames over 17s compile (was 2 frames). 5-test sched_idle_hook_spec.
std/progress background go-ticker for Live in TTY moded5ddaa36First animation fix: a polling go-task that calls _render_force on a fixed cadence. Superseded by 945cc265’s idle hook (kept for history).
std/progress: composable spinners, bars, multi-progress Live UI05581f77..be285aedNew stdlib library: 36 vendored cli-spinners styles, parameterized Col grammar ({spinner}, {bar}, {pos}, {msg}, {eta}, custom columns), Progress + Live structs. Live multi-progress surface with global bar + top-N active rows + above-region log. JSON mode via QZ_PROGRESS=json. 56-test progress_spec.
Quake tasks migrated to std/progress Live UIe5cd2224, 2d944089, 6ecab203, 53a17978qspec runner (Live UI), quake build (consume --progress events from compiler subprocess via sh_with_progress), quake guard (multi-phase Live with per-phase log lines), quake fmt/lint (progress bars over file walks).
QSpec triage sprint (Apr 12)885f80c9..8e70f0faFixed ~165 tests across 17 spec files in one session. Linter SIGSEGV (3 .size bugs + parser newline consumption), extern decl dedup false positive from string constants, match guard spec variant names, stale channel capacity expectations, panicking m[key] spec rewrite for map_index_spec and nested_generics, unicode .size() fixes, F32 coercion in packed_arrays, type_inference auto-coerce update, new option_narrowing_spec (5/7), committed pre-existing Option narrowing source that was in binary but not git.
Option ergonomics: is, !, if let, map_fetch5fe28075is keyword (lexer/parser/AST), postfix ! force-unwrap (parser desugars to $unwrap), if let/elsif let (desugars to if Some(v) = expr), panicking m[key] via map_fetch intrinsic (hashmap + intmap + unified map). 2,242 functions.
Compiler smoke testsa4277b67Enhanced brainfuck.qz (117→386 lines, 35+ features documented), new expr_eval.qz (330 lines, 22 tests, Tier 3 features). Run after quake guard to catch semantic regressions beyond fixpoint.
guard:source clean-room build task5fe28075Quakefile task: gen0→gen1→gen2 fixpoint + 3 smoke tests (brainfuck, expr_eval, style_demo). Verifies the source tree is buildable from scratch with no baked-in binary state.
Bool.to_s() → “true”/“false” + auto-coerce in String+Bool6a3d09a9New intrinsic chain: Bool$to_s, bool_to_str, qz_bool_to_str runtime. 2,242 functions.
String ergonomics: String+non-String auto-coerce, String*Int repetition3eea9906TC AST rewrite wraps non-String in to_str/f64_to_str; * routes to existing str_repeat. 11 new tests.
Fix unqualified variant payload types + dogfood 22 spec files1c68b67cMatch subject’s enum name derived from function return annotation. Unblocked 4 net/tls specs.
Retype Request.params/query/context as Map<String,String>ba4e4bd2Fixes SIGSEGV from UFCS dispatch on Int-typed map handles.
Dedup FFI declarations against both define and declaree8efa41aFixed text-based dedup that missed return types between declare/define and @name.
Fix float→i64 widen in MIR_INDEX/INDEX_STORE codegen8435d6634 handlers (INDEX, INDEX_RAW, INDEX_STORE, INDEX_STORE_RAW) lacked is_float branch.
Reject Float map keys; walk struct fields for hashability85152035F64/F32/CFloat/CDouble removed from hashable. tc_struct_is_hashable walks fields.
Bind for-in loop variable with container’s element type0b4e6fc5TC normalizes ptype to base for NODE_FOR.extra; loop var gets key/elem type.
Fix int-key map iteration (3 chained bugs)524abbfcTC ptype normalization + MIR map_key_types tracking + intmap_keys/values occupancy bitmap codegen.
Bootstrap recovery: mangle UAF + fossils + Map migration + Quake Guarda23f71edThe Apr 2026 recovery commit
Park HM type inference v2 modulesfd4810b8Moved to experiments/parked/hm-inference/
Complete first phase of HashMap → Map spec migrationbe13bf844 specs migrated, 6 holes filed
Dogfood char literals + UFCS in examples/benchmarks/projects/sitef47aded45 files
Update HashMap → Map terminology in documentation53e8cb017 docs
Dogfood char literals + UFCS in tools405156774 files (excl lint)
Dogfood UFCS + char literals + variants in stdlib3084eec624 files
Add 5 new spec files for v5.26+ language features1cf399e3+ 4 new compiler holes filed
Dogfood UFCS + char literals + variants in 92 spec filesda786e89+ CMP-VARIANT-PATTERN filed
HPACK: bounds-check guards + 7 new Huffman decode tests99750257std/net/hpack.qz + spec
WASM backend test expansion685a6f234 wasm spec files
http_server + http2: cross-platform TLS + frame helpersbdc050e62 networking files, 11 new defs
lint: extend QZ7104 + add QZ7206 bare-go rule36f376c5tools/lint.qz
Abstract trait methods (bodyless def in trait blocks)6362a9268 tests
Stdlib trait impls (Show, Eq, Serializable)5f6e7f539 tests
Trait auto-satisfaction + Container trait + HashMap→Map8d773bb08 tests
Multi-line function signatures781235386 tests
Trailing commas in parameter lists777757ee6 tests
Modernize 5 examples + activate group_by test576b13dc
Fix UFCS module name collision (param precedence)eb75db873 tests
Exhaustiveness for Bool and Result<T,E>655f405c4 tests
Fix UTF-8 double-encoding (sb_append_byte + 6 lexer paths)5f9448b168→0 corrupted strings
CLI style library: terminal detection + composable Style API35 tests
Fix UFCS impl method priority for call-expression receiversfixpoint
MirContext UFCS migrationab2130b95 MIR lowering files
MirFunc/Block/Instr UFCS844b88763 codegen files

Dogfooding Backlog

ItemStatusNotes
DG.2 Triple-quoted strings~93 spec files, ~132 commits
DG.3 Unified intrinsic registrySingle Map, 696 names
DG.4 String match + length dispatchLength→hash→str_eq for >=5 arms
GAP-1 Abstract trait methodsBodyless def, body=-1 sentinel
DG.5a-b UFCS compiler migration8 backend files migrated
DG.6a-d Stdlib trait impls + ContainerAuto-satisfaction, 4 collections
DG.8b-c Examples modernizationtraits.qz, fibonacci.qz, string_processing.qz
Trait auto-satisfactionSwift/Go model, resolver + typechecker
DF.10 OPQ Goal BPendingProvenance preservation via MIR_TYPE_PTR
DF.11 Memory optimizationPendingvec_shrink_to_fit, arena mmap
DF.12 std/ptr.qz modulePendingTyped pointer ops for FFI
DF.13 Compiler decompositionPartialPhase 0-1 done, Phases 2-3 pending

Reference