Replace WebTransport-only design with three-transport model: - Browser↔server: WebSocket (works today, no infrastructure changes) - Browser↔browser: WebRTC data channels (true P2P, NAT traversal) - Server↔server: HTTP federation (existing sx-pub) Home nodes relay WebRTC signaling. Reliable channels for chat/components, unreliable channels for game state/cursor positions. Transport-agnostic protocol layer — upgrade to WebTransport later with zero SX changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
385 lines
30 KiB
Plaintext
385 lines
30 KiB
Plaintext
;; sx-web: Federated Component Web
|
||
;; Browser-native peer network with content-addressed SX components.
|
||
;; Builds on sx-pub (phases 1-4 complete: publishing, federation, anchoring, IPFS).
|
||
|
||
(defcomp ~plans/sx-web/plan-sx-web-content ()
|
||
(~docs/page :title "sx-web: Federated Component Web"
|
||
|
||
(p :class "text-stone-500 text-sm italic mb-8"
|
||
"A two-tier peer network where server nodes pin to IPFS and browser tabs "
|
||
"participate via WebTransport. Components, tests, and content are all "
|
||
"content-addressed SX expressions verified by CID regardless of source. "
|
||
"Builds on sx-pub phases 1–4 (publishing, federation, anchoring, IPFS).")
|
||
|
||
;; =====================================================================
|
||
;; Architecture
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Architecture" :id "architecture"
|
||
|
||
(p "Two tiers, one logical network. Trust anchored on CIDs, not sources.")
|
||
|
||
(table :class "w-full mb-6 text-sm"
|
||
(thead
|
||
(tr (th :class "text-left p-2" "") (th :class "text-left p-2" "Server Node") (th :class "text-left p-2" "Browser Node")))
|
||
(tbody
|
||
(tr (td :class "p-2 font-medium" "Runtime") (td :class "p-2" "OCaml + Python") (td :class "p-2" "JavaScript (sx-browser.js)"))
|
||
(tr (td :class "p-2 font-medium" "Storage") (td :class "p-2" "IPFS daemon + PostgreSQL") (td :class "p-2" "IndexedDB + Cache API"))
|
||
(tr (td :class "p-2 font-medium" "IPFS") (td :class "p-2" "Full DHT peer, pins content") (td :class "p-2" "Verifies CIDs, caches locally"))
|
||
(tr (td :class "p-2 font-medium" "Transport") (td :class "p-2" "WebSocket + HTTP federation") (td :class "p-2" "WebSocket to home + WebRTC to peers"))
|
||
(tr (td :class "p-2 font-medium" "Availability") (td :class "p-2" "Long-lived, always on") (td :class "p-2" "Ephemeral, online when open"))
|
||
(tr (td :class "p-2 font-medium" "Trust") (td :class "p-2" "CID verification + blockchain anchor") (td :class "p-2" "CID verification (same guarantee)"))))
|
||
|
||
(p "A component fetched from IPFS, from a server, from another browser tab, "
|
||
"or from local IndexedDB cache is verified the same way: hash it, check the CID. "
|
||
"The transport is irrelevant to the trust model."))
|
||
|
||
;; =====================================================================
|
||
;; Phase 1: Browser Node Foundation
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Phase 1: Browser Node Foundation" :id "phase-1"
|
||
|
||
(p :class "text-stone-600 italic mb-4"
|
||
"Make the browser a first-class participant, not just a viewer.")
|
||
|
||
(~docs/subsection :title "1a. Content Store (IndexedDB)"
|
||
(p "Browser-local content-addressed store. Same interface as server-side "
|
||
(code "content-put") "/" (code "content-get") " but backed by IndexedDB.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Browser content store — pure SX, platform provides IndexedDB ops\n(define browser-store (make-content-store\n {:backend :indexeddb\n :db-name \"sx-web\"\n :verify true})) ;; always verify CID on read\n\n;; Same API as server store\n(content-put browser-store sx-source) ;; → CID\n(content-get browser-store cid) ;; → SX source | nil\n(content-has? browser-store cid) ;; → bool\n(content-pin browser-store cid) ;; mark as persistent\n(content-unpin browser-store cid) ;; allow eviction"
|
||
"lisp"))
|
||
|
||
(p "Two IndexedDB object stores:")
|
||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||
(li (strong "blobs") " — CID → SX source bytes (content-addressed, immutable)")
|
||
(li (strong "pins") " — CID → {pinned, last-accessed, size, source-node} (eviction metadata)"))
|
||
|
||
(p "Unpinned content evicted LRU when storage exceeds quota. "
|
||
"Pinned content survives until explicitly unpinned."))
|
||
|
||
(~docs/subsection :title "1b. Service Worker"
|
||
(p "Intercepts fetch requests to resolve CIDs locally before hitting the network.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Service Worker routes\n;;\n;; /sx-web/cid/<cid> → check IndexedDB first, then fetch from home node\n;; /sx-web/resolve/<path> → resolve path→CID via local index, then fetch\n;; /sx-web/eval/<cid> → fetch + evaluate, return rendered result\n;;\n;; Cache strategy:\n;; CID routes are immutable — cache forever once verified\n;; Path routes check freshness against home node"
|
||
"text"))
|
||
|
||
(p "The Service Worker makes the browser work offline for all pinned components. "
|
||
"Online, it deduplicates fetches across tabs — one IndexedDB, shared cache."))
|
||
|
||
(~docs/subsection :title "1c. CID Verification in JavaScript"
|
||
(p "SHA3-256 hashing in the browser for CID verification. "
|
||
"Uses SubtleCrypto API (hardware-accelerated on modern browsers).")
|
||
|
||
(~docs/code :src (highlight
|
||
";; New platform primitive for browser host\n(define content-hash\n (fn (source)\n (host-call (host-get (host-global \"crypto\") \"subtle\")\n \"digest\" \"SHA-256\" (encode-utf8 source))))\n\n;; Verification: hash content, compare to claimed CID\n(define verify-cid\n (fn (cid source)\n (= cid (content-hash source))))"
|
||
"lisp"))
|
||
|
||
(p "One new platform primitive: " (code "content-hash") ". "
|
||
"Everything else (verification, pinning, eviction) is pure SX."))
|
||
|
||
(~docs/subsection :title "1d. Deliverables"
|
||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||
(li (code "web/lib/content-store.sx") " — browser content store (~120 LOC)")
|
||
(li (code "web/lib/sw-routes.sx") " — Service Worker CID routing (~80 LOC)")
|
||
(li "Platform primitive: " (code "content-hash") " in JS host (~15 LOC)")
|
||
(li "IndexedDB schema: blobs + pins stores"))))
|
||
|
||
;; =====================================================================
|
||
;; Phase 2: WebTransport Peer Layer
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Phase 2: Peer Network" :id "phase-2"
|
||
|
||
(p :class "text-stone-600 italic mb-4"
|
||
"Three transports, one SX protocol. Browser↔server via WebSocket, "
|
||
"browser↔browser via WebRTC data channels, server↔server via federation. "
|
||
"The protocol layer doesn't know or care which pipe carries it.")
|
||
|
||
(~docs/subsection :title "2a. SX Protocol Definition"
|
||
(p "The protocol is an SX component. Nodes fetch, evaluate, and speak it.")
|
||
|
||
(~docs/code :src (highlight
|
||
"(defprotocol sx-sync \"0.1\"\n :messages {\n ;; Content exchange\n :have (fn (cids) ...) ;; I have these CIDs\n :want (fn (cids) ...) ;; I need these CIDs\n :provide (fn (cid source) ...) ;; Here's the content\n :reject (fn (cid reason) ...) ;; Can't provide\n\n ;; Discovery\n :announce (fn (cid metadata) ...) ;; New content published\n :query (fn (pattern) ...) ;; Search for components\n :results (fn (matches) ...) ;; Search results\n\n ;; Identity\n :hello (fn (node-id pubkey) ...) ;; Handshake\n :verify (fn (challenge sig) ...) ;; Prove identity\n }\n\n :flow {\n ;; Content resolution\n :want → :provide | :reject\n ;; Publishing\n :announce → :want (from interested peers)\n ;; Connection\n :hello → :verify → ready\n })"
|
||
"lisp"))
|
||
|
||
(p "The protocol definition is itself content-addressed. Nodes pin the protocol "
|
||
"version they speak. Protocol upgrades are published to sx-pub like any component."))
|
||
|
||
(~docs/subsection :title "2b. Three Transports"
|
||
|
||
(table :class "w-full mb-6 text-sm"
|
||
(thead
|
||
(tr (th :class "text-left p-2" "Link") (th :class "text-left p-2" "Transport") (th :class "text-left p-2" "Why")))
|
||
(tbody
|
||
(tr (td :class "p-2 font-medium" "Browser ↔ Home node") (td :class "p-2" "WebSocket") (td :class "p-2" "Reliable, always-on, works today, no infrastructure changes"))
|
||
(tr (td :class "p-2 font-medium" "Browser ↔ Browser") (td :class "p-2" "WebRTC data channel") (td :class "p-2" "True peer-to-peer, NAT traversal via ICE/STUN, UDP + TCP modes"))
|
||
(tr (td :class "p-2 font-medium" "Server ↔ Server") (td :class "p-2" "HTTP federation") (td :class "p-2" "Existing sx-pub protocol, signed activities"))))
|
||
|
||
(p "The SX protocol layer is transport-agnostic. " (code "peer-send") " and " (code "peer-listen") " "
|
||
"work identically whether the peer is connected via WebSocket, WebRTC, or HTTP. "
|
||
"Upgrade to WebTransport when HTTP/3 infrastructure matures — zero protocol changes."))
|
||
|
||
(~docs/subsection :title "2c. Browser ↔ Home Node (WebSocket)"
|
||
(p "Browser connects to home node on page load. Receives push updates, "
|
||
"sends publish requests, queries the federation.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Connect to home node — WebSocket, works today\n(define home (ws-connect (str \"wss://\" home-node \"/sx-web/ws\")))\n\n;; Same protocol regardless of transport\n(peer-send home (sx-hello (node-id) (node-pubkey)))\n(peer-listen home (fn (msg) (dispatch-protocol msg)))\n\n;; Push notifications from server:\n;; (announce \"Qm..xyz\" {:author \"...\" :type :component})\n;; Content exchange:\n;; (want (list \"Qm..abc\")) → (provide \"Qm..abc\" \"(defcomp ...)\")\n;; Peer introduction:\n;; (peers (list {:id \"...\" :offer sdp-offer}))"
|
||
"lisp")))
|
||
|
||
(~docs/subsection :title "2d. Browser ↔ Browser (WebRTC)"
|
||
(p "Direct peer-to-peer connections between browsers. Home nodes relay "
|
||
"the signaling (SDP offer/answer exchange), then browsers talk directly. "
|
||
"Essential for low-latency game state, collaborative editing, and peer content exchange.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Signaling: home nodes relay SDP offers\n;;\n;; Browser A → Home A (WebSocket): (rtc-offer peer-b-id sdp)\n;; Home A → Home B (federation): (rtc-relay peer-a-id sdp)\n;; Home B → Browser B (WebSocket): (rtc-offer peer-a-id sdp)\n;; Browser B → Home B: (rtc-answer peer-a-id sdp)\n;; ... relay back ...\n;; WebRTC connection established — browsers talk directly\n\n;; After signaling, same API as any peer\n(define peer-b (rtc-connect offer))\n(peer-send peer-b (signal-update \"player-x\" 42))\n(peer-listen peer-b (fn (msg) (dispatch-protocol msg)))"
|
||
"lisp"))
|
||
|
||
(~docs/code :src (highlight
|
||
";; WebRTC data channel modes — chosen per room\n;;\n;; Reliable ordered (TCP-like):\n;; Chat messages, component exchange, CID resolution\n;; Every message arrives, in order\n;;\n;; Unreliable unordered (UDP-like):\n;; Game state (player positions), cursor positions, audio levels\n;; Latest value matters, old values can drop\n;;\n;; A room can use both — reliable for commands, unreliable for state\n(define game-room (join-room home \"game-1\"\n {:reliable {:chat (signal (list)) :inventory (signal (dict))}\n :unreliable {:player-x (signal 0) :player-y (signal 0)\n :aim-angle (signal 0)}}))"
|
||
"lisp"))
|
||
|
||
(p "Peer-to-peer means:")
|
||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||
(li "Game state at LAN-like latency — no server round-trip")
|
||
(li "Content exchange between nearby browsers without hitting IPFS")
|
||
(li "Collaborative editing with sub-frame update propagation")
|
||
(li "Works even if both home nodes are on modest hardware")
|
||
(li "Server load scales with signaling, not with traffic")))
|
||
|
||
(~docs/subsection :title "2d. Shared Signals Over WebTransport"
|
||
(p "Signals synchronized across peers. The same reactive primitive "
|
||
"that drives local UI drives multiplayer state. A " (code "shared-signal") " "
|
||
"is a signal whose writes propagate over WebTransport to all subscribed peers.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Shared signal — local writes propagate to peers, remote writes update locally\n(define player-x (shared-signal room \"player-x\" 0))\n(define player-y (shared-signal room \"player-y\" 0))\n\n;; Local code uses normal signal ops — no awareness of networking\n(reset! player-x 42) ;; updates local + sends to peers\n(deref player-x) ;; reactive, triggers re-render\n(swap! player-y inc) ;; same — local update + peer sync\n\n;; Under the hood:\n;; (reset! shared-sig val)\n;; → (reset! local-signal val)\n;; → (peer-send room (signal-update \"player-x\" val))\n;;\n;; incoming (signal-update \"player-x\" val) from peer\n;; → (reset! local-signal val)\n;; → reactive DOM updates fire as normal"
|
||
"lisp"))
|
||
|
||
(p "A " (code "room") " is a WebTransport channel with a set of shared signals. "
|
||
"Peers join a room, receive current state, and get incremental updates. "
|
||
"The signal names are the contract — any SX component that reads "
|
||
(code "(deref player-x)") " works whether the signal is local or shared.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Room: a named set of shared signals over WebTransport\n(define room (join-room home-peer \"game-lobby\"\n {:signals {:player-x 0 :player-y 0 :chat-messages (list)}\n :on-join (fn (peer-id) (announce peer-id))\n :on-leave (fn (peer-id) (remove-player peer-id))}))\n\n;; Game loop reads shared signals — identical to local signals\n(effect (fn ()\n (draw-player (deref player-x) (deref player-y))))\n\n;; Chat is shared too — append a message, all peers see it\n(reset! chat-messages (append (deref chat-messages)\n (dict :from (node-id) :text \"hello\")))"
|
||
"lisp"))
|
||
|
||
(p "Protocol messages for shared state:")
|
||
(~docs/code :src (highlight
|
||
";; Added to sx-sync protocol\n:join-room (fn (room-id signal-names) ...) ;; subscribe to room\n:room-state (fn (room-id signals) ...) ;; full state snapshot\n:signal-update (fn (room-id name value) ...) ;; incremental update\n:leave-room (fn (room-id) ...) ;; unsubscribe\n\n;; Conflict resolution: last-writer-wins by default\n;; Opt-in CRDT merge for collaborative data (lists, sets, maps)\n:signal-merge (fn (room-id name op args) ...) ;; CRDT operation"
|
||
"text"))
|
||
|
||
(p "Games, collaborative editors, chat, multiplayer tools — all the same primitive. "
|
||
"A " (code "shared-signal") " is a signal. Existing components work unchanged. "
|
||
"The networking is invisible to the reactive layer."))
|
||
|
||
(~docs/subsection :title "2f. Deliverables"
|
||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||
(li (code "web/lib/sx-sync.sx") " — protocol definition incl. shared signals (~300 LOC)")
|
||
(li (code "web/lib/peer.sx") " — transport-agnostic peer abstraction (~200 LOC)")
|
||
(li (code "web/lib/shared-signal.sx") " — shared signal primitive (~120 LOC)")
|
||
(li "Platform primitives: " (code "ws-connect, rtc-connect, peer-send, peer-listen") " (~80 LOC JS)")
|
||
(li "Server: WebSocket endpoint + signaling relay + room state (~350 LOC Python)")
|
||
(li "Conflict resolution: LWW default + CRDT opt-in (~150 LOC SX)"))))
|
||
|
||
;; =====================================================================
|
||
;; Phase 3: In-Browser Authoring
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Phase 3: In-Browser Authoring" :id "phase-3"
|
||
|
||
(p :class "text-stone-600 italic mb-4"
|
||
"Edit, preview, test, and publish from a browser tab. No local dev environment.")
|
||
|
||
(~docs/subsection :title "3a. Live Editor"
|
||
(p "SX editor component with real-time preview. "
|
||
"Edit source on the left, rendered output on the right. "
|
||
"The editor is itself an SX island.")
|
||
|
||
(~docs/code :src (highlight
|
||
"(defisland ~sx-web/editor (&key initial-source)\n (let ((source (signal (or initial-source \"\")))\n (parsed (computed (fn () (try-parse (deref source)))))\n (preview (computed (fn () (try-render (deref parsed))))))\n (div :class \"flex h-full\"\n ;; Source editor (textarea or CodeMirror via foreign FFI)\n (div :class \"flex-1\"\n (textarea :bind source :class \"font-mono ...\"))\n ;; Live preview\n (div :class \"flex-1 border-l\"\n (if (get (deref preview) \"error\")\n (div :class \"text-red-600\" (get (deref preview) \"error\"))\n (div (get (deref preview) \"result\")))))))"
|
||
"lisp"))
|
||
|
||
(p "The evaluator runs client-side. No server round-trip for preview. "
|
||
"Syntax errors, type errors, and render errors show inline as you type."))
|
||
|
||
(~docs/subsection :title "3b. In-Browser Test Runner"
|
||
(p "Tests are SX expressions. The browser evaluator runs them directly.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Tests travel with components\n(deftest ~my-component/tests\n (test \"renders title\"\n (let ((html (render-to-html (~my-component :title \"Hello\"))))\n (assert-contains html \"Hello\")))\n\n (test \"handles empty props\"\n (let ((html (render-to-html (~my-component))))\n (assert-contains html \"Untitled\"))))\n\n;; Run in browser — same evaluator, same results\n(run-tests ~my-component/tests)\n;; => {:passed 2 :failed 0 :errors ()}"
|
||
"lisp"))
|
||
|
||
(p "Component + tests + docs are published as a single unit to sx-pub. "
|
||
"Any node can re-run the tests to verify the component works."))
|
||
|
||
(~docs/subsection :title "3c. Publish Flow"
|
||
(p "From browser, one action: edit → test → publish → federate.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Browser publish flow\n(define publish!\n (fn (source)\n (let ((cid (content-hash source))\n (tests (extract-tests source))\n (results (run-tests tests)))\n (when (all-passed? results)\n ;; Pin locally\n (content-pin browser-store cid)\n ;; Push to home node via WebTransport\n (peer-send home-peer\n (sx-publish cid source\n {:tests results\n :requires (extract-requires source)}))\n ;; Home node pins to IPFS + anchors + federates\n cid))))"
|
||
"lisp"))
|
||
|
||
(p "The browser handles editing, testing, hashing, and local pinning. "
|
||
"The home server handles IPFS pinning, blockchain anchoring, and federation delivery. "
|
||
"Clean split: browsers create, servers distribute."))
|
||
|
||
(~docs/subsection :title "3d. Deliverables"
|
||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||
(li (code "web/lib/editor.sx") " — live editor island (~300 LOC)")
|
||
(li (code "web/lib/test-runner.sx") " — browser test runner (~150 LOC)")
|
||
(li (code "web/lib/publish.sx") " — publish flow (~100 LOC)")
|
||
(li "Test extraction from SX source (find deftest forms)"))))
|
||
|
||
;; =====================================================================
|
||
;; Phase 4: Component Discovery & Resolution
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Phase 4: Component Discovery & Resolution" :id "phase-4"
|
||
|
||
(p :class "text-stone-600 italic mb-4"
|
||
"Find, resolve, and depend on components across the federation.")
|
||
|
||
(~docs/subsection :title "4a. Dependency Resolution"
|
||
(p "Components declare dependencies via " (code ":requires") ". "
|
||
"Resolution checks local cache first, then home node, then federation.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Component with declared dependencies\n(defcomp ~charts/bar-chart (&key data labels)\n :requires ((~cssx/tw \"Qm..css\") ;; pinned by CID\n (~charts/axis \"Qm..axis\") ;; specific version\n (~charts/tooltip :latest)) ;; resolve latest from publisher\n ...)\n\n;; Resolution order:\n;; 1. Local IndexedDB (by CID)\n;; 2. Service Worker cache\n;; 3. Home node (WebTransport :want)\n;; 4. Federation query (WebTransport :query)\n;; 5. IPFS gateway (HTTP fallback)"
|
||
"lisp"))
|
||
|
||
(p "CID dependencies are immutable — resolve once, cache forever. "
|
||
(code ":latest") " dependencies check the publisher's feed for the current CID."))
|
||
|
||
(~docs/subsection :title "4b. Component Browser"
|
||
(p "Browse, search, and preview components from the federated network. "
|
||
"Each component renders its own preview because it IS executable SX.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; Search the federation\n(peer-send home-peer\n (sx-query {:type :component\n :tags [\"chart\" \"visualization\"]\n :has-tests true}))\n\n;; Results include CID, metadata, AND the component itself\n;; Click to preview — evaluates client-side\n;; Click to pin — stores in IndexedDB\n;; Click to fork — opens in editor with source"
|
||
"lisp"))
|
||
|
||
(p "The component browser is itself an SX component published to sx-pub."))
|
||
|
||
(~docs/subsection :title "4c. Deliverables"
|
||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||
(li (code "web/lib/resolver.sx") " — dependency resolution chain (~200 LOC)")
|
||
(li (code "sx/sx/tools/component-browser.sx") " — federated browser (~400 LOC)")
|
||
(li (code "sx/sx/tools/search.sx") " — search interface (~150 LOC)")
|
||
(li "Server: search index over pinned components"))))
|
||
|
||
;; =====================================================================
|
||
;; Phase 5: Node Bootstrap
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Phase 5: Node Bootstrap" :id "phase-5"
|
||
|
||
(p :class "text-stone-600 italic mb-4"
|
||
"Standing up a new node: one command, minutes to first render.")
|
||
|
||
(~docs/subsection :title "5a. Server Node Bootstrap"
|
||
(~docs/code :src (highlight
|
||
"# One command. Pulls Docker image, starts all services.\ndocker run -d --name sx-node \\\n -e SX_SEED_PEER=pub.sx-web.org \\\n -e SX_DOMAIN=my-node.example.com \\\n -p 443:443 \\\n ghcr.io/rose-ash/sx-node:latest\n\n# What happens:\n# 1. IPFS daemon starts, connects to network\n# 2. Fetches spec CIDs from seed peer\n# 3. Evaluates specs — now has a working SX evaluator\n# 4. Follows seed peer — receives component announcements\n# 5. Caddy auto-provisions TLS via Let's Encrypt\n# 6. WebTransport endpoint live\n# 7. Ready to serve, publish, and federate"
|
||
"bash"))
|
||
|
||
(p "The bootstrap IS the proof: if your node can fetch specs from the network, "
|
||
"evaluate them, and render components — the system works. "
|
||
"No manual configuration beyond domain name and seed peer."))
|
||
|
||
(~docs/subsection :title "5b. Browser Node Bootstrap"
|
||
(~docs/code :src (highlight
|
||
";; Visit any sx-web node in a browser\n;; → sx-browser.js loads (evaluator + signals + DOM adapter)\n;; → Service Worker installs (CID caching)\n;; → WebTransport connects to host node\n;; → Spec CIDs pinned to IndexedDB\n;; → You're a node.\n;;\n;; No install. No signup. No CLI.\n;; Close the tab → ephemeral node disappears\n;; Reopen → reconnects, IndexedDB still has your pins"
|
||
"text"))
|
||
|
||
(p "A browser becomes a node by visiting a URL. "
|
||
"Leaving keeps your pins. Returning restores your state. "
|
||
"The barrier to entry is typing an address."))
|
||
|
||
(~docs/subsection :title "5c. Deliverables"
|
||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||
(li "Docker image: " (code "sx-node") " with OCaml host, IPFS, Caddy, PostgreSQL")
|
||
(li "Bootstrap script: fetch specs, verify CIDs, initialize DB")
|
||
(li "Seed peer protocol: advertise available specs + popular components")
|
||
(li "Browser auto-bootstrap: Service Worker install + spec pinning on first visit"))))
|
||
|
||
;; =====================================================================
|
||
;; Phase 6: AI Composition Layer
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Phase 6: AI Composition Layer" :id "phase-6"
|
||
|
||
(p :class "text-stone-600 italic mb-4"
|
||
"The federated graph is simultaneously training data, retrieval context, and composition target.")
|
||
|
||
(~docs/subsection :title "6a. Component Retrieval"
|
||
(p "AI resolves components by CID, not by guessing. "
|
||
"The federation is a typed, tested, searchable context window.")
|
||
|
||
(~docs/code :src (highlight
|
||
";; AI composition query\n(ai-compose\n {:intent \"dashboard with sales chart and user table\"\n :constraints {:has-tests true\n :min-pins 10 ;; popularity signal\n :verified true} ;; blockchain-anchored\n :context (deref current-page)})\n\n;; AI doesn't generate from scratch — it assembles:\n;; 1. Search federation for matching components\n;; 2. Check tests pass for each candidate\n;; 3. Resolve dependencies\n;; 4. Compose into page, respecting :requires\n;; 5. Output is auditable: list of CIDs + composition logic"
|
||
"lisp"))
|
||
|
||
(p "The output is a composition of existing, tested, content-addressed components. "
|
||
"Not generated code — assembled code. Deterministic, verifiable, traceable."))
|
||
|
||
(~docs/subsection :title "6b. Self-Improving Tooling"
|
||
(p "AI tools are SX components published to sx-pub. "
|
||
"They improve as the network grows — more components to compose from, "
|
||
"more tests to validate against, more usage patterns to learn from.")
|
||
|
||
(p "The network effect works for both humans and AI:")
|
||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||
(li "Every component published is more material for AI composition")
|
||
(li "Every test attached is more validation for AI output")
|
||
(li "Every fork is a preference signal for AI ranking")
|
||
(li "Every pin is a popularity signal for AI retrieval")))
|
||
|
||
(~docs/subsection :title "6c. Deliverables"
|
||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||
(li (code "web/lib/ai-compose.sx") " — composition interface (~200 LOC)")
|
||
(li "Embedding index over component metadata + signatures")
|
||
(li "Retrieval API: search by intent, filter by quality signals")
|
||
(li "Composition validator: check output resolves, tests pass"))))
|
||
|
||
;; =====================================================================
|
||
;; Implementation Order
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Implementation Order" :id "order"
|
||
|
||
(~docs/code :src (highlight
|
||
"Phase 1 Browser Node Foundation ~400 LOC SX + ~100 LOC JS\n IndexedDB store, Service Worker, CID verification\n Prerequisite: none (extends existing sx-browser.js)\n\nPhase 2 Peer Network ~770 LOC SX + ~430 LOC Python/JS\n WebSocket + WebRTC + protocol + shared signals + rooms\n Prerequisite: Phase 1 (needs content store)\n\nPhase 3 In-Browser Authoring ~550 LOC SX\n Editor, test runner, publish flow\n Prerequisite: Phase 1 + 2 (needs store + peer connection)\n\nPhase 4 Component Discovery ~750 LOC SX\n Dependency resolution, component browser, search\n Prerequisite: Phase 2 (needs federation queries)\n\nPhase 5 Node Bootstrap ~300 LOC config + scripts\n Docker image, auto-bootstrap, seed protocol\n Prerequisite: Phase 1–4 (packages everything)\n\nPhase 6 AI Composition ~400 LOC SX + embedding index\n Retrieval, composition, validation\n Prerequisite: Phase 4 (needs searchable component graph)\n\n Total new SX: ~2650 LOC\n Total new platform: ~350 LOC (JS + Python)\n Timeline: Phases are independent workstreams after Phase 1"
|
||
"text"))
|
||
|
||
(p "Phase 1 is the foundation — everything else depends on the browser being able to "
|
||
"store, verify, and cache content-addressed SX. After that, phases 2–4 can proceed "
|
||
"in parallel. Phase 5 packages it all. Phase 6 builds on the populated network."))
|
||
|
||
;; =====================================================================
|
||
;; What Already Exists
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "What Already Exists (sx-pub Phases 1–4)" :id "existing"
|
||
|
||
(table :class "w-full mb-6 text-sm"
|
||
(thead
|
||
(tr (th :class "text-left p-2" "Capability") (th :class "text-left p-2" "Status") (th :class "text-left p-2" "Location")))
|
||
(tbody
|
||
(tr (td :class "p-2" "IPFS async client") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/utils/ipfs_client.py"))
|
||
(tr (td :class "p-2" "Merkle tree + proofs") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/utils/anchoring.py"))
|
||
(tr (td :class "p-2" "OpenTimestamps anchoring") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/utils/anchoring.py"))
|
||
(tr (td :class "p-2" "ActivityPub federation") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/infrastructure/activitypub.py"))
|
||
(tr (td :class "p-2" "sx-pub HTTP handlers") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "sx/sx/handlers/pub-api.sx"))
|
||
(tr (td :class "p-2" "Publishing + CID resolution") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "sx/sxc/pages/pub_helpers.py"))
|
||
(tr (td :class "p-2" "SX wire format (aser)") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "web/adapter-sx.sx"))
|
||
(tr (td :class "p-2" "Content store (in-memory)") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "lib/content.sx"))
|
||
(tr (td :class "p-2" "Component localStorage cache") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "web/boot.sx"))
|
||
(tr (td :class "p-2" "Docker dev environment") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "docker-compose.dev-pub.yml"))))
|
||
|
||
(p "The server-side infrastructure is complete. The plan above extends it to browsers."))))
|