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}