Files
rose-ash/plans/relations-on-sx.md
giles c67aefa211
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m15s
relations: Phase 1 schema + direct relations (rel facts, relate/unrelate, children/parents/related) + 22 tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 11:42:32 +00:00

119 lines
6.0 KiB
Markdown

# 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/**` and `plans/relations-on-sx.md`. Do **not** edit
`spec/`, `hosts/`, `shared/`, `lib/datalog/**`, or other `lib/<lang>/`. You may
**import** from `lib/datalog/` (public API in `lib/datalog/datalog.sx`); do not
copy or modify Datalog.
- **Shared-file issues** → "Blockers" with a minimal repro; do not fix here.
- **SX files:** `sx-tree` MCP tools only; `sx_validate` after 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.md` records 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
- [x] `lib/relations/schema.sx``rel(Src, Dst, Kind)` fact projection; a small
kind vocabulary (`parent`, `member`, `reply`, `variant`, `origin`, …) kept open
- [x] `lib/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)`
- [x] `lib/relations/tests/direct.sx` — assert/retract, direct children/parents,
kind filtering, unknown node → empty
- [x] `lib/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 selection
- [ ] `lib/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
via `dl-program-data facts relations-rules` (Phase 1 rules empty — direct queries
need none); `relations-children-of`/`-parents-of`/`-related` are plain `dl-query`
on the `rel` relation, plucking the bound column from substitution dicts;
current-db convenience layer (`relations/load!`, `relations/relate`,
`relations/unrelate`, `relations/children`/`parents`/`related`) over `dl-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-insensitive `set=?`.
## Blockers
(loop fills this in)