Rename all 1,169 components to path-based names with namespace support
Component names now reflect filesystem location using / as path separator and : as namespace separator for shared components: ~sx-header → ~layouts/header ~layout-app-body → ~shared:layout/app-body ~blog-admin-dashboard → ~admin/dashboard 209 files, 4,941 replacements across all services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
;; Content-Addressed Components
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~plan-content-addressed-components-content ()
|
||||
(~doc-page :title "Content-Addressed Components"
|
||||
(defcomp ~plans/content-addressed-components/plan-content-addressed-components-content ()
|
||||
(~docs/page :title "Content-Addressed Components"
|
||||
|
||||
(~doc-section :title "The Premise" :id "premise"
|
||||
(~docs/section :title "The Premise" :id "premise"
|
||||
(p "SX components are pure functions. Boundary enforcement guarantees it — a component cannot call IO primitives, make network requests, access cookies, or touch the filesystem. " (code "Component.is_pure") " is a structural property, verified at registration time by scanning the transitive closure of IO references via " (code "deps.sx") ".")
|
||||
(p "Pure functions have a remarkable property: " (strong "their identity is their content.") " Two components that produce the same serialized form are the same component, regardless of who wrote them or where they're hosted. This means we can content-address them — compute a cryptographic hash of the canonical serialized form, and that hash " (em "is") " the component's identity.")
|
||||
(p "Content addressing turns components into shared infrastructure. Define " (code "~card") " once, pin it to IPFS, and every SX application on the planet can use it by CID. No package registry, no npm install, no version conflicts. The CID " (em "is") " the version. The hash " (em "is") " the trust. Boundary enforcement " (em "is") " the sandbox.")
|
||||
@@ -15,7 +15,7 @@
|
||||
;; Current State
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Current State" :id "current-state"
|
||||
(~docs/section :title "Current State" :id "current-state"
|
||||
(p "What already exists and what's missing.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
@@ -62,28 +62,28 @@
|
||||
;; Canonical Serialization
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 1: Canonical Serialization" :id "canonical-serialization"
|
||||
(~docs/section :title "Phase 1: Canonical Serialization" :id "canonical-serialization"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "The foundation")
|
||||
(p :class "text-violet-800" "Same component must always produce the same bytes, regardless of original formatting, whitespace, or comment placement. Without this, content addressing is meaningless."))
|
||||
|
||||
(~doc-subsection :title "The Problem"
|
||||
(~docs/subsection :title "The Problem"
|
||||
(p "Currently " (code "serialize(body, pretty=True)") " produces readable SX source from the parsed AST. But serialization isn't fully canonical — it depends on the internal representation order, and there's no normalization pass. Two semantically identical components formatted differently would produce different hashes.")
|
||||
(p "We need a " (strong "canonical form") " that strips all variance:"))
|
||||
|
||||
(~doc-subsection :title "Canonical Form Rules"
|
||||
(~docs/subsection :title "Canonical Form Rules"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(li (strong "Strip comments.") " Comments are parsing artifacts, not part of the AST. The serializer already ignores them (it works from the parsed tree), but any future comment-preserving parser must not affect canonical output.")
|
||||
(li (strong "Normalize whitespace.") " Single space between tokens, newline before each top-level form in a body. No trailing whitespace. No blank lines.")
|
||||
(li (strong "Sort keyword arguments alphabetically.") " In component calls: " (code "(~card :class \"x\" :title \"y\")") " not " (code "(~card :title \"y\" :class \"x\")") ". In dict literals: " (code "{:a 1 :b 2}") " not " (code "{:b 2 :a 1}") ".")
|
||||
(li (strong "Normalize string escapes.") " Use " (code "\\n") " not literal newlines in strings. Escape only what must be escaped.")
|
||||
(li (strong "Normalize numbers.") " " (code "1.0") " not " (code "1.00") " or " (code "1.") ". " (code "42") " not " (code "042") ".")
|
||||
(li (strong "Include the full definition form.") " Hash the complete " (code "(defcomp ~name (params) body)") ", not just the body. The name and parameter signature are part of the component's identity.")))
|
||||
(li (strong "Include the full definition form.") " Hash the complete " (code "(defcomp ~plans/content-addressed-components/name (params) body)") ", not just the body. The name and parameter signature are part of the component's identity.")))
|
||||
|
||||
(~doc-subsection :title "Implementation"
|
||||
(~docs/subsection :title "Implementation"
|
||||
(p "New spec function in a " (code "canonical.sx") " module:")
|
||||
(~doc-code :code (highlight "(define canonical-serialize\n (fn (node)\n ;; Produce a canonical s-expression string from an AST node.\n ;; Deterministic: same AST always produces same output.\n ;; Used for CID computation — NOT for human-readable output.\n (case (type-of node)\n \"list\"\n (str \"(\" (join \" \" (map canonical-serialize node)) \")\")\n \"dict\"\n (let ((sorted-keys (sort (keys node))))\n (str \"{\" (join \" \"\n (map (fn (k)\n (str \":\" k \" \" (canonical-serialize (get node k))))\n sorted-keys)) \"}\"))\n \"string\"\n (str '\"' (escape-canonical node) '\"')\n \"number\"\n (canonical-number node)\n \"symbol\"\n (symbol-name node)\n \"keyword\"\n (str \":\" (keyword-name node))\n \"boolean\"\n (if node \"true\" \"false\")\n \"nil\"\n \"nil\")))" "lisp"))
|
||||
(~docs/code :code (highlight "(define canonical-serialize\n (fn (node)\n ;; Produce a canonical s-expression string from an AST node.\n ;; Deterministic: same AST always produces same output.\n ;; Used for CID computation — NOT for human-readable output.\n (case (type-of node)\n \"list\"\n (str \"(\" (join \" \" (map canonical-serialize node)) \")\")\n \"dict\"\n (let ((sorted-keys (sort (keys node))))\n (str \"{\" (join \" \"\n (map (fn (k)\n (str \":\" k \" \" (canonical-serialize (get node k))))\n sorted-keys)) \"}\"))\n \"string\"\n (str '\"' (escape-canonical node) '\"')\n \"number\"\n (canonical-number node)\n \"symbol\"\n (symbol-name node)\n \"keyword\"\n (str \":\" (keyword-name node))\n \"boolean\"\n (if node \"true\" \"false\")\n \"nil\"\n \"nil\")))" "lisp"))
|
||||
(p "This function must be bootstrapped to both Python and JS — the server computes CIDs at registration time, the client verifies them on fetch.")
|
||||
(p "The canonical serializer is distinct from " (code "serialize()") " for display. " (code "serialize(pretty=True)") " remains for human-readable output. " (code "canonical-serialize") " is for hashing only.")))
|
||||
|
||||
@@ -91,26 +91,26 @@
|
||||
;; CID Computation
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 2: CID Computation" :id "cid-computation"
|
||||
(~docs/section :title "Phase 2: CID Computation" :id "cid-computation"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Every component gets a stable, unique content identifier. Same source → same CID, always. Different source → different CID, always."))
|
||||
|
||||
(~doc-subsection :title "CID Format"
|
||||
(~docs/subsection :title "CID Format"
|
||||
(p "Use " (a :href "https://github.com/multiformats/cid" :class "text-violet-700 underline" "CIDv1") " with:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (strong "Hash function:") " SHA3-256 (already used by artdag for content addressing)")
|
||||
(li (strong "Codec:") " raw (the content is the canonical SX source bytes, not a DAG-PB wrapper)")
|
||||
(li (strong "Base encoding:") " base32lower for URL-safe representation (" (code "bafy...") " prefix)"))
|
||||
(~doc-code :code (highlight ";; CID computation pipeline\n(define component-cid\n (fn (component)\n ;; 1. Reconstruct full defcomp form\n ;; 2. Canonical serialize\n ;; 3. SHA3-256 hash\n ;; 4. Wrap as CIDv1\n (let ((source (canonical-serialize\n (list 'defcomp\n (symbol (str \"~\" (component-name component)))\n (component-params-list component)\n (component-body component)))))\n (cid-v1 :sha3-256 :raw (encode-utf8 source)))))" "lisp")))
|
||||
(~docs/code :code (highlight ";; CID computation pipeline\n(define component-cid\n (fn (component)\n ;; 1. Reconstruct full defcomp form\n ;; 2. Canonical serialize\n ;; 3. SHA3-256 hash\n ;; 4. Wrap as CIDv1\n (let ((source (canonical-serialize\n (list 'defcomp\n (symbol (str \"~\" (component-name component)))\n (component-params-list component)\n (component-body component)))))\n (cid-v1 :sha3-256 :raw (encode-utf8 source)))))" "lisp")))
|
||||
|
||||
(~doc-subsection :title "Where CIDs Live"
|
||||
(~docs/subsection :title "Where CIDs Live"
|
||||
(p "Each " (code "Component") " object gains a " (code "cid") " field, computed at registration time:")
|
||||
(~doc-code :code (highlight ";; types.py extension\n@dataclass\nclass Component:\n name: str\n params: list[str]\n has_children: bool\n body: Any\n closure: dict[str, Any]\n css_classes: set[str]\n deps: set[str] # by name\n io_refs: set[str]\n cid: str | None = None # computed after registration\n dep_cids: dict[str, str] | None = None # name → CID" "python"))
|
||||
(~docs/code :code (highlight ";; types.py extension\n@dataclass\nclass Component:\n name: str\n params: list[str]\n has_children: bool\n body: Any\n closure: dict[str, Any]\n css_classes: set[str]\n deps: set[str] # by name\n io_refs: set[str]\n cid: str | None = None # computed after registration\n dep_cids: dict[str, str] | None = None # name → CID" "python"))
|
||||
(p "After " (code "compute_all_deps()") " runs, a new " (code "compute_all_cids()") " pass fills in CIDs for every component. Dependency CIDs are also recorded — when a component references " (code "~card") ", we store both the name and card's CID."))
|
||||
|
||||
(~doc-subsection :title "CID Stability"
|
||||
(~docs/subsection :title "CID Stability"
|
||||
(p "A component's CID changes when and only when its " (strong "semantics") " change:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li "Reformatting the " (code ".sx") " source file → same AST → same canonical form → " (strong "same CID"))
|
||||
@@ -123,14 +123,14 @@
|
||||
;; Component Manifest
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 3: Component Manifest" :id "manifest"
|
||||
(~docs/section :title "Phase 3: Component Manifest" :id "manifest"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Metadata that travels with a CID — what a component needs, what it provides, whether it's safe to run. Enough information to resolve, validate, and render without fetching the source first."))
|
||||
|
||||
(~doc-subsection :title "Manifest Structure"
|
||||
(~doc-code :code (highlight ";; Component manifest — published alongside the source\n(SxComponent\n :name \"~product-card\"\n :cid \"bafy...productcard\"\n :source-bytes 847\n :params (:title :price :image-url)\n :has-children true\n :pure true\n :deps (\n {:name \"~card\" :cid \"bafy...card\"}\n {:name \"~price-tag\" :cid \"bafy...pricetag\"}\n {:name \"~lazy-image\" :cid \"bafy...lazyimg\"})\n :css-atoms (:border :rounded :p-4 :text-sm :font-bold\n :text-green-700 :line-through :text-stone-400)\n :author \"https://rose-ash.com/apps/market\"\n :published \"2026-03-06T14:30:00Z\")" "lisp"))
|
||||
(~docs/subsection :title "Manifest Structure"
|
||||
(~docs/code :code (highlight ";; Component manifest — published alongside the source\n(SxComponent\n :name \"~product-card\"\n :cid \"bafy...productcard\"\n :source-bytes 847\n :params (:title :price :image-url)\n :has-children true\n :pure true\n :deps (\n {:name \"~card\" :cid \"bafy...card\"}\n {:name \"~price-tag\" :cid \"bafy...pricetag\"}\n {:name \"~lazy-image\" :cid \"bafy...lazyimg\"})\n :css-atoms (:border :rounded :p-4 :text-sm :font-bold\n :text-green-700 :line-through :text-stone-400)\n :author \"https://rose-ash.com/apps/market\"\n :published \"2026-03-06T14:30:00Z\")" "lisp"))
|
||||
(p "Key fields:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (code ":cid") " — content address of the canonical serialized source")
|
||||
@@ -140,7 +140,7 @@
|
||||
(li (code ":params") " — parameter signature for tooling, documentation, IDE support")
|
||||
(li (code ":author") " — who published this. AP actor URL, verifiable via HTTP Signatures")))
|
||||
|
||||
(~doc-subsection :title "Manifest CID"
|
||||
(~docs/subsection :title "Manifest CID"
|
||||
(p "The manifest itself is content-addressed. But the manifest CID is " (em "not") " the component CID — they're separate objects. The component CID is derived from the source alone (pure content). The manifest CID includes metadata that could change (author, publication date) without changing the component.")
|
||||
(p "Resolution order: manifest CID → manifest → component CID → component source. Or shortcut: component CID → source directly, if you already know what you need.")))
|
||||
|
||||
@@ -148,13 +148,13 @@
|
||||
;; IPFS Storage & Resolution
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 4: IPFS Storage & Resolution" :id "ipfs"
|
||||
(~docs/section :title "Phase 4: IPFS Storage & Resolution" :id "ipfs"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Components live on IPFS. Any browser can fetch them by CID. No origin server needed. No CDN. No DNS. The content network IS the distribution network."))
|
||||
|
||||
(~doc-subsection :title "Server-Side: Publication"
|
||||
(~docs/subsection :title "Server-Side: Publication"
|
||||
(p "On component registration (startup or hot-reload), the server:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(li "Computes canonical form and CID")
|
||||
@@ -163,14 +163,14 @@
|
||||
(li "Creates/updates " (code "IPFSPin") " record with " (code "pin_type=\"component\""))
|
||||
(li "Publishes manifest to IPFS (separate CID)")
|
||||
(li "Optionally announces via AP outbox for federated discovery"))
|
||||
(~doc-code :code (highlight ";; IPFSPin usage for components\nIPFSPin(\n content_hash=\"sha3-256:abcdef...\",\n ipfs_cid=\"bafy...productcard\",\n pin_type=\"component\",\n source_type=\"market\", # which service defined it\n metadata={\n \"name\": \"~product-card\",\n \"manifest_cid\": \"bafy...manifest\",\n \"deps\": [\"bafy...card\", \"bafy...pricetag\"],\n \"pure\": True\n }\n)" "python")))
|
||||
(~docs/code :code (highlight ";; IPFSPin usage for components\nIPFSPin(\n content_hash=\"sha3-256:abcdef...\",\n ipfs_cid=\"bafy...productcard\",\n pin_type=\"component\",\n source_type=\"market\", # which service defined it\n metadata={\n \"name\": \"~product-card\",\n \"manifest_cid\": \"bafy...manifest\",\n \"deps\": [\"bafy...card\", \"bafy...pricetag\"],\n \"pure\": True\n }\n)" "python")))
|
||||
|
||||
(~doc-subsection :title "Client-Side: Resolution"
|
||||
(~docs/subsection :title "Client-Side: Resolution"
|
||||
(p "New spec module " (code "resolve.sx") " — the client-side component resolution pipeline:")
|
||||
(~doc-code :code (highlight "(define resolve-component-by-cid\n (fn (cid callback)\n ;; Resolution cascade:\n ;; 1. Check component env (already loaded?)\n ;; 2. Check localStorage (keyed by CID = cache-forever)\n ;; 3. Check origin server (/sx/components?cid=bafy...)\n ;; 4. Fetch from IPFS gateway\n ;; 5. Verify hash matches CID\n ;; 6. Parse, validate purity, register, callback\n (let ((cached (local-storage-get (str \"sx-cid:\" cid))))\n (if cached\n (do\n (register-component-source cached)\n (callback true))\n (fetch-component-by-cid cid\n (fn (source)\n (if (verify-cid cid source)\n (do\n (local-storage-set (str \"sx-cid:\" cid) source)\n (register-component-source source)\n (callback true))\n (do\n (log-warn (str \"sx:cid verification failed \" cid))\n (callback false)))))))))" "lisp"))
|
||||
(~docs/code :code (highlight "(define resolve-component-by-cid\n (fn (cid callback)\n ;; Resolution cascade:\n ;; 1. Check component env (already loaded?)\n ;; 2. Check localStorage (keyed by CID = cache-forever)\n ;; 3. Check origin server (/sx/components?cid=bafy...)\n ;; 4. Fetch from IPFS gateway\n ;; 5. Verify hash matches CID\n ;; 6. Parse, validate purity, register, callback\n (let ((cached (local-storage-get (str \"sx-cid:\" cid))))\n (if cached\n (do\n (register-component-source cached)\n (callback true))\n (fetch-component-by-cid cid\n (fn (source)\n (if (verify-cid cid source)\n (do\n (local-storage-set (str \"sx-cid:\" cid) source)\n (register-component-source source)\n (callback true))\n (do\n (log-warn (str \"sx:cid verification failed \" cid))\n (callback false)))))))))" "lisp"))
|
||||
(p "The cache-forever semantics are the key insight: because CIDs are content-addressed, a cached component " (strong "can never be stale") ". If the source changes, it gets a new CID. Old CIDs remain valid forever. There is no cache invalidation problem."))
|
||||
|
||||
(~doc-subsection :title "Resolution Cascade"
|
||||
(~docs/subsection :title "Resolution Cascade"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
@@ -210,22 +210,22 @@
|
||||
;; Security Model
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 5: Security Model" :id "security"
|
||||
(~docs/section :title "Phase 5: Security Model" :id "security"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "The hard part")
|
||||
(p :class "text-violet-800" "Loading code from the network is the web's original sin. Content-addressed components are safe because of three structural guarantees — not policies, not trust, not sandboxes that can be escaped."))
|
||||
|
||||
(~doc-subsection :title "Guarantee 1: Purity is Structural"
|
||||
(~docs/subsection :title "Guarantee 1: Purity is Structural"
|
||||
(p "SX boundary enforcement isn't a runtime sandbox — it's a registration-time structural check. When a component is loaded from IPFS and parsed, " (code "compute_all_io_refs()") " walks its entire AST and transitive dependencies. If " (em "any") " node references an IO primitive, the component is classified as IO-dependent and " (strong "rejected for untrusted registration."))
|
||||
(p "This means the evaluator literally doesn't have IO primitives in scope when running an IPFS-loaded component. It's not that we catch IO calls — the names don't resolve. There's nothing to catch.")
|
||||
(~doc-code :code (highlight "(define register-untrusted-component\n (fn (source origin)\n ;; Parse the defcomp from source\n ;; Run compute-all-io-refs on the parsed component\n ;; If io_refs is non-empty → REJECT\n ;; If pure → register in env with :origin metadata\n (let ((comp (parse-component source)))\n (if (not (component-pure? comp))\n (do\n (log-warn (str \"sx:reject IO component from \" origin))\n nil)\n (do\n (register-component comp)\n (log-info (str \"sx:registered \" (component-name comp)\n \" from \" origin))\n comp)))))" "lisp")))
|
||||
(~docs/code :code (highlight "(define register-untrusted-component\n (fn (source origin)\n ;; Parse the defcomp from source\n ;; Run compute-all-io-refs on the parsed component\n ;; If io_refs is non-empty → REJECT\n ;; If pure → register in env with :origin metadata\n (let ((comp (parse-component source)))\n (if (not (component-pure? comp))\n (do\n (log-warn (str \"sx:reject IO component from \" origin))\n nil)\n (do\n (register-component comp)\n (log-info (str \"sx:registered \" (component-name comp)\n \" from \" origin))\n comp)))))" "lisp")))
|
||||
|
||||
(~doc-subsection :title "Guarantee 2: Content Verification"
|
||||
(~docs/subsection :title "Guarantee 2: Content Verification"
|
||||
(p "The CID IS the hash. When you fetch " (code "bafy...abc") " from any source — IPFS gateway, origin server, peer — you hash the response and compare. If it doesn't match, you reject it. No MITM attack can alter the content without changing the CID.")
|
||||
(p "This is stronger than HTTPS. HTTPS trusts the certificate authority, the DNS resolver, and the server operator. Content addressing trusts " (em "mathematics") ". The hash either matches or it doesn't."))
|
||||
|
||||
(~doc-subsection :title "Guarantee 3: Evaluation Limits"
|
||||
(~docs/subsection :title "Guarantee 3: Evaluation Limits"
|
||||
(p "Pure doesn't mean terminating. A component could contain an infinite loop or exponential recursion. SX evaluators enforce step limits:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (strong "Max eval steps:") " configurable per context. Untrusted components get a lower limit than local ones.")
|
||||
@@ -233,7 +233,7 @@
|
||||
(li (strong "Max output size:") " prevents a component from producing gigabytes of DOM nodes."))
|
||||
(p "Exceeding any limit halts evaluation and returns an error node. The worst case is wasted CPU — never data exfiltration, never unauthorized IO."))
|
||||
|
||||
(~doc-subsection :title "Trust Tiers"
|
||||
(~docs/subsection :title "Trust Tiers"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
@@ -258,7 +258,7 @@
|
||||
(td :class "px-3 py-2 text-stone-700" "Pure only (IO rejected)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Strict limits"))))))
|
||||
|
||||
(~doc-subsection :title "What Can Go Wrong"
|
||||
(~docs/subsection :title "What Can Go Wrong"
|
||||
(p "Honest accounting of the attack surface:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (strong "Visual spoofing:") " A malicious component could render UI that looks like a login form. Mitigation: untrusted components render inside a visually distinct container with origin attribution.")
|
||||
@@ -271,49 +271,49 @@
|
||||
;; Wire Format & Prefetch Integration
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 6: Wire Format & Prefetch Integration" :id "wire-format"
|
||||
(~docs/section :title "Phase 6: Wire Format & Prefetch Integration" :id "wire-format"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Pages and SX responses reference components by CID. The prefetch system resolves them from the most efficient source. Components become location-independent."))
|
||||
|
||||
(~doc-subsection :title "CID References in Page Registry"
|
||||
(~docs/subsection :title "CID References in Page Registry"
|
||||
(p "The page registry (shipped to the client as " (code "<script type=\"text/sx-pages\">") ") currently lists deps by name. Extend to include CIDs:")
|
||||
(~doc-code :code (highlight "{:name \"docs-page\" :path \"/language/docs/<slug>\"\n :auth \"public\" :has-data false\n :deps ({:name \"~essay-foo\" :cid \"bafy...essay\"}\n {:name \"~doc-code\" :cid \"bafy...doccode\"})\n :content \"(case slug ...)\" :closure {}}" "lisp"))
|
||||
(~docs/code :code (highlight "{:name \"docs-page\" :path \"/language/docs/<slug>\"\n :auth \"public\" :has-data false\n :deps ({:name \"~essay-foo\" :cid \"bafy...essay\"}\n {:name \"~doc-code\" :cid \"bafy...doccode\"})\n :content \"(case slug ...)\" :closure {}}" "lisp"))
|
||||
(p "The " (a :href "/sx/(etc.(plan.predictive-prefetch))" :class "text-violet-700 underline" "predictive prefetch system") " uses these CIDs to fetch components from the resolution cascade rather than only from the origin server's " (code "/sx/components") " endpoint."))
|
||||
|
||||
(~doc-subsection :title "SX Response Component Headers"
|
||||
(~docs/subsection :title "SX Response Component Headers"
|
||||
(p "Currently, " (code "SX-Components") " header lists loaded component names. Extend to support CIDs:")
|
||||
(~doc-code :code (highlight "Request:\nSX-Components: ~card:bafy...card,~nav:bafy...nav\n\nResponse:\nSX-Component-CIDs: ~essay-foo:bafy...essay,~doc-code:bafy...doccode\n\n;; Response body only includes defs the client doesn't have\n(defcomp ~essay-foo ...)" "http"))
|
||||
(~docs/code :code (highlight "Request:\nSX-Components: ~card:bafy...card,~plans/environment-images/nav:bafy...nav\n\nResponse:\nSX-Component-CIDs: ~plans/content-addressed-components/essay-foo:bafy...essay,~docs/code:bafy...doccode\n\n;; Response body only includes defs the client doesn't have\n(defcomp ~plans/content-addressed-components/essay-foo ...)" "http"))
|
||||
(p "The client can then verify received components match their declared CIDs. If the origin server is compromised, CID verification catches the tampered response."))
|
||||
|
||||
(~doc-subsection :title "Federated Content"
|
||||
(~docs/subsection :title "Federated Content"
|
||||
(p "When an ActivityPub activity arrives with SX content, it declares component requirements by CID:")
|
||||
(~doc-code :code (highlight "(Create\n :actor \"https://other-instance.com/users/bob\"\n :object (Note\n :content (~product-card :title \"Bob's Widget\" :price 29.99)\n :requires (list\n {:name \"~product-card\" :cid \"bafy...prodcard\"}\n {:name \"~price-tag\" :cid \"bafy...pricetag\"})))" "lisp"))
|
||||
(~docs/code :code (highlight "(Create\n :actor \"https://other-instance.com/users/bob\"\n :object (Note\n :content (~product-card :title \"Bob's Widget\" :price 29.99)\n :requires (list\n {:name \"~product-card\" :cid \"bafy...prodcard\"}\n {:name \"~price-tag\" :cid \"bafy...pricetag\"})))" "lisp"))
|
||||
(p "The receiving browser resolves required components through the cascade. If Bob's instance is down, the components are still fetchable from IPFS. The content is self-describing and self-resolving.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Component Sharing & Discovery
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 7: Sharing & Discovery" :id "sharing"
|
||||
(~docs/section :title "Phase 7: Sharing & Discovery" :id "sharing"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Servers publish component collections via AP. Other servers follow them. Like npm, but federated, content-addressed, and structurally safe."))
|
||||
|
||||
(~doc-subsection :title "Component Registry as AP Actor"
|
||||
(~docs/subsection :title "Component Registry as AP Actor"
|
||||
(p "Each server exposes a component registry actor:")
|
||||
(~doc-code :code (highlight "(Service\n :id \"https://rose-ash.com/sx-registry\"\n :type \"SxComponentRegistry\"\n :name \"Rose Ash Components\"\n :outbox \"https://rose-ash.com/sx-registry/outbox\"\n :followers \"https://rose-ash.com/sx-registry/followers\")" "lisp"))
|
||||
(~docs/code :code (highlight "(Service\n :id \"https://rose-ash.com/sx-registry\"\n :type \"SxComponentRegistry\"\n :name \"Rose Ash Components\"\n :outbox \"https://rose-ash.com/sx-registry/outbox\"\n :followers \"https://rose-ash.com/sx-registry/followers\")" "lisp"))
|
||||
(p "Follow the registry to receive component updates. The outbox is a chronological feed of Create/Update/Delete activities for components. 'Update' means a new CID for the same name — consumers decide whether to adopt it."))
|
||||
|
||||
(~doc-subsection :title "Discovery Protocol"
|
||||
(~docs/subsection :title "Discovery Protocol"
|
||||
(p "Webfinger-style lookup for components by name:")
|
||||
(~doc-code :code (highlight "GET /.well-known/sx-component?name=~product-card\n\n{\n \"name\": \"~product-card\",\n \"cid\": \"bafy...prodcard\",\n \"manifest_cid\": \"bafy...manifest\",\n \"gateway\": \"https://rose-ash.com/ipfs/\",\n \"author\": \"https://rose-ash.com/apps/market\"\n}" "http"))
|
||||
(~docs/code :code (highlight "GET /.well-known/sx-component?name=~product-card\n\n{\n \"name\": \"~product-card\",\n \"cid\": \"bafy...prodcard\",\n \"manifest_cid\": \"bafy...manifest\",\n \"gateway\": \"https://rose-ash.com/ipfs/\",\n \"author\": \"https://rose-ash.com/apps/market\"\n}" "http"))
|
||||
(p "This is an optional convenience — any consumer that knows the CID can skip discovery and fetch directly from IPFS. Discovery answers the question: " (em "\"what's the current version of ~product-card on rose-ash.com?\""))
|
||||
)
|
||||
|
||||
(~doc-subsection :title "Name Resolution"
|
||||
(~docs/subsection :title "Name Resolution"
|
||||
(p "Names are human-friendly aliases for CIDs. The same name on different servers can refer to different components (different CIDs). Conflict resolution is simple:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (strong "Local wins:") " If the server defines " (code "~card") ", that definition takes precedence over any federated " (code "~card") ".")
|
||||
@@ -324,7 +324,7 @@
|
||||
;; Spec modules
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Spec Modules" :id "spec-modules"
|
||||
(~docs/section :title "Spec Modules" :id "spec-modules"
|
||||
(p "Per the SX host architecture principle, all content-addressing logic is specced in " (code ".sx") " files and bootstrapped:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
@@ -351,7 +351,7 @@
|
||||
;; Critical files
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Critical Files" :id "critical-files"
|
||||
(~docs/section :title "Critical Files" :id "critical-files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
@@ -404,7 +404,7 @@
|
||||
;; Relationship
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Relationships" :id "relationships"
|
||||
(~docs/section :title "Relationships" :id "relationships"
|
||||
(p "This plan is the foundation for several other plans and roadmaps:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (a :href "/sx/(etc.(plan.sx-activity))" :class "text-violet-700 underline" "SX-Activity") " Phase 2 (content-addressed components on IPFS) is a summary of this plan. This plan supersedes that section with full detail.")
|
||||
|
||||
Reference in New Issue
Block a user