diff --git a/next/kernel/bootstrap.erl b/next/kernel/bootstrap.erl index 468d807c..5e86eaa6 100644 --- a/next/kernel/bootstrap.erl +++ b/next/kernel/bootstrap.erl @@ -4,7 +4,8 @@ default_base/0, ends_with_sx/1, build_genesis/1, verify_genesis/2, cidhash_path/1, write_cidhash/2, read_cidhash/1, - load_genesis/1, strip_sx_suffix/1]). + load_genesis/1, strip_sx_suffix/1, + populate_registry/0]). %% Genesis bundle reader per design §12.2. %% @@ -185,3 +186,22 @@ take_prefix(_, 0) -> <<>>; take_prefix(<>, N) when N > 0 -> Tail = take_prefix(Rest, N - 1), <>. + +%% populate_registry/0 — load the canonical genesis bundle and +%% register every entry in the running registry gen_server. The +%% caller is expected to have started the registry (via +%% registry:start_link/0) before calling this. Returns the count +%% of entries registered across all kinds. +populate_registry() -> + {ok, Sections} = read_genesis(), + populate_sections(Sections, 0). + +populate_sections([], Count) -> Count; +populate_sections([{Kind, Entries} | Rest], Count) -> + populate_sections(Rest, Count + populate_entries(Kind, Entries, 0)). + +populate_entries(_, [], Count) -> Count; +populate_entries(Kind, [{Name, Bytes} | Rest], Count) -> + BaseName = strip_sx_suffix(Name), + ok = registry:register(Kind, BaseName, Bytes), + populate_entries(Kind, Rest, Count + 1). diff --git a/next/tests/bootstrap_populate.sh b/next/tests/bootstrap_populate.sh new file mode 100755 index 00000000..e189bfa9 --- /dev/null +++ b/next/tests/bootstrap_populate.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# next/tests/bootstrap_populate.sh — Step 5c-populate acceptance test. +# +# Closes the bootstrap → registry loop end-to-end. Each test +# inlines registry:start_link() with bootstrap:populate_registry() +# because spawned processes don't survive separate erlang-eval-ast +# invocations. 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 + +# Shared prelude: starts registry, runs populate. +PRELUDE='registry:start_link(), N = bootstrap:populate_registry(),' + +cat > "$TMPFILE" <>) of {ok, B} -> is_binary(B) and (byte_size(B) > 100); _ -> false end\") :name)") + +;; A known object-type entry registered correctly +(epoch 31) +(eval "(get (erlang-eval-ast \"${PRELUDE} case registry:lookup(object_types, <<100,101,102,105,110,101,45,97,99,116,105,118,105,116,121>>) of {ok, B} -> is_binary(B); _ -> false end\") :name)") + +;; A known validator entry +(epoch 32) +(eval "(get (erlang-eval-ast \"${PRELUDE} case registry:lookup(validators, <<101,110,118,101,108,111,112,101,45,115,104,97,112,101>>) of {ok, B} -> is_binary(B); _ -> false 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="" + 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 2 "gen_server loaded" "gen_server" +check 3 "registry loaded" "registry" +check 4 "bootstrap loaded" "bootstrap" +check 10 "populate returns total 31" "31" +check 20 "activity_types count = 3" "3" +check 21 "object_types count = 10" "10" +check 22 "projections count = 7" "7" +check 23 "validators count = 3" "3" +check 24 "codecs count = 3" "3" +check 25 "sig_suites count = 2" "2" +check 26 "audience count = 3" "3" +check 30 "lookup activity_types/create" "true" +check 31 "lookup object_types/define-activity" "true" +check 32 "lookup validators/envelope-shape" "true" + +TOTAL=$((PASS+FAIL)) +if [ $FAIL -eq 0 ]; then + echo "ok $PASS/$TOTAL next/tests/bootstrap_populate.sh passed" +else + echo "FAIL $PASS/$TOTAL passed, $FAIL failed:" + echo "$ERRORS" +fi +[ $FAIL -eq 0 ] diff --git a/plans/fed-sx-milestone-1.md b/plans/fed-sx-milestone-1.md index c6c132be..0c2b49ff 100644 --- a/plans/fed-sx-milestone-1.md +++ b/plans/fed-sx-milestone-1.md @@ -340,7 +340,7 @@ created with a known stable CID. **Sub-deliverables:** - [x] **5a** — Pure-functional `next/kernel/registry.erl`: `new/0`, `kinds/0`, `register/4`, `lookup/3`, `list/2`. State is a property list keyed by kind atom; per-kind storage is a property list of `{Name, Entry}`. Unknown kinds rejected with `{error, unknown_kind}`. `next/tests/registry_pure.sh` (14 cases). - [x] **5b** — gen_server wrapper around the pure registry: `start_link/0`, registered name `registry`, `register/3 lookup/2 list/1 stop/0` API delegating through `gen_server:call`. `next/tests/registry_server.sh` (12 cases). Port note: each test combines start_link + ops in a single expression because spawned processes don't survive across separate `erlang-eval-ast` invocations. -- [ ] **5c** — `bootstrap:load_genesis/1` (Step 4e) populates the registry from `read_genesis` output. Dispatches by section atom → kind. +- [x] **5c-populate** — `bootstrap:populate_registry/0` walks `read_genesis` output and calls `registry:register/3` (the gen_server API) for each entry. Returns the total entries registered. `next/tests/bootstrap_populate.sh` (14 cases). - [ ] **5d** — define-registry projection fold integration: incoming `Create{Define*}` activities are routed through the projection scheduler (Step 7) and update the registry. **Deliverables:** @@ -996,6 +996,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 5c-populate: `bootstrap:populate_registry/0` walks `read_genesis` output and calls `registry:register/3` (gen_server API) for each entry. Total return is 31 = 3 + 10 + 7 + 3 + 3 + 2 + 3 across the seven kinds, matching the manifest authored in Step 4. `next/tests/bootstrap_populate.sh` 14/14 verifies per-kind counts + lookups against known names (`activity_types/create`, `object_types/define-activity`, `validators/envelope-shape`). Erlang conformance 729/729. - **2026-05-28** — Step 9-pre-fold: in-process integration test proving the full POST → publish → broadcast → projection-fold chain. With `projection:start_link` + `nx_kernel:start_link` + `nx_kernel:with_projections([p_count, p_collect])`, three authorized POST `/activity` calls advance both projections to 3 — and the kernel's log to 3 entries — and the projection's collected activity carries the POST body as `:object`. Unauthorized or sig-failed POSTs leave projection state unchanged. Step 9a/b proper (curl-driven smoke tests) wait on Step 8b-start (TCP) + Define\* SX-source eval bridge, but the structural chain is already verified end-to-end. `next/tests/http_publish_fold.sh` 10/10. Erlang conformance 729/729. Step 9 split into 9-pre-fold (done) + 9a + 9b. - **2026-05-28** — Step 8c-post-publish-http: POST `/activity` handler now bridges into `nx_kernel:publish/1` when the kernel gen_server is registered (`erlang:whereis(nx_kernel) =/= undefined`). On success the response carries the canonical CID via `cid_response/1`; on pipeline failure the response is 422 via `validation_failed_response/0`. When the kernel isn't registered, the handler falls through to the existing 200 stub — preserves backwards compatibility for the auth-only tests in `http_post_activity.sh`. Distinct POSTs produce distinct CIDs (next_published counter in nx_kernel state). Unauthorized POSTs never reach the kernel — log tip stays at 0. `next/tests/http_publish.sh` 10/10. The POST `/activity` → publish → fold loop is now functional end-to-end through the kernel. Erlang conformance 729/729. - **2026-05-28** — Step 8c-post-publish-srv: `nx_kernel.erl` extended with gen_server callbacks + named-process API. `start_link/3(ActorId, KeySpec, ActorState)` spawns the worker and registers under the literal `nx_kernel` atom; `publish/1(Request)` calls into `handle_call({publish, Request}, ...)` which delegates to the pure `publish/2` and reflects the new state back into the server. `query/0` returns the full state proplist; `log_tip/0` is a direct accessor; `with_projections/1` mutates the projections list. Same port quirks as Step 5b/7b documented (raw Pid return, no `?MODULE`, processes don't persist across separate `erlang-eval-ast` calls — tests inline start_link with operations). `next/tests/nx_kernel_server.sh` 11/11. Erlang conformance 729/729.