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