Step 3: IO registry — spec-level defio + io contract dispatch

Promotes defio from native OCaml special form to spec-level CEK
evaluator feature. The IO registry is now the contract layer between
evaluator and platform.

Evaluator additions (spec/evaluator.sx):
- *io-registry* mutable dict global (like *library-registry*)
- io-register!, io-registered?, io-lookup, io-names accessors
- defio-parse-kwargs! recursive keyword parser
- sf-defio processes (defio "name" :category :data :params (...) ...)
- "defio" dispatch in step-eval-list
- step-sf-io: the contract function — validates against registry,
  then delegates to perform for IO suspension
- "io" dispatch in step-eval-list

Native OCaml defio handlers removed from:
- sx_server.ml (~20 lines)
- sx_browser.ml (~20 lines)
- run_tests.ml (~18 lines)
All replaced with __io-registry alias to spec's *io-registry*.

IO accessor functions bound in run_tests.ml env so tests can
call io-registered?, io-lookup, io-names.

10 new tests (spec/tests/test-io-registry.sx):
- defio populates registry
- io-lookup returns spec with name/category/returns/doc
- io-registered?/io-names work correctly
- kwargs parsing (batchable, cacheable, params)
- io contract rejects unregistered ops
- io contract passes validation for registered ops

2608/2608 tests passing (+10 new).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 21:18:04 +00:00
parent b6f304e91a
commit 5f72801901
7 changed files with 256 additions and 120 deletions

View File

@@ -670,26 +670,9 @@ let () =
bind "define-page-helper" (fun _ -> Nil);
(* IO registry — starts empty in browser. Platforms extend via defio.
Browser has zero suspension points initially; future browser IO
(lazy module loads, fetch-request) will add entries here. *)
let io_registry = Hashtbl.create 16 in
ignore (env_bind global_env "__io-registry" (Dict io_registry));
ignore (Sx_ref.register_special_form (String "defio") (NativeFn ("defio", fun sf_args ->
let raw_args = match sf_args with
| [List a; Env _] | [ListRef { contents = a }; Env _] -> a
| _ -> [] in
match raw_args with
| String name :: rest ->
let entry = Hashtbl.create 8 in
let rec parse = function
| Keyword k :: v :: rest -> Hashtbl.replace entry k v; parse rest
| _ -> () in
parse rest;
Hashtbl.replace entry "name" (String name);
Hashtbl.replace io_registry name (Dict entry);
Dict entry
| _ -> Nil)));
(* IO registry — spec-level defio populates *io-registry* in evaluator.
Alias as __io-registry for backward compat. *)
ignore (env_bind global_env "__io-registry" Sx_ref._io_registry_);
(* --- Render --- *)
Sx_render.setup_render_env global_env;