host P2: all state changes emit canonical activities (LIVE-VERIFIED)
Generalizes emission beyond publish to the full event source. TWO ActivityPub-faithful classes: - CONTENT (host/blog--content-activity): Create on first publish, Update on a subsequent published edit. object-type is DERIVED from the post's is-a (host/blog--post-type), not hardcoded 'article'. - RELATION (host/blog--relation-activity): Add/Remove, carrying :relation + :target (the edge). host/blog--emit! runs any activity through behavior/process (logged + matched). emit-content-change! (create/update) wired into form-submit + edit-submit; emit-relation! (add/remove) wired into relate-submit + unrelate-submit. DEBT #1 FIXED — per-EVENT :id (not the bare CID): content = create:/update:+cid; relation = add:/remove:+src:kind:dst (EDGE-based, because a relation change doesn't shift the CID, so a CID-based id would false-dedup different edges on one object). The activity log is now the DURABLE EVENT SOURCE (string-keyed records under 'activitylog', boot-loaded), surfaced at /activities — what TA will push to peers. LIVE PROOF (blog.rose-ash.com): publish → /activities 'create article <cid>'; relate → 'add article p2-events — add welcome related'; unrelate → 'remove …'. blog 217/217 (+4 P2, reframed P0.3 fire tests for Update semantics), full host conformance 614/614. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1174,13 +1174,14 @@
|
||||
;; ── 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 "P0.4: publish-activity is the CANONICAL seam shape (:verb :object=cid :object-type :slug :category :id)"
|
||||
(host-bl-test "P0.4/P2: publish-activity is the CANONICAL seam shape (:verb :object=cid :object-type :slug :category); :id is per-event (create:cid)"
|
||||
(begin
|
||||
(host/blog-put! "pub1" "Pub One" "(article (h1 \"P\"))" "published")
|
||||
(host/blog-relate! "pub1" "article" "is-a")
|
||||
(host/blog--set-field-values! "pub1" {"category" "newsletter"})
|
||||
(let ((a (host/blog--publish-activity "pub1")))
|
||||
(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))))))
|
||||
(= (get a :id) (str "create:" (get a :object))) (not (= (get a :object) (get a :id))))))
|
||||
(list "create" "article" "newsletter" "pub1" true true))
|
||||
(host-bl-test "publish-activity category falls back to a tag, else urgent"
|
||||
(begin
|
||||
@@ -1213,6 +1214,35 @@
|
||||
(host/blog--maybe-publish! "p04r" "draft" "published") ;; then the transition
|
||||
(map (fn (e) (get e "verb")) host/blog--flow-log))
|
||||
(list "validate" "digest")) ;; newsletter→digest, not stale→notify
|
||||
;; ── P2: state-change → activity emission (ALL events, not just publish) ──
|
||||
(host-bl-test "P2: a relate emits an ADD activity with an EDGE-based id (DEBT #1 — no CID collision)"
|
||||
(begin
|
||||
(set! host/blog--activity-log (list))
|
||||
(host/blog-put! "p2r" "R" "(article (h1 \"r\"))" "published")
|
||||
(host/blog--emit-relation! "add" "p2r" "tagged" "urgent")
|
||||
(let ((a (first host/blog--activity-log)))
|
||||
(list (get a "verb") (get a "id"))))
|
||||
(list "add" "add:p2r:tagged:urgent"))
|
||||
(host-bl-test "P2: an unrelate emits a REMOVE activity (edge id, no CID)"
|
||||
(begin
|
||||
(set! host/blog--activity-log (list))
|
||||
(host/blog--emit-relation! "remove" "p2r" "tagged" "urgent")
|
||||
(let ((a (first host/blog--activity-log)))
|
||||
(list (get a "verb") (get a "id"))))
|
||||
(list "remove" "remove:p2r:tagged:urgent"))
|
||||
(host-bl-test "P2: relation :id ≠ content :id — DIFFERENT edges on one object don't collide"
|
||||
(list (get (host/blog--relation-activity "add" "x" "tagged" "a") :id)
|
||||
(get (host/blog--relation-activity "add" "x" "tagged" "b") :id))
|
||||
(list "add:x:tagged:a" "add:x:tagged:b"))
|
||||
(host-bl-test "P2: the activity log is DURABLE (round-trips through the store)"
|
||||
(begin
|
||||
(set! host/blog--activity-log (list))
|
||||
(persist/backend-kv-put host/blog-store host/blog--activitylog-key (list))
|
||||
(host/blog--emit-relation! "add" "p2d" "related" "p2e")
|
||||
(let ((before (len host/blog--activity-log)))
|
||||
(begin (set! host/blog--activity-log (list)) (host/blog-load-activitylog!)
|
||||
(list before (len host/blog--activity-log)))))
|
||||
(list 1 1))
|
||||
;; 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"
|
||||
@@ -1235,27 +1265,34 @@
|
||||
;; execute-fold runner + on-publish registry + transport + host driver) → effects land in the flow log.
|
||||
(set! host/blog--flow-log (list))
|
||||
(set! host/blog--activity-log (list))
|
||||
(host-bl-test "P0.3: draft→published fires the publish flow through the seam → effects logged"
|
||||
(host-bl-test "P0.3: draft→published fires the CREATE flow through the seam → effects logged"
|
||||
(begin
|
||||
(host/blog-put! "p03a" "P" "(article (h1 \"x\"))" "published")
|
||||
(host/blog--set-field-values! "p03a" {"category" "newsletter"})
|
||||
(host/blog--maybe-publish! "p03a" "draft" "published")
|
||||
(list (map (fn (e) (get e :verb)) host/blog--flow-log) (len host/blog--activity-log)))
|
||||
(list (list "validate" "digest") 1))
|
||||
(host-bl-test "P0.3: published→published does NOT re-fire (fire-once on the transition)"
|
||||
(list (map (fn (e) (get e :verb)) host/blog--flow-log)
|
||||
(get (first host/blog--activity-log) "verb")))
|
||||
(list (list "validate" "digest") "create"))
|
||||
(host-bl-test "P0.3/P2: published→published emits UPDATE (not a re-fired create — no new create effects)"
|
||||
(begin
|
||||
(host/blog--maybe-publish! "p03a" "published" "published")
|
||||
(list (map (fn (e) (get e :verb)) host/blog--flow-log) (len host/blog--activity-log)))
|
||||
(list (list "validate" "digest") 1))
|
||||
(host-bl-test "P0.3: a →draft transition does not fire"
|
||||
(begin (host/blog--maybe-publish! "p03a" "published" "draft") (len host/blog--activity-log)) 1)
|
||||
(host-bl-test "P0.3: a fresh nil→published (new post) fires, urgent→notify"
|
||||
(set! host/blog--flow-log (list))
|
||||
(set! host/blog--activity-log (list))
|
||||
(host/blog--emit-content-change! "p03a" "published" "published")
|
||||
(list (len host/blog--flow-log) (get (first host/blog--activity-log) "verb")))
|
||||
(list 0 "update"))
|
||||
(host-bl-test "P0.3: a →draft transition emits nothing (unobservable)"
|
||||
(begin
|
||||
(set! host/blog--activity-log (list))
|
||||
(host/blog--emit-content-change! "p03a" "published" "draft")
|
||||
(len host/blog--activity-log)) 0)
|
||||
(host-bl-test "P0.3: a fresh nil→published (new post) fires create, urgent→notify"
|
||||
(begin
|
||||
(set! host/blog--flow-log (list))
|
||||
(host/blog-put! "p03b" "U" "(article (h1 \"u\"))" "published")
|
||||
(host/blog--set-field-values! "p03b" {"category" "urgent"})
|
||||
(host/blog--maybe-publish! "p03b" nil "published")
|
||||
(map (fn (e) (get e "verb")) host/blog--flow-log))
|
||||
(list "validate" "digest" "validate" "notify"))
|
||||
(list "validate" "notify"))
|
||||
;; P0.3b: the flow log is DURABLE — it round-trips through the blog store (survives a restart).
|
||||
(host-bl-test "P0.3b: the flow log persists + reloads from the store (string-keyed, no split)"
|
||||
(begin
|
||||
|
||||
Reference in New Issue
Block a user