code wiki / (root) / nx_relate_store.nx

nx_relate_store.nx source

↩ module page · 165 lines · 8982 B

1// nx_relate_store.nx -- PERSISTENCE for the relationship graph (turns nx_relate from pure logic into a REAL 2// stored data layer). Entities + edges are saved as content-addressed canon records on the sovereign APPEND-ONLY 3// seg_store: additive (never delete -> tombstone), tamper-evident (content-addressed CID), on YOUR hardware. This is 4// the CRM "sovereign exceed" (E9) made concrete for the graph, not just the notify ledger. Proves: persist a graph, 5// read it back byte-exact, and additivity (a second commit preserves the first). Composes nx_canon_cid + nx_seg_store 6// (the exact pattern proven in nx_vizsla_notify). 7// Records: key "ent:"+cid, fields {kind=ent, type, name, tenant} ; key "edg:"+cid, fields {kind=edg, from, rel, to, ctx, role}. 8// Commands: selftest (argless -- persist+readback+additive gate). Durable use = same calls with a knowledge/ prefix. 9// expect_exit: 0 license_tier: ORIGINAL 10import "nx_syscalls.nx" 11import "nx_canon_cid.nx" 12import "nx_seg_store.nx" 13const K_MAGIC_2048: i64 = 2048 14 15func p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 16func pn(v: i64) -> i64 { 17 var m: i64 = v; if m < 0 { p("-" as *u8); m = 0 - m } 18 let t: *u8 = sys_mmap(24); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } 19 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 20 let o: *u8 = sys_mmap(24); var i: i64 = 0; while i < k { o[i] = t[k-1-i]; i = i + 1 } sys_write(1, o, k); return 0 21} 22func rs_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 23func rs_eq(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 rs_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i } 30func rs_catn(dst: *u8, off: i64, v: i64) -> i64 { 31 var o: i64 = off; var m: i64 = v 32 let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } 33 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 34 var i: i64 = 0; while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 } 35 return o + k 36} 37func rs_kmemeq(b: *u8, off: i64, n: i64, s: *u8) -> i64 { 38 if rs_slen(s) != n { return 0 } 39 var i: i64 = 0; while i < n { if b[off + i] != s[i] { return 0 } i = i + 1 } 40 return 1 41} 42// key at koff (len kl) starts with the 4-char prefix pfx? 43func rs_iskind(b: *u8, koff: i64, kl: i64, pfx: *u8) -> i64 { 44 if kl <= 4 { return 0 } 45 return rs_kmemeq(b, koff, 4, pfx) 46} 47// extract field `want` from canon record rec (len rl) -> out; returns 1 if found 48func rs_field(rec: *u8, rl: i64, want: *u8, out: *u8, cap: i64) -> i64 { 49 if rl < 8 { return 0 } 50 let nf: i64 = ss_r32(rec, 4) 51 var off: i64 = 8; var fi: i64 = 0 52 while fi < nf { 53 if off + 8 > rl { return 0 } 54 let kl: i64 = ss_r32(rec, off); let koff: i64 = off + 4 55 let vl: i64 = ss_r32(rec, koff + kl); let voff: i64 = koff + kl + 4 56 if rs_kmemeq(rec, koff, kl, want) == 1 { 57 var t: i64 = 0 58 while t < vl { if t < cap - 1 { out[t] = rec[voff + t] } t = t + 1 } 59 if t > cap - 1 { t = cap - 1 } 60 out[t] = 0 as u8; return 1 61 } 62 off = voff + vl; fi = fi + 1 63 } 64 return 0 65} 66 67// save one entity into the open writer 68func rs_save_entity(w: *i64, etype: *u8, name: *u8, tenant: *u8) -> i64 { 69 let keys: *i64 = sys_mmap(8 * 8) as *i64 70 let vals: *i64 = sys_mmap(8 * 8) as *i64 71 keys[0]="kind" as *u8 as i64; keys[1]="type" as *u8 as i64; keys[2]="name" as *u8 as i64; keys[3]="tenant" as *u8 as i64 72 vals[0]="ent" as *u8 as i64; vals[1]=etype as i64; vals[2]=name as i64; vals[3]=tenant as i64 73 let enc: *u8 = sys_mmap(K_MAGIC_2048); let el: i64 = canon_encode(keys, vals, 4, enc) 74 let cid: *u8 = sys_mmap(96); cid_of(enc, el, cid) 75 let key: *u8 = sys_mmap(128); var ko: i64 = 0; ko = rs_cat(key, ko, "ent:" as *u8); ko = rs_cat(key, ko, cid); key[ko] = 0 as u8 76 return ss_add(w, 1, key, enc, el) 77} 78func rs_save_edge(w: *i64, from: *u8, rel: *u8, to: *u8, ctx: *u8, role: *u8) -> i64 { 79 let keys: *i64 = sys_mmap(8 * 8) as *i64 80 let vals: *i64 = sys_mmap(8 * 8) as *i64 81 keys[0]="kind" as *u8 as i64; keys[1]="from" as *u8 as i64; keys[2]="rel" as *u8 as i64; keys[3]="to" as *u8 as i64; keys[4]="ctx" as *u8 as i64; keys[5]="role" as *u8 as i64 82 vals[0]="edg" as *u8 as i64; vals[1]=from as i64; vals[2]=rel as i64; vals[3]=to as i64; vals[4]=ctx as i64; vals[5]=role as i64 83 let enc: *u8 = sys_mmap(K_MAGIC_2048); let el: i64 = canon_encode(keys, vals, 6, enc) 84 let cid: *u8 = sys_mmap(96); cid_of(enc, el, cid) 85 let key: *u8 = sys_mmap(128); var ko: i64 = 0; ko = rs_cat(key, ko, "edg:" as *u8); ko = rs_cat(key, ko, cid); key[ko] = 0 as u8 86 return ss_add(w, 1, key, enc, el) 87} 88 89// count records whose key starts with kpfx across all segments; if wantname != 0, set foundp[0]=1 when a record's name==wantname 90func rs_count(prefix: *u8, kpfx: *u8, wantname: *u8, foundp: *i64) -> i64 { 91 let segp: *i64 = sys_mmap(16) as *i64 92 let ns: i64 = ss_manifest_dyn(prefix, segp) // uncapped: count the WHOLE store 93 let segs: *i64 = segp[0] as *i64 94 let nmb: *u8 = sys_mmap(256) 95 var cnt: i64 = 0 96 var s: i64 = 0 97 while s < ns { 98 let path: *u8 = sys_mmap(512); var po: i64 = 0 99 po = rs_cat(path, po, prefix); po = rs_cat(path, po, segs[s] as *u8); po = rs_cat(path, po, ".docs" as *u8); path[po] = 0 as u8 100 let szp: *i64 = sys_mmap(16) as *i64 101 let b: *u8 = ss_readall(path, szp); let sz: i64 = szp[0] 102 var j: i64 = 0 103 while j + 9 <= sz { 104 let kind: i64 = b[j]; let kl: i64 = ss_r32(b, j + 1); let koff: i64 = j + 5 105 let vl: i64 = ss_r32(b, koff + kl); let voff: i64 = koff + kl + 4 106 if kind == 1 { 107 if rs_iskind(b, koff, kl, kpfx) == 1 { 108 cnt = cnt + 1 109 if wantname as i64 != 0 { 110 let rec: *u8 = (b as i64 + voff) as *u8 111 rs_field(rec, vl, "name" as *u8, nmb, 256) 112 if rs_eq(nmb, wantname) == 1 { foundp[0] = 1 } 113 } 114 } 115 } 116 j = voff + vl 117 } 118 s = s + 1 119 } 120 return cnt 121} 122 123func main() -> i64 { 124 p("=== NX-RELATE-STORE SELFTEST (persist graph on seg_store -> read back -> additive) ===\n" as *u8) 125 // fresh unique prefix under /tmp 126 let prefix: *u8 = sys_mmap(128); var po: i64 = 0 127 po = rs_cat(prefix, po, "/tmp/relstore_" as *u8); po = rs_catn(prefix, po, sys_now_us()); po = rs_cat(prefix, po, "_" as *u8); prefix[po] = 0 as u8 128 let segid1: i64 = sys_now_us() 129 130 // ---- commit 1: 3 entities + 2 edges (one batch) ---- 131 let w: *i64 = ss_begin() 132 rs_save_entity(w, "person" as *u8, "Rose Andelin" as *u8, "andelinwest" as *u8) 133 rs_save_entity(w, "company" as *u8, "Andelin West" as *u8, "andelinwest" as *u8) 134 rs_save_entity(w, "org" as *u8, "First Ward" as *u8, "personal" as *u8) 135 rs_save_edge(w, "Rose Andelin" as *u8, "works-at" as *u8, "Andelin West" as *u8, "professional" as *u8, "Managing Partner" as *u8) 136 rs_save_edge(w, "Rose Andelin" as *u8, "member-of" as *u8, "First Ward" as *u8, "church" as *u8, "Relief Society President" as *u8) 137 if ss_commit(prefix, w, segid1) != 0 { p("STORE commit1 FAILED -- fail loud\n" as *u8); return 1 } 138 139 // ---- read back ---- 140 let foundp: *i64 = sys_mmap(16) as *i64; foundp[0] = 0 141 let ent1: i64 = rs_count(prefix, "ent:" as *u8, "Rose Andelin" as *u8, foundp) 142 let edg1: i64 = rs_count(prefix, "edg:" as *u8, 0 as *u8, foundp) 143 p(" commit1: entities=" as *u8); pn(ent1); p(" edges=" as *u8); pn(edg1); p(" rose-name-byte-exact=" as *u8); pn(foundp[0]); p("\n" as *u8) 144 145 // ---- additive: commit 2 adds one entity; commit 1 must survive ---- 146 let w2: *i64 = ss_begin() 147 rs_save_entity(w2, "household" as *u8, "Andelin Household" as *u8, "personal" as *u8) 148 if ss_commit(prefix, w2, segid1 + 1) != 0 { p("STORE commit2 FAILED -- fail loud\n" as *u8); return 1 } 149 let fp2: *i64 = sys_mmap(16) as *i64; fp2[0] = 0 150 let ent2: i64 = rs_count(prefix, "ent:" as *u8, "Rose Andelin" as *u8, fp2) 151 let edg2: i64 = rs_count(prefix, "edg:" as *u8, 0 as *u8, fp2) 152 p(" commit2 (additive): entities=" as *u8); pn(ent2); p(" edges=" as *u8); pn(edg2); p(" rose-preserved=" as *u8); pn(fp2[0]); p("\n" as *u8) 153 154 var ok: i64 = 1 155 if ent1 != 3 { ok = 0 } 156 if edg1 != 2 { ok = 0 } 157 if foundp[0] != 1 { ok = 0 } 158 if ent2 != 4 { ok = 0 } 159 if edg2 != 2 { ok = 0 } 160 if fp2[0] != 1 { ok = 0 } 161 p("NX-RELATE-STORE prefix=" as *u8); p(prefix); p(" " as *u8) 162 if ok == 1 { p("verdict=GREEN (graph persisted, read back byte-exact, additive history intact)\n" as *u8); return 0 } 163 p("verdict=RED\n" as *u8) 164 return 1 165}