RA-live: durable business logic in production — host drives the kernel service (LIVE)

Steps 1+2 of RA-live/TA-live, live-verified end-to-end on blog.rose-ash.com.

(1) DEPLOY: docker-compose.dev-sx-host.yml gains an sx_kernel service running next/kernel/serve.sh
(the durable-execution kernel), SX_HTTP_HOST=0.0.0.0 so the host container reaches it at
http://sx_kernel:8930.

(2) HOST AS CLIENT: lib/host/ra.sx gains a KERNEL runner — host/ra--make-kernel-runner drives the
kernel over HTTP (http-request, native primitive; returns {status headers body}). It advertises
{effect,branch,each,suspend}, so select-runner routes a durable DAG to it. host/blog.sx: the DAG
registry + runner fleet are now mutable (register-dag!/add-runner!); emit! records SUSPENSIONS in a
durable pending log; /flows shows suspended instances with a resume link (?resume=<id>) driving
host/ra--kernel-resume. serve.sh wires it: set kernel-base, add the kernel runner, register the
durable 'blog-digest' DAG, declare a DURABLE behavior on article (create→publish SYNC, update→
blog-digest DURABLE), add a 'category' field.

LIVE PROOF: editing a published newsletter article → Update → routes to the kernel runner → POST
/flow/start/newsletter → kernel SUSPENDS (instance 5, shown pending on /flows) → /flows?resume=5 →
host re-drives the kernel → DONE → digest-sent effect + pending cleared. Durable suspend/resume
across separate HTTP requests, on a deployed persistent kernel. urgent edits complete immediately
(digest). http-request works in the serving context. blog 217/217, full conformance green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 19:31:33 +00:00
parent c0d9cb3cf4
commit cb0d866002
4 changed files with 149 additions and 13 deletions

View File

@@ -190,11 +190,36 @@ EPOCH=1
echo "(epoch $EPOCH)"
echo "(eval \"(host/blog-seed-types!)\")"
EPOCH=$((EPOCH+1))
# RA-LIVE: point at the durable-execution KERNEL, add it to the runner fleet, register the durable
# "blog-digest" DAG (needs {suspend} → routes to the kernel), and declare a DURABLE behavior on the
# article: an UPDATE (a published edit) runs the kernel flow (newsletter suspends until resumed,
# urgent completes now). create→publish stays SYNCHRONOUS (exec-fold). Then reload the registry +
# the pending log. host/blog--kernel-base is a mutable define set here.
echo "(epoch $EPOCH)"
echo "(eval \"(set! host/blog--kernel-base \\\"http://sx_kernel:8930\\\")\")"
EPOCH=$((EPOCH+1))
echo "(epoch $EPOCH)"
echo "(eval \"(host/blog--add-runner! (host/ra--make-kernel-runner host/blog--kernel-base))\")"
EPOCH=$((EPOCH+1))
echo "(epoch $EPOCH)"
echo "(eval \"(host/blog--register-dag! \\\"blog-digest\\\" {:erl-flow \\\"blog_digest\\\" :needs (list \\\"effect\\\" \\\"branch\\\" \\\"suspend\\\")})\")"
EPOCH=$((EPOCH+1))
echo "(epoch $EPOCH)"
echo "(eval \"(host/blog--set-type-behavior! \\\"article\\\" (list {\\\"verb\\\" \\\"create\\\" \\\"type\\\" \\\"article\\\" \\\"dag\\\" \\\"publish\\\"} {\\\"verb\\\" \\\"update\\\" \\\"type\\\" \\\"article\\\" \\\"dag\\\" \\\"blog-digest\\\"}))\")"
EPOCH=$((EPOCH+1))
# Give the article type a "category" field so the edit form can set newsletter/urgent — the
# branch the durable kernel flow keys on (newsletter suspends until resumed).
echo "(epoch $EPOCH)"
echo "(eval \"(host/blog--set-fields! \\\"article\\\" (list {:name \\\"category\\\" :type \\\"text\\\"}))\")"
EPOCH=$((EPOCH+1))
# P1: gather the types' declared :behavior bindings into the registry the trigger match
# consults (so publishing an article fires its declared on-publish DAG, runner derived).
echo "(epoch $EPOCH)"
echo "(eval \"(host/blog--load-behaviors!)\")"
EPOCH=$((EPOCH+1))
echo "(epoch $EPOCH)"
echo "(eval \"(host/blog-load-pendinglog!)\")"
EPOCH=$((EPOCH+1))
# Seed a live demo of the composition fold (plans/composition-objects.md): /compose-demo
# is one composition object rendered by host/comp-render — renders differently by context.
echo "(epoch $EPOCH)"