fed-sx-m2: Step 5c — peer-actors cache + 19 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 34s

New next/kernel/peer_actors.erl is the federation-side cache for
{PeerActorId, PeerActorState} entries. PeerAS is exactly the shape
envelope:verify_signature/2 reads (proplist with :public_keys), so
the inbox handler can pipe the cache hit straight into
pipeline:validate_inbound/3 from Step 5b.

Pure-functional API:
  new/0
  lookup/2(PeerId, State) -> {ok, PeerAS} | not_found
  store/3(PeerId, PeerAS, State) -> NewState
  evict/2(PeerId, State) -> NewState
  peers/1(State) -> [PeerId]
  lookup_or_fetch/3(PeerId, FetchFn, State)
      -> {ok, PeerAS, NewState}      cache hit returns unchanged State,
                                     miss stores FetchFn result.
      | {error, Reason, State}        FetchFn failure preserves cache.
      | {error, {bad_fetch_return, X}, State}

FetchFn contract: (PeerId) -> {ok, PeerAS} | {error, Reason}.
Failed fetches do NOT poison the cache so callers can retry on
transient HTTP failures.

gen_server wrapper (registered name peer_actors):
  start_link/0,1   start_link/1 accepts initial proplist for fixtures
  stop/0
  lookup_srv/1
  store_srv/2
  lookup_or_fetch_srv/2
  peers_srv/0
  evict_srv/1

handle_call dispatches mirror the pure-fn paths exactly.

The actual HTTP-GET fetch implementation (peer's actor doc -> peer
AS proplist) is Step 5d's responsibility — for 5c, FetchFn is just
the contract callers fill in.

19/19 in next/tests/peer_actors.sh:
  - new/0 -> []
  - lookup miss -> not_found
  - store + lookup round-trip
  - peers/1 in insertion order
  - evict + evict-unknown no-op
  - lookup_or_fetch miss invokes FetchFn, hits cache after
  - lookup_or_fetch hit skips FetchFn (verified by tombstone fn)
  - fetch error preserves cache state
  - bad fetch return shape captured
  - gen_server start_link + miss/hit/fetch/evict round-trips
  - start_link/1 pre-populates cache from initial state

Conformance 761/761. 139/139 across 9 Step-5-adjacent suites
(inbox_pipeline, inbox_bucket, pipeline_signature, registry_server,
projection_server, nx_kernel_multi, bootstrap_start, http_publish,
smoke_app_pure, plus the new peer_actors).
This commit is contained in:
2026-06-06 16:36:19 +00:00
parent d103ecb863
commit d481af5791
3 changed files with 334 additions and 5 deletions

View File

@@ -360,11 +360,21 @@ actor *received*), and broadcasts to projections.
(rejected by stage_envelope before sig runs), wrong peer AS
(bad_signature), replay against inbox, distinct activities both
verify, stage short-circuit ordering verified.
- [ ] **5c** — Peer-actors cache projection (`peer_actors.erl`):
on first inbound from a new peer, fetches the peer's actor doc
and caches the public-keys. v2: synchronous fetch via the
http-client native primitive. Per design §13.6, stale-key
invalidation is v3.
- [x] **5c** — Peer-actors cache (`peer_actors.erl`). State shape
`[{PeerActorId, PeerActorState}, ...]` keyed by atom; PeerAS is
exactly the shape `envelope:verify_signature/2` reads (proplist
with `:public_keys`). Pure exports: `new/0`, `lookup/2`,
`store/3`, `evict/2`, `peers/1`, and the load-bearing
`lookup_or_fetch/3(PeerId, FetchFn, State)` that calls the
caller-supplied `FetchFn :: (PeerId) -> {ok, PeerAS} | {error, _}`
on miss and stores the successful result. Failed fetches do NOT
poison the cache so callers can retry on transient errors.
gen_server wrapper: `start_link/0,1`, `lookup_srv/1`,
`store_srv/2`, `lookup_or_fetch_srv/2`, `peers_srv/0`,
`evict_srv/1`. `start_link/1` accepts an initial state proplist
for tests / fixtures. 19/19 in `peer_actors.sh`. The actual
fetch implementation (HTTP GET of the peer's actor doc) is
Step 5d's responsibility — for 5c, FetchFn is just a contract.
- [ ] **5d** — http_server inbox handler wires the chain:
`POST /actors/<id>/inbox` body is the signed activity wire bytes;
parse → resolve peer-AS → `validate_inbound` → `append_inbox` →
@@ -780,6 +790,20 @@ proceed.
Newest first.
- **2026-06-06** — Step 5c: peer-actors cache (`peer_actors.erl`).
Pure-functional cache of `{PeerActorId, PeerAS}` entries with
the load-bearing `lookup_or_fetch/3(PeerId, FetchFn, State)`
entry: cache hit returns stored PeerAS unchanged; miss calls
`FetchFn(PeerId)`, stores success, returns `{ok, PeerAS,
NewState}`. Fetch errors don't poison the cache so callers can
retry on transient HTTP failures. gen_server wrapper exposes
the same shape under registered name `peer_actors`;
`start_link/1` accepts an initial proplist for tests.
Per-design v2 fetches are synchronous over plaintext HTTP; the
actual http-client call lands in Step 5d. 19/19 in
`peer_actors.sh`. Conformance 761/761. 139/139 across 9
Step-5-adjacent suites.
- **2026-06-06** — Step 5b: federation inbound pipeline.
`pipeline:validate_inbound/3(Activity, PeerAS, InboxLog)` runs
`stage_envelope` → `stage_signature(PeerAS)` → `stage_replay(InboxLog)`