fed-sx-m1: Step 5b — gen_server-wrapped registry + named-process API + 12 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
-module(registry).
|
-module(registry).
|
||||||
|
-behaviour(gen_server).
|
||||||
-export([new/0, kinds/0, register/4, lookup/3, list/2]).
|
-export([new/0, kinds/0, register/4, lookup/3, list/2]).
|
||||||
|
-export([start_link/0, register/3, lookup/2, list/1, stop/0]).
|
||||||
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
|
||||||
|
|
||||||
%% Pure-functional registry for the seven bootstrap kinds.
|
%% Pure-functional registry for the seven bootstrap kinds.
|
||||||
%%
|
%%
|
||||||
@@ -67,3 +70,51 @@ put_pair(K, V, [P | Rest]) -> [P | put_pair(K, V, Rest)].
|
|||||||
find_pair(_, []) -> not_found;
|
find_pair(_, []) -> not_found;
|
||||||
find_pair(K, [{K, V} | _]) -> {ok, V};
|
find_pair(K, [{K, V} | _]) -> {ok, V};
|
||||||
find_pair(K, [_ | Rest]) -> find_pair(K, Rest).
|
find_pair(K, [_ | Rest]) -> find_pair(K, Rest).
|
||||||
|
|
||||||
|
%% ── Step 5b: gen_server wrapper ─────────────────────────────────
|
||||||
|
%%
|
||||||
|
%% The named process owns the registry state; concurrent readers
|
||||||
|
%% and writers serialize through gen_server:call. The pure /3 and
|
||||||
|
%% /4 functions remain available for offline projection-replay and
|
||||||
|
%% for tests that don't need a process at all.
|
||||||
|
%%
|
||||||
|
%% Port notes: gen_server:start_link returns the raw Pid (not
|
||||||
|
%% `{ok, Pid}` as in OTP). `?MODULE` macro is unsupported here, so
|
||||||
|
%% the registered name is the literal `registry` atom in every call.
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
Pid = gen_server:start_link(registry, []),
|
||||||
|
erlang:register(registry, Pid),
|
||||||
|
Pid.
|
||||||
|
|
||||||
|
stop() ->
|
||||||
|
R = gen_server:call(registry, '$gen_stop'),
|
||||||
|
erlang:unregister(registry),
|
||||||
|
R.
|
||||||
|
|
||||||
|
register(Kind, Name, Entry) ->
|
||||||
|
gen_server:call(registry, {register, Kind, Name, Entry}).
|
||||||
|
|
||||||
|
lookup(Kind, Name) ->
|
||||||
|
gen_server:call(registry, {lookup, Kind, Name}).
|
||||||
|
|
||||||
|
list(Kind) ->
|
||||||
|
gen_server:call(registry, {list, Kind}).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
|
||||||
|
init(_) -> {ok, new()}.
|
||||||
|
|
||||||
|
handle_call({register, Kind, Name, Entry}, _From, State) ->
|
||||||
|
case register(Kind, Name, Entry, State) of
|
||||||
|
{ok, NewState} -> {reply, ok, NewState};
|
||||||
|
{error, R} -> {reply, {error, R}, State}
|
||||||
|
end;
|
||||||
|
handle_call({lookup, Kind, Name}, _From, State) ->
|
||||||
|
{reply, lookup(Kind, Name, State), State};
|
||||||
|
handle_call({list, Kind}, _From, State) ->
|
||||||
|
{reply, list(Kind, State), State}.
|
||||||
|
|
||||||
|
handle_cast(_, S) -> {noreply, S}.
|
||||||
|
|
||||||
|
handle_info(_, S) -> {noreply, S}.
|
||||||
|
|||||||
122
next/tests/registry_server.sh
Executable file
122
next/tests/registry_server.sh
Executable file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# next/tests/registry_server.sh — Step 5b acceptance test.
|
||||||
|
#
|
||||||
|
# Exercises the gen_server-wrapped registry. Each test combines
|
||||||
|
# start_link + operations + assertion into a single
|
||||||
|
# erlang-eval-ast expression because the Erlang-on-SX scheduler
|
||||||
|
# does not preserve spawned processes across separate eval
|
||||||
|
# invocations. 10 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
|
||||||
|
|
||||||
|
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/registry.erl\")) :name)")
|
||||||
|
|
||||||
|
;; start_link returns a Pid
|
||||||
|
(epoch 10)
|
||||||
|
(eval "(get (erlang-eval-ast \"is_pid(registry:start_link())\") :name)")
|
||||||
|
|
||||||
|
;; register + lookup round-trip
|
||||||
|
(epoch 11)
|
||||||
|
(eval "(get (erlang-eval-ast \"registry:start_link(), registry:register(activity_types, create, e1), registry:lookup(activity_types, create) =:= {ok, e1}\") :name)")
|
||||||
|
|
||||||
|
;; lookup unknown name returns not_found
|
||||||
|
(epoch 12)
|
||||||
|
(eval "(get (erlang-eval-ast \"registry:start_link(), registry:lookup(activity_types, missing) =:= not_found\") :name)")
|
||||||
|
|
||||||
|
;; register returns the atom 'ok'
|
||||||
|
(epoch 13)
|
||||||
|
(eval "(get (erlang-eval-ast \"registry:start_link(), registry:register(object_types, note, e_n) =:= ok\") :name)")
|
||||||
|
|
||||||
|
;; list returns all pairs in a kind
|
||||||
|
(epoch 14)
|
||||||
|
(eval "(erlang-eval-ast \"registry:start_link(), registry:register(activity_types, a, 1), registry:register(activity_types, b, 2), registry:register(activity_types, c, 3), length(registry:list(activity_types))\")")
|
||||||
|
|
||||||
|
;; Re-register overrides without growing the list
|
||||||
|
(epoch 15)
|
||||||
|
(eval "(erlang-eval-ast \"registry:start_link(), registry:register(activity_types, a, v1), registry:register(activity_types, a, v2), length(registry:list(activity_types))\")")
|
||||||
|
(epoch 16)
|
||||||
|
(eval "(get (erlang-eval-ast \"registry:start_link(), registry:register(activity_types, a, v1), registry:register(activity_types, a, v2), registry:lookup(activity_types, a) =:= {ok, v2}\") :name)")
|
||||||
|
|
||||||
|
;; State persists across multiple calls in the same expression
|
||||||
|
(epoch 17)
|
||||||
|
(eval "(erlang-eval-ast \"registry:start_link(), registry:register(activity_types, x, 1), registry:register(object_types, x, 2), {registry:lookup(activity_types, x), registry:lookup(object_types, x)} =:= {{ok, 1}, {ok, 2}}\")")
|
||||||
|
|
||||||
|
;; Unknown kind rejected via gen_server too
|
||||||
|
(epoch 18)
|
||||||
|
(eval "(get (erlang-eval-ast \"registry:start_link(), case registry:lookup(bogus_kind, foo) of {error, unknown_kind} -> ok; _ -> bad end\") :name)")
|
||||||
|
|
||||||
|
;; Empty kind list returns []
|
||||||
|
(epoch 19)
|
||||||
|
(eval "(get (erlang-eval-ast \"registry:start_link(), registry:list(validators) =:= []\") :name)")
|
||||||
|
EPOCHS
|
||||||
|
|
||||||
|
OUTPUT=$(timeout 120 "$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 2 "gen_server loaded" "gen_server"
|
||||||
|
check 3 "registry module loaded" "registry"
|
||||||
|
check 10 "start_link returns Pid" "true"
|
||||||
|
check 11 "register + lookup round-trip" "true"
|
||||||
|
check 12 "lookup missing -> not_found" "true"
|
||||||
|
check 13 "register returns ok atom" "true"
|
||||||
|
check 14 "three registers, list = 3" "3"
|
||||||
|
check 15 "re-register doesn't grow list" "1"
|
||||||
|
check 16 "re-register overrides value" "true"
|
||||||
|
check 17 "different kinds independent" "true"
|
||||||
|
check 18 "lookup bogus kind" "ok"
|
||||||
|
check 19 "empty kind list = []" "true"
|
||||||
|
|
||||||
|
# 12 cases total (epoch 2 + 3 are setup, but counted for honesty)
|
||||||
|
TOTAL=$((PASS+FAIL))
|
||||||
|
if [ $FAIL -eq 0 ]; then
|
||||||
|
echo "ok $PASS/$TOTAL next/tests/registry_server.sh passed"
|
||||||
|
else
|
||||||
|
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
|
||||||
|
echo "$ERRORS"
|
||||||
|
fi
|
||||||
|
[ $FAIL -eq 0 ]
|
||||||
@@ -339,7 +339,7 @@ created with a known stable CID.
|
|||||||
|
|
||||||
**Sub-deliverables:**
|
**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] **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).
|
||||||
- [ ] **5b** — gen_server wrapper around the pure registry: `start_link/0`, registered name `registry`, the pure API delegated through `gen_server:call`.
|
- [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.
|
- [ ] **5c** — `bootstrap:load_genesis/1` (Step 4e) populates the registry from `read_genesis` output. Dispatches by section atom → kind.
|
||||||
- [ ] **5d** — define-registry projection fold integration: incoming `Create{Define*}` activities are routed through the projection scheduler (Step 7) and update the registry.
|
- [ ] **5d** — define-registry projection fold integration: incoming `Create{Define*}` activities are routed through the projection scheduler (Step 7) and update the registry.
|
||||||
|
|
||||||
@@ -961,6 +961,7 @@ A few things still under-specified; resolve as work begins.
|
|||||||
Newest first. One line per sub-deliverable commit. Erlang conformance gate
|
Newest first. One line per sub-deliverable commit. Erlang conformance gate
|
||||||
(`bash lib/erlang/conformance.sh`) must remain 729/729 on every entry.
|
(`bash lib/erlang/conformance.sh`) must remain 729/729 on every entry.
|
||||||
|
|
||||||
|
- **2026-05-28** — Step 5b: `registry.erl` extended with gen_server callbacks + named-process API. `start_link/0` spawns the worker, registers it under the literal `registry` atom, returns the Pid (port returns raw Pid not `{ok, Pid}` — diverges from OTP). 3-arity `register`, 2-arity `lookup`, 1-arity `list` delegate to the pure /4 and /3 functions inside handle_call. Port note documented: `?MODULE` macro unsupported; tests must inline start_link with operations since spawned processes don't persist across separate `erlang-eval-ast` calls. `next/tests/registry_server.sh` 12/12. Erlang conformance 729/729.
|
||||||
- **2026-05-28** — Step 4e: `bootstrap:load_genesis/1` + `strip_sx_suffix/1` in `next/kernel/bootstrap.erl`. Walks `read_genesis` output and threads each entry through `registry:register/4`, using the section atom as the kind and the filename-minus-`.sx` as the entry name. Per-kind counts match the seven bootstrap sections exactly (3/10/7/3/3/2/3 = 31 entries total). `next/tests/bootstrap_load.sh` 15/15. Determinism verified by comparing `cid:to_string` of the loaded state across calls (faster than deep-equality on the nested-binary state). Step 4 is now complete end-to-end except for SX-source parsing of the loaded entries. Erlang conformance 729/729.
|
- **2026-05-28** — Step 4e: `bootstrap:load_genesis/1` + `strip_sx_suffix/1` in `next/kernel/bootstrap.erl`. Walks `read_genesis` output and threads each entry through `registry:register/4`, using the section atom as the kind and the filename-minus-`.sx` as the entry name. Per-kind counts match the seven bootstrap sections exactly (3/10/7/3/3/2/3 = 31 entries total). `next/tests/bootstrap_load.sh` 15/15. Determinism verified by comparing `cid:to_string` of the loaded state across calls (faster than deep-equality on the nested-binary state). Step 4 is now complete end-to-end except for SX-source parsing of the loaded entries. Erlang conformance 729/729.
|
||||||
- **2026-05-28** — Step 5a: `next/kernel/registry.erl` — pure-functional registry. State is `[{Kind, [{Name, Entry}, ...]}, ...]` keyed by the same seven section atoms as Step 4c (activity_types, object_types, projections, validators, codecs, sig_suites, audience). API: `new/0`, `kinds/0`, `register/4`, `lookup/3`, `list/2`. Unknown kinds rejected with `{error, unknown_kind}`; missing names return `not_found`; re-registering the same name overrides without growing the list. `next/tests/registry_pure.sh` 14/14. Step 5 broken into 5a–5d on the plan. Erlang conformance 729/729.
|
- **2026-05-28** — Step 5a: `next/kernel/registry.erl` — pure-functional registry. State is `[{Kind, [{Name, Entry}, ...]}, ...]` keyed by the same seven section atoms as Step 4c (activity_types, object_types, projections, validators, codecs, sig_suites, audience). API: `new/0`, `kinds/0`, `register/4`, `lookup/3`, `list/2`. Unknown kinds rejected with `{error, unknown_kind}`; missing names return `not_found`; re-registering the same name overrides without growing the list. `next/tests/registry_pure.sh` 14/14. Step 5 broken into 5a–5d on the plan. Erlang conformance 729/729.
|
||||||
- **2026-05-28** — Step 4d: `bootstrap:build_genesis/1` + `verify_genesis/2` + `.cidhash` helpers in `next/kernel/bootstrap.erl`. Bundle CID delegated to host `cid:to_string` over `{genesis_bundle, Sections}` — deterministic, ~59 byte CIDv1 binary. `verify_genesis/2` returns `ok` on match, `{error, {cid_mismatch, Got, Expected}}` on drift. `write_cidhash`/`read_cidhash` persist the CID to a `.cidhash` sibling file (path hand-spelled `<<...,47,46,99,...>>` per the string-literal-in-binary substrate quirk). `next/tests/bootstrap_build.sh` 12/12. Erlang conformance 729/729.
|
- **2026-05-28** — Step 4d: `bootstrap:build_genesis/1` + `verify_genesis/2` + `.cidhash` helpers in `next/kernel/bootstrap.erl`. Bundle CID delegated to host `cid:to_string` over `{genesis_bundle, Sections}` — deterministic, ~59 byte CIDv1 binary. `verify_genesis/2` returns `ok` on match, `{error, {cid_mismatch, Got, Expected}}` on drift. `write_cidhash`/`read_cidhash` persist the CID to a `.cidhash` sibling file (path hand-spelled `<<...,47,46,99,...>>` per the string-literal-in-binary substrate quirk). `next/tests/bootstrap_build.sh` 12/12. Erlang conformance 729/729.
|
||||||
|
|||||||
Reference in New Issue
Block a user