artdag: job-as-post-object projection (post.sx) + 12 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 47s

lib/artdag/post.sx — the artdag-side projection for 'a job is a type of post' (per the
host loop). job->post-object: {:type artdag/job :id <output content-id> :wire <dag->wire>},
post-id = content-id = natural AP object id. post-object-verify binds the id to the payload
(record ids recompute + post id present), rejecting tampered params/bogus ids. String
transport for the feed/SXTP body; post-run lets a peer decode->run->result, content-address
cache-hitting. Activity wrapping stays host-side. post 12/12, total 225/225.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 18:38:34 +00:00
parent 550d0db5a5
commit 0b7b3b9efb
6 changed files with 208 additions and 9 deletions

View File

@@ -13,7 +13,7 @@ if [ ! -x "$SX_SERVER" ]; then
exit 1
fi
SUITES=(dag analyze plan execute optimize fed cost serialize stats fault maude-optimize schedule)
SUITES=(dag analyze plan execute optimize fed cost serialize stats fault post maude-optimize schedule)
OUT_JSON="lib/artdag/scoreboard.json"
OUT_MD="lib/artdag/scoreboard.md"
@@ -96,6 +96,7 @@ ${MK_LOADS}
(load "lib/artdag/serialize.sx")
(load "lib/artdag/stats.sx")
(load "lib/artdag/fault.sx")
(load "lib/artdag/post.sx")
(load "lib/artdag/api.sx")
${BRIDGE_LOAD}
${SCHED_LOAD}

68
lib/artdag/post.sx Normal file
View File

@@ -0,0 +1,68 @@
; lib/artdag/post.sx — project an artdag job to/from a feed "post object", so a job
; can ride as the :object of a feed activity ({:actor :verb :object :at :tags}) per the
; host loop. A post object is content-addressed and self-verifying:
; {:type "artdag/job" :id <content-id of the output node> :wire <dag->wire>}
; The :id IS the post/object id (the stable structural digest = natural AP object id);
; the :wire is the self-describing, write/read-safe payload from serialize.sx whose
; records each carry their own content-id. The dag<->feed-activity wrapping (actor/verb/
; at/tags) stays on the host/feed side; this file is only the job<->object projection.
; Depends on dag.sx + serialize.sx (and execute.sx for post-run).
(define artdag/post-type "artdag/job")
; a job = a dag + the output node (by author name) the post is "about".
(define artdag/job->post-object (fn (dag output-name) {:id (artdag/dag-id dag output-name) :type artdag/post-type :wire (artdag/dag->wire dag)}))
(define
artdag/post-object?
(fn
(x)
(and
(= (type-of x) "dict")
(= (get x :type) artdag/post-type)
(has-key? x :id)
(has-key? x :wire))))
(define artdag/post-object-id (fn (post) (get post :id)))
(define artdag/post-object-wire (fn (post) (get post :wire)))
; integrity: the payload's records each verify (id == recomputed content-id) AND the
; claimed post id is actually produced by the job (present among the wire records).
(define
artdag/post-object-verify
(fn
(post)
(and
(artdag/post-object? post)
(artdag/wire-verify (get post :wire))
(artdag/member?
(get post :id)
(map (fn (rec) (nth rec 0)) (get post :wire))))))
; decode the payload back into a runnable dag (pure; verify separately, mirroring
; serialize.sx's wire->dag / wire-verify split).
(define
artdag/post-object->job
(fn (post) (artdag/wire->dag (get post :wire))))
; ---- string transport (drop into a feed activity / SXTP body) ----
(define
artdag/job->post-string
(fn
(dag output-name)
(write-to-string (artdag/job->post-object dag output-name))))
(define artdag/post-string->object (fn (s) (read (open-input-string s))))
; ---- run a received post: decode -> run -> result at the post id ----
; the peer recomputes the job (content-addressed, so a warm cache hits everything it
; already has). Returns the result of the output node the post is about.
(define
artdag/post-run
(fn
(post runner cache)
(artdag/result-of
(artdag/run (artdag/post-object->job post) runner cache)
(artdag/post-object-id post))))

View File

@@ -10,10 +10,11 @@
"serialize": {"pass": 13, "fail": 0},
"stats": {"pass": 12, "fail": 0},
"fault": {"pass": 14, "fail": 0},
"post": {"pass": 12, "fail": 0},
"maude-optimize": {"pass": 40, "fail": 0},
"schedule": {"pass": 15, "fail": 0}
},
"total_pass": 213,
"total_pass": 225,
"total_fail": 0,
"total": 213
"total": 225
}

