code wiki / (root) / nx_shelltool.nx

nx_shelltool.nx source

↩ module page · 458 lines · 24597 B

1// nx_shelltool.nx -- the INVOCABLE + MCP-READY surface for the shell-replacement tier (07-15 2// operator: "build our nishi grep and glob and search ... as full MCP API state of the art ... 3// available to these workstreams ... anything else that uses wsl when we could just use nishi"). 4// The S1-S9 organs (nx_grep etc.) are LIBS (functions, no main) -> not callable as tools + not on 5// MCP. This is the ONE CLI that COMPOSES the canonical nx_grep (per NISHI_SHELL_REPLACEMENT_ROADMAP 6// "consumers compose nx_grep", no proliferation) into filesystem verbs a workstream can invoke over 7// MCP tools/call instead of the Grep/Glob/Bash builtins + WSL: 8// nx_shelltool grep <pattern> <dir> [ext] -> recursive; prints path:lineno:line for matches 9// nx_shelltool glob <glob-pat> <dir> -> recursive; prints paths whose NAME matches (* wildcard) 10// nx_shelltool find <substr> <dir> -> recursive; prints paths whose NAME contains substr 11// Composes nx_grep_any (canonical match). Read-only. Exit 0; 2 usage; 3 baddir; 5 budget-partial. 12// 13// SEQ1292 ROOT FIX (2026-07-30, incident 2026-07-29 sitewide outage): every scan now runs under a 14// SCAN BUDGET -- an unbounded walk over a multi-GB store starved the whole hub (and the old code 15// mmap'd 1MiB per file, never unmapped => RSS grew linearly with files scanned = the thrash vector). 16// Budget source: shelltool_budget.conf in CWD (keys max_bytes= deadline_ms= max_files=), else 17// derived defaults: max_bytes 512MiB (~1.4% of the 36GB hub, seconds of sequential NAS IO), 18// deadline_ms 20000 (the proven WC_FETCH_BUDGET_MS wall-clock precedent), max_files 200000. 19// Checked per file + per getdents batch so exceeding STOPS IO, prints partial counts + a structured 20// NX-SHELLTOOL BUDGET-EXCEEDED line, exit 5 -- never a silent truncation, never a host wedge. 21// Buffers: ONE reused 1MiB read buffer; per-level walk buffers munmap'd; overlong paths (>4095) 22// skipped LOUDLY via skipped_toolong= in the summary. 23// license_tier: ORIGINAL expect_exit: 0 24import "nx_syscalls.nx" 25import "nx_grep_rt.nx" 26 27const S_EXIT_BADDIR: i64 = 3 // <dir> not a readable directory (loud, distinct from usage=2) 28const S_EXIT_BUDGET: i64 = 5 // budget exceeded -> PARTIAL results (loud, distinct) 29const S_PROBE_CAP: i64 = 4096 // one-getdents probe buffer (dir-ness check) 30const S_DEF_MAX_BYTES: i64 = 536870912 // 512MiB derived: ~1.4% hub RAM, seconds of NAS IO (seq1292) 31const S_DEF_DEADLINE_MS: i64 = 20000 // proven wall-clock budget precedent (WC_FETCH_BUDGET_MS) 32const S_DEF_MAX_FILES: i64 = 200000 // > any legitimate single-tree scan observed to date 33// --- out= support (id 1785516061: the MCP stdout capture truncates at 160KiB with NO marker, so a 34// large scan's result was arriving CLIPPED MID-LINE with its honesty trailer deleted. Same shape as 35// the nx_treediff OUTFILE fix: results to disk, envelope on the wire, coverage bounded by the corpus 36// and never by a caller payload cap.) 37const S_FD_STDOUT: i64 = 1 38const S_MODE_644: i64 = 420 39const S_EXIT_BADOUT: i64 = 6 // out= refused/unwritable (loud, distinct from usage/baddir/budget) 40const S_NUMBUF: i64 = 24 41const S_OUTKEY_LEN: i64 = 4 // len("out=") 42const S_OUT_MINLEN: i64 = 5 // at least one name char + ".out" 43const S_DOT: i64 = 46 44const S_SLASH: i64 = 47 45const S_CH_O: i64 = 111 46const S_CH_U: i64 = 117 47const S_CH_T: i64 = 116 48const S_BUD_OFD: i64 = 9 // bud[] already threads the scan context to every emit site -- 49const S_BUD_OBYTES: i64 = 10 // reusing 2 spare slots avoids changing 3 function signatures 50const S_GBUF_CAP: i64 = 1048576 // the ONE shared read buffer; a CHUNK size now, not a coverage limit 51const S_BUD_LONGLINE: i64 = 11 // count of single lines longer than the whole buffer (declared, never silent) 52 53func s_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 54func s_write(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 } 55func s_putn(v: i64) -> i64 { 56 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 57 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 58 let d: *u8 = sys_mmap(24); var k: i64 = 0 59 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 60 let o: *u8 = sys_mmap(24); var w: i64 = 0 61 while w < k { o[w] = d[k-1-w]; w = w + 1 } 62 sys_write(1, o, k) 63 sys_munmap(d, 24); sys_munmap(o, 24) 64 return 0 65} 66func s_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 67func s_starts(s: *u8, pre: *u8) -> i64 { var i: i64 = 0; while pre[i] != (0 as u8) { if s[i] != pre[i] { return 0 } i = i + 1 } return 1 } 68// fd-aware emit (id 1785516061): results may go to a FILE so stdout carries only the envelope. 69// Both return bytes written, so the envelope can report out_bytes without a second stat. 70func s_fputs(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return n } 71func s_fputn(fd: i64, v: i64) -> i64 { 72 if v == 0 { sys_write(fd, "0" as *u8, 1); return 1 } 73 var m: i64 = v 74 var w: i64 = 0 75 if m < 0 { sys_write(fd, "-" as *u8, 1); m = 0 - m; w = 1 } 76 let d: *u8 = sys_mmap(S_NUMBUF); var k: i64 = 0 77 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 78 let o: *u8 = sys_mmap(S_NUMBUF); var q: i64 = 0 79 while q < k { o[q] = d[k-1-q]; q = q + 1 } 80 sys_write(fd, o, k) 81 sys_munmap(d, S_NUMBUF); sys_munmap(o, S_NUMBUF) 82 return w + k 83} 84// out= turns a READ-ONLY tool into a writer, so the target is constrained BY CONSTRUCTION rather 85// than by convention: no absolute path, no .. traversal, and the name MUST end in .out -- which 86// makes clobbering any source (.nx), registry (.tsv/.conf) or binary (.elf) structurally impossible. 87func s_out_ok(p: *u8) -> i64 { 88 let n: i64 = s_slen(p) 89 if n < S_OUT_MINLEN { return 0 } 90 if p[0] == (S_SLASH as u8) { return 0 } 91 var i: i64 = 0 92 while i + 1 < n { if p[i] == (S_DOT as u8) { if p[i+1] == (S_DOT as u8) { return 0 } } i = i + 1 } 93 if p[n-4] != (S_DOT as u8) { return 0 } 94 if p[n-3] != (S_CH_O as u8) { return 0 } 95 if p[n-2] != (S_CH_U as u8) { return 0 } 96 if p[n-1] != (S_CH_T as u8) { return 0 } 97 return 1 98} 99func s_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 } 100 101// bounded whole-file read 102func s_read(path: *u8, buf: *u8, cap: i64) -> i64 { 103 let fd: i64 = sys_openat_rd(path) 104 if fd < 0 { return 0 - 1 } 105 var tot: i64 = 0 106 var n: i64 = sys_read(fd, buf, cap) 107 while n > 0 { tot = tot + n; if tot >= cap { n = 0 } else { n = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot) } } 108 sys_close(fd) 109 return tot 110} 111 112// first integer after <key> in buf (key includes the '='), -1 if absent/empty 113func s_num_after(buf: *u8, n: i64, key: *u8) -> i64 { 114 let kn: i64 = s_slen(key) 115 var i: i64 = 0 116 while i + kn <= n { 117 var j: i64 = 0 118 var ok: i64 = 1 119 while j < kn { if buf[i+j] != key[j] { ok = 0; j = kn } else { j = j + 1 } } 120 if ok == 1 { 121 var p: i64 = i + kn 122 var v: i64 = 0 123 var any: i64 = 0 124 var go: i64 = 1 125 while go == 1 { 126 go = 0 127 if p < n { 128 let c: i64 = buf[p] as i64 129 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; p = p + 1; go = 1 } } 130 } 131 } 132 if any == 1 { return v } 133 return 0 - 1 134 } 135 i = i + 1 136 } 137 return 0 - 1 138} 139 140func s_ends_with(name: *u8, nn: i64, ext: *u8, en: i64) -> i64 { 141 if en == 0 { return 1 } 142 if en > nn { return 0 } 143 var i: i64 = 0 144 while i < en { if name[nn-en+i] != ext[i] { return 0 } i = i + 1 } 145 return 1 146} 147 148// iterative glob match with '*' (any sequence) + literal chars; backtracking. 1 if match. 149func s_glob(pat: *u8, pn: i64, str: *u8, sn: i64) -> i64 { 150 var pi: i64 = 0 151 var si: i64 = 0 152 var star: i64 = 0 - 1 153 var mark: i64 = 0 154 var run: i64 = 1 155 while run == 1 { 156 if si < sn { 157 var advanced: i64 = 0 158 if pi < pn { if pat[pi] == (42 as u8) { star = pi; mark = si; pi = pi + 1; advanced = 1 } } 159 if advanced == 0 { if pi < pn { if pat[pi] == str[si] { pi = pi + 1; si = si + 1; advanced = 1 } } } 160 if advanced == 0 { if star >= 0 { pi = star + 1; mark = mark + 1; si = mark; advanced = 1 } } 161 if advanced == 0 { return 0 } 162 } else { run = 0 } 163 } 164 while pi < pn { if pat[pi] == (42 as u8) { pi = pi + 1 } else { return 0 } } 165 return 1 166} 167 168// the sticky budget check: bud[0]=deadline_abs_us bud[1]=bytes_left bud[2]=files_left bud[3]=exceeded 169// bud[4]=bytes_read bud[5]=files_seen bud[6]=max_bytes_cfg bud[7]=deadline_ms_cfg bud[8]=skipped_toolong 170func s_bud_ok(bud: *i64) -> i64 { 171 if bud[3] == 1 { return 0 } 172 if bud[1] <= 0 { bud[3] = 1; return 0 } 173 if bud[2] <= 0 { bud[3] = 1; return 0 } 174 if sys_clock_now_us() > bud[0] { bud[3] = 1; return 0 } 175 return 1 176} 177 178// emit one matching line. Lifted out of s_grep_file so the chunked scanner below has exactly ONE 179// emit site for both the mid-chunk case and the final no-trailing-newline case. 180func s_grep_emit(path: *u8, ln: i64, gbuf: *u8, ls: i64, le: i64, bud: *i64) -> i64 { 181 let ofd: i64 = bud[S_BUD_OFD] 182 var wb: i64 = s_fputs(ofd, path) 183 sys_write(ofd, ":" as *u8, 1); wb = wb + 1 184 wb = wb + s_fputn(ofd, ln) 185 sys_write(ofd, ":" as *u8, 1); wb = wb + 1 186 sys_write(ofd, ((gbuf as i64) + ls) as *u8, le - ls); wb = wb + (le - ls) 187 sys_write(ofd, "\n" as *u8, 1); wb = wb + 1 188 bud[S_BUD_OBYTES] = bud[S_BUD_OBYTES] + wb 189 return 1 190} 191// CHUNKED LINE SCAN (2026-08-06, debt 1786057862). THE DEFECT REPLACED: this function did 192// let n: i64 = s_read(path, gbuf, 1048575) 193// -- ONE read from offset 0 into the shared 1MiB buffer, with n NEVER compared against the file size 194// and NO flag emitted anywhere. Any file larger than the buffer had its TAIL SILENTLY UNSEARCHED, so 195// matches=0 meant "absent OR beyond 1MiB" and the two were indistinguishable. 196// MEASURED on knowledge/status/ws_sync.jrnl (1383495B): `frontier-program` was found at lines 1..1530 197// while THREE tokens appended to the tail of that same file the same day -- segstore-lock-adoption, 198// DOUBLE-DRIVE, ROUND 2 LANDED -- all returned matches=0, i.e. 334920 bytes (24.2pc) unreachable. 199// CONTROL THAT ISOLATES SIZE AS THE CAUSE: the same token WAS found at claims.jrnl:719, a LATE line in 200// a SMALLER file. So late-in-file works; large-file does not. 201// ***A SEARCH TOOL THAT UNDER-REPORTS IS WORSE THAN ONE THAT ERRORS, BECAUSE matches=0 IS ACTED ON AS 202// PROOF OF ABSENCE.*** It cost precisely that: a CORRECT index was suspected of over-closing 17 203// workstreams because the reap frames proving otherwise sat past the cap. 204// THE FIX IS NOT A BIGGER BUFFER -- that only moves the cliff. This reads the file in successive chunks 205// and CARRIES the trailing incomplete line to the front, so coverage is COMPLETE at any file size while 206// memory stays bounded by the buffer. Line numbers accumulate across chunks so path:lineno stays true. 207// THE ONLY RESIDUAL is a single line longer than the entire buffer: it is COUNTED, DECLARED as 208// linetoolong= in the envelope, and SKIPPED to its real end -- never silently split, because half a line 209// matched as a whole one is a FABRICATED match, the opposite failure and the worse one. 210func s_grep_file(path: *u8, pat: *u8, pn: i64, gbuf: *u8, bud: *i64) -> i64 { 211 if s_bud_ok(bud) == 0 { return 0 } 212 let fd: i64 = sys_openat_rd(path) 213 if fd < 0 { return 0 } 214 var hits: i64 = 0 215 var ln: i64 = 1 216 var carry: i64 = 0 217 var skipping: i64 = 0 218 var go: i64 = 1 219 while go == 1 { 220 var r: i64 = 0 221 let room: i64 = S_GBUF_CAP - carry 222 if room > 0 { r = sys_read(fd, ((gbuf as i64) + carry) as *u8, room) } 223 if r <= 0 { 224 go = 0 225 // a final line with NO trailing newline is still a line and must still be matched 226 if skipping == 0 { if carry > 0 { 227 if nx_grep_any(gbuf, carry, pat, pn) == 1 { hits = hits + s_grep_emit(path, ln, gbuf, 0, carry, bud) } 228 } } 229 } else { 230 bud[1] = bud[1] - r 231 bud[4] = bud[4] + r 232 let n: i64 = carry + r 233 var ls: i64 = 0 234 if skipping == 1 { 235 // discard the remainder of an overlong line up to its real terminator 236 var p: i64 = 0 237 var ff: i64 = 1 238 while ff == 1 { ff = 0; if p < n { if gbuf[p] != (10 as u8) { p = p + 1; ff = 1 } } } 239 if p < n { skipping = 0; ln = ln + 1; ls = p + 1 } else { ls = n } 240 } 241 var scan: i64 = 1 242 while scan == 1 { 243 var le: i64 = ls 244 var f: i64 = 1 245 while f == 1 { f = 0; if le < n { if gbuf[le] != (10 as u8) { le = le + 1; f = 1 } } } 246 if le < n { 247 if nx_grep_any(((gbuf as i64) + ls) as *u8, le - ls, pat, pn) == 1 { hits = hits + s_grep_emit(path, ln, gbuf, ls, le, bud) } 248 ln = ln + 1 249 ls = le + 1 250 } else { 251 scan = 0 252 carry = n - ls 253 if carry >= S_GBUF_CAP { 254 bud[S_BUD_LONGLINE] = bud[S_BUD_LONGLINE] + 1 255 carry = 0 256 skipping = 1 257 } else { 258 var m: i64 = 0 259 while m < carry { gbuf[m] = gbuf[ls + m]; m = m + 1 } 260 } 261 } 262 } 263 } 264 if s_bud_ok(bud) == 0 { go = 0 } 265 } 266 sys_close(fd) 267 return hits 268} 269 270// recursive walk under budget. mode: 0 grep, 1 glob, 2 find. Frees its own level buffers (munmap). 271// Returns the total match count across the subtree (files matched, or grep lines matched). 272func s_walk(dir: *u8, mode: i64, pat: *u8, pn: i64, ext: *u8, en: i64, gbuf: *u8, bud: *i64) -> i64 { 273 let fd: i64 = sys_openat_rd(dir) 274 if fd < 0 { return 0 } 275 let dbuf: *u8 = sys_mmap(65536) 276 let path: *u8 = sys_mmap(4096) 277 let dl: i64 = s_slen(dir) 278 var hits: i64 = 0 279 var run: i64 = 1 280 while run == 1 { 281 if s_bud_ok(bud) == 0 { run = 0 } else { 282 let nn: i64 = sys_getdents64(fd, dbuf, 65536) 283 if nn <= 0 { run = 0 } else { 284 var off: i64 = 0 285 while off < nn { 286 let rec: *u8 = ((dbuf as i64 + off) as *u8) 287 let reclen: i64 = dirent_reclen(rec) 288 if reclen <= 0 { off = nn } else { 289 if s_bud_ok(bud) == 0 { off = nn } else { 290 let name: *u8 = dirent_name(rec) 291 let dt: i64 = dirent_type(rec) 292 var skip: i64 = 0 293 if name[0] == (46 as u8) { if name[1] == (0 as u8) { skip = 1 } else { if name[1] == (46 as u8) { if name[2] == (0 as u8) { skip = 1 } } } } 294 if skip == 0 { 295 let nl: i64 = s_slen(name) 296 if dl + 1 + nl + 1 >= 4096 { bud[8] = bud[8] + 1 } else { 297 // build dir + "/" + name (bounds proven above) 298 var o: i64 = 0 299 var a: i64 = 0 300 while dir[a] != (0 as u8) { path[o] = dir[a]; o = o + 1; a = a + 1 } 301 path[o] = 47 as u8; o = o + 1 302 a = 0 303 while name[a] != (0 as u8) { path[o] = name[a]; o = o + 1; a = a + 1 } 304 path[o] = 0 as u8 305 if dt == 4 { hits = hits + s_walk(path, mode, pat, pn, ext, en, gbuf, bud) } else { 306 bud[2] = bud[2] - 1 307 bud[5] = bud[5] + 1 308 if mode == 0 { if s_ends_with(name, nl, ext, en) == 1 { hits = hits + s_grep_file(path, pat, pn, gbuf, bud) } } 309 if mode == 1 { if s_glob(pat, pn, name, nl) == 1 { let og: i64 = bud[S_BUD_OFD]; bud[S_BUD_OBYTES] = bud[S_BUD_OBYTES] + s_fputs(og, path) + 1; sys_write(og, "\n" as *u8, 1); hits = hits + 1 } } 310 if mode == 2 { if nx_grep_any(name, nl, pat, pn) == 1 { let of2: i64 = bud[S_BUD_OFD]; bud[S_BUD_OBYTES] = bud[S_BUD_OBYTES] + s_fputs(of2, path) + 1; sys_write(of2, "\n" as *u8, 1); hits = hits + 1 } } 311 } 312 } 313 } 314 off = off + reclen 315 } 316 } 317 } 318 } 319 } 320 } 321 sys_close(fd) 322 sys_munmap(dbuf, 65536) 323 sys_munmap(path, 4096) 324 return hits 325} 326 327// dir must open AND getdents -- a file or missing path is a LOUD error, never a silent empty 328// (the MCP capture is stdout, so the error goes to stdout + a distinct exit code). 329func s_dir_ok(dir: *u8) -> i64 { 330 let fd: i64 = sys_openat_rd(dir) 331 if fd < 0 { return 0 } 332 let probe: *u8 = sys_mmap(S_PROBE_CAP) 333 let nn: i64 = sys_getdents64(fd, probe, S_PROBE_CAP) 334 sys_close(fd) 335 sys_munmap(probe, S_PROBE_CAP) 336 if nn < 0 { return 0 } 337 return 1 338} 339 340func main(argc: i64, argv: *i64) -> i64 { 341 if argc < 4 { s_puts("usage: nx_shelltool grep <pattern> <dir> [ext] | glob <pat> <dir> | find <substr> <dir>\n" as *u8); return 2 } 342 let verb: *u8 = argv[1] as *u8 343 let a2: *u8 = argv[2] as *u8 344 let a3: *u8 = argv[3] as *u8 345 var mode: i64 = 0 - 1 346 if s_streq(verb, "grep" as *u8) == 1 { mode = 0 } 347 if s_streq(verb, "glob" as *u8) == 1 { mode = 1 } 348 if s_streq(verb, "find" as *u8) == 1 { mode = 2 } 349 if mode < 0 { s_puts("usage: nx_shelltool grep <pattern> <dir> [ext] | glob <pat> <dir> | find <substr> <dir>\n" as *u8); return 2 } 350 if s_dir_ok(a3) == 0 { 351 s_puts("NX-SHELLTOOL ERROR: not a readable directory: " as *u8) 352 s_puts(a3) 353 s_puts(" (verbs take <pattern> <dir>; to search ONE file pass its parent dir + ext)\n" as *u8) 354 return S_EXIT_BADDIR 355 } 356 // budget: shelltool_budget.conf in CWD (max_bytes= deadline_ms= max_files=) else derived defaults 357 var maxb: i64 = S_DEF_MAX_BYTES 358 var dlms: i64 = S_DEF_DEADLINE_MS 359 var maxf: i64 = S_DEF_MAX_FILES 360 let cbuf: *u8 = sys_mmap(256) 361 let cn: i64 = s_read("shelltool_budget.conf" as *u8, cbuf, 255) 362 if cn > 0 { 363 let v1: i64 = s_num_after(cbuf, cn, "max_bytes=" as *u8) 364 if v1 > 0 { maxb = v1 } 365 let v2: i64 = s_num_after(cbuf, cn, "deadline_ms=" as *u8) 366 if v2 > 0 { dlms = v2 } 367 let v3: i64 = s_num_after(cbuf, cn, "max_files=" as *u8) 368 if v3 > 0 { maxf = v3 } 369 } 370 sys_munmap(cbuf, 256) 371 // 128 not 96: the chunked scanner adds a declared long-line counter at slot 11 and 96 bytes held 372 // exactly 12 slots with 11 already in use, so growing the allocation is what keeps the new counter 373 // from writing one past the end of a buffer whose bound nothing was checking. 374 let bud: *i64 = sys_mmap(128) as *i64 375 bud[0] = sys_clock_now_us() + dlms * 1000 376 bud[1] = maxb 377 bud[2] = maxf 378 bud[3] = 0 379 bud[4] = 0 380 bud[5] = 0 381 bud[6] = maxb 382 bud[7] = dlms 383 bud[8] = 0 384 bud[S_BUD_LONGLINE] = 0 385 var ext: *u8 = "" as *u8 386 var en: i64 = 0 387 if mode == 0 { if argc >= 5 { let c4: *u8 = argv[4] as *u8; if s_starts(c4, "out=" as *u8) == 0 { ext = c4; en = s_slen(ext) } } } 388 // out=<path>.out is a KEY=VALUE token accepted in ANY trailing position, so it can never be 389 // confused with grep's positional [ext] and all three verbs take it identically. 390 // Rule 19: absent out= is byte-for-byte the old stdout behaviour. 391 var outp: *u8 = 0 as *u8 392 var ai: i64 = 4 393 while ai < argc { let av: *u8 = argv[ai] as *u8; if s_starts(av, "out=" as *u8) == 1 { outp = ((av as i64) + S_OUTKEY_LEN) as *u8 } ai = ai + 1 } 394 var ofd: i64 = S_FD_STDOUT 395 if (outp as i64) != 0 { 396 if s_out_ok(outp) == 0 { 397 s_puts("NX-SHELLTOOL ERROR: out= refused: " as *u8); s_puts(outp) 398 s_puts(" (must be relative, contain no .., and end in .out -- so a scan can never clobber source, registry or binary)\n" as *u8) 399 return S_EXIT_BADOUT 400 } 401 ofd = sys_openat_wr(outp, S_MODE_644) 402 if ofd < 0 { 403 s_puts("NX-SHELLTOOL ERROR: out= unwritable: " as *u8); s_puts(outp); s_puts("\n" as *u8) 404 return S_EXIT_BADOUT 405 } 406 } 407 bud[S_BUD_OFD] = ofd 408 bud[S_BUD_OBYTES] = 0 409 let gbuf: *u8 = sys_mmap(1048576) 410 let hits: i64 = s_walk(a3, mode, a2, s_slen(a2), ext, en, gbuf, bud) 411 s_puts("-- matches=" as *u8); s_putn(hits) 412 s_puts(" bytes=" as *u8); s_putn(bud[4]) 413 s_puts(" files=" as *u8); s_putn(bud[5]) 414 if bud[8] > 0 { s_puts(" skipped_toolong=" as *u8); s_putn(bud[8]) } 415 // coverage is now COMPLETE for grep regardless of file size, so the envelope says so positively -- 416 // a reader must be able to tell "I searched every byte" from "I stopped and did not mention it". 417 // ***SPELLED coverage_complete= BECAUSE LAW L011 DEFINES THAT TOKEN, NOT BECAUSE IT READS WELL.*** 418 // I first emitted "per_file_coverage=complete", which is the same fact in a private dialect. It cost 419 // exactly what a dialect costs: nx_envelope_audit -- which had just been ALIGNED to L011's vocabulary 420 // (scanned / coverage_complete / capped) -- still classified this tool a silent-cap candidate, because 421 // "per_file_coverage=complete" does not contain the substring "coverage_complete" (underscore vs 422 // equals). A near-miss spelling is indistinguishable from silence to every consumer. 423 // The fix belongs HERE, not in the detector: widening a detector to accept dialects never converges, 424 // while a law that names its token converges the moment each emitter uses it. 425 // ★WHEN A LAW NAMES THE TOKEN, THE EMITTER SPEAKS THE LAW'S SPELLING -- THE DETECTOR IS NOT THE PLACE 426 // TO ABSORB EVERY AUTHOR'S PHRASING. 427 if mode == 0 { s_puts(" coverage_complete=1" as *u8) } 428 // CORPUS vs PER-FILE, SPLIT 2026-08-07 -- and the irony is that the comment directly above spends nine 429 // lines insisting the TOKEN be spelled the way the law names it, while the field itself answers a 430 // NARROWER question than its name implies. coverage_complete=1 means EVERY FILE THAT WAS OPENED was 431 // scanned to its end (the honest per-file fix of 08-06). It says nothing about whether every file was 432 // VISITED, and it is emitted even when the walk stopped early on the budget -- it is printed ABOVE the 433 // bud[3] check, so the two coexist. 434 // MEASURED 2026-08-07 by nx_absent neg-control: a whole-tree grep returned matches=0 coverage_complete=1 435 // TOGETHER WITH BUDGET-EXCEEDED partial=1. A consumer trusting the flag alone publishes ABSENCE for a 436 // corpus it never finished reading -- and absence is the one claim that requires exhaustive coverage. 437 // ★A FLAG NAMED FOR THE QUESTION THE READER IS ASKING, WHICH ANSWERS A NARROWER ONE, IS A FALSE PROOF 438 // WITH AN AUTHORITATIVE NAME. Additive per rule 19: coverage_complete keeps its exact meaning forever, 439 // and the corpus-level fact gets its OWN token so no reader must know which coverage question a single 440 // flag answers. Emitted for EVERY verb, because the walk budget binds grep, glob and find alike. 441 if bud[3] == 1 { s_puts(" corpus_complete=0" as *u8) } 442 else { s_puts(" corpus_complete=1" as *u8) } 443 if bud[S_BUD_LONGLINE] > 0 { s_puts(" linetoolong=" as *u8); s_putn(bud[S_BUD_LONGLINE]) } 444 if ofd != S_FD_STDOUT { 445 sys_close(ofd) 446 s_puts(" out=" as *u8); s_puts(outp) 447 s_puts(" out_bytes=" as *u8); s_putn(bud[S_BUD_OBYTES]) 448 s_puts(" stdout_bounded=1" as *u8) 449 } 450 sys_write(1, "\n" as *u8, 1) 451 if bud[3] == 1 { 452 s_puts("NX-SHELLTOOL BUDGET-EXCEEDED partial=1 max_bytes=" as *u8); s_putn(bud[6]) 453 s_puts(" deadline_ms=" as *u8); s_putn(bud[7]) 454 s_puts(" (PARTIAL scan; raise shelltool_budget.conf deliberately, never remove the budget)\n" as *u8) 455 return S_EXIT_BUDGET 456 } 457 return 0 458}