fed-sx-m1: Step 4f-consolidate — bootstrap:start/3 one-call boot + 10 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
build_genesis/1, verify_genesis/2,
|
||||
cidhash_path/1, write_cidhash/2, read_cidhash/1,
|
||||
load_genesis/1, strip_sx_suffix/1,
|
||||
populate_registry/0]).
|
||||
populate_registry/0,
|
||||
start/3]).
|
||||
|
||||
%% Genesis bundle reader per design §12.2.
|
||||
%%
|
||||
@@ -205,3 +206,18 @@ populate_entries(Kind, [{Name, Bytes} | Rest], Count) ->
|
||||
BaseName = strip_sx_suffix(Name),
|
||||
ok = registry:register(Kind, BaseName, Bytes),
|
||||
populate_entries(Kind, Rest, Count + 1).
|
||||
|
||||
%% start/3 — one-call bring-up of the kernel substrate. Starts
|
||||
%% the registry gen_server, populates it from the canonical
|
||||
%% genesis bundle, then starts the nx_kernel gen_server with the
|
||||
%% supplied actor identity / key / state. Returns the nx_kernel
|
||||
%% Pid (gen_server start_link convention in this port returns the
|
||||
%% raw Pid, not {ok, Pid}).
|
||||
%%
|
||||
%% Tests + production bring-up share this entry point. The
|
||||
%% caller is still responsible for starting any application-level
|
||||
%% projections and wiring them via nx_kernel:with_projections/1.
|
||||
start(ActorId, KeySpec, ActorState) ->
|
||||
registry:start_link(),
|
||||
populate_registry(),
|
||||
nx_kernel:start_link(ActorId, KeySpec, ActorState).
|
||||
|
||||
134
next/tests/bootstrap_start.sh
Executable file
134
next/tests/bootstrap_start.sh
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bash
|
||||
# next/tests/bootstrap_start.sh — Step 4f-consolidate test.
|
||||
#
|
||||
# bootstrap:start/3 is the one-call kernel bring-up: starts the
|
||||
# registry gen_server, populates it from the genesis bundle,
|
||||
# and starts the nx_kernel gen_server. Each test inlines the
|
||||
# start call with downstream operations because spawned
|
||||
# processes don't survive across separate erlang-eval-ast calls.
|
||||
# 11 cases.
|
||||
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||
fi
|
||||
if [ ! -x "$SX_SERVER" ]; then
|
||||
echo "ERROR: sx_server.exe not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERBOSE="${1:-}"
|
||||
PASS=0; FAIL=0; ERRORS=""
|
||||
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
||||
|
||||
PRELUDE='KM = <<1,2,3,4>>, KS = [{key_id,k1},{algorithm,ed25519},{value,KM}], AS = [{public_keys,[[{id,k1},{created,0},{value,KM}]]}], bootstrap:start(alice, KS, AS),'
|
||||
|
||||
cat > "$TMPFILE" <<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!)")
|
||||
(epoch 3)
|
||||
(eval "(get (erlang-load-module (file-read \"next/kernel/envelope.erl\")) :name)")
|
||||
(epoch 4)
|
||||
(eval "(get (erlang-load-module (file-read \"next/kernel/log.erl\")) :name)")
|
||||
(epoch 5)
|
||||
(eval "(get (erlang-load-module (file-read \"next/kernel/pipeline.erl\")) :name)")
|
||||
(epoch 6)
|
||||
(eval "(get (erlang-load-module (file-read \"next/kernel/projection.erl\")) :name)")
|
||||
(epoch 7)
|
||||
(eval "(get (erlang-load-module (file-read \"next/kernel/registry.erl\")) :name)")
|
||||
(epoch 8)
|
||||
(eval "(get (erlang-load-module (file-read \"next/kernel/outbox.erl\")) :name)")
|
||||
(epoch 9)
|
||||
(eval "(get (erlang-load-module (file-read \"next/kernel/nx_kernel.erl\")) :name)")
|
||||
(epoch 10)
|
||||
(eval "(get (erlang-load-module (file-read \"next/kernel/bootstrap.erl\")) :name)")
|
||||
|
||||
;; bootstrap:start returns a Pid
|
||||
(epoch 20)
|
||||
(eval "(get (erlang-eval-ast \"${PRELUDE} is_pid(whereis(nx_kernel))\") :name)")
|
||||
|
||||
;; Registry has 3 activity types after start
|
||||
(epoch 21)
|
||||
(eval "(erlang-eval-ast \"${PRELUDE} length(registry:list(activity_types))\")")
|
||||
|
||||
;; Registry has 10 object types
|
||||
(epoch 22)
|
||||
(eval "(erlang-eval-ast \"${PRELUDE} length(registry:list(object_types))\")")
|
||||
|
||||
;; Registry has 7 projections
|
||||
(epoch 23)
|
||||
(eval "(erlang-eval-ast \"${PRELUDE} length(registry:list(projections))\")")
|
||||
|
||||
;; Total entries across all kinds = 31
|
||||
(epoch 24)
|
||||
(eval "(erlang-eval-ast \"${PRELUDE} L = lists:map(fun (K) -> length(registry:list(K)) end, registry:kinds()), lists:foldl(fun (X, A) -> X + A end, 0, L)\")")
|
||||
|
||||
;; nx_kernel fresh log_tip = 0
|
||||
(epoch 25)
|
||||
(eval "(erlang-eval-ast \"${PRELUDE} nx_kernel:log_tip()\")")
|
||||
|
||||
;; nx_kernel publish advances log_tip
|
||||
(epoch 26)
|
||||
(eval "(erlang-eval-ast \"${PRELUDE} nx_kernel:publish([{type, create}, {object, nil}]), nx_kernel:log_tip()\")")
|
||||
|
||||
;; nx_kernel state carries the supplied actor_id
|
||||
(epoch 27)
|
||||
(eval "(get (erlang-eval-ast \"${PRELUDE} nx_kernel:actor_id(nx_kernel:query()) =:= alice\") :name)")
|
||||
|
||||
;; Registry lookup works after start (canonical entry: Create)
|
||||
(epoch 28)
|
||||
(eval "(get (erlang-eval-ast \"${PRELUDE} case registry:lookup(activity_types, <<99,114,101,97,116,101>>) of {ok, _} -> ok; _ -> bad end\") :name)")
|
||||
EPOCHS
|
||||
|
||||
OUTPUT=$(timeout 300 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||
|
||||
check() {
|
||||
local epoch="$1" desc="$2" expected="$3"
|
||||
local actual
|
||||
actual=$(echo "$OUTPUT" | awk -v e="$epoch" '
|
||||
$0 ~ "^\\(ok-len " e " " { getline; print; exit }
|
||||
$0 ~ "^\\(ok " e " " { print; exit }
|
||||
$0 ~ "^\\(error " e " " { print; exit }
|
||||
')
|
||||
[ -z "$actual" ] && actual="<no output for epoch $epoch>"
|
||||
if echo "$actual" | grep -qF -- "$expected"; then
|
||||
PASS=$((PASS+1))
|
||||
[ "$VERBOSE" = "-v" ] && echo " ok $desc"
|
||||
else
|
||||
FAIL=$((FAIL+1))
|
||||
ERRORS+=" FAIL [$desc] (epoch $epoch) expected: $expected | actual: $actual
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
check 10 "bootstrap module loaded" "bootstrap"
|
||||
check 20 "whereis(nx_kernel) is Pid" "true"
|
||||
check 21 "activity_types count = 3" "3"
|
||||
check 22 "object_types count = 10" "10"
|
||||
check 23 "projections count = 7" "7"
|
||||
check 24 "total entries = 31" "31"
|
||||
check 25 "fresh log_tip = 0" "0"
|
||||
check 26 "publish advances tip to 1" "1"
|
||||
check 27 "actor_id = alice" "true"
|
||||
check 28 "registry has create" "ok"
|
||||
|
||||
TOTAL=$((PASS+FAIL))
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo "ok $PASS/$TOTAL next/tests/bootstrap_start.sh passed"
|
||||
else
|
||||
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
|
||||
echo "$ERRORS"
|
||||
fi
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -253,6 +253,7 @@ replay(LogState, InitAcc, Fun) -> ...
|
||||
- [x] **4c** — `bootstrap:read_genesis/0,1` + `read_section/2` + `sections/0` + `section_subdir/1` + `ends_with_sx/1` in Erlang: walk seven hardcoded section subdirs, filter `.sx` files via byte-pattern suffix match, read each into a binary. Returns `{ok, [{Section, [{Name, Bytes}, ...]}, ...]}`. Skips SX parsing — the substrate has no in-Erlang binary→SX-term path (same gap as Step 3b); bundle CID over raw bytes is enough for Step 4d. `next/tests/bootstrap_read.sh` (15 cases).
|
||||
- [x] **4d** — `bootstrap:build_genesis/1` + `verify_genesis/2` + `cidhash_path/1` + `write_cidhash/2` + `read_cidhash/1`: bundle CID via host `cid:to_string` over `{genesis_bundle, Sections}`; mismatch returns `{error, {cid_mismatch, Got, Expected}}`; `.cidhash` sibling file persists between runs. `next/tests/bootstrap_build.sh` (12 cases).
|
||||
- [x] **4e** — `bootstrap:load_genesis/1` + `strip_sx_suffix/1`: bridges `read_genesis` output into `registry` entries. Section atom = registry kind; entry name = filename minus `.sx` (binary); entry value = raw file bytes (parsed forms replace these once an SX-parser bridge exists). `next/tests/bootstrap_load.sh` (15 cases).
|
||||
- [x] **4f-consolidate** — `bootstrap:start/3(ActorId, KeySpec, ActorState)` — one-call bring-up: `registry:start_link/0` → `populate_registry/0` → `nx_kernel:start_link/3`. Returns the kernel Pid. `next/tests/bootstrap_start.sh` (10 cases).
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
@@ -1002,6 +1003,7 @@ A few things still under-specified; resolve as work begins.
|
||||
Newest first. One line per sub-deliverable commit. Erlang conformance gate
|
||||
(`bash lib/erlang/conformance.sh`) must remain 729/729 on every entry.
|
||||
|
||||
- **2026-05-28** — Step 4f-consolidate: `bootstrap:start/3(ActorId, KeySpec, ActorState)` brings up the full kernel substrate in one call — starts the registry gen_server, populates it from the canonical genesis bundle (31 entries across 7 kinds), then starts nx_kernel. Returns the kernel Pid (gen_server convention in this port returns raw Pid not `{ok, Pid}`). Tests verify whereis(nx_kernel), per-kind counts (3/10/7/3/3/2/3), registry lookup of a known entry (`create`), publish + log_tip advance. `next/tests/bootstrap_start.sh` 10/10. Erlang conformance 729/729.
|
||||
- **2026-05-28** — Step 7d-pure: `next/kernel/sandbox.erl` — `eval_pure/2(Fun, Arg)` and `eval_pure/3(Fun, Activity, State)`. try/catch envelope returns `{ok, Result}` on success and `{error, {Class, Reason}}` for each of the three exception classes (throw, error, exit). The 3-arity variant matches the projection-fold shape so the scheduler can wrap fold bodies. Port note: this Erlang implementation catches by explicit class names rather than the open `Class:Reason` pattern — wrappers enumerate `throw:Reason / error:Reason / exit:Reason` explicitly. Real gas budget + IO denial + env-stripping lands with SX-source eval; the wrapper API doesn't change. `next/tests/sandbox_eval.sh` 13/13. Erlang conformance 729/729.
|
||||
- **2026-05-28** — Step 9b-pure: **reactive application extensibility, proven end-to-end.** Mirrors §Step 9b structurally without TCP/curl/JSON. A trigger projection (Erlang-fun fold over `{Captured, Count}` state) matches Note activities whose `:object :tags` contains `smoketest`, constructs a derived `TestEcho` activity with `:object :echoes` pointing at the Note's `:id`, and captures it into projection state. Order-independent; non-Note + non-smoketest + Note-without-tags + sig-failed publishes all suppressed correctly. Multi-tag (e.g. `[smoketest, foo, bar]`) still matches. Cascade publish (the trigger actually publishing the derived activity back through outbox) is deferred — the gen_server reentrancy that introduces is a v2 concern; the projection-state capture is sufficient proof of the match-then-derive mechanism. `next/tests/smoke_app_pure.sh` 12/12. Erlang conformance 729/729.
|
||||
- **2026-05-28** — Step 9a-pure: **the first verb-extensibility smoke test, proven end-to-end.** Mirrors §Step 9a structurally without TCP/curl/JSON. Two projections wired into `nx_kernel:with_projections([define_reg, pin_state])` — `define_reg` uses `define_registry:fold_fn/0` (Step 5d-pure), `pin_state` uses an Erlang-fun fold that records `{Path, Cid}` from Pin activities. Publish `Create{DefineActivity{name: pin}}` → registry update visible via `registry:lookup(activity_types, pin, projection:query(define_reg))`; publish `Pin{path: docs_intro, cid: qm_cid_1}` → `projection:query(pin_state) =:= [{docs_intro, qm_cid_1}]`. Order-independent (DefineActivity-then-Pin and Pin-then-DefineActivity both succeed); Note + non-Define types are pass-throughs in both projections. The TCP/curl variant (Step 9a-tcp) layers on Step 8b-start. `next/tests/smoke_pin_pure.sh` 13/13. Erlang conformance 729/729.
|
||||
|
||||
Reference in New Issue
Block a user