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 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 isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -1835,8 +1835,10 @@ PRIMITIVES["step-continue"] = stepContinue;
var contData = continuationData(f);
return (function() {
var captured = get(contData, "captured");
var restK = get(contData, "rest-kont");
return makeCekValue(arg, env, concat(captured, restK));
return (function() {
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() {
var params = lambdaParams(f);

View File

@@ -2101,13 +2101,17 @@
(define continue-with-call
(fn (f args env raw-args kont)
(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)
(let ((arg (if (empty? args) nil (first args)))
(cont-data (continuation-data f)))
(let ((captured (get cont-data "captured"))
(rest-k (get cont-data "rest-kont")))
(make-cek-value arg env (concat captured rest-k))))
(let ((captured (get cont-data "captured")))
;; Run ONLY the captured frames (delimited by reset).
;; 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
(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."))
;; -----------------------------------------------------------------------
;; 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
;; -----------------------------------------------------------------------
@@ -397,7 +522,7 @@
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM"))
(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
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" "Self-Hosting Bootstrapper"))
@@ -436,15 +561,18 @@
(~docs/section :title "Outcome" :id "outcome"
(p "After completion:")
(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 "
"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 "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 "The architecture proof: one language, one compiled evaluator, "
"every platform just provides effects."))
(li "The only interpreter in the system is the CPU."))
(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."))))