Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
Step 7c made outbox depend on follower_graph + delivery, breaking
four tests that didn't load those modules. Background gate
revealed the failures after 7c had already been pushed.
Loads added:
auto_accept.sh — epoch 12: delivery (follower_graph
was already loaded at epoch 10)
nx_kernel_multi.sh — epochs 5+6: follower_graph + delivery
(existing modules shifted: outbox 5->7,
nx_kernel 6->8). Check 6 -> check 8.
http_publish.sh — epochs 100+101: follower_graph + delivery
(high epoch numbers to avoid collision
with test epochs at 10+)
http_publish_fold.sh — epochs 100+101: same pattern
All four green at 9/9, 26/26, 10/10, 10/10. No behaviour change
in outbox or downstream code; pure test-setup follow-up to 7c.
Conformance 761/761 (confirmed post-7c).
139 lines
7.5 KiB
Bash
Executable File
139 lines
7.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# next/tests/auto_accept.sh — m2 Step 6c test.
|
|
#
|
|
# Per design §13.2 the v2 Follow policy is open-world: every
|
|
# successfully-ingested Follow triggers an Accept publish from the
|
|
# target actor. Enabled per-Cfg via {auto_accept_follows, true};
|
|
# off by default so manual-moderation deployments can opt out.
|
|
|
|
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
|
|
|
|
# Alice is on this kernel (target). Bob is the peer (signs Follow
|
|
# with BobKS). Alice's outbox projection is `followers` so when
|
|
# alice publishes the Accept, it folds through follower_graph too —
|
|
# both sides of the relationship update without any test scaffolding.
|
|
SETUP='AK = <<1,2,3,4>>, AKS = [{key_id,k1},{algorithm,ed25519},{value,AK}], AAS = [{public_keys,[[{id,k1},{created,0},{value,AK}]]}], BK = <<5,6,7,8>>, BKS = [{key_id,k1},{algorithm,ed25519},{value,BK}], BAS = [{public_keys,[[{id,k1},{created,0},{value,BK}]]}], FollowEnv = outbox:construct(follow, bob, 1, alice), SignedFollow = outbox:sign(FollowEnv, BKS), Body = term_codec:encode(SignedFollow), projection:start_link(followers, follower_graph:new(), follower_graph:fold_fn()), nx_kernel:start_link(alice, AKS, AAS), nx_kernel:with_projections_for(alice, [followers]), Cfg = [{peer_as, [{bob, BAS}]}, {kernel, nx_kernel}, {inbox_projections, [followers]}, {auto_accept_follows, true}], InboxPath = <<47,97,99,116,111,114,115,47,97,108,105,99,101,47,105,110,98,111,120>>,'
|
|
|
|
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/outbox.erl\")) :name)")
|
|
(epoch 7)
|
|
(eval "(get (erlang-load-module (file-read \"next/kernel/projection.erl\")) :name)")
|
|
(epoch 8)
|
|
(eval "(get (erlang-load-module (file-read \"next/kernel/nx_kernel.erl\")) :name)")
|
|
(epoch 9)
|
|
(eval "(get (erlang-load-module (file-read \"next/kernel/term_codec.erl\")) :name)")
|
|
(epoch 10)
|
|
(eval "(get (erlang-load-module (file-read \"next/kernel/follower_graph.erl\")) :name)")
|
|
(epoch 11)
|
|
(eval "(get (erlang-load-module (file-read \"next/kernel/http_server.erl\")) :name)")
|
|
(epoch 12)
|
|
(eval "(get (erlang-load-module (file-read \"next/kernel/delivery.erl\")) :name)")
|
|
|
|
;; auto_accept on: Follow ingestion advances alice's outbox tip (Accept published)
|
|
(epoch 20)
|
|
(eval "(erlang-eval-ast \"${SETUP} Req = [{method, <<80,79,83,84>>}, {path, InboxPath}, {headers, []}, {body, Body}], http_server:route(Req, Cfg), nx_kernel:log_tip_for(alice)\")")
|
|
|
|
;; auto_accept on: alice's outbox entry is an Accept activity
|
|
(epoch 21)
|
|
(eval "(get (erlang-eval-ast \"${SETUP} Req = [{method, <<80,79,83,84>>}, {path, InboxPath}, {headers, []}, {body, Body}], http_server:route(Req, Cfg), {ok, L} = nx_kernel:log_state_for(alice), [E] = log:entries(L), envelope:get_field(type, E) =:= {ok, accept}\") :name)")
|
|
|
|
;; auto_accept on: follower_graph state converges to full Follow relationship
|
|
;; (alice.followers = [bob], bob.following = [alice]) after both inbox + outbox
|
|
;; projections fold through followers.
|
|
(epoch 22)
|
|
(eval "(get (erlang-eval-ast \"${SETUP} Req = [{method, <<80,79,83,84>>}, {path, InboxPath}, {headers, []}, {body, Body}], http_server:route(Req, Cfg), S = projection:query(followers), {follower_graph:followers(alice, S), follower_graph:following(bob, S)} =:= {[bob], [alice]}\") :name)")
|
|
|
|
;; auto_accept on: pendings cleared after the Accept fold
|
|
(epoch 23)
|
|
(eval "(get (erlang-eval-ast \"${SETUP} Req = [{method, <<80,79,83,84>>}, {path, InboxPath}, {headers, []}, {body, Body}], http_server:route(Req, Cfg), S = projection:query(followers), {follower_graph:pending_inbound(alice, S), follower_graph:pending_outbound(bob, S)} =:= {[], []}\") :name)")
|
|
|
|
;; auto_accept off (default): no outbox publish; outbox tip stays 0
|
|
(epoch 24)
|
|
(eval "(erlang-eval-ast \"${SETUP} CfgOff = [{peer_as, [{bob, BAS}]}, {kernel, nx_kernel}, {inbox_projections, [followers]}], Req = [{method, <<80,79,83,84>>}, {path, InboxPath}, {headers, []}, {body, Body}], http_server:route(Req, CfgOff), nx_kernel:log_tip_for(alice)\")")
|
|
|
|
;; auto_accept off: pending_inbound still gets populated (Step 6b path)
|
|
;; but no Accept fired, so alice.followers stays empty.
|
|
(epoch 25)
|
|
(eval "(get (erlang-eval-ast \"${SETUP} CfgOff = [{peer_as, [{bob, BAS}]}, {kernel, nx_kernel}, {inbox_projections, [followers]}], Req = [{method, <<80,79,83,84>>}, {path, InboxPath}, {headers, []}, {body, Body}], http_server:route(Req, CfgOff), S = projection:query(followers), {follower_graph:pending_inbound(alice, S), follower_graph:followers(alice, S)} =:= {[bob], []}\") :name)")
|
|
|
|
;; Non-Follow activity (Create{Note}) with auto_accept on: outbox stays empty
|
|
(epoch 26)
|
|
(eval "(erlang-eval-ast \"${SETUP} NoteEnv = outbox:construct(create, bob, 2, [{type, note}, {content, hi}]), SignedNote = outbox:sign(NoteEnv, BKS), NoteBody = term_codec:encode(SignedNote), Req = [{method, <<80,79,83,84>>}, {path, InboxPath}, {headers, []}, {body, NoteBody}], http_server:route(Req, Cfg), nx_kernel:log_tip_for(alice)\")")
|
|
|
|
;; Bad-sig Follow ingestion with auto_accept on: no Accept publish (short-circuit)
|
|
(epoch 27)
|
|
(eval "(erlang-eval-ast \"${SETUP} EvilK = <<9,9,9,9>>, EvilAS = [{public_keys,[[{id,k1},{created,0},{value,EvilK}]]}], EvilCfg = [{peer_as, [{bob, EvilAS}]}, {kernel, nx_kernel}, {inbox_projections, [followers]}, {auto_accept_follows, true}], Req = [{method, <<80,79,83,84>>}, {path, InboxPath}, {headers, []}, {body, Body}], http_server:route(Req, EvilCfg), nx_kernel:log_tip_for(alice)\")")
|
|
EPOCHS
|
|
|
|
OUTPUT=$(timeout 900 "$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 11 "http_server loaded" "http_server"
|
|
check 20 "auto_accept on: outbox tip = 1" "1"
|
|
check 21 "outbox entry is an Accept" "true"
|
|
check 22 "graph converges to full Follow" "true"
|
|
check 23 "pendings cleared after Accept" "true"
|
|
check 24 "auto_accept off: outbox tip = 0" "0"
|
|
check 25 "auto_accept off: pending only" "true"
|
|
check 26 "non-Follow ingestion: no Accept" "0"
|
|
check 27 "bad-sig short-circuits Accept" "0"
|
|
|
|
TOTAL=$((PASS+FAIL))
|
|
if [ $FAIL -eq 0 ]; then
|
|
echo "ok $PASS/$TOTAL next/tests/auto_accept.sh passed"
|
|
else
|
|
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
|
|
echo "$ERRORS"
|
|
fi
|
|
[ $FAIL -eq 0 ]
|