(defcomp ~applications/sxtp/content () (~docs/page :title "SXTP Protocol" (~docs/section :title "Overview" :id "overview" (p "SXTP — SX Transfer Protocol — is HTTP reimagined where the wire format " (em "is") " the language. Requests, responses, headers, cookies, status conditions, and bodies are all s-expressions. There is no text framing, no content-type negotiation, no URL query-string encoding.") (p "Design principles:") (ul :class "list-disc list-inside space-y-2 mt-2" (li (strong "SX all the way") " — every datum on the wire is a valid SX value") (li (strong "Open verb set") " — any symbol is a legal verb, not just GET/POST/PUT/DELETE") (li (strong "Structured metadata") " — headers and cookies are dicts, not flat strings") (li (strong "Capability-scoped") " — requests declare required capabilities") (li (strong "Content-addressed") " — responses can be cached by hash") (li (strong "Streamable") " — chunked responses are sequences of expressions"))) (~docs/section :title "Requests" :id "requests" (p "A request is a list beginning with the symbol " (code "request") ". All fields are keyword arguments.") (~docs/code :src (highlight "(request :verb navigate :path \"/\")" "lisp")) (p "Full request with all fields:") (~docs/code :src (highlight "(request\n :verb navigate\n :path \"/geography/capabilities\"\n :headers {:accept \"text/sx\" :language \"en\"}\n :cookies {:session \"tok_abc123\" :prefs {:theme \"dark\"}}\n :params {:page 1 :per-page 20}\n :capabilities (fetch query)\n :body nil)" "lisp")) (div :class "overflow-x-auto rounded border border-stone-200 mt-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" "Field") (th :class "px-3 py-2 font-medium text-stone-600" "Description"))) (tbody (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" ":verb") (td :class "px-3 py-2 text-stone-600" "Symbol — the action to perform (required)")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" ":path") (td :class "px-3 py-2 text-stone-600" "String — resource path (required)")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" ":headers") (td :class "px-3 py-2 text-stone-600" "Dict — structured request metadata")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" ":cookies") (td :class "px-3 py-2 text-stone-600" "Dict — client state, values can be any SX type")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" ":params") (td :class "px-3 py-2 text-stone-600" "Dict — query parameters as typed values")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" ":capabilities") (td :class "px-3 py-2 text-stone-600" "List — capabilities this request requires")) (tr (td :class "px-3 py-2 text-stone-700 font-mono" ":body") (td :class "px-3 py-2 text-stone-600" "Any SX value — request payload")))))) (~docs/section :title "Responses" :id "responses" (p "A response is a list beginning with the symbol " (code "response") ".") (~docs/code :src (highlight "(response :status ok\n :headers {:content-type \"text/sx\" :cache :immutable}\n :set-cookie {:session {:value \"tok_xyz\" :max-age 3600 :path \"/\"}}\n :body (page :title \"Home\" (h1 \"Welcome\")))" "lisp")) (p "The body isn't serialized HTML that needs parsing — it's a live component tree the browser evaluates directly.")) (~docs/section :title "Verbs" :id "verbs" (p "Unlike HTTP's fixed set, any symbol is a valid verb. Convention defines common verbs; domains add their own.") (div :class "overflow-x-auto rounded border border-stone-200 mt-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" "Verb") (th :class "px-3 py-2 font-medium text-stone-600" "Purpose"))) (tbody (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" "navigate") (td :class "px-3 py-2 text-stone-600" "Retrieve a page for display — analogous to GET for documents")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" "fetch") (td :class "px-3 py-2 text-stone-600" "Retrieve data — analogous to GET for APIs")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" "query") (td :class "px-3 py-2 text-stone-600" "Structured query — body contains a query expression")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" "mutate") (td :class "px-3 py-2 text-stone-600" "Change state — analogous to POST/PUT/PATCH")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" "create") (td :class "px-3 py-2 text-stone-600" "Create a new resource")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" "delete") (td :class "px-3 py-2 text-stone-600" "Remove a resource")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" "subscribe") (td :class "px-3 py-2 text-stone-600" "Open a streaming channel for real-time updates")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700 font-mono" "inspect") (td :class "px-3 py-2 text-stone-600" "Retrieve metadata about a resource (capabilities, schema)")) (tr (td :class "px-3 py-2 text-stone-700 font-mono" "ping") (td :class "px-3 py-2 text-stone-600" "Liveness check"))))) (p :class "mt-4" "Domains define their own verbs freely:") (~docs/code :src (highlight "(request :verb publish :path \"/blog/draft-123\")\n(request :verb checkout :path \"/cart\")\n(request :verb render :path \"/artdag/node/abc\" :params {:format \"png\"})\n(request :verb federate :path \"/outbox\" :body (activity ...))" "lisp"))) (~docs/section :title "What HTTP got wrong" :id "http-comparison" (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" (th :class "px-3 py-2 font-medium text-stone-600" "HTTP pain") (th :class "px-3 py-2 font-medium text-stone-600" "SXTP answer"))) (tbody (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700" "Fixed verb set (GET/POST/PUT/DELETE)") (td :class "px-3 py-2 text-stone-600" "Any symbol is a verb")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700" "Headers are flat string pairs") (td :class "px-3 py-2 text-stone-600" "Headers are dicts — nested, typed")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700" "Cookies are encoded strings") (td :class "px-3 py-2 text-stone-600" "Cookies are SX values")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700" "Body requires content-type negotiation") (td :class "px-3 py-2 text-stone-600" "Body is always SX — rendering is the client's job")) (tr :class "border-b border-stone-100" (td :class "px-3 py-2 text-stone-700" "URL query strings (?a=1&b=2)") (td :class "px-3 py-2 text-stone-600" "Params are part of the request expression")) (tr (td :class "px-3 py-2 text-stone-700" "Separate mechanisms for streaming") (td :class "px-3 py-2 text-stone-600" "Streaming is just :stream true + chunk sequences")))))) (~docs/section :title "Status and conditions" :id "status" (p "Status is a symbol, not a number. Conditions replace error codes with structured, informative values.") (~docs/code :src (highlight "(response :status not-found\n :body (condition :type resource-not-found\n :path \"/blog/nonexistent\"\n :message \"No such post\"\n :retry false))" "lisp")) (p "Conditions are extensible — domains define their own:") (~docs/code :src (highlight "(condition :type payment-declined\n :reason :insufficient-funds\n :provider \"sumup\")" "lisp"))) (~docs/section :title "Streaming" :id "streaming" (p "A streaming response sets " (code ":stream true") ". The body becomes a sequence of chunk expressions.") (~docs/code :src (highlight ";; Ordered chunks\n(response :status ok :stream true)\n(chunk :seq 0 :body (tr (td \"Row 1\") (td \"data\")))\n(chunk :seq 1 :body (tr (td \"Row 2\") (td \"data\")))\n(chunk :done true)\n\n;; Server-sent events via subscribe\n(request :verb subscribe :path \"/events/live\")\n\n(event :type new-event :id \"evt-42\"\n :body (div :class \"event-card\" (h3 \"Jazz Night\")))\n(event :type update :id \"evt-42\"\n :body {:attendees 51})\n(event :type heartbeat :time 1711612800)" "lisp"))) (~docs/section :title "Capabilities" :id "capabilities" (p "Requests declare the capabilities they need. The server checks these against the session's granted capabilities. Insufficient capabilities produce " (code "(response :status forbidden)") ".") (~docs/code :src (highlight ";; Client declares\n(request :verb query :path \"/events\"\n :capabilities (fetch db:read))\n\n;; Server grants on auth\n(response :status ok\n :set-cookie {:capabilities {:value (fetch query db:read mutate)\n :max-age 86400\n :secure true}})" "lisp")) (p "Inspect what a resource requires:") (~docs/code :src (highlight "(request :verb inspect :path \"/cart/checkout\")\n\n(response :status ok\n :body {:required-capabilities (mutate cart:checkout)\n :available-verbs (inspect mutate)\n :params-schema {:shipping-address \"dict\"\n :payment-method \"symbol\"}})" "lisp"))) (~docs/section :title "Caching" :id "caching" (p "Content-addressed caching. The response hash " (em "is") " the cache key. No ETags, no Last-Modified — just SX content hashes.") (~docs/code :src (highlight ";; Server provides hash\n(response :status ok\n :headers {:content-hash \"sha3-abc123...\"\n :cache :immutable}\n :body ...)\n\n;; Client validates\n(request :verb fetch :path \"/geography/capabilities\"\n :headers {:if-match \"sha3-abc123...\"})\n\n(response :status not-modified)" "lisp")) (p "Three cache policies: " (code ":immutable") " (content-addressed, never changes), " (code ":revalidate") " (check hash before using), " (code ":none") " (dynamic content).")) (~docs/section :title "Wire format" :id "wire-format" (p "On the wire, each message is a length-prefixed SX expression. Length is a decimal integer as ASCII, followed by newline. The SX expression is UTF-8 encoded.") (~docs/code :src (highlight "43\n(request :verb ping :path \"/\" :body nil)" "text")) (p "Connections are persistent — multiple request/response pairs on the same connection. Pipelining is allowed. TLS is the transport security layer: " (code "sxtp://") " is plaintext (port 5380), " (code "sxtps://") " is TLS (port 5381).")) (~docs/section :title "URI scheme" :id "uri" (p "The browser translates URIs into request expressions:") (~docs/code :src (highlight "sxtps://blog.rose-ash.com/geography/capabilities\n\n;; becomes\n\n(request :verb navigate\n :path \"/geography/capabilities\"\n :headers {:host \"blog.rose-ash.com\"})" "lisp"))) (~docs/section :title "Examples" :id "examples" (p "Page navigation:") (~docs/code :src (highlight "(request :verb navigate :path \"/geography/capabilities\"\n :headers {:host \"sx.rose-ash.com\" :accept \"text/sx\"})\n\n(response :status ok\n :headers {:content-type \"text/sx\"\n :content-hash \"sha3-9f2a...\"}\n :body (page :title \"Capabilities\"\n (h1 \"Geography Capabilities\")\n (~capability-list :domain \"geography\")))" "lisp")) (p "Structured query:") (~docs/code :src (highlight "(request :verb query :path \"/events\"\n :capabilities (fetch db:read)\n :params {:after \"2026-03-01\" :limit 10}\n :body (filter (events) (fn (e) (> (:attendees e) 50))))\n\n(response :status ok\n :headers {:cache :revalidate}\n :body ((event :id \"evt-42\" :title \"Jazz Night\" :attendees 87)\n (event :id \"evt-55\" :title \"Art Walk\" :attendees 120)))" "lisp")) (p "Creating a resource:") (~docs/code :src (highlight "(request :verb create :path \"/blog/posts\"\n :capabilities (mutate blog:publish)\n :cookies {:session \"tok_abc123\"}\n :body {:title \"SXTP Protocol\"\n :body (article (h1 \"SXTP\") (p \"Everything is SX.\"))\n :tags (\"protocol\" \"sx\" \"web\")})\n\n(response :status created\n :headers {:location \"/blog/posts/sxtp-protocol\"\n :content-hash \"sha3-ff01...\"}\n :body {:id \"post-789\"\n :path \"/blog/posts/sxtp-protocol\"\n :created-at 1711612800})" "lisp"))) (~docs/section :title "Specification" :id "spec" (p "The formal specification lives in " (code "applications/sxtp/spec.sx") " — a self-describing SX file where the field definitions are themselves SX data structures that the protocol can introspect."))))