Per the user: the execute-fold-vs-artdag split from P0.2 is a capability SNAPSHOT, not a permanent
boundary. artdag MAY grow +{effect,branch,each} node-kinds; business logic then migrates onto it to
inherit the DAG-engine superpowers — content-addressed memoization (recompute only on input-CID
change), optimize (fuse/dedup/dce), schedule, and above all FEDERATION (a flow result reused across
peers by content-id — the federation vision, for free). The capability model makes the migration
seamless (same DAGs + seam; the runner just advertises more). Named the real design work: dynamic
control in a static DAG (branch prunes a path); effect nodes non-cacheable vs pure nodes memoized.
Demand-driven (phase AX); execute-fold stays the lean default for cheap synchronous flows. Annotated
the P0.2 finding + flows.sx header so the finding doesn't harden into dogma.
Doc/comment-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
3.2 KiB
Plaintext
49 lines
3.2 KiB
Plaintext
;; lib/host/flows.sx — behavior DAGs + CAPABILITY-typed nodes / capability-advertising runners
|
|
;; (plans/business-logic-fed-flows.md). P0.2 finding: a SYNCHRONOUS business flow is an EXECUTE-FOLD
|
|
;; composition (host/execute.sx: effect/alt/each — content-addressed control flow), NOT an artdag
|
|
;; DATAFLOW DAG (which has no branch). Both are "content-addressed op-DAGs" — two instances of one
|
|
;; abstraction, run very differently: the execute-fold runner (control flow, synchronous) vs the
|
|
;; artdag runner (dataflow, memoized/parallel). The DIFFERENCE is which capabilities their nodes
|
|
;; need. A node declares its capability; a runner ADVERTISES what it supports; the binder checks
|
|
;; required ⊆ advertised (fail fast); so the sync/durable/distributed choice is DERIVED from the DAG.
|
|
;; NOTE (plan phase AX): this execute-fold-vs-artdag split is a capability SNAPSHOT, not a boundary
|
|
;; — artdag MAY grow +{effect,branch,each} node-kinds and business logic then migrates onto it (to
|
|
;; inherit content-addressed memoization / optimize / FEDERATION). The capability model makes that
|
|
;; migration seamless; the execute-fold stays the lean default for cheap synchronous flows.
|
|
|
|
;; ── capability typing: a node kind → the capability it needs ──────────
|
|
(define host/flow--node-cap
|
|
(fn (h)
|
|
(cond ((= h "effect") "effect")
|
|
((= h "alt") "branch")
|
|
((= h "each") "each")
|
|
((= h "wait") "suspend") ;; a timer/suspend node — the execute-fold canNOT run it
|
|
(else nil))))
|
|
(define host/flow--uniq-concat
|
|
(fn (a b) (reduce (fn (acc x) (if (contains? acc x) acc (concat acc (list x)))) a b)))
|
|
;; the capability SET a composition requires — the union of its nodes' caps (walked recursively).
|
|
(define host/flow--required-caps
|
|
(fn (node)
|
|
(if (not (= (type-of node) "list")) (list)
|
|
(let ((self (host/flow--node-cap (str (first node))))
|
|
(kids (reduce (fn (acc c) (host/flow--uniq-concat acc (host/flow--required-caps c)))
|
|
(list) (rest node))))
|
|
(if (nil? self) kids (host/flow--uniq-concat (list self) kids))))))
|
|
(define host/flow--subset? (fn (a b) (reduce (fn (ok x) (and ok (contains? b x))) true a)))
|
|
|
|
;; ── the SYNCHRONOUS op-table runner = the execute-fold ────────────────
|
|
;; a seam runner {:capabilities :run}. It ADVERTISES {effect, branch, each} — the execute-fold
|
|
;; vocabulary. run: fold the composition (dag) against the env's :ctx → the effect log (as data).
|
|
(define host/flow--exec-runner
|
|
{:capabilities (list "effect" "branch" "each")
|
|
:run (fn (dag env) {:status "done" :effects (host/exec-run dag (or (get env :ctx) {}))})})
|
|
|
|
;; DERIVE the runner: bind a DAG to a runner iff its required capabilities ⊆ the runner's advertised.
|
|
;; Fails fast (a {:bind-error …}) rather than mysteriously at run time. This is where "simple in SX
|
|
;; / durable in Erlang / distributed in celery-sx" becomes a checkable property of the DAG.
|
|
(define host/flow--bind
|
|
(fn (runner dag)
|
|
(let ((need (host/flow--required-caps dag)) (have (get runner :capabilities)))
|
|
(if (host/flow--subset? need have) {:ok true :runner runner}
|
|
{:ok false :bind-error {:needs need :has have}}))))
|