Files
rose-ash/sx/sx/plans/sx-web.sx
giles 91d5de0554 sx-web plan: WebSocket + WebRTC peer-to-peer architecture
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>
2026-03-26 12:18:47 +00:00

385 lines
30 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
;; 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 14 (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 14 (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 24 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 14)" :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."))))