federation production layer: actor model + follower graph + delivery timer + signatures (LIVE)
The full fed-sx production layer, live-verified across A (blog.rose-ash.com) and B (sx_host_b).
ACTOR MODEL + FOLLOWER GRAPH: activities carry a real :actor (SX_ACTOR); delivery targets FOLLOWERS,
not a static peer list. A peer subscribes by POSTing {verb:follow, actor, base} to /inbox
(host/blog--add-follower!); B follows A at boot (SX_FOLLOW) so A delivers to B. host/blog--{actor,
self-base, followers, follow!, delivery-bases} + durable followers store.
BACKGROUND DELIVERY TIMER: serve.sh's detached _fed_delivery_loop hits GET /fed-tick every 15s
(over /dev/tcp) → re-follow (idempotent, recovers a target that was down at boot) + flush the durable
outbox. Federation is eventually-consistent, not best-effort-at-emit.
SIGNATURE VERIFICATION: every federated POST is signed (host/blog--fed-sign = dr/sess-sig shared-secret
MAC over the body, SX_FED_SECRET); /inbox rejects a bad/missing signature with 403 (empty secret =
open). Applies to both follows and activity delivery.
PUBLIC DOMAIN: B joins externalnet so Caddy CAN reverse_proxy a subdomain to it — the DNS + Caddy
route itself is external ops config (no local Caddyfile).
LIVE PROOF: B follows A (followers:1); publish on A → SIGNED delivery to follower B → B verifies +
fires validate+notify; a forged POST (bad x-fed-sig) → 403; B down → publish queues → the background
timer auto-delivers the backlog when B returns (no manual flush). blog 218/218, full conformance green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,24 @@ _warm_serving_jit() {
|
||||
}
|
||||
_warm_serving_jit &
|
||||
|
||||
# TA-live: the background FEDERATION DELIVERY loop. Every 15s, hit /fed-tick (the container has no
|
||||
# curl, so speak HTTP over /dev/tcp), which re-follows our target (idempotent — recovers if it was
|
||||
# down at boot) and flushes the durable outbox (delivers backlog to followers who are now up). This
|
||||
# is the delivery TIMER — federation is eventually-consistent, not best-effort-at-emit-only.
|
||||
_fed_delivery_loop() {
|
||||
for _ in $(seq 1 240); do
|
||||
case "$( { exec 3<>/dev/tcp/127.0.0.1/"$PORT" && printf 'GET /health HTTP/1.0\r\nHost: x\r\n\r\n' >&3 && head -1 <&3; exec 3>&- 3<&-; } 2>/dev/null )" in
|
||||
*200*) break ;;
|
||||
esac
|
||||
sleep 0.5
|
||||
done
|
||||
while true; do
|
||||
{ exec 3<>/dev/tcp/127.0.0.1/"$PORT" && printf 'GET /fed-tick HTTP/1.0\r\nHost: x\r\n\r\n' >&3 && cat <&3 >/dev/null; exec 3>&- 3<&-; } 2>/dev/null
|
||||
sleep 15
|
||||
done
|
||||
}
|
||||
_fed_delivery_loop &
|
||||
|
||||
# Modules: every load line from conformance.sh's MODULES list, minus the ledger
|
||||
# (not needed to serve). server.sx supplies host/serve.
|
||||
MODULES=(
|
||||
@@ -220,15 +238,22 @@ EPOCH=1
|
||||
echo "(epoch $EPOCH)"
|
||||
echo "(eval \"(host/blog-load-pendinglog!)\")"
|
||||
EPOCH=$((EPOCH+1))
|
||||
# TA-live: federate emitted activities to peer /inbox endpoints (comma-separated SX_PEERS, e.g.
|
||||
# "http://sx_host_b:8000"). Empty by default (no federation). A peer that receives does NOT
|
||||
# re-federate, so an acyclic peer graph doesn't loop.
|
||||
PEERS_SX="(list"
|
||||
IFS=',' read -ra _PEER_ARR <<< "${SX_PEERS:-}"
|
||||
for _p in "${_PEER_ARR[@]:-}"; do [ -n "$_p" ] && PEERS_SX="$PEERS_SX \\\"$_p\\\""; done
|
||||
PEERS_SX="$PEERS_SX)"
|
||||
# TA-live ACTOR MODEL: our actor id (SX_ACTOR) + base URL (SX_SELF_URL — followers POST to
|
||||
# <base>/inbox). Load our persisted followers. If SX_FOLLOW is set, follow that actor's base at
|
||||
# boot (POST a follow to its /inbox), so it delivers ITS activities to us. Follower-based delivery
|
||||
# replaces the static peer list; the background loop re-follows (idempotent) if the target was down.
|
||||
echo "(epoch $EPOCH)"
|
||||
echo "(eval \"(host/blog--set-peers! $PEERS_SX)\")"
|
||||
echo "(eval \"(host/blog--set-actor! \\\"${SX_ACTOR:-site}\\\" \\\"${SX_SELF_URL:-}\\\")\")"
|
||||
EPOCH=$((EPOCH+1))
|
||||
echo "(epoch $EPOCH)"
|
||||
echo "(eval \"(host/blog-load-followers!)\")"
|
||||
EPOCH=$((EPOCH+1))
|
||||
echo "(epoch $EPOCH)"
|
||||
echo "(eval \"(host/blog--set-follow-target! \\\"${SX_FOLLOW:-}\\\")\")"
|
||||
EPOCH=$((EPOCH+1))
|
||||
# TA-live: the shared federation secret (peers sign every POST; /inbox verifies). Empty = open.
|
||||
echo "(epoch $EPOCH)"
|
||||
echo "(eval \"(host/blog--set-fed-secret! \\\"${SX_FED_SECRET:-}\\\")\")"
|
||||
EPOCH=$((EPOCH+1))
|
||||
# TA-live: rebuild the durable outbox + RETRY any deliveries that were pending from before a
|
||||
# restart (a peer that was down gets its backlog once it + we are back up).
|
||||
|
||||
Reference in New Issue
Block a user