# SX Hosts Review — findings (hosts axis)

Reviewer lane: per-target host implementations + FFI seam.
Targets: hosts/ocaml (kernel, VM/JIT, epoch protocol, server env, HTTP path),
hosts/javascript, hosts/python, hosts/native, WASM browser kernel, web/ adapters.

Status: COMPLETE — all 7 verification agents done (OCaml VM/JIT, sx_server epoch/env/HTTP,
JS host, Python host, web-FFI seam, cross-host parity, native/WASM). OCaml run_tests
baseline + `--jit` differential both done. The highest-severity findings were reproduced
live and self-verified by the lead reviewer (marked "self-verified").

Live HTTP-path verification (follow-up): booted `sx_server --http` on free ports (3 bounded runs,
own PIDs only — never pkill) and drove concurrent load. This UPGRADED three suspected HTTP-race
items to CONFIRMED — **S1** (multi-Domain render pool crashed intermittently under concurrent load;
empty responses, no exception, OOM ruled out at ~292 MB peak RSS), **S4** (routing failures served
as HTTP 200 and cached: cold 2.02s → warm 0.0005s), and **S5** (cache key ignores cookies/query —
three different `session=` cookies returned one identical cached body). **S2/S3** stay SUSPECTED:
their code paths were verified live but the stock static-docs app has no request-varying full-page
render to exhibit a visible symptom. Logs: /tmp/sx-review/http-server{,2,3}.log.

Finding tally: ~65 CONFIRMED (incl. S1/S4/S5 upgraded via live repro) + ~13 SUSPECTED
(incl. S2/S3) + 2 positive/informational. Full detail below.

TOP-LINE (most severe; CONFIRMED unless noted):
- **Production serving-JIT silently miscompiles** (J1): `(-> …)` in argument position and
  user-macro args (J3) yield wrong values; JIT-fallback double-applies side effects (J2). The
  http server registers the JIT hook UNCONDITIONALLY (sx_server.ml:4163) — live on sx.rose-ash.com.
- **One malformed line kills the whole sx_server process** (C1) — unguarded command-channel parse;
  a non-ASCII byte does the same (C1b).
- **JS bundle is hollow** (C0a): render/engine/router/signals/all adapters transpile to 0 bytes
  because the transpiler doesn't recurse into `define-library`; JS conformance is 2490 failing (C0b).
- **Python↔OCaml boundary + parser diverge**: NUL/escape corruption (C25), boundary validation is a
  permanent no-op (C24), bridge desync is unrecoverable (S-bridge).
- **Cross-host primitive parity is broken** in ~30 confirmed ways (P1–P12): OCaml lossy float wire,
  mixed-number sort, `into` needs a bridge; JS lenient-coercion cluster + missing equal?/eq?.
- **HTTP serving path breaks under concurrency (live-verified on a booted server):** the
  multi-Domain render pool crashed intermittently under concurrent load — a genuine data race on
  unsynchronized cross-Domain Hashtbls, OOM ruled out (S1, CONFIRMED); routing failures are served
  as HTTP 200 and cached indefinitely (S4, CONFIRMED); the response cache key ignores cookies/query,
  so it would serve one user's response to another on any cookie/auth-varying app (S5, CONFIRMED).
  Still SUSPECTED: per-request globals read by queued workers (S2) and `expand-components?` removed
  on the shared env by AJAX renders (S3) — both code paths verified live, but need a request-varying
  app (not the static docs site) to exhibit a visible symptom.

Baseline (no JIT): `run_tests.exe` → **5762 passed, 274 failed**. 273 failures are `hs-*`
hyperscript suites (in-progress guest project — baseline caveat, not a host bug; note the
`can-map-an-array` / "map with block" line prints with a blank suite label due to C9 but belongs
to a hyperscript compat suite). The single genuine non-hyperscript host failure is C2 (r7rs
string->number shadow). `--jit` run: 5760p/276f, with 2 deterministic JIT-only divergences (J9). Logs:
/tmp/sx-review/ocaml-run_tests-baseline.log, /tmp/sx-review/ocaml-run_tests-jit.log

Numbering note: findings carry mnemonic prefixes (J* serving-JIT, C* OCaml/kernel/protocol,
JS* JavaScript host, P* cross-host parity, S* suspected). Order within CONFIRMED is roughly
severity-desc but read the whole section — prefixes were assigned as agents reported, not in rank order.

---

## CONFIRMED (most severe first)

> **Serving-JIT correctness cluster (J1–J8).** The OCaml `http_mode` (the production
> HTTP server behind sx.rose-ash.com) registers the JIT hook **unconditionally**
> (sx_server.ml:4163) — it is NOT gated by SX_SERVING_JIT (that env var only gates the
> epoch/persistent serving mode, sx_server.ml:5011). So every named top-level lambda
> invoked during page rendering is JIT-compiled on first call, and the divergences below
> are reachable in production for any rendered lambda that hits these patterns. The default
> `run_tests --jit` uses a *different, low-hit-rate* JIT path and surfaced only 2 divergent
> tests (see J9); the serving-JIT path is where the silent-wrong-value bugs live. All J1–J8
> reproduced live at the kernel level.
>
> **IMPORTANT reconciliation:** there is a belief on record (project memory / a comment at
> sx_server.ml:5001-5011) that "serving-JIT is OPT-IN via SX_SERVING_JIT=1, default OFF." That gate
> applies ONLY to the epoch/persistent serving mode. The actual HTTP server (`http_mode`) calls
> `register_jit_hook env` UNCONDITIONALLY at sx_server.ml:4163 with no env-var check. So anyone who
> concludes "JIT is off in production, therefore J1–J8 don't apply" is wrong — the http server that
> serves sx.rose-ash.com runs with JIT on. Confirmed by reading the http_mode entry and by the live
> boot logs (`[jit] … compile in …s` lines during every page render).

### J1. `->` threading miscompiles under serving-JIT — silent wrong value + duplicated side effects
- severity: critical
- confidence: CONFIRMED (reproduced live, self-verified)
- where: lib/compiler.sx:934-951 (compile-thread-step); stale twin hosts/ocaml/lib/sx_compiler.ml:178
- what: For `(-> v f g)` with non-empty rest-forms, compile-thread-step compiles `call-expr`
  (pushing a value that's never popped) then recurses with `call-expr` re-embedded, compiling
  it AGAIN. Each non-final step is evaluated once per remaining step (side effects duplicated)
  and leaves a stack residue. When the `->` is argument ≥2 of a primitive call, CALL_PRIM pops
  the residue instead of the sibling arg → silently wrong value. In arg position of a user call
  the slot misaligns → "not callable" → self-heals via CEK fallback.
- repro: with lib/compiler.sx loaded, serving-JIT on:
  `(+ 1 (-> 2 inc inc))` on CEK → `5`; JIT'd `(define (t1) (+ 1 (-> 2 inc inc)))` called → `7`
  (self-verified: `(t1 ×5)` → `(7 7 7 7 7)`; same source with JIT off → 5). bytecode-inspect
  shows the residue CONST/CALL_PRIM that's never popped.

### J2. JIT-fallback re-runs the entire call on the CEK → side effects execute twice (persist/counter/emit double-apply)
- severity: high
- confidence: CONFIRMED (reproduced live, self-verified)
- where: sx_server.ml:1630-1638, 1674-1680 (catch-all `| e -> … l_compiled <- jit_failed; None`);
  sx_vm.ml:461-464, 490-493 (Component/Island `with _ -> cek_call_or_suspend` re-run)
- what: Any exception during VM execution of a JIT'd lambda (data-first HO form, macro call,
  raise/error, call/cc, undefined global) marks it jit-failed and re-runs the WHOLE call under
  the CEK. Mutations performed before the failure point are applied twice. A code comment claims
  idempotence "for the host's durable reads" — but it is NOT idempotent for writes (persist
  appends, counters, emits). The Component/Island path catches even VmSuspended, so a component
  that performed IO can have that IO re-issued.
- repro (self-verified): `(do (define fb-count 0) (define (fb) (set! fb-count (+ fb-count 1))
  (map (list 1 2) (fn (x) x))) (fb) fb-count)` → serving-JIT: `2` after ONE call; JIT off: `1`.

### J3. JIT'd functions eagerly evaluate arguments of user-macro calls before falling back
- severity: high
- confidence: CONFIRMED (reproduced live, self-verified)
- where: lib/compiler.sx compile-list/compile-call (no macro-expansion pass); sx_vm.ml:497 (vm_call has no Macro case)
- what: The compiler treats a user-macro call as an ordinary call — compiles and evaluates ALL
  arguments, then vm_call raises "not callable: <macro>" and the hook re-runs on the CEK. Code a
  macro guards from evaluation executes once in the VM.
- repro (self-verified): `my-unless` macro that expands `true` → skip body; JIT'd `(um)` that
  does `(my-unless true (set! hit (+ hit 1)))` → serving-JIT: `hit = 1`; JIT off: `hit = 0`.

### J4. VM component calls misparse positional string args equal to a param name
- severity: high
- confidence: CONFIRMED (agent-reproduced)
- where: sx_vm.ml:144-156 (parse_keyword_args), fed by compile-expr lowering `:kw` to its string name
- what: The compiler erases the keyword/string distinction (keywords become their string names in
  the constant pool), so parse_keyword_args treats ANY string matching a declared param as a
  keyword marker and consumes the next value → silent wrong props/children, no error, no fallback.
- repro: `(defcomp ~box2 (&key title &rest children) (list "BOX" title children))`;
  `(~box2 :title "T" "title" "x")` CEK → `("BOX" "T" ("title" "x"))`; JIT'd → `("BOX" "x" ())`.

### J5. Specialized opcodes freeze primitive semantics at compile time — redefinition ignored by JIT'd code
- severity: medium
- confidence: CONFIRMED (agent-reproduced)
- where: lib/compiler.sx:1061-1077 (specializes + - * / = < > cons 2-arg, not len first rest 1-arg → opcodes 160-172); sx_vm.ml:804-914
- what: CALL_PRIM resolves through vm.globals at run time (redefinitions respected), but the 12
  specialized opcodes inline the original semantics. A function JIT'd before `(define + …)` keeps
  the old `+` forever while the CEK sees the new one → CEK/JIT divergence.
- repro: `(define (q) (+ 1 2))` JIT'd → 3; `(define + (fn (a b) 999))`; `(q)` → 3 (stale OP_ADD)
  vs fresh CEK → 999.

