code wiki / _hdl_build / nx_inventory.nx
nx_inventory.nx source
↩ module page · 338 lines · 14031 B
1// nx_inventory.nx -- LIB: the UNIVERSAL inventory / supply-chain substrate (operator: "the inventory and other
2// systems hardware-rung-up to manage any and all supply chains -- cataloging for a will all the things a person
3// owns, what's in the pantry and when it's expiring, what can I make, meal planning"). ONE domain-agnostic item
4// store; the pantry, an estate/will asset catalog, and any other supply chain are just different `domain`s over
5// the same engine (the inventory analogue of the universal builder). Sovereign (seg-store, NO TSV), INTEGER
6// only (quantities are counts; money is CENTS -- no float), additive (history kept; consume = a new version).
7// Schema (prefix passed in, e.g. knowledge/store/inv-):
8// inv:<domain>:ids -> TAB list of item ids in the domain
9// inv:<domain>:item:<id> -> name <t> category <t> qty <t> unit <t> location <t> value_cents <t> expiry_day <t> owner
10// (expiry_day = integer day number; 0 = non-perishable. owner = beneficiary for a will, or "" for a pantry.)
11// license_tier: ORIGINAL
12import "nx_seg_store.nx"
13import "nx_ad_serve.nx"
14import "nx_syscalls.nx"
15
16// The id-list capacity every caller in this file allocates. ONE name for the number that sizes the
17// buffer AND bounds the write, so the two can never drift -- a cap that lives only at the allocation
18// is exactly what let iv_list overrun for as long as it took no bound.
19const IV_IDCAP: i64 = 256
20const IV_TAB: i64 = 9
21
22// ---- small helpers ----
23func iv_field(rec: *u8, rlen: i64, f: i64, out: *u8) -> i64 {
24 var cur: i64 = 0
25 var i: i64 = 0
26 var o: i64 = 0
27 while cur < f {
28 if i >= rlen { out[0] = 0 as u8; return 0 }
29 if rec[i] == (IV_TAB as u8) { cur = cur + 1 }
30 i = i + 1
31 }
32 var go: i64 = 1
33 while go == 1 {
34 if i >= rlen { go = 0 } else {
35 if rec[i] == (IV_TAB as u8) { go = 0 } else { out[o] = rec[i]; o = o + 1; i = i + 1 }
36 }
37 }
38 out[o] = 0 as u8
39 return o
40}
41func iv_atoi(s: *u8, slen: i64) -> i64 {
42 var v: i64 = 0
43 var i: i64 = 0
44 var neg: i64 = 0
45 if slen > 0 { if s[0] == (45 as u8) { neg = 1; i = 1 } }
46 while i < slen { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 }
47 if neg == 1 { return 0 - v }
48 return v
49}
50func iv_num(dst: *u8, off: i64, v: i64) -> i64 {
51 var m: i64 = v
52 var o: i64 = off
53 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m }
54 let t: *u8 = sys_mmap(28); var k: i64 = 0
55 if m == 0 { t[0] = 48 as u8; k = 1 }
56 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
57 var i: i64 = 0
58 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 }
59 return o + k
60}
61// append "$D.CC" for an integer cents amount (no float)
62func iv_money(dst: *u8, off: i64, cents: i64) -> i64 {
63 var o: i64 = off
64 dst[o] = 36 as u8; o = o + 1 // '$'
65 o = iv_num(dst, o, cents / 100)
66 dst[o] = 46 as u8; o = o + 1 // '.'
67 let r: i64 = cents % 100
68 dst[o] = (48 + (r / 10)) as u8; o = o + 1
69 dst[o] = (48 + (r % 10)) as u8; o = o + 1
70 return o
71}
72
73func iv_item_key(domain: *u8, id: *u8, out: *u8) -> i64 {
74 var o: i64 = 0
75 o = as_append(out, o, "inv:" as *u8)
76 o = as_append(out, o, domain)
77 o = as_append(out, o, ":item:" as *u8)
78 o = as_append(out, o, id)
79 out[o] = 0 as u8
80 return o
81}
82func iv_ids_key(domain: *u8, out: *u8) -> i64 {
83 var o: i64 = 0
84 o = as_append(out, o, "inv:" as *u8)
85 o = as_append(out, o, domain)
86 o = as_append(out, o, ":ids" as *u8)
87 out[o] = 0 as u8
88 return o
89}
90func iv_seg_next(prefix: *u8) -> i64 {
91 let segs: *i64 = sys_mmap(8 * 260) as *i64
92 let nseg: i64 = ss_manifest(prefix, segs)
93 if nseg < 0 { return 1 }
94 return 1 + nseg
95}
96func iv_streq_store(prefix: *u8, key: *u8, val: *u8, vlen: i64) -> i64 {
97 let pq: *i64 = sys_mmap(16) as *i64
98 let lq: *i64 = sys_mmap(16) as *i64
99 if ss_get(prefix, key, pq, lq) != 1 { return 0 }
100 let b: *u8 = pq[0] as *u8
101 let n: i64 = lq[0]
102 if n != vlen { return 0 }
103 var i: i64 = 0
104 while i < n { if b[i] != val[i] { return 0 } i = i + 1 }
105 return 1
106}
107// 1 if id is one of the TAB-separated tokens in buf[0..len)
108func iv_id_in_list(buf: *u8, len: i64, id: *u8) -> i64 {
109 let idl: i64 = as_len(id)
110 var i: i64 = 0
111 var ls: i64 = 0
112 while i <= len {
113 var sep: i64 = 0
114 if i == len { sep = 1 } else { if buf[i] == (IV_TAB as u8) { sep = 1 } }
115 if sep == 1 {
116 let tl: i64 = i - ls
117 if tl == idl {
118 var m: i64 = 1
119 var t: i64 = 0
120 while t < tl { if buf[ls + t] != id[t] { m = 0; t = tl } else { t = t + 1 } }
121 if m == 1 { return 1 }
122 }
123 ls = i + 1
124 }
125 i = i + 1
126 }
127 return 0
128}
129
130// ---- core: get / fields ----
131func iv_get(prefix: *u8, domain: *u8, id: *u8, ptrout: *i64, lenout: *i64) -> i64 {
132 let ikey: *u8 = sys_mmap(96)
133 iv_item_key(domain, id, ikey)
134 return ss_get(prefix, ikey, ptrout, lenout)
135}
136// extract field f of an item into out; 1 if the item exists, else 0
137func iv_field_of(prefix: *u8, domain: *u8, id: *u8, f: i64, out: *u8) -> i64 {
138 let pq: *i64 = sys_mmap(16) as *i64
139 let lq: *i64 = sys_mmap(16) as *i64
140 if iv_get(prefix, domain, id, pq, lq) != 1 { return 0 }
141 iv_field(pq[0] as *u8, lq[0], f, out)
142 return 1
143}
144func iv_int_field(prefix: *u8, domain: *u8, id: *u8, f: i64) -> i64 {
145 let out: *u8 = sys_mmap(32)
146 if iv_field_of(prefix, domain, id, f, out) == 0 { return 0 - 1 }
147 return iv_atoi(out, as_len(out))
148}
149func iv_qty(prefix: *u8, domain: *u8, id: *u8) -> i64 { return iv_int_field(prefix, domain, id, 2) }
150func iv_value(prefix: *u8, domain: *u8, id: *u8) -> i64 { return iv_int_field(prefix, domain, id, 5) }
151func iv_expiry(prefix: *u8, domain: *u8, id: *u8) -> i64 { return iv_int_field(prefix, domain, id, 6) }
152
153// ---- catalog: add/update an item (idempotent skip-if-unchanged; keeps the ids index in sync) ----
154func iv_add(prefix: *u8, domain: *u8, id: *u8, name: *u8, cat: *u8, qty: i64, unit: *u8, loc: *u8, value: i64, expiry: i64, owner: *u8) -> i64 {
155 let val: *u8 = sys_mmap(512)
156 var o: i64 = 0
157 o = as_append(val, o, name); val[o] = IV_TAB as u8; o = o + 1
158 o = as_append(val, o, cat); val[o] = IV_TAB as u8; o = o + 1
159 o = iv_num(val, o, qty); val[o] = IV_TAB as u8; o = o + 1
160 o = as_append(val, o, unit); val[o] = IV_TAB as u8; o = o + 1
161 o = as_append(val, o, loc); val[o] = IV_TAB as u8; o = o + 1
162 o = iv_num(val, o, value); val[o] = IV_TAB as u8; o = o + 1
163 o = iv_num(val, o, expiry); val[o] = IV_TAB as u8; o = o + 1
164 o = as_append(val, o, owner)
165 let vlen: i64 = o
166 val[o] = 0 as u8
167 let ikey: *u8 = sys_mmap(96)
168 iv_item_key(domain, id, ikey)
169 if iv_streq_store(prefix, ikey, val, vlen) == 1 { return 0 }
170 let idskey: *u8 = sys_mmap(64)
171 iv_ids_key(domain, idskey)
172 let pq: *i64 = sys_mmap(16) as *i64
173 let lq: *i64 = sys_mmap(16) as *i64
174 let newids: *u8 = sys_mmap(8192)
175 var nio: i64 = 0
176 var idschanged: i64 = 1
177 if ss_get(prefix, idskey, pq, lq) == 1 {
178 let cur: *u8 = pq[0] as *u8
179 let cl: i64 = lq[0]
180 var t: i64 = 0
181 while t < cl { newids[t] = cur[t]; t = t + 1 }
182 nio = cl
183 if iv_id_in_list(cur, cl, id) == 1 { idschanged = 0 } else { newids[nio] = IV_TAB as u8; nio = nio + 1; nio = as_append(newids, nio, id) }
184 } else {
185 nio = as_append(newids, 0, id)
186 }
187 let w: *i64 = ss_begin()
188 ss_add(w, 1, ikey, val, vlen)
189 if idschanged == 1 { newids[nio] = 0 as u8; ss_add(w, 1, idskey, newids, nio) }
190 let seg: i64 = iv_seg_next(prefix)
191 ss_commit(prefix, w, seg)
192 return 1
193}
194
195// list the item ids of a domain into outids (ptrs to fresh copies); returns count
196// ★★★★★★THE CALLER OWNS THE BUFFER, SO ONLY THE CALLER KNOWS THE BOUND -- IT MUST BE PASSED, OR THE
197// CAP IS AN UNENFORCEABLE ASSUMPTION. This took no `cap` until 2026-08-15 while every caller sized its
198// array with a bare `sys_mmap(8 * 256)`: a domain holding more than 256 ids wrote straight past the end
199// of the caller's allocation, in a path that renders legal estate documents. Nothing could have caught
200// it -- the number lived in the caller and the writing lived here.
201// ★NO SILENT CAP: on truncation it ANNOUNCES on stderr and returns what fit, because a capped list
202// published as a total is the defect this estate has paid for repeatedly. It stops at the bound rather
203// than refusing, so an over-large domain degrades to a visibly partial render instead of corrupting
204// memory -- wrong in the direction of doing less.
205func iv_list(prefix: *u8, domain: *u8, outids: *i64, cap: i64) -> i64 {
206 let idskey: *u8 = sys_mmap(64)
207 iv_ids_key(domain, idskey)
208 let pq: *i64 = sys_mmap(16) as *i64
209 let lq: *i64 = sys_mmap(16) as *i64
210 if ss_get(prefix, idskey, pq, lq) != 1 { return 0 }
211 let buf: *u8 = pq[0] as *u8
212 let len: i64 = lq[0]
213 var n: i64 = 0
214 var i: i64 = 0
215 var ls: i64 = 0
216 while i <= len {
217 var sep: i64 = 0
218 if i == len { sep = 1 } else { if buf[i] == (IV_TAB as u8) { sep = 1 } }
219 if sep == 1 {
220 let tl: i64 = i - ls
221 if tl > 0 {
222 if n >= cap {
223 let tm: *u8 = "iv_list: TRUNCATED -- domain has more ids than the caller's buffer holds; returning a PARTIAL list\n" as *u8
224 var tl2: i64 = 0
225 while tm[tl2] != (0 as u8) { tl2 = tl2 + 1 }
226 sys_write(2, tm, tl2)
227 i = len + 1
228 } else {
229 let tok: *u8 = sys_mmap(48)
230 var t: i64 = 0
231 while t < tl { tok[t] = buf[ls + t]; t = t + 1 }
232 tok[tl] = 0 as u8
233 outids[n] = tok as i64
234 n = n + 1
235 }
236 }
237 ls = i + 1
238 }
239 i = i + 1
240 }
241 return n
242}
243
244// total value (cents) across a domain -- the will/estate net worth
245func iv_value_total(prefix: *u8, domain: *u8) -> i64 {
246 let ids: *i64 = sys_mmap(8 * IV_IDCAP) as *i64
247 let n: i64 = iv_list(prefix, domain, ids, IV_IDCAP)
248 var tot: i64 = 0
249 var i: i64 = 0
250 while i < n { tot = tot + iv_value(prefix, domain, ids[i] as *u8); i = i + 1 }
251 return tot
252}
253
254// items expiring within `within` days of `as_of` (skips non-perishables, expiry==0) -> outids; returns count
255func iv_expiring(prefix: *u8, domain: *u8, as_of: i64, within: i64, outids: *i64) -> i64 {
256 let ids: *i64 = sys_mmap(8 * IV_IDCAP) as *i64
257 let n: i64 = iv_list(prefix, domain, ids, IV_IDCAP)
258 var c: i64 = 0
259 var i: i64 = 0
260 while i < n {
261 let e: i64 = iv_expiry(prefix, domain, ids[i] as *u8)
262 if e > 0 { if e <= as_of + within { outids[c] = ids[i]; c = c + 1 } }
263 i = i + 1
264 }
265 return c
266}
267
268// consume n units of an item (supply-chain depletion). Returns the new qty, or -1 if absent / insufficient.
269func iv_consume(prefix: *u8, domain: *u8, id: *u8, nconsume: i64) -> i64 {
270 let pq: *i64 = sys_mmap(16) as *i64
271 let lq: *i64 = sys_mmap(16) as *i64
272 if iv_get(prefix, domain, id, pq, lq) != 1 { return 0 - 1 }
273 let rec: *u8 = pq[0] as *u8
274 let rl: i64 = lq[0]
275 let f: *u8 = sys_mmap(64)
276 let name: *u8 = sys_mmap(128); iv_field(rec, rl, 0, name)
277 let cat: *u8 = sys_mmap(64); iv_field(rec, rl, 1, cat)
278 let ql: i64 = iv_field(rec, rl, 2, f); let qty: i64 = iv_atoi(f, ql)
279 let unit: *u8 = sys_mmap(32); iv_field(rec, rl, 3, unit)
280 let loc: *u8 = sys_mmap(64); iv_field(rec, rl, 4, loc)
281 let vl: i64 = iv_field(rec, rl, 5, f); let value: i64 = iv_atoi(f, vl)
282 let el: i64 = iv_field(rec, rl, 6, f); let expiry: i64 = iv_atoi(f, el)
283 let owner: *u8 = sys_mmap(64); iv_field(rec, rl, 7, owner)
284 if qty < nconsume { return 0 - 1 }
285 let newqty: i64 = qty - nconsume
286 iv_add(prefix, domain, id, name, cat, newqty, unit, loc, value, expiry, owner)
287 return newqty
288}
289
290// ---- reorder / par levels (the supply-chain management layer) ------------------------------------------
291// A par level is the desired minimum stock; reorder when qty < par, buying (par - qty). Stored separately
292// (inv:<domain>:par:<id>) so the item schema is untouched. par 0 / absent = not tracked for reorder.
293func iv_par_key(domain: *u8, id: *u8, out: *u8) -> i64 {
294 var o: i64 = 0
295 o = as_append(out, o, "inv:" as *u8)
296 o = as_append(out, o, domain)
297 o = as_append(out, o, ":par:" as *u8)
298 o = as_append(out, o, id)
299 out[o] = 0 as u8
300 return o
301}
302func iv_set_par(prefix: *u8, domain: *u8, id: *u8, par: i64) -> i64 {
303 let key: *u8 = sys_mmap(96)
304 iv_par_key(domain, id, key)
305 let val: *u8 = sys_mmap(24)
306 let vlen: i64 = iv_num(val, 0, par)
307 val[vlen] = 0 as u8
308 if iv_streq_store(prefix, key, val, vlen) == 1 { return 0 }
309 let w: *i64 = ss_begin()
310 ss_add(w, 1, key, val, vlen)
311 let seg: i64 = iv_seg_next(prefix)
312 ss_commit(prefix, w, seg)
313 return 1
314}
315func iv_get_par(prefix: *u8, domain: *u8, id: *u8) -> i64 {
316 let key: *u8 = sys_mmap(96)
317 iv_par_key(domain, id, key)
318 let pq: *i64 = sys_mmap(16) as *i64
319 let lq: *i64 = sys_mmap(16) as *i64
320 if ss_get(prefix, key, pq, lq) != 1 { return 0 }
321 return iv_atoi(pq[0] as *u8, lq[0])
322}
323// items needing reorder (qty < par, par>0) -> out_ids + out_need (= par - qty); returns count
324func iv_low_stock(prefix: *u8, domain: *u8, out_ids: *i64, out_need: *i64) -> i64 {
325 let ids: *i64 = sys_mmap(8 * IV_IDCAP) as *i64
326 let n: i64 = iv_list(prefix, domain, ids, IV_IDCAP)
327 var c: i64 = 0
328 var i: i64 = 0
329 while i < n {
330 let par: i64 = iv_get_par(prefix, domain, ids[i] as *u8)
331 if par > 0 {
332 let q: i64 = iv_qty(prefix, domain, ids[i] as *u8)
333 if q < par { out_ids[c] = ids[i]; out_need[c] = par - q; c = c + 1 }
334 }
335 i = i + 1
336 }
337 return c
338}