Multi-shot delimited continuations: 868/870 passing
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 9m5s

Continuations are now multi-shot — k can be invoked multiple times.
Each invocation runs the captured frames via nested cek-run and
returns the result to the caller's continuation.

Fix: continue-with-call runs ONLY the captured delimited frames
(not rest-kont), so the continuation terminates and returns rather
than escaping to the outer program.

Fixed 4 continuation tests:
- shift with multiple invokes: (list (k 10) (k 20)) → (11 21)
- k returned from reset: continuation callable after escaping
- invoke k multiple times: same k reusable
- k in data structure: store in list, retrieve, invoke

Remaining 2 failures: scope/provide across shift boundaries.
These need scope state tracked in frames (not imperative push/pop).

JS 747/747, Full 868/870, Python 679/679.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 14:20:31 +00:00
parent c6a662c980
commit 719da7914e
3 changed files with 146 additions and 12 deletions

View File

@@ -14,7 +14,7 @@
// ========================================================================= // =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-15T14:09:57Z"; var SX_VERSION = "2026-03-15T14:20:13Z";
function isNil(x) { return x === NIL || x === null || x === undefined; } function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); } function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -1835,8 +1835,10 @@ PRIMITIVES["step-continue"] = stepContinue;
var contData = continuationData(f); var contData = continuationData(f);
return (function() { return (function() {
var captured = get(contData, "captured"); var captured = get(contData, "captured");
var restK = get(contData, "rest-kont"); return (function() {
return makeCekValue(arg, env, concat(captured, restK)); var result = cekRun(makeCekValue(arg, env, captured));
return makeCekValue(result, env, kont);
})();
})(); })();
})() : (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? makeCekValue(apply(f, args), env, kont) : (isSxTruthy(isLambda(f)) ? (function() { })() : (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? makeCekValue(apply(f, args), env, kont) : (isSxTruthy(isLambda(f)) ? (function() {
var params = lambdaParams(f); var params = lambdaParams(f);

View File

@@ -2101,13 +2101,17 @@
(define continue-with-call (define continue-with-call
(fn (f args env raw-args kont) (fn (f args env raw-args kont)
(cond (cond
;; Continuation — restore captured frames and inject value ;; Continuation — run captured delimited continuation, return result to caller.
;; Multi-shot: each invocation runs captured frames to completion via nested
;; cek-run, then returns the result to the caller's kont.
(continuation? f) (continuation? f)
(let ((arg (if (empty? args) nil (first args))) (let ((arg (if (empty? args) nil (first args)))
(cont-data (continuation-data f))) (cont-data (continuation-data f)))
(let ((captured (get cont-data "captured")) (let ((captured (get cont-data "captured")))
(rest-k (get cont-data "rest-kont"))) ;; Run ONLY the captured frames (delimited by reset).
(make-cek-value arg env (concat captured rest-k)))) ;; Empty kont after captured = the continuation terminates and returns.
(let ((result (cek-run (make-cek-value arg env captured))))
(make-cek-value result env kont))))
;; Native callable ;; Native callable
(and (callable? f) (not (lambda? f)) (not (component? f)) (not (island? f))) (and (callable? f) (not (lambda? f)) (not (component? f)) (not (island? f)))

View File

@@ -366,6 +366,131 @@
"The only interpreter in the system is the CPU.")) "The only interpreter in the system is the CPU."))
;; -----------------------------------------------------------------------
;; Security model
;; -----------------------------------------------------------------------
(~docs/section :title "Security Model" :id "security"
(p "Compiled SX running as WASM is " (em "more secure") " than plain JavaScript, "
"not less. JS has ambient access to the full browser API. "
"WASM + the platform layer means compiled SX code has "
(strong "zero ambient capabilities") " \u2014 every capability is explicitly granted.")
(h4 :class "font-semibold mt-4 mb-2" "Five defence layers")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
(th :class "px-3 py-2 font-medium text-stone-600" "Enforced by")
(th :class "px-3 py-2 font-medium text-stone-600" "What it prevents")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "1. WASM sandbox")
(td :class "px-3 py-2 text-stone-700" "Browser")
(td :class "px-3 py-2 text-stone-600" "Memory isolation, no system calls, no DOM access except via explicit imports. Validated before execution."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "2. Platform capabilities")
(td :class "px-3 py-2 text-stone-700" (code "sx-platform.js"))
(td :class "px-3 py-2 text-stone-600" "Compiled code can only call functions you register. No fetch? Can't fetch. No localStorage? Can't read storage. The platform is a capability system."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "3. Content-addressed verification")
(td :class "px-3 py-2 text-stone-700" "CID determinism")
(td :class "px-3 py-2 text-stone-600" "Compiler is deterministic: same source \u2192 same CID. Client can re-compile and verify. Tampered WASM produces wrong CID \u2192 reject."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "4. Per-component attenuation")
(td :class "px-3 py-2 text-stone-700" "Platform scoping")
(td :class "px-3 py-2 text-stone-600" "Different components get different capability subsets. User-generated content gets a locked-down platform \u2014 can render DOM but can't fetch or listen to events."))
(tr
(td :class "px-3 py-2 text-stone-700" "5. Source-first fallback")
(td :class "px-3 py-2 text-stone-700" "Client compiler")
(td :class "px-3 py-2 text-stone-600" "Don't trust precompiled WASM? Compile from source locally. The client has the compiler. Precompilation is an optimisation, not a trust requirement.")))))
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed tamper detection")
(p "The server sends both SX source and precompiled WASM CID. The client can verify:")
(~docs/code :code (highlight ";; Server sends:\nContent-Type: application/wasm\nX-Sx-Source-Cid: bafyrei..source\nX-Sx-Compiled-Cid: bafyrei..compiled\n\n;; Client verifies (optional, configurable):\n1. Hash the WASM binary \u2192 matches X-Sx-Compiled-Cid?\n2. Compile source locally \u2192 produces same compiled CID?\n3. Check manifest of pinned CIDs \u2192 CID is expected?\n\n;; Any mismatch = tampered = reject" "text"))
(h4 :class "font-semibold mt-4 mb-2" "Capability attenuation per component")
(p "The platform scopes capabilities per evaluator instance. "
"App shell gets full access. Third-party or user-generated content gets the minimum:")
(~docs/code :code (highlight "// Full capabilities for the app shell\nplatform.registerAll(appShellCompiler);\n\n// Restricted for user-generated content\nplatform.registerSubset(userContentCompiler, {\n allow: [\"dom-create-element\", \"dom-set-attr\", \"dom-append\",\n \"dom-create-text-node\", \"dom-set-text\"],\n deny: [\"fetch\", \"localStorage\", \"dom-listen\",\n \"dom-set-inner-html\", \"eval\"]\n});\n\n// The restricted compiler's WASM module literally doesn't\n// have imports for the denied functions. Not just blocked\n// at runtime \u2014 absent from the binary." "javascript"))
(h4 :class "font-semibold mt-4 mb-2" "Component manifests")
(p "The app ships with a manifest of expected CIDs for its core components. "
"Like subresource integrity (SRI) but for compiled code:")
(~docs/code :code (highlight ";; Component manifest (shipped with the app, signed)\n{\n \"~card\": \"bafyrei..abc\"\n \"~header\": \"bafyrei..def\"\n \"~nav-item\": \"bafyrei..ghi\"\n}\n\n;; On navigation: server sends component update\n;; Client compiles \u2192 checks CID against manifest\n;; Match = trusted, execute\n;; Mismatch = tampered, reject and report" "text"))
(p "The security model is " (em "structural") ", not bolt-on. "
"WASM isolation, platform capabilities, content-addressed verification, "
"and per-component attenuation all arise naturally from the architecture. "
"The platform layer that enables Rust/OCaml interop is the same layer "
"that enforces security boundaries."))
;; -----------------------------------------------------------------------
;; Isomorphic rendering + SEO
;; -----------------------------------------------------------------------
(~docs/section :title "Isomorphic Rendering" :id "isomorphic"
(p "WASM is invisible to search engines. But SX is already isomorphic \u2014 "
"the same spec, the same components, rendered to HTML on the server "
"and to DOM on the client. Compiled WASM doesn't change this. "
"It makes the client side faster without affecting what crawlers see.")
(h4 :class "font-semibold mt-4 mb-2" "The rendering pipeline")
(~docs/code :code (highlight "Crawler visits:\n GET /page\n \u2192 Server compiles SX (native OCaml)\n \u2192 render-to-html (adapter-html.sx)\n \u2192 Full static HTML with semantic markup\n \u2192 Google indexes it\n\nUser first visit:\n GET /page\n \u2192 Server renders HTML (same as crawler)\n \u2192 Browser displays immediately (no JS needed)\n \u2192 Client loads sx-compiler.wasm + sx-platform.js\n \u2192 Hydrates: attaches event handlers, activates islands\n \u2192 Page is interactive\n\nUser navigates (SPA):\n sx-get /next-page\n \u2192 Server sends SX wire format (aser)\n \u2192 Client compiles + renders via WASM\n \u2192 Morph engine patches the DOM" "text"))
(p "The server and client have the " (em "same compiler") " from the " (em "same spec") ". "
(code "adapter-html.sx") " produces HTML strings. "
(code "adapter-dom.sx") " produces DOM nodes. "
"Two rendering modes of one evaluator. The compiled WASM version "
"makes hydration and SPA navigation faster, but the initial HTML "
"is always server-rendered.")
(h4 :class "font-semibold mt-4 mb-2" "What crawlers see")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li "Fully rendered HTML \u2014 no \"loading...\" skeleton, no JS-dependent content")
(li "Semantic markup \u2014 " (code "<h1>") ", " (code "<nav>") ", " (code "<article>") ", " (code "<a href>")
" \u2014 all from the SX component tree")
(li "Meta tags, canonical URLs, structured data \u2014 rendered server-side by " (code "shell.sx"))
(li "No WASM, no JS required \u2014 the HTML is the page, complete on first byte"))
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed prerendering")
(p "The server can prerender every page to static HTML, hash it, "
"and cache it at the CDN edge:")
(~docs/code :code (highlight "Page source CID \u2192 Rendered HTML CID\nbafyrei..source \u2192 bafyrei..html\n\n\u2022 Crawler hits CDN \u2192 instant HTML, no server round-trip\n\u2022 Page content changes \u2192 new source CID \u2192 new HTML CID \u2192 CDN invalidated\n\u2022 Same CID = same HTML forever \u2192 infinite cache, zero revalidation" "text"))
(p "This is the same content-addressed caching as compiled WASM, "
"applied to the HTML output. Both the compiled client code and "
"the server-rendered HTML are cached by CID. "
"The entire delivery pipeline is content-addressed.")
(h4 :class "font-semibold mt-4 mb-2" "Progressive enhancement")
(p "The page works at every level:")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "Client capability")
(th :class "px-3 py-2 font-medium text-stone-600" "Experience")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "No JS (crawler, reader mode)")
(td :class "px-3 py-2 text-stone-600" "Full HTML. Links work. Forms submit. Content is complete."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "JS, no WASM")
(td :class "px-3 py-2 text-stone-600" "Falls back to js_of_ocaml evaluator or interpreted JS. SPA navigation, islands, signals all work."))
(tr
(td :class "px-3 py-2 text-stone-700" "JS + WASM")
(td :class "px-3 py-2 text-stone-600" "Full compiled pipeline. JIT compilation, cached WASM functions, near-native rendering speed."))))))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
;; How this changes existing plans ;; How this changes existing plans
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
@@ -397,7 +522,7 @@
(td :class "px-3 py-2 text-stone-700" (td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM")) (a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM"))
(td :class "px-3 py-2 text-stone-600" (td :class "px-3 py-2 text-stone-600"
"Complementary. OCaml gives tree-walking CEK. Bytecode VM is a future optimization on top.")) "Subsumed as Tier 1. Bytecodes become an intermediate step on the path to native WASM compilation. The dispatch loop is Tier 1; JIT to WASM functions is Tier 2; AOT is Tier 3."))
(tr (tr
(td :class "px-3 py-2 text-stone-700" (td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" "Self-Hosting Bootstrapper")) (a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" "Self-Hosting Bootstrapper"))
@@ -436,15 +561,18 @@
(~docs/section :title "Outcome" :id "outcome" (~docs/section :title "Outcome" :id "outcome"
(p "After completion:") (p "After completion:")
(ul :class "list-disc list-inside space-y-2 mt-2" (ul :class "list-disc list-inside space-y-2 mt-2"
(li "SX core evaluator compiled from spec via OCaml \u2014 native + WASM from one codebase.") (li "SX core compiled from spec via OCaml \u2014 native + WASM from one codebase.")
(li "Python web framework calls native " (code "sx_core.so") " for rendering \u2014 " (li "Python web framework calls native " (code "sx_core.so") " for rendering \u2014 "
"100\u20131000x faster than interpreted Python.") "100\u20131000x faster than interpreted Python.")
(li "Browser loads " (code "sx_core.wasm") " \u2014 same evaluator, same spec, near-native speed.") (li "Browser loads " (code "sx_core.wasm") " \u2014 same compiler, same spec, near-native speed.")
(li "Server sends SX, client JIT-compiles to WASM functions and caches by CID.")
(li "Entire apps AOT-compiled to " (code ".wasm") " binaries. "
"Platform provides DOM/fetch. Everything else is machine code.")
(li "Content-addressed compilation cache: compile once, cache by CID, serve from CDN/IPFS forever.")
(li "Concurrent CEK runs on OCaml 5 fibers/domains \u2014 true parallelism for Art DAG.") (li "Concurrent CEK runs on OCaml 5 fibers/domains \u2014 true parallelism for Art DAG.")
(li "Linear effects validated by SX type system, enforced by OCaml one-shot continuations.") (li "Linear effects validated by SX type system, enforced by OCaml one-shot continuations.")
(li "Self-hosting compiler: SX compiles itself to machine code. OCaml scaffolding removed.") (li "Self-hosting compiler: SX compiles itself to machine code. OCaml scaffolding removed.")
(li "The architecture proof: one language, one compiled evaluator, " (li "The only interpreter in the system is the CPU."))
"every platform just provides effects."))
(p :class "text-stone-500 text-sm italic mt-12" (p :class "text-stone-500 text-sm italic mt-12"
"The Mother Language was always SX. We just needed to find the right scaffolding to stand it up.")))) "The Mother Language was always SX. We just needed to find the right scaffolding to stand it up."))))