nx_seg_store.nx source
↩ module page · 5113 lines · 252513 B
1// nx_seg_store.nx -- PART A of the sovereign storage substrate rung 1
2// (knowledge/specs/2026-06-09-tutoring-storage-substrate-rung1.md).
3//
4// Append-only IMMUTABLE segment store, pure NishiLang, NO SQL. Model:
5// - a segment is written once (seg-<id>.docs) and never mutated
6// - new data => a NEW segment; deletes => tombstone entries (ADDITIVE law:
7// history is never destroyed, old versions stay readable)
8// - THE COMMIT POINT: segment temp file -> rename(2), then manifest temp ->
9// rename(2). Readers only see segments listed in manifest.txt, so a
10// half-written segment (crash before commit) is INVISIBLE by construction.
11// That is the crash-safety claim the gate proves by fault injection.
12//
13// Entry format inside .docs: u8 kind (1=put 2=tombstone) | u32be klen | key
14// | u32be vlen | value-bytes
15// Values are nx_canon_cid canonical record bytes (content-addressed upstream).
16// "Directory" = a path PREFIX string (e.g. "/tmp/im123-"), so no mkdir is
17// needed (sys_mkdir landmine avoided); a durable store uses a knowledge/ prefix.
18//
19// Rung-2 additions 2026-06-10: sys_fsync (probe-proven x86 74) on every file
20// write + directory fsync after the commit renames = the IM3 power-loss debt
21// CLOSED at the code level; per-segment sorted key index seg-<id>.keys with
22// binary-search reads (ss_get_idx), scan path kept as the ORACLE the gate
23// races it against. Honest remaining scope (flagged, NOT silent caps):
24// term-postings for text search (IM2 full: delta+bitpack), manifest cap 256
25// segments + 4096 keys/segment (compaction/merge = IM4).
26// license_tier: ORIGINAL
27
28import "nx_syscalls.nx"
29const SS_MAGIC_16777619: i64 = 16777619
30const SS_MAGIC_65536: i64 = 65536
31const SS_MAGIC_1048576: i64 = 1048576
32const SS_MAGIC_4096: i64 = 4096
33// MADV_WILLNEED, named for exactly ONE purpose -- the index-blob prefetch in ss_open2 -- so it can
34// never drift into doubling as some other advice value. The six existing WILLNEED sites one file away
35// in nx_docportal_search_seg pass a bare 3; this is that same constant given a name at the layer that
36// now depends on it.
37const SS_MADV_WILLNEED: i64 = 3
38const SS_MAGIC_1024: i64 = 1024
39const SS_MAGIC_1039: i64 = 1039
40const SS_MAGIC_1040: i64 = 1040
41const SS_MAGIC_1071: i64 = 1071
42const SS_MAGIC_1072: i64 = 1072
43const SS_MAGIC_1279: i64 = 1279
44const SS_MAGIC_12352: i64 = 12352
45const SS_MAGIC_12543: i64 = 12543
46const SS_MAGIC_3040: i64 = 3040
47const SS_MAGIC_13312: i64 = 13312
48const SS_MAGIC_19903: i64 = 19903
49const SS_MAGIC_3400: i64 = 3400
50const SS_MAGIC_19968: i64 = 19968
51const SS_MAGIC_40959: i64 = 40959
52const SS_MAGIC_44032: i64 = 44032
53const SS_MAGIC_55215: i64 = 55215
54const SS_MAGIC_63744: i64 = 63744
55const SS_MAGIC_64255: i64 = 64255
56const SS_MAGIC_1469598103934665603: i64 = 1469598103934665603
57const SS_MAGIC_1099511628211: i64 = 1099511628211
58
59func ss_len(s: *u8) -> i64 {
60 var n: i64 = 0
61 while s[n] != (0 as u8) { n = n + 1 }
62 return n
63}
64
65func ss_cat(dst: *u8, off: i64, s: *u8) -> i64 {
66 var i: i64 = 0
67 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
68 return off + i
69}
70
71// append decimal of v
72func ss_catn(dst: *u8, off: i64, v: i64) -> i64 {
73 var m: i64 = v
74 var o: i64 = off
75 if m < 0 { m = 0 - m; dst[o] = 45 as u8; o = o + 1 }
76 let t: *u8 = sys_mmap(28)
77 var k: i64 = 0
78 if m == 0 { t[0] = 48 as u8; k = 1 }
79 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
80 var i: i64 = 0
81 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 }
82 return o + k
83}
84
85func ss_w32(p: *u8, off: i64, v: i64) -> i64 {
86 p[off] = ((v >> 24) & 0xff) as u8
87 p[off + 1] = ((v >> 16) & 0xff) as u8
88 p[off + 2] = ((v >> 8) & 0xff) as u8
89 p[off + 3] = (v & 0xff) as u8
90 return off + 4
91}
92
93func ss_r32(p: *u8, off: i64) -> i64 {
94 let a: i64 = p[off]
95 let b: i64 = p[off + 1]
96 let c: i64 = p[off + 2]
97 let d: i64 = p[off + 3]
98 return (((((a << 8) | b) << 8) | c) << 8) | d
99}
100
101// read whole file into fresh mmap; szout[0] = size (-1 if absent)
102// SS_READALL_PAD: the slack ss_readall maps beyond the file size. It lives HERE, beside the map it pads, and
103// ss_freeall (below) re-derives the mapping from it -- a caller forced to know its allocator's padding is a
104// coupling that drifts (the sys_read_file / sys_free_file pair is the precedent, 2026-08-17).
105const SS_READALL_PAD: i64 = 64
106func ss_readall(path: *u8, szout: *i64) -> *u8 {
107 let fd: i64 = sys_openat_rd(path)
108 if fd < 0 { szout[0] = 0 - 1; return 0 as *u8 }
109 let sz: i64 = sys_lseek(fd, 0, 2)
110 sys_lseek(fd, 0, 0)
111 let buf: *u8 = sys_mmap(sz + SS_READALL_PAD)
112 var got: i64 = 0
113 var n: i64 = 1
114 while n > 0 {
115 n = sys_read(fd, (buf as i64 + got) as *u8, SS_MAGIC_65536)
116 if n > 0 { got = got + n }
117 }
118 sys_close(fd)
119 szout[0] = got
120 return buf
121}
122// PAIRED FREE FOR ss_readall (2026-08-19): pass the length ss_readall reported through szout; null-safe and
123// negative-safe (an absent file returns 0 / -1), so callers need no guard. Every per-request caller that reads an
124// index through ss_readall and never released it was the per-request mmap-leak class (nx_opaque_login, 177 GB).
125func ss_freeall(buf: *u8, len: i64) -> i64 {
126 if (buf as i64) == 0 { return 0 }
127 if len < 0 { return 0 }
128 return sys_munmap(buf, len + SS_READALL_PAD)
129}
130
131// SEGMENT LOADER (2026-07-23, the mmap-serve rung): read a segment file EITHER by reading it fully into
132// anon RAM (usemmap=0, the historical default -- every consumer keeps this) OR by a read-only FILE-BACKED
133// mmap (usemmap=1). Same (ptr, *szout) contract as ss_readall, so it is a drop-in swap. mmap mode means:
134// load time ~0 (nothing copied), only TOUCHED pages become resident (a query reads its hits, not the whole
135// 1.6GB shard), the mapping is SHARED across forked children (read-only, no COW) AND backed by the OS page
136// cache that survives daemon restarts. This is what turns the RAM-bound serve ceiling into a disk-bound one
137// (research: "memory-mapped immutable segments give fast read-heavy retrieval"). Segments are append-only
138// immutable and readers never mutate segment bytes, so PROT_READ is safe by construction.
139func ss_loadfile(path: *u8, szout: *i64, usemmap: i64) -> *u8 {
140 if usemmap == 1 {
141 let m: *u8 = sys_map_file(path, szout)
142 if (m as i64) != 0 { return m }
143 // mmap failed (missing/empty) -> szout already 0; fall through to a normal read so callers that
144 // treat "size 0" as absent behave identically to ss_readall's -1/empty on a missing file.
145 }
146 return ss_readall(path, szout)
147}
148
149const SS_ERR_DURABILITY: i64 = -10
150
151func ss_writefile(path: *u8, bytes: *u8, n: i64) -> i64 {
152 let fd: i64 = sys_openat_wr(path, 0x1a4)
153 if fd < 0 { return 0 - 1 }
154 var off: i64 = 0
155 while off < n {
156 let wr: i64 = sys_write(fd, (bytes as i64 + off) as *u8, n - off)
157 if wr <= 0 { sys_close(fd); return 0 - 2 }
158 off = off + wr
159 }
160 let sync_rc: i64 = sys_fsync(fd)
161 let close_rc: i64 = sys_close(fd)
162 if sync_rc != 0 || close_rc != 0 { return SS_ERR_DURABILITY }
163 return 0
164}
165
166// fsync the directory holding the store files so the rename(2) commit point
167// itself reaches stable storage (power-loss closure of the IM3 debt; without
168// this only process-crash safety is proven). prefix = ".../name-" => dir is
169// everything up to the last "/"; no slash => current directory.
170func ss_syncdir(prefix: *u8) -> i64 {
171 let d: *u8 = sys_mmap(512)
172 var last: i64 = 0 - 1
173 var i: i64 = 0
174 while prefix[i] != (0 as u8) {
175 if prefix[i] == (47 as u8) { last = i }
176 i = i + 1
177 }
178 if last < 0 {
179 d[0] = 46 as u8
180 d[1] = 0 as u8
181 }
182 if last >= 0 {
183 var t: i64 = 0
184 while t <= last { d[t] = prefix[t]; t = t + 1 }
185 d[t] = 0 as u8
186 }
187 let fd: i64 = sys_openat_rd(d)
188 if fd < 0 { return 0 - 1 }
189 let rc: i64 = sys_fsync(fd)
190 sys_close(fd)
191 return rc
192}
193
194// segment writer state: w[0]=buf ptr, w[1]=len, w[2]=cap (heap-mmap idiom, no stack structs)
195// writer with a caller-sized buffer (compaction sizes it from the merged total -- no fixed cap).
196// ---- SCRATCH RELEASE LIST (2026-08-18, lane F box health) ---------------------------------------
197// The commit path (ss_build_keys -> ss_build_terms -> ss_write_seg) maps ~55x the writer size in
198// index scratch PER COMMIT and never released a byte of it: MEASURED on nx_web_crawl_step as 264-456
199// MB/min of address growth (nx_leak_check fleet, full population), and paid identically by every
200// seg-store writer in the estate. Every one of those mappings is consumed IN-CALL -- the builders emit
201// into caller-owned blobs (kout / tblob / pblob / qblob) or straight to disk -- so releasing them at
202// function exit is sound by construction.
203// SHAPE, chosen so it cannot drift: each builder owns a small release list; ss_sr_map records each
204// mapping AS IT IS MADE (pointer + the exact length passed to sys_mmap), and ONE ss_sr_release call on
205// EVERY exit path -- early returns included -- frees the lot. A future allocation routed through
206// ss_sr_map is freed automatically; nothing has to mirror sizes across eight return sites by hand.
207// Sub-256 B requests are arena-class where sys_munmap is a documented no-op; they pass through
208// unchanged (recorded, released as no-ops), so the list needs no size branch.
209// The list itself is arena-class (SS_SR_SLOTS x 16 B) -- unfreed by design, ~0.5 KB per commit,
210// which the arena chunk amortises; naming it here is the whole cost.
211const SS_SR_SLOTS: i64 = 40 // max mappings one builder records (build_terms makes 25; head-room named)
212const SS_SR_STRIDE: i64 = 2 // (ptr, len) per slot
213func ss_sr_new() -> *i64 { let l: *i64 = sys_mmap(8 * (1 + SS_SR_SLOTS * SS_SR_STRIDE)) as *i64; l[0] = 0; return l }
214func ss_sr_map(l: *i64, n: i64) -> *u8 {
215 let p: *u8 = sys_mmap(n)
216 let k: i64 = l[0]
217 if k < SS_SR_SLOTS { l[1 + k * SS_SR_STRIDE] = p as i64; l[2 + k * SS_SR_STRIDE] = n; l[0] = k + 1 }
218 else {
219 // FULL LIST ANNOUNCES rather than silently leaking the overflow: a cap reached in silence
220 // becomes a measurement nobody knows is partial.
221 sys_write(2, "SS-SR-LIST-FULL: a builder mapped more scratch than SS_SR_SLOTS records; the overflow is NOT released -- raise SS_SR_SLOTS\n" as *u8, 129)
222 }
223 return p
224}
225func ss_sr_release(l: *i64) -> i64 {
226 var i: i64 = 0
227 let k: i64 = l[0]
228 while i < k { sys_munmap(l[1 + i * SS_SR_STRIDE] as *u8, l[2 + i * SS_SR_STRIDE]); i = i + 1 }
229 l[0] = 0
230 return k
231}
232
233func ss_begin_cap(cap: i64) -> *i64 {
234 let w: *i64 = sys_mmap(32) as *i64
235 w[0] = sys_mmap(cap) as i64
236 w[1] = 0
237 w[2] = cap
238 return w
239}
240// default single-write buffer (a document is ~KB; ss_add2 fail-closes if exceeded, never truncates).
241func ss_begin() -> *i64 {
242 return ss_begin_cap(SS_MAGIC_1048576)
243}
244
245func ss_add(w: *i64, kind: i64, key: *u8, val: *u8, vlen: i64) -> i64 {
246 return ss_add2(w, kind, key, ss_len(key), val, vlen)
247}
248
249// explicit key length (compaction feeds keys straight from .docs bytes, not null-terminated)
250func ss_add2(w: *i64, kind: i64, key: *u8, kl: i64, val: *u8, vlen: i64) -> i64 {
251 let buf: *u8 = w[0] as *u8
252 var o: i64 = w[1]
253 if o + kl + vlen + 16 > w[2] { return 0 - 1 }
254 buf[o] = kind as u8
255 o = o + 1
256 o = ss_w32(buf, o, kl)
257 var t: i64 = 0
258 while t < kl { buf[o] = key[t]; o = o + 1; t = t + 1 }
259 o = ss_w32(buf, o, vlen)
260 t = 0
261 while t < vlen { buf[o] = val[t]; o = o + 1; t = t + 1 }
262 w[1] = o
263 return 0
264}
265
266// build "<prefix>seg-<id>.docs" (+ ".tmp" if tmp==1)
267func ss_segname(prefix: *u8, segid: i64, tmp: i64, out: *u8) -> i64 {
268 var o: i64 = 0
269 o = ss_cat(out, o, prefix)
270 o = ss_cat(out, o, "seg-" as *u8)
271 o = ss_catn(out, o, segid)
272 o = ss_cat(out, o, ".docs" as *u8)
273 if tmp == 1 { o = ss_cat(out, o, ".tmp" as *u8) }
274 out[o] = 0 as u8
275 return o
276}
277
278// length-aware byte-lex compare: <0, 0, >0 (shorter strict-prefix sorts first)
279// Key hash for the compactor's dedup INDEX (2026-08-06). Uses the same 131 multiplier as
280// sts_pfxhash so the estate carries ONE hashing idiom rather than two. Masked to stay positive;
281// wrap-around in the multiply is fine and intended for a hash.
282// THIS IS AN INDEX ONLY -- it never decides equality. Every probe still confirms with ss_kcmp, so a
283// collision costs one extra comparison and can never merge two distinct keys. A hash that is trusted
284// as an identity is a data-loss bug; a hash that is trusted as a HINT is just a speedup.
285func ss_khash(k: *u8, kl: i64) -> i64 {
286 var h: i64 = 2166136261
287 var i: i64 = 0
288 while i < kl {
289 h = h * 131 + (k[i] as i64)
290 h = h & 0x3FFFFFFFFFFFFFFF
291 i = i + 1
292 }
293 return h
294}
295func ss_kcmp(a: *u8, al: i64, b: *u8, bl: i64) -> i64 {
296 var n: i64 = al
297 if bl < n { n = bl }
298 var i: i64 = 0
299 while i < n {
300 let ca: i64 = a[i]
301 let cb: i64 = b[i]
302 if ca != cb { return ca - cb }
303 i = i + 1
304 }
305 return al - bl
306}
307
308// Build the sorted key index blob for a writer buffer (IM2 rung). Within a
309// segment the LAST entry for a key wins (identical to scan semantics), so
310// duplicates are dropped keeping the later one. Layout:
311// "NXK1" | u32be N | N x u32be entry-rel-offset | entries:
312// u8 kind | u32be klen | key | u32be voff(into .docs) | u32be vlen
313// Returns index byte length written into kout.
314// ONE derivation of the key-blob capacity for a writer, shared by ss_commit_body and the store gate: every emitted
315// index entry is smaller than three times its record, plus a page of headroom.
316func ss_keyblob_cap(w: *i64) -> i64 { return w[1] * 3 + SS_MAGIC_65536 }
317// ss_build_keys refusal codes, NAMED so the store gate plants and asserts them by name rather than by a second copy
318const SS_KEYS_REFUSE_KEY: i64 = 0 - 2 // a record's key length runs past the writer buffer
319const SS_KEYS_REFUSE_VALUE: i64 = 0 - 3 // a record's value length runs past the writer buffer
320func ss_build_keys(w: *i64, kout: *u8) -> i64 {
321 let buf: *u8 = w[0] as *u8
322 let blen: i64 = w[1]
323 // capacity DATA-DRIVEN from the writer size (every record >= 9 bytes)
324 let maxn: i64 = blen / 9 + 16
325 let srl: *i64 = ss_sr_new()
326 let kls: *i64 = ss_sr_map(srl, 8 * maxn) as *i64
327 let kps: *i64 = ss_sr_map(srl, 8 * maxn) as *i64
328 let vos: *i64 = ss_sr_map(srl, 8 * maxn) as *i64
329 let vls: *i64 = ss_sr_map(srl, 8 * maxn) as *i64
330 let kinds: *i64 = ss_sr_map(srl, 8 * maxn) as *i64
331 var n: i64 = 0
332 var i: i64 = 0
333 while i + 9 <= blen {
334 let kind: i64 = buf[i]
335 let kl: i64 = ss_r32(buf, i + 1)
336 let koff: i64 = i + 5
337 // A MALFORMED RECORD IS REFUSED, NEVER WALKED: a key or value length that runs past the writer buffer would
338 // read garbage as the next header and the emit below would then touch memory outside the buffer (measured
339 // 2026-09-14 as a SIGSEGV at the key-emit loop during an estate ingest). -2 = key past the buffer,
340 // -3 = value past the buffer; ss_commit_body already turns any negative into its commit refusal.
341 if kl < 0 { ss_sr_release(srl); return SS_KEYS_REFUSE_KEY }
342 if koff + kl + 4 > blen { ss_sr_release(srl); return SS_KEYS_REFUSE_KEY }
343 let vl: i64 = ss_r32(buf, koff + kl)
344 let voff: i64 = koff + kl + 4
345 if vl < 0 { ss_sr_release(srl); return SS_KEYS_REFUSE_VALUE }
346 if voff + vl > blen { ss_sr_release(srl); return SS_KEYS_REFUSE_VALUE }
347 if n >= maxn { ss_sr_release(srl); return 0 - 1 }
348 kinds[n] = kind
349 kls[n] = kl
350 kps[n] = (buf as i64) + koff
351 vos[n] = voff
352 vls[n] = vl
353 n = n + 1
354 i = voff + vl
355 }
356 // sort by (key bytes, original index) -- bottom-up merge, stable; equal
357 // keys end up chronological, so the LAST of a run is the shadowing entry
358 let sidx: *i64 = ss_sr_map(srl, 8 * (n + 16)) as *i64
359 let stmp: *i64 = ss_sr_map(srl, 8 * (n + 16)) as *i64
360 i = 0
361 while i < n { sidx[i] = i; i = i + 1 }
362 // RESEARCH-DRIVEN FAST-PATH (Knuth TAOCP vol.3 5.2.5): when keys are UNIFORM length, a stable LSD radix
363 // sort is O(width*n) and produces a result BIT-IDENTICAL to the stable merge sort (a stable sort is
364 // unique). Variable-length keys fall through to the merge sort. Proven 5.1x on the index in nx_radix_index_bench.
365 var ss_uniform: i64 = 1
366 let ss_ul: i64 = kls[0]
367 i = 1
368 while i < n { if kls[i] != ss_ul { ss_uniform = 0 } i = i + 1 }
369 var ss_radixed: i64 = 0
370 if ss_uniform == 1 { if n > 64 { if ss_ul > 0 {
371 let rcnt: *i64 = ss_sr_map(srl, 256 * 8) as *i64
372 var pos: i64 = ss_ul - 1
373 while pos >= 0 {
374 var c: i64 = 0
375 while c < 256 { rcnt[c] = 0; c = c + 1 }
376 i = 0
377 while i < n { let kp: *u8 = kps[sidx[i]] as *u8; let kb: i64 = kp[pos] as i64; rcnt[kb] = rcnt[kb] + 1; i = i + 1 }
378 var sm: i64 = 0
379 c = 0
380 while c < 256 { let tc: i64 = rcnt[c]; rcnt[c] = sm; sm = sm + tc; c = c + 1 }
381 i = 0
382 while i < n { let kp: *u8 = kps[sidx[i]] as *u8; let kb: i64 = kp[pos] as i64; stmp[rcnt[kb]] = sidx[i]; rcnt[kb] = rcnt[kb] + 1; i = i + 1 }
383 i = 0
384 while i < n { sidx[i] = stmp[i]; i = i + 1 }
385 pos = pos - 1
386 }
387 ss_radixed = 1
388 } } }
389 if ss_radixed == 0 {
390 var width: i64 = 1
391 while width < n {
392 var lo: i64 = 0
393 while lo < n {
394 var mid: i64 = lo + width
395 if mid > n { mid = n }
396 var hi: i64 = lo + 2 * width
397 if hi > n { hi = n }
398 var a2: i64 = lo
399 var b2: i64 = mid
400 var o2: i64 = lo
401 while o2 < hi {
402 var takea: i64 = 0
403 if a2 < mid {
404 if b2 >= hi { takea = 1 } else {
405 if ss_kcmp(kps[sidx[a2]] as *u8, kls[sidx[a2]], kps[sidx[b2]] as *u8, kls[sidx[b2]]) <= 0 { takea = 1 }
406 }
407 }
408 if takea == 1 { stmp[o2] = sidx[a2]; a2 = a2 + 1 } else { stmp[o2] = sidx[b2]; b2 = b2 + 1 }
409 o2 = o2 + 1
410 }
411 o2 = lo
412 while o2 < hi { sidx[o2] = stmp[o2]; o2 = o2 + 1 }
413 lo = lo + 2 * width
414 }
415 width = width * 2
416 }
417 }
418 // collapse equal-key runs to their LAST (latest) entry
419 let idx: *i64 = ss_sr_map(srl, 8 * (n + 16)) as *i64
420 var m: i64 = 0
421 i = 0
422 while i < n {
423 var j: i64 = i + 1
424 var run: i64 = 1
425 while run == 1 {
426 if j >= n { run = 0 }
427 if run == 1 {
428 if ss_kcmp(kps[sidx[j]] as *u8, kls[sidx[j]], kps[sidx[i]] as *u8, kls[sidx[i]]) == 0 { j = j + 1 } else { run = 0 }
429 }
430 }
431 idx[m] = sidx[j - 1]
432 m = m + 1
433 i = j
434 }
435 // emit
436 kout[0] = 78 as u8 // N
437 kout[1] = 88 as u8 // X
438 kout[2] = 75 as u8 // K
439 kout[3] = 49 as u8 // 1
440 ss_w32(kout, 4, m)
441 let tbl: i64 = 8
442 let base: i64 = 8 + 4 * m
443 var o: i64 = base
444 i = 0
445 while i < m {
446 let e: i64 = idx[i]
447 ss_w32(kout, tbl + 4 * i, o - base)
448 kout[o] = kinds[e] as u8
449 o = o + 1
450 o = ss_w32(kout, o, kls[e])
451 let kp: *u8 = kps[e] as *u8
452 var t: i64 = 0
453 while t < kls[e] { kout[o] = kp[t]; o = o + 1; t = t + 1 }
454 o = ss_w32(kout, o, vos[e])
455 o = ss_w32(kout, o, vls[e])
456 i = i + 1
457 }
458 ss_sr_release(srl) // kout (caller-owned) holds the result; every scratch mapping above is dead here
459 return o
460}
461
462// FAULT INJECTION: write ONLY the temp file = death before the commit point.
463// The gate uses this to prove a half-written segment is invisible.
464func ss_crashwrite(prefix: *u8, w: *i64, segid: i64) -> i64 {
465 let p: *u8 = sys_mmap(512)
466 ss_segname(prefix, segid, 1, p)
467 return ss_writefile(p, w[0] as *u8, w[1])
468}
469
470// ---- IM2b: term postings (text search on the store) ----
471// Tokenizer: lowercased alphanumeric runs, length 2..32 (longer runs
472// truncated at 32). IDENTICAL function serves index build, query, and the
473// gate's brute-force oracle -- consistency by construction.
474// .terms layout: "NXT1" | u32be nterms | nterms x u32be entry-off | entries:
475// u32be tlen | term | u32be postoff | u32be postlen | u32be dcount
476// .post layout: "NXP1" | u32be ndocs | ndocs x u32be docs-entry-offset |
477// per-term varint-delta docid streams (ascending; <=128 docs
478// per segment block = exactly the spec's varint tail block;
479// bitpacked 128-blocks = flagged optimization rung)
480
481// varint append (LE 7-bit, high bit = continue); returns new offset
482func ss_vw(p: *u8, off: i64, v: i64) -> i64 {
483 var m: i64 = v
484 var o: i64 = off
485 var go: i64 = 1
486 while go == 1 {
487 let b7: i64 = m % 128
488 m = m / 128
489 if m > 0 { p[o] = (b7 + 128) as u8 } else { p[o] = b7 as u8; go = 0 }
490 o = o + 1
491 }
492 return o
493}
494
495// varint read at pos[0]; advances pos
496func ss_vr(p: *u8, pos: *i64) -> i64 {
497 var v: i64 = 0
498 var sh: i64 = 0
499 var go: i64 = 1
500 while go == 1 {
501 let b: i64 = p[pos[0]]
502 pos[0] = pos[0] + 1
503 v = v + ((b % 128) << sh)
504 sh = sh + 7
505 if b < 128 { go = 0 }
506 }
507 return v
508}
509
510// 256-entry token class table: tbl[c] = 0 for separators, else the
511// lowercased byte. THE one tokenizer definition -- build, query and oracle
512// all tokenize through this table (semantic consistency by construction).
513func ss_tok_table(tbl: *u8) -> i64 {
514 var c: i64 = 0
515 while c < 256 {
516 var v: i64 = 0
517 if c >= 97 { if c <= 122 { v = c } }
518 if c >= 65 { if c <= 90 { v = c + 32 } }
519 if c >= 48 { if c <= 57 { v = c } }
520 tbl[c] = v as u8
521 c = c + 1
522 }
523 return 0
524}
525
526// IDENTIFIER ANCHORS (search rung E1, 2026-09-14). The one tokenizer above keeps letters and digits and treats every
527// other byte -- the underscore included -- as a separator, so `sx_agent_decide` indexes as three common words and the
528// file that DEFINES it ranks behind any page that says agent and decide. For a source corpus the identifier IS the
529// anchor a reader searches by. ss_ident_anchors appends, for every run of identifier bytes that carries at least one
530// underscore, its COLLAPSED form (underscores removed, so the tokenizer keeps it whole), space separated; the query
531// side collapses a query term through ss_ident_collapse -- ONE derivation on both sides, so they cannot drift.
532// Returns bytes written and stops when out is full; the caller sizes out from its input (an anchor is never longer
533// than the run it came from) so a full buffer is a caller defect, not a silent truncation.
534const SS_LOWER_A: i64 = 97
535const SS_LOWER_Z: i64 = 122
536const SS_UPPER_A: i64 = 65
537const SS_UPPER_Z: i64 = 90
538const SS_DIG_0: i64 = 48
539const SS_DIG_9: i64 = 57
540const SS_UNDERSCORE: i64 = 95
541const SS_SP: i64 = 32
542const SS_IDENT_MIN: i64 = 2 // a collapsed anchor shorter than this is noise (an underscore beside one letter)
543func ss_is_ident_byte(c: i64) -> i64 {
544 if c >= SS_LOWER_A { if c <= SS_LOWER_Z { return 1 } }
545 if c >= SS_UPPER_A { if c <= SS_UPPER_Z { return 1 } }
546 if c >= SS_DIG_0 { if c <= SS_DIG_9 { return 1 } }
547 if c == SS_UNDERSCORE { return 1 }
548 return 0
549}
550func ss_ident_anchors(src: *u8, n: i64, out: *u8, cap: i64) -> i64 {
551 var o: i64 = 0
552 var i: i64 = 0
553 while i < n {
554 if ss_is_ident_byte(src[i] as i64) == 0 { i = i + 1 } else {
555 var e: i64 = i
556 var us: i64 = 0
557 var go: i64 = 1
558 while go == 1 { if e >= n { go = 0 } else { let c: i64 = src[e] as i64; if ss_is_ident_byte(c) == 1 { if c == SS_UNDERSCORE { us = us + 1 } e = e + 1 } else { go = 0 } } }
559 if us > 0 {
560 let len: i64 = e - i - us
561 if len >= SS_IDENT_MIN { if o + len + 1 < cap {
562 var k: i64 = i
563 while k < e { let c2: i64 = src[k] as i64; if c2 != SS_UNDERSCORE { out[o] = c2 as u8; o = o + 1 } k = k + 1 }
564 out[o] = SS_SP as u8; o = o + 1
565 } }
566 }
567 i = e
568 }
569 }
570 return o
571}
572// the query-side twin: collapse ONE NUL-terminated term when it carries an underscore. Returns the collapsed length
573// written to out (NUL-terminated), 0 when the term has no underscore -- the caller then adds nothing.
574func ss_ident_collapse(term: *u8, out: *u8, cap: i64) -> i64 {
575 var us: i64 = 0
576 var i: i64 = 0
577 while term[i] != (0 as u8) { if (term[i] as i64) == SS_UNDERSCORE { us = us + 1 } i = i + 1 }
578 if us == 0 { return 0 }
579 var o: i64 = 0
580 i = 0
581 while term[i] != (0 as u8) { if (term[i] as i64) != SS_UNDERSCORE { if o < cap - 1 { out[o] = term[i]; o = o + 1 } } i = i + 1 }
582 out[o] = 0 as u8
583 if o < SS_IDENT_MIN { return 0 }
584 return o
585}
586
587// E1b DEFINITION TOKEN (2026-09-14): the def prefix glued to the collapsed name -- the term a source file carries
588// only when it DEFINES the symbol (the ingest emits one per function head, the query asks for one per typed term).
589// ONE derivation for both sides: a prefix spelled in two organs would agree the day it was written and drift on
590// the next edit. Returns the token length written to out (NUL-terminated).
591const SS_DEF_PREFIX: *u8 = "def"
592func ss_def_token(name: *u8, nlen: i64, out: *u8, cap: i64) -> i64 {
593 let pfx: *u8 = SS_DEF_PREFIX
594 var o: i64 = 0
595 var p: i64 = 0
596 while pfx[p] != (0 as u8) { if o < cap - 1 { out[o] = pfx[p]; o = o + 1 } p = p + 1 }
597 var j: i64 = 0
598 while j < nlen { let c: i64 = name[j] as i64; if c != SS_UNDERSCORE { if o < cap - 1 { out[o] = c as u8; o = o + 1 } } j = j + 1 }
599 out[o] = 0 as u8
600 return o
601}
602
603// ACCENT FOLD (2026-07-03, the Unicode rung): fold a decoded codepoint to its ASCII base letter --
604// Latin-1 Supplement (U+00C0-U+00FF) + Latin Extended-A (U+0100-U+017F) via 64/128-entry base-letter
605// tables ('.' = stays a separator: multiplication/division signs). "KyÃ…ÂÂka" tokenizes as "kyoka" on BOTH
606// the index and query sides (same ss_tok_next2), so accented pages match plain-ASCII queries; compaction
607// re-tokenizes shards through ss_build_terms = the live upgrade path. Non-Latin (CJK etc.) folds to 0 =
608// separator -- CJK SEGMENTATION is a separate named rung, honestly not claimed here.
609func ss_fold_cp(cp: i64) -> i64 {
610 if cp >= 192 { if cp <= 255 {
611 let t: *u8 = "aaaaaaaceeeeiiiidnooooo.ouuuuytsaaaaaaaceeeeiiiidnooooo.ouuuuyty" as *u8
612 let ch: i64 = t[cp - 192] as i64
613 if ch == 46 { return 0 }
614 return ch
615 } }
616 if cp >= 256 { if cp <= 383 {
617 let t2: *u8 = "aaaaaaccccccccddddeeeeeeeeeegggggggghhhhiiiiiiiiiiiijjkkkllllllllllnnnnnnnnnoooooooorrrrrrssssssssttttttuuuuuuuuuuuuwwyyyzzzzzzs" as *u8
618 return t2[cp - 256] as i64
619 } }
620 return 0
621}
622
623// MULTILINGUAL rung (2026-07-23, operator: index ru/eu/zh/ja/ko not just English):
624// decode ONE multibyte UTF-8 char at i -> packed (cp<<3)|adv; cp=0 when not a valid multibyte lead
625// (caller's ASCII fast path owns bytes <194). No allocations -- lives under the tf-scan hot loop.
626func ss_u8cp(b: *u8, sz: i64, i: i64) -> i64 {
627 if i >= sz { return 1 }
628 let c0: i64 = b[i] as i64
629 if c0 < 194 { return 1 }
630 if c0 < 224 {
631 if i + 1 < sz { let c1: i64 = b[i+1] as i64; if c1 >= 128 { if c1 < 192 {
632 return ((c0 - 192) * 64 + (c1 - 128)) * 8 + 2
633 } } }
634 return 1
635 }
636 if c0 < 240 {
637 if i + 2 < sz {
638 let c1: i64 = b[i+1] as i64
639 let c2: i64 = b[i+2] as i64
640 if c1 >= 128 { if c1 < 192 { if c2 >= 128 { if c2 < 192 {
641 return ((c0 - 224) * SS_MAGIC_4096 + (c1 - 128) * 64 + (c2 - 128)) * 8 + 3
642 } } } }
643 }
644 return 1
645 }
646 if i + 3 < sz { return 4 }
647 return 1
648}
649// case-fold a Cyrillic/Greek codepoint to its lowercase WORD form (kept as UTF-8 in the token,
650// unlike ss_fold_cp which folds TO ASCII). 0 = not a word char in these scripts.
651func ss_fold_word_cp(cp: i64) -> i64 {
652 if cp >= SS_MAGIC_1024 { if cp <= SS_MAGIC_1039 { return cp + 80 } } // U+0400-040F upper -> U+0450-045F
653 if cp >= SS_MAGIC_1040 { if cp <= SS_MAGIC_1071 { return cp + 32 } } // ÃÂÂÂ-ï -> ð-ÑÂÂ
654 if cp >= SS_MAGIC_1072 { if cp <= SS_MAGIC_1279 { return cp } } // ð-Ѡ+ Cyrillic ext: identity
655 if cp >= 913 { if cp <= 937 { if cp != 930 { return cp + 32 } } } // Greek Α-Ω -> α-É (03A2 hole)
656 if cp == 962 { return 963 } // final sigma -> sigma
657 if cp >= 945 { if cp <= 969 { return cp } } // α-É identity
658 return 0
659}
660// CJK char class for BIGRAM tokenization (no-space scripts + Hangul): Han + Ext-A + compat, kana, Hangul
661// syllables. Standard CJK IR practice: overlapping character bigrams, symmetric index+query.
662func ss_is_cjk(cp: i64) -> i64 {
663 if cp >= SS_MAGIC_12352 { if cp <= SS_MAGIC_12543 { return 1 } } // Hiragana U+SS_MAGIC_3040-309F + Katakana U+30A0-30FF
664 if cp >= SS_MAGIC_13312 { if cp <= SS_MAGIC_19903 { return 1 } } // CJK Ext-A U+SS_MAGIC_3400-4DBF
665 if cp >= SS_MAGIC_19968 { if cp <= SS_MAGIC_40959 { return 1 } } // CJK Unified U+4E00-9FFF
666 if cp >= SS_MAGIC_44032 { if cp <= SS_MAGIC_55215 { return 1 } } // Hangul syllables U+AC00-D7AF
667 if cp >= SS_MAGIC_63744 { if cp <= SS_MAGIC_64255 { return 1 } } // CJK Compat U+F900-FAFF
668 return 0
669}
670
671// next token from b[pos[0]..sz): lowercased alnum run len>=2 (cap 32) into
672// tout (null-terminated); returns token length, or -1 when exhausted.
673// UTF-8 aware: Latin accents FOLD to ASCII (ss_fold_cp); Cyrillic/Greek case-fold and stay UTF-8
674// word chars (ss_fold_word_cp); CJK/kana/Hangul emit overlapping character BIGRAMS (isolated char =
675// unigram); everything else separates, whole sequences consumed so continuations never mangle a token.
676func ss_tok_next2(b: *u8, sz: i64, pos: *i64, tout: *u8, tbl: *u8) -> i64 {
677 var i: i64 = pos[0]
678 var l: i64 = 0
679 while i < sz {
680 // ASCII FAST PATH FIRST (one byte load, one compare added vs the pre-fold loop -- the tf scan
681 // over 32KB x 128 candidates lives here; the reversed branch order cost p95 169->211ms)
682 let c0: i64 = b[i] as i64
683 if c0 < 194 {
684 let m: i64 = tbl[c0]
685 if m != 0 {
686 if l < 32 { tout[l] = m as u8; l = l + 1 }
687 }
688 if m == 0 {
689 if l >= 2 { pos[0] = i + 1; tout[l] = 0 as u8; return l }
690 l = 0
691 }
692 i = i + 1
693 } else {
694 // UTF-8 lead byte (0xC2..0xF4): decode + classify
695 let v1: i64 = ss_u8cp(b, sz, i)
696 let cp: i64 = v1 >> 3
697 let adv: i64 = v1 & 7
698 // 1) Latin accent fold -> ASCII base letter continues the word
699 var f: i64 = 0
700 if cp > 0 { f = ss_fold_cp(cp) }
701 if f != 0 {
702 if l < 32 { tout[l] = f as u8; l = l + 1 }
703 i = i + adv
704 } else {
705 // 2) Cyrillic/Greek word char: case-fold, keep as UTF-8 (2 bytes) in the token
706 var wcp: i64 = 0
707 if cp > 0 { wcp = ss_fold_word_cp(cp) }
708 if wcp != 0 {
709 if l + 2 <= 32 {
710 tout[l] = (192 + wcp / 64) as u8
711 tout[l + 1] = (128 + (wcp % 64)) as u8
712 l = l + 2
713 }
714 i = i + adv
715 } else {
716 // 3) CJK/kana/Hangul: overlapping bigrams (run>=2) / unigram (isolated char)
717 var cjk: i64 = 0
718 if cp > 0 { cjk = ss_is_cjk(cp) }
719 if cjk == 1 {
720 if l >= 2 { pos[0] = i; tout[l] = 0 as u8; return l } // flush word; re-read this char next call
721 l = 0
722 let j: i64 = i + adv
723 let v2: i64 = ss_u8cp(b, sz, j)
724 let cp2: i64 = v2 >> 3
725 let adv2: i64 = v2 & 7
726 var cjk2: i64 = 0
727 if cp2 > 0 { cjk2 = ss_is_cjk(cp2) }
728 if cjk2 == 1 {
729 var x: i64 = 0
730 while x < adv { tout[x] = b[i + x]; x = x + 1 }
731 var y: i64 = 0
732 while y < adv2 { tout[adv + y] = b[j + y]; y = y + 1 }
733 tout[adv + adv2] = 0 as u8
734 // resume AT the second char while the run continues (overlap); past it at run end
735 let k: i64 = j + adv2
736 let v3: i64 = ss_u8cp(b, sz, k)
737 let cp3: i64 = v3 >> 3
738 var cjk3: i64 = 0
739 if cp3 > 0 { cjk3 = ss_is_cjk(cp3) }
740 if cjk3 == 1 { pos[0] = j } else { pos[0] = k }
741 return adv + adv2
742 }
743 var x2: i64 = 0
744 while x2 < adv { tout[x2] = b[i + x2]; x2 = x2 + 1 }
745 tout[adv] = 0 as u8
746 pos[0] = i + adv
747 return adv
748 }
749 // 4) separator (symbols, unclaimed scripts, stray sequences)
750 if l >= 2 { pos[0] = i + adv; tout[l] = 0 as u8; return l }
751 l = 0
752 i = i + adv
753 }
754 }
755 }
756 }
757 pos[0] = sz
758 if l >= 2 { tout[l] = 0 as u8; return l }
759 return 0 - 1
760}
761
762// compat wrapper (one-shot callers; hot loops build the table once instead)
763func ss_tok_next(b: *u8, sz: i64, pos: *i64, tout: *u8) -> i64 {
764 let tbl: *u8 = sys_mmap(272)
765 ss_tok_table(tbl)
766 return ss_tok_next2(b, sz, pos, tout, tbl)
767}
768
769// does the value contain token `term`? (the gate's brute-force oracle)
770func ss_tok_has(b: *u8, sz: i64, term: *u8) -> i64 {
771 let pos: *i64 = sys_mmap(16) as *i64
772 pos[0] = 0
773 let t: *u8 = sys_mmap(40)
774 let tbl: *u8 = sys_mmap(272)
775 ss_tok_table(tbl)
776 var go: i64 = 1
777 while go == 1 {
778 let l: i64 = ss_tok_next2(b, sz, pos, t, tbl)
779 if l < 0 { go = 0 }
780 if go == 1 {
781 var eq: i64 = 1
782 var x: i64 = 0
783 while eq == 1 {
784 if t[x] != term[x] { eq = 0 }
785 if eq == 1 { if t[x] == (0 as u8) { return 1 } }
786 x = x + 1
787 }
788 }
789 }
790 return 0
791}
792
793// 64-bit FNV-1a over a null-terminated token
794func ss_fnv(s: *u8) -> i64 {
795 var h: i64 = SS_MAGIC_1469598103934665603
796 var i: i64 = 0
797 while s[i] != (0 as u8) {
798 h = h ^ (s[i] as i64)
799 h = h * SS_MAGIC_1099511628211
800 i = i + 1
801 }
802 if h < 0 { h = 0 - h }
803 return h
804}
805
806// Build .terms (tblob) + .post (pblob) for a writer buffer; lengths into
807// louts[0]/louts[1]. Capacities are DATA-DRIVEN, derived from the writer
808// size (every record >= 9 bytes => docs <= blen/9; every counted (term,doc)
809// pair consumes >= 3 source bytes => pairs and unique terms <= blen/3) --
810// no fixed caps to silently or loudly hit; the construction bounds are
811// defensive-checked LOUD (-1) anyway. Term lookup = open-addressing FNV
812// hash (O(1) per token); postings emit = counting buckets (O(pairs));
813// term dict ordering = bottom-up merge sort (O(n log n)).
814// 2026-07-03 PHRASE RUNG (additive): qblob receives the POSITIONS sidecar ("NXQ1" | u32 nterms |
815// u32 entryoff[nterms] | per-term entries), louts[2] = its length. An entry holds, for each posting doc
816// IN THE SAME (sorted-term, ascending-doc) ORDER as .post: varint(npos) + delta-varint token indexes.
817// Positions are indexes among EMITTED tokens (the same space the query tokenizer sees; 1-char words are
818// invisible to both sides, so they never break adjacency -- documented semantics, not an accident).
819func ss_build_terms(w: *i64, tblob: *u8, pblob: *u8, qblob: *u8, louts: *i64) -> i64 {
820 let buf: *u8 = w[0] as *u8
821 let blen: i64 = w[1]
822 let maxdocs: i64 = blen / 9 + 16
823 let maxterms: i64 = blen / 3 + 64
824 let maxpairs: i64 = blen / 3 + 64
825 let maxocc: i64 = blen / 2 + 64 // every emitted token consumes >= 2 bytes of source
826 let srl: *i64 = ss_sr_new()
827 let docoff: *i64 = ss_sr_map(srl, 8 * maxdocs) as *i64
828 let pool: *u8 = ss_sr_map(srl, blen + SS_MAGIC_65536)
829 var pooloff: i64 = 0
830 let terms: *i64 = ss_sr_map(srl, 8 * maxterms) as *i64
831 var nterms: i64 = 0
832 let pterm: *i64 = ss_sr_map(srl, 8 * maxpairs) as *i64
833 let pdoc: *i64 = ss_sr_map(srl, 8 * maxpairs) as *i64
834 var npairs: i64 = 0
835 let oterm: *i64 = ss_sr_map(srl, 8 * maxocc) as *i64
836 let odoc: *i64 = ss_sr_map(srl, 8 * maxocc) as *i64
837 let opos: *i64 = ss_sr_map(srl, 8 * maxocc) as *i64
838 var nocc: i64 = 0
839 var hts: i64 = 16
840 while hts < maxterms * 2 { hts = hts * 2 }
841 let ht: *i64 = ss_sr_map(srl, 8 * hts) as *i64
842 // per-term last-doc-seen (stores nd+1; 0 = never) -- O(1) per-doc dedupe
843 let lastdoc: *i64 = ss_sr_map(srl, 8 * maxterms) as *i64
844 let tlens: *i64 = ss_sr_map(srl, 8 * maxterms) as *i64
845 var nd: i64 = 0
846 var i: i64 = 0
847 let pos: *i64 = ss_sr_map(srl, 16) as *i64
848 let tok: *u8 = ss_sr_map(srl, 40)
849 let ttbl: *u8 = ss_sr_map(srl, 272)
850 ss_tok_table(ttbl)
851 while i + 9 <= blen {
852 let kind: i64 = buf[i]
853 let kl: i64 = ss_r32(buf, i + 1)
854 let vl: i64 = ss_r32(buf, i + 5 + kl)
855 let voff: i64 = i + 5 + kl + 4
856 if nd >= maxdocs { ss_sr_release(srl); return 0 - 1 }
857 docoff[nd] = i
858 if kind == 1 {
859 pos[0] = voff
860 var tokidx: i64 = 0
861 var go: i64 = 1
862 while go == 1 {
863 let tl: i64 = ss_tok_next2(buf, voff + vl, pos, tok, ttbl)
864 if tl < 0 { go = 0 }
865 if go == 1 {
866 // find/create term id via the hash table (slot holds tid+1)
867 var tid: i64 = 0 - 1
868 var slot: i64 = ss_fnv(tok) % hts
869 var probe: i64 = 1
870 while probe == 1 {
871 if ht[slot] == 0 { probe = 0 } else {
872 let cand: i64 = ht[slot] - 1
873 let tp: *u8 = terms[cand] as *u8
874 var eq: i64 = 1
875 var x: i64 = 0
876 while eq == 1 {
877 if tp[x] != tok[x] { eq = 0 }
878 if eq == 1 { if tp[x] == (0 as u8) { tid = cand; eq = 0 } }
879 x = x + 1
880 }
881 if tid >= 0 { probe = 0 } else {
882 slot = slot + 1
883 if slot >= hts { slot = 0 }
884 }
885 }
886 }
887 if tid < 0 {
888 if nterms >= maxterms { ss_sr_release(srl); return 0 - 1 }
889 if pooloff + tl + 2 >= blen + SS_MAGIC_65536 { ss_sr_release(srl); return 0 - 1 }
890 let dst: *u8 = (pool as i64 + pooloff) as *u8
891 var x2: i64 = 0
892 while x2 <= tl { dst[x2] = tok[x2]; x2 = x2 + 1 }
893 terms[nterms] = dst as i64
894 tlens[nterms] = tl
895 tid = nterms
896 nterms = nterms + 1
897 pooloff = pooloff + tl + 1
898 ht[slot] = tid + 1
899 }
900 // per-doc dedupe: O(1) via the per-term last-doc-seen mark
901 if lastdoc[tid] != nd + 1 {
902 lastdoc[tid] = nd + 1
903 if npairs >= maxpairs { ss_sr_release(srl); return 0 - 1 }
904 pterm[npairs] = tid
905 pdoc[npairs] = nd
906 npairs = npairs + 1
907 }
908 // EVERY occurrence carries its token index (the phrase rung's raw material)
909 if nocc >= maxocc { ss_sr_release(srl); return 0 - 1 }
910 oterm[nocc] = tid
911 odoc[nocc] = nd
912 opos[nocc] = tokidx
913 nocc = nocc + 1
914 tokidx = tokidx + 1
915 }
916 }
917 }
918 nd = nd + 1
919 i = voff + vl
920 }
921 // sort term ids by term bytes (bottom-up merge sort over sidx)
922 let sidx: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
923 let stmp: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
924 var t3: i64 = 0
925 while t3 < nterms { sidx[t3] = t3; t3 = t3 + 1 }
926 var width: i64 = 1
927 while width < nterms {
928 var lo: i64 = 0
929 while lo < nterms {
930 var mid: i64 = lo + width
931 if mid > nterms { mid = nterms }
932 var hi: i64 = lo + 2 * width
933 if hi > nterms { hi = nterms }
934 var a2: i64 = lo
935 var b2: i64 = mid
936 var o2: i64 = lo
937 while o2 < hi {
938 var takea: i64 = 0
939 if a2 < mid {
940 if b2 >= hi { takea = 1 } else {
941 if ss_kcmp(terms[sidx[a2]] as *u8, tlens[sidx[a2]], terms[sidx[b2]] as *u8, tlens[sidx[b2]]) <= 0 { takea = 1 }
942 }
943 }
944 if takea == 1 { stmp[o2] = sidx[a2]; a2 = a2 + 1 } else { stmp[o2] = sidx[b2]; b2 = b2 + 1 }
945 o2 = o2 + 1
946 }
947 o2 = lo
948 while o2 < hi { sidx[o2] = stmp[o2]; o2 = o2 + 1 }
949 lo = lo + 2 * width
950 }
951 width = width * 2
952 }
953 // bucket pairs by term (counting sort; docs stay ascending within a term)
954 let dcnt: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
955 var p9: i64 = 0
956 while p9 < npairs { dcnt[pterm[p9]] = dcnt[pterm[p9]] + 1; p9 = p9 + 1 }
957 let bstart: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
958 let bfill: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
959 var acc: i64 = 0
960 var t9: i64 = 0
961 while t9 < nterms {
962 bstart[t9] = acc
963 bfill[t9] = acc
964 acc = acc + dcnt[t9]
965 t9 = t9 + 1
966 }
967 let bdoc: *i64 = ss_sr_map(srl, 8 * (npairs + 16)) as *i64
968 p9 = 0
969 while p9 < npairs {
970 bdoc[bfill[pterm[p9]]] = pdoc[p9]
971 bfill[pterm[p9]] = bfill[pterm[p9]] + 1
972 p9 = p9 + 1
973 }
974 // emit .post: magic | ndocs | doc-offset table | per-term delta-varint streams
975 pblob[0] = 78 as u8
976 pblob[1] = 88 as u8
977 pblob[2] = 80 as u8
978 pblob[3] = 49 as u8
979 ss_w32(pblob, 4, nd)
980 var po: i64 = 8 + 4 * nd
981 var d2: i64 = 0
982 while d2 < nd { ss_w32(pblob, 8 + 4 * d2, docoff[d2]); d2 = d2 + 1 }
983 let postoffs: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
984 let postlens: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
985 let dcounts: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
986 t3 = 0
987 while t3 < nterms {
988 let tid2: i64 = sidx[t3]
989 postoffs[t3] = po
990 var prev: i64 = 0
991 var p4: i64 = bstart[tid2]
992 let pend: i64 = bstart[tid2] + dcnt[tid2]
993 while p4 < pend {
994 po = ss_vw(pblob, po, bdoc[p4] - prev)
995 prev = bdoc[p4]
996 p4 = p4 + 1
997 }
998 postlens[t3] = po - postoffs[t3]
999 dcounts[t3] = dcnt[tid2]
1000 t3 = t3 + 1
1001 }
1002 // emit .terms: magic | nterms | entry-off table | entries
1003 tblob[0] = 78 as u8
1004 tblob[1] = 88 as u8
1005 tblob[2] = 84 as u8
1006 tblob[3] = 49 as u8
1007 ss_w32(tblob, 4, nterms)
1008 let base: i64 = 8 + 4 * nterms
1009 var to: i64 = base
1010 t3 = 0
1011 while t3 < nterms {
1012 ss_w32(tblob, 8 + 4 * t3, to - base)
1013 let tp2: *u8 = terms[sidx[t3]] as *u8
1014 let tl2: i64 = tlens[sidx[t3]]
1015 to = ss_w32(tblob, to, tl2)
1016 var x3: i64 = 0
1017 while x3 < tl2 { tblob[to] = tp2[x3]; to = to + 1; x3 = x3 + 1 }
1018 to = ss_w32(tblob, to, postoffs[t3])
1019 to = ss_w32(tblob, to, postlens[t3])
1020 to = ss_w32(tblob, to, dcounts[t3])
1021 t3 = t3 + 1
1022 }
1023 // emit the POSITIONS sidecar blob (NXQ1): bucket occurrences by term (counting sort preserves the
1024 // (doc asc, position asc) collection order), then per SORTED term emit doc-runs aligned with .post:
1025 // varint(npos) + delta-varint token indexes. NOTE the magic: .post already uses "NXP1" -- NXQ1 here.
1026 let ocnt: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
1027 var o9: i64 = 0
1028 while o9 < nocc { ocnt[oterm[o9]] = ocnt[oterm[o9]] + 1; o9 = o9 + 1 }
1029 let ostart: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
1030 let ofill: *i64 = ss_sr_map(srl, 8 * (nterms + 16)) as *i64
1031 var oacc: i64 = 0
1032 var t8: i64 = 0
1033 while t8 < nterms {
1034 ostart[t8] = oacc
1035 ofill[t8] = oacc
1036 oacc = oacc + ocnt[t8]
1037 t8 = t8 + 1
1038 }
1039 let obdoc: *i64 = ss_sr_map(srl, 8 * (nocc + 16)) as *i64
1040 let obpos: *i64 = ss_sr_map(srl, 8 * (nocc + 16)) as *i64
1041 o9 = 0
1042 while o9 < nocc {
1043 let f9: i64 = ofill[oterm[o9]]
1044 obdoc[f9] = odoc[o9]
1045 obpos[f9] = opos[o9]
1046 ofill[oterm[o9]] = f9 + 1
1047 o9 = o9 + 1
1048 }
1049 qblob[0] = 78 as u8
1050 qblob[1] = 88 as u8
1051 qblob[2] = 81 as u8
1052 qblob[3] = 49 as u8
1053 ss_w32(qblob, 4, nterms)
1054 let qbase: i64 = 8 + 4 * nterms
1055 var qo: i64 = qbase
1056 t3 = 0
1057 while t3 < nterms {
1058 let tid3: i64 = sidx[t3]
1059 ss_w32(qblob, 8 + 4 * t3, qo - qbase)
1060 var r0: i64 = ostart[tid3]
1061 let rend: i64 = ostart[tid3] + ocnt[tid3]
1062 while r0 < rend {
1063 // one doc-run: occurrences of THIS doc are contiguous (collection order)
1064 let dcur: i64 = obdoc[r0]
1065 var r1: i64 = r0
1066 var run9: i64 = 1
1067 while run9 == 1 {
1068 if r1 >= rend { run9 = 0 } else {
1069 if obdoc[r1] == dcur { r1 = r1 + 1 } else { run9 = 0 }
1070 }
1071 }
1072 qo = ss_vw(qblob, qo, r1 - r0)
1073 var prevp: i64 = 0
1074 var r2: i64 = r0
1075 while r2 < r1 {
1076 qo = ss_vw(qblob, qo, obpos[r2] - prevp)
1077 prevp = obpos[r2]
1078 r2 = r2 + 1
1079 }
1080 r0 = r1
1081 }
1082 t3 = t3 + 1
1083 }
1084 louts[0] = to
1085 louts[1] = po
1086 louts[2] = qo
1087 ss_sr_release(srl) // tblob/pblob/qblob (caller-owned) hold the result; every scratch mapping above is dead here
1088 return 0
1089}
1090
1091// ---- IMPACT-ORDERED POSTINGS sidecar (2026-07-25, the WAND rung, seq606) ------------------------
1092// .imp layout: "NXW1" | u32be nterms | nterms x u32be entry-off (relative to table end) | per SORTED
1093// term (same ordinal as .terms/NXQ1): varint(k) then k x (varint docidx, varint tf) -- the k highest-
1094// tf postings of the term (k = min(dcount, SS_IMP_K); set membership is by tf, walk order within).
1095// WHY: per-term candidacy caps truncated in ASCENDING-DOC order, so a high-tf doc past the cap (an
1096// entity's PROFILE page under a common name) could never become a candidate at any score. The impact
1097// list makes the cap keep the BEST postings, and the stored tf makes candidacy need zero doc reads.
1098// OPTIONAL like .pos: absent file -> readers fall back to ss_term; ss_write_seg emits it, so both
1099// compaction AND nx_seg_imp_build (in-place, additive-only) are format-upgrade paths.
1100const SS_IMP_K: i64 = 512
1101
1102// top-k selection threshold via a tf histogram (tf clamped into 1023 buckets so the scan is O(n+1024)
1103// whatever the tf range): outs[0]=T, outs[1]=count(clamped tf > T). Emit contract used by builder and
1104// reader: every clamped-tf>T item plus (k - outs[1]) clamped-tf==T items = exactly min(n,k) items.
1105func ss_imp_thresh(tfs: *i64, n: i64, k: i64, outs: *i64) -> i64 {
1106 if n <= k { outs[0] = 0; outs[1] = n; return 0 }
1107 let hist: *i64 = sys_mmap(8 * SS_MAGIC_1024) as *i64
1108 var i: i64 = 0
1109 while i < n {
1110 var v: i64 = tfs[i]
1111 if v > 1023 { v = 1023 }
1112 if v < 0 { v = 0 }
1113 hist[v] = hist[v] + 1
1114 i = i + 1
1115 }
1116 var acc: i64 = 0
1117 var b: i64 = 1023
1118 var t: i64 = 0
1119 var going: i64 = 1
1120 while going == 1 {
1121 if b < 0 { going = 0 } else {
1122 if acc + hist[b] >= k { t = b; going = 0 } else { acc = acc + hist[b]; b = b - 1 }
1123 }
1124 }
1125 outs[0] = t
1126 outs[1] = acc
1127 return 0
1128}
1129
1130// build the .imp blob from the three emitted index blobs alone (no writer-buffer access), so a LIVE
1131// segment can be upgraded from its .idx + .pos without recompacting. Returns blob length, -1 LOUD on
1132// malformed input. tf of (term,doc) = the NXQ1 doc-run occurrence count (npos), true value stored
1133// unclamped -- the 1023 clamp exists only inside selection.
1134func ss_build_imp(tb: *u8, tsz: i64, pb: *u8, psz: i64, qb: *u8, qsz: i64, wblob: *u8) -> i64 {
1135 if tsz < 8 { return 0 - 1 }
1136 if psz < 8 { return 0 - 1 }
1137 if qsz < 8 { return 0 - 1 }
1138 if qb[0] != (78 as u8) { return 0 - 1 }
1139 if qb[2] != (81 as u8) { return 0 - 1 }
1140 let nterms: i64 = ss_r32(tb, 4)
1141 if ss_r32(qb, 4) != nterms { return 0 - 1 }
1142 let nd: i64 = ss_r32(pb, 4)
1143 let tbase: i64 = 8 + 4 * nterms
1144 let qbase: i64 = 8 + 4 * nterms
1145 let docs: *i64 = sys_mmap(8 * (nd + 16)) as *i64
1146 let tfs: *i64 = sys_mmap(8 * (nd + 16)) as *i64
1147 let pv: *i64 = sys_mmap(16) as *i64
1148 let qv: *i64 = sys_mmap(16) as *i64
1149 let touts: *i64 = sys_mmap(32) as *i64
1150 wblob[0] = 78 as u8
1151 wblob[1] = 88 as u8
1152 wblob[2] = 87 as u8
1153 wblob[3] = 49 as u8
1154 ss_w32(wblob, 4, nterms)
1155 let wbase: i64 = 8 + 4 * nterms
1156 var wo: i64 = wbase
1157 var t: i64 = 0
1158 while t < nterms {
1159 ss_w32(wblob, 8 + 4 * t, wo - wbase)
1160 let eo: i64 = tbase + ss_r32(tb, 8 + 4 * t)
1161 let tl: i64 = ss_r32(tb, eo)
1162 let postoff: i64 = ss_r32(tb, eo + 4 + tl)
1163 let dcount: i64 = ss_r32(tb, eo + 4 + tl + 8)
1164 if dcount > nd { return 0 - 1 }
1165 // lockstep walk: .post deltas give ascending docs, the NXQ1 run headers give per-doc tf
1166 pv[0] = postoff
1167 qv[0] = qbase + ss_r32(qb, 8 + 4 * t)
1168 var prev: i64 = 0
1169 var i: i64 = 0
1170 while i < dcount {
1171 if qv[0] >= qsz { return 0 - 1 }
1172 prev = prev + ss_vr(pb, pv)
1173 docs[i] = prev
1174 let np: i64 = ss_vr(qb, qv)
1175 var sk: i64 = 0
1176 while sk < np { ss_vr(qb, qv); sk = sk + 1 }
1177 tfs[i] = np
1178 i = i + 1
1179 }
1180 ss_imp_thresh(tfs, dcount, SS_IMP_K, touts)
1181 let thr: i64 = touts[0]
1182 var k2: i64 = dcount
1183 if k2 > SS_IMP_K { k2 = SS_IMP_K }
1184 var room: i64 = k2 - touts[1]
1185 wo = ss_vw(wblob, wo, k2)
1186 // pass 1: every clamped tf > T; pass 2: clamped tf == T fills the remaining room
1187 var pass: i64 = 0
1188 while pass < 2 {
1189 i = 0
1190 while i < dcount {
1191 var cv: i64 = tfs[i]
1192 if cv > 1023 { cv = 1023 }
1193 var take: i64 = 0
1194 if pass == 0 { if cv > thr { take = 1 } }
1195 if pass == 1 { if cv == thr { if room > 0 { take = 1; room = room - 1 } } }
1196 if take == 1 { wo = ss_vw(wblob, wo, docs[i]); wo = ss_vw(wblob, wo, tfs[i]) }
1197 i = i + 1
1198 }
1199 pass = pass + 1
1200 }
1201 t = t + 1
1202 }
1203 return wo
1204}
1205
1206// build "<prefix>seg-<id>.terms" / ".post" (+ ".tmp")
1207func ss_auxname(prefix: *u8, segid: i64, ext: *u8, tmp: i64, out: *u8) -> i64 {
1208 var o: i64 = 0
1209 o = ss_cat(out, o, prefix)
1210 o = ss_cat(out, o, "seg-" as *u8)
1211 o = ss_catn(out, o, segid)
1212 o = ss_cat(out, o, ext)
1213 if tmp == 1 { o = ss_cat(out, o, ".tmp" as *u8) }
1214 out[o] = 0 as u8
1215 return o
1216}
1217
1218// write one segment's .docs + .keys files (temp -> rename each); NOT yet
1219// visible to readers until a manifest names it (commit or compaction swap)
1220// seq1730 COMPACTOR HOLE CLOSED: the guard used to sit at ss_commit ONLY, but compactors
1221// (nx_web_shard_compact.nx:165, nx_shard_compact.nx) call ss_write_seg DIRECTLY and then hand-write
1222// the manifest -- they never touch ss_commit, so the old placement had a hole EXACTLY where
1223// dp-web-pub- was actually corrupted. ss_write_seg is the deepest point BOTH paths share.
1224// ss_segid_ok is defined further down this file; forward references resolve (probed in isolation
1225// via nx_fwdref_probe: build exit=0 AND run exit=0), so no definition move and no compile-break
1226// window on a lib every organ imports.
1227func ss_write_seg(prefix: *u8, w: *i64, segid: i64) -> i64 {
1228 // ONE release discipline for the whole function (see SS_SR_* above): the list is created first so
1229 // the refusal path and the pre-write name scratch are covered too, not only the blob section.
1230 let srl: *i64 = ss_sr_new()
1231 if ss_segid_ok(segid) == 0 {
1232 let eb: *u8 = ss_sr_map(srl, 512)
1233 var eo: i64 = ss_cat(eb, 0, "SS-WRITE-SEG REFUSED: segid=" as *u8)
1234 eo = ss_catn(eb, eo, segid)
1235 eo = ss_cat(eb, eo, " is an ADDRESS or negative, not a segment id -- refusing to poison plane " as *u8)
1236 eo = ss_cat(eb, eo, prefix)
1237 eo = ss_cat(eb, eo, " (seq1730). NOTHING WRITTEN. Caller is deriving its id from a manifest array that holds POINTERS: parse the digits, or call ss_next_segid.\n" as *u8)
1238 sys_write(2, eb, eo)
1239 ss_sr_release(srl)
1240 return 0 - 7
1241 }
1242 let pt: *u8 = ss_sr_map(srl, 512)
1243 let pf: *u8 = ss_sr_map(srl, 512)
1244 ss_segname(prefix, segid, 1, pt)
1245 ss_segname(prefix, segid, 0, pf)
1246 if ss_writefile(pt, w[0] as *u8, w[1]) != 0 { ss_sr_release(srl); return 0 - 1 }
1247 if sys_renameat(pt, pf) != 0 { ss_sr_release(srl); return 0 - 2 }
1248 // MERGED aux index (IM6c): keys + terms + post in ONE seg-<id>.idx file
1249 // "NXI1" | u32be klen | u32be tlen | u32be plen | keys | terms | post
1250 // -- one write + fsync + rename instead of three (the race-localized
1251 // fsync-ceremony cost), same temp -> rename discipline. Readers slice.
1252 // Every blob below is written to disk in this call and dead afterwards; w[0] (the writer buffer)
1253 // is the CALLER's and is never on the list.
1254 let kblob: *u8 = ss_sr_map(srl, ss_keyblob_cap(w))
1255 let klen2: i64 = ss_build_keys(w, kblob)
1256 if klen2 < 0 { ss_sr_release(srl); return 0 - 5 }
1257 let tblob: *u8 = ss_sr_map(srl, w[1] * 8 + SS_MAGIC_65536)
1258 let pblob: *u8 = ss_sr_map(srl, w[1] * 8 + SS_MAGIC_65536)
1259 let qblob: *u8 = ss_sr_map(srl, w[1] * 4 + SS_MAGIC_65536)
1260 let louts: *i64 = ss_sr_map(srl, 32) as *i64
1261 if ss_build_terms(w, tblob, pblob, qblob, louts) != 0 { ss_sr_release(srl); return 0 - 11 }
1262 let isz: i64 = 16 + klen2 + louts[0] + louts[1]
1263 let iblob: *u8 = ss_sr_map(srl, isz + 64)
1264 iblob[0] = 78 as u8
1265 iblob[1] = 88 as u8
1266 iblob[2] = 73 as u8
1267 iblob[3] = 49 as u8
1268 ss_w32(iblob, 4, klen2)
1269 ss_w32(iblob, 8, louts[0])
1270 ss_w32(iblob, 12, louts[1])
1271 var co: i64 = 16
1272 var ci: i64 = 0
1273 while ci < klen2 { iblob[co] = kblob[ci]; co = co + 1; ci = ci + 1 }
1274 ci = 0
1275 while ci < louts[0] { iblob[co] = tblob[ci]; co = co + 1; ci = ci + 1 }
1276 ci = 0
1277 while ci < louts[1] { iblob[co] = pblob[ci]; co = co + 1; ci = ci + 1 }
1278 let it: *u8 = ss_sr_map(srl, 512)
1279 let if2: *u8 = ss_sr_map(srl, 512)
1280 ss_auxname(prefix, segid, ".idx" as *u8, 1, it)
1281 ss_auxname(prefix, segid, ".idx" as *u8, 0, if2)
1282 if ss_writefile(it, iblob, isz) != 0 { ss_sr_release(srl); return 0 - 7 }
1283 if sys_renameat(it, if2) != 0 { ss_sr_release(srl); return 0 - 8 }
1284 // POSITIONS SIDECAR (2026-07-03, phrase rung): seg-<id>.pos = the NXQ1 blob. OPTIONAL by design --
1285 // an old segment simply lacks the file and phrase queries degrade to AND there; compaction rebuilds
1286 // through THIS function, so compacting a shard upgrades it. Same temp->rename discipline, fail LOUD.
1287 let qt: *u8 = ss_sr_map(srl, 512)
1288 let qf: *u8 = ss_sr_map(srl, 512)
1289 ss_auxname(prefix, segid, ".pos" as *u8, 1, qt)
1290 ss_auxname(prefix, segid, ".pos" as *u8, 0, qf)
1291 if ss_writefile(qt, qblob, louts[2]) != 0 { ss_sr_release(srl); return 0 - 13 }
1292 if sys_renameat(qt, qf) != 0 { ss_sr_release(srl); return 0 - 14 }
1293 // IMPACT sidecar (2026-07-25, WAND rung): seg-<id>.imp = the NXW1 blob. OPTIONAL like .pos --
1294 // readers fall back when absent; compaction runs through here so compacting upgrades a shard.
1295 let wcap: i64 = louts[0] * 2 + louts[1] * 10 + SS_MAGIC_65536
1296 let wblob2: *u8 = ss_sr_map(srl, wcap)
1297 let wlen: i64 = ss_build_imp(tblob, louts[0], pblob, louts[1], qblob, louts[2], wblob2)
1298 if wlen < 0 { ss_sr_release(srl); return 0 - 15 }
1299 let wt: *u8 = ss_sr_map(srl, 512)
1300 let wf: *u8 = ss_sr_map(srl, 512)
1301 ss_auxname(prefix, segid, ".imp" as *u8, 1, wt)
1302 ss_auxname(prefix, segid, ".imp" as *u8, 0, wf)
1303 if ss_writefile(wt, wblob2, wlen) != 0 { ss_sr_release(srl); return 0 - 16 }
1304 if sys_renameat(wt, wf) != 0 { ss_sr_release(srl); return 0 - 17 }
1305 ss_sr_release(srl)
1306 return 0
1307}
1308
1309// load a segment's index blobs: NEW merged .idx (sliced) or LEGACY 3-file
1310// (.keys/.terms/.post) fallback -- compaction is the format-upgrade path.
1311// outs: [0]=keys ptr [1]=keys sz [2]=terms ptr [3]=terms sz [4]=post ptr [5]=post sz
1312func ss_load_aux(prefix: *u8, segname: *u8, outs: *i64) -> i64 {
1313 return ss_load_aux2(prefix, segname, outs, 0)
1314}
1315func ss_load_aux2(prefix: *u8, segname: *u8, outs: *i64, usemmap: i64) -> i64 {
1316 let path: *u8 = sys_mmap(512)
1317 var o: i64 = 0
1318 o = ss_cat(path, o, prefix)
1319 o = ss_cat(path, o, segname)
1320 o = ss_cat(path, o, ".idx" as *u8)
1321 path[o] = 0 as u8
1322 let szp: *i64 = sys_mmap(16) as *i64
1323 let ib: *u8 = ss_loadfile(path, szp, usemmap)
1324 if szp[0] >= 16 { if ib[0] == (78 as u8) { if ib[2] == (73 as u8) {
1325 let kl: i64 = ss_r32(ib, 4)
1326 let tl: i64 = ss_r32(ib, 8)
1327 let pl: i64 = ss_r32(ib, 12)
1328 if 16 + kl + tl + pl <= szp[0] {
1329 outs[0] = (ib as i64) + 16
1330 outs[1] = kl
1331 outs[2] = (ib as i64) + 16 + kl
1332 outs[3] = tl
1333 outs[4] = (ib as i64) + 16 + kl + tl
1334 outs[5] = pl
1335 // Release this call's scratch before handing back (seq997). `ib` is NOT freed -- outs[] point
1336 // INTO it and the handle owns it from here; ss_close reclaims it as ONE mapping via the
1337 // merged-.idx branch. Leaking these two cost ~2 pages x nsegs x opens, the last T8 residual.
1338 sys_munmap(path, 512)
1339 sys_munmap(szp as *u8, 16)
1340 return 1
1341 }
1342 } } }
1343 // legacy fallback: three separate files
1344 var fi: i64 = 0
1345 while fi < 3 {
1346 var ext: *u8 = ".keys" as *u8
1347 if fi == 1 { ext = ".terms" as *u8 }
1348 if fi == 2 { ext = ".post" as *u8 }
1349 o = 0
1350 o = ss_cat(path, o, prefix)
1351 o = ss_cat(path, o, segname)
1352 o = ss_cat(path, o, ext)
1353 path[o] = 0 as u8
1354 outs[fi * 2] = ss_loadfile(path, szp, usemmap) as i64
1355 outs[fi * 2 + 1] = szp[0]
1356 fi = fi + 1
1357 }
1358 return 0
1359}
1360
1361// COMMIT: seg temp -> rename into place; manifest rewritten via temp -> rename.
1362// The manifest rename IS the commit point (atomicity without a txn engine).
1363// ---- seg-id sanity (seq1730: POINTER-SHAPED SEGIDS SILENTLY CORRUPTED SEVEN PLANES) ---------------
1364// A segment id is either a small counter (1, 2, 1001) or an epoch (sec ~1.8e9, ms ~1.8e12, us ~1.8e15).
1365// It is NEVER AN ADDRESS. Seven planes under knowledge/store were poisoned by a caller passing a pointer
1366// into the segid slot, and because ss_next_segid derives max(existing)+1, ONE poisoned id PINS that plane
1367// near 1.4e14 FOREVER: every later epoch-derived write then sorts BELOW the poisoned segment in supersede
1368// order, so its rows are shadowed by older data -- silently, unboundedly, while the writer sees rc=0.
1369// THE BAND IS SEPARABLE BY CONSTRUCTION, NOT BY TASTE: x86-64 user-space mmap lives under 2^47, and the
1370// observed poison sat at 1.35e14-1.41e14. Epoch-ms (~1.8e12) is two orders BELOW the low bound; epoch-us
1371// (~1.8e15) is an order ABOVE the high bound. So no legitimate id scale can collide with this window.
1372// BOUND HERE because ss_commit is THE ONE ACT EVERY WRITER PERFORMS -- 408 call sites, one chokepoint.
1373// A fix that needs 408 authors to remember it is not a fix.
1374const SS_SEGID_PTRBAND_LO: i64 = 10000000000000
1375const SS_SEGID_PTRBAND_HI: i64 = 140737488355328
1376
1377// 1 = a usable segment id, 0 = refuse (negative, or inside the user-space pointer band).
1378func ss_segid_ok(v: i64) -> i64 {
1379 if v < 0 { return 0 }
1380 if v >= SS_SEGID_PTRBAND_LO { if v < SS_SEGID_PTRBAND_HI { return 0 } }
1381 return 1
1382}
1383
1384// ---- THE PLANE COMMIT LOCK (2026-08-06) ---------------------------------------------------------
1385// ss_commit is the one act every writer performs, and until now it took NO lock. Everything it does
1386// is a read-modify-write: the segid arrives from an unlocked ss_next_segid max+1 scan, and the
1387// manifest readall->rename below is a second RMW. The rename is atomic, so the manifest is never
1388// TORN -- and that is precisely what disguised the lost update for so long. ATOMIC IS NOT
1389// SERIALISABLE: an atomic swap makes each writer's result whole, not each writer's result present.
1390//
1391// MEASURED on the pre-fix code by nx_segrace_gate, 6 processes x 10 commits on one plane:
1392// 16 segment files on disk, a manifest naming only 7 of them, and seg-1 listed THREE times.
1393// -- duplicate ids => writers overwrote each other's segment file, bytes destroyed
1394// -- orphaned files => written and fsynced, then erased from the manifest, invisible forever
1395//
1396// ★WHY THIS LOCK IS <prefix>slock AND NOT <prefix>plock, WHICH IS THE WHOLE REASON IT IS SAFE.
1397// nx_store_seed_lib.nx twice rules out locking a shared primitive, and it is RIGHT about plock:
1398// flock is per OPEN FILE DESCRIPTION, so taking plock here would make nx_debt -- which already holds
1399// plock via db_lock and then calls sts_seed -> ss_commit -- block against ITSELF on a second fd and
1400// hang the debt board for every seat. That objection is specific to plock, not to locking at all.
1401// slock is a DIFFERENT file and a strictly INNER lock: it is acquired only inside ss_commit and
1402// released before ss_commit returns, and ss_commit never acquires plock. So the order is always
1403// plock (outer, held across a caller's whole read-modify-write) then slock (inner, held across one
1404// commit), never the reverse. A cycle is impossible BY CONSTRUCTION, so a deadlock is impossible --
1405// and unlike a caller-held lock, no author has to remember anything.
1406// ★THE COST IS MEASURED, NOT ASSUMED: nx_regprof_test already profiled lock_cycle=35us against
1407// reg_put=129335us steady-state -- the lock is 0.03% of a write. Correctness here is nearly free.
1408const SS_LOCK_EX: i64 = 2
1409const SS_LOCK_UN: i64 = 8
1410const SS_LOCK_NB: i64 = 4 // flock LOCK_NB -- ORed with LOCK_EX to poll instead of block
1411const SS_LOCK_SLEEP_MS: i64 = 50 // backoff between attempts
1412const SS_LOCK_TRIES: i64 = 2400 // 2400 x 50ms = 120s ceiling. RAISED FROM 30s BY MEASUREMENT: nx_segrace_gate stresses 7 processes doing fsync-per-commit on one plane, and at 30s FOUR writers were refused while merely QUEUED (not wedged) on a disk at IO load ~15. A bound must exceed legitimate worst-case contention or it manufactures the failure it exists to report; 120s still bounds the hang, and the >10min stall this was built for is still caught.
1413const SS_LOCKMODE: i64 = 420
1414const SS_LOCKPATHCAP: i64 = 512
1415
1416// Returns the held fd, or -1 LOUDLY naming the path -- "cannot lock" alone has historically blamed
1417// flock when the real cause was an open failure.
1418func ss_plane_lock(prefix: *u8) -> i64 {
1419 let p: *u8 = sys_mmap(SS_LOCKPATHCAP)
1420 var o: i64 = ss_cat(p, 0, prefix)
1421 o = ss_cat(p, o, "slock" as *u8)
1422 p[o] = 0 as u8
1423 let fd: i64 = sys_openat_append(p, SS_LOCKMODE)
1424 if fd < 0 {
1425 let eb: *u8 = sys_mmap(512)
1426 var eo: i64 = ss_cat(eb, 0, "SS-PLANE-LOCK open-failed (not flock) path=" as *u8)
1427 eo = ss_cat(eb, eo, p)
1428 eo = ss_cat(eb, eo, "\n" as *u8)
1429 sys_write(2, eb, eo)
1430 return 0 - 1
1431 }
1432 // BOUNDED WAIT REVERTED 2026-08-06 -- see debt 1786070596. A LOCK_NB retry loop lived here and
1433 // it was WRONG for a reason an isolated gate could not see: flock is per OPEN FILE DESCRIPTION
1434 // and fork() COPIES fds, so a child inherits a HELD lock and then blocks acquiring it on a fresh
1435 // fd -- deadlocked against a lock it already owns. /proc/locks named it: pid 22685 HELD inode
1436 // 42096345 while sleeping in its own retry backoff, and every other writer was refused behind it.
1437 // nx_lockbound_gate passed 2/2 because it never forked while holding.
1438 // >>AN ISOLATED GATE PROVES THE MECHANISM, NOT THE INTERACTION<< -- the retry did exactly what it
1439 // promised and still broke the system, converting an instant self-deadlock into a silent stall.
1440 // Blocking flock is restored: it is what nx_segrace_gate proved 24/24 and what is live today.
1441 // Re-attempt ONLY with a fork-inheritance guard (close-after-fork / CLOEXEC, or a process-local
1442 // registry of held planes) AND a gate that forks WHILE HOLDING, which this one did not.
1443 // BOUNDED WAIT, ATTEMPT 2 (2026-08-07, debt 1786070596). Attempt 1 was reverted after a deadlock
1444 // I attributed to fork-inheritance -- but that diagnosis was CONTAMINATED: the same session had
1445 // accidentally injected an unreleased write lock into four READER functions via a non-unique
1446 // replace-all, which alone explains a process holding a lock while waiting for it. This is the
1447 // controlled re-test: the library is verified GREEN on all six gates with NO injection, and this
1448 // is now the ONLY variable.
1449 // WHY BOUND IT AT ALL: a blocking flock has no timeout, so one writer wedged in a filesystem
1450 // journal commit (observed: state D, wchan=wait_for_commit, host at IO load 15.63) blocks every
1451 // other writer on that plane forever. Fail-fast beats fail-never -- a caller told NO can retry,
1452 // alert or degrade; a caller that hangs takes its supervisor's health check down with it.
1453 // ss_commit treats a negative return as fail-closed (-8, NOTHING WRITTEN), so refusing here can
1454 // never produce a partial write.
1455 var tries: i64 = 0
1456 var got: i64 = 0
1457 while got == 0 {
1458 if sys_flock(fd, SS_LOCK_EX + SS_LOCK_NB) == 0 {
1459 got = 1
1460 } else {
1461 tries = tries + 1
1462 if tries >= SS_LOCK_TRIES {
1463 let wb: *u8 = sys_mmap(512)
1464 var wo: i64 = ss_cat(wb, 0, "SS-PLANE-LOCK BUSY -- refusing after " as *u8)
1465 wo = ss_catn(wb, wo, (SS_LOCK_TRIES * SS_LOCK_SLEEP_MS) / 1000)
1466 wo = ss_cat(wb, wo, "s waiting on " as *u8)
1467 wo = ss_cat(wb, wo, p)
1468 wo = ss_cat(wb, wo, " -- another writer holds it (check /proc/<pid>/wchan: locks_lock_inode_wait = queued, wait_for_commit = wedged on disk). NOTHING WRITTEN.\n" as *u8)
1469 sys_write(2, wb, wo)
1470 sys_close(fd)
1471 return 0 - 1
1472 }
1473 sys_sleep_ms(SS_LOCK_SLEEP_MS)
1474 }
1475 }
1476 return fd
1477}
1478
1479// 1 if two NUL-terminated segment names are identical. Used by compaction to tell a segment it
1480// merged from one that landed WHILE it was merging -- the difference between folding history and
1481// deleting a commit.
1482func ss_name_eq(a: *u8, b: *u8) -> i64 {
1483 var i: i64 = 0
1484 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
1485 if b[i] != (0 as u8) { return 0 }
1486 return 1
1487}
1488
1489func ss_plane_unlock(fd: i64) -> i64 {
1490 if fd < 0 { return 0 }
1491 sys_flock(fd, SS_LOCK_UN)
1492 sys_close(fd)
1493 return 0
1494}
1495
1496// The max-segid scan, lifted OUT of ss_next_segid so ss_commit can reach it. ss_next_segid is
1497// defined ~240 lines BELOW ss_commit and NishiLang resolves identifiers in textual order, so a
1498// forward reference would not compile; duplicating the scan would be two copies of one rule (rule
1499// 15). ss_next_segid now delegates here, so both paths can never disagree.
1500// Returns the highest usable seg-<N> in the manifest, or -1 if there is none.
1501// Poisoned pointer-band ids are SKIPPED (seq1730 repair half): a corrupted manifest heals itself on
1502// the next write instead of staying pinned near 1.4e14 forever.
1503func ss_max_segid(prefix: *u8) -> i64 {
1504 let mf: *u8 = sys_mmap(512)
1505 var o: i64 = ss_cat(mf, 0, prefix)
1506 o = ss_cat(mf, o, "manifest.txt" as *u8)
1507 mf[o] = 0 as u8
1508 let szp: *i64 = sys_mmap(16) as *i64
1509 let b: *u8 = ss_readall(mf, szp)
1510 let sz: i64 = szp[0]
1511 if sz <= 0 { return 0 - 1 }
1512 var mx: i64 = 0 - 1
1513 var i: i64 = 0
1514 while i + 4 <= sz {
1515 var m: i64 = 0
1516 if b[i] == (115 as u8) { if b[i+1] == (101 as u8) { if b[i+2] == (103 as u8) { if b[i+3] == (45 as u8) { m = 1 } } } }
1517 if m == 1 {
1518 var j: i64 = i + 4
1519 var v: i64 = 0
1520 var any: i64 = 0
1521 var go: i64 = 1
1522 while go == 1 { go = 0; if j < sz { let c: i64 = b[j] as i64; if c >= (48 as i64) { if c <= (57 as i64) { v = v * (10 as i64) + (c - (48 as i64)); any = 1; j = j + 1; go = 1 } } } }
1523 if any == 1 { if ss_segid_ok(v) == 1 { if v > mx { mx = v } } }
1524 i = j
1525 } else { i = i + 1 }
1526 }
1527 return mx
1528}
1529
1530// RAW COMMIT BODY -- byte-identical to the ss_commit that shipped before 2026-08-06, deliberately
1531// untouched so the segid-poison guard and the manifest logic are provably unchanged. It is UNSAFE
1532// on its own (it is a read-modify-write with no lock); ss_commit below is now the only way in.
1533func ss_commit_body(prefix: *u8, w: *i64, segid: i64) -> i64 {
1534 if ss_segid_ok(segid) == 0 {
1535 let eb: *u8 = sys_mmap(512)
1536 var eo: i64 = ss_cat(eb, 0, "SS-COMMIT REFUSED: segid=" as *u8)
1537 eo = ss_catn(eb, eo, segid)
1538 eo = ss_cat(eb, eo, " is an ADDRESS or negative, not a segment id -- refusing to poison plane " as *u8)
1539 eo = ss_cat(eb, eo, prefix)
1540 eo = ss_cat(eb, eo, " (seq1730). NOTHING WRITTEN. Fix the caller: it is passing a pointer into the third argument of ss_commit.\n" as *u8)
1541 sys_write(2, eb, eo)
1542 return 0 - 7
1543 }
1544 let wrc: i64 = ss_write_seg(prefix, w, segid)
1545 if wrc != 0 { return wrc }
1546 let mf: *u8 = sys_mmap(512)
1547 let mt: *u8 = sys_mmap(512)
1548 var o: i64 = 0
1549 o = ss_cat(mf, o, prefix)
1550 o = ss_cat(mf, o, "manifest.txt" as *u8)
1551 mf[o] = 0 as u8
1552 o = 0
1553 o = ss_cat(mt, o, prefix)
1554 o = ss_cat(mt, o, "manifest.tmp" as *u8)
1555 mt[o] = 0 as u8
1556 let szp: *i64 = sys_mmap(16) as *i64
1557 let old: *u8 = ss_readall(mf, szp)
1558 var osz: i64 = szp[0]
1559 if osz < 0 { osz = 0 }
1560 let nb: *u8 = sys_mmap(osz + 128)
1561 var no: i64 = 0
1562 var t: i64 = 0
1563 while t < osz { nb[no] = old[t]; no = no + 1; t = t + 1 }
1564 no = ss_cat(nb, no, "seg-" as *u8)
1565 no = ss_catn(nb, no, segid)
1566 nb[no] = 10 as u8
1567 no = no + 1
1568 if ss_writefile(mt, nb, no) != 0 { return 0 - 3 }
1569 if sys_renameat(mt, mf) != 0 { return 0 - 4 }
1570 // A visible manifest is not a durable acknowledgement if the directory barrier failed.
1571 if ss_syncdir(prefix) != 0 { return SS_ERR_DURABILITY }
1572 return 0
1573}
1574
1575// ---- ss_commit_cas: COMPARE-AND-SWAP COMMIT (2026-08-06) ----------------------------------------
1576// expect_max = the ss_max_segid the caller's snapshot was taken at. If the plane has moved since,
1577// another writer committed inside the caller's read-modify-write and committing now would overwrite
1578// them from a stale snapshot -> refuse with SS_ERR_STALE and write NOTHING. Pass SS_CAS_ANY to skip.
1579//
1580// ★THE CHECK MUST LIVE HERE, NOT IN THE CALLER, AND THIS WAS PROVEN BY MEASUREMENT, NOT REASONING.
1581// The first version of this guard sat at the top of sts_seed. It changed NOTHING -- nx_sts_cas_gate
1582// still reported 6 writers told yes and 1 row present -- because sts_seed checks, then builds the
1583// writer and ss_add's every row, and only THEN commits. All six workers passed the check before any
1584// of them committed. A COMPARISON SEPARATED FROM THE WRITE IT GUARDS BY A WINDOW IS NOT A GUARD; it
1585// is a second race. Here the read of ss_max_segid and the commit are under ONE plane lock, so the
1586// answer cannot change between the test and the write. That is the whole difference.
1587const SS_CAS_ANY: i64 = 0 - 2
1588const SS_ERR_STALE: i64 = 0 - 9
1589func ss_commit_cas(prefix: *u8, w: *i64, segid: i64, expect_max: i64) -> i64 {
1590 if ss_segid_ok(segid) == 0 { return ss_commit_body(prefix, w, segid) }
1591 let lk: i64 = ss_plane_lock(prefix)
1592 if lk < 0 { return 0 - 8 }
1593 let mx: i64 = ss_max_segid(prefix)
1594 if expect_max != SS_CAS_ANY { if mx != expect_max { ss_plane_unlock(lk); return SS_ERR_STALE } }
1595 var eff: i64 = segid
1596 if mx >= 0 { if mx + 1 > eff { eff = mx + 1 } }
1597 let rc: i64 = ss_commit_body(prefix, w, eff)
1598 ss_plane_unlock(lk)
1599 return rc
1600}
1601
1602// ---- ss_commit: THE SERIALISED ENTRY POINT (2026-08-06) -----------------------------------------
1603// Same name, same signature, same return codes -- so all 261 ss_ call sites and BOTH sts_ families
1604// reach this without a single edit. That is the entire point. A fix that needs 261 authors to
1605// remember it is not a fix: the one already-built safe twin, sts_append_row, was gate-proven on
1606// 2026-07-30 and a day later still had ONE consumer against 62 call sites, while new unlocked
1607// writers landed FASTER than old ones were migrated (52 -> 56 in a single day). Migration was
1608// losing a race it cannot win, so make the primitive itself safe and let every caller inherit it.
1609// NEW failure code -8 = could not take the plane lock; callers already treat nonzero as failure.
1610func ss_commit(prefix: *u8, w: *i64, segid: i64) -> i64 {
1611 // Refusals are delegated UNLOCKED and UNCHANGED: a poisoned segid writes nothing, so there is
1612 // nothing to serialise, and locking merely to reject an argument would only add a failure mode.
1613 if ss_segid_ok(segid) == 0 { return ss_commit_body(prefix, w, segid) }
1614 let lk: i64 = ss_plane_lock(prefix)
1615 if lk < 0 { return 0 - 8 }
1616 // RE-DERIVE THE ID UNDER THE LOCK. The caller computed its segid BEFORE the lock -- the universal
1617 // shape is ss_commit(p, w, ss_next_segid(p)) -- so by now another writer may already own it. This
1618 // is what stops two writers both writing seg-<N>.docs, one silently destroying the other.
1619 // Only ever move FORWARD: a caller passing a deliberately higher id keeps it, so intentional id
1620 // schemes survive and only genuine collisions are resolved.
1621 var eff: i64 = segid
1622 let mx: i64 = ss_max_segid(prefix)
1623 if mx >= 0 { if mx + 1 > eff { eff = mx + 1 } }
1624 let rc: i64 = ss_commit_body(prefix, w, eff)
1625 ss_plane_unlock(lk)
1626 return rc
1627}
1628
1629// ---- BATCHED DURABILITY (added 2026-08-01 ws=legal, PROFILE-DRIVEN, debt 1785610685) --------------
1630// MEASURED, not guessed: nx_regprof_test decomposed reg_put on a fresh plane and found
1631// lock_cycle=35us u00b7 ss_get_absent=19us u00b7 ss_get@20seg=371us u00b7 reg_put=129335us steady-state.
1632// The lock is 0.03% of a write and the read path 0.3%, and cost FALLS as segments accumulate -- so
1633// neither the plane lock nor the full-index-per-segment shape is the cost. What remains is the
1634// per-commit DIRECTORY FSYNC on the line above: one durability barrier per appended row.
1635//
1636// u2605THE POINT: ss_commit is CORRECT and stays byte-for-byte unchanged. A single logical transaction
1637// that appends N rows does not need N barriers -- it needs ONE, at the end. Paying per row is paying
1638// for a guarantee nobody asked for at that granularity.
1639//
1640// ADDITIVE BY CONSTRUCTION: ss_commit and all 261 of its callers are untouched. A caller opts in by
1641// using ss_commit_deferred for the rows and calling ss_sync_now ONCE afterwards.
1642//
1643// u2605u2605THE HONEST BOUND, stated so nobody mistakes what this buys: after ss_commit_deferred the data IS
1644// VISIBLE -- the segment file is written and the manifest rename has already happened, so any reader
1645// sees the row immediately. ONLY DURABILITY ACROSS POWER LOSS is deferred. A crash between the last
1646// deferred commit and ss_sync_now can lose the tail of the batch. That is exactly the trade a batch
1647// wants and exactly the trade a single critical write must NOT make: u2605USE ss_commit FOR ROWS THAT MUST
1648// SURVIVE A CRASH ON THEIR OWN, ss_commit_deferred ONLY INSIDE A BATCH YOU WILL SYNC.
1649// RAW DEFERRED BODY -- byte-identical to what shipped before 2026-08-06. Same hazard as
1650// ss_commit_body: an unlocked read-modify-write. ss_commit_deferred below is now the only way in.
1651func ss_commit_deferred_body(prefix: *u8, w: *i64, segid: i64) -> i64 {
1652 if ss_segid_ok(segid) == 0 {
1653 let eb: *u8 = sys_mmap(512)
1654 var eo: i64 = ss_cat(eb, 0, "SS-COMMIT-DEFERRED REFUSED: segid=" as *u8)
1655 eo = ss_catn(eb, eo, segid)
1656 eo = ss_cat(eb, eo, " is an ADDRESS or negative, not a segment id -- refusing to poison plane " as *u8)
1657 eo = ss_cat(eb, eo, prefix)
1658 eo = ss_cat(eb, eo, ". NOTHING WRITTEN.\n" as *u8)
1659 sys_write(2, eb, eo)
1660 return 0 - 7
1661 }
1662 let wrc: i64 = ss_write_seg(prefix, w, segid)
1663 if wrc != 0 { return wrc }
1664 let mf: *u8 = sys_mmap(512)
1665 let mt: *u8 = sys_mmap(512)
1666 var o: i64 = 0
1667 o = ss_cat(mf, o, prefix)
1668 o = ss_cat(mf, o, "manifest.txt" as *u8)
1669 mf[o] = 0 as u8
1670 o = 0
1671 o = ss_cat(mt, o, prefix)
1672 o = ss_cat(mt, o, "manifest.tmp" as *u8)
1673 mt[o] = 0 as u8
1674 let szp: *i64 = sys_mmap(16) as *i64
1675 let old: *u8 = ss_readall(mf, szp)
1676 var osz: i64 = szp[0]
1677 if osz < 0 { osz = 0 }
1678 let nb: *u8 = sys_mmap(osz + 128)
1679 var no: i64 = 0
1680 var t: i64 = 0
1681 while t < osz { nb[no] = old[t]; no = no + 1; t = t + 1 }
1682 no = ss_cat(nb, no, "seg-" as *u8)
1683 no = ss_catn(nb, no, segid)
1684 nb[no] = 10 as u8
1685 no = no + 1
1686 if ss_writefile(mt, nb, no) != 0 { return 0 - 3 }
1687 if sys_renameat(mt, mf) != 0 { return 0 - 4 }
1688 // u2605deliberately NO ss_syncdir here -- that is the whole difference, and the caller owes one.
1689 return 0
1690}
1691
1692// u2605THE BARRIER THE BATCH OWES. Call once after a run of ss_commit_deferred. Idempotent and cheap to
1693// over-call: syncing a directory that is already durable is a no-op, so when in doubt, call it.
1694func ss_sync_now(prefix: *u8) -> i64 {
1695 ss_syncdir(prefix)
1696 return 0
1697}
1698
1699// ---- ss_commit_deferred: SERIALISED ENTRY POINT (2026-08-06) ------------------------------------
1700// The batch variant needs the SAME lock for the same reason: deferring the fsync changes only WHEN
1701// the bytes become durable, never whether two writers can pick the same segid or clobber each
1702// other's manifest line. Skipping the lock here would leave a fully open race behind a door marked
1703// "performance", and every ss_commit_deferred caller is by definition a HIGH-VOLUME writer -- the
1704// most likely to collide, not the least.
1705// The lock is per COMMIT, not per batch: each row is serialised against other writers, while the
1706// batch's durability barrier stays the caller's to place via ss_sync_now. Those are independent
1707// guarantees and conflating them is what would make this wrong.
1708func ss_commit_deferred(prefix: *u8, w: *i64, segid: i64) -> i64 {
1709 if ss_segid_ok(segid) == 0 { return ss_commit_deferred_body(prefix, w, segid) }
1710 let lk: i64 = ss_plane_lock(prefix)
1711 if lk < 0 { return 0 - 8 }
1712 var eff: i64 = segid
1713 let mx: i64 = ss_max_segid(prefix)
1714 if mx >= 0 { if mx + 1 > eff { eff = mx + 1 } }
1715 let rc: i64 = ss_commit_deferred_body(prefix, w, eff)
1716 ss_plane_unlock(lk)
1717 return rc
1718}
1719
1720// -- named caps (rule-11 burn-down of the 256/260 magic-number class) ---------------------------------
1721// SS_MANIFEST_LEGACY_CAP: the byte-identical legacy API cap (ss_manifest/_file). UNCAPPED paths exist and
1722// are canonical: ss_manifest_dyn (enumeration) + ss_next_segid (writers). New code must NOT use the legacy
1723// capped pair -- past the cap, count-as-segid CLOBBERS the cap segment (the reg_put persistence bug class).
1724const SS_MANIFEST_LEGACY_CAP: i64 = 256
1725const SS_VER_SLOTS: i64 = 260 // per-key version buffer allocation (kinds/ptrs/lens/srcs slots)
1726const SS_VER_WINDOW: i64 = 256 // versions of ONE key a scan retains; on overflow the OLDEST slides out
1727 // so the LATEST is ALWAYS kept (ss_get correctness for high-churn keys
1728 // like every registry's __idx__, which gains a version per put)
1729
1730// parse a manifest-format file (one "seg-<id>" name per line) into segs[];
1731// returns count. Serves BOTH the live manifest and the compaction archive.
1732// LEGACY-CAPPED (SS_MANIFEST_LEGACY_CAP): kept byte-identical for old callers; use ss_manifest_dyn.
1733func ss_manifest_file(prefix: *u8, fname: *u8, segs: *i64) -> i64 {
1734 let mf: *u8 = sys_mmap(512)
1735 var o: i64 = 0
1736 o = ss_cat(mf, o, prefix)
1737 o = ss_cat(mf, o, fname)
1738 mf[o] = 0 as u8
1739 let szp: *i64 = sys_mmap(16) as *i64
1740 let b: *u8 = ss_readall(mf, szp)
1741 let sz: i64 = szp[0]
1742 if sz <= 0 { return 0 }
1743 var cnt: i64 = 0
1744 var i: i64 = 0
1745 var ls: i64 = 0
1746 while i < sz {
1747 if b[i] == (10 as u8) {
1748 let name: *u8 = sys_mmap(128)
1749 var t: i64 = 0
1750 while ls + t < i { name[t] = b[ls + t]; t = t + 1 }
1751 name[t] = 0 as u8
1752 if cnt < SS_MANIFEST_LEGACY_CAP { segs[cnt] = name as i64; cnt = cnt + 1 }
1753 ls = i + 1
1754 }
1755 i = i + 1
1756 }
1757 return cnt
1758}
1759
1760// parse the LIVE manifest into segs[]; returns count
1761// LEGACY-CAPPED: writers must use ss_next_segid; enumerators ss_manifest_dyn.
1762func ss_manifest(prefix: *u8, segs: *i64) -> i64 {
1763 return ss_manifest_file(prefix, "manifest.txt" as *u8, segs)
1764}
1765
1766// SOTA data-driven manifest read (NO hardcoded cap -- the 256/260 magic-number cap is the debt this eats):
1767// read the live manifest ONCE, size the segs[] buffer from the ACTUAL entry count, and return the WHOLE
1768// corpus's live segment list. *out_segs receives the freshly-allocated, exactly-sized buffer; returns count.
1769// Sentinel guarding the stashed manifest-buffer slots in the segs slack. ss_manifest_free only trusts
1770// those slots when it sees this value, so a short read (cnt < mc, leaving a NAME pointer at segs[n])
1771// degrades to leaking rather than munmapping an address that was never a manifest buffer.
1772//
1773// DECLARED HERE, ABOVE ITS FIRST READER, 2026-07-26. It used to sit BELOW ss_manifest_file_dyn, which
1774// writes it at `segs[mc] = SS_MF_MAGIC`. A module const read before its declaration silently evaluated
1775// to 0, so the writer stamped 0 while ss_manifest_free compared against the real 0x53534D46 -- the
1776// sentinel COULD NEVER MATCH and the stashed manifest buffer (segs[n+1], size segs[n+2]) was NEVER
1777// munmapped. A real leak on every manifest read, in the most-shared primitive in the ecosystem, in the
1778// same memory class as seq905/seq975 that this file's own comments already reference. It stayed
1779// invisible until the sovereign compiler learned to REFUSE use-before-declaration instead of silently
1780// reading 0. KEEP THIS ABOVE ss_manifest_file_dyn.
1781const SS_MF_MAGIC: i64 = 0x53534D46
1782func ss_manifest_file_dyn(prefix: *u8, fname: *u8, out_segs: *i64) -> i64 {
1783 let mf: *u8 = sys_mmap(512)
1784 var o: i64 = 0
1785 o = ss_cat(mf, o, prefix)
1786 o = ss_cat(mf, o, fname)
1787 mf[o] = 0 as u8
1788 let szp: *i64 = sys_mmap(16) as *i64
1789 let b: *u8 = ss_readall(mf, szp)
1790 let sz: i64 = szp[0]
1791 if sz <= 0 { out_segs[0] = sys_mmap(64) as i64; return 0 }
1792 var mc: i64 = 0
1793 var i: i64 = 0
1794 while i < sz { if b[i] == (10 as u8) { mc = mc + 1 } i = i + 1 }
1795 // NAME POOL (seq905/seq975): each segment name used to get its OWN sys_mmap(128) -- a full 4 KiB page for
1796 // a 128-byte string, so an N-segment store burned N pages PER OPEN (the live 117-segment vault store:
1797 // ~468 KiB per open, leaked forever). One pooled allocation, sliced 128 bytes per name.
1798 // mf/szp are dead here (sz already extracted); free them rather than leak a page each per open.
1799 sys_munmap(mf, 512)
1800 sys_munmap(szp as *u8, 16)
1801 let namepool: *u8 = sys_mmap(128 * mc + 128)
1802 let segs: *i64 = sys_mmap(8 * mc + 64) as *i64
1803 // Hand the manifest read to ss_manifest_free via the slack slots (mc+8 slots exist; mc..mc+2 used).
1804 segs[mc] = SS_MF_MAGIC
1805 segs[mc + 1] = b as i64
1806 segs[mc + 2] = sz + 64
1807 out_segs[0] = segs as i64
1808 var cnt: i64 = 0
1809 var ls: i64 = 0
1810 i = 0
1811 while i < sz {
1812 if b[i] == (10 as u8) {
1813 let name: *u8 = sys_mmap(128)
1814 var t: i64 = 0
1815 while ls + t < i { name[t] = b[ls + t]; t = t + 1 }
1816 name[t] = 0 as u8
1817 if cnt < mc { let slot: *u8 = ((namepool as i64) + 128 * cnt) as *u8; var c2: i64 = 0; while name[c2] != (0 as u8) { slot[c2] = name[c2]; c2 = c2 + 1 } slot[c2] = 0 as u8; sys_munmap(name, 128); segs[cnt] = slot as i64; cnt = cnt + 1 } else { sys_munmap(name, 128) }
1818 ls = i + 1
1819 }
1820 i = i + 1
1821 }
1822 return cnt
1823}
1824// live-manifest convenience over ss_manifest_file_dyn (data-driven, no cap).
1825// ss_manifest_free -- release what ss_manifest_file_dyn handed back through out_segs. Names are POOLED
1826// contiguously from offset 0, so segs[0] IS the pool base -- no metadata slot and no signature change needed.
1827// Sizes are computed from the RETURNED count n, which is <= the allocated mc, so this UNDER-frees by at most
1828// a page rather than over-freeing: munmapping less than was mapped is safe, munmapping more can release a
1829// neighbouring mapping. Callers pair this with ss_manifest_dyn exactly as ss_close pairs with ss_open.
1830// SS_MF_MAGIC is declared at the TOP of this file, above ss_manifest_file_dyn which writes it.
1831// Declaring it here meant the writer read 0 and the sentinel never matched -- see the note there.
1832func ss_manifest_free(segs: *i64, n: i64) -> i64 {
1833 if (segs as i64) == 0 { return 0 }
1834 if n > 0 { if segs[0] != 0 { sys_munmap(segs[0] as *u8, 128 * n) } }
1835 // Stashed manifest buffer (sentinel-guarded; slots live in the +64 slack past mc, and n==mc in practice
1836 // because cnt increments exactly once per counted newline). This is the read `b` that ss_manifest_file_dyn
1837 // cannot free itself -- the name-parsing loop still needs it and that function has no single-exit tail.
1838 if segs[n] == SS_MF_MAGIC { if segs[n + 1] != 0 { if segs[n + 2] > 0 { sys_munmap(segs[n + 1] as *u8, segs[n + 2]) } } }
1839 sys_munmap(segs as *u8, 8 * n + 64)
1840 return 0
1841}
1842
1843func ss_manifest_dyn(prefix: *u8, out_segs: *i64) -> i64 {
1844 return ss_manifest_file_dyn(prefix, "manifest.txt" as *u8, out_segs)
1845}
1846
1847// CANONICAL next segment id for a commit under `prefix` = (max existing seg-<N> in the manifest) + 1,
1848// UNCAPPED. This is THE writer-side fix for the legacy-cap clobber class: deriving segid from the capped
1849// ss_manifest count returns the cap forever once exceeded, so every later commit overwrites the cap
1850// segment and concurrent writers clobber each other (proven: the toolreg store stuck at seg-256).
1851// Scanning for max is also duplicate-line-proof and hole-proof (ids only need uniqueness+monotonicity).
1852// Lifted from the proven reg_next_segid (nx_registry now delegates here). Empty/missing manifest -> 0.
1853func ss_next_segid(prefix: *u8) -> i64 {
1854 // DELEGATES to ss_max_segid, which had to be lifted above ss_commit so the commit path could
1855 // reach it (NishiLang resolves identifiers in textual order). ONE scan, ONE rule: two copies of
1856 // a max-segid rule that must agree is precisely how one registry drifts from its twin, and the
1857 // poisoned-id skip below is exactly the kind of rule that would get fixed in only one of them.
1858 let mxd: i64 = ss_max_segid(prefix)
1859 if mxd < 0 { return 0 }
1860 return mxd + 1
1861}
1862
1863// The pre-2026-08-06 body VERBATIM, retained as the equivalence ORACLE for ss_max_segid -- the same
1864// role sts_load_slow plays for sts_load. Not for production use; it is the control, not the code.
1865func ss_next_segid_oracle(prefix: *u8) -> i64 {
1866 let mf: *u8 = sys_mmap(512)
1867 var o: i64 = ss_cat(mf, 0, prefix)
1868 o = ss_cat(mf, o, "manifest.txt" as *u8)
1869 mf[o] = 0 as u8
1870 let szp: *i64 = sys_mmap(16) as *i64
1871 let b: *u8 = ss_readall(mf, szp)
1872 let sz: i64 = szp[0]
1873 if sz <= 0 { return 0 }
1874 var mx: i64 = 0 - 1
1875 var i: i64 = 0
1876 while i + 4 <= sz {
1877 var m: i64 = 0
1878 if b[i] == (115 as u8) { if b[i+1] == (101 as u8) { if b[i+2] == (103 as u8) { if b[i+3] == (45 as u8) { m = 1 } } } }
1879 if m == 1 {
1880 var j: i64 = i + 4
1881 var v: i64 = 0
1882 var any: i64 = 0
1883 var go: i64 = 1
1884 while go == 1 { go = 0; if j < sz { let c: i64 = b[j] as i64; if c >= (48 as i64) { if c <= (57 as i64) { v = v * (10 as i64) + (c - (48 as i64)); any = 1; j = j + 1; go = 1 } } } }
1885 // seq1730 REPAIR HALF: a poisoned id must not PIN the plane. Skipping pointer-shaped ids in
1886 // the max scan means an already-corrupted manifest heals itself on the next write instead of
1887 // staying stuck near 1.4e14 forever. The poisoned SEGMENT stays on disk and readable (rule 13,
1888 // additive-only) -- it simply stops dictating what the next id may be.
1889 if any == 1 { if ss_segid_ok(v) == 1 { if v > mx { mx = v } } }
1890 i = j
1891 } else { i = i + 1 }
1892 }
1893 if mx < 0 { return 0 }
1894 return mx + 1
1895}
1896
1897// author=tutor (GALX-PROD-FULL): backward-compatible higher-capacity manifest readers.
1898// IDENTICAL parse to ss_manifest_file/ss_manifest, but the segment-slot cap is a PARAMETER
1899// rather than the hard 256 baked into ss_manifest_file. Callers supplying a segs[] buffer of
1900// at least `cap` slots (8*cap bytes) can browse the WHOLE corpus, not just the first 256.
1901// The originals ss_manifest_file/ss_manifest are left BYTE-IDENTICAL (cap 256) so all 16
1902// existing callers (260-slot buffers) keep their exact behavior; this is purely additive.
1903func ss_manifest_file_cap(prefix: *u8, fname: *u8, segs: *i64, cap: i64) -> i64 {
1904 let mf: *u8 = sys_mmap(512)
1905 var o: i64 = 0
1906 o = ss_cat(mf, o, prefix)
1907 o = ss_cat(mf, o, fname)
1908 mf[o] = 0 as u8
1909 let szp: *i64 = sys_mmap(16) as *i64
1910 let b: *u8 = ss_readall(mf, szp)
1911 let sz: i64 = szp[0]
1912 if sz <= 0 { return 0 }
1913 var cnt: i64 = 0
1914 var i: i64 = 0
1915 var ls: i64 = 0
1916 while i < sz {
1917 if b[i] == (10 as u8) {
1918 let name: *u8 = sys_mmap(128)
1919 var t: i64 = 0
1920 while ls + t < i { name[t] = b[ls + t]; t = t + 1 }
1921 name[t] = 0 as u8
1922 if cnt < cap { segs[cnt] = name as i64; cnt = cnt + 1 }
1923 ls = i + 1
1924 }
1925 i = i + 1
1926 }
1927 return cnt
1928}
1929
1930// parse the LIVE manifest into segs[] honoring a caller-supplied cap; returns count
1931func ss_manifest_cap(prefix: *u8, segs: *i64, cap: i64) -> i64 {
1932 return ss_manifest_file_cap(prefix, "manifest.txt" as *u8, segs, cap)
1933}
1934
1935// author=tutor (GALX-PROD-FULL): cap-aware sibling of ss_scan. Identical chronological
1936// version walk, but the segment list is read with ss_manifest_cap so it sees ALL segments
1937// up to `cap` (not just 256). The per-key kinds/ptrs/lens buffers count VERSIONS of one key
1938// (tiny, caller-sized), independent of segment count. ss_scan is left untouched.
1939func ss_scan_cap(prefix: *u8, key: *u8, kinds: *i64, ptrs: *i64, lens: *i64, cap: i64) -> i64 {
1940 let segs: *i64 = sys_mmap(8 * cap) as *i64
1941 let ns: i64 = ss_manifest_cap(prefix, segs, cap)
1942 let srcs: *i64 = sys_mmap(8 * cap) as *i64
1943 return ss_scan_seglist(prefix, segs, ns, key, kinds, ptrs, lens, srcs, 1, 0, SS_VER_WINDOW)
1944}
1945
1946// author=tutor (GALX-PROD-FULL): cap-aware sibling of ss_get for the FULL-corpus ingest
1947// dedup path. Same semantics (1=found,0=tombstoned,-1=absent) but scans up to `cap`
1948// segments so re-ingest stays idempotent past the 256th image. ss_get is left untouched.
1949func ss_get_cap(prefix: *u8, key: *u8, ptrout: *i64, lenout: *i64, cap: i64) -> i64 {
1950 let kinds: *i64 = sys_mmap(8 * SS_VER_SLOTS) as *i64
1951 let ptrs: *i64 = sys_mmap(8 * SS_VER_SLOTS) as *i64
1952 let lens: *i64 = sys_mmap(8 * SS_VER_SLOTS) as *i64
1953 let n: i64 = ss_scan_cap(prefix, key, kinds, ptrs, lens, cap)
1954 if n == 0 { return 0 - 1 }
1955 let last: i64 = n - 1
1956 if kinds[last] == 2 { return 0 }
1957 ptrout[0] = ptrs[last]
1958 lenout[0] = lens[last]
1959 return 1
1960}
1961
1962// walk a segment list CHRONOLOGICALLY for `key`, appending versions at cnt0;
1963// srcs[i]=srcval marks where each version came from. Returns the new count.
1964// `cap` = the caller's version-buffer window. When a key has MORE versions than cap, the window SLIDES
1965// (oldest drops, newest kept) instead of silently truncating at the FIRST cap versions -- the old
1966// first-cap behavior made ss_get return a STALE value for any key with >cap versions (every registry's
1967// __idx__ index key gains one version per put, so registries past cap puts served a stale index).
1968func ss_scan_seglist(prefix: *u8, segs: *i64, ns: i64, key: *u8, kinds: *i64, ptrs: *i64, lens: *i64, srcs: *i64, srcval: i64, cnt0: i64, cap: i64) -> i64 {
1969 let kl0: i64 = ss_len(key)
1970 var cnt: i64 = cnt0
1971 var s: i64 = 0
1972 while s < ns {
1973 let path: *u8 = sys_mmap(512)
1974 var o: i64 = 0
1975 o = ss_cat(path, o, prefix)
1976 o = ss_cat(path, o, segs[s] as *u8)
1977 o = ss_cat(path, o, ".docs" as *u8)
1978 path[o] = 0 as u8
1979 let szp: *i64 = sys_mmap(16) as *i64
1980 let b: *u8 = ss_readall(path, szp)
1981 let sz: i64 = szp[0]
1982 var i: i64 = 0
1983 while i + 9 <= sz {
1984 let kind: i64 = b[i]
1985 let kl: i64 = ss_r32(b, i + 1)
1986 let koff: i64 = i + 5
1987 let vl: i64 = ss_r32(b, koff + kl)
1988 let voff: i64 = koff + kl + 4
1989 var eq: i64 = 1
1990 if kl != kl0 { eq = 0 }
1991 var t: i64 = 0
1992 while t < kl {
1993 if eq == 1 { if b[koff + t] != key[t] { eq = 0 } }
1994 t = t + 1
1995 }
1996 if eq == 1 {
1997 if cnt < cap {
1998 kinds[cnt] = kind
1999 ptrs[cnt] = (b as i64) + voff
2000 lens[cnt] = vl
2001 srcs[cnt] = srcval
2002 cnt = cnt + 1
2003 } else {
2004 // window full: slide the OLDEST version out so the LATEST is always retained
2005 // (ss_get takes [cnt-1]; pre-fix this silently kept the FIRST cap versions = stale get)
2006 var sh: i64 = 1
2007 while sh < cap { kinds[sh-1] = kinds[sh]; ptrs[sh-1] = ptrs[sh]; lens[sh-1] = lens[sh]; srcs[sh-1] = srcs[sh]; sh = sh + 1 }
2008 kinds[cap-1] = kind
2009 ptrs[cap-1] = (b as i64) + voff
2010 lens[cap-1] = vl
2011 srcs[cap-1] = srcval
2012 }
2013 }
2014 i = voff + vl
2015 }
2016 s = s + 1
2017 }
2018 return cnt
2019}
2020
2021// scan LIVE committed segments CHRONOLOGICALLY for `key`; fills kinds/ptrs/lens
2022// (every version incl. tombstones = the time-travel surface); returns count.
2023func ss_scan(prefix: *u8, key: *u8, kinds: *i64, ptrs: *i64, lens: *i64) -> i64 {
2024 let sp: *i64 = sys_mmap(8) as *i64
2025 let ns: i64 = ss_manifest_dyn(prefix, sp)
2026 let segs: *i64 = sp[0] as *i64
2027 let srcs: *i64 = sys_mmap(8 * SS_VER_SLOTS) as *i64
2028 return ss_scan_seglist(prefix, segs, ns, key, kinds, ptrs, lens, srcs, 1, 0, SS_VER_WINDOW)
2029}
2030
2031// ARCHIVE TIME-TRAVEL (IM-AR): the FULL history surface. Compaction retires
2032// segments into manifest-archive.txt without deleting them (additive law);
2033// this reads them back: every version of `key` across ARCHIVED segments
2034// (chronological, srcs=0) then LIVE segments (srcs=1). Post-compaction the
2035// live merged segment re-states the latest archived version -- that copy is
2036// reported honestly as its own LIVE row, never collapsed. Returns count.
2037func ss_scan_all(prefix: *u8, key: *u8, kinds: *i64, ptrs: *i64, lens: *i64, srcs: *i64) -> i64 {
2038 let asp: *i64 = sys_mmap(8) as *i64
2039 let na: i64 = ss_manifest_file_dyn(prefix, "manifest-archive.txt" as *u8, asp)
2040 let asegs: *i64 = asp[0] as *i64
2041 var cnt: i64 = ss_scan_seglist(prefix, asegs, na, key, kinds, ptrs, lens, srcs, 0, 0, SS_VER_WINDOW)
2042 let lsp: *i64 = sys_mmap(8) as *i64
2043 let nl: i64 = ss_manifest_dyn(prefix, lsp)
2044 let lsegs: *i64 = lsp[0] as *i64
2045 cnt = ss_scan_seglist(prefix, lsegs, nl, key, kinds, ptrs, lens, srcs, 1, cnt, SS_VER_WINDOW)
2046 return cnt
2047}
2048
2049// Binary-search an in-memory .keys blob for key.
2050// Returns kind (1 put / 2 tombstone) with voff/vlen in outs, or -1 not present,
2051// or -2 blob malformed.
2052// NXK1 uses a four-byte signature/count and u32 offset table; entries hold
2053// one kind byte plus three u32 fields and key bytes. These are format widths.
2054const SS_IDX_U32: i64 = 4
2055const SS_IDX_HEADER: i64 = SS_IDX_U32 * 2
2056const SS_IDX_ENTRY_FIXED: i64 = 1 + SS_IDX_U32 * 3
2057func ss_idx_find(b: *u8, sz: i64, key: *u8, voffout: *i64, vlenout: *i64) -> i64 {
2058 voffout[0]=0; vlenout[0]=0
2059 if sz < SS_IDX_HEADER { return 0-2 }
2060 if (b as i64) == 0 { return 0-2 }
2061 if b[0] != (78 as u8) { return 0-2 }
2062 if b[1] != (88 as u8) { return 0-2 }
2063 if b[2] != (75 as u8) { return 0-2 }
2064 if b[3] != (49 as u8) { return 0-2 }
2065 let n: i64 = ss_r32(b,SS_IDX_U32)
2066 // Bound before multiplying or following any offset supplied by the file.
2067 if n > (sz-SS_IDX_HEADER)/SS_IDX_U32 { return 0-2 }
2068 let base: i64 = SS_IDX_HEADER+SS_IDX_U32*n
2069 let kl0: i64 = ss_len(key)
2070 var lo: i64 = 0
2071 var hi: i64 = n-1
2072 while lo <= hi {
2073 let mid: i64 = lo+(hi-lo)/2
2074 let offset: i64 = ss_r32(b,SS_IDX_HEADER+SS_IDX_U32*mid)
2075 if offset > sz-base { return 0-2 }
2076 let eo: i64 = base+offset
2077 if sz-eo < SS_IDX_ENTRY_FIXED { return 0-2 }
2078 let kind: i64 = b[eo]
2079 if kind != 1 { if kind != 2 { return 0-2 } }
2080 let kl: i64 = ss_r32(b,eo+1)
2081 if kl > sz-eo-SS_IDX_ENTRY_FIXED { return 0-2 }
2082 let key_at: i64 = eo+1+SS_IDX_U32
2083 let c: i64 = ss_kcmp((b as i64+key_at) as *u8,kl,key,kl0)
2084 if c == 0 {
2085 voffout[0]=ss_r32(b,key_at+kl)
2086 vlenout[0]=ss_r32(b,key_at+kl+SS_IDX_U32)
2087 return kind
2088 }
2089 if c < 0 { lo=mid+1 }
2090 if c > 0 { hi=mid-1 }
2091 }
2092 return 0-1
2093}
2094
2095// per-call wrapper: load ONE segment's .keys from disk and search it
2096func ss_idx_lookup(prefix: *u8, segname: *u8, key: *u8, voffout: *i64, vlenout: *i64) -> i64 {
2097 let outs: *i64 = sys_mmap(8 * 8) as *i64
2098 ss_load_aux(prefix, segname, outs)
2099 return ss_idx_find(outs[0] as *u8, outs[1], key, voffout, vlenout)
2100}
2101
2102// OPEN-STORE HANDLE (the race-named read optimization): load the manifest and
2103// every live segment's .keys + .docs ONCE; lookups then binary-search in
2104// memory with zero per-call file IO. Snapshot semantics: the handle sees the
2105// store as of open (immutable segments make this safe -- a later commit adds
2106// segments the handle simply does not list; reopen to see them).
2107// Layout: h[0]=nsegs; per segment i (8 slots): h[1+8i]=keys ptr, h[2+8i]=keys
2108// size, h[3+8i]=docs ptr, h[4+8i]=docs size, h[5+8i]=terms ptr, h[6+8i]=terms
2109// size, h[7+8i]=post ptr, h[8+8i]=post size; h[1+8*ns+i]=LIVE-DOC map ptr for
2110// segment i (1 byte per doc entry, 1 = this entry IS its key's current state
2111// -- computed ONCE here so term queries check currency in O(1) instead of a
2112// per-candidate keyed binary search). Returns 0 ptr if no manifest.
2113// default open: read every segment file fully into anon RAM (historical behavior; ALL non-search consumers
2114// keep this -- small shards, byte-for-byte unchanged).
2115// ss_close -- give back EVERYTHING ss_open/ss_open2 mapped. Its ABSENCE was the seq905 keystone: ss_open
2116// loads every live segment's .keys/.docs/.terms/.post plus a derived live-doc map, and NOTHING ever released
2117// them, so EVERY seg-store consumer leaked BY CONSTRUCTION (`func ss_close` grepped to 0 matches ecosystem-
2118// wide while ss_open had dozens of callers). Measured cost 2026-07-25: ~20.6 GB of 24.3 GB swap consumed on
2119// the NAS, which parks long-running consumers in uninterruptible sleep -- at which point they stop making
2120// progress AND stop responding to SIGKILL, so the leak presents as a HANG, not as an OOM.
2121//
2122// Every size is DERIVABLE from the handle, so this needs no layout change and no new bookkeeping:
2123// h[0]=ns; per segment i: keys(h[1+8i],h[2+8i]) docs(h[3+8i],h[4+8i]) terms(h[5+8i],h[6+8i])
2124// post(h[7+8i],h[8+8i]); live-doc map h[1+8ns+i], sized dsz/9+16 exactly as ss_open2 allocated it.
2125//
2126// DELIBERATELY CONSERVATIVE on blob length: ss_readall maps sz+64 but REPORTS sz, while the usemmap=1 path
2127// maps exactly sz. We free the REPORTED size, never sz+64 -- unmapping past a file-backed mapping could
2128// release a NEIGHBOURING mapping, and a sub-page residue is strictly better than freeing memory we do not
2129// own. Pointers are nulled as freed and the handle is released LAST (all sizes are read out of it first), so
2130// a partially-populated handle from a failed open still closes cleanly. Null/zero entries are skipped.
2131// license_tier: ORIGINAL No hw writes (Rule 26).
2132// ss_open3 handle slots + limits (declared here, ABOVE ss_close, because the parser resolves module consts in file order)
2133const SS3_SLOT_NAMES: i64 = 7
2134const SS3_SLOT_TSEG: i64 = 8
2135const SS3_SLOT_TEO: i64 = 9
2136const SS3_SLOT_CAP: i64 = 10
2137const SS3_SLOT_INS: i64 = 11
2138const SS3_SLOT_MODE: i64 = 12
2139// ---- AUX ROWS (2026-09-14, search plan L3: index-resident candidacy) ------------------------------
2140// Slot 13 of the handle tail holds ONE anonymous array of SS_AUXW words PER SEGMENT, built beside the live
2141// map and moved/freed with it: [0] d2k = doc index -> .keys entry offset (so a posting hit reads its key
2142// from the small, warm .keys slice instead of faulting the multi-GB .docs mmap -- MEASURED 2026-09-14 by
2143// the search phase timers as 7.5-7.9 s of an 11.5-12.5 s cold two-common-term query, every byte of it
2144// stage-1 key reads), [1] d2k words, [2] tsample = every SS_TSAMPLE-th sorted term's SS_TPREFIX-byte packed
2145// prefix + ordinal (a RAM index over the .terms dictionary so an exact or prefix lookup touches ONE window
2146// of the blob instead of log2(n) cold pages), [3] tsample entries. Both are DERIVED from bytes the open
2147// already reads, so every reader they serve answers byte-for-byte what the unindexed search answered; a
2148// row that is absent (0) makes every consumer fall back to the old path.
2149const SS3_SLOT_AUX: i64 = 13
2150const SS_AUXW: i64 = 8
2151const SS_TSAMPLE: i64 = 64
2152const SS_TPREFIX: i64 = 7
2153const SS3_MODE_FULL: i64 = 0
2154const SS3_MODE_INCR: i64 = 1
2155const SS3_STATBUF: i64 = 160
2156const SS3_ST_SIZE_OFF: i64 = 48
2157const SS3_ANN_CAP: i64 = 256
2158const SS3_PATHCAP: i64 = 512
2159const SS3_KEYMAX: i64 = 500
2160// TABLE LOAD BOUND for an incremental insert: distinct keys (retained INS + every new entry, an upper bound) may fill
2161// at most 3/4 of the open-addressed table -- linear probing averages ~2.5 probes per hit there, and the FULL build
2162// sizes the table at 2x ENTRIES so this leaves headroom equal to roughly the whole prior key count. The gate fixture
2163// (128-slot table, 45 keys, 18 new entries) is exactly what a half-load rule refused on 2026-09-02.
2164const SS3_LOAD_NUM: i64 = 3
2165const SS3_LOAD_DEN: i64 = 4
2166// PER-SEGMENT RELEASE, extracted from ss_close 2026-09-02 so the incremental open (ss_open3) can back out
2167// the rows it loaded itself without a second copy of the merged-.idx single-mapping arithmetic.
2168// ---- AUX ROW HELPERS (2026-09-14) -- see SS3_SLOT_AUX above for what the rows hold and why ------
2169func ss_aux(h: *i64) -> *i64 {
2170 if (h as i64) == 0 { return 0 as *i64 }
2171 let ns: i64 = h[0]
2172 if ns <= 0 { return 0 as *i64 }
2173 return h[SS3_SLOT_AUX + 9 * ns] as *i64
2174}
2175func ss_aux_row(h: *i64, s: i64) -> *i64 {
2176 let ax: *i64 = ss_aux(h)
2177 if (ax as i64) == 0 { return 0 as *i64 }
2178 if s < 0 { return 0 as *i64 }
2179 if s >= h[0] { return 0 as *i64 }
2180 return ((ax as i64) + 8 * SS_AUXW * s) as *i64
2181}
2182// release one segment's derived aux rows (d2k + term sample); idempotent, pointers zeroed
2183func ss_aux_free_row(h: *i64, s: i64) -> i64 {
2184 let r: *i64 = ss_aux_row(h, s)
2185 if (r as i64) == 0 { return 0 }
2186 if r[0] != 0 { if r[1] > 0 { sys_munmap(r[0] as *u8, 8 * r[1]) } }
2187 if r[2] != 0 { if r[3] > 0 { sys_munmap(r[2] as *u8, 16 * r[3]) } }
2188 r[0] = 0; r[1] = 0; r[2] = 0; r[3] = 0
2189 return 0
2190}
2191// pack the first SS_TPREFIX bytes of a key big-endian into a NON-NEGATIVE i64: 7 bytes leave the sign bit
2192// clear, so ordinary signed compare orders packed prefixes exactly as ss_kcmp orders the same bytes, and a
2193// shorter key pads with zeros and therefore sorts first -- ss_kcmp's shorter-is-less rule.
2194func ss_tpack(p: *u8, n: i64) -> i64 {
2195 var v: i64 = 0
2196 var i: i64 = 0
2197 while i < SS_TPREFIX {
2198 var b: i64 = 0
2199 if i < n { b = p[i] as i64 }
2200 v = v * 256 + b
2201 i = i + 1
2202 }
2203 return v
2204}
2205// build segment s's term sample from its .terms blob: (packed prefix, ordinal) for every SS_TSAMPLE-th sorted
2206// term -- one sequential pass over the entry stripe, the pages the open's WILLNEED already asked for.
2207// DOC-KEY PREDICATE, ONE COPY (search L5, 2026-09-14): a .keys entry whose kind byte is 1 and whose key starts
2208// with "doc:" -- the corpus-size predicate ss_doc_count has always used, extracted so the per-row count below
2209// and the whole-shard walk cannot drift apart.
2210const SS_AUX_DOCN: i64 = 4
2211func ss_is_doc_key(kb: *u8, eo: i64) -> i64 {
2212 if (kb[eo] as i64) != 1 { return 0 }
2213 let kl9: i64 = ss_r32(kb, eo + 1)
2214 if kl9 < 4 { return 0 }
2215 if kb[eo + 5] != (100 as u8) { return 0 }
2216 if kb[eo + 6] != (111 as u8) { return 0 }
2217 if kb[eo + 7] != (99 as u8) { return 0 }
2218 if kb[eo + 8] != (58 as u8) { return 0 }
2219 return 1
2220}
2221// PER-ROW DOC-KEY COUNT (search L5, 2026-09-14): counted ONCE when the row is loaded (a segment is immutable),
2222// stored as count+1 in aux slot SS_AUX_DOCN (0 = absent) and carried through every incremental reopen by
2223// ss3_move like the other aux words. ss_doc_count sums these instead of walking every .keys entry per query:
2224// the phase timers measured that walk at 324 ms of a 331 ms prep on every query, warm or cold. Same predicate,
2225// same number; the entry count is clamped to the mapped bytes exactly as the live-map loop clamps it.
2226func ss_doccount_build(h: *i64, s: i64) -> i64 {
2227 let r: *i64 = ss_aux_row(h, s)
2228 if (r as i64) == 0 { return 0 }
2229 let kb: *u8 = h[1 + 8 * s] as *u8
2230 let ksz: i64 = h[2 + 8 * s]
2231 if ksz < 8 { r[SS_AUX_DOCN] = 1; return 0 }
2232 var m9: i64 = ss_r32(kb, 4)
2233 if 8 + 4 * m9 > ksz { m9 = (ksz - 8) / 4 }
2234 var n: i64 = 0
2235 var e9: i64 = 0
2236 while e9 < m9 {
2237 let eo: i64 = 8 + 4 * m9 + ss_r32(kb, 8 + 4 * e9)
2238 n = n + ss_is_doc_key(kb, eo)
2239 e9 = e9 + 1
2240 }
2241 r[SS_AUX_DOCN] = n + 1
2242 return n
2243}
2244func ss_tsample_build(h: *i64, s: i64) -> i64 {
2245 let r: *i64 = ss_aux_row(h, s)
2246 if (r as i64) == 0 { return 0 }
2247 let tb: *u8 = h[5 + 8 * s] as *u8
2248 let tsz: i64 = h[6 + 8 * s]
2249 if tsz < 8 { return 0 }
2250 if tb[0] != (78 as u8) { return 0 }
2251 if tb[2] != (84 as u8) { return 0 }
2252 var n: i64 = ss_r32(tb, 4)
2253 if 8 + 4 * n > tsz { n = (tsz - 8) / 4 }
2254 if n <= 0 { return 0 }
2255 let base: i64 = 8 + 4 * n
2256 let cnt: i64 = (n + SS_TSAMPLE - 1) / SS_TSAMPLE
2257 let sm: *i64 = sys_mmap(16 * cnt) as *i64
2258 var k: i64 = 0
2259 var i: i64 = 0
2260 while i < n {
2261 let eo: i64 = base + ss_r32(tb, 8 + 4 * i)
2262 var tl: i64 = 0
2263 if eo + 4 <= tsz { tl = ss_r32(tb, eo) }
2264 if eo + 4 + tl > tsz { tl = 0 }
2265 sm[2 * k] = ss_tpack(((tb as i64) + eo + 4) as *u8, tl)
2266 sm[2 * k + 1] = i
2267 k = k + 1
2268 i = i + SS_TSAMPLE
2269 }
2270 r[2] = sm as i64
2271 r[3] = k
2272 return k
2273}
2274// the ordinal window [lo, hi) that holds EVERY term whose packed prefix equals pk (and, for a lower-bound
2275// search, the first term whose prefix is >= pk): from the sample just BELOW the first sample >= pk to the
2276// first sample > pk. Without a sample the window is the whole dictionary, i.e. the pre-2026-09-14 search.
2277func ss_tsample_window(h: *i64, s: i64, pk: i64, n: i64, lohi: *i64) -> i64 {
2278 lohi[0] = 0
2279 lohi[1] = n
2280 let r: *i64 = ss_aux_row(h, s)
2281 if (r as i64) == 0 { return 0 }
2282 if r[2] == 0 { return 0 }
2283 let sm: *i64 = r[2] as *i64
2284 let cnt: i64 = r[3]
2285 if cnt <= 0 { return 0 }
2286 var lo: i64 = 0
2287 var hi: i64 = cnt
2288 while lo < hi { let mid: i64 = (lo + hi) / 2; if sm[2 * mid] < pk { lo = mid + 1 } else { hi = mid } }
2289 let first_ge: i64 = lo
2290 var lo2: i64 = first_ge
2291 var hi2: i64 = cnt
2292 while lo2 < hi2 { let mid2: i64 = (lo2 + hi2) / 2; if sm[2 * mid2] <= pk { lo2 = mid2 + 1 } else { hi2 = mid2 } }
2293 let first_gt: i64 = lo2
2294 if first_ge > 0 { lohi[0] = sm[2 * (first_ge - 1) + 1] } else { lohi[0] = 0 }
2295 if first_gt < cnt { lohi[1] = sm[2 * first_gt + 1] } else { lohi[1] = n }
2296 return 1
2297}
2298// EXACT term lookup in segment s through the sample: the same answer ss_terms_ordinal gives over the whole
2299// blob (the window provably contains every term with this prefix), touching one window instead of log2(n)
2300// scattered pages. Returns the ordinal (>= 0) with outs = postoff/postlen/dcount, -1 absent, -2 malformed.
2301static sst_lohi: *i64
2302func ss_terms_ordinal_h(h: *i64, s: i64, term: *u8, outs: *i64) -> i64 {
2303 let tb: *u8 = h[5 + 8 * s] as *u8
2304 let tsz: i64 = h[6 + 8 * s]
2305 if tsz < 8 { return 0 - 2 }
2306 if tb[0] != (78 as u8) { return 0 - 2 }
2307 if tb[2] != (84 as u8) { return 0 - 2 }
2308 let n: i64 = ss_r32(tb, 4)
2309 let base: i64 = 8 + 4 * n
2310 let tl0: i64 = ss_len(term)
2311 if (sst_lohi as i64) == 0 { sst_lohi = sys_mmap(16) as *i64 }
2312 ss_tsample_window(h, s, ss_tpack(term, tl0), n, sst_lohi)
2313 var lo: i64 = sst_lohi[0]
2314 var hi: i64 = sst_lohi[1] - 1
2315 while lo <= hi {
2316 let mid: i64 = (lo + hi) / 2
2317 let eo: i64 = base + ss_r32(tb, 8 + 4 * mid)
2318 let tl: i64 = ss_r32(tb, eo)
2319 let c: i64 = ss_kcmp((tb as i64 + eo + 4) as *u8, tl, term, tl0)
2320 if c == 0 {
2321 outs[0] = ss_r32(tb, eo + 4 + tl)
2322 outs[1] = ss_r32(tb, eo + 4 + tl + 4)
2323 outs[2] = ss_r32(tb, eo + 4 + tl + 8)
2324 return mid
2325 }
2326 if c < 0 { lo = mid + 1 }
2327 if c > 0 { hi = mid - 1 }
2328 }
2329 return 0 - 1
2330}
2331// ss_terms_find's contract (1 found / -1 absent / -2 malformed) over the sampled lookup
2332func ss_terms_find_h(h: *i64, s: i64, term: *u8, outs: *i64) -> i64 {
2333 let r: i64 = ss_terms_ordinal_h(h, s, term, outs)
2334 if r >= 0 { return 1 }
2335 return r
2336}
2337// the key of doc index d in segment s WITHOUT touching .docs: d2k maps it to its .keys entry, whose key bytes
2338// are the same bytes the .docs entry carries. 1 = kp/kl filled; 0 = no row or no entry (the caller reads the
2339// .docs entry exactly as before).
2340func ss_key_of_doc(h: *i64, s: i64, d: i64, kpout: *i64, klout: *i64) -> i64 {
2341 let r: *i64 = ss_aux_row(h, s)
2342 if (r as i64) == 0 { return 0 }
2343 if r[0] == 0 { return 0 }
2344 if d < 0 { return 0 }
2345 if d >= r[1] { return 0 }
2346 let d2k: *i64 = r[0] as *i64
2347 let eo: i64 = d2k[d]
2348 if eo <= 0 { return 0 }
2349 let kb: *u8 = h[1 + 8 * s] as *u8
2350 let ksz: i64 = h[2 + 8 * s]
2351 if eo + 5 > ksz { return 0 }
2352 let kl: i64 = ss_r32(kb, eo + 1)
2353 if eo + 5 + kl > ksz { return 0 }
2354 kpout[0] = (kb as i64) + eo + 5
2355 klout[0] = kl
2356 return 1
2357}
2358func ss_close_seg(h: *i64, ns: i64, s: i64) -> i64 {
2359 let kp: i64 = h[1 + 8 * s]
2360 let ksz: i64 = h[2 + 8 * s]
2361 // MERGED-.idx DETECTION (seq997). ss_load_aux2 returns keys/terms/post as INTERIOR SLICES of ONE
2362 // mapping when the segment has a merged .idx (base = keys-16, total = 16+kl+tl+pl). Freeing those as
2363 // three independent mappings is WRONG and never releases the base. The LEGACY 3-file fallback DOES
2364 // return three separate allocations, so this must branch, not assume. Discriminator: exact adjacency
2365 // FIRST (pure arithmetic, no dereference), and only then the NXI header magic -- so keys-16 is read
2366 // only once it is known to sit inside the same mapping, never off the front of a legacy blob.
2367 var merged: i64 = 0
2368 if kp != 0 { if ksz > 0 { let mbase: *u8 = (kp - 16) as *u8
2369 if h[5 + 8 * s] == kp + ksz { if h[7 + 8 * s] == h[5 + 8 * s] + h[6 + 8 * s] {
2370 if mbase[0] == (78 as u8) { if mbase[2] == (73 as u8) { merged = 1 } } } } } }
2371 if merged == 1 { sys_munmap((kp - 16) as *u8, 16 + ksz + h[6 + 8 * s] + h[8 + 8 * s]) } else { if kp != 0 { if ksz > 0 { sys_munmap(kp as *u8, ksz) } } }
2372 let dpp: i64 = h[3 + 8 * s]
2373 let dsz: i64 = h[4 + 8 * s]
2374 if dpp != 0 { if dsz > 0 { sys_munmap(dpp as *u8, dsz) } }
2375 let tp: i64 = h[5 + 8 * s]
2376 let tsz: i64 = h[6 + 8 * s]
2377 if merged == 0 { if tp != 0 { if tsz > 0 { sys_munmap(tp as *u8, tsz) } } }
2378 let pp: i64 = h[7 + 8 * s]
2379 let psz: i64 = h[8 + 8 * s]
2380 if merged == 0 { if pp != 0 { if psz > 0 { sys_munmap(pp as *u8, psz) } } }
2381 let lmp: i64 = h[1 + 8 * ns + s]
2382 if lmp != 0 { if dsz >= 0 { sys_munmap(lmp as *u8, dsz / 9 + 16) } }
2383 ss_aux_free_row(h, s) // AUX ROW (2026-09-14): d2k + term sample go with the live map
2384 h[1 + 8 * s] = 0
2385 h[3 + 8 * s] = 0
2386 h[5 + 8 * s] = 0
2387 h[7 + 8 * s] = 0
2388 h[1 + 8 * ns + s] = 0
2389 return 0
2390}
2391
2392func ss_close(h: *i64) -> i64 {
2393 if (h as i64) == 0 { return 0 }
2394 let ns: i64 = h[0]
2395 if ns < 0 { return 0 }
2396 var s: i64 = 0
2397 while s < ns { ss_close_seg(h, ns, s); s = s + 1 }
2398 h[0] = 0
2399 // QUERY SCRATCH ARENA -- six further mappings the handle OWNS, allocated in ss_open2's tail at
2400 // h[1+9ns]..h[6+9ns]. The first cut of ss_close missed these and the non-vacuous VmSize tooth caught it.
2401 // maxdocs is not stored anywhere, but it is DERIVABLE exactly as ss_open2 computed it (max over segments
2402 // of dsz/9+16, floor 16), so these free precisely with no handle-layout change. Sizes live in h[4+8q],
2403 // which the loop above never zeroes -- only pointers are nulled -- so this recompute is still valid here.
2404 var md: i64 = 16
2405 var q: i64 = 0
2406 while q < ns {
2407 let dz: i64 = h[4 + 8 * q]
2408 if dz / 9 + 16 > md { md = dz / 9 + 16 }
2409 q = q + 1
2410 }
2411 if h[1 + 9 * ns] != 0 { sys_munmap(h[1 + 9 * ns] as *u8, 64) }
2412 if h[2 + 9 * ns] != 0 { sys_munmap(h[2 + 9 * ns] as *u8, 32) }
2413 if h[3 + 9 * ns] != 0 { sys_munmap(h[3 + 9 * ns] as *u8, 8 * md) }
2414 if h[4 + 9 * ns] != 0 { sys_munmap(h[4 + 9 * ns] as *u8, 8 * md) }
2415 if h[5 + 9 * ns] != 0 { sys_munmap(h[5 + 9 * ns] as *u8, 8 * 68) }
2416 if h[6 + 9 * ns] != 0 { sys_munmap(h[6 + 9 * ns] as *u8, 8 * 68) }
2417 // RETAINED NAMES + TABLE (ss_open3, 2026-09-02): both live in the spare slots past the arena and are freed
2418 // here, so a handle that was reopened incrementally N times still owns exactly one copy of each.
2419 let nm9: i64 = h[SS3_SLOT_NAMES + 9 * ns]
2420 if nm9 != 0 { ss_manifest_free(nm9 as *i64, ns); h[SS3_SLOT_NAMES + 9 * ns] = 0 }
2421 if h[SS3_SLOT_TSEG + 9 * ns] != 0 {
2422 sys_munmap(h[SS3_SLOT_TSEG + 9 * ns] as *u8, 8 * h[SS3_SLOT_CAP + 9 * ns])
2423 sys_munmap(h[SS3_SLOT_TEO + 9 * ns] as *u8, 8 * h[SS3_SLOT_CAP + 9 * ns])
2424 h[SS3_SLOT_TSEG + 9 * ns] = 0
2425 h[SS3_SLOT_TEO + 9 * ns] = 0
2426 }
2427 // AUX ROW ARRAY (2026-09-14): every row's mappings were freed by ss_close_seg above; the array itself here.
2428 if h[SS3_SLOT_AUX + 9 * ns] != 0 { sys_munmap(h[SS3_SLOT_AUX + 9 * ns] as *u8, 8 * SS_AUXW * (ns + 1)); h[SS3_SLOT_AUX + 9 * ns] = 0 }
2429 sys_munmap(h as *u8, 8 * (9 * ns + 16))
2430 return 0
2431}
2432
2433func ss_open(prefix: *u8) -> *i64 {
2434 return ss_open2(prefix, 0)
2435}
2436// usemmap=1: file-BACKED read-only maps for the segment files instead of read-all (the mmap-serve rung --
2437// the big web shard uses this so the ~1.25M-doc RAM ceiling becomes disk-bound; shared across forked
2438// children + page-cache-persistent). The derived live-doc map + query scratch stay ANON (they are computed
2439// + mutated). Handle layout is IDENTICAL either way, so every ss_hget/ss_term/ss_phrase reader is unchanged.
2440// ---- LINEAR SHADOW TABLE (2026-08-01) --------------------------------------------------------
2441// Defined HERE rather than imported: nx_livemap_fast.nx imports this file, so importing it back
2442// would be circular. These are the same functions that gate proved equivalent, renamed ssl_*.
2443//
2444// They replace the per-key rescan of all newer segments in ss_open2's live-doc map loop -- a scan
2445// that was O(keys x segments) and therefore QUADRATIC in segment count: 4,454,778 key entries over
2446// 96 segments on the live web shard, and worsening with every crawler commit.
2447// EMPTY IS 0, AND SEGMENTS ARE STORED +1 (changed 2026-08-15). It used to be -1, which forced a
2448// 16,777,216-slot CLEAR LOOP on every open just to write a sentinel a fresh mmap could have provided
2449// for free -- 134 MB of dirty pages per open on a host measured at swap 715 permil, where the memory
2450// pressure costs more than the milliseconds. sys_mmap returns ZERO-FILLED memory, so with 0 as EMPTY
2451// the table is correctly initialised the instant it is mapped and the loop disappears.
2452// Segment indices are 0-based and must stay distinguishable from empty, hence the +1 on store and the
2453// -1 on read -- the same offset-by-one this file already uses for the doc-offset index below.
2454// ★CHOOSE THE SENTINEL THE ALLOCATOR ALREADY GIVES YOU: a sentinel that disagrees with fresh memory
2455// buys an O(capacity) initialisation for nothing.
2456const SSL_EMPTY: i64 = 0
2457// TOMBSTONE (rung L1b, 2026-09-02): a slot whose row VANISHED in a fold. Never produced by ssl_build; only
2458// ss3_remap_table writes it, and ss3_insert_new may re-fill it. Lookups probe THROUGH it.
2459const SSL_TOMB: i64 = 0 - 1
2460
2461func ssl_pow2(n: i64) -> i64 {
2462 var p: i64 = 16
2463 while p < n { p = p * 2 }
2464 return p
2465}
2466
2467func ssl_total_keys(h: *i64, ns: i64) -> i64 {
2468 var t: i64 = 0
2469 var s: i64 = 0
2470 while s < ns {
2471 let kb: *u8 = h[1 + 8 * s] as *u8
2472 let ksz: i64 = h[2 + 8 * s]
2473 if ksz >= 8 { t = t + ss_r32(kb, 4) }
2474 s = s + 1
2475 }
2476 return t
2477}
2478
2479func ssl_key_eq(h: *i64, seg: i64, eo: i64, kb2: *u8, eo2: i64) -> i64 {
2480 let kb: *u8 = h[1 + 8 * seg] as *u8
2481 let kl: i64 = ss_r32(kb, eo + 1)
2482 let kl2: i64 = ss_r32(kb2, eo2 + 1)
2483 if kl != kl2 { return 0 }
2484 var i: i64 = 0
2485 while i < kl {
2486 if kb[eo + 5 + i] != kb2[eo2 + 5 + i] { return 0 }
2487 i = i + 1
2488 }
2489 return 1
2490}
2491
2492func ssl_hash_entry(kb: *u8, eo: i64) -> i64 {
2493 let kl: i64 = ss_r32(kb, eo + 1)
2494 var hsh: i64 = 0x811c9dc5
2495 var i: i64 = 0
2496 while i < kl {
2497 hsh = hsh ^ (kb[eo + 5 + i] as i64)
2498 hsh = (hsh * SS_MAGIC_16777619) & 0xffffffff
2499 i = i + 1
2500 }
2501 return hsh
2502}
2503
2504// Same FNV-1a family as ssl_hash_entry -- SAME seed, SAME named SS_MAGIC_16777619 -- over the 4 bytes of
2505// a 32-bit doc-table offset. One hash idiom in this file, not two, and no fresh magic multiplier.
2506func ssl_hash_i32(v: i64) -> i64 {
2507 var hsh: i64 = 0x811c9dc5
2508 var i: i64 = 0
2509 while i < 4 {
2510 hsh = hsh ^ ((v >> (8 * i)) & 0xff)
2511 hsh = (hsh * SS_MAGIC_16777619) & 0xffffffff
2512 i = i + 1
2513 }
2514 return hsh
2515}
2516
2517// Walk segments NEWEST-FIRST and refuse to overwrite an occupied slot: the first writer for a key
2518// is therefore the newest segment holding it, with no segment-number comparison needed.
2519func ssl_build(h: *i64, ns: i64, tbl_seg: *i64, tbl_eo: *i64, cap: i64) -> i64 {
2520 // (no clear loop: SSL_EMPTY is 0 and sys_mmap hands back zero-filled pages, so the table arrives
2521 // initialised. This removed 16,777,216 slot-writes -- 134 MB touched -- from every open.)
2522 let mask: i64 = cap - 1
2523 var inserted: i64 = 0
2524 var s: i64 = ns - 1
2525 while s >= 0 {
2526 let kb: *u8 = h[1 + 8 * s] as *u8
2527 let ksz: i64 = h[2 + 8 * s]
2528 if ksz >= 8 {
2529 let m: i64 = ss_r32(kb, 4)
2530 var e: i64 = 0
2531 while e < m {
2532 let eo: i64 = 8 + 4 * m + ss_r32(kb, 8 + 4 * e)
2533 let kl: i64 = ss_r32(kb, eo + 1)
2534 if kl < 500 {
2535 var slot: i64 = ssl_hash_entry(kb, eo) & mask
2536 var placed: i64 = 0
2537 while placed == 0 {
2538 if tbl_seg[slot] == SSL_EMPTY {
2539 tbl_seg[slot] = s + 1
2540 tbl_eo[slot] = eo
2541 inserted = inserted + 1
2542 placed = 1
2543 } else {
2544 if ssl_key_eq(h, tbl_seg[slot] - 1, tbl_eo[slot], kb, eo) == 1 { placed = 1 } else { slot = (slot + 1) & mask }
2545 }
2546 }
2547 }
2548 e = e + 1
2549 }
2550 }
2551 s = s - 1
2552 }
2553 return inserted
2554}
2555
2556func ssl_lookup(h: *i64, tbl_seg: *i64, tbl_eo: *i64, cap: i64, kb: *u8, eo: i64) -> i64 {
2557 let mask: i64 = cap - 1
2558 var slot: i64 = ssl_hash_entry(kb, eo) & mask
2559 var guard: i64 = 0
2560 while guard < cap {
2561 if tbl_seg[slot] == SSL_EMPTY { return 0 - 1 }
2562 if tbl_seg[slot] != SSL_TOMB { if ssl_key_eq(h, tbl_seg[slot] - 1, tbl_eo[slot], kb, eo) == 1 { return tbl_seg[slot] - 1 } }
2563 slot = (slot + 1) & mask
2564 guard = guard + 1
2565 }
2566 return 0 - 1
2567}
2568
2569// ---- OPT-IN OPEN TRACE (2026-08-15) --------------------------------------------------------------
2570// WHY: opening the live web shard was MEASURED at ~9.8-10.0s (STARTUP warm_ms=9796/9976 in the docportal,
2571// and REQ done parent_us=9783316 when the same open runs on the accept path). That single number is the
2572// root cause of the /search 503s -- but "the open is slow" is not a diagnosis, and this function has
2573// THREE candidate costs: the per-segment file loads, ssl_build over ~4.45M key entries, and the
2574// per-segment live-doc map loop. I have guessed wrong about this subsystem repeatedly today, so the
2575// phases get measured before anything is optimised.
2576// DEFAULT OFF: ss_trace is 0 unless a caller opts in, so every production path is byte-for-byte
2577// unchanged in behaviour and pays one integer compare per phase. A TRACE THAT IS ALWAYS ON BECOMES
2578// OUTPUT NOBODY READS AND A COST EVERYBODY PAYS.
2579static ss_trace: i64
2580func ss_open_trace(v: i64) -> i64 { ss_trace = v; return 0 }
2581func ss_tr(lbl: *u8, us: i64) -> i64 {
2582 if ss_trace != 1 { return 0 }
2583 let b: *u8 = sys_mmap(128)
2584 var n: i64 = 0
2585 n = ss_cat(b, n, "SSOPEN " as *u8)
2586 n = ss_cat(b, n, lbl)
2587 n = ss_cat(b, n, " us=" as *u8)
2588 var v: i64 = us
2589 if v == 0 { b[n] = 48 as u8; n = n + 1 } else {
2590 var pw: i64 = 1
2591 while v / pw >= 10 { pw = pw * 10 }
2592 while pw > 0 { b[n] = (48 + ((v / pw) % 10)) as u8; n = n + 1; pw = pw / 10 }
2593 }
2594 b[n] = 10 as u8; n = n + 1
2595 sys_write(1, b, n)
2596 sys_munmap(b, 128)
2597 return 0
2598}
2599
2600// SEGMENT-ABSENT ANNOUNCE (2026-09-02, debt 1788361379). Printed by ss_open2 when a manifest row's .docs did
2601// NOT load (dsz == 0: absent, empty, or mid-rename by a writer). The segment then contributes NO live-doc
2602// marks -- absent docs cannot be served anyway, so skipping is the safe direction -- and the condition is
2603// VISIBLE in the daemon's own log instead of only in its symptoms. MEASURED on the serving daemon: the
2604// unguarded loop wrote past a one-page live map (lm is sized from dsz, the marks were bounded by the .post
2605// doc count), which produced one SIGSEGV in the accept-path refresh and then a respawn that spun 15.9h of
2606// CPU inside this function with the listen port never opened. Always written (fd 2), not gated on ss_trace.
2607func ss_open_seg_absent(segname: *u8) -> i64 {
2608 let b: *u8 = sys_mmap(512)
2609 var n: i64 = 0
2610 n = ss_cat(b, n, "SSOPEN SKIP seg=" as *u8)
2611 n = ss_cat(b, n, segname)
2612 n = ss_cat(b, n, " docs-absent: index present, .docs did not load -- segment contributes no live docs (debt 1788361379)\n" as *u8)
2613 sys_write(2, b, n)
2614 sys_munmap(b, 512)
2615 return 0
2616}
2617
2618
2619// ================= INCREMENTAL, WARM-PRESERVING REOPEN -- search plan rung L1, ss_open_incr (2026-09-02) =================
2620// WHY: the docportal parent reopens the web shard on every manifest change, between accept() and fork(), and a
2621// reopen rebuilt ssl_build plus the live-doc map over EVERY segment -- 2.45 GB of page touches on a swapped array,
2622// measured as a 10.9 s parent stall against a 785 ms handler while the listen socket queued. Segments are IMMUTABLE
2623// and append-only, so when the new manifest is a strict PREFIX-EXTENSION of the previous one (same names, same
2624// .docs bytes on disk) everything derived for the old rows is still true except ONE thing: a key that a NEW row
2625// re-puts or tombstones is no longer live in its old row. ss_open3 therefore MOVES the old rows' mappings, live maps
2626// and the key->newest-segment table out of prev, loads only the new rows, inserts their keys newest-first, clears
2627// the old marks they shadow, and marks the new rows exactly as a FULL open would. EQUIVALENT BY CONSTRUCTION and
2628// PROVEN, not assumed: nx_segopen_incr_gate diffs every live map and every table answer against a FULL open of the
2629// same manifest and both must be byte-identical.
2630// FALLS BACK TO FULL, ANNOUNCED, on: no prev / prev empty / prev opened without retain (no table) / no names /
2631// manifest shrank / a leading name differs / a leading row's .docs size on disk differs / the table would pass half
2632// load. The FULL path is the pre-existing code, byte-for-byte in behaviour, so every one-shot caller of ss_open2
2633// (compact, pagerank, lexstat, crawl-step, segbloom, the gates) is unchanged.
2634// COST ENVELOPE (rule: name what is retained): the names array (8*ns+64 plus 128*ns bytes) is retained on every
2635// open; the two table halves (2 x cap x 8 B, cap = pow2(2 x distinct keys + 16), ~268 MB virtual for the 4.45M-key
2636// web shard) are retained ONLY when retain == 1 -- the daemon's parent refresh -- and exactly ONE copy exists at a
2637// time because ss3_move transfers it; ss_close frees both. A one-shot open (retain 0) frees the table as before.
2638// SLOTS: the handle is allocated 9*ns+16 wide and slots 7..12 past the arena were spare; they are the handle's own
2639// storage now (a handle built by anything other than ss_open2/ss_open3 has never existed in this tree).
2640
2641func ss3_streq(a: *u8, b: *u8) -> i64 {
2642 var i: i64 = 0
2643 while a[i] != (0 as u8) { if b[i] != a[i] { return 0 } i = i + 1 }
2644 if b[i] == (0 as u8) { return 1 }
2645 return 0
2646}
2647// READERS (pure, fork-safe): the mode the handle was built by, a manifest row's name, and the retained table.
2648func ss_open_mode(h: *i64) -> i64 {
2649 if (h as i64) == 0 { return 0 - 1 }
2650 let ns: i64 = h[0]
2651 if ns <= 0 { return 0 - 1 }
2652 return h[SS3_SLOT_MODE + 9 * ns]
2653}
2654func ss_open_segname(h: *i64, s: i64) -> *u8 {
2655 if (h as i64) == 0 { return 0 as *u8 }
2656 let ns: i64 = h[0]
2657 if s < 0 { return 0 as *u8 }
2658 if s >= ns { return 0 as *u8 }
2659 let names: *i64 = h[SS3_SLOT_NAMES + 9 * ns] as *i64
2660 if (names as i64) == 0 { return 0 as *u8 }
2661 return names[s] as *u8
2662}
2663func ss_open_table(h: *i64, outs: *i64) -> i64 {
2664 outs[0] = 0
2665 outs[1] = 0
2666 outs[2] = 0
2667 outs[3] = 0
2668 if (h as i64) == 0 { return 0 }
2669 let ns: i64 = h[0]
2670 if ns <= 0 { return 0 }
2671 if h[SS3_SLOT_TSEG + 9 * ns] == 0 { return 0 }
2672 outs[0] = h[SS3_SLOT_TSEG + 9 * ns]
2673 outs[1] = h[SS3_SLOT_TEO + 9 * ns]
2674 outs[2] = h[SS3_SLOT_CAP + 9 * ns]
2675 outs[3] = h[SS3_SLOT_INS + 9 * ns]
2676 return 1
2677}
2678// ANNOUNCE on fd 2, always (a reopen is a per-manifest-change event, bounded, and the daemon's stderr is its log):
2679// SSOPEN INCR reused= built= shadowed= us= or SSOPEN FULL reason=<why> ns= pn= <third>= us=
2680func ss3_ann(kind: *u8, k1: *u8, v1: i64, k2: *u8, v2: i64, k3: *u8, v3: i64, reason: *u8, us: i64) -> i64 {
2681 let b: *u8 = sys_mmap(SS3_ANN_CAP)
2682 var n: i64 = ss_cat(b, 0, "SSOPEN " as *u8)
2683 n = ss_cat(b, n, kind)
2684 if (reason as i64) != 0 { n = ss_cat(b, n, " reason=" as *u8); n = ss_cat(b, n, reason) }
2685 n = ss_cat(b, n, " " as *u8)
2686 n = ss_cat(b, n, k1)
2687 n = ss_catn(b, n, v1)
2688 n = ss_cat(b, n, " " as *u8)
2689 n = ss_cat(b, n, k2)
2690 n = ss_catn(b, n, v2)
2691 n = ss_cat(b, n, " " as *u8)
2692 n = ss_cat(b, n, k3)
2693 n = ss_catn(b, n, v3)
2694 n = ss_cat(b, n, " us=" as *u8)
2695 n = ss_catn(b, n, us)
2696 b[n] = 10 as u8
2697 n = n + 1
2698 sys_write(2, b, n)
2699 sys_munmap(b, SS3_ANN_CAP)
2700 return 0
2701}
2702func ss3_docs_size(prefix: *u8, segname: *u8) -> i64 {
2703 let p: *u8 = sys_mmap(SS3_PATHCAP)
2704 var o: i64 = ss_cat(p, 0, prefix)
2705 o = ss_cat(p, o, segname)
2706 o = ss_cat(p, o, ".docs" as *u8)
2707 p[o] = 0 as u8
2708 let stb: *u8 = sys_mmap(SS3_STATBUF)
2709 var sz: i64 = 0 - 1
2710 if sys_fstatat(p, stb) == 0 { let szp: *i64 = ((stb as i64) + SS3_ST_SIZE_OFF) as *i64; sz = szp[0] }
2711 sys_munmap(p, SS3_PATHCAP)
2712 sys_munmap(stb, SS3_STATBUF)
2713 return sz
2714}
2715// ADMISSION (rung L1b, generalised 2026-09-02 from the prefix-only rule shipped the same day). The live
2716// compactor is a RANGE FOLD: a contiguous run of rows is merged and replaced IN PLACE, concurrent ships are
2717// appended after it. So the new manifest is always common PREFIX + middle of NEW rows + common SUFFIX of
2718// survivors, and the prefix-only rule fell back to FULL on every fold (measured live: 3 FULL to 4 INCR).
2719// reuse[s] = prev row index for new row s, or -1 when s must be loaded. Returns the reused count; 0 = FULL.
2720// Names AND .docs size on disk must match for a row to be reused (segments are immutable; a same-named
2721// rewrite is refused by the size). Every refusal is announced when a prev was offered.
2722func ss3_admit(prefix: *u8, segs: *i64, ns: i64, prev: *i64, reuse: *i64, t0: i64) -> i64 {
2723 var i: i64 = 0
2724 while i < ns { reuse[i] = 0 - 1; i = i + 1 }
2725 if (prev as i64) == 0 { return 0 }
2726 let pn: i64 = prev[0]
2727 if pn <= 0 { ss3_ann("FULL" as *u8, "ns=" as *u8, ns, "pn=" as *u8, pn, "s0=" as *u8, 0, "prev-empty" as *u8, sys_now_us() - t0); return 0 }
2728 if prev[SS3_SLOT_TSEG + 9 * pn] == 0 { ss3_ann("FULL" as *u8, "ns=" as *u8, ns, "pn=" as *u8, pn, "s0=" as *u8, 0, "no-table" as *u8, sys_now_us() - t0); return 0 }
2729 let names: *i64 = prev[SS3_SLOT_NAMES + 9 * pn] as *i64
2730 if (names as i64) == 0 { ss3_ann("FULL" as *u8, "ns=" as *u8, ns, "pn=" as *u8, pn, "s0=" as *u8, 0, "no-names" as *u8, sys_now_us() - t0); return 0 }
2731 // common prefix: same name and same .docs bytes on disk
2732 var pre: i64 = 0
2733 var go: i64 = 1
2734 while go == 1 {
2735 if pre >= ns { go = 0 } else { if pre >= pn { go = 0 } else {
2736 if ss3_streq(segs[pre] as *u8, names[pre] as *u8) == 0 { go = 0 } else {
2737 if ss3_docs_size(prefix, names[pre] as *u8) != prev[4 + 8 * pre] { go = 0 } else { reuse[pre] = pre; pre = pre + 1 }
2738 }
2739 } }
2740 }
2741 // common suffix, never overlapping the prefix on either side
2742 var suf: i64 = 0
2743 go = 1
2744 while go == 1 {
2745 let ni: i64 = ns - 1 - suf
2746 let oi: i64 = pn - 1 - suf
2747 if ni < pre { go = 0 } else { if oi < pre { go = 0 } else {
2748 if ss3_streq(segs[ni] as *u8, names[oi] as *u8) == 0 { go = 0 } else {
2749 if ss3_docs_size(prefix, names[oi] as *u8) != prev[4 + 8 * oi] { go = 0 } else { reuse[ni] = oi; suf = suf + 1 }
2750 }
2751 } }
2752 }
2753 if pre + suf == 0 { ss3_ann("FULL" as *u8, "ns=" as *u8, ns, "pn=" as *u8, pn, "s0=" as *u8, 0, "no-common-rows" as *u8, sys_now_us() - t0); return 0 }
2754 return pre + suf
2755}
2756// MOVE every reused row (blob mappings + live map) and the table out of prev into h; remap[o] = new index of
2757// prev row o, or -1 when it vanished in the fold. prev keeps its header, dsz values and arena, so ss_close(prev)
2758// frees nothing h now owns.
2759func ss3_move(h: *i64, ns: i64, prev: *i64, reuse: *i64, remap: *i64) -> i64 {
2760 let pn: i64 = prev[0]
2761 var o: i64 = 0
2762 while o < pn { remap[o] = 0 - 1; o = o + 1 }
2763 var moved: i64 = 0
2764 var s: i64 = 0
2765 while s < ns {
2766 let old: i64 = reuse[s]
2767 if old >= 0 {
2768 var k: i64 = 1
2769 while k <= 8 { h[k + 8 * s] = prev[k + 8 * old]; k = k + 1 }
2770 h[1 + 8 * ns + s] = prev[1 + 8 * pn + old]
2771 // AUX ROW (2026-09-14) travels with its segment, ownership moved (prev's row zeroed)
2772 let axn: *i64 = ss_aux(h)
2773 let axp: *i64 = ss_aux(prev)
2774 if (axn as i64) != 0 { if (axp as i64) != 0 {
2775 var aw: i64 = 0
2776 while aw < SS_AUXW { axn[SS_AUXW * s + aw] = axp[SS_AUXW * old + aw]; axp[SS_AUXW * old + aw] = 0; aw = aw + 1 }
2777 } }
2778 prev[1 + 8 * old] = 0
2779 prev[3 + 8 * old] = 0
2780 prev[5 + 8 * old] = 0
2781 prev[7 + 8 * old] = 0
2782 prev[1 + 8 * pn + old] = 0
2783 remap[old] = s
2784 moved = moved + 1
2785 }
2786 s = s + 1
2787 }
2788 h[SS3_SLOT_TSEG + 9 * ns] = prev[SS3_SLOT_TSEG + 9 * pn]
2789 h[SS3_SLOT_TEO + 9 * ns] = prev[SS3_SLOT_TEO + 9 * pn]
2790 h[SS3_SLOT_CAP + 9 * ns] = prev[SS3_SLOT_CAP + 9 * pn]
2791 h[SS3_SLOT_INS + 9 * ns] = prev[SS3_SLOT_INS + 9 * pn]
2792 prev[SS3_SLOT_TSEG + 9 * pn] = 0
2793 prev[SS3_SLOT_TEO + 9 * pn] = 0
2794 prev[SS3_SLOT_CAP + 9 * pn] = 0
2795 prev[SS3_SLOT_INS + 9 * pn] = 0
2796 let pnames: *i64 = prev[SS3_SLOT_NAMES + 9 * pn] as *i64
2797 if (pnames as i64) != 0 { ss_manifest_free(pnames, pn) }
2798 prev[SS3_SLOT_NAMES + 9 * pn] = 0
2799 return moved
2800}
2801// REMAP the moved table from prev row indices to new ones; entries of vanished rows become TOMBSTONES.
2802// Returns the tombstone count (subtracted from the inserted count by the caller). O(cap) over resident
2803// anonymous pages -- no file page is touched.
2804func ss3_remap_table(tseg: *i64, cap: i64, remap: *i64, pn: i64) -> i64 {
2805 var tomb: i64 = 0
2806 var i: i64 = 0
2807 while i < cap {
2808 let v: i64 = tseg[i]
2809 if v > 0 {
2810 let old: i64 = v - 1
2811 var nw: i64 = 0 - 1
2812 if old < pn { nw = remap[old] }
2813 if nw >= 0 { tseg[i] = nw + 1 } else { tseg[i] = SSL_TOMB; tomb = tomb + 1 }
2814 }
2815 i = i + 1
2816 }
2817 return tomb
2818}
2819func ssl_total_keys_range(h: *i64, a: i64, b: i64) -> i64 {
2820 var t: i64 = 0
2821 var s: i64 = a
2822 while s < b {
2823 let kb: *u8 = h[1 + 8 * s] as *u8
2824 let ksz: i64 = h[2 + 8 * s]
2825 if ksz >= 8 { t = t + ss_r32(kb, 4) }
2826 s = s + 1
2827 }
2828 return t
2829}
2830func ssl_total_keys_mask(h: *i64, ns: i64, newmask: *i64) -> i64 {
2831 var t: i64 = 0
2832 var s: i64 = 0
2833 while s < ns {
2834 if newmask[s] == 1 {
2835 let kb: *u8 = h[1 + 8 * s] as *u8
2836 let ksz: i64 = h[2 + 8 * s]
2837 if ksz >= 8 { t = t + ss_r32(kb, 4) }
2838 }
2839 s = s + 1
2840 }
2841 return t
2842}
2843// CLEAR the live mark of the reused-row entry (oseg, oeo) that a new row has just shadowed. The doc index comes
2844// from the same doc-offset inverse index the FULL mark loop builds, built lazily ONCE per row a new key touches
2845// (dhk/dhv/dhc are per-row caches owned by ss3_insert_new). Returns 1 iff a set mark was cleared.
2846func ss3_clear_mark(h: *i64, ns: i64, oseg: i64, oeo: i64, dhk: *i64, dhv: *i64, dhc: *i64) -> i64 {
2847 let okb: *u8 = h[1 + 8 * oseg] as *u8
2848 let okl: i64 = ss_r32(okb, oeo + 1)
2849 let ovof: i64 = ss_r32(okb, oeo + 5 + okl)
2850 let ioff: i64 = ovof - 9 - okl
2851 let pb: *u8 = h[7 + 8 * oseg] as *u8
2852 let psz: i64 = h[8 + 8 * oseg]
2853 let dsz: i64 = h[4 + 8 * oseg]
2854 let lm: *u8 = h[1 + 8 * ns + oseg] as *u8
2855 if (lm as i64) == 0 { return 0 }
2856 if psz < 8 { return 0 }
2857 if dhk[oseg] == 0 {
2858 var nd: i64 = ss_r32(pb, 4)
2859 if dsz <= 0 { nd = 0 }
2860 if 8 + 4 * nd > psz { nd = (psz - 8) / 4 }
2861 let cap: i64 = ssl_pow2(nd * 2 + 16)
2862 let k: *i64 = sys_mmap(8 * cap) as *i64
2863 let v: *i64 = sys_mmap(8 * cap) as *i64
2864 var ci: i64 = 0
2865 while ci < cap { k[ci] = 0; ci = ci + 1 }
2866 var i: i64 = 0
2867 while i < nd {
2868 let dv0: i64 = ss_r32(pb, 8 + 4 * i)
2869 var slot: i64 = ssl_hash_i32(dv0) & (cap - 1)
2870 var placed: i64 = 0
2871 while placed == 0 {
2872 if k[slot] == 0 { k[slot] = dv0 + 1; v[slot] = i; placed = 1 }
2873 else { if k[slot] == dv0 + 1 { placed = 1 } else { slot = (slot + 1) & (cap - 1) } }
2874 }
2875 i = i + 1
2876 }
2877 dhk[oseg] = k as i64
2878 dhv[oseg] = v as i64
2879 dhc[oseg] = cap
2880 }
2881 let k2: *i64 = dhk[oseg] as *i64
2882 let v2: *i64 = dhv[oseg] as *i64
2883 let mask2: i64 = dhc[oseg] - 1
2884 var slot2: i64 = ssl_hash_i32(ioff) & mask2
2885 var looking: i64 = 1
2886 while looking == 1 {
2887 if k2[slot2] == 0 { looking = 0 }
2888 else {
2889 if k2[slot2] == ioff + 1 {
2890 let idx: i64 = v2[slot2]
2891 if idx < dsz / 9 + 16 { if lm[idx] == (1 as u8) { lm[idx] = 0 as u8; return 1 } }
2892 looking = 0
2893 } else { slot2 = (slot2 + 1) & mask2 }
2894 }
2895 }
2896 return 0
2897}
2898// INSERT the keys of every NEW row (newmask[s]==1) into the remapped table, newest row first. Probing runs
2899// THROUGH tombstones (a key may sit past one) and remembers the first tombstone as the placement slot. On an
2900// equal key from row r: r < s means r is a REUSED row the new row now shadows (new rows older than s are not
2901// inserted yet, descending order) -> clear r's mark and overwrite; r > s means a newer new row already holds
2902// the key -> keep. This reproduces ssl_build's newest-wins over the new manifest order exactly. outs[0] +=
2903// slots newly filled, outs[1] += tombstones re-filled. Returns the number of reused-row marks cleared.
2904func ss3_insert_new(h: *i64, ns: i64, newmask: *i64, tseg: *i64, teo: *i64, cap: i64, outs: *i64) -> i64 {
2905 let mask: i64 = cap - 1
2906 var shadowed: i64 = 0
2907 let dhk: *i64 = sys_mmap(8 * (ns + 1)) as *i64
2908 let dhv: *i64 = sys_mmap(8 * (ns + 1)) as *i64
2909 let dhc: *i64 = sys_mmap(8 * (ns + 1)) as *i64
2910 var s: i64 = ns - 1
2911 while s >= 0 {
2912 if newmask[s] == 1 {
2913 let kb: *u8 = h[1 + 8 * s] as *u8
2914 let ksz: i64 = h[2 + 8 * s]
2915 if ksz >= 8 {
2916 var m: i64 = ss_r32(kb, 4)
2917 if 8 + 4 * m > ksz { m = (ksz - 8) / 4 }
2918 var e: i64 = 0
2919 while e < m {
2920 let eo: i64 = 8 + 4 * m + ss_r32(kb, 8 + 4 * e)
2921 let kl: i64 = ss_r32(kb, eo + 1)
2922 if kl < SS3_KEYMAX {
2923 var slot: i64 = ssl_hash_entry(kb, eo) & mask
2924 var tombslot: i64 = 0 - 1
2925 var placed: i64 = 0
2926 while placed == 0 {
2927 if tseg[slot] == SSL_EMPTY {
2928 if tombslot >= 0 { tseg[tombslot] = s + 1; teo[tombslot] = eo; outs[1] = outs[1] + 1 }
2929 else { tseg[slot] = s + 1; teo[slot] = eo; outs[0] = outs[0] + 1 }
2930 placed = 1
2931 } else {
2932 if tseg[slot] == SSL_TOMB {
2933 if tombslot < 0 { tombslot = slot }
2934 slot = (slot + 1) & mask
2935 } else {
2936 if ssl_key_eq(h, tseg[slot] - 1, teo[slot], kb, eo) == 1 {
2937 let r: i64 = tseg[slot] - 1
2938 if r < s {
2939 shadowed = shadowed + ss3_clear_mark(h, ns, r, teo[slot], dhk, dhv, dhc)
2940 tseg[slot] = s + 1
2941 teo[slot] = eo
2942 }
2943 placed = 1
2944 } else { slot = (slot + 1) & mask }
2945 }
2946 }
2947 }
2948 }
2949 e = e + 1
2950 }
2951 }
2952 }
2953 s = s - 1
2954 }
2955 s = 0
2956 while s < ns { if dhk[s] != 0 { sys_munmap(dhk[s] as *u8, 8 * dhc[s]); sys_munmap(dhv[s] as *u8, 8 * dhc[s]) } s = s + 1 }
2957 sys_munmap(dhk as *u8, 8 * (ns + 1))
2958 sys_munmap(dhv as *u8, 8 * (ns + 1))
2959 sys_munmap(dhc as *u8, 8 * (ns + 1))
2960 return shadowed
2961}
2962// count tombstones still standing after the insert: every one is a vanished key NO new row carries, i.e. its
2963// second-newest entry (somewhere in a reused row) became live again and the table cannot name it -> FULL.
2964func ss3_tomb_count(tseg: *i64, cap: i64) -> i64 {
2965 var t: i64 = 0
2966 var i: i64 = 0
2967 while i < cap { if tseg[i] == SSL_TOMB { t = t + 1 } i = i + 1 }
2968 return t
2969}
2970// SEGMENT LOAD for manifest rows [a,b), extracted from ss_open2 2026-09-02: the FULL open loads every row,
2971// the INCR open loads only the rows prev does not already hold. Same body, one copy.
2972func ss3_load_range(prefix: *u8, segs: *i64, h: *i64, a: i64, b: i64, usemmap: i64, dp: *u8, dszp: *i64, aouts: *i64) -> i64 {
2973 var s: i64 = a
2974 while s < b {
2975 // dp hoisted above the loop (seq905)
2976 var o: i64 = 0
2977 o = ss_cat(dp, o, prefix)
2978 o = ss_cat(dp, o, segs[s] as *u8)
2979 o = ss_cat(dp, o, ".docs" as *u8)
2980 dp[o] = 0 as u8
2981 // dszp hoisted above the loop (seq905)
2982 h[3 + 8 * s] = ss_loadfile(dp, dszp, usemmap) as i64
2983 h[4 + 8 * s] = dszp[0]
2984 // index blobs: merged .idx slices (or legacy 3-file fallback)
2985 ss_load_aux2(prefix, segs[s] as *u8, aouts, usemmap)
2986 h[1 + 8 * s] = aouts[0]
2987 h[2 + 8 * s] = aouts[1]
2988 h[5 + 8 * s] = aouts[2]
2989 h[6 + 8 * s] = aouts[3]
2990 h[7 + 8 * s] = aouts[4]
2991 h[8 + 8 * s] = aouts[5]
2992 s = s + 1
2993 }
2994 return 0
2995}
2996
2997// ss_open2 keeps its signature and its behaviour: a FULL open with the table freed at the end (retain 0).
2998func ss_open2(prefix: *u8, usemmap: i64) -> *i64 {
2999 return ss_open3(prefix, usemmap, 0 as *i64, 0)
3000}
3001// ss_open_incr IS the incremental open the search plan rung L1 names (contract symbol ss_open_incr): reuse prev where the
3002// manifest is a prefix-extension, retain the table so the NEXT reopen can do the same. The daemon parent refresh calls this.
3003func ss_open_incr(prefix: *u8, usemmap: i64, prev: *i64) -> *i64 {
3004 return ss_open3(prefix, usemmap, prev, 1)
3005}
3006func ss_open3(prefix: *u8, usemmap: i64, prev: *i64, retain: i64) -> *i64 {
3007 let t_open: i64 = sys_now_us()
3008 let sp: *i64 = sys_mmap(8) as *i64
3009 let ns: i64 = ss_manifest_dyn(prefix, sp)
3010 let segs: *i64 = sp[0] as *i64
3011 let h: *i64 = sys_mmap(8 * (9 * ns + 16)) as *i64
3012 h[0] = ns
3013 // AUX ROWS (2026-09-14): one zero-filled row per segment; filled in the live-map loop, moved by ss3_move,
3014 // freed by ss_close_seg/ss_close. sys_mmap returns zero pages, so an unfilled row reads as absent.
3015 h[SS3_SLOT_AUX + 9 * ns] = sys_mmap(8 * SS_AUXW * (ns + 1)) as i64
3016 // INCREMENTAL ADMISSION (rungs L1 + L1b): reuse[s] names the prev row that new row s already holds warm
3017 // (common prefix and common suffix of the two manifests), -1 = load it. nreuse == 0 means FULL.
3018 let reuse: *i64 = sys_mmap(8 * (ns + 1)) as *i64
3019 let newmask: *i64 = sys_mmap(8 * (ns + 1)) as *i64
3020 var nreuse: i64 = ss3_admit(prefix, segs, ns, prev, reuse, t_open)
3021 var s0: i64 = 0
3022 if nreuse > 0 { s0 = 1 }
3023 var nm0: i64 = 0
3024 while nm0 < ns { if reuse[nm0] < 0 { newmask[nm0] = 1 } else { newmask[nm0] = 0 } nm0 = nm0 + 1 }
3025 let aouts: *i64 = sys_mmap(8 * 8) as *i64
3026 // HOISTED (seq905): dp/dszp were allocated INSIDE the per-segment loop, so opening an N-segment store
3027 // leaked N page-sized scratch mappings PER OPEN -- and nothing could ever reclaim them because they are
3028 // not reachable from the returned handle, so even a correct ss_close could not help. One allocation,
3029 // reused per segment. Same lesson nx_treepack banked as "buffers hoisted, no mmap in inner loops".
3030 let dp: *u8 = sys_mmap(512)
3031 let dszp: *i64 = sys_mmap(16) as *i64
3032 var s: i64 = 0
3033 while s < ns { if newmask[s] == 1 { ss3_load_range(prefix, segs, h, s, s + 1, usemmap, dp, dszp, aouts) } s = s + 1 }
3034 // TABLE-LOAD CHECK before anything is moved out of prev: the retained table was sized 2x entries at its last
3035 // FULL build; past SS3_LOAD_NUM/SS3_LOAD_DEN load open addressing degrades and
3036 // the honest answer is a FULL rebuild -- announced, the rows this open loaded released via ss_close_seg,
3037 // and prev untouched so the caller still owns a complete handle.
3038 var vanished: i64 = 0
3039 var tombs: i64 = 0
3040 let remap: *i64 = sys_mmap(8 * (ns + 1 + 256)) as *i64
3041 if s0 > 0 {
3042 let pn9: i64 = prev[0]
3043 let newk: i64 = ssl_total_keys_mask(h, ns, newmask)
3044 if (prev[SS3_SLOT_INS + 9 * pn9] + newk) * SS3_LOAD_DEN > prev[SS3_SLOT_CAP + 9 * pn9] * SS3_LOAD_NUM {
3045 ss3_ann("FULL" as *u8, "ns=" as *u8, ns, "pn=" as *u8, pn9, "newkeys=" as *u8, newk, "table-load" as *u8, sys_now_us() - t_open)
3046 var rs: i64 = 0
3047 while rs < ns { if newmask[rs] == 1 { ss_close_seg(h, ns, rs) } newmask[rs] = 1; rs = rs + 1 }
3048 s0 = 0
3049 nreuse = 0
3050 ss3_load_range(prefix, segs, h, 0, ns, usemmap, dp, dszp, aouts)
3051 }
3052 }
3053 if s0 > 0 {
3054 let pn8: i64 = prev[0]
3055 // the remap array is sized for prev's rows; pn8 can exceed ns (a fold shrinks the manifest)
3056 var rmp: *i64 = remap
3057 if pn8 > ns + 256 { rmp = sys_mmap(8 * (pn8 + 1)) as *i64 }
3058 ss3_move(h, ns, prev, reuse, rmp)
3059 vanished = pn8 - nreuse
3060 tombs = ss3_remap_table(h[SS3_SLOT_TSEG + 9 * ns] as *i64, h[SS3_SLOT_CAP + 9 * ns], rmp, pn8)
3061 }
3062 // PREFETCH THE INDEX BLOBS BEFORE THE TWO WHOLE-CORPUS WALKS BELOW (2026-09-01).
3063 // THE SAME PROVEN IDIOM, ONE LAYER DOWN. nx_docportal_search_seg's PASS 0 already batches
3064 // madvise(MADV_WILLNEED) over candidate doc extents before its serial tf/prox walk, and measured
3065 // 1350 candidates at 8.7s cold versus 375ms warm -- the delta IS the serial fault chain. ssl_build
3066 // and the live-doc map loop below are that same shape one level lower: this function's OWN trace
3067 // measured them at SSOPEN livemap us=9953625 of TOTAL us=12346598, explicitly because they are
3068 // "tens of millions of page touches" served ONE FAULT AT A TIME off a contended array.
3069 // WHY IT MATTERS HERE: this open runs between accept() and fork() in nx_docportal_admin_daemon, so
3070 // its cost is charged to a live request. MEASURED 2026-09-01 on the public SERP: wall 11142ms
3071 // against a handler that reported only 785ms -- the daemon's own timer brackets the forked CHILD
3072 // and is structurally blind to the parent's block, which is why this stayed unattributed for weeks.
3073 // CORRECTNESS-NEUTRAL BY CONSTRUCTION: madvise is ADVISORY and computes nothing. The loops below
3074 // read the identical bytes, just warm, so this cannot make any verdict wrong -- only slower or
3075 // faster. The return value is deliberately ignored, as at every other WILLNEED site in this estate.
3076 // GATED ON usemmap, DELIBERATELY: that flag is this codebase's "large FILE-BACKED shard" signal.
3077 // ss_open() is ss_open2(prefix, 0) (read-all into anonymous memory, already resident), so the DEFAULT
3078 // open path is untouched and no small store pays for readahead it cannot use.
3079 // ⚠SCOPE MEASURED, NOT ASSUMED -- AND WIDER THAN ONE CALLER. A first draft of this comment claimed
3080 // "dss_open_maybe_cached passes 1 and nothing else does"; a grep REFUTED that before it shipped.
3081 // Callers passing 1 (FLOOR, not a total -- the scan returned corpus_complete=0 BUDGET-EXCEEDED, so
3082 // there may be more): nx_docportal_search_seg (the serving daemon), nx_web_shard_compact,
3083 // nx_pagerank_build, nx_doc_lexstat, nx_web_crawl_step, nx_segbloom, nx_livemap_fast_gate,
3084 // nx_docportal_search_sticky_gate.
3085 // THAT WIDER SET IS ACCEPTABLE AND MOSTLY BENEFICIAL: every one of them opens a large shard and then
3086 // WALKS it (compact, pagerank, lexstat, crawl-step, the livemap gate), which is precisely the access
3087 // pattern batched readahead exists for. The one shape that pays without gaining is an organ that
3088 // opens a big shard and reads only a little -- the sticky gate is the candidate -- and its cost is
3089 // bounded readahead the kernel is free to ignore, never a wrong answer.
3090 // ★A HEADER IS NOT A MEASUREMENT: this comment names the callers a grep FOUND, with the envelope
3091 // that says the list is a floor, rather than restating an assumption as a scope.
3092 // TERMS BLOB DELIBERATELY NOT PREFETCHED: ss_terms_find is a BINARY search done at QUERY time, not
3093 // during the open, so warming it here would be readahead this function cannot show a cost for.
3094 // Name the scope of a fix rather than widening it until something improves.
3095 if usemmap == 1 {
3096 var pf: i64 = 0
3097 while pf < ns {
3098 if newmask[pf] == 0 { pf = pf + 1 } else {
3099 let kba: i64 = h[1 + 8 * pf]
3100 let kbn: i64 = h[2 + 8 * pf]
3101 if kba != 0 {
3102 if kbn > 0 {
3103 let kal: i64 = (kba / SS_MAGIC_4096) * SS_MAGIC_4096
3104 sys_madvise(kal as *u8, (kba - kal) + kbn, SS_MADV_WILLNEED)
3105 }
3106 }
3107 // TERMS SLICE NOW PREFETCHED TOO (2026-09-14): the sample build below reads every SS_TSAMPLE-th entry
3108 // of it sequentially, so the readahead has a consumer the old comment said it lacked.
3109 let tba: i64 = h[5 + 8 * pf]
3110 let tbn: i64 = h[6 + 8 * pf]
3111 if tba != 0 {
3112 if tbn > 0 {
3113 let tal: i64 = (tba / SS_MAGIC_4096) * SS_MAGIC_4096
3114 sys_madvise(tal as *u8, (tba - tal) + tbn, SS_MADV_WILLNEED)
3115 }
3116 }
3117 let pba: i64 = h[7 + 8 * pf]
3118 let pbn: i64 = h[8 + 8 * pf]
3119 if pba != 0 {
3120 if pbn > 0 {
3121 let pfl: i64 = (pba / SS_MAGIC_4096) * SS_MAGIC_4096
3122 sys_madvise(pfl as *u8, (pba - pfl) + pbn, SS_MADV_WILLNEED)
3123 }
3124 }
3125 pf = pf + 1
3126 }
3127 }
3128 }
3129 // live-doc maps: derived from the .keys indexes, ONCE per open. A .keys
3130 // entry is already its key's LATEST entry within its segment, so an entry
3131 // is current iff NO NEWER segment's index knows the key (put or
3132 // tombstone both shadow). Docs not in their segment's .keys (shadowed
3133 // within the segment) stay dead. Single-segment stores need ZERO probes.
3134 let vo9: *i64 = sys_mmap(16) as *i64
3135 let vl9: *i64 = sys_mmap(16) as *i64
3136 let kcp: *u8 = sys_mmap(512)
3137 // key -> NEWEST segment holding it, built ONCE per open (2026-08-01). Replaces the per-key
3138 // rescan of all newer segments in the loop below. Sized 2x the total entry count and rounded to
3139 // a power of two so the probe masks instead of dividing; open addressing wants <=50% load.
3140 var ssl_total: i64 = 0
3141 var ssl_cap: i64 = 0
3142 var ssl_seg: *i64 = 0 as *i64
3143 var ssl_eo: *i64 = 0 as *i64
3144 var ssl_ins: i64 = 0
3145 var ssl_shadowed: i64 = 0
3146 ss_tr("segload" as *u8, sys_now_us() - t_open)
3147 let t_ssl: i64 = sys_now_us()
3148 if s0 > 0 {
3149 // INCR: the table came across from prev in ss3_move; only the new rows' keys are inserted and every old
3150 // mark they shadow is cleared, so the marks of rows [0,s0) are correct BEFORE the loop below marks the rest.
3151 ssl_seg = h[SS3_SLOT_TSEG + 9 * ns] as *i64
3152 ssl_eo = h[SS3_SLOT_TEO + 9 * ns] as *i64
3153 ssl_cap = h[SS3_SLOT_CAP + 9 * ns]
3154 let insp: *i64 = sys_mmap(16) as *i64
3155 insp[0] = 0
3156 insp[1] = 0
3157 ssl_shadowed = ss3_insert_new(h, ns, newmask, ssl_seg, ssl_eo, ssl_cap, insp)
3158 ssl_ins = h[SS3_SLOT_INS + 9 * ns] - tombs + insp[0] + insp[1]
3159 sys_munmap(insp as *u8, 16)
3160 // COVERAGE: a tombstone left standing is a vanished key no new row carries -- its next-newest entry lives
3161 // in a reused row the table cannot name, so the incremental marks would be WRONG. Rebuild everything
3162 // derived, in place, from the rows h already holds (no file is re-mapped): announced as FULL.
3163 let tleft: i64 = ss3_tomb_count(ssl_seg, ssl_cap)
3164 if tleft > 0 {
3165 ss3_ann("FULL" as *u8, "ns=" as *u8, ns, "vanished=" as *u8, vanished, "uncovered=" as *u8, tleft, "uncovered-vanished-keys" as *u8, sys_now_us() - t_open)
3166 var rz: i64 = 0
3167 while rz < ns {
3168 let lmz: i64 = h[1 + 8 * ns + rz]
3169 if lmz != 0 { sys_munmap(lmz as *u8, h[4 + 8 * rz] / 9 + 16); h[1 + 8 * ns + rz] = 0 }
3170 newmask[rz] = 1
3171 rz = rz + 1
3172 }
3173 sys_munmap(ssl_seg as *u8, 8 * ssl_cap)
3174 sys_munmap(ssl_eo as *u8, 8 * ssl_cap)
3175 s0 = 0
3176 ssl_total = ssl_total_keys(h, ns)
3177 ssl_cap = ssl_pow2(ssl_total * 2 + 16)
3178 ssl_seg = sys_mmap(8 * ssl_cap) as *i64
3179 ssl_eo = sys_mmap(8 * ssl_cap) as *i64
3180 ssl_ins = ssl_build(h, ns, ssl_seg, ssl_eo, ssl_cap)
3181 }
3182 } else {
3183 ssl_total = ssl_total_keys(h, ns)
3184 ssl_cap = ssl_pow2(ssl_total * 2 + 16)
3185 ssl_seg = sys_mmap(8 * ssl_cap) as *i64
3186 ssl_eo = sys_mmap(8 * ssl_cap) as *i64
3187 ssl_ins = ssl_build(h, ns, ssl_seg, ssl_eo, ssl_cap)
3188 }
3189 ss_tr("sslbuild" as *u8, sys_now_us() - t_ssl)
3190 let t_live: i64 = sys_now_us()
3191 var maxdocs: i64 = 16
3192 s = 0
3193 while s < ns { let dz0: i64 = h[4 + 8 * s]; if dz0 / 9 + 16 > maxdocs { maxdocs = dz0 / 9 + 16 } s = s + 1 }
3194 s = 0
3195 while s < ns {
3196 if newmask[s] == 0 { s = s + 1 } else {
3197 let kb: *u8 = h[1 + 8 * s] as *u8
3198 let ksz: i64 = h[2 + 8 * s]
3199 let pb: *u8 = h[7 + 8 * s] as *u8
3200 let dsz: i64 = h[4 + 8 * s]
3201 let lm: *u8 = sys_mmap(dsz / 9 + 16)
3202 h[1 + 8 * ns + s] = lm as i64
3203 if dsz / 9 + 16 > maxdocs { maxdocs = dsz / 9 + 16 }
3204 if ksz >= 8 { if h[8 + 8 * s] >= 8 {
3205 var nd9: i64 = ss_r32(pb, 4)
3206 // FAIL-SAFE ADMISSION (2026-09-02, debt 1788361379). lm above is sized from dsz, but every mark below
3207 // is indexed by a doc number read from the .post blob. If this segment's .docs did NOT load (dsz == 0:
3208 // absent, empty, mid-rename) the two disagree and the marks overflow a one-page lm -- SIGSEGV when the
3209 // next page is unmapped, silent table corruption and an endless probe when it is; BOTH were measured
3210 // on the serving daemon (one crash in the accept-path refresh, then a respawn spinning 15.9h of CPU
3211 // here with the port never opened). nd9 = 0 makes the inverse index empty, so no mark is written and
3212 // the segment serves no docs, which is the truth. The blob-derived counts are ALSO clamped to the
3213 // bytes actually mapped so a short or corrupt .post/.idx cannot steer a read past its mapping, and
3214 // lmcap9 bounds the mark itself. Refereed by nx_segopen_absent_gate (fixture: idx present, docs gone).
3215 let lmcap9: i64 = dsz / 9 + 16
3216 if dsz <= 0 { nd9 = 0; ss_open_seg_absent(segs[s] as *u8) }
3217 if 8 + 4 * nd9 > h[8 + 8 * s] { nd9 = (h[8 + 8 * s] - 8) / 4 }
3218 // AUX ROWS FOR THIS SEGMENT (2026-09-14): d2k (doc index -> .keys entry offset, filled beside the live
3219 // mark below for every live key -- a dead doc is never emitted, so it needs no entry) and the term
3220 // sample. The row is freed first so the in-place FULL rebuild above cannot leak a previous build.
3221 ss_aux_free_row(h, s)
3222 let d2kn: i64 = nd9 + 1
3223 let d2k9: *i64 = sys_mmap(8 * d2kn) as *i64
3224 let axr9: *i64 = ss_aux_row(h, s)
3225 if (axr9 as i64) != 0 { axr9[0] = d2k9 as i64; axr9[1] = d2kn }
3226 ss_tsample_build(h, s)
3227 ss_doccount_build(h, s) // search L5: the row's doc-key count, once, here in the parent
3228 var m9: i64 = ss_r32(kb, 4)
3229 if 8 + 4 * m9 > ksz { m9 = (ksz - 8) / 4 } // clamp to the bytes mapped (2026-09-02, debt 1788361379)
3230 // DOC-OFFSET INVERSE INDEX (2026-08-15). The loop below used to BINARY SEARCH pb once per key
3231 // entry. MEASURED with the ss_trace probe: that loop is 80.6pct of the entire shard open
3232 // (SSOPEN livemap us=9953625 of TOTAL us=12346598), because ~5.28M entries x ~log2(nd9)
3233 // random probes over mmap'd pages is tens of millions of page touches -- and the shard open is
3234 // in turn the root cause of the /search 503s (paid at startup AND on the accept path).
3235 // Build the inverse ONCE per segment instead: offset -> doc index, then each key is ONE probe.
3236 // Complexity goes from O(m log nd) to O(nd + m).
3237 // EQUIVALENT BY CONSTRUCTION, NOT BY HOPE: the binary search only ever acted on an EXACT match
3238 // (dv == ioff), so an exact-match hash decides the identical predicate. It is still PROVEN --
3239 // nx_livemap_fast_gate diffs every rebuilt map against the reference across the real shard and
3240 // must stay MISMATCHED=0 (baseline 14 segments / 5282736 entries / 369993949 bytes / 7-7 GREEN).
3241 // ⚠I FIRST PROPOSED A TWO-POINTER MERGE HERE AND MEASUREMENT KILLED IT: 2480606 of 5282736 key
3242 // entries (47pct) are NOT in ascending offset order, so a merge would have silently produced a
3243 // WRONG visibility map. A hash needs no ordering assumption at all.
3244 // DOC-OFFSET INVERSE INDEX. Replaces a per-key BINARY SEARCH of the .post doc table, which
3245 // the ss_trace phase timers measured at 80.6pct of the whole shard open (SSOPEN livemap
3246 // us=9953625 of TOTAL us=12346598) -- and that open is the root cause of the /search 503s,
3247 // paid at startup AND on the accept path. O(m log nd) -> O(nd + m).
3248 // EQUIVALENT BY CONSTRUCTION: the binary search only ever acted on an EXACT match, so an
3249 // exact-match hash decides the identical predicate. PROVEN, not assumed: nx_livemap_fast_gate
3250 // 7/7 GREEN, MISMATCHED=0 over 15 segments / 5283159 entries / 370396017 bytes.
3251 // ⚠A two-pointer merge was proposed here first and MEASUREMENT KILLED IT: 47pct of key
3252 // entries are not in ascending offset order, so a merge would have produced a WRONG
3253 // visibility map. A hash needs no ordering assumption.
3254 // PRODUCTION-VERIFIED, which is a separate axis from the gate and from the CLI: with this
3255 // live the daemon's refresh (a full open on the accept path) measured parent_us=7757144
3256 // versus 9783316 before, and per-request REQ child ms stayed 0-4.
3257 let dh_cap: i64 = ssl_pow2(nd9 * 2 + 16)
3258 let dh_mask: i64 = dh_cap - 1
3259 let dh_key: *i64 = sys_mmap(8 * dh_cap) as *i64
3260 let dh_val: *i64 = sys_mmap(8 * dh_cap) as *i64
3261 var dh_i: i64 = 0
3262 while dh_i < dh_cap { dh_key[dh_i] = 0; dh_i = dh_i + 1 }
3263 dh_i = 0
3264 while dh_i < nd9 {
3265 let dv0: i64 = ss_r32(pb, 8 + 4 * dh_i)
3266 var dslot: i64 = ssl_hash_i32(dv0) & dh_mask
3267 var dplaced: i64 = 0
3268 while dplaced == 0 {
3269 if dh_key[dslot] == 0 { dh_key[dslot] = dv0 + 1; dh_val[dslot] = dh_i; dplaced = 1 }
3270 else {
3271 if dh_key[dslot] == dv0 + 1 { dplaced = 1 } else { dslot = (dslot + 1) & dh_mask }
3272 }
3273 }
3274 dh_i = dh_i + 1
3275 }
3276 var e9: i64 = 0
3277 while e9 < m9 {
3278 let eo: i64 = 8 + 4 * m9 + ss_r32(kb, 8 + 4 * e9)
3279 let kind9: i64 = kb[eo]
3280 let kl9: i64 = ss_r32(kb, eo + 1)
3281 let vof: i64 = ss_r32(kb, eo + 5 + kl9)
3282 if kind9 == 1 { if kl9 < 500 {
3283 // shadowed by any newer segment knowing this key?
3284 var shadowed: i64 = 0
3285 // SHADOW TEST, NOW LINEAR (2026-08-01). This block asked `does any NEWER segment
3286 // know this key` by RESCANNING every newer segment, once per key -- O(keys x
3287 // segments) index probes, QUADRATIC in segment count: 4,454,778 key entries across
3288 // 96 segments on the live web shard. It is the measured cause of the ~8 minute
3289 // docportal pre-warm, and it got worse every time the crawler committed (87 -> 96
3290 // segments in one session once the crawler's segments=0 bug was fixed). A correct
3291 // fix at the leaf had surfaced a latent quadratic in the keystone.
3292 // ssl_tbl is built ONCE per open, walking segments NEWEST-FIRST, so the first
3293 // writer for a key is by construction the newest segment holding it. The entire
3294 // rescan collapses to one hash probe.
3295 // PROVEN BEFORE THE SWITCH, NOT AFTER: nx_livemap_fast_gate rebuilt every live-doc
3296 // map this way and diffed it against the old implementation on the REAL shard --
3297 // 96 segments, 4,454,778 entries, 177,262,743 bytes compared, MISMATCHED=0, 7/7.
3298 // These maps decide which documents are visible at all, so a faster map that is
3299 // subtly different would not make search fast, it would make search WRONG.
3300 let newest9: i64 = ssl_lookup(h, ssl_seg, ssl_eo, ssl_cap, kb, eo)
3301 if newest9 > s { shadowed = 1 }
3302 if shadowed == 0 {
3303 // map entry offset -> doc index: ONE probe into the per-segment inverse index.
3304 // An empty slot terminates the walk exactly where the binary search used to fail
3305 // to find a match -- both simply mark nothing.
3306 // ⚠I BRIEFLY REVERTED THIS ON A FALSE ALARM. Web requests hit ~10.7s shortly after
3307 // the deploy, I attributed it to this change, "rolled back", and saw recovery.
3308 // The rollback NEVER LANDED -- the deploy returned PROMOTED while the live .elf
3309 // stayed 3809f5da -- so the recovery came from the RESTART, not the revert, and
3310 // the slow window was my own three back-to-back CLI probes each mapping the full
3311 // 1.6GB shard through the page cache this daemon shares.
3312 // ★★★★★★A ROLLBACK THAT DID NOT LAND TURNS THE NEXT RECOVERY INTO FALSE EVIDENCE
3313 // FOR WHATEVER YOU THOUGHT YOU REVERTED -- verify the artifact bytes, never the
3314 // deploy receipt, BEFORE reading the result as a control.
3315 // ★AND DO NOT MEASURE A SERVICE WHILE GENERATING LOAD AGAINST ITS OWN PAGE CACHE.
3316 let ioff: i64 = vof - 9 - kl9
3317 var dslot2: i64 = ssl_hash_i32(ioff) & dh_mask
3318 var looking: i64 = 1
3319 while looking == 1 {
3320 if dh_key[dslot2] == 0 { looking = 0 } else {
3321 if dh_key[dslot2] == ioff + 1 { if dh_val[dslot2] < lmcap9 { lm[dh_val[dslot2]] = 1 as u8; if dh_val[dslot2] < d2kn { d2k9[dh_val[dslot2]] = eo } } looking = 0 }
3322 else { dslot2 = (dslot2 + 1) & dh_mask }
3323 }
3324 }
3325 }
3326 } }
3327 e9 = e9 + 1
3328 }
3329 // Per-segment scratch: freed HERE, not left to ss_close, because it is not reachable from the
3330 // returned handle -- the exact leak class seq905 recorded for dp/dszp a few lines above.
3331 sys_munmap(dh_key as *u8, 8 * dh_cap)
3332 sys_munmap(dh_val as *u8, 8 * dh_cap)
3333 } }
3334 s = s + 1
3335 }
3336 }
3337 ss_tr("livemap" as *u8, sys_now_us() - t_live)
3338 ss_tr("TOTAL" as *u8, sys_now_us() - t_open)
3339 // (A one-shot ordering probe stood here to decide whether key entries arrive in ascending ioff
3340 // order -- the precondition for replacing the binary search with a two-pointer merge. IT ANSWERED
3341 // PERMANENTLY: 2480801 of 5283159 entries, 47pct, are NOT ascending, so that merge is invalid and
3342 // was never written. The answer is banked in debt 1786816618; the probe is removed because a
3343 // diagnostic that has settled its question is dead weight in a hot library, and re-deriving it is
3344 // a 30-line probe away if the writer ever changes. The reusable PHASE timers above stay.)
3345 // query scratch arena (handle is single-threaded; queries allocate nothing)
3346 h[1 + 9 * ns] = sys_mmap(64) as i64
3347 h[2 + 9 * ns] = sys_mmap(32) as i64
3348 h[3 + 9 * ns] = sys_mmap(8 * maxdocs) as i64
3349 h[4 + 9 * ns] = sys_mmap(8 * maxdocs) as i64
3350 h[5 + 9 * ns] = sys_mmap(8 * 68) as i64
3351 h[6 + 9 * ns] = sys_mmap(8 * 68) as i64
3352 // PER-OPEN SCRATCH RELEASE (seq905/seq975): none of these are reachable from the returned handle, so
3353 // ss_close can NEVER reclaim them -- they must be given back HERE or every open leaks them forever.
3354 // All are dead past this point: dp/dszp and aouts served the segment loop; vo9/vl9/kcp served the
3355 // live-doc-map loop; sp was only the out-param holder. ss_manifest_free releases the segment-name pool
3356 // and the segs array (paired with ss_manifest_dyn exactly as ss_close pairs with ss_open).
3357 h[SS3_SLOT_NAMES + 9 * ns] = segs as i64
3358 sys_munmap(dp, 512)
3359 sys_munmap(dszp as *u8, 16)
3360 sys_munmap(aouts as *u8, 8 * 8)
3361 sys_munmap(vo9 as *u8, 16)
3362 sys_munmap(vl9 as *u8, 16)
3363 sys_munmap(kcp, 512)
3364 sys_munmap(sp as *u8, 8)
3365 // ssl_seg/ssl_eo (the 2026-08-01 key->newest-segment table) are DEAD past the live-doc loop and,
3366 // like everything else in this block, unreachable from the returned handle -- ss_close can never
3367 // reclaim them. They were the ONLY per-open allocations missing from this release list, and at
3368 // web-shard scale the pair is 2 x 8 x pow2(2 x 4.45M keys) = 256 MB PER REOPEN -- measured live
3369 // 2026-08-01 as orphaned 256MB anon VMAs accumulating in nx_docportal_admin_daemon (debt
3370 // 1785622778), one leaked pair per manifest change under an active crawler.
3371 // THE CHECKLIST LAW: WHEN YOU ADD A PER-OPEN ALLOCATION, ADD ITS RELEASE IN THE SAME EDIT --
3372 // this block exists precisely because seq905 paid for the same lesson.
3373 if retain == 1 {
3374 h[SS3_SLOT_TSEG + 9 * ns] = ssl_seg as i64
3375 h[SS3_SLOT_TEO + 9 * ns] = ssl_eo as i64
3376 h[SS3_SLOT_CAP + 9 * ns] = ssl_cap
3377 h[SS3_SLOT_INS + 9 * ns] = ssl_ins
3378 } else {
3379 h[SS3_SLOT_TSEG + 9 * ns] = 0
3380 h[SS3_SLOT_TEO + 9 * ns] = 0
3381 h[SS3_SLOT_CAP + 9 * ns] = 0
3382 h[SS3_SLOT_INS + 9 * ns] = 0
3383 sys_munmap(ssl_seg as *u8, 8 * ssl_cap)
3384 sys_munmap(ssl_eo as *u8, 8 * ssl_cap)
3385 }
3386 if s0 > 0 {
3387 h[SS3_SLOT_MODE + 9 * ns] = SS3_MODE_INCR
3388 ss3_ann("INCR" as *u8, "reused=" as *u8, nreuse, "built=" as *u8, ns - nreuse, "shadowed=" as *u8, ssl_shadowed, 0 as *u8, sys_now_us() - t_open)
3389 ss3_ann("INCR-FOLD" as *u8, "vanished=" as *u8, vanished, "tombstoned=" as *u8, tombs, "ins=" as *u8, ssl_ins, 0 as *u8, sys_now_us() - t_open)
3390 } else {
3391 h[SS3_SLOT_MODE + 9 * ns] = SS3_MODE_FULL
3392 }
3393 return h
3394}
3395
3396// handle get: identical semantics to ss_get_idx, zero file IO per call
3397// ---- CACHED OPEN (added 2026-07-25, debt seq962) -------------------------------------------------
3398// WHY: ss_open/ss_open2 read EVERY segment fully into anonymous RAM and there is NO ss_close anywhere
3399// in the tree, so a caller that opens PER REQUEST leaks the whole store per request. MEASURED on the
3400// live box: nx_hub_gw does 3 opens per HTTP request and grew VmData 1336 kB PER REQUEST, with
3401// VmSize==VmPeak (monotonic). Nine long-lived organs carried that same fingerprint, ~23.6 GiB of
3402// anonymous address space, which drove swap to 92.3% and the box into sustained thrash.
3403//
3404// THIS IS NOT A NEW IDEA -- it is an EXISTING in-tree remedy finally promoted into the primitive.
3405// The same cache was independently rediscovered at least three times and never generalized:
3406// 1. nx_docportal_search_seg.nx:128 dss_open_maybe_cached (manifest-signature invalidation)
3407// 2. nx_docportal_admin_daemon.nx:652 per-request ss_open measured ~0.7s/query without this
3408// 3. nx_store_seed_lib.nx:94 helped the reader family OOM the host. NOW: ss_open ONCE
3409// Three local patches, zero generalization -- fix the CLASS, not the call site.
3410//
3411// PURELY ADDITIVE BY DESIGN: there is NO munmap here. That is deliberate. ss_load_aux2 publishes
3412// INTERIOR POINTERS into a single mapping on the merged-.idx path but three INDEPENDENT mappings on
3413// the legacy path, and ss_open2 discards the return value that distinguishes them -- so any freeing
3414// close is a partial-unmap/use-after-free hazard until that ledger is fixed (debt seq947). Caching
3415// removes the leak without ever freeing: you cannot leak what you never allocate twice.
3416//
3417// LIVE-EDIT PRESERVED (nx_hub_gw depends on it): invalidation is the manifest (st_size, st_mtime),
3418// so editing a store is still picked up with NO restart -- on the next call.
3419// RESIDUAL (honest): a miss re-opens WITHOUT freeing the previous handle, so re-opens fall from
3420// per-request to per-store-EDIT. Bounded and rare; ss_close remains worth building for that path.
3421const SSC_SLOTS: i64 = 16
3422const SSC_W: i64 = 4
3423// RETIRE RING (seq1057). Capacity in HANDLES; slot 0 of the mapping holds the count, handles live at 1..n.
3424const SSC_RETIRE: i64 = 32
3425static ssc_tab: *i64 // SSC_SLOTS x SSC_W: [0]=prefix-copy ptr [1]=st_size [2]=st_mtime [3]=handle
3426static ssc_names: *u8 // SSC_SLOTS x 256 bytes of prefix copies (slot i at +i*256)
3427static ssc_scr: *u8 // one-shot scratch: [0..559]=path [560..719]=statbuf [720..735]=sig
3428static ssc_retired: *i64 // [0]=count, [1..SSC_RETIRE]=orphaned handles awaiting an explicit reap
3429// CACHE TELEMETRY (2026-08-14). The fail-open fallback in ss_open_cached was UNCOUNTED, so a process
3430// that exhausted the 16 slots leaked a whole store mapping per call with nothing anywhere to show it.
3431// An unbounded leak behind a silent fallback is invisible by construction; these counters make the
3432// condition READABLE before it becomes a 3 GB daemon.
3433const SSC_ST_HIT: i64 = 0
3434const SSC_ST_REOPEN: i64 = 1
3435const SSC_ST_NEWSLOT: i64 = 2
3436const SSC_ST_FALLBACK: i64 = 3 // slots exhausted -> per-call open (now retired, formerly leaked)
3437const SSC_ST_DROP: i64 = 4 // retire ring full -> pointer dropped (the documented bounded leak)
3438const SSC_ST_SLOTS: i64 = 8
3439static ssc_stats: *i64
3440
3441// allocate the cache ONCE (static POINTERS to lazy-mmap'd tables -- a BSS static ARRAY silently
3442// crashes handler modules on startup, so never use [N]i64 here).
3443func ssc_init() -> i64 {
3444 if (ssc_tab as i64) != 0 { return 1 }
3445 ssc_tab = sys_mmap(8 * SSC_SLOTS * SSC_W) as *i64
3446 ssc_names = sys_mmap(SSC_SLOTS * 256)
3447 ssc_scr = sys_mmap(SS_MAGIC_1024)
3448 ssc_retired = sys_mmap(8 * (SSC_RETIRE + 1)) as *i64
3449 ssc_stats = sys_mmap(8 * SSC_ST_SLOTS) as *i64
3450 return 1
3451}
3452// Read one telemetry counter (SSC_ST_*). Observability for gates, daemons and leak triage.
3453func ss_cache_stat(idx: i64) -> i64 {
3454 ssc_init()
3455 if idx < 0 { return 0 }
3456 if idx >= SSC_ST_SLOTS { return 0 }
3457 return ssc_stats[idx]
3458}
3459
3460// ---- RETIRE / REAP (seq1057) -------------------------------------------------------------------
3461// THE PROBLEM: on a stale manifest signature ss_open_cached re-opens and OVERWRITES the cached handle.
3462// The previous handle's whole mapping set (segments, keys, docs, terms, post, live-doc maps, scratch
3463// arena) was simply dropped on the floor -- orphaned on every edit to that store.
3464//
3465// WHY NOT JUST ss_close(old) THERE: ss_open_cached hands back a RAW handle and promises the same handle
3466// semantics as ss_open. There is no refcount and no generation, so a caller that took the handle before
3467// the invalidation is still dereferencing it. Freeing inline would turn a bounded LEAK into an unbounded
3468// USE-AFTER-FREE in the most-shared primitive in the ecosystem. That trade is never worth it.
3469//
3470// THE SHAPE THAT IS SOUND WITHOUT TOUCHING ~180 READERS: retiring NEVER frees, so it cannot fault. The
3471// orphan is merely remembered instead of lost. Reclamation is an EXPLICIT, OPT-IN call that a consumer
3472// makes at a point where it knows it holds no handle -- the top of a request loop, between batches. Until
3473// a consumer opts in, behaviour is byte-for-byte what it is today, so this can never regress a caller.
3474//
3475// FAIL-SAFE DIRECTION: if the ring is full we DROP the pointer (leak it) rather than free it. Overflow
3476// degrades to exactly today's behaviour, never to a free that might still be in use.
3477func ssc_retire_handle(h: i64) -> i64 {
3478 if h == 0 { return 0 }
3479 let n: i64 = ssc_retired[0]
3480 // The ring-full DROP is a deliberate, documented, bounded leak (dropping beats a free that may still
3481 // be in use). It was never COUNTED, though, so nobody could tell whether it ever actually happened in
3482 // production -- a leak-by-design must be bounded AND named AND observable, not just the first two.
3483 if n >= SSC_RETIRE { ssc_stats[SSC_ST_DROP] = ssc_stats[SSC_ST_DROP] + 1; return 0 }
3484 ssc_retired[1 + n] = h
3485 ssc_retired[0] = n + 1
3486 return 1
3487}
3488
3489// Release every retired handle. Returns how many were closed.
3490// *** CONTRACT: the CALLER guarantees no seg-store handle from ss_open_cached is still in use.
3491// Call it where that is structurally true (top of a request loop, between batches) -- never from inside
3492// a function that is holding a handle, and never from the primitive itself.
3493func ss_cache_reap() -> i64 {
3494 ssc_init()
3495 let n: i64 = ssc_retired[0]
3496 var i: i64 = 0
3497 var freed: i64 = 0
3498 while i < n {
3499 let h: i64 = ssc_retired[1 + i]
3500 if h != 0 { ss_close(h as *i64); freed = freed + 1 }
3501 ssc_retired[1 + i] = 0
3502 i = i + 1
3503 }
3504 ssc_retired[0] = 0
3505 return freed
3506}
3507
3508// How many orphans are awaiting a reap (observability for gates and daemons).
3509func ss_cache_retired() -> i64 {
3510 ssc_init()
3511 return ssc_retired[0]
3512}
3513func ssc_streq(a: *u8, b: *u8) -> i64 {
3514 var i: i64 = 0
3515 while a[i] != (0 as u8) {
3516 if b[i] != a[i] { return 0 }
3517 i = i + 1
3518 }
3519 if b[i] == (0 as u8) { return 1 }
3520 return 0
3521}
3522// manifest signature -> sig[0]=st_size sig[1]=st_mtime-sec ((0,0) when absent).
3523// Reuses the ONE static scratch buffer, so this probe allocates nothing per call.
3524func ssc_sig_of(prefix: *u8, sig: *i64) -> i64 {
3525 let mp: *u8 = ssc_scr
3526 var o: i64 = 0
3527 o = ss_cat(mp, o, prefix)
3528 o = ss_cat(mp, o, "manifest.txt" as *u8)
3529 mp[o] = 0 as u8
3530 let stb: *u8 = ((ssc_scr as i64) + 560) as *u8
3531 sig[0] = 0
3532 sig[1] = 0
3533 if sys_fstatat(mp, stb) == 0 {
3534 let szp: *i64 = ((stb as i64) + 48) as *i64
3535 let mtp: *i64 = ((stb as i64) + 88) as *i64
3536 sig[0] = szp[0]
3537 sig[1] = mtp[0]
3538 }
3539 return 0
3540}
3541// THE ONE OPEN long-lived readers should use. Same handle semantics as ss_open -- every ss_hget /
3542// ss_term / ss_phrase reader is unchanged. Fail-open: if the table is full we fall back to a plain
3543// ss_open rather than refusing, so caching can never break a caller.
3544func ss_open_cached(prefix: *u8) -> *i64 {
3545 ssc_init()
3546 let sig: *i64 = ((ssc_scr as i64) + 720) as *i64
3547 ssc_sig_of(prefix, sig)
3548 var i: i64 = 0
3549 var freeslot: i64 = 0 - 1
3550 while i < SSC_SLOTS {
3551 let np: i64 = ssc_tab[i * SSC_W]
3552 if np == 0 {
3553 if freeslot < 0 { freeslot = i }
3554 } else {
3555 if ssc_streq(np as *u8, prefix) == 1 {
3556 if ssc_tab[i * SSC_W + 3] != 0 {
3557 if ssc_tab[i * SSC_W + 1] == sig[0] {
3558 if ssc_tab[i * SSC_W + 2] == sig[1] { return ssc_tab[i * SSC_W + 3] as *i64 }
3559 }
3560 }
3561 let hr: *i64 = ss_open(prefix)
3562 // seq1057: the previous handle used to be overwritten and lost here. Retiring does NOT
3563 // free it (a caller may still hold it) -- it only makes it reclaimable by ss_cache_reap.
3564 ssc_retire_handle(ssc_tab[i * SSC_W + 3])
3565 ssc_tab[i * SSC_W + 1] = sig[0]
3566 ssc_tab[i * SSC_W + 2] = sig[1]
3567 ssc_tab[i * SSC_W + 3] = hr as i64
3568 return hr
3569 }
3570 }
3571 i = i + 1
3572 }
3573 if freeslot < 0 {
3574 // FAIL-OPEN WITHOUT LEAKING (2026-08-14). This branch used to be `return ss_open(prefix)` with the
3575 // mapping UNTRACKED: not placed in a slot, never retired, never reaped. Slots are NEVER released
3576 // once claimed, so as soon as 16 distinct prefixes existed in a process, EVERY later call here
3577 // leaked an entire store mapping -- permanently, and with no counter anywhere to show it.
3578 // MEASURED on nx_hub_gw (pid 15668, 2026-08-14): 239.93 kB leaked per request over 60 requests,
3579 // and a 12-sample series under load returned tau=1000permille rate=227102kB/min verdict=LEAK on a
3580 // perfectly monotone climb to 3.0 GB. That daemon's own code is clean (4 unbalanced funcs / 7
3581 // sites) and so is its whole import closure -- the leak was entirely this one untracked return.
3582 // THE FIX IS THE INCUMBENT MECHANISM, NOT A NEW ONE: retire the handle immediately.
3583 // ssc_retire_handle NEVER frees (it only records), so this cannot fault, and the handle is
3584 // reclaimed by the caller's next ss_cache_reap() under exactly the contract reap already states.
3585 // Slot-cached handles are deliberately NOT retired -- they are meant to outlive the call. Only
3586 // this per-call fallback is, because only it has no owner.
3587 let hf: *i64 = ss_open(prefix)
3588 ssc_stats[SSC_ST_FALLBACK] = ssc_stats[SSC_ST_FALLBACK] + 1
3589 ssc_retire_handle(hf as i64)
3590 return hf
3591 }
3592 let nm: *u8 = ((ssc_names as i64) + freeslot * 256) as *u8
3593 var k: i64 = 0
3594 while prefix[k] != (0 as u8) { if k < 255 { nm[k] = prefix[k] } k = k + 1 }
3595 if k > 255 { k = 255 }
3596 nm[k] = 0 as u8
3597 let hn: *i64 = ss_open(prefix)
3598 ssc_tab[freeslot * SSC_W] = nm as i64
3599 ssc_tab[freeslot * SSC_W + 1] = sig[0]
3600 ssc_tab[freeslot * SSC_W + 2] = sig[1]
3601 ssc_tab[freeslot * SSC_W + 3] = hn as i64
3602 return hn
3603}
3604
3605// PER-CALL SCRATCH, ALLOCATED ONCE (2026-07-30). ss_hget used to do TWO sys_mmap(16) calls PER
3606// INVOCATION and never free them. sys_mmap is a REAL mmap syscall (nx_syscalls.nx:167), not a bump
3607// allocator, so the kernel rounds each to a full page: ~8 KB LEAKED PER ss_hget CALL. ss_hget is the
3608// hottest read primitive in the ecosystem -- 205 call sites, and the loaders call it ONCE PER ROW --
3609// so a 10k-row scan leaked ~80 MB in scratch alone, dwarfing the per-open leak this lane started on.
3610//
3611// This is the SAME defect seq905 already fixed one function away: ss_open2's dp/dszp were allocated
3612// inside the per-segment loop and were hoisted out ("buffers hoisted, no mmap in inner loops"). The
3613// identical pattern in ss_hget was missed because the loop is in the CALLER, not in the function.
3614// ★LAW: a per-call allocation in a primitive is a per-CALLER-LOOP leak -- audit the primitive's call
3615// frequency, not just its own body.
3616//
3617// SAFE AS A STATIC: vo/vl are pure scratch for ss_idx_find's two outputs, written then read immediately
3618// with no intervening call that could re-enter ss_hget (VERIFIED: ss_idx_find does not call ss_hget).
3619// Static POINTER + lazy mmap is the established idiom here -- a BSS static ARRAY silently crashes
3620// handler modules on startup, so never use [N]i64.
3621static ssh_vo: *i64
3622static ssh_vl: *i64
3623// FNV-1a over RAW key bytes -- the SAME arithmetic as ssl_hash_entry over a .keys entry, so a raw key lands in
3624// the slot ssl_build filled for its entry. One hash idiom, spelled twice only because one reads an entry and
3625// the other a caller's buffer.
3626func ssl_hash_raw(key: *u8, kl: i64) -> i64 {
3627 var hsh: i64 = 0x811c9dc5
3628 var i: i64 = 0
3629 while i < kl {
3630 hsh = hsh ^ (key[i] as i64)
3631 hsh = (hsh * SS_MAGIC_16777619) & 0xffffffff
3632 i = i + 1
3633 }
3634 return hsh
3635}
3636// KEY LOCATE THROUGH THE RETAINED TABLE (2026-09-14, search plan L3 residual). When the open retained its key
3637// table (ss_open_incr: the daemon's cached web handle, the referee bench) a key's NEWEST segment is one hash
3638// probe instead of a newest-first binary search of every segment's .keys -- 2048 candidates x 4-5 lookups x up
3639// to 33 segments per query, MEASURED by the search phase timers at 0.65-0.75 s of a 1.15 s cold query.
3640// SAME ANSWER BY CONSTRUCTION: ssl_build inserts every .keys entry shorter than SS3_KEYMAX newest-first and
3641// refuses to overwrite, and ss3_insert_new keeps that invariant across incremental reopens, so the slot IS the
3642// newest segment holding the key -- exactly where the loop stops. A key of SS3_KEYMAX bytes or more is never in
3643// the table and takes the loop (-2), as does a handle opened without retention. outs[0]=segment, outs[1]=entry
3644// offset in that segment's .keys. Returns 1 found, 0 absent (an EMPTY slot ends the probe exactly as the loop's
3645// exhaustion does), -2 no table (caller walks).
3646func ss_hlocate(h: *i64, key: *u8, kl: i64, outs: *i64) -> i64 {
3647 let ns: i64 = h[0]
3648 if ns <= 0 { return 0 - 2 }
3649 if kl >= SS3_KEYMAX { return 0 - 2 }
3650 let tseg: *i64 = h[SS3_SLOT_TSEG + 9 * ns] as *i64
3651 if (tseg as i64) == 0 { return 0 - 2 }
3652 let teo: *i64 = h[SS3_SLOT_TEO + 9 * ns] as *i64
3653 let cap: i64 = h[SS3_SLOT_CAP + 9 * ns]
3654 if cap <= 0 { return 0 - 2 }
3655 let mask: i64 = cap - 1
3656 var slot: i64 = ssl_hash_raw(key, kl) & mask
3657 var guard: i64 = 0
3658 while guard < cap {
3659 let tv: i64 = tseg[slot]
3660 if tv == SSL_EMPTY { return 0 }
3661 if tv != SSL_TOMB {
3662 let s: i64 = tv - 1
3663 let eo: i64 = teo[slot]
3664 let kb: *u8 = h[1 + 8 * s] as *u8
3665 let ksz: i64 = h[2 + 8 * s]
3666 if eo + 5 + kl <= ksz { if ss_r32(kb, eo + 1) == kl {
3667 var i: i64 = 0
3668 var eq: i64 = 1
3669 while i < kl { if kb[eo + 5 + i] != key[i] { eq = 0; i = kl } else { i = i + 1 } }
3670 if eq == 1 { outs[0] = s; outs[1] = eo; return 1 }
3671 } }
3672 }
3673 slot = (slot + 1) & mask
3674 guard = guard + 1
3675 }
3676 return 0
3677}
3678func ss_hget(h: *i64, key: *u8, ptrout: *i64, lenout: *i64) -> i64 {
3679 let ns: i64 = h[0]
3680 if ns == 0 { return 0 - 1 }
3681 if (ssh_vo as i64) == 0 { ssh_vo = sys_mmap(16) as *i64 }
3682 if (ssh_vl as i64) == 0 { ssh_vl = sys_mmap(16) as *i64 }
3683 let vo: *i64 = ssh_vo
3684 let vl: *i64 = ssh_vl
3685 // FAST PATH (2026-09-14): one probe of the retained key table; the walk below is the fallback and the proof
3686 let kl9: i64 = ss_len(key)
3687 let hl9: i64 = ss_hlocate(h, key, kl9, vo)
3688 if hl9 == 1 {
3689 let s9: i64 = vo[0]
3690 let eo9: i64 = vo[1]
3691 let kb9: *u8 = h[1 + 8 * s9] as *u8
3692 if kb9[eo9] == (2 as u8) { return 0 } // tombstone in the newest segment shadows, as the walk answers
3693 let voff9: i64 = ss_r32(kb9, eo9 + 5 + kl9)
3694 let vlen9: i64 = ss_r32(kb9, eo9 + 5 + kl9 + 4)
3695 if h[4 + 8 * s9] < voff9 + vlen9 { return 0 - 2 }
3696 ptrout[0] = h[3 + 8 * s9] + voff9
3697 lenout[0] = vlen9
3698 return 1
3699 }
3700 if hl9 == 0 { return 0 - 1 }
3701 var s: i64 = ns - 1
3702 while s >= 0 {
3703 let r: i64 = ss_idx_find(h[1 + 8 * s] as *u8, h[2 + 8 * s], key, vo, vl)
3704 if r == 2 { return 0 }
3705 if r == 1 {
3706 if h[4 + 8 * s] < vo[0] + vl[0] { return 0 - 2 }
3707 ptrout[0] = h[3 + 8 * s] + vo[0]
3708 lenout[0] = vl[0]
3709 return 1
3710 }
3711 s = s - 1
3712 }
3713 return 0 - 1
3714}
3715
3716// binary-search a .terms blob; returns 1 + postoff/postlen/dcount in outs,
3717// -1 not found, -2 malformed/absent (old-format segment -> caller fails LOUD)
3718func ss_terms_find(tb: *u8, tsz: i64, term: *u8, outs: *i64) -> i64 {
3719 if tsz < 8 { return 0 - 2 }
3720 if tb[0] != (78 as u8) { return 0 - 2 }
3721 if tb[2] != (84 as u8) { return 0 - 2 }
3722 let n: i64 = ss_r32(tb, 4)
3723 let base: i64 = 8 + 4 * n
3724 let tl0: i64 = ss_len(term)
3725 var lo: i64 = 0
3726 var hi: i64 = n - 1
3727 while lo <= hi {
3728 let mid: i64 = (lo + hi) / 2
3729 let eo: i64 = base + ss_r32(tb, 8 + 4 * mid)
3730 let tl: i64 = ss_r32(tb, eo)
3731 let c: i64 = ss_kcmp((tb as i64 + eo + 4) as *u8, tl, term, tl0)
3732 if c == 0 {
3733 outs[0] = ss_r32(tb, eo + 4 + tl)
3734 outs[1] = ss_r32(tb, eo + 4 + tl + 4)
3735 outs[2] = ss_r32(tb, eo + 4 + tl + 8)
3736 return 1
3737 }
3738 if c < 0 { lo = mid + 1 }
3739 if c > 0 { hi = mid - 1 }
3740 }
3741 return 0 - 1
3742}
3743
3744// TERM SEARCH with CURRENT-STATE semantics. A posting hit counts ONLY if it
3745// is the key's CURRENT entry (verified against the key index): a newer
3746// version without the term, or a tombstone, silently shadows older hits --
3747// stale text never answers for a live record. Each current entry exists
3748// exactly once, so no dedup list is needed. Returns match count (key
3749// ptr/len pairs into the handle's docs buffers), or -2 if ANY live segment
3750// lacks a .terms index (old-format -- fail LOUD, compaction is the upgrade
3751// path; never a silent partial answer).
3752func ss_term(h: *i64, term: *u8, kpout: *i64, klout: *i64, max: i64) -> i64 {
3753 let ns: i64 = h[0]
3754 var cnt: i64 = 0
3755 // loud pre-check: every live segment must carry the term index
3756 var s: i64 = 0
3757 while s < ns {
3758 if h[6 + 8 * s] < 8 { return 0 - 2 }
3759 s = s + 1
3760 }
3761 let outs: *i64 = h[1 + 9 * ns] as *i64
3762 let pos: *i64 = h[2 + 9 * ns] as *i64
3763 s = ns - 1
3764 while s >= 0 {
3765 let tb: *u8 = h[5 + 8 * s] as *u8
3766 let pb: *u8 = h[7 + 8 * s] as *u8
3767 let db: i64 = h[3 + 8 * s]
3768 let lm: *u8 = h[1 + 8 * ns + s] as *u8
3769 let r: i64 = ss_terms_find_h(h, s, term, outs) // sampled window, same answer (2026-09-14)
3770 if r == 1 {
3771 pos[0] = outs[0]
3772 var prev: i64 = 0
3773 var k: i64 = 0
3774 while k < outs[2] {
3775 let d: i64 = prev + ss_vr(pb, pos)
3776 prev = d
3777 // currency check: O(1) via the handle's live-doc map (computed
3778 // once at open; identical semantics to the keyed re-verify)
3779 if lm[d] == (1 as u8) {
3780 if cnt < max {
3781 // INDEX-RESIDENT KEY (2026-09-14): .keys answers when its d2k row covers the doc
3782 if ss_key_of_doc(h, s, d, ((kpout as i64) + 8 * cnt) as *i64, ((klout as i64) + 8 * cnt) as *i64) == 0 {
3783 let eoff: i64 = ss_r32(pb, 8 + 4 * d)
3784 let ep: *u8 = (db + eoff) as *u8
3785 kpout[cnt] = db + eoff + 5
3786 klout[cnt] = ss_r32(ep, 1)
3787 }
3788 cnt = cnt + 1
3789 }
3790 }
3791 k = k + 1
3792 }
3793 }
3794 s = s - 1
3795 }
3796 return cnt
3797}
3798
3799// MULTI-TERM AND (IM2c): keys whose CURRENT record contains EVERY term.
3800// A key's current entry lives in exactly ONE segment, so the AND is computed
3801// per segment: every term's postings list (sorted ascending doc ids) is
3802// merge-intersected (O(sum of list lengths)), then survivors pass the O(1)
3803// live-doc currency check -- same current-state semantics as ss_term (stale
3804// versions and tombstones never answer). Returns match count into
3805// kpout/klout, or -2 when any live segment lacks a .terms index (fail LOUD,
3806// never a silent partial answer).
3807func ss_term_and(h: *i64, terms: *i64, nterms: i64, kpout: *i64, klout: *i64, max: i64) -> i64 {
3808 if nterms <= 0 { return 0 }
3809 let ns: i64 = h[0]
3810 var s: i64 = 0
3811 while s < ns {
3812 if h[6 + 8 * s] < 8 { return 0 - 2 }
3813 s = s + 1
3814 }
3815 if nterms == 1 { return ss_term(h, terms[0] as *u8, kpout, klout, max) }
3816 if nterms > 64 { return 0 - 3 }
3817 let outs: *i64 = h[1 + 9 * ns] as *i64
3818 let pos: *i64 = h[2 + 9 * ns] as *i64
3819 let poffs: *i64 = h[5 + 9 * ns] as *i64
3820 let pdcs: *i64 = h[6 + 9 * ns] as *i64
3821 var cnt: i64 = 0
3822 s = ns - 1
3823 while s >= 0 {
3824 let tb: *u8 = h[5 + 8 * s] as *u8
3825 let pb: *u8 = h[7 + 8 * s] as *u8
3826 let db: i64 = h[3 + 8 * s]
3827 let lm: *u8 = h[1 + 8 * ns + s] as *u8
3828 // every term must appear in THIS segment's dict or it contributes 0
3829 var allp: i64 = 1
3830 var t: i64 = 0
3831 while t < nterms {
3832 let r: i64 = ss_terms_find_h(h, s, terms[t] as *u8, outs) // sampled window (2026-09-14)
3833 if r == 1 { poffs[t] = outs[0]; pdcs[t] = outs[2] } else { allp = 0 }
3834 t = t + 1
3835 }
3836 if allp == 1 {
3837 let la: *i64 = h[3 + 9 * ns] as *i64
3838 let lb: *i64 = h[4 + 9 * ns] as *i64
3839 // decode term 0 into la
3840 pos[0] = poffs[0]
3841 var prev: i64 = 0
3842 var na: i64 = 0
3843 var k: i64 = 0
3844 while k < pdcs[0] {
3845 prev = prev + ss_vr(pb, pos)
3846 la[na] = prev
3847 na = na + 1
3848 k = k + 1
3849 }
3850 // streaming two-pointer intersect with each further term's list
3851 t = 1
3852 while t < nterms {
3853 if na == 0 { t = nterms } else {
3854 pos[0] = poffs[t]
3855 prev = 0
3856 var ai: i64 = 0
3857 var nb: i64 = 0
3858 k = 0
3859 while k < pdcs[t] {
3860 prev = prev + ss_vr(pb, pos)
3861 var adv: i64 = 1
3862 while adv == 1 {
3863 if ai >= na { adv = 0 } else {
3864 if la[ai] < prev { ai = ai + 1 } else { adv = 0 }
3865 }
3866 }
3867 if ai < na { if la[ai] == prev { lb[nb] = prev; nb = nb + 1; ai = ai + 1 } }
3868 k = k + 1
3869 }
3870 var c2: i64 = 0
3871 while c2 < nb { la[c2] = lb[c2]; c2 = c2 + 1 }
3872 na = nb
3873 t = t + 1
3874 }
3875 }
3876 // survivors: O(1) currency check, then emit key ptr/len
3877 var i2: i64 = 0
3878 while i2 < na {
3879 let d: i64 = la[i2]
3880 if lm[d] == (1 as u8) {
3881 let eoff: i64 = ss_r32(pb, 8 + 4 * d)
3882 let ep: *u8 = (db + eoff) as *u8
3883 if cnt < max {
3884 kpout[cnt] = db + eoff + 5
3885 klout[cnt] = ss_r32(ep, 1)
3886 cnt = cnt + 1
3887 }
3888 }
3889 i2 = i2 + 1
3890 }
3891 }
3892 s = s - 1
3893 }
3894 return cnt
3895}
3896
3897// ---- RANKING STATISTICS (additive, 2026-07-03: the BM25/IDF rung reads them) --------------------
3898// n_t: how many docs carry `term`, summed from each live segment's .terms dcount (the statistic
3899// ss_build_terms already persists). Write-time counts: a shadowed older version still counts -- for
3900// IDF ranking that bias is negligible and the read is O(log terms) per segment, no posting decode.
3901// Same loud contract as ss_term: -2 when any live segment lacks a .terms index.
3902func ss_term_dcount(h: *i64, term: *u8) -> i64 {
3903 let ns: i64 = h[0]
3904 var s: i64 = 0
3905 while s < ns {
3906 if h[6 + 8 * s] < 8 { return 0 - 2 }
3907 s = s + 1
3908 }
3909 let outs: *i64 = h[1 + 9 * ns] as *i64
3910 var n: i64 = 0
3911 s = 0
3912 while s < ns {
3913 let r: i64 = ss_terms_find_h(h, s, term, outs) // sampled window (2026-09-14): idf lookups stop faulting the cold dictionary
3914 if r == 1 { n = n + outs[2] }
3915 s = s + 1
3916 }
3917 return n
3918}
3919// TERM-DICTIONARY ITERATION (additive, 2026-07-03: the typo/suggest rung reads the index's own sorted
3920// term dictionary -- the same .terms blobs the postings live in; no separate dictionary artifact).
3921// ss_term_count(h, s) = how many terms segment s indexes; ss_term_at fills term ptr/len + dcount for
3922// entry idx (terms are sorted within a segment). Returns 1 ok / 0 out-of-range or absent index.
3923func ss_term_count(h: *i64, s: i64) -> i64 {
3924 if s < 0 { return 0 }
3925 if s >= h[0] { return 0 }
3926 let tb: *u8 = h[5 + 8 * s] as *u8
3927 if h[6 + 8 * s] < 8 { return 0 }
3928 return ss_r32(tb, 4)
3929}
3930func ss_term_at(h: *i64, s: i64, idx: i64, tp_out: *i64, tl_out: *i64, dc_out: *i64) -> i64 {
3931 if s < 0 { return 0 }
3932 if s >= h[0] { return 0 }
3933 let tb: *u8 = h[5 + 8 * s] as *u8
3934 let tsz: i64 = h[6 + 8 * s]
3935 if tsz < 8 { return 0 }
3936 let n: i64 = ss_r32(tb, 4)
3937 if idx < 0 { return 0 }
3938 if idx >= n { return 0 }
3939 let base: i64 = 8 + 4 * n
3940 let eo: i64 = base + ss_r32(tb, 8 + 4 * idx)
3941 let tl: i64 = ss_r32(tb, eo)
3942 tp_out[0] = (tb as i64) + eo + 4
3943 tl_out[0] = tl
3944 dc_out[0] = ss_r32(tb, eo + 4 + tl + 8)
3945 return 1
3946}
3947// PREFIX LOWER BOUND over a segment's SORTED term dictionary -> the ordinal of the first term whose
3948// first `pfxlen` bytes are >= pfx[0..pfxlen). The dictionary is sorted BY CONSTRUCTION (ss_build_terms:
3949// "term dict ordering = bottom-up merge sort"), and ss_terms_find already binary-searches it for an
3950// EXACT key -- this is that same search generalised to a PREFIX, so a caller can walk only the matching
3951// run instead of the whole dictionary. Returns n when no term reaches the prefix (the caller's
3952// `while e < tc` then simply does not execute). pfxlen <= 0 returns 0 = "start at the beginning": an
3953// empty prefix selects the whole dictionary, which is exactly what a full scan would have done.
3954func ss_term_lb(h: *i64, s: i64, pfx: *u8, pfxlen: i64) -> i64 {
3955 if s < 0 { return 0 }
3956 if s >= h[0] { return 0 }
3957 let tb: *u8 = h[5 + 8 * s] as *u8
3958 let tsz: i64 = h[6 + 8 * s]
3959 if tsz < 8 { return 0 }
3960 let n: i64 = ss_r32(tb, 4)
3961 if pfxlen <= 0 { return 0 }
3962 let base: i64 = 8 + 4 * n
3963 // SAMPLED WINDOW (2026-09-14): the first term >= the prefix lies between the sample just below the first
3964 // sample >= the zero-padded prefix and the first sample above it (a term whose bytes are below the prefix
3965 // packs below it; one at or above packs at or above it), so the search starts there instead of at [0, n).
3966 if (sst_lohi as i64) == 0 { sst_lohi = sys_mmap(16) as *i64 }
3967 ss_tsample_window(h, s, ss_tpack(pfx, pfxlen), n, sst_lohi)
3968 var lo: i64 = sst_lohi[0]
3969 var hi: i64 = sst_lohi[1]
3970 while lo < hi {
3971 let mid: i64 = (lo + hi) / 2
3972 let eo: i64 = base + ss_r32(tb, 8 + 4 * mid)
3973 let tl: i64 = ss_r32(tb, eo)
3974 let tp: *u8 = (tb as i64 + eo + 4) as *u8
3975 // c = sign of compare(term[0..min(tl,pfxlen)), pfx[0..pfxlen)); a term SHORTER than the prefix
3976 // sorts below it. The exit uses a separate `done` flag and NEVER the cursor `k`, so the loop
3977 // cannot destroy the position it is reading.
3978 var c: i64 = 0
3979 var k: i64 = 0
3980 var done: i64 = 0
3981 while done == 0 {
3982 if k >= pfxlen { done = 1 } else {
3983 if k >= tl { c = 0 - 1; done = 1 } else {
3984 let av: i64 = tp[k] as i64
3985 let bv: i64 = pfx[k] as i64
3986 if av < bv { c = 0 - 1; done = 1 } else {
3987 if av > bv { c = 1; done = 1 } else { k = k + 1 }
3988 }
3989 }
3990 }
3991 }
3992 if c < 0 { lo = mid + 1 } else { hi = mid }
3993 }
3994 return lo
3995}
3996
3997// N: how many "doc:"-prefixed put-keys the live segments' key indexes hold (each .keys entry is its
3998// key's latest-in-segment; cross-segment re-adds are prevented upstream by skip-if-present ingest, so
3999// this is the corpus size for IDF at O(total keys) once per query -- exactness via compaction later).
4000func ss_doc_count_walk(h: *i64) -> i64 {
4001 let ns: i64 = h[0]
4002 var n: i64 = 0
4003 var s: i64 = 0
4004 while s < ns {
4005 let kb: *u8 = h[1 + 8 * s] as *u8
4006 if h[2 + 8 * s] >= 8 {
4007 let m9: i64 = ss_r32(kb, 4)
4008 var e9: i64 = 0
4009 while e9 < m9 {
4010 let eo: i64 = 8 + 4 * m9 + ss_r32(kb, 8 + 4 * e9)
4011 if (kb[eo] as i64) == 1 {
4012 let kl9: i64 = ss_r32(kb, eo + 1)
4013 if kl9 >= 4 {
4014 if kb[eo + 5] == (100 as u8) { if kb[eo + 6] == (111 as u8) { if kb[eo + 7] == (99 as u8) { if kb[eo + 8] == (58 as u8) {
4015 n = n + 1
4016 } } } }
4017 }
4018 }
4019 e9 = e9 + 1
4020 }
4021 }
4022 s = s + 1
4023 }
4024 return n
4025}
4026// ss_doc_count: the corpus size for idf. FAST PATH (search L5, 2026-09-14): every row carries its own doc-key count
4027// from load time (ss_doccount_build, aux slot SS_AUX_DOCN), so this sums ns words; if any row lacks one (a handle
4028// built by an older path) it falls back to the walk above, so the number is the same either way. PROVEN on the
4029// live shard by nx_doccount_gate: sum == walk, a poisoned row moves the sum by exactly its poison while the walk
4030// does not, and a cleared row still answers the walk's number through the fallback.
4031func ss_doc_count(h: *i64) -> i64 {
4032 let ns: i64 = h[0]
4033 var sum: i64 = 0
4034 var all: i64 = 1
4035 var sq: i64 = 0
4036 while sq < ns {
4037 let r: *i64 = ss_aux_row(h, sq)
4038 if (r as i64) == 0 { all = 0 } else { if r[SS_AUX_DOCN] <= 0 { all = 0 } else { sum = sum + (r[SS_AUX_DOCN] - 1) } }
4039 sq = sq + 1
4040 }
4041 if all == 1 { return sum }
4042 return ss_doc_count_walk(h)
4043}
4044
4045// ---- PHRASE SUPPORT (2026-07-03, additive): the NXQ1 positions sidecar readers + the evaluator ----
4046// like ss_terms_find but returns the term's SORTED ORDINAL (the NXQ1 entry index) instead of just 1
4047func ss_terms_ordinal(tb: *u8, tsz: i64, term: *u8, outs: *i64) -> i64 {
4048 if tsz < 8 { return 0 - 2 }
4049 if tb[0] != (78 as u8) { return 0 - 2 }
4050 if tb[2] != (84 as u8) { return 0 - 2 }
4051 let n: i64 = ss_r32(tb, 4)
4052 let base: i64 = 8 + 4 * n
4053 let tl0: i64 = ss_len(term)
4054 var lo: i64 = 0
4055 var hi: i64 = n - 1
4056 while lo <= hi {
4057 let mid: i64 = (lo + hi) / 2
4058 let eo: i64 = base + ss_r32(tb, 8 + 4 * mid)
4059 let tl: i64 = ss_r32(tb, eo)
4060 let c: i64 = ss_kcmp((tb as i64 + eo + 4) as *u8, tl, term, tl0)
4061 if c == 0 {
4062 outs[0] = ss_r32(tb, eo + 4 + tl)
4063 outs[1] = ss_r32(tb, eo + 4 + tl + 4)
4064 outs[2] = ss_r32(tb, eo + 4 + tl + 8)
4065 return mid
4066 }
4067 if c < 0 { lo = mid + 1 }
4068 if c > 0 { hi = mid - 1 }
4069 }
4070 return 0 - 1
4071}
4072// decode the position run for posting-rank `rank` of term-ordinal `ord` from an NXQ1 blob.
4073// Returns npos decoded into posout (cap maxpos; longer runs are truncated -- adjacency over the first
4074// maxpos occurrences), or -1 malformed/absent.
4075func ss_q_positions(qb: *u8, qsz: i64, ord: i64, rank: i64, posout: *i64, maxpos: i64) -> i64 {
4076 if qsz < 8 { return 0 - 1 }
4077 if qb[0] != (78 as u8) { return 0 - 1 }
4078 if qb[2] != (81 as u8) { return 0 - 1 }
4079 let nt: i64 = ss_r32(qb, 4)
4080 if ord < 0 { return 0 - 1 }
4081 if ord >= nt { return 0 - 1 }
4082 let qbase: i64 = 8 + 4 * nt
4083 let pos: *i64 = sys_mmap(16) as *i64
4084 pos[0] = qbase + ss_r32(qb, 8 + 4 * ord)
4085 // skip `rank` runs
4086 var r: i64 = 0
4087 while r < rank {
4088 let n0: i64 = ss_vr(qb, pos)
4089 var k0: i64 = 0
4090 while k0 < n0 { ss_vr(qb, pos); k0 = k0 + 1 }
4091 r = r + 1
4092 }
4093 let np: i64 = ss_vr(qb, pos)
4094 var cum: i64 = 0
4095 var w: i64 = 0
4096 var k: i64 = 0
4097 while k < np {
4098 cum = cum + ss_vr(qb, pos)
4099 if w < maxpos { posout[w] = cum; w = w + 1 }
4100 k = k + 1
4101 }
4102 return w
4103}
4104// IMPACT-ORDERED TERM SEARCH (2026-07-25, the WAND rung): like ss_term but when the term has more
4105// postings than `max`, the cap keeps the HIGHEST-tf postings across all live segments instead of the
4106// first `max` in (segment, doc-asc) order -- and each hit's tf is returned (tfout) so candidacy needs
4107// zero doc reads. Cost is O(segments * SS_IMP_K), NOT O(total postings): the full-list decode ss_term
4108// pays per query disappears. Requires EVERY live segment to carry .imp: returns -3 when any lacks it
4109// and the caller falls back to ss_term (exact legacy behavior; compaction / nx_seg_imp_build upgrade a
4110// shard in place). -2 = missing .terms (same loud contract as ss_term). Currency semantics identical:
4111// the per-segment live-doc map filters stale versions and tombstones. Ties in tf resolve newest-
4112// segment-first then ascending doc -- deterministic.
4113// satout[0] (1-slot): set to 1 iff the answer is TRUNCATED -- a segment's stored impact list was
4114// build-capped at SS_IMP_K, the cross-segment collect filled, or the emit dropped past `max`. This is
4115// the reader's OWN truncation knowledge: exact by construction, immune to the shadow-inflated
4116// write-time dcount statistic (a re-committed doc inflates dcnt but never this flag).
4117// ADDITIVE 2026-08-06 -- ss_term_top with an optional PER-SEGMENT SKIP MASK. mask[s]==0 means the
4118// caller has already PROVEN that segment s cannot contain this term (e.g. from a per-segment term
4119// bloom it owns and has verified sound against ground truth), so the segment is skipped ENTIRELY:
4120// its .imp is never opened and its terms block is never touched.
4121//
4122// WHY THIS IS THE HOT PATH: the loop below opens <prefix><segid>.imp for EVERY live segment BEFORE
4123// any term lookup happens -- 900 file opens per query on the web shard, whether or not the term
4124// exists anywhere in it. MEASURED 2026-08-06: that up-front load is the ~6.6s floor that makes a
4125// query matching ZERO documents cost the same as one matching 28,000 (a 42ms control on the same
4126// daemon proves the cost is here, not in transport, dispatch or ranking).
4127//
4128// ORDERING SEMANTICS ARE PRESERVED, NOT TRADED AWAY. The all-or-nothing .imp check exists so a
4129// half-upgraded shard cannot serve inconsistent ordering. A segment that provably lacks the term
4130// contributes NOTHING to this query's candidate set, so its .imp can affect neither the results nor
4131// their order -- skipping it is sound for exactly the reason the mask is. A bloom may return a false
4132// POSITIVE (costing one wasted probe) but NEVER a false negative, so no candidate is ever lost.
4133//
4134// The store deliberately learns NOTHING about blooms: importing a filter module HERE would make
4135// every consumer of nx_seg_store depend on it -- the trap nx_hostctl_keepbackoff_gate documents
4136// ("would leave the supervisor UNBUILDABLE on any host that has not yet received that module").
4137// The caller owns the filter and hands down an opaque byte mask.
4138// mask == 0 => probe every segment == ss_term_top's exact historical behaviour.
4139func ss_term_top_mask(prefix: *u8, h: *i64, mask: *u8, term: *u8, kpout: *i64, klout: *i64, tfout: *i64, max: i64, satout: *i64) -> i64 {
4140 satout[0] = 0
4141 let ns: i64 = h[0]
4142 var s0: i64 = 0
4143 while s0 < ns {
4144 if h[6 + 8 * s0] < 8 { return 0 - 2 }
4145 s0 = s0 + 1
4146 }
4147 if max <= 0 { return 0 }
4148 let sp2: *i64 = sys_mmap(8) as *i64
4149 let ns2: i64 = ss_manifest_dyn(prefix, sp2)
4150 let segs: *i64 = sp2[0] as *i64
4151 var nseg: i64 = ns
4152 if ns2 < nseg { nseg = ns2 }
4153 if nseg <= 0 { return 0 }
4154 // load every live segment's .imp up front: ANY absent -> -3 (all-or-nothing keeps the ordering
4155 // semantics whole; a half-upgraded shard serves exactly like a non-upgraded one)
4156 let ibs: *i64 = sys_mmap(8 * (nseg + 4)) as *i64
4157 let iszs: *i64 = sys_mmap(8 * (nseg + 4)) as *i64
4158 let np9: *u8 = sys_mmap(512)
4159 let szp9: *i64 = sys_mmap(16) as *i64
4160 var s: i64 = 0
4161 while s < nseg {
4162 var o9: i64 = 0
4163 o9 = ss_cat(np9, o9, prefix)
4164 o9 = ss_cat(np9, o9, segs[s] as *u8)
4165 o9 = ss_cat(np9, o9, ".imp" as *u8)
4166 np9[o9] = 0 as u8
4167 szp9[0] = 0
4168 // MASK GATE: a segment the caller proved cannot hold this term never gets its .imp opened.
4169 // This is the 900-file-opens-per-query cost, and it is paid BEFORE any term lookup.
4170 // LAZY (2026-08-06): do NOT load .imp here at all. It is READ ONLY inside `od >= 0` below --
4171 // i.e. only for a segment that actually holds the term -- so loading all 900 up front is pure
4172 // waste, and it IS the measured ~6.6s floor: a query matching ZERO documents paid 900 file
4173 // opens to read none of them. The load moves down to its single consumer.
4174 var want9: i64 = 0
4175 var ib: *u8 = 0 as *u8
4176 var ok9: i64 = 0
4177 if (ib as i64) != 0 { if szp9[0] >= 8 { if ib[0] == (78 as u8) { if ib[2] == (87 as u8) { ok9 = 1 } } } }
4178 // A MASKED-OUT SEGMENT IS NOT A MISSING .imp. The -3 refusal below means "this shard is half
4179 // upgraded, degrade the whole query"; a segment we deliberately skipped has not been examined
4180 // and must not be reported as broken, or the mask would turn every fast query into a refusal.
4181 if want9 == 0 { ok9 = 1 }
4182 if ok9 == 0 { return 0 - 3 }
4183 ibs[s] = ib as i64
4184 iszs[s] = szp9[0]
4185 s = s + 1
4186 }
4187 // collect live (segment, docidx, tf) from each impact list, newest segment first
4188 let cap: i64 = nseg * SS_IMP_K + 16
4189 let colls: *i64 = sys_mmap(8 * cap) as *i64
4190 let colld: *i64 = sys_mmap(8 * cap) as *i64
4191 let collt: *i64 = sys_mmap(8 * cap) as *i64
4192 var n: i64 = 0
4193 let outs: *i64 = sys_mmap(32) as *i64
4194 let vp: *i64 = sys_mmap(16) as *i64
4195 // PREFETCH PASS (2026-09-14, from the search phase timers: stage 1 read 0.2-0.5 s cold once its key reads
4196 // were index-resident, one cold impact-block fault per (segment, term) served in series). Resolve the term
4197 // in every segment first, map its .imp and hand the kernel the term's whole impact block, so the collect
4198 // below reads warm and the disk services all segments together. Advisory: the collect is unchanged.
4199 var sq: i64 = nseg - 1
4200 while sq >= 0 {
4201 var useq: i64 = 1
4202 if (mask as i64) != 0 { if mask[sq] == (0 as u8) { useq = 0 } }
4203 var odq: i64 = 0 - 1
4204 if useq == 1 { odq = ss_terms_ordinal_h(h, sq, term, outs) }
4205 if odq >= 0 {
4206 if ibs[sq] == 0 {
4207 var oq: i64 = 0
4208 oq = ss_cat(np9, oq, prefix)
4209 oq = ss_cat(np9, oq, segs[sq] as *u8)
4210 oq = ss_cat(np9, oq, ".imp" as *u8)
4211 np9[oq] = 0 as u8
4212 szp9[0] = 0
4213 let ibq: *u8 = ss_loadfile(np9, szp9, 1)
4214 var okq: i64 = 0
4215 if (ibq as i64) != 0 { if szp9[0] >= 8 { if ibq[0] == (78 as u8) { if ibq[2] == (87 as u8) { okq = 1 } } } }
4216 if okq == 1 { ibs[sq] = ibq as i64; iszs[sq] = szp9[0] }
4217 }
4218 if ibs[sq] != 0 {
4219 let ibq2: *u8 = ibs[sq] as *u8
4220 let ntq: i64 = ss_r32(ibq2, 4)
4221 if odq < ntq {
4222 let ibaseq: i64 = 8 + 4 * ntq
4223 let bsq: i64 = ibaseq + ss_r32(ibq2, 8 + 4 * odq)
4224 var beq: i64 = iszs[sq]
4225 if odq + 1 < ntq { beq = ibaseq + ss_r32(ibq2, 8 + 4 * (odq + 1)) }
4226 if beq > iszs[sq] { beq = iszs[sq] }
4227 if beq > bsq {
4228 let baq: i64 = (ibq2 as i64) + bsq
4229 let balq: i64 = (baq / SS_MAGIC_4096) * SS_MAGIC_4096
4230 sys_madvise(balq as *u8, (baq - balq) + (beq - bsq), SS_MADV_WILLNEED)
4231 }
4232 }
4233 }
4234 }
4235 sq = sq - 1
4236 }
4237 s = nseg - 1
4238 while s >= 0 {
4239 let tb: *u8 = h[5 + 8 * s] as *u8
4240 let lm: *u8 = h[1 + 8 * ns + s] as *u8
4241 // MASK GATE 2: skip the terms-block probe too. Together with the .imp gate above, a masked
4242 // segment costs ZERO page faults instead of one file open plus one terms-block fault.
4243 var od: i64 = 0 - 1
4244 var use9: i64 = 1
4245 if (mask as i64) != 0 { if mask[s] == (0 as u8) { use9 = 0 } }
4246 if use9 == 1 { od = ss_terms_ordinal_h(h, s, term, outs) } // sampled window, same answer (2026-09-14)
4247 if od >= 0 {
4248 // LAZY .imp LOAD -- the single consumer. Only a segment that ACTUALLY holds the term
4249 // needs its impact list, and -3 (shard not .imp-upgraded) still fires here, where it is
4250 // load-bearing: a segment that cannot contribute a candidate cannot affect ordering, so
4251 // never examining it is sound. Loaded once per segment per call, then cached in ibs[].
4252 if ibs[s] == 0 {
4253 var oL: i64 = 0
4254 oL = ss_cat(np9, oL, prefix)
4255 oL = ss_cat(np9, oL, segs[s] as *u8)
4256 oL = ss_cat(np9, oL, ".imp" as *u8)
4257 np9[oL] = 0 as u8
4258 szp9[0] = 0
4259 let ibL: *u8 = ss_loadfile(np9, szp9, 1)
4260 var okL: i64 = 0
4261 if (ibL as i64) != 0 { if szp9[0] >= 8 { if ibL[0] == (78 as u8) { if ibL[2] == (87 as u8) { okL = 1 } } } }
4262 if okL == 0 { return 0 - 3 }
4263 ibs[s] = ibL as i64
4264 iszs[s] = szp9[0]
4265 }
4266 let ib2: *u8 = ibs[s] as *u8
4267 let nt9: i64 = ss_r32(ib2, 4)
4268 if od < nt9 {
4269 let ibase: i64 = 8 + 4 * nt9
4270 vp[0] = ibase + ss_r32(ib2, 8 + 4 * od)
4271 let k9: i64 = ss_vr(ib2, vp)
4272 if k9 >= SS_IMP_K { satout[0] = 1 }
4273 var i9: i64 = 0
4274 while i9 < k9 {
4275 if vp[0] > iszs[s] { i9 = k9 } else {
4276 let d9: i64 = ss_vr(ib2, vp)
4277 let f9: i64 = ss_vr(ib2, vp)
4278 if lm[d9] == (1 as u8) {
4279 if n < cap { colls[n] = s; colld[n] = d9; collt[n] = f9; n = n + 1 } else { satout[0] = 1 }
4280 }
4281 i9 = i9 + 1
4282 }
4283 }
4284 }
4285 }
4286 s = s - 1
4287 }
4288 if n == 0 { return 0 }
4289 // STRICT tf-DESCENDING emit via counting sort over the clamped-tf buckets: O(n + 1024), stable
4290 // within a bucket (collect order = newest segment first, list order within = ascending doc), so
4291 // ties are deterministic. tf >= 1023 collapses into the top bucket -- ordering granularity above
4292 // that is irrelevant to candidacy and the STORED tf stays true. A future WAND early-termination
4293 // reader can rely on this prefix-is-best contract.
4294 let hist2: *i64 = sys_mmap(8 * SS_MAGIC_1024) as *i64
4295 var ih: i64 = 0
4296 while ih < n {
4297 var cv: i64 = collt[ih]
4298 if cv > 1023 { cv = 1023 }
4299 hist2[cv] = hist2[cv] + 1
4300 ih = ih + 1
4301 }
4302 let boff: *i64 = sys_mmap(8 * SS_MAGIC_1024) as *i64
4303 var acc2: i64 = 0
4304 var bb: i64 = 1023
4305 while bb >= 0 {
4306 boff[bb] = acc2
4307 acc2 = acc2 + hist2[bb]
4308 bb = bb - 1
4309 }
4310 let ord: *i64 = sys_mmap(8 * (n + 4)) as *i64
4311 ih = 0
4312 while ih < n {
4313 var cv2: i64 = collt[ih]
4314 if cv2 > 1023 { cv2 = 1023 }
4315 ord[boff[cv2]] = ih
4316 boff[cv2] = boff[cv2] + 1
4317 ih = ih + 1
4318 }
4319 var k2: i64 = n
4320 if k2 > max { k2 = max; satout[0] = 1 }
4321 var cnt: i64 = 0
4322 while cnt < k2 {
4323 let i2: i64 = ord[cnt]
4324 let sx: i64 = colls[i2]
4325 let pb: *u8 = h[7 + 8 * sx] as *u8
4326 let db: i64 = h[3 + 8 * sx]
4327 // INDEX-RESIDENT KEY (2026-09-14): the .keys slice answers the key; only a segment without its d2k row
4328 // (or a doc the row does not cover) reads the .docs entry as before. Same bytes either way.
4329 if ss_key_of_doc(h, sx, colld[i2], ((kpout as i64) + 8 * cnt) as *i64, ((klout as i64) + 8 * cnt) as *i64) == 0 {
4330 let eoff: i64 = ss_r32(pb, 8 + 4 * colld[i2])
4331 let ep: *u8 = (db + eoff) as *u8
4332 kpout[cnt] = db + eoff + 5
4333 klout[cnt] = ss_r32(ep, 1)
4334 }
4335 tfout[cnt] = collt[i2]
4336 cnt = cnt + 1
4337 }
4338 return cnt
4339}
4340
4341// BACK-COMPAT WRAPPER: every existing caller keeps its exact signature and its exact behaviour.
4342// mask == 0 makes both gates above no-ops, so this is the pre-2026-08-06 function byte-for-byte in
4343// effect. Rule 19: add the capability, never break the contract.
4344func ss_term_top(prefix: *u8, h: *i64, term: *u8, kpout: *i64, klout: *i64, tfout: *i64, max: i64, satout: *i64) -> i64 {
4345 return ss_term_top_mask(prefix, h, 0 as *u8, term, kpout, klout, tfout, max, satout)
4346}
4347
4348// THE PHRASE EVALUATOR: docs whose CURRENT text contains terms[0..nterms) as ADJACENT tokens, in order.
4349// Candidates come from the per-segment postings intersect (AND); adjacency is checked in the NXQ1
4350// sidecar. A segment WITHOUT a sidecar (pre-phrase format) contributes its AND matches and clears
4351// exactout[0] -- graceful degrade, never a refusal; compaction upgrades it. -2 = missing .terms (loud,
4352// same contract as ss_term). exactout[0]: 1 = adjacency enforced everywhere, 0 = degraded somewhere.
4353func ss_phrase(prefix: *u8, h: *i64, terms: *i64, nterms: i64, kpout: *i64, klout: *i64, max: i64, exactout: *i64) -> i64 {
4354 exactout[0] = 1
4355 if nterms < 2 { return 0 }
4356 if nterms > 8 { return 0 - 3 }
4357 let ns: i64 = h[0]
4358 var s0: i64 = 0
4359 while s0 < ns {
4360 if h[6 + 8 * s0] < 8 { return 0 - 2 }
4361 s0 = s0 + 1
4362 }
4363 let sp2: *i64 = sys_mmap(8) as *i64
4364 let ns2: i64 = ss_manifest_dyn(prefix, sp2)
4365 let segs: *i64 = sp2[0] as *i64
4366 var nseg: i64 = ns
4367 if ns2 < nseg { nseg = ns2 }
4368 let ords: *i64 = sys_mmap(8 * 8) as *i64
4369 let outs: *i64 = sys_mmap(32) as *i64
4370 let poffs: *i64 = sys_mmap(8 * 8) as *i64
4371 let pdcs: *i64 = sys_mmap(8 * 8) as *i64
4372 let p1: *i64 = sys_mmap(8 * 520) as *i64
4373 let p2: *i64 = sys_mmap(8 * 520) as *i64
4374 let qszp: *i64 = sys_mmap(16) as *i64
4375 let vpos: *i64 = sys_mmap(16) as *i64
4376 var cnt: i64 = 0
4377 var s: i64 = 0
4378 while s < nseg {
4379 let tb: *u8 = h[5 + 8 * s] as *u8
4380 let pb: *u8 = h[7 + 8 * s] as *u8
4381 let db: i64 = h[3 + 8 * s]
4382 let lm: *u8 = h[1 + 8 * ns + s] as *u8
4383 // every phrase term must exist in THIS segment's dict
4384 var allp: i64 = 1
4385 var t: i64 = 0
4386 while t < nterms {
4387 let od: i64 = ss_terms_ordinal_h(h, s, terms[t] as *u8, outs) // sampled window (2026-09-14)
4388 if od < 0 { allp = 0 } else {
4389 ords[t] = od
4390 poffs[t] = outs[0]
4391 pdcs[t] = outs[2]
4392 }
4393 t = t + 1
4394 }
4395 if allp == 1 {
4396 // decode each term's posting doc list (ascending)
4397 let dlist: *i64 = sys_mmap(8 * 8) as *i64
4398 var t2: i64 = 0
4399 while t2 < nterms {
4400 let arr: *i64 = sys_mmap(8 * (pdcs[t2] + 4)) as *i64
4401 vpos[0] = poffs[t2]
4402 var prev: i64 = 0
4403 var k: i64 = 0
4404 while k < pdcs[t2] {
4405 prev = prev + ss_vr(pb, vpos)
4406 arr[k] = prev
4407 k = k + 1
4408 }
4409 dlist[t2] = arr as i64
4410 t2 = t2 + 1
4411 }
4412 // the segment's positions sidecar (absent -> degrade this segment to AND)
4413 let qp: *u8 = sys_mmap(512)
4414 var qo2: i64 = 0
4415 qo2 = ss_cat(qp, qo2, prefix)
4416 qo2 = ss_cat(qp, qo2, segs[s] as *u8)
4417 qo2 = ss_cat(qp, qo2, ".pos" as *u8)
4418 qp[qo2] = 0 as u8
4419 qszp[0] = 0
4420 let qb: *u8 = ss_readall(qp, qszp)
4421 var haveq: i64 = 0
4422 if (qb as i64) != 0 { if qszp[0] >= 8 { haveq = 1 } }
4423 if haveq == 0 { exactout[0] = 0 }
4424 // walk term0's docs; a doc survives iff present in EVERY list (ranks captured for NXQ1)
4425 let l0: *i64 = dlist[0] as *i64
4426 var i0: i64 = 0
4427 while i0 < pdcs[0] {
4428 let d: i64 = l0[i0]
4429 var inall: i64 = 1
4430 let ranks: *i64 = sys_mmap(8 * 8) as *i64
4431 ranks[0] = i0
4432 var t3: i64 = 1
4433 while t3 < nterms {
4434 let lt: *i64 = dlist[t3] as *i64
4435 var lo2: i64 = 0
4436 var hi2: i64 = pdcs[t3] - 1
4437 var fnd: i64 = 0 - 1
4438 while lo2 <= hi2 {
4439 let mid2: i64 = (lo2 + hi2) / 2
4440 if lt[mid2] == d { fnd = mid2; lo2 = hi2 + 1 } else {
4441 if lt[mid2] < d { lo2 = mid2 + 1 } else { hi2 = mid2 - 1 }
4442 }
4443 }
4444 if fnd < 0 { inall = 0; t3 = nterms } else { ranks[t3] = fnd }
4445 t3 = t3 + 1
4446 }
4447 if inall == 1 { if lm[d] == (1 as u8) {
4448 var matched: i64 = 1
4449 if haveq == 1 {
4450 // adjacency ladder over the sidecar positions
4451 var na: i64 = ss_q_positions(qb, qszp[0], ords[0], ranks[0], p1, 512)
4452 var t4: i64 = 1
4453 while t4 < nterms {
4454 if na <= 0 { t4 = nterms } else {
4455 let nb: i64 = ss_q_positions(qb, qszp[0], ords[t4], ranks[t4], p2, 512)
4456 // S = { q in positions(t4) : q-1 in S_prev } (two-pointer, both ascending)
4457 var wa: i64 = 0
4458 var ia: i64 = 0
4459 var ib: i64 = 0
4460 while ia < na {
4461 if ib >= nb { ia = na } else {
4462 let want: i64 = p1[ia] + 1
4463 if p2[ib] == want { p1[wa] = want; wa = wa + 1; ia = ia + 1; ib = ib + 1 } else {
4464 if p2[ib] < want { ib = ib + 1 } else { ia = ia + 1 }
4465 }
4466 }
4467 }
4468 na = wa
4469 t4 = t4 + 1
4470 }
4471 }
4472 if na <= 0 { matched = 0 }
4473 }
4474 if matched == 1 {
4475 let eoff: i64 = ss_r32(pb, 8 + 4 * d)
4476 let ep: *u8 = (db + eoff) as *u8
4477 if cnt < max {
4478 kpout[cnt] = db + eoff + 5
4479 klout[cnt] = ss_r32(ep, 1)
4480 cnt = cnt + 1
4481 }
4482 }
4483 } }
4484 i0 = i0 + 1
4485 }
4486 }
4487 s = s + 1
4488 }
4489 return cnt
4490}
4491
4492// Indexed get (IM2): newest segment -> oldest, binary search each .keys;
4493// first hit decides (identical semantics to the chronological scan's
4494// last-match -- the gate proves equivalence against the scan as ORACLE).
4495// Any missing/malformed index => honest fallback to the scan path.
4496// SCRATCH ALLOCATED ONCE (2026-07-30, same class as the ss_hget fix above). ss_get_idx allocated FIVE
4497// page-granular scratch buffers PER CALL (sp/vo/vl/path/szp) and never freed them -- ~20 KB per call
4498// across 30 call sites. Statics are safe here for the same reason as ss_hget: these are pure scratch,
4499// written then read with no intervening call that re-enters ss_get_idx (VERIFIED: neither ss_get nor
4500// ss_idx_lookup calls back into it).
4501// âš DELIBERATELY NOT CACHED: the `b` buffer below (ss_readall of the whole .docs file) is what ptrout
4502// POINTS INTO, so it is returned data, not scratch -- caching or freeing it is the seq1356
4503// borrowed-pointer question and must not be done casually. Filed, not guessed at.
4504// âš ALSO STILL OPEN: this function re-reads the MANIFEST and re-maps a whole segment file on EVERY
4505// call -- the O(rows x segments) amplification seq356 already identified. The real remedy is the one
4506// sts_load adopted: ss_open ONCE + ss_hget per row. These statics only stop the bleeding.
4507static ssg_sp: *i64
4508static ssg_vo: *i64
4509static ssg_vl: *i64
4510func ss_get_idx(prefix: *u8, key: *u8, ptrout: *i64, lenout: *i64) -> i64 {
4511 return ss_get_idx_mode(prefix,key,ptrout,lenout,1)
4512}
4513// Authorization callers cannot interpret a failed index read as evidence of absence.
4514// Keep scan compatibility explicit for existing readers; strict callers receive -2.
4515func ss_get_idx_required(prefix: *u8, key: *u8, ptrout: *i64, lenout: *i64) -> i64 {
4516 return ss_get_idx_mode(prefix,key,ptrout,lenout,0)
4517}
4518func ss_get_idx_mode(prefix: *u8, key: *u8, ptrout: *i64, lenout: *i64, allow_scan: i64) -> i64 {
4519 if (ssg_sp as i64) == 0 { ssg_sp = sys_mmap(8) as *i64 }
4520 let sp: *i64 = ssg_sp
4521 let ns: i64 = ss_manifest_dyn(prefix, sp)
4522 let segs: *i64 = sp[0] as *i64
4523 if ns == 0 { return 0 - 1 }
4524 if (ssg_vo as i64) == 0 { ssg_vo = sys_mmap(16) as *i64 }
4525 if (ssg_vl as i64) == 0 { ssg_vl = sys_mmap(16) as *i64 }
4526 let vo: *i64 = ssg_vo
4527 let vl: *i64 = ssg_vl
4528 var s: i64 = ns - 1
4529 while s >= 0 {
4530 let r: i64 = ss_idx_lookup(prefix, segs[s] as *u8, key, vo, vl)
4531 if r == (0 - 2) {
4532 if allow_scan == 1 { return ss_get(prefix,key,ptrout,lenout) }
4533 return 0 - 2
4534 }
4535 if r == 2 { return 0 }
4536 if r == 1 {
4537 let path: *u8 = sys_mmap(512)
4538 var o: i64 = 0
4539 o = ss_cat(path, o, prefix)
4540 o = ss_cat(path, o, segs[s] as *u8)
4541 o = ss_cat(path, o, ".docs" as *u8)
4542 path[o] = 0 as u8
4543 let szp: *i64 = sys_mmap(16) as *i64
4544 let b: *u8 = ss_readall(path, szp)
4545 if szp[0] < vo[0] + vl[0] { return 0 - 2 }
4546 ptrout[0] = (b as i64) + vo[0]
4547 lenout[0] = vl[0]
4548 return 1
4549 }
4550 s = s - 1
4551 }
4552 return 0 - 1
4553}
4554
4555// latest state of key: 1=found (ptrout[0]/lenout[0] set), 0=tombstoned, -1=absent
4556func ss_get(prefix: *u8, key: *u8, ptrout: *i64, lenout: *i64) -> i64 {
4557 let kinds: *i64 = sys_mmap(8 * SS_VER_SLOTS) as *i64
4558 let ptrs: *i64 = sys_mmap(8 * SS_VER_SLOTS) as *i64
4559 let lens: *i64 = sys_mmap(8 * SS_VER_SLOTS) as *i64
4560 let n: i64 = ss_scan(prefix, key, kinds, ptrs, lens)
4561 if n == 0 { return 0 - 1 }
4562 let last: i64 = n - 1
4563 if kinds[last] == 2 { return 0 }
4564 ptrout[0] = ptrs[last]
4565 lenout[0] = lens[last]
4566 return 1
4567}
4568
4569// ===== SEQUENTIAL CURSOR (added 2026-07-30) ================================
4570// THE DEFECT THIS KILLS: ss_get is a POINT lookup costing O(WHOLE-STORE BYTES)
4571// every call -- ss_scan -> ss_scan_seglist ss_readall()s the ENTIRE <seg>.docs
4572// of EVERY live segment per call and returns a pointer INTO that fresh mapping
4573// (so it can never be freed while in use). A whole-store pass built from point
4574// lookups is quadratic in time AND unbounded in mapped memory.
4575// MEASURED on the mvault store (37,058 records / 3 segments): 200 lookups
4576// survive, 10,000 DIES (rc=1, zero output). Read-side twin of the eaten
4577// seg-store quadratic WRITE defect.
4578// COST: one mapping per LIVE SEGMENT, not one per RECORD.
4579// CONTRACT: kout/vout point INTO the current mapping and are valid only until
4580// the NEXT ss_cur_next call. Records arrive chronologically, so for a key with
4581// several versions the LAST seen is current -- the same rule ss_get applies.
4582
4583const SS_CUR_SLOTS: i64 = 8
4584
4585func ss_cur_open(prefix: *u8) -> *i64 {
4586 let st: *i64 = sys_mmap(8 * SS_CUR_SLOTS) as *i64
4587 let sp: *i64 = sys_mmap(8) as *i64
4588 let ns: i64 = ss_manifest_dyn(prefix, sp)
4589 st[0] = sp[0]
4590 st[1] = ns
4591 st[2] = 0
4592 st[3] = 0
4593 st[4] = 0
4594 st[5] = 0
4595 st[6] = 0
4596 return st
4597}
4598
4599func ss_cur_next(prefix: *u8, st: *i64,
4600 kout: *i64, klout: *i64, vout: *i64, vlout: *i64) -> i64 {
4601 var guard: i64 = 0
4602 while guard < SS_MAGIC_1048576 {
4603 guard = guard + 1
4604 if st[3] == 0 {
4605 if st[2] >= st[1] { return 0 }
4606 let segs: *i64 = st[0] as *i64
4607 let path: *u8 = sys_mmap(512)
4608 var o: i64 = ss_cat(path, 0, prefix)
4609 o = ss_cat(path, o, segs[st[2]] as *u8)
4610 o = ss_cat(path, o, ".docs" as *u8)
4611 path[o] = 0 as u8
4612 let szp: *i64 = sys_mmap(16) as *i64
4613 let b: *u8 = ss_readall(path, szp)
4614 st[4] = szp[0]
4615 st[5] = 0
4616 if (b as i64) == 0 { st[4] = 0 }
4617 if st[4] <= 0 {
4618 st[3] = 0
4619 st[2] = st[2] + 1
4620 } else {
4621 st[3] = b as i64
4622 }
4623 }
4624 if st[3] != 0 {
4625 let b: *u8 = st[3] as *u8
4626 let sz: i64 = st[4]
4627 let i: i64 = st[5]
4628 var bad: i64 = 0
4629 if i + 9 > sz { bad = 1 }
4630 if bad == 0 {
4631 let kind: i64 = b[i]
4632 let kl: i64 = ss_r32(b, i + 1)
4633 let koff: i64 = i + 5
4634 if kl < 0 { bad = 1 }
4635 if koff + kl + 4 > sz { bad = 1 }
4636 if bad == 0 {
4637 let vl: i64 = ss_r32(b, koff + kl)
4638 let voff: i64 = koff + kl + 4
4639 if vl < 0 { bad = 1 }
4640 if voff + vl > sz { bad = 1 }
4641 if bad == 0 {
4642 st[5] = voff + vl
4643 st[6] = kind
4644 kout[0] = (b as i64) + koff
4645 klout[0] = kl
4646 vout[0] = (b as i64) + voff
4647 vlout[0] = vl
4648 return 1
4649 }
4650 }
4651 }
4652 st[3] = 0
4653 st[2] = st[2] + 1
4654 }
4655 }
4656 return 0
4657}
4658
4659// COMPACTION (IM4): fold all live segments into ONE new segment holding only
4660// the LATEST entry per key (superseded versions dropped; tombstones KEPT so
4661// get() semantics are EXACTLY preserved incl. GONE-vs-ABSENT -- tombstone GC
4662// is a later policy rung). HISTORY IS NOT DESTROYED: retired segment files
4663// stay on disk and their names are appended to <prefix>manifest-archive.txt
4664// BEFORE the live-manifest swap (crash between the two leaves the old
4665// manifest intact = consistent; the archive only ever gains rows). The swap
4666// itself is the same atomic temp->rename commit point.
4667// Returns the new segid (>0) or negative on failure.
4668// VERIFY-THEN-COMMIT (2026-08-07). Re-read the segment we just wrote and prove it reproduces the
4669// merged table EXACTLY. Called BEFORE the manifest swap, which is what makes it fail-closed BY
4670// CONSTRUCTION: a failed verify returns with the live manifest still pointing at the untouched
4671// originals, so the plane is exactly as it was and the next beat simply retries.
4672//
4673// MATCHED FROM THE KNOWN GOOD, THEN MADE AFFORDABLE. nx_store_compact already does verify-then-commit
4674// (snapshot -> compact -> re-read -> restore manifest on mismatch) and that shape is right. But it
4675// verifies with a PER-KEY ss_get, which is O(keys x data) -- the exact walk its own sc_snapshot header
4676// records as having leaked >15GB on a 1355-segment plane before it had to be killed. That cost is why
4677// the verifier never reached nx_seg_compact_cli, the compactor that actually runs every 10 minutes
4678// across ~1096 planes. Reusing the merge's OWN hash index makes the identical check O(output): one
4679// pass over the new segment, one expected-O(1) probe per record.
4680//
4681// WHAT THIS CATCHES THAT A SIZE CHECK CANNOT: it reads back the ARTIFACT, so it covers the WRITER --
4682// ss_add2 framing, the writer cap, a short or partial write -- not just the merge decision. A byte
4683// count cannot tell a correct segment from a same-size mangled one.
4684// ★A COMPACTOR THAT NEVER RE-READS WHAT IT WROTE IS TRUSTING THE ONE STEP NOBODY CHECKED.
4685//
4686// `seen != nk` is the load-bearing line: it catches BOTH loss (a key missing from the output) and
4687// duplication (the writer emitted one twice). No per-record check can see either on its own.
4688// Returns 0 ok, -1 on any disagreement.
4689func ss_verify_merged(prefix: *u8, segid: i64, tkp: *i64, tkl: *i64, tkind: *i64, tvp: *i64, tvl: *i64, nk: i64, hidx: *i64, hcap: i64) -> i64 {
4690 let path: *u8 = sys_mmap(512)
4691 var o: i64 = 0
4692 o = ss_cat(path, o, prefix)
4693 o = ss_cat(path, o, "seg-" as *u8)
4694 o = ss_catn(path, o, segid)
4695 o = ss_cat(path, o, ".docs" as *u8)
4696 path[o] = 0 as u8
4697 let szp: *i64 = sys_mmap(16) as *i64
4698 let b: *u8 = ss_readall(path, szp)
4699 if (b as i64) == 0 { return 0 - 1 }
4700 let sz: i64 = szp[0]
4701 var seen: i64 = 0
4702 var i: i64 = 0
4703 while i + 9 <= sz {
4704 let kind: i64 = b[i] as i64
4705 let kl: i64 = ss_r32(b, i + 1)
4706 let koff: i64 = i + 5
4707 let vl: i64 = ss_r32(b, koff + kl)
4708 let voff: i64 = koff + kl + 4
4709 let kp: *u8 = ((b as i64) + koff) as *u8
4710 var probe: i64 = ss_khash(kp, kl) & (hcap - 1)
4711 var hit: i64 = 0 - 1
4712 var probing: i64 = 1
4713 while probing == 1 {
4714 let e: i64 = hidx[probe]
4715 if e == 0 { probing = 0 } else {
4716 if ss_kcmp(tkp[e - 1] as *u8, tkl[e - 1], kp, kl) == 0 { hit = e - 1; probing = 0 }
4717 else { probe = (probe + 1) & (hcap - 1) }
4718 }
4719 }
4720 if hit < 0 { return 0 - 1 }
4721 if tkind[hit] != kind { return 0 - 1 }
4722 if tvl[hit] != vl { return 0 - 1 }
4723 let va: *u8 = tvp[hit] as *u8
4724 let vb: *u8 = ((b as i64) + voff) as *u8
4725 var q: i64 = 0
4726 while q < vl {
4727 if va[q] != vb[q] { return 0 - 1 }
4728 q = q + 1
4729 }
4730 seen = seen + 1
4731 i = voff + vl
4732 }
4733 if seen != nk { return 0 - 1 }
4734 return 0
4735}
4736
4737func ss_compact(prefix: *u8, segid: i64) -> i64 { // THE COMPACTOR IS THE MOST DESTRUCTIVE WRITER ON THE PLANE, so it takes the same lock.
4738 // It lists the live segments below and then, at the bottom, swaps the manifest to name ONLY the
4739 // merged segment. Any ss_commit landing in between is therefore ERASED FROM THE MANIFEST -- not
4740 // a lost row but a lost SEGMENT, whose file is still on disk, fsynced, and unreachable forever.
4741 // That is a background beat silently deleting committed writes, and it is exactly why an
4742 // intermittent seg-store failure MOVES: which data vanishes depends purely on timing.
4743 // The lock covers the WHOLE merge, not just the swap, because the segment set is read before the
4744 // swap and must not change underneath. This does block writers to this plane for the duration of
4745 // a merge -- an availability cost taken knowingly, since the alternative is a compactor that
4746 // destroys committed data. Finer-grained (merge unlocked, then verify-and-swap locked) is the
4747 // follow-on; correctness first.
4748 let lk: i64 = ss_plane_lock(prefix)
4749 if lk < 0 { return 0 - 8 }
4750 let sp: *i64 = sys_mmap(8) as *i64
4751 let ns: i64 = ss_manifest_dyn(prefix, sp)
4752 let segs: *i64 = sp[0] as *i64
4753 if ns <= 0 { ss_plane_unlock(lk); return 0 - 1 }
4754 // read every live segment first; key-table capacity is DATA-DRIVEN from
4755 // the total bytes (every record >= 9 bytes), never a silent fixed cap
4756 let bptrs: *i64 = sys_mmap(8 * ns + 64) as *i64
4757 let bszs: *i64 = sys_mmap(8 * ns + 64) as *i64
4758 var total: i64 = 0
4759 var s: i64 = 0
4760 while s < ns {
4761 let path: *u8 = sys_mmap(512)
4762 var o: i64 = 0
4763 o = ss_cat(path, o, prefix)
4764 o = ss_cat(path, o, segs[s] as *u8)
4765 o = ss_cat(path, o, ".docs" as *u8)
4766 path[o] = 0 as u8
4767 let szp: *i64 = sys_mmap(16) as *i64
4768 bptrs[s] = ss_readall(path, szp) as i64
4769 bszs[s] = szp[0]
4770 if bszs[s] > 0 { total = total + bszs[s] }
4771 s = s + 1
4772 }
4773 let maxk: i64 = total / 9 + 16
4774 let tkp: *i64 = sys_mmap(8 * maxk) as *i64
4775 let tkl: *i64 = sys_mmap(8 * maxk) as *i64
4776 let tkind: *i64 = sys_mmap(8 * maxk) as *i64
4777 let tvp: *i64 = sys_mmap(8 * maxk) as *i64
4778 let tvl: *i64 = sys_mmap(8 * maxk) as *i64
4779 // DEDUP INDEX: open-addressed hash -> key-slot+1 (0 = empty). Power-of-two so the probe can mask
4780 // instead of divide. Sized 2x maxk to keep the load factor <= 0.5, which is where linear probing
4781 // stays flat rather than degrading back toward the scan this replaces.
4782 // maxk is already a deliberate WORST CASE (total/9, i.e. every record the 9-byte minimum), so this
4783 // table is large in address space and small in resident pages: sys_mmap is lazy, and only the ~nk
4784 // slots actually probed are ever faulted in. The five arrays above are provisioned the same way
4785 // for the same reason -- address space is not memory until it is touched.
4786 var hcap: i64 = 16
4787 while hcap < maxk * 2 { hcap = hcap * 2 }
4788 let hidx: *i64 = sys_mmap(8 * hcap) as *i64
4789 var nk: i64 = 0
4790 s = 0
4791 while s < ns {
4792 let b: *u8 = bptrs[s] as *u8
4793 let sz: i64 = bszs[s]
4794 var i: i64 = 0
4795 while i + 9 <= sz {
4796 let kind: i64 = b[i]
4797 let kl: i64 = ss_r32(b, i + 1)
4798 let koff: i64 = i + 5
4799 let vl: i64 = ss_r32(b, koff + kl)
4800 let voff: i64 = koff + kl + 4
4801 // find existing key slot (chronological walk => overwrite = last wins)
4802 // HASH-INDEXED LOOKUP (2026-08-06). The loop this replaces walked the ENTIRE key table for
4803 // EVERY record -- and had no early exit: `if hit < 0` skipped the comparison but the loop
4804 // still ran to nk every time, so a hit cost exactly as much as a miss. That is O(records x
4805 // keys) with the full scan always paid.
4806 // MEASURED CONSEQUENCE: nx_seg_compact_cli completed mvaultpath at ELEVEN live segments
4807 // (~155MB) on 2026-07-30 17:54 and has SIGKILLed at TWELVE (~170MB) on every sweep since --
4808 // a 10% size increase flipping success to timeout is the signature of a quadratic, not of a
4809 // plane that is merely large. Because the CLI is fail-closed before the manifest swap, a
4810 // timeout changes nothing, so a plane that crosses that cliff ONCE can never be reduced
4811 // again: mvaultpath has been stuck at 12 for SEVEN DAYS, burning a 300s timeout every
4812 // sweep, and it is the single largest consumer on this box.
4813 // ORDER AND SEMANTICS ARE UNCHANGED BY CONSTRUCTION: the tkp/tkl/tkind/tvp/tvl arrays stay
4814 // insertion-ordered and the merge loop below still walks them 0..nk, so output byte order
4815 // and last-wins are identical. Only the LOOKUP changes.
4816 let kptr: *u8 = ((b as i64) + koff) as *u8
4817 var probe: i64 = ss_khash(kptr, kl) & (hcap - 1)
4818 var hit: i64 = 0 - 1
4819 var scan: i64 = 1
4820 while scan == 1 {
4821 let e: i64 = hidx[probe]
4822 if e == 0 { scan = 0 } else {
4823 // hidx stores slot+1 so 0 can mean EMPTY. Every probe CONFIRMS with ss_kcmp --
4824 // the hash narrows the search, it never decides identity.
4825 if ss_kcmp(tkp[e - 1] as *u8, tkl[e - 1], kptr, kl) == 0 { hit = e - 1; scan = 0 }
4826 else { probe = (probe + 1) & (hcap - 1) }
4827 }
4828 }
4829 if hit < 0 {
4830 if nk >= maxk { ss_plane_unlock(lk); return 0 - 7 }
4831 hit = nk
4832 nk = nk + 1
4833 hidx[probe] = hit + 1
4834 tkp[hit] = (b as i64) + koff
4835 tkl[hit] = kl
4836 }
4837 if hit >= 0 {
4838 tkp[hit] = (b as i64) + koff
4839 tkl[hit] = kl
4840 tkind[hit] = kind
4841 tvp[hit] = (b as i64) + voff
4842 tvl[hit] = vl
4843 }
4844 i = voff + vl
4845 }
4846 s = s + 1
4847 }
4848 // merged segment = latest entry per key; writer sized data-driven from the merged total (no fixed cap)
4849 let w: *i64 = ss_begin_cap(total + SS_MAGIC_65536)
4850 var t2: i64 = 0
4851 while t2 < nk {
4852 if ss_add2(w, tkind[t2], tkp[t2] as *u8, tkl[t2], tvp[t2] as *u8, tvl[t2]) != 0 { ss_plane_unlock(lk); return 0 - 2 }
4853 t2 = t2 + 1
4854 }
4855 // CORRECTED 2026-08-07 -- THE BLOCK BELOW DESCRIBES A DESIGN THIS FUNCTION DOES NOT IMPLEMENT.
4856 // Read the code: ss_plane_lock is taken at the TOP and released only on return, so the merge runs
4857 // WITH the lock held, and there is NO staleness check anywhere in either compactor. The comment at
4858 // the head of ss_compact is the accurate one: 'The lock covers the WHOLE merge, not just the swap
4859 // ... Finer-grained (merge unlocked, then verify-and-swap locked) is the follow-on; correctness
4860 // first.' Kept rather than deleted because it records real intent and a real measurement -- but it
4861 // must not be read as a description of current behaviour.
4862 // **A COMMENT DESCRIBING AN INTENDED REFACTOR IS INDISTINGUISHABLE FROM ONE DESCRIBING THE CODE,
4863 // AND THE DANGEROUS READER IS THE ONE WHO 'RESTORES' THE SHORTER SECTION WITHOUT THE STALENESS
4864 // CHECK IT PROMISES -- that reader would ship exactly the erasure the lock exists to prevent.
4865 // SHORT CRITICAL SECTION (2026-08-06 refinement of debt 1786054662). The merge above now runs
4866 // WITHOUT the lock; only the write + manifest swap below need it. Holding it across the ENTIRE
4867 // merge was measured to cost real writes the moment ss_plane_lock gained a bounded wait:
4868 // nx_segrace_gate T4 showed FOUR concurrent writers REFUSED after 30s and 10 of 24 keys landing.
4869 // A compactor is OPTIONAL background work and must never cost a writer its commit.
4870 // STALENESS CHECK, and it is what makes the shorter section safe: if the live segment count moved
4871 // while we merged unlocked, a writer committed and our merged set is STALE -- swapping the
4872 // manifest now would drop their segment, which is the very erasure this lock exists to prevent.
4873 // So ABORT instead. Compaction retries on the next beat; a lost commit never does.
4874 // ★PREFER REDOING OPTIONAL WORK OVER DELAYING OR DESTROYING MANDATORY WORK.
4875
4876 if ss_write_seg(prefix, w, segid) != 0 { ss_plane_unlock(lk); return 0 - 3 }
4877 // VERIFY BEFORE COMMIT -- see ss_verify_merged. Deliberately placed between the write and
4878 // the manifest swap: on failure the live manifest is untouched, so the plane is exactly as
4879 // it was and the next beat retries. rc -10 is distinct so a verify refusal is never read as
4880 // a write failure.
4881 if ss_verify_merged(prefix, segid, tkp, tkl, tkind, tvp, tvl, nk, hidx, hcap) != 0 { ss_plane_unlock(lk); return 0 - 10 }
4882 // archive the retired segment names (append-only; survives any crash here)
4883 let ap: *u8 = sys_mmap(512)
4884 var ao: i64 = 0
4885 ao = ss_cat(ap, ao, prefix)
4886 ao = ss_cat(ap, ao, "manifest-archive.txt" as *u8)
4887 ap[ao] = 0 as u8
4888 let afd: i64 = sys_openat_append(ap, 0x1a4)
4889 if afd < 0 { ss_plane_unlock(lk); return 0 - 4 }
4890 s = 0
4891 while s < ns {
4892 let nm: *u8 = segs[s] as *u8
4893 sys_write(afd, nm, ss_len(nm))
4894 sys_write(afd, "\n" as *u8, 1)
4895 s = s + 1
4896 }
4897 sys_fsync(afd)
4898 sys_close(afd)
4899 // atomic live-manifest swap to ONLY the merged segment
4900 let mf: *u8 = sys_mmap(512)
4901 let mt: *u8 = sys_mmap(512)
4902 var o2: i64 = 0
4903 o2 = ss_cat(mf, o2, prefix)
4904 o2 = ss_cat(mf, o2, "manifest.txt" as *u8)
4905 mf[o2] = 0 as u8
4906 o2 = 0
4907 o2 = ss_cat(mt, o2, prefix)
4908 o2 = ss_cat(mt, o2, "manifest.tmp" as *u8)
4909 mt[o2] = 0 as u8
4910 let nb: *u8 = sys_mmap(65536)
4911 var no: i64 = 0
4912 no = ss_cat(nb, no, "seg-" as *u8)
4913 no = ss_catn(nb, no, segid)
4914 nb[no] = 10 as u8
4915 no = no + 1
4916 if ss_writefile(mt, nb, no) != 0 { ss_plane_unlock(lk); return 0 - 5 }
4917 if sys_renameat(mt, mf) != 0 { ss_plane_unlock(lk); return 0 - 6 }
4918 ss_syncdir(prefix)
4919 ss_plane_unlock(lk)
4920 return segid
4921}
4922
4923// author=tutor (LM-028 fix): cap-aware sibling of ss_compact for stores with >256 live segments
4924// (the GALX-PROD-FULL family: galx-prod holds ONE segment per image, so thousands accumulate and
4925// each ss_get/ss_scan re-reads every seg .docs = O(n) per lookup). ss_compact CANNOT fold such a
4926// store -- OR RATHER IT COULD NOT WHEN THIS WAS WRITTEN. CORRECTED 2026-08-07 by reading the code:
4927// ss_compact now reads the manifest via ss_manifest_dyn (NO cap) and sizes its writer with
4928// ss_begin_cap(total + SS_MAGIC_65536), so BOTH limits named on the next line are GONE. This sibling
4929// still earns its place -- it takes an explicit cap argument and is the variant the standing fold
4930// beat drives -- but "ss_compact CANNOT fold a big store" is no longer true, and it must not be cited
4931// to justify an exclusion. It briefly misled me into believing a 913-segment plane was structurally
4932// unfoldable when the real remaining barrier is MEMORY (~1.7GB), not a cap.
4933// **RE-CHECK THE REASON BEHIND AN EXCLUSION, NEVER ITS LABEL** -- and re-read the code, because
4934// **A COMMENT THAT WAS TRUE WHEN WRITTEN IS STILL A LIE ONCE THE CODE MOVES.**
4935// (HISTORIC, NOW FALSE:) it read the manifest via ss_manifest (HARD cap 256) AND ss_begin() capped at 1MB,
4936// so on a big store it would merge only the first 256 segs (or overflow the writer) and then swap the
4937// live manifest to that partial segment -- silently dropping ~90% of the corpus. This mirror reads
4938// ss_manifest_cap(cap) and allocates a DATA-DRIVEN writer (>= total input bytes; dedup only shrinks),
4939// so any segment count folds to ONE with NO data loss. ss_compact is left BYTE-IDENTICAL (purely
4940// additive). Every failure path returns BEFORE the atomic manifest swap, so a failure leaves the live
4941// store intact (crash/fail-safe). Returns the new segid (>0) or negative on failure.
4942func ss_compact_cap(prefix: *u8, segid: i64, cap: i64) -> i64 {
4943 // Same lock, same reason as ss_compact -- and this is the variant the standing beat actually
4944 // drives (nx_store_fold_beat, nx_galx_compact, nx_shard_compact, nx_store_compact fold), so it is
4945 // the one most likely to be merging while a writer commits.
4946 let lk: i64 = ss_plane_lock(prefix)
4947 if lk < 0 { return 0 - 8 }
4948 let segs: *i64 = sys_mmap(8 * cap) as *i64
4949 let ns: i64 = ss_manifest_cap(prefix, segs, cap)
4950 if ns <= 0 { ss_plane_unlock(lk); return 0 - 1 }
4951 // read every live segment; key-table + writer capacities are DATA-DRIVEN from total bytes
4952 let bptrs: *i64 = sys_mmap(8 * cap) as *i64
4953 let bszs: *i64 = sys_mmap(8 * cap) as *i64
4954 var total: i64 = 0
4955 var s: i64 = 0
4956 while s < ns {
4957 let path: *u8 = sys_mmap(512)
4958 var o: i64 = 0
4959 o = ss_cat(path, o, prefix)
4960 o = ss_cat(path, o, segs[s] as *u8)
4961 o = ss_cat(path, o, ".docs" as *u8)
4962 path[o] = 0 as u8
4963 let szp: *i64 = sys_mmap(16) as *i64
4964 bptrs[s] = ss_readall(path, szp) as i64
4965 bszs[s] = szp[0]
4966 if bszs[s] > 0 { total = total + bszs[s] }
4967 s = s + 1
4968 }
4969 let maxk: i64 = total / 9 + 16
4970 let tkp: *i64 = sys_mmap(8 * maxk) as *i64
4971 let tkl: *i64 = sys_mmap(8 * maxk) as *i64
4972 let tkind: *i64 = sys_mmap(8 * maxk) as *i64
4973 let tvp: *i64 = sys_mmap(8 * maxk) as *i64
4974 let tvl: *i64 = sys_mmap(8 * maxk) as *i64
4975 // DEDUP INDEX: open-addressed hash -> key-slot+1 (0 = empty), power-of-two so the probe masks
4976 // instead of dividing, sized 2x maxk to hold the load factor <= 0.5 where linear probing stays
4977 // flat rather than degrading back into the scan it replaces. maxk is already a deliberate WORST
4978 // CASE (total/9, every record the 9-byte minimum), so this table is large in address space and
4979 // small in resident pages: sys_mmap is lazy, and only the ~nk slots actually probed fault in.
4980 var hcap: i64 = 16
4981 while hcap < maxk * 2 { hcap = hcap * 2 }
4982 let hidx: *i64 = sys_mmap(8 * hcap) as *i64
4983 var nk: i64 = 0
4984 s = 0
4985 while s < ns {
4986 let b: *u8 = bptrs[s] as *u8
4987 let sz: i64 = bszs[s]
4988 var i: i64 = 0
4989 while i + 9 <= sz {
4990 let kind: i64 = b[i]
4991 let kl: i64 = ss_r32(b, i + 1)
4992 let koff: i64 = i + 5
4993 let vl: i64 = ss_r32(b, koff + kl)
4994 let voff: i64 = koff + kl + 4
4995 // chronological walk => last write wins (find existing key slot).
4996 // HASH-INDEXED probe, ported 2026-08-07 from the ss_compact fix proven live the same day:
4997 // mvaultpath folded 12 -> 1 segments in under ~47s on a plane that had SIGKILLed at the
4998 // 300s timeout on EVERY sweep for the seven days prior. The loop this replaces walked the
4999 // ENTIRE key table for EVERY record with NO early exit -- the `if hit < 0` guard skipped
5000 // only the comparison, never the iteration, so a hit cost exactly as much as a miss.
5001 // This variant matters MORE than ss_compact: per the header above it is the one the
5002 // standing beat drives (nx_store_fold_beat, nx_galx_compact, nx_shard_compact).
5003 // The hash is an INDEX, never an identity -- every probe still byte-verifies with
5004 // ss_kcmp, so a collision costs one extra comparison and can never merge distinct keys.
5005 let kp: *u8 = ((b as i64) + koff) as *u8
5006 var probe: i64 = ss_khash(kp, kl) & (hcap - 1)
5007 var hit: i64 = 0 - 1
5008 var probing: i64 = 1
5009 while probing == 1 {
5010 let e: i64 = hidx[probe]
5011 if e == 0 { probing = 0 } else {
5012 if ss_kcmp(tkp[e - 1] as *u8, tkl[e - 1], kp, kl) == 0 { hit = e - 1; probing = 0 }
5013 else { probe = (probe + 1) & (hcap - 1) }
5014 }
5015 }
5016 if hit < 0 {
5017 if nk >= maxk { ss_plane_unlock(lk); return 0 - 7 }
5018 hit = nk
5019 hidx[probe] = nk + 1
5020 nk = nk + 1
5021 tkp[hit] = (b as i64) + koff
5022 tkl[hit] = kl
5023 }
5024 if hit >= 0 {
5025 tkp[hit] = (b as i64) + koff
5026 tkl[hit] = kl
5027 tkind[hit] = kind
5028 tvp[hit] = (b as i64) + voff
5029 tvl[hit] = vl
5030 }
5031 i = voff + vl
5032 }
5033 s = s + 1
5034 }
5035 // merged segment = latest entry per key; writer cap is DATA-DRIVEN (merged <= total + slack),
5036 // NOT the fixed 1MB of ss_begin() -- that is the second half of the cap fix.
5037 let w: *i64 = sys_mmap(32) as *i64
5038 let wcap: i64 = total + SS_MAGIC_1048576
5039 w[0] = sys_mmap(wcap) as i64
5040 w[1] = 0
5041 w[2] = wcap
5042 var t2: i64 = 0
5043 while t2 < nk {
5044 if ss_add2(w, tkind[t2], tkp[t2] as *u8, tkl[t2], tvp[t2] as *u8, tvl[t2]) != 0 { ss_plane_unlock(lk); return 0 - 2 }
5045 t2 = t2 + 1
5046 }
5047 // CORRECTED 2026-08-07 -- THE BLOCK BELOW DESCRIBES A DESIGN THIS FUNCTION DOES NOT IMPLEMENT.
5048 // Read the code: ss_plane_lock is taken at the TOP and released only on return, so the merge runs
5049 // WITH the lock held, and there is NO staleness check anywhere in either compactor. The comment at
5050 // the head of ss_compact is the accurate one: 'The lock covers the WHOLE merge, not just the swap
5051 // ... Finer-grained (merge unlocked, then verify-and-swap locked) is the follow-on; correctness
5052 // first.' Kept rather than deleted because it records real intent and a real measurement -- but it
5053 // must not be read as a description of current behaviour.
5054 // **A COMMENT DESCRIBING AN INTENDED REFACTOR IS INDISTINGUISHABLE FROM ONE DESCRIBING THE CODE,
5055 // AND THE DANGEROUS READER IS THE ONE WHO 'RESTORES' THE SHORTER SECTION WITHOUT THE STALENESS
5056 // CHECK IT PROMISES -- that reader would ship exactly the erasure the lock exists to prevent.
5057 // SHORT CRITICAL SECTION (2026-08-06 refinement of debt 1786054662). The merge above now runs
5058 // WITHOUT the lock; only the write + manifest swap below need it. Holding it across the ENTIRE
5059 // merge was measured to cost real writes the moment ss_plane_lock gained a bounded wait:
5060 // nx_segrace_gate T4 showed FOUR concurrent writers REFUSED after 30s and 10 of 24 keys landing.
5061 // A compactor is OPTIONAL background work and must never cost a writer its commit.
5062 // STALENESS CHECK, and it is what makes the shorter section safe: if the live segment count moved
5063 // while we merged unlocked, a writer committed and our merged set is STALE -- swapping the
5064 // manifest now would drop their segment, which is the very erasure this lock exists to prevent.
5065 // So ABORT instead. Compaction retries on the next beat; a lost commit never does.
5066 // ★PREFER REDOING OPTIONAL WORK OVER DELAYING OR DESTROYING MANDATORY WORK.
5067
5068 if ss_write_seg(prefix, w, segid) != 0 { ss_plane_unlock(lk); return 0 - 3 }
5069 // VERIFY BEFORE COMMIT -- see ss_verify_merged. Deliberately placed between the write and
5070 // the manifest swap: on failure the live manifest is untouched, so the plane is exactly as
5071 // it was and the next beat retries. rc -10 is distinct so a verify refusal is never read as
5072 // a write failure.
5073 if ss_verify_merged(prefix, segid, tkp, tkl, tkind, tvp, tvl, nk, hidx, hcap) != 0 { ss_plane_unlock(lk); return 0 - 10 }
5074 // archive the retired segment names (append-only; survives any crash here)
5075 let ap: *u8 = sys_mmap(512)
5076 var ao: i64 = 0
5077 ao = ss_cat(ap, ao, prefix)
5078 ao = ss_cat(ap, ao, "manifest-archive.txt" as *u8)
5079 ap[ao] = 0 as u8
5080 let afd: i64 = sys_openat_append(ap, 0x1a4)
5081 if afd < 0 { ss_plane_unlock(lk); return 0 - 4 }
5082 s = 0
5083 while s < ns {
5084 let nm: *u8 = segs[s] as *u8
5085 sys_write(afd, nm, ss_len(nm))
5086 sys_write(afd, "\n" as *u8, 1)
5087 s = s + 1
5088 }
5089 sys_fsync(afd)
5090 sys_close(afd)
5091 // atomic live-manifest swap to ONLY the merged segment
5092 let mf: *u8 = sys_mmap(512)
5093 let mt: *u8 = sys_mmap(512)
5094 var o2: i64 = 0
5095 o2 = ss_cat(mf, o2, prefix)
5096 o2 = ss_cat(mf, o2, "manifest.txt" as *u8)
5097 mf[o2] = 0 as u8
5098 o2 = 0
5099 o2 = ss_cat(mt, o2, prefix)
5100 o2 = ss_cat(mt, o2, "manifest.tmp" as *u8)
5101 mt[o2] = 0 as u8
5102 let nb: *u8 = sys_mmap(65536)
5103 var no: i64 = 0
5104 no = ss_cat(nb, no, "seg-" as *u8)
5105 no = ss_catn(nb, no, segid)
5106 nb[no] = 10 as u8
5107 no = no + 1
5108 if ss_writefile(mt, nb, no) != 0 { ss_plane_unlock(lk); return 0 - 5 }
5109 if sys_renameat(mt, mf) != 0 { ss_plane_unlock(lk); return 0 - 6 }
5110 ss_syncdir(prefix)
5111 ss_plane_unlock(lk)
5112 return segid
5113}