nx_matter_lib.nx source
↩ module page · 201 lines · 8213 B
1// nx_matter_lib.nx -- Nishi Office: Matter/Client SPINE (F502) core lib.
2// Client/matter/party records on the append-only immutable seg_store (nx_registry),
3// canonicalized + content-addressed (nx_canon_cid). The legal-SOTA feature = CONFLICTS
4// CHECK (RPC 1.7/1.9): scan every matter's parties for a name across the whole book.
5// LIB (no main) so the CLI organ AND the independent gate import the SAME shipped code.
6// SCALE-LAW (DECLARED): conflict scan is O(N-parties) linear (fail-closed, exact); a
7// party-name index = the flagged next rung, NOT a hidden cap. license_tier: ORIGINAL
8
9import "nx_canon_cid.nx"
10import "nx_registry.nx"
11import "nx_itoa_lib.nx" // THE shared MSB-first emitter -- mt_catn composes it (see below)
12const K_MAGIC_2048: i64 = 2048
13
14func mt_r32(p: *u8, off: i64) -> i64 {
15 let a: i64 = p[off]
16 let b: i64 = p[off + 1]
17 let c: i64 = p[off + 2]
18 let d: i64 = p[off + 3]
19 return (((((a << 8) | b) << 8) | c) << 8) | d
20}
21
22func mt_catcopy(dst: *u8, off: i64, s: *u8) -> i64 {
23 var i: i64 = 0
24 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
25 return off + i
26}
27
28// ★COMPOSES THE SHARED EMITTER (2026-08-15). This was a hand-rolled clone -- one of the ~87 the
29// nx_itoa_lib header counts -- and re-typing it had cost THREE separate defects, all of which the
30// incumbent already handled:
31// 1. NEGATIVES emitted ZERO BYTES. The digit loop was `while m > 0` with `m == 0` as the only
32// special case, so a negative rendered as an EMPTY FIELD. Caught live as `overdraw_rc=` with
33// nothing after it -- the value was -1, the REFUSAL SENTINEL, and the tooth asserting it was
34// PASSING throughout. ★★A NUMBER FORMATTER THAT EMITS NOTHING FOR A NEGATIVE TURNS AN ERROR CODE
35// INTO AN ABSENT FIELD, and an absent field reads as "not measured" rather than "measured, and
36// negative" -- the worst available direction for a BILLING library, because every positive
37// amount kept printing perfectly.
38// 2. i64 MIN. My first fix wrote `m = 0 - m`, which for INT64_MIN stays NEGATIVE, so the loop ran
39// zero times and it emitted a lone '-'. nxi_buf clamps that case explicitly.
40// 3. A LEAK: `sys_mmap(24)` PER CALL, never freed -- needed only because the old body built digits
41// least-significant-first and had to reverse through scratch. nxi_buf is MSB-first and allocates
42// NOTHING. That is the exact leak nx_itoa_lib was extracted to kill.
43// ★★★FIXING A CLONE BY PATCHING IT LEAVES THE OTHER 86 AND ADDS A NEW BUG; FIXING IT BY COMPOSING THE
44// INCUMBENT INHERITS EVERY GUARD THE INCUMBENT ALREADY EARNED. Contract is identical (digits only, no
45// NUL, returns the offset AFTER them), which is precisely why nxi_buf exists alongside ccz_cat_num.
46func mt_catn(dst: *u8, off: i64, v: i64) -> i64 {
47 return nxi_buf(dst, off, v)
48}
49
50func mt_streq(a: *u8, b: *u8) -> i64 {
51 var i: i64 = 0
52 while 1 == 1 {
53 if a[i] != b[i] { return 0 }
54 if a[i] == (0 as u8) { return 1 }
55 i = i + 1
56 }
57 return 1
58}
59
60// decode a field value from an NXR1 canonical record ("NXR1"|u32 n|(u32 kl|k|u32 vl|v)*)
61// into out (NUL-terminated); returns vlen or -1 if absent.
62func mt_field(rec: *u8, reclen: i64, key: *u8, keylen: i64, out: *u8) -> i64 {
63 let n: i64 = mt_r32(rec, 4)
64 var o: i64 = 8
65 var i: i64 = 0
66 while i < n {
67 let kl: i64 = mt_r32(rec, o)
68 o = o + 4
69 let kstart: i64 = o
70 o = o + kl
71 let vl: i64 = mt_r32(rec, o)
72 o = o + 4
73 let vstart: i64 = o
74 o = o + vl
75 if kl == keylen {
76 var mm: i64 = 1
77 var c: i64 = 0
78 while c < kl { if rec[kstart + c] != key[c] { mm = 0 } c = c + 1 }
79 if mm == 1 {
80 var d: i64 = 0
81 while d < vl { out[d] = rec[vstart + d]; d = d + 1 }
82 out[vl] = 0 as u8
83 return vl
84 }
85 }
86 i = i + 1
87 }
88 out[0] = 0 as u8
89 return 0 - 1
90}
91
92// build a canonical record from n (key,val) string pairs into out; returns len
93func mt_rec(keys: *i64, vals: *i64, n: i64, out: *u8) -> i64 {
94 return canon_encode(keys, vals, n, out)
95}
96
97func mt_client_put(prefix: *u8, id: *u8, name: *u8, email: *u8) -> i64 {
98 let k: *i64 = sys_mmap(8 * 3) as *i64
99 let v: *i64 = sys_mmap(8 * 3) as *i64
100 k[0] = ("name" as *u8) as i64
101 v[0] = name as i64
102 k[1] = ("email" as *u8) as i64
103 v[1] = email as i64
104 k[2] = ("type" as *u8) as i64
105 v[2] = ("client" as *u8) as i64
106 let rec: *u8 = sys_mmap(K_MAGIC_2048)
107 let rl: i64 = canon_encode(k, v, 3, rec)
108 return reg_put(prefix, "client:" as *u8, "client:__idx__" as *u8, id, rec, rl)
109}
110
111func mt_matter_put(prefix: *u8, mid: *u8, client: *u8, title: *u8, area: *u8) -> i64 {
112 let k: *i64 = sys_mmap(8 * 4) as *i64
113 let v: *i64 = sys_mmap(8 * 4) as *i64
114 k[0] = ("client" as *u8) as i64
115 v[0] = client as i64
116 k[1] = ("title" as *u8) as i64
117 v[1] = title as i64
118 k[2] = ("area" as *u8) as i64
119 v[2] = area as i64
120 k[3] = ("status" as *u8) as i64
121 v[3] = ("open" as *u8) as i64
122 let rec: *u8 = sys_mmap(K_MAGIC_2048)
123 let rl: i64 = canon_encode(k, v, 4, rec)
124 return reg_put(prefix, "matter:" as *u8, "matter:__idx__" as *u8, mid, rec, rl)
125}
126
127// party id = "<mid>:<seq>"
128func mt_party_add(prefix: *u8, mid: *u8, seq: i64, name: *u8, role: *u8) -> i64 {
129 let pid: *u8 = sys_mmap(128)
130 var o: i64 = mt_catcopy(pid, 0, mid)
131 pid[o] = 58 as u8
132 o = o + 1
133 o = mt_catn(pid, o, seq)
134 pid[o] = 0 as u8
135 let k: *i64 = sys_mmap(8 * 3) as *i64
136 let v: *i64 = sys_mmap(8 * 3) as *i64
137 k[0] = ("matter" as *u8) as i64
138 v[0] = mid as i64
139 k[1] = ("name" as *u8) as i64
140 v[1] = name as i64
141 k[2] = ("role" as *u8) as i64
142 v[2] = role as i64
143 let rec: *u8 = sys_mmap(K_MAGIC_2048)
144 let rl: i64 = canon_encode(k, v, 3, rec)
145 return reg_put(prefix, "party:" as *u8, "party:__idx__" as *u8, pid, rec, rl)
146}
147
148// CONFLICTS CHECK: count parties named `name` across ALL matters; append matching
149// matter ids (newline-sep) into matters. Returns the match count.
150func mt_conflict(prefix: *u8, name: *u8, matters: *u8) -> i64 {
151 // SIZE-TO-NEED: buffer derived from the index itself. The pair this replaces
152 // returned -1 once the party index passed 1 MiB, and -1 skips the walk below
153 // -- a CONFLICT CHECK would have reported NO CONFLICT because the index grew.
154 let idxbox: *i64 = sys_mmap(16) as *i64
155 let ilen: i64 = reg_index_read(prefix, "party:__idx__" as *u8, idxbox)
156 let idx: *u8 = idxbox[0] as *u8
157 let idb: *u8 = sys_mmap(256)
158 let po: *i64 = sys_mmap(16) as *i64
159 let lo: *i64 = sys_mmap(16) as *i64
160 let nmb: *u8 = sys_mmap(256)
161 let mtb: *u8 = sys_mmap(256)
162 var count: i64 = 0
163 var mo: i64 = 0
164 var ls: i64 = 0
165 var i: i64 = 0
166 while i <= ilen {
167 var eol: i64 = 0
168 if i == ilen { eol = 1 }
169 if i < ilen { if idx[i] == 10 as u8 { eol = 1 } }
170 if eol == 1 {
171 if i > ls {
172 var c: i64 = 0
173 while ls + c < i { idb[c] = idx[ls + c]; c = c + 1 }
174 idb[c] = 0 as u8
175 if reg_get(prefix, "party:" as *u8, idb, po, lo) == 1 {
176 let rec: *u8 = po[0] as *u8
177 mt_field(rec, lo[0], "name" as *u8, 4, nmb)
178 if mt_streq(nmb, name) == 1 {
179 mt_field(rec, lo[0], "matter" as *u8, 6, mtb)
180 mo = mt_catcopy(matters, mo, mtb)
181 matters[mo] = 10 as u8
182 mo = mo + 1
183 count = count + 1
184 }
185 }
186 }
187 ls = i + 1
188 }
189 i = i + 1
190 }
191 matters[mo] = 0 as u8
192 return count
193}
194
195// fetch matter field into out; returns vlen or -1
196func mt_matter_field(prefix: *u8, mid: *u8, field: *u8, flen: i64, out: *u8) -> i64 {
197 let po: *i64 = sys_mmap(16) as *i64
198 let lo: *i64 = sys_mmap(16) as *i64
199 if reg_get(prefix, "matter:" as *u8, mid, po, lo) != 1 { out[0] = 0 as u8; return 0 - 1 }
200 return mt_field(po[0] as *u8, lo[0], field, flen, out)
201}