diff --git a/sx/sx/nav-data.sx b/sx/sx/nav-data.sx index 537d021..874ac09 100644 --- a/sx/sx/nav-data.sx +++ b/sx/sx/nav-data.sx @@ -114,7 +114,9 @@ (dict :label "SX-Activity" :href "/plans/sx-activity" :summary "A new web built on SX — executable content, shared components, parsers, and logic on IPFS, provenance on Bitcoin, all running within your own security context.") (dict :label "Predictive Prefetching" :href "/plans/predictive-prefetch" - :summary "Prefetch missing component definitions before the user clicks — hover a link, fetch its deps, navigate client-side."))) + :summary "Prefetch missing component definitions before the user clicks — hover a link, fetch its deps, navigate client-side.") + (dict :label "Content-Addressed Components" :href "/plans/content-addressed-components" + :summary "Components identified by CID, stored on IPFS, fetched from anywhere. Canonical serialization, content verification, federated sharing."))) (define bootstrappers-nav-items (list (dict :label "Overview" :href "/bootstrappers/") diff --git a/sx/sx/plans.sx b/sx/sx/plans.sx index a31d6b4..8f6126a 100644 --- a/sx/sx/plans.sx +++ b/sx/sx/plans.sx @@ -594,6 +594,422 @@ (td :class "px-3 py-2 text-stone-700" "Content addressing — shared with component CIDs") (td :class "px-3 py-2 text-stone-600" "2, 3")))))))) +;; --------------------------------------------------------------------------- +;; Content-Addressed Components +;; --------------------------------------------------------------------------- + +(defcomp ~plan-content-addressed-components-content () + (~doc-page :title "Content-Addressed Components" + + (~doc-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.") + (p "This plan details how to get from the current name-based, per-server component model to a content-addressed, globally-shared one.")) + + ;; ----------------------------------------------------------------------- + ;; Current State + ;; ----------------------------------------------------------------------- + + (~doc-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" + (table :class "w-full text-left text-sm" + (thead (tr :class "border-b border-stone-200 bg-stone-100" + (th :class "px-3 py-2 font-medium text-stone-600" "Capability") + (th :class "px-3 py-2 font-medium text-stone-600" "Status") + (th :class "px-3 py-2 font-medium text-stone-600" "Where"))) + (tbody + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Deterministic serialization") + (td :class "px-3 py-2 text-stone-700" "Partial — " (code "serialize(body, pretty=True)") " from AST, but no canonical normalization") + (td :class "px-3 py-2 font-mono text-sm text-violet-700" "parser.py:296-427")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Component identity") + (td :class "px-3 py-2 text-stone-700" "By name (" (code "~card") ") — names are mutable, server-local") + (td :class "px-3 py-2 font-mono text-sm text-violet-700" "types.py:157-180")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Bundle hashing") + (td :class "px-3 py-2 text-stone-700" "SHA256 of all defs concatenated — per-bundle, not per-component") + (td :class "px-3 py-2 font-mono text-sm text-violet-700" "jinja_bridge.py:60-86")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Purity verification") + (td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete") " — " (code "is_pure") " via transitive IO ref analysis") + (td :class "px-3 py-2 font-mono text-sm text-violet-700" "deps.sx, boundary.py")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Dependency graph") + (td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete") " — " (code "Component.deps") " transitive closure") + (td :class "px-3 py-2 font-mono text-sm text-violet-700" "deps.sx")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "IPFS infrastructure") + (td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Exists") " — IPFSPin model, async upload tasks, CID tracking") + (td :class "px-3 py-2 font-mono text-sm text-violet-700" "models/federation.py, artdag/l1/tasks/")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Client component caching") + (td :class "px-3 py-2 text-stone-700" "Hash-based localStorage — but keyed by bundle hash, not individual CID") + (td :class "px-3 py-2 font-mono text-sm text-violet-700" "boot.sx, helpers.py")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Content-addressed components") + (td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not yet") " — no per-component CID, no IPFS resolution") + (td :class "px-3 py-2 text-stone-600" "—")))))) + + ;; ----------------------------------------------------------------------- + ;; Canonical Serialization + ;; ----------------------------------------------------------------------- + + (~doc-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" + (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" + (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."))) + + (~doc-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")) + (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."))) + + ;; ----------------------------------------------------------------------- + ;; CID Computation + ;; ----------------------------------------------------------------------- + + (~doc-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" + (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"))) + + (~doc-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")) + (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" + (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")) + (li "Adding a comment → stripped by parser → same AST → " (strong "same CID")) + (li "Changing a class name in the body → different AST → " (strong "different CID")) + (li "Renaming the component → different defcomp form → " (strong "different CID") " (name is part of identity)")) + (p "This means CIDs are " (em "immutable versions") ". There's no " (code "~card@1.2.3") " — there's " (code "~card") " at CID " (code "bafy...abc") " and " (code "~card") " at CID " (code "bafy...def") ". The name is a human-friendly alias; the CID is the truth."))) + + ;; ----------------------------------------------------------------------- + ;; Component Manifest + ;; ----------------------------------------------------------------------- + + (~doc-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")) + (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") + (li (code ":deps") " — dependency CIDs, not just names. A consumer can recursively resolve the entire tree by CID without name ambiguity") + (li (code ":pure") " — pre-computed purity flag. The consumer " (em "re-verifies") " this after fetching (never trust the manifest alone), but it enables fast rejection of IO-dependent components before downloading") + (li (code ":css-atoms") " — CSSX class names the component uses. The consumer can pre-resolve CSS rules without parsing the source") + (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" + (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."))) + + ;; ----------------------------------------------------------------------- + ;; IPFS Storage & Resolution + ;; ----------------------------------------------------------------------- + + (~doc-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" + (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") + (li "Checks " (code "IPFSPin") " — if CID already pinned, skip (content can't have changed)") + (li "Pins canonical source to IPFS (async Celery task, same pattern as artdag)") + (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"))) + + (~doc-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")) + (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" + (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" + (th :class "px-3 py-2 font-medium text-stone-600" "Layer") + (th :class "px-3 py-2 font-medium text-stone-600" "Lookup") + (th :class "px-3 py-2 font-medium text-stone-600" "Latency") + (th :class "px-3 py-2 font-medium text-stone-600" "When"))) + (tbody + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "1. Component env") + (td :class "px-3 py-2 font-mono text-sm text-stone-700" "(env-has? env cid)") + (td :class "px-3 py-2 text-stone-600" "0ms") + (td :class "px-3 py-2 text-stone-600" "Already loaded this session")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "2. localStorage") + (td :class "px-3 py-2 font-mono text-sm text-stone-700" "localStorage[\"sx-cid:\" + cid]") + (td :class "px-3 py-2 text-stone-600" "<1ms") + (td :class "px-3 py-2 text-stone-600" "Previously fetched, persists across sessions")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "3. Origin server") + (td :class "px-3 py-2 font-mono text-sm text-stone-700" "GET /sx/components?cid=bafy...") + (td :class "px-3 py-2 text-stone-600" "~20ms") + (td :class "px-3 py-2 text-stone-600" "Same-origin component, not yet cached")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "4. IPFS gateway") + (td :class "px-3 py-2 font-mono text-sm text-stone-700" "GET https://gateway/ipfs/{cid}") + (td :class "px-3 py-2 text-stone-600" "~200ms") + (td :class "px-3 py-2 text-stone-600" "Foreign component, federated content")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "5. Local IPFS node") + (td :class "px-3 py-2 font-mono text-sm text-stone-700" "ipfs cat {cid}") + (td :class "px-3 py-2 text-stone-600" "~5ms") + (td :class "px-3 py-2 text-stone-600" "User runs own IPFS node (power users)"))))) + (p "Layer 5 is optional — checked between 2 and 3 if " (code "window.ipfs") " or a local gateway is detected. For most users, layers 1-4 cover all cases."))) + + ;; ----------------------------------------------------------------------- + ;; Security Model + ;; ----------------------------------------------------------------------- + + (~doc-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" + (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"))) + + (~doc-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" + (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.") + (li (strong "Max recursion depth:") " prevents stack exhaustion.") + (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" + (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" + (th :class "px-3 py-2 font-medium text-stone-600" "Tier") + (th :class "px-3 py-2 font-medium text-stone-600" "Source") + (th :class "px-3 py-2 font-medium text-stone-600" "Allowed") + (th :class "px-3 py-2 font-medium text-stone-600" "Eval limits"))) + (tbody + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 font-semibold text-stone-800" "Local") + (td :class "px-3 py-2 text-stone-700" "Server's own " (code ".sx") " files") + (td :class "px-3 py-2 text-stone-700" "Pure + IO primitives + page helpers") + (td :class "px-3 py-2 text-stone-600" "None (trusted)")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 font-semibold text-stone-800" "Followed") + (td :class "px-3 py-2 text-stone-700" "Components from followed AP actors") + (td :class "px-3 py-2 text-stone-700" "Pure only (IO rejected)") + (td :class "px-3 py-2 text-stone-600" "Standard limits")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 font-semibold text-stone-800" "Federated") + (td :class "px-3 py-2 text-stone-700" "Components from any IPFS source") + (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" + (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.") + (li (strong "CSS abuse:") " A component's CSS atoms could interfere with page layout. Mitigation: scoped CSS — untrusted components' classes are namespaced.") + (li (strong "Resource exhaustion:") " A component could be expensive to evaluate. Mitigation: step limits, timeout, lazy rendering for off-screen components.") + (li (strong "Privacy leak via CSS:") " Background-image URLs could phone home. Mitigation: CSP restrictions on untrusted component rendering contexts.") + (li (strong "Dependency confusion:") " A malicious manifest could claim deps that are different components with the same name. Mitigation: deps are referenced by CID, not name. Name is informational only.")))) + + ;; ----------------------------------------------------------------------- + ;; Wire Format & Prefetch Integration + ;; ----------------------------------------------------------------------- + + (~doc-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" + (p "The page registry (shipped to the client as " (code "