Skip to content

Latest commit

 

History

History
460 lines (411 loc) · 51.1 KB

File metadata and controls

460 lines (411 loc) · 51.1 KB

TurboLang — Engineering Backlog (autonomous-agent queue)

This is the work queue for autonomous agents (Codex loop / Claude). It exists so automation works on real, verifiable correctness and feature work instead of converging on packaging/hygiene busywork. It was seeded from the 2026-06-28 product review + 0.9.2 hardening sprint.

Rules for autonomous runs

  1. Pull the highest-priority unchecked item ([ ]) whose status is not IN PROGRESS. Work ONE item per branch.
  2. Definition of done (every item): the fix/feature is implemented, new tests are added that would fail without the change, and the full suite is green — cargo test --workspace --manifest-path turbo/Cargo.toml, cd turbo && ./tests/run_tests.sh, turbo/tests/parity/run_parity.sh, and cargo clippy ... -D warnings + cargo fmt --check. Update this file: check the box and note the commit.
  3. No hygiene busywork. Do NOT open PRs whose substance is "keep X reproducible", "guard Y", "prevent drift", README/metadata polish, dependency bumps, or re-formatting — unless an item below explicitly calls for it. If you cannot make real progress on a backlog item, stop and leave a note here rather than inventing hygiene work.
  4. Honesty bar: never add a performance/benchmark/marketing claim that isn't backed by a committed, reproducible measurement (see the 0.9.2 benchmark correction). Use current evidence and methodology; older best-of results and the new paired evaluator are not interchangeable before/after measurements.
  5. Keep changes surgical and matched to existing style. One concern per branch; respect the file-ownership discipline if multiple agents run in parallel.

P1 — correctness & credibility

Approved native-platform execution (2026-09-06)

User-directed sequence: master plan, acceptance spec. Preserve prior BL history; BL-27B/BL-28 remain owned by their existing entries. The user paused the aggregate goal and explicitly requested no further goal execution on 2026-09-07. The authorized work is a bounded v0.16.0 release closeout, not continuation of later milestones. OMX CLI is not installed and no matching Circular project is exposed. No shadow OMX ledger has been fabricated.

