|
|
|
|
@@ -282,7 +282,7 @@
|
|
|
|
|
(li "Inline content renders immediately")
|
|
|
|
|
(li "CID-referenced content shows placeholder → fetches from IPFS → renders")
|
|
|
|
|
(li "Large media uses IPFS streaming (chunked CIDs)")
|
|
|
|
|
(li "Integrates with Phase 5 of isomorphic plan (streaming/suspense)")))))
|
|
|
|
|
(li "Integrates with Phase 6 of isomorphic plan (streaming/suspense)")))))
|
|
|
|
|
|
|
|
|
|
(~doc-subsection :title "Verification"
|
|
|
|
|
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
|
|
|
|
@@ -535,7 +535,7 @@
|
|
|
|
|
(li "Phase 1 (component distribution) → IPFS replaces per-server bundles")
|
|
|
|
|
(li "Phase 2 (IO detection) → pure components safe for IPFS publication")
|
|
|
|
|
(li "Phase 3 (client routing) → client can resolve federated content without server")
|
|
|
|
|
(li "Phase 5 (streaming/suspense) → progressive IPFS resolution uses same infrastructure"))))
|
|
|
|
|
(li "Phase 6 (streaming/suspense) → progressive IPFS resolution uses same infrastructure"))))
|
|
|
|
|
|
|
|
|
|
;; -----------------------------------------------------------------------
|
|
|
|
|
;; Critical Files
|
|
|
|
|
@@ -1316,7 +1316,25 @@
|
|
|
|
|
(div :class "flex items-center gap-2 mb-1"
|
|
|
|
|
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
|
|
|
|
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 3: Client-Side Routing"))
|
|
|
|
|
(p :class "text-sm text-stone-600" "router.sx spec, page registry via <script type=\"text/sx-pages\">, client route matching, try-first/fallback to server. Pure pages render without server roundtrips."))))
|
|
|
|
|
(p :class "text-sm text-stone-600" "router.sx spec, page registry via <script type=\"text/sx-pages\">, client route matching, try-first/fallback to server. Pure pages render without server roundtrips."))
|
|
|
|
|
|
|
|
|
|
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
|
|
|
|
(div :class "flex items-center gap-2 mb-1"
|
|
|
|
|
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
|
|
|
|
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 4: Client Async & IO Bridge"))
|
|
|
|
|
(p :class "text-sm text-stone-600" "Server evaluates :data expressions, serializes as SX wire format. Client fetches pre-evaluated data, caches with 30s TTL, renders :content locally. 30 unit tests."))
|
|
|
|
|
|
|
|
|
|
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
|
|
|
|
(div :class "flex items-center gap-2 mb-1"
|
|
|
|
|
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
|
|
|
|
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 5: Client IO Proxy"))
|
|
|
|
|
(p :class "text-sm text-stone-600" "IO primitives (highlight, asset-url, etc.) proxied to server via registerIoDeps(). Async DOM renderer handles promises through the render tree. Components with IO deps render client-side via server round-trips — no continuations needed."))
|
|
|
|
|
|
|
|
|
|
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
|
|
|
|
(div :class "flex items-center gap-2 mb-1"
|
|
|
|
|
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
|
|
|
|
(a :href "/testing/" :class "font-semibold text-green-800 underline" "Modular Test Architecture"))
|
|
|
|
|
(p :class "text-sm text-stone-600" "Per-module test specs (eval, parser, router, render) with 161 tests. Three runners: Python, Node.js, browser. 5 platform functions, everything else pure SX."))))
|
|
|
|
|
|
|
|
|
|
;; -----------------------------------------------------------------------
|
|
|
|
|
;; In Progress / Partial
|
|
|
|
|
@@ -1330,13 +1348,7 @@
|
|
|
|
|
(div :class "flex items-center gap-2 mb-1"
|
|
|
|
|
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase" "Partial")
|
|
|
|
|
(a :href "/plans/fragment-protocol" :class "font-semibold text-amber-900 underline" "Fragment Protocol"))
|
|
|
|
|
(p :class "text-sm text-stone-600" "Fragment GET infrastructure works. The planned POST/sexp structured protocol for transferring component definitions between services is not yet implemented. Fragment endpoints still use legacy GET + X-Fragment-Request headers."))
|
|
|
|
|
|
|
|
|
|
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
|
|
|
|
|
(div :class "flex items-center gap-2 mb-1"
|
|
|
|
|
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase" "Partial")
|
|
|
|
|
(a :href "/isomorphism/" :class "font-semibold text-amber-900 underline" "Isomorphic Phase 4: Client Async & IO Bridge"))
|
|
|
|
|
(p :class "text-sm text-stone-600" "Some async evaluation infrastructure exists (helpers.py, async_eval.py). The io-bridge.sx spec file for client-side IO primitives (query -> REST, frag -> fetch) does not exist yet."))))
|
|
|
|
|
(p :class "text-sm text-stone-600" "Fragment GET infrastructure works. The planned POST/sexp structured protocol for transferring component definitions between services is not yet implemented. Fragment endpoints still use legacy GET + X-Fragment-Request headers."))))
|
|
|
|
|
|
|
|
|
|
;; -----------------------------------------------------------------------
|
|
|
|
|
;; Not Started
|
|
|
|
|
@@ -1377,14 +1389,14 @@
|
|
|
|
|
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
|
|
|
|
(div :class "flex items-center gap-2 mb-1"
|
|
|
|
|
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
|
|
|
|
|
(a :href "/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 5: Streaming & Suspense"))
|
|
|
|
|
(p :class "text-sm text-stone-600" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately, fills in suspended parts. No suspense.sx spec or chunked transfer implementation.")
|
|
|
|
|
(p :class "text-sm text-stone-500 mt-1" "Depends on: Phase 4 (client async)."))
|
|
|
|
|
(a :href "/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Streaming & Suspense"))
|
|
|
|
|
(p :class "text-sm text-stone-600" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately, fills in suspended parts. Requires async-aware delimited continuations for suspension.")
|
|
|
|
|
(p :class "text-sm text-stone-500 mt-1" "Depends on: Phase 5 (IO proxy), continuations spec."))
|
|
|
|
|
|
|
|
|
|
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
|
|
|
|
(div :class "flex items-center gap-2 mb-1"
|
|
|
|
|
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
|
|
|
|
|
(a :href "/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Full Isomorphism"))
|
|
|
|
|
(a :href "/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 7: Full Isomorphism"))
|
|
|
|
|
(p :class "text-sm text-stone-600" "Runtime boundary optimizer, affinity annotations, offline data layer via Service Worker + IndexedDB, isomorphic testing harness.")
|
|
|
|
|
(p :class "text-sm text-stone-500 mt-1" "Depends on: all previous phases."))))))
|
|
|
|
|
|
|
|
|
|
@@ -1822,36 +1834,47 @@
|
|
|
|
|
;; Phase 5
|
|
|
|
|
;; -----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
(~doc-section :title "Phase 5: Async Continuations & Inline IO" :id "phase-5"
|
|
|
|
|
(~doc-section :title "Phase 5: Client IO Proxy" :id "phase-5"
|
|
|
|
|
|
|
|
|
|
(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 call IO primitives directly in their body. The evaluator suspends mid-evaluation via async-aware continuations, fetches data, resumes. Same component source works on both server (Python async/await) and client (continuation-based suspension)."))
|
|
|
|
|
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
|
|
|
|
(div :class "flex items-center gap-2 mb-2"
|
|
|
|
|
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete"))
|
|
|
|
|
(p :class "text-green-900 font-medium" "What it enables")
|
|
|
|
|
(p :class "text-green-800" "Components with IO dependencies render client-side. IO primitives are proxied to the server — the client evaluator calls them like normal functions, the proxy fetches results via HTTP, the async DOM renderer awaits the promises and continues."))
|
|
|
|
|
|
|
|
|
|
(~doc-subsection :title "The Problem"
|
|
|
|
|
(p "The existing shift/reset continuations extension is synchronous (throw/catch). Client-side IO via fetch() returns a Promise — you can't throw-catch across an async boundary. The evaluator needs Promise-aware continuations or a CPS transform."))
|
|
|
|
|
(~doc-subsection :title "How it works"
|
|
|
|
|
(p "Instead of async-aware continuations (originally planned), Phase 5 was solved by combining three mechanisms that emerged from Phases 3-4:")
|
|
|
|
|
|
|
|
|
|
(~doc-subsection :title "Approach"
|
|
|
|
|
(div :class "space-y-4"
|
|
|
|
|
(div
|
|
|
|
|
(h4 :class "font-semibold text-stone-700" "1. Async-aware shift/reset")
|
|
|
|
|
(p "Extend the continuations extension: sfShift captures the continuation and returns a Promise, sfReset awaits Promise results in the trampoline. Continuation resume feeds the fetched value back into evaluation."))
|
|
|
|
|
(h4 :class "font-semibold text-stone-700" "1. IO dependency detection (from Phase 2)")
|
|
|
|
|
(p "The component dep analyzer scans AST bodies for IO primitive names (highlight, asset-url, query, frag, etc.) and computes transitive IO refs. Pages include their IO dep list in the page registry."))
|
|
|
|
|
|
|
|
|
|
(div
|
|
|
|
|
(h4 :class "font-semibold text-stone-700" "2. IO primitive bridge")
|
|
|
|
|
(p "Register async IO primitives in client PRIMITIVES:")
|
|
|
|
|
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
|
|
|
|
(li "query → fetch to /internal/data/")
|
|
|
|
|
(li "service → fetch to target service internal endpoint")
|
|
|
|
|
(li "frag → fetch fragment HTML")
|
|
|
|
|
(li "current-user → cached from initial page load")))
|
|
|
|
|
(h4 :class "font-semibold text-stone-700" "2. IO proxy registration")
|
|
|
|
|
(p (code "registerIoDeps(names)") " in orchestration.sx registers proxy functions for each IO primitive. When the client evaluator encounters " (code "(highlight code \"sx\")") ", the proxy sends an HTTP request to the server's IO endpoint and returns a Promise."))
|
|
|
|
|
|
|
|
|
|
(div
|
|
|
|
|
(h4 :class "font-semibold text-stone-700" "3. CPS transform option")
|
|
|
|
|
(p "Alternative: transform the evaluator to continuation-passing style. Every eval step takes a continuation argument. IO primitives call the continuation after fetch resolves. Architecturally cleaner but requires deeper changes."))))
|
|
|
|
|
(h4 :class "font-semibold text-stone-700" "3. Async DOM renderer")
|
|
|
|
|
(p (code "asyncRenderToDom") " walks the expression tree and handles Promises transparently. When a subexpression returns a Promise (from an IO proxy call), the renderer awaits it and continues building the DOM tree. No continuations needed — JavaScript's native Promise mechanism provides the suspension."))))
|
|
|
|
|
|
|
|
|
|
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
|
|
|
|
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 4 (data endpoint infrastructure).")))
|
|
|
|
|
(~doc-subsection :title "Why continuations weren't needed"
|
|
|
|
|
(p "The original Phase 5 plan called for async-aware shift/reset or a CPS transform of the evaluator. In practice, JavaScript's Promise mechanism provided the same capability: the async DOM renderer naturally suspends when it encounters a Promise and resumes when it resolves.")
|
|
|
|
|
(p "Delimited continuations remain valuable for Phase 6 (streaming/suspense on the " (em "server") " side, where Python doesn't have native Promise-based suspension in the evaluator). But for client-side IO, Promises + async render were sufficient."))
|
|
|
|
|
|
|
|
|
|
(~doc-subsection :title "Files"
|
|
|
|
|
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
|
|
|
|
(li "shared/sx/ref/orchestration.sx — registerIoDeps, IO proxy registration")
|
|
|
|
|
(li "shared/sx/ref/bootstrap_js.py — asyncRenderToDom, IO proxy HTTP transport")
|
|
|
|
|
(li "shared/sx/helpers.py — io_deps in page registry entries")
|
|
|
|
|
(li "shared/sx/deps.py — transitive IO ref computation")))
|
|
|
|
|
|
|
|
|
|
(~doc-subsection :title "Verification"
|
|
|
|
|
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
|
|
|
|
(li "Navigate to any page with IO deps (e.g. /testing/eval) — console shows IO proxy calls")
|
|
|
|
|
(li "Components using " (code "highlight") " render correctly via proxy")
|
|
|
|
|
(li "Pages with " (code "asset-url") " resolve script paths via proxy")
|
|
|
|
|
(li "Async render completes without blocking — partial results appear as promises resolve"))))
|
|
|
|
|
|
|
|
|
|
;; -----------------------------------------------------------------------
|
|
|
|
|
;; Phase 6
|
|
|
|
|
@@ -1863,6 +1886,10 @@
|
|
|
|
|
(p :class "text-violet-900 font-medium" "What it enables")
|
|
|
|
|
(p :class "text-violet-800" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately, fills in suspended parts. Like React Suspense but built on delimited continuations."))
|
|
|
|
|
|
|
|
|
|
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mb-4"
|
|
|
|
|
(p :class "text-amber-800 text-sm" (strong "Prerequisite: ") "Async-aware delimited continuations. The client solved IO suspension via JavaScript Promises (Phase 5), but the server needs continuations to suspend mid-evaluation when IO is encountered during streaming. Python's evaluator must capture the continuation at an IO call, emit a placeholder, schedule the IO, and resume the continuation when the result arrives."))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(~doc-subsection :title "Approach"
|
|
|
|
|
|
|
|
|
|
(div :class "space-y-4"
|
|
|
|
|
@@ -1887,7 +1914,7 @@
|
|
|
|
|
(p "Above-fold content resolves first. All IO starts concurrently (asyncio.create_task), results flushed in priority order."))))
|
|
|
|
|
|
|
|
|
|
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
|
|
|
|
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 5 (async continuations for filling suspended subtrees), Phase 2 (IO analysis for priority).")))
|
|
|
|
|
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 5 (IO proxy for client rendering), async-aware delimited continuations (for server-side suspension), Phase 2 (IO analysis for priority).")))
|
|
|
|
|
|
|
|
|
|
;; -----------------------------------------------------------------------
|
|
|
|
|
;; Phase 7
|
|
|
|
|
@@ -2014,4 +2041,134 @@
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/suspense.sx")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Streaming/suspension (new)")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-600" "5"))))))))
|
|
|
|
|
(td :class "px-3 py-2 text-stone-600" "5")))))))))))))
|
|
|
|
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
|
;; SX CI Pipeline
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
(defcomp ~plan-sx-ci-content ()
|
|
|
|
|
(~doc-page :title "SX CI Pipeline"
|
|
|
|
|
|
|
|
|
|
(p :class "text-stone-500 text-sm italic mb-8"
|
|
|
|
|
"Build, test, and deploy Rose Ash using the same language the application is written in.")
|
|
|
|
|
|
|
|
|
|
(~doc-section :title "Context" :id "context"
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"Rose Ash currently uses shell scripts for CI: " (code "deploy.sh") " auto-detects changed services via git diff, builds Docker images, pushes to the registry, and restarts Swarm services. " (code "dev.sh") " starts the dev environment and runs tests. These work, but they are opaque imperative scripts with no reuse, no composition, and no relationship to SX.")
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"The CI pipeline is the last piece of infrastructure not expressed in s-expressions. Fixing that completes the \"one representation for everything\" claim — the same language that defines the spec, the components, the pages, the essays, and the deployment config also defines the build pipeline."))
|
|
|
|
|
|
|
|
|
|
(~doc-section :title "Design" :id "design"
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"Pipeline definitions are " (code ".sx") " files. A minimal Python CLI runner evaluates them using " (code "sx_ref.py") ". CI-specific IO primitives (shell execution, Docker, git) are boundary-declared and only available to the pipeline runner — never to web components.")
|
|
|
|
|
(~doc-code :code (highlight ";; pipeline/deploy.sx\n(let ((targets (if (= (length ARGS) 0)\n (~detect-changed :base \"HEAD~1\")\n (filter (fn (svc) (some (fn (a) (= a (get svc \"name\"))) ARGS))\n services))))\n (when (= (length targets) 0)\n (log-step \"No changes detected\")\n (exit 0))\n\n (log-step (str \"Deploying: \" (join \" \" (map (fn (s) (get s \"name\")) targets))))\n\n ;; Tests first\n (~unit-tests)\n (~sx-spec-tests)\n\n ;; Build, push, restart\n (for-each (fn (svc) (~build-service :service svc)) targets)\n (for-each (fn (svc) (~restart-service :service svc)) targets)\n\n (log-step \"Deploy complete\"))" "lisp"))
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"Pipeline steps are components. " (code "~unit-tests") ", " (code "~build-service") ", " (code "~detect-changed") " are " (code "defcomp") " definitions that compose by nesting — the same mechanism used for page layouts, navigation, and every other piece of the system."))
|
|
|
|
|
|
|
|
|
|
(~doc-section :title "CI Primitives" :id "primitives"
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"New IO primitives declared in " (code "boundary.sx") ", implemented only in the CI runner context:")
|
|
|
|
|
(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" "Primitive")
|
|
|
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Signature")
|
|
|
|
|
(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 font-mono text-sm text-violet-700" "shell-run")
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(command) -> dict")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Execute shell command, return " (code "{:exit N :stdout \"...\" :stderr \"...\"}") ""))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shell-run!")
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(command) -> dict")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Execute shell command, throw on non-zero exit"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "docker-build")
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(&key file tag context) -> nil")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Build Docker image"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "docker-push")
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(tag) -> nil")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Push image to registry"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "docker-restart")
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(service) -> nil")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Restart Swarm service"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "git-diff-files")
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(base head) -> list")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "List changed files between commits"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "git-branch")
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "() -> string")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Current branch name"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "log-step")
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(message) -> nil")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Formatted pipeline output"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "fail!")
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(message) -> nil")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Abort pipeline with error")))))
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"The boundary system ensures these primitives are " (em "only") " available in the CI context. Web components cannot call " (code "shell-run!") " — the evaluator will refuse to resolve the symbol, just as it refuses to resolve any other unregistered IO primitive. The sandbox is structural, not a convention."))
|
|
|
|
|
|
|
|
|
|
(~doc-section :title "Reusable Steps" :id "steps"
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"Pipeline steps are components — same " (code "defcomp") " as UI components, same " (code "&key") " params, same composition by nesting:")
|
|
|
|
|
(~doc-code :code (highlight "(defcomp ~detect-changed (&key base)\n (let ((files (git-diff-files (or base \"HEAD~1\") \"HEAD\")))\n (if (some (fn (f) (starts-with? f \"shared/\")) files)\n services\n (filter (fn (svc)\n (some (fn (f) (starts-with? f (str (get svc \"dir\") \"/\"))) files))\n services))))\n\n(defcomp ~build-service (&key service)\n (let ((name (get service \"name\"))\n (tag (str registry \"/\" name \":latest\")))\n (log-step (str \"Building \" name))\n (docker-build :file (str (get service \"dir\") \"/Dockerfile\") :tag tag :context \".\")\n (docker-push tag)))\n\n(defcomp ~bootstrap-check ()\n (log-step \"Checking bootstrapped files are up to date\")\n (shell-run! \"python shared/sx/ref/bootstrap_js.py\")\n (shell-run! \"python shared/sx/ref/bootstrap_py.py\")\n (let ((diff (shell-run \"git diff --name-only shared/static/scripts/sx-ref.js shared/sx/ref/sx_ref.py\")))\n (when (not (= (get diff \"stdout\") \"\"))\n (fail! \"Bootstrapped files are stale — rebootstrap and commit\"))))" "lisp"))
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"Compare this to GitHub Actions YAML, where \"reuse\" means composite actions with " (code "uses:") " references, input/output mappings, shell script blocks inside YAML strings, and a " (a :href "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions" :class "text-violet-600 hover:underline" "100-page syntax reference") ". SX pipeline reuse is function composition. That is all it has ever been."))
|
|
|
|
|
|
|
|
|
|
(~doc-section :title "Pipelines" :id "pipelines"
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"Two primary pipelines, each a single " (code ".sx") " file:")
|
|
|
|
|
(div :class "space-y-4"
|
|
|
|
|
(div :class "rounded border border-stone-200 p-4"
|
|
|
|
|
(h4 :class "font-semibold text-stone-700 mb-2" "pipeline/test.sx")
|
|
|
|
|
(p :class "text-sm text-stone-600" "Unit tests, SX spec tests (Python + Node), bootstrap staleness check, Tailwind CSS check. Run locally or in CI.")
|
|
|
|
|
(p :class "text-sm font-mono text-violet-700 mt-1" "python -m shared.sx.ci pipeline/test.sx"))
|
|
|
|
|
(div :class "rounded border border-stone-200 p-4"
|
|
|
|
|
(h4 :class "font-semibold text-stone-700 mb-2" "pipeline/deploy.sx")
|
|
|
|
|
(p :class "text-sm text-stone-600" "Auto-detect changed services (or accept explicit args), run tests, build Docker images, push to registry, restart Swarm services.")
|
|
|
|
|
(p :class "text-sm font-mono text-violet-700 mt-1" "python -m shared.sx.ci pipeline/deploy.sx blog market"))))
|
|
|
|
|
|
|
|
|
|
(~doc-section :title "Why this matters" :id "why"
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"CI pipelines are the strongest test case for \"one representation for everything.\" GitHub Actions, GitLab CI, CircleCI — all use YAML. YAML is not a programming language. So every CI system reinvents conditionals (" (code "if:") " expressions evaluated as strings), iteration (" (code "matrix:") " strategies), composition (" (code "uses:") " references with input/output schemas), and error handling (" (code "continue-on-error:") " booleans) — all in a data format that was never designed for any of it.")
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"The result is a domain-specific language trapped inside YAML, with worse syntax than any language designed to be one. Every CI pipeline of sufficient complexity becomes a programming task performed in a notation that actively resists programming.")
|
|
|
|
|
(p :class "text-stone-600"
|
|
|
|
|
"SX pipelines use real conditionals, real functions, real composition, and real error handling — because SX is a real language. The pipeline definition and the application code are the same thing. An AI that can generate SX components can generate SX pipelines. A developer who reads SX pages can read SX deploys. The representation is universal."))
|
|
|
|
|
|
|
|
|
|
(~doc-section :title "Files" :id "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"
|
|
|
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
|
|
|
|
(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 font-mono text-sm text-violet-700" "shared/sx/ci.py")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Pipeline runner CLI (~150 lines)"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ci_primitives.py")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "CI IO primitive implementations"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/services.sx")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Service registry (data)"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/steps.sx")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Reusable step components"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/test.sx")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Test pipeline"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/deploy.sx")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Deploy pipeline"))
|
|
|
|
|
(tr :class "border-b border-stone-100"
|
|
|
|
|
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boundary.sx")
|
|
|
|
|
(td :class "px-3 py-2 text-stone-700" "Add CI primitive declarations"))))))))
|
|
|
|
|
|
|
|
|
|
|