### J6. Redefining a primitive the compiler itself uses poisons subsequent JIT compilation
- severity: medium
- confidence: CONFIRMED (agent-reproduced)
- where: sx_vm.ml:732-766 (CALL_PRIM globals-first lookup) + the compiler running as VM bytecode
- what: The SX compiler's own bytecode calls +, first, nth… through CALL_PRIM → globals. After a
  user redefines one, every subsequent JIT compile computes with the user's fn — observed as
  corrupted pools/offsets ("CONST index 999 out of bounds"). Currently every hit errors → CEK
  fallback (correct), but a redefinition returning plausible numbers could emit structurally-valid
  wrong bytecode silently.

### J7. Data-first HO forms `(map coll fn)` / `-> coll (map fn)` always fail under the VM → permanent deopt (+ J2 double-effect on first call)
- severity: medium (perf/coverage; correctness preserved by fallback, but triggers J2)
- confidence: CONFIRMED (agent-reproduced)
- where: sx_primitives.ml:1584-1637 (map/filter/reduce/some/every?/for-each accept fn-first only); CEK ho-swap-args has no VM counterpart
- what: Every JIT'd function using the documented data-first arg order (or threading a collection
  into an HO form) errors on first VM call and runs on the CEK forever. Results correct, but never
  benefits from JIT and the first call incurs the J2 double-side-effect hazard.

### J8. Local bindings shadow HO-form names in the VM but not the CEK
- severity: medium-low
- confidence: CONFIRMED (agent-reproduced)
- where: compiler compile-call scope resolution (locals win) vs CEK HO-form dispatch (form wins)
- what: `(let ((map (fn (a b) 42))) (map 1 2))` → JIT'd: `42`; CEK inline: error "rest: 1 list arg"
  (dispatches as HO map). Divergent semantics for the 7 HO-form names.

### J9. `run_tests --jit` diverges from interpreter on 2 hyperscript scoping tests (deterministic)
- severity: medium
- confidence: CONFIRMED (self-verified, 3× each mode, deterministic)
- where: default run_tests --jit path (guard-installing lambdas interpret-only); hs-upstream-core/scoping suite
- what: `hs-upstream-core/scoping > behavior scoping is isolated from other behaviors` and
  `… from the core element scope` PASS interpreter-only but FAIL under `--jit` with an empty
  result (`Expected 20, got `). Deterministic across 3 runs each mode. This is the low-hit-rate
  default-JIT path (distinct from serving-JIT J1–J8), so it's a second, independent JIT/CEK
  divergence surface. (Baseline 5762p/274f → --jit 5760p/276f; the hs-runtime-e2e "def function"
  entry fails in BOTH and is not a JIT regression.)

### J10. Stale/incomplete Sx_compiler stub still bound as `compile`; JIT hook registered even if lib/compiler.sx fails to load
- severity: medium-low
- confidence: CONFIRMED (binding + staleness) / SUSPECTED (reachability)
- where: sx_server.ml:1315 (bind), comment at :5014 ("incomplete stub"); hosts/ocaml/lib/sx_compiler.ml (May-7, no POP after LOCAL_SET, no rest-arity/guard/match/scope)
- what: If lib/compiler.sx fails to load in serving-JIT mode the code warns but still registers
  the JIT hook, so jit_compile_lambda would compile with the stub whose missing LOCAL_SET/POP
  discipline can silently corrupt stacks (let-in-argument-position) rather than merely erroring.

