From 9cbf14fe8cc8abba94c25ab1a1008035f421842d Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 26 May 2026 19:55:13 +0000 Subject: [PATCH] =?UTF-8?q?fed-sx-m1:=20Step=201b=20=E2=80=94=20nx=5Fcid?= =?UTF-8?q?=20kernel=20module=20+=2013=20canonical=20CID=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next/kernel/nx_cid.erl | 24 ++++++++ next/tests/cid.sh | 117 ++++++++++++++++++++++++++++++++++++ plans/fed-sx-milestone-1.md | 3 +- 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 next/kernel/nx_cid.erl create mode 100755 next/tests/cid.sh diff --git a/next/kernel/nx_cid.erl b/next/kernel/nx_cid.erl new file mode 100644 index 00000000..e99f1ad8 --- /dev/null +++ b/next/kernel/nx_cid.erl @@ -0,0 +1,24 @@ +-module(nx_cid). +-export([from_sx/1, to_string/1, from_string/1, equals/2]). + +%% The kernel-side CID wrapper. The host BIF `cid:to_string/1` already +%% produces a canonical CIDv1 (raw codec, sha2-256 multihash) over the +%% deterministic textual form of any term (er-format-value); we expose +%% it under the kernel namespace and add the equality + round-trip +%% helpers the rest of the kernel needs. +%% +%% Naming note: the BIF module is `cid`, so we use `nx_cid` to avoid +%% shadowing. Plans/fed-sx-milestone-1.md §Step 1 spells the file as +%% `cid.erl`; the briefing flags Erlang snippets as illustrative. + +from_sx(V) -> + cid:to_string(V). + +to_string(Cid) -> + Cid. + +from_string(S) -> + S. + +equals(A, B) -> + A =:= B. diff --git a/next/tests/cid.sh b/next/tests/cid.sh new file mode 100755 index 00000000..776836e3 --- /dev/null +++ b/next/tests/cid.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# next/tests/cid.sh — Step 1b acceptance test. +# +# Loads next/kernel/nx_cid.erl into the Erlang-on-SX runtime and checks +# the canonical CID contract: determinism, uniqueness, equality, and +# to_string/from_string round-trip. 12 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 "(get (erlang-load-module (file-read \"next/kernel/nx_cid.erl\")) :name)") + +;; from_sx returns a binary +(epoch 10) +(eval "(get (erlang-eval-ast \"is_binary(nx_cid:from_sx(foo))\") :name)") + +;; from_sx is deterministic on atoms / ints / compound terms +(epoch 11) +(eval "(get (erlang-eval-ast \"nx_cid:from_sx(foo) =:= nx_cid:from_sx(foo)\") :name)") +(epoch 12) +(eval "(get (erlang-eval-ast \"nx_cid:from_sx(42) =:= nx_cid:from_sx(42)\") :name)") +(epoch 13) +(eval "(get (erlang-eval-ast \"nx_cid:from_sx({a, [1, 2, 3]}) =:= nx_cid:from_sx({a, [1, 2, 3]})\") :name)") + +;; from_sx is collision-resistant on distinct terms +(epoch 20) +(eval "(get (erlang-eval-ast \"nx_cid:from_sx(foo) =/= nx_cid:from_sx(bar)\") :name)") +(epoch 21) +(eval "(get (erlang-eval-ast \"nx_cid:from_sx(1) =/= nx_cid:from_sx(2)\") :name)") +(epoch 22) +(eval "(get (erlang-eval-ast \"nx_cid:from_sx([1, 2]) =/= nx_cid:from_sx([1, 2, 3])\") :name)") + +;; equals/2 is alias for =:= +(epoch 30) +(eval "(get (erlang-eval-ast \"nx_cid:equals(nx_cid:from_sx(foo), nx_cid:from_sx(foo))\") :name)") +(epoch 31) +(eval "(get (erlang-eval-ast \"nx_cid:equals(nx_cid:from_sx(foo), nx_cid:from_sx(bar))\") :name)") + +;; to_string + from_string round-trip +(epoch 40) +(eval "(get (erlang-eval-ast \"nx_cid:equals(nx_cid:from_string(nx_cid:to_string(nx_cid:from_sx(foo))), nx_cid:from_sx(foo))\") :name)") +(epoch 41) +(eval "(get (erlang-eval-ast \"is_binary(nx_cid:to_string(nx_cid:from_sx({tuple, 1, 2})))\") :name)") + +;; CIDv1 raw codec sha256 base32 form is around 59 chars; sanity-check length +(epoch 50) +(eval "(get (erlang-eval-ast \"byte_size(nx_cid:from_sx(hello)) > 50\") :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="" + 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 "module load name" "nx_cid" +check 10 "from_sx returns binary" "true" +check 11 "from_sx atom deterministic" "true" +check 12 "from_sx int deterministic" "true" +check 13 "from_sx compound deterministic" "true" +check 20 "from_sx atoms distinct" "true" +check 21 "from_sx ints distinct" "true" +check 22 "from_sx lists distinct" "true" +check 30 "equals same CIDs" "true" +check 31 "equals different CIDs" "false" +check 40 "to_string/from_string round-trip" "true" +check 41 "to_string returns binary" "true" +check 50 "CIDv1 base32 length sanity" "true" + +TOTAL=$((PASS+FAIL)) +if [ $FAIL -eq 0 ]; then + echo "ok $PASS/$TOTAL next/tests/cid.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 38ae3afb..2fcd31fb 100644 --- a/plans/fed-sx-milestone-1.md +++ b/plans/fed-sx-milestone-1.md @@ -101,7 +101,7 @@ in isolation, and a clear acceptance check. **Sub-deliverables:** - [x] **1a** — `next/` directory skeleton, README, `.gitignore` for `data/` -- [ ] **1b** — `next/kernel/cid.erl` (from_sx/to_string/from_string/equals) + `next/tests/cid.sh` (10+ cases) +- [x] **1b** — `next/kernel/nx_cid.erl` (from_sx/to_string/from_string/equals) + `next/tests/cid.sh` (13 cases). Module is `nx_cid` not `cid` — the `cid` BIF module would be shadowed by a user module of the same name; plan §Step 1's `cid.erl` is illustrative per briefing. **Deliverables:** @@ -932,5 +932,6 @@ 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-26** — Step 1b: `next/kernel/nx_cid.erl` (from_sx/to_string/from_string/equals) — thin Erlang wrapper around the `cid:to_string/1` BIF. `next/tests/cid.sh` 13/13 pass. Module named `nx_cid` to avoid shadowing the `cid` BIF (user-module dispatch takes precedence over BIFs by module name). Erlang conformance 729/729 preserved. - **2026-05-26** — Step 1a: `next/` skeleton created (kernel/, genesis/, tests/, data/), README, `.gitignore data/`. Erlang conformance 729/729 preserved.