datalog: rose-ash demo programs (Phase 10 syntactic, 153/153)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
New lib/datalog/demo.sx with three Datalog-as-query-language demos
over synthetic rose-ash data:
Federation: (mutual A B), (reachable A B), (foaf A C) over a
follows graph.
Content: (post-likes P N) via count aggregation, (popular P)
for likes >= 3, (interesting Me P) joining follows
+ authored + popular.
Permissions: (in-group A G) over transitive subgroup chains,
(can-access A R).
10 tests run each program against in-memory EDB tuples loaded via
dl-program-data.
Wiring to PostgreSQL and exposing as a service endpoint (/internal
/datalog) is out of scope for this loop — both would require
edits outside lib/datalog/. Programs above document the EDB shape
a real loader would populate.
This commit is contained in:
@@ -13,6 +13,7 @@ PRELOADS=(
|
|||||||
lib/datalog/strata.sx
|
lib/datalog/strata.sx
|
||||||
lib/datalog/eval.sx
|
lib/datalog/eval.sx
|
||||||
lib/datalog/api.sx
|
lib/datalog/api.sx
|
||||||
|
lib/datalog/demo.sx
|
||||||
)
|
)
|
||||||
|
|
||||||
SUITES=(
|
SUITES=(
|
||||||
@@ -25,4 +26,5 @@ SUITES=(
|
|||||||
"negation:lib/datalog/tests/negation.sx:(dl-negation-tests-run!)"
|
"negation:lib/datalog/tests/negation.sx:(dl-negation-tests-run!)"
|
||||||
"aggregates:lib/datalog/tests/aggregates.sx:(dl-aggregates-tests-run!)"
|
"aggregates:lib/datalog/tests/aggregates.sx:(dl-aggregates-tests-run!)"
|
||||||
"api:lib/datalog/tests/api.sx:(dl-api-tests-run!)"
|
"api:lib/datalog/tests/api.sx:(dl-api-tests-run!)"
|
||||||
|
"demo:lib/datalog/tests/demo.sx:(dl-demo-tests-run!)"
|
||||||
)
|
)
|
||||||
|
|||||||
75
lib/datalog/demo.sx
Normal file
75
lib/datalog/demo.sx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
;; lib/datalog/demo.sx — example programs over rose-ash-shaped data.
|
||||||
|
;;
|
||||||
|
;; Phase 10 prototypes Datalog as a rose-ash query language. Wiring
|
||||||
|
;; the EDB to actual PostgreSQL is out of scope for this loop (it
|
||||||
|
;; would touch service code outside lib/datalog/), but the programs
|
||||||
|
;; below show the shape of queries we want, and the test suite runs
|
||||||
|
;; them against synthetic in-memory tuples loaded via dl-program-data.
|
||||||
|
;;
|
||||||
|
;; Three thematic demos:
|
||||||
|
;;
|
||||||
|
;; 1. Federation — follow graph, transitive reach, mutuals.
|
||||||
|
;; 2. Content — posts, tags, likes, popularity, "for you" feed.
|
||||||
|
;; 3. Permissions — group membership and resource access.
|
||||||
|
|
||||||
|
;; ── Demo 1: federation follow graph ─────────────────────────────
|
||||||
|
;; EDB: (follows ACTOR-A ACTOR-B) — A follows B.
|
||||||
|
;; IDB:
|
||||||
|
;; (mutual A B) — A follows B and B follows A
|
||||||
|
;; (reachable A B) — transitive follow closure
|
||||||
|
;; (foaf A C) — friend of a friend (mutual filter)
|
||||||
|
(define
|
||||||
|
dl-demo-federation-rules
|
||||||
|
(quote
|
||||||
|
((mutual A B <- (follows A B) (follows B A))
|
||||||
|
(reachable A B <- (follows A B))
|
||||||
|
(reachable A C <- (follows A B) (reachable B C))
|
||||||
|
(foaf A C <- (follows A B) (follows B C) (!= A C)))))
|
||||||
|
|
||||||
|
;; ── Demo 2: content recommendation ──────────────────────────────
|
||||||
|
;; EDB:
|
||||||
|
;; (authored ACTOR POST)
|
||||||
|
;; (tagged POST TAG)
|
||||||
|
;; (liked ACTOR POST)
|
||||||
|
;; IDB:
|
||||||
|
;; (post-likes POST N) — count of likes per post
|
||||||
|
;; (popular POST) — posts with >= 3 likes
|
||||||
|
;; (tagged-by-mutual ACTOR POST) — post tagged TOPIC by someone
|
||||||
|
;; A's mutuals follow.
|
||||||
|
(define
|
||||||
|
dl-demo-content-rules
|
||||||
|
(quote
|
||||||
|
((post-likes P N <- (authored Author P) (count N L (liked L P)))
|
||||||
|
(popular P <- (authored Author P) (post-likes P N) (>= N 3))
|
||||||
|
(interesting Me P
|
||||||
|
<-
|
||||||
|
(follows Me Buddy)
|
||||||
|
(authored Buddy P)
|
||||||
|
(popular P)))))
|
||||||
|
|
||||||
|
;; ── Demo 3: role-based permissions ──────────────────────────────
|
||||||
|
;; EDB:
|
||||||
|
;; (member ACTOR GROUP)
|
||||||
|
;; (subgroup CHILD PARENT)
|
||||||
|
;; (allowed GROUP RESOURCE)
|
||||||
|
;; IDB:
|
||||||
|
;; (in-group ACTOR GROUP) — direct or via subgroup chain
|
||||||
|
;; (can-access ACTOR RESOURCE) — actor inherits group permission
|
||||||
|
(define
|
||||||
|
dl-demo-perm-rules
|
||||||
|
(quote
|
||||||
|
((in-group A G <- (member A G))
|
||||||
|
(in-group A G <- (member A H) (subgroup-trans H G))
|
||||||
|
(subgroup-trans X Y <- (subgroup X Y))
|
||||||
|
(subgroup-trans X Z <- (subgroup X Y) (subgroup-trans Y Z))
|
||||||
|
(can-access A R <- (in-group A G) (allowed G R)))))
|
||||||
|
|
||||||
|
;; ── Loader stub ──────────────────────────────────────────────────
|
||||||
|
;; Wiring to PostgreSQL would replace these helpers with calls into
|
||||||
|
;; rose-ash's internal HTTP RPC (fetch_data → /internal/data/...).
|
||||||
|
;; The shape returned by dl-load-from-edb! is the same in either case.
|
||||||
|
(define
|
||||||
|
dl-demo-make
|
||||||
|
(fn
|
||||||
|
(facts rules)
|
||||||
|
(dl-program-data facts rules)))
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"lang": "datalog",
|
"lang": "datalog",
|
||||||
"total_passed": 143,
|
"total_passed": 153,
|
||||||
"total_failed": 0,
|
"total_failed": 0,
|
||||||
"total": 143,
|
"total": 153,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"tokenize","passed":26,"failed":0,"total":26},
|
{"name":"tokenize","passed":26,"failed":0,"total":26},
|
||||||
{"name":"parse","passed":18,"failed":0,"total":18},
|
{"name":"parse","passed":18,"failed":0,"total":18},
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
{"name":"semi_naive","passed":8,"failed":0,"total":8},
|
{"name":"semi_naive","passed":8,"failed":0,"total":8},
|
||||||
{"name":"negation","passed":10,"failed":0,"total":10},
|
{"name":"negation","passed":10,"failed":0,"total":10},
|
||||||
{"name":"aggregates","passed":10,"failed":0,"total":10},
|
{"name":"aggregates","passed":10,"failed":0,"total":10},
|
||||||
{"name":"api","passed":9,"failed":0,"total":9}
|
{"name":"api","passed":9,"failed":0,"total":9},
|
||||||
|
{"name":"demo","passed":10,"failed":0,"total":10}
|
||||||
],
|
],
|
||||||
"generated": "2026-05-08T08:33:54+00:00"
|
"generated": "2026-05-08T08:45:37+00:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# datalog scoreboard
|
# datalog scoreboard
|
||||||
|
|
||||||
**143 / 143 passing** (0 failure(s)).
|
**153 / 153 passing** (0 failure(s)).
|
||||||
|
|
||||||
| Suite | Passed | Total | Status |
|
| Suite | Passed | Total | Status |
|
||||||
|-------|--------|-------|--------|
|
|-------|--------|-------|--------|
|
||||||
@@ -13,3 +13,4 @@
|
|||||||
| negation | 10 | 10 | ok |
|
| negation | 10 | 10 | ok |
|
||||||
| aggregates | 10 | 10 | ok |
|
| aggregates | 10 | 10 | ok |
|
||||||
| api | 9 | 9 | ok |
|
| api | 9 | 9 | ok |
|
||||||
|
| demo | 10 | 10 | ok |
|
||||||
|
|||||||
202
lib/datalog/tests/demo.sx
Normal file
202
lib/datalog/tests/demo.sx
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
;; lib/datalog/tests/demo.sx — Phase 10 demo programs.
|
||||||
|
|
||||||
|
(define dl-demo-pass 0)
|
||||||
|
(define dl-demo-fail 0)
|
||||||
|
(define dl-demo-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
dl-demo-deep=?
|
||||||
|
(fn
|
||||||
|
(a b)
|
||||||
|
(cond
|
||||||
|
((and (list? a) (list? b))
|
||||||
|
(and (= (len a) (len b)) (dl-demo-deq-l? a b 0)))
|
||||||
|
((and (dict? a) (dict? b))
|
||||||
|
(let ((ka (keys a)) (kb (keys b)))
|
||||||
|
(and (= (len ka) (len kb)) (dl-demo-deq-d? a b ka 0))))
|
||||||
|
((and (number? a) (number? b)) (= a b))
|
||||||
|
(else (equal? a b)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
dl-demo-deq-l?
|
||||||
|
(fn
|
||||||
|
(a b i)
|
||||||
|
(cond
|
||||||
|
((>= i (len a)) true)
|
||||||
|
((not (dl-demo-deep=? (nth a i) (nth b i))) false)
|
||||||
|
(else (dl-demo-deq-l? a b (+ i 1))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
dl-demo-deq-d?
|
||||||
|
(fn
|
||||||
|
(a b ka i)
|
||||||
|
(cond
|
||||||
|
((>= i (len ka)) true)
|
||||||
|
((let ((k (nth ka i)))
|
||||||
|
(not (dl-demo-deep=? (get a k) (get b k))))
|
||||||
|
false)
|
||||||
|
(else (dl-demo-deq-d? a b ka (+ i 1))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
dl-demo-set=?
|
||||||
|
(fn
|
||||||
|
(a b)
|
||||||
|
(and
|
||||||
|
(= (len a) (len b))
|
||||||
|
(dl-demo-subset? a b)
|
||||||
|
(dl-demo-subset? b a))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
dl-demo-subset?
|
||||||
|
(fn
|
||||||
|
(xs ys)
|
||||||
|
(cond
|
||||||
|
((= (len xs) 0) true)
|
||||||
|
((not (dl-demo-contains? ys (first xs))) false)
|
||||||
|
(else (dl-demo-subset? (rest xs) ys)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
dl-demo-contains?
|
||||||
|
(fn
|
||||||
|
(xs target)
|
||||||
|
(cond
|
||||||
|
((= (len xs) 0) false)
|
||||||
|
((dl-demo-deep=? (first xs) target) true)
|
||||||
|
(else (dl-demo-contains? (rest xs) target)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
dl-demo-test-set!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(if
|
||||||
|
(dl-demo-set=? got expected)
|
||||||
|
(set! dl-demo-pass (+ dl-demo-pass 1))
|
||||||
|
(do
|
||||||
|
(set! dl-demo-fail (+ dl-demo-fail 1))
|
||||||
|
(append!
|
||||||
|
dl-demo-failures
|
||||||
|
(str
|
||||||
|
name
|
||||||
|
"\n expected (set): " expected
|
||||||
|
"\n got: " got))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
dl-demo-run-all!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(do
|
||||||
|
;; ── Federation ──────────────────────────────────────────
|
||||||
|
(dl-demo-test-set! "mutuals"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote ((follows alice bob) (follows bob alice)
|
||||||
|
(follows bob carol) (follows carol dave)))
|
||||||
|
dl-demo-federation-rules)
|
||||||
|
(quote (mutual alice X)))
|
||||||
|
(list {:X (quote bob)}))
|
||||||
|
|
||||||
|
(dl-demo-test-set! "reachable transitive"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote ((follows alice bob) (follows bob carol) (follows carol dave)))
|
||||||
|
dl-demo-federation-rules)
|
||||||
|
(quote (reachable alice X)))
|
||||||
|
(list {:X (quote bob)} {:X (quote carol)} {:X (quote dave)}))
|
||||||
|
|
||||||
|
(dl-demo-test-set! "foaf"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote ((follows alice bob) (follows bob carol) (follows alice dave)))
|
||||||
|
dl-demo-federation-rules)
|
||||||
|
(quote (foaf alice X)))
|
||||||
|
(list {:X (quote carol)}))
|
||||||
|
|
||||||
|
;; ── Content ─────────────────────────────────────────────
|
||||||
|
(dl-demo-test-set! "popular posts"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote
|
||||||
|
((authored alice p1) (authored bob p2) (authored carol p3)
|
||||||
|
(liked u1 p1) (liked u2 p1) (liked u3 p1)
|
||||||
|
(liked u1 p2)))
|
||||||
|
dl-demo-content-rules)
|
||||||
|
(quote (popular P)))
|
||||||
|
(list {:P (quote p1)}))
|
||||||
|
|
||||||
|
(dl-demo-test-set! "interesting feed"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote
|
||||||
|
((follows me alice) (follows me bob)
|
||||||
|
(authored alice p1) (authored bob p2)
|
||||||
|
(liked u1 p1) (liked u2 p1) (liked u3 p1)
|
||||||
|
(liked u4 p2)))
|
||||||
|
dl-demo-content-rules)
|
||||||
|
(quote (interesting me P)))
|
||||||
|
(list {:P (quote p1)}))
|
||||||
|
|
||||||
|
(dl-demo-test-set! "post likes count"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote
|
||||||
|
((authored alice p1)
|
||||||
|
(liked u1 p1) (liked u2 p1) (liked u3 p1)))
|
||||||
|
dl-demo-content-rules)
|
||||||
|
(quote (post-likes p1 N)))
|
||||||
|
(list {:N 3}))
|
||||||
|
|
||||||
|
;; ── Permissions ─────────────────────────────────────────
|
||||||
|
(dl-demo-test-set! "direct group access"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote
|
||||||
|
((member alice editors)
|
||||||
|
(allowed editors blog)))
|
||||||
|
dl-demo-perm-rules)
|
||||||
|
(quote (can-access X blog)))
|
||||||
|
(list {:X (quote alice)}))
|
||||||
|
|
||||||
|
(dl-demo-test-set! "subgroup access"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote
|
||||||
|
((member bob writers)
|
||||||
|
(subgroup writers editors)
|
||||||
|
(allowed editors blog)))
|
||||||
|
dl-demo-perm-rules)
|
||||||
|
(quote (can-access X blog)))
|
||||||
|
(list {:X (quote bob)}))
|
||||||
|
|
||||||
|
(dl-demo-test-set! "transitive subgroup"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote
|
||||||
|
((member carol drafters)
|
||||||
|
(subgroup drafters writers)
|
||||||
|
(subgroup writers editors)
|
||||||
|
(allowed editors blog)))
|
||||||
|
dl-demo-perm-rules)
|
||||||
|
(quote (can-access X blog)))
|
||||||
|
(list {:X (quote carol)}))
|
||||||
|
|
||||||
|
(dl-demo-test-set! "no access without grant"
|
||||||
|
(dl-query
|
||||||
|
(dl-demo-make
|
||||||
|
(quote ((member dave outsiders) (allowed editors blog)))
|
||||||
|
dl-demo-perm-rules)
|
||||||
|
(quote (can-access X blog)))
|
||||||
|
(list)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
dl-demo-tests-run!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(do
|
||||||
|
(set! dl-demo-pass 0)
|
||||||
|
(set! dl-demo-fail 0)
|
||||||
|
(set! dl-demo-failures (list))
|
||||||
|
(dl-demo-run-all!)
|
||||||
|
{:passed dl-demo-pass
|
||||||
|
:failed dl-demo-fail
|
||||||
|
:total (+ dl-demo-pass dl-demo-fail)
|
||||||
|
:failures dl-demo-failures})))
|
||||||
@@ -234,15 +234,25 @@ large graphs.
|
|||||||
rose-ash ActivityPub follow relationships (Phase 10).
|
rose-ash ActivityPub follow relationships (Phase 10).
|
||||||
|
|
||||||
### Phase 10 — Datalog as a query language for rose-ash
|
### Phase 10 — Datalog as a query language for rose-ash
|
||||||
- [ ] Schema: map SQLAlchemy model relationships to Datalog EDB facts
|
- [x] Schema sketches in `lib/datalog/demo.sx`:
|
||||||
(e.g. `(follows user1 user2)`, `(authored user post)`, `(tagged post tag)`)
|
- **Federation**: `(follows A B)` → `(mutual A B)`, `(reachable A B)`,
|
||||||
- [ ] Loader: `dl-load-from-db!` — query PostgreSQL, populate Datalog EDB
|
`(foaf A C)` (friend-of-a-friend, distinct).
|
||||||
- [ ] Query examples:
|
- **Content**: `(authored A P)`, `(liked U P)`, `(tagged P T)` →
|
||||||
- `?- ancestor(me, X), authored(X, Post), tagged(Post, cooking).`
|
`(post-likes P N)` via aggregation, `(popular P)` for likes ≥ 3,
|
||||||
→ posts about cooking by people I follow (transitively)
|
`(interesting Me P)` joining follows + authored + popular.
|
||||||
- `?- popular(Post) :- tagged(Post, T), count(L, (liked(L, Post))) >= 10.`
|
- **Permissions**: `(member A G)`, `(subgroup C P)`, `(allowed G R)`
|
||||||
→ posts with 10+ likes
|
→ `(in-group A G)` over transitive subgroups, `(can-access A R)`.
|
||||||
- [ ] Expose as a rose-ash service endpoint: `POST /internal/datalog` with program + query
|
- [ ] Loader `dl-load-from-db!` — out of scope for this loop
|
||||||
|
(would need to edit `shared/services/` outside `lib/datalog/`).
|
||||||
|
Programs in `demo.sx` already document the EDB shape expected
|
||||||
|
from such a loader. `dl-program-data` consumes the same shape.
|
||||||
|
- [x] Query examples covered by `lib/datalog/tests/demo.sx` (10):
|
||||||
|
mutuals, transitive reach, FOAF, popular posts, interesting feed,
|
||||||
|
post likes count, direct/subgroup/transitive group access, no
|
||||||
|
access without grant.
|
||||||
|
- [ ] Service endpoint `POST /internal/datalog` — out of scope as above.
|
||||||
|
Once exposed, server-side handler would be `dl-program-data` +
|
||||||
|
`dl-query`, returning JSON-encoded substitutions.
|
||||||
|
|
||||||
## Blockers
|
## Blockers
|
||||||
|
|
||||||
@@ -259,6 +269,16 @@ large graphs.
|
|||||||
|
|
||||||
_Newest first._
|
_Newest first._
|
||||||
|
|
||||||
|
- 2026-05-08 — Phase 10 syntactic demo. New `lib/datalog/demo.sx`
|
||||||
|
with three programs over rose-ash-shaped data: federation
|
||||||
|
(`mutual`, `reachable`, `foaf`), content recommendation
|
||||||
|
(`post-likes` via count aggregation, `popular`, `interesting`),
|
||||||
|
and role-based permissions (`in-group` over transitive subgroups,
|
||||||
|
`can-access`). 10 demo tests pass against synthetic EDB tuples.
|
||||||
|
Postgres loader and `/internal/datalog` HTTP endpoint remain
|
||||||
|
out of scope for this loop (they need service-tree edits beyond
|
||||||
|
`lib/datalog/**`). Conformance now 153/153.
|
||||||
|
|
||||||
- 2026-05-08 — Phase 5b perf: hash-set membership in `dl-add-fact!`.
|
- 2026-05-08 — Phase 5b perf: hash-set membership in `dl-add-fact!`.
|
||||||
db gains a parallel `:facts-keys {<rel>: {<tuple-string>: true}}`
|
db gains a parallel `:facts-keys {<rel>: {<tuple-string>: true}}`
|
||||||
index alongside `:facts`. `dl-tuple-key` derives a stable string
|
index alongside `:facts`. `dl-tuple-key` derives a stable string
|
||||||
|
|||||||
Reference in New Issue
Block a user