Step 3 — federation, live-verified with TWO real host instances. - host/ta.sx: host/ta--post/make-http-wire/federate (POST a serialized activity to a peer's /inbox over real HTTP). host/blog.sx: POST /inbox (host/blog-inbox → receive! → process locally, does NOT re-federate — no loops). - DURABLE OUTBOX (fed-sx reliability, after the user asked 'if B is down does it still work?'): emit! processes locally (always succeeds), QUEUES per-peer to a persisted outbox, delivers best-effort. A peer being DOWN no longer fails the publish — delivery is GUARDED (SX guard catches the http-request connection error), failed items stay queued and retry on next emit / on boot / manual /flows?flush=1. /flows shows the outbox depth. - serve.sh: SX_PEERS → peers; boot load+flush of the outbox. docker-compose: a 2nd host sx_host_b (peer B, own store, no peers). LIVE PROOF: (1) a peer POSTs create/article to blog.rose-ash.com/inbox → A fires validate+notify. (2) publish on A → federates to B → B fires ITS behaviors on A's activity (B's /flows + /activities). (3) RESILIENCE: publish with B DOWN → A returns 303 (was 500) + queues; start B + flush → B receives the backlog + fires. blog 218/218 (+TA receive test), full host conformance green. A = blog.rose-ash.com (public/Caddy); B = sx_host_b (internal docker DNS only, no public domain). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
3.4 KiB
Plaintext
56 lines
3.4 KiB
Plaintext
;; lib/host/ta.sx — TA: the fed-sx TRANSPORT ADAPTER (plans/business-logic-fed-flows.md). A seam
|
|
;; transport {:emit :deliver} (behavior.sx) over a directional WIRE, so an activity emitted on one
|
|
;; instance reaches another instance's engine and fires ITS behaviors — federation.
|
|
;;
|
|
;; The transport is the SERIALIZATION boundary: activities cross the wire as SX-source strings
|
|
;; (string-keyed for persist/wire safety, reconstructed on arrival). The wire is dumb string
|
|
;; transport {:send :recv}; the REAL wire is next/ delivery (outbox→peers / inbox), DEFERRED to
|
|
;; TA-live (same persistent-kernel prerequisite as RA-live); a mem-wire drives the tests.
|
|
;;
|
|
;; DIRECTIONAL: :emit → an OUT wire (this instance's outbox → followers); :deliver → an IN wire
|
|
;; (its inbox ← those it follows). Separate channels, so processing a delivered activity (which the
|
|
;; seam re-emits to log it) goes to THIS instance's outbox, NOT back into the inbox — no loop.
|
|
|
|
;; canonical activity (keyword-keyed) <-> the flat string-keyed WIRE form (persist/serialise-safe).
|
|
(define host/ta--activity->wire
|
|
(fn (a)
|
|
{"verb" (get a :verb) "actor" (get a :actor) "object" (get a :object)
|
|
"type" (get a :object-type) "slug" (get a :slug) "category" (get a :category)
|
|
"relation" (get a :relation) "target" (get a :target)
|
|
"delta" (get a :delta) "id" (get a :id)}))
|
|
(define host/ta--wire->activity
|
|
(fn (w)
|
|
{:verb (get w "verb") :actor (get w "actor") :object (get w "object")
|
|
:object-type (get w "type") :slug (get w "slug") :category (get w "category")
|
|
:relation (get w "relation") :target (get w "target")
|
|
:delta (get w "delta") :id (get w "id")}))
|
|
(define host/ta--serialize (fn (a) (serialize (host/ta--activity->wire a))))
|
|
(define host/ta--deserialize (fn (s) (host/ta--wire->activity (parse-safe s))))
|
|
|
|
;; the transport: emit serialises to the OUT wire; deliver deserialises from the IN wire.
|
|
(define host/ta--make-transport
|
|
(fn (out-wire in-wire)
|
|
{:emit (fn (a) ((get out-wire :send) (host/ta--serialize a)))
|
|
:deliver (fn () (map host/ta--deserialize ((get in-wire :recv))))}))
|
|
|
|
;; an in-memory directional wire {:send :recv} (a captured mutable queue) — the test substrate.
|
|
(define host/ta--make-mem-wire
|
|
(fn ()
|
|
(let ((q (list)))
|
|
{:send (fn (s) (set! q (concat q (list s))))
|
|
:recv (fn () (let ((batch q)) (begin (set! q (list)) batch)))})))
|
|
|
|
;; TA-LIVE: an HTTP fed-wire — :send POSTs a serialized activity to a PEER's /inbox over real HTTP
|
|
;; (http-request, native primitive). :recv is unused: a peer's /inbox route pushes received
|
|
;; activities straight into its engine (host/blog--receive!), so delivery is push, not poll. This is
|
|
;; the fed-sx transport in production — an activity emitted here fires a REMOTE instance's behaviors.
|
|
;; POST a pre-serialized wire string to a peer's /inbox (may raise on connection failure — callers
|
|
;; that must not fail the local emit wrap this in a guard, per the durable-outbox pattern).
|
|
(define host/ta--post (fn (peer-base s) (http-request "POST" (str peer-base "/inbox") {"content-type" "text/plain"} s)))
|
|
(define host/ta--make-http-wire
|
|
(fn (peer-base)
|
|
{:send (fn (s) (host/ta--post peer-base s))
|
|
:recv (fn () (list))}))
|
|
;; serialize an activity + POST it to a peer (direct; the outbox path serializes-then-queues instead).
|
|
(define host/ta--federate (fn (peer-base a) (host/ta--post peer-base (host/ta--serialize a))))
|