Files
rose-ash/lib/relations/engine.sx
giles 1dacb0c8dd
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 39s
relations: Phase 4 federation (erel trust-gating, peer_rel/trust, fed-sx mock transport, revocation) + 22 tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:14:38 +00:00

147 lines
4.5 KiB
Plaintext

;; lib/relations/engine.sx — recursive reachability + roots/leaves + cycles.
;;
;; The whole engine is one Datalog ruleset, derived from the EFFECTIVE relation
;; `erel`, not raw `rel`. `erel` unions local edges with trust-gated federated
;; edges:
;;
;; erel(S,D,K) :- rel(S,D,K). ; local edge, always
;; erel(S,D,K) :- peer_rel(P,S,D,K), trust(P). ; peer edge, gated by trust
;;
;; Trust is a body literal, re-checked every query, so revoking trust (or a peer
;; link) takes effect on the next saturation. Trust is NOT transitive — only a
;; peer's own links, under a local trust(P) fact, bind. With no peer_rel/trust
;; facts, erel ≡ rel, so non-federated behaviour is unchanged.
;;
;; Reachability is the bottom-up transitive closure acl-on-sx uses for
;; inheritance, parameterised by Kind so closures never leak across kinds:
;;
;; reach(K,X,Y) :- erel(X,Y,K). ; one hop
;; reach(K,X,Y) :- erel(X,Z,K), reach(K,Z,Y). ; transitive
;;
;; `reach_any` is the kind-agnostic closure (any edge, any kind) used for
;; mixed-kind reachability — distinct from single-kind `reach`.
;;
;; rnode collects the nodes touched by a kind; root/leaf are those with no
;; incoming / no outgoing edge (stratified negation over has_parent/has_child).
;; Cycles are ordinary data: `reach(K,X,X)` simply holds for nodes on a cycle —
;; cycle?/acyclic? are queries, not errors. Do not assume a DAG.
(define
relations-rules
(quote
((erel S D K <- (rel S D K))
(erel S D K <- (peer_rel P S D K) (trust P))
(reach K X Y <- (erel X Y K))
(reach K X Y <- (erel X Z K) (reach K Z Y))
(reach_any X Y <- (erel X Y K))
(reach_any X Y <- (erel X Z K) (reach_any Z Y))
(rnode K X <- (erel X Y K))
(rnode K Y <- (erel X Y K))
(has_parent K Y <- (erel X Y K))
(has_child K X <- (erel X Y K))
(root K X <- (rnode K X) {:neg (has_parent K X)})
(leaf K X <- (rnode K X) {:neg (has_child K X)}))))
;; Pull one column (by keyword key) out of a list of substitution dicts.
(define
relations-pluck
(fn (substs key) (map (fn (s) (get s key)) substs)))
;; Direct children: every Dst with an effective edge erel(node, Dst, kind).
(define
relations-children-of
(fn
(db node kind)
(relations-pluck
(dl-query db (list (quote erel) node (quote Dst) kind))
:Dst)))
;; Direct parents: every Src with an effective edge erel(Src, node, kind).
(define
relations-parents-of
(fn
(db node kind)
(relations-pluck
(dl-query db (list (quote erel) (quote Src) node kind))
:Src)))
;; Directly related: neighbours in either direction under kind.
(define
relations-related
(fn
(db node kind)
(append
(relations-children-of db node kind)
(relations-parents-of db node kind))))
;; Transitive descendants of node under kind (everything reachable forward).
(define
relations-descendants
(fn
(db node kind)
(relations-pluck
(dl-query db (list (quote reach) kind node (quote Y)))
:Y)))
;; Transitive ancestors of node under kind (everything that reaches node).
(define
relations-ancestors
(fn
(db node kind)
(relations-pluck
(dl-query db (list (quote reach) kind (quote X) node))
:X)))
;; Is b reachable from a under kind (transitive)?
(define
relations-reachable?
(fn
(db a b kind)
(> (len (dl-query db (list (quote reach) kind a b))) 0)))
;; Mixed-kind: descendants reachable from node over edges of ANY kind.
(define
relations-descendants-any
(fn
(db node)
(relations-pluck
(dl-query db (list (quote reach_any) node (quote Y)))
:Y)))
;; Mixed-kind: is b reachable from a over edges of ANY kind?
(define
relations-reachable-any?
(fn
(db a b)
(> (len (dl-query db (list (quote reach_any) a b))) 0)))
;; Roots: nodes touched by kind with no incoming edge.
(define
relations-roots
(fn
(db kind)
(relations-pluck (dl-query db (list (quote root) kind (quote X))) :X)))
;; Leaves: nodes touched by kind with no outgoing edge.
(define
relations-leaves
(fn
(db kind)
(relations-pluck (dl-query db (list (quote leaf) kind (quote X))) :X)))
;; Is node on a cycle under kind (reachable from itself)?
(define
relations-cycle?
(fn
(db node kind)
(> (len (dl-query db (list (quote reach) kind node node))) 0)))
;; Has the kind any cycle at all? (no node reaches itself)
(define
relations-acyclic?
(fn
(db kind)
(=
(len (dl-query db (list (quote reach) kind (quote X) (quote X))))
0)))