code wiki / (root) / nx_mvault_walk.nx

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}