host P0.4: canonical seam activity shape + RA marshaller (LIVE-VERIFIED) — P0 COMPLETE
host/blog--publish-activity now emits the CANONICAL seam shape {:verb :actor :object <cid>
:object-type :slug :category :delta :id}: :object is a content-addressed REFERENCE (the CID, not an
inlined dict), :id the dedup identity, :slug+:category the domain fields the DAG reads. Consumers
reconciled — the on-publish trigger matches :verb+:object-type; publish-ctx reads top-level
:category+:slug. Added host/blog--activity->erl: marshals the canonical activity → next/'s Erlang
proplist for the Erlang runner adapter (RA) — defined + tested, unused until RA so the reconcile is
complete and RA's bridge is ready. (:ts/:prev omitted — no clock primitive in the host; deferred.)
LIVE PROOF: published on blog.rose-ash.com → /flows fired validate+notify with the canonical
activity. blog 209/209, full host conformance 597/597.
P0 COMPLETE: the synchronous publish workflow runs end-to-end on the live host through the
substrate-agnostic seam, durably, in the canonical shape, with the RA marshaller staged. RA (Erlang
runner) + TA (fed-sx transport) plug in next without touching the DAG or the wiring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -108,11 +108,13 @@
|
||||
nil (host/blog-slugs))))
|
||||
|
||||
;; ── business logic as federated composition-flows (plans/business-logic-fed-flows.md) ──
|
||||
;; P0.1: the PUBLISH-ACTIVITY contract. A published post is described as a fed-sx activity —
|
||||
;; the same shape next/'s trigger machinery consumes ({:type verb :actor :id CID :object …}).
|
||||
;; The trigger (on-publish → a flow, e.g. blog_publish_digest) fires on a matching activity.
|
||||
;; category (drives the flow's branch: newsletter suspends, urgent fires now, else skip) comes
|
||||
;; from the post's "category" field-value, else its first tag, else "urgent" (fires the demo).
|
||||
;; P0.1/P0.4: the PUBLISH-ACTIVITY contract, in the CANONICAL SEAM SHAPE
|
||||
;; {:verb :actor :object <cid> :object-type :slug :category :delta :id} — :object is a
|
||||
;; content-addressed REFERENCE (the CID), not an inlined dict; :id is the dedup identity; :slug +
|
||||
;; :category are the domain fields the publish-DAG's ctx reads. Each runner adapter MARSHALS this
|
||||
;; to its substrate (host/blog--activity->erl → next/'s proplist for the Erlang runner, RA).
|
||||
;; category (the DAG's branch: newsletter/urgent/else) comes from the "category" field-value, else
|
||||
;; the first tag, else "urgent".
|
||||
(define host/blog--post-category
|
||||
(fn (slug)
|
||||
(let ((fc (get (host/blog-field-values-of slug) "category")))
|
||||
@@ -123,11 +125,24 @@
|
||||
(fn (slug)
|
||||
(let ((r (host/blog-get slug)))
|
||||
(if (nil? r) nil
|
||||
{:type "create" ;; publishing = the article enters the fed world
|
||||
:actor "site" ;; P0: a fixed site actor; per-author later
|
||||
:id (host/blog-cid slug) ;; the object's content CID
|
||||
:object {:type "article" :slug slug
|
||||
:category (host/blog--post-category slug)}}))))
|
||||
(let ((cid (host/blog-cid slug)))
|
||||
{:verb "create" ;; the transition (draft→published = create)
|
||||
:actor "site" ;; P0: a fixed site actor; per-author later
|
||||
:object cid ;; content-addressed REFERENCE (the CID)
|
||||
:object-type "article"
|
||||
:slug slug ;; the handle the publish-DAG's ctx reads
|
||||
:category (host/blog--post-category slug) ;; the field the DAG branches on
|
||||
:delta "published" ;; what changed
|
||||
:id cid}))))) ;; dedup identity (the object CID)
|
||||
;; MARSHAL the canonical activity → next/'s Erlang proplist shape, for the Erlang runner adapter
|
||||
;; (RA). The seam activity is canonical; each runner adapter maps it to its substrate. Unused until
|
||||
;; RA, defined + tested here so the reconcile is complete and RA has its bridge ready.
|
||||
(define host/blog--activity->erl
|
||||
(fn (a)
|
||||
{:type (get a :verb)
|
||||
:actor (get a :actor)
|
||||
:id (get a :id)
|
||||
:object {:type (get a :object-type) :slug (get a :slug) :category (get a :category)}}))
|
||||
;; P0.2: the publish WORKFLOW as an EXECUTE-FOLD composition (host/execute.sx) — the SYNCHRONOUS
|
||||
;; business flow. Validate, then BRANCH on category (newsletter → build a digest, urgent → notify
|
||||
;; now, else skip). Content flow (effect/alt), NOT dataflow — so it's the execute-fold, not artdag.
|
||||
@@ -142,8 +157,9 @@
|
||||
(when (eq "category" "urgent") (effect notify (field "slug")))
|
||||
(else (effect skip))))))
|
||||
;; the ctx a publish activity presents to the publish-DAG (string keys — preds read ctx by key).
|
||||
;; Reads the canonical activity's top-level :category + :slug (P0.4).
|
||||
(define host/blog--publish-ctx
|
||||
(fn (activity) (let ((o (get activity :object))) {"category" (get o :category) "slug" (get o :slug)})))
|
||||
(fn (activity) {"category" (get activity :category) "slug" (get activity :slug)}))
|
||||
|
||||
;; ── P0.3: the seam WIRED on the live host ──────────────────────────────
|
||||
;; The publish ENGINE = the execute-fold runner (flows.sx) + a local-SX on-publish trigger registry
|
||||
@@ -159,7 +175,7 @@
|
||||
:deliver (fn () (list))}) ;; nothing inbound yet — P0 is synchronous
|
||||
(define host/blog--triggers
|
||||
{:register! (fn (spec dag hint) nil)
|
||||
:match (fn (a) (if (and (= (get a :type) "create") (= (get (get a :object) :type) "article"))
|
||||
:match (fn (a) (if (and (= (get a :verb) "create") (= (get a :object-type) "article"))
|
||||
(list {:dag host/blog--publish-dag}) (list)))})
|
||||
;; P0.3b: the flow log is DURABLE — string-keyed records (dodge the keyword/persist top-level split),
|
||||
;; persisted to the blog store under one key, so /flows survives a restart. Boot-loaded via
|
||||
|
||||
Reference in New Issue
Block a user