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}