code wiki / _hdl_build / nx_store_put.nx

nx_store_put.nx source

↩ module page · 513 lines · 25486 B

1// nx_store_put.nx -- THE UNIFIED INFORMATION-PLANE WRITE VERB (operator 2026-07-18: "it shouldnt 2// be on the tsv we are supposed to use a unified nishi information management plane system thats 3// better than git ... not just debt it should be new feature capable"). 4// Row-level, provenanced, additive writes DIRECTLY against any 7-col seg-store plane 5// (id title sev status owner scope note) -- debt-, work- (features), or any sibling prefix. 6// Flat staging files demote to bootstrap/recovery; the LIVE write path is this verb. 7// BETTER-THAN-GIT properties, in-plane: every mutation appends a revision to <prefix minus '-'>hist- 8// as epoch<TAB>actor<TAB>verb<TAB>id<TAB>old-row<TAB>new-row (inner tabs -> '|'), so history + 9// provenance live in the SOVEREIGN plane itself -- no working tree, no index, no merge dance; 10// row-independent writes cannot conflict, and the full before/after of every row is queryable. 11// nx_store_put <prefix> put <actor> <id> <field>... (N-col: id + 1..15 fields, ANY plane schema 12// incl the 9-col frontier -- v2 uplevel 07-18, 13// operator: "stop engaging with the tsv") 14// nx_store_put <prefix> setcol <actor> <id> <colidx> <value> (flip ONE cell, e.g. frontier status col5 T->D) 15// nx_store_put <prefix> close <actor> <id> <closing-note> (7-col convention: col3 open->closed, note appended) 16// nx_store_put <prefix> load (dump the plane) 17// nx_store_put <prefix> hist (dump the revision plane) 18// put on an unseeded prefix BOOTSTRAPS the plane (first row creates it -- no staging file needed). 19// close/setcol are FAIL-CLOSED: unknown id (or colidx past the row) -> exit 4, store untouched. 20// Whole-store last-writer-wins per write; writers serialized socially by session claims (flock = next rung). 21// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 22import "nx_store_seed_lib.nx" 23import "nx_seg_store.nx" 24import "nx_syscalls.nx" 25const NSP_MAGIC_4096: i64 = 4096 26 27const NSP_CAP: i64 = 1048576 28const NSP_NL: i64 = 10 29const NSP_TAB: i64 = 9 30const NSP_PIPE: i64 = 124 31const NSP_DASH: i64 = 45 32const NSP_NCOL: i64 = 7 33const NSP_PAIR: i64 = 2 34const NSP_SP_BYTES: i64 = 128 35const NSP_STDERR: i64 = 2 36const NSP_PFXCAP: i64 = 256 37const NSP_MSGCAP: i64 = 256 38const NSP_ARGC_PUT_MIN: i64 = 6 // prog prefix put actor id + at least 1 more field 39const NSP_ARGC_PUT_MAX: i64 = 20 // id + up to 15 fields (16-col plane cap) 40const NSP_ARGC_CLOSE: i64 = 6 // prog prefix close actor id note 41const NSP_ARGC_SETCOL: i64 = 7 // prog prefix setcol actor id colidx value 42const NSP_ARGC_MIN: i64 = 3 43const NSP_MAXCOL: i64 = 16 // max columns a plane row may carry 44const NSP_SP2_BYTES: i64 = 256 // 16 cols * 2 slots * 8B 45const NSP_EXIT_USAGE: i64 = 2 46const NSP_EXIT_IO: i64 = 1 47const NSP_EXIT_REFUSED: i64 = 4 48// put field argv indices: 49const NSP_F_ACTOR: i64 = 3 50const NSP_F_ID: i64 = 4 51const NSP_F_LAST: i64 = 10 52// close argv indices: 53const NSP_C_ID: i64 = 4 54const NSP_C_NOTE: i64 = 5 55// status column index in the 7-col row: 56const NSP_COL_STATUS: i64 = 3 57const NSP_COL_NOTE: i64 = 6 58 59func nsp_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 60func nsp_werr(s: *u8) -> i64 { sys_write(NSP_STDERR, s, nsp_slen(s)); return 0 } 61func nsp_eqs(a: *u8, b: *u8) -> i64 { 62 var i: i64 = 0 63 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 64 if b[i] != (0 as u8) { return 0 } 65 return 1 66} 67// slice [a,b) of q equals C-string s 68func nsp_slice_eqs(q: *u8, a: i64, b: i64, s: *u8) -> i64 { 69 let sn: i64 = nsp_slen(s) 70 if b - a != sn { return 0 } 71 var i: i64 = 0 72 while i < sn { if q[a+i] != s[i] { return 0 } i = i + 1 } 73 return 1 74} 75// split line [ls,le) into up to NSP_MAXCOL (start,end) pairs (callers pass NSP_SP2_BYTES scratch) 76func nsp_cols(q: *u8, ls: i64, le: i64, sp: *i64) -> i64 { 77 var c: i64 = 0 78 var p: i64 = ls 79 while c < NSP_MAXCOL { 80 var e: i64 = p 81 var s: i64 = 1 82 while s == 1 { if e >= le { s = 0 } else { if q[e] == (NSP_TAB as u8) { s = 0 } else { e = e + 1 } } } 83 sp[c*NSP_PAIR] = p 84 sp[c*NSP_PAIR+1] = e 85 c = c + 1 86 if e >= le { return c } 87 p = e + 1 88 } 89 return c 90} 91func nsp_cat_slice(d: *u8, o: i64, q: *u8, a: i64, b: i64) -> i64 { 92 var oo: i64 = o 93 var i: i64 = a 94 while i < b { d[oo] = q[i]; oo = oo + 1; i = i + 1 } 95 return oo 96} 97// copy slice with inner tabs -> '|' (hist rows stay 6-col clean) 98func nsp_cat_slice_pipe(d: *u8, o: i64, q: *u8, a: i64, b: i64) -> i64 { 99 var oo: i64 = o 100 var i: i64 = a 101 while i < b { 102 var c: i64 = q[i] 103 if c == NSP_TAB { c = NSP_PIPE } 104 d[oo] = c as u8 105 oo = oo + 1 106 i = i + 1 107 } 108 return oo 109} 110// derive the hist prefix: "knowledge/store/debt-" -> "knowledge/store/debthist-" 111func nsp_hist_prefix(prefix: *u8, out: *u8) -> i64 { 112 var n: i64 = nsp_slen(prefix) 113 if n > 0 { if prefix[n-1] == (NSP_DASH as u8) { n = n - 1 } } 114 var i: i64 = 0 115 while i < n { out[i] = prefix[i]; i = i + 1 } 116 var o: i64 = ss_cat(out, n, "hist-" as *u8) 117 out[o] = 0 as u8 118 return o 119} 120// append one hist revision row: epoch actor verb id old new 121func nsp_hist_append(prefix: *u8, actor: *u8, verb: *u8, id: *u8, q: *u8, olda: i64, oldb: i64, neu: *u8, na: i64, nb: i64) -> i64 { 122 let hp: *u8 = sys_mmap(NSP_PFXCAP) 123 nsp_hist_prefix(prefix, hp) 124 let hb: *u8 = sys_mmap(NSP_CAP) 125 // O(1) APPEND 2026-07-30: this used to sts_load the ENTIRE hist plane just to append ONE revision 126 // row, then sts_seed the WHOLE plane back -- O(rows x segments) write amplification on EVERY put 127 // through this chokepoint. The row is a PURE APPEND (no existing row is modified), so we now build 128 // ONLY the new row at offset 0 and hand it to sts_append_fast. 129 var o: i64 = 0 130 o = ss_catn(hb, o, sys_now_realtime_sec()) 131 hb[o] = NSP_TAB as u8 132 o = o + 1 133 o = ss_cat(hb, o, actor) 134 hb[o] = NSP_TAB as u8 135 o = o + 1 136 o = ss_cat(hb, o, verb) 137 hb[o] = NSP_TAB as u8 138 o = o + 1 139 o = ss_cat(hb, o, id) 140 hb[o] = NSP_TAB as u8 141 o = o + 1 142 if oldb > olda { o = nsp_cat_slice_pipe(hb, o, q, olda, oldb) } else { o = ss_cat(hb, o, "-" as *u8) } 143 hb[o] = NSP_TAB as u8 144 o = o + 1 145 if nb > na { o = nsp_cat_slice_pipe(hb, o, neu, na, nb) } else { o = ss_cat(hb, o, "-" as *u8) } 146 hb[o] = NSP_NL as u8 147 o = o + 1 148 // FAST PATH: O(1) append -- writes ONLY this row, not the whole plane. o-1 strips the trailing 149 // newline because sts_append_fast takes the bare row. BOOTSTRAP: sts_append_fast requires an 150 // existing q:n, so a hist plane that does not exist YET legitimately returns <0; in that case 151 // (and only that case) fall back to a full seed to create it. Every later append is O(1). 152 // LOCKED form: the hist plane is NOT the plane `main` locked, so this append is otherwise 153 // unprotected -- and sts_append_fast is itself a read-modify-write on q:n (two appenders read the 154 // same n, write the same q:<n>, and newest-wins silently OVERWRITES one row). Lock order is always 155 // main-then-hist, so no ABBA deadlock is possible. 156 var rc: i64 = sts_append_fast_locked(hp, hb, o - 1) 157 if rc < 0 { rc = sts_seed(hp, hb, o) } 158 if rc < 0 { return 0 - 1 } 159 return 0 160} 161func nsp_atoi(s: *u8) -> i64 { 162 var v: i64 = 0 163 var i: i64 = 0 164 while s[i] != (0 as u8) { 165 let c: i64 = s[i] 166 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } 167 i = i + 1 168 } 169 return v 170} 171 172func nsp_report(verb: *u8, id: *u8, replaced: i64, rows: i64) -> i64 { 173 let m: *u8 = sys_mmap(NSP_MSGCAP) 174 var o: i64 = ss_cat(m, 0, verb) 175 o = ss_cat(m, o, " " as *u8) 176 o = ss_cat(m, o, id) 177 o = ss_cat(m, o, " replaced=" as *u8) 178 o = ss_catn(m, o, replaced) 179 o = ss_cat(m, o, " rows=" as *u8) 180 o = ss_catn(m, o, rows) 181 m[o] = NSP_NL as u8 182 o = o + 1 183 sys_write(1, m, o) 184 return 0 185} 186 187// SHARED ANTI-CLOBBER TOOTH (2026-08-02, debts 1785713047/1785713373) -- ONE guard, FOUR callers. 188// EVERY write verb here rebuilds the WHOLE plane from what sts_load could reach, then re-seeds it. If that 189// load came back PARTIAL, every row it could not see is silently DELETED. put/putn ADD rows; close/setcol 190// MODIFY one row in place -- so NONE of them can ever legitimately REDUCE the row count. A count below what 191// the plane already declares therefore means the load was short, and committing it would destroy data 192// (commontask- 37->10, dedupq- 21->3, both recovered 2026-08-02). Refuse instead of committing the loss. 193// FAIL-OPEN when q:n is absent -- that call is BOOTSTRAPPING the plane, which is legitimate. 194// ★Deliberately NOT in the shared sts_seed primitive: there it would refuse nx_plane_append's ROLLBACK and 195// nx_plane_repair, both of which shrink BY DEFINITION (1785713047). The guard belongs to the WRITER. 196// Proven both directions by nx_store_put_shrink_gate 4/4: it BITES a manufactured over-declared plane 197// (rc=4, store untouched) and SPARES a healthy one (rc=0, q:n 3->4). 198func nsp_would_shrink(prefix: *u8, rows: i64) -> i64 { 199 let vq: *i64 = sys_mmap(NSP_MAGIC_4096) as *i64 200 let vl: *i64 = sys_mmap(NSP_MAGIC_4096) as *i64 201 if ss_get(prefix, "q:n" as *u8, vq, vl) == 1 { 202 let declared: i64 = sts_atoi(vq[0] as *u8, vl[0]) 203 if rows < declared { return 1 } 204 } 205 return 0 206} 207func nsp_refuse_shrink(vname: *u8) -> i64 { 208 nsp_werr("REFUSED: this " as *u8) 209 nsp_werr(vname) 210 nsp_werr(" would SHRINK the plane -- the load came back partial, so committing it would delete the rows it could not see (the commontask-/dedupq- clobber class). Store untouched; re-run, and if the plane is genuinely damaged use nx_plane_repair deliberately.\n" as *u8) 211 return 0 212} 213 214func main(argc: i64, argv: *i64) -> i64 { 215 if argc < NSP_ARGC_MIN { nsp_werr("usage: nx_store_put <prefix> {put <actor> <id> <title> <sev> <status> <owner> <scope> <note> | close <actor> <id> <note> | load | hist}\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 216 let prefix: *u8 = argv[1] as *u8 217 let verb: *u8 = argv[2] as *u8 218 219 // seq1559 MIGRATION 2026-07-30 (ws=sev-eater). Every mutating verb here is a READ-MODIFY-WRITE 220 // over a shared plane: sts_load -> rewrite the whole buffer -> sts_seed. Unlocked, two concurrent 221 // invocations interleave as A-loads / B-loads / A-commits / B-commits-without-A and one caller's 222 // row is gone silently. That is not theoretical -- nx_sts_lock_gate measures 5 of 6 rows destroyed 223 // under 6-way concurrency, and 0 lost once the lock is held. 224 // Taken HERE, before the first load, so the critical section covers the entire operation rather 225 // than just the commit -- locking only the write would still let two readers race the same 226 // snapshot. Uses sts_lock, i.e. the SAME <prefix>plock nx_debt / nx_plane_append / sts_append_row 227 // use; a private lock file would have been easier and would have excluded nobody. 228 // NO UNLOCK IS NEEDED ON ANY PATH: this is a one-shot CLI organ and every exit -- success or a 229 // fail-closed sys_exit() -- terminates the process, which releases the flock. There is therefore 230 // no path that can leak the lock, which is why the lock is safe to take this early. 231 sts_lock(prefix) 232 233 if nsp_eqs("load" as *u8, verb) == 1 { 234 let b: *u8 = sys_mmap(NSP_CAP) 235 let n: i64 = sts_load(prefix, b, NSP_CAP) 236 if n <= 0 { nsp_werr("plane EMPTY / unseeded\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 237 sys_write(1, b, n) 238 sys_exit(0) 239 return 0 240 } 241 if nsp_eqs("hist" as *u8, verb) == 1 { 242 let hp: *u8 = sys_mmap(NSP_PFXCAP) 243 nsp_hist_prefix(prefix, hp) 244 let b2: *u8 = sys_mmap(NSP_CAP) 245 let n2: i64 = sts_load(hp, b2, NSP_CAP) 246 if n2 <= 0 { nsp_werr("hist EMPTY (no revisions yet)\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 247 sys_write(1, b2, n2) 248 sys_exit(0) 249 return 0 250 } 251 252 if nsp_eqs("put" as *u8, verb) == 1 { 253 if argc < NSP_ARGC_PUT_MIN { nsp_werr("put needs <actor> <id> <field>... (1..15 fields after id)\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 254 if argc > NSP_ARGC_PUT_MAX { nsp_werr("put: too many fields (16-col plane cap)\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 255 let actor: *u8 = argv[NSP_F_ACTOR] as *u8 256 let id: *u8 = argv[NSP_F_ID] as *u8 257 let cur: *u8 = sys_mmap(NSP_CAP) 258 var cn: i64 = sts_load(prefix, cur, NSP_CAP - NSP_MAGIC_4096) 259 if cn < 0 { cn = 0 } 260 let neu: *u8 = sys_mmap(NSP_CAP) 261 let sp: *i64 = sys_mmap(NSP_SP2_BYTES) as *i64 262 // literal counters first 263 var no: i64 = 0 264 var replaced: i64 = 0 265 var rows: i64 = 0 266 var olda: i64 = 0 267 var oldb: i64 = 0 268 var i: i64 = 0 269 while i < cn { 270 var le: i64 = i 271 var s: i64 = 1 272 while s == 1 { if le >= cn { s = 0 } else { if cur[le] == (NSP_NL as u8) { s = 0 } else { le = le + 1 } } } 273 if le > i { 274 nsp_cols(cur, i, le, sp) 275 if nsp_slice_eqs(cur, sp[0], sp[1], id) == 1 { 276 replaced = 1 277 olda = i 278 oldb = le 279 } else { 280 no = nsp_cat_slice(neu, no, cur, i, le) 281 neu[no] = NSP_NL as u8 282 no = no + 1 283 rows = rows + 1 284 } 285 } 286 i = le + 1 287 } 288 // append the new/updated row (ALL fields argv[4..argc-1] tab-joined -- N-col, schema-agnostic) 289 let na: i64 = no 290 let flast: i64 = argc - 1 291 var f: i64 = NSP_F_ID 292 while f <= flast { 293 no = ss_cat(neu, no, argv[f] as *u8) 294 if f < flast { neu[no] = NSP_TAB as u8; no = no + 1 } 295 f = f + 1 296 } 297 let nb: i64 = no 298 neu[no] = NSP_NL as u8 299 no = no + 1 300 rows = rows + 1 301 if nsp_would_shrink(prefix, rows) == 1 { nsp_refuse_shrink("put" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 302 let rc: i64 = sts_seed(prefix, neu, no) 303 if rc < 0 { nsp_werr("plane commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 304 if nsp_hist_append(prefix, actor, "put" as *u8, id, cur, olda, oldb, neu, na, nb) < 0 { nsp_werr("hist commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 305 nsp_report("PUT" as *u8, id, replaced, rows) 306 sys_exit(0) 307 return 0 308 } 309 310 if nsp_eqs("close" as *u8, verb) == 1 { 311 if argc < NSP_ARGC_CLOSE { nsp_werr("close needs <actor> <id> <note>\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 312 let actor2: *u8 = argv[NSP_F_ACTOR] as *u8 313 let id2: *u8 = argv[NSP_C_ID] as *u8 314 let cnote: *u8 = argv[NSP_C_NOTE] as *u8 315 let cur2: *u8 = sys_mmap(NSP_CAP) 316 let cn2: i64 = sts_load(prefix, cur2, NSP_CAP - NSP_MAGIC_4096) 317 if cn2 <= 0 { nsp_werr("plane EMPTY: nothing to close\n" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 318 let neu2: *u8 = sys_mmap(NSP_CAP) 319 let sp2: *i64 = sys_mmap(NSP_SP2_BYTES) as *i64 320 var no2: i64 = 0 321 var found: i64 = 0 322 var rows2: i64 = 0 323 var olda2: i64 = 0 324 var oldb2: i64 = 0 325 var na2: i64 = 0 326 var nb2: i64 = 0 327 var j: i64 = 0 328 while j < cn2 { 329 var le2: i64 = j 330 var s2: i64 = 1 331 while s2 == 1 { if le2 >= cn2 { s2 = 0 } else { if cur2[le2] == (NSP_NL as u8) { s2 = 0 } else { le2 = le2 + 1 } } } 332 if le2 > j { 333 let nc: i64 = nsp_cols(cur2, j, le2, sp2) 334 if nsp_slice_eqs(cur2, sp2[0], sp2[1], id2) == 1 { 335 found = 1 336 olda2 = j 337 oldb2 = le2 338 na2 = no2 339 // rebuild: cols 0..2 as-is, col3 = closed, cols 4..5 as-is, col6 = old-note | close-note 340 var c: i64 = 0 341 while c < nc { 342 if c == NSP_COL_STATUS { 343 no2 = ss_cat(neu2, no2, "closed" as *u8) 344 } else { 345 no2 = nsp_cat_slice(neu2, no2, cur2, sp2[c*NSP_PAIR], sp2[c*NSP_PAIR+1]) 346 } 347 if c == NSP_COL_NOTE { 348 no2 = ss_cat(neu2, no2, " | " as *u8) 349 no2 = ss_cat(neu2, no2, cnote) 350 } 351 if c < nc - 1 { neu2[no2] = NSP_TAB as u8; no2 = no2 + 1 } 352 c = c + 1 353 } 354 nb2 = no2 355 neu2[no2] = NSP_NL as u8 356 no2 = no2 + 1 357 rows2 = rows2 + 1 358 } else { 359 no2 = nsp_cat_slice(neu2, no2, cur2, j, le2) 360 neu2[no2] = NSP_NL as u8 361 no2 = no2 + 1 362 rows2 = rows2 + 1 363 } 364 } 365 j = le2 + 1 366 } 367 if found == 0 { nsp_werr("REFUSED: id not in plane (close is fail-closed; store untouched)\n" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 368 if nsp_would_shrink(prefix, rows2) == 1 { nsp_refuse_shrink("close" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 369 let rc2: i64 = sts_seed(prefix, neu2, no2) 370 if rc2 < 0 { nsp_werr("plane commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 371 if nsp_hist_append(prefix, actor2, "close" as *u8, id2, cur2, olda2, oldb2, neu2, na2, nb2) < 0 { nsp_werr("hist commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 372 nsp_report("CLOSED" as *u8, id2, found, rows2) 373 sys_exit(0) 374 return 0 375 } 376 377 if nsp_eqs("putn" as *u8, verb) == 1 { 378 // BATCH put (W008 residual, 2026-07-18): N rows in ONE call -- one store commit, per-row hist. 379 // putn <actor> <ncols> <field>... (groups of ncols fields, first field of each group = id) 380 if argc < 7 { nsp_werr("putn needs <actor> <ncols> <field>... (groups of ncols, id first)\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 381 let actorn: *u8 = argv[NSP_F_ACTOR] as *u8 382 let ncols: i64 = nsp_atoi(argv[4] as *u8) 383 if ncols < 2 { nsp_werr("putn: ncols must be 2..15\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 384 if ncols > 15 { nsp_werr("putn: ncols must be 2..15\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 385 let nfields: i64 = argc - 5 386 let ngroups: i64 = nfields / ncols 387 if ngroups * ncols != nfields { nsp_werr("putn REFUSED: field count not a multiple of ncols (fail-closed, store untouched)\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 388 if ngroups < 1 { nsp_werr("putn: no groups\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 389 if ngroups > 32 { nsp_werr("putn: max 32 groups per call\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 390 var abuf: *u8 = sys_mmap(NSP_CAP) 391 var bbuf: *u8 = sys_mmap(NSP_CAP) 392 var an: i64 = sts_load(prefix, abuf, NSP_CAP - NSP_MAGIC_4096) 393 if an < 0 { an = 0 } 394 let spn: *i64 = sys_mmap(NSP_SP2_BYTES) as *i64 395 var totrows: i64 = 0 396 var g: i64 = 0 397 while g < ngroups { 398 let base: i64 = 5 + g * ncols 399 let idn: *u8 = argv[base] as *u8 400 var no: i64 = 0 401 var olda: i64 = 0 402 var oldb: i64 = 0 403 var rows: i64 = 0 404 var i2: i64 = 0 405 while i2 < an { 406 var le: i64 = i2 407 var s: i64 = 1 408 while s == 1 { if le >= an { s = 0 } else { if abuf[le] == (NSP_NL as u8) { s = 0 } else { le = le + 1 } } } 409 if le > i2 { 410 nsp_cols(abuf, i2, le, spn) 411 if nsp_slice_eqs(abuf, spn[0], spn[1], idn) == 1 { olda = i2; oldb = le } else { 412 no = nsp_cat_slice(bbuf, no, abuf, i2, le) 413 bbuf[no] = NSP_NL as u8 414 no = no + 1 415 rows = rows + 1 416 } 417 } 418 i2 = le + 1 419 } 420 let na: i64 = no 421 var f2: i64 = base 422 let flast2: i64 = base + ncols - 1 423 while f2 <= flast2 { 424 no = ss_cat(bbuf, no, argv[f2] as *u8) 425 if f2 < flast2 { bbuf[no] = NSP_TAB as u8; no = no + 1 } 426 f2 = f2 + 1 427 } 428 let nb: i64 = no 429 bbuf[no] = NSP_NL as u8 430 no = no + 1 431 rows = rows + 1 432 if nsp_hist_append(prefix, actorn, "putn" as *u8, idn, abuf, olda, oldb, bbuf, na, nb) < 0 { nsp_werr("hist commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 433 let tswap: *u8 = abuf 434 abuf = bbuf 435 bbuf = tswap 436 an = no 437 totrows = rows 438 g = g + 1 439 } 440 // putn accumulates across groups, so totrows is the final count -- same invariant as put: a BATCH of 441 // adds can only grow or hold steady. ⚠This one commits ONCE after N groups whose hist frames are 442 // ALREADY written, so an unguarded partial load here loses rows the history says were just put. 443 if nsp_would_shrink(prefix, totrows) == 1 { nsp_refuse_shrink("putn" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 444 if sts_seed(prefix, abuf, an) < 0 { nsp_werr("plane commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 445 nsp_report("PUTN-GROUPS" as *u8, actorn, ngroups, totrows) 446 sys_exit(0) 447 return 0 448 } 449 450 if nsp_eqs("setcol" as *u8, verb) == 1 { 451 if argc < NSP_ARGC_SETCOL { nsp_werr("setcol needs <actor> <id> <colidx> <value>\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 452 let actor3: *u8 = argv[NSP_F_ACTOR] as *u8 453 let id3: *u8 = argv[NSP_C_ID] as *u8 454 let colidx: i64 = nsp_atoi(argv[5] as *u8) 455 let val: *u8 = argv[6] as *u8 456 let cur3: *u8 = sys_mmap(NSP_CAP) 457 let cn3: i64 = sts_load(prefix, cur3, NSP_CAP - NSP_MAGIC_4096) 458 if cn3 <= 0 { nsp_werr("plane EMPTY: nothing to setcol\n" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 459 let neu3: *u8 = sys_mmap(NSP_CAP) 460 let sp3: *i64 = sys_mmap(NSP_SP2_BYTES) as *i64 461 var no3: i64 = 0 462 var found3: i64 = 0 463 var rows3: i64 = 0 464 var olda3: i64 = 0 465 var oldb3: i64 = 0 466 var na3: i64 = 0 467 var nb3: i64 = 0 468 var j3: i64 = 0 469 while j3 < cn3 { 470 var le3: i64 = j3 471 var s3: i64 = 1 472 while s3 == 1 { if le3 >= cn3 { s3 = 0 } else { if cur3[le3] == (NSP_NL as u8) { s3 = 0 } else { le3 = le3 + 1 } } } 473 if le3 > j3 { 474 let nc3: i64 = nsp_cols(cur3, j3, le3, sp3) 475 if nsp_slice_eqs(cur3, sp3[0], sp3[1], id3) == 1 { 476 if colidx >= nc3 { nsp_werr("REFUSED: colidx past the row (setcol is fail-closed; store untouched)\n" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 477 found3 = 1 478 olda3 = j3 479 oldb3 = le3 480 na3 = no3 481 var c3: i64 = 0 482 while c3 < nc3 { 483 if c3 == colidx { no3 = ss_cat(neu3, no3, val) } else { no3 = nsp_cat_slice(neu3, no3, cur3, sp3[c3*NSP_PAIR], sp3[c3*NSP_PAIR+1]) } 484 if c3 < nc3 - 1 { neu3[no3] = NSP_TAB as u8; no3 = no3 + 1 } 485 c3 = c3 + 1 486 } 487 nb3 = no3 488 neu3[no3] = NSP_NL as u8 489 no3 = no3 + 1 490 rows3 = rows3 + 1 491 } else { 492 no3 = nsp_cat_slice(neu3, no3, cur3, j3, le3) 493 neu3[no3] = NSP_NL as u8 494 no3 = no3 + 1 495 rows3 = rows3 + 1 496 } 497 } 498 j3 = le3 + 1 499 } 500 if found3 == 0 { nsp_werr("REFUSED: id not in plane (setcol is fail-closed; store untouched)\n" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 501 if nsp_would_shrink(prefix, rows3) == 1 { nsp_refuse_shrink("setcol" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 502 let rc3: i64 = sts_seed(prefix, neu3, no3) 503 if rc3 < 0 { nsp_werr("plane commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 504 if nsp_hist_append(prefix, actor3, "setcol" as *u8, id3, cur3, olda3, oldb3, neu3, na3, nb3) < 0 { nsp_werr("hist commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 505 nsp_report("SETCOL" as *u8, id3, found3, rows3) 506 sys_exit(0) 507 return 0 508 } 509 510 nsp_werr("usage: nx_store_put <prefix> {put <actor> <id> <field>... | setcol <actor> <id> <colidx> <value> | close <actor> <id> <note> | load | hist}\n" as *u8) 511 sys_exit(NSP_EXIT_USAGE) 512 return NSP_EXIT_USAGE 513}