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>
42 lines
1.8 KiB
Erlang
42 lines
1.8 KiB
Erlang
%% plans/ra_kernel.erl — RA/TA persistent-kernel SPIKE.
|
|
%% A minimal long-lived next/ kernel: starts flow_store + registers the publish-digest flow, then
|
|
%% blocks in http:listen — keeping the er-scheduler (and flow_store's gen_server) alive across
|
|
%% requests. Two routes drive flow_store over HTTP: GET /start (start a newsletter flow → suspend)
|
|
%% and GET /resume (resume instance 1 → done). If /resume completes in a SEPARATE request from
|
|
%% /start, the gen_server persisted across requests — the persistent-kernel prerequisite for
|
|
%% RA-live + TA-live holds.
|
|
-module(ra_kernel).
|
|
-export([start/1]).
|
|
|
|
start(Port) ->
|
|
flow_store:start_link(),
|
|
FF = fun (_) -> [f1, f2, f3] end,
|
|
flow_store:register_flow(bd, blog_publish_digest:build([{fetch_followers, FF}])),
|
|
http:listen(Port, fun (Req) -> route(Req) end).
|
|
|
|
route(Req) ->
|
|
[{status, 200}, {headers, []}, {body, respond(field(path, Req))}].
|
|
|
|
%% /start (bytes 47,115,116,97,114,116)
|
|
respond(<<47,115,116,97,114,116>>) ->
|
|
Env = [{activity, [{type, create}, {actor, alice}, {id, <<110,49>>},
|
|
{object, [{type, article}, {category, newsletter}]}]},
|
|
{actor, alice}],
|
|
case flow_store:start(bd, Env) of
|
|
{ok, _Id, {flow_suspended, _}} -> <<"start:suspended">>;
|
|
{ok, _Id, {flow_done, _}} -> <<"start:done">>;
|
|
_ -> <<"start:other">>
|
|
end;
|
|
%% /resume (bytes 47,114,101,115,117,109,101)
|
|
respond(<<47,114,101,115,117,109,101>>) ->
|
|
case flow_store:resume(1, morning_ts) of
|
|
{ok, {flow_done, _}} -> <<"resume:done">>;
|
|
{flow_done, _} -> <<"resume:done">>;
|
|
_ -> <<"resume:other">>
|
|
end;
|
|
respond(_) -> <<"path:unknown">>.
|
|
|
|
field(K, [{K, V} | _]) -> V;
|
|
field(K, [_ | Rest]) -> field(K, Rest);
|
|
field(_, []) -> nil.
|