Release-closeout checkpoint: JSON serializer concat intermediates and owned temporary inputs now reclaim in JIT/AOT, including the array serializer. The eighth diagnostic fixture verifies complete JSON output and zero live tracked allocations; full-size post-fix evidence reports 6,668,371 allocations/frees in each mode. Its one-pair timing and unequal JSON API paths are explicitly non-qualifying. Cargo reference artifacts are source-matched from build JSON, not guessed filenames. Fresh local gates: workspace, 358 integration/10 helper skips, 37 parity, normal/profile Clippy, fmt, C ASan/observer UBSan, five native allocation suites and 43 evaluator tests. Public roadmap/claims and v0.16 release metadata are prepared. G2.1 and all broader unchecked goals remain incomplete; release completion is not goal completion.

  • G1.1 — HTTP/SQLite JSON round trips. DONE in e494051 on codex/g1-json-roundtrip. Three initial regressions failed before the fix; five CLI tests now cover byte-identical JIT/AOT serializer output, POST→GET→restart, port collision recovery and non-retry compiler errors. Fixed native struct/array/root string quoting and C control escaping, retained SQL binding, and corrected AOT body truncation at byte0x01. Fresh verification: workspace tests,350 integration passes/10 helper skips,37 native parity,11 existing WASM tests, C-runtime ASan, Clippy/fmt, C embedding smoke. Independent code-reviewer APPROVE and architect CLEAR after feedback fixes. Embedded NUL remains unsupported by the existing C-string ABI; no broader serializer/WASM/production qualification is claimed.

  • G2.1 — Performance/allocation evaluator. IN PROGRESS on codex/g2-evaluator. First slice: paired monotonic native-process collector, per-child RSS, hierarchical bootstrap, fail-closed qualification, retained raw evidence, independent output oracles and manifest preserving all11 planned cases. Only fib and wordcount are runnable in this slice; wordcount is labeled an application comparison because top20 algorithms differ. Allocation/RC/live-byte instrumentation, remaining fixtures/held-out corpus, in-program batching and cross-host evidence remain required. Controlled fixture forms remain pending G3, not silently passed. See benchmarks/EVALUATOR.md; all broader G1–G8 outcomes remain open.

    • Measurement foundation committed in 75f27e5;22 evaluator tests pass, including runtime stderr rejection, untracked provenance, checksum tampering, exclusive evidence creation and cross-process measurement locking. Independent code review APPROVE and architecture CLEAR. Native regression gates remain green (workspace,350 integration/10 helper skips,37 parity,Clippy/fmt).
    • Recorded diagnostic baseline: benchmarks/results/g2-initial-20260906/,60 measured pairs/case across3 batches and281 retained process records; all output oracles pass. Qualification remains incomplete and the CPU subset fails the target. This is evidence for further work, not closure of G2.1 or a Rust-parity claim.
    • Next slice implemented: opt-in allocation-profile compiler flavor with one C accounting engine shared by JIT/AOT; normal builds compile out hooks. Shared-header allocation/data/header/live/peak/RC metrics, real arena reclamation and concurrent observer tests; calibration gives exactly1 allocation/32 data bytes/16 header bytes/1 retain/2 releases in each mode. Separate evaluator profile phase rejects invalid records and instrumented timing compilers. Whole-heap and reference-language coverage are still outstanding. A returning-entry wordcount smoke observed1,054,614 allocations and frees in each runtime with no live tracked allocations; not a whole-heap claim.
    • Observer/v2 fixture slice committed in 2c8d9f1:32MiB logical byte-buffer scan/update/checksum and2,097,152-operation integer/string hashmap churn, with pinned sources and independent oracles. Default-sized AOT/Rust smoke and small JIT/AOT/Rust checks pass;28 evaluator tests pass. Full normal workspace tests,351 integration/10 helper skips,37 native parity, Clippy/fmt and C ASan/UBSan pass. Code review APPROVE; architecture WATCH remains specifically for incomplete whole-heap/source/host qualification, not an identified code blocker.
    • Particle/v3 slice:10,000 seeded managed particles with32,768 exact dyadic fixed steps; safe Rust inline-AoS counterpart and independent closed-form integer oracle. Default AOT/Rust output checks pass; real-fixture JIT/AOT/Rust regression covers five parameter combinations through65,536 steps plus invalid input.31 evaluator tests, full workspace,351 integration/10 helper skips,37 native parity, Clippy/fmt pass. Independent code review APPROVE and slice architecture CLEAR; broader whole-heap/source/host WATCH is unchanged. Retained 512-step allocation diagnostic:5,130,018 shared-header allocations/frees in both modes, not a whole-heap or performance qualification. Five of11 fixtures are runnable; JSON/string/tree/SQLite/HTTP/worker and broader measurement coverage remain. G2.1 is not complete.
    • Particle slice committed in 5e1c1ba. String/v4 slice adds1,048,576 visits over a pinned UTF-8 log-token corpus with explicit ASCII-margin/literal-replacement semantics and independent weighted-record oracle. Real-fixture JIT/AOT/Rust tests cover the golden corpus, alternate combining/ZWJ/mixed-script corpus and invalid steps;34 evaluator tests, full workspace,351 integration/10 helper skips,37 parity, Clippy/fmt pass. Default-sized native output smoke passes; independent code APPROVE and slice architecture CLEAR. Six of11 fixtures now run; JSON/tree/SQLite/HTTP/worker, full memory coverage and broader qualification remain.
    • Unresolved G1 native Unicode parity defects exposed during E2: JIT/AOT differ for upper("Straße"), lower("É"), NBSP trimming and empty-separator UTF-8 splitting. Actual probe outputs and reproduction steps are retained in EVALUATOR.md. These must be corrected under the native semantic-parity work; the bounded token fixture does not certify or close them. No runtime behavior was changed by the string benchmark slice.
    • String slice committed in ce06242. Tree prerequisite corrective slice: a two-node recursive struct aborted the compiler from unbounded inline cleanup generation, and recursive/forward enum payloads vanished during type resolution. Added two-phase nominal resolution, arity-preserving diagnostics and private recursive cleanup helpers without changing headers/field layout. Allocation regressions exposed and fixed unbalanced readonly-call references, ?? temporary ownership and payload-free enum-constructor ownership; late-argument reassignment is protected across named/method/UFCS/indirect calls, including mutable hashmap function-value adapters. Nested generic inference preserves the existing generic-array-index program and rejects inconsistent bindings/bound failures. Six200-round native ownership fixtures pass valid identical JIT/AOT profiles with zero live tracked allocations; separate function-value tests cover argument safety, not closure/environment reclamation. Fresh gates: workspace,358 integration/10 helper skips,37 parity, normal/profile Clippy, fmt, C ASan/UBSan,34 evaluator tests and all6 existing benchmark output oracles pass. Independent architecture CLEAR and final code review APPROVE after fixing review findings. Tree timing fixture is still pending, so the runnable count stays6/11; G2.1 and the aggregate remain open.
    • Tree prerequisites committed in fb118a0. Tree/v5 fixture: sixteen seeded depth19 recursive enum trees,1,048,575 nodes each, with construction/postorder hashing/drop included. Rust uses safe boxed nodes; the independent oracle uses flat breadth-first construction and bottom-up reduction. JIT/AOT/Rust fixtures pass five small parameter combinations and invalid bounds. Profile tests require zero live allocations and identical counters, with the same peak for one versus four same-depth rounds; evaluator profiles enforce the zero-live contract and retain rejected raw records. The full-size diagnostic records16,777,204 tracked allocations/frees in each mode, zero live and peak1,048,575; its one-pair timing is explicitly not qualification.37 evaluator tests, workspace,358 integration/10 helper skips,37 parity, normal/profile Clippy and fmt pass. Independent architecture CLEAR and final code review APPROVE. Seven of11 fixtures now run; JSON/SQLite/HTTP/worker and broader G2/G3 qualification remain open.
    • Tree/v5 committed in 4d1054c. JSON extraction prerequisite: AOT json_get(json_stringify(...)) failed escaped-text round trips that JIT passed. Replaced the loose C lookup scanner with bounded complete-object validation, decoded UTF-8/Unicode-escape keys/values, last-duplicate selection and complete raw spans for nested values. Invalid escapes, malformed tails, invalid UTF-8, non-finite numbers and over-depth containers reject consistently in the covered corpus; long finite numeric tokens and underflow are accepted. Validation-only string scans allocate no decoded buffers; local numeric locales avoid global locale mutation. Shared decoder also fixes root-string escapes. C ASan and CLI serde-oracle/mutation/zero-live-ARC tests pass, plus workspace,358 integration/10 helper skips,37 parity, normal/profile Clippy/fmt and37 evaluator tests. Independent code APPROVE and architecture CLEAR. Canonical non-string formatting, raw-NUL ABI behavior, parse-once typed JSON values and the timed JSON fixture remain open; benchmark count is unchanged at7/11.
  • BL-1 — Finish retiring the ?.unwrap() panic class in codegen. Status: DONE (Claude, 2026-06-28, commit 47bef1ce68adbc2be4640fc5ad8f012c06e81a6e). The 0.9.2 sprint fixed the 2 reachable panics but ~152 compile_expr(...)?.unwrap() remain in turbo/crates/turbo-codegen-cranelift/src/builtins.rs (+5 in src/expr.rs: ~1595/1620/1646/1851/1890). All are sema-guarded today, but a future sema gap panics the compiler (exit 101) instead of diagnosing. Convert them to ?.ok_or_else(|| CodegenError { code: <fitting ErrorCode, e.g. E0400/E0403>, message })?. AC: no ?.unwrap() on a compile_expr result remains in those two files; add a fuzz/regression pass (extend turbo/fuzz/ or add adversarial .tbs) that throws malformed-but-parseable input at codegen and asserts a clean diagnostic, never a panic. Done: converted all 152 sites in builtins.rs and all 5 in expr.rs to ?.ok_or_else(|| CodegenError { code: ErrorCode::E0400, message: "<fn>: produced no value during code generation" })? (context-specific messages). grep -c '?.unwrap()' is now 0 in both files. Coverage: 15 in-crate regression tests (builtins.rs mod panic_class_regression, run under cargo test) that drive the backend with sema bypassed and assert a graceful E0400 for every expr.rs site + representative builtins.rs sites; a 55-program deterministic sema-bypass corpus in turbo/fuzz/src/codegen_fuzz.rs (run_robustness_corpus, runs in CI); and 3 adversarial .tbs (tests/adversarial/bl1_unit_in_*) asserting a clean non-101 diagnostic end-to-end. Residual: 2 non-compile_expr .unwrap()s in builtins.rs (lookup_variant_tag_static(...).unwrap(), lines ~3351/3422) were intentionally left — they are infallible by construction (the variant tag was just resolved), not unwraps of a compiled subexpression.

  • BL-2 — Add a real-world benchmark (CEO's top "next"). Done (commit 79129ef, merged to master). Added a word-count workload (turbo/benchmarks/wordcount.tb + C/Rust/Go baselines + deterministic input generator + run_wordcount.sh runner with warmup, best-of-5, and byte-for-byte output-equality enforcement). Measured on M5 Max: Turbo AOT ~240 ms vs C ~110 / Rust ~125 / Go ~130 — ~2.2x slower than C (further behind than fib40's ~1.3x). Published honestly in README + website with a reproduce command. Root-caused the gap to runtime hashmap/string handling → see BL-9.

