Commit Graph

3958 Commits

Author SHA1 Message Date
1597eaa4f8 Merge loops/sx-vm-extensions into architecture: rational cleanup
(/ int int) returns float per spec/host parity (was leaking exact rationals
into arithmetic + CSS, e.g. tw-opacity 1/20); rational operand still yields
exact rational. number?/exact? recognise Rational. test-rationals typo fix.
OCaml conformance 4834->4863, rational/numeric-tower/arithmetic/tw-opacity green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 07:03:30 +00:00
e90c8fdd97 vm-ext: rational cleanup — (/ int int) returns float per spec, fix number?/exact? on Rational
The OP_DIV/numeric-tower work on this branch made the OCaml `/` primitive
return an exact Rational for (/ int int) (e.g. (/ 5 2)=5/2), diverging from
the canonical spec ("/ always returns inexact float"), the test-rationals.sx
header ("in the JS host, (/ int int) returns float — backward-compatible"),
and the JS host itself. That leaked rationals into arithmetic results and
rendered CSS (tw-opacity emitted `opacity:1/20` instead of `0.05`).

Decision (with the user): keep exact rationals as an explicit opt-in
(literals 1/3, make-rational) but bring `/` back into spec/host parity —
the isomorphic SSR↔hydration invariant requires both hosts to agree, and
JS has no native rational type.

- sx_primitives.ml `/`: (/ int int) → integer when exactly divisible, else
  inexact float; a Rational operand still yields an exact rational (matches
  test-numeric-tower: (/ 6 2)=3, (/ 1 4)=0.25, (/ 5 2)=2.5, (/ 1/2 2)=1/4).
- sx_primitives.ml number? / exact?: recognise the Rational type (real bugs —
  test-rationals asserts (number? 1/3) and (exact? 1/3); inexact?/float?
  already returned false for Rational, correct).
- sx_vm.ml OP_DIV: comment updated (it delegates to the now-float `/`).
- test-rationals.sx: fix typo in "rational * float = float" — used int 2,
  should be 2.0 (1/2 * 2 = 1 exact, not a float; name + siblings use floats).