View File

@@ -14,6 +14,7 @@ _Generated by `lib/artdag/conformance.sh`_
| serialize | 13 | 0 | 13 |
| stats | 12 | 0 | 12 |
| fault | 14 | 0 | 14 |
| post | 12 | 0 | 12 |
| maude-optimize | 40 | 0 | 40 |
| schedule | 15 | 0 | 15 |
| **Total** | **213** | **0** | **213** |
| **Total** | **225** | **0** | **225** |

111
lib/artdag/tests/post.sx Normal file
View File

@@ -0,0 +1,111 @@
; Forward direction — artdag job as a feed "post object" (per the host loop).
; A job projects to a content-addressed, self-verifying object suitable as a feed
; activity :object; a peer decodes, verifies and runs it to the same result.
(define po-runner (artdag/op-table-runner {:blur (fn (params inputs) (+ (first inputs) (get params :radius))) :src (fn (params inputs) 0) :over (fn (params inputs) (+ (nth inputs 0) (nth inputs 1)))}))
(define
po-job
(artdag/build
(list
(list "s" "src" (list) {})
(list "b" "blur" (list "s") {:radius 2})
(list "c" "blur" (list "s") {:radius 3})
(list "out" "over" (list "b" "c") {} true))))
(define po-out-id (artdag/dag-id po-job "out"))
(define po-post (artdag/job->post-object po-job "out"))
(artdag-test
"post: is a well-formed post object"
(artdag/post-object? po-post)
true)
(artdag-test "post: type tag is artdag/job" (get po-post :type) "artdag/job")
(artdag-test
"post: post id is the output node's content-id"
(artdag/post-object-id po-post)
po-out-id)
(artdag-test
"post: payload is the whole dag (one record per node)"
(len (artdag/post-object-wire po-post))
(artdag/node-count po-job))
(artdag-test
"post: verifies (ids intact, output present)"
(artdag/post-object-verify po-post)
true)
; ---- round-trip: decode reconstructs the job by content-id ----
(define po-job2 (artdag/post-object->job po-post))
(artdag-test
"post: decoded job contains the output node by content-id"
(artdag/member? po-out-id (keys (artdag/dag-nodes po-job2)))
true)
(artdag-test
"post: decoded job has the same node count"
(artdag/node-count po-job2)
(artdag/node-count po-job))
; ---- string transport (feed activity / SXTP body) ----
(define po-str (artdag/job->post-string po-job "out"))
(define po-post2 (artdag/post-string->object po-str))
(artdag-test
"post: survives string transport (id preserved)"
(artdag/post-object-id po-post2)
po-out-id)
(artdag-test
"post: transported post still verifies"
(artdag/post-object-verify po-post2)
true)
; ---- a peer runs the received post to the same result ----
(define
po-local-result
(artdag/result-of (artdag/run po-job po-runner (persist/open)) po-out-id))
(define po-peer-result (artdag/post-run po-post2 po-runner (persist/open)))
(artdag-test
"post: peer runs the received job to the same result"
(= po-peer-result po-local-result)
true)
; ---- tamper detection: mutate a param under a stale id ----
(define
po-tampered
(assoc
po-post
:wire (map
(fn
(rec)
(if
(= (nth rec 1) "blur")
(list
(nth rec 0)
(nth rec 1)
(nth rec 2)
{:radius 99}
(nth rec 4))
rec))
(artdag/post-object-wire po-post))))
(artdag-test
"post: tampered payload fails verification"
(artdag/post-object-verify po-tampered)
false)
; ---- an id not produced by the job fails verification ----
(artdag-test
"post: post id absent from payload fails verification"
(artdag/post-object-verify (assoc po-post :id "node:bogus"))
false)