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:
2026-07-02 15:07:58 +00:00
parent 5b6a5e4f19
commit 77e89a9965
3 changed files with 66 additions and 23 deletions

View File

@@ -1163,25 +1163,34 @@
;; ── P0.1: business-logic fed-flows — the publish-activity contract ──
;; a published post is described as a fed-sx create activity that next/'s trigger machinery
;; consumes; category (drives the flow branch) comes from a field-value, else a tag, else urgent.
(host-bl-test "publish-activity describes a published post as a fed-sx create activity"
(host-bl-test "P0.4: publish-activity is the CANONICAL seam shape (:verb :object=cid :object-type :slug :category :id)"
(begin
(host/blog-put! "pub1" "Pub One" "(article (h1 \"P\"))" "published")
(host/blog--set-field-values! "pub1" {"category" "newsletter"})
(let ((a (host/blog--publish-activity "pub1")))
(list (get a :type) (get (get a :object) :type) (get (get a :object) :category)
(get (get a :object) :slug) (not (nil? (get a :id))))))
(list "create" "article" "newsletter" "pub1" true))
(list (get a :verb) (get a :object-type) (get a :category) (get a :slug)
(= (get a :object) (get a :id)) (not (nil? (get a :id))))))
(list "create" "article" "newsletter" "pub1" true true))
(host-bl-test "publish-activity category falls back to a tag, else urgent"
(begin
(host/blog-put! "pub2" "Pub Two" "(article (h1 \"Q\"))" "published")
(host/blog-seed! "urgent" "Urgent" "(article (h1 \"u\"))" "published")
(host/blog-relate! "pub2" "urgent" "tagged")
(host/blog-put! "pub3" "Pub Three" "(article (h1 \"R\"))" "published") ;; no field, no tag
(list (get (get (host/blog--publish-activity "pub2") :object) :category)
(get (get (host/blog--publish-activity "pub3") :object) :category)))
(list (get (host/blog--publish-activity "pub2") :category)
(get (host/blog--publish-activity "pub3") :category)))
(list "urgent" "urgent"))
(host-bl-test "publish-activity of a missing post is nil"
(host/blog--publish-activity "nope-nope-nope") nil)
;; P0.4: the marshaller maps the canonical shape → next/'s Erlang proplist (for the RA runner).
(host-bl-test "activity->erl marshals canonical → next/ proplist ({type,object:{type,slug,category}})"
(begin
(host/blog-put! "pub4" "P4" "(article (h1 \"m\"))" "published")
(host/blog--set-field-values! "pub4" {"category" "newsletter"})
(let ((e (host/blog--activity->erl (host/blog--publish-activity "pub4"))))
(list (get e :type) (get (get e :object) :type)
(get (get e :object) :slug) (get (get e :object) :category))))
(list "create" "article" "pub4" "newsletter"))
;; P0.2: the publish WORKFLOW as an execute-fold DAG — branches on category, needs {effect,branch},
;; binds to the synchronous execute-fold runner (derived, not chosen).
(host-bl-test "publish-DAG: category branch (newsletter→digest) via the execute-fold"