nx_mvault_walk.nx source
↩ module page · 990 lines · 47176 B
1// nx_mvault_walk.nx -- the LIVE reindex DRIVER, VW02 (MV-1 bulk-migration engine).
2// VW01 was a single-level, full-read walk: it SKIPPED >512MiB files (seq119 OOM
3// guard) -- i.e. most cam recordings -- and its one-reg_put-per-item store path
4// would have both crawled (a new segment per put) and corrupted the ids index
5// past ~15k items (fixed 1MiB index buffer). VW02 is the measured rebuild:
6// * RECURSIVE: iterative BFS dir queue, ONE shared getdents buffer.
7// * STREAMING CID: every file hashes in chunks (the head window doubles as
8// the sniff bytes) -- nothing is skipped for size, RAM is O(1) per file.
9// * IN-MEMORY DEDUP SETS seeded once from mv:ids / p:ids -- zero per-file
10// store reads; BATCHED commits (one segment per ~24MiB of records, not one
11// per item) -- the seg-store quadratic + index-rewrite blowup is dodged.
12// * PATH-RESUME PLANE knowledge/store/mvaultpath-: key p:<cid of "path|size">,
13// val = content CID. Re-runs skip already-hashed unchanged paths (daily DVR
14// ingest = incremental by design). Size-keyed, not mtime-keyed: recordings
15// are append-once; an in-place same-size rewrite would be missed (declared).
16// * BG MODE (`bg <logfile>`): forks a detached worker; fds 0/1/2 close so the
17// tools-daemon capture pipe EOFs immediately; progress + final JSON append
18// to <logfile>. TB-scale hashing runs for hours -- a synchronous MCP call
19// drops its response at ~15s.
20// * POLICY FILTERS (counted, never silent): dot-names skipped (.trickplay
21// sidecar dirs), *-poster.jpg sidecars skipped, 0-byte files skipped
22// (hard-crash torn artifacts must not become vault records).
23// * selftest: KAT-anchored teeth, emits verdict=GREEN|RED (D001 anchor).
24// RECORD-ONLY = NON-MUTATING BY CONSTRUCTION (unchanged): never moves a byte;
25// only the seg-store index planes are written (and only on commit).
26// ENVELOPE (declared): set ~950k cids / queue 16MiB dirs / ids blob 64MiB --
27// overflow sets fatal=1 in the output, never a silent cap.
28//
29// nx_mvault_walk <dir> <gen|real> <source> [commit|dry] [bg <logfile>]
30// nx_mvault_walk selftest
31// license_tier: ORIGINAL No hw writes (Rule 26).
32import "nx_syscalls.nx"
33import "nx_sha256.nx"
34import "nx_canon_cid.nx"
35import "_hdl_build/nx_proc_ctl.nx" // proc_count_by_name -- the single-walker guard
36import "nx_media_pool.nx"
37import "nx_media_sniff.nx"
38import "nx_mvault_ingest.nx"
39import "nx_mvault_context.nx"
40import "nx_mvault_record.nx"
41import "nx_mvault_layout.nx"
42import "nx_mvault_reclaim.nx"
43import "nx_mvault_reindex.nx"
44import "nx_seg_store.nx"
45const MW_MAGIC_4096: i64 = 4096
46const MW_MAGIC_8192: i64 = 8192
47const MW_MAGIC_8191: i64 = 8191
48const MW_MAGIC_300000: i64 = 300000
49const MW_MAGIC_1024: i64 = 1024
50
51const MW_DIRBUF: i64 = 1048576 // 1 MiB getdents buffer (shared; BFS reuses it)
52const MW_PATH: i64 = 4096
53const MW_LARGE: i64 = 104857600 // 100 MiB -> "large file surfaced for value review"
54const MW_HEAD: i64 = 1048576 // head window: sniff bytes + first hash read
55const MW_CHUNK: i64 = 4194304 // 4 MiB streaming read chunk
56const MW_QCAP: i64 = 16777216 // dir-queue arena (~280k dirs)
57const MW_SETSLOTS: i64 = 1048576 // dedup hash slots (pow2; fills at 75%)
58const MW_SETARENA: i64 = 67108864 // 64 MiB cid arena (~950k cids)
59const MW_IDSCAP: i64 = 67108864 // ids-index blob cap (rewritten per flush)
60const MW_WCAP: i64 = 33554432 // 32 MiB batch writer buffer
61const MW_FLUSH: i64 = 25165824 // flush a batch past 24 MiB of records
62const MW_PROG: i64 = 256 // progress line every N hashed files (bg)
63const MW_OLDMAX: i64 = 536870912 // VW01's skip threshold -- now just a counter
64
65func mw_seq(a: *u8, b: *u8) -> i64 {
66 var i: i64 = 0
67 while 1 == 1 {
68 if a[i] != b[i] { return 0 }
69 if a[i] == (0 as u8) { return 1 }
70 i = i + 1
71 }
72 return 0
73}
74
75// build "<dir>/<name>\0" into dst
76func mw_join(dst: *u8, dir: *u8, name: *u8) -> i64 {
77 var o: i64 = 0
78 var i: i64 = 0
79 while dir[i] != (0 as u8) { dst[o] = dir[i]; o = o + 1; i = i + 1 }
80 dst[o] = 47 as u8
81 o = o + 1
82 i = 0
83 while name[i] != (0 as u8) { dst[o] = name[i]; o = o + 1; i = i + 1 }
84 dst[o] = 0 as u8
85 return o
86}
87
88// extension after the LAST '.'; "bin" if none
89func mw_ext(name: *u8, ext: *u8) -> i64 {
90 var last: i64 = 0 - 1
91 var i: i64 = 0
92 while name[i] != (0 as u8) {
93 if name[i] == (46 as u8) { last = i }
94 i = i + 1
95 }
96 if last < 0 {
97 ext[0] = 98 as u8
98 ext[1] = 105 as u8
99 ext[2] = 110 as u8
100 ext[3] = 0 as u8
101 return 3
102 }
103 var j: i64 = last + 1
104 var o: i64 = 0
105 while name[j] != (0 as u8) { ext[o] = name[j]; o = o + 1; j = j + 1 }
106 ext[o] = 0 as u8
107 return o
108}
109
110func mw_ch(dst: *u8, off: i64, c: i64) -> i64 { dst[off] = c as u8; return off + 1 }
111func mw_cat(dst: *u8, off: i64, s: *u8) -> i64 {
112 var o: i64 = off
113 var i: i64 = 0
114 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 }
115 return o
116}
117// zero-alloc decimal (VW01's mmap'd a page per call -- per-file callers leak GBs)
118func mw_udec(dst: *u8, off: i64, v: i64) -> i64 {
119 if v == 0 { dst[off] = 48 as u8; return off + 1 }
120 var div: i64 = 1
121 var m: i64 = v
122 while m >= 10 { div = div * 10; m = m / 10 }
123 var o: i64 = off
124 var rest: i64 = v
125 while div > 0 {
126 let d: i64 = rest / div
127 dst[o] = (48 + d) as u8
128 rest = rest - d * div
129 div = div / 10
130 o = o + 1
131 }
132 return o
133}
134
135// name ends with "-poster.jpg" (cam-capture sidecar, not source media)
136func mw_is_poster(name: *u8) -> i64 {
137 var n: i64 = 0
138 while name[n] != (0 as u8) { n = n + 1 }
139 if n < 11 { return 0 }
140 let suf: *u8 = "-poster.jpg" as *u8
141 var i: i64 = 0
142 while i < 11 {
143 if name[n - 11 + i] != suf[i] { return 0 }
144 i = i + 1
145 }
146 return 1
147}
148
149// ---- open-addressing cid set: h[0]=table (slot=arena off+1), h[1]=slots,
150// h[2]=items, h[3]=arena, h[4]=arena used, h[5]=arena cap ----
151func mws_new() -> *i64 {
152 let h: *i64 = sys_mmap(64) as *i64
153 h[0] = (sys_mmap(8 * MW_SETSLOTS)) as i64
154 h[1] = MW_SETSLOTS
155 h[2] = 0
156 h[3] = (sys_mmap(MW_SETARENA)) as i64
157 h[4] = 0
158 h[5] = MW_SETARENA
159 return h
160}
161// first 16 hex chars after "nxc1-" -> i64 (sha256 is uniform)
162func mws_hash(cid: *u8) -> i64 {
163 var v: i64 = 0
164 var i: i64 = 5
165 while i < 21 {
166 let c: i64 = cid[i]
167 var d: i64 = c - 48
168 if c >= 97 { d = c - 87 }
169 v = (v << 4) | (d & 15)
170 i = i + 1
171 }
172 return v
173}
174func mws_eq(a: *u8, b: *u8) -> i64 {
175 var i: i64 = 0
176 while i < 69 { if a[i] != b[i] { return 0 } i = i + 1 }
177 return 1
178}
179func mws_has(h: *i64, cid: *u8) -> i64 {
180 let tab: *i64 = h[0] as *i64
181 let mask: i64 = h[1] - 1
182 var s: i64 = mws_hash(cid) & mask
183 while 1 == 1 {
184 let v: i64 = tab[s]
185 if v == 0 { return 0 }
186 let ap: *u8 = (h[3] + (v - 1)) as *u8
187 if mws_eq(ap, cid) == 1 { return 1 }
188 s = (s + 1) & mask
189 }
190 return 0
191}
192// 0 added, 1 was-present, -1 table/arena full (caller FAILS LOUD)
193func mws_add(h: *i64, cid: *u8) -> i64 {
194 if mws_has(h, cid) == 1 { return 1 }
195 if h[2] * 4 >= h[1] * 3 { return 0 - 1 }
196 if h[4] + 70 > h[5] { return 0 - 1 }
197 let tab: *i64 = h[0] as *i64
198 let mask: i64 = h[1] - 1
199 var s: i64 = mws_hash(cid) & mask
200 var done: i64 = 0
201 while done == 0 {
202 if tab[s] == 0 { done = 1 } else { s = (s + 1) & mask }
203 }
204 let off: i64 = h[4]
205 let ap: *u8 = (h[3] + off) as *u8
206 var i: i64 = 0
207 while i < 69 { ap[i] = cid[i]; i = i + 1 }
208 ap[69] = 0 as u8
209 h[4] = off + 70
210 tab[s] = off + 1
211 h[2] = h[2] + 1
212 return 0
213}
214
215// seed a set (and optionally the run-level ids blob) from <prefix><idxkey>;
216// returns cid count, -1 on set overflow.
217func mw_load_ids(prefix: *u8, idxkey: *u8, h: *i64, blob: *u8, bloblen: *i64) -> i64 {
218 let ipo: *i64 = sys_mmap(16) as *i64
219 let ilo: *i64 = sys_mmap(16) as *i64
220 var n: i64 = 0
221 if ss_get(prefix, idxkey, ipo, ilo) >= 0 {
222 let src: *u8 = ipo[0] as *u8
223 let sl: i64 = ilo[0]
224 if (blob as i64) != 0 {
225 var c: i64 = 0
226 while c < sl { blob[c] = src[c]; c = c + 1 }
227 bloblen[0] = sl
228 }
229 var ls: i64 = 0
230 var i: i64 = 0
231 while i <= sl {
232 var eol: i64 = 0
233 if i == sl { eol = 1 } else { if src[i] == (10 as u8) { eol = 1 } }
234 if eol == 1 {
235 if i - ls == 69 {
236 if mws_add(h, (src as i64 + ls) as *u8) < 0 { return 0 - 1 }
237 n = n + 1
238 }
239 ls = i + 1
240 }
241 i = i + 1
242 }
243 }
244 return n
245}
246
247// free the per-hash scratch sha256_init mmaps (per-file callers leak GBs otherwise)
248func mw_sha_free(c: *Sha256) -> i64 {
249 sys_munmap(c.bufptr as *u8, 64)
250 sys_munmap(c.kptr as *u8, 512)
251 sys_munmap(c.wptr as *u8, 512)
252 sys_munmap(c.k32ptr as *u8, 256)
253 sys_munmap(c.st8ptr as *u8, 32)
254 return 0
255}
256
257// stream-hash one file in ONE pass: head lands in headbuf (the sniff window),
258// the remainder streams through chunkbuf. O(1) memory, scratch freed.
259// out: cid, szbox[0]=total bytes, hnbox[0]=head bytes. 0 ok, -1 error.
260func mw_hash_file(path: *u8, ctx: *Sha256, headbuf: *u8, chunkbuf: *u8,
261 dg: *u8, cid: *u8, szbox: *i64, hnbox: *i64) -> i64 {
262 let fd: i64 = sys_openat_rd(path)
263 if fd < 0 { return 0 - 1 }
264 sha256_init(ctx)
265 var bad: i64 = 0
266 var hn: i64 = 0
267 var done: i64 = 0
268 while done == 0 {
269 let n: i64 = sys_read(fd, (headbuf as i64 + hn) as *u8, MW_HEAD - hn)
270 if n < 0 { bad = 1; done = 1 }
271 if n == 0 { done = 1 }
272 if n > 0 {
273 hn = hn + n
274 if hn >= MW_HEAD { done = 1 }
275 }
276 }
277 var total: i64 = hn
278 if bad == 0 {
279 if hn > 0 { sha256_update(ctx, headbuf, hn) }
280 if hn >= MW_HEAD {
281 var more: i64 = 1
282 while more == 1 {
283 let n2: i64 = sys_read(fd, chunkbuf, MW_CHUNK)
284 if n2 < 0 { bad = 1; more = 0 }
285 if n2 == 0 { more = 0 }
286 if n2 > 0 { sha256_update(ctx, chunkbuf, n2); total = total + n2 }
287 }
288 }
289 }
290 sys_close(fd)
291 var r: i64 = 0 - 1
292 if bad == 0 {
293 sha256_final(ctx, dg)
294 cid_from_digest(dg, cid)
295 szbox[0] = total
296 hnbox[0] = hn
297 r = 0
298 }
299 mw_sha_free(ctx)
300 return r
301}
302
303// cid of the resume key "<path>|<size>" (a hash key, not content). zero-alloc.
304func mw_pathkey(path: *u8, sz: i64, ctx: *Sha256, pkbuf: *u8, dg: *u8, pkcid: *u8) -> i64 {
305 var o: i64 = 0
306 var i: i64 = 0
307 while path[i] != (0 as u8) { pkbuf[o] = path[i]; o = o + 1; i = i + 1 }
308 pkbuf[o] = 124 as u8
309 o = o + 1
310 o = mw_udec(pkbuf, o, sz)
311 sha256_init(ctx)
312 sha256_update(ctx, pkbuf, o)
313 sha256_final(ctx, dg)
314 cid_from_digest(dg, pkcid)
315 mw_sha_free(ctx)
316 return o
317}
318
319// append one id line to a run-level ids blob
320func mw_ids_append(idsbuf: *u8, lenbox: *i64, cid: *u8) -> i64 {
321 var o: i64 = lenbox[0]
322 if o + 70 > MW_IDSCAP { return 0 - 1 }
323 var i: i64 = 0
324 while i < 69 { idsbuf[o] = cid[i]; o = o + 1; i = i + 1 }
325 idsbuf[o] = 10 as u8
326 lenbox[0] = o + 1
327 return 0
328}
329
330// add a "<kp><cid>" record to the batch writer
331func mw_add_rec(wbox: *i64, kp: *u8, cid: *u8, val: *u8, vlen: i64, kbuf: *u8) -> i64 {
332 var o: i64 = mw_cat(kbuf, 0, kp)
333 o = mw_cat(kbuf, o, cid)
334 kbuf[o] = 0 as u8
335 let w: *i64 = wbox[0] as *i64
336 return ss_add(w, 1, kbuf, val, vlen)
337}
338
339// commit the current batch (records + the complete ids index in the SAME
340// segment -- readers stay atomic) and start a fresh writer. 1=committed,
341// 0=nothing pending, -1=error.
342func mw_flush_plane(prefix: *u8, wbox: *i64, idxkey: *u8, idsbuf: *u8, idslen: i64) -> i64 {
343 let w: *i64 = wbox[0] as *i64
344 if w[1] == 0 { return 0 }
345 if ss_add(w, 1, idxkey, idsbuf, idslen) < 0 { return 0 - 1 }
346 // CANONICAL writer-side id (uncapped max+1): count-as-segid re-clobbers
347 // after compaction or past any cap (the proven toolreg seg-256 class)
348 let segid: i64 = ss_next_segid(prefix)
349 if segid < 0 { return 0 - 1 }
350 let rc: i64 = ss_commit(prefix, w, segid)
351 sys_munmap(w[0] as *u8, w[2])
352 sys_munmap(w as *u8, 32)
353 wbox[0] = (ss_begin_cap(MW_WCAP)) as i64
354 if rc < 0 { return 0 - 1 }
355 return 1
356}
357
358func mw_prog(logfd: i64, st: *i64, buf: *u8) -> i64 {
359 if logfd < 0 { return 0 }
360 var o: i64 = 0
361 o = mw_cat(buf, o, "PROG files=" as *u8); o = mw_udec(buf, o, st[0])
362 o = mw_cat(buf, o, " seen=" as *u8); o = mw_udec(buf, o, st[1])
363 o = mw_cat(buf, o, " already=" as *u8); o = mw_udec(buf, o, st[2])
364 o = mw_cat(buf, o, " dirs=" as *u8); o = mw_udec(buf, o, st[5])
365 o = mw_cat(buf, o, " commits=" as *u8); o = mw_udec(buf, o, st[7])
366 o = mw_cat(buf, o, " rerr=" as *u8); o = mw_udec(buf, o, st[4])
367 o = mw_ch(buf, o, 10)
368 sys_write(logfd, buf, o)
369 return 0
370}
371
372func mw_fail(out: *u8, msg: *u8) -> i64 {
373 var o: i64 = 0
374 o = mw_cat(out, o, "{\"error\":\"" as *u8)
375 o = mw_cat(out, o, msg)
376 o = mw_cat(out, o, "\"}" as *u8)
377 o = mw_ch(out, o, 10)
378 return o
379}
380
381func mw_emit(out: *u8, st: *i64, commit: i64) -> i64 {
382 var o: i64 = 0
383 o = mw_ch(out, o, 123)
384 o = mw_cat(out, o, "\"envelope\":\"recursive-walk record-only non-mutating; streaming-cid; path-resume; batch-commit\"" as *u8)
385 o = mw_ch(out, o, 44)
386 o = mw_cat(out, o, "\"files\":" as *u8); o = mw_udec(out, o, st[0]); o = mw_ch(out, o, 44)
387 o = mw_cat(out, o, "\"seen\":" as *u8); o = mw_udec(out, o, st[1]); o = mw_ch(out, o, 44)
388 o = mw_cat(out, o, "\"unique\":" as *u8); o = mw_udec(out, o, st[11]); o = mw_ch(out, o, 44)
389 o = mw_cat(out, o, "\"dup\":" as *u8); o = mw_udec(out, o, st[12]); o = mw_ch(out, o, 44)
390 o = mw_cat(out, o, "\"reclaimable_bytes\":" as *u8); o = mw_udec(out, o, st[13]); o = mw_ch(out, o, 44)
391 o = mw_cat(out, o, "\"already\":" as *u8); o = mw_udec(out, o, st[2]); o = mw_ch(out, o, 44)
392 o = mw_cat(out, o, "\"large_streamed\":" as *u8); o = mw_udec(out, o, st[3]); o = mw_ch(out, o, 44)
393 o = mw_cat(out, o, "\"read_errs\":" as *u8); o = mw_udec(out, o, st[4]); o = mw_ch(out, o, 44)
394 o = mw_cat(out, o, "\"dirs\":" as *u8); o = mw_udec(out, o, st[5]); o = mw_ch(out, o, 44)
395 o = mw_cat(out, o, "\"dirs_dropped\":" as *u8); o = mw_udec(out, o, st[6]); o = mw_ch(out, o, 44)
396 o = mw_cat(out, o, "\"sidecars_skipped\":" as *u8); o = mw_udec(out, o, st[8]); o = mw_ch(out, o, 44)
397 o = mw_cat(out, o, "\"empty_skipped\":" as *u8); o = mw_udec(out, o, st[9]); o = mw_ch(out, o, 44)
398 o = mw_cat(out, o, "\"commits\":" as *u8); o = mw_udec(out, o, st[7]); o = mw_ch(out, o, 44)
399 o = mw_cat(out, o, "\"skipped_large\":0" as *u8); o = mw_ch(out, o, 44)
400 o = mw_cat(out, o, "\"fatal\":" as *u8); o = mw_udec(out, o, st[14]); o = mw_ch(out, o, 44)
401 o = mw_cat(out, o, "\"commit\":" as *u8); o = mw_udec(out, o, commit)
402 o = mw_ch(out, o, 125)
403 o = mw_ch(out, o, 10)
404 return o
405}
406
407// The full run: BFS from root, per-file streaming pipeline, batched commits.
408// Emits the result JSON into out (returns length); fills st (16 slots).
409// Wall-clock heartbeat interval. PROG ticks every MW_PROG NEWLY-PROCESSED files, which structurally CANNOT
410// distinguish "slow" from "stuck": a resumed run path-skips tens of thousands of entries emitting nothing, and
411// a single multi-GB recording stream-hashes for minutes emitting nothing. Both look identical to a dead worker.
412// A wall-clock tick proves LIVENESS independently of how much work has completed -- count seconds, not work.
413const MW_HB_SECS: i64 = 30
414
415func mw_run(root: *u8, prov: i64, source: *u8, commit: i64,
416 mvprefix: *u8, ppprefix: *u8, dest_root: *u8,
417 logfd: i64, out: *u8, st: *i64) -> i64 {
418 let class_code: i64 = mv_class(prov)
419 var hb_last: i64 = sys_now_realtime_sec()
420 var i0: i64 = 0
421 while i0 < 16 { st[i0] = 0; i0 = i0 + 1 }
422 let ctx: *Sha256 = sys_mmap(256) as *Sha256
423 let headbuf: *u8 = sys_mmap(MW_HEAD)
424 let chunkbuf: *u8 = sys_mmap(MW_CHUNK)
425 let dbuf: *u8 = sys_mmap(MW_DIRBUF)
426 let dg: *u8 = sys_mmap(64)
427 let ocid: *u8 = sys_mmap(96)
428 let pkcid: *u8 = sys_mmap(96)
429 let path: *u8 = sys_mmap(MW_PATH)
430 let pkbuf: *u8 = sys_mmap(MW_PATH + 64)
431 let ext: *u8 = sys_mmap(256)
432 let opath: *u8 = sys_mmap(MW_PATH)
433 let rec: *u8 = sys_mmap(MW_MAGIC_4096)
434 let kbuf: *u8 = sys_mmap(512)
435 let szbox: *i64 = sys_mmap(16) as *i64
436 let hnbox: *i64 = sys_mmap(16) as *i64
437 let progbuf: *u8 = sys_mmap(512)
438 let rcl: *i64 = mv_reclaim_new()
439 let cidset: *i64 = mws_new()
440 let pathset: *i64 = mws_new()
441 let ids: *u8 = sys_mmap(MW_IDSCAP)
442 let idslen: *i64 = sys_mmap(16) as *i64
443 idslen[0] = 0
444 let pids: *u8 = sys_mmap(MW_IDSCAP)
445 let pidslen: *i64 = sys_mmap(16) as *i64
446 pidslen[0] = 0
447 if mw_load_ids(mvprefix, "mv:ids" as *u8, cidset, ids, idslen) < 0 { return mw_fail(out, "cid-set overflow at load" as *u8) }
448 if mw_load_ids(ppprefix, "p:ids" as *u8, pathset, pids, pidslen) < 0 { return mw_fail(out, "path-set overflow at load" as *u8) }
449 let wm: *i64 = sys_mmap(16) as *i64
450 wm[0] = (ss_begin_cap(MW_WCAP)) as i64
451 let wp: *i64 = sys_mmap(16) as *i64
452 wp[0] = (ss_begin_cap(MW_WCAP)) as i64
453 let q: *u8 = sys_mmap(MW_QCAP)
454 var qh: i64 = 0
455 var qt: i64 = 0
456 var rl0: i64 = 0
457 while root[rl0] != (0 as u8) { q[qt] = root[rl0]; qt = qt + 1; rl0 = rl0 + 1 }
458 q[qt] = 0 as u8
459 qt = qt + 1
460 var fatal: i64 = 0
461 while qh < qt {
462 let dir: *u8 = (q as i64 + qh) as *u8
463 var dl: i64 = 0
464 while dir[dl] != (0 as u8) { dl = dl + 1 }
465 qh = qh + dl + 1
466 st[5] = st[5] + 1
467 let dfd: i64 = sys_openat_rd(dir)
468 if dfd < 0 { st[4] = st[4] + 1 } else {
469 var done: i64 = 0
470 while done == 0 {
471 if fatal == 1 { done = 1 } else {
472 let nread: i64 = sys_getdents64(dfd, dbuf, MW_DIRBUF)
473 if nread <= 0 { done = 1 } else {
474 var off: i64 = 0
475 while off < nread {
476 let drec: *u8 = (dbuf as i64 + off) as *u8
477 let reclen: i64 = dirent_reclen(drec)
478 let dtype: i64 = dirent_type(drec)
479 let name: *u8 = dirent_name(drec)
480 var skip: i64 = 0
481 if name[0] == (46 as u8) { skip = 1 }
482 if skip == 0 {
483 if dtype == DT_DIR {
484 let need: i64 = mw_join(path, dir, name)
485 if qt + need + 2 > MW_QCAP { st[6] = st[6] + 1 } else {
486 var cq: i64 = 0
487 while cq <= need { q[qt + cq] = path[cq]; cq = cq + 1 }
488 qt = qt + need + 1
489 }
490 }
491 if dtype == DT_REG {
492 if mw_is_poster(name) == 1 { st[8] = st[8] + 1 } else {
493 st[0] = st[0] + 1
494 // LIVENESS TICK -- runs for EVERY directory entry (skipped or processed),
495 // so a long path-resume run and a long single-file hash both keep proving
496 // the worker is alive. Deliberately cheap: one clock read per entry.
497 if logfd >= 0 {
498 let hb_now: i64 = sys_now_realtime_sec()
499 if hb_now - hb_last >= MW_HB_SECS {
500 hb_last = hb_now
501 var ho: i64 = mw_cat(progbuf, 0, "HB t=" as *u8)
502 ho = mw_udec(progbuf, ho, hb_now)
503 ho = mw_cat(progbuf, ho, " files=" as *u8); ho = mw_udec(progbuf, ho, st[0])
504 ho = mw_cat(progbuf, ho, " seen=" as *u8); ho = mw_udec(progbuf, ho, st[1])
505 ho = mw_cat(progbuf, ho, " already=" as *u8); ho = mw_udec(progbuf, ho, st[2])
506 ho = mw_cat(progbuf, ho, " commits=" as *u8); ho = mw_udec(progbuf, ho, st[7])
507 ho = mw_ch(progbuf, ho, 10)
508 sys_write(logfd, progbuf, ho)
509 }
510 }
511 mw_join(path, dir, name)
512 let pfd: i64 = sys_openat_rd(path)
513 var fsz: i64 = 0 - 1
514 if pfd >= 0 {
515 fsz = sys_lseek(pfd, 0, 2)
516 sys_close(pfd)
517 }
518 if fsz < 0 { st[4] = st[4] + 1 } else {
519 if fsz == 0 { st[9] = st[9] + 1 } else {
520 mw_pathkey(path, fsz, ctx, pkbuf, dg, pkcid)
521 if mws_has(pathset, pkcid) == 1 { st[2] = st[2] + 1 } else {
522 if mw_hash_file(path, ctx, headbuf, chunkbuf, dg, ocid, szbox, hnbox) < 0 { st[4] = st[4] + 1 } else {
523 if fsz > MW_OLDMAX { st[3] = st[3] + 1 }
524 mw_ext(name, ext)
525 let vtype: i64 = mv_classify_plan(headbuf, hnbox[0], class_code, MV_CTX_NONE, dest_root, source, ext, opath, ocid)
526 var isdup: i64 = 0
527 if mws_has(cidset, ocid) == 1 { isdup = 1 }
528 mv_reclaim_add(rcl, fsz, isdup, MW_LARGE)
529 st[1] = st[1] + 1
530 if commit == 1 {
531 if isdup == 0 {
532 let rl: i64 = mv_record_build(rec, class_code, vtype, MV_LVL_TS, source, opath)
533 if mw_add_rec(wm, "mv:" as *u8, ocid, rec, rl, kbuf) < 0 { fatal = 1 }
534 if mw_ids_append(ids, idslen, ocid) < 0 { fatal = 1 }
535 }
536 if mw_add_rec(wp, "p:" as *u8, pkcid, ocid, 70, kbuf) < 0 { fatal = 1 }
537 if mw_ids_append(pids, pidslen, pkcid) < 0 { fatal = 1 }
538 if mws_add(pathset, pkcid) < 0 { fatal = 1 }
539 }
540 if isdup == 0 { if mws_add(cidset, ocid) < 0 { fatal = 1 } }
541 if commit == 1 {
542 if fatal == 0 {
543 // seq625: also flush per 4096 files -- a multi-day
544 // run must advance the resume plane incrementally
545 st[15] = st[15] + 1
546 var doflush: i64 = 0
547 if st[15] >= MW_MAGIC_4096 { doflush = 1 }
548 let wmi: *i64 = wm[0] as *i64
549 if wmi[1] > MW_FLUSH { doflush = 1 }
550 let wpi: *i64 = wp[0] as *i64
551 if wpi[1] > MW_FLUSH { doflush = 1 }
552 if doflush == 1 {
553 st[15] = 0
554 let f1: i64 = mw_flush_plane(mvprefix, wm, "mv:ids" as *u8, ids, idslen[0])
555 if f1 < 0 { fatal = 1 }
556 if f1 == 1 { st[7] = st[7] + 1 }
557 let f2: i64 = mw_flush_plane(ppprefix, wp, "p:ids" as *u8, pids, pidslen[0])
558 if f2 < 0 { fatal = 1 }
559 }
560 }
561 }
562 st[10] = st[10] + 1
563 if st[10] >= MW_PROG { st[10] = 0; mw_prog(logfd, st, progbuf) }
564 }
565 }
566 }
567 }
568 }
569 }
570 }
571 if reclen <= 0 { off = nread } else { off = off + reclen }
572 }
573 }
574 }
575 }
576 sys_close(dfd)
577 }
578 if fatal == 1 { qh = qt }
579 }
580 // 2026-07-30 DATA-LOSS FIX: the tail flush used to be guarded by
581 // `fatal == 0`, so any run that set fatal DISCARDED up to MW_FLUSH (24MiB)
582 // of ALREADY-COMPUTED records -- and still printed verdict=DONE. MEASURED
583 // on /volume1/ai/images: files=127283 unique=127276 commits=30 fatal=1,
584 // announced DONE, tail batch never written. Losing a completed batch is
585 // strictly WORSE than a partial commit: those records are CORRECT; only the
586 // announcement was wrong. Flush unconditionally; report honestly below.
587 if commit == 1 {
588 let f3: i64 = mw_flush_plane(mvprefix, wm, "mv:ids" as *u8, ids, idslen[0])
589 if f3 < 0 { fatal = 1 }
590 if f3 == 1 { st[7] = st[7] + 1 }
591 let f4: i64 = mw_flush_plane(ppprefix, wp, "p:ids" as *u8, pids, pidslen[0])
592 if f4 < 0 { fatal = 1 }
593 }
594 st[11] = mv_reclaim_unique(rcl)
595 st[12] = mv_reclaim_dupcount(rcl)
596 st[13] = mv_reclaim_reclaimable(rcl)
597 st[14] = fatal
598 return mw_emit(out, st, commit)
599}
600
601// loop-write a whole buffer (a single write may be partial)
602func mw_st_write(path: *u8, bytes: *u8, n: i64) -> i64 {
603 let fd: i64 = sys_openat_wr(path, 420)
604 if fd < 0 { return 0 - 1 }
605 var off: i64 = 0
606 var bad: i64 = 0
607 while off < n {
608 let w: i64 = sys_write(fd, (bytes as i64 + off) as *u8, n - off)
609 if w <= 0 { bad = 1; off = n } else { off = off + w }
610 }
611 sys_close(fd)
612 if bad == 1 { return 0 - 1 }
613 return 0
614}
615
616func mw_st_tooth(name: *u8, ok: i64, passbox: *i64) -> i64 {
617 let b: *u8 = sys_mmap(256)
618 var o: i64 = mw_cat(b, 0, name)
619 if ok == 1 { o = mw_cat(b, o, ": PASS" as *u8); passbox[0] = passbox[0] + 1 } else { o = mw_cat(b, o, ": FAIL" as *u8) }
620 o = mw_ch(b, o, 10)
621 sys_write(1, b, o)
622 return 0
623}
624
625// VmSize (kB) from /proc/self/status -- the EXACT metric the seq905 finding used to prove the leak
626// ("all 9 organs' VmSize==VmPeak"). Reading our own address-space size is how a leak is measured from the
627// inside; RSS would understate it because leaked-but-untouched pages are not resident.
628const MW_VM_ITERS: i64 = 200
629// RATCHET (seq1015). Measured after the seq905 fixes: delta_kb=800 over MW_VM_ITERS=200 opens = exactly
630// 4096 bytes (ONE PAGE) per open, down from the whole store per open. 850 sits just above that, so the tooth
631// now LOCKS IN the gain: any regression worse than ~4.3KB/open fails immediately instead of hiding inside a
632// generous slack. Do NOT loosen this to make a failing build pass -- find the regression. When the last page
633// is eliminated, ratchet again toward 0.
634const MW_VM_SLACK_KB: i64 = 850
635func mw_vmsize() -> i64 {
636 let fd: i64 = sys_openat_rd("/proc/self/status" as *u8)
637 if fd < 0 { return 0 - 1 }
638 let b: *u8 = sys_mmap(MW_MAGIC_8192)
639 let n: i64 = sys_read(fd, b, MW_MAGIC_8191)
640 sys_close(fd)
641 if n <= 0 { sys_munmap(b, MW_MAGIC_8192); return 0 - 2 }
642 b[n] = 0 as u8
643 var i: i64 = 0
644 var out: i64 = 0 - 1
645 while i + 7 <= n {
646 if out < 0 {
647 if b[i] == (86 as u8) { if b[i+1] == (109 as u8) { if b[i+2] == (83 as u8) { if b[i+3] == (105 as u8) {
648 if b[i+4] == (122 as u8) { if b[i+5] == (101 as u8) { if b[i+6] == (58 as u8) {
649 var j: i64 = i + 7
650 // /proc/self/status separates label and value with a TAB, not a space. Skipping only 0x20
651 // left j parked on the tab, the digit scan hit a non-digit immediately and returned v=0 --
652 // and 0 then failed the `vm0 > 0` guard, so T8 reported FAIL for four rounds while measuring
653 // nothing (seq1010). Skip BOTH separators.
654 var sk: i64 = 1
655 while sk == 1 { sk = 0; if b[j] == (32 as u8) { j = j + 1; sk = 1 } else { if b[j] == (9 as u8) { j = j + 1; sk = 1 } } }
656 var v: i64 = 0
657 var run: i64 = 1
658 while run == 1 {
659 let c: i64 = b[j] as i64
660 if c < 48 { run = 0 } else { if c > 57 { run = 0 } else { v = v * 10 + (c - 48); j = j + 1 } }
661 }
662 out = v
663 } } } } } } }
664 }
665 i = i + 1
666 }
667 sys_munmap(b, MW_MAGIC_8192)
668 // Distinct failure codes so a dead probe reports WHY instead of blending into a leak verdict (seq1010):
669 // -1 open failed, -2 read failed, -3 parsed but no VmSize line matched.
670 if out < 0 { return 0 - 3 }
671 return out
672}
673
674func mw_selftest() -> i64 {
675 let pass: *i64 = sys_mmap(16) as *i64
676 pass[0] = 0
677 sys_mkdir("tmp_mvwalk_st" as *u8, 511)
678 sys_mkdir("tmp_mvwalk_st/tree" as *u8, 511)
679 sys_mkdir("tmp_mvwalk_st/tree/sub" as *u8, 511)
680 // deterministic re-runs: drop prior store artifacts (plain data files, not locks)
681 sys_unlinkat("tmp_mvwalk_st/s-manifest.txt" as *u8)
682 sys_unlinkat("tmp_mvwalk_st/p-manifest.txt" as *u8)
683 let nb: *u8 = sys_mmap(128)
684 var si: i64 = 0
685 while si < 4 {
686 var o1: i64 = mw_cat(nb, 0, "tmp_mvwalk_st/s-seg-" as *u8)
687 o1 = mw_udec(nb, o1, si)
688 o1 = mw_cat(nb, o1, ".docs" as *u8)
689 nb[o1] = 0 as u8
690 sys_unlinkat(nb)
691 o1 = mw_cat(nb, 0, "tmp_mvwalk_st/s-seg-" as *u8)
692 o1 = mw_udec(nb, o1, si)
693 o1 = mw_cat(nb, o1, ".keys" as *u8)
694 nb[o1] = 0 as u8
695 sys_unlinkat(nb)
696 o1 = mw_cat(nb, 0, "tmp_mvwalk_st/p-seg-" as *u8)
697 o1 = mw_udec(nb, o1, si)
698 o1 = mw_cat(nb, o1, ".docs" as *u8)
699 nb[o1] = 0 as u8
700 sys_unlinkat(nb)
701 o1 = mw_cat(nb, 0, "tmp_mvwalk_st/p-seg-" as *u8)
702 o1 = mw_udec(nb, o1, si)
703 o1 = mw_cat(nb, o1, ".keys" as *u8)
704 nb[o1] = 0 as u8
705 sys_unlinkat(nb)
706 si = si + 1
707 }
708 // T0 -- absolute anchor: FIPS KAT sha256("abc")
709 let cid: *u8 = sys_mmap(96)
710 cid_of("abc" as *u8, 3, cid)
711 let kat: *u8 = "nxc1-ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" as *u8
712 mw_st_tooth("T0 cid_of sha256-KAT(abc)" as *u8, mws_eq(cid, kat), pass)
713 // T1 -- streaming == one-shot; a tiny prime chunk forces block-boundary feeds
714 let b100: *u8 = sys_mmap(128)
715 var i: i64 = 0
716 while i < 100 { b100[i] = ((i * 7 + 13) % 251) as u8; i = i + 1 }
717 mw_st_write("tmp_mvwalk_st/tree/f1.bin" as *u8, b100, 100)
718 let want: *u8 = sys_mmap(96)
719 cid_of(b100, 100, want)
720 let got: *u8 = sys_mmap(96)
721 let sz: *i64 = sys_mmap(16) as *i64
722 var t1: i64 = 0
723 if cid_of_file_chunk("tmp_mvwalk_st/tree/f1.bin" as *u8, 7, sz, got) == 69 {
724 if mws_eq(got, want) == 1 { if sz[0] == 100 { t1 = 1 } }
725 }
726 mw_st_tooth("T1 cid_of_file_chunk(7) == cid_of" as *u8, t1, pass)
727 // T2 -- the unified hasher streams past the head window
728 let big: i64 = MW_HEAD + MW_MAGIC_300000
729 let bb: *u8 = sys_mmap(big + 16)
730 i = 0
731 while i < big { bb[i] = ((i % 253) + 1) as u8; i = i + 1 }
732 mw_st_write("tmp_mvwalk_st/tree/f2.bin" as *u8, bb, big)
733 let want2: *u8 = sys_mmap(96)
734 cid_of(bb, big, want2)
735 let ctx: *Sha256 = sys_mmap(256) as *Sha256
736 let hb: *u8 = sys_mmap(MW_HEAD)
737 let cb: *u8 = sys_mmap(MW_CHUNK)
738 let dg: *u8 = sys_mmap(64)
739 let hn: *i64 = sys_mmap(16) as *i64
740 var t2: i64 = 0
741 if mw_hash_file("tmp_mvwalk_st/tree/f2.bin" as *u8, ctx, hb, cb, dg, got, sz, hn) == 0 {
742 if mws_eq(got, want2) == 1 { if sz[0] == big { if hn[0] == MW_HEAD { t2 = 1 } } }
743 }
744 mw_st_tooth("T2 mw_hash_file streams past head" as *u8, t2, pass)
745 // T3 -- dedup set semantics
746 let hset: *i64 = mws_new()
747 var t3: i64 = 1
748 if mws_add(hset, want) != 0 { t3 = 0 }
749 if mws_has(hset, want) != 1 { t3 = 0 }
750 if mws_add(hset, want) != 1 { t3 = 0 }
751 if mws_has(hset, kat) == 1 { t3 = 0 }
752 mw_st_tooth("T3 cid-set add/has/dup" as *u8, t3, pass)
753 // dup content + a nested distinct file for the walk teeth
754 mw_st_write("tmp_mvwalk_st/tree/f1dup.bin" as *u8, b100, 100)
755 let b77: *u8 = sys_mmap(96)
756 i = 0
757 while i < 77 { b77[i] = (200 - i) as u8; i = i + 1 }
758 mw_st_write("tmp_mvwalk_st/tree/sub/f3.bin" as *u8, b77, 77)
759 let out: *u8 = sys_mmap(MW_MAGIC_4096)
760 let st: *i64 = sys_mmap(16 * 8) as *i64
761 let mvp: *u8 = "tmp_mvwalk_st/s-" as *u8
762 let ppp: *u8 = "tmp_mvwalk_st/p-" as *u8
763 let segs0: *i64 = sys_mmap(8 * 16) as *i64
764 // T4 -- dry: counts + non-mutating
765 mw_run("tmp_mvwalk_st/tree" as *u8, MV_PROV_REAL, "selftest" as *u8, 0, mvp, ppp, "/volume1/vault" as *u8, 0 - 1, out, st)
766 var t4: i64 = 1
767 if st[0] != 4 { t4 = 0 }
768 if st[1] != 4 { t4 = 0 }
769 if st[5] != 2 { t4 = 0 }
770 if st[11] != 3 { t4 = 0 }
771 if st[12] != 1 { t4 = 0 }
772 if st[2] != 0 { t4 = 0 }
773 if ss_manifest_cap(mvp, segs0, 16) > 0 { t4 = 0 }
774 mw_st_tooth("T4 dry walk: counts + non-mutating" as *u8, t4, pass)
775 // T5 -- commit: one batch segment, ids complete
776 mw_run("tmp_mvwalk_st/tree" as *u8, MV_PROV_REAL, "selftest" as *u8, 1, mvp, ppp, "/volume1/vault" as *u8, 0 - 1, out, st)
777 var t5: i64 = 1
778 if st[1] != 4 { t5 = 0 }
779 if st[7] != 1 { t5 = 0 }
780 if st[14] != 0 { t5 = 0 }
781 if ss_manifest_cap(mvp, segs0, 16) != 1 { t5 = 0 }
782 let vh1: *i64 = mws_new()
783 if mw_load_ids(mvp, "mv:ids" as *u8, vh1, 0 as *u8, 0 as *i64) != 3 { t5 = 0 }
784 let vh2: *i64 = mws_new()
785 if mw_load_ids(ppp, "p:ids" as *u8, vh2, 0 as *u8, 0 as *i64) != 4 { t5 = 0 }
786 mw_st_tooth("T5 commit: 1 segment, 3 cids, 4 path keys" as *u8, t5, pass)
787 // T6 -- resume: everything path-skipped, no new segment
788 mw_run("tmp_mvwalk_st/tree" as *u8, MV_PROV_REAL, "selftest" as *u8, 1, mvp, ppp, "/volume1/vault" as *u8, 0 - 1, out, st)
789 var t6: i64 = 1
790 if st[2] != 4 { t6 = 0 }
791 if st[1] != 0 { t6 = 0 }
792 if st[7] != 0 { t6 = 0 }
793 if ss_manifest_cap(mvp, segs0, 16) != 1 { t6 = 0 }
794 mw_st_tooth("T6 resume: all already, no new commit" as *u8, t6, pass)
795 // T7/T8 -- ss_close (the seq905 keystone). ss_open loads every live segment's blobs into memory and until
796 // 2026-07-25 NOTHING ever released them (`func ss_close` grepped to 0 matches against ~180 ss_open call
797 // sites), so every consumer leaked BY CONSTRUCTION -- measured as ~20.6 GB of 24.3 GB swap consumed, which
798 // parks long walkers in uninterruptible sleep where they neither progress nor accept SIGKILL.
799 var t7: i64 = 1
800 if ss_close(0 as *i64) != 0 { t7 = 0 }
801 mw_st_tooth("T7 ss_close(0) is a null-safe no-op" as *u8, t7, pass)
802 // NON-VACUOUS BY SIZING: without ss_close, MW_VM_ITERS opens of even this tiny store grow VmSize by far
803 // more than MW_VM_SLACK_KB, so this tooth FAILS on the pre-fix binary rather than passing trivially.
804 let vm0: i64 = mw_vmsize()
805 var itc: i64 = 0
806 while itc < MW_VM_ITERS {
807 let hx: *i64 = ss_open(mvp)
808 if (hx as i64) != 0 { ss_close(hx) }
809 itc = itc + 1
810 }
811 let vm1: i64 = mw_vmsize()
812 var t8: i64 = 0
813 if vm0 > 0 { if vm1 > 0 { if vm1 - vm0 <= MW_VM_SLACK_KB { t8 = 1 } } }
814 // Report the MAGNITUDE, not just pass/fail. A binary FAIL says "something leaks"; the delta divided by
815 // MW_VM_ITERS gives BYTES PER OPEN, which localises the culprit to a specific allocation size instead of
816 // leaving the next session to guess. An instrument that only says NO is half an instrument.
817 let d8: *u8 = sys_mmap(256)
818 var do8: i64 = mw_cat(d8, 0, " T8 delta_kb=" as *u8)
819 do8 = mw_udec(d8, do8, vm1 - vm0)
820 do8 = mw_cat(d8, do8, " vm0_kb=" as *u8); do8 = mw_udec(d8, do8, vm0)
821 do8 = mw_cat(d8, do8, " vm1_kb=" as *u8); do8 = mw_udec(d8, do8, vm1)
822 do8 = mw_cat(d8, do8, " iters=" as *u8); do8 = mw_udec(d8, do8, MW_VM_ITERS)
823 do8 = mw_cat(d8, do8, " bytes_per_open=" as *u8); do8 = mw_udec(d8, do8, (vm1 - vm0) * MW_MAGIC_1024 / MW_VM_ITERS)
824 if vm0 == 0 - 1 { do8 = mw_cat(d8, do8, " probe=OPENFAIL" as *u8) }
825 if vm0 == 0 - 2 { do8 = mw_cat(d8, do8, " probe=READFAIL" as *u8) }
826 if vm0 == 0 - 3 { do8 = mw_cat(d8, do8, " probe=PARSEFAIL" as *u8) }
827 if vm0 > 0 { do8 = mw_cat(d8, do8, " probe=LIVE" as *u8) }
828 do8 = mw_ch(d8, do8, 10)
829 sys_write(1, d8, do8)
830 sys_munmap(d8, 256)
831 mw_st_tooth("T8 200x ss_open+ss_close: VmSize flat (seq905 leak closed)" as *u8, t8, pass)
832 let vb: *u8 = sys_mmap(256)
833 var vo: i64 = 0
834 vo = mw_cat(vb, vo, "nx_mvault_walk selftest T=9 PASS=" as *u8)
835 vo = mw_udec(vb, vo, pass[0])
836 if pass[0] == 9 { vo = mw_cat(vb, vo, " verdict=GREEN" as *u8) } else { vo = mw_cat(vb, vo, " verdict=RED" as *u8) }
837 vo = mw_ch(vb, vo, 10)
838 sys_write(1, vb, vo)
839 if pass[0] == 9 { return 0 }
840 return 1
841}
842
843func main(argc: i64, argv: *i64) -> i64 {
844 if argc >= 2 {
845 if mw_seq((argv[1]) as *u8, "selftest" as *u8) == 1 { return mw_selftest() }
846 // STOP verb (debt seq931). The single-walker guard could REFUSE a double-start but nothing could
847 // TERMINATE a wedged worker -- the only recovery was ssh, which the api-first doctrine retires. A
848 // long walker that stops committing is otherwise indistinguishable from a slow one AND unkillable,
849 // so it holds the single-walker slot forever. Self-exclusion is mandatory: our own cmdline matches
850 // the needle, so an unguarded kill-by-name SIGKILLs this process before it can report anything.
851 if mw_seq((argv[1]) as *u8, "stop" as *u8) == 1 {
852 // REPORT BEFORE KILLING (debt seq935). The first cut relied on __syscall(39) getpid to exclude
853 // self; that did not hold, so the scanner SIGKILLed this process before it could write anything --
854 // `stop` returned EMPTY while genuinely killing the worker, which reads as a no-op and invites the
855 // operator to run it again. Ordering is the robust fix: the count comes from proc_count_by_name
856 // (self-inclusive, so subtract 1) and the line is flushed to the capture pipe BEFORE any signal,
857 // so even if we do kill ourselves the report has already left.
858 let sb: *u8 = sys_mmap(256)
859 var tgt: i64 = proc_count_by_name("nx_mvault_walk" as *u8) - 1
860 if tgt < 0 { tgt = 0 }
861 var sq: i64 = mw_cat(sb, 0, "{\"action\":\"STOPPING\",\"targets\":" as *u8)
862 sq = mw_udec(sb, sq, tgt)
863 sq = mw_cat(sb, sq, ",\"note\":\"SIGKILL by cmdline; re-run the walk to resume (path-resume plane skips completed paths)\"}" as *u8)
864 sq = mw_ch(sb, sq, 10)
865 sys_write(1, sb, sq)
866 if tgt > 0 {
867 let me: i64 = __syscall(39, 0, 0, 0, 0, 0, 0)
868 proc_kill_by_name_except("nx_mvault_walk" as *u8, 9, me)
869 }
870 return 0
871 }
872 }
873 if argc < 4 {
874 let ub: *u8 = sys_mmap(256)
875 var uo: i64 = mw_cat(ub, 0, "usage: nx_mvault_walk <dir> <gen|real> <source> [commit|dry] [bg <logfile>] | selftest" as *u8)
876 uo = mw_ch(ub, uo, 10)
877 sys_write(2, ub, uo)
878 return 2
879 }
880 let dir: *u8 = (argv[1]) as *u8
881 let prov_s: *u8 = (argv[2]) as *u8
882 let source: *u8 = (argv[3]) as *u8
883 var commit: i64 = 1
884 if argc >= 5 { if mw_seq((argv[4]) as *u8, "dry" as *u8) == 1 { commit = 0 } }
885 var prov: i64 = MV_PROV_REAL
886 if mw_seq(prov_s, "gen" as *u8) == 1 { prov = MV_PROV_GEN }
887 // CWD-robust: stores write CWD-relative knowledge/store/ (gate T6 lesson)
888 // YIELD TO THE CONTROL PLANE BY CONSTRUCTION (2026-07-30). This walk is a
889 // BULK job: on 2026-07-30 it saturated the NAS and every forked organ queued
890 // behind its I/O, so EVERY agent MCP call 503'd for minutes. A manual
891 // `renice 19` restored interactive service immediately -- so the walk now
892 // does it to ITSELF at startup rather than waiting for an operator to notice.
893 sys_setpriority(19)
894 sys_mkdir("knowledge" as *u8, 511)
895 sys_mkdir("knowledge/store" as *u8, 511)
896 let mvprefix: *u8 = "knowledge/store/mvault-" as *u8
897 let ppprefix: *u8 = "knowledge/store/mvaultpath-" as *u8
898 let dest_root: *u8 = "/volume1/vault" as *u8
899 let out: *u8 = sys_mmap(MW_MAGIC_4096)
900 let st: *i64 = sys_mmap(16 * 8) as *i64
901 var bg: i64 = 0
902 if argc >= 7 { if mw_seq((argv[5]) as *u8, "bg" as *u8) == 1 { bg = 1 } }
903 if bg == 1 {
904 let logpath: *u8 = (argv[6]) as *u8
905 // SINGLE-WALKER BY CONSTRUCTION (debt seq621). Two concurrent walkers both read-modify-write the
906 // lock-free seg-store -- that is CORRUPTION of a 20k+ record plane, not merely duplicated work. The
907 // old discipline was "the operator must remember not to double-start"; a tool that CANNOT do the
908 // wrong thing beats a session that must remember. Our OWN cmdline matches too, so >=2 == another walker.
909 if proc_count_by_name("nx_mvault_walk" as *u8) >= 2 {
910 var ro: i64 = mw_cat(out, 0, "{\"action\":\"REFUSED\",\"verdict\":\"SINGLE-WALKER\",\"reason\":\"another nx_mvault_walk is already running; not starting a second (seg-store RMW would corrupt)\"}" as *u8)
911 ro = mw_ch(out, ro, 10)
912 sys_write(1, out, ro)
913 return 3
914 }
915 let pid: i64 = sys_fork()
916 if pid != 0 {
917 var o: i64 = mw_cat(out, 0, "{\"action\":\"BG-STARTED\",\"log\":\"" as *u8)
918 o = mw_cat(out, o, logpath)
919 o = mw_cat(out, o, "\"}" as *u8)
920 o = mw_ch(out, o, 10)
921 sys_write(1, out, o)
922 return 0
923 }
924 // child: detach from the capture pipe BEFORE the long run
925 sys_close(0)
926 sys_close(1)
927 sys_close(2)
928 // SESSION LEADER. Closing 0/1/2 detaches the capture pipe but NOT the session:
929 // the worker stayed in the tools-daemon's session and died with it -- the measured
930 // cause of the 2026-07-23 run stopping at 21,521 files with no verdict=DONE.
931 // Every durable daemon here (nx_hostctl, gallery guard, pulse/conductor) does
932 // fork + nx_setsid for exactly this reason; this one-shot worker had skipped it.
933 nx_setsid()
934 let logfd: i64 = sys_openat_append(logpath, 420)
935 if logfd < 0 { sys_exit(3) }
936 // START line BEFORE any hashing (debt seq618). The PROG tick fires every 256 NEWLY-PROCESSED files,
937 // so on a RESUMED run (most paths path-skip instantly, then 256 multi-GB recordings must stream-hash)
938 // the log stayed 0 bytes for a long time -- and "still hashing" was indistinguishable from "died on
939 // launch". An immediate line makes worker liveness observable from byte one instead of inferred.
940 var so: i64 = mw_cat(out, 0, "START nx_mvault_walk dir=" as *u8)
941 so = mw_cat(out, so, dir)
942 so = mw_cat(out, so, " prov=" as *u8)
943 so = mw_cat(out, so, prov_s)
944 so = mw_cat(out, so, " source=" as *u8)
945 so = mw_cat(out, so, source)
946 so = mw_cat(out, so, " commit=" as *u8)
947 so = mw_udec(out, so, commit)
948 so = mw_ch(out, so, 10)
949 sys_write(logfd, out, so)
950 let n: i64 = mw_run(dir, prov, source, commit, mvprefix, ppprefix, dest_root, logfd, out, st)
951 var wo: i64 = 0
952 var bad: i64 = 0
953 while wo < n {
954 let ww: i64 = sys_write(logfd, (out as i64 + wo) as *u8, n - wo)
955 if ww <= 0 { bad = 1; wo = n } else { wo = wo + ww }
956 }
957 // 2026-07-30: the verdict must reflect the ERROR FLAG, not merely
958 // reaching the end. This wrote DONE unconditionally, so a run with
959 // fatal=1 announced success (measured: images root, 127,283 files,
960 // fatal=1, verdict=DONE). A COMPLETION VERDICT THAT IGNORES ITS OWN
961 // ERROR FLAG IS A FALSE GREEN. Scan the emitted JSON for "fatal":0.
962 var okrun: i64 = 0
963 var fi: i64 = 0
964 while fi + 9 < n {
965 if out[fi] == (102 as u8) {
966 if out[fi + 1] == (97 as u8) {
967 if out[fi + 2] == (116 as u8) {
968 if out[fi + 3] == (97 as u8) {
969 if out[fi + 4] == (108 as u8) {
970 if out[fi + 7] == (48 as u8) { okrun = 1 }
971 }
972 }
973 }
974 }
975 }
976 fi = fi + 1
977 }
978 if okrun == 1 {
979 sys_write(logfd, "verdict=DONE\n" as *u8, 13)
980 } else {
981 sys_write(logfd, "verdict=INCOMPLETE fatal=1 -- some items may be missing; re-run this root\n" as *u8, 74)
982 }
983 sys_close(logfd)
984 if bad == 1 { sys_exit(4) }
985 sys_exit(0)
986 }
987 let n2: i64 = mw_run(dir, prov, source, commit, mvprefix, ppprefix, dest_root, 0 - 1, out, st)
988 sys_write(1, out, n2)
989 return 0
990}