# UCE Code Review — Full Findings (2026-06-11) Scope: the pending working-tree changes (73 modified tracked files plus new untracked sources — notably `src/lib/sqlite-connector.cpp/.h` and the uce-starter theme/router rework). Method: seven independent review angles (line-by-line diff scan, removed-behavior audit, cross-file call tracing, reuse, simplification, efficiency, altitude), followed by a verification pass on the correctness candidates. Status legend: - **Confirmed** — verified against the working tree, decisive lines quoted. - **Plausible** — surfaced by a review angle, not individually re-verified. - **Refuted** — investigated and found not to be an issue (kept here so nobody re-flags it). --- ## Part 1 — Correctness (all confirmed) ### 1.1 XSS: island props break out of single-quoted attribute **File:** `site/examples/uce-starter/components/theme/web_affordances.uce:12` The island component emits JSON props inside a single-quoted attribute (`data-props=''`), but `html_escape()` (`src/lib/functionlib.cpp:936`) escapes only `& < > "`, and `json_encode` / `json_escape` (`src/lib/dtree.cpp:794`) never escape apostrophes. Any `'` in a prop value terminates the attribute. **Failure:** a view passes user-influenced prop text containing an apostrophe, e.g. `x' autofocus onfocus='alert(1)` — the attribute terminates early and attacker-controlled attributes/event handlers land on the div. Even benign values like `Don't` silently truncate the payload. **Fix:** use a double-quoted attribute (html_escape covers `"`), or extend `html_escape` to escape `'` as `'`. **Status:** fixed, but fix not verified yet. ### 1.2 Crash: positional `?` placeholders in sqlite_query **File:** `src/lib/sqlite-connector.cpp:86` `bind_params` constructs a `String` directly from `sqlite3_bind_parameter_name(stmt, i)`, which returns NULL for nameless positional `?` parameters. `std::string(nullptr)` is UB and crashes before the `if(name == "") continue;` guard on the next line can run. **Failure:** any page calling `sqlite_query(db, "select * from notes where id = ?", params)` with a bare `?` instead of `:name` segfaults the worker (500 recovery page). **Fix:** remove support for positional placeholders from sqlite and mysql API in favor of named placeholders, adjust the documentation accordingly. **Status:** fixed, but fix not verified yet. ### 1.3 HTTP parsing: split_http_headers assumes lines[0] is the request line **File:** `src/lib/functionlib.cpp:754` The rewritten parser unconditionally treats the first line as the HTTP request line. The old parser located the first colon-free line, which tolerated a leading CRLF (RFC 7230 §3.5) and header-only input. Two failure modes: - The direct-HTTP caller (`src/fastcgi/src/fcgicc.cc:693`) passes the raw buffer unstripped — a client sending a stray leading CRLF before `GET /x.uce HTTP/1.1` gets the request line silently dropped (empty `REQUEST_METHOD`, request fails). - Header-only input (`Host: example.test\nX-Token: abc`, e.g. page code parsing a raw header block) parses `Host:` as `REQUEST_METHOD` and loses `HTTP_HOST` entirely. The function is directly callable from .uce pages. **Fix:** skip leading empty lines before consuming the request line, and only treat a colon-free first line as the request line. **Status:** fixed, but fix not verified yet. ### 1.4 Silent data loss: multi-statement SQL drops everything after the first `;` **File:** `src/lib/sqlite-connector.cpp:178` `SQLite::query` passes `0` for `pzTail` to `sqlite3_prepare_v2` and never checks for remaining SQL, so multi-statement strings execute only the first statement and still report `ok`. **Failure:** a migration page runs `sqlite_query(db, "create table t(id integer); insert into t values(1);")` — only the CREATE executes, the INSERT is silently dropped, `sqlite_error()` says `ok`, leaving the database half-migrated with no error signal. **Fix:** capture `pzTail` and either loop over remaining statements or raise an error when trailing SQL is present. **Status:** fixed, but fix not verified yet. ### 1.5 Diagnostics regression: fault backtrace captured after siglongjmp **File:** `src/linux_fastcgi.cpp:895` The in-handler `capture_backtrace_string` call (`request_fault_trace`) was deleted; the trace is now captured after `siglongjmp` back in `handle_complete`, where the faulting stack has already been unwound. No capture remains in `on_request_fault_signal`. **Failure:** a `.uce` page null-derefs → SIGSEGV → the error page's Trace shows only `handle_complete`/`main` frames. The faulting unit's frames are gone, making crash reports undiagnosable beyond the signal number (the README still advertises a native backtrace of the failure). **Fix:** restore the capture inside `on_request_fault_signal` (on the faulting stack) and hand the result across the longjmp, as the previous code did. **Status:** fixed, but fix not verified yet. ### 1.7 Memory leak: SQLite wrapper objects never freed by request cleanup **File:** `src/lib/sqlite-connector.cpp:248` `cleanup_sqlite_connections()` closes the raw `sqlite3` handles tracked in `resources.sqlite_connections` but never deletes the heap-allocated `SQLite` wrappers from `sqlite_connect` (`new` without `delete`; no arena allocator is compiled in). `sqlite_disconnect` itself is correct (`delete db` after unregistering). Note: this mirrors a pre-existing identical leak in the MySQL connector (`cleanup_mysql_connections`, `mysql-connector.cpp:298-304`). **Failure:** a page calls `sqlite_connect()` per request and relies on end-of-request cleanup instead of `sqlite_disconnect()` — exactly what the cleanup path exists for. One wrapper leaks per request; unbounded RSS growth in the long-lived FastCGI worker. **Fix:** track the wrapper objects (not raw handles) in `resources.sqlite_connections` and delete them in cleanup; fix the MySQL connector the same way, or factor a shared registry (see 5.1). **Status:** fixed, but fix not verified yet. ### 1.8 Asset shims emit stylesheets mid-`` **File:** `site/examples/uce-starter/components/example/marketing_assets.uce:8` (also `theme_assets.uce`, `gauges/assets.uce`) The new ONCE-based asset shims fire inside the view's `ob_start()` capture in `index.uce` (lines 67-85: view is captured into `fragments["main"]`, then `themes/page` renders), so their `` output is baked into the main fragment and spliced into the content div — inside ``, not ``. **Failure:** every marketing/theme/gauges page ships its stylesheet mid-body (FOUC, invalid-ish markup). The deleted `page_shell` asset registry rendered these in ``. While this behavior is often okay in practice, we should improve on this to encourage cleaner output. **Fix:** Part A: restore a registration mechanism: record component and asset output that's intended as once per page in context.call["fragments"]["once"] by default and the page template component can then explicitly slot this in where appropriate. Part B: Modify the preprocessor so directives support attributes like this: ONCE(Request& context) @fragment my-fragment-name { ... } Which will then automatically (in this example) slot the output into context.call["fragments"]["my-fragment-name"] instead of the default slot name "once". In the future we'll introduce more attributes with this syntax. Using this flexible mechanism for the fragment slot name, we can leave it up to the page template where to slot in what. ONCE, COMPONENT, and RENDER should support the fragment attribute. **Status:** fixed, but fix not verified yet. ### 1.9 Error page "Generated C++" hint prints a nonexistent path **File:** `src/linux_fastcgi.cpp:79` `render_request_failure` computes the hint as `path_join(BIN_DIRECTORY, SCRIPT_FILENAME) + ".cpp"`, but `path_join` returns an absolute child unchanged (`src/lib/sys.cpp:179`), while the compiler builds the real path by plain concatenation `BIN_DIRECTORY + src_path` (`src/lib/compiler.cpp:631-636`). SCRIPT_FILENAME is always absolute in the FastCGI path. **Failure:** every runtime failure page prints e.g. `Generated C++: /var/www/site/page.uce.cpp` — the BIN_DIRECTORY prefix is silently dropped and the path never exists (real file: `/var/cache/uce/work/var/www/site/page.uce.cpp`). **Fix:** export one helper from `compiler.cpp` that maps a source file to its generated artifact (`su->pre_path + "/" + su->pre_file_name` — the new `compiler_format_compile_failure` in this same changeset already computes it correctly) and use it in both places. **Status:** fixed, but fix not verified yet. ### 1.10 O(n²) and per-row deep copies in dtree_map / dtree_filter **File:** `src/lib/functionlib.cpp:182` Both helpers call `tree.is_list()` inside the per-element callback — `is_list()` (`src/lib/dtree.cpp:165`) walks the entire map, making the operation O(n²) — even though both already compute `is_list()` once before the loop. Additionally, `DTree::each` (`dtree.h:22`) takes the callback element by value (`DTree t`), deep-copying every subtree per iteration. **Failure:** mapping over a 1000-row `sqlite_query` result does ~1M key-validation checks plus a full deep copy of each row tree. **Fix:** hoist `is_list()` into a `bool` local reused in the callback; change `each()` to pass `const DTree&`. **Status:** fixed, but fix not verified yet. ### 1.11 Proactive compiler child can std::terminate on lock failure **File:** `src/lib/compiler.cpp:288` `compiler_with_registry_lock` now throws `std::runtime_error` when the registry lock file can't be opened (previously: warning + proceed unlocked). `run_proactive_compiler` (`src/linux_fastcgi.cpp:1270-1372`) calls `compiler_list_known_units` / `compiler_set_known_units` with no try/catch anywhere in the chain, so a transient failure (fd exhaustion, disk full) aborts the forked child via `std::terminate`. **Blast radius is small:** the proactive compiler runs in a forked child that the main loop respawns each iteration, and request-path callers are protected by the try/catch in `handle_complete`. Still, a catch-and-log around the proactive scan is cheap insurance. **Status:** fixed, but fix not verified yet. --- ## Part 2 — Reuse / duplication (plausible unless noted) ### 2.1 request_query_path() duplicates request_query_route() **File:** `src/lib/uri.cpp:311` `request_query_path()` copy-pastes `request_query_route()`'s (line 325) first-keyless-segment scan verbatim — two identical "find first &-part without =" loops plus `route_path_sanitize` calls that must be kept in sync. It also has **zero callers** in `src/` or `site/` (only a doc page references it). **Fix:** implement as `return request_query_route(context, default_path)["l_path"].to_string();`. **Status:** fixed, but fix not verified yet. ### 2.2 list_filter / list_map re-implement the generic filter / map templates **File:** `src/lib/functionlib.cpp:71` `StringList` is `typedef std::vector` (`types.h:52`), so the existing `filter` template (`functionlib.h:68`, declared ~10 lines above the new `list_*` declarations) already does exactly what `list_filter` does. Two filter implementations now live in the same module; behavior fixes must be applied twice. **Fix:** drop it and point the doc page at `filter`); same consideration for `list_map`. **Status:** fixed, but fix not verified yet. ### 2.3 Hand-rolled redirects instead of the redirect() helper **Files:** `site/examples/uce-starter/views/account/login.uce:13`, `logout.uce:6`, `profile.uce:8`, `site/demo/sqlite.uce:21-22` Four call sites inline `context.set_status(302, "Found"); context.header["Location"] = ...` instead of calling the existing runtime helper `redirect(String url, s32 code = 302)` (`src/lib/uri.cpp:414`). This replaced the single `app_redirect()` helper the changeset deleted. **Note — security aspect refuted (see 6.1):** headers are sanitized centrally on write-out, so this is a pure reuse nit, not a vulnerability. **Fix:** use `redirect(app_link("account/profile", context))` or restore one `app_redirect` wrapper. **Status:** fixed, but fix not verified yet. ### 2.5 Asset tag boilerplate repeated across ~a dozen files **Files:** `components/example/marketing_assets.uce:5`, `components/example/theme_assets.uce`, `components/gauges/assets.uce`, plus inline `` blocks in `components/theme/head.uce`, `components/data/widgets.uce`, `components/workspace/primitives.uce`, `views/dashboard.uce` About a dozen hand-written copies of the stylesheet/script tag pattern around `app_asset_url()`; a versioning or attribute change (defer/integrity) touches every file. The marketing and theme shims differ only in one CSS path. **Fix:** collapse the three shims into one parameterizable assets component. **Status:** fixed, but fix not verified yet. --- ## Part 3 — Simplification (plausible) ### 3.1 request_query_route() emits a derivable "rejected" flag **File:** `src/lib/uri.cpp:347` The route tree contains both `route["valid"]` and `route["rejected"]` where `rejected` is exactly `!valid`, and nothing in the codebase reads `"rejected"`. Redundant derivable state doubles the invariant surface — a future path that sets one flag without the other produces a route tree that lies. **Fix:** drop the `"rejected"` key; callers needing it can write `!route["valid"].to_bool()`. **Status:** fixed, but fix not verified yet. ### 3.2 Starter router carries write-only diagnostic state **File:** `site/examples/uce-starter/index.uce:38` The router rewrite adds candidate `kind`/`matched` fields and `context.call["route"]["candidates"]/["resolved"]/["missed"]`, none of which is read by any component, theme, or test. It also uses `dtree_filter` to `file_exists`-stat every candidate after the first match is already found. ~55 lines plus a builder helper replace what the deleted `app_resolve_view` did with three early-return `file_exists` checks. **Fix:** remove unused parts. **Status:** fixed, but fix not verified yet. ### 3.4 Single-link shim components where a direct ONCE block suffices **File:** `site/examples/uce-starter/components/example/marketing_assets.uce:8` (and `theme_assets.uce`) These are single-`` shim files with empty COMPONENT bodies, invoked via `print(component(...))` of an empty string. Routed views are mutually exclusive per request, so the cross-unit dedup the shims provide can never trigger; they exist only to host one ONCE line at the cost of two extra files and an indirection on every view. `dashboard.uce:3-6` already demonstrates the simpler form (ONCE block directly in the view). `gauges/assets.uce` is the justified case (multiple sibling components per page) and can stay. **Status:** fixed, but fix not verified yet. ### 3.5 Compile-failure artifact file is self-referential **File:** `src/lib/compiler.cpp:847` `compile_shared_unit()` overwrites `su->compiler_messages` with the formatted failure report, then writes that report into `compile_output_file_name` — the same artifact the report's own "Compile output:" line (`compiler_format_compile_failure`, line 800) points readers at. No copy of the raw, unformatted compiler output survives for tooling to parse. **Fix:** keep raw messages as the stored/recorded artifact content and format only at the print/response boundary. **Status:** fixed, but fix not verified yet. --- ## Part 4 — Efficiency (plausible unless noted) ### 4.1 QUERY_STRING parsed twice per request **File:** `src/linux_fastcgi.cpp:744` `prepare_request_body_maps` calls `request_populate_context_params` (which splits QUERY_STRING on `&` + uri-decodes inside `request_query_route`) and then `parse_query(QUERY_STRING)` re-splits and re-decodes the identical string on the next line. Runs on every HTTP request, CLI invocation, and websocket event. Route params are also computed eagerly for requests that never read `ROUTE_*`/`BASE_URL`. **Fix:** parse QUERY_STRING once into `request.get` first and derive the route token from that single pass, or populate the route params lazily. **Status:** fixed, but fix not verified yet. ### 4.2 Repeated normalization in request_populate_context_params **File:** `src/lib/uri.cpp:351` Per request: `route_path_normalize` runs three times on the same path (directly, inside `route_path_sanitize`, and inside `route_path_is_safe`), `request_script_url` is computed twice (for SCRIPT_URL and again inside `request_base_url`), and `route_path_normalize` (line 255) strips slashes via `path = path.substr(1)` in a while loop — O(n²) copies per slash run. **Fix:** normalize once and pass the normalized string down; compute script_url into a local used by both params; replace the substr loops with `find_first_not_of("/")` / `find_last_not_of("/")` and a single substr. **Status:** fixed, but fix not verified yet. ### 4.4 collect_rows rebuilds column-name strings for every row **File:** `src/lib/sqlite-connector.cpp:118` For R rows × C columns the row loop does R×C `sqlite3_column_name` calls plus R×C String heap constructions and map inserts keyed on the fresh string, though column names are invariant across rows. A 10k-row, 8-column result builds 80k redundant name strings per query. **Fix:** build a `std::vector names(column_count)` once before the step loop and index it inside (`row[names[i]]`). **Status:** fixed, but fix not verified yet. ### 4.5 bind_params copies the params map twice and binds SQLITE_TRANSIENT **File:** `src/lib/sqlite-connector.cpp:95` `SQLite::query` takes `StringMap` by value and passes it by value again to `bind_params`, then binds with `SQLITE_TRANSIENT`, forcing SQLite to memcpy each value a third time — even though the copied map outlives the statement (it lives until after `sqlite3_finalize`). **Fix:** pass `const StringMap&` through `query()`/`bind_params` and bind with `SQLITE_STATIC` (or at least drop the two by-value map copies). **Status:** fixed, but fix not verified yet. ### 4.6 Per-request connection opens re-run pragmas; busy_timeout set twice **File:** `src/lib/sqlite-connector.cpp:15` `connect()` calls `sqlite3_busy_timeout(5000)` and then `apply_default_pragmas` runs `PRAGMA busy_timeout = 5000` again (pure duplicate). `PRAGMA journal_mode = WAL` is persistent in the database file but re-issued on every open. Because cleanup closes all handles at request end, a page like `site/demo/sqlite.uce` pays `sqlite3_open_v2` + 4 pragmas + `CREATE TABLE IF NOT EXISTS` on every request. **Fix:** drop the redundant busy_timeout pragma; cache open connections per worker keyed by path across requests (resetting state at request end) so pragmas and schema checks run once per worker. **Status:** fixed, but fix not verified yet. --- ## Part 5 — Altitude / design (plausible unless noted) ### 5.3 Test suites hand-registered in three parallel lists — already drifting **Files:** `site/tests/index.uce:15`, `tests/plugins/uce_site_suite.py` A test page must be registered in three places: the `site/tests/*.uce` file itself, a `site_tests_card()` line in `index.uce`, and a tuple in `uce_site_suite.py`. The new `sqlite.uce` was added to all three by hand. The lists have already drifted: `site/tests/call_helpers.uce` exists on disk but appears in neither `index.uce` nor any plugin list, and `security_headers.uce` is covered only by the security-smoke plugin, not the index cards — new suites can silently fall out of the dashboard and/or CI. **Fix:** enumerate `site/tests/*.uce` with ls()/glob in both `index.uce` and `uce_site_suite.py`, with title/tags metadata declared once (in the test page or a single shared manifest). **Status:** fixed, but fix not verified yet. ### 5.4 compiler_developer_hints() couples the runtime to clang's English message text **File:** `src/lib/compiler.cpp:778` The hint table pattern-matches hardcoded English clang diagnostic substrings ("no member named", "expected ';'", ...). A clang version bump, a switch to gcc (COMPILE_SCRIPT is user-configurable server config), or localized diagnostics silently degrades every hint to the generic fallback, and each new error class means hand-extending an if-chain in runtime C++. **Fix:** the excerpt mechanism added in the same change (`compiler_format_compile_failure`'s source/generated excerpts + artifact paths) already carries the diagnostic value; make the hints a data-driven table or drop them in favor of the excerpts. **Status:** fixed, but fix not verified yet. ### 5.5 Generated-artifact path computed by hand in two places **File:** `src/linux_fastcgi.cpp:79` vs `src/lib/compiler.cpp:631-636` The deeper-fix framing of 1.9: the moment the compiler's artifact layout or naming changes (hashing, per-config subdirs), any hand-recomputed path goes stale. One exported source-file → artifact-path helper in `compiler.cpp`, used by both the compile-failure formatter and the runtime failure page. **Status:** fixed, but fix not verified yet. --- ## Part 7 — Verification of the fixes (review pass, 2026-06-11) All fixes above were re-reviewed against commit `7f75765` and, where possible, verified live against the dev server (network suite: 21/21 public tests pass). > **Status update (later the same day):** 7.1–7.8 are fixed, built, deployed, > and verified — full suite including internal tests passes 76/76, once-init > renders again, dashboard/workspace ship their ONCE assets (now asserted by > the smoke suite), and a new `uce_demo_smoke.py` plugin covers every > `site/demo/*.uce` page. Unit ABI bumped to 3 (the @fragment prelude now > instantiates the shared `UceFragmentCapture` from `functionlib.h`). Two notes: > > - Fixing 7.3 exposed a latent bug: on a failed `mysql_real_connect`, the old > code left `connection` pointing at the freed handle, and `error()` read the > error message out of freed memory — that's the only reason the services > page's "skip when no MySQL" branch ever worked. `connect()` now nulls the > handle and signals failure via `_preload_next_error_code`, and `query()` / > `error()` guard against a null connection (clean error message instead of > the SIGSEGV this otherwise caused). > - The 7.8 nit about the `assets.uce` lambda taking `DTree` by value is > withdrawn: `DTree::to_string()` is non-const, so a `const DTree&` parameter > cannot call it. Making the DTree read accessors const-correct would be the > real fix and is a separate, larger change (worth doing before the WASM > DTree C ABI freezes the surface). ### Verified clean **1.1** (double-quoted attr + `html_escape` escapes `'`, regression test), **1.4** (multi-statement rejection is comment/whitespace-tolerant, test), **1.9/5.5** (`compiler_generated_cpp_path` agrees with `setup_unit_paths`; confirmed live — the error page now prints the real `/tmp/uce/work/...` path), **1.10** (`each`/`push` take `const DTree&`, `is_list()` hoisted), **1.11** (both the initial scan and the loop body are wrapped), **2.1, 2.2, 2.3, 2.5/3.4, 3.1, 3.2, 3.5, 4.1, 4.2, 4.4, 4.5** (SQLITE_STATIC is safe: the params map outlives the statement), **5.3, 5.4**. ### 7.1 REGRESSION (live): ONCE output captured into a slot nobody prints **Files:** `src/lib/compiler-parser.cpp` (default slot `once`), `components/theme/head.uce:23` `ONCE` now defaults to `@fragment once`, but no theme prints `context.call["fragments"]["once"]`. Verified live: the dashboard page serves without `views/dashboard.css`, the widgets page without any ag-grid assets — the ONCE blocks in `views/dashboard.uce`, `components/data/widgets.uce`, and `components/workspace/primitives.uce` are silently swallowed. The suite passed anyway because it only checks body markers, not asset tags. **Fix:** print `fragments["once"]` in `head.uce` (next to `fragments["head"]`), or convert those three ONCE blocks to `@fragment head`. Also worth adding an asset-tag assertion to the dashboard smoke test so this can't regress silently. ### 7.2 REGRESSION (live): fragment rewriter fires on literal text and duplicates lines **File:** `src/lib/compiler-parser.cpp` (`compiler_rewrite_fragment_attributes`) The rewriter is a line-based pre-pass with no literal awareness, so any literal HTML line starting with `ONCE(` / `RENDER(` / `COMPONENT(` is treated as an entry point. Verified live: `/demo/once-init.uce` is currently a compile error ("extraneous closing brace") because the heading text `ONCE() and INIT()` matches. Two compounding defects: - When the entry line has no `{`, the code scans forward across arbitrary lines to inject the prelude into whatever `{` it finds next. - If no `{` is ever found, the inner loop emits the remaining lines but never advances `i`, so the outer loop emits them all a second time. **Fix (minimal):** only treat a match as an entry point when the `{` is on the entry line or the immediately following non-`@`-attribute line, and advance `i = j` when the forward scan exhausts. **Fix (right altitude):** literal tracking lives in the char-wise pass — fold fragment rewriting into it eventually; `compiler_rewrite_named_render_syntax` shares the same blind spot. ### 7.3 Dangling pointers: stack-allocated connectors register `this` but never unregister **Files:** `src/lib/mysql-connector.cpp:40`, `src/lib/sqlite-connector.cpp` (connect paths), exposed by `site/tests/services.uce:100` `connect()` now registers `this` in `context->resources.*_connections`, but neither class has a destructor, so a stack-allocated connector (`MySQL mysql; mysql.connect(...)` — exactly what `services.uce` does) leaves a dangling pointer behind when it goes out of scope. End-of-request cleanup then calls `disconnect()` on dead stack memory. Currently latent only because the dev host has no reachable MySQL server (the test skips). **Fix:** add `~MySQL() { disconnect(); }` and `~SQLite() { disconnect(); }` — `disconnect()` already unregisters (and forgets the worker cache entry), so destructors make stack usage safe and cleanup only ever sees live heap objects. ### 7.4 Leaks on the failed-connect path; `worker_cache` set before the cache insert **File:** `src/lib/sqlite-connector.cpp:267` (`sqlite_connect`), `mysql-connector.h:38` (`mysql_connect`) Registration happens only when `connect()` succeeds, so on failure the factory returns a wrapper that is in no registry: `request_cleanup_delete = true` never fires and the wrapper leaks per failed connect. Worse for sqlite: if `sqlite3_open_v2` succeeds but `apply_default_pragmas` fails, the wrapper has `worker_cache = true` but is *not* in the cache map — cleanup skips both the close and the delete, leaking an open connection per occurrence. **Fix:** set `worker_cache = true` only at the point of cache insertion, and register the wrapper with the request on the failure path too (the caller still needs it alive to read `sqlite_error`). ### 7.5 Worker-cached sqlite connections can carry an open transaction across requests **File:** `src/lib/sqlite-connector.cpp` (`cleanup_sqlite_connections`) The end-of-request reset clears stats/error state but not transaction state. A page that runs `BEGIN` and then faults (or simply forgets `COMMIT`) leaves the cached connection mid-transaction; the next request on this worker inherits it, holding the WAL write lock indefinitely. **Fix:** in the cleanup reset branch, `if(!sqlite3_get_autocommit(handle)) sqlite3_exec(handle, "ROLLBACK", ...)`. ### 7.6 split_http_headers: request lines containing `:` are misclassified as headers **File:** `src/lib/functionlib.cpp:745` The 1.3 fix keys request-line detection on "first non-blank line contains no colon". A legal request line like `GET /page.uce?t=12:30 HTTP/1.1` (colon in the query string, absolute-form URIs, IPv6 hosts) now parses as a header and `REQUEST_METHOD` stays empty — this regresses the direct-HTTP server path for real-world URLs. **Fix:** classify as header only when the colon appears before the first space/tab (`colon != npos && colon < first_whitespace`); otherwise it is the request line. Add `GET /x.uce?t=12:30 HTTP/1.1` to the core.uce checks. ### 7.7 Backtrace capture now mallocs inside the signal handler **File:** `src/linux_fastcgi.cpp:108` The 1.5 fix captures on the correct (faulting) stack, but `capture_backtrace_string` calls `backtrace_symbols` (malloc) and builds `String`s inside the SIGSEGV handler. If the fault itself is heap corruption or happens inside malloc, the handler deadlocks on the heap lock or double-faults — the worker dies with no graceful 500 at all, which is worse than a shallow trace. Acceptable tradeoff for a diagnostics path, but the standard shape is cheap: **Fix:** in the handler, only `backtrace()` into a `static void* frames[32]` (no allocation) and stash the depth; call `backtrace_symbols` + string-building in `handle_complete` after the `siglongjmp`. Call `backtrace()` once at worker startup so libgcc's lazy init doesn't allocate on first use either. ### 7.8 Minor / cleanup - `request_populate_context_params` (`src/lib/uri.cpp:367`) has zero callers now — delete it or make it a two-line wrapper over the `_from_route` variant (its body is a verbatim duplicate). - MySQL positional-`?` check runs twice on the params path (params overload + the re-check inside `query(String)` after substitution; substituted values are always quoted by `escape()`, so the inner check alone suffices). - Both unquoted-`?` scanners treat `?` inside SQL comments (`-- ?`, `/* ? */`) as positional and reject the query — worth a code comment as a known limitation. - `MySQL::error()` consumes/clears state in the new `statement_info` branch but not in the errno-switch branch, and `SQLite::error()` never consumes — pick one semantic. - `theme/assets.uce` lambda still takes `DTree item` by value; make it `const DTree&` to match the new `each()` signature. - `__UceFragmentCapture` is regenerated inline in every captured entry point; define the struct once in the runtime headers and have the prelude emit only the instantiation line (less generated-code surface — also smaller wasm unit modules later, where the capture logic belongs in the core module). - `uce_site_suite.py` does `from run_network_tests import TestFailure` while the runner executes as `__main__` — the import creates a second module instance, so the runner's `except TestFailure` never matches the plugin's class and missing-manifest cases report as "unexpected error" instead of a clean failure (still red, just noisier). Compare by name or pass a fail helper through the context. - Default `run_network_tests.py` executes only the public set (21 cases); sqlite/services/io/zip/tasks are internal-tagged and need the internal run to count as a gate. Worth wiring both into whatever becomes the WASM Phase 5 parity gate, plus a demo-pages smoke plugin — both live regressions above (7.1, 7.2) sit exactly in the coverage gaps (asset tags, `demo/`). ### 7.9 NEW: lists with ten or more entries iterate in lexicographic, not numeric, order **File:** `src/lib/dtree.cpp` (`DTree::each`), found while documenting the accessors (2026-06-11) `DTree` stores list entries as string keys in a `std::map`, so iteration order is lexicographic: `"0", "1", "10", "11", "2", ...`. Every consumer of `each()` inherits this — `dtree_map`/`dtree_filter`/`dtree_values` re-push in that order and therefore *scramble* any `push()`-built list with ≥ 10 entries, and a `sqlite_query()` result with ≥ 10 rows renders rows 10+ before row 2 when iterated. `is_list()` still reports true because the key *set* is sequential. **Fix options:** iterate numerically in `each()` when `is_list()` is true (cheapest, fixes all consumers at once); or use a numeric-aware comparator in the map type. Documented honestly on the `each` doc page in the meantime. This also needs deciding before the WASM DTree C ABI freezes iteration-order semantics. **Status:** fixed — and the regression test exposed the deeper layer: `is_list()` itself validated keys against *map iteration order*, so it returned false for any list with ≥ 10 entries. That silently flipped `push()` out of list mode on the 12th element and made the json/yaml encoders serialize big lists as objects instead of arrays. `is_list()` now does an order-independent check (n unique canonical index keys with max n-1), index keys require canonical form (`"1"`, not `"01"`), and `each()` iterates lists in numeric index order, matching the encoders. Regression test with a 12-entry list in core.uce covers `each`, `dtree_values`, and `dtree_map`.