Consolidates the three-lane review (core K01-K110, hosts J*/C*/JS*/P*/S*,
conformance F1-F15) into plans/sx-review/:
- PLAN.md — 15 workstreams, phased execution, full per-finding coverage
ledger (every ~213 finding-instances mapped to a workstream + status)
- RULINGS.md — 40 draft normative rulings (Phase-0 gate)
- core.md / hosts.md / conformance.md — the lane evidence files
dc7aa709 quick-wins batch marked DONE in the ledger; K01 (guard re-raise
hang), S1 (live HTTP crash), K03 (shift-k), and W14 (test gate) flagged as
the highest-value open work.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 KiB
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,
intoneeds 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 defaultrun_tests --jituses 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) callsregister_jit_hook envUNCONDITIONALLY 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 …slines 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 compilescall-expr(pushing a value that's never popped) then recurses withcall-exprre-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/Islandwith _ -> cek_call_or_suspendre-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:2after 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: " and the hook re-runs on the CEK. Code a macro guards from evaluation executes once in the VM.
- repro (self-verified):
my-unlessmacro that expandstrue→ 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
:kwto 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 behaviorsand… from the core element scopePASS interpreter-only but FAIL under--jitwith 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 deliversString msginstead 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 bytypeofchecks 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.jsthen 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 lineoutside anytry; the enclosing handler only catchesEnd_of_file, soSx_types.Parse_errorfrom 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 from59ac51a8, 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 (writesmodule-manifest.jsononly) - what:
loadWebStack()drives client module loading frommodule-manifest.sx, but the generator now only emits.json; no.sxwriter exists. The served.sxmanifest is from 2026-04-16 while modules are from Jul 1-2. Concretely_entry :lazy-depsis missinghs-workerandhs-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 aths-integration hs-htmx; module-manifest.json includeshs-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_errorthat 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 …); nomakeRtdin 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))→ JSERROR: 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
-returnsargs[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!andenv-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-256is 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 concatenatesString(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()doesfrom .ref.boundary_parser import …butshared/sx/ref/boundary_parser.pydoes not exist (real file is hosts/python/boundary_parser.py). The ModuleNotFoundError is swallowed by a bareexcept Exceptionand 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: 0andNO 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\0as literal backslash+zero and preserves the backslash on unknown escapes (\x→\x). Python serialize() emits\0for 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.</scriptalso diverges (Python<\/scriptvs 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, nostart) - what:
_resolve_sx_reader_macro(auto-resolving#namereader macros like#z3) callsOcamlSync().start(), but OcamlSync exposes_ensure(). The AttributeError is caught byexcept 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_refimports); 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 4test_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->numberwith&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 adjacentnumber->stringwrapper (r7rs.sx:76) does this correctly by delegating to the captured native prim for the radix case —string->numberjust 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-*.sxall point at files that no longer exist (moved to web/web-signals.sx, web/lib/hypersx.sx, shared/sx/templates/tw-*.sx).fs.existsSyncguards 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 emitsattr="true", DOM emitsattr="". With valuefalse: HTML emitsattr="false", DOM omits the attribute. Anydata-*/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 producesdata-y=""and dropsdata-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-CSRFTokenunconditionally (no origin gate) and computes across-originfield 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>)updatescurrent_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 := nunconditionally; 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.sxisn't loaded (e.g.loadfailed 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 currentsxlibrary 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
.sxbcSX-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-permissionsinvocations. 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->numberwith 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
compareorders 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
intobut 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-sourcecommand paths that don't service helper IO) can't useinto. - repro (self-verified): OCaml
(into {} (list (list :a 1)))→ io-request thenerror … "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.0as"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; evalInfinity→ 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-pairsreturns((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
matchform - what: Four independent breaks in the Python bootstrapper: (1) _mangle RENAMES lacks
*winders*→ SyntaxError; (2) no rule for->in names →def string_>symbolinvalid; (3) thematchspecial form is emitted as an eager function call → undefinedmatch, 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 identicalsxOCaml 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 --httpon 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_cachefrom workers + main), :4654 (main reads it), :2666-2670 (env.bindingsbind+remove), :345 (_stream_mutexcreated but NEVER locked) - what:
http_modespawns 4 realDomain.spawnworkers 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 writingresponse_cache/env.bindingssimultaneously), 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_bodyHashtbl.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_cookiesthat 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_astreturned asSome body), cached at :4312/:4677 - what: A routing failure renders
error_page_astreturned as a normalSome bodyand served with HTTP 200; the worker/ajax paths cache anySome 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 (onlysx-home-stepperbypasses 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 differentCookie:headers (differentsession=values) → three byte-identical responses from one cache entry; same path with?u=1vs?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,intoare 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-embeddeddata-sx-manifesteager-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_resolverinstalled at sx_server.ml:4166 - what: When
performfires 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 localsettleloop. 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 topage:-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 insx-hydratingmode. 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 withreleases 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 guardline[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→ blockingread_io_response),evaldispatch at :1816 - what:
query/action/helperblock 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, fulleval_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--jitdifferential run is in progress to diff pass/fail sets — results pending. sx_scope.mldoes not exist on this branch (only in sibling worktrees); memory/docs referencing it are stale forarchitecture. 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 == CEKparity fix needs a re-sync + recompile pipeline fix. - conformance: module-manifest format split (C11) — regenerate
.sxor 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.sxto 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 emitsattr=""/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:
letis 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-argrange, empty-sepsplit,parse-intfailure,getstored-nil-vs-default,(max),roundhalf-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; OCamlsortnumeric-compare Integer/Number; OCamlinto/contains?-on-dict/merge-skip-nil made native+conformant; OCamlzip-pairsreconciled 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->numberradix 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:
-
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. -
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.
-
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.
-
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.