fed-sx-m2: Step 4c — route/3 with kernel access + 8 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s

http_server:route/3(Req, Cfg, Kernel) is the new extended entry
point: folds the kernel reference (typically the registered
nx_kernel atom) into Cfg as {kernel, Kernel}. route/2 is
unchanged and stays the M1 surface.

The dispatch chain gained Cfg threading all the way down:
  dispatch/3 -> dispatch/4 (M, P, F, Cfg)
  actor_get/2 -> actor_get/3 (Rest, F, Cfg)
  actor_subresource_get/3 -> /4 (Id, Sub, F, Cfg)

actor_outbox_response_for/3 (new) reads :kernel from Cfg and,
when the kernel atom is registered AND the actor exists, renders
'tip: <N>' alongside the actor id in text / JSON / SX content-
negotiated bodies. Unknown actors or unregistered kernels fall
back to the 4a stub.

Inbox / followers / following handlers accept Cfg but ignore it
for now — they layer real state lookup in 4d/4e/Step 5+.

Substrate gotcha logged in the Progress log: try/of/catch around
gen_server:call(nx_kernel, _) deadlocks in this port's scheduler
(probably the catch frame's mask defers reply delivery). The
live kernel_log_tip/2 helper does a bare call + integer guard
instead. nx_kernel_multi.sh already proves bare gen_server:call
into the same kernel works correctly.

8 new cases in next/tests/http_multi_actor.sh (33/33 total):
  - route/3 with registered kernel: outbox body includes tip=0
  - tip advances after POST publish through route/3 + token map
  - unknown actor (ghost) falls back to 4a stub (no tip:)
  - unregistered kernel ref falls back to stub
  - JSON Accept renders {"outbox":"alice","tip":0}
  - SX Accept renders (outbox "alice" :tip 0)
  - Bob's outbox tip stays 0 while Alice publishes (per-actor)
  - route/2 path unchanged: no tip field in body

Conformance 761/761. 121/121 across 10 Step-4-adjacent suites
(http_multi_actor, http_route, http_publish, http_post_format,
http_marshal, http_publish_fold, http_listen_bif, http_server_start,
nx_kernel_multi, bootstrap_start, actor_lifecycle).
This commit is contained in:
2026-06-06 14:59:59 +00:00
parent 271632c923
commit e04a65d400
3 changed files with 187 additions and 32 deletions

View File

@@ -146,6 +146,44 @@ cat > "$TMPFILE" <<'EPOCHS'
(epoch 46)
(eval "(get (erlang-eval-ast \"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}]]}], nx_kernel:start_link(alice, AKS, AAS), nx_kernel:add_actor(bob, BKS, BAS), AliceTok = <<97,108,105,99,101,45,116,111,107,101,110>>, LegacyTok = <<102,111,111>>, AuthKey = <<97,117,116,104,111,114,105,122,97,116,105,111,110>>, AliceAuth = <<66,101,97,114,101,114,32,97,108,105,99,101,45,116,111,107,101,110>>, LegacyAuth = <<66,101,97,114,101,114,32,102,111,111>>, Cfg = [{tokens, [{AliceTok, alice}]}, {publish_token, LegacyTok}], Req1 = [{method, <<80,79,83,84>>}, {path, http_server:activity_path()}, {headers, [{AuthKey, AliceAuth}]}, {body, <<104,105>>}], Req2 = [{method, <<80,79,83,84>>}, {path, http_server:activity_path()}, {headers, [{AuthKey, LegacyAuth}]}, {body, <<104,105>>}], http_server:route(Req1, Cfg), http_server:route(Req2, Cfg), {nx_kernel:log_tip_for(alice), nx_kernel:log_tip_for(bob)} =:= {2, 0}\") :name)")
;; ── Step 4c: route/3 with kernel access ───────────────────────
;; route/3 folds the Kernel into Cfg under :kernel. The outbox
;; sub-resource handler now reads :kernel and includes "tip: N"
;; when the actor exists in the kernel. Other handlers ignore the
;; field for now (they layer real state in 4d/4e).
;; route/3 with kernel reference: GET /actors/alice/outbox includes log tip
(epoch 50)
(eval "(get (erlang-eval-ast \"AK = <<1,2,3,4>>, AKS = [{key_id,k1},{algorithm,ed25519},{value,AK}], AAS = [{public_keys,[[{id,k1},{created,0},{value,AK}]]}], nx_kernel:start_link(alice, AKS, AAS), Req = [{method, <<71,69,84>>}, {path, <<47,97,99,116,111,114,115,47,97,108,105,99,101,47,111,117,116,98,111,120>>}, {headers, []}, {body, <<>>}], R = http_server:route(Req, [], nx_kernel), [_, _, {body, B}] = R, http_server:match_prefix(<<111,117,116,98,111,120,58,32,97,108,105,99,101,10,116,105,112,58,32,48>>, B) =/= nomatch\") :name)")
;; route/3 with kernel reference: outbox tip advances after publish
(epoch 51)
(eval "(get (erlang-eval-ast \"AK = <<1,2,3,4>>, AKS = [{key_id,k1},{algorithm,ed25519},{value,AK}], AAS = [{public_keys,[[{id,k1},{created,0},{value,AK}]]}], nx_kernel:start_link(alice, AKS, AAS), AliceTok = <<97,108,105,99,101,45,116,111,107,101,110>>, AuthKey = <<97,117,116,104,111,114,105,122,97,116,105,111,110>>, AliceAuth = <<66,101,97,114,101,114,32,97,108,105,99,101,45,116,111,107,101,110>>, Cfg = [{tokens, [{AliceTok, alice}]}], PostReq = [{method, <<80,79,83,84>>}, {path, http_server:activity_path()}, {headers, [{AuthKey, AliceAuth}]}, {body, <<104,105>>}], http_server:route(PostReq, Cfg, nx_kernel), GetReq = [{method, <<71,69,84>>}, {path, <<47,97,99,116,111,114,115,47,97,108,105,99,101,47,111,117,116,98,111,120>>}, {headers, []}, {body, <<>>}], R = http_server:route(GetReq, [], nx_kernel), [_, _, {body, B}] = R, http_server:match_prefix(<<111,117,116,98,111,120,58,32,97,108,105,99,101,10,116,105,112,58,32,49>>, B) =/= nomatch\") :name)")
;; route/3 with unknown actor -> falls back to /2 stub (no tip)
(epoch 52)
(eval "(get (erlang-eval-ast \"AK = <<1,2,3,4>>, AKS = [{key_id,k1},{algorithm,ed25519},{value,AK}], AAS = [{public_keys,[[{id,k1},{created,0},{value,AK}]]}], nx_kernel:start_link(alice, AKS, AAS), Req = [{method, <<71,69,84>>}, {path, <<47,97,99,116,111,114,115,47,103,104,111,115,116,47,111,117,116,98,111,120>>}, {headers, []}, {body, <<>>}], R = http_server:route(Req, [], nx_kernel), [_, _, {body, B}] = R, http_server:match_prefix(<<111,117,116,98,111,120,58,32,103,104,111,115,116,10>>, B) =/= nomatch andalso http_server:match_prefix(<<116,105,112,58>>, B) =:= nomatch\") :name)")
;; route/3 without kernel registered -> falls back to stub
(epoch 53)
(eval "(get (erlang-eval-ast \"Req = [{method, <<71,69,84>>}, {path, <<47,97,99,116,111,114,115,47,97,108,105,99,101,47,111,117,116,98,111,120>>}, {headers, []}, {body, <<>>}], R = http_server:route(Req, [], unregistered_kernel), [_, _, {body, B}] = R, http_server:match_prefix(<<111,117,116,98,111,120,58,32,97,108,105,99,101,10>>, B) =/= nomatch andalso http_server:match_prefix(<<116,105,112,58>>, B) =:= nomatch\") :name)")
;; route/3 with kernel + JSON Accept -> JSON body carries :tip
(epoch 54)
(eval "(get (erlang-eval-ast \"AK = <<1,2,3,4>>, AKS = [{key_id,k1},{algorithm,ed25519},{value,AK}], AAS = [{public_keys,[[{id,k1},{created,0},{value,AK}]]}], nx_kernel:start_link(alice, AKS, AAS), AcceptKey = <<97,99,99,101,112,116>>, AcceptVal = <<97,112,112,108,105,99,97,116,105,111,110,47,106,115,111,110>>, Req = [{method, <<71,69,84>>}, {path, <<47,97,99,116,111,114,115,47,97,108,105,99,101,47,111,117,116,98,111,120>>}, {headers, [{AcceptKey, AcceptVal}]}, {body, <<>>}], R = http_server:route(Req, [], nx_kernel), [_, _, {body, B}] = R, http_server:match_prefix(<<123,34,111,117,116,98,111,120,34,58,34,97,108,105,99,101,34,44,34,116,105,112,34,58,48>>, B) =/= nomatch\") :name)")
;; route/3 with kernel + SX Accept -> SX body carries :tip
(epoch 55)
(eval "(get (erlang-eval-ast \"AK = <<1,2,3,4>>, AKS = [{key_id,k1},{algorithm,ed25519},{value,AK}], AAS = [{public_keys,[[{id,k1},{created,0},{value,AK}]]}], nx_kernel:start_link(alice, AKS, AAS), AcceptKey = <<97,99,99,101,112,116>>, AcceptVal = <<97,112,112,108,105,99,97,116,105,111,110,47,115,120>>, Req = [{method, <<71,69,84>>}, {path, <<47,97,99,116,111,114,115,47,97,108,105,99,101,47,111,117,116,98,111,120>>}, {headers, [{AcceptKey, AcceptVal}]}, {body, <<>>}], R = http_server:route(Req, [], nx_kernel), [_, _, {body, B}] = R, http_server:match_prefix(<<40,111,117,116,98,111,120,32,34,97,108,105,99,101,34,32,58,116,105,112,32,48,41>>, B) =/= nomatch\") :name)")
;; route/3 with kernel + multi-actor: bob's outbox tip is independent
(epoch 56)
(eval "(get (erlang-eval-ast \"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}]]}], nx_kernel:start_link(alice, AKS, AAS), nx_kernel:add_actor(bob, BKS, BAS), AliceTok = <<97,108,105,99,101,45,116,111,107,101,110>>, AuthKey = <<97,117,116,104,111,114,105,122,97,116,105,111,110>>, AliceAuth = <<66,101,97,114,101,114,32,97,108,105,99,101,45,116,111,107,101,110>>, Cfg = [{tokens, [{AliceTok, alice}]}], PostReq = [{method, <<80,79,83,84>>}, {path, http_server:activity_path()}, {headers, [{AuthKey, AliceAuth}]}, {body, <<104,105>>}], http_server:route(PostReq, Cfg, nx_kernel), GetReq = [{method, <<71,69,84>>}, {path, <<47,97,99,116,111,114,115,47,98,111,98,47,111,117,116,98,111,120>>}, {headers, []}, {body, <<>>}], R = http_server:route(GetReq, [], nx_kernel), [_, _, {body, B}] = R, http_server:match_prefix(<<111,117,116,98,111,120,58,32,98,111,98,10,116,105,112,58,32,48>>, B) =/= nomatch\") :name)")
;; route/2 path (no kernel arg) still returns the 4a stub — back-compat
(epoch 57)
(eval "(get (erlang-eval-ast \"AK = <<1,2,3,4>>, AKS = [{key_id,k1},{algorithm,ed25519},{value,AK}], AAS = [{public_keys,[[{id,k1},{created,0},{value,AK}]]}], nx_kernel:start_link(alice, AKS, AAS), Req = [{method, <<71,69,84>>}, {path, <<47,97,99,116,111,114,115,47,97,108,105,99,101,47,111,117,116,98,111,120>>}, {headers, []}, {body, <<>>}], R = http_server:route(Req, []), [_, _, {body, B}] = R, http_server:match_prefix(<<116,105,112,58>>, B) =:= nomatch\") :name)")
;; Token resolution before kernel is registered -> auth-stub published response
(epoch 47)
(eval "(get (erlang-eval-ast \"AliceTok = <<97,108,105,99,101,45,116,111,107,101,110>>, AuthKey = <<97,117,116,104,111,114,105,122,97,116,105,111,110>>, AliceAuth = <<66,101,97,114,101,114,32,97,108,105,99,101,45,116,111,107,101,110>>, Cfg = [{tokens, [{AliceTok, alice}]}], Req = [{method, <<80,79,83,84>>}, {path, http_server:activity_path()}, {headers, [{AuthKey, AliceAuth}]}, {body, <<>>}], R = http_server:route(Req, Cfg), case R of [{status, 200}, _, {body, B}] -> http_server:match_prefix(<<112,117,98,108,105,115,104,101,100>>, B) =/= nomatch; _ -> false end\") :name)")
@@ -197,6 +235,14 @@ check 44 "unknown token -> 401" "true"
check 45 "legacy :publish_token still works" "true"
check 46 "tokens map + legacy back-compat" "true"
check 47 "no kernel + token map -> stub 200" "true"
check 50 "route/3 outbox includes tip = 0" "true"
check 51 "tip advances after publish" "true"
check 52 "unknown actor -> stub fallback" "true"
check 53 "unregistered kernel -> stub" "true"
check 54 "JSON outbox carries tip field" "true"
check 55 "SX outbox carries :tip field" "true"
check 56 "Bob outbox tip independent" "true"
check 57 "route/2 unchanged (no tip)" "true"
TOTAL=$((PASS+FAIL))
if [ $FAIL -eq 0 ]; then