P2 — language & tooling depth

  • BL-3 — f32 across the spawn/generic/closure ABI boundary. Done (commit 7e4a6d5, merged to master). Root cause was a single ABI disagreement: resolve_cl_type mapped f32 → F32 for internal function signatures while every float value flows as F64 (f32 locals/arithmetic/returns already round-trip as f64), so the param var was declared F32 but fed an F64 value → Cranelift panic / closure + spawn miscompiles. Fix: promote f32 → F64 uniformly across the internal Turbo ABI (type_conv.rs), and preserve a real 32-bit F32 only at the C FFI (extern) boundary via a new resolve_cl_type_ffi threaded through compile.rs. Removed the 0.9.2 sema rejects (reject_f32_params + closure-param reject). New tests: 4 JIT↔AOT parity programs (f32_abi_{param,closure,spawn,generic}) + 5 phase1 value-assertion tests; removed the obsolete f32_{spawn,closure}_reject ERROR tests. Verified: 503 unit / 238 integration / 18 parity, clippy + fmt clean. No new semantic loss (the language never promised 32-bit rounding for f32).

  • BL-4 — LSP scope/binding resolution. Done (commit 4ff0476, merged to master). New turbo-lsp/src/resolve.rs walks the AST with a lexical scope stack mapping each ident occurrence (by span) to its declaration, respecting shadowing; rewired go-to-def, references, rename, hover, and a new document-highlight to be scope-precise (shadowed inner x no longer renamed with an outer x). Correct-over-complete: field-access-to-struct-field resolution still falls back (needs type inference). LSP tests 50 → 70. Follow-on (future): field/method resolution once a span→type map exists.

  • BL-5 — AST-based formatter. Done (commit 677d243, merged to master). Replaced the line-based tidier with a real lex→parse→print pretty-printer (canonical operator/:/-> spacing, block expansion, 4-space indent, trailing commas). Safety-gated: re-parses its own output and compares ASTs (behaviour- equality) + a comment multiset check; on any mismatch / unparseable input / block comments it returns the source byte-for-byte. Idempotent + semantics-preserving across all 204 phase1 + regression + adversarial programs; init scaffold passes fmt --check. turbo-formatter tests 8 → 36.

  • BL-6 — Split the two god-functions. Done 2026-06-28 (sema commit 65a5ada, codegen commit d08fe36, merged to master). check_expr_inner (~4,540 lines) → a thin dispatcher delegating to 35 per-variant check_<variant> handlers (the ~2,800-line builtin section fanned out to 13 category helpers via check_builtin_call); longest fn now check_match at 370 lines. The compile_call _ fallback arm (~311 lines, six concerns) → compile_call 577→202 lines via compile_enum_variant_ctor / compile_ufcs_method_call / compile_closure_call / compile_plain_fn_call (+ compile_fn_call_args / infer_generic_ret_tty / try_inline_fn_call), all ≤84 lines. Zero behavior change — verified by identical ErrorCode/builtin-name/return multisets (sema) and JIT≡AOT parity (codegen); full suite green (267 integration / 28 parity). Holdout (out of scope, named): the pre-existing compile_expr_inner Expr-dispatcher (1,347 lines) — BL-6 codegen was scoped to compile_call's _ arm; a future split could target it.

