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>
69 lines
3.1 KiB
Bash
Executable File
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
|