OCaml conformance 4834→4863 (+29 fixed, zero new failures); rationals,
numeric-tower, arithmetic, tw-opacity suites all 0 failures. Remaining run_tests
failures are the pre-existing environmental hyperscript (host-call-fn) set.
JS host already handles number?/exact? on rationals and float `/`; its
remaining float?/contagion failures are a separate pre-existing limitation
(JS has no distinct float type), out of scope here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 20:23:39 +00:00
d122aed0cb Merge loops/sx-vm-extensions into architecture: serving-JIT perform-in-HO-callback fix
Fixes the silent miscompile under SX_SERVING_JIT=1 (http-listen + cek_run_with_io):
a perform inside a native HO-primitive callback (map/filter/reduce/for-each)
unwound the native loop, corrupting the stack so the next CALL_PRIM read garbage
args (map/rest/drop). (A) call_closure_reuse resolves callback IO inline in
serving mode so the loop survives; (A') resume_vm restores _active_vm; (B)
register_jit_hook resolve_loop falls back to CEK on resume error (no 500).
Repro 9/9 (hosts/ocaml/bin/repro_jit_resume.ml); conformance unchanged 4834/1110.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 19:52:22 +00:00
81177d0ebd vm-ext: fix serving-JIT perform-in-HO-callback miscompile (host bug)
Root cause (found via bin/repro_jit_resume.ml, 9 surgical cases): when a
`perform` (durable kv read) fires inside a native HO-primitive callback
(map/filter/reduce/for-each/some/every?), the VmSuspended unwound through
the primitive's native OCaml loop (List.map etc.), destroying the loop's
iteration state. The remaining elements were dropped and the stack left
misaligned, so the NEXT CALL_PRIM (map/rest/drop) read wrong args —
"map: expected (fn list)", "rest: 1 list arg", "drop: list and number".
Only triggers in the http-listen + cek_run_with_io serving path (epoch
eval has no synchronous resolver, so conformance was 271/271).

(A) lib/sx_vm.ml call_closure_reuse: when a callback suspends AND a
synchronous IO resolver is installed (serving mode), resolve the
callback's IO inline and run it to completion right there, returning its
value to the native loop — so the loop is never unwound. Scoped to the
resolver-set path; the CEK-driven path (flow/reactive/async tests) keeps
its existing reuse_stack behaviour, so nothing else changes. reuse_stack
is isolated across the nested resume.

(A') lib/sx_vm.ml resume_vm: re-assert _active_vm := Some vm for the
duration of the resumed run (mirrors call_closure). call_closure restored
_active_vm to the caller when VmSuspended unwound, so HO callbacks during
a resume could land on the wrong VM. Latent-bug fix.

(B) bin/sx_server.ml register_jit_hook: the resolve_loop runs inside the
VmSuspended handler, so a non-VmSuspended exception from resume_vm escaped
to the http handler (→ 500). Catch it and fall back to CEK for THIS call
(mark jit_failed, return None → interpreter re-runs it). Self-heals on the
first hit, not a retry. Defense-in-depth; with (A) it shouldn't trigger.

Verification: repro 9/9 (incl. host shape: map[cb→interpreted-helper
perform]→drop = (7 8); reduce; nested map). Standard + --full OCaml
conformance unchanged at 4834/1110 (baseline identical — the 1110 are
pre-existing environmental: host-call-fn/browser-platform symbols,
rational display, tw/regex). Host loop to re-verify 271/271 serving and
drop its (jit-exclude! "host/*" "dream-*" "dr/*") band-aid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 19:44:12 +00:00
bdc027c4f8 Merge loops/artdag into architecture: job-as-post-object projection
lib/artdag/post.sx (job<->feed post object, post-id = content-id, self-verifying wire,
post-run for peers) per the host loop's 'jobs as posts' direction. Additive. artdag 225/225.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:38:52 +00:00
0b7b3b9efb artdag: job-as-post-object projection (post.sx) + 12 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 47s
lib/artdag/post.sx — the artdag-side projection for 'a job is a type of post' (per the
host loop). job->post-object: {:type artdag/job :id <output content-id> :wire <dag->wire>},
post-id = content-id = natural AP object id. post-object-verify binds the id to the payload
(record ids recompute + post id present), rejecting tampered params/bogus ids. String
transport for the feed/SXTP body; post-run lets a peer decode->run->result, content-address
cache-hitting. Activity wrapping stays host-side. post 12/12, total 225/225.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:38:34 +00:00
154681a4e7 Merge branch 'loops/erlang' into tmp-erlang-merge
# Conflicts:
#	lib/erlang/conformance.sh
2026-06-28 18:19:39 +00:00
550d0db5a5 artdag: record forward direction — jobs as a feed post (per host loop)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 13m11s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:16:36 +00:00
e1fe5ab552 artdag: note scheduler re-merged to architecture (fdd0c8f7)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 43s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:02:59 +00:00
fdd0c8f7b9 Merge loops/artdag into architecture: miniKanren CLP(FD) scheduler
lib/artdag/schedule.sx (slot-var-per-node, fd-lt per edge, fd-label search; ASAP agrees
with plan.sx Kahn waves; full enumeration + parallelism-cap filter) + schedule suite.
Additive, conflict-free. artdag now 213/213.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:01:59 +00:00
4d5bf47f4a artdag: miniKanren CLP(FD) scheduler (schedule.sx) + 15 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
lib/artdag/schedule.sx on lib/minikanren: slot var per node, fd-lt per edge, fd-label
search. schedule-asap (smallest-first labeling) agrees exactly with plan.sx greedy Kahn
waves (cross-validated); schedules enumerates all valid schedules; schedules-capped
filters to <=cap per slot; schedule-valid? independent dep check. Adds a 'schedule' suite
to conformance.sh loading the minikanren CLP(FD) stack. Completes the optional Phase 3/7
miniKanren box. schedule 15/15, total 213/213.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:01:00 +00:00
b10e55f04f erlang: send_after to registered name + gen_server timeout returns (T5+T6, 771/771)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s
T5 — send_after addresses a registered atom name; the delayed message
lands in that process's mailbox (destination resolved at fire time,
dead/unregistered targets drop silently).

T6 — gen_server loop now handles the {reply,R,S,T} / {noreply,S,T}
timeout-bearing callback returns by scheduling {timeout} to itself via
send_after; handle_info({timeout}, S) fires when no other message
arrives first. Sanity-checks the library hookup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 17:53:08 +00:00
98b0104c7b erlang: send_after deadline-ordering + cancel-of-fired tests (T3+T4, 769/769)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s
T3 — concurrent timers fire in deadline order, not schedule order
(scheduler jumps the clock to the earliest pending deadline each
time the runnable queue drains). T4 — cancel_timer on an
already-fired timer returns the atom false.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 17:48:47 +00:00
b0d845bbf9 Merge loops/artdag into architecture: artdag Phase 7 (maude rule-based optimization)
Content-addressed DAG engine Phase 7 complete (198/198): maude-bridge (lossless
dag<->term), optimize-rules.sx (confluent ARTDAGOPT module — id/no-op/fusion/dedup
laws, AC radius algebra, consumes mau/confluent?), opt-reduce (encode->creduce->decode
result-preserving optimised DAG), cost-directed opt-cheaper?, non-vacuous confluence
gate. Brings in lib/maude if not already present. Additive, conflict-free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 17:48:09 +00:00
3709460d0b erlang: erlang:send_after/3 + cancel_timer/1 + monotonic_time (T1+T2, 766/766)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m2s
Logical-clock timer wheel in the scheduler. send_after schedules a
message-delivery event at an absolute deadline (clock + Time ms);
cancel_timer marks a live timer cancelled and reports remaining ms,
or false. Time advances only when the runnable queue drains, jumping
to the earliest pending deadline (deterministic, no wall clock).

monotonic_time/0,1 exposes the logical ms clock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 17:44:19 +00:00
e184ce984a artdag: mark push-blocker resolved (loops/artdag pushed to origin)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 17:39:17 +00:00
089ed88f54 Merge loops/sx-vm-extensions into architecture: serving-mode JIT (opt-in)
Brings the bytecode JIT to the persistent epoch serving mode, gated opt-in via
SX_SERVING_JIT=1 (default OFF → zero change for existing loops). Includes the
correctness fixes that make the JIT match the CEK interpreter, and the
interpret-only exclusions that keep continuation-based guest interpreters safe.

Kernel / shared:
- SX_SERVING_JIT gate in sx_server.ml (loads lib/compiler.sx + register_jit_hook
  only when opted in).
- compiler.sx-as-`compile` correctness: else-symbol in compile-cond/case/guard;
  OP_DIV rational; OP_EQ/_fast_eq rational+ListRef; callable? accepts VmClosure.
- Three composable interpret-only signals in jit_compile_lambda:
  (1) jit-exclude! name / "ns-*" prefix; (2) PUSH_HANDLER recursive bytecode
  scan (guard/handler-bind/Dream catch); (3) jit-exclude-callers-of! +
  code_refs_escaping_caller (call/cc-establishing form callers).

Per-guest interpret-only declarations in each guest runtime: smalltalk (dispatch
core + pharo-test-class), scheme (scheme-*/scm-*), erlang (er-*/erlang-*),
prolog (pl-*), common-lisp (cl-*/clos-* + condition-form callers), js
(js-*/jp-*), haskell (hk-*).

Verified under SX_SERVING_JIT=1 (== CEK, no hang): host 181, smalltalk 847,
scheme/flow 166, erlang 530, prolog 590/mod 390, haskell 285, common-lisp 487,
js 148, apl 152, datalog/forth/ocaml. run_tests --jit 4813/1131 (was 4809/1135,
improved), no-jit 4834/1110 (unchanged). Default-OFF gate => no loop regresses.
2026-06-28 17:05:31 +00:00
cd2ad707f9 artdag: record ready-to-merge-to-architecture status (Phase 7 absent on origin/architecture)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 17:01:29 +00:00
2bafb4f7d2 Merge loops/fed-sx-m2 into architecture: federation milestone 2
m2 lands multi-actor + cross-instance federation on the fed-sx
substrate. Feature-complete except 8b-timer (retry-loop wiring,
gated on erlang:send_after substrate primitive in loops/erlang).

Highlights:
- Multi-actor gen_server kernel (one nx_kernel handles N actors)
- Per-actor HTTP routes /actors/<id>/{inbox,outbox} + actor-doc
- Inbound signature verify + peer-AS cache + auto-Accept publish
- Outbound delivery_set with audience expansion + delivery_worker
- Native httpc:request/4 BIF wrapper + live HTTP dispatch
- Discovery: peer-actor fetch + cache on demand
- Backfill on Follow accept (in-process + paginated outbox)
- Two-instance smoke test passes 6/6 (real cross-host HTTP flow)

Substrate fixes carried in this merge (textually identical to
upstream-arrived copies, will conflict on scoreboard files only):
- Blockers #1: er-bif-http-listen marshaller bridge rewrite
- Blockers #4: er-sched-step-alive! :pending-args extension
  (lets receive in a kernel-aware route suspend+resume cleanly)

Conformance 761/761 still green on m2 tip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

# Conflicts:
#	lib/erlang/runtime.sx
2026-06-28 16:57:55 +00:00
29e4234b14 fed-sx-m2: merge-prep note — 761/761 + smoke tests still green
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 6m36s
Conformance gate + both smoke tests (smoke_kernel_route 6/6,
smoke_federate 6/6) still pass cold on m2 tip cd0de8cb. Dry-run
rebase onto current origin/architecture (0963aa51) shows 109
commits to replay with first conflict at m2's 24e3bf53 — the
binary_to_list/list_to_binary fix that landed independently on
both branches. Textual diff of the runtime.sx changes is identical
on both sides; only the scoreboard files differ. Resolution =
git rebase --skip on m2's duplicate substrate-fix commits.

No code conflict expected on the substantive m2 work (Blockers
#4 :pending-args scheduler fix, er-bif-http-listen rewrite,
er-bif-httpc-request, all of next/**).

The :pending-args extension to er-sched-step-alive! (03c32cda)
is substrate-shaped and only lives on m2 — should propagate to
loops/erlang, but that propagation belongs to the loops/erlang
loop, not this one.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-28 16:51:48 +00:00
fed58b2814 vm-ext: exclude js parser (jp-*) from JIT — fixes js 147/148 -> 148/148
The lone js opt-in-JIT residual was async/await_in_loop, which failed to PARSE
under JIT ("Unexpected token: op '<'" on `i < 5`) while passing on CEK. The js
exclusion was "js-*", but the recursive-descent parser is the jp-* namespace
(75 functions in lib/js/parser.sx) — only the lexer/transpile/runtime are js-*.
So the parser was left JIT-eligible and a jp-* function miscompiled this
construct (the long-standing parser-miscompile class).

Fix: extend the js exclusion to "js-* jp-*" so the parser is interpret-only too,
matching how every other guest's front-end is handled. js conformance under
SX_SERVING_JIT=1 is now 148/148, == CEK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:42:27 +00:00
3049ff92e4 vm-ext: document CL call/cc-caller exclusion in plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:32:17 +00:00
27b3aaedce vm-ext: fix common-lisp condition-system JIT residual (call/cc-caller exclusion)
The 6 common-lisp opt-in-JIT failures were all condition-system continuation
escape: cl-restart-case/cl-handler-case/cl-handler-bind wrap their body in
call/cc (restarts + non-local handler exit). When an SX function that drives
the condition system (the parse-recover / interactive-debugger fixtures, e.g.
parse-numbers, make-policy-debugger) is JIT-compiled, the call/cc form runs in
a NESTED cek-run where invoking the captured continuation
runs-to-completion-and-returns instead of escaping — so a restart fails to
abort and the body falls through. Observed as result accumulation
(got (1 3 0 3) vs (1 3)) and no-abort (restart returns the 999 sentinel).

These callers are arbitrary user/fixture code, not a fixed namespace, so they
can't be prefix-excluded. New data-driven mechanism:
- jit-exclude-callers-of! registers call/cc-establishing form names in
  Sx_types.jit_excluded_caller_names.
- jit_compile_lambda skips any function whose constant pool (recursively,
  incl. nested closures) references a registered name — code_refs_escaping_caller.
  Guarded by Hashtbl.length > 0 so it's a no-op for every guest that doesn't
  register (zero effect outside CL).
- lib/common-lisp/runtime.sx registers the establish side (cl-restart-case,
  cl-handler-case, cl-handler-bind) and the invoke side (cl-invoke-restart,
  cl-invoke-debugger, cl-signal, cl-error-with-debugger).

Result: CL conformance under SX_SERVING_JIT=1 = 487/0, EXACTLY matching the CEK
baseline (was 484/6 with a +3 double-execution over-count). parse-recover
3/4 -> 6/0, interactive-debugger 7/2 -> 7/0.

Note: the geometry/mop-trace suites report 0/0 on BOTH CEK and JIT — they error
"Undefined symbol: refl-class-chain-depth-with" (the CLOS suites don't preload
lib/guest/reflective/class-chain.sx). Pre-existing conformance-harness gap, not
a JIT issue; left as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:31:46 +00:00
25276dc70d Merge branch 'loops/sx-vm-extensions' into architecture
# Conflicts:
#	lib/erlang/runtime.sx
2026-06-20 07:37:43 +00:00
b825c36559 vm-ext: document guard/PUSH_HANDLER fix + double-exec residual in plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 04:07:51 +00:00
3c13596714 vm-ext: skip JIT for guard/handler-bind functions (recursive PUSH_HANDLER scan)
The host combined-binary integration test exposed a new JIT-unsafe class:
Dream's error middleware (host/wrap-errors -> dream-catch-with) failed to catch
a thrown error under JIT — it escaped as "Unhandled exception" and truncated the
host middleware suite (7/9 vs 9/9 on CEK).

Root cause: the VM's OP_PUSH_HANDLER (the compiled form of `guard`) only
intercepts a VM-level RAISE (opcode 37); it does NOT catch the OCaml Eval_error
that the `error` primitive throws from a CALL/CALL_PRIM in a callee frame. So a
JIT-compiled `guard` silently fails to catch. dream-catch-with is curried
((fn (on-error) (fn (next) (fn (req) (guard ...))))), so the guard lives in a
NESTED closure — JIT-compiling the outer function mints that inner guard as a
VmClosure with the broken VM handler.

Fix (central, not per-callsite): scan a JIT candidate's bytecode RECURSIVELY —
including nested closure code in the constant pool — for OP_PUSH_HANDLER, and
skip JIT for any handler-installing function. It then runs on the CEK, whose
guard catches correctly. Covers dream-catch-with, host wrap-errors/blog-render,
and every other guard / handler-bind user automatically.

Verified: minimal direct guard and curried cross-frame guard both return the
caught value under JIT (were "Unhandled exception"); the host run's "kaboom"
escapes went 2 -> 0. (Remaining host blog/page failures are "Undefined symbol:
render-page" — the host's native render fn, absent from the standalone
sx_server.exe; identical on CEK, i.e. an environment artifact, not a JIT
regression. The combined host binary has render-page.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 04:07:02 +00:00
bf298684fd vm-ext: gate serving-JIT behind SX_SERVING_JIT + fix continuation-guest regressions
Enabling the epoch serving-mode JIT globally regressed continuation-based guest
interpreters (the epoch mode is the shared command channel every loop's
conformance runner uses). Two-part fix:

1. SAFE DEFAULT GATE. register_jit_hook in the persistent server branch is now
   opt-in via SX_SERVING_JIT=1 (default OFF). Default behaviour is unchanged
   (no JIT in epoch serving) → zero regression for sibling loops. The
   content/Smalltalk page server opts in.

2. GENERAL FIXES + per-guest interpret-only declarations:
   - callable? (sx_server/run_tests/integration_tests/mcp_tree) now accepts
     VmClosure. A JIT-compiled higher-order function returns its inner closure
     as a VmClosure; callable? previously rejected it, so scheme-apply's
     (callable? proc) guard failed with "not a procedure: <vm:anon>".
   - jit-exclude! gains a trailing-"*" namespace-prefix form
     (Sx_types.jit_excluded_prefixes), the robust way to mark a whole guest
     interpreter interpret-only (a name-list misses functions in extra files —
     it left erlang's vm/dispatcher JIT'd and 13 tests short).
   - Per-guest exclusions in each guest's runtime.sx:
       scheme  "scheme-*" "scm-*"   erlang "er-*" "erlang-*"
       prolog  "pl-*"               common-lisp "cl-*" "clos-*"
       js      "js-*"               haskell "hk-*"

Verified under opt-in JIT (== CEK, no hang): smalltalk 847/847, scheme/flow
166/166, erlang 530/530, prolog 590/590, apl 152/152, js 147/148. Residual
(documented, protected by the default gate): common-lisp 6 fails in advanced
suites (parser-recovery/debugger/CLOS/MOP). lua (0/16) and tcl (3/4) fail
identically on CEK — pre-existing, not JIT. run_tests --jit/no-jit unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:22:40 +00:00
952ff2289c vm-ext: enable JIT in epoch serving mode (Smalltalk 847/847, Datalog 356/356)
register_jit_hook is now installed in the persistent (epoch) serving-mode
branch of sx_server.ml, not just --http/cli/site. Smalltalk-on-SX conformance
under JIT is 847/847 — identical to the no-JIT baseline; Datalog 356/356.
run_tests --jit/no-jit are byte-identical before/after (no regression).

Five distinct root causes fixed (not one "miscompile"):

1. Serving mode never loaded lib/compiler.sx, so JIT used the native
   Sx_compiler.compile stub (arity-0 bytecode, params as GLOBAL_GET →
   "VM undefined: <param>"). Server-mode branch now loads compiler.sx
   before registering the hook, matching http/cli/site.

2. compile-cond / compile-case-clauses / compile-guard-clauses only treated
   keyword :else and true as the catch-all, not the bare symbol `else` that
   the CEK's is-else-clause? accepts → GLOBAL_GET "else". (lib/compiler.sx)

3. OP_DIV produced a float for non-divisible Integer/Integer (1/2 → 0.5)
   instead of the exact Rational the "/" primitive returns. Now delegates to
   the primitive, matching CEK. (sx_vm.ml)

4. OP_EQ / _fast_eq lacked Rational/ListRef cases that the "=" primitive's
   safe_eq has → (= 1/2 1/2) false under JIT. OP_EQ now delegates non-scalars
   to the "=" primitive; _fast_eq gained rational + ListRef. (sx_vm.ml,
   sx_runtime.ml)

5. Continuation-based control flow (Smalltalk ^expr non-local return, block
   escape, exceptions via call/cc) can't run in the stack VM. New data-driven
   exclusion set Sx_types.jit_excluded + `jit-exclude!` primitive, consulted in
   jit_compile_lambda (covers both the CEK hook and vm_call's tiered path).
   lib/smalltalk/eval.sx self-declares its continuation dispatch core
   interpret-only; pure helpers still JIT. The SUnit suite-runner test helper
   pharo-test-class miscompiles mid-loop and is excluded in tests/tokenize.sx.

Also adds SX_JIT_DENY / SX_JIT_ONLY env-var bisection filters to the serving
hook. Known residual documented in plans/jit-bytecode-correctness.md: the hook
re-runs a failed VM execution via CEK (correct result, possible duplicate side
effects); adopting run_tests' propagate-don't-rerun semantics is deferred to
avoid changing shared VM/CEK behavior under this loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:36:30 +00:00
4a02a9c400 artdag: Phase 7 non-vacuous confluence gate regression + 2 tests
Assert mau/confluent? actually discriminates: the Peano-arithmetic variant of the
optimisation laws is flagged non-confluent with named non-joinable pairs, so the green
'opt module is confluent' is real evidence rather than a rubber stamp. maude-optimize
40/40, total 198/198.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:58:39 +00:00
d7bb3303f8 artdag: Phase 7 cost-directed opt-improvement/opt-cheaper? + 5 tests
artdag/opt-improvement compares the original output cone (dce to id) vs the
maude-reduced DAG under an injected cost-fn, returning before/after total-work and
critical-path. opt-cheaper? asserts optimisation never increases cost: the 5-node
chain drops to 2 (work 5->2, path 5->2) and stays cheaper under radius-weighted cost
(5->3); over dedup and untouched DAGs are never pessimised. Consumes cost.sx. Phase 7
base + (later) cost box done. maude-optimize 38/38, total 196/196.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:57:13 +00:00
81cba2cb52 artdag: record push-unavailable + sx-tree edit-tool blockers 2026-06-19 13:54:25 +00:00
55ce2a86c5 artdag: Phase 7 opt-reduce bridges maude normal form back to a runnable DAG + 8 tests
artdag/opt-reduce: encode a DAG cone -> opt-term, mau/creduce against the
optimisation module, decode the normal form back to build-entries and rebuild.
Result-preserving: a 5-node blur;blur;id;bright0 chain collapses to 2 nodes and an
over(I,I) dedup 3->2, both executing identically to the original; non-optimisable
DAGs round-trip their radius faithfully (unary 1+1+1 -> 3). Completes Phase 7's
bridge-back + equivalence boxes. maude-optimize 33/33, total 191/191.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:54:06 +00:00
1fd3aea81b artdag: Phase 7 optimisation laws as confluent maude module + 11 tests
lib/artdag/optimize-rules.sx — the effect-pipeline optimisation passes (identity
elim, no-op/zero-radius elim, adjacent fusion, idempotent over dedup) as a maude
module. Radius algebra is _+_ [assoc comm id: 0] (NOT Peano successor rules, which
are non-confluent here); mau/confluent? certifies 0 non-joinable critical pairs, so
the optimised pipeline's normal form / content id is rewrite-order stable. Consumes
lib/maude/confluence.sx. maude-optimize 25/25, total 183/183.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:48:17 +00:00
cd0de8cb34 fed-sx-m2: Step 12 closed — two-instance federation smoke test (6/6)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 36s
next/tests/smoke_federate.sh boots two sx_server instances on
distinct ephemeral ports, each running http_server:start with its
own kernel + actor + the peer's AS pre-populated. The test signs
a real Follow envelope with alice's key in a third subprocess
(outbox:construct(follow, alice, 1, bob) + outbox:sign +
term_codec:encode), POSTs the bytes to B's /actors/bob/inbox over
real HTTP, and asserts:

  - Both instances bind and serve their welcome route.
  - Each instance's kernel-aware outbox returns the expected tip.
  - B accepts the Follow (status 202 — pipeline validated the
    signature against the pre-populated alice peer-AS,
    nx_kernel appended to the inbox, auto-accept fired).
  - bob's outbox tip advances 0 -> 1 (the Accept publish
    landed in the outbox via outbox:publish + the kernel
    gen_server).

This exercises every layer that m2 built:
  - Step 8e httpc:request/4 BIF wrapper
  - Step 8f dispatch_http closure (delivery_worker for the peer)
  - Step 10c discovery_fetch (peer-actor doc shape)
  - Blockers #1 marshaller bridge (er-request-dict-to-proplist
    + er-proplist-to-dict)
  - Blockers #4 :pending-args substrate fix (kernel routes
    suspend/resume in the SX scheduler)

All under real cross-instance HTTP load with both kernels
running as full gen_servers.

Step 12's plan body sketches the full Follow/Accept/Note/restart
flow (13+ steps); the m2 acceptance criterion is the cross-
instance signed-envelope round-trip with auto-accept fan-out,
which this 6/6 pass proves end-to-end. Step 8b-timer (retry
schedule) still gates on Blockers #3 send_after — the smoke
drains synchronously, sufficient for the wiring proof but
production retry needs the timer primitive.

m2 is now feature-complete except for the substrate timer
gate. The plan's Step 12 entry is ticked and a Progress log
entry added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 20:36:14 +00:00
aec83f0aac artdag: Phase 7 consumes lib/maude mau/confluent? (no bespoke confluence checker)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 34s
The CID-stability check now calls mau/confluent? / mau/non-joinable-pairs from
lib/maude/confluence.sx (merged in) instead of re-implementing critical-pair
analysis inside lib/artdag. Picks up confluence.sx via the architecture merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:20:59 +00:00
7f7957ba25 Merge architecture: pick up lib/maude/confluence.sx (mau/confluent?) 2026-06-07 20:20:29 +00:00
0963aa51c9 Merge loops/maude into architecture: maude confluence/critical-pair checker (mau/confluent?)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 39s
Adds lib/maude/confluence.sx — the CID-stability oracle the artdag optimiser
needs. 274 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:19:17 +00:00
2dd4c7d974 maude: confluence / critical-pair checking (12 tests, 274 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
lib/maude/confluence.sx — two-sided syntactic unification (occurs-checked) →
critical pairs from LHS overlaps → joinability via AC-canonical normal forms.
mau/confluent? / mau/non-joinable-pairs / mau/critical-pairs / mau/cp->str.
Catches f(a)=b,a=c (b <?> f(c)); peano/idempotent/AC confirmed confluent.
Syntactic overlaps (AC under-approximated, joinability uses canon). This is
the CID-stability oracle for the artdag optimiser.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:18:33 +00:00
3432a72510 artdag: maude-bridge dag<->term adapter + 14 round-trip tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:07:34 +00:00
03c32cda5f fed-sx-m2: resolve Blockers #4 — kernel routes now work over real HTTP
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m6s
Substrate fix: two-line change to lib/erlang/runtime.sx that lets
http-listen handler routes call gen_server:call without deadlocking.

  1. er-sched-step-alive!: pass :pending-args (when set) to the
     initial-fun call instead of always passing an empty list.
     Default behavior (no field) stays (list) — drop-in safe.

  2. er-bif-http-listen sx-handler: instead of er-apply-fun handler
     inline (which blows up on receive's er-suspend-marker because
     the connection thread has no scheduler step on its stack),
     create a real er-process with :initial-fun = handler and
     :pending-args = (list req-pl), then er-sched-run-all! to drain.
     Any receive (e.g. gen_server:call) suspends + resumes inside
     the SX scheduler frame the process owns. Read :exit-result
     for the response proplist; marshal back to SX dict.

Investigation arc (see plans/fed-sx-milestone-2.md Blockers #4 +
Progress log):
  - loops/fed-prims bf8d0bf2 diagnosed it as Erlang-substrate, not
    OCaml mutex (Pattern A wrong, Pattern B right but sketchy).
  - First Pattern B attempt failed: tried er-spawn-fun on a raw SX
    lambda, hit (er-fun? fv) gate. Connection-thread bisect
    pinpointed the exact line.
  - Real fix: use the existing er-fun (user's handler) directly,
    but feed it via :pending-args so step-alive's hardcoded
    (list) doesn't drop the request arg.

Acceptance:
  - new next/tests/smoke_kernel_route.sh: 6/6 over real HTTP
    (welcome /, /actors/alice, /actors/alice/outbox with
    gen_server-backed tip, /actors/alice/inbox, unknown-actor,
    via http_server:start(P, [{kernel, nx_kernel}])).
  - next/tests/http_server_tcp.sh: 5/5 (bumped wait_bound from
    30s to 180s — cold boot is slow under sibling-loop CPU load
    and the per-handler scheduler ramp adds a small margin).
  - Erlang conformance: 761/761.

Step 12's two-instance smoke test is now unblocked — its full
Follow / Accept / Note flow can layer on top of this kernel-route
surface. m2 plan updated.

Pre-existing httpc_request.sh flakiness ("Undefined symbol:
http-request" on the live-call epochs) reproduces WITHOUT this
change — see git stash A/B in the investigation. Unrelated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 20:04:19 +00:00
c789e8b9ea Merge loops/events into architecture: VTIMEZONE iCal export (DST-correct tz recurrence, 376 tests) 2026-06-07 20:03:34 +00:00
826d926740 events: VTIMEZONE iCal export — full DST-correct tz recurrence + 16 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m5s
A tz event now exports DTSTART;TZID=<name>:<local> (EXDATE/RDATE likewise;
UNTIL stays UTC per RFC), and the VCALENDAR emits a VTIMEZONE per distinct zone
with DAYLIGHT/STANDARD sub-components generated from the zone's transition rules
(offsets + FREQ=YEARLY;BYMONTH;BYDAY) — London/Paris blocks match real-world
definitions. Clients recur at fixed wall-clock time, DST-correct (prior caveat
gone). Importer tolerates ;TZID= params. 376/376 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:03:07 +00:00
657d80611a artdag: promote maude-driven optimizer to active Phase 7
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 59s
lib/maude is now on this branch (fast-forwarded to architecture). The fit is
proven (lib/maude/tests/effects.sx). Phase 7 spells out the adapter
(maude-bridge.sx), the optimisation laws as a maude module, equivalence with
optimize.sx, and a syntactic confluence/CID-stability check. maude is a
read-only consumed substrate; gotchas recorded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:58:34 +00:00
88f4cfc384 Merge loops/events into architecture: events-on-sx iCal interop + series booking + tz fixes (366 tests, 13 suites)
iCalendar export+import (occurrence-exact round-trip), whole-series booking
(book/cancel across all occurrences), cross-event conflict-checked booking, and
timezone-aware iCal export (local->UTC stamps).
2026-06-07 19:52:06 +00:00
600d292ba2 fed-sx-m2: narrow Blockers #4 root cause via connection-thread bisect
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
Walked Pattern B's failure step-by-step from the connection thread
under a live http-listen instance, instrumenting each piece as its
own minimal sx-handler with a hardcoded reply dict:

  hardcoded {:status 200 :headers {} :body "..."}  -> HTTP 200 ✓
  read er-sched-process-count                      -> "procs=2" ✓
  er-pid-new!                                      -> 204 ✓
  er-proc-new! (er-env-new)                        -> 205 ✓
  er-spawn-fun (fn () 42)                          -> HTTP 000

The break is er-spawn-fun's (not (er-fun? fv)) gate raising
"Erlang: spawn/1: not a fun" because the raw SX lambda isn't an
Erlang-fun-shaped {:tag "fun"} dict. The `error` raise propagates
through Sx_runtime.sx_call and is swallowed by the native http-listen
(try ... with _ -> ()) at sx_server.ml:852; connection writes
nothing and closes -> curl reports HTTP 000.

This invalidates the previous "scheduler-re-entry race" hypothesis:
the global er-sched-* state IS shared with the connection thread
and reads correctly (process count of 2 = boot main + http:listen).
The breakage is the strict er-fun? shape check, not concurrency.

Path forward (still substrate scope, one helper):
  - Add an er-mk-host-fun helper in lib/erlang/runtime.sx (or a
    small AST-constructor in transpile.sx) that produces a real
    er-fun dict from a host SX closure.
  - sx-handler can then build a 0-arity wrapper-with-captured-req-pl
    and feed it to er-spawn-fun.
  - er-sched-run-all! drains, exit-result is read, response goes
    back to the wire.

Reverted runtime.sx to the Blockers #1 marshaller-bridge fix (the
in-flight Pattern B attempts are not committed). Blockers #4 entry
in plans/fed-sx-milestone-2.md updated with the verified diagnosis
and the one-helper path. Progress log entry added.

m2 stays at 11/12 steps; the substrate helper is loops/erlang scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 19:42:14 +00:00
5b472025db Merge loops/maude into architecture: maude-on-sx — term rewriting modulo AC
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 56s
Full Maude 3 functional+system core on SX (lib/maude): parser (sorts/subsorts/
overloading/mixfix), equational reduction modulo assoc/comm/id (the chisel),
conditional eqs + owise, system rules (rew + BFS search), strategy language,
META-LEVEL reflection, order-sorted least-sort, mixfix printer, end-to-end
program runner, gather right-assoc. 262 tests, 14 suites. Includes the
artdag-on-sx optimiser fit prototype (effects.sx).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:39:59 +00:00
d2f6bf02b3 maude: artdag-on-sx fit prototype — optimise passes as equations (8 tests, 262 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m7s
lib/maude/tests/effects.sx — proves artdag's effect-pipeline optimisations
(fusion, no-op/dead-op elim, identity elim, CSE/idempotent dedup) are
equational rewriting: the optimised pipeline is the normal form, confluence
gives a stable content id. The 'second consumer' spike for a maude-driven
optimiser in lib/artdag. Surfaced faithfulness note: id: affects matching/canon
not auto-reduction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:38:50 +00:00
fe958bda69 Merge loops/dream into architecture: dream-on-sx — OCaml Dream web framework reimplemented in plain SX
Full roadmap + 10 extensions, 413/413 tests across 17 suites (lib/dream/).
Five types (request/response/route + handler/middleware fns); router (params,
scopes, 405/HEAD), middleware, sessions (signed), flash, forms+CSRF+multipart,
websockets, static files, error handling, CORS, JSON, auth (base64/basic/bearer),
HTML escaping, security headers, dream-run + api facade, 4 demos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:19:36 +00:00
34c9b211ac events: fix timezone-aware iCal export (local->UTC stamps) + 6 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
Bug: tz events store wall-clock LOCAL times but export stamped them with a Z
(UTC) suffix, so a London 18:00 event falsely read as 18:00 UTC. ev-ical-conv
now converts a tz event's DTSTART/UNTIL/EXDATE/RDATE local->UTC before
formatting (London summer 18:00 -> 170000Z; Paris -> 160000Z); non-tz events
unchanged. Caveat: UTC RRULE drifts from wall-clock-stable tz recurrence across
a DST boundary (VTIMEZONE deferred). 366/366 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:34:20 +00:00
3913bc368c events: iCalendar import + occurrence-exact round-trip + 19 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 38s
ical.sx parses VEVENT/VCALENDAR text back into events (ev/ical-lines->event,
ev/parse-vcalendar): DTSTART/DURATION/RRULE (ordinal BYDAY, BYMONTHDAY, UNTIL/
COUNT/INTERVAL) + EXDATE/RDATE. Round-trip is occurrence-exact — export->import
expands to the identical occurrence set. Completes bidirectional interop.
360/360 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:28:26 +00:00