diff --git a/lib/erlang/runtime.sx b/lib/erlang/runtime.sx index dc7588c7..484af858 100644 --- a/lib/erlang/runtime.sx +++ b/lib/erlang/runtime.sx @@ -1590,7 +1590,33 @@ (not (er-fun? handler)) (raise (er-mk-error-marker (er-mk-atom "badarg"))) :else (let - ((sx-handler (fn (req-dict) (er-http-resp-to-sx (er-apply-fun handler (list (er-http-req-of-sx req-dict))))))) + ;; Bridge between native http-listen and Erlang handler. + ;; + ;; Inbound: native passes Req as SX Dict + ;; {:method :path :query :headers :body} + ;; converted to Erlang request proplist via the live + ;; er-request-dict-to-proplist marshaller — that's the + ;; same shape http_server:route/2 consumes (binaries + ;; for path/method/body, dict-like proplist for headers). + ;; + ;; Outbound: Erlang handler returns + ;; [{status, Int}, {headers, [{Bin, Bin}, ...]}, {body, Bin}] + ;; converted back to SX Dict via er-proplist-to-dict — + ;; binary values become SX strings, the headers cons + ;; flattens to a nested SX dict (via er-to-sx-deep's + ;; proplist-2tuple detection). Matches what native + ;; http-listen serialises to the wire. + ;; + ;; (Step 8b-bridge originally shipped parallel + ;; er-http-req-of-sx / er-http-resp-to-sx helpers; commit + ;; 78eae9ef deleted them as dead because the BIF body + ;; still referenced them — Blockers #1. This rewrite + ;; threads through the live marshallers instead.) + ((sx-handler + (fn (req-dict) + (let ((req-pl (er-request-dict-to-proplist req-dict))) + (let ((resp-pl (er-apply-fun handler (list req-pl)))) + (er-proplist-to-dict resp-pl)))))) (http-listen port sx-handler)))))) ;; httpc:request/4(Url, Method, Headers, Body) - BRIEFING-EXCEPTION: diff --git a/plans/fed-sx-milestone-2.md b/plans/fed-sx-milestone-2.md index 2731fb61..7a130cb4 100644 --- a/plans/fed-sx-milestone-2.md +++ b/plans/fed-sx-milestone-2.md @@ -1041,21 +1041,17 @@ Pre-existing regressions inherited from the M1 closeout. Out of m2 scope (substrate, not `next/**`), tracked here so iteration can proceed. -1. **`next/tests/http_server_tcp.sh` 0/5** — pre-existing regression - introduced by `78eae9ef` (`fed-sx-m1: 8b-bridge cleanup`). - `lib/erlang/runtime.sx:1593` still references `er-http-resp-to-sx` - and `er-http-req-of-sx` in `er-bif-http-listen`'s sx-handler body, - but the cleanup commit removed both helpers without rewriting the - BIF. Listener binds (TCP socket accepts), but every request handler - crashes on first call to the undefined helpers — curl gets 000 / - empty body. Fix needs to rewrite the sx-handler body around the - live `er-request-dict-to-proplist` / `er-proplist-to-dict` - helpers (which the cleanup commit's message claimed are already - in use, but which the BIF body never picked up). Substrate work, - belongs on `loops/erlang`. m2 work continues against the in-process - HTTP layer (`http_marshal.sh` 10/10, `http_publish_fold.sh` 10/10) - until resolved. Confirmed pre-existing by stashing 1a's changes and - re-running on the unmodified m1 closeout HEAD. +1. **`next/tests/http_server_tcp.sh` 0/5** — ~~pre-existing + regression~~ **RESOLVED 2026-06-07** during Step 12 prep. The + `er-bif-http-listen` sx-handler in `lib/erlang/runtime.sx` + referenced the now-deleted `er-http-resp-to-sx` / + `er-http-req-of-sx` helpers; rewrote the bridge to thread + through the live `er-request-dict-to-proplist` (inbound) + + `er-proplist-to-dict` (outbound) marshallers — the same shape + `http_server:route/2` already consumes and emits. 5/5 now + passing. This is the surface Step 12's real two-instance smoke + test (rather than an in-process loopback) uses to spin up each + instance's HTTP listener. 2. **Native `http-request` (HTTP client) primitive missing** — ~~discovered during Step 8e prep~~ **RESOLVED 2026-06-07** by @@ -1086,6 +1082,34 @@ proceed. Newest first. +- **2026-06-07** — Blockers #1 RESOLVED. The + `er-bif-http-listen` sx-handler in `lib/erlang/runtime.sx` + referenced `er-http-resp-to-sx` / `er-http-req-of-sx` — + helpers deleted by `78eae9ef` because the BIF body never + picked them up. Rewrote the bridge to thread through the + live marshallers `er-request-dict-to-proplist` (inbound + SX Dict → Erlang request proplist matching what + `http_server:route/2` consumes) and `er-proplist-to-dict` + (outbound Erlang response proplist → SX Dict matching + what the native http-listen primitive serialises to the + wire). The marshallers convert binary header values to + strings + flatten the nested headers proplist via + `er-to-sx-deep`'s 2-tuple detection, so the response + shape matches what http-listen expects without any + additional shape coercion. + `next/tests/http_server_tcp.sh` 5/5 (GET /, capabilities, + unknown → 404, POST /activity no/bad bearer → 401). + Conformance 761/761 + 6 adjacent gates (httpc_request, + dispatch_http, http_listen_bif, discovery_fetch, + http_multi_actor, http_marshal) all green. + + This is technically substrate work in lib/erlang/runtime.sx, + but stays within the m2 briefing's allowed exception scope + (the http BIF wrappers — Step 8a / 8e / now 12-prep — are + the explicit substrate carve-outs). Unblocks Step 12's + REAL two-instance smoke test (rather than an in-process + loopback variant). + - **2026-06-07** — Step 10c (closes Step 10): peer-actor doc fetch + cache write. New `next/kernel/discovery_fetch.erl` produces a 1-arity FetchFn closure for