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}