From 1c2bf505f436c96d37fd94fd5522186a0fc81c7c Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 30 Jun 2026 17:16:41 +0000 Subject: [PATCH] =?UTF-8?q?plan:=20composition=20is=20universal=20?= =?UTF-8?q?=E2=80=94=20a=20fold=20per=20domain=20(render/execute/eval/redu?= =?UTF-8?q?ce/extent)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The composition DAG is not a content mechanism; render is fold #1. The same structure (content-addressed objects + ordered labelled forks + seq/par/alt/each) is interpreted by a different fold per domain: content=render, behaviour=execute (flow-on-sx), query=eval (Datalog), pipeline=reduce (artdag, literally a content-addressed composition DAG), types=extent (and/or = intersection/union). "Relations just a fork" generalises: relation kind + fold = domain. The X-on-sx loops already ARE these folds — the composition DAG is the fleet convergence point. Payoff: build composition once, reuse per domain via interpreters; the block editor + metamodel UI generalise to every fold (author a workflow like a document). System collapses to four ideas: content-addressed objects + composition algebra + per-domain folds + decidable-core predicates. Roadmap +2: prove universality with a second (execute) fold over the same seq/alt/each; then factor out the shared compose core. Co-Authored-By: Claude Opus 4.8 --- plans/composition-objects.md | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/plans/composition-objects.md b/plans/composition-objects.md index 726f7c3a..3d555d4e 100644 --- a/plans/composition-objects.md +++ b/plans/composition-objects.md @@ -57,6 +57,35 @@ responsive/personalised/variant space; rendering picks the path. Render-fold and behaviour interpreter are the **same shape** — interpreters over content-addressed objects + the decidable-core predicate set + the graph. The system converges on: objects + small interpreters. +## Beyond content — composition is universal; a fold per domain + +The render-fold isn't "the content renderer" — it's **fold #1**. The composition DAG is a +**universal algebra** (`seq/par/alt/each` over content-addressed objects); *content* is just one +*interpretation*. Same structure, a different **fold** per domain — what changes is what the +combinators and leaves *mean*: + +| domain | the fold | `seq` | `par` | `alt`+`when` | `each` | substrate | +|--------|----------|-------|-------|-----------|--------|-----------| +| **content** | render → HTML | block order | layout/columns | choose variant | map items | `compose.sx` (done) | +| **behaviour** | execute → effects | steps in order | concurrent | branch (if/cond) | for-each | `[[project_flow_on_sx]]` | +| **query** | eval → results | join/chain | union | conditional | iterate/quantify | `[[project_relations_on_sx]]` (Datalog) | +| **pipeline** | reduce → data | dataflow stages | parallel ops | choose path | fan-out | `[[project_artdag_on_sx]]` (content-addressed DAG) | +| **types** | extent → set | — | ∧ intersection | — | ∨ union | the type algebra (`make-and!`/`make-or!`) | + +So **"relations just a fork" generalises**: a `contains` fork folded by *render* is a document; a +`then` fork folded by *execute* is a workflow step; a `depends-on` fork folded by *eval* is a +dependency graph. **The relation kind + the fold = the domain.** This isn't aspirational — the +repo's `X-on-sx` loops ALREADY ARE these folds (flow = execute, Datalog = eval, artdag = a +content-addressed composition DAG); we just hadn't seen them as one shape. The composition DAG is +the **convergence point** the whole fleet has been circling. + +The payoff is concrete: **build the composition machinery ONCE** (forks + ordered edges + the four +combinators + a fold framework) → reuse for every domain by writing one interpreter. **The block +editor edits *any* composition** — author a workflow like a document, same structure, one editor. +The whole system collapses to four ideas: **content-addressed objects + a composition algebra + +per-domain folds + the decidable-core predicates (`when`).** The render-fold's shape (walk the +composition, dispatch combinators, recurse, read the context) is the *template* for every other fold. + ## What lives elsewhere (not composition primitives) Transclusion = a `ref` leaf. Sort/filter/limit/group = the *source query* language (Datalog). @@ -76,3 +105,13 @@ Transclusion = a `ref` leaf. Sort/filter/limit/group = the *source query* langua 5. The typed importer decomposes Ghost Lexical into card objects + a `contains` body (cards-as- objects), instead of one `sx_content` string. 6. The block editor edits the body (insert/reorder/`alt`/`each`) — the metamodel editor for content. +7. **Prove universality with a second fold.** Write a tiny `execute`-fold over the *same* + `seq/alt/each` structure that *runs* a workflow (leaves = effects; `seq` = steps in order, `alt` + = branch, `each` = for-each) — the way the recursive tree proved recursion, this proves the + composition algebra is domain-agnostic. Then the *behaviour* model (Slice 9) is "an `execute`-fold + over a composition object", not a separate system. +8. **Factor out the shared machinery** once two folds exist: the fork model (ordered, labelled, + `when`), the combinator dispatch, the context-environment, and recursion become a reusable + `compose` core; each domain (`render`, `execute`, `eval`, …) supplies only its leaf + combinator + semantics. The block editor + the metamodel UI then generalise to *every* fold — one composition + editor authors documents, workflows, queries, and pipelines alike.