;; lib/host/tests/ra.sx — the RA (Erlang durable) runner adapter (lib/host/ra.sx), unit-tested with ;; a MOCK erl-eval (no Erlang runtime needed). The REAL dispatch is proven in plans/ra-spike.sh. (define host-ra-pass 0) (define host-ra-fail 0) (define host-ra-fails (list)) (define host-ra-test (fn (name actual expected) (if (= actual expected) (set! host-ra-pass (+ host-ra-pass 1)) (begin (set! host-ra-fail (+ host-ra-fail 1)) (append! host-ra-fails {:name name :actual actual :expected expected}))))) (define ra-urgent {:verb "create" :actor "site" :id "u1" :object-type "article" :category "urgent"}) (define ra-news {:verb "create" :actor "site" :id "n1" :object-type "article" :category "newsletter"}) ;; ── marshalling: our canonical activity → Erlang source ── (host-ra-test "erl-src marshals the activity → Erlang activity-proplist source" (host/ra--erl-src ra-urgent) "[{type, 'create'}, {actor, 'site'}, {id, <<\"u1\">>}, {object, [{type, 'article'}, {category, 'urgent'}]}]") (host-ra-test "start-expr wraps it in flow_store:start with the env" (host/ra--start-expr "bd" ra-urgent) "flow_store:start(bd, [{activity, [{type, 'create'}, {actor, 'site'}, {id, <<\"u1\">>}, {object, [{type, 'article'}, {category, 'urgent'}]}]}, {actor, 'site'}])") ;; ── result parsing: flow_store's er-to-sx-deep'd result → the seam runner contract ── (host-ra-test "parse DONE → {:status done :effects … :flow-id}" (let ((p (host/ra--parse (list "ok" 1 (list "flow_done" (list "digest_sent")))))) (list (get p :status) (get p :flow-id))) (list "done" 1)) (host-ra-test "parse SUSPENDED → {:status suspended :resume {:id :tag}}" (let ((p (host/ra--parse (list "ok" 1 (list "flow_suspended" "morning"))))) (list (get p :status) (get (get p :resume) :id) (get (get p :resume) :tag))) (list "suspended" 1 "morning")) (host-ra-test "parse garbage → failed" (get (host/ra--parse (list "error" "boom")) :status) "failed") ;; ── the runner (MOCK erl-eval keyed on the marshalled src) ── (define ra-mock (fn (src) (if (>= (index-of src "newsletter") 0) (list "ok" 7 (list "flow_suspended" "morning")) (list "ok" 3 (list "flow_done" (list "digest_sent")))))) (define ra-runner (host/ra--make-runner ra-mock)) (host-ra-test "RA runner advertises {effect, branch, each, suspend}" (get ra-runner :capabilities) (list "effect" "branch" "each" "suspend")) (host-ra-test "RA runner: urgent → done; newsletter → suspended (via the injected erl-eval)" (list (get ((get ra-runner :run) {:erl-flow "bd"} {:activity ra-urgent}) :status) (get ((get ra-runner :run) {:erl-flow "bd"} {:activity ra-news}) :status)) (list "done" "suspended")) (host-ra-test "RA resume drives a suspended instance (async re-entry)" (get (host/ra--resume ra-mock 7 "morning_ts") :status) "done") ;; ── dual-runner routing: the capability model, now REAL (2 runners) ── (host-ra-test "select-runner: {effect,branch} composition → exec-runner; {suspend} dag → RA" (let ((fleet (list host/flow--exec-runner ra-runner))) (list (get (host/flow--select-runner fleet (quote (alt (when (eq "k" "v") (effect a)) (else (effect b))))) :capabilities) (get (host/flow--select-runner fleet {:erl-flow "bd" :needs (list "effect" "branch" "suspend")}) :capabilities))) (list (list "effect" "branch" "each") (list "effect" "branch" "each" "suspend"))) (define host-ra-tests-run! (fn () {:total (+ host-ra-pass host-ra-fail) :passed host-ra-pass :failed host-ra-fail :fails host-ra-fails}))