code wiki / (root) / nx_store_put_load_file_t196.nx

nx_store_put_load_file_t196.nx source

↩ module page · 858 lines · 48879 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// ROW-LEVEL COMMITS (2026-09-02, loadgov LV17): put/close/setcol commit ONE row -- sts_append_fast for a new id, sts_replace_fast 22// (overwrite q:<seq>, newest-wins, no reader change) for an existing one. The whole-plane sts_seed is now only the bootstrap of an 23// unseeded plane and the fallback when a row cannot be located by seq; the shrink guard stays on that fallback. MEASURED CAUSE: 24// five whole-plane reseeds in flight held the RAID in D-state and refused every build on the estate for hours (2026-09-02). 25// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 26import "nx_store_seed_lib.nx" 27import "nx_seg_store.nx" 28import "nx_syscalls.nx" 29import "nx_fs_create_lib.nx" 30const NSP_MAGIC_4096: i64 = 4096 31 32// 2026-08-08 RAISED 1048576 -> 33554432. THE FIFTH HAND-COPY OF THIS CAP, AND THE ONE NOBODY RAISED: 33// nx_debt DB_CAP, nx_debtlive DL_CAP, nx_debtcluster and nx_debt_view were all moved to 33554432 on 34// 2026-08-06 in lockstep; nx_dora's DCAP carries the measurement and the reason -- "knowledge/store/debt- 35// loads to 4,396,892B. At the old 1 MiB this organ silently lost 76% of the board, and because AN 36// APPEND-ONLY PLANE PAST A PREFIX CAP LOSES ITS NEWEST ROWS FIRST it was reporting on the OLDEST 37// quarter -- degrading exactly as new work arrived." This organ was missed. Measured today: that plane 38// is 4,974,565B / 3,785 rows. 39// TWO LIVE CONSEQUENCES, both from this one constant: 40// (1) the `load` verb returned a silent 1 MiB PREFIX -- that is what fed nx_debtmine a 21.3% read it 41// reported as complete coverage (debt 1786235467). 42// (2) put/close/setcol were EFFECTIVELY DEAD on any plane over 1 MiB. They rebuild the whole store from 43// the rows they loaded, so on a 4.9 MB plane `neu` holds ~1 MiB of rows and nsp_would_shrink 44// correctly REFUSES every commit. NO DATA WAS EVER AT RISK -- that guard is the reason -- but the 45// unified write verb could not write to the estate's largest planes at all. 46// ***THE GUARD IS WHY THIS WAS AN OUTAGE AND NOT A CATASTROPHE.*** nsp_would_shrink turns a truncated 47// read into a refusal instead of a whole-store rewrite that silently drops the newest 2,900 rows. Do not 48// weaken it when raising this cap: a larger buffer moves the cliff, the guard removes the precipice. 49// Re-verify the sizing with the estate's own ruler: nx_planefit knowledge/store/debt- 33554432 50const NSP_CAP: i64 = 33554432 51const NSP_NL: i64 = 10 52const NSP_TAB: i64 = 9 53const NSP_PIPE: i64 = 124 54const NSP_DASH: i64 = 45 55const NSP_NCOL: i64 = 7 56const NSP_PAIR: i64 = 2 57const NSP_SP_BYTES: i64 = 128 58const NSP_STDERR: i64 = 2 59const NSP_PFXCAP: i64 = 256 60const NSP_MSGCAP: i64 = 256 61const NSP_ARGC_PUT_MIN: i64 = 6 // prog prefix put actor id + at least 1 more field 62const NSP_ARGC_PUT_MAX: i64 = 20 // id + up to 15 fields (16-col plane cap) 63const NSP_ARGC_CLOSE: i64 = 6 // prog prefix close actor id note 64const NSP_ARGC_SETCOL: i64 = 7 // prog prefix setcol actor id colidx value 65const NSP_ARGC_MIN: i64 = 3 66const NSP_MAXCOL: i64 = 16 // max columns a plane row may carry 67const NSP_SP2_BYTES: i64 = 256 // 16 cols * 2 slots * 8B 68const NSP_EXIT_USAGE: i64 = 2 69const NSP_EXIT_IO: i64 = 1 70const NSP_EXIT_REFUSED: i64 = 4 71// putmany (2026-08-18): file-fed batch. NSP_BATCH_MAX bounds the per-call row count so the range 72// arrays are sized once; a records file with more lines is REFUSED (never silently truncated). The 73// records file is read whole via sys_read_file (lseek-END sized, cannot short-read), so it carries no 74// cap of its own. 4096 rows is ~46x the 88-contract comparewatch queue that motivated it -- headroom 75// named, not a guess, and the refusal makes an over-large batch a loud error rather than lost rows. 76const NSP_HASH: i64 = 35 // '#': comment lines in a records file are skipped, like a staging buffer 77const NSP_BATCH_MAX: i64 = 4096 78const NSP_F_RECFILE: i64 = 4 // putmany argv: prog prefix putmany actor recfile 79const NSP_ARGC_PUTMANY: i64 = 5 80// put field argv indices: 81const NSP_F_ACTOR: i64 = 3 82const NSP_F_ID: i64 = 4 83const NSP_F_LAST: i64 = 10 84// close argv indices: 85const NSP_C_ID: i64 = 4 86const NSP_C_NOTE: i64 = 5 87// status column index in the 7-col row: 88const NSP_COL_STATUS: i64 = 3 89const NSP_COL_NOTE: i64 = 6 90 91func nsp_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 92func nsp_werr(s: *u8) -> i64 { sys_write(NSP_STDERR, s, nsp_slen(s)); return 0 } 93func nsp_eqs(a: *u8, b: *u8) -> i64 { 94 var i: i64 = 0 95 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 96 if b[i] != (0 as u8) { return 0 } 97 return 1 98} 99// slice [a,b) of q equals C-string s 100// two byte-slices equal? (putmany matches an input-record id slice against a loaded-plane id slice) 101func nsp_slice_eq_slice(qa: *u8, a1: i64, b1: i64, qb: *u8, a2: i64, b2: i64) -> i64 { 102 if b1 - a1 != b2 - a2 { return 0 } 103 var i: i64 = 0 104 while a1 + i < b1 { if qa[a1 + i] != qb[a2 + i] { return 0 } i = i + 1 } 105 return 1 106} 107func nsp_slice_eqs(q: *u8, a: i64, b: i64, s: *u8) -> i64 { 108 let sn: i64 = nsp_slen(s) 109 if b - a != sn { return 0 } 110 var i: i64 = 0 111 while i < sn { if q[a+i] != s[i] { return 0 } i = i + 1 } 112 return 1 113} 114// split line [ls,le) into up to NSP_MAXCOL (start,end) pairs (callers pass NSP_SP2_BYTES scratch) 115func nsp_cols(q: *u8, ls: i64, le: i64, sp: *i64) -> i64 { 116 var c: i64 = 0 117 var p: i64 = ls 118 while c < NSP_MAXCOL { 119 var e: i64 = p 120 var s: i64 = 1 121 while s == 1 { if e >= le { s = 0 } else { if q[e] == (NSP_TAB as u8) { s = 0 } else { e = e + 1 } } } 122 sp[c*NSP_PAIR] = p 123 sp[c*NSP_PAIR+1] = e 124 c = c + 1 125 if e >= le { return c } 126 p = e + 1 127 } 128 return c 129} 130func nsp_cat_slice(d: *u8, o: i64, q: *u8, a: i64, b: i64) -> i64 { 131 var oo: i64 = o 132 var i: i64 = a 133 while i < b { d[oo] = q[i]; oo = oo + 1; i = i + 1 } 134 return oo 135} 136// copy slice with inner tabs -> '|' (hist rows stay 6-col clean) 137func nsp_cat_slice_pipe(d: *u8, o: i64, q: *u8, a: i64, b: i64) -> i64 { 138 var oo: i64 = o 139 var i: i64 = a 140 while i < b { 141 var c: i64 = q[i] 142 if c == NSP_TAB { c = NSP_PIPE } 143 d[oo] = c as u8 144 oo = oo + 1 145 i = i + 1 146 } 147 return oo 148} 149// derive the hist prefix: "knowledge/store/debt-" -> "knowledge/store/debthist-" 150func nsp_hist_prefix(prefix: *u8, out: *u8) -> i64 { 151 var n: i64 = nsp_slen(prefix) 152 if n > 0 { if prefix[n-1] == (NSP_DASH as u8) { n = n - 1 } } 153 var i: i64 = 0 154 while i < n { out[i] = prefix[i]; i = i + 1 } 155 var o: i64 = ss_cat(out, n, "hist-" as *u8) 156 out[o] = 0 as u8 157 return o 158} 159// append one hist revision row: epoch actor verb id old new 160func nsp_hist_append(prefix: *u8, actor: *u8, verb: *u8, id: *u8, q: *u8, olda: i64, oldb: i64, neu: *u8, na: i64, nb: i64) -> i64 { 161 let hp: *u8 = sys_mmap(NSP_PFXCAP) 162 nsp_hist_prefix(prefix, hp) 163 let hb: *u8 = sys_mmap(NSP_CAP) 164 // O(1) APPEND 2026-07-30: this used to sts_load the ENTIRE hist plane just to append ONE revision 165 // row, then sts_seed the WHOLE plane back -- O(rows x segments) write amplification on EVERY put 166 // through this chokepoint. The row is a PURE APPEND (no existing row is modified), so we now build 167 // ONLY the new row at offset 0 and hand it to sts_append_fast. 168 var o: i64 = 0 169 o = ss_catn(hb, o, sys_now_realtime_sec()) 170 hb[o] = NSP_TAB as u8 171 o = o + 1 172 o = ss_cat(hb, o, actor) 173 hb[o] = NSP_TAB as u8 174 o = o + 1 175 o = ss_cat(hb, o, verb) 176 hb[o] = NSP_TAB as u8 177 o = o + 1 178 o = ss_cat(hb, o, id) 179 hb[o] = NSP_TAB as u8 180 o = o + 1 181 if oldb > olda { o = nsp_cat_slice_pipe(hb, o, q, olda, oldb) } else { o = ss_cat(hb, o, "-" as *u8) } 182 hb[o] = NSP_TAB as u8 183 o = o + 1 184 if nb > na { o = nsp_cat_slice_pipe(hb, o, neu, na, nb) } else { o = ss_cat(hb, o, "-" as *u8) } 185 hb[o] = NSP_NL as u8 186 o = o + 1 187 // FAST PATH: O(1) append -- writes ONLY this row, not the whole plane. o-1 strips the trailing 188 // newline because sts_append_fast takes the bare row. BOOTSTRAP: sts_append_fast requires an 189 // existing q:n, so a hist plane that does not exist YET legitimately returns <0; in that case 190 // (and only that case) fall back to a full seed to create it. Every later append is O(1). 191 // LOCKED form: the hist plane is NOT the plane `main` locked, so this append is otherwise 192 // unprotected -- and sts_append_fast is itself a read-modify-write on q:n (two appenders read the 193 // same n, write the same q:<n>, and newest-wins silently OVERWRITES one row). Lock order is always 194 // main-then-hist, so no ABBA deadlock is possible. 195 var rc: i64 = sts_append_fast_locked(hp, hb, o - 1) 196 if rc < 0 { rc = sts_seed(hp, hb, o) } 197 if rc < 0 { return 0 - 1 } 198 return 0 199} 200// span-atoi for the q:n value slice (ss_get hands back ptr+len, not a NUL string) 201func nsp_atoi_span(q: *u8, n: i64) -> i64 { 202 var v: i64 = 0 203 var i: i64 = 0 204 while i < n { let c: i64 = q[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 } 205 return v 206} 207// BATCHED HIST COMMIT (2026-08-19, the putmany residual). putmany collapsed the MAIN plane to one 208// commit and then appended its audit rows ONE AT A TIME: N x sts_append_fast_locked = N x (lock + 209// q:n read + segment commit + manifest rename + syncdir) on the hist plane -- MEASURED as the 210// surviving slow leg: a 163-row batch spent ~15 min in btrfs_log_inode_parent doing exactly this 211// while holding the MAIN plane lock, which is the whole convoy shrunk by one layer, not cured. 212// This is sts_append_fast generalized to k rows: ONE lock, ONE q:n read, k adds, ONE commit. 213// Same CAS discipline (gen0 before the read, refuse-on-stale under the lock); same bootstrap 214// (no q:n yet -> full seed of just these rows); lock order stays main-then-hist. 215// rows: concatenated row TEXTS (no trailing newlines); offs/lens: per-row slices; returns 0 or -1. 216func nsp_hist_commit_rows(hp: *u8, rows: *u8, offs: *i64, lens: *i64, k: i64) -> i64 { 217 if k <= 0 { return 0 } 218 let lk: i64 = sts_lock(hp) 219 let gen0: i64 = ss_max_segid(hp) 220 let pq: *i64 = sys_mmap(16) as *i64 221 let lq: *i64 = sys_mmap(16) as *i64 222 if ss_get(hp, "q:n" as *u8, pq, lq) != 1 { 223 // BOOTSTRAP: no q:n row yet -- seed a fresh plane holding exactly these rows. 224 let nlj: *u8 = sys_mmap(NSP_CAP) 225 var no2: i64 = 0 226 var b: i64 = 0 227 while b < k { 228 var z: i64 = 0 229 while z < lens[b] { nlj[no2] = rows[offs[b] + z]; no2 = no2 + 1; z = z + 1 } 230 nlj[no2] = NSP_NL as u8; no2 = no2 + 1 231 b = b + 1 232 } 233 let src: i64 = sts_seed(hp, nlj, no2) 234 sts_unlock(lk) 235 if src < 0 { return 0 - 1 } 236 return 0 237 } 238 let n0: i64 = nsp_atoi_span(pq[0] as *u8, lq[0]) 239 var need: i64 = STS_WSLACK 240 var t: i64 = 0 241 while t < k { need = need + lens[t] + STS_KEYCAP + STS_ROWOVH; t = t + 1 } 242 let w: *i64 = ss_begin_cap(need) 243 let key: *u8 = sys_mmap(STS_KEYCAP) 244 var i: i64 = 0 245 var addfail: i64 = 0 246 while i < k { 247 sts_rowkey(n0 + i, key) 248 if ss_add(w, STS_KIND_LIVE, key, ((rows as i64) + offs[i]) as *u8, lens[i]) < 0 { addfail = 1 } 249 i = i + 1 250 } 251 let cb: *u8 = sys_mmap(STS_NUMCAP) 252 let cl: i64 = ss_catn(cb, 0, n0 + k) 253 if ss_add(w, STS_KIND_LIVE, "q:n" as *u8, cb, cl) < 0 { addfail = 1 } 254 if addfail == 1 { sts_unlock(lk); return 0 - 1 } 255 let crc: i64 = ss_commit_cas(hp, w, ss_next_segid(hp), gen0) 256 sts_unlock(lk) 257 if crc != 0 { return 0 - 1 } 258 return 0 259} 260func nsp_atoi(s: *u8) -> i64 { 261 var v: i64 = 0 262 var i: i64 = 0 263 while s[i] != (0 as u8) { 264 let c: i64 = s[i] 265 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } 266 i = i + 1 267 } 268 return v 269} 270 271func nsp_report(verb: *u8, id: *u8, replaced: i64, rows: i64) -> i64 { 272 let m: *u8 = sys_mmap(NSP_MSGCAP) 273 var o: i64 = ss_cat(m, 0, verb) 274 o = ss_cat(m, o, " " as *u8) 275 o = ss_cat(m, o, id) 276 o = ss_cat(m, o, " replaced=" as *u8) 277 o = ss_catn(m, o, replaced) 278 o = ss_cat(m, o, " rows=" as *u8) 279 o = ss_catn(m, o, rows) 280 m[o] = NSP_NL as u8 281 o = o + 1 282 sys_write(1, m, o) 283 return 0 284} 285 286// SHARED ANTI-CLOBBER TOOTH (2026-08-02, debts 1785713047/1785713373) -- ONE guard, FOUR callers. 287// EVERY write verb here rebuilds the WHOLE plane from what sts_load could reach, then re-seeds it. If that 288// load came back PARTIAL, every row it could not see is silently DELETED. put/putn ADD rows; close/setcol 289// MODIFY one row in place -- so NONE of them can ever legitimately REDUCE the row count. A count below what 290// the plane already declares therefore means the load was short, and committing it would destroy data 291// (commontask- 37->10, dedupq- 21->3, both recovered 2026-08-02). Refuse instead of committing the loss. 292// FAIL-OPEN when q:n is absent -- that call is BOOTSTRAPPING the plane, which is legitimate. 293// ★Deliberately NOT in the shared sts_seed primitive: there it would refuse nx_plane_append's ROLLBACK and 294// nx_plane_repair, both of which shrink BY DEFINITION (1785713047). The guard belongs to the WRITER. 295// Proven both directions by nx_store_put_shrink_gate 4/4: it BITES a manufactured over-declared plane 296// (rc=4, store untouched) and SPARES a healthy one (rc=0, q:n 3->4). 297func nsp_would_shrink(prefix: *u8, rows: i64) -> i64 { 298 let vq: *i64 = sys_mmap(NSP_MAGIC_4096) as *i64 299 let vl: *i64 = sys_mmap(NSP_MAGIC_4096) as *i64 300 if ss_get(prefix, "q:n" as *u8, vq, vl) == 1 { 301 let declared: i64 = sts_atoi(vq[0] as *u8, vl[0]) 302 if rows < declared { return 1 } 303 } 304 return 0 305} 306func nsp_refuse_shrink(vname: *u8) -> i64 { 307 nsp_werr("REFUSED: this " as *u8) 308 nsp_werr(vname) 309 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) 310 return 0 311} 312 313 314// Retained full-plane export: data bypasses the bounded MCP stdout channel. 315// Exclusive publication and identical-byte retries preserve existing artifacts. 316func nsp_export_equal(path:*u8,b:*u8,n:i64)->i64{ 317 let fd:i64=sys_openat_rd(path);if fd<0{return -1} 318 let size:i64=sys_lseek(fd,0,2);if size!=n{sys_close(fd);return 0} 319 if sys_lseek(fd,0,0)<0{sys_close(fd);return -1} 320 let chunk:*u8=sts_mm(FSX_DENT_BUF);var off:i64=0 321 while off<n{var take:i64=n-off;if take>FSX_DENT_BUF{take=FSX_DENT_BUF} 322 let got:i64=sys_read(fd,chunk,take);if got<=0{sys_close(fd);sys_munmap(chunk,FSX_DENT_BUF);return -1} 323 var i:i64=0;while i<got{if chunk[i]!=b[off+i]{sys_close(fd);sys_munmap(chunk,FSX_DENT_BUF);return 0};i=i+1};off=off+got 324 };sys_close(fd);sys_munmap(chunk,FSX_DENT_BUF);return 1 325} 326func nsp_export_sync_parent(path:*u8)->i64{ 327 let n:i64=nsp_slen(path);var last:i64=-1;var i:i64=0 328 while i<n{if path[i]==(47 as u8){last=i};i=i+1} 329 let parent:*u8=sts_mm(n+2);if last<0{parent[0]=46 as u8;parent[1]=0 as u8}else{if last==0{parent[0]=47 as u8;parent[1]=0 as u8}else{i=0;while i<last{parent[i]=path[i];i=i+1};parent[last]=0 as u8}} 330 let fd:i64=sys_openat_rd(parent);sys_munmap(parent,n+2);if fd<0{return -1};let rc:i64=sys_fsync(fd);sys_close(fd);return rc 331} 332func nsp_export_coverage(prefix:*u8)->i64{ 333 let sp:*i64=sts_mm(16) as *i64;let count:i64=ss_manifest_dyn(prefix,sp);if count<1{return -1} 334 let names:*i64=sp[0] as *i64;let pn:i64=nsp_slen(prefix);var i:i64=0 335 while i<count{let name:*u8=names[i] as *u8;let nn:i64=nsp_slen(name);let path:*u8=sts_mm(pn+nn+6) 336 var p:i64=ss_cat(path,0,prefix);p=ss_cat(path,p,name);p=ss_cat(path,p,".docs");path[p]=0 as u8 337 let fd:i64=sys_openat_rd(path);sys_munmap(path,pn+nn+6);if fd<0{return -1};let size:i64=sys_lseek(fd,0,2);sys_close(fd);if size<0{return -1};i=i+1 338 };return count 339} 340func nsp_load_file(prefix:*u8,path:*u8)->i64{ 341 if fsx_denied(prefix)==1||fsx_write_denied(path)==1{nsp_werr("LOAD-FILE REFUSED reason=path-policy\n");return NSP_EXIT_REFUSED} 342 let segments:i64=nsp_export_coverage(prefix);if segments<1{nsp_werr("LOAD-FILE REFUSED reason=segment-coverage-unproven\n");return NSP_EXIT_IO} 343 let ln:*i64=sts_mm(16) as *i64;let b:*u8=sts_load_fit(prefix,ln);let n:i64=ln[0] 344 if (b as i64)==0||n<1{nsp_werr("LOAD-FILE REFUSED reason=empty-or-incomplete-row-plane\n");return NSP_EXIT_IO} 345 let honest:*u8=sts_mm(n+1);let flags:*i64=sts_mm(24) as *i64;let hn:i64=sts_load_honest(prefix,honest,n+1,flags) 346 if hn!=n{nsp_werr("LOAD-FILE REFUSED reason=initialized-span-disagreement\n");return NSP_EXIT_REFUSED} 347 var rows:i64=0;var i:i64=0;var same:i64=1;while i<n{if b[i]==(10 as u8){rows=rows+1};if honest[i]!=b[i]{same=0};i=i+1} 348 if hn!=n||same!=1||flags[0]!=rows||flags[1]!=rows||flags[2]!=0{nsp_werr("LOAD-FILE REFUSED reason=row-coverage-disagreement\n");return NSP_EXIT_REFUSED} 349 let rc:i64=fxc_create(path,b,n);var changed:i64=1 350 if rc==FXC_EXISTS{changed=0}else{if rc!=n{nsp_werr("LOAD-FILE REFUSED reason=exclusive-write-or-resource-failure\n");return NSP_EXIT_IO}} 351 if nsp_export_equal(path,b,n)!=1{nsp_werr("LOAD-FILE REFUSED reason=existing-artifact-differs-or-readback-failed\n");return NSP_EXIT_REFUSED} 352 if nsp_export_sync_parent(path)<0{nsp_werr("LOAD-FILE INCOMPLETE reason=parent-fsync-failed artifact-may-exist=1\n");return NSP_EXIT_IO} 353 fsx_puts("LOAD-FILE OK bytes=");fsx_putn(n);fsx_puts(" rows=");fsx_putn(rows);fsx_puts(" declared_rows=");fsx_putn(flags[0]);fsx_puts(" loaded_rows=");fsx_putn(flags[1]);fsx_puts(" beyond_declared_observed=");fsx_putn(flags[2]);fsx_puts(" segments_readable=");fsx_putn(segments);fsx_puts(" changed=");fsx_putn(changed);fsx_puts(" coverage=declared-live-manifest archive_scanned=0 beyond_probe_window=");fsx_putn(STS_PROBE_WINDOW);fsx_puts(" beyond_probe_miss_run=");fsx_putn(STS_PROBE_MISS_RUN);fsx_puts(" path=");fsx_puts(path);fsx_puts("\n");return 0 354} 355 356func main(argc: i64, argv: *i64) -> i64 { 357 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 } 358 let prefix: *u8 = argv[1] as *u8 359 let verb: *u8 = argv[2] as *u8 360 361 // seq1559 MIGRATION 2026-07-30 (ws=sev-eater). Every mutating verb here is a READ-MODIFY-WRITE 362 // over a shared plane: sts_load -> rewrite the whole buffer -> sts_seed. Unlocked, two concurrent 363 // invocations interleave as A-loads / B-loads / A-commits / B-commits-without-A and one caller's 364 // row is gone silently. That is not theoretical -- nx_sts_lock_gate measures 5 of 6 rows destroyed 365 // under 6-way concurrency, and 0 lost once the lock is held. 366 // Taken HERE, before the first load, so the critical section covers the entire operation rather 367 // than just the commit -- locking only the write would still let two readers race the same 368 // snapshot. Uses sts_lock, i.e. the SAME <prefix>plock nx_debt / nx_plane_append / sts_append_row 369 // use; a private lock file would have been easier and would have excluded nobody. 370 // NO UNLOCK IS NEEDED ON ANY PATH: this is a one-shot CLI organ and every exit -- success or a 371 // fail-closed sys_exit() -- terminates the process, which releases the flock. There is therefore 372 // no path that can leak the lock, which is why the lock is safe to take this early. 373 if nsp_eqs("load-file",verb)==1 { if argc!=4{return NSP_EXIT_USAGE};let lock:i64=sts_lock(prefix);if lock<0{return NSP_EXIT_IO};return nsp_load_file(prefix,argv[3] as *u8) } 374 sts_lock(prefix) 375 376 // ---- ADOPTS sts_load_fit ON BOTH READ VERBS (2026-08-08) --------------------------------- 377 // ***NSP_CAP IS NO LONGER IN THE CORRECTNESS PATH HERE.*** Raising it (which I did earlier today, 378 // 1048576 -> 33554432) is the INFERIOR HALF of a migration four organs completed on 2026-08-06: 379 // nx_dora:222, nx_pm_board:162/167/172/177, nx_sheriff:311 and nx_planefit:229 all moved to 380 // sts_load_fit, and nx_debt's own DB_CAP comment had already named the requirement -- "THIS IS THE 381 // THIRD RAISE, NOT A FIX ... A CAP THAT CAN BE CROSSED IN SILENCE WILL BE CROSSED AGAIN." This verb 382 // was missed in BOTH halves, which is exactly why nx_debtmine forked it and received 1,048,576 of 383 // 5,048,015 plane bytes -- 21.3% -- and reported it as complete coverage. 384 // sts_load_fit needs no stat and no heuristic: sts_load truncates ONLY when its buffer fills, so a 385 // return STRICTLY LESS THAN cap PROVES completeness; n == cap is the one ambiguous case and the only 386 // one it grows on (8 MiB doubling to 1 GiB, munmapping each attempt), then it REFUSES with a named 387 // diagnostic rather than hand back a partial board. 388 if nsp_eqs("load" as *u8, verb) == 1 { 389 let ln: *i64 = sys_mmap(16) as *i64 390 let b: *u8 = sts_load_fit(prefix, ln) 391 if (b as i64) == 0 { nsp_werr("load REFUSED: sts_load_fit exhausted its growth and will NOT return a partial board -- shard the plane or raise STS_FIT_MAXTRIES deliberately\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 392 let n: i64 = ln[0] 393 // THREE STATES, NOT TWO (2026-08-15). This printed "plane EMPTY / unseeded" for EVERY zero-row 394 // outcome, and that is A FALSE DIAGNOSIS WITH AN AUTHORITATIVE NAME whenever the prefix names a 395 // store that HAS data. MEASURED THE SAME DAY, and it cost three successively-wrong debt rows and 396 // one PUBLISHED falsehood before the control was finally run: knowledge/store/ecomat returns zero 397 // rows here while its own manifest lists 35 segments and nx_ecosystem_maturity_rollup reads 26 398 // domains out of that exact prefix in the same minute. 399 // NOTHING IS BROKEN. This verb is a ROW-plane reader (id title sev status owner scope note); 400 // ecomat is not a row plane, so it carries no q:n row index and a row reader correctly finds 401 // nothing. The control that settled it was one call: the same verb on knowledge/store/roi- 402 // returns every row, which exonerates sts_load_fit, sts_load, ss_open and ss_open2 at once. 403 // * A READER THAT REPORTS "EMPTY" WHEN IT MEANS "NOT MY FORMAT" INVITES THE NEXT READER TO 404 // RESEED A PLANE THAT IS ALREADY FULL. 405 // * ASK OF ANY VERDICT: IS THE SUBJECT THE THING THE MESSAGE NAMES? Here it was not. 406 // The exit code is deliberately UNCHANGED (rule 19 -- an exit code is an API contract and no 407 // caller asked for a new one); only the sentence a human reads is corrected. 408 if n <= 0 { 409 let hh: *i64 = ss_open(prefix) 410 var segs: i64 = 0 411 if (hh as i64) != 0 { segs = hh[0] } 412 if segs > 0 { 413 nsp_werr("load: NOT-A-ROW-PLANE -- this prefix resolves to a store that HAS segments but carries no q:n row index, so a ROW reader finds nothing. That is NOT the same fact as an empty plane: the records are present and addressed another way, and seeding here would DUPLICATE LIVE DATA. Read it with the organ that owns this plane.\n" as *u8) 414 sys_exit(NSP_EXIT_IO) 415 return NSP_EXIT_IO 416 } 417 nsp_werr("plane EMPTY / unseeded (no segments resolve under this prefix)\n" as *u8) 418 sys_exit(NSP_EXIT_IO) 419 return NSP_EXIT_IO 420 } 421 sys_write(1, b, n) 422 sys_exit(0) 423 return 0 424 } 425 if nsp_eqs("hist" as *u8, verb) == 1 { 426 let hp: *u8 = sys_mmap(NSP_PFXCAP) 427 nsp_hist_prefix(prefix, hp) 428 let l2: *i64 = sys_mmap(16) as *i64 429 let b2: *u8 = sts_load_fit(hp, l2) 430 if (b2 as i64) == 0 { nsp_werr("hist REFUSED: sts_load_fit exhausted its growth and will NOT return a partial revision log\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 431 let n2: i64 = l2[0] 432 if n2 <= 0 { nsp_werr("hist EMPTY (no revisions yet)\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 433 sys_write(1, b2, n2) 434 sys_exit(0) 435 return 0 436 } 437 438 if nsp_eqs("put" as *u8, verb) == 1 { 439 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 } 440 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 } 441 let actor: *u8 = argv[NSP_F_ACTOR] as *u8 442 let id: *u8 = argv[NSP_F_ID] as *u8 443 let cur: *u8 = sys_mmap(NSP_CAP) 444 var cn: i64 = sts_load(prefix, cur, NSP_CAP - NSP_MAGIC_4096) 445 if cn < 0 { cn = 0 } 446 let neu: *u8 = sys_mmap(NSP_CAP) 447 let sp: *i64 = sys_mmap(NSP_SP2_BYTES) as *i64 448 // literal counters first 449 var no: i64 = 0 450 var replaced: i64 = 0 451 var rows: i64 = 0 452 var olda: i64 = 0 453 var oldb: i64 = 0 454 var i: i64 = 0 455 while i < cn { 456 var le: i64 = i 457 var s: i64 = 1 458 while s == 1 { if le >= cn { s = 0 } else { if cur[le] == (NSP_NL as u8) { s = 0 } else { le = le + 1 } } } 459 if le > i { 460 nsp_cols(cur, i, le, sp) 461 if nsp_slice_eqs(cur, sp[0], sp[1], id) == 1 { 462 replaced = 1 463 olda = i 464 oldb = le 465 } else { 466 no = nsp_cat_slice(neu, no, cur, i, le) 467 neu[no] = NSP_NL as u8 468 no = no + 1 469 rows = rows + 1 470 } 471 } 472 i = le + 1 473 } 474 // append the new/updated row (ALL fields argv[4..argc-1] tab-joined -- N-col, schema-agnostic) 475 let na: i64 = no 476 let flast: i64 = argc - 1 477 var f: i64 = NSP_F_ID 478 while f <= flast { 479 no = ss_cat(neu, no, argv[f] as *u8) 480 if f < flast { neu[no] = NSP_TAB as u8; no = no + 1 } 481 f = f + 1 482 } 483 let nb: i64 = no 484 neu[no] = NSP_NL as u8 485 no = no + 1 486 rows = rows + 1 487 // ROW-LEVEL COMMIT (LV17): commit ONE row, never the plane. An existing id is replaced at its q:<seq>; a new id is 488 // appended as q:<n>. The whole-plane seed runs ONLY to bootstrap an unseeded plane or when the row cannot be located 489 // by seq, and only there does the shrink guard still apply (it exists to stop a SEED from deleting unseen rows). 490 var rc: i64 = 0 - 1 491 var cpath_rc: *u8 = "replace" as *u8 492 if replaced == 1 { 493 let seq_rc: i64 = sts_find_seq(prefix, id) 494 if seq_rc >= 0 { rc = sts_replace_fast(prefix, seq_rc, ((neu as i64) + na) as *u8, nb - na) } 495 } else { 496 rc = sts_append_fast(prefix, ((neu as i64) + na) as *u8, nb - na) 497 cpath_rc = "append" as *u8 498 } 499 if rc < 0 { 500 cpath_rc = "seed" as *u8 501 if nsp_would_shrink(prefix, rows) == 1 { nsp_refuse_shrink("put" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 502 rc = sts_seed(prefix, neu, no) 503 } 504 nsp_werr("commit-path=" as *u8); nsp_werr(cpath_rc); nsp_werr("\n" as *u8) 505 if rc < 0 { nsp_werr("plane commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 506 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 } 507 nsp_report("PUT" as *u8, id, replaced, rows) 508 sys_exit(0) 509 return 0 510 } 511 512 if nsp_eqs("close" as *u8, verb) == 1 { 513 if argc < NSP_ARGC_CLOSE { nsp_werr("close needs <actor> <id> <note>\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 514 let actor2: *u8 = argv[NSP_F_ACTOR] as *u8 515 let id2: *u8 = argv[NSP_C_ID] as *u8 516 let cnote: *u8 = argv[NSP_C_NOTE] as *u8 517 let cur2: *u8 = sys_mmap(NSP_CAP) 518 let cn2: i64 = sts_load(prefix, cur2, NSP_CAP - NSP_MAGIC_4096) 519 if cn2 <= 0 { nsp_werr("plane EMPTY: nothing to close\n" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 520 let neu2: *u8 = sys_mmap(NSP_CAP) 521 let sp2: *i64 = sys_mmap(NSP_SP2_BYTES) as *i64 522 var no2: i64 = 0 523 var found: i64 = 0 524 var rows2: i64 = 0 525 var olda2: i64 = 0 526 var oldb2: i64 = 0 527 var na2: i64 = 0 528 var nb2: i64 = 0 529 var j: i64 = 0 530 while j < cn2 { 531 var le2: i64 = j 532 var s2: i64 = 1 533 while s2 == 1 { if le2 >= cn2 { s2 = 0 } else { if cur2[le2] == (NSP_NL as u8) { s2 = 0 } else { le2 = le2 + 1 } } } 534 if le2 > j { 535 let nc: i64 = nsp_cols(cur2, j, le2, sp2) 536 if nsp_slice_eqs(cur2, sp2[0], sp2[1], id2) == 1 { 537 found = 1 538 olda2 = j 539 oldb2 = le2 540 na2 = no2 541 // rebuild: cols 0..2 as-is, col3 = closed, cols 4..5 as-is, col6 = old-note | close-note 542 var c: i64 = 0 543 while c < nc { 544 if c == NSP_COL_STATUS { 545 no2 = ss_cat(neu2, no2, "closed" as *u8) 546 } else { 547 no2 = nsp_cat_slice(neu2, no2, cur2, sp2[c*NSP_PAIR], sp2[c*NSP_PAIR+1]) 548 } 549 if c == NSP_COL_NOTE { 550 no2 = ss_cat(neu2, no2, " | " as *u8) 551 no2 = ss_cat(neu2, no2, cnote) 552 } 553 if c < nc - 1 { neu2[no2] = NSP_TAB as u8; no2 = no2 + 1 } 554 c = c + 1 555 } 556 nb2 = no2 557 neu2[no2] = NSP_NL as u8 558 no2 = no2 + 1 559 rows2 = rows2 + 1 560 } else { 561 no2 = nsp_cat_slice(neu2, no2, cur2, j, le2) 562 neu2[no2] = NSP_NL as u8 563 no2 = no2 + 1 564 rows2 = rows2 + 1 565 } 566 } 567 j = le2 + 1 568 } 569 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 } 570 // ROW-LEVEL COMMIT (LV17): commit ONE row, never the plane. An existing id is replaced at its q:<seq>; a new id is 571 // appended as q:<n>. The whole-plane seed runs ONLY to bootstrap an unseeded plane or when the row cannot be located 572 // by seq, and only there does the shrink guard still apply (it exists to stop a SEED from deleting unseen rows). 573 var rc2: i64 = 0 - 1 574 var cpath_rc2: *u8 = "replace" as *u8 575 let seq_rc2: i64 = sts_find_seq(prefix, id2) 576 if seq_rc2 >= 0 { rc2 = sts_replace_fast(prefix, seq_rc2, ((neu2 as i64) + na2) as *u8, nb2 - na2) } 577 if rc2 < 0 { 578 cpath_rc2 = "seed" as *u8 579 if nsp_would_shrink(prefix, rows2) == 1 { nsp_refuse_shrink("close" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 580 rc2 = sts_seed(prefix, neu2, no2) 581 } 582 nsp_werr("commit-path=" as *u8); nsp_werr(cpath_rc2); nsp_werr("\n" as *u8) 583 if rc2 < 0 { nsp_werr("plane commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 584 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 } 585 nsp_report("CLOSED" as *u8, id2, found, rows2) 586 sys_exit(0) 587 return 0 588 } 589 590 if nsp_eqs("putn" as *u8, verb) == 1 { 591 // BATCH put (W008 residual, 2026-07-18): N rows in ONE call -- one store commit, per-row hist. 592 // putn <actor> <ncols> <field>... (groups of ncols fields, first field of each group = id) 593 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 } 594 let actorn: *u8 = argv[NSP_F_ACTOR] as *u8 595 let ncols: i64 = nsp_atoi(argv[4] as *u8) 596 if ncols < 2 { nsp_werr("putn: ncols must be 2..15\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 597 if ncols > 15 { nsp_werr("putn: ncols must be 2..15\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 598 let nfields: i64 = argc - 5 599 let ngroups: i64 = nfields / ncols 600 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 } 601 if ngroups < 1 { nsp_werr("putn: no groups\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 602 if ngroups > 32 { nsp_werr("putn: max 32 groups per call\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 603 var abuf: *u8 = sys_mmap(NSP_CAP) 604 var bbuf: *u8 = sys_mmap(NSP_CAP) 605 var an: i64 = sts_load(prefix, abuf, NSP_CAP - NSP_MAGIC_4096) 606 if an < 0 { an = 0 } 607 let spn: *i64 = sys_mmap(NSP_SP2_BYTES) as *i64 608 var totrows: i64 = 0 609 var g: i64 = 0 610 while g < ngroups { 611 let base: i64 = 5 + g * ncols 612 let idn: *u8 = argv[base] as *u8 613 var no: i64 = 0 614 var olda: i64 = 0 615 var oldb: i64 = 0 616 var rows: i64 = 0 617 var i2: i64 = 0 618 while i2 < an { 619 var le: i64 = i2 620 var s: i64 = 1 621 while s == 1 { if le >= an { s = 0 } else { if abuf[le] == (NSP_NL as u8) { s = 0 } else { le = le + 1 } } } 622 if le > i2 { 623 nsp_cols(abuf, i2, le, spn) 624 if nsp_slice_eqs(abuf, spn[0], spn[1], idn) == 1 { olda = i2; oldb = le } else { 625 no = nsp_cat_slice(bbuf, no, abuf, i2, le) 626 bbuf[no] = NSP_NL as u8 627 no = no + 1 628 rows = rows + 1 629 } 630 } 631 i2 = le + 1 632 } 633 let na: i64 = no 634 var f2: i64 = base 635 let flast2: i64 = base + ncols - 1 636 while f2 <= flast2 { 637 no = ss_cat(bbuf, no, argv[f2] as *u8) 638 if f2 < flast2 { bbuf[no] = NSP_TAB as u8; no = no + 1 } 639 f2 = f2 + 1 640 } 641 let nb: i64 = no 642 bbuf[no] = NSP_NL as u8 643 no = no + 1 644 rows = rows + 1 645 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 } 646 let tswap: *u8 = abuf 647 abuf = bbuf 648 bbuf = tswap 649 an = no 650 totrows = rows 651 g = g + 1 652 } 653 // putn accumulates across groups, so totrows is the final count -- same invariant as put: a BATCH of 654 // adds can only grow or hold steady. ⚠This one commits ONCE after N groups whose hist frames are 655 // ALREADY written, so an unguarded partial load here loses rows the history says were just put. 656 if nsp_would_shrink(prefix, totrows) == 1 { nsp_refuse_shrink("putn" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 657 if sts_seed(prefix, abuf, an) < 0 { nsp_werr("plane commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 658 nsp_report("PUTN-GROUPS" as *u8, actorn, ngroups, totrows) 659 sys_exit(0) 660 return 0 661 } 662 663 if nsp_eqs("setcol" as *u8, verb) == 1 { 664 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 } 665 let actor3: *u8 = argv[NSP_F_ACTOR] as *u8 666 let id3: *u8 = argv[NSP_C_ID] as *u8 667 let colidx: i64 = nsp_atoi(argv[5] as *u8) 668 let val: *u8 = argv[6] as *u8 669 let cur3: *u8 = sys_mmap(NSP_CAP) 670 let cn3: i64 = sts_load(prefix, cur3, NSP_CAP - NSP_MAGIC_4096) 671 if cn3 <= 0 { nsp_werr("plane EMPTY: nothing to setcol\n" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 672 let neu3: *u8 = sys_mmap(NSP_CAP) 673 let sp3: *i64 = sys_mmap(NSP_SP2_BYTES) as *i64 674 var no3: i64 = 0 675 var found3: i64 = 0 676 var rows3: i64 = 0 677 var olda3: i64 = 0 678 var oldb3: i64 = 0 679 var na3: i64 = 0 680 var nb3: i64 = 0 681 var j3: i64 = 0 682 while j3 < cn3 { 683 var le3: i64 = j3 684 var s3: i64 = 1 685 while s3 == 1 { if le3 >= cn3 { s3 = 0 } else { if cur3[le3] == (NSP_NL as u8) { s3 = 0 } else { le3 = le3 + 1 } } } 686 if le3 > j3 { 687 let nc3: i64 = nsp_cols(cur3, j3, le3, sp3) 688 if nsp_slice_eqs(cur3, sp3[0], sp3[1], id3) == 1 { 689 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 } 690 found3 = 1 691 olda3 = j3 692 oldb3 = le3 693 na3 = no3 694 var c3: i64 = 0 695 while c3 < nc3 { 696 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]) } 697 if c3 < nc3 - 1 { neu3[no3] = NSP_TAB as u8; no3 = no3 + 1 } 698 c3 = c3 + 1 699 } 700 nb3 = no3 701 neu3[no3] = NSP_NL as u8 702 no3 = no3 + 1 703 rows3 = rows3 + 1 704 } else { 705 no3 = nsp_cat_slice(neu3, no3, cur3, j3, le3) 706 neu3[no3] = NSP_NL as u8 707 no3 = no3 + 1 708 rows3 = rows3 + 1 709 } 710 } 711 j3 = le3 + 1 712 } 713 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 } 714 // ROW-LEVEL COMMIT (LV17): commit ONE row, never the plane. An existing id is replaced at its q:<seq>; a new id is 715 // appended as q:<n>. The whole-plane seed runs ONLY to bootstrap an unseeded plane or when the row cannot be located 716 // by seq, and only there does the shrink guard still apply (it exists to stop a SEED from deleting unseen rows). 717 var rc3: i64 = 0 - 1 718 var cpath_rc3: *u8 = "replace" as *u8 719 let seq_rc3: i64 = sts_find_seq(prefix, id3) 720 if seq_rc3 >= 0 { rc3 = sts_replace_fast(prefix, seq_rc3, ((neu3 as i64) + na3) as *u8, nb3 - na3) } 721 if rc3 < 0 { 722 cpath_rc3 = "seed" as *u8 723 if nsp_would_shrink(prefix, rows3) == 1 { nsp_refuse_shrink("setcol" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 724 rc3 = sts_seed(prefix, neu3, no3) 725 } 726 nsp_werr("commit-path=" as *u8); nsp_werr(cpath_rc3); nsp_werr("\n" as *u8) 727 if rc3 < 0 { nsp_werr("plane commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 728 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 } 729 nsp_report("SETCOL" as *u8, id3, found3, rows3) 730 sys_exit(0) 731 return 0 732 } 733 734 if nsp_eqs("putmany" as *u8, verb) == 1 { 735 // FILE-FED BATCH: one sts_load, one sts_seed, per-row hist. Collapses N forked full-plane 736 // rewrites into ONE, killing the concurrent-writer D-state storm by construction (a single 737 // sequential process holding the lock once). Records file = one row per line, TAB-separated 738 // fields, first field = id; '#'/blank lines skipped -- byte-shape identical to a plane row. 739 if argc < NSP_ARGC_PUTMANY { nsp_werr("putmany needs <actor> <recordsfile>\n" as *u8); sys_exit(NSP_EXIT_USAGE); return NSP_EXIT_USAGE } 740 let actorm: *u8 = argv[NSP_F_ACTOR] as *u8 741 let recpath: *u8 = argv[NSP_F_RECFILE] as *u8 742 let rlen: *i64 = sys_mmap(16) as *i64 743 let recs: *u8 = sys_read_file(recpath, rlen) 744 if (recs as i64) == 0 { nsp_werr("putmany REFUSED: records file unreadable (store untouched)\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 745 let rn: i64 = rlen[0] 746 // parse input records into (line, id) slice ranges. '#' and blank lines are skipped so a 747 // records file can carry comments exactly like a staging buffer. 748 let la: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 // input line start 749 let lb: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 // input line end (exclusive) 750 let ida: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 // input id start 751 let idb: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 // input id end 752 var nin: i64 = 0 753 var rp: i64 = 0 754 while rp < rn { 755 var re: i64 = rp 756 while re < rn { if recs[re] == (NSP_NL as u8) { break } re = re + 1 } 757 if re > rp { if recs[rp] != (NSP_HASH as u8) { 758 if nin >= NSP_BATCH_MAX { nsp_werr("putmany REFUSED: more than NSP_BATCH_MAX records (store untouched) -- split the batch or raise the const deliberately\n" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 759 // id = first TAB-delimited field 760 var ie: i64 = rp 761 while ie < re { if recs[ie] == (NSP_TAB as u8) { break } ie = ie + 1 } 762 la[nin] = rp; lb[nin] = re; ida[nin] = rp; idb[nin] = ie 763 nin = nin + 1 764 } } 765 rp = re + 1 766 } 767 if nin == 0 { nsp_report("PUTMANY" as *u8, actorm, 0, 0); sys_exit(0); return 0 } 768 // load the plane ONCE 769 let cur: *u8 = sys_mmap(NSP_CAP) 770 var cn: i64 = sts_load(prefix, cur, NSP_CAP - NSP_MAGIC_4096) 771 if cn < 0 { cn = 0 } 772 let neu: *u8 = sys_mmap(NSP_CAP) 773 let spm: *i64 = sys_mmap(NSP_SP2_BYTES) as *i64 774 // per-input OLD range in cur (the row this input replaces, if any) for hist 775 let olda: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 776 let oldb: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 777 var k0: i64 = 0 778 while k0 < nin { olda[k0] = 0; oldb[k0] = 0; k0 = k0 + 1 } 779 var no: i64 = 0 780 var rows: i64 = 0 781 // PASS 1: copy every existing row whose id is NOT in the input; record the OLD range for the 782 // input that replaces it (matched-input hist gets a real before-image, new-input gets '-'). 783 var i: i64 = 0 784 while i < cn { 785 var le: i64 = i 786 var s: i64 = 1 787 while s == 1 { if le >= cn { s = 0 } else { if cur[le] == (NSP_NL as u8) { s = 0 } else { le = le + 1 } } } 788 if le > i { 789 nsp_cols(cur, i, le, spm) 790 var matched: i64 = 0 - 1 791 var q: i64 = 0 792 while q < nin { 793 if nsp_slice_eq_slice(cur, spm[0], spm[1], recs, ida[q], idb[q]) == 1 { matched = q; q = nin } else { q = q + 1 } 794 } 795 if matched >= 0 { olda[matched] = i; oldb[matched] = le } else { 796 no = nsp_cat_slice(neu, no, cur, i, le) 797 neu[no] = NSP_NL as u8; no = no + 1 798 rows = rows + 1 799 } 800 } 801 i = le + 1 802 } 803 // PASS 2: append every input row; record its NEW range for hist. 804 let na: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 805 let nb: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 806 var k: i64 = 0 807 while k < nin { 808 na[k] = no 809 no = nsp_cat_slice(neu, no, recs, la[k], lb[k]) 810 nb[k] = no 811 neu[no] = NSP_NL as u8; no = no + 1 812 rows = rows + 1 813 k = k + 1 814 } 815 // the SAME anti-clobber guard the other writers use: a batch of replace-or-append can only grow 816 // or hold the row count, so a lower count means the load came back partial -- refuse, never commit. 817 if nsp_would_shrink(prefix, rows) == 1 { nsp_refuse_shrink("putmany" as *u8); sys_exit(NSP_EXIT_REFUSED); return NSP_EXIT_REFUSED } 818 // ONE plane commit for the whole batch (this is the 88->1 fsync collapse). 819 if sts_seed(prefix, neu, no) < 0 { nsp_werr("plane commit error\n" as *u8); sys_exit(NSP_EXIT_IO); return NSP_EXIT_IO } 820 // per-row hist AFTER the single commit (matches putn's ordering). Each is an O(1) fast append; 821 // a hist failure is reported per id but does not unwind the committed plane (same as putn). 822 // ONE hist commit for the whole batch (2026-08-19): the per-row sts_append_fast loop was the 823 // surviving fsync convoy (~15 min for 163 rows, measured in btrfs_log_inode_parent). Rows are 824 // built with the SAME fields nsp_hist_append writes, then committed once. 825 let hp2: *u8 = sys_mmap(NSP_PFXCAP) 826 nsp_hist_prefix(prefix, hp2) 827 let hrows: *u8 = sys_mmap(NSP_CAP) 828 let hoffs: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 829 let hlens: *i64 = sys_mmap(8 * NSP_BATCH_MAX) as *i64 830 let nowts: i64 = sys_now_realtime_sec() 831 var ho: i64 = 0 832 var hk: i64 = 0 833 while hk < nin { 834 hoffs[hk] = ho 835 ho = ss_catn(hrows, ho, nowts) 836 hrows[ho] = NSP_TAB as u8; ho = ho + 1 837 ho = ss_cat(hrows, ho, actorm) 838 hrows[ho] = NSP_TAB as u8; ho = ho + 1 839 ho = ss_cat(hrows, ho, "putmany" as *u8) 840 hrows[ho] = NSP_TAB as u8; ho = ho + 1 841 ho = nsp_cat_slice(hrows, ho, recs, ida[hk], idb[hk]) 842 hrows[ho] = NSP_TAB as u8; ho = ho + 1 843 if oldb[hk] > olda[hk] { ho = nsp_cat_slice_pipe(hrows, ho, cur, olda[hk], oldb[hk]) } else { ho = ss_cat(hrows, ho, "-" as *u8) } 844 hrows[ho] = NSP_TAB as u8; ho = ho + 1 845 if nb[hk] > na[hk] { ho = nsp_cat_slice_pipe(hrows, ho, neu, na[hk], nb[hk]) } else { ho = ss_cat(hrows, ho, "-" as *u8) } 846 hlens[hk] = ho - hoffs[hk] 847 hk = hk + 1 848 } 849 if nsp_hist_commit_rows(hp2, hrows, hoffs, hlens, nin) < 0 { nsp_werr("putmany: hist batch commit failed (plane committed; audit trail missing this batch)\n" as *u8) } 850 nsp_report("PUTMANY" as *u8, actorm, nin, rows) 851 sys_exit(0) 852 return 0 853 } 854 855 nsp_werr("usage: nx_store_put <prefix> {put <actor> <id> <field>... | putmany <actor> <recordsfile> | setcol <actor> <id> <colidx> <value> | close <actor> <id> <note> | load | hist}\n" as *u8) 856 sys_exit(NSP_EXIT_USAGE) 857 return NSP_EXIT_USAGE 858}