### J11. JIT debug/aux paths diverge from the real VM (vm-trace, code_from_value locals-scan, resume-error handler, threshold)
- severity: low (tooling / edge)
- confidence: CONFIRMED (by reading; J-threshold self-observed)
- where: sx_vm.ml:1436-1632 (trace_run), :230-252 (code_from_value), :990-1005 (resume handler); sx_server.ml:1640-1656 (hook ignores jit_threshold)
- what: (a) vm-trace resolves CALL_PRIM via get_primitive BEFORE globals (real run does opposite →
  user redefs invisible in trace), pushes Nil for non-NativeFn callees, inline ops 160-175 handle
  only Number, OP_EQ uses OCaml structural `=` → traces can show different values than execution.
  (b) code_from_value's max-local scan mis-walks operands for opcodes 8/33/35/144/51 (masked by +16
  headroom). (c) resume-path error handler delivers `String msg` instead of the raised condition
  value (only reachable via extension opcodes / hand-built bytecode). (d) the CEK-side hook ignores
  jit_threshold=4 and compiles on the very FIRST call (why J1's t1 was already wrong on call #1);
  lambdas defined inside let-wrapped library modules never JIT at all (e.g. all of lib/highlight.sx).

### J12. (verification, positive) perform/resume stack-misalignment from repro_jit_resume.ml is FIXED
- confidence: CONFIRMED — all 9 repro cases (direct/multiframe/map-callback/pending_cek/reduce/
  nested-map perform) produce expected values; the "drop all-but-first in map/for-each" hazard from
  the memory note does NOT reproduce at this level. Also verified matching (no divergence): closures
  with set! on captured locals, &rest under/over-application, define in do/let, named let, letrec,
  TCO + non-tail at 100k, quasiquote unquote/splice incl nested, dict literals, cond/case/when
  multi-expr, and/or, string/arith edge prims, keyword equality, apply, guard (correctly JIT-excluded),
  call/cc fallback, map over rest/filter/append lists, CEK↔VM global visibility both ways.

### C0a. JS transpiler is blind to `define-library` — the browser/CI bundle is hollow (render/engine/router/signals/all adapters = 0 bytes)
- severity: critical
- confidence: CONFIRMED (reproduced live, self-verified — fresh build)
- where: hosts/javascript/platform.py:15-35 (extract_defines), bootstrap.py:205-226
- what: extract_defines only extracts top-level `(define …)` / `register-special-form!`. The
  spec+web files were since wrapped in R7RS `(define-library … (begin …))` (spec/render.sx,
  web/adapter-html/-dom/-sx.sx, web/engine.sx, web/router.sx, spec/signals.sx, web/deps.sx,
  web/page-helpers.sx, web/orchestration.sx, web/web-signals.sx, lib/freeze.sx), so those files
  now transpile to ZERO bytes. The bundle still references the missing functions (renderToHtml
  in its own public API; dom-* guarded by `typeof` checks that silently skip), so rendering,
  signals, router, engine are simply ABSENT. spec/content.sx no longer exists and is silently
  skipped. This is the JS host's single biggest defect and reframes its status.
- repro (self-verified): `python3 hosts/javascript/cli.py --output <scratch>/verify-js.js` then
  per-section byte counts → render/adapter-html/adapter-sx/adapter-dom/engine/orchestration/deps/
  page-helpers/router/signals/freeze/content all = 6 bytes (marker only); evaluator 140KB,
  signals-web 99KB, boot 14KB, web-forms 8KB, parser 12KB survive (not define-library-wrapped).

### C0b. `node run_tests.js` fails 2490/5086 — the JS conformance/CI gate is structurally red
- severity: critical
- confidence: CONFIRMED (reproduced live, self-verified)
- where: hosts/javascript/run_tests.js; .gitea/run-ci-tests.sh (CI gates on this)
- what: `node hosts/javascript/run_tests.js` → **2596 passed, 2490 failed** (exit 1); `--full`
  → 2453p/3203f. A fresh rebuild produces identical failures — structural, not stale artifact.
  Same corpus on OCaml → 5762p/274f. Failure signature: 1190× `Undefined symbol: dom-set-inner-html`,
  538× hs-to-sx*, 229× `renderToHtml is not defined`, 13× `makeRtd is not defined` — the hollow
  bundle (C0a) plus a runner that never learned to load the newer lib deps (parser-combinators,
  gql, hyperscript) the OCaml runner loads. Memory's "957/957 standard, 1080/1080 full" is stale.
  The JS host is alive as a CI/test host (sx-build-all.sh, run-tests.sh, .gitea CI) but DEAD as a
  served artifact (the live shell injects wasm/sx_browser.bc.wasm.js, not scripts/sx-browser.js) —
  so this drift breaks CI/conformance, not visitors.
- repro (self-verified): `timeout 400 node hosts/javascript/run_tests.js` → `Results: 2596 passed, 2490 failed`.

### C1. Malformed command line crashes the whole persistent/site sx_server process
- severity: critical
- confidence: CONFIRMED (reproduced live)
- where: hosts/ocaml/bin/sx_server.ml:5048 (persistent mode) and :4950 (site_mode)
- what: The dispatch loop calls `Sx_parser.parse_all line` outside any `try`; the
  enclosing handler only catches `End_of_file`, so `Sx_types.Parse_error` from one
  malformed line kills the entire process (the shared command channel used by
  bridges/conformance runners). site_mode has the identical unguarded parse.
- repro: `printf '(epoch 2)\n(eval "(+ 1 2"\n(epoch 3)\n(eval "99")\n' | timeout 60 hosts/ocaml/_build/default/bin/sx_server.exe`
  → `Fatal error: exception Sx_types.Parse_error("Unterminated list")`; subsequent
  commands never run. Same with a plain-garbage line (`not an s-expr ]]] {{{`).

### C10. Served browser JIT compiler is one fix behind lib/compiler.sx — the HO-loop desugar parity fix never shipped to the browser
- severity: high
- confidence: CONFIRMED (drift) / SUSPECTED (user impact)
- where: shared/static/wasm/sx/compiler.sx + compiler.sxbc vs lib/compiler.sx:390-443
- what: Commit 921db09f (2026-06-30, "jit: HO-loop desugar + --jit == CEK parity") added
  map/filter/reduce/for-each lambda-inlining to lib/compiler.sx, but the copy served to
  the browser (content from 59ac51a8, 2026-06-29) lacks all 54 lines; the .sxbc was
  rebuilt later (1e2ff387, Jul 2) but from the stale source. The browser JIT therefore
  misses the exact parity fix for the known "map/for-each drops all-but-first" bug class
  fixed server-side.
- repro: `diff lib/compiler.sx shared/static/wasm/sx/compiler.sx | head` → `390,443d389`;
  `grep -c "_hml" lib/compiler.sx shared/static/wasm/sx/compiler.sx` → 6 vs 0.

### C11. Client reads module-manifest.sx that nothing generates anymore — stale lazy-dep graph
- severity: high
- confidence: CONFIRMED
- where: consumer hosts/ocaml/browser/sx-platform.js:575 (fetches `sx/module-manifest.sx`);
  generator hosts/ocaml/browser/compile-modules.js:504 (writes `module-manifest.json` only)
- what: `loadWebStack()` drives client module loading from `module-manifest.sx`, but the
  generator now only emits `.json`; no `.sx` writer exists. The served `.sx` manifest is
  from 2026-04-16 while modules are from Jul 1-2. Concretely `_entry :lazy-deps` is
  missing `hs-worker` and `hs-prolog`, so those plugins never lazy-load in the browser;
  any future dep/export change is invisible to the client.
- repro: `grep '_entry' shared/static/wasm/sx/module-manifest.sx` → lazy-deps ends at
  `hs-integration hs-htmx`; module-manifest.json includes `hs-worker`, `hs-prolog`.

### C1b. Non-ASCII byte on the top-level command channel also crashes the sx_server process (2nd instance of C1)
- severity: critical
- confidence: CONFIRMED (reproduced live, self-verified)
- where: hosts/ocaml/lib/sx_parser.ml:38 (is_symbol_char is ASCII-only) + the unguarded
  command-channel parse (C1, sx_server.ml:5048)
- what: A non-ASCII byte reaching the kernel's top-level command reader raises an uncaught
  `Parse_error` that kills the subprocess — same unguarded-parse root cause as C1, but reachable
  from any client that forwards a unicode-named symbol. The Python parser *accepts and serializes*
  bare unicode symbols (shared/sx/parser.py:158 accepts -), so a Python-bridge caller
  can trigger this. Inside `(eval "…")` it's a catchable error; on the command channel it's fatal.
- repro: `printf '(epoch 1)\n(eval (quote café))\n(epoch 2)\n(eval "99")\n' | timeout 30 hosts/ocaml/_build/default/bin/sx_server.exe`
  → `Fatal error: exception Sx_types.Parse_error("Unexpected char: \195 …")`; epoch-2 never runs.

### JS1. `define-record-type` broken on JS: `makeRtd` platform constructor missing
- severity: high
- confidence: CONFIRMED (agent-reproduced)
- where: spec/evaluator.sx:2584 calls `(make-rtd …)`; no `makeRtd` in hosts/javascript/platform.py
- what: The transpiled evaluator's record support calls makeRtd, which the JS platform never
  defines. Any define-record-type throws; test-cross-lang-types.sx (18 deftests) aborts.
- repro: `(define-record-type point (make-point x y) point? (x point-x) (y point-y))` →
  JS `ERROR: makeRtd is not defined`; OCaml `(point-x (make-point 3 4))` → 3.

### JS2. `host-callback` / `host-await` never fire for SX lambdas (wrong type tag)
- severity: high
- confidence: CONFIRMED (agent-reproduced)
- where: hosts/javascript/platform.py:2332-2342 (host-callback), 2354-2361 (host-await)
- what: Both check `fn._type === "lambda"`, but Lambda instances are tagged `_lambda === true`
  (platform.py:889); the check never matches, so SX lambdas wrapped as JS callbacks become
  silent no-op functions. Any DOM event/promise callback registered through these does nothing.
- repro: JS `(let ((cb (host-callback (fn (x) (* x 2))))) (cb 21))` → nil; OCaml → 42.

### JS3. JS arithmetic silently drops extra arguments (arity not checked)
- severity: high
- confidence: CONFIRMED (agent-reproduced)
- where: hosts/javascript/platform.py:1021 (-), 1045 (/), 1086-1101 (< > <= >=)
- what: Non-rational `-` returns `args[0]-args[1]` regardless of arity; `/` is strictly 2-arg;
  comparisons ignore args past the 2nd. OCaml errors on wrong arity, so buggy guest code passes
  silently on JS with wrong values.
- repro: JS `(- 5 1 2)` → 4 (OCaml 2); `(/ 24 2 3)` → 12 (OCaml errors); `(< 1 2 3)` → true (OCaml errors).

### JS4. Parser rejects `.` as a symbol — whole hyperscript-parser test file aborts
- severity: high
- confidence: CONFIRMED (agent-reproduced)
- where: hosts/javascript/platform.py:2622 (_identStartRe lacks `.`)
- what: `.` is a valid symbol on OCaml but "Unexpected character: ." on JS. test-hyperscript-parser.sx
  uses `(quote .)`, so the file fails to parse and all 93 deftests are skipped (counted as 1 failure).
- repro: JS `(str (quote .))` → ERROR; OCaml → `"."`, type-of → "symbol". (Handoff: spec must rule
  whether `.` is a legal symbol.)

### JS5. run_tests.js harness undercounts + shadows semantics
- severity: medium
- confidence: CONFIRMED (agent-reproduced)
- where: hosts/javascript/run_tests.js:457-467, 78-79, 88-96, 126
- what: (1) Each test file is wrapped in one try/catch — the first throw abandons the rest of the
  file and counts as ONE failure (~111 deftests silently skipped this run). (2) `env-bind!` and
  `env-set!` are defined identically (`e[k]=v`) — no scope-chain walk for env-set!, contradicting
  the platform's own envSet (platform.py:2098) and the spec bind/set distinction, so tests
  exercising that semantics test the wrong thing. (3) `sha3-256` is a fake 32-bit hash padded to
  64 hex — cross-host content-address comparison is meaningless. (4) lib/harness load errors are
  logged but never counted.

### JS6. Transpiled `(str …)` doesn't skip nil (emission vs runtime disagree)
- severity: medium
- confidence: CONFIRMED (agent, emission rule read + runtime verified opposite)
- where: hosts/javascript/transpiler.sx js-emit-list str clause vs runtime platform.py:1138-1144
- what: The transpiler emits `String(a)+String(b)` for str, so transpiled spec code concatenates
  `String(NIL)` → "nil", while the CEK runtime skips nils. Identical expressions disagree between
  transpiled internals and guest code.

### JS7. Transpiler has no quasiquote emission; quoted dicts emit garbage
- severity: low
- confidence: CONFIRMED (code read)
- where: hosts/javascript/transpiler.sx js-emit-quote (no dict clause → `[object Object]`); no quasiquote case in js-emit-list
- what: Only a hazard if a transpiled spec file uses top-level quasiquote or quoted dict literals;
  runtime quasiquote via CEK is correct on both hosts.

### JS8. Stale JS host metadata + stale checked-in sx-full-test.js
- severity: low
- confidence: CONFIRMED (agent)
- where: bootstrap.py:2-11 docstring (refs deleted js.sx/run_js_sx.py/platform_js.py/shared/sx/ref/*),
  bootstrap.py:287 (bad default output path), cli.py:17 (_PROJECT one level too high → /root on sys.path),
  manifest.py (reports the hollow bundle as healthy); shared/static/scripts/sx-full-test.js (Apr 26, predates open-input-string → 35 --full failures)

### C24. Python↔OCaml boundary validation is permanently a no-op (imports a nonexistent module)
- severity: high
- confidence: CONFIRMED (reproduced live, self-verified)
- where: shared/sx/boundary.py:34
- what: `_load_declarations()` does `from .ref.boundary_parser import …` but
  `shared/sx/ref/boundary_parser.py` does not exist (real file is hosts/python/boundary_parser.py).
  The ModuleNotFoundError is swallowed by a bare `except Exception` and NOT cached, so every
  validate_primitive/validate_io/validate_helper/validate_boundary_value silently returns without
  checking — even under SX_BOUNDARY_STRICT=1. The whole SX-boundary enforcement subsystem is dead.
  NOTE: shared/sx/ is load-bearing in production (Quart services render SX via the bridge), so this
  is not dead-host code.
- repro: `SX_BOUNDARY_STRICT=1 python3 -c "from shared.sx import boundary; boundary.validate_primitive('definitely-not-a-primitive-xyz')"`
  → `pure: 0` and `NO ERROR RAISED`.

### C25. Parser string-escape divergence corrupts data across the Python↔OCaml boundary (\0, \x, unknown escapes)
- severity: high
- confidence: CONFIRMED (reproduced live, self-verified)
- where: shared/sx/parser.py:108 (_ESCAPE_MAP) / :471 (serialize) vs hosts/ocaml/lib/sx_parser.ml:61-79
- what: Python maps `\0`→NUL and drops the backslash on unknown escapes (`\x`→`x`); OCaml treats
  `\0` as literal backslash+zero and preserves the backslash on unknown escapes (`\x`→`\x`).
  Python serialize() emits `\0` for a NUL char, so a 3-char Python string round-trips to a
  4-char OCaml string — silent corruption on any path that serializes a NUL-bearing string and
  re-parses it in the kernel. `</script` also diverges (Python `<\/script` vs OCaml differently).
- repro: `python3 -c "from shared.sx.parser import serialize; print(repr(serialize('a\x00b')))"` → `'"a\\0b"'`;
  kernel `(len "a\0b")` → `4` (should be 3).

### C26. Python parser accepts bare unicode symbols the OCaml kernel rejects → error or crash
- severity: high
- confidence: CONFIRMED (reproduced live, self-verified — see C1b)
- where: shared/sx/parser.py:158 (SYMBOL regex accepts -) / :462 (serialize bare) vs sx_parser.ml:38 (ASCII-only)
- what: The Python tokenizer accepts unicode letters in symbol names and serializes them bare;
  OCaml's symbol charset is ASCII-only and raises Parse_error on the first non-ASCII byte. In an
  `(eval "…")` string it's catchable; on the command channel it's a fatal subprocess kill (C1b).
- repro: `python3 -c "from shared.sx.parser import serialize; from shared.sx.types import Symbol; print(serialize(Symbol('café')))"` → `café` (bare); kernel crashes on it.

### C27. Dict key ordering not preserved across hosts (wire/golden/cache-key mismatch)
- severity: medium
- confidence: CONFIRMED (reproduced live, self-verified)
- where: shared/sx/parser.py:394-413 / :488-493 vs OCaml kernel dict serialization
- what: Python dict preserves insertion order; the OCaml kernel reorders/reverses. Any cache key,
  golden fixture, or wire payload relying on stable dict serialization mismatches between the
  Python-serialized form and the kernel-serialized form.
- repro: Python `serialize(parse('{:a 1 :b "two"}'))` → `{:a 1 :b "two"}`; kernel `(quote {:a 1 :b "two"})` → `{:b "two" :a 1}`.

### C28. Two distinct `SxExpr` classes; serialize() double-quotes the types.py one (nested wire corruption)
- severity: medium
- confidence: CONFIRMED (agent-reproduced)
- where: shared/sx/parser.py:71 and shared/sx/types.py:408 (two unrelated `class SxExpr(str)`); parser.py:441 isinstance checks only the parser one
- what: parser.SxExpr and types.SxExpr are unrelated classes. serialize() emits pre-built SX
  unquoted only for parser.SxExpr; a types.SxExpr falls through to the plain-string branch and is
  double-quoted/escaped. handlers.py:182 returns `SxExpr(result_sx)` (the types one), so any path
  nesting a handler result into an AST and re-serializing wraps the wire fragment in quotes.
- repro: `serialize([Symbol('div'), types.SxExpr('(~inner :a 1)')])` → `(div "(~inner :a 1)")` (wrong)
  vs parser.SxExpr → `(div (~inner :a 1))` (right).

### C29. Reader-macro auto-resolution broken (OcamlSync.start() doesn't exist)
- severity: medium
- confidence: CONFIRMED (agent-reproduced)
- where: shared/sx/parser.py:57 (`_sync.start()`); shared/sx/ocaml_sync.py (class has `_ensure`, no `start`)
- what: `_resolve_sx_reader_macro` (auto-resolving `#name` reader macros like `#z3`) calls
  `OcamlSync().start()`, but OcamlSync exposes `_ensure()`. The AttributeError is caught by
  `except Exception: return None`, so every auto-resolve silently returns None →
  `ParseError("Unknown reader macro: #…")`. The feature is non-functional.
- repro: `python3 -c "from shared.sx.ocaml_sync import OcamlSync; print(hasattr(OcamlSync(),'start'))"` → `False`.

### C30. Python standalone host (hosts/python/) is a non-functional stale target
- severity: medium
- confidence: CONFIRMED (agent-reproduced)
- where: hosts/python/bootstrap.py, platform.py, transpiler.sx
- what: hosts/python/ masquerades as a supported host but its bootstrapper emits syntactically
  invalid Python (`if sx_truthy((len(*winders*) > …))` — SyntaxError) and can't produce a working
  evaluator. Nothing imports it except its own internal bootstrap→platform. The hosts/python/tests/
  dir referenced by memory/RESTRUCTURE_PLAN.md does not exist. Transpiler also warns its spec has
  drifted (`eval.sx dispatches forms not in special-forms.sx: provide, scope, define-record-type …`).
- repro: `python3 hosts/python/bootstrap.py > /tmp/t.py; python3 -c "import t"` → SyntaxError at line 1564.

### C31. shared/sx/tests/ is 14/33 files broken; test_ocaml_bridge.py has 5 live failures
- severity: medium
- confidence: CONFIRMED (agent-reproduced)
- where: shared/sx/tests/ (dangling `shared.sx.ref.sx_ref` imports); test_ocaml_bridge.py
- what: 14/33 test files fail collection (import the deleted shared.sx.ref.sx_ref). test_ocaml_bridge.py
  has 5 failures: `test_parse_response_ok_number` (the `(ok N)` epoch-ambiguity below) and 4
  `test_render_*` that still use flat `(let (x v) body)` binding syntax the kernel now rejects
  (real Python-test/kernel drift).

### C2. lib/r7rs.sx shadows `string->number`, dropping the spec's optional radix — spec test fails on the OCaml runner
- severity: medium
- confidence: CONFIRMED (baseline test failure + root cause isolated + differential vs bare server)
- what: spec/primitives.sx:1104 declares `string->number` with `&rest (radix :as number)`,
  and the native prim (hosts/ocaml/lib/sx_primitives.ml:486) accepts 1–2 args. But
  lib/r7rs.sx:83 redefines it as `(fn (s) ...)` (1 param) and re-exports to the global
  namespace ("backward compatibility" import at end of file). Every environment that
  loads r7rs.sx (run_tests does, at run_tests.ml:3711) loses the radix arg:
  `(string->number "ff" 16)` → `string->number expects 1 args, got 2`.
  Note the adjacent `number->string` wrapper (r7rs.sx:76) does this correctly by
  delegating to the captured native prim for the radix case — `string->number` just
  wasn't given the same treatment. The shadow also changes 1-arg semantics
  (parse-int/parse-float vs the native parser).
- repro: baseline log line 4179 `FAIL: > string->number: string->number expects 1 args, got 2`
  (spec/tests/test-math.sx:112). Bare server WITHOUT r7rs loaded:
  `printf '(epoch 1)\n(eval "(string->number \"ff\" 16)")\n' | timeout 30 hosts/ocaml/_build/default/bin/sx_server.exe` → `255` (correct).

### C12. compile-modules.js SOURCE_MAP has 5 dead source paths that silently no-op
- severity: medium
- confidence: CONFIRMED
- where: hosts/ocaml/browser/compile-modules.js:38-49
- what: `signals.sx→web/signals.sx`, `hypersx.sx→web/hypersx.sx`, `tw-layout/tw-type/tw.sx→web/tw-*.sx`
  all point at files that no longer exist (moved to web/web-signals.sx, web/lib/hypersx.sx,
  shared/sx/templates/tw-*.sx). `fs.existsSync` guards make the skips silent, so edits to
  the real sources are never synced by this script — the rot vector that plausibly produced
  the C10 compiler drift.
- repro: `for f in web/signals.sx web/hypersx.sx web/tw-layout.sx web/tw-type.sx web/tw.sx; do [ -f $f ] || echo MISSING $f; done` → all 5 MISSING.

### C13. test_platform.js broken — references deleted web/signals.sx (browser platform test tier unrunnable)
- severity: medium
- confidence: CONFIRMED
- where: hosts/ocaml/browser/test_platform.js:65
- what: The "full WASM + platform stack" Node test crashes at load: web/signals.sx was
  renamed to web/web-signals.sx and the file list was never updated.
- repro: `timeout 60 node hosts/ocaml/browser/test_platform.js` → `Error: ENOENT … web/signals.sx`.

### C14. hosts/ocaml/browser/dist/ is an 8-week-stale duplicate bundle that sx-build-all.sh still feeds
- severity: medium
- confidence: CONFIRMED
- where: hosts/ocaml/browser/dist/ (72 files, May 7) vs shared/static/wasm/ (101 files, Jul 1-2);
  scripts/sx-build-all.sh:19-32
- what: Runtime serving is exclusively from shared/static/wasm/ (shared/sx/helpers.py:1080,
  sx_server.ml:3349/4525), but bundle.sh/compile-modules.js default to dist/ and
  sx-build-all.sh still syncs into and compiles from it — a full run can compile .sxbc
  from May-7-era sources for anything its sync list misses. Two half-overlapping bundle
  dirs are the root cause of C10/C11.
- repro: `ls hosts/ocaml/browser/dist/sx | wc -l` → 72 vs 101; dist mtimes 2026-05-07.

### C15. Git-tracked stale kernel artifacts in hosts/ocaml/shared/static/wasm (5MB dead blob)
- severity: medium
- confidence: CONFIRMED
- where: hosts/ocaml/shared/static/wasm/{sx_browser.bc.js (5.0MB Apr 9), sx_browser.bc.wasm.js, sx-platform.js (Apr 16)}
- what: A wrong-cwd copy of the deployed wasm dir, committed to git, months stale
  (missing newer kernel exports like resetStepCount/setStepLimit); nothing references it.
- repro: `git ls-files hosts/ocaml/shared/` → 3 files.

### C19. HTML vs DOM adapter disagree on non-boolean attributes with raw boolean values (SSR/hydration mismatch)
- severity: medium
- confidence: CONFIRMED (HTML side run; DOM side unambiguous in source)
- where: spec/render.sx:render-attrs (~263-283) vs web/adapter-dom.sx:render-dom-element (~356-366)
- what: For an attr NOT in BOOLEAN_ATTRS with value `true`: HTML emits `attr="true"`,
  DOM emits `attr=""`. With value `false`: HTML emits `attr="false"`, DOM omits the
  attribute. Any `data-*`/aria attr driven by a boolean diverges between SSR and
  client render → hydration mismatch + needless sync-attrs morph churn.
- repro: `sx_harness_eval (render-to-html (quote (div :data-x false :data-y true :data-z nil)))`
  → `<div data-y="true" data-x="false"></div>`; DOM branch produces `data-y=""` and drops `data-x`.

### C20. CSRF token attached to cross-origin fetches; computed `cross-origin` flag is dead
- severity: medium
- confidence: CONFIRMED (code path) / SUSPECTED (browser-level exploitability)
- where: web/orchestration.sx:do-fetch (line 183 header injection, 207-208 cross-origin flag);
  consumer web/lib/boot-helpers.sx:fetch-request (354-390)
- what: do-fetch adds `X-CSRFToken` unconditionally (no origin gate) and computes a
  `cross-origin` field that fetch-request never reads (nor sets credentials/mode).
  Net: CSRF token leaks to third-party endpoints; the cross-origin classification is inert.
  No test exercises CSRF header injection or origin gating.

### C21. Test harness IO model bypasses the real perform/suspend path (structural coverage gap)
- severity: medium
- confidence: CONFIRMED
- where: spec/harness.sx default-platform (line 34), make-interceptor (52), install-interceptors (55)
- what: The harness binds IO ops as plain synchronous NativeFns, never as perform-suspending
  primitives — so no harness test can exercise CEK suspend/cek-resume or the VM inline-settle
  path. The known HO+perform element-drop class (S10) is structurally invisible to the harness:
  `(map (fn (u) (fetch u)) …)` under a mock returns all elements because fetch is a direct call.
- repro: sx_harness_eval of `(map (fn (u) (get (fetch u) "status")) (list "a" "b" "c"))` with
  fetch mock → `(200 200 200)`, all logged — real serving path is the one at risk.

### C22. Harness interceptor logs IO call only after the mock returns — throwing mocks invisible
- severity: low
- confidence: CONFIRMED
- where: spec/harness.sx:make-interceptor (line 52)
- what: `append!` to the IO log happens after mock-fn returns; a raising mock leaves no
  log entry, so assert-io-called/count falsely report "never invoked" on error-path tests.
  (Positive side-finding: host IO errors DO surface as catchable SX errors.)
- repro: try-catch around `(fetch "http://a")` with mock raising "boom-io" → caught, but
  IO log shows "(no IO calls)".

### C23. adapter-dom test suite tests membership predicates only — zero render-output tests
- severity: low
- confidence: CONFIRMED
- where: web/tests/test-adapter-dom.sx (18 deftests)
- what: All 18 tests assert `*-is-a-render-form?`/RENDER_HTML_FORMS membership; none test
  actual render-to-dom output (boolean attrs, on-*/bind/ref/key, reactive attrs, void
  elements, hydration cursor). The 1512-line DOM adapter holding C19 + hydration handoff
  is the thinnest-tested adapter relative to size.

### C3. Dead stale-io-response guard in the epoch loop (13-char literal vs 14-char substring)
- severity: low
- confidence: CONFIRMED (reproduced live)
- where: hosts/ocaml/bin/sx_server.ml:5043-5046
- what: `String.sub line 0 14 = "(io-response "` compares a 14-byte substring to a
  13-byte literal — never true, guard never fires. A stray `(io-response …)` line
  falls through to dispatch and emits an extra `(error N "Unknown command …")` reply
  instead of being silently discarded — an unexpected extra response for the client.
- repro: `printf '(epoch 1)\n(io-response 1 42)\n(eval "5")\n' | timeout 60 …/sx_server.exe`
  → `(error 1 "Unknown command: (io-response 1 42)")` then the eval reply.

### C4. Malformed `(epoch)` / `(epoch foo)` doesn't update the epoch — responses tagged stale
- severity: low
- confidence: CONFIRMED (reproduced live)
- where: hosts/ocaml/bin/sx_server.ml:5051-5054
- what: Only `(epoch <number>)` updates `current_epoch`; malformed epoch markers fall
  through as unknown commands and the old epoch keeps tagging subsequent responses.
  A client whose epoch line was mangled will discard every following response as
  stale → hang.
- repro: `printf '(epoch)\n(eval "2")\n(epoch foo)\n(eval "3")\n' | …` → all replies
  tagged epoch 0.

### C5. No monotonic-epoch enforcement despite protocol comment claiming it
- severity: low
- confidence: CONFIRMED (reproduced live)
- where: hosts/ocaml/bin/sx_server.ml:5051-5054, :4952-4955 (comment at :259-262)
- what: `current_epoch := n` unconditionally; decreasing/repeated epochs accepted
  silently, so client bugs/reordering mis-tag responses instead of being detected.
- repro: `printf '(epoch 9)\n(epoch 3)\n(eval "42")\n' | …` → `(ok-len 3 2)`.

### C6. Two commands on one line → both dropped with a single error (client desync)
- severity: low
- confidence: CONFIRMED (reproduced live)
- where: hosts/ocaml/bin/sx_server.ml:5055-5056
- what: A line parsing to >1 expr returns one `(error … "Expected single command, got 2")`
  and executes neither — a pipelining client gets a response-count desync. Related:
  a command with no preceding `(epoch N)` is answered under the previous epoch tag.

### C7. vm-trace with compiler not loaded errors as "Not callable: nil"
- severity: low
- confidence: CONFIRMED (reproduced live)
- where: hosts/ocaml/bin/sx_server.ml:2193-2201 (vm-trace dispatch)
- what: If `lib/compiler.sx` isn't loaded (e.g. `load` failed because it is cwd-relative
  and the server was started from hosts/ocaml), `(vm-trace "…")` fails with the opaque
  `(error N "Not callable: nil")` instead of "compiler not loaded". Minor DX.
