← Corpus / augment-it / issue
Relation kinds are inverse pairs — one stored string can't speak from both seats
NextLadder is funded_by Ballmer Group; Ballmer Group is funder_of NextLadder — the same edge needs a different kind string per perspective, and today only one pair is special-cased in a read-time map.
- Path
- issues/Relation-Kinds-Are-Inverse-Pairs.md
- Authors
- Michael Staton
- Augmented with
- Claude Code on Claude Fable 5
- Tags
- Issue · Augment-It · Org-Relations · Data-Modeling
Relation kinds are inverse pairs
The observation (operator, 2026-07-28)
relationships have the kind/type string. They are often inverse, so need to be written both ways inverted. So NextLadder is funded_by Ballmer Group, Ballmer Group is funder_of NextLadder.
One org↔org edge, one truth — but the kind string reads differently
depending on which card you’re standing on. rel (parent/child/peer)
already gets this right via read-time projection; kind mostly doesn’t.
Current state (shipped same day, deliberately partial)
- Kinds are authored ONCE, from the edge’s
inside (ballmer-group —funder_of→ nextladder). Storage is a single string. listOrgRelationsinverts at read time via a hardcoded map inorg-relations.ts(KIND_INVERSE):funder_of ↔ funded_by,funds → funded_by. That fixed the NextLadder card — and nothing else.- Every other directional kind still reads wrong from one seat:
- Hierarchical kinds on the CHILDREN listing: the parent’s card shows
cfat (initiative_of)— reading as if CFAT were initiative_of the viewer’s child, when the honest label from the parent’s seat is something likehas_initiative. - Any future directional kind (
member_of,spun_out_of,fiscal_sponsor_of…) silently inherits the defect until someone adds a map entry.
- Hierarchical kinds on the CHILDREN listing: the parent’s card shows
- Symmetric kinds (
partners_with) are fine — they’re their own inverse.
Design space (log, don’t decide)
- Extend the read-time map — cheapest; the vocabulary lives in code:
initiative_of → has_initiative,fund_of → has_fund,program_of → has_program,agency_of → has_agency,chapter_of → has_chapter, symmetric kinds mapping to themselves. Con: open-vocabulary kinds (the operator can type anything) get no inverse until code changes. - Store both directions on the edge (
kindas authored +kind_inverse, both writable at relate/update time) — redundancy-over- normalization house ethos; the operator can author asymmetric wording; reads pick by seat. Con: two fields to keep coherent on update. - A kind-vocabulary table (pairs as data, per client like
tag_vocab) — inverse pairs become operator-curated data, the datalist can offer both forms, exports/didi read the same source. Most machinery.
Whatever the choice, the UI datalist and didi’s slab should present kinds
in the operator’s seat-relative form (“is funded_by…” when relating from
the recipient’s side), and the export’s related_orgs column has the
same seat problem.
See also
services/record-surrealdb-resolver/src/org-relations.ts— theKIND_INVERSEmap (the partial fix) + the shapeRelated projection.- [[../plans/Org-Relations-Parent-Child-Peer-Plus-Org-Tags]] — the shipped model this refines.
- Live examples to test any fix against: NextLadder’s four funders, the National Academy for AI Instruction’s three, CFAT ⊂ carnegie-foundation, FIPSE ⊂ us-department-of-education.