Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.0 KiB
relations-on-sx: Cross-domain relationship graph on Datalog
rose-ash's internal relations service tracks parent/child and peer relationships
across domains — a blog post's comments, a thread's replies, a product's
variants, an order's line items, a resource's containment tree, a federated
content's origin. The questions are graph questions: who are X's children? its
ancestors? is A reachable from B? what's the chain that connects them? is there a
cycle?
That is recursive Datalog in one rule — the same bottom-up reachability acl-on-sx
uses for group/resource inheritance. Decisions come with a trace: not just
"yes, related," but the path that proves it. relations is an internal-only
service (no public URL); other domains call it to resolve hierarchy and linkage.
End-state: a Datalog-on-SX layer for typed relationship facts, with reachability,
path explanation, cycle detection, and a federation extension for cross-instance
links. Reuses lib/datalog/ — does not reimplement the engine.
Status (rolling)
bash lib/relations/conformance.sh → 22/22 (Phase 1 complete)
Ground rules
- Scope: only
lib/relations/**andplans/relations-on-sx.md. Do not editspec/,hosts/,shared/,lib/datalog/**, or otherlib/<lang>/. You may import fromlib/datalog/(public API inlib/datalog/datalog.sx); do not copy or modify Datalog. - Shared-file issues → "Blockers" with a minimal repro; do not fix here.
- SX files:
sx-treeMCP tools only;sx_validateafter every edit. - Architecture: relationships are
rel(Src, Dst, Kind)Datalog facts; reachability/ancestry are recursive rules; the proof tree is the connecting path; the lifecycle (assert/retract) is an SX layer over the db. Keep relations content-agnostic — a node is an opaque id string; domains own what ids mean. - Shared with acl-sx: both run on Datalog and both lean on the same recursive-
reachability shape (
reach(X,Y) :- edge(X,Y)./reach(X,Y) :- edge(X,Z), reach(Z,Y).). Watch for it; flag convergence in the Progress log, but do not extract —plans/mod-on-sx.mdrecords why cross-subsystem extraction waits for the architecture integrator with all consumers in view. - Commits: one feature per commit. Keep Progress log updated and tick boxes.
Architecture sketch
relate(src, dst, kind) query
│ │
▼ ▼
lib/relations/schema.sx lib/relations/engine.sx
— rel(Src,Dst,Kind) facts — children/parents/ancestors/descendants
— kind vocabulary — reachable?(A,B), cycle?(X)
│ ▲
▼ │
lib/relations/api.sx lib/relations/explain.sx
— relate / unrelate — path(A,B): the connecting chain
— registry over a live db (from the Datalog derivation)
│
▼
lib/relations/federation.sx
— cross-instance links via fed-sx (replicated rel facts, peer-trust gated)
Phase 1 — Schema + direct relations
lib/relations/schema.sx—rel(Src, Dst, Kind)fact projection; a small kind vocabulary (parent,member,reply,variant,origin, …) kept openlib/relations/api.sx—(relations/relate src dst kind)/(unrelate …)over a live Datalog db (assert/retract);(children-of db node kind),(parents-of db node kind),(related db node kind)lib/relations/tests/direct.sx— assert/retract, direct children/parents, kind filtering, unknown node → emptylib/relations/conformance.sh+ scoreboard
Phase 2 — Reachability + cycles
- recursive reachability rules:
ancestors,descendants,reachable?(A,B)(transitive closure over a kind, the acl inheritance shape) roots/leaves(no parents / no children) for a kind- cycle detection:
cycle?(X)⇔reachable(X, X);acyclic?(db, kind) lib/relations/tests/reach.sx— deep chains, diamonds, disconnected nodes, self-loops, multi-kind isolation
Phase 3 — Typed relations + path explanation
- multiple kinds coexisting; mixed-kind vs single-kind reachability
lib/relations/explain.sx—(path db a b kind)returns the connecting chain (the relationship equivalent of acl's proof tree), nil if unreachable(distance db a b kind)(hops) + shortest-path selectionlib/relations/tests/path.sx— path correctness, shortest among many, no-path
Phase 4 — Federation
- cross-instance relationships — a peer asserts
rel(local, remote, kind); replicate rel facts via fed-sx (mock the transport in tests) - trust gating — a peer's link binds locally only under a local trust fact
(mirror acl's non-transitive
trust/gate-in-engine model; do NOT copy acl code, re-derive the shape) - revocation — retract a replicated link; reachability re-saturates
lib/relations/tests/fed.sx— federated reachability chains, trust gating, revocation
Progress log
- Phase 1 — schema + direct relations (22/22).
schema.sx:rel(Src,Dst,Kind)fact constructor + accessors, open kind vocabulary (parent member reply variant origin link),relations-fact-valid?/relations-known-kind?.api.sx: db built viadl-program-data facts relations-rules(Phase 1 rules empty — direct queries need none);relations-children-of/-parents-of/-relatedare plaindl-queryon therelrelation, plucking the bound column from substitution dicts; current-db convenience layer (relations/load!,relations/relate,relations/unrelate,relations/children/parents/related) overdl-assert!/dl-retract!, mirroring lib/acl/api.sx. Tests cover direct children/parents, leaf/ root empties, kind isolation (parent query skips reply edge), retract, the api layer, and schema/constructor shape. Note: query result order is nondeterministic — tests use an order-insensitiveset=?.
Blockers
(loop fills this in)