fed-sx-m2: Step 10c — peer-actor doc fetch + cache (+ 11 tests)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s
Closes Step 10 (10a discovery + 10b webfinger + 10c fetch). New
next/kernel/discovery_fetch.erl produces a 1-arity FetchFn closure
suitable for peer_actors:lookup_or_fetch_srv/2, completing the
discovery half that Step 5c's peer_actors cache stubbed out.
discovery_fetch API:
make_fetch_fn(Cfg) -> fun((PeerId) -> {ok, AS} | {error, _})
fetch(Url, Cfg) -> {ok, AS} | {error, _}
actor_doc_url(BaseUrl, PeerAtom) -> <Base>/actors/<peer>
accept_header/0 -> <<"application/vnd.fed-sx.actor-doc">>
decode_body(Body) -> {ok, AS} | {error, bad_actor_doc}
Closure GETs <base>/actors/<peer> via the Step 8e BIF with
Accept = application/vnd.fed-sx.actor-doc, decodes the response
body via term_codec:decode/1, returns the peer-actor-state
proplist (currently [{public_keys, [...]}]) in the shape
envelope:verify_signature consumes.
Cfg reuses dispatch_http's :peer_url / :peer_url_fn resolution so
a single Cfg threads through both delivery (8f) and discovery (10c).
Server side: http_server.erl extended to serve the same MIME.
- accept_format/1 matches application/vnd.fed-sx.actor-doc first
via the new actor_doc_prefix/0 — content negotiation atom is
`actor_doc`.
- content_type_for(actor_doc) emits the MIME on outbound.
- actor_doc_response_for/3 kernel-aware arm: with kernel + actor
-> 200 + term_codec:encode of nx_kernel:state_for/1 result.
Unknown actor -> not_found_response/0. Other formats fall
through to the existing /2 stub variants.
- actor_get/3 route dispatch threads Cfg to the /3 arm.
Port quirks documented:
* This Erlang doesn't support Mod:Fun(X) dispatch on a variable
module — kernel_actor_state/2 hardcodes nx_kernel; the Cfg
:kernel field is just a "no kernel wired" -> nil flag.
* nx_kernel:actor_state/1 is the LEGACY single-bucket accessor
that takes State (not ActorId); the server-side variant we
want is state_for/1 (gen_server:call wrapper). Easy mismatch,
documented in the comment.
Outcome mapping:
2xx + decodable body -> {ok, AS}
2xx + bad body -> {error, bad_actor_doc}
non-2xx -> {error, {status, N}}
resolver miss -> {error, no_peer_url}
transport -> {error, Reason} (BIF re-raises)
Test: next/tests/discovery_fetch.sh 11/11
Server side (in-process via http_server:actor_doc_response_for):
- Accept negotiation
- kernel + actor -> 200 + decodable body w/ :public_keys
- unknown actor -> 404
Closure side (live HTTP against background python stub returning
hand-crafted term_codec bytes):
- URL construction <base>/actors/X
- fetch live -> {ok, AS}
- make_fetch_fn closure -> {ok, AS} via static :peer_url map
- missing peer -> {error, no_peer_url}
- 404 path -> {error, {status, 404}}
- peer_actors:lookup_or_fetch/3 caches the result
Test setup note: Python term_codec encoder uses ELEMENT COUNT
(not byte length) for l/t headers — see encode/1 in term_codec.erl
which does integer_to_list(length(T)). Easy bug, documented in the
test's python source.
No-regression gates green: Erlang conformance 761/761,
httpc_request 10/10, dispatch_http 10/10, http_listen_bif 5/5,
peer_actors 19/19, discovery 12/12, http_accept 13/13,
http_actors 13/13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -754,11 +754,25 @@ Per §13.7: webfinger plus actor doc fetch.
|
||||
(no-kernel, with-kernel, host-match), 404 paths
|
||||
(missing-resource, bad-acct, unknown-actor, host-mismatch,
|
||||
wrong-method).
|
||||
- [ ] **10c** — Peer-actor fetch + cache write. Gates on
|
||||
Blockers #2 (native `http-request` primitive missing).
|
||||
Step 5's peer_actors cache already exposes the
|
||||
`lookup_or_fetch` shape; this Step 10c plugs the discovery
|
||||
HTTP fetch into the FetchFn slot.
|
||||
- [x] **10c** — Peer-actor fetch + cache write. New
|
||||
`discovery_fetch.erl` produces a 1-arity FetchFn closure
|
||||
suitable for `peer_actors:lookup_or_fetch_srv/2`: GETs
|
||||
`<base>/actors/<peer>` with
|
||||
`Accept: application/vnd.fed-sx.actor-doc`, decodes the body
|
||||
via `term_codec:decode/1`, and returns `{ok, AS}` where AS is
|
||||
the peer's `[{public_keys, [...]}]` proplist
|
||||
(`envelope:verify_signature` shape). Cfg reuses the same
|
||||
`:peer_url` / `:peer_url_fn` resolution as `dispatch_http`.
|
||||
Server side: http_server now serves the same MIME — new
|
||||
`actor_doc` content-negotiation atom, `actor_doc_response_for/3`
|
||||
kernel-aware arm calls `nx_kernel:state_for/1` and emits the
|
||||
`term_codec:encode/1` of the AS. Test:
|
||||
`next/tests/discovery_fetch.sh` 11/11 — Accept negotiation,
|
||||
server-side encode (with kernel) → 200 + decodable body,
|
||||
unknown actor → 404, URL construction, live fetch +
|
||||
decode, closure resolution (static map + closure peer
|
||||
resolver), missing peer → `no_peer_url`, 404 → `{status, 404}`,
|
||||
end-to-end `peer_actors:lookup_or_fetch` cache write.
|
||||
|
||||
**Tests:**
|
||||
|
||||
@@ -1072,6 +1086,63 @@ proceed.
|
||||
|
||||
Newest first.
|
||||
|
||||
- **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
|
||||
`peer_actors:lookup_or_fetch_srv/2`. Closure GETs
|
||||
`<base>/actors/<peer>` via Step 8e's `httpc:request/4` BIF
|
||||
with `Accept: application/vnd.fed-sx.actor-doc`, decodes
|
||||
the body via `term_codec:decode/1`, returns `{ok, AS}` where
|
||||
AS is the peer-actor-state proplist (`[{public_keys, [...]}]`,
|
||||
the shape `envelope:verify_signature` consumes). Cfg reuses
|
||||
the same `:peer_url` / `:peer_url_fn` resolution as
|
||||
`dispatch_http` (Step 8f) so a single Cfg can thread through
|
||||
both delivery and discovery.
|
||||
|
||||
Server side: `http_server.erl` now serves the same MIME.
|
||||
New `actor_doc` content-negotiation atom — `accept_format/1`
|
||||
matches `application/vnd.fed-sx.actor-doc` first
|
||||
(`actor_doc_prefix/0`); `content_type_for(actor_doc)`
|
||||
emits it on outbound. New `actor_doc_response_for/3`
|
||||
kernel-aware arm: when Cfg carries `{kernel, Kernel}` and
|
||||
the kernel has the actor, calls `nx_kernel:state_for/1`
|
||||
(NOT the legacy single-bucket `actor_state/1` accessor) and
|
||||
emits `term_codec:encode/1` of the AS. Other formats fall
|
||||
through to the existing /2 stub variants. Unknown actor →
|
||||
`not_found_response/0`. `actor_get/3` route dispatch now
|
||||
threads Cfg through to the /3 arm.
|
||||
|
||||
Subtle port note: this port's Erlang doesn't support
|
||||
`Mod:Fun(X)` dispatch on a variable module, so the
|
||||
Cfg `:kernel` field exists to flag "no kernel wired" →
|
||||
nil short-circuit; the actual call is hardcoded to
|
||||
`nx_kernel:state_for/1` (the only kernel module in play).
|
||||
Documented inline.
|
||||
|
||||
Outcome mapping (discovery_fetch):
|
||||
2xx + decodable → {ok, AS}
|
||||
2xx + bad body → {error, bad_actor_doc}
|
||||
non-2xx → {error, {status, N}}
|
||||
resolver miss → {error, no_peer_url}
|
||||
transport → {error, Reason} (BIF's network re-raise)
|
||||
|
||||
Test: `next/tests/discovery_fetch.sh` 11/11 — both halves.
|
||||
Server side: Accept negotiation, kernel + actor → 200 +
|
||||
decodable body, unknown actor → 404. Closure side: URL
|
||||
construction `<base>/actors/<peer>`, live GET against the
|
||||
background python stub returning hand-crafted term_codec
|
||||
bytes (Python encoding helper mirrors term_codec.erl's
|
||||
netstring format — count-based not byte-length headers for
|
||||
l/t), make_fetch_fn closure resolves through static map +
|
||||
closure peer_url_fn, missing peer → `no_peer_url`, 404 →
|
||||
`{status, 404}`, end-to-end `peer_actors:lookup_or_fetch/3`
|
||||
caches the result.
|
||||
|
||||
Adjacent gates: Erlang conformance 761/761, httpc_request
|
||||
10/10, dispatch_http 10/10, http_listen_bif 5/5,
|
||||
peer_actors 19/19, discovery 12/12, http_accept 13/13,
|
||||
http_actors 13/13 — all green.
|
||||
|
||||
- **2026-06-07** — Step 8f (closes Step 8 except 8b-timer which
|
||||
still gates on Blockers #3 send_after): live HTTP dispatch
|
||||
through `httpc:request/4`. New `next/kernel/dispatch_http.erl`
|
||||
|
||||
Reference in New Issue
Block a user