Files
rose-ash/plans/ra-kernel-spike.sh
giles 836d32474f spike: PERSISTENT next/ kernel is viable — unblocks RA-live + TA-live
The shared prerequisite for both live steps was: does a next/ kernel process hold gen_server state
(flow_store) across HTTP requests? Confirmed yes. plans/ra_kernel.erl is a minimal kernel
(flow_store + register the publish-digest flow, then a blocking http:listen that keeps the
er-scheduler + gen_server alive); plans/ra-kernel-spike.sh boots it as a background sx_server and
drives it with two SEPARATE curls: GET /start suspends instance 1, GET /resume resumes that SAME
live instance → done. So durable suspend→resume across requests works on a persistent kernel.

Design decision (per the discussion): chose the persistent-kernel path (B) over host-side replay-log
(A). B serves BOTH durability (RA) and federation (TA) on one fed-sx-native substrate and exposes the
full next/ kernel (projections, outbox, actor model); A only solves flow durability and mixes Erlang
into the host process. The er-scheduler-context bug (which kills an in-process kernel, option C) does
NOT bite a separate-process kernel — er-bif-http-listen spawns each handler in-scheduler, so
gen_server:call completes. Gotchas recorded: a blocking listener hangs any in-process
erlang-eval-ast (the kernel must be a dedicated TCP-driven process), and binary =:= is buggy (always
true) so routes must pattern-match paths as byte-list binaries.

RA-live + TA-live are now BUILD work (a real kernel service + the host as HTTP client + the actor
model), not research — the prerequisite is proven.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:00:32 +00:00

69 lines
3.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# plans/ra-kernel-spike.sh — does a PERSISTENT next/ kernel hold flow_store across HTTP requests?
# Boots a background sx_server running ra_kernel:start (flow_store + a blocking http:listen), then
# drives it with TWO separate curls: /start (suspend instance 1) then /resume (resume instance 1).
# If /resume returns done, the gen_server persisted across requests → RA-live + TA-live are unblocked.
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
[ -x "$SX_SERVER" ] || SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
PORT=51877
EPOCH_FILE=$(mktemp); LOG_FILE=$(mktemp)
cleanup() {
[ -n "${SXPID:-}" ] && { kill -KILL "$SXPID" 2>/dev/null || true; wait "$SXPID" 2>/dev/null || true; }
[ -n "${HOLDPID:-}" ] && { kill -KILL "$HOLDPID" 2>/dev/null || true; wait "$HOLDPID" 2>/dev/null || true; }
rm -f "$EPOCH_FILE" "$LOG_FILE"
}
trap cleanup EXIT
cat > "$EPOCH_FILE" <<EPOCHS
(epoch 1)
(load "lib/erlang/tokenizer.sx")
(load "lib/erlang/parser.sx")
(load "lib/erlang/parser-core.sx")
(load "lib/erlang/parser-expr.sx")
(load "lib/erlang/parser-module.sx")
(load "lib/erlang/transpile.sx")
(load "lib/erlang/runtime.sx")
(load "lib/erlang/vm/dispatcher.sx")
(epoch 2)
(eval "(er-load-gen-server!)")
(eval "(get (erlang-load-module (file-read \"next/kernel/envelope.erl\")) :name)")
(eval "(get (erlang-load-module (file-read \"next/flow/flow.erl\")) :name)")
(eval "(get (erlang-load-module (file-read \"next/flow/flow_spec.erl\")) :name)")
(eval "(get (erlang-load-module (file-read \"next/flow/flow_store.erl\")) :name)")
(eval "(get (erlang-load-module (file-read \"next/flow/flows/blog_publish_digest.erl\")) :name)")
(eval "(get (erlang-load-module (file-read \"plans/ra_kernel.erl\")) :name)")
(epoch 3)
(eval "(erlang-eval-ast \"ra_kernel:start($PORT)\")")
EPOCHS
FIFO=$(mktemp -u); mkfifo "$FIFO"
( cat "$EPOCH_FILE"; sleep 120 ) > "$FIFO" &
HOLDPID=$!
"$SX_SERVER" < "$FIFO" > "$LOG_FILE" 2>&1 &
SXPID=$!
rm -f "$FIFO"
echo "── waiting for the kernel to bind :$PORT ──"
BOUND=""
for i in $(seq 1 240); do
if (exec 3<>/dev/tcp/127.0.0.1/$PORT) 2>/dev/null; then BOUND=1; exec 3>&- 3<&-; echo "bound (iter $i)"; break; fi
sleep 1
done
if [ -z "$BOUND" ]; then echo "FAIL: never bound"; echo "--- log ---"; tail -20 "$LOG_FILE"; exit 1; fi
echo "── request 1: GET /start (creates instance 1, suspends) ──"
R1=$(curl -s -m 8 "http://127.0.0.1:$PORT/start")
echo " /start → $R1"
echo "── request 2 (SEPARATE): GET /resume (must hit the SAME live instance 1) ──"
R2=$(curl -s -m 8 "http://127.0.0.1:$PORT/resume")
echo " /resume → $R2"
echo "─────────────────────────────────────────────────────"
if echo "$R1" | grep -q "start:suspended" && echo "$R2" | grep -q "resume:done"; then
echo "PASS — flow_store PERSISTED across requests. Persistent kernel is VIABLE."
else
echo "FAIL — R1='$R1' R2='$R2'"; echo "--- log tail ---"; tail -20 "$LOG_FILE"
fi