code wiki / (root) / nx_relate.nx

nx_relate.nx source

↩ module page · 171 lines · 10509 B

1// nx_relate.nx -- THE UNIFIED RELATIONSHIP GRAPH (rung 1 of Nishi CRM/Relationships to SOTA). 2// Operator 2026-07-08: "more than just crm ... a personal AND a professional aka network of people so 3// relationships across everything can be managed -- even church or other orgs or whatever". 4// ONE graph, typed: 5// ENTITY-TYPE : person | company | org | household (people AND the things they belong to) 6// REL-EDGE : typed relationship between two entities (works-at | member-of | family | reports-to | 7// donor-to | friend | colleague) -- semantics-checked (works-at needs a company, member-of 8// needs an org/household, family needs two people, ...) 9// REL-ROLE : the role on that edge (title / position / ministry) -- e.g. Managing Partner, Relief Society President 10// REL-CONTEXT : which world the relationship lives in (personal | professional | church | civic | community) 11// HOUSEHOLD : a family/household is a first-class entity that groups people 12// CUSTOM-FIELD: arbitrary key=value on any entity (flexible schema, Attio-style) 13// TENANT : every entity is scoped to a workspace/tenant, so clients like andelinwest get isolation 14// So one person can be family + a church member + an employee at once -- across everything, in ONE graph. 15// Integrates with nx_send (a send-intent targets any entity) + Vizsla contacts/timeline/reminders. 16// Pure logic + a self-test gate (argless = selftest). Persistence + UI = next rungs. 17// Commands: entity <tenant> <type> <name> | edge <tenant> <fromType> <fromName> <kind> <toType> <toName> <context> [role] | field <tenant> <entity> <key> <value> | selftest 18// expect_exit: 0 license_tier: ORIGINAL 19import "nx_syscalls.nx" 20 21func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 22func p(s: *u8) -> i64 { sys_write(1, s, slen(s)); return 0 } 23func seq(a: *u8, b: *u8) -> i64 { 24 var i: i64 = 0 25 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 26 if b[i] != (0 as u8) { return 0 } 27 return 1 28} 29func is_empty(s: *u8) -> i64 { if s[0] == (0 as u8) { return 1 } return 0 } 30 31// ---- ENTITY-TYPE ---- 32func re_valid_type(t: *u8) -> i64 { 33 if seq(t, "person" as *u8) == 1 { return 1 } 34 if seq(t, "company" as *u8) == 1 { return 1 } 35 if seq(t, "org" as *u8) == 1 { return 1 } 36 if seq(t, "household" as *u8) == 1 { return 1 } 37 return 0 38} 39// ---- REL-EDGE kinds ---- 40func re_valid_kind(k: *u8) -> i64 { 41 if seq(k, "works-at" as *u8) == 1 { return 1 } 42 if seq(k, "member-of" as *u8) == 1 { return 1 } 43 if seq(k, "family" as *u8) == 1 { return 1 } 44 if seq(k, "reports-to" as *u8) == 1 { return 1 } 45 if seq(k, "donor-to" as *u8) == 1 { return 1 } 46 if seq(k, "friend" as *u8) == 1 { return 1 } 47 if seq(k, "colleague" as *u8) == 1 { return 1 } 48 if seq(k, "recruits" as *u8) == 1 { return 1 } // recruiter --recruits--> candidate (career ring) 49 return 0 50} 51// ---- REL-CONTEXT ---- 52func re_valid_context(c: *u8) -> i64 { 53 if seq(c, "personal" as *u8) == 1 { return 1 } 54 if seq(c, "professional" as *u8) == 1 { return 1 } 55 if seq(c, "church" as *u8) == 1 { return 1 } 56 if seq(c, "civic" as *u8) == 1 { return 1 } 57 if seq(c, "community" as *u8) == 1 { return 1 } 58 return 0 59} 60// edge semantics: are these entity types compatible with this relationship kind? 61func re_edge_ok(fromType: *u8, kind: *u8, toType: *u8) -> i64 { 62 if re_valid_kind(kind) == 0 { return 0 } 63 if seq(kind, "works-at" as *u8) == 1 { if seq(fromType, "person" as *u8) == 1 { if seq(toType, "company" as *u8) == 1 { return 1 } } return 0 } 64 if seq(kind, "member-of" as *u8) == 1 { if seq(fromType, "person" as *u8) == 1 { if seq(toType, "org" as *u8) == 1 { return 1 } if seq(toType, "household" as *u8) == 1 { return 1 } } return 0 } 65 if seq(kind, "donor-to" as *u8) == 1 { if seq(fromType, "person" as *u8) == 1 { if seq(toType, "org" as *u8) == 1 { return 1 } } return 0 } 66 // family | reports-to | friend | colleague are person <-> person 67 if seq(fromType, "person" as *u8) == 1 { if seq(toType, "person" as *u8) == 1 { return 1 } } 68 return 0 69} 70 71func cmd_entity(tenant: *u8, t: *u8, name: *u8) -> i64 { 72 if re_valid_type(t) == 0 { p("ENTITY-TYPE ERROR unknown type=" as *u8); p(t); p(" (person|company|org|household) -- fail loud\n" as *u8); return 1 } 73 p("ENTITY-TYPE tenant=" as *u8); p(tenant); p(" type=" as *u8); p(t); p(" name=" as *u8); p(name); p("\n" as *u8) 74 return 0 75} 76func cmd_edge(tenant: *u8, fromType: *u8, fromName: *u8, kind: *u8, toType: *u8, toName: *u8, context: *u8, role: *u8) -> i64 { 77 var ok: i64 = 1 78 var why: *u8 = "ok" as *u8 79 if re_valid_type(fromType) == 0 { ok = 0; why = "bad from-entity type" as *u8 } 80 if re_valid_type(toType) == 0 { ok = 0; why = "bad to-entity type" as *u8 } 81 if re_valid_kind(kind) == 0 { ok = 0; why = "unknown relationship kind" as *u8 } 82 if re_valid_context(context) == 0 { ok = 0; why = "unknown context" as *u8 } 83 if ok == 1 { if re_edge_ok(fromType, kind, toType) == 0 { ok = 0; why = "entity types incompatible with this relationship" as *u8 } } 84 p("REL-EDGE tenant=" as *u8); p(tenant); p(" " as *u8); p(fromType); p(":" as *u8); p(fromName) 85 p(" --[" as *u8); p(kind); p("]--> " as *u8); p(toType); p(":" as *u8); p(toName) 86 p(" REL-CONTEXT=" as *u8); p(context); p(" REL-ROLE=" as *u8); p(role) 87 if ok == 1 { p(" status=ok\n" as *u8); return 0 } 88 p(" status=REJECTED (" as *u8); p(why); p(")\n" as *u8) 89 return 1 90} 91func cmd_field(tenant: *u8, entity: *u8, key: *u8, val: *u8) -> i64 { 92 if is_empty(key) == 1 { p("CUSTOM-FIELD ERROR empty key -- fail loud\n" as *u8); return 1 } 93 p("CUSTOM-FIELD tenant=" as *u8); p(tenant); p(" entity=" as *u8); p(entity); p(" " as *u8); p(key); p("=" as *u8); p(val); p("\n" as *u8) 94 return 0 95} 96 97func cmd_selftest() -> i64 { 98 p("=== NX-RELATE SELFTEST (ENTITY-TYPE + REL-EDGE + REL-ROLE + REL-CONTEXT + HOUSEHOLD + CUSTOM-FIELD + TENANT) ===\n" as *u8) 99 var ok: i64 = 1 100 // entity types (pos + neg) 101 if re_valid_type("person" as *u8) != 1 { ok = 0 } 102 if re_valid_type("company" as *u8) != 1 { ok = 0 } 103 if re_valid_type("org" as *u8) != 1 { ok = 0 } 104 if re_valid_type("household" as *u8) != 1 { ok = 0 } 105 if re_valid_type("teammm" as *u8) != 0 { ok = 0 } 106 // kinds 107 if re_valid_kind("works-at" as *u8) != 1 { ok = 0 } 108 if re_valid_kind("member-of" as *u8) != 1 { ok = 0 } 109 if re_valid_kind("donor-to" as *u8) != 1 { ok = 0 } 110 if re_valid_kind("recruits" as *u8) != 1 { ok = 0 } 111 if re_valid_kind("hates" as *u8) != 0 { ok = 0 } 112 // contexts 113 if re_valid_context("personal" as *u8) != 1 { ok = 0 } 114 if re_valid_context("professional" as *u8) != 1 { ok = 0 } 115 if re_valid_context("church" as *u8) != 1 { ok = 0 } 116 if re_valid_context("civic" as *u8) != 1 { ok = 0 } 117 if re_valid_context("nonsense" as *u8) != 0 { ok = 0 } 118 // edge semantics (pos + neg) 119 if re_edge_ok("person" as *u8, "works-at" as *u8, "company" as *u8) != 1 { ok = 0 } 120 if re_edge_ok("person" as *u8, "works-at" as *u8, "org" as *u8) != 0 { ok = 0 } 121 if re_edge_ok("person" as *u8, "member-of" as *u8, "org" as *u8) != 1 { ok = 0 } 122 if re_edge_ok("person" as *u8, "member-of" as *u8, "household" as *u8) != 1 { ok = 0 } 123 if re_edge_ok("person" as *u8, "family" as *u8, "person" as *u8) != 1 { ok = 0 } 124 if re_edge_ok("company" as *u8, "family" as *u8, "person" as *u8) != 0 { ok = 0 } 125 if re_edge_ok("person" as *u8, "donor-to" as *u8, "org" as *u8) != 1 { ok = 0 } 126 // ---- the "relationships across everything" demo: ONE person, three worlds, one graph ---- 127 p("-- one person (Rose), three contexts, ONE graph --\n" as *u8) 128 let e1: i64 = cmd_entity("andelinwest" as *u8, "person" as *u8, "Rose Andelin" as *u8) 129 let x1: i64 = cmd_edge("personal" as *u8, "person" as *u8, "Rose" as *u8, "family" as *u8, "person" as *u8, "Ken" as *u8, "personal" as *u8, "grandmother" as *u8) 130 let x2: i64 = cmd_edge("church" as *u8, "person" as *u8, "Rose" as *u8, "member-of" as *u8, "org" as *u8, "First Ward" as *u8, "church" as *u8, "Relief Society President" as *u8) 131 let x3: i64 = cmd_edge("andelinwest" as *u8, "person" as *u8, "Rose" as *u8, "works-at" as *u8, "company" as *u8, "Andelin West" as *u8, "professional" as *u8, "Managing Partner" as *u8) 132 // a genuinely invalid edge must be rejected (works-at a church org is not employment) 133 let x4: i64 = cmd_edge("andelinwest" as *u8, "person" as *u8, "Marcus" as *u8, "recruits" as *u8, "person" as *u8, "Emma" as *u8, "professional" as *u8, "tech recruiter" as *u8) 134 let xbad: i64 = cmd_edge("church" as *u8, "person" as *u8, "Bob" as *u8, "works-at" as *u8, "org" as *u8, "First Ward" as *u8, "church" as *u8, "-" as *u8) 135 let hh: i64 = cmd_entity("personal" as *u8, "household" as *u8, "Andelin Household" as *u8) 136 let cf: i64 = cmd_field("andelinwest" as *u8, "Rose" as *u8, "birthday" as *u8, "1950-03-14" as *u8) 137 if e1 != 0 { ok = 0 } 138 if x1 != 0 { ok = 0 } 139 if x2 != 0 { ok = 0 } 140 if x3 != 0 { ok = 0 } 141 if x4 != 0 { ok = 0 } 142 if xbad != 1 { ok = 0 } 143 if hh != 0 { ok = 0 } 144 if cf != 0 { ok = 0 } 145 p("NX-RELATE-SELFTEST types=4 kinds=7 contexts=5 cross-context-graph=1 " as *u8) 146 if ok == 1 { p("verdict=GREEN\n" as *u8); return 0 } 147 p("verdict=RED\n" as *u8) 148 return 1 149} 150 151func main(argc: i64, argv: *i64) -> i64 { 152 if argc < 2 { return cmd_selftest() } 153 let cmd: *u8 = argv[1] as *u8 154 if seq(cmd, "selftest" as *u8) == 1 { return cmd_selftest() } 155 if seq(cmd, "entity" as *u8) == 1 { 156 if argc < 5 { p("usage: entity <tenant> <type> <name>\n" as *u8); return 1 } 157 return cmd_entity(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8) 158 } 159 if seq(cmd, "edge" as *u8) == 1 { 160 if argc < 9 { p("usage: edge <tenant> <fromType> <fromName> <kind> <toType> <toName> <context> [role]\n" as *u8); return 1 } 161 var role: *u8 = "-" as *u8 162 if argc > 9 { role = argv[9] as *u8 } 163 return cmd_edge(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argv[6] as *u8, argv[7] as *u8, argv[8] as *u8, role) 164 } 165 if seq(cmd, "field" as *u8) == 1 { 166 if argc < 6 { p("usage: field <tenant> <entity> <key> <value>\n" as *u8); return 1 } 167 return cmd_field(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8) 168 } 169 p("NX-RELATE usage: entity | edge | field | selftest\n" as *u8) 170 return 1 171}