host TA: the fed-sx transport adapter — federation loop proven at the seam
lib/host/ta.sx — a seam transport {:emit :deliver} over a DIRECTIONAL wire (out = outbox→followers,
in = inbox←follows). The transport is the SERIALIZATION boundary: activities cross the wire as
SX-source strings (host/ta--serialize/deserialize map the keyword-keyed activity ↔ a flat
string-keyed wire form of the P2 activity fields). host/ta--make-transport(out-wire, in-wire) +
host/ta--make-mem-wire (an in-memory directional queue for tests).
Proven (ta 5/5): content + relation activities round-trip through the wire; the FEDERATION LOOP —
instance A emits an activity → the wire carries it → instance B's behavior/pump delivers + processes
it → B's engine fires ITS behavior on A's activity; DIRECTIONAL (B re-emits to its own outbox, not
back into the inbox — no loop). 'Everything works over fed-sx', proven at the seam.
TA-live (deferred, same shape as RA-live): swap the mem-wire for the real next/ delivery wire —
needs a PERSISTENT next/ kernel (gen_servers don't survive across erlang-eval-ast calls) + the ACTOR
MODEL (peer_actors/follower_graph decide who the out-wire delivers to) + pushing /activities onto it.
Full host conformance green (+ta 5).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,7 @@ MODULES=(
|
|||||||
"lib/host/behavior.sx"
|
"lib/host/behavior.sx"
|
||||||
"lib/host/flows.sx"
|
"lib/host/flows.sx"
|
||||||
"lib/host/ra.sx"
|
"lib/host/ra.sx"
|
||||||
|
"lib/host/ta.sx"
|
||||||
"lib/host/htmlsx.sx"
|
"lib/host/htmlsx.sx"
|
||||||
"lib/host/blog.sx"
|
"lib/host/blog.sx"
|
||||||
"lib/host/page.sx"
|
"lib/host/page.sx"
|
||||||
@@ -118,6 +119,7 @@ SUITES=(
|
|||||||
"behavior host-be-tests-run! lib/host/tests/behavior.sx"
|
"behavior host-be-tests-run! lib/host/tests/behavior.sx"
|
||||||
"flows host-fl-tests-run! lib/host/tests/flows.sx"
|
"flows host-fl-tests-run! lib/host/tests/flows.sx"
|
||||||
"ra host-ra-tests-run! lib/host/tests/ra.sx"
|
"ra host-ra-tests-run! lib/host/tests/ra.sx"
|
||||||
|
"ta host-ta-tests-run! lib/host/tests/ta.sx"
|
||||||
"compose host-cp-tests-run! lib/host/tests/compose.sx"
|
"compose host-cp-tests-run! lib/host/tests/compose.sx"
|
||||||
"execute host-ex-tests-run! lib/host/tests/execute.sx"
|
"execute host-ex-tests-run! lib/host/tests/execute.sx"
|
||||||
"session host-se-tests-run! lib/host/tests/session.sx"
|
"session host-se-tests-run! lib/host/tests/session.sx"
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ MODULES=(
|
|||||||
"lib/host/behavior.sx"
|
"lib/host/behavior.sx"
|
||||||
"lib/host/flows.sx"
|
"lib/host/flows.sx"
|
||||||
"lib/host/ra.sx"
|
"lib/host/ra.sx"
|
||||||
|
"lib/host/ta.sx"
|
||||||
"lib/host/htmlsx.sx"
|
"lib/host/htmlsx.sx"
|
||||||
"lib/host/blog.sx"
|
"lib/host/blog.sx"
|
||||||
"lib/host/server.sx"
|
"lib/host/server.sx"
|
||||||
|
|||||||
41
lib/host/ta.sx
Normal file
41
lib/host/ta.sx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
;; 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)))})))
|
||||||
68
lib/host/tests/ta.sx
Normal file
68
lib/host/tests/ta.sx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
;; lib/host/tests/ta.sx — TA the fed-sx transport adapter (lib/host/ta.sx). Proves the federation
|
||||||
|
;; loop with an in-memory wire: an activity emitted on instance A crosses to instance B and fires
|
||||||
|
;; B's engine. The REAL wire (next/ delivery) is TA-live (deferred, persistent-kernel prerequisite).
|
||||||
|
|
||||||
|
(define host-ta-pass 0)
|
||||||
|
(define host-ta-fail 0)
|
||||||
|
(define host-ta-fails (list))
|
||||||
|
(define host-ta-test
|
||||||
|
(fn (name actual expected)
|
||||||
|
(if (= actual expected)
|
||||||
|
(set! host-ta-pass (+ host-ta-pass 1))
|
||||||
|
(begin (set! host-ta-fail (+ host-ta-fail 1))
|
||||||
|
(append! host-ta-fails {:name name :actual actual :expected expected})))))
|
||||||
|
|
||||||
|
;; ── serialization: activities cross the wire as SX-source and round-trip ──
|
||||||
|
(host-ta-test "a content activity round-trips through the wire (serialize → deserialize)"
|
||||||
|
(let ((r (host/ta--deserialize (host/ta--serialize
|
||||||
|
{:verb "create" :actor "alice" :object "cid1" :object-type "article"
|
||||||
|
:slug "post1" :category "urgent" :delta "create" :id "create:cid1"}))))
|
||||||
|
(list (get r :verb) (get r :object-type) (get r :slug) (get r :category) (get r :id)))
|
||||||
|
(list "create" "article" "post1" "urgent" "create:cid1"))
|
||||||
|
(host-ta-test "a relation activity round-trips (relation + target preserved)"
|
||||||
|
(let ((r (host/ta--deserialize (host/ta--serialize
|
||||||
|
{:verb "add" :actor "site" :object "p1" :object-type "article"
|
||||||
|
:relation "tagged" :target "urgent" :delta "add tagged urgent" :id "add:p1:tagged:urgent"}))))
|
||||||
|
(list (get r :verb) (get r :relation) (get r :target) (get r :id)))
|
||||||
|
(list "add" "tagged" "urgent" "add:p1:tagged:urgent"))
|
||||||
|
|
||||||
|
;; ── the in-memory wire accumulates + drains ──
|
||||||
|
(host-ta-test "mem-wire: send accumulates, recv drains"
|
||||||
|
(let ((w (host/ta--make-mem-wire)))
|
||||||
|
(begin ((get w :send) "a") ((get w :send) "b")
|
||||||
|
(let ((batch ((get w :recv)))) (list (len batch) (len ((get w :recv)))))))
|
||||||
|
(list 2 0))
|
||||||
|
|
||||||
|
;; ── the federation LOOP: A emits → wire → B pump → B's behavior fires on A's activity ──
|
||||||
|
(define ta-wire-ab (host/ta--make-mem-wire)) ;; A's outbox → B's inbox
|
||||||
|
(define ta-sink (host/ta--make-mem-wire)) ;; B's outbox (its own followers; ignored here)
|
||||||
|
(define ta-empty (host/ta--make-mem-wire)) ;; A's inbox (empty)
|
||||||
|
(define ta-A (host/ta--make-transport ta-wire-ab ta-empty))
|
||||||
|
(define ta-fired (list))
|
||||||
|
(define ta-B-engine
|
||||||
|
(behavior/make-engine
|
||||||
|
{:triggers {:register! (fn (s d h) nil)
|
||||||
|
:match (fn (a) (if (= (get a :verb) "create") (list {:dag "peer-dag"}) (list)))}
|
||||||
|
:runner {:capabilities (list)
|
||||||
|
:run (fn (dag env) {:status "done"
|
||||||
|
:effects (list {:kind "peer-fired" :for (get (get env :activity) :id)})})}
|
||||||
|
:transport (host/ta--make-transport ta-sink ta-wire-ab) ;; B delivers from wire-ab, emits to sink
|
||||||
|
:driver {:dispatch (fn (eff) (begin (set! ta-fired (concat ta-fired (list eff))) (list)))}}))
|
||||||
|
(host-ta-test "federation loop: A emits → wire → B pump → B's behavior fires on A's activity"
|
||||||
|
(begin
|
||||||
|
(set! ta-fired (list))
|
||||||
|
((get ta-A :emit) {:verb "create" :actor "alice" :object "cid1" :object-type "article" :id "create:cid1"})
|
||||||
|
(let ((trace (behavior/pump ta-B-engine)))
|
||||||
|
(list (len (get trace :emitted)) (get (first ta-fired) :kind) (get (first ta-fired) :for))))
|
||||||
|
(list 1 "peer-fired" "create:cid1"))
|
||||||
|
(host-ta-test "directional: B re-emits to its OWN outbox, not back into the inbox (no loop)"
|
||||||
|
(begin
|
||||||
|
((get ta-A :emit) {:verb "create" :actor "a" :object "c2" :object-type "article" :id "create:c2"})
|
||||||
|
(behavior/pump ta-B-engine) ;; delivers + fires; re-emit → sink
|
||||||
|
(len (get (behavior/pump ta-B-engine) :emitted))) ;; wire-ab now empty → nothing
|
||||||
|
0)
|
||||||
|
|
||||||
|
(define host-ta-tests-run!
|
||||||
|
(fn ()
|
||||||
|
{:total (+ host-ta-pass host-ta-fail)
|
||||||
|
:passed host-ta-pass :failed host-ta-fail :fails host-ta-fails}))
|
||||||
@@ -271,10 +271,23 @@ the flow instance Id is the resume handle.
|
|||||||
The runner + marshalling + suspend/resume mechanics are all proven; this is process lifecycle + wiring.
|
The runner + marshalling + suspend/resume mechanics are all proven; this is process lifecycle + wiring.
|
||||||
|
|
||||||
## TA — the FED-SX TRANSPORT adapter ← federation proper
|
## TA — the FED-SX TRANSPORT adapter ← federation proper
|
||||||
- [ ] A seam transport over next/ delivery: :emit → outbox → peers; :deliver → inbox. A remote
|
- [x] **TA TRANSPORT BUILT + the federation LOOP PROVEN 2026-07-02.** lib/host/ta.sx — a seam
|
||||||
peer's engine fires its own triggers. Everything works over fed-sx. RISK: next/ delivery M2
|
transport {:emit :deliver} over a DIRECTIONAL wire (out=outbox→followers, in=inbox←follows). The
|
||||||
blockers (er-scheduler context). Needs the ACTOR MODEL real (:actor was a P0 placeholder —
|
transport is the SERIALIZATION boundary: activities cross as SX-source strings via host/ta--
|
||||||
peer_actors / follower_graph / per-author identity).
|
serialize/deserialize (keyword-keyed activity ↔ flat string-keyed wire form — the P2 activity
|
||||||
|
fields). host/ta--make-transport(out-wire, in-wire) + host/ta--make-mem-wire (an in-memory
|
||||||
|
directional queue). PROVEN (ta 5/5): a content + a relation activity round-trip through the wire;
|
||||||
|
the FEDERATION LOOP — instance A emits an activity → the wire carries it → instance B's
|
||||||
|
behavior/pump delivers + processes it → B's engine fires ITS behavior on A's activity; DIRECTIONAL
|
||||||
|
(B re-emits to its own outbox, not back into the inbox — no loop). This is "everything works over
|
||||||
|
fed-sx" proven at the seam. Full host conformance green.
|
||||||
|
- [ ] **TA-LIVE (deferred — same shape as RA-live).** Swap the mem-wire for the REAL next/ delivery
|
||||||
|
wire (outbox → http_server → peer inbox). Needs: (a) a PERSISTENT next/ kernel process (gen_servers
|
||||||
|
don't survive across erlang-eval-ast calls — the RA-live finding; next/ outbox/http_server are the
|
||||||
|
persistent side); (b) the ACTOR MODEL real (:actor is a "site" placeholder — peer_actors /
|
||||||
|
follower_graph / per-author identity decide WHO the out-wire delivers to); (c) push /activities
|
||||||
|
(the P2 event source) onto the out-wire. RISK: next/ delivery M2 blockers (er-scheduler context).
|
||||||
|
The transport contract + serialization + the loop are proven; TA-live is the wire impl + placement.
|
||||||
|
|
||||||
## AX — artdag GROWS control-flow (business logic MIGRATES to artdag) [DEMAND-DRIVEN]
|
## AX — artdag GROWS control-flow (business logic MIGRATES to artdag) [DEMAND-DRIVEN]
|
||||||
Today artdag is pure dataflow and the execute-fold is the synchronous control-flow runner. That's
|
Today artdag is pure dataflow and the execute-fold is the synchronous control-flow runner. That's
|
||||||
@@ -303,6 +316,13 @@ covers everything until a DAG's cost/latency/placement forces the substrate.
|
|||||||
activities), so business logic can change state, which federates, which triggers more flows.
|
activities), so business logic can change state, which federates, which triggers more flows.
|
||||||
|
|
||||||
## Progress log (newest first)
|
## Progress log (newest first)
|
||||||
|
- 2026-07-02 — TA TRANSPORT built + the federation LOOP proven (lib/host/ta.sx, ta 5/5). A seam
|
||||||
|
transport over a directional wire (serialization boundary; activities cross as SX-source). Proven
|
||||||
|
in-memory: A emits → wire → B pump → B's engine fires ITS behavior on A's activity (directional, no
|
||||||
|
loop). "Everything works over fed-sx" proven at the seam. TA-live (real next/ delivery wire) deferred
|
||||||
|
— needs the persistent kernel (RA-live finding) + the actor model (who to deliver to). NEXT: the
|
||||||
|
DISTRIBUTED HALF is now all proven-at-the-seam (RA + TA); the remaining live steps (RA-live, TA-live)
|
||||||
|
share ONE prerequisite — a persistent next/ kernel process + the actor model. Or P4/AX/RX.
|
||||||
- 2026-07-02 — P2 DONE + LIVE-VERIFIED. All observable state changes now emit canonical activities
|
- 2026-07-02 — P2 DONE + LIVE-VERIFIED. All observable state changes now emit canonical activities
|
||||||
through the seam: content Create/Update + relation Add/Remove. DEBT #1 fixed (per-event ids; edge-
|
through the seam: content Create/Update + relation Add/Remove. DEBT #1 fixed (per-event ids; edge-
|
||||||
based for relations). The activity log is the durable event source, surfaced at /activities. Live:
|
based for relations). The activity log is the durable event source, surfaced at /activities. Live:
|
||||||
|
|||||||
Reference in New Issue
Block a user