Files
rose-ash/lib/host/ta.sx
giles 355bcbefdc cross-domain slice 1: events as a fed-sx peer + allocate-a-post-to-a-calendar (LIVE)
The first cross-domain federated workflow — behaviors defined by TYPES, across domains.

- events.rose-ash.com is now a fed-sx PEER: a lib/host instance with SX_DOMAIN=events whose 'calendar'
  TYPE declares an on-allocate behavior. Replaces the Python events service (no strangler). serve.sh
  gates domain types/behaviors on SX_DOMAIN (blog=article publish/digest; events=calendar+allocate).
- DIRECTED cross-domain delivery: an activity with :to <peer-base> is delivered to that peer's inbox
  (∪ followers). The wire gains 'to'. So 'allocate' targets the events peer specifically.
- host/blog--allocate-activity/allocate! + POST /:slug/allocate?calendar=<id>; the events calendar
  type's allocate-link DAG (an execute-fold effect) fires on receipt.
- docker-compose: the sx_events service (own store, shared SX_FED_SECRET, externalnet for a future
  events.rose-ash.com Caddy route).

LIVE PROOF: publish 'Gig Night' on blog.rose-ash.com → POST /gig-night/allocate?calendar=main → the
events peer RECEIVES the directed, signed activity (/activities: 'allocate article gig-night') and
its calendar type's on-allocate behavior FIRES (/flows: 'linked gig-night'). blog 218/218, full
conformance green.

NEXT: events runs lib/events (real calendars/recurrence/ticketing); link event→post; shop
(lib/commerce) sells tickets — same federated, type-declared shape.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:25:37 +00:00

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) "to" (get a :to)
"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") :to (get w "to")
: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))))