Merge loops/artdag into architecture: job-as-post-object projection
lib/artdag/post.sx (job<->feed post object, post-id = content-id, self-verifying wire, post-run for peers) per the host loop's 'jobs as posts' direction. Additive. artdag 225/225. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
68
lib/artdag/post.sx
Normal 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))))
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
111
lib/artdag/tests/post.sx
Normal 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)
|
||||
@@ -31,19 +31,19 @@ edges.
|
||||
|
||||
## Status (rolling)
|
||||
|
||||
`bash lib/artdag/conformance.sh` → **213/213** (12 suites: dag, analyze, plan, execute, optimize, fed, cost, serialize, stats, fault, maude-optimize, schedule)
|
||||
`bash lib/artdag/conformance.sh` → **225/225** (13 suites: dag, analyze, plan, execute, optimize, fed, cost, serialize, stats, fault, post, maude-optimize, schedule)
|
||||
|
||||
Base roadmap (Phases 1–6) COMPLETE + Phase 7 (maude rule-based optimization) COMPLETE
|
||||
(only optional miniKanren scheduling remains). Now hardening only.
|
||||
|
||||
## Integration / merge status (2026-06-28)
|
||||
|
||||
**MERGED to architecture (Phase 7) at `b0d845bb`** — local `architecture` now carries
|
||||
`lib/maude` + artdag Phase 7, conformance green there (198/198 at merge time). The merge
|
||||
was clean/additive; `architecture` itself was NOT pushed (pushing it triggers a large dev
|
||||
reload — a deliberate separate call). **NOTE: re-merge needed** — `loops/artdag` has since
|
||||
added `lib/artdag/schedule.sx` (miniKanren CLP(FD) scheduler, 213/213), not yet on
|
||||
architecture.
|
||||
**MERGED to architecture, fully up to date.** Phase 7 merged at `b0d845bb`; the miniKanren
|
||||
CLP(FD) scheduler re-merged at `fdd0c8f7` (architecture HEAD). Local `architecture` now
|
||||
carries `lib/maude` + the complete artdag engine, conformance green there (213/213). Both
|
||||
merges were clean/additive (only artdag-scoped files). `architecture` itself was NOT pushed
|
||||
to origin — pushing it triggers a large dev reload, a deliberate separate call left to a
|
||||
maintainer.
|
||||
|
||||
(Historical, for the Phase 7 merge above:) `origin/architecture`'s `lib/artdag/`
|
||||
is stale — it predates the maude-bridge, so it is missing ALL of Phase 7
|
||||
@@ -104,6 +104,28 @@ lib/artdag/optimize.sx lib/artdag/federation.sx
|
||||
CSE (free from content-addressing) result import/export + provenance/trust
|
||||
```
|
||||
|
||||
## Forward direction — artdag jobs as a feed "post" (per the host loop)
|
||||
|
||||
artdag jobs will become a **type of post**: a job rides as the `:object` of a feed
|
||||
activity (`{:actor :verb :object :at :tags}`, `:verb` "post"), served through the host
|
||||
loop's `GET/POST /feed`. The engine already has the primitives; the wrapping lives on the
|
||||
**host/feed** side, NOT in `lib/artdag`:
|
||||
- **content-id = the post/object id** (stable structural digest = natural AP object id).
|
||||
- **wire format = the post payload** — `dag->wire`/`dag->string` are self-describing +
|
||||
content-addressed and survive `write-to-string`/`read`; `wire-verify` re-checks ids on
|
||||
receipt (self-verifying embeddable object).
|
||||
- **federation already mirrors the feed** — `fed-export`/`fed-import` are trust-gated with
|
||||
provenance; a re-posted job dedupes/cache-hits by global content-id.
|
||||
|
||||
Boundary: host loop owns the activity wrapping (actor/verb/at/tags); `lib/artdag` owns the
|
||||
job⇄object projection. **BUILT — `lib/artdag/post.sx`** (post suite 12/12): a post object is
|
||||
`{:type "artdag/job" :id <output content-id> :wire <dag->wire>}` — `job->post-object`,
|
||||
`post-object->job`, `post-object-verify` (wire ids intact + the post id is produced by the
|
||||
job; rejects tampered params and bogus ids), `job->post-string`/`post-string->object` for the
|
||||
feed/SXTP body, and `post-run` (a peer decodes → runs → result at the post id, content-address
|
||||
cache-hitting what it already has). The host loop drops the object into a feed activity's
|
||||
`:object`; post-id = content-id = the AP object id.
|
||||
|
||||
## Phase 1 — DAG model + content addressing
|
||||
|
||||
- [x] `lib/artdag/dag.sx` — node `{:op :inputs :params}`; structural content-id =
|
||||
@@ -219,6 +241,19 @@ be an op token.
|
||||
|
||||
## Progress log
|
||||
|
||||
- **2026-06-28 Forward — job as a feed post object** (post suite 12/12, total 225/225).
|
||||
`lib/artdag/post.sx`: the artdag-side projection for "a job is a type of post" (per the
|
||||
host loop). `job->post-object dag output-name` → `{:type "artdag/job" :id <content-id of
|
||||
output> :wire <dag->wire>}` — the post/object id IS the output node's content-id (= the
|
||||
natural AP object id), the body is serialize.sx's self-describing wire. `post-object-verify`
|
||||
binds the claimed id to the payload (every record's id recomputes + the post id is present
|
||||
among them) — rejects a param tampered under a stale id and a bogus post id.
|
||||
`job->post-string`/`post-string->object` carry it in a feed activity / SXTP body;
|
||||
`post-object->job` decodes; `post-run post runner cache` lets a peer decode → run → read the
|
||||
result at the post id (content-addressed, so a warm peer cache-hits what it already holds).
|
||||
The activity wrapper (actor/verb/at/tags) stays on the host/feed side — this is only
|
||||
job⇄object. Reuses serialize.sx + execute.sx; no new substrate.
|
||||
|
||||
- **2026-06-28 Phase 3/7 — miniKanren CLP(FD) scheduler** (schedule suite 15/15, total
|
||||
213/213). `lib/artdag/schedule.sx` on `lib/minikanren` (read-only substrate): each node
|
||||
gets a slot var in `[1..max-slots]`, every edge `(input->node)` imposes `fd-lt
|
||||
|
||||
Reference in New Issue
Block a user