more plans
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 12m0s

This commit is contained in:
2026-03-09 18:07:23 +00:00
parent ec1093d372
commit 31a6e708fc
27 changed files with 1670 additions and 15 deletions

View File

@@ -1,11 +1,19 @@
;; Docs page content — fully self-contained, no Python intermediaries
(defcomp ~sx-home-content ()
(div :id "main-content"
(~sx-hero (highlight "(div :class \"p-4 bg-white rounded shadow\"\n (h1 :class \"text-2xl font-bold\" \"Hello\")\n (button :sx-get \"/api/data\"\n :sx-target \"#result\"\n \"Load data\"))" "lisp"))
(~sx-philosophy)
(~sx-how-it-works)
(~sx-credits)))
(div :id "main-content" :class "max-w-3xl mx-auto px-4 py-6"
(highlight "(defcomp ~sx-header ()
(a :href \"/\"
:sx-get \"/\" :sx-target \"#main-panel\"
:sx-select \"#main-panel\"
:sx-swap \"outerHTML\" :sx-push-url \"true\"
:class \"block max-w-3xl mx-auto px-4 pt-8 pb-4 text-center no-underline\"
(span :class \"text-4xl font-bold font-mono text-violet-700 block mb-2\"
\"(<sx>)\")
(p :class \"text-lg text-stone-500 mb-1\"
\"Framework free reactive hypermedia\")
(p :class \"text-xs text-stone-400\"
\"© Giles Bradshaw 2026\")))" "lisp")))
(defcomp ~docs-introduction-content ()
(~doc-page :title "Introduction"

View File

@@ -11,12 +11,11 @@
;; Logo + tagline + copyright — always shown at top of page area.
(defcomp ~sx-header ()
(div :class "max-w-3xl mx-auto px-4 pt-8 pb-4 text-center"
(a :href "/"
:sx-get "/" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "block mb-2"
(span :class "text-4xl font-bold font-mono text-violet-700" "(<sx>)"))
(a :href "/"
:sx-get "/" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "block max-w-3xl mx-auto px-4 pt-8 pb-4 text-center no-underline"
(span :class "text-4xl font-bold font-mono text-violet-700 block mb-2" "(<sx>)")
(p :class "text-lg text-stone-500 mb-1"
"Framework free reactive hypermedia")
(p :class "text-xs text-stone-400"
@@ -70,7 +69,7 @@
;; Used by every defpage :content to embed nav inside the page content area.
;; ---------------------------------------------------------------------------
(defcomp ~sx-doc (&key path &rest children)
(defcomp ~sx-doc (&key path &rest children) :affinity :server
(let ((nav-state (resolve-nav-path sx-nav-tree (or path "/"))))
(<>
(div :id "sx-nav" :class "mb-6"

View File

@@ -188,7 +188,13 @@
(dict :label "Live Streaming" :href "/plans/live-streaming"
:summary "SSE and WebSocket transports for re-resolving suspense slots after initial page load — live data, real-time collaboration.")
(dict :label "sx-web Platform" :href "/plans/sx-web-platform"
:summary "sx-web.org as online development platform — embedded Claude Code, IPFS storage, sx-activity publishing, sx-ci testing. Author, stage, test, deploy from the browser.")))
:summary "sx-web.org as online development platform — embedded Claude Code, IPFS storage, sx-activity publishing, sx-ci testing. Author, stage, test, deploy from the browser.")
(dict :label "sx-forge" :href "/plans/sx-forge"
:summary "Git forge in SX — repositories, issues, pull requests, CI, permissions, and federation. Configuration as macros, diffs as components.")
(dict :label "sx-swarm" :href "/plans/sx-swarm"
:summary "Container orchestration in SX — service definitions, environment macros, deploy pipelines. Replace YAML with a real language.")
(dict :label "sx-proxy" :href "/plans/sx-proxy"
:summary "Reverse proxy in SX — routes, TLS, middleware chains, load balancing. Macros generate config from the same service definitions as the orchestrator.")))
(define reactive-islands-nav-items (list
(dict :label "Overview" :href "/reactive-islands/"

157
sx/sx/plans/sx-forge.sx Normal file
View File

@@ -0,0 +1,157 @@
;; sx-forge — SX-based Git Forge
;; Plan: a Gitea/Forgejo-style git hosting platform where everything —
;; repositories, issues, pull requests, CI, permissions — is SX.
(defcomp ~plan-sx-forge-content ()
(~doc-page :title "sx-forge: Git Forge in SX"
(~doc-section :title "Vision" :id "vision"
(p "A git forge where the entire interface, configuration, and automation layer "
"is written in SX. Repositories are browsed, issues are filed, pull requests "
"are reviewed, and CI pipelines are triggered — all through SX components "
"rendered via the same hypermedia pipeline as every other SX app.")
(p "Configuration is SX. Webhooks are SX. Access control policies are SX macros "
"that expand to permission checks. Repository templates are defcomps. "
"The forge doesn't use SX — it " (em "is") " SX."))
(~doc-section :title "Why" :id "why"
(p "Gitea/Forgejo are excellent but they're Go binaries with YAML/INI config, "
"Markdown rendering, and a template engine that's separate from the application logic. "
"Every layer speaks a different language.")
(p "sx-forge collapses these layers:")
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li "Repository browsing = SX components rendering git tree objects")
(li "Issue tracking = SX forms with sx-post, stored as content-addressed SX documents")
(li "Pull requests = SX diff viewer + sx-activity for review comments")
(li "CI integration = sx-ci pipelines triggered by push hooks")
(li "Configuration = SX s-expressions, manipulated by macros")
(li "Access control = SX macros that expand to permission predicates")
(li "API = SX wire format (text/sx) alongside JSON for compatibility")))
(~doc-section :title "Architecture" :id "architecture"
(div :class "overflow-x-auto mt-4"
(table :class "w-full text-sm text-left"
(thead
(tr :class "border-b border-stone-200"
(th :class "py-2 px-3 font-semibold text-stone-700" "Layer")
(th :class "py-2 px-3 font-semibold text-stone-700" "Implementation")
(th :class "py-2 px-3 font-semibold text-stone-700" "Notes")))
(tbody :class "text-stone-600"
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Git backend")
(td :class "py-2 px-3" "libgit2 or shell-out to git")
(td :class "py-2 px-3" "Smart HTTP + SSH protocols. Bare repos on disk."))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "UI")
(td :class "py-2 px-3" "SX components (defcomp)")
(td :class "py-2 px-3" "Tree browser, diff viewer, blame, commit log — all defcomps."))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Issues / PRs")
(td :class "py-2 px-3" "SX documents on IPFS")
(td :class "py-2 px-3" "Content-addressed. Federated via sx-activity."))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "CI")
(td :class "py-2 px-3" "sx-ci pipelines")
(td :class "py-2 px-3" "Push hook triggers pipeline. Results as SX components."))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Auth")
(td :class "py-2 px-3" "OAuth2 + SX policy macros")
(td :class "py-2 px-3" "Permissions are macro-expanded predicates."))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Config")
(td :class "py-2 px-3" "SX s-expressions")
(td :class "py-2 px-3" "forge.sx per-instance. repo.sx per-repo."))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Federation")
(td :class "py-2 px-3" "sx-activity (ActivityPub)")
(td :class "py-2 px-3" "Cross-instance PRs, issues, stars, forks."))))))
(~doc-section :title "Configuration as SX" :id "config"
(p "Instance configuration is an SX file, not YAML or INI:")
(highlight "(define forge-config
{:name \"Rose Ash Forge\"
:domain \"forge.rose-ash.com\"
:ssh-port 2222
:storage {:backend :filesystem :root \"/data/repos\"}
:auth {:provider :oauth2
:issuer \"https://account.rose-ash.com\"}
:federation {:enabled true
:allowlist (list \"*.rose-ash.com\")}
:ci {:runner :sx-ci
:default-pipeline \"ci/default.sx\"}})" "lisp")
(p "Macros transform configuration:")
(highlight ";; Macro: generate mirror config from upstream
(defmacro mirror-repo (name upstream)
`(define-repo ,name
{:mirror true
:upstream ,upstream
:sync-interval 3600
:ci false}))" "lisp")
(p "Per-repository configuration lives in " (code "repo.sx") " at the repo root:")
(highlight "(define repo-config
{:default-branch \"main\"
:ci (pipeline
(stage :test (sx-ci/run \"test.sx\"))
(stage :deploy
(when-branch \"main\"
(sx-ci/deploy :target :production))))
:permissions
{:push (or (role? :maintainer) (role? :admin))
:merge-pr (and (ci-passed?) (approved-by? 1))
:admin (role? :admin)}})" "lisp"))
(~doc-section :title "SX Diff Viewer" :id "diff-viewer"
(p "Diffs rendered as SX components, not pre-formatted text:")
(highlight ";; The diff viewer is a defcomp, composable like any other
(defcomp ~diff-view (&key diff)
(map (fn (hunk)
(~diff-hunk
:file (get hunk \"file\")
:old-start (get hunk \"old-start\")
:new-start (get hunk \"new-start\")
:lines (get hunk \"lines\")))
(get diff \"hunks\")))" "lisp")
(p "Because diffs are SX data, macros can transform them:")
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li "Syntax highlighting via the same " (code "highlight") " helper used everywhere")
(li "Inline review comments as SX forms (sx-post to comment endpoint)")
(li "Suggestion blocks — click to apply a proposed change")
(li "SX-aware diffs — show component-level changes, not just line changes")))
(~doc-section :title "Federated Forge" :id "federation"
(p "sx-activity enables cross-instance collaboration:")
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li (strong "Cross-instance PRs") " — open a PR from your fork on another instance")
(li (strong "Federated issues") " — file an issue on a remote repo from your instance")
(li (strong "Stars and forks") " — ActivityPub Follow/Like activities")
(li (strong "Mirror sync") " — subscribe to upstream changes via sx-activity")
(li (strong "Review comments") " — threaded discussions federated as SX documents"))
(p "Every issue, comment, and review is a content-addressed SX document on IPFS. "
"Federation distributes references. The content is permanent and verifiable."))
(~doc-section :title "Git Operations as IO Primitives" :id "git-ops"
(p "Git operations exposed as SX IO primitives in boundary.sx:")
(highlight ";; boundary.sx additions
(io git-log (repo &key branch limit offset) list)
(io git-tree (repo ref path) list)
(io git-blob (repo ref path) string)
(io git-diff (repo base head) dict)
(io git-refs (repo) list)
(io git-commit (repo message files &key author) dict)
(io git-create-branch (repo name from) dict)
(io git-merge (repo source target &key strategy) dict)" "lisp")
(p "Pages use these directly — no controller layer, no ORM:"))
(~doc-section :title "Implementation Path" :id "implementation"
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
(li (strong "Phase 1: Read-only browser") " — git-tree, git-blob, git-log, git-diff as IO primitives. "
"SX components for tree view, blob view, commit log, diff view.")
(li (strong "Phase 2: Issues") " — SX forms for create/edit. Content-addressed storage. "
"Labels, milestones, assignees as SX data.")
(li (strong "Phase 3: Pull requests") " — fork model, diff + review UI, merge strategies. "
"CI status checks from sx-ci.")
(li (strong "Phase 4: CI integration") " — push hooks trigger sx-ci pipelines. "
"Results rendered as SX components on the PR page.")
(li (strong "Phase 5: Federation") " — sx-activity for cross-instance PRs, issues, stars.")
(li (strong "Phase 6: Admin") " — SX macro-based permission policies. "
"Instance and org management via SX config.")))))

186
sx/sx/plans/sx-proxy.sx Normal file
View File

@@ -0,0 +1,186 @@
;; sx-proxy — SX-based Reverse Proxy
;; Plan: a Caddy-style reverse proxy where routes, TLS, middleware,
;; and load balancing are SX s-expressions manipulated by macros.
(defcomp ~plan-sx-proxy-content ()
(~doc-page :title "sx-proxy: Reverse Proxy in SX"
(~doc-section :title "Vision" :id "vision"
(p "A reverse proxy where routing rules, TLS configuration, middleware chains, "
"and load balancing policies are SX. Not a config file that gets parsed — "
"actual SX that gets evaluated. Macros generate routes from service definitions. "
"The proxy config is derived from the same stack definition as the deployment.")
(p "Caddy's Caddyfile is close — it's almost a language. But it stops short. "
"No functions, no macros, no computed values, no integration with the rest of the stack. "
"sx-proxy goes all the way: the proxy configuration " (em "is") " the application."))
(~doc-section :title "Why" :id "why"
(p "Every proxy config language reinvents the same features badly:")
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li "Nginx: custom DSL with if-is-evil, no real conditionals, include for composition")
(li "Caddy: Caddyfile is nice but no functions, no macros, no types")
(li "Traefik: labels on Docker containers — stringly-typed, no validation")
(li "HAProxy: line-oriented config, ACLs as string matching, no abstraction"))
(p "All of them: separate config from application. "
"SX unifies them. The proxy reads the same service definitions "
"that the orchestrator deploys."))
(~doc-section :title "Route Definitions" :id "routes"
(p "Routes as SX, with macros for common patterns:")
(highlight ";; Basic route definition
(route blog.rose-ash.com
:upstream \"http://blog:8000\"
:tls :auto)
;; Service route macro — generates from service definition
(defmacro service-route (service)
(let ((domain (service-domain service))
(port (service-port service)))
`(route ,domain
:upstream ,(str \"http://\" (get service \"name\") \":\" port)
:tls :auto
:headers {:X-Forwarded-Proto \"https\"
:X-Real-IP (client-ip)})))
;; Generate all routes from the stack definition
(define routes
(map service-route (get rose-ash-stack \"services\")))" "lisp")
(p "One macro generates all routes from the same service definitions "
"that sx-swarm uses for deployment. Change the service, the route updates."))
(~doc-section :title "Middleware as Composition" :id "middleware"
(p "Middleware chains are function composition:")
(highlight ";; Middleware are functions: request -> response -> response
(define rate-limit
(middleware :name :rate-limit
:window \"1m\" :max 100 :key (client-ip)))
(define cors
(middleware :name :cors
:origins (list \"*.rose-ash.com\")
:methods (list :GET :POST :PUT :DELETE)
:headers (list :Authorization :Content-Type)))
(define auth-required
(middleware :name :auth
:provider :oauth2
:redirect \"https://account.rose-ash.com/login\"))
;; Compose middleware chains
(define public-chain
(chain rate-limit cors))
(define private-chain
(chain rate-limit cors auth-required))
;; Apply to routes
(route account.rose-ash.com
:upstream \"http://account:8000\"
:middleware private-chain
:tls :auto)" "lisp")
(p "Middleware is data. Chains compose with " (code "chain") ". "
"Apply different chains to different routes. "
"No nginx location blocks, no Caddy handle nesting."))
(~doc-section :title "TLS Configuration" :id "tls"
(p "TLS as SX with macro-generated cert management:")
(highlight ";; Auto TLS via ACME (like Caddy)
(define tls-auto
{:provider :acme
:ca \"https://acme-v02.api.letsencrypt.org/directory\"
:email \"admin@rose-ash.com\"
:storage \"/data/certs\"})
;; Wildcard via DNS challenge
(define tls-wildcard
{:provider :acme
:ca \"https://acme-v02.api.letsencrypt.org/directory\"
:dns-challenge {:provider :cloudflare
:api-token (from-secret \"cf-token\")}
:domains (list \"*.rose-ash.com\")})
;; Per-route TLS macro
(defmacro with-tls (config &rest routes)
`(map (fn (r) (assoc r :tls ,config)) (list ,@routes)))" "lisp"))
(~doc-section :title "Load Balancing" :id "load-balancing"
(p "Load balancing policies as SX values:")
(highlight ";; Load balancing strategies
(define round-robin (lb :strategy :round-robin))
(define least-conn (lb :strategy :least-connections))
(define ip-hash (lb :strategy :ip-hash))
;; Health-checked upstream pool
(define blog-pool
(upstream-pool
:backends (list \"blog-1:8000\" \"blog-2:8000\" \"blog-3:8000\")
:strategy least-conn
:health-check {:path \"/health\"
:interval \"10s\"
:timeout \"3s\"
:unhealthy-threshold 3}))
(route blog.rose-ash.com
:upstream blog-pool
:tls :auto)" "lisp"))
(~doc-section :title "Dynamic Reconfiguration" :id "dynamic"
(p "The proxy evaluates SX — so config can be dynamic:")
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li (strong "Hot reload") " — change the SX, proxy re-evaluates. No restart.")
(li (strong "API-driven") " — sx-post new routes. The proxy is an SX app with endpoints.")
(li (strong "Swarm-aware") " — subscribe to swarm-status changes, "
"auto-update upstreams when services scale or move.")
(li (strong "Feature flags") " — route traffic based on SX predicates "
"(header match, percentage split, user attribute).")
(li (strong "A/B testing") " — split traffic via SX expressions, "
"not nginx map blocks or Traefik weighted services."))
(highlight ";; Traffic splitting — 90% to v2, 10% to v1
(route blog.rose-ash.com
:upstream (traffic-split
{:weight 90 :backend \"blog-v2:8000\"}
{:weight 10 :backend \"blog-v1:8000\"})
:tls :auto)
;; Feature flag routing
(route blog.rose-ash.com
:upstream (if (header? \"X-Beta\" \"true\")
\"blog-beta:8000\"
\"blog-prod:8000\")
:tls :auto)" "lisp"))
(~doc-section :title "Integration with sx-swarm" :id "integration"
(p "sx-proxy and sx-swarm share the same service definitions:")
(highlight ";; One source of truth
(defservice blog
:image \"rose-ash/blog:latest\"
:port 8000
:domain \"blog.rose-ash.com\"
:replicas 2)
;; sx-swarm reads: image, port, replicas -> container orchestration
;; sx-proxy reads: port, domain -> route generation
;; sx-ci reads: image -> build pipeline
;; sx-forge reads: domain -> webhook URLs
;; Everything derived from the same definition.
;; Change it once, everything updates." "lisp")
(p "This is the point of using one language for everything. "
"The proxy, the orchestrator, the CI, and the forge all consume "
"the same SX service definitions. No YAML-to-Caddyfile-to-Dockerfile translation."))
(~doc-section :title "Implementation Path" :id "implementation"
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
(li (strong "Phase 1: Config compiler") " — SX route definitions compiled to Caddy JSON API. "
"Use Caddy as the runtime, SX as the config language.")
(li (strong "Phase 2: Middleware macros") " — defmacro for common middleware patterns. "
"Chain composition. Apply to route groups.")
(li (strong "Phase 3: Swarm integration") " — auto-generate routes from sx-swarm service defs. "
"Hot reload on swarm state changes.")
(li (strong "Phase 4: Dynamic routing") " — traffic splitting, feature flags, A/B testing. "
"SX predicates evaluated per-request.")
(li (strong "Phase 5: Native proxy") " — replace Caddy with SX-native HTTP proxy. "
"Event loop in the host language, routing logic in SX. "
"Like phase 5 of sx-swarm: the ambitious endgame."))
(p "Phase 1-3 are practical today — Caddy's JSON API is a clean compilation target. "
"The SX layer adds macros, composition, and integration that Caddyfile lacks."))))

171
sx/sx/plans/sx-swarm.sx Normal file
View File

@@ -0,0 +1,171 @@
;; sx-swarm — SX-based Container Orchestration
;; Plan: Docker Swarm management where stack definitions, service configs,
;; and deployment logic are SX s-expressions manipulated by macros.
(defcomp ~plan-sx-swarm-content ()
(~doc-page :title "sx-swarm: Container Orchestration in SX"
(~doc-section :title "Vision" :id "vision"
(p "Replace docker-compose.yml and Docker Swarm stack files with SX. "
"Service definitions are defcomps. Environment configs are macros that expand "
"differently per target (dev, staging, production). Deployments are SX pipelines "
"executed by sx-ci. The entire infrastructure is the same language as the application.")
(p "YAML is a data format pretending to be a configuration language. "
"It has no functions, no macros, no composition, no type checking. "
"SX has all of these. A service definition is a value. A deployment is a function. "
"An environment override is a macro."))
(~doc-section :title "Why Not YAML" :id "why-not-yaml"
(p "Docker Compose and Swarm stack files share a fundamental problem: "
"they're static data with ad-hoc templating bolted on. "
"Variable substitution (${VAR}), extension fields (x-), YAML anchors (&/*) — "
"all workarounds for not having a real language.")
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li "No functions — can't abstract repeated patterns")
(li "No macros — can't generate config at definition time")
(li "No conditionals — can't branch on environment")
(li "No types — a typo in an environment variable is a runtime error")
(li "No composition — merging files is fragile and order-dependent")
(li "No verification — can't check constraints before deploy"))
(p "SX solves all of these because it's a programming language, not a data format."))
(~doc-section :title "Service Definitions" :id "services"
(p "Services defined as SX, with macros for common patterns:")
(highlight ";; Base service macro — shared defaults
(defmacro defservice (name &rest body)
`(define ,name
(merge-service
{:restart-policy {:condition :on-failure :max-attempts 3}
:logging {:driver :json-file :options {:max-size \"10m\"}}
:networks (list \"internal\")}
(dict ,@body))))
;; A concrete service
(defservice blog-service
:image \"rose-ash/blog:latest\"
:ports (list \"8001:8000\")
:environment (env-for :blog)
:volumes (list \"./blog:/app/blog:ro\")
:depends-on (list :postgres :redis)
:deploy {:replicas 2
:update-config {:parallelism 1 :delay \"10s\"}})" "lisp")
(p "The " (code "defservice") " macro injects shared defaults. "
"Every service gets restart policy, logging, and network config for free. "
"Override any field by specifying it in the body."))
(~doc-section :title "Environment Macros" :id "environments"
(p "Environment-specific config via macros, not file merging:")
(highlight ";; Environment macro — expands differently per target
(defmacro env-for (service)
(let ((base (service-env-base service))
(target (current-deploy-target)))
(case target
:dev (merge base
{:DEBUG \"true\"
:DATABASE_URL (str \"postgresql://\" service \"_dev/\" service)})
:staging (merge base
{:DATABASE_URL (from-secret (str service \"-db-staging\"))})
:production (merge base
{:DATABASE_URL (from-secret (str service \"-db-prod\"))
:SENTRY_DSN (from-secret \"sentry-dsn\")}))))
;; Volume mounts differ per environment
(defmacro dev-volumes (service)
`(list ,(str \"./\" service \":/app/\" service)
,(str \"./shared:/app/shared\")
\"./dev.sh:/app/dev.sh:ro\"))
;; Production: no bind mounts, just named volumes
(defmacro prod-volumes (service)
`(list ,(str service \"-data:/data\")))" "lisp")
(p "No more docker-compose.yml + docker-compose.dev.yml + docker-compose.prod.yml. "
"One definition, macros handle the rest."))
(~doc-section :title "Stack Composition" :id "composition"
(p "Stacks compose like functions:")
(highlight ";; Infrastructure services shared across all stacks
(define infra-services
(list
(defservice postgres
:image \"postgres:16\"
:volumes (list \"pg-data:/var/lib/postgresql/data\")
:environment {:POSTGRES_PASSWORD (from-secret \"pg-pass\")})
(defservice redis
:image \"redis:7-alpine\"
:volumes (list \"redis-data:/data\"))
(defservice pgbouncer
:image \"edoburu/pgbouncer\"
:depends-on (list :postgres))))
;; Full stack = infra + app services
(define rose-ash-stack
(stack :name \"rose-ash\"
:services (concat infra-services app-services)
:networks (list
(network :name \"internal\" :driver :overlay :internal true)
(network :name \"public\" :driver :overlay))
:volumes (list
(volume :name \"pg-data\" :driver :local)
(volume :name \"redis-data\" :driver :local))))" "lisp"))
(~doc-section :title "Deploy as SX Pipeline" :id "deploy"
(p "Deployment is an sx-ci pipeline, not a shell script:")
(highlight "(define deploy-pipeline
(pipeline :name \"deploy\"
(stage :build
(map (fn (svc)
(docker-build
:context (str \"./\" (get svc \"name\"))
:tag (str \"rose-ash/\" (get svc \"name\") \":\" (git-sha))))
(changed-services)))
(stage :push
(map (fn (svc)
(docker-push (service-image svc)))
(changed-services)))
(stage :deploy
(swarm-deploy rose-ash-stack
:target (current-deploy-target)
:rolling true
:health-check-interval \"5s\"))))" "lisp"))
(~doc-section :title "Swarm Operations as IO" :id "swarm-ops"
(p "Swarm management exposed as SX IO primitives:")
(highlight ";; boundary.sx additions
(io swarm-deploy (stack &key target rolling) dict)
(io swarm-status (&key stack service) list)
(io swarm-scale (service replicas) dict)
(io swarm-rollback (service) dict)
(io swarm-logs (service &key since tail follow) list)
(io swarm-inspect (service) dict)
(io swarm-nodes () list)
(io swarm-secrets (&key create delete) dict)
(io docker-build (&key context tag dockerfile) dict)
(io docker-push (image) dict)" "lisp")
(p "These compose with sx-ci and the forge — push to forge triggers CI, "
"CI runs tests, tests pass, deploy pipeline executes, swarm updates."))
(~doc-section :title "Health and Monitoring" :id "monitoring"
(p "Service health as live SX components:")
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li (strong "Dashboard") " — defcomp rendering swarm-status, auto-refreshing via sx-get polling")
(li (strong "Log viewer") " — swarm-logs streamed via SSE, rendered as SX")
(li (strong "Alerts") " — SX predicates on service state, notifications via sx-activity")
(li (strong "Rollback") " — one-click rollback via swarm-rollback IO primitive"))
(p "The monitoring dashboard is an SX page like any other. "
"No Grafana, no separate monitoring stack. Same language, same renderer, same platform."))
(~doc-section :title "Implementation Path" :id "implementation"
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
(li (strong "Phase 1: Stack definition") " — SX data structures for services, networks, volumes. "
"Compiler to docker-compose.yml / docker stack deploy format.")
(li (strong "Phase 2: Environment macros") " — defmacro for env-for, dev-volumes, prod-volumes. "
"Single source file, multiple targets.")
(li (strong "Phase 3: Deploy pipeline") " — sx-ci integration. Build, push, deploy as SX stages.")
(li (strong "Phase 4: Swarm IO") " — boundary primitives wrapping Docker API. "
"Dashboard and log viewer components.")
(li (strong "Phase 5: Native orchestration") " — replace Docker Swarm entirely. "
"SX-native container scheduling, networking, service discovery. "
"The ultimate goal: no Docker, no Swarm — just SX managing containers directly."))
(p "Phase 1-3 are pragmatic — compile SX to existing Docker tooling. "
"Phase 5 is ambitious — replace Docker tooling with SX. "
"The architecture supports both because the abstraction boundary is clean."))))

View File

@@ -2,7 +2,7 @@
(defcomp ~doc-page (&key title &rest children)
(div :class "max-w-4xl mx-auto px-6 py-8"
(h1 :class "text-4xl font-bold text-stone-900 mb-8" title)
(h1 :class "text-4xl font-bold text-stone-900 mb-8 text-center" title)
(div :class "prose prose-stone max-w-none space-y-6" children)))
(defcomp ~doc-section (&key title id &rest children)

View File

@@ -513,6 +513,9 @@
"sx-ci" (~plan-sx-ci-content)
"live-streaming" (~plan-live-streaming-content)
"sx-web-platform" (~plan-sx-web-platform-content)
"sx-forge" (~plan-sx-forge-content)
"sx-swarm" (~plan-sx-swarm-content)
"sx-proxy" (~plan-sx-proxy-content)
:else (~plans-index-content))))
;; ---------------------------------------------------------------------------