nx_relate_serve.nx source
↩ module page · 1336 lines · 73958 B
1// nx_relate_serve.nx -- the INTERACTIVE handler for the Relationship OS (turns /relate from a static view into a
2// live app). GET renders the PERSISTED graph (from seg_store); POST /add-entity + /add-edge parse a form, SAVE to the
3// store, and the next GET shows the new record. Sovereign "pure core + deferred socket I/O" discipline (email-stack
4// pattern): this organ gates the interactive CORE (parse request -> read/write the persisted store -> render) against
5// a real store, NO sockets -- the accept-loop + public deploy (route /relate -> this daemon) is the ops shell after.
6// Self-contained store (canon_cid + seg_store), same proven pattern as nx_relate_store.
7// PURE CORE (no main): rv_handle (request->response), rv_read_req/rv_write_all (fd shells), store save/count,
8// rv_selftest (the offline gate body). Consumers: _hdl_build/nx_relate_serve_gate (offline), nx_relate_live_gate
9// (real loopback socket), nx_relate_daemon (deployable accept-loop).
10// license_tier: ORIGINAL
11import "nx_syscalls.nx"
12import "nx_canon_cid.nx"
13import "nx_seg_store.nx"
14import "nx_rebac.nx" // the shared ReBAC plane: friends/groups on /relate are rb_ tuples in THIS same store
15
16// R2 AUTH surfaces (pure -- the daemon shell does the olg_whoami crypto and passes the verdict in):
17// token-less browser nav gets the sessionStorage bootstrap (the PROVEN /project pattern: nsess -> re-fetch with
18// X-Nishi-Session -> render in place; no token -> /login?return=/relate). NO data ever renders unauthed.
19const RV_BOOTSTRAP: *u8 = "<!DOCTYPE html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'><title>Nishi Relationship OS</title></head><body><script>(function(){var t=sessionStorage.getItem('nsess');if(!t){location='/login?return=/relate';return}fetch(location.pathname,{headers:{'X-Nishi-Session':t}}).then(function(r){return r.ok?r.text():null}).then(function(x){if(x){document.open();document.write(x);document.close()}else{location='/login?return=/relate'}})})();</script></body></html>"
20
21func p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
22func pn(v: i64) -> i64 {
23 var m: i64 = v; if m < 0 { p("-" as *u8); m = 0 - m }
24 let t: *u8 = sys_mmap(24); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 }
25 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
26 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
27}
28func rv_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
29func rv_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 rv_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 rv_kmemeq(b: *u8, off: i64, n: i64, s: *u8) -> i64 {
38 if rv_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}
42func rv_iskind(b: *u8, koff: i64, kl: i64, pfx: *u8) -> i64 { if kl <= 4 { return 0 } return rv_kmemeq(b, koff, 4, pfx) }
43// HTML-escape cat for USER-STORED data. Without this, a stored <script> would run in every viewer's session --
44// and once auth ships (sess synchronizer token embedded in the page) stored XSS = token theft. Escape by default.
45func rv_cat_esc(dst: *u8, off: i64, s: *u8) -> i64 {
46 var o: i64 = off
47 var i: i64 = 0
48 while s[i] != (0 as u8) {
49 let c: i64 = s[i] as i64
50 var lit: i64 = 1
51 if c == 38 { o = rv_cat(dst, o, "&" as *u8); lit = 0 }
52 if c == 60 { o = rv_cat(dst, o, "<" as *u8); lit = 0 }
53 if c == 62 { o = rv_cat(dst, o, ">" as *u8); lit = 0 }
54 if c == 34 { o = rv_cat(dst, o, """ as *u8); lit = 0 }
55 if c == 39 { o = rv_cat(dst, o, "'" as *u8); lit = 0 }
56 if lit == 1 { dst[o] = s[i]; o = o + 1 }
57 i = i + 1
58 }
59 return o
60}
61// emit the hidden `sess` synchronizer field (b64 alphabet = attr-safe) when a token is present; no-op when empty.
62func rv_sess_field(dst: *u8, off: i64, tok: *u8) -> i64 {
63 var o: i64 = off
64 if tok[0] != (0 as u8) {
65 o = rv_cat(dst, o, "<input type='hidden' name='sess' value='" as *u8)
66 o = rv_cat(dst, o, tok)
67 o = rv_cat(dst, o, "'>" as *u8)
68 }
69 return o
70}
71func rv_field(rec: *u8, rl: i64, want: *u8, out: *u8, cap: i64) -> i64 {
72 if rl < 8 { return 0 }
73 let nf: i64 = ss_r32(rec, 4)
74 var off: i64 = 8; var fi: i64 = 0
75 while fi < nf {
76 if off + 8 > rl { return 0 }
77 let kl: i64 = ss_r32(rec, off); let koff: i64 = off + 4
78 let vl: i64 = ss_r32(rec, koff + kl); let voff: i64 = koff + kl + 4
79 if rv_kmemeq(rec, koff, kl, want) == 1 {
80 var t: i64 = 0
81 while t < vl { if t < cap - 1 { out[t] = rec[voff + t] } t = t + 1 }
82 if t > cap - 1 { t = cap - 1 }
83 out[t] = 0 as u8; return 1
84 }
85 off = voff + vl; fi = fi + 1
86 }
87 return 0
88}
89// substring search
90func rv_has(buf: *u8, n: i64, needle: *u8) -> i64 {
91 var i: i64 = 0
92 while i < n { var k: i64 = 0; var hit: i64 = 1
93 while needle[k] != (0 as u8) { if i + k >= n { hit = 0; k = 0 - 1; break } if buf[i+k] != needle[k] { hit = 0; break } k = k + 1 }
94 if hit == 1 { if k > 0 { return 1 } } i = i + 1 }
95 return 0
96}
97
98// ---- store write (same proven pattern as nx_relate_store) ----
99func rv_save_entity(w: *i64, etype: *u8, name: *u8, tenant: *u8) -> i64 {
100 let keys: *i64 = sys_mmap(8 * 8) as *i64; let vals: *i64 = sys_mmap(8 * 8) as *i64
101 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
102 vals[0]="ent" as *u8 as i64; vals[1]=etype as i64; vals[2]=name as i64; vals[3]=tenant as i64
103 let enc: *u8 = sys_mmap(2048); let el: i64 = canon_encode(keys, vals, 4, enc)
104 let cid: *u8 = sys_mmap(96); cid_of(enc, el, cid)
105 let key: *u8 = sys_mmap(128); var ko: i64 = 0; ko = rv_cat(key, ko, "ent:" as *u8); ko = rv_cat(key, ko, cid); key[ko] = 0 as u8
106 return ss_add(w, 1, key, enc, el)
107}
108func rv_save_edge(w: *i64, from: *u8, rel: *u8, to: *u8, ctx: *u8, role: *u8) -> i64 {
109 let keys: *i64 = sys_mmap(8 * 8) as *i64; let vals: *i64 = sys_mmap(8 * 8) as *i64
110 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
111 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
112 let enc: *u8 = sys_mmap(2048); let el: i64 = canon_encode(keys, vals, 6, enc)
113 let cid: *u8 = sys_mmap(96); cid_of(enc, el, cid)
114 let key: *u8 = sys_mmap(128); var ko: i64 = 0; ko = rv_cat(key, ko, "edg:" as *u8); ko = rv_cat(key, ko, cid); key[ko] = 0 as u8
115 return ss_add(w, 1, key, enc, el)
116}
117// persist a single entity (own commit)
118func rv_persist_entity(prefix: *u8, etype: *u8, name: *u8, tenant: *u8) -> i64 {
119 let w: *i64 = ss_begin()
120 rv_save_entity(w, etype, name, tenant)
121 return ss_commit(prefix, w, sys_now_us())
122}
123func rv_atoi(s: *u8) -> i64 {
124 var v: i64 = 0
125 var i: i64 = 0
126 while s[i] != (0 as u8) {
127 let d: i64 = (s[i] as i64) - 48
128 if d >= 0 { if d <= 9 { v = v * 10 + d } }
129 i = i + 1
130 }
131 return v
132}
133func rv_seq(a: *u8, b: *u8) -> i64 {
134 var i: i64 = 0
135 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
136 if b[i] != (0 as u8) { return 0 }
137 return 1
138}
139// stage vocab MIRROR of nx_crm stage_prob/stage_open (import would collide on shared helpers);
140// the selftest cross-checks the recomputed forecast against the nx_crm_store oracle (122500) = drift guard.
141func rv_stage_prob(s: *u8) -> i64 {
142 if rv_seq(s, "Lead" as *u8) == 1 { return 10 }
143 if rv_seq(s, "Qualified" as *u8) == 1 { return 25 }
144 if rv_seq(s, "Proposal" as *u8) == 1 { return 50 }
145 if rv_seq(s, "Negotiation" as *u8) == 1 { return 75 }
146 if rv_seq(s, "ClosedWon" as *u8) == 1 { return 100 }
147 if rv_seq(s, "ClosedLost" as *u8) == 1 { return 0 }
148 return 0 - 1
149}
150func rv_stage_open(s: *u8) -> i64 {
151 if rv_seq(s, "ClosedWon" as *u8) == 1 { return 0 }
152 if rv_seq(s, "ClosedLost" as *u8) == 1 { return 0 }
153 return 1
154}
155// deal + gift writers (record-format CONTRACT: keep field sets in sync with nx_crm_store cs_save_deal/cs_save_gift;
156// `tenant` is an ADDITIVE field for the ACL wall -- readers look fields up by name, absent = founding-tenant default)
157func rv_save_deal(w: *i64, name: *u8, account: *u8, amount: i64, stage: *u8, tenant: *u8) -> i64 {
158 if rv_stage_prob(stage) < 0 { return 0 - 1 }
159 if amount < 0 { return 0 - 1 }
160 let ab: *u8 = sys_mmap(32)
161 var ao: i64 = 0
162 ao = rv_catn(ab, ao, amount)
163 ab[ao] = 0 as u8
164 let keys: *i64 = sys_mmap(8 * 8) as *i64
165 let vals: *i64 = sys_mmap(8 * 8) as *i64
166 keys[0]="kind" as *u8 as i64; keys[1]="name" as *u8 as i64; keys[2]="account" as *u8 as i64; keys[3]="amount" as *u8 as i64; keys[4]="stage" as *u8 as i64; keys[5]="tenant" as *u8 as i64
167 vals[0]="dea" as *u8 as i64; vals[1]=name as i64; vals[2]=account as i64; vals[3]=ab as i64; vals[4]=stage as i64; vals[5]=tenant as i64
168 let enc: *u8 = sys_mmap(2048)
169 let el: i64 = canon_encode(keys, vals, 6, enc)
170 let cid: *u8 = sys_mmap(96)
171 cid_of(enc, el, cid)
172 let key: *u8 = sys_mmap(128)
173 var ko: i64 = 0
174 ko = rv_cat(key, ko, "dea:" as *u8)
175 ko = rv_cat(key, ko, cid)
176 key[ko] = 0 as u8
177 return ss_add(w, 1, key, enc, el)
178}
179func rv_save_gift(w: *i64, donor: *u8, fund: *u8, amount: i64, gtype: *u8) -> i64 {
180 if amount < 0 { return 0 - 1 }
181 if gtype[0] == (0 as u8) { return 0 - 1 }
182 let ab: *u8 = sys_mmap(32)
183 var ao: i64 = 0
184 ao = rv_catn(ab, ao, amount)
185 ab[ao] = 0 as u8
186 let keys: *i64 = sys_mmap(8 * 8) as *i64
187 let vals: *i64 = sys_mmap(8 * 8) as *i64
188 keys[0]="kind" as *u8 as i64; keys[1]="donor" as *u8 as i64; keys[2]="fund" as *u8 as i64; keys[3]="amount" as *u8 as i64; keys[4]="gtype" as *u8 as i64
189 vals[0]="giv" as *u8 as i64; vals[1]=donor as i64; vals[2]=fund as i64; vals[3]=ab as i64; vals[4]=gtype as i64
190 let enc: *u8 = sys_mmap(2048)
191 let el: i64 = canon_encode(keys, vals, 5, enc)
192 let cid: *u8 = sys_mmap(96)
193 cid_of(enc, el, cid)
194 let key: *u8 = sys_mmap(128)
195 var ko: i64 = 0
196 ko = rv_cat(key, ko, "giv:" as *u8)
197 ko = rv_cat(key, ko, cid)
198 key[ko] = 0 as u8
199 return ss_add(w, 1, key, enc, el)
200}
201func rv_persist_deal(prefix: *u8, name: *u8, account: *u8, amount: i64, stage: *u8, tenant: *u8) -> i64 {
202 let w: *i64 = ss_begin()
203 if rv_save_deal(w, name, account, amount, stage, tenant) != 0 { return 0 - 1 }
204 return ss_commit(prefix, w, sys_now_us())
205}
206// share vocab MIRROR of nx_share sh_itype_ok (helper-collision blocks the import; record shape = the contract)
207func rv_itype_ok(t: *u8) -> i64 {
208 if rv_seq(t, "job" as *u8) == 1 { return 1 }
209 if rv_seq(t, "resource" as *u8) == 1 { return 1 }
210 if rv_seq(t, "intro" as *u8) == 1 { return 1 }
211 return 0
212}
213// share writer (record-format CONTRACT: keep field set in sync with nx_share sh_save; tenant ADDITIVE for the wall)
214// a: [0]=itype [1]=title [2]=from [3]=to [4]=note [5]=reqs-or-empty [6]=tenant
215func rv_save_share(w: *i64, a: *i64) -> i64 {
216 if rv_itype_ok(a[0] as *u8) == 0 { return 0 - 1 }
217 let keys: *i64 = sys_mmap(8 * 8) as *i64
218 let vals: *i64 = sys_mmap(8 * 8) as *i64
219 keys[0]="kind" as *u8 as i64; keys[1]="itype" as *u8 as i64; keys[2]="title" as *u8 as i64
220 keys[3]="from" as *u8 as i64; keys[4]="to" as *u8 as i64; keys[5]="note" as *u8 as i64; keys[6]="reqs" as *u8 as i64; keys[7]="tenant" as *u8 as i64
221 vals[0]="shr" as *u8 as i64; vals[1]=a[0]; vals[2]=a[1]; vals[3]=a[2]; vals[4]=a[3]; vals[5]=a[4]; vals[6]=a[5]; vals[7]=a[6]
222 let enc: *u8 = sys_mmap(4096)
223 let el: i64 = canon_encode(keys, vals, 8, enc)
224 let cid: *u8 = sys_mmap(96)
225 cid_of(enc, el, cid)
226 let key: *u8 = sys_mmap(128)
227 var ko: i64 = 0
228 ko = rv_cat(key, ko, "shr:" as *u8)
229 ko = rv_cat(key, ko, cid)
230 key[ko] = 0 as u8
231 return ss_add(w, 1, key, enc, el)
232}
233// ---- ACL memberships (the nx_crm_acl tenant-wall model, PERSISTED): kind "acl" = handle/tenant/role/grantor.
234// Roles: 2 member / 3 manager / 4 admin. DENY-BY-DEFAULT: no record = no access; the WALL BEATS ADMIN (a role in
235// tenant A grants NOTHING in tenant B). Grants are written by a tenant ADMIN (role 4); the very FIRST grant on an
236// EMPTY acl plane may be SELF-issued by an authed+registered user (single-household bootstrap; grantor recorded).
237func rv_save_acl(w: *i64, handle: *u8, tenant: *u8, role: i64, grantor: *u8) -> i64 {
238 if role < 2 { return 0 - 1 }
239 if role > 4 { return 0 - 1 }
240 if handle[0] == (0 as u8) { return 0 - 1 }
241 if tenant[0] == (0 as u8) { return 0 - 1 }
242 let rb: *u8 = sys_mmap(16)
243 let ro: i64 = rv_catn(rb, 0, role)
244 rb[ro] = 0 as u8
245 let keys: *i64 = sys_mmap(8 * 8) as *i64
246 let vals: *i64 = sys_mmap(8 * 8) as *i64
247 keys[0]="kind" as *u8 as i64; keys[1]="handle" as *u8 as i64; keys[2]="tenant" as *u8 as i64; keys[3]="role" as *u8 as i64; keys[4]="grantor" as *u8 as i64
248 vals[0]="acl" as *u8 as i64; vals[1]=handle as i64; vals[2]=tenant as i64; vals[3]=rb as i64; vals[4]=grantor as i64
249 let enc: *u8 = sys_mmap(2048)
250 let el: i64 = canon_encode(keys, vals, 5, enc)
251 let cid: *u8 = sys_mmap(96)
252 cid_of(enc, el, cid)
253 let key: *u8 = sys_mmap(128)
254 var ko: i64 = 0
255 ko = rv_cat(key, ko, "acl:" as *u8)
256 ko = rv_cat(key, ko, cid)
257 key[ko] = 0 as u8
258 return ss_add(w, 1, key, enc, el)
259}
260// does ANY acl record exist? (the bootstrap window: 0 = first-grant self-issue allowed)
261func rv_acl_any(prefix: *u8) -> i64 {
262 let segp: *i64 = sys_mmap(16) as *i64
263 let ns: i64 = ss_manifest_dyn(prefix, segp) // uncapped: sees the WHOLE store
264 let segs: *i64 = segp[0] as *i64
265 var s: i64 = 0
266 while s < ns {
267 let path: *u8 = sys_mmap(512)
268 var po: i64 = 0
269 po = rv_cat(path, po, prefix); po = rv_cat(path, po, segs[s] as *u8); po = rv_cat(path, po, ".docs" as *u8); path[po] = 0 as u8
270 let szp: *i64 = sys_mmap(16) as *i64
271 let b: *u8 = ss_readall(path, szp)
272 let sz: i64 = szp[0]
273 var j: i64 = 0
274 while j + 9 <= sz {
275 let kl: i64 = ss_r32(b, j + 1); let koff: i64 = j + 5; let vl: i64 = ss_r32(b, koff + kl); let voff: i64 = koff + kl + 4
276 if b[j] == 1 { if rv_iskind(b, koff, kl, "acl:" as *u8) == 1 { return 1 } }
277 j = voff + vl
278 }
279 s = s + 1
280 }
281 return 0
282}
283// load the caller's memberships into (tenants[i], roles[i]); returns n (max 16). LAST record per tenant WINS
284// (append-only store -> role changes = re-grants). Empty handle -> 0 = deny-all (authed but unmapped/ungranted).
285func rv_acl_load(prefix: *u8, handle: *u8, tenants: *i64, roles: *i64) -> i64 {
286 var n: i64 = 0
287 if handle[0] == (0 as u8) { return 0 }
288 let segp: *i64 = sys_mmap(16) as *i64
289 let ns: i64 = ss_manifest_dyn(prefix, segp) // uncapped: sees the WHOLE store
290 let segs: *i64 = segp[0] as *i64
291 let fh: *u8 = sys_mmap(256)
292 let ftn: *u8 = sys_mmap(256)
293 let fr: *u8 = sys_mmap(64)
294 var s: i64 = 0
295 while s < ns {
296 let path: *u8 = sys_mmap(512)
297 var po: i64 = 0
298 po = rv_cat(path, po, prefix); po = rv_cat(path, po, segs[s] as *u8); po = rv_cat(path, po, ".docs" as *u8); path[po] = 0 as u8
299 let szp: *i64 = sys_mmap(16) as *i64
300 let b: *u8 = ss_readall(path, szp)
301 let sz: i64 = szp[0]
302 var j: i64 = 0
303 while j + 9 <= sz {
304 let kl: i64 = ss_r32(b, j + 1); let koff: i64 = j + 5; let vl: i64 = ss_r32(b, koff + kl); let voff: i64 = koff + kl + 4
305 if b[j] == 1 {
306 if rv_iskind(b, koff, kl, "acl:" as *u8) == 1 {
307 let rec: *u8 = (b as i64 + voff) as *u8
308 fh[0] = 0 as u8; ftn[0] = 0 as u8; fr[0] = 0 as u8
309 rv_field(rec, vl, "handle" as *u8, fh, 256)
310 rv_field(rec, vl, "tenant" as *u8, ftn, 256)
311 rv_field(rec, vl, "role" as *u8, fr, 64)
312 if rv_seq(fh, handle) == 1 {
313 let rl: i64 = rv_atoi(fr)
314 var hit: i64 = 0 - 1
315 var q: i64 = 0
316 while q < n { if rv_seq(tenants[q] as *u8, ftn) == 1 { hit = q; q = n } q = q + 1 }
317 if hit >= 0 { roles[hit] = rl } else {
318 if n < 16 {
319 let tc: *u8 = sys_mmap(256)
320 var tq: i64 = 0
321 tq = rv_cat(tc, tq, ftn)
322 tc[tq] = 0 as u8
323 tenants[n] = tc as i64
324 roles[n] = rl
325 n = n + 1
326 }
327 }
328 }
329 }
330 }
331 j = voff + vl
332 }
333 s = s + 1
334 }
335 return n
336}
337// records created before the tenant wall carry no tenant field -> they belong to the FOUNDING tenant.
338func rv_ten_default(t: *u8) -> i64 {
339 if t[0] == (0 as u8) { let o: i64 = rv_cat(t, 0, "andelinwest" as *u8); t[o] = 0 as u8 }
340 return 0
341}
342// caller's role in `ten` from the grants bundle gr (null = legacy OPEN mode -> full role). 0 = none.
343// gr layout: [0]=handle *u8, [1]=n, [2]=tenants *i64, [3]=roles *i64
344func rv_role_of(gr: *i64, ten: *u8) -> i64 {
345 if (gr as i64) == 0 { return 4 }
346 let n: i64 = gr[1]
347 let ts: *i64 = gr[2] as *i64
348 let rs: *i64 = gr[3] as *i64
349 var i: i64 = 0
350 while i < n { if rv_seq(ts[i] as *u8, ten) == 1 { return rs[i] } i = i + 1 }
351 return 0
352}
353func rv_vis(gr: *i64, ten: *u8) -> i64 { if rv_role_of(gr, ten) >= 2 { return 1 } return 0 }
354func rv_amt_ok(gr: *i64, ten: *u8) -> i64 { if rv_role_of(gr, ten) >= 3 { return 1 } return 0 }
355// write-gate for a POST: parse tenant from the body into tb (defaulted), require role>=2. 1 allow / 0 deny.
356func rv_wcheck(gr: *i64, body: *u8, blen: i64, tb: *u8) -> i64 {
357 if rv_form_get(body, blen, "tenant" as *u8, tb, 128) == 0 { tb[0] = 0 as u8 }
358 rv_ten_default(tb)
359 if (gr as i64) == 0 { return 1 }
360 if rv_role_of(gr, tb) >= 2 { return 1 }
361 return 0
362}
363func rv_emit_403(out: *u8) -> i64 {
364 let body: *u8 = "{\"type\":\"about:blank\",\"title\":\"Forbidden\",\"status\":403,\"detail\":\"your session is valid but you hold no role on that tenant (deny-by-default; the tenant wall beats admin)\"}" as *u8
365 var o: i64 = 0
366 o = rv_cat(out, o, "HTTP/1.1 403 Forbidden\r\nContent-Type: application/problem+json\r\nConnection: close\r\nContent-Length: " as *u8)
367 o = rv_catn(out, o, rv_slen(body))
368 o = rv_cat(out, o, "\r\n\r\n" as *u8)
369 o = rv_cat(out, o, body)
370 return o
371}
372
373// count entity records
374func rv_count_ent(prefix: *u8) -> i64 {
375 let segp: *i64 = sys_mmap(16) as *i64; let ns: i64 = ss_manifest_dyn(prefix, segp); let segs: *i64 = segp[0] as *i64 // uncapped
376 var cnt: i64 = 0; var s: i64 = 0
377 while s < ns {
378 let path: *u8 = sys_mmap(512); var po: i64 = 0
379 po = rv_cat(path, po, prefix); po = rv_cat(path, po, segs[s] as *u8); po = rv_cat(path, po, ".docs" as *u8); path[po] = 0 as u8
380 let szp: *i64 = sys_mmap(16) as *i64; let b: *u8 = ss_readall(path, szp); let sz: i64 = szp[0]
381 var j: i64 = 0
382 while j + 9 <= sz {
383 let kl: i64 = ss_r32(b, j + 1); let koff: i64 = j + 5; let vl: i64 = ss_r32(b, koff + kl); let voff: i64 = koff + kl + 4
384 if b[j] == 1 { if rv_iskind(b, koff, kl, "ent:" as *u8) == 1 { cnt = cnt + 1 } }
385 j = voff + vl
386 }
387 s = s + 1
388 }
389 return cnt
390}
391
392// display a "type:id" tuple ref as just the id (HTML-escaped): "user:rose" -> "rose".
393func rv_disp(dst: *u8, off: i64, s: *u8) -> i64 {
394 var i: i64 = 0
395 while s[i] != (0 as u8) { if (s[i] as i64) == 58 { return rv_cat_esc(dst, off, (s as i64 + i + 1) as *u8) } i = i + 1 }
396 return rv_cat_esc(dst, off, s)
397}
398// build "user:<handle>" into out (NUL-term); returns length. the caller's own identity as a ReBAC subject.
399func rv_userref(out: *u8, handle: *u8) -> i64 {
400 var o: i64 = 0
401 o = rv_cat(out, o, "user:" as *u8)
402 o = rv_cat(out, o, handle)
403 out[o] = 0 as u8
404 return o
405}
406// SECURITY: a handle/group name must be [a-zA-Z0-9_-], 1..64 -- so it can NEVER inject ':' (forge a type) or '#'
407// (forge a userset) into a tuple ref. Reject everything else. 1 = ok, 0 = reject.
408func rv_tok_ok(s: *u8) -> i64 {
409 if s[0] == (0 as u8) { return 0 }
410 var i: i64 = 0
411 while s[i] != (0 as u8) {
412 let c: i64 = s[i] as i64
413 var ok: i64 = 0
414 if c >= 97 { if c <= 122 { ok = 1 } }
415 if c >= 65 { if c <= 90 { ok = 1 } }
416 if c >= 48 { if c <= 57 { ok = 1 } }
417 if c == 95 { ok = 1 }
418 if c == 45 { ok = 1 }
419 if ok == 0 { return 0 }
420 if i >= 64 { return 0 }
421 i = i + 1
422 }
423 return 1
424}
425// build "group:<name>" into out (NUL-term); returns length.
426func rv_groupref(out: *u8, name: *u8) -> i64 {
427 var o: i64 = 0
428 o = rv_cat(out, o, "group:" as *u8)
429 o = rv_cat(out, o, name)
430 out[o] = 0 as u8
431 return o
432}
433
434// ---- render the persisted graph as HTML into out; returns length ----
435// tok (never null; may be EMPTY) = the caller's validated session token, embedded as a hidden `sess` field in
436// every form so plain zero-JS form POSTs stay authenticated (synchronizer-token = CSRF-resistant by construction;
437// same exposure class as the sessionStorage copy the bootstrap already holds). Empty tok = no field (legacy/open).
438// gr = the caller's grants bundle ([0]=handle,[1]=n,[2]=tenants,[3]=roles) or NULL = legacy OPEN (unfiltered).
439// Every section renders ONLY records whose tenant the caller holds a role on (deny-by-default tenant wall);
440// giving AMOUNTS additionally need manager+ (field-level security, the nx_crm_acl minrole=3 rule).
441func rv_render_tok(prefix: *u8, out: *u8, cap: i64, tok: *u8, gr: *i64) -> i64 {
442 var o: i64 = 0
443 o = rv_cat(out, o, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n" as *u8)
444 o = rv_cat(out, o, "<html><head><meta charset='utf-8'><title>Nishi Relationship OS</title><style>body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:rgb(15,16,22);color:rgb(228,230,235);max-width:760px;margin:5vh auto;padding:0 20px}h1{font-size:24px}h2{font-size:14px;text-transform:uppercase;letter-spacing:.4px;color:rgb(150,200,255);margin-top:24px}.row{padding:8px 0;border-bottom:1px solid rgb(34,38,48)}.mut{color:rgb(150,155,165);font-size:13px}form{margin-top:10px}input,select{background:rgb(26,30,40);color:rgb(228,230,235);border:1px solid rgb(48,54,66);border-radius:8px;padding:8px;margin-right:6px}button{background:rgb(40,110,210);color:white;border:0;border-radius:8px;padding:9px 14px;font-weight:600}</style></head><body>" as *u8)
445 o = rv_cat(out, o, "<h1>Nishi Relationship OS</h1><div class='mut'>Live from your own append-only store -- add a person and it persists, on your hardware.</div>" as *u8)
446
447 // walk records
448 let segp: *i64 = sys_mmap(16) as *i64; let ns: i64 = ss_manifest_dyn(prefix, segp); let segs: *i64 = segp[0] as *i64 // uncapped
449 let f1: *u8 = sys_mmap(256); let f2: *u8 = sys_mmap(256); let f3: *u8 = sys_mmap(256)
450 let f4: *u8 = sys_mmap(256); let f5: *u8 = sys_mmap(256); let ft: *u8 = sys_mmap(256)
451 o = rv_cat(out, o, "<h2>People and entities</h2>" as *u8)
452 var s: i64 = 0
453 while s < ns {
454 let path: *u8 = sys_mmap(512); var po: i64 = 0
455 po = rv_cat(path, po, prefix); po = rv_cat(path, po, segs[s] as *u8); po = rv_cat(path, po, ".docs" as *u8); path[po] = 0 as u8
456 let szp: *i64 = sys_mmap(16) as *i64; let b: *u8 = ss_readall(path, szp); let sz: i64 = szp[0]
457 var j: i64 = 0
458 while j + 9 <= sz {
459 let kl: i64 = ss_r32(b, j + 1); let koff: i64 = j + 5; let vl: i64 = ss_r32(b, koff + kl); let voff: i64 = koff + kl + 4
460 if b[j] == 1 {
461 if rv_iskind(b, koff, kl, "ent:" as *u8) == 1 {
462 let rec: *u8 = (b as i64 + voff) as *u8
463 f3[0] = 0 as u8
464 rv_field(rec, vl, "name" as *u8, f1, 256); rv_field(rec, vl, "type" as *u8, f2, 256); rv_field(rec, vl, "tenant" as *u8, f3, 256)
465 rv_ten_default(f3)
466 if rv_vis(gr, f3) == 1 {
467 o = rv_cat(out, o, "<div class='row'><b>" as *u8); o = rv_cat_esc(out, o, f1)
468 o = rv_cat(out, o, "</b> <span class='mut'>" as *u8); o = rv_cat_esc(out, o, f2); o = rv_cat(out, o, " · " as *u8); o = rv_cat_esc(out, o, f3); o = rv_cat(out, o, "</span></div>" as *u8)
469 }
470 }
471 }
472 j = voff + vl
473 }
474 s = s + 1
475 }
476 // relationships
477 o = rv_cat(out, o, "<h2>Relationships</h2>" as *u8)
478 s = 0
479 while s < ns {
480 let path: *u8 = sys_mmap(512); var po: i64 = 0
481 po = rv_cat(path, po, prefix); po = rv_cat(path, po, segs[s] as *u8); po = rv_cat(path, po, ".docs" as *u8); path[po] = 0 as u8
482 let szp: *i64 = sys_mmap(16) as *i64; let b: *u8 = ss_readall(path, szp); let sz: i64 = szp[0]
483 var j: i64 = 0
484 while j + 9 <= sz {
485 let kl: i64 = ss_r32(b, j + 1); let koff: i64 = j + 5; let vl: i64 = ss_r32(b, koff + kl); let voff: i64 = koff + kl + 4
486 if b[j] == 1 {
487 if rv_iskind(b, koff, kl, "edg:" as *u8) == 1 {
488 let rec: *u8 = (b as i64 + voff) as *u8
489 ft[0] = 0 as u8
490 rv_field(rec, vl, "tenant" as *u8, ft, 256)
491 rv_ten_default(ft)
492 if rv_vis(gr, ft) == 1 {
493 rv_field(rec, vl, "from" as *u8, f1, 256); rv_field(rec, vl, "rel" as *u8, f2, 256); rv_field(rec, vl, "to" as *u8, f3, 256); rv_field(rec, vl, "ctx" as *u8, f4, 256); rv_field(rec, vl, "role" as *u8, f5, 256)
494 o = rv_cat(out, o, "<div class='row'>" as *u8); o = rv_cat_esc(out, o, f1); o = rv_cat(out, o, " —[" as *u8); o = rv_cat_esc(out, o, f2); o = rv_cat(out, o, "]→ " as *u8); o = rv_cat_esc(out, o, f3)
495 o = rv_cat(out, o, " <span class='mut'>" as *u8); o = rv_cat_esc(out, o, f4); o = rv_cat(out, o, " · " as *u8); o = rv_cat_esc(out, o, f5); o = rv_cat(out, o, "</span></div>" as *u8)
496 }
497 }
498 }
499 j = voff + vl
500 }
501 s = s + 1
502 }
503 // deal pipeline -- LIVE from the persisted store (weighted forecast recomputed per render)
504 o = rv_cat(out, o, "<h2>Deal pipeline (live from your store)</h2>" as *u8)
505 var open_t: i64 = 0
506 var wgt: i64 = 0
507 s = 0
508 while s < ns {
509 let path: *u8 = sys_mmap(512)
510 var po: i64 = 0
511 po = rv_cat(path, po, prefix)
512 po = rv_cat(path, po, segs[s] as *u8)
513 po = rv_cat(path, po, ".docs" as *u8)
514 path[po] = 0 as u8
515 let szp: *i64 = sys_mmap(16) as *i64
516 let b: *u8 = ss_readall(path, szp)
517 let sz: i64 = szp[0]
518 var j: i64 = 0
519 while j + 9 <= sz {
520 let kl: i64 = ss_r32(b, j + 1)
521 let koff: i64 = j + 5
522 let vl: i64 = ss_r32(b, koff + kl)
523 let voff: i64 = koff + kl + 4
524 if b[j] == 1 {
525 if rv_iskind(b, koff, kl, "dea:" as *u8) == 1 {
526 let rec: *u8 = (b as i64 + voff) as *u8
527 ft[0] = 0 as u8
528 rv_field(rec, vl, "tenant" as *u8, ft, 256)
529 rv_ten_default(ft)
530 // the wall gates the SUMS too: the forecast is the caller's tenant-scoped view, not a leak
531 if rv_vis(gr, ft) == 1 {
532 rv_field(rec, vl, "name" as *u8, f1, 256)
533 rv_field(rec, vl, "account" as *u8, f2, 256)
534 rv_field(rec, vl, "amount" as *u8, f3, 256)
535 rv_field(rec, vl, "stage" as *u8, f4, 256)
536 let amt: i64 = rv_atoi(f3)
537 if rv_stage_open(f4) == 1 { open_t = open_t + amt; wgt = wgt + (amt * rv_stage_prob(f4)) / 100 }
538 o = rv_cat(out, o, "<div class='row'><b>" as *u8)
539 o = rv_cat_esc(out, o, f1)
540 o = rv_cat(out, o, "</b> <span class='mut'>" as *u8)
541 o = rv_cat_esc(out, o, f2)
542 o = rv_cat(out, o, " · $" as *u8)
543 o = rv_cat_esc(out, o, f3)
544 o = rv_cat(out, o, " · " as *u8)
545 o = rv_cat_esc(out, o, f4)
546 o = rv_cat(out, o, "</span></div>" as *u8)
547 }
548 }
549 }
550 j = voff + vl
551 }
552 s = s + 1
553 }
554 o = rv_cat(out, o, "<div class='row'><b>Open $" as *u8)
555 o = rv_catn(out, o, open_t)
556 o = rv_cat(out, o, " · Weighted forecast $" as *u8)
557 o = rv_catn(out, o, wgt)
558 o = rv_cat(out, o, "</b></div>" as *u8)
559 // giving -- LIVE from the persisted store
560 o = rv_cat(out, o, "<h2>Giving (live from your store)</h2>" as *u8)
561 var gtot: i64 = 0
562 s = 0
563 while s < ns {
564 let path: *u8 = sys_mmap(512)
565 var po: i64 = 0
566 po = rv_cat(path, po, prefix)
567 po = rv_cat(path, po, segs[s] as *u8)
568 po = rv_cat(path, po, ".docs" as *u8)
569 path[po] = 0 as u8
570 let szp: *i64 = sys_mmap(16) as *i64
571 let b: *u8 = ss_readall(path, szp)
572 let sz: i64 = szp[0]
573 var j: i64 = 0
574 while j + 9 <= sz {
575 let kl: i64 = ss_r32(b, j + 1)
576 let koff: i64 = j + 5
577 let vl: i64 = ss_r32(b, koff + kl)
578 let voff: i64 = koff + kl + 4
579 if b[j] == 1 {
580 if rv_iskind(b, koff, kl, "giv:" as *u8) == 1 {
581 let rec: *u8 = (b as i64 + voff) as *u8
582 ft[0] = 0 as u8
583 rv_field(rec, vl, "tenant" as *u8, ft, 256)
584 rv_ten_default(ft)
585 if rv_vis(gr, ft) == 1 {
586 rv_field(rec, vl, "donor" as *u8, f1, 256)
587 rv_field(rec, vl, "fund" as *u8, f2, 256)
588 rv_field(rec, vl, "amount" as *u8, f3, 256)
589 rv_field(rec, vl, "gtype" as *u8, f4, 256)
590 o = rv_cat(out, o, "<div class='row'>" as *u8)
591 o = rv_cat_esc(out, o, f1)
592 o = rv_cat(out, o, " <span class='mut'>" as *u8)
593 o = rv_cat_esc(out, o, f2)
594 // field-level security (nx_crm_acl minrole=3): members see the gift, managers+ see the AMOUNT
595 if rv_amt_ok(gr, ft) == 1 {
596 gtot = gtot + rv_atoi(f3)
597 o = rv_cat(out, o, " · $" as *u8)
598 o = rv_cat_esc(out, o, f3)
599 } else {
600 o = rv_cat(out, o, " · (amount restricted)" as *u8)
601 }
602 o = rv_cat(out, o, " · " as *u8)
603 o = rv_cat_esc(out, o, f4)
604 o = rv_cat(out, o, "</span></div>" as *u8)
605 }
606 }
607 }
608 j = voff + vl
609 }
610 s = s + 1
611 }
612 o = rv_cat(out, o, "<div class='row'><b>Received $" as *u8)
613 o = rv_catn(out, o, gtot)
614 o = rv_cat(out, o, "</b></div>" as *u8)
615 // share inbox -- LIVE from the persisted store (stored + shared through the graph)
616 o = rv_cat(out, o, "<h2>Share inbox (live from your store)</h2>" as *u8)
617 var shn: i64 = 0
618 s = 0
619 while s < ns {
620 let path: *u8 = sys_mmap(512)
621 var po: i64 = 0
622 po = rv_cat(path, po, prefix)
623 po = rv_cat(path, po, segs[s] as *u8)
624 po = rv_cat(path, po, ".docs" as *u8)
625 path[po] = 0 as u8
626 let szp: *i64 = sys_mmap(16) as *i64
627 let b: *u8 = ss_readall(path, szp)
628 let sz: i64 = szp[0]
629 var j: i64 = 0
630 while j + 9 <= sz {
631 let kl: i64 = ss_r32(b, j + 1)
632 let koff: i64 = j + 5
633 let vl: i64 = ss_r32(b, koff + kl)
634 let voff: i64 = koff + kl + 4
635 if b[j] == 1 {
636 if rv_iskind(b, koff, kl, "shr:" as *u8) == 1 {
637 let rec: *u8 = (b as i64 + voff) as *u8
638 ft[0] = 0 as u8
639 rv_field(rec, vl, "tenant" as *u8, ft, 256)
640 rv_ten_default(ft)
641 if rv_vis(gr, ft) == 1 {
642 rv_field(rec, vl, "itype" as *u8, f1, 256)
643 rv_field(rec, vl, "title" as *u8, f2, 256)
644 rv_field(rec, vl, "from" as *u8, f3, 256)
645 rv_field(rec, vl, "to" as *u8, f4, 256)
646 rv_field(rec, vl, "note" as *u8, f5, 256)
647 shn = shn + 1
648 o = rv_cat(out, o, "<div class='row'><b>" as *u8)
649 o = rv_cat_esc(out, o, f2)
650 o = rv_cat(out, o, "</b> <span class='mut'>" as *u8)
651 o = rv_cat_esc(out, o, f1)
652 o = rv_cat(out, o, " · " as *u8)
653 o = rv_cat_esc(out, o, f3)
654 o = rv_cat(out, o, " → " as *u8)
655 o = rv_cat_esc(out, o, f4)
656 o = rv_cat(out, o, " · " as *u8)
657 o = rv_cat_esc(out, o, f5)
658 o = rv_cat(out, o, "</span></div>" as *u8)
659 }
660 }
661 }
662 j = voff + vl
663 }
664 s = s + 1
665 }
666 o = rv_cat(out, o, "<div class='mut'>shares=" as *u8)
667 o = rv_catn(out, o, shn)
668 o = rv_cat(out, o, " · jobs arrive scored (nx_share fit-on-receipt); consent + recommendation gates apply at the share layer.</div>" as *u8)
669 // ACCESS section (enforced mode only): the caller's memberships + the grant form. The HANDLER enforces who
670 // may grant (admin-of-tenant, or the single first bootstrap grant); rendering the form is not permission.
671 if (gr as i64) != 0 {
672 o = rv_cat(out, o, "<h2>Access</h2><div class='mut'>you are " as *u8)
673 o = rv_cat_esc(out, o, gr[0] as *u8)
674 o = rv_cat(out, o, "</div>" as *u8)
675 let gts: *i64 = gr[2] as *i64
676 let grs: *i64 = gr[3] as *i64
677 var gi: i64 = 0
678 while gi < gr[1] {
679 o = rv_cat(out, o, "<div class='row'>" as *u8)
680 o = rv_cat_esc(out, o, gts[gi] as *u8)
681 o = rv_cat(out, o, " <span class='mut'>role " as *u8)
682 o = rv_catn(out, o, grs[gi])
683 o = rv_cat(out, o, "</span></div>" as *u8)
684 gi = gi + 1
685 }
686 o = rv_cat(out, o, "<form method='post' action='/relate/add-acl'>" as *u8)
687 o = rv_sess_field(out, o, tok)
688 o = rv_cat(out, o, "<input name='handle' placeholder='Handle'><input name='tenant' placeholder='Tenant'><input name='role' value='2'><button>Grant</button></form>" as *u8)
689
690 // MY CIRCLE -- friends + groups, backed by the SHARED nx_rebac plane (tuples in this same store). This is
691 // the operator's social example live: friend/defriend + join/leave groups, one authorization engine.
692 let me: *u8 = sys_mmap(320); rv_userref(me, gr[0] as *u8)
693 o = rv_cat(out, o, "<h2>My circle (shared authorization plane)</h2>" as *u8)
694 // friends: distinct current subjects of (user:me, friend, *)
695 let fl: *i64 = sys_mmap(8 * 64) as *i64
696 let nfr: i64 = rb_list_subjects(prefix, me, "friend" as *u8, fl, 64)
697 o = rv_cat(out, o, "<div class='mut'>Friends (" as *u8); o = rv_catn(out, o, nfr); o = rv_cat(out, o, ")</div>" as *u8)
698 var qi: i64 = 0
699 while qi < nfr {
700 o = rv_cat(out, o, "<div class='row'>" as *u8); o = rv_disp(out, o, fl[qi] as *u8)
701 o = rv_cat(out, o, " <form method='post' action='/relate/defriend' style='display:inline'>" as *u8)
702 o = rv_sess_field(out, o, tok)
703 o = rv_cat(out, o, "<input type='hidden' name='who' value='" as *u8); o = rv_disp(out, o, fl[qi] as *u8)
704 o = rv_cat(out, o, "'><button>defriend</button></form></div>" as *u8)
705 qi = qi + 1
706 }
707 o = rv_cat(out, o, "<form method='post' action='/relate/add-friend'>" as *u8)
708 o = rv_sess_field(out, o, tok)
709 o = rv_cat(out, o, "<input name='who' placeholder='handle to friend'><button>Add friend</button></form>" as *u8)
710 // groups: distinct current objects where (group, owner|member, user:me)
711 let go: *i64 = sys_mmap(8 * 64) as *i64
712 let ngo: i64 = rb_list_objects_for_sub(prefix, me, "owner" as *u8, go, 64)
713 let gm: *i64 = sys_mmap(8 * 64) as *i64
714 let ngm: i64 = rb_list_objects_for_sub(prefix, me, "member" as *u8, gm, 64)
715 o = rv_cat(out, o, "<div class='mut'>Groups (" as *u8); o = rv_catn(out, o, ngo + ngm); o = rv_cat(out, o, ")</div>" as *u8)
716 var gj: i64 = 0
717 while gj < ngo {
718 o = rv_cat(out, o, "<div class='row'>" as *u8); o = rv_disp(out, o, go[gj] as *u8); o = rv_cat(out, o, " <span class='mut'>owner</span></div>" as *u8)
719 gj = gj + 1
720 }
721 gj = 0
722 while gj < ngm {
723 o = rv_cat(out, o, "<div class='row'>" as *u8); o = rv_disp(out, o, gm[gj] as *u8)
724 o = rv_cat(out, o, " <span class='mut'>member</span> <form method='post' action='/relate/leave-group' style='display:inline'>" as *u8)
725 o = rv_sess_field(out, o, tok)
726 o = rv_cat(out, o, "<input type='hidden' name='group' value='" as *u8); o = rv_disp(out, o, gm[gj] as *u8)
727 o = rv_cat(out, o, "'><button>leave</button></form></div>" as *u8)
728 gj = gj + 1
729 }
730 o = rv_cat(out, o, "<form method='post' action='/relate/new-group'>" as *u8)
731 o = rv_sess_field(out, o, tok)
732 o = rv_cat(out, o, "<input name='group' placeholder='new group name'><button>Create group</button></form>" as *u8)
733 o = rv_cat(out, o, "<form method='post' action='/relate/join-group'>" as *u8)
734 o = rv_sess_field(out, o, tok)
735 o = rv_cat(out, o, "<input name='group' placeholder='group to join'><button>Join group</button></form>" as *u8)
736 }
737 // add-person + add-deal + share forms (POST back to this handler). Actions are /relate/-prefixed so they
738 // route correctly THROUGH the public edge (the daemon matches routes by substring, so direct loopback POSTs
739 // to the bare paths still work). Each form carries the hidden `sess` synchronizer token when authed.
740 o = rv_cat(out, o, "<h2>Add a person</h2><form method='post' action='/relate/add-entity'>" as *u8)
741 o = rv_sess_field(out, o, tok)
742 o = rv_cat(out, o, "<input name='type' value='person'><input name='name' placeholder='Full name'><input name='tenant' value='andelinwest'><button>Add</button></form>" as *u8)
743 o = rv_cat(out, o, "<h2>Add a deal</h2><form method='post' action='/relate/add-deal'>" as *u8)
744 o = rv_sess_field(out, o, tok)
745 o = rv_cat(out, o, "<input name='name' placeholder='Deal name'><input name='account' placeholder='Account'><input name='amount' placeholder='Amount'><input name='stage' value='Lead'><input name='tenant' value='andelinwest'><button>Add</button></form>" as *u8)
746 o = rv_cat(out, o, "<h2>Share an item</h2><form method='post' action='/relate/add-share'>" as *u8)
747 o = rv_sess_field(out, o, tok)
748 o = rv_cat(out, o, "<input name='itype' value='job'><input name='title' placeholder='Title'><input name='from' placeholder='From'><input name='to' placeholder='To'><input name='note' placeholder='Note'><input name='reqs' placeholder='reqs skill:lvl,...'><input name='tenant' value='andelinwest'><button>Share</button></form>" as *u8)
749 o = rv_cat(out, o, "</body></html>" as *u8)
750 return o
751}
752// legacy open-mode render (gates + any static consumer): no token, no sess fields, NO tenant filter.
753func rv_render(prefix: *u8, out: *u8, cap: i64) -> i64 {
754 let mt: *u8 = sys_mmap(8)
755 return rv_render_tok(prefix, out, cap, mt, 0 as *i64)
756}
757
758// urldecode form value for `key` from body into out (handles + and %XX)
759func rv_hexval(c: i64) -> i64 {
760 if c >= 48 { if c <= 57 { return c - 48 } }
761 if c >= 65 { if c <= 70 { return c - 55 } }
762 if c >= 97 { if c <= 102 { return c - 87 } }
763 return 0
764}
765func rv_form_get(body: *u8, blen: i64, key: *u8, out: *u8, cap: i64) -> i64 {
766 let kl: i64 = rv_slen(key)
767 var i: i64 = 0
768 while i < blen {
769 // match key= at a field boundary (i==0 or prev char is &)
770 var atk: i64 = 0
771 if i == 0 { atk = 1 } else { if body[i-1] == (38 as u8) { atk = 1 } }
772 if atk == 1 {
773 var m: i64 = 1; var j: i64 = 0
774 while j < kl { if i + j >= blen { m = 0; j = kl } else { if body[i+j] != key[j] { m = 0; j = kl } else { j = j + 1 } } }
775 if m == 1 { if i + kl < blen { if body[i+kl] == (61 as u8) {
776 var q: i64 = i + kl + 1; var t: i64 = 0
777 var go: i64 = 1
778 while go == 1 {
779 if q >= blen { go = 0 } else {
780 let c: i64 = body[q] as i64
781 if c == 38 { go = 0 } else {
782 var ch: i64 = c
783 if c == 43 { ch = 32 }
784 if c == 37 { if q + 2 < blen { ch = rv_hexval(body[q+1] as i64) * 16 + rv_hexval(body[q+2] as i64); q = q + 2 } }
785 if t < cap - 1 { out[t] = ch as u8; t = t + 1 }
786 q = q + 1
787 }
788 }
789 }
790 out[t] = 0 as u8; return t
791 } } }
792 }
793 i = i + 1
794 }
795 out[0] = 0 as u8; return 0
796}
797
798// header value extractor (bytes after `key` up to CR/LF, one leading space skipped). 0 = header absent.
799func rv_hdr_val(req: *u8, rn: i64, key: *u8, out: *u8, cap: i64) -> i64 {
800 let kl: i64 = rv_slen(key)
801 var i: i64 = 0
802 while i + kl < rn {
803 var m: i64 = 1
804 var k: i64 = 0
805 while k < kl { if req[i + k] != key[k] { m = 0; k = kl } else { k = k + 1 } }
806 if m == 1 {
807 var v: i64 = i + kl
808 if v < rn { if req[v] == (32 as u8) { v = v + 1 } }
809 var t: i64 = 0
810 var go: i64 = 1
811 while go == 1 {
812 if v >= rn { go = 0 } else {
813 let c: i64 = req[v] as i64
814 if c == 13 { go = 0 } else { if c == 10 { go = 0 } else {
815 if t < cap - 1 { out[t] = c as u8; t = t + 1 }
816 v = v + 1
817 } }
818 }
819 }
820 out[t] = 0 as u8
821 return t
822 }
823 i = i + 1
824 }
825 out[0] = 0 as u8
826 return 0
827}
828// session token for THIS request: X-Nishi-Session header (fetch clients / the bootstrap re-fetch) wins; else the
829// `sess` form field on a POST body (the zero-JS synchronizer flow -- rv_form_get urldecodes %2B/%2F/%3D back to
830// the b64 alphabet). Returns token length, 0 = none. PURE parse; the caller does the crypto.
831func rv_get_sess(req: *u8, reqlen: i64, out: *u8, cap: i64) -> i64 {
832 let hl: i64 = rv_hdr_val(req, reqlen, "X-Nishi-Session:" as *u8, out, cap)
833 if hl > 0 { return hl }
834 var bs: i64 = 0 - 1
835 var i: i64 = 0
836 while i + 3 < reqlen {
837 if req[i] == (13 as u8) { if req[i+1] == (10 as u8) { if req[i+2] == (13 as u8) { if req[i+3] == (10 as u8) { bs = i + 4; i = reqlen } } } }
838 i = i + 1
839 }
840 if bs < 0 { out[0] = 0 as u8; return 0 }
841 return rv_form_get((req as i64 + bs) as *u8, reqlen - bs, "sess" as *u8, out, cap)
842}
843// RFC9457 problem+json refusal for an unauthenticated mutation (fail-closed: NOTHING persists on this path).
844func rv_emit_401(out: *u8) -> i64 {
845 let body: *u8 = "{\"type\":\"about:blank\",\"title\":\"Unauthorized\",\"status\":401,\"detail\":\"a valid Nishi session is required; sign in at /login and retry\"}" as *u8
846 var o: i64 = 0
847 o = rv_cat(out, o, "HTTP/1.1 401 Unauthorized\r\nContent-Type: application/problem+json\r\nConnection: close\r\nContent-Length: " as *u8)
848 o = rv_catn(out, o, rv_slen(body))
849 o = rv_cat(out, o, "\r\n\r\n" as *u8)
850 o = rv_cat(out, o, body)
851 return o
852}
853// hex-encode n bytes of src into dst (lowercase, NUL-term); returns chars written (2n).
854func rv_hex(dst: *u8, src: *u8, n: i64) -> i64 {
855 let hx: *u8 = "0123456789abcdef" as *u8
856 var o: i64 = 0
857 var i: i64 = 0
858 while i < n {
859 let c: i64 = (src[i] as i64) & 0xff
860 dst[o] = hx[(c >> 4) & 15]; dst[o + 1] = hx[c & 15]
861 o = o + 2; i = i + 1
862 }
863 dst[o] = 0 as u8
864 return o
865}
866// uidhex -> handle from the login daemon's index (TAB-separated: field0=uidhex, field1=handle; '#' = comment).
867// returns handle length into out_h (NUL-term), 0 = not found. Pure over the buffer (gate-testable).
868func rv_idx_lookup(idx: *u8, idxlen: i64, uidhex: *u8, uxn: i64, out_h: *u8, cap: i64) -> i64 {
869 var ls: i64 = 0
870 while ls < idxlen {
871 var le: i64 = ls
872 while le < idxlen { if (idx[le] as i64) == 10 { break } le = le + 1 }
873 if le > ls { if (idx[ls] as i64) != 35 {
874 var tab: i64 = ls
875 while tab < le { if (idx[tab] as i64) == 9 { break } tab = tab + 1 }
876 if tab < le {
877 let f0len: i64 = tab - ls
878 if f0len == uxn {
879 var m: i64 = 1
880 var k: i64 = 0
881 while k < uxn { if idx[ls + k] != uidhex[k] { m = 0; k = uxn } else { k = k + 1 } }
882 if m == 1 {
883 var o: i64 = 0
884 var q: i64 = tab + 1
885 while q < le { if o < cap - 1 { out_h[o] = idx[q]; o = o + 1 } q = q + 1 }
886 out_h[o] = 0 as u8
887 return o
888 }
889 }
890 }
891 } }
892 ls = le + 1
893 }
894 out_h[0] = 0 as u8
895 return 0
896}
897// resolve a raw uid (uidn bytes) to its registered handle via the on-disk index. 0 = unmapped (fail-closed:
898// the caller then holds ZERO grants -> deny-all). idxpath is read fresh per request (small file; always current).
899func rv_resolve_handle(idxpath: *u8, uid: *u8, uidn: i64, out_h: *u8, cap: i64) -> i64 {
900 let szp: *i64 = sys_mmap(16) as *i64
901 let idx: *u8 = ss_readall(idxpath, szp)
902 if (idx as i64) == 0 { out_h[0] = 0 as u8; return 0 }
903 let uxbuf: *u8 = sys_mmap(128)
904 let uxn: i64 = rv_hex(uxbuf, uid, uidn)
905 return rv_idx_lookup(idx, szp[0], uxbuf, uxn, out_h, cap)
906}
907
908// write all n bytes to fd
909func rv_write_all(fd: i64, buf: *u8, n: i64) -> i64 {
910 var w: i64 = 0
911 while w < n { let k: i64 = sys_write(fd, (buf as i64 + w) as *u8, n - w); if k <= 0 { return 0 - 1 } w = w + k }
912 return 0
913}
914
915// read ONE full HTTP request from fd: until CRLFCRLF, plus Content-Length body bytes if present. returns total.
916func rv_read_req(fd: i64, buf: *u8, cap: i64) -> i64 {
917 var n: i64 = 0
918 var hdr_end: i64 = 0 - 1
919 var want: i64 = 0 - 1
920 var go: i64 = 1
921 while go == 1 {
922 if n >= cap - 1 { go = 0 } else {
923 let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - 1 - n)
924 if r <= 0 { go = 0 } else {
925 n = n + r
926 if hdr_end < 0 {
927 var i: i64 = 0
928 while i + 3 < n {
929 if buf[i] == (13 as u8) { if buf[i+1] == (10 as u8) { if buf[i+2] == (13 as u8) { if buf[i+3] == (10 as u8) { hdr_end = i + 4; i = n } } } }
930 i = i + 1
931 }
932 if hdr_end >= 0 {
933 // parse Content-Length (absent -> headers-only request)
934 var cl: i64 = 0 - 1
935 let key: *u8 = "Content-Length:" as *u8
936 let kl: i64 = 15
937 var j: i64 = 0
938 while j + kl < hdr_end {
939 var m: i64 = 1
940 var q: i64 = 0
941 while q < kl { if buf[j+q] != key[q] { m = 0; q = kl } else { q = q + 1 } }
942 if m == 1 {
943 var v: i64 = 0
944 var t: i64 = j + kl
945 while t < hdr_end {
946 let c: i64 = buf[t] as i64
947 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } }
948 if c == 13 { t = hdr_end }
949 t = t + 1
950 }
951 cl = v
952 j = hdr_end
953 }
954 j = j + 1
955 }
956 if cl < 0 { want = hdr_end } else { want = hdr_end + cl }
957 }
958 }
959 if want >= 0 { if n >= want { go = 0 } }
960 }
961 }
962 }
963 return n
964}
965
966// the AUTH+ACL-AWARE interactive handler: request bytes -> response bytes (out); returns response length.
967// auth = the DAEMON's verdict bundle (this core stays crypto-free + offline-gateable):
968// null -> legacy OPEN mode (gates/loopback tools): no gate, no tenant wall, no sess fields.
969// auth[0] (tok) -> 0 = NO valid session: GET = the sessionStorage bootstrap (NO data), POST = 401.
970// auth[1] (hdl) -> the resolved registered handle; EMPTY = authed-but-unmapped -> ZERO grants (deny-all).
971// Authed requests get the caller's grants bundle built from the persisted acl records; every render + write
972// goes through the tenant wall (deny-by-default; the wall beats admin -- the nx_crm_acl law, persisted).
973func rv_handle_auth(prefix: *u8, req: *u8, reqlen: i64, out: *u8, cap: i64, auth: *i64) -> i64 {
974 // method = first token
975 var is_post: i64 = 0
976 if req[0] == (80 as u8) { if req[1] == (79 as u8) { is_post = 1 } } // "PO"
977 var tok: *u8 = 0 as *u8
978 var gr: *i64 = 0 as *i64
979 if (auth as i64) == 0 {
980 tok = sys_mmap(8) // legacy open: empty token (no sess fields), null gr (no wall)
981 } else {
982 tok = auth[0] as *u8
983 if (tok as i64) == 0 {
984 if is_post == 1 { return rv_emit_401(out) }
985 var bo: i64 = 0
986 bo = rv_cat(out, bo, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n" as *u8)
987 bo = rv_cat(out, bo, RV_BOOTSTRAP)
988 return bo
989 }
990 gr = sys_mmap(8 * 4) as *i64
991 gr[0] = auth[1]
992 let gts: *i64 = sys_mmap(8 * 16) as *i64
993 let grs: *i64 = sys_mmap(8 * 16) as *i64
994 gr[1] = rv_acl_load(prefix, auth[1] as *u8, gts, grs)
995 gr[2] = gts as i64
996 gr[3] = grs as i64
997 }
998 // find body (after CRLFCRLF)
999 var bs: i64 = 0 - 1; var i: i64 = 0
1000 while i + 3 < reqlen {
1001 if req[i] == (13 as u8) { if req[i+1] == (10 as u8) { if req[i+2] == (13 as u8) { if req[i+3] == (10 as u8) { bs = i + 4; i = reqlen } } } }
1002 i = i + 1
1003 }
1004 if is_post == 1 {
1005 if bs >= 0 {
1006 let body: *u8 = (req as i64 + bs) as *u8
1007 let blen: i64 = reqlen - bs
1008 if rv_has(req, reqlen, "/add-acl" as *u8) == 1 {
1009 // GRANT a membership. Allowed: a role-4 ADMIN of that tenant; or the BOOTSTRAP window --
1010 // the acl plane is EMPTY and the caller (registered, nonempty handle) grants THEMSELVES first.
1011 let ah: *u8 = sys_mmap(256); let at: *u8 = sys_mmap(128); let arr: *u8 = sys_mmap(32)
1012 rv_form_get(body, blen, "handle" as *u8, ah, 256)
1013 rv_form_get(body, blen, "tenant" as *u8, at, 128)
1014 rv_form_get(body, blen, "role" as *u8, arr, 32)
1015 var allowed: i64 = 0
1016 var grantor: *u8 = "local" as *u8
1017 if (gr as i64) == 0 { allowed = 1 } else {
1018 grantor = gr[0] as *u8
1019 if rv_role_of(gr, at) >= 4 { allowed = 1 }
1020 if allowed == 0 {
1021 if rv_acl_any(prefix) == 0 { if grantor[0] != (0 as u8) { if rv_seq(ah, grantor) == 1 { allowed = 1 } } }
1022 }
1023 }
1024 if allowed == 0 { return rv_emit_403(out) }
1025 if ah[0] != (0 as u8) {
1026 if at[0] != (0 as u8) {
1027 let wa: *i64 = ss_begin()
1028 if rv_save_acl(wa, ah, at, rv_atoi(arr), grantor) == 0 { ss_commit(prefix, wa, sys_now_us()) }
1029 // the caller's own grants may have changed -> reload the bundle before rendering
1030 if (gr as i64) != 0 { gr[1] = rv_acl_load(prefix, gr[0] as *u8, gr[2] as *i64, gr[3] as *i64) }
1031 }
1032 }
1033 }
1034 // SOCIAL: friend/defriend + groups via the SHARED nx_rebac plane. Subject = the AUTHED caller (gr[0]);
1035 // a client CANNOT act as anyone else (no spoofing). Only in enforced mode (a real handle exists).
1036 if (gr as i64) != 0 {
1037 let meh: *u8 = gr[0] as *u8
1038 if meh[0] != (0 as u8) {
1039 let me: *u8 = sys_mmap(320); rv_userref(me, meh)
1040 if rv_has(req, reqlen, "/add-friend" as *u8) == 1 {
1041 let who: *u8 = sys_mmap(128); rv_form_get(body, blen, "who" as *u8, who, 128)
1042 if rv_tok_ok(who) == 1 {
1043 let them: *u8 = sys_mmap(320); rv_userref(them, who)
1044 // mutual friendship (v1: direct on add; a request/accept flow is the next rung)
1045 rb_put(prefix, me, "friend" as *u8, them, meh, 1)
1046 rb_put(prefix, them, "friend" as *u8, me, meh, 1)
1047 }
1048 }
1049 if rv_has(req, reqlen, "/defriend" as *u8) == 1 {
1050 let who: *u8 = sys_mmap(128); rv_form_get(body, blen, "who" as *u8, who, 128)
1051 if rv_tok_ok(who) == 1 {
1052 let them: *u8 = sys_mmap(320); rv_userref(them, who)
1053 rb_put(prefix, me, "friend" as *u8, them, meh, 0)
1054 rb_put(prefix, them, "friend" as *u8, me, meh, 0)
1055 }
1056 }
1057 if rv_has(req, reqlen, "/new-group" as *u8) == 1 {
1058 let gn: *u8 = sys_mmap(128); rv_form_get(body, blen, "group" as *u8, gn, 128)
1059 if rv_tok_ok(gn) == 1 {
1060 let gref: *u8 = sys_mmap(320); rv_groupref(gref, gn)
1061 // claim only if UNCLAIMED (rb_may_grant bootstrap) -> caller becomes owner AND member
1062 if rb_may_grant(prefix, me, gref) == 1 {
1063 rb_put(prefix, gref, "owner" as *u8, me, meh, 1)
1064 rb_put(prefix, gref, "member" as *u8, me, meh, 1)
1065 }
1066 }
1067 }
1068 if rv_has(req, reqlen, "/join-group" as *u8) == 1 {
1069 let gn: *u8 = sys_mmap(128); rv_form_get(body, blen, "group" as *u8, gn, 128)
1070 if rv_tok_ok(gn) == 1 {
1071 let gref: *u8 = sys_mmap(320); rv_groupref(gref, gn)
1072 rb_put(prefix, gref, "member" as *u8, me, meh, 1) // open join (demo); group-policy gate = next rung
1073 }
1074 }
1075 if rv_has(req, reqlen, "/leave-group" as *u8) == 1 {
1076 let gn: *u8 = sys_mmap(128); rv_form_get(body, blen, "group" as *u8, gn, 128)
1077 if rv_tok_ok(gn) == 1 {
1078 let gref: *u8 = sys_mmap(320); rv_groupref(gref, gn)
1079 rb_put(prefix, gref, "member" as *u8, me, meh, 0)
1080 }
1081 }
1082 }
1083 }
1084 if rv_has(req, reqlen, "/add-entity" as *u8) == 1 {
1085 var ty: *u8 = sys_mmap(128); let nm: *u8 = sys_mmap(256); let tn: *u8 = sys_mmap(128)
1086 rv_form_get(body, blen, "type" as *u8, ty, 128)
1087 rv_form_get(body, blen, "name" as *u8, nm, 256)
1088 if rv_wcheck(gr, body, blen, tn) == 0 { return rv_emit_403(out) }
1089 if ty[0] == (0 as u8) { ty = "person" as *u8 }
1090 if nm[0] != (0 as u8) { rv_persist_entity(prefix, ty, nm, tn) }
1091 }
1092 if rv_has(req, reqlen, "/add-deal" as *u8) == 1 {
1093 let dn: *u8 = sys_mmap(256); let da: *u8 = sys_mmap(256); let dm: *u8 = sys_mmap(64); let dsg: *u8 = sys_mmap(64); let dtn: *u8 = sys_mmap(128)
1094 rv_form_get(body, blen, "name" as *u8, dn, 256)
1095 rv_form_get(body, blen, "account" as *u8, da, 256)
1096 rv_form_get(body, blen, "amount" as *u8, dm, 64)
1097 rv_form_get(body, blen, "stage" as *u8, dsg, 64)
1098 if rv_wcheck(gr, body, blen, dtn) == 0 { return rv_emit_403(out) }
1099 // validated: unknown stage / empty name never persists (deny-by-default)
1100 if dn[0] != (0 as u8) { rv_persist_deal(prefix, dn, da, rv_atoi(dm), dsg, dtn) }
1101 }
1102 if rv_has(req, reqlen, "/add-share" as *u8) == 1 {
1103 let sty: *u8 = sys_mmap(64); let stt: *u8 = sys_mmap(256); let sfr: *u8 = sys_mmap(256)
1104 let sto: *u8 = sys_mmap(256); let sno: *u8 = sys_mmap(256); let sre: *u8 = sys_mmap(512); let stn: *u8 = sys_mmap(128)
1105 rv_form_get(body, blen, "itype" as *u8, sty, 64)
1106 rv_form_get(body, blen, "title" as *u8, stt, 256)
1107 rv_form_get(body, blen, "from" as *u8, sfr, 256)
1108 rv_form_get(body, blen, "to" as *u8, sto, 256)
1109 rv_form_get(body, blen, "note" as *u8, sno, 256)
1110 rv_form_get(body, blen, "reqs" as *u8, sre, 512)
1111 if rv_wcheck(gr, body, blen, stn) == 0 { return rv_emit_403(out) }
1112 // validated: bad itype / empty title never persists
1113 if stt[0] != (0 as u8) {
1114 if rv_itype_ok(sty) == 1 {
1115 let sa: *i64 = sys_mmap(8 * 8) as *i64
1116 sa[0] = sty as i64
1117 sa[1] = stt as i64
1118 sa[2] = sfr as i64
1119 sa[3] = sto as i64
1120 sa[4] = sno as i64
1121 sa[5] = sre as i64
1122 sa[6] = stn as i64
1123 let w2: *i64 = ss_begin()
1124 if rv_save_share(w2, sa) == 0 { ss_commit(prefix, w2, sys_now_us()) }
1125 }
1126 }
1127 }
1128 }
1129 }
1130 return rv_render_tok(prefix, out, cap, tok, gr)
1131}
1132// legacy entry (offline gate + live gate + any pre-auth caller): OPEN mode -- no gate, no wall, no sess fields.
1133func rv_handle(prefix: *u8, req: *u8, reqlen: i64, out: *u8, cap: i64) -> i64 {
1134 return rv_handle_auth(prefix, req, reqlen, out, cap, 0 as *i64)
1135}
1136
1137func rv_selftest() -> i64 {
1138 p("=== NX-RELATE-SERVE SELFTEST (GET renders persisted graph; POST /add-entity persists + shows up) ===\n" as *u8)
1139 let prefix: *u8 = sys_mmap(128); var po: i64 = 0
1140 po = rv_cat(prefix, po, "/tmp/relserve_" as *u8); po = rv_catn(prefix, po, sys_now_us()); po = rv_cat(prefix, po, "_" as *u8); prefix[po] = 0 as u8
1141
1142 // seed the store: entities + 2 deals + 1 gift (weighted oracle: 50000*50 + 120000*75 = 115000)
1143 let w: *i64 = ss_begin()
1144 rv_save_entity(w, "person" as *u8, "Rose Andelin" as *u8, "andelinwest" as *u8)
1145 rv_save_entity(w, "company" as *u8, "Andelin West" as *u8, "andelinwest" as *u8)
1146 rv_save_deal(w, "Westfield Renovation" as *u8, "Westfield Group" as *u8, 50000, "Proposal" as *u8, "andelinwest" as *u8)
1147 rv_save_deal(w, "Riverside Contract" as *u8, "Riverside LLC" as *u8, 120000, "Negotiation" as *u8, "andelinwest" as *u8)
1148 rv_save_gift(w, "Rose" as *u8, "Tithing" as *u8, 500, "donation" as *u8)
1149 let sha: *i64 = sys_mmap(8 * 8) as *i64
1150 sha[0]="job" as *u8 as i64; sha[1]="Sales Director at Riverside" as *u8 as i64; sha[2]="Marcus" as *u8 as i64
1151 sha[3]="Emma" as *u8 as i64; sha[4]="warm intro from your recruiter" as *u8 as i64; sha[5]="sales:5,leadership:4,forecasting:3" as *u8 as i64
1152 sha[6]="andelinwest" as *u8 as i64
1153 rv_save_share(w, sha)
1154 if ss_commit(prefix, w, sys_now_us()) != 0 { p("seed FAILED -- fail loud\n" as *u8); return 1 }
1155
1156 let out: *u8 = sys_mmap(65536)
1157 // 1) GET renders the seeded graph
1158 let n1: i64 = rv_handle(prefix, "GET / HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 27, out, 65536)
1159 let get_shows_rose: i64 = rv_has(out, n1, "Rose Andelin" as *u8)
1160 let get_shows_deal: i64 = rv_has(out, n1, "Westfield Renovation" as *u8)
1161 let get_shows_wgt: i64 = rv_has(out, n1, "Weighted forecast $115000" as *u8)
1162 let get_shows_gift: i64 = rv_has(out, n1, "Received $500" as *u8)
1163 // 2) POST adds First Ward (urlencoded "First+Ward")
1164 let post: *u8 = "POST /add-entity HTTP/1.1\r\nContent-Length: 41\r\n\r\ntype=org&name=First+Ward&tenant=personal" as *u8
1165 let n2: i64 = rv_handle(prefix, post, rv_slen(post), out, 65536)
1166 let ent_after: i64 = rv_count_ent(prefix)
1167 // 3) GET again shows the newly added First Ward
1168 let n3: i64 = rv_handle(prefix, "GET / HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 27, out, 65536)
1169 let get_shows_ward: i64 = rv_has(out, n3, "First Ward" as *u8)
1170
1171 // 4) POST /add-deal (Downtown 30000 Qualified) -> forecast moves to the canonical 122500
1172 let dpost: *u8 = "POST /add-deal HTTP/1.1\r\nContent-Length: 68\r\n\r\nname=Downtown+Lease&account=Downtown+Co&amount=30000&stage=Qualified" as *u8
1173 let n4: i64 = rv_handle(prefix, dpost, rv_slen(dpost), out, 65536)
1174 let n5: i64 = rv_handle(prefix, "GET / HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 27, out, 65536)
1175 let deal_added: i64 = rv_has(out, n5, "Downtown Lease" as *u8)
1176 let wgt_moved: i64 = rv_has(out, n5, "Weighted forecast $122500" as *u8)
1177 // 5) a bad stage NEVER persists (deny-by-default)
1178 let bpost: *u8 = "POST /add-deal HTTP/1.1\r\nContent-Length: 47\r\n\r\nname=Bad+Deal&account=X&amount=9999&stage=Vibes" as *u8
1179 let n6: i64 = rv_handle(prefix, bpost, rv_slen(bpost), out, 65536)
1180 let n7: i64 = rv_handle(prefix, "GET / HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 27, out, 65536)
1181 let bad_absent: i64 = rv_has(out, n7, "Bad Deal" as *u8)
1182 let wgt_stable: i64 = rv_has(out, n7, "Weighted forecast $122500" as *u8)
1183 // 6) the SHARE inbox: seeded share renders; POST /add-share persists; bad itype never lands
1184 let share_seeded: i64 = rv_has(out, n7, "Sales Director at Riverside" as *u8)
1185 let spost: *u8 = "POST /add-share HTTP/1.1\r\nContent-Length: 70\r\n\r\nitype=resource&title=Negotiation+notes&from=Dana&to=Emma¬e=ch3&reqs=" as *u8
1186 let n8: i64 = rv_handle(prefix, spost, rv_slen(spost), out, 65536)
1187 let share_added: i64 = rv_has(out, n8, "Negotiation notes" as *u8)
1188 let xpost: *u8 = "POST /add-share HTTP/1.1\r\nContent-Length: 55\r\n\r\nitype=gossip&title=Bad+Share&from=X&to=Y¬e=n&reqs=" as *u8
1189 let n9: i64 = rv_handle(prefix, xpost, rv_slen(xpost), out, 65536)
1190 let badshare_absent: i64 = rv_has(out, n9, "Bad Share" as *u8)
1191
1192 // 7) AUTH MODE (auth bundle: [0]=tok, [1]=handle; the daemon shell does the crypto, this gates semantics):
1193 // unauthed GET = bootstrap ONLY (no stored data leaks); unauthed POST = 401 + NOTHING persists.
1194 let ub: *i64 = sys_mmap(16) as *i64 // ub[0]=0 -> no valid session
1195 let na: i64 = rv_handle_auth(prefix, "GET / HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 27, out, 65536, ub)
1196 let gate_bootstrap: i64 = rv_has(out, na, "/login?return=/relate" as *u8)
1197 let gate_leaks: i64 = rv_has(out, na, "Rose Andelin" as *u8)
1198 let ent_pre_evil: i64 = rv_count_ent(prefix)
1199 let epost: *u8 = "POST /add-entity HTTP/1.1\r\nContent-Length: 41\r\n\r\ntype=org&name=Evil+Org&tenant=andelinwest" as *u8
1200 let nb: i64 = rv_handle_auth(prefix, epost, rv_slen(epost), out, 65536, ub)
1201 let post_401: i64 = rv_has(out, nb, "401 Unauthorized" as *u8)
1202 var evil_blocked: i64 = 0
1203 if rv_count_ent(prefix) == ent_pre_evil { evil_blocked = 1 }
1204 // 10) THE TENANT WALL (deny-by-default, persisted acl records, the nx_crm_acl law):
1205 // authed + ZERO grants -> the app frame renders (forms + sess token) but NO records leak
1206 let ftok: *u8 = "FAKETOKb64FORRENDER" as *u8
1207 let ab: *i64 = sys_mmap(16) as *i64
1208 ab[0] = ftok as i64
1209 ab[1] = "tester" as *u8 as i64
1210 let nc: i64 = rv_handle_auth(prefix, "GET / HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 27, out, 65536, ab)
1211 let tok_embedded: i64 = rv_has(out, nc, "name='sess' value='FAKETOKb64FORRENDER'" as *u8)
1212 let wall_denies_all: i64 = 1 - rv_has(out, nc, "Rose Andelin" as *u8)
1213 // authed + no grant: WRITE refused 403 + nothing persists
1214 let dpost2: *u8 = "POST /add-deal HTTP/1.1\r\nContent-Length: 66\r\n\r\nname=Blocked+Deal&account=X&amount=5&stage=Lead&tenant=andelinwest" as *u8
1215 let nw: i64 = rv_handle_auth(prefix, dpost2, rv_slen(dpost2), out, 65536, ab)
1216 let write_403: i64 = rv_has(out, nw, "403 Forbidden" as *u8)
1217 // BOOTSTRAP: acl plane EMPTY -> a registered caller may self-grant the FIRST membership
1218 let bpost2: *u8 = "POST /relate/add-acl HTTP/1.1\r\nContent-Length: 39\r\n\r\nhandle=tester&tenant=andelinwest&role=4" as *u8
1219 let ng: i64 = rv_handle_auth(prefix, bpost2, rv_slen(bpost2), out, 65536, ab)
1220 let boot_grant_ok: i64 = rv_has(out, ng, "you are tester" as *u8)
1221 // granted andelinwest: Rose visible, but First Ward (tenant=personal) stays WALLED OFF
1222 let nc2: i64 = rv_handle_auth(prefix, "GET / HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 27, out, 65536, ab)
1223 let authed_shows: i64 = rv_has(out, nc2, "Rose Andelin" as *u8)
1224 let ward_walled: i64 = 1 - rv_has(out, nc2, "First Ward" as *u8)
1225 let admin_sees_amt: i64 = rv_has(out, nc2, "Received $500" as *u8)
1226 // bootstrap window CLOSED: a second no-grant user cannot self-grant anymore
1227 let ab2: *i64 = sys_mmap(16) as *i64
1228 ab2[0] = ftok as i64
1229 ab2[1] = "tester2" as *u8 as i64
1230 let cpost: *u8 = "POST /relate/add-acl HTTP/1.1\r\nContent-Length: 40\r\n\r\nhandle=tester2&tenant=andelinwest&role=4" as *u8
1231 let nh: i64 = rv_handle_auth(prefix, cpost, rv_slen(cpost), out, 65536, ab2)
1232 let boot_closed: i64 = rv_has(out, nh, "403 Forbidden" as *u8)
1233 // the ADMIN grants a MEMBER (role 2); members see records but giving AMOUNTS are field-masked (minrole 3)
1234 let gpost: *u8 = "POST /relate/add-acl HTTP/1.1\r\nContent-Length: 40\r\n\r\nhandle=tester3&tenant=andelinwest&role=2" as *u8
1235 let ni: i64 = rv_handle_auth(prefix, gpost, rv_slen(gpost), out, 65536, ab)
1236 let ab3: *i64 = sys_mmap(16) as *i64
1237 ab3[0] = ftok as i64
1238 ab3[1] = "tester3" as *u8 as i64
1239 let nj: i64 = rv_handle_auth(prefix, "GET / HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 27, out, 65536, ab3)
1240 let member_sees: i64 = rv_has(out, nj, "Rose Andelin" as *u8)
1241 let member_masked: i64 = rv_has(out, nj, "(amount restricted)" as *u8)
1242 // cross-tenant WRITE by a member of another tenant -> 403 + nothing persists (the wall beats intent)
1243 let ent_pre_cross: i64 = rv_count_ent(prefix)
1244 let xpost2: *u8 = "POST /add-entity HTTP/1.1\r\nContent-Length: 39\r\n\r\ntype=org&name=Cross+Org&tenant=personal" as *u8
1245 let nk: i64 = rv_handle_auth(prefix, xpost2, rv_slen(xpost2), out, 65536, ab3)
1246 let cross_403: i64 = rv_has(out, nk, "403 Forbidden" as *u8)
1247 var cross_blocked: i64 = 0
1248 if rv_count_ent(prefix) == ent_pre_cross { cross_blocked = 1 }
1249 // 8) rv_get_sess parse: header wins; POST-body `sess` field urldecodes back to the b64 alphabet
1250 let sb: *u8 = sys_mmap(600)
1251 let hreq: *u8 = "GET / HTTP/1.1\r\nX-Nishi-Session: AbCd+/=99\r\n\r\n" as *u8
1252 let hn: i64 = rv_get_sess(hreq, rv_slen(hreq), sb, 600)
1253 let hdr_tok_ok: i64 = rv_seq(sb, "AbCd+/=99" as *u8)
1254 let freq: *u8 = "POST /relate/add-entity HTTP/1.1\r\nContent-Length: 21\r\n\r\nsess=Zz%2Bq%2F%3D&x=1" as *u8
1255 let fn2: i64 = rv_get_sess(freq, rv_slen(freq), sb, 600)
1256 let fld_tok_ok: i64 = rv_seq(sb, "Zz+q/=" as *u8)
1257 // 9) stored-XSS liar-kill: a stored <script> renders ESCAPED (with the sess token in the page, raw = theft)
1258 let xw: *i64 = ss_begin()
1259 rv_save_entity(xw, "person" as *u8, "<script>alert(1)</script>" as *u8, "t" as *u8)
1260 ss_commit(prefix, xw, sys_now_us())
1261 let nd: i64 = rv_handle(prefix, "GET / HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 27, out, 65536)
1262 let xss_raw: i64 = rv_has(out, nd, "<script>alert" as *u8)
1263 let xss_esc: i64 = rv_has(out, nd, "<script>alert" as *u8)
1264 // 11) uid->handle resolution (the daemon maps a validated session uid to its registered handle for the ACL):
1265 let hxb: *u8 = sys_mmap(16)
1266 let twob: *u8 = sys_mmap(4); twob[0] = 0xAB as u8; twob[1] = 0x0F as u8
1267 rv_hex(hxb, twob, 2)
1268 let hex_ok: i64 = rv_seq(hxb, "ab0f" as *u8)
1269 let idxbuf: *u8 = "# comment line\naabb\thandleA\ncc11\thandleB\n" as *u8
1270 let hb2: *u8 = sys_mmap(64)
1271 rv_idx_lookup(idxbuf, rv_slen(idxbuf), "cc11" as *u8, 4, hb2, 64)
1272 let idx_hit: i64 = rv_seq(hb2, "handleB" as *u8)
1273 let miss: i64 = rv_idx_lookup(idxbuf, rv_slen(idxbuf), "dead" as *u8, 4, hb2, 64)
1274 var idx_miss: i64 = 0
1275 if miss == 0 { idx_miss = 1 }
1276
1277 p(" GET1 bytes=" as *u8); pn(n1); p(" shows-Rose=" as *u8); pn(get_shows_rose)
1278 p(" deal=" as *u8); pn(get_shows_deal); p(" wgt115000=" as *u8); pn(get_shows_wgt); p(" gift500=" as *u8); pn(get_shows_gift); p("\n" as *u8)
1279 p(" after POSTs: entities=" as *u8); pn(ent_after); p(" ward=" as *u8); pn(get_shows_ward)
1280 p(" deal-added=" as *u8); pn(deal_added); p(" wgt122500=" as *u8); pn(wgt_moved)
1281 p(" bad-stage-absent=" as *u8); pn(1 - bad_absent); p(" wgt-stable=" as *u8); pn(wgt_stable); p("\n" as *u8)
1282
1283 var ok: i64 = 1
1284 if get_shows_rose != 1 { ok = 0 }
1285 if get_shows_deal != 1 { ok = 0 }
1286 if get_shows_wgt != 1 { ok = 0 }
1287 if get_shows_gift != 1 { ok = 0 }
1288 if ent_after != 3 { ok = 0 }
1289 if get_shows_ward != 1 { ok = 0 }
1290 if deal_added != 1 { ok = 0 }
1291 if wgt_moved != 1 { ok = 0 }
1292 if bad_absent != 0 { ok = 0 }
1293 if wgt_stable != 1 { ok = 0 }
1294 if share_seeded != 1 { ok = 0 }
1295 if share_added != 1 { ok = 0 }
1296 if badshare_absent != 0 { ok = 0 }
1297 if gate_bootstrap != 1 { ok = 0 }
1298 if gate_leaks != 0 { ok = 0 }
1299 if post_401 != 1 { ok = 0 }
1300 if evil_blocked != 1 { ok = 0 }
1301 if tok_embedded != 1 { ok = 0 }
1302 if authed_shows != 1 { ok = 0 }
1303 if hdr_tok_ok != 1 { ok = 0 }
1304 if fld_tok_ok != 1 { ok = 0 }
1305 if xss_raw != 0 { ok = 0 }
1306 if xss_esc != 1 { ok = 0 }
1307 // R3.5 tenant wall (the nx_crm_acl law, persisted + enforced in the serve path)
1308 if wall_denies_all != 1 { ok = 0 }
1309 if write_403 != 1 { ok = 0 }
1310 if boot_grant_ok != 1 { ok = 0 }
1311 if ward_walled != 1 { ok = 0 }
1312 if admin_sees_amt != 1 { ok = 0 }
1313 if boot_closed != 1 { ok = 0 }
1314 if member_sees != 1 { ok = 0 }
1315 if member_masked != 1 { ok = 0 }
1316 if cross_403 != 1 { ok = 0 }
1317 if cross_blocked != 1 { ok = 0 }
1318 if hex_ok != 1 { ok = 0 }
1319 if idx_hit != 1 { ok = 0 }
1320 if idx_miss != 1 { ok = 0 }
1321 p(" shares: seeded=" as *u8); pn(share_seeded); p(" added=" as *u8); pn(share_added); p(" bad-itype-absent=" as *u8); pn(1 - badshare_absent); p("\n" as *u8)
1322 p(" auth: gate-bootstrap=" as *u8); pn(gate_bootstrap); p(" no-leak=" as *u8); pn(1 - gate_leaks)
1323 p(" post-401=" as *u8); pn(post_401); p(" evil-blocked=" as *u8); pn(evil_blocked)
1324 p(" tok-embedded=" as *u8); pn(tok_embedded); p("\n" as *u8)
1325 p(" WALL: authed-no-grant-denies-all=" as *u8); pn(wall_denies_all); p(" write-403=" as *u8); pn(write_403)
1326 p(" bootstrap-self-grant=" as *u8); pn(boot_grant_ok); p(" bootstrap-CLOSES=" as *u8); pn(boot_closed); p("\n" as *u8)
1327 p(" admin-sees(rose=" as *u8); pn(authed_shows); p(",amt=" as *u8); pn(admin_sees_amt); p(") other-tenant-ward-WALLED=" as *u8); pn(ward_walled); p("\n" as *u8)
1328 p(" member-sees-records=" as *u8); pn(member_sees); p(" but-amount-field-masked=" as *u8); pn(member_masked)
1329 p(" cross-tenant-write-403=" as *u8); pn(cross_403); p(" nothing-persisted=" as *u8); pn(cross_blocked); p("\n" as *u8)
1330 p(" sess-parse: hdr=" as *u8); pn(hdr_tok_ok); p(" field-urldecoded=" as *u8); pn(fld_tok_ok)
1331 p(" xss: raw-absent=" as *u8); pn(1 - xss_raw); p(" escaped=" as *u8); pn(xss_esc); p("\n" as *u8)
1332 p("NX-RELATE-SERVE " as *u8)
1333 if ok == 1 { p("verdict=GREEN (live-from-store app + R2 AUTH + R3.5 TENANT WALL: unauthed=bootstrap/401; authed-no-grant sees NOTHING; deny-by-default wall beats admin; cross-tenant read+write walled; giving amounts field-masked below manager; bootstrap self-grant closes after first; stored data HTML-escaped)\n" as *u8); return 0 }
1334 p("verdict=RED\n" as *u8)
1335 return 1
1336}