- repro: from hosts/ocaml: `printf '(epoch 1)\n(load "lib/compiler.sx")\n(epoch 2)\n(vm-trace "(+ 1 2)")\n' | timeout 60 _build/default/bin/sx_server.exe`
  → `(error 1 "File error: …")` then `(error 2 "Not callable: nil")`.

### C8. Stray triplicated source tree hosts/ocaml/hosts/ocaml/hosts/ocaml/
- severity: low
- confidence: CONFIRMED
- what: A nested duplicate of the OCaml tree (154 KB Apr-9 copy of sx_server.ml etc.)
  sits inside hosts/ocaml. Not built by dune, but a grep/edit-wrong-file trap.

### C16. hosts/native is an orphaned but healthy PoC — builds green, referenced by nothing
- severity: low
- confidence: CONFIRMED
- where: hosts/native/ (own dune-workspace; lib_sx → ../../hosts/ocaml/lib symlink)
- what: SDL2/Cairo native SX renderer, single commit f0d8db9b (2026-04-09), unreferenced
  by scripts/CI/docs. NOT bit-rotted: compiles against current `sx` library and its smoke
  test passes (parse → 25-node render tree → layout → Cairo paint → PNG). Because it has
  its own dune-workspace, the main build never compiles it, so silent breakage would go
  unnoticed. Positive: it shares the evaluator by tracked symlink — no fork drift possible.