P3 — coverage & ops

  • BL-7 — WASM backend feature gaps (closures). Done (commit bedac76, merged to master). AC met: closures now compile to WASM and run under wasmtime, parity-matched to native. A closure is a heap {fn_ptr, env_ptr} pair; the body is lifted to a top-level C fn(env, params) with captures in an env struct; clang lowers the fn pointers to call_indirect. Works: direct closures, captures (int/bool/str/f64 via bit-pun), and map/filter closures. Fails loud (clean E0403, no miscompile): closures as user higher-order-fn params, and float-array map/filter. +5 WASM exec tests (the runner now does a 3-way wasm == native == .expected check) +6 codegen unit tests; no native regression. Remaining WASM gaps (future): async, some match patterns, higher-order closure params, returning/storing closures, and the BL-27 Part B struct/array COW.

  • BL-8 — Homebrew formula sha sync. Done (commit f5f0bf5, merged to master). The real checksums are only known post-build and release.yml already writes the authoritative formula to the ZVN-DEV/homebrew-turbo tap, so the in-repo copy can't carry trustworthy shas. Marked it a non-authoritative template and extended scripts/check_release_consistency.py (already run in CI via ci.yml + nightly.yml) to FAIL if placeholder all-zero shas appear WITHOUT the template marker (or if a marked template carries real-looking shas) — so the repo can never silently present fake-but-real-looking checksums. Network-free; --self-test covers all three states + a mutation check.

  • BL-9 — Runtime hashmap/string-key performance. Surfaced by BL-2. Done 2026-06-28. The str→int hashmap path was re-stringifying (snprintf), re-parsing (strtoll), and re-allocating (strdup/free) the value on every increment — a get_int then a separate set_int, each hashing the key. Fix: int values are now stored inline in the hashmap entry (tagged is_int + ivalue) in both runtimes (turbo_rt.c AOT + runtime.rs JIT), so hashmap_get_int/hashmap_set_int do a single hash + single probe with no per-update allocation or stringification; str→str semantics are unchanged (hashmap_get stringifies an int entry on demand). Also added a fused hashmap_inc(map, key[, delta]) -> int primitive (the str→int counterpart of C's table[k]++; single lookup-or-insert) wired through sema + JIT/AOT codegen. Measured (best of 5, ~5 MB, Apple M5 Max / macOS 26.5.1, run_wordcount.sh, output-equal): Turbo AOT ~150 ms vs C ~108 ms → ~1.4x (was ~240 ms / ~2.2x) — clears the ≤1.5x AC. The fused hashmap_inc is within run-to-run noise of the optimized get_int/set_int on word-count (the win was the inline storage, not the second hash), so wordcount.tb is left UNCHANGED on the idiomatic get_int/set_int pattern — the published number needs no benchmark rewrite. JIT still round-trips strings for get_int/set_int (~205 ms / ~1.9x, ~unchanged). Microbenchmark: bench_hashmap_inc.tb (~5M increments, ~46 ms AOT). Tests: tests/phase1/hashmap_inc.{tb,expected} + parity tests/parity/programs/hashmap_inc.tb (JIT≡AOT).


Product-Team Cycle 2 (2026-06-28) — new findings

Seeded from a full /product-team cycle (strategist + e2e + frontend + backend + product-designer), scoped to NEW/forward-looking issues (excludes the already-fixed 0.9.2 items and BL-1..BL-9). Each item notes its source lane. Same rules apply (real tests, full green suite, no hygiene busywork, honest claims).

P1 — correctness, trust, and the wedge

  • BL-10 — Struct field-assignment aliases storage (no COW) → silent data corruption. [backend] Done (commit 40ad9ef, merged to master). Structs already carried the same [cap][refcount] header as arrays but never consulted it on write. New rt_struct_cow (in both runtime.rs JIT and turbo_rt.c AOT) mirrors rt_array_set's refcount>1 → private copy → drop shared ref dance; FieldAssign calls it before the store and repoints the binding, with retains added at the mut-param and array-element binding sites so the refcount is honest. Restores the docs/SAFETY.md CoW value-semantics guarantee for structs. +3 phase1 tests + 1 JIT/AOT parity test (struct_value_semantics); 241 integration / 19 parity green. Surfaced BL-27.

  • BL-11 — Printing a compound value yields an opaque placeholder. [e2e] Done (branch fix/bl11-compound-printing). print(arr) / interpolating an array → [array]; structs → [struct P]; results → [result] (only scalars + Optionals rendered). The official Collections tutorial visibly emitted [array]. Biggest day-1 debugging wall. Fix: extended the codegen-side recursive renderer (convert_to_str in turbo-codegen-cranelift/src/builtins.rs, the shared to_str/interpolation path) to walk arrays ([1, 2, 3], [], nested), structs (Point { x: 9, y: 2 } — distinct from to_json's {"x":9}), and results (ok(7) / err(reason)), mirroring the existing Optional some(x)/none mechanism; print() now delegates all compound types to it. Strings render unquoted to match some("hi")some(hi). Also fixed a latent bug where float/bool optional payloads rendered garbage. +4 phase1 tests + 1 JIT/AOT parity test (compound_printing); 245 integration / 20 parity green.

  • BL-12 — Docs lie about syntax (explain examples + SYNTAX.md [Implemented]). [designer#1, e2e] Done (commit 57bab40, merged to master). Corrected the enum/:: snippets in explain docs E0200/E0303/E0307/E0316 to type/. syntax (each compile-verified via turbolang run); relabeled five design/SYNTAX.md [Implemented] section headers to [Partial] with per-construct [Planned] tags (array/slice patterns, default params, named args, inferred-arrow, array/nested destructuring, tuples, type aliases, turbofish, @derive(Debug/Serialize) — each status verified against the release binary), plus a legend pinning tags to "works on the current release binary." docs/errors.md had no code snippets (no change). Pre-existing quirk noted: a Color.Red to an undefined enum routes to E0300, not E0303 — left out of scope.

  • BL-13 — Runtime/operational errors are second-class. [designer#2,#3] DONE. BL-13a (commit 57bab40): renderer no longer glues more info: onto an empty Help: label. BL-13b (commit 64991cc, merged to master): added an E0601-E0611 runtime/operational code range (fully documented; explain works). Runtime traps (div-by-zero E0601, index OOB E0602, overflow E0603) print a styled runtime error[E06xx]: + Help: + more info: footer, byte-identical on JIT and AOT (twin helpers, TTY-gated color). Operational errors get the full colored error[E06xx]: envelope — file-not-found E0611 + import E0610 drop the raw (os error N) jargon. WASM runtime traps left naked (separate backend) — minor follow-up.

  • BL-14 — No in-browser "Try it"; playground gated behind install. [designer#4, strategist theme 2] DONE 2026-07-06: PR #27 merged (post-review fixes: output truncation instead of discard, JSON-escape-aware response cap, real FizzBuzz expected output, ALLOW_UNSAFE_HOST moved from the image to explicit per-deployment opt-in). Runner deployed to Fly app turbolang-playground-runner (org zvn-dev, 2 machines iad, token secret set); Vercel production has TURBO_PLAYGROUND_RUNNER_URL=https://turbolang-playground-runner.fly.dev/run + token. Verified live: /healthz ok, authenticated run returns stdout, tokenless requests 401, exec rejected, and npm run smoke:playground -- https://turbolang.dev passed against production. turbolang playground serves a working browser playground — but only after a CLI install. For a language, "run code in 5s" is the highest-converting action. AC: host the existing playground at turbolang.dev/play and add it as the hero's primary secondary-CTA ("Try in browser →") + a persistent nav item. Progress 2026-07-04 (branch codex/bl14-browser-playground): added a static /play route, homepage secondary CTA, persistent nav/footer links, sitemap coverage, and regression tests. /play now server-renders the default playground UI without a client-side rendering bailout while still hydrating shared ?code= links. Added a same-origin /api/playground/run proxy contract that refuses local shell execution, rejects non-JSON content types, caps request JSON before parsing, validates source size/shape, and only forwards to TURBO_PLAYGROUND_RUNNER_URL when a separate sandbox runner is configured. Added a dependency-free website/playground-runner/ service + Dockerfile that runs turbolang through execFile with a fixed minimal child environment inside the documented container hardening boundary; startup now refuses missing/blank TURBO_PLAYGROUND_RUNNER_TOKEN unless explicitly opted into tokenless isolated-local testing, configured tokens are trimmed, blank tokens are never accepted as bearer secrets, and startup no longer logs token-derived fingerprints. The runner now rejects excess concurrent executions with HTTP 429. Runner tests cover auth, HTTP /run, non-JSON content-type rejection, malformed UTF-8 request rejection, concurrency backpressure, failed execution stderr, SIGKILL-enforced timeout, oversized-output failure, JSON failure envelopes, and source-policy rejection for compile-time imports, unsafe/FFI features, plus host-access filesystem/process/network builtins including args and http_post_with_headers while allowing pure path string helpers; bundled examples are now regression-checked against that public-runner policy, and the collections example was smoke-run through turbo/target/release/turbolang (Total: 29, Count: 4). The website proxy now returns explicit no-store / nosniff JSON, passes through safe runner validation/backpressure errors (400/413/429) instead of flattening them into a generic 502, while still hiding upstream server/auth failures behind proxy-owned errors. Added scriptable runner and public-site smoke probes (npm run smoke:playground-runner, npm run smoke:playground) that check /healthz, /play, safe execution, and exec rejection before deployment. Local public-site smoke against next start verified /play contains the real playground UI, has no CSR bailout marker, safely executes code through the runner, and rejects exec. Local smoke against turbo/target/release/turbolang returned stdout and enforced token auth. A configured next start smoke (TURBO_PLAYGROUND_RUNNER_URL=http://localhost:8790/run) successfully proxied /api/playground/run through the runner and returned Turbo stdout. Browser QA verified desktop and 320px mobile /play rendering, Run, example selection, copy/share fallback behavior, and fixed the cramped mobile header by hiding the external GitHub nav link below sm. The website proxy now treats an HTTPS runner URL without a nonblank TURBO_PLAYGROUND_RUNNER_TOKEN as incomplete configuration and does not make unauthenticated upstream runner calls. Full local gates passed: cargo fmt --check from turbo/, cargo test --workspace --manifest-path turbo/Cargo.toml, cargo build --release --manifest-path turbo/Cargo.toml, cd turbo && ./tests/run_tests.sh (272 passed, 0 failed, 10 skipped), cd turbo && ./tests/parity/run_parity.sh (29 passed), and cargo clippy --workspace --manifest-path turbo/Cargo.toml -- -D warnings. Local Docker image verification is blocked by an OrbStack data-image permission issue on this Mac, but PR CI now builds website/playground-runner/Dockerfile, starts the resulting image, waits for /healthz, and runs npm run smoke:playground-runner against the containerized service. That CI pass exposed and fixed a stale rust:1.86-slim builder image pin that no longer satisfies the current lockfile (home@0.5.12 requires Rust 1.88), then exposed and fixed the missing docs/errors public-docs tree that turbo-cli/build.rs requires during the runner image build. The public page deliberately does not proxy arbitrary source to a shell-running /api/run; BL-14 remains open until the runner is deployed behind the website and smoke-tested at turbolang.dev/play.

  • BL-15 — Website SEO: duplicate metadata, no sitemap/robots/OG card. [frontend#1,#3] Done (commit 4482ae9, merged to master). Root layout sets metadataBase (turbolang.dev) + a title template; all 15 docs routes now carry unique titles + content-derived descriptions; added app/sitemap.ts (reads the docs dir from disk so it can't drift), app/robots.ts, and a generated next/og opengraph-image + Twitter summary_large_image card. All 21 routes stay static SSG; lint + tsc clean. Per-page og:title/og:desc left inheriting the brand default (noted as a one-helper follow-up).

  • BL-16 — Commit the embeddable-typed-scripting wedge: libturbo C API spike. [strategist] DONE (commit 0b3841c). The biggest gap is positioning, not code: as a "general-purpose compiled language" Turbo loses to Go/Rust on every axis. The one differentiated asset is a real Cranelift JIT. There is zero cdylib/staticlib in the repo. AC (spike): ship the codegen crate as a cdylib/staticlib; minimal libturbo C API (turbo_vm_new, turbo_eval(src), register one host fn callable from Turbo, call one Turbo fn from C, marshal int + string both ways); a runnable "C host calls Turbo, exchanges typed values, calls a host fn back" demo + docs; one line in design/VISION.md committing the north star. Target trusted/first-party scripts first (no sandbox). Separately time-box a sandbox-feasibility investigation (can the JIT be stripped of file/net/ syscall in a quarter?) → go/no-go, NOT a finished sandbox. Do this BEFORE any LLVM/perf or domain-sidecar work. Done: turbo-codegen-cranelift now builds rlib/cdylib/staticlib; libturbo.h exposes VM creation, host callback registration, turbo_eval, and zero-arg i64/str Turbo calls; examples/libturbo-c-host compiles and runs a C host that exchanges typed values through registered callbacks. Docs in docs/libturbo.md mark the current boundary as trusted first-party only and record the sandbox feasibility result: go for trusted embedding, no-go for untrusted scripts until the runtime symbol table becomes capability-shaped. Verified with the full backlog gates plus the CI-backed examples/libturbo-c-host/smoke.sh C host smoke.

P2 — robustness, language depth, conversion

  • BL-17 — AOT HTTP correctness (JIT divergences). [backend F2,F3] Done (commit f929328, merged to master). F2: rt_parse_response colon-fallback now uses a strict rt_parse_status_u16 (mirrors the JIT's parse::<u16>()) — a non-numeric prefix is sent as a 200 body instead of HTTP/1.1 0 OK + colon-truncated body. F3: request bodies that don't fit the 16KB stack buffer are read into a content_length-sized heap buffer (capped by RT_HTTP_MAX_BODY), mirroring the JIT's read_exact — no more spurious 431 on >16KB POSTs; all existing guards preserved. +2 JIT/AOT parity programs (http_colon_response, http_body_limits); fail-before-fix proven by stashing the C change.

  • BL-18 — Mutex can't express atomic read-modify-write. [backend F4] Done (commit 5a6b36b, merged to master). Added mutex_update(m, closure) — the closure (fn(int)->int) runs UNDER the lock (lock → new = closure(old) → store → unlock), reusing the existing runtime-calls-closure pattern from the HTTP route handler (JIT MutexGuard RAII; AOT pthread_mutex_lock/unlock). A shared counter is now correct under contention (4×25k → exactly 100000, 6/6 runs on JIT and AOT; get-then-set lost ~57%). Fixed docs/stdlib.md + CONCURRENCY.md to stop implying bare get/set suffice for concurrent read-modify-write. +phase1 +parity +2 codegen unit tests. Mutex values remain int-only (existing design); generic-T mutex out of scope.

  • BL-19 — Sized integer types are an unusable island. [e2e] Done (commit 634a57a, merged to master). Added an as cast operator end-to-end (lexer keyword → Expr::Cast → sema → Cranelift JIT+AOT; Rust-style precedence between unary and binary; numeric↔numeric only, str as i32 rejected via E0100; narrowing wraps two's-complement, float→int saturates toward zero). Extended untyped-literal coercion into annotated sized types for array literals ([u8] = [104,105]), struct-field init, and literal-operand arithmetic (n:i32; n+1). +3 phase1 tests +1 parity (int_casts); 248 integration / 23 parity. Deferred: to_i32-style builtins (superseded by as); literal coercion in comparisons + into float types; narrow array-element assignment.

  • BL-20 — No CLI args (args() is a stub). [e2e] Done (commit 6b08cad, merged to master). AOT main(argc, argv)rt_set_argsrt_args() builds [str] from argv[1..]; JIT uses a PROGRAM_ARGS global set by the CLI before jit_run; clap Run gained a trailing_var_arg so turbolang run f.tb -- a b c (and trailing args) forward to the program. Convention: args()[0] is the first user arg (binary/source path excluded); JIT≡AOT (run f.tb -- a b and ./bin a b produce identical output). +dedicated args shell test (tests/args/, JIT==AOT, hyphen-leading + empty cases) +parity. Docs updated. WASM args() untouched.

  • BL-21 — bench reports "0/N passed" on every valid benchmark. [designer#8, e2e] Done (commit 13b3850, merged to master). Root cause was the parser rejecting @bench (unknown attribute), so BOTH the JIT and AOT bench paths failed. Made @bench a first-class marker attribute (FnDef.is_bench) accepted on every backend and round-tripped by fmt. bench reporting now leads with the timing, makes AOT-vs-JIT parity a separate non-fatal line (AOT parity: ok/…), labels by function name, and never prints 0/N for a run that produced a valid timing (headline is N/M benchmarks completed). +@bench fixture +CLI integration test +TOOLCHAIN.md docs. @bench is a marker only (no Bencher/b.iter() API yet) — noted aspirational in the doc.

  • BL-22 — Website conversion friction. [frontend#2, designer#9] Done (commit 533501e, merged to master). New CopyButton client component copies a raw prompt-free command via a string prop (never DOM-scraped, so $ markers can't leak); homepage install block stores clean commands with the $ as a select-none aria-hidden affordance. Installation leads with Homebrew; hero reduced to one primary CTA + an icon GitHub link; "Run the flagship demo" → "See" (links to static docs — no playground route exists yet; that's BL-14). All 21 routes stay static SSG; lint clean. Docs-page <pre> blocks hand-roll markup (no shared component) so copy buttons there were out of cheap scope.

  • BL-23 — stdlib.md is missing ~18 working builtins. [e2e] Done (commit a5d74fc, merged to master). Documented all 19 (str_to_int/str_to_float with their Result returns, sort, slice, random, random_range, pad_left/right, list_dir, mkdir, delete_file, file_exists, path_join, time_now, time_ms, format_time, exit, str_from_char, type_of) with compile-verified signatures read from the sema built-in env; added Filesystem + Date/Time sections. Count reconciliation: the brittle/ false "all 104 built-in functions" claims in README.md + GETTING-STARTED.md were softened to "100+" (drops the inaccurate exact count AND the false "all", since the docs still aren't exhaustive). stdlib.md still omits some builtins (substring, args, reverse, array_contains, any, all, int/float casts, math family) — a future doc pass, not blocking.

  • BL-24 — Website a11y defects. [frontend#4-#7] Done (commit 6f105e1, merged to master). Single <main> per docs route (inner → <div>); sub-AA text-gray-500/600 small text → AA-passing gray-400 (footer, benchmark labels, sidebar headers, captions; the only gray-500 left is the decorative aria-hidden $ prompt); global :focus-visible ring using the real accent token; aria-label on all 3 navs + a sr-only skip-to-content link; prefers-reduced-motion guard in globals.css. All 21 routes stay static SSG; lint clean.

  • BL-25 — Long-running-server memory model. Done 2026-06-28 (code commit 8b3dcab + docs, merged to master; Kirby chose "truthful fix + go deep"). The investigation found the README's leak warning was backwards: the per-request bump arena already bounds AOT servers (~1.8 MB RSS plateau), while the unbounded leak was on JIT (turbolang runhandle_http_connection never reset STRING_ARENA, which only drains when main() returns) — and the flagship demo instructed exactly that JIT path. It also surfaced an UNDISCLOSED use-after-free: a stateful AOT server (a startup hashmap mutated in a handler) had its entries freed by rt_arena_end(). A1 — the JIT HTTP handler records a per-request arena high-water mark and truncates back to it after each response: RSS ~62 MB/1k-req leak → flat ~11 MB plateau (stateless web-dashboard byte-identical across requests; non-server run unchanged). A2 — persistent hashmaps allocate entry storage with real malloc/free (scope-following: request-local maps stay arena-backed → no residual leak), so server state survives rt_arena_end(): ASan heap-use-after-free in rt_hashmap_inc → clean, counter 1,2,1,3…1..10. JIT hashmaps needed no A2 (they own keys/values as Rust Strings). +2 ASan-gated C-runtime regression tests + an examples/stateful-counter/ demo; JIT≡AOT parity preserved (28/28). Docs: corrected the backwards leak claim in README.md + docs/SAFETY.md (servers are bounded on both backends; residual narrowed to non-server infinite loops) and listed the stateful-counter example. Also fixed a latent CI red: BL-26's whole-float change made rt_f64_to_str(-0.0)"0.0" but the C-runtime test_rt.c expectation (run by tests.sh, which the standard local gate omits) still asserted "0" (commit 28b0dfb) — tests.sh + the ASan test_rt.c build are now in the local pre-push routine. A2 follow-on: the AOT channel_queue uses turbo_calloc, so a channel created INSIDE a handler is arena-scoped (a startup channel is malloc-backed and fine) — not the BL-25 bug, left untouched; revisit for BL-16's embedded VM if in-handler channel persistence is ever needed.

  • BL-27 — COW parity gaps surfaced by BL-10. [backend, follow-on] Two parts: native arrays (Part A, DONE) and the WASM backend (Part B, still open).

    • Part A — native arrays (DONE, this commit). Confirmed that arrays had the same aliasing at two of three sites: passing an array to a mut param and let row = grid[0]; row[0]=… still aliased the source (only let b = a array copy was correct). The array COW machinery already existed (rt_array_set's refcount>1 → private copy + the IndexAssign binding repoint); only the refcount was dishonest because BL-10's retains were gated to TurboTy::Struct(_). Fix: widened both retain-site gates to TurboTy::Struct(_) | TurboTy::Array(_) — the mut-param arg retain in compile_call (src/expr.rs) and the array-element extraction retain in let (src/stmt.rs). No runtime change (rt_array_set self-sizes its copy). +2 phase1 tests (array_cow_mut_param, array_cow_nested_element) + 1 JIT/AOT parity test (array_value_semantics); JIT≡AOT verified on both reproductions.
    • Part B — WASM backend struct + array value semantics (STILL OPEN, deferred). The WASM C-transpiler (wasm_codegen.rs / turbo_rt_wasm.c) aliases on EVERY binding site for BOTH structs and arrays, and is a genuinely larger fix than Part A — deferred rather than shipped half-done. Confirmed under wasmtime: let b = a; b.x=99 (struct) and let b = a; b[0]=99 (array) and array-mut-param all print the mutated value (e.g. 99 99 where native prints 1 99); a struct passed to a fn does not even compile (void bump(long long p) vs a void* arg → clang -Wint-conversion), and nested-array element extraction (let row = grid[0]) likewise fails to compile. Root cause (why it is bigger than Part A): (1) the WASM transpiler never emits rt_retain at ANY binding site, so the refcount is always 1 → rt_array_set's COW never fires and there is no struct COW at all; (2) there is no rt_struct_cow in turbo_rt_wasm.c and FieldAssign does a direct store; (3) the stmt-context IndexAssign discards rt_array_set's return, so even with honest refcounts the COW copy's write would be lost rather than repointed; (4) the coarse string type-tags (var_types) don't track struct names (needed to size rt_struct_cow's field count) or array element types (needed to safely gate a retain on grid[0] vs a scalar ints[0]); (5) the struct/array param ABI is typed long long while values are void*. Exact remaining scope: (a) fix the struct/array param ABI typing; (b) enrich var_types/inference to carry struct name + array element type; (c) port rt_struct_cow to turbo_rt_wasm.c (8-byte refcount header, no cap field) and call it in FieldAssign with a binding repoint; (d) emit rt_retain at the three binding sites (let b = a, mut-param arg, array/struct element extraction), gated on the enriched types; (e) make the stmt-context IndexAssign repoint obj = rt_array_set(obj, idx, val) when obj is an lvalue; (f) add turbo/tests/wasm/ value-semantics coverage + a WASM↔native parity case. Why deferred: a partial WASM change is either a no-op (no retains ⇒ COW never triggers) or actively unsafe (a mis-gated rt_retain on a scalar treats an integer as addr-8 and corrupts memory — strictly worse than the current honest aliasing). Per the "don't make it worse" bar, the WASM gap is recorded here rather than half-fixed.
    • Known bounded leak (unchanged): BL-10 left a small bounded leak — a named struct (now also array) passed to a read-only callee is retained without a matching release (callee never COWs); the Part A array widening inherits the same shape but does not worsen it. Tighten if the embedded/long-running memory model (BL-25/BL-16) needs it.

P3 — polish (batch; do NOT spawn busywork PRs — fold opportunistically)

  • BL-29 — Harvest the surviving automation/* branches. [triage done 2026-07-06] Of the twelve 2026-06-27 autonomous-agent branches, seven were verified superseded by master and deleted (local + origin). Five contain genuine gaps vs current master but were authored pre-refactor, so each needs re-porting, not merging: automation/hashmap-doc-truth-20260627-0811 (sema BUILTIN_FNS misses hashmap_set_int/hashmap_get_int — shipped builtins are unguarded against redefinition), automation/stdlib-import-truth-20260627-1011 (STDLIB_MODULES lacks std/fs/std/system/std/time + many shipped builtins, so import std/fs { path_join } fails today), automation/lsp-builtin-completions-20260627-1111 (LSP completions omit all builtins; needs a builtin_function_names() sema export), automation/fmt-directory-20260627-1411 (turbo fmt can't take a directory; re-port onto the turbo-formatter crate), automation/recursive-bench-discovery-20260627-1511 (collect_bench_files doesn't recurse like collect_test_files does). Delete each branch once its gap ships.

  • BL-26 — Error/CLI/runtime polish cluster. FULLY DONE 2026-06-28 (3 rounds). Weak/missing Help: that should echo the actual signature/field list/missing variant (E0100/E0200/E0315); messages rename the user's type via aliases (i64int) instead of echoing source spelling (E0110); import error doesn't teach import { x } from "./m.tb"; explain rejects 100/ e0100 (normalize input); whole-number floats print as ints (ambiguous type); raw (os error 2) jargon in file errors; test summary ordering/color/total-time; empty-RHS let x = emits a misleading double-error; REPL spurious unused variable across lines; consider raw strings (r"…") so JSON/{-strings paste verbatim; empty [] can't infer. Backend P3s: rt_spawn_with_args ignores pthread_create return (joins uninit pthread_t on thread exhaustion); rt_format_time uses non-reentrant localtime() (use localtime_r); JIT hashmap &mut *ptr is a data race under concurrent spawn writes. Hashmap-handle type confusion (pre-existing, found during BL-9): a hashmap is an opaque i64 handle, so sema does NOT reject assigning an int to a hashmap-typed var (e.g. m = hashmap_get_int(m, k) or m = hashmap_inc(m, k)) — the next hashmap_*(m, …) then dereferences an integer and segfaults instead of producing a type error. Reproduces with pre-existing builtins too; the fix is a distinct opaque-handle type for hashmaps (and likely mutex/http handles) so int↔handle assignment is a clean sema error. (The fmt :/->/operator-spacing gap here is already covered by BL-5.)

    DONE 2026-06-28 (contained high-value subset): E0100 arity Help: now echoes the full signature + what was passed ('add' takes 2 args (a: int, b: int); you passed 1); E0200 Help: names the missing variant(s) (add an arm 'Blue => ...' or a catch-all '_ => ...'); E0315 Help: lists the struct's fields with a did-you-mean ('Point' has fields 'x', 'y' — did you mean 'width'?); E0110 now echoes the source spelling (i64, not int); malformed imports now teach the syntax (imports look like \import { sqrt, pi } from "./math.tb"`); explainnormalizes100/e0100/E100E0100; rt_format_timeswitched tolocaltime_r; rt_spawn_with_argsnow checks thepthread_createreturn and fails cleanly instead of joining an uninitializedpthread_t`.

    DONE 2026-06-28 (Round 2 — 4 parallel branches, all merged to master, full suite green: 267 integration / 28 parity / workspace, clippy + fmt clean): raw strings r"…" (lexer-only; reuses the existing string node so sema/codegen are untouched, brace re-encoding keeps interpolation literal — commit b526e8a); whole-number floats now print with a trailing .0 (2.0 not 2, so the type is unambiguous; one shared JIT+AOT float helper + 14 .expected updated; JIT≡AOT byte-identical — commit 9bcf269); hashmap/mutex/http opaque-handle typing — int↔handle mixing (m = hashmap_get_int(m,k) / m = hashmap_inc(m,k)) is now a clean compile-time E0111 instead of a runtime SEGFAULT (exit 139→1), via a distinct Ty::Handle(HandleKind{HashMap,Mutex,HttpServer}) with one-way int→Handle coercion that preserves the legit "pass a handle to an i64 param" idiom (commit ed4d266); CLI polish — remaining (os error N) IO leaks translated to plain language, test summary gains TTY-gated color + total-time, and the REPL no longer flags a later-used binding as unused (commit 87fd7fb).

    DONE 2026-06-28 (Round 3 — 4 parallel branches, all merged to master, full suite green: 271 integration / 29 parity / 7 WASM / C-runtime tests.sh, clippy + fmt clean): empty-[] inference — a bare [] now adopts its element type from an annotated let/param/struct-field/return context (concrete-type guard keeps genuinely uninferrable let xs = [] / generic [T] a clean E0115; commit d93be28); empty-RHS let x = now emits one clear diagnostic anchored on the = ("expected an expression after = in let binding") — the literal double-error was already gone, the message was just misdirected at }/EOF (commit aabf0f0); WASM whole-float driftturbo_rt_wasm.c's rt_format_f64 ported byte-identical from the AOT rule, verified wasm == native == expected under wasmtime 43 (commit a5386eb); JIT hashmap data race — the JIT hashmap is now Mutex<HashMap> behind the same i64 handle (one lock_hashmap helper for all 10 ops, no reentrancy/deadlock, no escaping borrows); an 8-thread × 50k-inc stress repro went from 30/30 crashes → 30/30 correct (commit bc0170c). BL-26 is fully resolved. New finding while aligning WASM floats → tracked as BL-28.

  • BL-28 — WASM backend mis-types index/field/unary float expressions as int (value truncation). [found during BL-26 Round 3 WASM-float work] On the WASM target, a float value reached via an array index (xs[0]), struct field (v.x), or unary negation (-3.0, -0.0) is mis-tagged as int by infer_type_tag in wasm_codegen.rs (it falls through to "int" for Expr::Index / Expr::FieldAccess / Expr::UnaryOp), so it is printed via rt_print_i64 — which not only drops the .0 but truncates the value (xs[1] == 2.5 prints 2, -3.0 prints -3). Native (JIT/AOT) handles these correctly; this is WASM-only. Also mis-dispatches to_str() of float arrays/structs to rt_i64_to_str. AC: WASM infer_type_tag resolves the element/field/operand float type (the WASM backend needs to track array element types + struct field types it currently doesn't) so these values print/serialize correctly; add WASM↔native parity coverage for float-via-index/field/negation. Distinct from BL-27 Part B (that's WASM struct/array CoW value semantics; this is scalar float type inference).


When all boxes are checked, STOP and ask for the next priorities — do not invent hygiene work.