- repro: `cd hosts/native && eval $(opam env) && timeout 280 dune build ./test/test_render.exe`
  → exit 0; run → `=== All OK! ===`.

### C17. Tracked-but-dead browser files: sx-platform-2.js and 23 *.sxbc.json
- severity: low
- confidence: CONFIRMED (dead refs) / SUSPECTED (.sxbc.json fully unused)
- where: shared/static/wasm/sx-platform-2.js (Apr 16); shared/static/wasm/sx/*.sxbc.json
- what: sx-platform-2.js referenced by nothing. .sxbc.json written by mcp_tree.ml:1211 and
  copied by compile-modules.js:355, but the client loads the `.sxbc` SX-text format — no
  runtime reader found. All ~2.5 months stale vs their .sxbc twins.

### C18. Untracked repo-root clutter: spa-debug.js, scripts/sx-sessions-restore.sh
- severity: low
- confidence: CONFIRMED
- what: spa-debug.js is a leftover Playwright compare of direct-load vs boosted SPA nav of
  relate-picker (Jun 30 debugging); sx-sessions-restore.sh is an ops script embedding
  session UUIDs + `claude --dangerously-skip-permissions` invocations. Commit, relocate,
  or remove.

### C9. run_tests spec suites print with empty suite label
- severity: low
- confidence: CONFIRMED (baseline log)
- what: spec test suites (e.g. test-math.sx) report as `FAIL:  > string->number` with a
  blank suite name in run_tests output — harness labeling defect, makes triage harder.

---

## CROSS-HOST PARITY (CONFIRMED divergences — same expr, different result per host)

Enumeration (900-name universe): spec declares 202 primitives; OCaml resolves 536, JS 668,
Python 112. Only 97 names common to all three. Spec primitives MISSING: OCaml 9
(`eq? eqv? escape format format-date parse-datetime pluralize strip-tags sx-parse`), JS 6
(`downcase eq? eqv? equal? json-encode upcase`), Python 108 (whole modules — see PY block).
Differential battery files in scratchpad: battery*.json, parity-js.js.

### P1. OCaml float serialization is lossy — SX-text wire round-trip destroys precision
- severity: high
- confidence: CONFIRMED (self-verified)
- where: hosts/ocaml/lib/sx_types.ml:415-423 (format_number uses `%g` = 6 sig digits for fractional floats)
- what: All fractional floats print with 6 sig digits; JS prints shortest-round-trip. Any value
  crossing the SX text wire (aser, epoch protocol, persisted SX) from OCaml silently loses precision.
  Integral floats ≥1e16 use `%.17g` (lossless) — inconsistent within the same host. This is the
  PRODUCTION evaluator, so it affects live wire payloads.
- repro (self-verified): OCaml `(number->string (/ 1 3))` → `"0.333333"`; `(+ 0.1 0.2)` → `"0.3"`;
  `(= (parse-number (number->string (/ 1 3))) (/ 1 3))` → `false`. JS same → true / 0.3333333333333333.

### P2. OCaml `sort` breaks on mixed int/float lists (polymorphic compare on constructor tag)
- severity: high
- confidence: CONFIRMED (self-verified)
- where: hosts/ocaml/lib/sx_primitives.ml:1290-1292 (`List.sort compare l`)
- what: OCaml polymorphic `compare` orders Integer before Number by variant tag, so numerically
  mixed lists sort wrongly and silently.
- repro (self-verified): OCaml `(sort (list 1.5 10 2))` → `(2 10 1.5)`; JS → `(1.5 2 10)`.

### P3. `into` is not native on the OCaml kernel — routed over the IO helper bridge, fails standalone
- severity: medium
- confidence: CONFIRMED (self-verified)
- where: OCaml resolves `into` but execution emits `(io-request N "helper" "into" …)`
- what: A spec core.dict primitive requires a live Python-side helper on OCaml; JS/Python have it
  native. Any pure-OCaml context (tests, CLI, and notably the `eval`/`load-source` command paths
  that don't service helper IO) can't use `into`.
- repro (self-verified): OCaml `(into {} (list (list :a 1)))` → io-request then
  `error … "IO bridge: stdin closed while waiting for io-response"`; JS → `{:a 1}`.

### P4. Exact-integer semantics diverge (OCaml int63 Integer vs JS float64)
- severity: high
- confidence: CONFIRMED (agent)
- where: hosts/ocaml/lib/sx_types.ml:47-48 (Integer distinct from Number); JS has only doubles
- what: Arithmetic differs beyond 2^53; literals above int63 silently become floats on OCaml with
  different text form; exactness is host-dependent and not wire-preserved (both serialize `1.0` as
  `"1"`, which OCaml re-reads as exact Integer).
- repro: `(+ 9007199254740992 1)` → OCaml 9007199254740993, JS …992; `(* 111111111 111111111)` →
  OCaml 12345678987654321, JS …320; `(float? 1.0)` → OCaml true, JS false.

### P5. JS `=` is not deep on dicts (spec: alias for equal?); `equal?`/`eq?`/`eqv?` missing from JS bundle
- severity: high
- confidence: CONFIRMED (agent + JS agent)
- where: hosts/javascript/platform.py:841 (sxEq falls through to false for plain-object dicts);
  no PRIMITIVES["equal?"/"eq?"/"eqv?"] in the JS bundle (run_tests.js:126 patches them only into the test env)
- what: `=` deep-compares lists but reference-compares dicts on JS; OCaml deep-compares both. And the
  spec equality trio is absent from the browser bundle — harness assertions (spec/harness.sx:82,85,88
  call equal?) and any component using equal? crash client-side. OCaml also lacks eq?/eqv? (has
  non-spec identical? instead).
- repro: `(= {:a 1} {:a 1})` → OCaml true, JS false; `(equal? (list 1 2) (list 1 2))` → OCaml true, JS ERROR.

### P6. String length/indexing units differ across hosts (UTF-8 bytes vs UTF-16 code units)
- severity: high
- confidence: CONFIRMED (agent)
- where: OCaml String.length (bytes) vs JS .length (UTF-16, platform.py:1386)
- what: Neither counts codepoints; any substring/index arithmetic on non-ASCII text diverges
  cross-host (and both differ from user-perceived length).
- repro: `(len "héllo")` → OCaml 6, JS 5; `(len "🎉")` → OCaml 4, JS 2.

### P7. JS lenient-coercion cluster vs OCaml strictness (silent wrong values on the client)
- severity: medium (aggregate; several individually medium/high)
- confidence: CONFIRMED (agent + JS agent)
- where: hosts/javascript/platform.py (str 1138, range 1380, len 1386, cons 1391, get 1385, parse-int 1500, split 1148, round 1058, slice 1167, max/min 1065)
- what: JS coerces where OCaml errors/strict → divergent results the client renders silently:
  `(str (list 1 2))` JS "1,2" (OCaml "(1 2)"); `(str {:a 1})` JS "[object Object]" (OCaml "{:a 1}");
  `(range 3)` JS `()` (OCaml `(0 1 2)`); `(len nil)` JS 2 (OCaml 0); `(cons 1 nil)` JS `(1 nil)`
  (OCaml `(1)`); `(get {:a nil} :a 42)` JS nil (OCaml 42); `(get "abc" 1)` JS "b" (OCaml nil);
  `(parse-int "abc")` JS 0 (OCaml nil); `(parse-int "42px")` JS 42 (OCaml nil); `(split "abc" "")`
  JS `("abc")` (OCaml `("a" "b" "c")`); `(round -2.5)` JS -2 (OCaml -3); `(slice "hello" -3)` JS "llo"
  (OCaml "hello"); `(max)` JS -Infinity (OCaml errors); `(+ 1 "2")` JS "12" (OCaml 3); `(mod 10 0)`
  JS NaN (OCaml Division_by_zero).

### P8. nil/list-strictness family diverges, some arms violating the spec on OCaml's side
- severity: medium
- confidence: CONFIRMED (agent)
- where: OCaml sx_primitives.ml strict arms vs JS lenient (platform.py:1391-1392)
- what: `(merge nil {:a 1})` → OCaml ERROR (spec says skip nil → OCaml BUG) vs JS `{:a 1}`;
  `(contains? {:a 1} :a)` → OCaml ERROR "contains?: 2 args" (spec: dict key check → OCaml BUG) vs JS true;
  `(append nil (list 1))` → OCaml `(1)` vs JS ERROR; `(cons 1 2)` → OCaml ERROR vs JS `(1 2)`;
  `(reverse "abc")` → OCaml ERROR vs JS "cba".

### P9. Dict key ordering differs per host (Hashtbl vs insertion order) — SX text wire not canonical
- severity: medium
- confidence: CONFIRMED (agent; also self-verified in the Python block C27)
- where: OCaml dicts are Hashtbl (arbitrary bucket order); JS objects preserve insertion; Python preserves insertion
- what: keys/vals iteration order and serialized dict text differ per host — anything comparing or
  hashing serialized SX text cross-host diverges. CBOR/CID layer is safe (sx_cbor.ml:67 sorts keys).
- repro: `(keys {:z 1 :a 2 :m 3})` → OCaml `("m" "z" "a")`, JS `("z" "a" "m")`.

### P10. NaN/Infinity are not portable wire tokens; JS can't re-read its own output
- severity: medium
- confidence: CONFIRMED (agent)
- where: sx_types.ml:416-418 prints nan/inf/-inf (OCaml re-reads all forms) vs JS prints Infinity/NaN (resolves neither)
- repro: `(/ 1 0)` → OCaml inf, JS Infinity; eval `Infinity` → OCaml inf, JS ERROR undefined symbol.

### P11. `upper`/`lower` Unicode behavior differs; `upcase`/`downcase` aliases missing on JS; `round` half-mode differs
- severity: medium
- confidence: CONFIRMED (agent)
- where: sx_primitives.ml:890-897 (uppercase_ascii) vs platform.py:1145 (full Unicode); JS lacks upcase/downcase; round half-mode
- repro: `(upper "héllo")` → OCaml "HéLLO", JS "HÉLLO"; `(upcase "x")` → OCaml "X", JS ERROR;
  `(round -2.5)` → OCaml -3, JS -2.

### P12. OCaml `zip-pairs` chunks pairs; spec + lib/stdlib.sx define a sliding window (kernel diverges from its own spec)
- severity: medium
- confidence: CONFIRMED (JS agent handoff)
- where: OCaml native zip-pairs vs spec/primitives.sx:663 + lib/stdlib.sx:207 (JS matches spec)
- what: OCaml native `zip-pairs` returns `((1 2)(3 4))` but the spec + stdlib define sliding window
  `((1 2)(2 3)(3 4))`. Handoff to core, but the OCaml *host* is the one diverging from the spec.

### PY (Python host). Standalone hosts/python/ cannot evaluate the current spec at all
- severity: critical (as a supported target) — see C30 for the standalone-host summary
- confidence: CONFIRMED (agent)
- where: hosts/python/bootstrap.py:119 (_mangle), generated lines 1564/3482/3498; spec `match` form
- what: Four independent breaks in the Python bootstrapper: (1) _mangle RENAMES lacks `*winders*`
  → SyntaxError; (2) no rule for `->` in names → `def string_>symbol` invalid; (3) the `match`
  special form is emitted as an eager function call → undefined `match`, and even shimmed it
  evaluates all branches eagerly (core dispatch unrunnable); (4) references missing natives
  make-char/aser-call/aser-fragment. Python resolves only 112 of the 900-name universe and lacks
  8 primitive modules (math/bitwise/bytevectors/vectors/regexp/sets/rational/hash-table = 108 spec
  primitives). This overlaps/extends C30. NOTE: shared/sx/ Python (parser+bridge) IS load-bearing
  in production; the standalone evaluator host is what's dead.
- repro: `python3 hosts/python/bootstrap.py > /tmp/x.py && python3 -c "import x"` → SyntaxError line 1564;
  after mangle-patch, `(+ 1 2)` → `name 'match' is not defined`.

---

### (informational) ok-len framing is byte-accurate and desync-resistant
- confidence: CONFIRMED — length prefixes are byte counts; newlines/framing patterns in
  payloads are escaped; multibyte UTF-8 counted correctly. (Positive result.)

### (informational) WASM browser kernel boots green; kernel is single-source with server
- confidence: CONFIRMED — test_boot.sh → `WASM boot: OK`; test_wasm.sh → 29/29;
  test_kernel.js → 24/24; deployed kernel artifacts byte-identical to _build outputs.
  Browser links the identical `sx` OCaml library as sx_server (no evaluator fork);
  only sx_browser.ml FFI glue is browser-specific. (Positive result.)

---

## SUSPECTED (reasoning only) — NOTE: S1/S4/S5 UPGRADED to CONFIRMED via live HTTP repro

> Follow-up live testing (bounded `sx_server --http` on free ports, 3 runs) upgraded three of the
> HTTP-race items and clarified two more:
> - **S1 CONFIRMED** — observed a live intermittent crash (empty responses, no exception, OOM ruled
>   out at ~292 MB peak) under concurrent multi-Domain load.
> - **S4 CONFIRMED** — routing-failure page served as HTTP 200 and cached (cold 2.02s → warm 0.0005s).
> - **S5 CONFIRMED** — cache key ignores cookies (3 different `session=` → identical cached body) and query.
> - **S2/S3 remain SUSPECTED** — mechanism verified live in the code path, but no visible symptom is
>   triggerable in the stock static-docs app (no full-page route renders request args / no
>   expansion-sensitive component to observe). They become observable on request-varying apps.
> Their entries below carry updated per-finding confidence lines.

### S1. HTTP mode: shared mutable state raced across parallel render-worker Domains — LIVE CRASH OBSERVED
- severity: high
- confidence: **CONFIRMED (live crash reproduced once; race is real by construction)** — was SUSPECTED
- where: hosts/ocaml/bin/sx_server.ml:4717 (`Domain.spawn` × `max 4 (recommended_domain_count)`),
  :4312/:4677 (`Hashtbl.replace response_cache` from workers + main), :4654 (main reads it),
  :2666-2670 (`env.bindings` bind+remove), :345 (`_stream_mutex` created but NEVER locked)
- what: `http_mode` spawns 4 real `Domain.spawn` workers rendering full-page requests on the
  single shared env, concurrently with the accept-domain doing AJAX renders. There is no lock
  around rendering. Workers + main mutate process-global Stdlib.Hashtbls (`response_cache`,
  `env.bindings`) concurrently; OCaml 5 Stdlib.Hashtbl is NOT domain-safe (memory corruption on
  rehash → segfault, no catchable exception). The comment at :4204 claiming cache writes happen
  "only during single-threaded startup" is false (workers write at runtime); the :343-345 comment
  admits "concurrent CEK evaluations corrupt shared state".
- **LIVE REPRO (self-verified).** Booted `sx_server --http <port>` on a free port (3 runs). Under a
  burst of ~300 concurrent AJAX + ~150 concurrent full-page requests to distinct uncached paths
  (all 4 worker Domains + main thread writing `response_cache`/`env.bindings` simultaneously),
  **run 2 CRASHED**: the process vanished mid-render — no OCaml exception logged (the try/with at
  :4315 would have printed `[render] Error`; none present), no "Binary changed" restart, and a
  concurrent poll of a known-good cached page began returning EMPTY bodies (md5 of "") for 59/60
  reads as the server died. **OOM ruled out**: run 3 under an even heavier burst survived with peak
  RSS ~292 MB (baseline ~200 MB) — memory never approached exhaustion. The crash is INTERMITTENT
  (run 1 clean, run 2 crashed, run 3 survived) — the classic signature of a data race, not a
  deterministic fault. Net: the production HTTP server can crash under concurrent load; I couldn't
  capture a core dump (apport core_pattern, restricted) to pin the exact fault address, so the
  "Hashtbl-rehash-segfault" mechanism is inferred from the crash profile + the unsynchronized-access
  code, not a stack trace. Logs: /tmp/sx-review/http-server{,2,3}.log.

### S2. HTTP mode: per-request state is process-global → cross-request contamination
- severity: high
- confidence: SUSPECTED (mechanism confirmed live; visible symptom not triggerable in the stock docs app)
- where: sx_server.ml:4335-4354 (main domain sets `_req_method/_req_query/_req_headers/_req_body`
  + `Hashtbl.clear/replace _request_cookies`), :4703 (full-page render QUEUED to a worker with only
  `(fd, path, headers)` — NOT the request context), consumed by request-* primitives at :3772-3829
- what: I confirmed live that full-page misses are **queued** to worker Domains carrying only the
  path, while the worker's render reads the process-global `_req_query`/`_req_body`/`_request_cookies`
  that the main loop overwrites on the very next request. So a queued render can read a *subsequent*
  request's query/body/cookies (plus a plain main-writes-while-worker-reads data race). I could NOT
  produce a visible crossed-value symptom because the sx-docs app has no full-page route that renders
  `(request-arg …)`/`get-cookie`/body into its output — the docs pages are static. The contamination
  is therefore structurally present and live-reachable but only becomes observable for an app whose
  full-page renders vary by request args (e.g. any of the real Quart-replacement domains). Kept
  SUSPECTED honestly: mechanism verified, symptom not exhibited on the stock app.

### S3. `expand-components?` global binding removed mid-flight by AJAX render
- severity: medium
- confidence: SUSPECTED (bind/remove verified live in code path; visible symptom not isolated)
- where: sx_server.ml:4157 (bound globally = true for ALL renders), :2666/:2670 (AJAX branch binds
  then `Hashtbl.remove env.bindings "expand-components?"`)
- what: http_mode binds `expand-components?` once globally so full-page worker renders expand
  components. The AJAX branch of http_render_page binds and then REMOVES it on the SAME shared env —
  so after any AJAX request the global binding is gone, and a concurrent (or subsequent) full-page
  aser render can observe it vanished (components silently not expanded), plus the remove races
  worker lookups. Confirmed the bind/remove-on-shared-env code path executes during live AJAX
  requests (the log shows "[sx-http] … (SX) aser=" for every AJAX hit); did not isolate a
  visibly-unexpanded component in output because the docs pages' aser output is SSR-expanded through
  a different path. Mechanism confirmed; symptom not pinned.

### S4. Response cache stores soft error pages (routing failures cached as HTTP 200) — LIVE CONFIRMED
- severity: medium
- confidence: **CONFIRMED (self-verified live)** — was SUSPECTED
- where: sx_server.ml:2635-2658 (`error_page_ast` returned as `Some body`), cached at :4312/:4677
- what: A routing failure renders `error_page_ast` returned as a normal `Some body` and served with
  HTTP **200**; the worker/ajax paths cache any `Some body`. So the not-found/error page is cached
  and served from cache to every subsequent visitor until restart. (Hard 500s from raised exceptions
  at :4315 are NOT cached; soft error pages ARE.) The "transient cross-service failure" sub-case
  (a fetch error rendered into the page, then cached) is reasoned from the same code path but needs
  the Python bridge to trigger, so it stays inferred.
- repro (self-verified): `GET /sx/<random-nonexistent-path>` → HTTP 200 containing the "404"/not-found
  page; a 2nd GET of the same path returned byte-identical bytes served from cache — cold render 2.02s
  vs warm 0.0005s (~4000×), and the server log shows only ONE render line for that path.

### S5. Response cache keying ignores cookies/query/auth (except one hardcoded cookie) — LIVE CONFIRMED
- severity: medium
- confidence: **CONFIRMED (self-verified live)** — was SUSPECTED
- where: sx_server.ml:4296/:4651-4654 (`cache_key` = path, +ajax:/htmx: prefix), :4649 (only
  `sx-home-stepper` bypasses the cache), :4331-4332 (query stripped before keying)
- what: The cache key is the path only; cookies (except the hardcoded `sx-home-stepper`) and the
  query string are absent from it. Any page whose output varied by cookie/session/auth or query would
  be cached under a shared key and served across users. Benign for the current public docs (pages
  don't vary by cookie), but a real footgun for the cookie/auth-dependent Quart-replacement domains.
- repro (self-verified): `GET /sx/(geography.(hypermedia))` with three different `Cookie:` headers
  (different `session=` values) → three byte-identical responses from one cache entry; same path with
  `?u=1` vs `?u=2` → byte-identical (query stripped from key).

### S8. Browser env missing spec platform constructors + a small primitive tail — latent break for shared modules
- severity: medium
- where: sx_browser.ml:626+ (91 server-only binds vs 22 browser-only; lists in scratchpad
  server-binds.txt / browser-binds.txt)
- what: `make-lambda/make-component/make-macro/make-thunk`, `now`, `json-encode`,
  `parse-safe`, `pretty-print`, `into` are bound only in sx_server's env, absent
  client-side. Currently harmless (spec/evaluator.sx isn't shipped to the browser; the
  only consumer of the latter group is web/io.sx, not bundled) — but any shared .sx
  module that starts using them silently breaks in the browser only.

### S9. Aser/SPA boosted-nav component expansion still fragile — hinges on boot-manifest eager-load fed by stale artifacts
- severity: medium
- where: web/adapter-sx.sx; sx-platform.js:649-660 (page-manifest boot array);
  lib/host/sx/relate-picker.sx; bundle.sh:74-77
- what: The April blocker was fixed (ac65666f) and Jun-29 commits added server-embedded
  `data-sx-manifest` eager-loading, but the untracked Jun-30 spa-debug.js shows boosted-nav
  expansion of the picker was still being debugged the next day — and both the stale served
  compiler (C10) and stale module-manifest.sx (C11) sit directly on this code path.

### S10. VM inline-resolve of IO inside HO-primitive callbacks cannot suspend/resume (async-IO correctness cliff)
- severity: high
- confidence: SUSPECTED (documented + code-confirmed; not reproducible through the harness — see C21)
- where: hosts/ocaml/lib/sx_vm.ml:call_closure_reuse (~349-375); `_cek_io_resolver` installed at sx_server.ml:4166
- what: When `perform` fires inside a JIT/VM HO-primitive callback (map/filter/reduce/for-each),
  the native OCaml loop sits between perform and resume, so it can't unwind-and-resume; it
  resolves IO inline via a local `settle` loop. The in-code comment states the alternative
  "would drop the remaining elements — corrupting the stack so the next CALL_PRIM sees wrong
  args." This works only because durable-KV reads are synchronous; a genuinely async IO op
  inside an HO callback on the serving path is not correctly supported. This is the same
  hazard class as the memory note about serving JIT dropping all-but-first in map/for-each.

### S11. Server page routing evaluates the URL as SX — any env-bound function is URL-invokable
- severity: medium
- confidence: SUSPECTED
- where: web/request-handler.sx (sx-url-to-expr, sx-auto-quote, sx-eval-page/call-page);
  driven by sx_server.ml:http_render_page:2620 (sx-handle-request)
- what: sx-url-to-expr strips `/sx/`, splits on `.`, joins with spaces; call-page parses and
  invokes it. sx-auto-quote only quotes *unbound* symbols, so a bound symbol as list head is
  called with attacker-supplied (recursively evaluated) args. A path with parens (a legal
  path char) can reach `(env-get env "some-fn")` + apply for any render-env binding. cek-try
  swallows errors (→ nil/error page) limiting impact, but this is an SSR eval surface worth
  whitelisting to `page:`-prefixed bindings. Couldn't confirm whether the OCaml front door
  pre-sanitizes the path.

### S12. Island hydration replaceChildren-then-render → hydration cursor reuses no SSR DOM
- severity: low
- confidence: SUSPECTED
- where: web/boot.sx:hydrate-island (~333-350)
- what: hydrate-island calls `(host-call el "replaceChildren")` (empties the element) then
  renders the body in `sx-hydrating` mode. But the hydration matcher reads childNodes.item(idx)
  on the just-emptied element, so it reuses nothing — effectively clear-and-fresh-render. If
  intended, the sx-hydrating scope push is dead work; if not, element-rooted islands never
  reuse SSR DOM. Not runnable through the DOM path to confirm.

### S13. SSR/client parity relies on island body being pure over serialized kwargs (no dev-mode check)
- severity: low
- confidence: SUSPECTED
- where: web/adapter-html.sx:render-html-island + serialize-island-state (608) vs web/boot.sx:hydrate-island
- what: Only kwargs are serialized into data-sx-state; a body reading request-scoped/
  non-deterministic IO (now, request-arg, random) or scope/context values not in kwargs
  produces SSR HTML differing from the client render → hydration mismatch. Inherent to the
  design, but there's no guard or dev-mode mismatch check and no test for a non-pure body.

### S14. Deep (≥2-level) nested-list children flatten differently between HTML and aser
- severity: low
- confidence: SUSPECTED
- where: spec/render.sx render-list path (recursive) vs web/adapter-sx.sx:aser-call/aser-fragment (one-level flatten)
- what: HTML recursively flattens arbitrarily nested child lists to text; aser flattens one
  level and serializes deeper lists structurally. Single-level map output agrees (tested),
  but 2+-level raw nesting could serialize differently between modes. No test covers depth ≥ 2.

### S-bridge. Python↔OCaml bridge desync on coroutine cancellation is unrecoverable (dead _restart, no timeouts)
- severity: high
- confidence: SUSPECTED (bridge-side reasoning; kernel-side stale-message mechanism self-verified)
- where: shared/sx/ocaml_bridge.py:99 (_restart, 0 callers), :613 (_read_until_ok), :139 (async with self._lock)
- what: render/eval/aser hold self._lock while blocked in _read_until_ok awaiting a kernel io-response.
  If the awaiting coroutine is cancelled (client disconnect / timeout), the `async with` releases the
  lock while the kernel is still blocked in read_io_response for the OLD epoch. The next coroutine
  acquires the lock and sends `(epoch N+1)\n(command)`; the kernel — still inside read_io_response —
  consumes both lines as "stale messages" and keeps blocking, so the new command never runs and the
  new coroutine hangs on _readline forever. No asyncio.wait_for wraps bridge calls, and _restart()
  pipe-recovery is never invoked, so the bridge can't self-heal — it stays wedged until the process
  is killed. Under a shared multi-Domain HTTP server (S1/S2) this is a real liveness hazard.
- repro (self-verified, kernel-side mechanism): `printf '(epoch 1)\n(eval "(helper \"w\" 1)")\n(epoch 2)\n(eval "(+ 1 2)")\n' | timeout 20 hosts/ocaml/_build/default/bin/sx_server.exe`
  → `(io-request 1 "helper" "w" nil)`, then `[io] discarding stale message (…)` ×2 eating the epoch-2
  command, then the epoch-2 eval never runs.

### S-bridge2. `_parse_response` treats a numeric result value as an epoch (ambiguity)
- severity: low
- confidence: CONFIRMED (agent)
- where: shared/sx/ocaml_bridge.py:915, shared/sx/ocaml_sync.py:115
- what: `(ok EPOCH VALUE)` parsing strips a leading number as the epoch, but the guard
  `line[4:-1].isdigit()` classifies `(ok 3)` as epoch-only-no-value and returns `('ok', None)` instead
  of `('ok','3')`. Latent (kernel always emits `(ok EPOCH VALUE)`), but the digit-sniffing epoch strip
  is fragile for any value that begins with a digit. test_ocaml_bridge.py has 5 live failures incl this.

### S6. io_request-based primitives under `eval` can consume subsequent command lines
- severity: low
- where: sx_server.ml:617-669 (`query`/`action`/`helper` → blocking `read_io_response`),
  `eval` dispatch at :1816
- what: `query`/`action`/`helper` block reading the next stdin line as an IO response;
  under a client that doesn't speak the IO sub-protocol, the next queued command is
  consumed → desync/hang.

### S7. Multiple eval entry points with divergent IO semantics for the same source
- severity: low
- where: `eval` (:1816-1849, import+persist inline), `load-source` (:1772-1783, no
  IO handling), `load` (:1755, full `eval_expr_io`), http render (:2575, import→nil)
- what: The same source behaves differently depending on the wrapping command —
  the known "unify eval/JIT paths" TODO surfacing as behavioral drift.

---

## Notes / baseline caveats

- 272/274 baseline run_tests failures are the in-progress hyperscript (`hs-*`) suites —
  guest-language project, not host defects. Undefined symbols: `hs-is-set?` (16),
  `eval-hs-error` (18), `hs-ref-eq` (4), `host-hs-normalize-exc` (2), plus DOM-mock
  assertion failures.
- run_tests JIT is opt-in (`--jit`, run_tests.ml:4035). Baseline ran interpreter-only
  (`[jit] calls=3045835 hit=0 miss=0 skip=3045835`). A `--jit` differential run is in
  progress to diff pass/fail sets — results pending.
- `sx_scope.ml` does not exist on this branch (only in sibling worktrees); memory/docs
  referencing it are stale for `architecture`. Scope prims live in sx_primitives.ml
  as process-globals (see S1/handoffs).

## Handoffs to other lanes

- core: served browser JIT compiler missing 921db09f's HO-loop desugar (see C10) — the
  browser-side `--jit == CEK` parity fix needs a re-sync + recompile pipeline fix.
- conformance: module-manifest format split (C11) — regenerate `.sx` or point
  sx-platform.js at `.json`; until then hs-worker/hs-prolog never lazy-load client-side.
- conformance: hosts/native has a working test nothing runs; test_platform.js needs
  `web/signals.sx` → `web/web-signals.sx` to restore the browser platform test tier.
- hygiene: unify dist/ vs shared/static/wasm/ behind one sync script; delete the ignored
  hosts/ocaml/hosts/ nest, tracked stale hosts/ocaml/shared/static/wasm blobs,
  sx-platform-2.js, and (if reader-less) *.sxbc.json.
- core (spec/render.sx parity): render-attrs renders non-BOOLEAN_ATTRS boolean-valued attrs
  as `attr="true"`/`attr="false"` while adapter-dom emits `attr=""`/omits — pick one contract
  and align all four adapters; add conformance cases (see C19).
- conformance (spec/harness.sx): (1) log the IO call before invoking the mock so throwing
  mocks are recorded (C22); (2) add a harness mode that routes IO through real
  perform/cek-resume so the HO+perform element-drop cliff (S10) becomes testable (C21).
- host (sx_vm.ml): add a targeted conformance test performing IO inside a JIT-compiled HO
  callback over a function-produced list — the inline-settle path is a correctness cliff
  for any future genuinely-async seam IO op (S10).
- core: `_scope_stacks` / `_request_cookies` / `_req_*` being process-global means
  scope/provide/emit! and request primitives are not isolable per concurrent flow;
  spec scope semantics assume single-flow evaluation. Needs a per-flow story if the
  OCaml HTTP server stays multi-Domain.
- core: eval vs load-source vs load vs http-render resolve `import`/durable IO
  differently — converge on one IO-resolution policy.
- core/eval: `scope`/`emit!` appears to LEAK across invocations — `(scope (emit! :k 1)
  (emit! :k 2) (len (emitted :k)))` returns 2, 4, 6… cumulatively across epoch-server calls,
  with JIT disabled too (so not a VM bug — looks like a scope-stack state leak in scope-in-frames
  or the epoch env's scope stack). Flagged by the VM/JIT agent.
- core/docs: `let` is SEQUENTIAL (let*) in BOTH CEK and VM (`(let ((x 2) (y x)) y)` → 2 with
  outer x=1) — contradicts the documented "let is parallel" authoring rule. Engines agree; docs don't.
- core/spec: nested quasiquote evaluates inner unquotes at depth 2 on both OCaml and JS
  (```` `(a `(b ,(c))) ```` errors) — nonstandard but consistent; needs a spec ruling.
- core/spec: `=` deep-on-dicts, `eq?`/`eqv?`/`equal?`, `upcase`/`downcase`, `json-encode`,
  1-arg `range`, empty-sep `split`, `parse-int` failure, `get` stored-nil-vs-default, `(max)`,
  `round` half-negative, string-length units, exactness-across-wire — all need normative rulings;
  then each host aligned. OCaml wire float printing (`%g`) should be shortest-round-trip; OCaml
  `sort` numeric-compare Integer/Number; OCaml `into`/`contains?`-on-dict/`merge`-skip-nil made
  native+conformant; OCaml `zip-pairs` reconciled with its own spec.
- conformance: NO differential CEK-vs-JIT parity suite exists — all four silent-wrong-value JIT
  bugs (J1 `->`, J3 macro-arg, J4 component-kwargs, J5 stale opcodes) pass the existing tests.
  A harness that runs each conformance expr both interpreted and through a forced-JIT wrapper
  would catch them. Also add a cross-host differential battery (scratchpad battery*.json) — the
  spec corpus currently runs green only against OCaml; the JS bundle is 2490 failing and the
  Python standalone host can't load.
- conformance: add a protocol-fuzz suite for the epoch loop (malformed line must not
  crash; `(epoch)`/`(epoch foo)`; stray `(io-response …)`; two-exprs-per-line).
- conformance: no suite catches the r7rs `string->number` radix shadow — the spec
  test fails only in the OCaml runner env; JS/Python runner r7rs load status differs.

## Recommended triage / next steps

This is a reviewer's suggested reading order, NOT a verdict — severity/confidence tags on each
finding are the ranking signal; a maintainer decides what blocks a release. No new claims here; every
item points back to the evidence above.

**Confirmed-severe clusters, in suggested blocker order:**

1. **Production serving-JIT silently miscompiles (J1–J8).** Highest concern because it produces
   *wrong values with no error* on the live sx.rose-ash.com HTTP path (JIT hook is unconditional at
   sx_server.ml:4163 — see the reconciliation note; the "opt-in/default-off" belief does not apply to
   http_mode). J1 (`->` in arg position) and J3 (macro args) return wrong results; J2 double-applies
   side effects on fallback (a data-integrity risk for persist/counter writes). Blast radius depends
   on whether rendered lambdas hit those patterns — a fleet audit of served pages, or disabling the
   http_mode JIT hook until fixed, is the pragmatic call. Fix lives in lib/compiler.sx (compile-thread-step
   at 934-951 for J1) + macro handling in the compiler/VM.

2. **Process-killing parse crash (C1 / C1b).** One malformed or non-ASCII line on the command channel
   kills the whole sx_server process — it's the shared channel for the Python bridge and conformance
   runners. Small, localized fix (wrap the command-channel parse at sx_server.ml:5048/:4950 in a
   handler that catches Parse_error). Cheap to fix, high blast radius; good early win.

3. **HTTP serving path breaks under concurrency (S1 / S4 / S5).** S1 crashed the live server
   intermittently (data race on unsynchronized cross-Domain Hashtbls, OOM ruled out) — needs either a
   lock around cache/env mutation or per-Domain state. S4 (error pages cached as 200) and S5 (cache
   key ignores cookies/query) are cheap keying fixes but S5 becomes a cross-user data-leak on any
   cookie/auth-varying domain, so fix it before the Quart-replacement domains go live on this server.

4. **Hollow JS bundle + red JS CI (C0a / C0b).** Breaks conformance/CI, not visitors (the live site
   serves the wasm kernel). Fix is a define-library-aware `extract_defines` (hosts/javascript/platform.py:15)
   plus loading the newer lib deps + make-rtd/dom prims — or, if the JS host is being retired, stop CI
   gating on it. A product/ownership decision as much as a code fix.

**Not-yet-symptomatic, needs a real app to exercise:**
- S2 (queued workers read process-global request context) and S3 (`expand-components?` removed on the
  shared env) are mechanism-confirmed but only exhibit a visible symptom on a full-page route that
  varies by request args/cookies. Re-run the S1-style concurrent-load repro against one of the real
  Quart-replacement domains (not the static docs site) to turn these CONFIRMED or clear them.

**Cross-cutting, lower urgency:**
- Cross-host parity (P1–P12) and the Python boundary/bridge issues (C24–C31, S-bridge) matter most if
  the JS/Python hosts are still targets; if OCaml is the sole production evaluator, prioritize the
  OCaml-side ones that affect the wire (P1 lossy float, P9 dict order, P3 `into`).
- Hygiene items (C8, C14–C18) are safe cleanups, no behavior change.

**Handoffs above** route the core/conformance-owned items (scope leak, let-sequential docs, the missing
CEK-vs-JIT differential suite) to the sibling review lanes — not this lane's to fix.
